{"_id":"@aperim/sequelize-hierarchy","_rev":"1-135cf7c487732001c0643797776086ad","name":"@aperim/sequelize-hierarchy","dist-tags":{"latest":"2.0.5"},"versions":{"2.0.5":{"name":"@aperim/sequelize-hierarchy","version":"2.0.5","description":"Nested hierarchies for Sequelize","main":"index.js","author":{"name":"Overlook Motel"},"repository":{"type":"git","url":"https://github.com/aperim/sequelize-hierarchy.git"},"bugs":{"url":"https://github.com/aperim/sequelize-hierarchy/issues"},"dependencies":{"bluebird":"^3.7.2","is-generator":"^1.0.3","lodash":"^4.17.20","semver-select":"^1.1.0"},"devDependencies":{"@overlookmotel/eslint-config":"^7.2.1","@overlookmotel/eslint-config-tests":"^4.1.0","chai":"^4.2.0","chai-as-promised":"^7.1.1","coveralls":"^3.1.0","cross-env":"^7.0.3","eslint":"^7.17.0","eslint-config-airbnb-base":"^14.2.1","eslint-plugin-chai-friendly":"^0.6.0","eslint-plugin-eslint-comments":"^3.2.0","eslint-plugin-import":"^2.22.1","istanbul":"^0.4.5","mocha":"^8.2.1","mysql2":"^2.2.5","pg":"^8.5.1","pg-hstore":"^2.3.3","pg-native":"^3.0.0","sequelize":"^6.3.5","sqlite3":"^5.0.0","tedious":"^9.2.3"},"keywords":["sequelize","sequelize-plugin","hierarchy","nested","tree"],"scripts":{"test":"npm run lint && npm run test-main","lint":"eslint '*.js' '.*.js' '**/*.js' '**/.*.js'","lint-fix":"eslint '*.js' '.*.js' '**/*.js' '**/.*.js' --fix","test-mysql":"cross-env DIALECT=mysql npm run test-main","test-postgres":"cross-env DIALECT=postgres npm run test-main","test-postgres-native":"cross-env DIALECT=postgres-native npm run test-main","test-sqlite":"cross-env DIALECT=sqlite npm run test-main","test-mssql":"cross-env DIALECT=mssql npm run test-main","test-main":"mocha --check-leaks --colors -t 30000 -R spec \"test/**/*.test.js\"","cover":"npm run cover-main && rm -rf coverage","coveralls":"npm run cover-main && cat ./coverage/lcov.info | coveralls && rm -rf ./coverage","cover-main":"cross-env COVERAGE=true istanbul cover _mocha --report lcovonly -- -t 30000 -R spec \"test/**/*.test.js\"","ci":"if [ $COVERAGE ]; then npm run coveralls; else npm test; fi"},"engines":{"node":">=8"},"license":"MIT","licenseText":"Copyright (c) 2019 Overlook Motel (theoverlookmotel@gmail.com)\n\n Permission is hereby granted, free of charge, to any person obtaining a copy\n of this software and associated documentation files (the \"Software\"), to deal\n in the Software without restriction, including without limitation the rights\n to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n copies of the Software, and to permit persons to whom the Software is\n furnished to do so, subject to the following conditions:\n\n The above copyright notice and this permission notice shall be included in\n all copies or substantial portions of the Software.\n\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n THE SOFTWARE.\n","_id":"@aperim/sequelize-hierarchy@2.0.5","dist":{"shasum":"5d8fa36884715937d5e97f97c19148848e390305","integrity":"sha512-kj17g7tIXKKNasVn8gNvrcA4+WnwbXYVoqzM7wrB/qNM+8bQinSNxGjCJlhkZxQRtMzf9zpRsZvfq/acGuFoww==","tarball":"https://registry.npmjs.org/@aperim/sequelize-hierarchy/-/sequelize-hierarchy-2.0.5.tgz","fileCount":21,"unpackedSize":64365,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.13\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJf84fkCRA9TVsSAnZWagAAwMcQAIhC1oWVLxRm1DTPl5yJ\n2iTHfOsvsgb9zmXrpOLJHfdjC7TYAr9o6Qvr6pzBOEiRgTgqj61omv5afoPE\n3Xa6CV0pQ8Ojm4mE7QTiQKtoLwNyMhsFSqLHeBzszVwwxDKpRsGlb65zZ58N\nlmBLKNy9CR0DH/aCFbitUvTo3nFrAZWgbm+Av/ruj26HtB2JQyRrBRp1bSLi\nJaduoEXX3RqRteegHqW24g1mhZ9hdtHw38PqwNWIQynugETbqhwdgR/W4dR7\n7j3NKF0/PFEtCUb9tI1erJUL+W9rZdB9ddv28VIuwMMKPxjqa+gsZbETnD+O\nSQpfx5iIwRV/P6YXVlE9uvXXWvJ9WnOf+ega6C+d6MzKUuAkVw7uon0AB83s\nqEdftUcQiPe9Q/5wIgZn11dlGpjqgFwC1eWGyGfQAeZYmcD2E5Ijw1Qrqx5Y\ndGDjgx2JaHEequn/eKQdif/nShhnsmp03BjATajMRWBOA/48f/UVH+iObkkE\nWgM7Q1SAnOsiFBecuSGthsMzWMqkA2dF9QJXY8ngTg8r/Z4jjWZYbJQRQWQ/\nd5bxgjANX0Kx3PaPtquvkhwHqGEPtsoPKZQrdBlDBzeWKLmU62hF7CDsWTb7\nzfy5oWgzlNufG2WNKXL6YL7ZavVrVUOzx/TS1bQIqRc6sTseDhSXWP05uojc\na2JV\r\n=4lkX\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIEjWvhLA8JrgT4Fhp1c04aNYGxwmYO76ASNE9MbAMoLIAiAtWAwemtM69mH/LNVmK2V85c7cYsQDa0CbfvBGbIiXwA=="}]},"_npmUser":{"name":"troykelly","email":"troy@troykelly.com"},"directories":{},"maintainers":[{"name":"imdanwalton","email":"npmjs.com@s.danielwalton.io"},{"name":"troykelly","email":"troy@troykelly.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/sequelize-hierarchy_2.0.5_1609795555881_0.28525191344169754"},"_hasShrinkwrap":false}},"time":{"created":"2021-01-04T21:25:55.755Z","2.0.5":"2021-01-04T21:25:56.063Z","modified":"2022-04-04T14:32:46.793Z"},"maintainers":[{"name":"imdanwalton","email":"npmjs.com@s.danielwalton.io"},{"name":"troykelly","email":"troy@troykelly.com"}],"description":"Nested hierarchies for Sequelize","keywords":["sequelize","sequelize-plugin","hierarchy","nested","tree"],"repository":{"type":"git","url":"https://github.com/aperim/sequelize-hierarchy.git"},"author":{"name":"Overlook Motel"},"bugs":{"url":"https://github.com/aperim/sequelize-hierarchy/issues"},"license":"MIT","readme":"# sequelize-hierarchy.js\n\n# Nested hierarchies for Sequelize\n\n[![NPM version](https://img.shields.io/npm/v/sequelize-hierarchy.svg)](https://www.npmjs.com/package/sequelize-hierarchy)\n[![Build Status](https://img.shields.io/travis/overlookmotel/sequelize-hierarchy/master.svg)](http://travis-ci.org/overlookmotel/sequelize-hierarchy)\n[![Dependency Status](https://img.shields.io/david/overlookmotel/sequelize-hierarchy.svg)](https://david-dm.org/overlookmotel/sequelize-hierarchy)\n[![Dev dependency Status](https://img.shields.io/david/dev/overlookmotel/sequelize-hierarchy.svg)](https://david-dm.org/overlookmotel/sequelize-hierarchy)\n[![Greenkeeper badge](https://badges.greenkeeper.io/overlookmotel/sequelize-hierarchy.svg)](https://greenkeeper.io/)\n[![Coverage Status](https://img.shields.io/coveralls/overlookmotel/sequelize-hierarchy/master.svg)](https://coveralls.io/r/overlookmotel/sequelize-hierarchy)\n\n## What's it for?\n\nRelational databases aren't very good at dealing with nested hierarchies.\n\nExamples of hierarchies are:\n\n* Nested folders where each folder has many subfolders, those subfolders themselves have subfolders, and so on\n* Categories and sub-categories e.g. for a newspaper with sections for different sports, Sports category splits into Track Sports and Water Sports, Water Sports into Swimming and Diving, Diving into High Board, Middle Board and Low Board etc\n* Tree structures\n\nTo store a hierarchy in a database, the usual method is to give each record a ParentID field which says which is the record one level above it.\n\nFetching the parent or children of any record is easy, but if you want to retrieve an entire tree/hierarchy structure from the database, it requires multiple queries, recursively getting each level of the hierarchy. For a big tree structure, this is a lengthy process, and annoying to code.\n\nThis plugin for [Sequelize](http://sequelizejs.com/) solves this problem.\n\n## Current status\n\nAPI is stable. All features and options are fairly well tested. Works with all dialects of SQL supported by Sequelize (MySQL, Postgres, SQLite) except for Microsoft SQL Server.\n\nRequires Sequelize v2.x.x, v3.x.x, v4.x.x or v5.x.x. Supports only Node v8 or higher.\n\n## Usage\n\n### Loading module\n\nTo load module:\n\n```js\nconst Sequelize = require('sequelize-hierarchy')();\n// NB Sequelize must also be present in `node_modules`\n```\n\nor, a more verbose form useful if chaining multiple Sequelize plugins:\n\n```js\nconst Sequelize = require('sequelize');\nrequire('sequelize-hierarchy')(Sequelize);\n```\n\n### Initializing hierarchy\n\n#### Model#isHierarchy( [options] )\n\n```js\nconst sequelize = new Sequelize('database', 'user', 'password');\n\nconst Folder = sequelize.define('folder', { name: Sequelize.STRING });\nFolder.isHierarchy();\n```\n\n`Folder.isHierarchy()` does the following:\n\n* Adds a column `parentId` to Folder model\n* Adds a column `hierarchyLevel` to Folder model (which should not be updated directly)\n* Creates a new model `FolderAncestor` which contains the ancestry information (columns `folderId` and `ancestorId`)\n* Creates the following associations (with foreign key constraints):\n  * `Folder.belongsTo(Folder, {as: 'parent', foreignKey: 'parentId'})`\n  * `Folder.hasMany(Folder, {as: 'children', foreignKey: 'parentId'})`\n  * `Folder.belongsToMany(Folder, {as: 'descendents', foreignKey: 'ancestorId', through: FolderAncestor})`\n  * `Folder.belongsToMany(Folder, {as: 'ancestors', foreignKey: 'folderId', through: FolderAncestor})`\n* Creates hooks into standard Sequelize methods (create, update, destroy, bulkCreate etc) to automatically update the ancestry table and `hierarchyLevel` field as details in the folder table change\n* Creates hooks into Sequelize's `Model#find()` and `Model#findAll()` methods so that hierarchies can be returned as javascript object tree structures\n\nThe column and table names etc can be modified by passing options to `.isHierarchy()`. See below for details.\n\n#### via Sequelize#define() options\n\nHierarchies can also be created in `define()`:\n\n```js\nconst Folder = sequelize.define('folder', {\n  name: Sequelize.STRING\n}, {\n  hierarchy: true\n});\n```\n\nor on an attribute in `define()`:\n\n```js\nconst Folder = sequelize.define('folder', {\n  name: Sequelize.STRING,\n  parentId: {\n    type: Sequelize.INTEGER,\n    hierarchy: true\n  }\n});\n```\n\nIf defining the hierarchy via model options, do not also call `.isHierarchy()`. The two methods are equivalent - only use one or the other.\n\n#### Creating database tables\n\nDefining the hierarchy sets up the *models* in Sequelize, not the database tables. You will need to create or modify the tables in the database.\n\nIf table already exists, add the following columns:\n\n* `parentId` (same type as `id`)\n* `hierarchyLevel` (`INTEGER` type)\n\nIf the table does not already exist, you can ask Sequelize to create it:\n\n```js\nawait Folder.sync();\n```\n\nNB Call `.sync()` *after* `.isHierarchy()`.\n\nThe ancestry model (`FolderAncestor` in the above example) also needs its database table created:\n\n```js\nawait sequelize.models.FolderAncestor.sync();\n```\n\n### Retrieving hierarchies\n\nExamples of getting a hierarchy structure:\n\n```js\n// Get entire hierarchy as a flat list\nconst folders = await Folder.findAll();\n// [\n//   { id: 1, parentId: null, name: 'a' },\n//   { id: 2, parentId: 1, name: 'ab' },\n//   { id: 3, parentId: 2, name: 'abc' }\n// ]\n\n// Get entire hierarchy as a nested tree\nconst folders = await Folder.findAll({ hierarchy: true });\n// [\n//   { id: 1, parentId: null, name: 'a', children: [\n//     { id: 2, parentId: 1, name: 'ab', children: [\n//       { id: 3, parentId: 2, name: 'abc' }\n//     ] }\n//   ] }\n// ]\n\n// Get all the descendents of a particular item\nconst folder = await Folder.findOne({\n  where: { name: 'a' },\n  include: {\n    model: Folder,\n    as: 'descendents',\n    hierarchy: true\n  }\n});\n// { id: 1, parentId: null, name: 'a', children: [\n//   { id: 2, parentId: 1, name: 'ab', children: [\n//     { id: 3, parentId: 2, name: 'abc' }\n//   ] }\n// ] }\n\n// Get all the ancestors (i.e. parent and parent's parent and so on)\nconst folder = await Folder.findOne({\n  where: { name: 'abc' },\n  include: [ { model: Folder, as: 'ancestors' } ],\n  order: [ [ { model: Folder, as: 'ancestors' }, 'hierarchyLevel' ] ]\n});\n// { id: 3, parentId: 2, name: 'abc', ancestors: [\n//   { id: 1, parentId: null, name: 'a' },\n//   { id: 2, parentId: 1, name: 'ab' }\n// ] }\n```\n\nThe forms with `{ hierarchy: true }` are equivalent to using `Folder.findAll({ include: { model: Folder, as: 'children' } })` except that the include is recursed however deeply the tree structure goes.\n\n### Accessors\n\nAccessors are also supported:\n\n```js\nfolder.getParent()\nfolder.getChildren()\nfolder.getAncestors()\nfolder.getDescendents()\n```\n\nSetters work as usual e.g. `folder.setParent()`, `folder.addChild()`.\n\n### Options\n\nThe following options can be passed to `Model#isHierarchy( { /* options */ } )` or in a model definition:\n\n```js\nconst Folder = sequelize.define('folder', {\n  name: Sequelize.STRING\n}, {\n  hierarchy: { /* options */ }\n});\n```\n\nDefaults are inherited from `sequelize.options.hierarchy` if defined in call to `new Sequelize()`.\n\nExamples:\n\n```js\nFolder.isHierarchy( { as: 'above' } );\n\nconst Folder = sequelize.define('folder', {\n  name: Sequelize.STRING\n}, {\n  hierarchy: { as: 'above' }\n});\n```\n\n#### Aliases for relations\n\n* `as`: Name of parent association. Defaults to `'parent'`.\n* `childrenAs`: Name of children association. Defaults to `'children'`.\n* `ancestorsAs`: Name of ancestors association. Defaults to `'ancestors'`.\n* `descendentsAs`: Name of descendents association. Defaults to `'descendents'`.\n\nThese affect the naming of accessors e.g. `instance.getParent()`\n\n#### Fields\n\n* `levelFieldName`: Name of the hierarchy depth field. Defaults to `'hierarchyLevel'`.\n* `levelFieldType`: Type of the hierarchy depth field. Defaults to `Sequelize.INTEGER.UNSIGNED`.\n* `levelFieldAttributes`: Attributes to add to the hierarchy depth field. Defaults to `undefined`.\n* `primaryKey`: Name of the primary key. Defaults to model's `primaryKeyAttribute`.\n* `foreignKey`: Name of the parent field. Defaults to `'parentId'`.\n* `foreignKeyAttributes`: Attributes to add to the parent field. Defaults to `undefined`.\n* `throughKey`: Name of the instance field in hierarchy (through) table. Defaults to `'<model name>Id'`.\n* `throughForeignKey`: Name of the ancestor field in hierarchy (through) table. Defaults to `'ancestorId'`.\n\n#### Hierarchy (through) table\n\n* `through`: Name of hierarchy (through) model. Defaults to `'<model name>ancestor'`.\n* `throughTable`: Name of hierarchy (through) table. Defaults to `'<model name plural>ancestors'`.\n* `throughSchema`: Schema of hierarchy (through) table. Defaults to `model.options.schema`, and is optional.\n* `freezeTableName`: When `true`, through table name is same as through model name. Inherits from sequelize define options.\n* `camelThrough`: When `true`, through model name and table name are camelized (i.e. `folderAncestor` not `folderancestor`). Inherits from sequelize define options.\n\nAll auto-created field names respect the setting of `model.options.underscored` and the through table name respects `sequelize.options.define.underscoredAll`.\n\n#### Cascading deletions\n\n* `onDelete`: Set to `'CASCADE'` if you want deleting a node to delete all its children.\n\n#### Misc\n\n* `labels`: When `true`, creates an attribute `label` on the created `parentId` and `hierarchyLevel` fields which is a human-readable version of the field name. Inherits from sequelize define options or `false`.\n\n### Rebuilding the hierarchy\n#### Model#rebuildHierarchy( [options] )\n\nTo build the hierarchy data on an existing table, or if hierarchy data gets corrupted in some way (e.g. by changes to parentId being made directly in the database not through Sequelize), you can rebuild it with:\n\n```js\nawait Folder.rebuildHierarchy()\n```\n\nNB: In normal circumstances, you should never need to use this method. It is only intended for the above two use cases.\n\n### Bulk creation\n\nYou can use `.bulkCreate()` method in the usual way. Ensure that parents are created before their children.\n\n### Errors\n\nErrors thrown by the plugin are of type `HierarchyError`. The error class can be accessed at `Sequelize.HierarchyError`.\n\n## Tests\n\nUse `npm test` to run the tests. Use `npm run cover` to check coverage.\n\nTo run tests on a particular database, use `npm run test-mysql`, `npm run test-postgres`, `npm run test-postgres-native`, `npm run test-sqlite` or `npm run test-mssql`.\n\nRequires a database called 'sequelize_test' and a db user 'sequelize_test' with no password.\n\n## Changelog\n\nSee [changelog.md](https://github.com/overlookmotel/sequelize-hierarchy/blob/master/changelog.md)\n\n## Issues\n\nIf you discover a bug, please raise an issue on Github. https://github.com/overlookmotel/sequelize-hierarchy/issues\n\n## Contribution\n\nPull requests are very welcome. Please:\n\n* ensure all tests pass before submitting PR\n* do not add an entry to changelog - changelog will be created when cutting releases\n* add tests for new features\n* document new functionality/API additions in README\n","readmeFilename":"README.md"}