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.
- package/package.json +6 -5
- package/src/auth/Auth.js +13 -8
- package/src/auth/AuthController.js +45 -134
- package/src/auth/AuthMiddleware.js +12 -23
- package/src/auth/AuthUser.js +98 -0
- package/src/auth/RoleMiddleware.js +7 -17
- package/src/cli.js +1 -1
- package/src/commands/migrate.js +46 -31
- package/src/commands/serve.js +238 -38
- package/src/container/AppInitializer.js +158 -0
- package/src/container/Application.js +288 -183
- package/src/container/HttpServer.js +156 -0
- package/src/container/MillasApp.js +23 -280
- package/src/container/MillasConfig.js +163 -0
- package/src/controller/Controller.js +79 -300
- package/src/core/auth.js +9 -0
- package/src/core/db.js +8 -0
- package/src/core/foundation.js +67 -0
- package/src/core/http.js +11 -0
- package/src/core/mail.js +6 -0
- package/src/core/queue.js +7 -0
- package/src/core/validation.js +29 -0
- package/src/errors/ErrorRenderer.js +640 -0
- package/src/facades/Admin.js +49 -0
- package/src/facades/Auth.js +29 -0
- package/src/facades/Cache.js +28 -0
- package/src/facades/Database.js +43 -0
- package/src/facades/Events.js +25 -0
- package/src/facades/Facade.js +197 -0
- package/src/facades/Http.js +51 -0
- package/src/facades/Log.js +32 -0
- package/src/facades/Mail.js +35 -0
- package/src/facades/Queue.js +30 -0
- package/src/facades/Storage.js +25 -0
- package/src/facades/Url.js +53 -0
- package/src/http/HttpClient.js +673 -0
- package/src/http/MillasRequest.js +253 -0
- package/src/http/MillasResponse.js +196 -0
- package/src/http/RequestContext.js +176 -0
- package/src/http/ResponseDispatcher.js +51 -0
- package/src/http/UrlGenerator.js +375 -0
- package/src/http/WelcomePage.js +273 -0
- package/src/http/adapters/ExpressAdapter.js +315 -0
- package/src/http/adapters/HttpAdapter.js +168 -0
- package/src/http/adapters/index.js +9 -0
- package/src/http/helpers.js +164 -0
- package/src/http/index.js +13 -0
- package/src/index.js +5 -91
- package/src/logger/formatters/PrettyFormatter.js +15 -5
- package/src/logger/internal.js +76 -0
- package/src/logger/patchConsole.js +145 -0
- package/src/middleware/CorsMiddleware.js +22 -30
- package/src/middleware/LogMiddleware.js +27 -59
- package/src/middleware/Middleware.js +24 -15
- package/src/middleware/MiddlewarePipeline.js +30 -67
- package/src/middleware/MiddlewareRegistry.js +106 -0
- package/src/middleware/ThrottleMiddleware.js +22 -26
- package/src/orm/fields/index.js +124 -56
- package/src/orm/migration/ModelInspector.js +339 -336
- package/src/orm/model/Model.js +96 -6
- package/src/orm/query/QueryBuilder.js +141 -3
- package/src/providers/AuthServiceProvider.js +9 -5
- package/src/providers/CacheStorageServiceProvider.js +3 -1
- package/src/providers/EventServiceProvider.js +2 -1
- package/src/providers/LogServiceProvider.js +88 -17
- package/src/providers/MailServiceProvider.js +3 -2
- package/src/providers/ProviderRegistry.js +14 -1
- package/src/providers/QueueServiceProvider.js +3 -2
- package/src/providers/ServiceProvider.js +40 -8
- package/src/router/Router.js +121 -222
- package/src/scaffold/maker.js +24 -59
- package/src/scaffold/templates.js +21 -19
- package/src/validation/BaseValidator.js +193 -0
- package/src/validation/Validator.js +680 -0
package/src/index.js
CHANGED
|
@@ -1,94 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
const Controller = require('./controller/Controller');
|
|
5
|
-
const Middleware = require('./middleware/Middleware');
|
|
6
|
-
const MiddlewarePipeline = require('./middleware/MiddlewarePipeline');
|
|
7
|
-
const CorsMiddleware = require('./middleware/CorsMiddleware');
|
|
8
|
-
const ThrottleMiddleware = require('./middleware/ThrottleMiddleware');
|
|
9
|
-
const LogMiddleware = require('./middleware/LogMiddleware');
|
|
10
|
-
const HttpError = require('./errors/HttpError');
|
|
3
|
+
const Millas = require('./container/MillasApp');
|
|
11
4
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const ServiceProvider = require('./providers/ServiceProvider');
|
|
17
|
-
const ProviderRegistry = require('./providers/ProviderRegistry');
|
|
18
|
-
|
|
19
|
-
// ── ORM ───────────────────────────────────────────────────────────
|
|
20
|
-
const { Model, fields, QueryBuilder, DatabaseManager,
|
|
21
|
-
SchemaBuilder, MigrationRunner, ModelInspector } = require('./orm');
|
|
22
|
-
const DatabaseServiceProvider = require('./providers/DatabaseServiceProvider');
|
|
23
|
-
|
|
24
|
-
// ── Auth ──────────────────────────────────────────────────────────
|
|
25
|
-
const Auth = require('./auth/Auth');
|
|
26
|
-
const Hasher = require('./auth/Hasher');
|
|
27
|
-
const JwtDriver = require('./auth/JwtDriver');
|
|
28
|
-
const AuthMiddleware = require('./auth/AuthMiddleware');
|
|
29
|
-
const RoleMiddleware = require('./auth/RoleMiddleware');
|
|
30
|
-
const AuthController = require('./auth/AuthController');
|
|
31
|
-
const AuthServiceProvider = require('./providers/AuthServiceProvider');
|
|
32
|
-
|
|
33
|
-
// ── Mail ──────────────────────────────────────────────────────────
|
|
34
|
-
const { Mail, MailMessage, TemplateEngine,
|
|
35
|
-
SmtpDriver, SendGridDriver, MailgunDriver, LogDriver } = require('./mail');
|
|
36
|
-
const MailServiceProvider = require('./providers/MailServiceProvider');
|
|
37
|
-
|
|
38
|
-
// ── Queue ─────────────────────────────────────────────────────────
|
|
39
|
-
const Queue = require('./queue/Queue');
|
|
40
|
-
const Job = require('./queue/Job');
|
|
41
|
-
const QueueWorker = require('./queue/workers/QueueWorker');
|
|
42
|
-
const { dispatch } = require('./queue/Queue');
|
|
43
|
-
const QueueServiceProvider = require('./providers/QueueServiceProvider');
|
|
44
|
-
|
|
45
|
-
// ── Events ────────────────────────────────────────────────────────
|
|
46
|
-
const EventEmitter = require('./events/EventEmitter');
|
|
47
|
-
const Event = require('./events/Event');
|
|
48
|
-
const Listener = require('./events/Listener');
|
|
49
|
-
const { emit } = require('./events/EventEmitter');
|
|
50
|
-
const EventServiceProvider = require('./providers/EventServiceProvider');
|
|
51
|
-
|
|
52
|
-
// ── Cache ─────────────────────────────────────────────────────────
|
|
53
|
-
const Cache = require('./cache/Cache');
|
|
54
|
-
const MemoryDriver = require('./cache/drivers/MemoryDriver');
|
|
55
|
-
const FileDriver = require('./cache/drivers/FileDriver');
|
|
56
|
-
const NullDriver = require('./cache/drivers/NullDriver');
|
|
57
|
-
const { CacheServiceProvider, StorageServiceProvider } = require('./providers/CacheStorageServiceProvider');
|
|
58
|
-
|
|
59
|
-
// ── Storage ───────────────────────────────────────────────────────
|
|
60
|
-
const Storage = require('./storage/Storage');
|
|
61
|
-
const LocalDriver = require('./storage/drivers/LocalDriver');
|
|
62
|
-
|
|
63
|
-
module.exports = {
|
|
64
|
-
// HTTP
|
|
65
|
-
Controller, Middleware, MiddlewarePipeline,
|
|
66
|
-
CorsMiddleware, ThrottleMiddleware, LogMiddleware, HttpError,
|
|
67
|
-
// DI
|
|
68
|
-
Container, Application, MillasApp, ServiceProvider, ProviderRegistry,
|
|
69
|
-
// ORM
|
|
70
|
-
Model, fields, QueryBuilder, DatabaseManager, SchemaBuilder,
|
|
71
|
-
MigrationRunner, ModelInspector, DatabaseServiceProvider,
|
|
72
|
-
// Auth
|
|
73
|
-
Auth, Hasher, JwtDriver, AuthMiddleware, RoleMiddleware,
|
|
74
|
-
AuthController, AuthServiceProvider,
|
|
75
|
-
// Mail
|
|
76
|
-
Mail, MailMessage, TemplateEngine,
|
|
77
|
-
SmtpDriver, SendGridDriver, MailgunDriver, LogDriver, MailServiceProvider,
|
|
78
|
-
// Queue
|
|
79
|
-
Queue, Job, QueueWorker, dispatch, QueueServiceProvider,
|
|
80
|
-
// Events
|
|
81
|
-
EventEmitter, Event, Listener, emit, EventServiceProvider,
|
|
82
|
-
// Cache
|
|
83
|
-
Cache, MemoryDriver, FileDriver, NullDriver, CacheServiceProvider,
|
|
84
|
-
// Storage
|
|
85
|
-
Storage, LocalDriver, StorageServiceProvider,
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
// ── Admin ─────────────────────────────────────────────────────────
|
|
89
|
-
const { Admin, AdminResource, AdminField, AdminFilter } = require('./admin');
|
|
90
|
-
const AdminServiceProvider = require('./providers/AdminServiceProvider');
|
|
91
|
-
|
|
92
|
-
Object.assign(module.exports, {
|
|
93
|
-
Admin, AdminResource, AdminField, AdminFilter, AdminServiceProvider,
|
|
94
|
-
});
|
|
5
|
+
/**
|
|
6
|
+
* @module millas
|
|
7
|
+
*/
|
|
8
|
+
module.exports = { Millas };
|
|
@@ -56,19 +56,29 @@ class PrettyFormatter {
|
|
|
56
56
|
parts.push(`${b}${tagStr}${r}`);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
// Message
|
|
60
|
-
|
|
59
|
+
// Message (handle multi-line)
|
|
60
|
+
const lines = message.split('\n');
|
|
61
|
+
parts.push(`${c}${lines[0]}${r}`);
|
|
62
|
+
|
|
63
|
+
let output = parts.join(' ');
|
|
64
|
+
|
|
65
|
+
// Continuation lines (aligned with first line)
|
|
66
|
+
if (lines.length > 1) {
|
|
67
|
+
const prefix = parts.slice(0, -1).map(p => p.replace(/\x1b\[[0-9;]*m/g, '')).join(' ');
|
|
68
|
+
const indent = ' '.repeat(prefix.length + 2);
|
|
69
|
+
for (let i = 1; i < lines.length; i++) {
|
|
70
|
+
output += `\n${indent}${c}${lines[i]}${r}`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
61
73
|
|
|
62
74
|
// Context object
|
|
63
75
|
if (context !== undefined && context !== null) {
|
|
64
76
|
const ctx = typeof context === 'object'
|
|
65
77
|
? JSON.stringify(context, null, 0)
|
|
66
78
|
: String(context);
|
|
67
|
-
|
|
79
|
+
output += ` ${d}${ctx}${r}`;
|
|
68
80
|
}
|
|
69
81
|
|
|
70
|
-
let output = parts.join(' ');
|
|
71
|
-
|
|
72
82
|
// Error stack
|
|
73
83
|
if (error instanceof Error) {
|
|
74
84
|
output += `\n${d}${error.stack || error.message}${r}`;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* src/logger/internal.js
|
|
5
|
+
*
|
|
6
|
+
* MillasLog — the Millas framework's own internal logger.
|
|
7
|
+
*
|
|
8
|
+
* This is separate from the user-facing `Log` singleton. It is used by:
|
|
9
|
+
* - ORM (queries, relation warnings, migration runner)
|
|
10
|
+
* - Admin panel
|
|
11
|
+
* - Queue worker
|
|
12
|
+
* - Any other framework-internal code
|
|
13
|
+
*
|
|
14
|
+
* It always works — no provider, no config, no try/catch needed at call sites.
|
|
15
|
+
* It starts in a sensible default state (WARN+ to console) and is upgraded
|
|
16
|
+
* by LogServiceProvider once the app boots and config/logging.js is read.
|
|
17
|
+
*
|
|
18
|
+
* ── Usage inside framework code ────────────────────────────────────────────
|
|
19
|
+
*
|
|
20
|
+
* const MillasLog = require('../logger/internal');
|
|
21
|
+
*
|
|
22
|
+
* MillasLog.d('ORM', 'Running query', { table: 'users' });
|
|
23
|
+
* MillasLog.w('ORM', 'Relation not defined', { model: 'Post', name: 'tags' });
|
|
24
|
+
* MillasLog.e('Migration', 'Failed to run migration', error);
|
|
25
|
+
*
|
|
26
|
+
* ── Configuring from config/logging.js ─────────────────────────────────────
|
|
27
|
+
*
|
|
28
|
+
* module.exports = {
|
|
29
|
+
* // ... app channels ...
|
|
30
|
+
*
|
|
31
|
+
* internal: {
|
|
32
|
+
* level: 'debug', // show all ORM/framework logs (default: 'warn')
|
|
33
|
+
* format: 'pretty', // pretty | simple | json
|
|
34
|
+
* },
|
|
35
|
+
* };
|
|
36
|
+
*
|
|
37
|
+
* ── Disabling internal logs entirely ───────────────────────────────────────
|
|
38
|
+
*
|
|
39
|
+
* internal: false
|
|
40
|
+
*
|
|
41
|
+
* ── Writing to a separate file ─────────────────────────────────────────────
|
|
42
|
+
*
|
|
43
|
+
* internal: {
|
|
44
|
+
* level: 'debug',
|
|
45
|
+
* channels: [
|
|
46
|
+
* { driver: 'console', format: 'pretty', level: 'warn' },
|
|
47
|
+
* { driver: 'file', format: 'simple', path: 'storage/logs', prefix: 'millas-internal', level: 'debug' },
|
|
48
|
+
* ],
|
|
49
|
+
* },
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
const Logger = require('./Logger');
|
|
53
|
+
const { LEVELS } = require('./levels');
|
|
54
|
+
const ConsoleChannel = require('./channels/ConsoleChannel');
|
|
55
|
+
const PrettyFormatter = require('./formatters/PrettyFormatter');
|
|
56
|
+
const { NullChannel } = require('./channels/index');
|
|
57
|
+
|
|
58
|
+
// ── Create the MillasLog singleton ───────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
const MillasLog = new Logger();
|
|
61
|
+
|
|
62
|
+
// Default: WARN and above, pretty-formatted to console.
|
|
63
|
+
// This means normal app runs are quiet — you only see warnings and errors
|
|
64
|
+
// from the framework itself unless you opt in to lower levels.
|
|
65
|
+
MillasLog.configure({
|
|
66
|
+
defaultTag: 'Millas',
|
|
67
|
+
minLevel: LEVELS.VERBOSE,
|
|
68
|
+
channel: new ConsoleChannel({
|
|
69
|
+
formatter: new PrettyFormatter({
|
|
70
|
+
colour: process.stdout.isTTY !== false,
|
|
71
|
+
}),
|
|
72
|
+
minLevel: LEVELS.VERBOSE,
|
|
73
|
+
}),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
module.exports = MillasLog;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {LEVELS} = require('./levels');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* patchConsole(Log, defaultTag)
|
|
7
|
+
*
|
|
8
|
+
* Replaces the global console.* methods so that all console output
|
|
9
|
+
* in the app goes through the configured Log channels — same formatting,
|
|
10
|
+
* same level filtering, same file output.
|
|
11
|
+
*
|
|
12
|
+
* Safe: ConsoleChannel writes via process.stdout.write / process.stderr.write,
|
|
13
|
+
* never via console.*, so there is no recursion risk.
|
|
14
|
+
*
|
|
15
|
+
* Called once from LogServiceProvider.boot() when interceptConsole is enabled.
|
|
16
|
+
* Returns a restore() function that puts the originals back.
|
|
17
|
+
*
|
|
18
|
+
* Level mapping:
|
|
19
|
+
* console.log → Log.i (INFO)
|
|
20
|
+
* console.info → Log.i (INFO)
|
|
21
|
+
* console.warn → Log.w (WARN)
|
|
22
|
+
* console.error → Log.e (ERROR)
|
|
23
|
+
* console.debug → Log.d (DEBUG)
|
|
24
|
+
* console.trace → Log.v (VERBOSE)
|
|
25
|
+
* console.dir → Log.d (DEBUG)
|
|
26
|
+
*/
|
|
27
|
+
function patchConsole(Log, defaultTag = 'App') {
|
|
28
|
+
|
|
29
|
+
// Save originals — restore() puts these back
|
|
30
|
+
const originals = {
|
|
31
|
+
log: console.log.bind(console),
|
|
32
|
+
info: console.info.bind(console),
|
|
33
|
+
warn: console.warn.bind(console),
|
|
34
|
+
error: console.error.bind(console),
|
|
35
|
+
debug: console.debug.bind(console),
|
|
36
|
+
trace: console.trace.bind(console),
|
|
37
|
+
dir: console.dir.bind(console),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Build a dispatcher for a given level
|
|
41
|
+
function make(level) {
|
|
42
|
+
return function (...args) {
|
|
43
|
+
const {message, context, error} = parse(args);
|
|
44
|
+
Log._emit({
|
|
45
|
+
level,
|
|
46
|
+
tag: defaultTag,
|
|
47
|
+
message: message || '',
|
|
48
|
+
context,
|
|
49
|
+
error,
|
|
50
|
+
timestamp: new Date().toISOString(),
|
|
51
|
+
pid: process.pid,
|
|
52
|
+
});
|
|
53
|
+
return true
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
console.log = make(LEVELS.INFO);
|
|
58
|
+
console.info = make(LEVELS.INFO);
|
|
59
|
+
console.warn = make(LEVELS.WARN);
|
|
60
|
+
console.error = make(LEVELS.ERROR);
|
|
61
|
+
console.debug = make(LEVELS.DEBUG);
|
|
62
|
+
console.trace = make(LEVELS.VERBOSE);
|
|
63
|
+
console.dir = (obj) => Log._emit({
|
|
64
|
+
level: LEVELS.DEBUG,
|
|
65
|
+
tag: defaultTag,
|
|
66
|
+
message: '',
|
|
67
|
+
context: obj,
|
|
68
|
+
error: undefined,
|
|
69
|
+
timestamp: new Date().toISOString(),
|
|
70
|
+
pid: process.pid
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return function restore() {
|
|
74
|
+
Object.assign(console, originals);
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ── Argument parser ───────────────────────────────────────────────────────────
|
|
79
|
+
//
|
|
80
|
+
// Handles the main shapes people pass to console.*:
|
|
81
|
+
//
|
|
82
|
+
// console.log('message')
|
|
83
|
+
// console.log('message', { ctx })
|
|
84
|
+
// console.log('message', error)
|
|
85
|
+
// console.error(error)
|
|
86
|
+
// console.log({ obj })
|
|
87
|
+
// console.log('x:', 42) → message: 'x: 42'
|
|
88
|
+
// console.log('a', 'b', 'c') → message: 'a b c'
|
|
89
|
+
|
|
90
|
+
function parse(args) {
|
|
91
|
+
if (args.length === 0) {
|
|
92
|
+
return {message: '', context: undefined, error: undefined};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (args.length === 1) {
|
|
96
|
+
const a = args[0];
|
|
97
|
+
if (a instanceof Error) return {message: a.message, context: undefined, error: a};
|
|
98
|
+
if (typeof a === 'object' && a !== null) return {message: '', context: a, error: undefined};
|
|
99
|
+
return {message: String(a), context: undefined, error: undefined};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const [first, ...rest] = args;
|
|
103
|
+
|
|
104
|
+
// First arg is an Error
|
|
105
|
+
if (first instanceof Error) {
|
|
106
|
+
return {message: first.message, context: rest.length ? rest : undefined, error: first};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// First arg is a string message
|
|
110
|
+
if (typeof first === 'string') {
|
|
111
|
+
// Single extra arg
|
|
112
|
+
if (rest.length === 1) {
|
|
113
|
+
const r = rest[0];
|
|
114
|
+
if (r instanceof Error) return {message: first, context: undefined, error: r};
|
|
115
|
+
if (typeof r === 'object' && r !== null) return {message: first, context: r, error: undefined};
|
|
116
|
+
// Scalar extra: append to message (console.log('count:', 42))
|
|
117
|
+
return {message: first + ' ' + String(r), context: undefined, error: undefined};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Multiple extra args — find a trailing Error, collect the rest as context
|
|
121
|
+
const lastArg = rest[rest.length - 1];
|
|
122
|
+
if (lastArg instanceof Error) {
|
|
123
|
+
const ctx = rest.slice(0, -1);
|
|
124
|
+
return {message: first, context: ctx.length ? ctx : undefined, error: lastArg};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// All strings/scalars — join into message
|
|
128
|
+
if (rest.every(r => typeof r !== 'object' || r === null)) {
|
|
129
|
+
return {message: [first, ...rest].map(String).join(' '), context: undefined, error: undefined};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Mixed — put extras in context
|
|
133
|
+
return {message: first, context: rest, error: undefined};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// First arg is an object
|
|
137
|
+
if (typeof first === 'object' && first !== null) {
|
|
138
|
+
return {message: '', context: first, error: undefined};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Fallback — join everything as a string
|
|
142
|
+
return {message: args.map(String).join(' '), context: undefined, error: undefined};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
module.exports = patchConsole;
|
|
@@ -1,58 +1,50 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const Middleware
|
|
3
|
+
const Middleware = require('./Middleware');
|
|
4
|
+
const MillasResponse = require('../http/MillasResponse');
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* CorsMiddleware
|
|
7
8
|
*
|
|
8
|
-
* Adds
|
|
9
|
-
*
|
|
10
|
-
* Register:
|
|
11
|
-
* middlewareRegistry.register('cors', CorsMiddleware);
|
|
12
|
-
*
|
|
13
|
-
* Configure in bootstrap/app.js:
|
|
14
|
-
* new CorsMiddleware({
|
|
15
|
-
* origins: ['https://myapp.com'],
|
|
16
|
-
* methods: ['GET', 'POST'],
|
|
17
|
-
* })
|
|
18
|
-
*
|
|
19
|
-
* Or register the class directly for default permissive settings.
|
|
9
|
+
* Adds CORS headers. Uses the new ctx signature.
|
|
20
10
|
*/
|
|
21
11
|
class CorsMiddleware extends Middleware {
|
|
22
12
|
constructor(options = {}) {
|
|
23
13
|
super();
|
|
24
14
|
this.origins = options.origins || ['*'];
|
|
25
|
-
this.methods = options.methods || ['GET',
|
|
26
|
-
this.headers = options.headers || ['Content-Type',
|
|
15
|
+
this.methods = options.methods || ['GET','POST','PUT','PATCH','DELETE','OPTIONS'];
|
|
16
|
+
this.headers = options.headers || ['Content-Type','Authorization','X-Requested-With'];
|
|
27
17
|
this.credentials = options.credentials ?? false;
|
|
28
18
|
this.maxAge = options.maxAge || 86400;
|
|
29
19
|
}
|
|
30
20
|
|
|
31
|
-
async handle(req
|
|
32
|
-
const origin = req.
|
|
21
|
+
async handle({ req }, next) {
|
|
22
|
+
const origin = req.header('origin');
|
|
33
23
|
|
|
34
|
-
//
|
|
24
|
+
// Build headers map
|
|
25
|
+
const h = {};
|
|
35
26
|
if (this.origins.includes('*')) {
|
|
36
|
-
|
|
27
|
+
h['Access-Control-Allow-Origin'] = '*';
|
|
37
28
|
} else if (origin && this.origins.includes(origin)) {
|
|
38
|
-
|
|
39
|
-
|
|
29
|
+
h['Access-Control-Allow-Origin'] = origin;
|
|
30
|
+
h['Vary'] = 'Origin';
|
|
40
31
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
res.setHeader('Access-Control-Max-Age', String(this.maxAge));
|
|
45
|
-
|
|
32
|
+
h['Access-Control-Allow-Methods'] = this.methods.join(', ');
|
|
33
|
+
h['Access-Control-Allow-Headers'] = this.headers.join(', ');
|
|
34
|
+
h['Access-Control-Max-Age'] = String(this.maxAge);
|
|
46
35
|
if (this.credentials) {
|
|
47
|
-
|
|
36
|
+
h['Access-Control-Allow-Credentials'] = 'true';
|
|
48
37
|
}
|
|
49
38
|
|
|
50
|
-
//
|
|
39
|
+
// Preflight — short-circuit with 204
|
|
51
40
|
if (req.method === 'OPTIONS') {
|
|
52
|
-
return
|
|
41
|
+
return MillasResponse.empty(204).withHeaders(h);
|
|
53
42
|
}
|
|
54
43
|
|
|
55
|
-
|
|
44
|
+
// Proceed — but we still need headers on the eventual response.
|
|
45
|
+
// Store on raw req so ResponseDispatcher can pick them up.
|
|
46
|
+
req.raw._corsHeaders = h;
|
|
47
|
+
return next();
|
|
56
48
|
}
|
|
57
49
|
}
|
|
58
50
|
|
|
@@ -5,31 +5,8 @@ const Middleware = require('./Middleware');
|
|
|
5
5
|
/**
|
|
6
6
|
* LogMiddleware
|
|
7
7
|
*
|
|
8
|
-
* Django-style HTTP request
|
|
9
|
-
* Uses the
|
|
10
|
-
*
|
|
11
|
-
* Log levels per status:
|
|
12
|
-
* 2xx, 3xx → INFO (green)
|
|
13
|
-
* 4xx → WARN (yellow)
|
|
14
|
-
* 5xx → ERROR (red)
|
|
15
|
-
* slow req → WARN (with slow=true in context)
|
|
16
|
-
*
|
|
17
|
-
* Output:
|
|
18
|
-
* I HTTP GET /api/users 200 4ms
|
|
19
|
-
* W HTTP POST /api/login 401 12ms
|
|
20
|
-
* E HTTP GET /api/crash 500 3ms
|
|
21
|
-
*
|
|
22
|
-
* Usage (register as middleware):
|
|
23
|
-
* app.use(new LogMiddleware().handle.bind(new LogMiddleware()));
|
|
24
|
-
*
|
|
25
|
-
* Or use Log.requestMiddleware() directly for more options.
|
|
26
|
-
*
|
|
27
|
-
* Options:
|
|
28
|
-
* silent — suppress all output (default: false)
|
|
29
|
-
* includeQuery — include query string in URL (default: false)
|
|
30
|
-
* includeIp — include client IP (default: true)
|
|
31
|
-
* slowThreshold — warn if response > Nms (default: 1000)
|
|
32
|
-
* skip — function(req, res) => bool — skip matching routes
|
|
8
|
+
* Django-style HTTP request logging via MillasLog.
|
|
9
|
+
* Uses the new ctx signature: handle({ req }, next)
|
|
33
10
|
*/
|
|
34
11
|
class LogMiddleware extends Middleware {
|
|
35
12
|
constructor(options = {}) {
|
|
@@ -41,46 +18,37 @@ class LogMiddleware extends Middleware {
|
|
|
41
18
|
this.skip = options.skip ?? null;
|
|
42
19
|
}
|
|
43
20
|
|
|
44
|
-
async handle(req
|
|
21
|
+
async handle({ req }, next) {
|
|
45
22
|
if (this.silent) return next();
|
|
46
|
-
if (typeof this.skip === 'function' && this.skip(req
|
|
47
|
-
|
|
48
|
-
// Lazy-require Log so this file loads even before LogServiceProvider runs
|
|
49
|
-
let Log;
|
|
50
|
-
try {
|
|
51
|
-
Log = require('../logger/index').Log;
|
|
52
|
-
} catch {
|
|
53
|
-
return next();
|
|
54
|
-
}
|
|
23
|
+
if (typeof this.skip === 'function' && this.skip(req)) return next();
|
|
55
24
|
|
|
56
25
|
const start = Date.now();
|
|
57
|
-
const { LEVELS } = require('../logger/levels');
|
|
58
|
-
|
|
59
|
-
res.on('finish', () => {
|
|
60
|
-
const ms = Date.now() - start;
|
|
61
|
-
const status = res.statusCode;
|
|
62
|
-
const method = req.method;
|
|
63
|
-
let url = req.path || req.url || '/';
|
|
64
|
-
|
|
65
|
-
if (this.includeQuery && req.url && req.url.includes('?')) {
|
|
66
|
-
url = req.url;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Level based on status + response time
|
|
70
|
-
let level;
|
|
71
|
-
if (status >= 500) level = LEVELS.ERROR;
|
|
72
|
-
else if (status >= 400) level = LEVELS.WARN;
|
|
73
|
-
else if (ms > this.slowThreshold) level = LEVELS.WARN;
|
|
74
|
-
else level = LEVELS.INFO;
|
|
75
|
-
|
|
76
|
-
const ctx = { status, ms };
|
|
77
|
-
if (this.includeIp) ctx.ip = req.ip || req.connection?.remoteAddress;
|
|
78
|
-
if (ms > this.slowThreshold) ctx.slow = true;
|
|
79
26
|
|
|
80
|
-
|
|
27
|
+
// We can't hook into the response here since next() returns undefined.
|
|
28
|
+
// Instead attach a finish listener to the raw Express res.
|
|
29
|
+
req.raw.res.on('finish', () => {
|
|
30
|
+
try {
|
|
31
|
+
const MillasLog = require('../logger/internal');
|
|
32
|
+
const { LEVELS } = require('../logger/levels');
|
|
33
|
+
const ms = Date.now() - start;
|
|
34
|
+
const status = req.raw.res.statusCode;
|
|
35
|
+
let url = req.path;
|
|
36
|
+
if (this.includeQuery && req.raw.url?.includes('?')) url = req.raw.url;
|
|
37
|
+
|
|
38
|
+
let level = status >= 500 ? LEVELS.ERROR
|
|
39
|
+
: status >= 400 ? LEVELS.WARN
|
|
40
|
+
: ms > this.slowThreshold ? LEVELS.WARN
|
|
41
|
+
: LEVELS.INFO;
|
|
42
|
+
|
|
43
|
+
const ctx = { status, ms };
|
|
44
|
+
if (this.includeIp) ctx.ip = req.ip;
|
|
45
|
+
if (ms > this.slowThreshold) ctx.slow = true;
|
|
46
|
+
|
|
47
|
+
MillasLog._log(level, 'HTTP', `${req.method} ${url} ${status} ${ms}ms`, ctx);
|
|
48
|
+
} catch {}
|
|
81
49
|
});
|
|
82
50
|
|
|
83
|
-
next();
|
|
51
|
+
return next();
|
|
84
52
|
}
|
|
85
53
|
}
|
|
86
54
|
|
|
@@ -4,33 +4,42 @@
|
|
|
4
4
|
* Middleware
|
|
5
5
|
*
|
|
6
6
|
* Base class for all Millas middleware.
|
|
7
|
-
* Subclasses must implement handle(req, res, next).
|
|
8
7
|
*
|
|
9
|
-
*
|
|
8
|
+
* Middleware receives a RequestContext and a next() function.
|
|
9
|
+
* Destructure exactly what you need from the context — same as route handlers.
|
|
10
|
+
*
|
|
10
11
|
* class AuthMiddleware extends Middleware {
|
|
11
|
-
* async handle(
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* async handle({ headers, user }, next) {
|
|
13
|
+
* if (!headers.authorization) {
|
|
14
|
+
* return jsonify({ error: 'Unauthorized' }, { status: 401 });
|
|
15
|
+
* }
|
|
16
|
+
* return next();
|
|
17
|
+
* }
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* class LogMiddleware extends Middleware {
|
|
21
|
+
* async handle({ req }, next) {
|
|
22
|
+
* Log.i('HTTP', `${req.method} ${req.path}`);
|
|
23
|
+
* return next();
|
|
14
24
|
* }
|
|
15
25
|
* }
|
|
16
26
|
*/
|
|
17
27
|
class Middleware {
|
|
18
28
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* @
|
|
22
|
-
* @param {import('express').Response} res
|
|
23
|
-
* @param {Function} next
|
|
29
|
+
* @param {import('../http/RequestContext')} ctx
|
|
30
|
+
* @param {Function} next
|
|
31
|
+
* @returns {import('../http/MillasResponse')|Promise<import('../http/MillasResponse')>}
|
|
24
32
|
*/
|
|
25
|
-
async handle(
|
|
26
|
-
throw new Error(`${this.constructor.name} must implement handle(
|
|
33
|
+
async handle(ctx, next) {
|
|
34
|
+
throw new Error(`${this.constructor.name} must implement handle(ctx, next).`);
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
/**
|
|
30
|
-
* Called after the response is
|
|
31
|
-
*
|
|
38
|
+
* Called after the response is dispatched.
|
|
39
|
+
* @param {import('../http/RequestContext')} ctx
|
|
40
|
+
* @param {import('../http/MillasResponse')} response
|
|
32
41
|
*/
|
|
33
|
-
async terminate(
|
|
42
|
+
async terminate(ctx, response) {}
|
|
34
43
|
}
|
|
35
44
|
|
|
36
45
|
module.exports = Middleware;
|