├── .editorconfig ├── .eslintrc ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── .husky └── pre-commit ├── .nvmrc ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── lib ├── DB.js ├── index.d.ts ├── index.js ├── instances.js ├── models.js └── schema.js ├── package-lock.json ├── package.json └── test ├── index.js └── models ├── shop └── product │ ├── category.js │ └── product.js └── user.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 4 8 | indent_style = space 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | max_line_length = 0 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "parserOptions": { 4 | "ecmaVersion": 8 5 | }, 6 | "rules": { 7 | "quotes": [2, "single", { "allowTemplateLiterals": true }] 8 | }, 9 | "env": { 10 | "node": true, 11 | "es6": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: valtlfelipe 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: actions/setup-node@v3 14 | with: 15 | cache: npm 16 | node-version: 16 17 | - run: npm ci 18 | - run: npx semantic-release 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 22 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | types: 9 | - opened 10 | - synchronize 11 | 12 | jobs: 13 | test_matrix: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | node: [12, 14, 16, 18] 18 | name: Node v${{ matrix.node }} test 19 | steps: 20 | - uses: actions/checkout@v3 21 | - uses: actions/setup-node@v3 22 | with: 23 | cache: npm 24 | node-version: ${{ matrix.node }} 25 | - run: npm ci 26 | - run: npm run lint 27 | - run: npm test 28 | 29 | test: 30 | runs-on: ubuntu-latest 31 | needs: test_matrix 32 | steps: 33 | - uses: actions/checkout@v3 34 | - uses: actions/setup-node@v3 35 | with: 36 | node-version: 16 37 | cache: npm 38 | - run: npm ci 39 | - run: npm run lint 40 | - run: npm test 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | npm-debug.log 4 | .DS_Store 5 | myhostname 6 | test/db 7 | test/db.sqlite 8 | coverage.html 9 | yarn.lock 10 | yarn-error.log 11 | .npmrc 12 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 14.17.3 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "prettier.arrowParens": "avoid", 3 | "prettier.bracketSpacing": true, 4 | "prettier.printWidth": 100, 5 | "prettier.semi": true, 6 | "prettier.singleQuote": true, 7 | "prettier.tabWidth": 4, 8 | "prettier.trailingComma": "all", 9 | "prettier.useTabs": false 10 | } 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Felipe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hapi-sequelizejs 2 | 3 | [![npm version](https://img.shields.io/npm/v/hapi-sequelizejs.svg)](https://www.npmjs.com/package/hapi-sequelizejs) 4 | [![Build Status](https://github.com/valtlfelipe/hapi-sequelizejs/workflows/Test/badge.svg?branch=master)](https://github.com/valtlfelipe/hapi-sequelizejs/actions?query=workflow%3ATest+branch%3Amaster) 5 | 6 | A [hapi.js](https://github.com/hapijs/hapi) plugin to connect with [Sequelize ORM](https://github.com/sequelize/sequelize/). 7 | 8 | ## Support me 9 | 10 | If you like this plugin, please support my work and help maintaining it. 11 | 12 | Buy Me A Coffee 13 | 14 | Thanks in advance ❤️ 15 | 16 | ## Compatibility 17 | 18 | Compatible with these versions: 19 | 20 | - hapi.js 21 | - `19.x` 22 | - `20.x` 23 | - `21.x` 24 | - sequelize 25 | - `5.x` 26 | - `6.x` 27 | 28 | Check the [releases page](https://github.com/valtlfelipe/hapi-sequelizejs/releases) for the changelog. 29 | 30 | ## Installation 31 | 32 | `npm install hapi-sequelizejs` 33 | 34 | ## Configuration 35 | 36 | Simply pass in your sequelize instance and a few basic options and voila. Options accepts a single object 37 | or an array for multiple dbs. 38 | 39 | ```javascript 40 | server.register([ 41 | { 42 | plugin: require('hapi-sequelizejs'), 43 | options: [ 44 | { 45 | name: 'dbname', // identifier 46 | models: [__dirname + '/server/models/**/*.js'], // paths/globs to model files 47 | ignoredModels: [__dirname + '/server/models/**/*.js'], // OPTIONAL: paths/globs to ignore files 48 | sequelize: new Sequelize(config, opts), // sequelize instance 49 | sync: true, // sync models - default false 50 | forceSync: false, // force sync (drops tables) - default false 51 | }, 52 | ], 53 | }, 54 | ]); 55 | ``` 56 | 57 | ## Model Definitions 58 | 59 | A model should export a function that returns a Sequelize model definition ([http://docs.sequelizejs.com/en/latest/docs/models-definition/](http://docs.sequelizejs.com/en/latest/docs/models-definition/)). 60 | 61 | ```javascript 62 | module.exports = function (sequelize, DataTypes) { 63 | const Category = sequelize.define('Category', { 64 | name: DataTypes.STRING, 65 | rootCategory: DataTypes.BOOLEAN, 66 | }); 67 | 68 | return Category; 69 | }; 70 | ``` 71 | 72 | ### Setting Model associations 73 | 74 | Using the sequelize model instance, define a method called `associate`, that is a function, and receives as parameter all models defined. 75 | 76 | ```javascript 77 | module.exports = function (sequelize, DataTypes) { 78 | const Category = sequelize.define('Category', { 79 | name: DataTypes.STRING, 80 | rootCategory: DataTypes.BOOLEAN, 81 | }); 82 | 83 | Category.associate = function (models) { 84 | models.Category.hasMany(models.Product); 85 | }; 86 | 87 | return Category; 88 | }; 89 | ``` 90 | 91 | ## Database Instances 92 | 93 | Each registration adds a DB instance to the `server.plugins['hapi-sequelizejs']` object with the 94 | name option as the key. 95 | 96 | ```javascript 97 | function DB(sequelize, models) { 98 | this.sequelize = sequelize; 99 | this.models = models; 100 | } 101 | 102 | // something like this 103 | server.plugins['hapi-sequelizejs'][opts.name] = new DB(opts.sequelize, models); 104 | ``` 105 | 106 | ## API 107 | 108 | ### Using `request` object 109 | 110 | #### `getDb(name)` 111 | 112 | The request object gets decorated with the method `getDb`. This allows you to easily grab a 113 | DB instance in a route handler. If you have multiple registrations pass the name of the one 114 | you would like returned or else the single or first registration will be returned. 115 | 116 | ```javascript 117 | handler(request, reply) { 118 | const db1 = request.getDb('db1'); 119 | console.log(db1.sequelize); 120 | console.log(db1.models); 121 | } 122 | ``` 123 | 124 | > If there isn't a db instance for the given name or no registered db instance, an Error is thrown: `hapi-sequelizejs cannot find the ${dbName} database instance`. 125 | 126 | ##### `db.getModel('User')` 127 | 128 | Returns single model that matches the passed argument or null if the model doesn't exist. 129 | 130 | ##### `db.getModels()` 131 | 132 | Returns all models on the db instance 133 | 134 | #### `getModels(dbName?)` 135 | 136 | Returns all models registered in the given db's name or the models from the first registered db instance if no name is given to the function. 137 | 138 | ```javascript 139 | handler(request, reply) { 140 | const models = request.getModels('db1'); 141 | ... 142 | } 143 | ``` 144 | 145 | > If there isn't a db instance for the given name or no registered db instance, an Error is thrown: `hapi-sequelizejs cannot find the ${dbName} database instance`. 146 | 147 | #### `getModel(dbName, modelName?)` 148 | 149 | Return the model to the db's name instance. You may give only the model name to the function, if it's the case, it returns the model from the first registered db instance. 150 | 151 | ```javascript 152 | handler(request, reply) { 153 | const myModel = request.getModel('db1', 'myModel'); 154 | ... 155 | } 156 | ``` 157 | 158 | > If there isn't a db instance for the given name or no registered db instance, an Error is thrown: `hapi-sequelizejs cannot find the ${dbName} database instance`. 159 | > If there isn't a model for the given name, an Error is thrown: `hapi-sequelizejs cannot find the ${modelName} model`. 160 | 161 | --- 162 | 163 | ### Without `request` object 164 | 165 | To access the dbs intances without using the `request` object you may do this: 166 | 167 | ```javascript 168 | const instances = require('hapi-sequelizejs').instances; 169 | ``` 170 | 171 | #### `instance.dbs` 172 | 173 | Returns an Object with all instances registered. 174 | 175 | ```javascript 176 | { 177 | [db.name]: db.instance 178 | } 179 | ``` 180 | 181 | ```javascript 182 | const instances = require('hapi-sequelizejs').instances; 183 | const dbs = instances.dbs; 184 | 185 | dbs.myDb.getModel('User'); 186 | ``` 187 | 188 | #### `getDb(name?)` 189 | 190 | Returns the db instance for the given name or the first registered db instance if no name is given to the function. 191 | 192 | ```javascript 193 | const instances = require('hapi-sequelizejs').instances; 194 | 195 | const myDb = instances.getDb('myDb'); 196 | 197 | const firstRegisteredDb = instances.getDb(); 198 | ``` 199 | 200 | > If there isn't a db instance for the given name or no registered db instance, an Error is thrown: `hapi-sequelizejs cannot find the ${dbName} database instance`. 201 | 202 | #### `getModels(dbName?)` 203 | 204 | Returns all models registered in the given db's name or the models from the first registered db instance if no name is given to the function. 205 | 206 | ```javascript 207 | const instances = require('hapi-sequelizejs').instances; 208 | 209 | const myDbModels = instances.getModels('myDb'); 210 | 211 | const firstRegisteredDbModels = instances.getModels(); 212 | ``` 213 | 214 | > If there isn't a db instance for the given name or no registered db instance, an Error is thrown: `hapi-sequelizejs cannot find the ${dbName} database instance`. 215 | 216 | #### `getModel(dbName, modelName?)` 217 | 218 | Return the model to the db's name instance. You may give only the model name to the function, if it's the case, it returns the model from the first registered db instance. 219 | 220 | ```javascript 221 | const instances = require('hapi-sequelizejs').instances; 222 | 223 | const myDbMyModel = instances.getModel('myDb', 'myModel'); 224 | 225 | const firstRegisteredDbMyModel = instances.getModel('myModel'); 226 | ``` 227 | 228 | > If there isn't a db instance for the given name or no registered db instance, an Error is thrown: `hapi-sequelizejs cannot find the ${dbName} database instance`. 229 | > If there isn't a model for the given name, an Error is thrown: `hapi-sequelizejs cannot find the ${modelName} model`. 230 | -------------------------------------------------------------------------------- /lib/DB.js: -------------------------------------------------------------------------------- 1 | class DB { 2 | constructor(sequelize, models) { 3 | this.sequelize = sequelize; 4 | this.models = models; 5 | } 6 | 7 | getModel(name) { 8 | return name in this.models ? this.models[name] : null; 9 | } 10 | 11 | getModels() { 12 | return this.models; 13 | } 14 | } 15 | 16 | module.exports = DB; 17 | -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'hapi-sequelizejs'; 2 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const { 3 | assert 4 | } = require('@hapi/hoek'); 5 | const Schema = require('./schema'); 6 | const Models = require('./models'); 7 | const DB = require('./DB'); 8 | const Pkg = require('../package.json'); 9 | const instances = require('./instances'); 10 | 11 | module.exports = { 12 | pkg: Pkg, 13 | once: true, 14 | register, 15 | instances, 16 | }; 17 | 18 | async function register(server, options) { 19 | assert(options, 'Missing hapi-sequelizejs plugin options'); 20 | 21 | if (!Array.isArray(options)) { 22 | options = [options]; 23 | } 24 | 25 | await Joi.assert(options, Schema.options); 26 | 27 | setupDecorators(server); 28 | 29 | server.events.on('stop', async function onStop() { 30 | const dbNames = options.map((option) => option.name); 31 | const pluginContent = server.plugins[Pkg.name]; 32 | 33 | await Promise.all(dbNames.map((dbName) => pluginContent[dbName].sequelize.close())); 34 | }); 35 | 36 | const configured = options.reduce( 37 | (acc, options) => [ 38 | ...acc, 39 | configure(options).then((db) => { 40 | server.expose(options.name, db); 41 | return db; 42 | }), 43 | ], 44 | [], 45 | ); 46 | 47 | return Promise.all(configured); 48 | } 49 | 50 | async function configure(options) { 51 | try { 52 | await options.sequelize.authenticate(); 53 | } catch (error) { 54 | throw new Error( 55 | `An error occurred while attempting to connect to DB[${options.name}], 56 | please check the configuration. Details: ${error.message}`, 57 | ); 58 | } 59 | 60 | let db = null; 61 | if (options.models) { 62 | const files = await Models.getFiles(options.models, options.ignoredModels); 63 | let models = await Models.load(files, options.sequelize); 64 | models = await Models.applyRelations(models); 65 | 66 | if (options.sync) { 67 | await options.sequelize.sync({ 68 | force: options.forceSync, 69 | }); 70 | } 71 | 72 | db = new DB(options.sequelize, models); 73 | } else { 74 | db = new DB(options.sequelize, []); 75 | } 76 | 77 | instances.register(options.name, db); 78 | 79 | if (options.onConnect) { 80 | let onConnect = options.onConnect(db); 81 | if (onConnect && typeof onConnect.then === 'function') { 82 | await onConnect; 83 | } 84 | } 85 | 86 | return db; 87 | } 88 | 89 | function setupDecorators(server) { 90 | const options = { 91 | apply: true, 92 | }; 93 | server.decorate('request', 'getDb', () => instances.getDb, options); 94 | server.decorate('request', 'getModel', () => instances.getModel, options); 95 | server.decorate('request', 'getModels', () => instances.getModels, options); 96 | } 97 | -------------------------------------------------------------------------------- /lib/instances.js: -------------------------------------------------------------------------------- 1 | const db = { 2 | register, 3 | getDb, 4 | getModel, 5 | getModels, 6 | }; 7 | 8 | let instances = []; 9 | 10 | Object.defineProperty(db, 'dbs', { 11 | configurable: false, 12 | enumerable: true, 13 | get: () => 14 | instances.reduce((dbs, instance) => { 15 | dbs[instance.name] = instance.db; 16 | return dbs; 17 | }, {}), 18 | }); 19 | 20 | module.exports = db; 21 | 22 | function register(name, db) { 23 | const instance = findDb(name); 24 | if (!instance) { 25 | instances.push({ 26 | name, 27 | db, 28 | }); 29 | } else { 30 | instance.db = db; 31 | } 32 | } 33 | 34 | function getModel(dbName, modelName) { 35 | let instance; 36 | let model; 37 | 38 | if (modelName) { 39 | instance = getDb(dbName); 40 | } else { 41 | modelName = dbName; 42 | instance = getDb(); 43 | } 44 | 45 | model = instance.models[modelName]; 46 | 47 | if (!model) { 48 | throw new Error(`hapi-sequelizejs cannot find the ${modelName} model`); 49 | } 50 | 51 | return model; 52 | } 53 | 54 | function getModels(dbName) { 55 | return getDb(dbName).models; 56 | } 57 | 58 | function findDb(dbName) { 59 | return instances.find((db) => db.name === dbName); 60 | } 61 | 62 | function getDb(dbName) { 63 | let instance; 64 | if (dbName) { 65 | instance = findDb(dbName); 66 | } else { 67 | instance = instances[0]; 68 | } 69 | 70 | if (!instance) { 71 | throw new Error(`hapi-sequelizejs cannot find the ${dbName} database instance`); 72 | } 73 | 74 | return instance.db; 75 | } 76 | -------------------------------------------------------------------------------- /lib/models.js: -------------------------------------------------------------------------------- 1 | const Path = require('path'); 2 | const Glob = require('glob'); 3 | 4 | module.exports = { 5 | getFiles, 6 | load, 7 | applyRelations, 8 | }; 9 | 10 | async function getFiles(paths, ignored) { 11 | const options = { 12 | nodir: true, 13 | dot: false, 14 | }; 15 | 16 | if (!Array.isArray(paths)) { 17 | paths = [paths]; 18 | } 19 | 20 | if (ignored) { 21 | options.ignore = ignored; 22 | } 23 | 24 | return paths.reduce((acc, pattern) => { 25 | const joinPaths = Array.prototype.concat.bind([], acc); 26 | const paths = Glob.sync(pattern, options); 27 | return joinPaths(paths); 28 | }, []); 29 | } 30 | 31 | async function load(files, sequelize) { 32 | if (!files) { 33 | return []; 34 | } 35 | 36 | if (!Array.isArray(files)) { 37 | files = [files]; 38 | } 39 | 40 | return files.reduce((acc, file) => { 41 | const models = {}; 42 | const filepath = Path.isAbsolute(file) ? file : Path.join(process.cwd(), file); 43 | const Model = require(filepath)(sequelize, sequelize.Sequelize.DataTypes); 44 | models[Model.name] = Model; 45 | return Object.assign({}, acc, models); 46 | }, {}); 47 | } 48 | 49 | async function applyRelations(models) { 50 | if (typeof models !== 'object') { 51 | throw new Error(`Can't apply relationships on invalid models object`); 52 | } 53 | 54 | Object.keys(models).forEach((name) => { 55 | if ('associate' in models[name]) { 56 | models[name].associate(models); 57 | } 58 | }); 59 | 60 | return models; 61 | } 62 | -------------------------------------------------------------------------------- /lib/schema.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const Sequelize = require('sequelize'); 3 | 4 | const option = Joi.object().keys({ 5 | name: Joi.string().token().required(), 6 | models: Joi.alternatives().try(Joi.string(), Joi.array().items(Joi.string())), 7 | ignoredModels: Joi.alternatives().try(Joi.string(), Joi.array().items(Joi.string())), 8 | sequelize: Joi.object().instance(Sequelize).required(), 9 | sync: Joi.boolean().default(false), 10 | forceSync: Joi.boolean().default(false), 11 | debug: Joi.boolean(), 12 | onConnect: Joi.func(), 13 | }); 14 | 15 | const options = Joi.alternatives().try(Joi.array().items(option), option); 16 | 17 | module.exports = { 18 | option, 19 | options, 20 | }; 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hapi-sequelizejs", 3 | "version": "0.0.0-development", 4 | "description": "hapi.js plugin for the Sequelize ORM", 5 | "main": "lib/index.js", 6 | "files": [ 7 | "lib/**/*" 8 | ], 9 | "scripts": { 10 | "test": "lab --leaks", 11 | "test-cov-html": "lab --leaks -a @hapi/code -r html -o coverage.html", 12 | "lint": "eslint lib/*.js", 13 | "coveralls": "lab -r lcov | ./node_modules/.bin/coveralls", 14 | "prettify": "prettier --print-width 100 --tab-width 4 --single-quote --trailing-comma all --write \"{{lib,test}/**/*.js,*.{js,md,json}}\"", 15 | "semantic-release": "semantic-release" 16 | }, 17 | "lint-staged": { 18 | "{{lib,test}/**/*.js,*.{js,md,json}}": [ 19 | "prettier --print-width 100 --tab-width 4 --single-quote --trailing-comma all --write" 20 | ] 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/valtlfelipe/hapi-sequelizejs.git" 25 | }, 26 | "keywords": [ 27 | "sequelize", 28 | "hapi", 29 | "hapi-plugin", 30 | "mysql", 31 | "sqlite", 32 | "postgresql", 33 | "postgres", 34 | "orm", 35 | "plugin", 36 | "database", 37 | "nodejs", 38 | "javascript" 39 | ], 40 | "author": "Felipe Valtl de Mello ", 41 | "contributors": [ 42 | "Dane Grant", 43 | "Felipe Valtl de Mello (https://github.com/valtlfelipe)", 44 | "Jonas Pauthier (https://github.com/Nargonath)" 45 | ], 46 | "license": "MIT", 47 | "devDependencies": { 48 | "@hapi/code": "^9.0.1", 49 | "@hapi/hapi": "^21.0.0", 50 | "@hapi/lab": "^25.0.1", 51 | "coveralls": "^3.1.1", 52 | "eslint": "^8.27.0", 53 | "husky": "^8.0.2", 54 | "lint-staged": "^13.0.3", 55 | "mysql2": "^2.3.3", 56 | "prettier": "^2.7.1", 57 | "sequelize": "^6.25.5", 58 | "sqlite3": "^5.1.2", 59 | "semantic-release": "^19.0.5" 60 | }, 61 | "peerDependencies": { 62 | "sequelize": "5.x || 6.x", 63 | "@hapi/hapi": "19.x || 20.x || 21.x" 64 | }, 65 | "engines": { 66 | "node": ">=12.0.0", 67 | "npm": ">=5.6.0" 68 | }, 69 | "dependencies": { 70 | "@hapi/hoek": "10.0.1", 71 | "glob": "8.0.3", 72 | "joi": "^17.7.0" 73 | }, 74 | "funding": { 75 | "type": "individual", 76 | "url": "https://www.buymeacoffee.com/valtlfelipe" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | // Load modules 2 | const Path = require('path'); 3 | const Lab = require('@hapi/lab'); 4 | const Code = require('@hapi/code'); 5 | const Hapi = require('@hapi/hapi'); 6 | const Sequelize = require('sequelize'); 7 | 8 | const DB = require('../lib/DB'); 9 | 10 | // Test shortcuts 11 | const lab = (exports.lab = Lab.script()); 12 | const { suite, test } = lab; 13 | const { expect } = Code; 14 | 15 | suite('hapi-sequelizejs', () => { 16 | test('should fail to load with no options', async () => { 17 | const server = new Hapi.Server(); 18 | 19 | try { 20 | await server.register([ 21 | { 22 | plugin: require('../lib/'), 23 | options: {}, 24 | }, 25 | ]); 26 | } catch (error) { 27 | expect(error).to.be.instanceof(Error); 28 | } 29 | }); 30 | 31 | test('should load a good configuration', async () => { 32 | const server = new Hapi.Server(); 33 | await server.register([ 34 | { 35 | plugin: require('../lib/'), 36 | options: [ 37 | { 38 | name: 'test', 39 | sequelize: new Sequelize('test', null, null, { 40 | logging: false, 41 | dialect: 'sqlite', 42 | storage: Path.join(__dirname, 'db.sqlite'), 43 | }), 44 | }, 45 | ], 46 | }, 47 | ]); 48 | 49 | expect(server.plugins['hapi-sequelizejs']).to.be.an.object(); 50 | expect(server.plugins['hapi-sequelizejs'].test).to.be.instanceof(DB); 51 | }); 52 | 53 | test('should load a bad models configuration', async () => { 54 | const server = new Hapi.Server(); 55 | 56 | try { 57 | await server.register([ 58 | { 59 | plugin: require('../lib/'), 60 | options: [ 61 | { 62 | name: 'test', 63 | models: '***', 64 | sequelize: new Sequelize('test', null, null, { 65 | logging: false, 66 | dialect: 'sqlite', 67 | storage: Path.join(__dirname, 'db.sqlite'), 68 | }), 69 | }, 70 | ], 71 | }, 72 | ]); 73 | } catch (err) { 74 | expect(err).to.be.instanceof(Error); 75 | } 76 | }); 77 | 78 | test('should load all models', async () => { 79 | const server = new Hapi.Server(); 80 | await server.register([ 81 | { 82 | plugin: require('../lib/'), 83 | options: [ 84 | { 85 | name: 'test', 86 | models: [Path.join(__dirname, '/models/**/*.js')], 87 | sequelize: new Sequelize('test', null, null, { 88 | logging: false, 89 | dialect: 'sqlite', 90 | storage: Path.join(__dirname, 'db.sqlite'), 91 | }), 92 | }, 93 | ], 94 | }, 95 | ]); 96 | 97 | expect(server.plugins['hapi-sequelizejs']).to.be.an.object(); 98 | expect(server.plugins['hapi-sequelizejs'].test).to.be.instanceof(DB); 99 | expect(server.plugins['hapi-sequelizejs'].test.getModels()).to.be.an.object(); 100 | expect(server.plugins['hapi-sequelizejs'].test.getModel('User')).to.be.a.function(); 101 | expect(server.plugins['hapi-sequelizejs'].test.getModel('Category')).to.be.a.function(); 102 | expect(server.plugins['hapi-sequelizejs'].test.getModel('Product')).to.be.a.function(); 103 | expect(server.plugins['hapi-sequelizejs'].test.getModel('DoesNotExists')).to.be.null(); 104 | }); 105 | 106 | test('should load ignore specific models', async () => { 107 | const server = new Hapi.Server(); 108 | await server.register([ 109 | { 110 | plugin: require('../lib/'), 111 | options: [ 112 | { 113 | name: 'test', 114 | models: [Path.join(__dirname, '/models/**/*.js')], 115 | ignoredModels: [Path.join(__dirname, '/models/shop/**/*.js')], 116 | sequelize: new Sequelize('test', null, null, { 117 | logging: false, 118 | dialect: 'sqlite', 119 | storage: Path.join(__dirname, 'db.sqlite'), 120 | }), 121 | }, 122 | ], 123 | }, 124 | ]); 125 | 126 | expect(server.plugins['hapi-sequelizejs']).to.be.an.object(); 127 | expect(server.plugins['hapi-sequelizejs'].test).to.be.instanceof(DB); 128 | expect(server.plugins['hapi-sequelizejs'].test.getModels()).to.be.an.object(); 129 | expect(server.plugins['hapi-sequelizejs'].test.getModel('User')).to.be.a.function(); 130 | expect(server.plugins['hapi-sequelizejs'].test.getModel('Category')).to.be.null(); 131 | expect(server.plugins['hapi-sequelizejs'].test.getModel('Product')).to.be.null(); 132 | expect(server.plugins['hapi-sequelizejs'].test.getModel('DoesNotExists')).to.be.null(); 133 | }); 134 | 135 | test('should sync all models', async () => { 136 | const server = new Hapi.Server(); 137 | await server.register([ 138 | { 139 | plugin: require('../lib/'), 140 | options: [ 141 | { 142 | name: 'test', 143 | models: Path.join(__dirname, '/models/**/*.js'), 144 | sync: true, 145 | sequelize: new Sequelize('test', null, null, { 146 | logging: false, 147 | dialect: 'sqlite', 148 | storage: Path.join(__dirname, 'db.sqlite'), 149 | }), 150 | }, 151 | ], 152 | }, 153 | ]); 154 | 155 | expect(server.plugins['hapi-sequelizejs']).to.be.an.object(); 156 | expect(server.plugins['hapi-sequelizejs'].test).to.be.instanceof(DB); 157 | expect(server.plugins['hapi-sequelizejs'].test.getModels()).to.be.an.object(); 158 | expect(server.plugins['hapi-sequelizejs'].test.getModel('User')).to.be.a.function(); 159 | expect(server.plugins['hapi-sequelizejs'].test.getModel('Category')).to.be.a.function(); 160 | expect(server.plugins['hapi-sequelizejs'].test.getModel('Product')).to.be.a.function(); 161 | }); 162 | 163 | test('should get DB instance on request', async () => { 164 | const server = new Hapi.Server(); 165 | 166 | await server.register([ 167 | { 168 | plugin: require('../lib/'), 169 | options: [ 170 | { 171 | name: 'test', 172 | models: [Path.join(__dirname, '/models/**/*.js')], 173 | sync: true, 174 | sequelize: new Sequelize('test', null, null, { 175 | logging: false, 176 | dialect: 'sqlite', 177 | storage: Path.join(__dirname, 'db.sqlite'), 178 | }), 179 | }, 180 | ], 181 | }, 182 | ]); 183 | 184 | server.route([ 185 | { 186 | method: 'GET', 187 | path: '/', 188 | handler(request, h) { 189 | const instance = request.getDb(); 190 | expect(instance).to.be.instanceof(DB); 191 | return h.response(); 192 | }, 193 | }, 194 | ]); 195 | 196 | const response = await server.inject({ 197 | method: 'GET', 198 | url: '/', 199 | }); 200 | expect(response.statusCode).to.equal(204); 201 | }); 202 | 203 | test('should get named DB instance on request', async () => { 204 | const server = new Hapi.Server(); 205 | await server.register([ 206 | { 207 | plugin: require('../lib/'), 208 | options: [ 209 | { 210 | name: 'test', 211 | models: [Path.join(__dirname, '/models/**/*.js')], 212 | sync: true, 213 | sequelize: new Sequelize('test', null, null, { 214 | logging: false, 215 | dialect: 'sqlite', 216 | storage: Path.join(__dirname, 'db.sqlite'), 217 | }), 218 | }, 219 | ], 220 | }, 221 | ]); 222 | 223 | server.route([ 224 | { 225 | method: 'GET', 226 | path: '/', 227 | handler(request, h) { 228 | const instance = request.getDb('test'); 229 | expect(instance).to.be.instanceof(DB); 230 | return h.response(); 231 | }, 232 | }, 233 | ]); 234 | 235 | const response = await server.inject({ 236 | method: 'GET', 237 | url: '/', 238 | }); 239 | expect(response.statusCode).to.equal(204); 240 | }); 241 | 242 | test('should get User model on request', async () => { 243 | const server = await instanceTestServer(); 244 | 245 | server.route([ 246 | { 247 | method: 'GET', 248 | path: '/', 249 | handler(request, h) { 250 | expect(request.getModel('User')).to.be.a.function(); 251 | return h.response(); 252 | }, 253 | }, 254 | ]); 255 | 256 | const response = await server.inject({ 257 | method: 'GET', 258 | url: '/', 259 | }); 260 | expect(response.statusCode).to.equal(204); 261 | }); 262 | 263 | test('should get all models on request', async () => { 264 | const server = await instanceTestServer(); 265 | 266 | server.route([ 267 | { 268 | method: 'GET', 269 | path: '/', 270 | handler(request, h) { 271 | const models = request.getModels(); 272 | expect(models).to.be.a.object(); 273 | expect(models.User).to.be.a.function(); 274 | return h.response(); 275 | }, 276 | }, 277 | ]); 278 | 279 | const response = await server.inject({ 280 | method: 'GET', 281 | url: '/', 282 | }); 283 | expect(response.statusCode).to.equal(204); 284 | }); 285 | 286 | test('should call onConnect returning a non promise', () => { 287 | const server = new Hapi.Server(); 288 | 289 | return server.register([ 290 | { 291 | plugin: require('../lib/'), 292 | options: [ 293 | { 294 | name: 'test', 295 | sequelize: new Sequelize('test', null, null, { 296 | logging: false, 297 | dialect: 'sqlite', 298 | storage: Path.join(__dirname, 'db.sqlite'), 299 | }), 300 | onConnect: (instance) => { 301 | expect(instance).to.be.instanceof(DB); 302 | return true; 303 | }, 304 | }, 305 | ], 306 | }, 307 | ]); 308 | }); 309 | 310 | test('should call onConnect returning a falsy value', () => { 311 | const server = new Hapi.Server(); 312 | 313 | return server.register([ 314 | { 315 | plugin: require('../lib/'), 316 | options: [ 317 | { 318 | name: 'test', 319 | sequelize: new Sequelize('test', null, null, { 320 | logging: false, 321 | dialect: 'sqlite', 322 | storage: Path.join(__dirname, 'db.sqlite'), 323 | }), 324 | onConnect: (instance) => { 325 | expect(instance).to.be.instanceof(DB); 326 | }, 327 | }, 328 | ], 329 | }, 330 | ]); 331 | }); 332 | 333 | test('should call onConnect with a promise', () => { 334 | const server = new Hapi.Server(); 335 | 336 | return server.register([ 337 | { 338 | plugin: require('../lib/'), 339 | options: [ 340 | { 341 | name: 'test', 342 | sequelize: new Sequelize('test', null, null, { 343 | logging: false, 344 | dialect: 'sqlite', 345 | storage: Path.join(__dirname, 'db.sqlite'), 346 | }), 347 | onConnect: (instance) => { 348 | expect(instance).to.be.instanceof(DB); 349 | return Promise.resolve(); 350 | }, 351 | }, 352 | ], 353 | }, 354 | ]); 355 | }); 356 | 357 | test('should throw error on getting invalid named DB instance', async () => { 358 | const server = new Hapi.Server(); 359 | 360 | await server.register([ 361 | { 362 | plugin: require('../lib/'), 363 | options: [ 364 | { 365 | name: 'test', 366 | models: [Path.join(__dirname, '/models/**/*.js')], 367 | sync: true, 368 | sequelize: new Sequelize('test', null, null, { 369 | logging: false, 370 | dialect: 'sqlite', 371 | storage: Path.join(__dirname, 'db.sqlite'), 372 | }), 373 | }, 374 | ], 375 | }, 376 | ]); 377 | 378 | server.route([ 379 | { 380 | method: 'GET', 381 | path: '/', 382 | handler(request) { 383 | try { 384 | request.getDb('inexistent'); 385 | } catch (err) { 386 | expect(err).to.be.instanceOf(Error); 387 | throw err; 388 | } 389 | }, 390 | }, 391 | ]); 392 | 393 | const response = await server.inject({ 394 | method: 'GET', 395 | url: '/', 396 | }); 397 | expect(response.statusCode).to.equal(500); 398 | }); 399 | 400 | test('plugin fails to register when Sequelize fails to connect', async () => { 401 | const server = new Hapi.Server(); 402 | 403 | try { 404 | await server.register([ 405 | { 406 | plugin: require('../lib/'), 407 | options: [ 408 | { 409 | name: 'test', 410 | models: [Path.join(__dirname, '/models/**/*.js')], 411 | sync: true, 412 | sequelize: new Sequelize('shop', 'root', '', { 413 | logging: false, 414 | host: '127.0.0.1', 415 | port: 3307, 416 | dialect: 'mysql', 417 | }), 418 | }, 419 | ], 420 | }, 421 | ]); 422 | } catch (err) { 423 | expect(err).to.be.instanceOf(Error); 424 | expect(err.message).to.include('ECONNREFUSED'); 425 | } 426 | }); 427 | 428 | test('should close sequelize connection on server stop', async () => { 429 | const server = new Hapi.Server(); 430 | 431 | await server.register([ 432 | { 433 | plugin: require('../lib/'), 434 | options: [ 435 | { 436 | name: 'test', 437 | models: [Path.join(__dirname, '/models/**/*.js')], 438 | sync: true, 439 | sequelize: new Sequelize('test', null, null, { 440 | logging: false, 441 | dialect: 'sqlite', 442 | storage: Path.join(__dirname, 'db.sqlite'), 443 | }), 444 | }, 445 | ], 446 | }, 447 | ]); 448 | 449 | await server.stop(); 450 | 451 | const sequelizeInstance = server.plugins['hapi-sequelizejs'].test.sequelize; 452 | expect(sequelizeInstance.authenticate()).to.reject(); 453 | }); 454 | 455 | test('should get db instances from instances singleton using dbs property', async () => { 456 | await instanceTestServer(); 457 | 458 | const instances = require('../lib').instances; 459 | 460 | expect(instances.dbs).to.be.an.object(); 461 | expect(instances.dbs.test).to.be.instanceof(DB); 462 | }); 463 | 464 | test('should get db instance from instances singleton using getDb', async () => { 465 | await instanceTestServer(); 466 | 467 | const instances = require('../lib').instances; 468 | 469 | expect(instances.getDb('test')).to.be.an.object(); 470 | expect(instances.getDb('test')).to.be.instanceof(DB); 471 | }); 472 | 473 | test('should get first db instance when no dbName is given to getDb', async () => { 474 | await instanceTestServer(); 475 | 476 | const instances = require('../lib').instances; 477 | 478 | expect(instances.getDb()).to.be.instanceof(DB); 479 | expect(instances.getDb()).to.be.equal(instances.getDb('test')); 480 | }); 481 | 482 | test('should load all models from db instance using getDb', async () => { 483 | await instanceTestServer(); 484 | 485 | const instances = require('../lib').instances; 486 | 487 | expect(instances.getDb('test').getModels()).to.be.an.object(); 488 | expect(instances.getDb('test').getModel('User')).to.be.a.function(); 489 | expect(instances.getDb('test').getModel('Category')).to.be.a.function(); 490 | expect(instances.getDb('test').getModel('Product')).to.be.a.function(); 491 | expect(instances.getDb('test').getModel('DoesNotExists')).to.be.null(); 492 | }); 493 | 494 | test('should load all models from db instance using getModels', async () => { 495 | await instanceTestServer(); 496 | 497 | const instances = require('../lib').instances; 498 | 499 | expect(instances.getModels('test')).to.be.an.object(); 500 | expect(instances.getModels('test').User).to.be.a.function(); 501 | expect(instances.getModels('test').Category).to.be.a.function(); 502 | expect(instances.getModels('test').Product).to.be.a.function(); 503 | expect(instances.getModels('test').DoesNotExists).to.be.undefined(); 504 | }); 505 | 506 | test('should load all models from first db instance when no dbName is given using getModels', async () => { 507 | await instanceTestServer(); 508 | 509 | const instances = require('../lib').instances; 510 | 511 | expect(instances.getModels()).to.be.an.object(); 512 | expect(instances.getModels().User).to.be.a.function(); 513 | expect(instances.getModels().Category).to.be.a.function(); 514 | expect(instances.getModels().Product).to.be.a.function(); 515 | expect(instances.getModels().DoesNotExists).to.be.undefined(); 516 | }); 517 | 518 | test('should load model from db instance using getModel', async () => { 519 | await instanceTestServer(); 520 | 521 | const instances = require('../lib').instances; 522 | 523 | expect(instances.getModel('test', 'User')).to.be.an.function(); 524 | expect(instances.getModel('test', 'User')).to.be.equal( 525 | instances.getDb('test').getModel('User'), 526 | ); 527 | }); 528 | 529 | test('should load model from first db instance when no dbName is given using getModel', async () => { 530 | await instanceTestServer(); 531 | 532 | const instances = require('../lib').instances; 533 | 534 | expect(instances.getModel('User')).to.be.an.function(); 535 | expect(instances.getModel('User')).to.be.equal(instances.getDb('test').getModel('User')); 536 | }); 537 | 538 | test('should fail when there is no db instance to the given name using getDb', async () => { 539 | await instanceTestServer(); 540 | 541 | const instances = require('../lib').instances; 542 | 543 | try { 544 | instances.getDb('other-testdb'); 545 | } catch (error) { 546 | expect(error).to.be.instanceOf(Error); 547 | expect(error.message).to.be.equal( 548 | 'hapi-sequelizejs cannot find the other-testdb database instance', 549 | ); 550 | } 551 | }); 552 | 553 | test('should fail when there is no model to the given name using getModels', async () => { 554 | await instanceTestServer(); 555 | 556 | const instances = require('../lib').instances; 557 | 558 | try { 559 | instances.getModels('other-testdb'); 560 | } catch (error) { 561 | expect(error).to.be.instanceOf(Error); 562 | expect(error.message).to.be.equal( 563 | 'hapi-sequelizejs cannot find the other-testdb database instance', 564 | ); 565 | } 566 | }); 567 | 568 | test('should fail when there is no model to the given name using getModel', async () => { 569 | await instanceTestServer(); 570 | 571 | const instances = require('../lib').instances; 572 | 573 | try { 574 | instances.getModel('test', 'DoesNotExist'); 575 | } catch (error) { 576 | expect(error).to.be.instanceOf(Error); 577 | expect(error.message).to.be.equal( 578 | 'hapi-sequelizejs cannot find the DoesNotExist model', 579 | ); 580 | } 581 | }); 582 | 583 | test('request DB should be the same object as instances DB', async () => { 584 | const server = await instanceTestServer(); 585 | 586 | const instances = require('../lib').instances; 587 | 588 | server.route([ 589 | { 590 | method: 'GET', 591 | path: '/', 592 | handler(request, h) { 593 | const instance = request.getDb(); 594 | expect(instance).to.be.equals(instances.getDb()); 595 | return h.response(); 596 | }, 597 | }, 598 | ]); 599 | 600 | const response = await server.inject({ 601 | method: 'GET', 602 | url: '/', 603 | }); 604 | expect(response.statusCode).to.equal(204); 605 | }); 606 | }); 607 | 608 | async function instanceTestServer() { 609 | const server = new Hapi.Server(); 610 | 611 | await server.register([ 612 | { 613 | plugin: require('../lib/'), 614 | options: [ 615 | { 616 | name: 'test', 617 | models: Path.join(__dirname, '/models/**/*.js'), 618 | sequelize: new Sequelize('test', null, null, { 619 | logging: false, 620 | dialect: 'sqlite', 621 | storage: Path.join(__dirname, 'db.sqlite'), 622 | }), 623 | }, 624 | ], 625 | }, 626 | ]); 627 | 628 | return server; 629 | } 630 | -------------------------------------------------------------------------------- /test/models/shop/product/category.js: -------------------------------------------------------------------------------- 1 | // return Category model as a function 2 | 3 | module.exports = function (sequelize, DataTypes) { 4 | const Category = sequelize.define('Category', { 5 | name: DataTypes.STRING, 6 | rootCategory: DataTypes.BOOLEAN, 7 | }); 8 | 9 | Category.associate = function (models) { 10 | models.Category.hasMany(models.Product); 11 | }; 12 | 13 | return Category; 14 | }; 15 | -------------------------------------------------------------------------------- /test/models/shop/product/product.js: -------------------------------------------------------------------------------- 1 | // return Product model as a function 2 | 3 | module.exports = function (sequelize, DataTypes) { 4 | const Product = sequelize.define('Product', { 5 | name: DataTypes.STRING, 6 | inventory: DataTypes.INTEGER, 7 | }); 8 | 9 | Product.associate = function (models) { 10 | models.Product.belongsTo(models.Category); 11 | }; 12 | 13 | return Product; 14 | }; 15 | -------------------------------------------------------------------------------- /test/models/user.js: -------------------------------------------------------------------------------- 1 | // return User model as a function 2 | 3 | module.exports = function (sequelize, DataTypes) { 4 | return sequelize.define('User', { 5 | email: DataTypes.STRING, 6 | password: DataTypes.STRING, 7 | }); 8 | }; 9 | --------------------------------------------------------------------------------