millas 0.2.12-beta → 0.2.12-beta-2
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 +3 -16
- package/src/admin/ActivityLog.js +153 -52
- package/src/admin/Admin.js +400 -167
- package/src/admin/AdminAuth.js +213 -98
- package/src/admin/FormGenerator.js +372 -0
- package/src/admin/HookRegistry.js +256 -0
- package/src/admin/QueryEngine.js +263 -0
- package/src/admin/ViewContext.js +309 -0
- package/src/admin/WidgetRegistry.js +406 -0
- package/src/admin/index.js +17 -0
- package/src/admin/resources/AdminResource.js +383 -97
- package/src/admin/static/admin.css +1341 -0
- package/src/admin/static/date-picker.css +157 -0
- package/src/admin/static/date-picker.js +316 -0
- package/src/admin/static/json-editor.css +649 -0
- package/src/admin/static/json-editor.js +1429 -0
- package/src/admin/static/ui.js +1044 -0
- package/src/admin/views/layouts/base.njk +65 -1013
- package/src/admin/views/pages/detail.njk +40 -16
- package/src/admin/views/pages/form.njk +47 -599
- package/src/admin/views/pages/list.njk +145 -62
- package/src/admin/views/partials/form-field.njk +53 -0
- package/src/admin/views/partials/form-footer.njk +28 -0
- package/src/admin/views/partials/form-readonly.njk +114 -0
- package/src/admin/views/partials/form-scripts.njk +476 -0
- package/src/admin/views/partials/form-widget.njk +296 -0
- package/src/admin/views/partials/json-dialog.njk +80 -0
- package/src/admin/views/partials/json-editor.njk +37 -0
- package/src/admin.zip +0 -0
- package/src/auth/Auth.js +31 -10
- package/src/auth/AuthController.js +3 -1
- package/src/auth/AuthUser.js +119 -0
- package/src/cli.js +4 -2
- package/src/commands/createsuperuser.js +254 -0
- package/src/commands/lang.js +589 -0
- package/src/commands/migrate.js +154 -81
- package/src/commands/serve.js +82 -110
- package/src/container/AppInitializer.js +215 -0
- package/src/container/Application.js +278 -253
- package/src/container/HttpServer.js +156 -0
- package/src/container/MillasApp.js +29 -279
- package/src/container/MillasConfig.js +192 -0
- package/src/core/admin.js +5 -0
- package/src/core/auth.js +9 -0
- package/src/core/db.js +9 -0
- package/src/core/foundation.js +59 -0
- package/src/core/http.js +11 -0
- package/src/core/lang.js +1 -0
- package/src/core/mail.js +6 -0
- package/src/core/queue.js +7 -0
- package/src/core/validation.js +29 -0
- package/src/facades/Admin.js +1 -1
- package/src/facades/Auth.js +22 -39
- package/src/facades/Cache.js +21 -10
- package/src/facades/Database.js +1 -1
- package/src/facades/Events.js +18 -17
- package/src/facades/Facade.js +197 -0
- package/src/facades/Http.js +42 -45
- package/src/facades/Log.js +25 -49
- package/src/facades/Mail.js +27 -32
- package/src/facades/Queue.js +22 -15
- package/src/facades/Storage.js +18 -10
- package/src/facades/Url.js +53 -0
- package/src/http/HttpClient.js +673 -0
- package/src/http/ResponseDispatcher.js +18 -111
- 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/i18n/I18nServiceProvider.js +91 -0
- package/src/i18n/Translator.js +635 -0
- package/src/i18n/defaults.js +122 -0
- package/src/i18n/index.js +164 -0
- package/src/i18n/locales/en.js +55 -0
- package/src/i18n/locales/sw.js +48 -0
- package/src/index.js +5 -144
- package/src/logger/formatters/PrettyFormatter.js +103 -57
- package/src/logger/internal.js +2 -2
- package/src/logger/patchConsole.js +91 -81
- package/src/middleware/MiddlewareRegistry.js +62 -82
- package/src/migrations/system/0001_users.js +21 -0
- package/src/migrations/system/0002_admin_log.js +25 -0
- package/src/migrations/system/0003_sessions.js +23 -0
- package/src/orm/fields/index.js +210 -188
- package/src/orm/migration/DefaultValueParser.js +325 -0
- package/src/orm/migration/InteractiveResolver.js +191 -0
- package/src/orm/migration/Makemigrations.js +312 -0
- package/src/orm/migration/MigrationGraph.js +227 -0
- package/src/orm/migration/MigrationRunner.js +202 -108
- package/src/orm/migration/MigrationWriter.js +463 -0
- package/src/orm/migration/ModelInspector.js +412 -344
- package/src/orm/migration/ModelScanner.js +225 -0
- package/src/orm/migration/ProjectState.js +213 -0
- package/src/orm/migration/RenameDetector.js +175 -0
- package/src/orm/migration/SchemaBuilder.js +8 -81
- package/src/orm/migration/operations/base.js +57 -0
- package/src/orm/migration/operations/column.js +191 -0
- package/src/orm/migration/operations/fields.js +252 -0
- package/src/orm/migration/operations/index.js +55 -0
- package/src/orm/migration/operations/models.js +152 -0
- package/src/orm/migration/operations/registry.js +131 -0
- package/src/orm/migration/operations/special.js +51 -0
- package/src/orm/migration/utils.js +208 -0
- package/src/orm/model/Model.js +81 -13
- package/src/providers/AdminServiceProvider.js +66 -9
- package/src/providers/AuthServiceProvider.js +46 -7
- package/src/providers/CacheStorageServiceProvider.js +5 -3
- package/src/providers/DatabaseServiceProvider.js +3 -2
- package/src/providers/EventServiceProvider.js +2 -1
- package/src/providers/LogServiceProvider.js +7 -3
- package/src/providers/MailServiceProvider.js +4 -3
- package/src/providers/QueueServiceProvider.js +4 -3
- package/src/router/Router.js +119 -152
- package/src/scaffold/templates.js +83 -26
- package/src/facades/Validation.js +0 -69
|
@@ -1,88 +1,134 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
3
|
+
const { LEVEL_TAGS, LEVEL_COLOURS, RESET, BOLD } = require('../levels');
|
|
4
|
+
|
|
5
|
+
const SEP = ' ';
|
|
6
|
+
const TAG_WIDTH = 18;
|
|
7
|
+
|
|
8
|
+
// Matches /absolute/path/to/file.js or /absolute/path/to/file.js:12:34
|
|
9
|
+
const FILE_PATH_RE = /(\/[^\s)'"]+\.(js|ts|json|mjs|cjs)(?::\d+(?::\d+)?)?)/g;
|
|
10
|
+
// Matches http(s):// URLs
|
|
11
|
+
const URL_RE = /(https?:\/\/[^\s)'"]+)/g;
|
|
12
|
+
|
|
13
|
+
function linkify(text, dim, useColour) {
|
|
14
|
+
if (!useColour) return text;
|
|
15
|
+
|
|
16
|
+
text = text.replace(FILE_PATH_RE, (match) => {
|
|
17
|
+
const filePath = match.replace(/(:\d+)+$/, '');
|
|
18
|
+
const uri = `file://${filePath}`;
|
|
19
|
+
return `${RESET}\x1b]8;;${uri}\x1b\\${match}\x1b]8;;\x1b\\`;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
text = text.replace(URL_RE, (match) => {
|
|
23
|
+
return `${RESET}\x1b]8;;${match}\x1b\\${match}\x1b]8;;\x1b\\`;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return text;
|
|
27
|
+
}
|
|
28
|
+
|
|
18
29
|
class PrettyFormatter {
|
|
19
|
-
/**
|
|
20
|
-
* @param {object} options
|
|
21
|
-
* @param {boolean} [options.timestamp=true] — show timestamp
|
|
22
|
-
* @param {boolean} [options.tag=true] — show tag/component name
|
|
23
|
-
* @param {boolean} [options.colour=true] — ANSI colour (disable for pipes/files)
|
|
24
|
-
* @param {string} [options.timestampFormat] — 'iso' | 'short' (default: 'short')
|
|
25
|
-
*/
|
|
26
30
|
constructor(options = {}) {
|
|
27
31
|
this.showTimestamp = options.timestamp !== false;
|
|
28
32
|
this.showTag = options.tag !== false;
|
|
29
33
|
this.colour = options.colour !== false;
|
|
30
34
|
this.tsFormat = options.timestampFormat || 'short';
|
|
35
|
+
this.tagWidth = options.tagWidth || TAG_WIDTH;
|
|
31
36
|
}
|
|
32
37
|
|
|
33
38
|
format(entry) {
|
|
34
39
|
const { level, tag, message, context, error } = entry;
|
|
35
40
|
|
|
36
|
-
const c
|
|
37
|
-
const r
|
|
38
|
-
const b
|
|
39
|
-
const d
|
|
40
|
-
const lvl
|
|
41
|
+
const c = this.colour ? (LEVEL_COLOURS[level] || '') : '';
|
|
42
|
+
const r = this.colour ? RESET : '';
|
|
43
|
+
const b = this.colour ? BOLD : '';
|
|
44
|
+
const d = this.colour ? '\x1b[2m' : '';
|
|
45
|
+
const lvl = LEVEL_TAGS[level] || '?';
|
|
41
46
|
|
|
42
|
-
|
|
47
|
+
// ── 1. Measure plain prefix once ─────────────────────────────────────────
|
|
48
|
+
const ts = this._timestamp();
|
|
49
|
+
const plainCols = [];
|
|
50
|
+
if (this.showTimestamp) plainCols.push(`[${ts}]`);
|
|
51
|
+
plainCols.push(lvl);
|
|
52
|
+
if (this.showTag && tag) plainCols.push(tag.padEnd(this.tagWidth));
|
|
43
53
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const ts = this._timestamp();
|
|
47
|
-
parts.push(`${d}[${ts}]${r}`);
|
|
48
|
-
}
|
|
54
|
+
const indentWidth = plainCols.join(SEP).length + SEP.length;
|
|
55
|
+
const indent = ' '.repeat(indentWidth);
|
|
49
56
|
|
|
50
|
-
//
|
|
51
|
-
|
|
57
|
+
// ── 2. Coloured prefix ───────────────────────────────────────────────────
|
|
58
|
+
const colCols = [];
|
|
59
|
+
if (this.showTimestamp) colCols.push(`${d}[${ts}]${r}`);
|
|
60
|
+
colCols.push(`${c}${b}${lvl}${r}`);
|
|
61
|
+
if (this.showTag && tag) colCols.push(`${b}${tag.padEnd(this.tagWidth)}${r}`);
|
|
52
62
|
|
|
53
|
-
|
|
54
|
-
if (this.showTag && tag) {
|
|
55
|
-
const tagStr = tag.padEnd(18);
|
|
56
|
-
parts.push(`${b}${tagStr}${r}`);
|
|
57
|
-
}
|
|
63
|
+
const prefix = colCols.join(SEP) + SEP;
|
|
58
64
|
|
|
59
|
-
//
|
|
60
|
-
|
|
65
|
+
// ── 3. Terminal width ────────────────────────────────────────────────────
|
|
66
|
+
const termWidth = (process.stdout.columns || 120);
|
|
67
|
+
const msgWidth = termWidth - indentWidth;
|
|
61
68
|
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
: String(context);
|
|
67
|
-
parts.push(`${d}${ctx}${r}`);
|
|
68
|
-
}
|
|
69
|
+
// ── 4. Collect all logical lines ─────────────────────────────────────────
|
|
70
|
+
const logicalLines = [];
|
|
71
|
+
|
|
72
|
+
for (const l of String(message).split('\n')) logicalLines.push({ text: l, dim: false });
|
|
69
73
|
|
|
70
|
-
|
|
74
|
+
if (context != null) {
|
|
75
|
+
const ctx = typeof context === 'object' ? JSON.stringify(context) : String(context);
|
|
76
|
+
logicalLines.push({ text: ctx, dim: true });
|
|
77
|
+
}
|
|
71
78
|
|
|
72
|
-
// Error stack
|
|
73
79
|
if (error instanceof Error) {
|
|
74
|
-
|
|
80
|
+
for (const l of (error.stack || error.message).split('\n'))
|
|
81
|
+
logicalLines.push({ text: l, dim: true });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ── 5. Hard-wrap (skip stack frames so paths stay intact) ────────────────
|
|
85
|
+
const wrappedLines = [];
|
|
86
|
+
|
|
87
|
+
for (const { text, dim } of logicalLines) {
|
|
88
|
+
const isStackFrame = dim && /^\s*at /.test(text);
|
|
89
|
+
|
|
90
|
+
if (isStackFrame || text.length <= msgWidth) {
|
|
91
|
+
wrappedLines.push({ text, dim });
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const words = text.split(' ');
|
|
96
|
+
let chunk = '';
|
|
97
|
+
for (const word of words) {
|
|
98
|
+
if (chunk.length === 0) {
|
|
99
|
+
if (word.length > msgWidth) {
|
|
100
|
+
for (let i = 0; i < word.length; i += msgWidth)
|
|
101
|
+
wrappedLines.push({ text: word.slice(i, i + msgWidth), dim });
|
|
102
|
+
} else {
|
|
103
|
+
chunk = word;
|
|
104
|
+
}
|
|
105
|
+
} else if (chunk.length + 1 + word.length <= msgWidth) {
|
|
106
|
+
chunk += ' ' + word;
|
|
107
|
+
} else {
|
|
108
|
+
wrappedLines.push({ text: chunk, dim });
|
|
109
|
+
chunk = word;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (chunk.length) wrappedLines.push({ text: chunk, dim });
|
|
75
113
|
}
|
|
76
114
|
|
|
77
|
-
//
|
|
115
|
+
// ── 6. Render + linkify ───────────────────────────────────────────────────
|
|
116
|
+
const rendered = wrappedLines.map((line, i) => {
|
|
117
|
+
const col = line.dim ? d : c;
|
|
118
|
+
const text = linkify(line.text, line.dim, this.colour);
|
|
119
|
+
if (i === 0) return `${prefix}${col}${text}${r}`;
|
|
120
|
+
return `${indent}${col}${text}${r}`;
|
|
121
|
+
}).join('\n');
|
|
122
|
+
|
|
123
|
+
// ── 7. WTF banner ─────────────────────────────────────────────────────────
|
|
78
124
|
if (level === 5) {
|
|
79
|
-
const
|
|
125
|
+
const bar = this.colour
|
|
80
126
|
? `\x1b[35m\x1b[1m${'━'.repeat(60)}\x1b[0m`
|
|
81
127
|
: '━'.repeat(60);
|
|
82
|
-
|
|
128
|
+
return `${bar}\n${rendered}\n${bar}`;
|
|
83
129
|
}
|
|
84
130
|
|
|
85
|
-
return
|
|
131
|
+
return rendered;
|
|
86
132
|
}
|
|
87
133
|
|
|
88
134
|
_timestamp() {
|
|
@@ -92,4 +138,4 @@ class PrettyFormatter {
|
|
|
92
138
|
}
|
|
93
139
|
}
|
|
94
140
|
|
|
95
|
-
module.exports = PrettyFormatter;
|
|
141
|
+
module.exports = PrettyFormatter;
|
package/src/logger/internal.js
CHANGED
|
@@ -64,12 +64,12 @@ const MillasLog = new Logger();
|
|
|
64
64
|
// from the framework itself unless you opt in to lower levels.
|
|
65
65
|
MillasLog.configure({
|
|
66
66
|
defaultTag: 'Millas',
|
|
67
|
-
minLevel: LEVELS.
|
|
67
|
+
minLevel: LEVELS.VERBOSE,
|
|
68
68
|
channel: new ConsoleChannel({
|
|
69
69
|
formatter: new PrettyFormatter({
|
|
70
70
|
colour: process.stdout.isTTY !== false,
|
|
71
71
|
}),
|
|
72
|
-
minLevel: LEVELS.
|
|
72
|
+
minLevel: LEVELS.VERBOSE,
|
|
73
73
|
}),
|
|
74
74
|
});
|
|
75
75
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const {LEVELS} = require('./levels');
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* patchConsole(Log, defaultTag)
|
|
@@ -25,44 +25,54 @@ const { LEVELS } = require('./levels');
|
|
|
25
25
|
* console.dir → Log.d (DEBUG)
|
|
26
26
|
*/
|
|
27
27
|
function patchConsole(Log, defaultTag = 'App') {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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,
|
|
49
69
|
timestamp: new Date().toISOString(),
|
|
50
|
-
pid:
|
|
51
|
-
|
|
70
|
+
pid: process.pid
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return function restore() {
|
|
74
|
+
Object.assign(console, originals);
|
|
52
75
|
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
console.log = make(LEVELS.INFO);
|
|
56
|
-
console.info = make(LEVELS.INFO);
|
|
57
|
-
console.warn = make(LEVELS.WARN);
|
|
58
|
-
console.error = make(LEVELS.ERROR);
|
|
59
|
-
console.debug = make(LEVELS.DEBUG);
|
|
60
|
-
console.trace = make(LEVELS.VERBOSE);
|
|
61
|
-
console.dir = (obj) => Log._emit({ level: LEVELS.DEBUG, tag: defaultTag, message: '', context: obj, error: undefined, timestamp: new Date().toISOString(), pid: process.pid });
|
|
62
|
-
|
|
63
|
-
return function restore() {
|
|
64
|
-
Object.assign(console, originals);
|
|
65
|
-
};
|
|
66
76
|
}
|
|
67
77
|
|
|
68
78
|
// ── Argument parser ───────────────────────────────────────────────────────────
|
|
@@ -78,58 +88,58 @@ function patchConsole(Log, defaultTag = 'App') {
|
|
|
78
88
|
// console.log('a', 'b', 'c') → message: 'a b c'
|
|
79
89
|
|
|
80
90
|
function parse(args) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (args.length === 1) {
|
|
86
|
-
const a = args[0];
|
|
87
|
-
if (a instanceof Error) return { message: a.message, context: undefined, error: a };
|
|
88
|
-
if (typeof a === 'object' && a !== null) return { message: '', context: a, error: undefined };
|
|
89
|
-
return { message: String(a), context: undefined, error: undefined };
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const [first, ...rest] = args;
|
|
93
|
-
|
|
94
|
-
// First arg is an Error
|
|
95
|
-
if (first instanceof Error) {
|
|
96
|
-
return { message: first.message, context: rest.length ? rest : undefined, error: first };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// First arg is a string message
|
|
100
|
-
if (typeof first === 'string') {
|
|
101
|
-
// Single extra arg
|
|
102
|
-
if (rest.length === 1) {
|
|
103
|
-
const r = rest[0];
|
|
104
|
-
if (r instanceof Error) return { message: first, context: undefined, error: r };
|
|
105
|
-
if (typeof r === 'object' && r !== null) return { message: first, context: r, error: undefined };
|
|
106
|
-
// Scalar extra: append to message (console.log('count:', 42))
|
|
107
|
-
return { message: first + ' ' + String(r), context: undefined, error: undefined };
|
|
91
|
+
if (args.length === 0) {
|
|
92
|
+
return {message: '', context: undefined, error: undefined};
|
|
108
93
|
}
|
|
109
94
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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};
|
|
115
100
|
}
|
|
116
101
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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};
|
|
120
107
|
}
|
|
121
108
|
|
|
122
|
-
//
|
|
123
|
-
|
|
124
|
-
|
|
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
|
+
}
|
|
125
135
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
136
|
+
// First arg is an object
|
|
137
|
+
if (typeof first === 'object' && first !== null) {
|
|
138
|
+
return {message: '', context: first, error: undefined};
|
|
139
|
+
}
|
|
130
140
|
|
|
131
|
-
|
|
132
|
-
|
|
141
|
+
// Fallback — join everything as a string
|
|
142
|
+
return {message: args.map(String).join(' '), context: undefined, error: undefined};
|
|
133
143
|
}
|
|
134
144
|
|
|
135
145
|
module.exports = patchConsole;
|
|
@@ -1,54 +1,68 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const MillasRequest = require('../http/MillasRequest');
|
|
4
|
-
const MillasResponse = require('../http/MillasResponse');
|
|
5
|
-
const ResponseDispatcher = require('../http/ResponseDispatcher');
|
|
6
|
-
const RequestContext = require('../http/RequestContext');
|
|
7
|
-
|
|
8
3
|
/**
|
|
9
4
|
* MiddlewareRegistry
|
|
10
5
|
*
|
|
11
|
-
* Maps string aliases → middleware
|
|
12
|
-
*
|
|
6
|
+
* Maps string aliases → Millas middleware classes or instances.
|
|
7
|
+
* Resolution produces adapter-native handler functions via the adapter,
|
|
8
|
+
* so this class has zero knowledge of Express (or any HTTP engine).
|
|
9
|
+
*
|
|
10
|
+
* The adapter is injected at resolution time (not construction time)
|
|
11
|
+
* so the registry can be built before the adapter exists.
|
|
13
12
|
*/
|
|
14
13
|
class MiddlewareRegistry {
|
|
15
|
-
constructor(
|
|
16
|
-
this._map
|
|
17
|
-
this._container = container;
|
|
14
|
+
constructor() {
|
|
15
|
+
this._map = {};
|
|
18
16
|
}
|
|
19
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Register a middleware alias.
|
|
20
|
+
*
|
|
21
|
+
* registry.register('auth', AuthMiddleware)
|
|
22
|
+
* registry.register('throttle', new ThrottleMiddleware({ max: 60 }))
|
|
23
|
+
*/
|
|
20
24
|
register(alias, handler) {
|
|
21
25
|
this._map[alias] = handler;
|
|
22
26
|
return this;
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
/**
|
|
26
|
-
* Resolve a middleware alias or
|
|
27
|
-
*
|
|
28
|
-
* Millas middleware (class with handle(req, next)):
|
|
29
|
-
* - Receives MillasRequest
|
|
30
|
-
* - Returns MillasResponse or calls next()
|
|
31
|
-
* - Kernel dispatches the MillasResponse if returned
|
|
30
|
+
* Resolve a middleware alias or class/instance into an adapter-native handler.
|
|
32
31
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
32
|
+
* @param {string|Function|object} aliasOrFn
|
|
33
|
+
* @param {import('../http/adapters/HttpAdapter')} adapter
|
|
34
|
+
* @param {object|null} container
|
|
35
|
+
* @returns {Function} adapter-native handler
|
|
35
36
|
*/
|
|
36
|
-
resolve(aliasOrFn) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
37
|
+
resolve(aliasOrFn, adapter, container = null) {
|
|
38
|
+
const Handler = typeof aliasOrFn === 'string'
|
|
39
|
+
? this._map[aliasOrFn]
|
|
40
|
+
: aliasOrFn;
|
|
41
41
|
|
|
42
|
-
const Handler = this._map[aliasOrFn];
|
|
43
42
|
if (!Handler) {
|
|
44
43
|
throw new Error(`Middleware "${aliasOrFn}" is not registered.`);
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
return this.
|
|
46
|
+
return this._wrap(Handler, adapter, container);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Resolve all aliases in a list.
|
|
51
|
+
*/
|
|
52
|
+
resolveAll(list = [], adapter, container = null) {
|
|
53
|
+
return list.map(m => this.resolve(m, adapter, container));
|
|
48
54
|
}
|
|
49
55
|
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Return a no-op passthrough handler for the given adapter.
|
|
58
|
+
* Used when a middleware alias is missing but should not crash the app.
|
|
59
|
+
*/
|
|
60
|
+
resolvePassthrough(adapter) {
|
|
61
|
+
// Adapter-agnostic: return a function matching the native signature
|
|
62
|
+
// by asking the adapter to wrap a no-op middleware instance.
|
|
63
|
+
return adapter.wrapMiddleware({
|
|
64
|
+
handle: (_ctx, next) => next(),
|
|
65
|
+
}, null);
|
|
52
66
|
}
|
|
53
67
|
|
|
54
68
|
has(alias) {
|
|
@@ -56,71 +70,37 @@ class MiddlewareRegistry {
|
|
|
56
70
|
}
|
|
57
71
|
|
|
58
72
|
all() {
|
|
59
|
-
return { ...this._map };
|
|
73
|
+
return { ...this._map };
|
|
74
|
+
}
|
|
60
75
|
|
|
61
|
-
//
|
|
76
|
+
// ── Internal ────────────────────────────────────────────────────────────────
|
|
62
77
|
|
|
63
|
-
|
|
64
|
-
// Pre-instantiated Millas middleware object
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
|
|
78
|
+
_wrap(Handler, adapter, container) {
|
|
79
|
+
// Pre-instantiated Millas middleware object with handle()
|
|
80
|
+
if (
|
|
81
|
+
typeof Handler === 'object' &&
|
|
82
|
+
Handler !== null &&
|
|
83
|
+
typeof Handler.handle === 'function'
|
|
84
|
+
) {
|
|
85
|
+
return adapter.wrapMiddleware(Handler, container);
|
|
68
86
|
}
|
|
69
87
|
|
|
70
|
-
// Millas middleware class (
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
88
|
+
// Millas middleware class (handle on prototype)
|
|
89
|
+
if (
|
|
90
|
+
typeof Handler === 'function' &&
|
|
91
|
+
Handler.prototype &&
|
|
92
|
+
typeof Handler.prototype.handle === 'function'
|
|
93
|
+
) {
|
|
94
|
+
return adapter.wrapMiddleware(new Handler(), container);
|
|
76
95
|
}
|
|
77
96
|
|
|
78
|
-
//
|
|
79
|
-
// Pass through unchanged — developers using old style still work
|
|
97
|
+
// Raw adapter-native function — pass through as-is (escape hatch)
|
|
80
98
|
if (typeof Handler === 'function') {
|
|
81
99
|
return Handler;
|
|
82
100
|
}
|
|
83
101
|
|
|
84
|
-
throw new Error(
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Build an Express-compatible function from a Millas middleware instance.
|
|
89
|
-
*
|
|
90
|
-
* The middleware's handle(req, next) is called with a MillasRequest.
|
|
91
|
-
* If it returns a MillasResponse, that response is dispatched immediately.
|
|
92
|
-
* If it calls next(), Express continues down the chain.
|
|
93
|
-
*/
|
|
94
|
-
_buildMillasWrapper(instance) {
|
|
95
|
-
const container = this._container;
|
|
96
|
-
|
|
97
|
-
return (expressReq, expressRes, expressNext) => {
|
|
98
|
-
const millaReq = new MillasRequest(expressReq);
|
|
99
|
-
const ctx = new RequestContext(millaReq, container);
|
|
100
|
-
|
|
101
|
-
const next = () => {
|
|
102
|
-
expressNext();
|
|
103
|
-
return undefined;
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
new Promise((resolve, reject) => {
|
|
107
|
-
try {
|
|
108
|
-
resolve(instance.handle(ctx, next));
|
|
109
|
-
} catch (err) {
|
|
110
|
-
reject(err);
|
|
111
|
-
}
|
|
112
|
-
})
|
|
113
|
-
.then(value => {
|
|
114
|
-
if (value !== undefined && value !== null && !expressRes.headersSent) {
|
|
115
|
-
const response = MillasResponse.isResponse(value)
|
|
116
|
-
? value
|
|
117
|
-
: ResponseDispatcher.autoWrap(value);
|
|
118
|
-
ResponseDispatcher.dispatch(response, expressRes);
|
|
119
|
-
}
|
|
120
|
-
})
|
|
121
|
-
.catch(expressNext);
|
|
122
|
-
};
|
|
102
|
+
throw new Error('Middleware must be a function or a class with handle().');
|
|
123
103
|
}
|
|
124
104
|
}
|
|
125
105
|
|
|
126
|
-
module.exports = MiddlewareRegistry;
|
|
106
|
+
module.exports = MiddlewareRegistry;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* System migration: 0001_users
|
|
5
|
+
*
|
|
6
|
+
* Previously created the users table directly.
|
|
7
|
+
*
|
|
8
|
+
* As of Millas 0.3+, the users table is owned by the APP — not the framework.
|
|
9
|
+
* AuthUser is now fully abstract (no table). The app defines its own User model
|
|
10
|
+
* that extends AuthUser and owns whatever table it wants (typically 'users').
|
|
11
|
+
*
|
|
12
|
+
* This migration is kept as a no-op so existing projects that depend on
|
|
13
|
+
* ['system', '0001_users'] don't break. It creates nothing.
|
|
14
|
+
*
|
|
15
|
+
* Equivalent to Django's pattern: AbstractUser has no table; your custom
|
|
16
|
+
* User model creates its own table via app migrations.
|
|
17
|
+
*/
|
|
18
|
+
module.exports = {
|
|
19
|
+
dependencies: [],
|
|
20
|
+
operations: [],
|
|
21
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { CreateModel } = require('../../orm/migration/operations');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* System migration: millas_admin_log
|
|
7
|
+
* Equivalent to Django's django_admin_log.
|
|
8
|
+
*/
|
|
9
|
+
module.exports = {
|
|
10
|
+
dependencies: [['system', '0001_users']],
|
|
11
|
+
|
|
12
|
+
operations: [
|
|
13
|
+
new CreateModel('millas_admin_log', {
|
|
14
|
+
id: { type: 'id', unsigned: true, nullable: false, unique: false, default: null, max: null, enumValues: null, references: null, precision: null, scale: null },
|
|
15
|
+
user_id: { type: 'integer', unsigned: true, nullable: true, unique: false, default: null, max: null, enumValues: null, references: { table: 'users', column: 'id', onDelete: 'SET NULL' }, precision: null, scale: null },
|
|
16
|
+
user_email: { type: 'string', max: 255, nullable: true, unique: false, default: null, unsigned: false, enumValues: null, references: null, precision: null, scale: null },
|
|
17
|
+
resource: { type: 'string', max: 100, nullable: false, unique: false, default: null, unsigned: false, enumValues: null, references: null, precision: null, scale: null },
|
|
18
|
+
record_id: { type: 'string', max: 100, nullable: true, unique: false, default: null, unsigned: false, enumValues: null, references: null, precision: null, scale: null },
|
|
19
|
+
action: { type: 'enum', enumValues: ['create','update','delete'], nullable: false, unique: false, default: null, max: null, unsigned: false, references: null, precision: null, scale: null },
|
|
20
|
+
label: { type: 'string', max: 255, nullable: true, unique: false, default: null, unsigned: false, enumValues: null, references: null, precision: null, scale: null },
|
|
21
|
+
change_msg: { type: 'text', nullable: true, unique: false, default: null, max: null, unsigned: false, enumValues: null, references: null, precision: null, scale: null },
|
|
22
|
+
created_at: { type: 'timestamp', nullable: true, unique: false, default: null, max: null, unsigned: false, enumValues: null, references: null, precision: null, scale: null },
|
|
23
|
+
}),
|
|
24
|
+
],
|
|
25
|
+
};
|