@vulkano/core 1.15.5 β†’ 1.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,80 +1,347 @@
1
1
  <p align="center">
2
- <img src="https://avatars.githubusercontent.com/u/42077334?s=200&v=4" alt="Nodemon Logo">
2
+ <img src="https://avatars.githubusercontent.com/u/42077334?s=200&v=4" alt="Vulkano Logo" width="100">
3
3
  </p>
4
4
 
5
- # Vulkano
5
+ <h1 align="center">@vulkano/core</h1>
6
6
 
7
- Vulkano is a small, simple, and fast framework for creating web applications using NodeJS. Inspired by KumbiaPHP.
8
-
9
- [![Backers on Open Collective](https://opencollective.com/vulkanojs/backers/badge.svg)](#backers)
10
- [![Sponsors on Open Collective](https://opencollective.com/vulkanojs/sponsors/badge.svg)](#sponsors)
7
+ <p align="center">
8
+ A fast, convention-based MVC framework for Node.js β€” built on top of Express.
9
+ </p>
11
10
 
12
- ## Backers
11
+ <p align="center">
12
+ <a href="https://opencollective.com/vulkanojs#backer"><img src="https://opencollective.com/vulkanojs/backers/badge.svg" alt="Backers"></a>
13
+ <a href="https://opencollective.com/vulkanojs#sponsor"><img src="https://opencollective.com/vulkanojs/sponsors/badge.svg" alt="Sponsors"></a>
14
+ </p>
13
15
 
14
- Thank you to all [our backers](https://opencollective.com/vulkanojs#backer)! πŸ™
16
+ ---
15
17
 
16
- [![vulkano backers](https://opencollective.com/vulkanojs/tiers/backer.svg?avatarHeight=50)](https://opencollective.com/vulkanojs#backers)
18
+ ## What is Vulkano?
17
19
 
20
+ Vulkano is a lightweight MVC framework for building web applications and APIs with Node.js. It wires together Express, MongoDB (Mongoose), Socket.io, i18n, JWT, file uploads, cron jobs, and more β€” so you spend time writing features, not boilerplate.
18
21
 
19
- ## Buy me a coffe
22
+ Inspired by [KumbiaPHP](https://www.kumbiaphp.com).
20
23
 
21
- [Buy me a coffe](https://buymeacoffee.com/argordmel) πŸ™
24
+ ---
22
25
 
23
- ## Install
26
+ ## Requirements
24
27
 
25
- ### System
28
+ - **Node.js** `^22`
29
+ - **MongoDB** (optional β€” only needed if you use models)
30
+ - **Redis** (optional β€” only needed for Socket.io Redis adapter or sessions)
26
31
 
27
- - Unix
28
- - Node.js v20+
32
+ ---
29
33
 
30
- ### Packages
34
+ ## Installation
31
35
 
32
36
  ```bash
33
- $ npm install @vulkano/core
37
+ npm install @vulkano/core
34
38
  ```
35
39
 
36
- ## Your App Structure
37
-
38
- - `public/` - HTTP Public folder
39
- - `vulkano/` - Vulkano App (config, controllers, models, views)
40
- - `app.js` - Server entry point
40
+ ---
41
41
 
42
- ## Your Server entry point
42
+ ## Quick Start
43
43
 
44
- ```bash
45
- /**
46
- * app.js
47
- *
48
- * To start the server, run: ⁠ node app.js ⁠.
49
- *
50
- * For example:
51
- * => ⁠ npm run start ⁠
52
- * => ⁠ node app.js ⁠
53
- */
44
+ ### 1. Entry point β€” `app.js`
54
45
 
46
+ ```js
55
47
  const vulkano = require('@vulkano/core');
48
+ vulkano();
49
+ ```
50
+
51
+ ### 2. Project structure
52
+
53
+ ```
54
+ your-app/
55
+ β”œβ”€β”€ app.js # Entry point
56
+ β”œβ”€β”€ public/ # Static files served over HTTP
57
+ β”‚ β”œβ”€β”€ css/
58
+ β”‚ β”œβ”€β”€ js/
59
+ β”‚ β”œβ”€β”€ img/
60
+ β”‚ └── files/ # Uploaded files
61
+ └── vulkano/ # Your application
62
+ β”œβ”€β”€ config/
63
+ β”‚ β”œβ”€β”€ settings.js # App-wide settings (port, database, JWT…)
64
+ β”‚ β”œβ”€β”€ routes.js # Explicit route overrides (optional)
65
+ β”‚ β”œβ”€β”€ env/ # Per-environment config overrides
66
+ β”‚ β”œβ”€β”€ express/ # Express middleware customization
67
+ β”‚ β”œβ”€β”€ settings.js # App-wide settings (port, database, JWT…)
68
+ β”‚ β”œβ”€β”€ routes.js # Explicit route overrides (optional)
69
+ β”‚ └── locales/ # i18n translation files (en.js, es.js, etc.)
70
+ β”œβ”€β”€ controllers/ # Request handlers
71
+ β”œβ”€β”€ models/ # Mongoose model definitions
72
+ └── services/ # Shared services & libs (auto-loaded as globals)
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Routing
78
+
79
+ Vulkano resolves routes **by convention** β€” no route file required for standard CRUD.
80
+
81
+ ### Convention-based (automatic)
82
+
83
+ | HTTP method | URL | Resolves to |
84
+ |-------------|------------------|-------------------------------------|
85
+ | `GET` | `/user` | `UserController.get` |
86
+ | `POST` | `/user` | `UserController.post` |
87
+ | `PUT` | `/user/42` | `UserController['put :id']` |
88
+ | `PATCH` | `/user/42` | `UserController['patch :id']` |
89
+ | `DELETE` | `/user/42` | `UserController['delete :id']` |
90
+ | `POST` | `/user/save` | `UserController['post save']` |
91
+ | `GET` | `/user/42/info` | `UserController['get :id/info']` |
92
+
93
+ ### Controller example
94
+
95
+ ```js
96
+ // vulkano/controllers/UserController.js
97
+ module.exports = {
98
+
99
+ get(req, res) {
100
+ res.vsr(Promise.resolve({ users: [] }));
101
+ },
102
+
103
+ 'get :id': function (req, res) {
104
+ res.vsr(Promise.resolve({ id: req.params.id }));
105
+ },
106
+
107
+ 'post save': function (req, res) {
108
+ res.vsr(Promise.resolve({ saved: true }));
109
+ }
110
+
111
+ };
112
+ ```
113
+
114
+ ### Explicit routes β€” `config/routes.js`
115
+
116
+ ```js
117
+ module.exports = [
118
+ { method: 'GET', path: '/health', controller: 'StatusController', action: 'ping' },
119
+ { method: 'POST', path: '/auth/login', controller: 'AuthController', action: 'login' },
120
+ ];
121
+ ```
122
+
123
+ You can also register routes with inline handlers or using `app.vulkano.get/post/…`.
124
+
125
+ ---
126
+
127
+ ## Responses β€” `res.vsr()`
128
+
129
+ Every controller action uses `res.vsr(promise)`. It wraps the resolved value in a standard envelope:
130
+
131
+ ```json
132
+ { "success": true, "statusCode": 200, "data": { … } }
133
+ ```
134
+
135
+ Errors are handled automatically:
136
+
137
+ ```js
138
+ // Custom error with status code
139
+ res.vsr(VSError.reject('Not allowed', 403));
140
+
141
+ // 404
142
+ res.vsr(VSError.notFound('User'));
143
+
144
+ // Plain rejection β†’ 500
145
+ res.vsr(Promise.reject(new Error('Something went wrong')));
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Scaffold β€” zero-code REST API
151
+
152
+ Point a controller at a model and get a full REST API for free:
153
+
154
+ ```js
155
+ // vulkano/controllers/api/ProductController.js
156
+ module.exports = {
157
+ scaffold: 'Product', // Mongoose model name
158
+ allowedMethods: ['get', 'post', 'put', 'patch', 'delete']
159
+ };
160
+ ```
161
+
162
+ This automatically exposes:
163
+
164
+ | Method | Path | Action |
165
+ |----------|--------------------|------------------|
166
+ | `GET` | `/api/product` | List (paginated) |
167
+ | `GET` | `/api/product/:id` | Get by ID |
168
+ | `POST` | `/api/product` | Create |
169
+ | `PUT` | `/api/product/:id` | Replace |
170
+ | `PATCH` | `/api/product/:id` | Partial update |
171
+ | `DELETE` | `/api/product/:id` | Soft-delete |
172
+
173
+ Query string params supported on list: `page`, `per_page`, `sort`, `search`, `fields`.
174
+
175
+ ---
176
+
177
+ ## Models
56
178
 
57
- vulkano(); ⁠
179
+ Models live in `vulkano/models/` and are auto-loaded as globals (e.g., `Product`).
180
+
181
+ ```js
182
+ // vulkano/models/Product.js
183
+ module.exports = {
184
+ attributes: {
185
+ name: { type: String, required: true },
186
+ price: { type: Number, default: 0 },
187
+ tags: { type: [String] }
188
+ },
189
+
190
+ // Lifecycle hooks
191
+ beforeSave(next) {
192
+ this.updatedAt = new Date();
193
+ next();
194
+ }
195
+ };
196
+ ```
197
+
198
+ Every model automatically gets `active`, `createdAt`, and `updatedAt` fields.
199
+ Models use [`mongoose-paginate-v2`](https://github.com/aravindnc/mongoose-paginate-v2) for pagination.
200
+
201
+ ---
202
+
203
+ ## Built-in Global Libs
204
+
205
+ All files in `vulkano/services/` are auto-loaded as globals. The framework also exposes:
206
+
207
+ | Global | Description |
208
+ |-------------|-----------------------------------------------------------------|
209
+ | `VSError` | Structured error factory (`reject`, `notFound`, `badRequest`) |
210
+ | `Jwt` | Sign / verify JWT tokens |
211
+ | `Paginate` | Query serialization and pagination helpers |
212
+ | `Merge` | Deep-merge utility (deepmerge-compatible, full option support) |
213
+ | `Encrypter` | Hash and compare passwords (bcrypt-based) |
214
+ | `Filter` | Input sanitization helpers |
215
+ | `Crontab` | Schedule recurring jobs with cron expressions |
216
+ | `ApiClient` | HTTP client for calling external APIs (native fetch + undici) |
217
+ | `Download` | File download helper |
218
+ | `i18n` | Internationalization via i18next |
219
+ | `mongoose` | Mongoose instance |
220
+
221
+ ---
222
+
223
+ ## File Uploads
224
+
225
+ Vulkano uses [Multer](https://github.com/expressjs/multer) v2. Files are available on `req.files` after a `multipart/form-data` POST:
226
+
227
+ ```js
228
+ 'post upload': function (req, res) {
229
+ const files = (req.files || []).map((f) => ({
230
+ fieldname: f.fieldname,
231
+ originalname: f.originalname,
232
+ mimetype: f.mimetype,
233
+ size: f.size
234
+ }));
235
+ res.vsr(Promise.resolve({ uploaded: files.length, files }));
236
+ }
237
+ ```
238
+
239
+ ---
240
+
241
+ ## JWT Authentication
242
+
243
+ Configure in `vulkano/config/express/jwt.js`:
244
+
245
+ ```js
246
+ module.exports = {
247
+ secret: process.env.JWT_SECRET,
248
+ unless: ['/auth/login', '/health'] // Public paths (no token required)
249
+ };
250
+ ```
251
+
252
+ Sign a token anywhere in your app:
253
+
254
+ ```js
255
+ const token = Jwt.encode({ userId: user._id });
256
+ ```
257
+
258
+ ---
259
+
260
+ ## Cron Jobs
261
+
262
+ ```js
263
+ // vulkano/services/Jobs.js
264
+ module.exports = {
265
+ init() {
266
+ Crontab.add('cleanup', '0 3 * * *', async () => {
267
+ await Report.deleteMany({ active: false });
268
+ });
269
+ }
270
+ };
58
271
  ```
59
272
 
273
+ ---
274
+
275
+ ## i18n
276
+
277
+ Translation files go in `vulkano/config/locales/`. Use `i18n.t('key')` anywhere in your app.
278
+
279
+ ---
280
+
281
+ ## Socket.io
282
+
283
+ Enabled via `vulkano/config/settings.js`. Adapters for MongoDB and Redis are included out of the box.
284
+
285
+ ---
286
+
287
+ ## Configuration β€” `vulkano/config/settings.js`
288
+
289
+ ```js
290
+ module.exports = {
291
+
292
+ // PORT to listen on
293
+ port: process.env.PORT || 3000,
294
+
295
+ // Salt for password
296
+ salt: process.env.SALT_KEY || '',
297
+
298
+ // Database configuration
299
+ database: {
300
+
301
+ // MONGO_URI connection
302
+ connection: process.env.MONGO_URI,
303
+
304
+ // Settings before to connect
305
+ settings: {
306
+ strictQuery: false,
307
+ debug: false
308
+ },
309
+
310
+ // Additional config to mongoose
311
+ config: {
312
+ useNewUrlParser: true,
313
+ useUnifiedTopology: true,
314
+ // family: 4 // 4 (IPv4), 6 (IPv6), or null (default: OS family)
315
+ // useFindAndModify: false,
316
+ // useCreateIndex: true
317
+ }
318
+
319
+ }
320
+ };
321
+ ```
322
+
323
+ ---
324
+
325
+ ## Running Tests
326
+
327
+ ```bash
328
+ npm test # all suites
329
+ npm run test:watch # watch mode
330
+ npm run test:coverage # with coverage report
331
+ ```
332
+
333
+ > Tests require Node.js 22. Use `nvm use 22` if needed.
334
+
335
+ ---
336
+
337
+ ## Support
60
338
 
61
- ## Your Vulkano App Folder
339
+ - [Open an issue](https://github.com/vulkanojs/vulkano-core/issues)
340
+ - [Open Collective β€” Backers](https://opencollective.com/vulkanojs#backers)
341
+ - [Buy me a coffee](https://buymeacoffee.com/argordmel)
62
342
 
63
- - `vulkano/` - Vulkano App
64
- - `config` - Your config files
65
- - `env` - Folder for custom environment settings
66
- - `express` - Folder to customize the server (cookies, jwt, helmet, cors and settings)
67
- - `locales` - i18n folder
68
- - `views` - Folder to customize filters and helpers to Nunjukcs
69
- - `controllers` - Your controllers
70
- - `models` - Your models
71
- - `services` - Your services or libs
343
+ ---
72
344
 
73
- ## Your Vulkano Public Folder
345
+ ## License
74
346
 
75
- - `public/` - Public Path
76
- - `css` - Styles
77
- - `fonts` - Fonts
78
- - `img` - Images
79
- - `js` - Javascript
80
- - `files` - Files uploaded
347
+ MIT Β© [Vulkano Team](https://github.com/vulkanojs)
package/app.js CHANGED
@@ -6,18 +6,14 @@
6
6
  */
7
7
 
8
8
  // Start Time for logs
9
- global.START_TIME = new Date();
9
+ global.START_TIME = Date.now();
10
10
 
11
11
  const dotenv = require('dotenv');
12
12
  const path = require('path');
13
- const moment = require('moment');
14
- const merge = require('deepmerge');
15
- const _ = require('underscore');
16
13
  const v8 = require('v8');
17
14
  const fs = require('fs');
18
15
 
19
16
  global.app = {};
20
- global._ = _;
21
17
 
22
18
  const rootProject = path.resolve(process.cwd());
23
19
 
@@ -62,7 +58,7 @@ if (!fs.existsSync(PUBLIC_PATH)) {
62
58
  }
63
59
 
64
60
  // Read Dontenv config
65
- dotenv.config({ path: `${ABS_PATH}/.env` });
61
+ dotenv.config({ path: `${ABS_PATH}/.env`, quiet: !process.env.DOTENV_VERBOSE });
66
62
 
67
63
  // Include all api config
68
64
  const config = require('include-all')({
@@ -71,6 +67,8 @@ const config = require('include-all')({
71
67
  optional: true
72
68
  });
73
69
 
70
+ const merge = require('./libs/Merge');
71
+
74
72
  //
75
73
  // Get package.json information
76
74
  //
@@ -236,7 +234,7 @@ async function startVulkano() {
236
234
  const startUpConfig = [];
237
235
  startUpConfig.push(' SOCKETS: ', `${colors.fg.green}${showColumn(socketText, 10)}${colors.reset}`);
238
236
  startUpConfig.push(' | ');
239
- startUpConfig.push(` STARTUP: ${colors.fg.green}${moment(moment().diff(global.START_TIME)).format('s.SSS')}s${colors.reset}`);
237
+ startUpConfig.push(` STARTUP: ${colors.fg.green}${((Date.now() - global.START_TIME) / 1000).toFixed(3)}s${colors.reset}`);
240
238
  console.log(startUpConfig.join(''));
241
239
 
242
240
  const dbConfig = [];
@@ -1,4 +1,4 @@
1
- const merge = require('deepmerge');
1
+ const merge = require('../libs/Merge');
2
2
 
3
3
  module.exports = function getExpressConfiguration() {
4
4
 
@@ -56,6 +56,10 @@ module.exports = function loadServer() {
56
56
 
57
57
  const vulkano = express();
58
58
 
59
+ // Expose the Express instance early so that custom() initializers in
60
+ // config/routes.js can register routes via app.vulkano.get(), app.vulkano.post(), etc.
61
+ app.vulkano = vulkano;
62
+
59
63
  // Settings
60
64
  vulkano.enable('trust proxy');
61
65
 
@@ -173,8 +177,8 @@ module.exports = function loadServer() {
173
177
  res.header('Access-Control-Allow-Origin', '*');
174
178
  res.header('Access-Control-Allow-Headers', 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept');
175
179
  }
176
- res.header('Allow', 'GET,PUT,POST,DELETE,OPTIONS');
177
- res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
180
+ res.header('Allow', 'GET,PUT,PATCH,POST,DELETE,OPTIONS');
181
+ res.header('Access-Control-Allow-Methods', 'GET,PUT,PATCH,POST,DELETE,OPTIONS');
178
182
 
179
183
  res.status(200).end();
180
184
 
@@ -230,7 +234,7 @@ module.exports = function loadServer() {
230
234
  tmpCorsHeaders = tmpCorsHeaders.concat(expressConfig.cors.headers || []);
231
235
 
232
236
  res.header('Access-Control-Allow-Origin', expressConfig.cors.origin);
233
- res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
237
+ res.header('Access-Control-Allow-Methods', 'GET,PUT,PATCH,POST,DELETE,OPTIONS');
234
238
  res.header('Access-Control-Allow-Headers', tmpCorsHeaders.join(', '));
235
239
 
236
240
  // Disable CACHE in API resources.
@@ -440,10 +444,15 @@ module.exports = function loadServer() {
440
444
  next();
441
445
  });
442
446
 
443
- // Middleware Folder
447
+ // Middleware Folder β€” routes.js always loads first if present
444
448
  const middlewares = app.config.middlewares || {};
445
449
 
450
+ if (middlewares.routes && typeof middlewares.routes === 'function') {
451
+ vulkano.use(middlewares.routes);
452
+ }
453
+
446
454
  Object.keys(middlewares).forEach( (item) => {
455
+ if (item === 'routes') return;
447
456
  const middlewareFunction = middlewares[item];
448
457
  if (typeof middlewareFunction === 'function') {
449
458
  vulkano.use(middlewareFunction);
@@ -497,13 +506,26 @@ module.exports = function loadServer() {
497
506
  Object.keys(this.routes || {}).forEach((i) => {
498
507
 
499
508
  const current = this.routes[i];
509
+
510
+ // Initializer functions: keys that are not path-based ('/...') and not
511
+ // 'METHOD path' pairs are treated as startup hooks that register routes
512
+ // directly via app.vulkano.get(), app.vulkano.post(), etc.
513
+ const keyParts = i.split(' ');
514
+ const isPathRoute = i.includes('/');
515
+ const isMethodRoute = ['get', 'post', 'put', 'delete', 'patch'].includes(keyParts[0].toLowerCase()) && keyParts.length > 1;
516
+
517
+ if (!isPathRoute && !isMethodRoute && typeof current === 'function') {
518
+ current();
519
+ return;
520
+ }
521
+
500
522
  const parts = i.split(' ');
501
523
  let pathToRun = parts.pop();
502
524
 
503
525
  // Capture the HTTP Method
504
526
  let option = (parts[0] !== undefined) ? String(parts[0]).toLowerCase() : 'get';
505
527
 
506
- if (option !== 'get' && option !== 'post' && option !== 'put' && option !== 'delete') {
528
+ if (option !== 'get' && option !== 'post' && option !== 'put' && option !== 'patch' && option !== 'delete') {
507
529
  option = 'get';
508
530
  }
509
531
 
@@ -600,7 +622,9 @@ module.exports = function loadServer() {
600
622
  path: routePath
601
623
  } = r || {};
602
624
 
603
- const routerControllerToCheck = Filter.get(req.path, 'trim', '/').split('/')[0];
625
+ const routerControllerToCheck = (typeof Filter !== 'undefined')
626
+ ? Filter.get(req.path, 'trim', '/').split('/')[0]
627
+ : req.path.replace(/^\/+|\/+$/g, '').split('/')[0];
604
628
 
605
629
  return routePath.startsWith(`/${routerControllerToCheck}`);
606
630
 
@@ -668,21 +692,26 @@ module.exports = function loadServer() {
668
692
 
669
693
  }
670
694
 
671
- let errorViewToShow = `${CORE_PATH}/views/errors/exception.html`;
695
+ const errStack = (err && err.stack) ? String(err.stack) : '';
696
+ const isMissingTemplate = errStack.includes('template not found');
672
697
 
673
- if (err.stack.indexOf('template not found') >= 0) {
674
- errorViewToShow = `${CORE_PATH}/views/errors/no_view.html`;
698
+ let missingView = '';
699
+ if (isMissingTemplate) {
700
+ const afterNotFound = errStack.split('not found:')[1] || '';
701
+ missingView = `${afterNotFound.split('.')[0].trim()}.html`;
675
702
  }
676
703
 
704
+ const errorViewToShow = isMissingTemplate
705
+ ? `${CORE_PATH}/views/errors/no_view.html`
706
+ : `${CORE_PATH}/views/errors/exception.html`;
707
+
677
708
  res.render(errorViewToShow, {
678
709
  statusCode: status,
679
710
  method: req.method,
680
711
  controller: req.path.split('/')[1],
681
712
  action: req.path.split('/')[2],
682
- view: err.stack.indexOf('template not found') >= 0
683
- ? String( `${(err.stack.split('not found:')[1]).split('.')[0]}.html` ).trim()
684
- : '',
685
- stack: err.stack
713
+ view: missingView,
714
+ stack: errStack
686
715
  });
687
716
 
688
717
  });
@@ -733,10 +762,14 @@ module.exports = function loadServer() {
733
762
 
734
763
  if ( String(adapter).toLocaleLowerCase() === 'redis') {
735
764
 
765
+ const {
766
+ socket: socketRedisConfig
767
+ } = redisAdapter || {};
768
+
736
769
  const {
737
770
  host,
738
771
  port
739
- } = redisAdapter || {};
772
+ } = socketRedisConfig || redisAdapter || {};
740
773
 
741
774
  if (!host || !port) {
742
775
  throw new Error('Unable to connect to Redis. File: "app/config/sockets/adapters/redis.js" to connect the sockets');
@@ -762,16 +795,9 @@ module.exports = function loadServer() {
762
795
 
763
796
  if ( String(adapter).toLocaleLowerCase() === 'redis') {
764
797
 
765
- const propsToRedis = {
766
- host: redisAdapter.host,
767
- port: redisAdapter.port
768
- };
769
-
770
- if (redisAdapter.password) {
771
- propsToRedis.password = redisAdapter.password;
772
- }
798
+ pubClient = socketRedis(redisAdapter);
799
+ pubClient.on('error', (err) => console.log('Socket Redis Client Error', err));
773
800
 
774
- pubClient = socketRedis(propsToRedis);
775
801
  subClient = pubClient.duplicate();
776
802
 
777
803
  io.adapter(socketRedisAdapter(pubClient, subClient));
@@ -792,7 +818,7 @@ module.exports = function loadServer() {
792
818
 
793
819
  let mongoClientDB = null;
794
820
 
795
- // if not has a mongo conection try to create it
821
+ // If no active Mongoose connection, create a dedicated one
796
822
  if (!mongoose.connection.readyState) {
797
823
 
798
824
  if (!socketsConnection) {
@@ -914,7 +940,7 @@ module.exports = function loadServer() {
914
940
 
915
941
  });
916
942
 
917
- // next line is the money
943
+ // Expose io globally so controllers can emit events
918
944
  global.io = io;
919
945
  vulkano.set('socketio', io);
920
946
 
@@ -93,6 +93,20 @@ module.exports = (modelName, allowedMethods) => {
93
93
 
94
94
  },
95
95
 
96
+ 'patch :id': function onPatchRecord(req, res) {
97
+
98
+ const {
99
+ id
100
+ } = req.params || {};
101
+
102
+ const {
103
+ body
104
+ } = req || {};
105
+
106
+ res.vsr(global[modelName].update(id, body), 202);
107
+
108
+ },
109
+
96
110
  'delete :id': function onDeleteRecord(req, res) {
97
111
 
98
112
  const {
@@ -124,6 +138,10 @@ module.exports = (modelName, allowedMethods) => {
124
138
  delete allMethods['put :id'];
125
139
  }
126
140
 
141
+ if (!tempAllowedMethods.includes('patch')) {
142
+ delete allMethods['patch :id'];
143
+ }
144
+
127
145
  if (!tempAllowedMethods.includes('delete')) {
128
146
  delete allMethods['delete :id'];
129
147
  }
@@ -17,7 +17,7 @@ module.exports = function loadControllersApplication() {
17
17
 
18
18
  Object.keys(AllControllers).forEach( (controller) => {
19
19
 
20
- const methods = ['get', 'post', 'put', 'delete'];
20
+ const methods = ['get', 'post', 'put', 'patch', 'delete'];
21
21
  const current = AllControllers[controller];
22
22
 
23
23
  const {
@@ -101,7 +101,7 @@ module.exports = function loadControllersApplication() {
101
101
  method = pathToRun.toLowerCase();
102
102
  pathToRun = `/${moduleName}/${controllerName}/`;
103
103
  } else {
104
- pathToRun = `/${moduleName}/${controllerName}/${pathToRun.replace(/GET|POST|DELETE|PUT/i, '')}`;
104
+ pathToRun = `/${moduleName}/${controllerName}/${pathToRun.replace(/GET|POST|DELETE|PUT|PATCH/i, '')}`;
105
105
  }
106
106
 
107
107
  }
@@ -132,7 +132,7 @@ module.exports = function loadControllersApplication() {
132
132
  method = pathToRun.toLowerCase();
133
133
  pathToRun = `/${controllerName}/`;
134
134
  } else {
135
- pathToRun = `/${controllerName}/${pathToRun.replace(/GET|POST|DELETE|PUT/i, '')}`;
135
+ pathToRun = `/${controllerName}/${pathToRun.replace(/GET|POST|DELETE|PUT|PATCH/i, '')}`;
136
136
  }
137
137
  }
138
138