├── .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 | [](https://www.npmjs.com/package/hapi-sequelizejs)
4 | [](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 |
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 |
--------------------------------------------------------------------------------