millas 0.2.11 → 0.2.12-beta

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 (47) hide show
  1. package/package.json +17 -3
  2. package/src/auth/AuthController.js +42 -133
  3. package/src/auth/AuthMiddleware.js +12 -23
  4. package/src/auth/RoleMiddleware.js +7 -17
  5. package/src/commands/migrate.js +46 -31
  6. package/src/commands/serve.js +266 -37
  7. package/src/container/Application.js +88 -8
  8. package/src/controller/Controller.js +79 -300
  9. package/src/errors/ErrorRenderer.js +640 -0
  10. package/src/facades/Admin.js +49 -0
  11. package/src/facades/Auth.js +46 -0
  12. package/src/facades/Cache.js +17 -0
  13. package/src/facades/Database.js +43 -0
  14. package/src/facades/Events.js +24 -0
  15. package/src/facades/Http.js +54 -0
  16. package/src/facades/Log.js +56 -0
  17. package/src/facades/Mail.js +40 -0
  18. package/src/facades/Queue.js +23 -0
  19. package/src/facades/Storage.js +17 -0
  20. package/src/facades/Validation.js +69 -0
  21. package/src/http/MillasRequest.js +253 -0
  22. package/src/http/MillasResponse.js +196 -0
  23. package/src/http/RequestContext.js +176 -0
  24. package/src/http/ResponseDispatcher.js +144 -0
  25. package/src/http/helpers.js +164 -0
  26. package/src/http/index.js +13 -0
  27. package/src/index.js +55 -2
  28. package/src/logger/internal.js +76 -0
  29. package/src/logger/patchConsole.js +135 -0
  30. package/src/middleware/CorsMiddleware.js +22 -30
  31. package/src/middleware/LogMiddleware.js +27 -59
  32. package/src/middleware/Middleware.js +24 -15
  33. package/src/middleware/MiddlewarePipeline.js +30 -67
  34. package/src/middleware/MiddlewareRegistry.js +126 -0
  35. package/src/middleware/ThrottleMiddleware.js +22 -26
  36. package/src/orm/fields/index.js +124 -56
  37. package/src/orm/migration/ModelInspector.js +7 -3
  38. package/src/orm/model/Model.js +96 -6
  39. package/src/orm/query/QueryBuilder.js +141 -3
  40. package/src/providers/LogServiceProvider.js +88 -18
  41. package/src/providers/ProviderRegistry.js +14 -1
  42. package/src/providers/ServiceProvider.js +40 -8
  43. package/src/router/Router.js +155 -223
  44. package/src/scaffold/maker.js +24 -59
  45. package/src/scaffold/templates.js +13 -12
  46. package/src/validation/BaseValidator.js +193 -0
  47. package/src/validation/Validator.js +680 -0
@@ -0,0 +1,196 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * MillasResponse
5
+ *
6
+ * An immutable value object representing an HTTP response.
7
+ * Nothing is written to the socket until ResponseDispatcher.dispatch() is called.
8
+ *
9
+ * Developers never instantiate this directly — use the helper functions:
10
+ * jsonify(data, options)
11
+ * view('template', data, options)
12
+ * redirect('/path', options)
13
+ * text('Hello', options)
14
+ * file('/path/to/file')
15
+ * empty(204)
16
+ *
17
+ * Route handlers return a MillasResponse (or a plain value that gets
18
+ * auto-wrapped). Middleware can inspect or modify the response before
19
+ * it reaches the dispatcher.
20
+ *
21
+ * Fluent mutation — each method returns a NEW MillasResponse:
22
+ * return jsonify(user).status(201).header('X-Custom', 'value');
23
+ */
24
+ class MillasResponse {
25
+ /**
26
+ * @param {object} options
27
+ * @param {string} options.type — 'json' | 'html' | 'text' | 'redirect' | 'file' | 'empty' | 'stream'
28
+ * @param {*} options.body — response body
29
+ * @param {number} [options.status] — HTTP status code (default: 200)
30
+ * @param {object} [options.headers]— additional headers
31
+ * @param {object} [options.cookies]— cookies to set: { name: { value, options } }
32
+ */
33
+ constructor({ type, body, status = 200, headers = {}, cookies = {} } = {}) {
34
+ this._type = type;
35
+ this._body = body;
36
+ this._status = status;
37
+ this._headers = { ...headers };
38
+ this._cookies = { ...cookies };
39
+
40
+ // Make immutable after construction
41
+ Object.freeze(this._headers);
42
+ Object.freeze(this._cookies);
43
+ }
44
+
45
+ // ─── Accessors ──────────────────────────────────────────────────────────────
46
+
47
+ get type() { return this._type; }
48
+ get body() { return this._body; }
49
+ get statusCode() { return this._status; }
50
+ get headers() { return this._headers; }
51
+ get cookies() { return this._cookies; }
52
+
53
+ // ─── Fluent builders (return new instance each time) ─────────────────────
54
+
55
+ /**
56
+ * Set the HTTP status code.
57
+ * return jsonify(data).status(201)
58
+ */
59
+ status(code) {
60
+ return new MillasResponse({
61
+ type: this._type,
62
+ body: this._body,
63
+ status: code,
64
+ headers: this._headers,
65
+ cookies: this._cookies,
66
+ });
67
+ }
68
+
69
+ /**
70
+ * Add or override a response header.
71
+ * return jsonify(data).header('X-Custom-Id', '123')
72
+ */
73
+ header(name, value) {
74
+ return new MillasResponse({
75
+ type: this._type,
76
+ body: this._body,
77
+ status: this._status,
78
+ headers: { ...this._headers, [name]: value },
79
+ cookies: this._cookies,
80
+ });
81
+ }
82
+
83
+ /**
84
+ * Add multiple headers at once.
85
+ * return jsonify(data).withHeaders({ 'X-A': '1', 'X-B': '2' })
86
+ */
87
+ withHeaders(map = {}) {
88
+ return new MillasResponse({
89
+ type: this._type,
90
+ body: this._body,
91
+ status: this._status,
92
+ headers: { ...this._headers, ...map },
93
+ cookies: this._cookies,
94
+ });
95
+ }
96
+
97
+ /**
98
+ * Set a cookie on the response.
99
+ *
100
+ * return jsonify(data).cookie('token', value, { httpOnly: true, maxAge: 3600 })
101
+ */
102
+ cookie(name, value, options = {}) {
103
+ return new MillasResponse({
104
+ type: this._type,
105
+ body: this._body,
106
+ status: this._status,
107
+ headers: this._headers,
108
+ cookies: { ...this._cookies, [name]: { value, options } },
109
+ });
110
+ }
111
+
112
+ /**
113
+ * Clear a cookie.
114
+ * return jsonify(data).clearCookie('session')
115
+ */
116
+ clearCookie(name, options = {}) {
117
+ return this.cookie(name, '', { ...options, maxAge: 0, expires: new Date(0) });
118
+ }
119
+
120
+ // ─── Static factories ─────────────────────────────────────────────────────
121
+
122
+ /** JSON response */
123
+ static json(data, { status = 200, headers = {} } = {}) {
124
+ return new MillasResponse({
125
+ type: 'json',
126
+ body: data,
127
+ status,
128
+ headers: { 'Content-Type': 'application/json', ...headers },
129
+ });
130
+ }
131
+
132
+ /** HTML response */
133
+ static html(html, { status = 200, headers = {} } = {}) {
134
+ return new MillasResponse({
135
+ type: 'html',
136
+ body: html,
137
+ status,
138
+ headers: { 'Content-Type': 'text/html; charset=utf-8', ...headers },
139
+ });
140
+ }
141
+
142
+ /** Plain text response */
143
+ static text(text, { status = 200, headers = {} } = {}) {
144
+ return new MillasResponse({
145
+ type: 'text',
146
+ body: String(text),
147
+ status,
148
+ headers: { 'Content-Type': 'text/plain; charset=utf-8', ...headers },
149
+ });
150
+ }
151
+
152
+ /** Redirect response */
153
+ static redirect(url, { status = 302 } = {}) {
154
+ return new MillasResponse({
155
+ type: 'redirect',
156
+ body: url,
157
+ status,
158
+ headers: { Location: url },
159
+ });
160
+ }
161
+
162
+ /** File download / serve response */
163
+ static file(filePath, { download = false, name = null, headers = {} } = {}) {
164
+ return new MillasResponse({
165
+ type: 'file',
166
+ body: { path: filePath, download, name },
167
+ status: 200,
168
+ headers,
169
+ });
170
+ }
171
+
172
+ /** Empty response (204 No Content by default) */
173
+ static empty(status = 204) {
174
+ return new MillasResponse({ type: 'empty', body: null, status });
175
+ }
176
+
177
+ /** Rendered view/template response */
178
+ static view(template, data = {}, { status = 200, headers = {} } = {}) {
179
+ return new MillasResponse({
180
+ type: 'view',
181
+ body: { template, data },
182
+ status,
183
+ headers: { 'Content-Type': 'text/html; charset=utf-8', ...headers },
184
+ });
185
+ }
186
+
187
+ /**
188
+ * Check if something is a MillasResponse instance.
189
+ * Used by the router to distinguish from plain return values.
190
+ */
191
+ static isResponse(value) {
192
+ return value instanceof MillasResponse;
193
+ }
194
+ }
195
+
196
+ module.exports = MillasResponse;
@@ -0,0 +1,176 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * RequestContext
5
+ *
6
+ * The single argument passed to every Millas route handler and middleware.
7
+ * Developers destructure exactly what they need — nothing else is in scope.
8
+ *
9
+ * Inspired by FastAPI's parameter injection. Each key maps to a specific
10
+ * part of the request — no more digging through a monolithic req object.
11
+ *
12
+ * ── Usage ───────────────────────────────────────────────────────────────────
13
+ *
14
+ * // Route params → params
15
+ * Route.get('/users/:id', ({ params }) => User.findOrFail(params.id))
16
+ *
17
+ * // Query string → query
18
+ * Route.get('/users', ({ query }) => User.paginate(query.page, query.per_page))
19
+ *
20
+ * // Request body → body (alias: json)
21
+ * Route.post('/users', async ({ body }) => {
22
+ * const user = await User.create(body);
23
+ * return jsonify(user, { status: 201 });
24
+ * })
25
+ *
26
+ * // Uploaded files → files
27
+ * Route.post('/upload', ({ files }) => {
28
+ * const avatar = files.avatar;
29
+ * return jsonify({ size: avatar.size });
30
+ * })
31
+ *
32
+ * // Authenticated user → user
33
+ * Route.get('/me', ({ user }) => jsonify(user))
34
+ *
35
+ * // Multiple at once — destructure only what you need
36
+ * Route.put('/users/:id', async ({ params, body, user }) => {
37
+ * if (user.id !== params.id) abort(403);
38
+ * return jsonify(await User.update(params.id, body));
39
+ * })
40
+ *
41
+ * // Inline validation on body
42
+ * Route.post('/posts', async ({ body }) => {
43
+ * const data = await body.validate({
44
+ * title: 'required|string|max:255',
45
+ * content: 'required|string',
46
+ * });
47
+ * return jsonify(await Post.create(data));
48
+ * })
49
+ *
50
+ * // DI container — for resolving services at request time
51
+ * Route.get('/stats', ({ container }) => {
52
+ * const cache = container.make('Cache');
53
+ * return cache.remember('stats', 60, () => Stats.compute());
54
+ * })
55
+ *
56
+ * // Full MillasRequest escape hatch — when you need something not covered
57
+ * Route.get('/raw', ({ req }) => {
58
+ * const ip = req.ip;
59
+ * return jsonify({ ip });
60
+ * })
61
+ *
62
+ * ── Context shape ────────────────────────────────────────────────────────────
63
+ *
64
+ * {
65
+ * params, // route parameters { id: '5' }
66
+ * query, // query string { page: '2', search: 'alice' }
67
+ * body, // request body { name: 'Alice', email: '...' } + .validate()
68
+ * json, // alias for body (same object)
69
+ * files, // uploaded files { avatar: File, resume: File }
70
+ * headers, // request headers { authorization: 'Bearer ...' }
71
+ * cookies, // cookies { session: 'abc123' }
72
+ * user, // authenticated user (set by AuthMiddleware)
73
+ * req, // full MillasRequest (escape hatch)
74
+ * container, // DI container container.make('Cache')
75
+ * }
76
+ */
77
+ class RequestContext {
78
+ /**
79
+ * @param {import('./MillasRequest')} millaReq
80
+ * @param {import('../container/Container')|null} container
81
+ */
82
+ constructor(millaReq, container = null) {
83
+ this._req = millaReq;
84
+ this._container = container;
85
+
86
+ // ── params ────────────────────────────────────────────────────────────────
87
+ // Route parameters — /users/:id → params.id
88
+ this.params = millaReq.raw.params || {};
89
+
90
+ // ── query ─────────────────────────────────────────────────────────────────
91
+ // Query string — ?page=2&search=alice → query.page, query.search
92
+ this.query = millaReq.raw.query || {};
93
+
94
+ // ── body / json ───────────────────────────────────────────────────────────
95
+ // Parsed request body (JSON, form data, etc.)
96
+ // body and json are the same object — use whichever reads better.
97
+ const rawBody = millaReq.raw.body || {};
98
+ this.body = this._buildBody(rawBody, millaReq);
99
+ this.json = this.body; // alias
100
+
101
+ // ── files ─────────────────────────────────────────────────────────────────
102
+ // Uploaded files (populated by multer or similar middleware)
103
+ this.files = millaReq.raw.files || {};
104
+
105
+ // ── headers ───────────────────────────────────────────────────────────────
106
+ this.headers = millaReq.raw.headers || {};
107
+
108
+ // ── cookies ───────────────────────────────────────────────────────────────
109
+ this.cookies = millaReq.raw.cookies || {};
110
+
111
+ // ── user ──────────────────────────────────────────────────────────────────
112
+ // Authenticated user — set by AuthMiddleware via req.user
113
+ Object.defineProperty(this, 'user', {
114
+ get: () => millaReq.raw.user ?? null,
115
+ set: (v) => { millaReq.raw.user = v; },
116
+ enumerable: true,
117
+ });
118
+
119
+ // ── req ───────────────────────────────────────────────────────────────────
120
+ // Full MillasRequest — escape hatch for anything not covered above
121
+ this.req = millaReq;
122
+
123
+ // ── container ─────────────────────────────────────────────────────────────
124
+ // DI container — resolve services at request time
125
+ this.container = container;
126
+ }
127
+
128
+ // ─── Body with validation ──────────────────────────────────────────────────
129
+
130
+ /**
131
+ * Build the body object with an attached .validate() method.
132
+ * This keeps validation ergonomic and co-located with the body itself:
133
+ *
134
+ * const data = await body.validate({
135
+ * name: 'required|string|max:100',
136
+ * email: 'required|email',
137
+ * });
138
+ */
139
+ _buildBody(rawBody, millaReq) {
140
+ // Start with the raw body data
141
+ const body = Object.assign(Object.create(null), rawBody);
142
+
143
+ // Attach validate() directly on the body object
144
+ Object.defineProperty(body, 'validate', {
145
+ enumerable: false, // doesn't show up in Object.keys / JSON.stringify
146
+ value: async function validate(rules) {
147
+ const { Validator } = require('../validation/Validator');
148
+ return Validator.validate(rawBody, rules);
149
+ },
150
+ });
151
+
152
+ // Attach only() and except() helpers too
153
+ Object.defineProperty(body, 'only', {
154
+ enumerable: false,
155
+ value: function only(keys) {
156
+ return keys.reduce((acc, k) => {
157
+ if (k in rawBody) acc[k] = rawBody[k];
158
+ return acc;
159
+ }, {});
160
+ },
161
+ });
162
+
163
+ Object.defineProperty(body, 'except', {
164
+ enumerable: false,
165
+ value: function except(keys) {
166
+ return Object.fromEntries(
167
+ Object.entries(rawBody).filter(([k]) => !keys.includes(k))
168
+ );
169
+ },
170
+ });
171
+
172
+ return body;
173
+ }
174
+ }
175
+
176
+ module.exports = RequestContext;
@@ -0,0 +1,144 @@
1
+ 'use strict';
2
+
3
+ const MillasResponse = require('./MillasResponse');
4
+
5
+ /**
6
+ * ResponseDispatcher
7
+ *
8
+ * The only place in the entire framework where Express res methods are called.
9
+ * Takes a MillasResponse value object and drives the Express response.
10
+ *
11
+ * This is an internal kernel component — developers never call this directly.
12
+ * The Router calls it after the handler (and middleware pipeline) resolves.
13
+ */
14
+ class ResponseDispatcher {
15
+
16
+ /**
17
+ * Dispatch a MillasResponse to the Express res.
18
+ *
19
+ * @param {MillasResponse} response
20
+ * @param {import('express').Response} expressRes
21
+ */
22
+ static dispatch(response, expressRes) {
23
+ if (!response || !MillasResponse.isResponse(response)) {
24
+ throw new Error(
25
+ '[ResponseDispatcher] Expected a MillasResponse instance. ' +
26
+ 'Got: ' + typeof response
27
+ );
28
+ }
29
+
30
+ // ── Status ──────────────────────────────────────────────────────────────
31
+ expressRes.status(response.statusCode);
32
+
33
+ // ── CORS passthrough headers ─────────────────────────────────────────────
34
+ // CorsMiddleware stores headers on req._corsHeaders when it calls next()
35
+ // (i.e. non-preflight requests). Apply them here so every response carries
36
+ // the CORS headers regardless of what the route handler returned.
37
+ const corsHeaders = expressRes.req?._corsHeaders;
38
+ if (corsHeaders) {
39
+ for (const [name, value] of Object.entries(corsHeaders)) {
40
+ expressRes.setHeader(name, value);
41
+ }
42
+ }
43
+
44
+ // ── Response headers ─────────────────────────────────────────────────────
45
+ for (const [name, value] of Object.entries(response.headers)) {
46
+ expressRes.setHeader(name, value);
47
+ }
48
+
49
+ // ── Cookies ─────────────────────────────────────────────────────────────
50
+ for (const [name, { value, options }] of Object.entries(response.cookies)) {
51
+ if (options.maxAge === 0 || options.expires?.getTime() === 0) {
52
+ expressRes.clearCookie(name, options);
53
+ } else {
54
+ expressRes.cookie(name, value, options);
55
+ }
56
+ }
57
+
58
+ // ── Body ─────────────────────────────────────────────────────────────────
59
+ const { type, body } = response;
60
+
61
+ switch (type) {
62
+
63
+ case 'json':
64
+ // Let Express handle JSON serialisation and Content-Type
65
+ return expressRes.json(body);
66
+
67
+ case 'html':
68
+ return expressRes.send(body);
69
+
70
+ case 'text':
71
+ return expressRes.send(body);
72
+
73
+ case 'redirect':
74
+ return expressRes.redirect(response.statusCode, body);
75
+
76
+ case 'empty':
77
+ return expressRes.end();
78
+
79
+ case 'file': {
80
+ const { path: filePath, download, name: fileName } = body;
81
+ if (download) {
82
+ return expressRes.download(filePath, fileName || require('path').basename(filePath));
83
+ }
84
+ return expressRes.sendFile(require('path').resolve(filePath));
85
+ }
86
+
87
+ case 'view': {
88
+ // Nunjucks (or whatever template engine is wired) renders the template.
89
+ // expressRes.render() is configured by the framework's view engine setup.
90
+ const { template, data } = body;
91
+ return expressRes.render(template, data);
92
+ }
93
+
94
+ case 'stream': {
95
+ // body is a readable stream
96
+ if (body && typeof body.pipe === 'function') {
97
+ body.pipe(expressRes);
98
+ } else {
99
+ expressRes.end();
100
+ }
101
+ return;
102
+ }
103
+
104
+ default:
105
+ // Unknown type — try to send as-is
106
+ return expressRes.send(body);
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Auto-wrap a plain return value into a MillasResponse.
112
+ *
113
+ * Called when a handler returns something that is NOT a MillasResponse —
114
+ * e.g. a plain object, string, number, or array.
115
+ *
116
+ * @param {*} value
117
+ * @returns {MillasResponse}
118
+ */
119
+ static autoWrap(value) {
120
+ if (MillasResponse.isResponse(value)) return value;
121
+
122
+ if (value instanceof Error) {
123
+ // Let the caller handle errors — don't wrap them into responses
124
+ throw value;
125
+ }
126
+
127
+ if (typeof value === 'string') {
128
+ // Detect HTML (starts with < tag) vs plain text
129
+ const isHtml = value.trimStart().startsWith('<');
130
+ return isHtml
131
+ ? MillasResponse.html(value)
132
+ : MillasResponse.text(value);
133
+ }
134
+
135
+ if (typeof value === 'object' || typeof value === 'number' || typeof value === 'boolean') {
136
+ return MillasResponse.json(value);
137
+ }
138
+
139
+ // Fallback
140
+ return MillasResponse.text(String(value));
141
+ }
142
+ }
143
+
144
+ module.exports = ResponseDispatcher;
@@ -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
+ };