millas 0.2.11 → 0.2.12-beta-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.
Files changed (74) hide show
  1. package/package.json +6 -5
  2. package/src/auth/Auth.js +13 -8
  3. package/src/auth/AuthController.js +45 -134
  4. package/src/auth/AuthMiddleware.js +12 -23
  5. package/src/auth/AuthUser.js +98 -0
  6. package/src/auth/RoleMiddleware.js +7 -17
  7. package/src/cli.js +1 -1
  8. package/src/commands/migrate.js +46 -31
  9. package/src/commands/serve.js +238 -38
  10. package/src/container/AppInitializer.js +158 -0
  11. package/src/container/Application.js +288 -183
  12. package/src/container/HttpServer.js +156 -0
  13. package/src/container/MillasApp.js +23 -280
  14. package/src/container/MillasConfig.js +163 -0
  15. package/src/controller/Controller.js +79 -300
  16. package/src/core/auth.js +9 -0
  17. package/src/core/db.js +8 -0
  18. package/src/core/foundation.js +67 -0
  19. package/src/core/http.js +11 -0
  20. package/src/core/mail.js +6 -0
  21. package/src/core/queue.js +7 -0
  22. package/src/core/validation.js +29 -0
  23. package/src/errors/ErrorRenderer.js +640 -0
  24. package/src/facades/Admin.js +49 -0
  25. package/src/facades/Auth.js +29 -0
  26. package/src/facades/Cache.js +28 -0
  27. package/src/facades/Database.js +43 -0
  28. package/src/facades/Events.js +25 -0
  29. package/src/facades/Facade.js +197 -0
  30. package/src/facades/Http.js +51 -0
  31. package/src/facades/Log.js +32 -0
  32. package/src/facades/Mail.js +35 -0
  33. package/src/facades/Queue.js +30 -0
  34. package/src/facades/Storage.js +25 -0
  35. package/src/facades/Url.js +53 -0
  36. package/src/http/HttpClient.js +673 -0
  37. package/src/http/MillasRequest.js +253 -0
  38. package/src/http/MillasResponse.js +196 -0
  39. package/src/http/RequestContext.js +176 -0
  40. package/src/http/ResponseDispatcher.js +51 -0
  41. package/src/http/UrlGenerator.js +375 -0
  42. package/src/http/WelcomePage.js +273 -0
  43. package/src/http/adapters/ExpressAdapter.js +315 -0
  44. package/src/http/adapters/HttpAdapter.js +168 -0
  45. package/src/http/adapters/index.js +9 -0
  46. package/src/http/helpers.js +164 -0
  47. package/src/http/index.js +13 -0
  48. package/src/index.js +5 -91
  49. package/src/logger/formatters/PrettyFormatter.js +15 -5
  50. package/src/logger/internal.js +76 -0
  51. package/src/logger/patchConsole.js +145 -0
  52. package/src/middleware/CorsMiddleware.js +22 -30
  53. package/src/middleware/LogMiddleware.js +27 -59
  54. package/src/middleware/Middleware.js +24 -15
  55. package/src/middleware/MiddlewarePipeline.js +30 -67
  56. package/src/middleware/MiddlewareRegistry.js +106 -0
  57. package/src/middleware/ThrottleMiddleware.js +22 -26
  58. package/src/orm/fields/index.js +124 -56
  59. package/src/orm/migration/ModelInspector.js +339 -336
  60. package/src/orm/model/Model.js +96 -6
  61. package/src/orm/query/QueryBuilder.js +141 -3
  62. package/src/providers/AuthServiceProvider.js +9 -5
  63. package/src/providers/CacheStorageServiceProvider.js +3 -1
  64. package/src/providers/EventServiceProvider.js +2 -1
  65. package/src/providers/LogServiceProvider.js +88 -17
  66. package/src/providers/MailServiceProvider.js +3 -2
  67. package/src/providers/ProviderRegistry.js +14 -1
  68. package/src/providers/QueueServiceProvider.js +3 -2
  69. package/src/providers/ServiceProvider.js +40 -8
  70. package/src/router/Router.js +121 -222
  71. package/src/scaffold/maker.js +24 -59
  72. package/src/scaffold/templates.js +21 -19
  73. package/src/validation/BaseValidator.js +193 -0
  74. package/src/validation/Validator.js +680 -0
@@ -0,0 +1,315 @@
1
+ 'use strict';
2
+
3
+ const HttpAdapter = require('./HttpAdapter');
4
+ const MillasRequest = require('../MillasRequest');
5
+ const MillasResponse = require('../MillasResponse');
6
+ const RequestContext = require('../RequestContext');
7
+ const ErrorRenderer = require('../../errors/ErrorRenderer');
8
+ const WelcomePage = require('../WelcomePage');
9
+ const http = require("http");
10
+
11
+ /**
12
+ * ExpressAdapter
13
+ *
14
+ * The Express implementation of HttpAdapter.
15
+ * This is the ONLY file in the entire Millas codebase that imports Express
16
+ * or calls Express APIs (req, res, next, app[verb], app.use, etc.).
17
+ *
18
+ * Swapping to a different HTTP engine means writing a new adapter class —
19
+ * zero changes to the kernel, Router, MiddlewareRegistry, or any user code.
20
+ */
21
+ class ExpressAdapter extends HttpAdapter {
22
+
23
+ /**
24
+ * @param {import('express').Application} expressApp
25
+ */
26
+ constructor(expressApp) {
27
+ super();
28
+ this._app = expressApp;
29
+ this._server = null;
30
+ }
31
+
32
+ // ── Setup ──────────────────────────────────────────────────────────────────
33
+
34
+ applyBodyParsers() {
35
+ const express = require('express');
36
+ this._app.use(express.json());
37
+ this._app.use(express.urlencoded({extended: true}));
38
+ }
39
+
40
+ applyMiddleware(fn) {
41
+ this._app.use(fn);
42
+ }
43
+
44
+ // ── Route mounting ─────────────────────────────────────────────────────────
45
+
46
+ mountRoute(verb, path, handlers) {
47
+ this._app[verb.toLowerCase()](path, ...handlers);
48
+ }
49
+
50
+ mountWelcome(handler) {
51
+ this._app.get('/', handler);
52
+ }
53
+
54
+ mountNotFound() {
55
+ this._app.use(ErrorRenderer.notFound());
56
+ }
57
+
58
+ mountErrorHandler() {
59
+ this._app.use(ErrorRenderer.handler());
60
+ }
61
+
62
+ // ── Request / Response bridge ──────────────────────────────────────────────
63
+
64
+ /**
65
+ * Wrap a Millas kernel handler into an Express (req, res, next) function.
66
+ *
67
+ * This is the ONLY place in the codebase where Express req/res/next
68
+ * appear outside of adapter code. The kernel handler never sees them.
69
+ */
70
+ wrapKernelHandler(kernelFn, displayName, container) {
71
+ const fnName = displayName || kernelFn.name || 'anonymous';
72
+
73
+ return (expressReq, expressRes, expressNext) => {
74
+ const millaReq = new MillasRequest(expressReq);
75
+ const ctx = new RequestContext(millaReq, container);
76
+
77
+ let nextCalled = false;
78
+ const trackedNext = (...args) => {
79
+ nextCalled = true;
80
+ expressNext(...args);
81
+ };
82
+
83
+ new Promise((resolve, reject) => {
84
+ try {
85
+ resolve(kernelFn(ctx, trackedNext));
86
+ } catch (err) {
87
+ reject(err);
88
+ }
89
+ })
90
+ .then(value => {
91
+ if (nextCalled) return;
92
+ if (expressRes.headersSent) return;
93
+
94
+ if (value === undefined || value === null) {
95
+ return expressNext(Object.assign(
96
+ new Error(
97
+ `Route handler "${fnName}" did not return a response.\n` +
98
+ `Return a MillasResponse or a plain value:\n\n` +
99
+ ` return jsonify({ ok: true })\n` +
100
+ ` return { ok: true } // auto-wrapped\n` +
101
+ ` return 'Hello world' // auto-wrapped\n` +
102
+ ` return redirect('/login')\n` +
103
+ ` return view('home', { data })`
104
+ ),
105
+ {status: 500, statusCode: 500}
106
+ ));
107
+ }
108
+
109
+ if (value instanceof Error) return expressNext(value);
110
+
111
+ try {
112
+ const response = MillasResponse.isResponse(value)
113
+ ? value
114
+ : this._autoWrap(value);
115
+ this.dispatch(response, expressRes);
116
+ } catch (dispatchErr) {
117
+ expressNext(dispatchErr);
118
+ }
119
+ })
120
+ .catch(expressNext);
121
+ };
122
+ }
123
+
124
+ /**
125
+ * Wrap a Millas middleware instance into an Express (req, res, next) function.
126
+ */
127
+ wrapMiddleware(instance, container) {
128
+ return (expressReq, expressRes, expressNext) => {
129
+ const millaReq = new MillasRequest(expressReq);
130
+ const ctx = new RequestContext(millaReq, container);
131
+
132
+ const next = () => {
133
+ expressNext();
134
+ return undefined;
135
+ };
136
+
137
+ new Promise((resolve, reject) => {
138
+ try {
139
+ resolve(instance.handle(ctx, next));
140
+ } catch (err) {
141
+ reject(err);
142
+ }
143
+ })
144
+ .then(value => {
145
+ if (value !== undefined && value !== null && !expressRes.headersSent) {
146
+ const response = MillasResponse.isResponse(value)
147
+ ? value
148
+ : this._autoWrap(value);
149
+ this.dispatch(response, expressRes);
150
+ }
151
+ })
152
+ .catch(expressNext);
153
+ };
154
+ }
155
+
156
+ /**
157
+ * Dispatch a MillasResponse to the Express res object.
158
+ * This is the ONLY place in the codebase where Express res methods are called.
159
+ */
160
+ dispatch(response, expressRes) {
161
+ if (!response || !MillasResponse.isResponse(response)) {
162
+ throw new Error(
163
+ '[ExpressAdapter] Expected a MillasResponse. Got: ' + typeof response
164
+ );
165
+ }
166
+
167
+ // Status
168
+ expressRes.status(response.statusCode);
169
+
170
+ // CORS headers stored by CorsMiddleware on next() calls
171
+ const corsHeaders = expressRes.req?._corsHeaders;
172
+ if (corsHeaders) {
173
+ for (const [name, value] of Object.entries(corsHeaders)) {
174
+ expressRes.setHeader(name, value);
175
+ }
176
+ }
177
+
178
+ // Response headers
179
+ for (const [name, value] of Object.entries(response.headers)) {
180
+ expressRes.setHeader(name, value);
181
+ }
182
+
183
+ // Cookies
184
+ for (const [name, {value, options}] of Object.entries(response.cookies)) {
185
+ if (options.maxAge === 0 || options.expires?.getTime() === 0) {
186
+ expressRes.clearCookie(name, options);
187
+ } else {
188
+ expressRes.cookie(name, value, options);
189
+ }
190
+ }
191
+
192
+ // Body
193
+ const {type, body} = response;
194
+
195
+ switch (type) {
196
+ case 'json':
197
+ return expressRes.json(body);
198
+
199
+ case 'html':
200
+ case 'text':
201
+ return expressRes.send(body);
202
+
203
+ case 'redirect':
204
+ return expressRes.redirect(response.statusCode, body);
205
+
206
+ case 'empty':
207
+ return expressRes.end();
208
+
209
+ case 'file': {
210
+ const {path: filePath, download, name: fileName} = body;
211
+ if (download) {
212
+ return expressRes.download(
213
+ filePath,
214
+ fileName || require('path').basename(filePath)
215
+ );
216
+ }
217
+ return expressRes.sendFile(require('path').resolve(filePath));
218
+ }
219
+
220
+ case 'view': {
221
+ const {template, data} = body;
222
+ return expressRes.render(template, data);
223
+ }
224
+
225
+ case 'stream': {
226
+ if (body && typeof body.pipe === 'function') {
227
+ body.pipe(expressRes);
228
+ } else {
229
+ expressRes.end();
230
+ }
231
+ return;
232
+ }
233
+
234
+ default:
235
+ return expressRes.send(body);
236
+ }
237
+ }
238
+
239
+ // ── Server lifecycle ───────────────────────────────────────────────────────
240
+
241
+ listen(port, host) {
242
+ return new Promise((resolve, reject) => {
243
+ this.initListener(port, host, resolve, reject);
244
+ });
245
+ }
246
+
247
+ initListener(port, host, onListening, onError) {
248
+ const server = http.createServer(this._app);
249
+ server.listen(port, host);
250
+ server.on('error', onError);
251
+ server.on('listening', onListening);
252
+ }
253
+
254
+ close() {
255
+ return new Promise((resolve) => {
256
+ if (!this._server) return resolve();
257
+ this._server.close(() => resolve());
258
+ });
259
+ }
260
+
261
+ // ── WelcomePage factory ───────────────────────────────────────────────────
262
+
263
+ /**
264
+ * Build the welcome page native handler.
265
+ * Returned value is passed directly to mountWelcome().
266
+ */
267
+ makeWelcomeHandler(version) {
268
+ return WelcomePage.handler(version);
269
+ }
270
+
271
+ // ── Internal ──────────────────────────────────────────────────────────────
272
+
273
+ /**
274
+ * Auto-wrap a plain JS value into a MillasResponse.
275
+ * Kept inside the adapter — not on the kernel — because it ultimately
276
+ * decides what to send over the wire, which is adapter territory.
277
+ */
278
+ _autoWrap(value) {
279
+ if (MillasResponse.isResponse(value)) return value;
280
+ if (value instanceof Error) throw value;
281
+
282
+ if (typeof value === 'string') {
283
+ return value.trimStart().startsWith('<')
284
+ ? MillasResponse.html(value)
285
+ : MillasResponse.text(value);
286
+ }
287
+
288
+ if (
289
+ typeof value === 'object' ||
290
+ typeof value === 'number' ||
291
+ typeof value === 'boolean'
292
+ ) {
293
+ return MillasResponse.json(value);
294
+ }
295
+
296
+ return MillasResponse.text(String(value));
297
+ }
298
+
299
+ /**
300
+ * Expose the raw Express app for escape hatches.
301
+ * Prefer not using this — it couples code to Express.
302
+ */
303
+ get nativeApp() {
304
+ return this._app;
305
+ }
306
+
307
+ /**
308
+ * Expose the raw net.Server once listen() has been called.
309
+ */
310
+ get server() {
311
+ return this._server;
312
+ }
313
+ }
314
+
315
+ module.exports = ExpressAdapter;
@@ -0,0 +1,168 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * HttpAdapter
5
+ *
6
+ * Abstract interface between the Millas kernel and any underlying HTTP engine.
7
+ * The kernel never imports Express (or Fastify, or Hono) directly — it only
8
+ * calls the methods defined here.
9
+ *
10
+ * To add a new HTTP engine, create a class that extends HttpAdapter and
11
+ * implements every method. The kernel works unchanged.
12
+ *
13
+ * ── Implemented by ───────────────────────────────────────────────────────────
14
+ *
15
+ * ExpressAdapter (default, ships with Millas)
16
+ * FastifyAdapter (future)
17
+ * HonoAdapter (future)
18
+ *
19
+ * ── Lifecycle ────────────────────────────────────────────────────────────────
20
+ *
21
+ * 1. adapter.applyBodyParsers() — JSON + urlencoded
22
+ * 2. adapter.applyMiddleware(fn) — any raw adapter-level middleware
23
+ * 3. adapter.mountRoute(verb, path, handlers) — register app routes
24
+ * 4. adapter.mountWelcome(handler) — optional dev welcome page
25
+ * 5. adapter.mountNotFound() — 404 handler
26
+ * 6. adapter.mountErrorHandler() — global error handler
27
+ * 7. await adapter.listen(port, host) — start accepting connections
28
+ * 8. adapter.close() — graceful shutdown
29
+ */
30
+ class HttpAdapter {
31
+
32
+ // ── Setup ──────────────────────────────────────────────────────────────────
33
+
34
+ /**
35
+ * Apply JSON + urlencoded body parsers.
36
+ * Called once during bootstrap, before any routes are mounted.
37
+ */
38
+ applyBodyParsers() {
39
+ throw new Error(`${this.constructor.name} must implement applyBodyParsers()`);
40
+ }
41
+
42
+ /**
43
+ * Apply a single raw middleware function at the adapter level.
44
+ * Used for things like helmet(), compression() that are engine-specific.
45
+ *
46
+ * @param {Function} fn — adapter-native middleware function
47
+ */
48
+ applyMiddleware(fn) {
49
+ throw new Error(`${this.constructor.name} must implement applyMiddleware(fn)`);
50
+ }
51
+
52
+ // ── Route mounting ─────────────────────────────────────────────────────────
53
+
54
+ /**
55
+ * Register a route handler.
56
+ *
57
+ * @param {string} verb — 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS'
58
+ * @param {string} path — e.g. '/users/:id'
59
+ * @param {Function[]} handlers — [middleware..., terminalHandler]
60
+ * Each handler is a Millas kernel handler
61
+ * (expressReq, expressRes, expressNext) already
62
+ * converted by the adapter's wrapKernelHandler().
63
+ */
64
+ mountRoute(verb, path, handlers) {
65
+ throw new Error(`${this.constructor.name} must implement mountRoute(verb, path, handlers)`);
66
+ }
67
+
68
+ /**
69
+ * Mount the dev welcome page for GET /.
70
+ * Only called when no user route covers GET /.
71
+ *
72
+ * @param {Function} handler — adapter-native handler function
73
+ */
74
+ mountWelcome(handler) {
75
+ throw new Error(`${this.constructor.name} must implement mountWelcome(handler)`);
76
+ }
77
+
78
+ /**
79
+ * Mount the 404 fallback handler.
80
+ * Must be called AFTER all routes and mountWelcome().
81
+ */
82
+ mountNotFound() {
83
+ throw new Error(`${this.constructor.name} must implement mountNotFound()`);
84
+ }
85
+
86
+ /**
87
+ * Mount the global error handler.
88
+ * Must be called LAST — after mountNotFound().
89
+ */
90
+ mountErrorHandler() {
91
+ throw new Error(`${this.constructor.name} must implement mountErrorHandler()`);
92
+ }
93
+
94
+ // ── Request / Response bridge ──────────────────────────────────────────────
95
+
96
+ /**
97
+ * Wrap a Millas kernel handler function into an adapter-native handler.
98
+ *
99
+ * The kernel handler signature is:
100
+ * (millaCtx: RequestContext, trackedNext: Function) => Promise<MillasResponse>
101
+ *
102
+ * The adapter wraps this into whatever its native handler signature is,
103
+ * e.g. (req, res, next) for Express.
104
+ *
105
+ * @param {Function} kernelFn
106
+ * @param {string} displayName — for error messages
107
+ * @param {object} container — DI container
108
+ * @returns {Function} adapter-native handler
109
+ */
110
+ wrapKernelHandler(kernelFn, displayName, container) {
111
+ throw new Error(`${this.constructor.name} must implement wrapKernelHandler()`);
112
+ }
113
+
114
+ /**
115
+ * Wrap a Millas middleware instance into an adapter-native handler.
116
+ *
117
+ * @param {object} instance — Millas Middleware instance with handle(ctx, next)
118
+ * @param {object} container — DI container
119
+ * @returns {Function} adapter-native handler
120
+ */
121
+ wrapMiddleware(instance, container) {
122
+ throw new Error(`${this.constructor.name} must implement wrapMiddleware()`);
123
+ }
124
+
125
+ /**
126
+ * Dispatch a MillasResponse to the underlying engine's response object.
127
+ *
128
+ * @param {MillasResponse} response
129
+ * @param {*} nativeRes — e.g. Express res
130
+ */
131
+ dispatch(response, nativeRes) {
132
+ throw new Error(`${this.constructor.name} must implement dispatch(response, nativeRes)`);
133
+ }
134
+
135
+ // ── Server lifecycle ───────────────────────────────────────────────────────
136
+
137
+ /**
138
+ * Start listening on port/host.
139
+ * Returns a Promise that resolves once the server is bound.
140
+ *
141
+ * @param {number} port
142
+ * @param {string} host
143
+ * @returns {Promise<void>}
144
+ */
145
+ listen(port, host) {
146
+ throw new Error(`${this.constructor.name} must implement listen(port, host)`);
147
+ }
148
+
149
+ /**
150
+ * Gracefully close the server.
151
+ * Returns a Promise that resolves once all connections are drained.
152
+ *
153
+ * @returns {Promise<void>}
154
+ */
155
+ close() {
156
+ throw new Error(`${this.constructor.name} must implement close()`);
157
+ }
158
+
159
+ /**
160
+ * The name of this adapter, used in logs and error messages.
161
+ * @returns {string}
162
+ */
163
+ get name() {
164
+ return this.constructor.name;
165
+ }
166
+ }
167
+
168
+ module.exports = HttpAdapter;
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ const HttpAdapter = require('./HttpAdapter');
4
+ const ExpressAdapter = require('./ExpressAdapter');
5
+
6
+ module.exports = {
7
+ HttpAdapter,
8
+ ExpressAdapter,
9
+ };
@@ -0,0 +1,164 @@
1
+ 'use strict';
2
+
3
+ const MillasResponse = require('./MillasResponse');
4
+ const HttpError = require('../errors/HttpError');
5
+
6
+ /**
7
+ * Millas HTTP Helper Functions
8
+ *
9
+ * These are the only response-building tools developers need.
10
+ * Import them at the top of any route/controller file.
11
+ *
12
+ * const { jsonify, view, redirect, text, abort } = require('millas');
13
+ *
14
+ * Every helper returns a MillasResponse instance. Nothing is written
15
+ * to the socket until the kernel's ResponseDispatcher processes it.
16
+ */
17
+
18
+ /**
19
+ * Return a JSON response.
20
+ *
21
+ * return jsonify(users)
22
+ * return jsonify(user, { status: 201 })
23
+ * return jsonify({ error: 'Not found' }, { status: 404 })
24
+ * return jsonify(data).header('X-Total', String(total))
25
+ * return jsonify(data).cookie('token', jwt, { httpOnly: true })
26
+ *
27
+ * @param {*} data
28
+ * @param {object} [options]
29
+ * @param {number} [options.status=200]
30
+ * @param {object} [options.headers={}]
31
+ * @returns {MillasResponse}
32
+ */
33
+ function jsonify(data, options = {}) {
34
+ return MillasResponse.json(data, options);
35
+ }
36
+
37
+ /**
38
+ * Return an HTML view (template) response.
39
+ *
40
+ * return view('users/index', { users })
41
+ * return view('emails/welcome', { user }, { status: 200 })
42
+ *
43
+ * @param {string} template — template path relative to views directory
44
+ * @param {object} [data={}] — data passed to the template
45
+ * @param {object} [options]
46
+ * @param {number} [options.status=200]
47
+ * @returns {MillasResponse}
48
+ */
49
+ function view(template, data = {}, options = {}) {
50
+ return MillasResponse.view(template, data, options);
51
+ }
52
+
53
+ /**
54
+ * Return a redirect response.
55
+ *
56
+ * return redirect('/login')
57
+ * return redirect('/dashboard', { status: 301 })
58
+ * return redirect('back') // redirects to Referer header or '/'
59
+ *
60
+ * @param {string} url
61
+ * @param {object} [options]
62
+ * @param {number} [options.status=302]
63
+ * @returns {MillasResponse}
64
+ */
65
+ function redirect(url, options = {}) {
66
+ return MillasResponse.redirect(url, options);
67
+ }
68
+
69
+ /**
70
+ * Return a plain text response.
71
+ *
72
+ * return text('Hello, world')
73
+ * return text('Created', { status: 201 })
74
+ *
75
+ * @param {string} content
76
+ * @param {object} [options]
77
+ * @param {number} [options.status=200]
78
+ * @returns {MillasResponse}
79
+ */
80
+ function text(content, options = {}) {
81
+ return MillasResponse.text(content, options);
82
+ }
83
+
84
+ /**
85
+ * Return a file response (send / download).
86
+ *
87
+ * return file('/storage/uploads/report.pdf')
88
+ * return file('/storage/uploads/report.pdf', { download: true })
89
+ * return file('/storage/uploads/report.pdf', { download: true, name: 'report.pdf' })
90
+ *
91
+ * @param {string} filePath — absolute or relative path
92
+ * @param {object} [options]
93
+ * @param {boolean} [options.download=false] — force download (Content-Disposition: attachment)
94
+ * @param {string} [options.name] — filename shown to the user on download
95
+ * @returns {MillasResponse}
96
+ */
97
+ function file(filePath, options = {}) {
98
+ return MillasResponse.file(filePath, options);
99
+ }
100
+
101
+ /**
102
+ * Return an empty response.
103
+ *
104
+ * return empty() // 204 No Content
105
+ * return empty(200) // 200 with no body
106
+ *
107
+ * @param {number} [status=204]
108
+ * @returns {MillasResponse}
109
+ */
110
+ function empty(status = 204) {
111
+ return MillasResponse.empty(status);
112
+ }
113
+
114
+ /**
115
+ * Throw an HTTP error — caught by the kernel and rendered by ErrorRenderer.
116
+ *
117
+ * abort(404)
118
+ * abort(403, 'You are not allowed to do that')
119
+ * abort(422, 'Validation failed', { email: ['Email is required'] })
120
+ *
121
+ * @param {number} status
122
+ * @param {string} [message]
123
+ * @param {object} [errors]
124
+ * @throws {HttpError}
125
+ */
126
+ function abort(status, message, errors = null) {
127
+ throw new HttpError(status, message, errors);
128
+ }
129
+
130
+ /**
131
+ * Throw a 404 Not Found error.
132
+ * notFound()
133
+ * notFound('User not found')
134
+ */
135
+ function notFound(message = 'Not Found') {
136
+ abort(404, message);
137
+ }
138
+
139
+ /**
140
+ * Throw a 401 Unauthorized error.
141
+ */
142
+ function unauthorized(message = 'Unauthorized') {
143
+ abort(401, message);
144
+ }
145
+
146
+ /**
147
+ * Throw a 403 Forbidden error.
148
+ */
149
+ function forbidden(message = 'Forbidden') {
150
+ abort(403, message);
151
+ }
152
+
153
+ module.exports = {
154
+ jsonify,
155
+ view,
156
+ redirect,
157
+ text,
158
+ file,
159
+ empty,
160
+ abort,
161
+ notFound,
162
+ unauthorized,
163
+ forbidden,
164
+ };
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ const MillasRequest = require('./MillasRequest');
4
+ const MillasResponse = require('./MillasResponse');
5
+ const ResponseDispatcher = require('./ResponseDispatcher');
6
+ const helpers = require('./helpers');
7
+
8
+ module.exports = {
9
+ MillasRequest,
10
+ MillasResponse,
11
+ ResponseDispatcher,
12
+ ...helpers,
13
+ };