millas 0.2.12-beta → 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 (52) hide show
  1. package/package.json +3 -16
  2. package/src/auth/Auth.js +13 -8
  3. package/src/auth/AuthController.js +3 -1
  4. package/src/auth/AuthUser.js +98 -0
  5. package/src/cli.js +1 -1
  6. package/src/commands/serve.js +81 -110
  7. package/src/container/AppInitializer.js +158 -0
  8. package/src/container/Application.js +278 -253
  9. package/src/container/HttpServer.js +156 -0
  10. package/src/container/MillasApp.js +23 -280
  11. package/src/container/MillasConfig.js +163 -0
  12. package/src/core/auth.js +9 -0
  13. package/src/core/db.js +8 -0
  14. package/src/core/foundation.js +67 -0
  15. package/src/core/http.js +11 -0
  16. package/src/core/mail.js +6 -0
  17. package/src/core/queue.js +7 -0
  18. package/src/core/validation.js +29 -0
  19. package/src/facades/Admin.js +1 -1
  20. package/src/facades/Auth.js +22 -39
  21. package/src/facades/Cache.js +21 -10
  22. package/src/facades/Database.js +1 -1
  23. package/src/facades/Events.js +18 -17
  24. package/src/facades/Facade.js +197 -0
  25. package/src/facades/Http.js +42 -45
  26. package/src/facades/Log.js +25 -49
  27. package/src/facades/Mail.js +27 -32
  28. package/src/facades/Queue.js +22 -15
  29. package/src/facades/Storage.js +18 -10
  30. package/src/facades/Url.js +53 -0
  31. package/src/http/HttpClient.js +673 -0
  32. package/src/http/ResponseDispatcher.js +18 -111
  33. package/src/http/UrlGenerator.js +375 -0
  34. package/src/http/WelcomePage.js +273 -0
  35. package/src/http/adapters/ExpressAdapter.js +315 -0
  36. package/src/http/adapters/HttpAdapter.js +168 -0
  37. package/src/http/adapters/index.js +9 -0
  38. package/src/index.js +5 -144
  39. package/src/logger/formatters/PrettyFormatter.js +15 -5
  40. package/src/logger/internal.js +2 -2
  41. package/src/logger/patchConsole.js +91 -81
  42. package/src/middleware/MiddlewareRegistry.js +62 -82
  43. package/src/orm/migration/ModelInspector.js +339 -340
  44. package/src/providers/AuthServiceProvider.js +9 -5
  45. package/src/providers/CacheStorageServiceProvider.js +3 -1
  46. package/src/providers/EventServiceProvider.js +2 -1
  47. package/src/providers/LogServiceProvider.js +3 -2
  48. package/src/providers/MailServiceProvider.js +3 -2
  49. package/src/providers/QueueServiceProvider.js +3 -2
  50. package/src/router/Router.js +119 -152
  51. package/src/scaffold/templates.js +8 -7
  52. package/src/facades/Validation.js +0 -69
package/package.json CHANGED
@@ -1,25 +1,12 @@
1
1
  {
2
2
  "name": "millas",
3
- "version": "0.2.12-beta",
3
+ "version": "0.2.12-beta-1",
4
4
  "description": "A modern batteries-included backend framework for Node.js — built on Express, inspired by Laravel, Django, and FastAPI",
5
5
  "main": "src/index.js",
6
6
  "exports": {
7
7
  ".": "./src/index.js",
8
- "./src": "./src/index.js",
9
- "./src/*": "./src/*.js",
10
- "./bin/*": "./bin/*.js",
11
- "./validation": "./src/validation/Validator.js",
12
- "./facades/Http": "./src/facades/Http.js",
13
- "./facades/Validation": "./src/facades/Validation.js",
14
- "./facades/Database": "./src/facades/Database.js",
15
- "./facades/Auth": "./src/facades/Auth.js",
16
- "./facades/Log": "./src/facades/Log.js",
17
- "./facades/Cache": "./src/facades/Cache.js",
18
- "./facades/Mail": "./src/facades/Mail.js",
19
- "./facades/Queue": "./src/facades/Queue.js",
20
- "./facades/Events": "./src/facades/Events.js",
21
- "./facades/Storage": "./src/facades/Storage.js",
22
- "./facades/Admin": "./src/facades/Admin.js"
8
+ "./core/*": "./src/core/*.js",
9
+ "./facades/*": "./src/facades/*.js"
23
10
  },
24
11
  "bin": {
25
12
  "millas": "./bin/millas.js"
package/src/auth/Auth.js CHANGED
@@ -230,13 +230,18 @@ class Auth {
230
230
  }
231
231
 
232
232
  _buildTokenPayload(user) {
233
- return {
234
- id: user.id,
235
- sub: user.id,
236
- email: user.email,
237
- role: user.role || null,
238
- iat: Math.floor(Date.now() / 1000),
239
- };
233
+ // If the model defines tokenPayload(), use it — allows custom JWT claims.
234
+ // Otherwise fall back to the standard shape.
235
+ const base = typeof user.tokenPayload === 'function'
236
+ ? user.tokenPayload()
237
+ : {
238
+ id: user.id,
239
+ sub: user.id,
240
+ email: user.email,
241
+ role: user.role || null,
242
+ };
243
+
244
+ return { ...base, iat: Math.floor(Date.now() / 1000) };
240
245
  }
241
246
 
242
247
  _requireUserModel() {
@@ -251,4 +256,4 @@ class Auth {
251
256
 
252
257
  // Singleton facade
253
258
  module.exports = new Auth();
254
- module.exports.Auth = Auth;
259
+ module.exports.Auth = Auth;
@@ -87,6 +87,8 @@ class AuthController extends Controller {
87
87
 
88
88
  _safeUser(user) {
89
89
  if (!user) return null;
90
+ // Use the model's toSafeObject() if defined (AuthUser and subclasses provide this)
91
+ if (typeof user.toSafeObject === 'function') return user.toSafeObject();
90
92
  const data = user.toJSON ? user.toJSON() : { ...user };
91
93
  delete data.password;
92
94
  delete data.remember_token;
@@ -94,4 +96,4 @@ class AuthController extends Controller {
94
96
  }
95
97
  }
96
98
 
97
- module.exports = AuthController;
99
+ module.exports = AuthController;
@@ -0,0 +1,98 @@
1
+ 'use strict';
2
+
3
+ const Model = require('../orm/model/Model');
4
+ const fields = require('../orm/fields/index').fields;
5
+
6
+ /**
7
+ * AuthUser
8
+ *
9
+ * Base model for authentication. Ships with Millas.
10
+ * Covers the exact contract that Auth, AuthMiddleware, and AuthController expect:
11
+ * - email (unique, required for login)
12
+ * - password (hashed by Auth.register / Auth.hashPassword)
13
+ * - role (read by RoleMiddleware)
14
+ *
15
+ * ── Extending ────────────────────────────────────────────────────────────────
16
+ *
17
+ * Extend this instead of writing a User model from scratch.
18
+ * Add your own fields on top — all Auth behaviour is inherited.
19
+ *
20
+ * class User extends AuthUser {
21
+ * static table = 'users';
22
+ * static fields = {
23
+ * ...AuthUser.fields,
24
+ * phone: fields.string({ nullable: true }),
25
+ * avatar_url: fields.string({ nullable: true }),
26
+ * bio: fields.text({ nullable: true }),
27
+ * };
28
+ * }
29
+ *
30
+ * ── Customising the token payload ─────────────────────────────────────────
31
+ *
32
+ * Override tokenPayload() to add custom claims to the JWT:
33
+ *
34
+ * class User extends AuthUser {
35
+ * tokenPayload() {
36
+ * return {
37
+ * ...super.tokenPayload(),
38
+ * plan: this.plan,
39
+ * orgId: this.org_id,
40
+ * };
41
+ * }
42
+ * }
43
+ *
44
+ * ── Customising register / login hooks ────────────────────────────────────
45
+ *
46
+ * Override static hooks to run logic around auth operations:
47
+ *
48
+ * class User extends AuthUser {
49
+ * static async afterCreate(instance) {
50
+ * await emit('user.registered', { user: instance });
51
+ * }
52
+ * }
53
+ */
54
+ class AuthUser extends Model {
55
+ static table = 'users';
56
+
57
+ static fields = {
58
+ id: fields.id(),
59
+ name: fields.string({ max: 100 }),
60
+ email: fields.string({ unique: true }),
61
+ password: fields.string(),
62
+ role: fields.enum(['admin', 'user'], { default: 'user' }),
63
+ created_at: fields.timestamp(),
64
+ updated_at: fields.timestamp(),
65
+ };
66
+
67
+ // ── Auth contract helpers ──────────────────────────────────────────────────
68
+
69
+ /**
70
+ * The fields to include in the JWT payload.
71
+ * Override to add custom claims.
72
+ *
73
+ * @returns {object}
74
+ */
75
+ tokenPayload() {
76
+ return {
77
+ id: this.id,
78
+ sub: this.id,
79
+ email: this.email,
80
+ role: this.role || null,
81
+ };
82
+ }
83
+
84
+ /**
85
+ * Safe representation — strips sensitive fields.
86
+ * Used by AuthController._safeUser().
87
+ *
88
+ * @returns {object}
89
+ */
90
+ toSafeObject() {
91
+ const data = { ...this };
92
+ delete data.password;
93
+ delete data.remember_token;
94
+ return data;
95
+ }
96
+ }
97
+
98
+ module.exports = AuthUser;
package/src/cli.js CHANGED
@@ -7,7 +7,7 @@ const program = new Command();
7
7
  program
8
8
  .name('millas')
9
9
  .description(chalk.cyan('⚡ Millas — A modern batteries-included Node.js framework'))
10
- .version('0.1.0');
10
+ .version('0.2.12-beta-1');
11
11
 
12
12
  // Load all command modules
13
13
  require('./commands/new')(program);
@@ -6,8 +6,9 @@ const fs = require('fs-extra');
6
6
  const fsnative = require('fs');
7
7
  const {fork} = require('child_process');
8
8
  const chokidar = require('chokidar');
9
-
10
- // ── ASCII banner ──────────────────────────────────────────────────────────────
9
+ const patchConsole = require("../logger/patchConsole");
10
+ const Logger = require("../logger/internal");
11
+ // ── ASCII banner ───────────────────────────────────────────────────────────────
11
12
 
12
13
  const BANNER_LINES = [
13
14
  ' ███╗ ███╗██╗██╗ ██╗ █████╗ ███████╗',
@@ -20,10 +21,9 @@ const BANNER_LINES = [
20
21
 
21
22
  function printBanner(host, port) {
22
23
  const env = process.env.NODE_ENV || 'development';
23
- const ver = 'v' + (require('../../package.json').version || '0.1.2');
24
+ const ver = 'v' + (require('../../package.json').version || '0.1.0');
24
25
  const url = `http://${host}:${port}`;
25
26
  const hr = chalk.dim(' ' + '─'.repeat(54));
26
-
27
27
  const envColour = env === 'production' ? chalk.red
28
28
  : env === 'staging' ? chalk.yellow
29
29
  : chalk.green;
@@ -32,52 +32,37 @@ function printBanner(host, port) {
32
32
  for (const line of BANNER_LINES) {
33
33
  process.stdout.write(chalk.bold.cyan(line) + '\n');
34
34
  }
35
- process.stdout.write('\n');
36
- process.stdout.write(hr + '\n');
35
+ process.stdout.write('\n' + hr + '\n');
37
36
  process.stdout.write(
38
- ' ' +
39
- chalk.dim(ver.padEnd(8)) +
40
- chalk.dim('│') + ' ' +
41
- envColour('⬤ ' + env) + ' ' +
42
- chalk.dim('│') + ' ' +
43
- chalk.bold.white(url) +
44
- '\n'
37
+ ' ' + chalk.dim(ver.padEnd(8)) +
38
+ chalk.dim('│') + ' ' + envColour('⬤ ' + env) + ' ' +
39
+ chalk.dim('│') + ' ' + chalk.bold.white(url) + '\n'
45
40
  );
46
41
  process.stdout.write(hr + '\n\n');
47
42
  }
48
43
 
49
- // ── Hot reload watcher ────────────────────────────────────────────────────────
50
-
51
- /**
52
- * Directories watched for changes (relative to project root).
53
- * Watching parent dirs is enough — fs.watch recursive covers all descendants.
54
- */
55
- const WATCH_DIRS = [
56
- 'app',
57
- 'routes',
58
- 'config',
59
- 'bootstrap',
60
- 'providers',
61
- 'middleware',
62
- ];
44
+ // ── Constants ─────────────────────────────────────────────────────────────────
63
45
 
64
- /** File extensions that trigger a reload when changed. */
46
+ const WATCH_DIRS = ['app', 'routes', 'config', 'bootstrap', 'providers', 'middleware'];
65
47
  const WATCH_EXTS = new Set(['.js', '.mjs', '.cjs', '.json', '.njk', '.env']);
48
+ const DEBOUNCE_MS = 250;
49
+
66
50
 
67
- /** How long to wait after the last change before restarting (ms). */
68
- const DEBOUNCE_MS = 300;
51
+ // ── HotReloader ───────────────────────────────────────────────────────────────
69
52
 
70
53
  class HotReloader {
71
- constructor(bootstrapPath, env) {
54
+ constructor(bootstrapPath, publicPort, publicHost) {
72
55
  this._bootstrap = bootstrapPath;
73
- this._cwd = path.dirname(bootstrapPath.replace(/bootstrap.+$/, '')) ||
74
- process.cwd();
75
- this._env = env;
56
+ this._initialised = false
57
+
76
58
  this._child = null;
77
- this._watchers = [];
78
- this._timer = null;
79
59
  this._starting = false;
80
60
  this._restarts = 0;
61
+
62
+ // Queued { req, res, timer } entries while child is restarting
63
+ this._queue = [];
64
+ this._watchers = [];
65
+ this._timer = null;
81
66
  }
82
67
 
83
68
  start() {
@@ -86,75 +71,74 @@ class HotReloader {
86
71
  this._handleSignals();
87
72
  }
88
73
 
89
- // ── Child process management ──────────────────────────────────────────────
90
-
91
74
  _spawnChild() {
92
- if (this._starting) return;
75
+ if (this._starting) {
76
+ return;
77
+ }
93
78
  this._starting = true;
94
-
79
+ const extra = {}
80
+ if (!this._initialised) {
81
+ extra["MILLAS_START_UP"] = true
82
+ this._initialised = true
83
+ }
95
84
  this._child = fork(this._bootstrap, [], {
96
- env: {...process.env, ...this._env},
97
- stdio: 'inherit', // child shares parent's stdout/stderr — output appears inline
98
- // detached: false — child dies with parent
85
+ env: {
86
+ ...extra,
87
+ MILLAS_CHILD: '1',
88
+ },
89
+ stdio: 'inherit',
99
90
  });
100
91
 
92
+ // Application.listen() sends { type:'ready' } via IPC once bound
93
+ this._child.on('message', msg => {
94
+ if (msg && msg.type === 'ready') {
95
+ this._starting = false;
96
+ }
97
+ });
98
+ //
101
99
  this._child.on('exit', (code, signal) => {
102
100
  this._starting = false;
103
- // Abnormal exit (crash) — don't auto-restart, let the developer fix it
101
+
104
102
  if (code !== 0 && signal !== 'SIGTERM' && signal !== 'SIGKILL') {
105
- process.stdout.write(
106
- '\n' + chalk.red(' ✖ App crashed') +
103
+ console.error(chalk.red('✖ App crashed') +
107
104
  chalk.dim(` (exit ${code ?? signal})`) +
108
- chalk.dim(' — waiting for changes…\n\n')
105
+ chalk.dim(' — fix the error, file watcher will reload…')
109
106
  );
110
107
  }
111
108
  });
112
-
113
- this._child.on('error', (err) => {
109
+ //
110
+ this._child.on('error', err => {
114
111
  this._starting = false;
115
- process.stderr.write(chalk.red(`\n ✖ Child error: ${err.message}\n\n`));
112
+ console.error(chalk.red(`✖ ${err.message}`));
116
113
  });
117
114
  }
118
115
 
119
116
  _killChild(cb) {
120
- if (!this._child || this._child.exitCode !== null) {
121
- cb();
122
- return;
123
- }
117
+ if (!this._child || this._child.exitCode !== null) return cb();
124
118
  this._child.once('exit', cb);
125
119
  this._child.kill('SIGTERM');
126
-
127
- // Force kill if graceful shutdown takes too long
128
- setTimeout(() => {
129
- if (this._child && this._child.exitCode === null) {
130
- this._child.kill('SIGKILL');
131
- }
132
- }, 3000).unref();
133
120
  }
134
121
 
135
122
  _restart(changedFile) {
136
- const rel = changedFile ? path.relative(process.cwd(), changedFile) : '';
137
123
  const link = changedFile
138
- ? `\x1b]8;;file://${changedFile}\x07${changedFile}\x1b]8;;\x07`
124
+ ? `\x1b]8;;file://${changedFile}\x07${path.relative(process.cwd(), changedFile)}\x1b]8;;\x07`
139
125
  : '';
140
-
141
- process.stdout.write(
142
- '\n ' + chalk.yellow('↺') + ' ' +
126
+ console.warn(
127
+ chalk.yellow('↺') + ' ' +
143
128
  chalk.white('Reloading') +
144
- (link ? ' ' + chalk.cyan(link) : '') +
145
- '\n'
129
+ (link ? chalk.blueBright(' ' + link) : '')
146
130
  );
147
131
 
148
132
  this._restarts++;
149
-
150
- this._killChild(() => {
151
- this._spawnChild();
152
- });
133
+ this._killChild(() => this._spawnChild());
153
134
  }
154
135
 
155
- // ── File watching ─────────────────────────────────────────────────────────
136
+ // ── Watcher ───────────────────────────────────────────────────────────────
156
137
 
157
138
  _watch() {
139
+ console.log(chalk.green('✔') + ' ' +
140
+ chalk.dim('Watching for changes…')
141
+ );
158
142
  const cwd = process.cwd();
159
143
  const watchPaths = [
160
144
  ...WATCH_DIRS.map(d => path.join(cwd, d)),
@@ -166,6 +150,9 @@ class HotReloader {
166
150
  persistent: true,
167
151
  ignoreInitial: true,
168
152
  ignored: /(^|[\/\\])\..(?!env)/,
153
+ // Wait for the file write to settle before reloading.
154
+ // Prevents double-restarts on editors that truncate then rewrite.
155
+ awaitWriteFinish: {stabilityThreshold: 80, pollInterval: 20},
169
156
  });
170
157
 
171
158
  watcher.on('all', (event, filePath) => {
@@ -179,9 +166,7 @@ class HotReloader {
179
166
 
180
167
  _scheduleRestart(changedFile) {
181
168
  clearTimeout(this._timer);
182
- this._timer = setTimeout(() => {
183
- this._restart(changedFile);
184
- }, DEBOUNCE_MS);
169
+ this._timer = setTimeout(() => this._restart(changedFile), DEBOUNCE_MS);
185
170
  }
186
171
 
187
172
  _stopWatching() {
@@ -194,8 +179,7 @@ class HotReloader {
194
179
  this._watchers = [];
195
180
  }
196
181
 
197
-
198
- // ── Signal handling ───────────────────────────────────────────────────────
182
+ // ── Signals ───────────────────────────────────────────────────────────────
199
183
 
200
184
  _handleSignals() {
201
185
  const cleanup = () => {
@@ -204,16 +188,14 @@ class HotReloader {
204
188
  if (this._child) this._child.kill('SIGTERM');
205
189
  process.exit(0);
206
190
  };
207
-
208
- process.on('SIGINT', cleanup);
209
- process.on('SIGTERM', cleanup);
191
+ process.once('SIGINT', cleanup);
192
+ process.once('SIGTERM', cleanup);
210
193
  }
211
194
  }
212
195
 
213
- // ── Command definition ────────────────────────────────────────────────────────
196
+ // ── Command ────────────────────────────────────────────────────────────────────
214
197
 
215
198
  module.exports = function (program) {
216
-
217
199
  program
218
200
  .command('serve')
219
201
  .description('Start the development server with hot reload')
@@ -221,6 +203,8 @@ module.exports = function (program) {
221
203
  .option('-h, --host <host>', 'Host to bind to', 'localhost')
222
204
  .option('--no-reload', 'Disable hot reload (run once, like production)')
223
205
  .action((options) => {
206
+
207
+ const restoreAfterPatch = patchConsole(Logger,"SystemOut")
224
208
  const appBootstrap = path.resolve(process.cwd(), 'bootstrap/app.js');
225
209
 
226
210
  if (!fs.existsSync(appBootstrap)) {
@@ -229,51 +213,38 @@ module.exports = function (program) {
229
213
  process.exit(1);
230
214
  }
231
215
 
216
+
217
+ const publicPort = parseInt(options.port, 10) || 3000;
218
+ const publicHost = options.host || 'localhost';
219
+
232
220
  const env = {
233
- MILLAS_PORT: options.port,
234
- MILLAS_HOST: options.host,
235
- NODE_ENV: process.env.NODE_ENV || 'development',
236
- MILLAS_RELOAD: options.reload ? '1' : '0',
221
+ NODE_ENV: process.env.APP_ENV || 'development',
222
+ MILLERS_NODE_ENV: true,
223
+ MILLAS_HOST: publicHost,
224
+ MILLAS_PORT: String(publicPort),
237
225
  };
238
226
 
239
227
  Object.assign(process.env, env);
228
+ printBanner(publicHost, publicPort);
240
229
 
241
- printBanner(options.host, options.port);
242
-
243
- const hotReload = options.reload !== false;
244
-
245
- if (hotReload) {
246
- // ── Hot reload mode ──────────────────────────────────────────────────
247
- // Parent process watches files. App runs in a child process.
248
- // On change: kill child, clear require cache, refork.
249
- process.stdout.write(
250
- ' ' + chalk.green('✔') + ' ' +
251
- chalk.dim('Watching for file changes…') +
252
- '\n'
253
- );
254
-
255
- const reloader = new HotReloader(appBootstrap, env);
256
- reloader.start();
230
+ if (options.reload !== false) {
257
231
 
232
+ new HotReloader(appBootstrap, publicPort, publicHost).start();
258
233
  } else {
259
- // ── Single-run mode (--no-reload) ────────────────────────────────────
260
234
  try {
261
235
  require(appBootstrap);
262
- } catch (err) {
263
- process.stderr.write(chalk.red(`\n ✖ Failed to start: ${err.message}\n\n`));
264
- if (process.env.DEBUG) process.stderr.write(chalk.dim(err.stack) + '\n');
265
- process.exit(1);
236
+ } catch (e) {
237
+ console.log("Error starting server: ", +e)
266
238
  }
267
239
  }
268
240
  });
269
241
 
270
242
  };
271
243
 
272
- // Exported so other commands (queue:work) can validate the project exists
273
244
  module.exports.requireProject = function (command) {
274
245
  const bootstrapPath = path.resolve(process.cwd(), 'bootstrap/app.js');
275
246
  if (!fsnative.existsSync(bootstrapPath)) {
276
247
  process.stderr.write(chalk.red(`\n ✖ Not inside a Millas project (${command}).\n\n`));
277
248
  process.exit(1);
278
249
  }
279
- };
250
+ };
@@ -0,0 +1,158 @@
1
+ 'use strict';
2
+
3
+ const express = require('express');
4
+ const Application = require('./Application');
5
+ const HttpServer = require('./HttpServer');
6
+
7
+ /**
8
+ * AppInitialiser
9
+ *
10
+ * The internal engine that receives what bootstrap/app.js exports
11
+ * (a MillasInstance), extracts its config, and wires up everything:
12
+ *
13
+ * - ExpressAdapter
14
+ * - Application kernel (DI container, providers, routes, middleware)
15
+ * - Admin panel
16
+ * - HttpServer (port, signals, startup log, IPC ready signal)
17
+ *
18
+ * Developers never see or interact with this class.
19
+ * It is used exclusively by runner.js (millas serve) and the no-reload path.
20
+ *
21
+ * ── Flow ─────────────────────────────────────────────────────────────────────
22
+ *
23
+ * bootstrap/app.js → MillasInstance → AppInitialiser.boot()
24
+ * ├─ builds ExpressAdapter
25
+ * ├─ builds Application
26
+ * ├─ registers providers
27
+ * ├─ registers routes
28
+ * ├─ registers middleware aliases
29
+ * ├─ boots providers
30
+ * ├─ mounts routes
31
+ * ├─ mounts admin (if configured)
32
+ * ├─ mounts fallbacks
33
+ * └─ starts HttpServer
34
+ */
35
+ class AppInitializer {
36
+ /**
37
+ * @param {Object} config — the sealed export of bootstrap/app.js
38
+ */
39
+ constructor(config) {
40
+ this._config = config;
41
+ this._kernel = null;
42
+ this._adapter = null;
43
+ }
44
+
45
+ /**
46
+ * Boot the full application and start the HTTP server.
47
+ * Returns a Promise that resolves once the server is listening.
48
+ */
49
+ async boot() {
50
+ const cfg = this._config;
51
+
52
+ // ── Build the HTTP adapter ───────────────────────────────────────────────
53
+ const ExpressAdapter = require('../http/adapters/ExpressAdapter');
54
+ const expressApp = express();
55
+ this._adapter = new ExpressAdapter(expressApp);
56
+ this._adapter.applyBodyParsers();
57
+
58
+ // Raw adapter middleware (helmet, compression, etc.)
59
+ for (const mw of (cfg.adapterMiddleware || [])) {
60
+ this._adapter.applyMiddleware(mw);
61
+ }
62
+
63
+ // ── Build the Application kernel ─────────────────────────────────────────
64
+ this._kernel = new Application(this._adapter);
65
+
66
+ // Core providers (auto-enabled unless disabled in config)
67
+ const coreProviders = this._buildCoreProviders(cfg);
68
+ this._kernel.providers([...coreProviders, ...cfg.providers]);
69
+
70
+ // Named middleware aliases
71
+ for (const {alias, handler} of (cfg.middleware || [])) {
72
+ this._kernel.middleware(alias, handler);
73
+ }
74
+
75
+ // Route definitions
76
+ if (cfg.routes) {
77
+ this._kernel.routes(cfg.routes);
78
+ }
79
+
80
+ // ── Boot providers ───────────────────────────────────────────────────────
81
+ await this._kernel.boot();
82
+
83
+ // ── Mount routes ─────────────────────────────────────────────────────────
84
+ if (!process.env.MILLAS_ROUTE_LIST) {
85
+ this._kernel.mountRoutes();
86
+
87
+ // Admin panel — mounted between routes and fallbacks
88
+ if (cfg.admin !== null) {
89
+ try {
90
+ const Admin = require('../admin/Admin');
91
+ if (cfg.admin && Object.keys(cfg.admin).length) {
92
+ Admin.configure(cfg.admin);
93
+ }
94
+ Admin.mount(expressApp);
95
+ } catch (err) {
96
+ process.stderr.write(`[millas] Admin mount failed: ${err.message}\n`);
97
+ }
98
+ }
99
+
100
+ this._kernel.mountFallbacks();
101
+
102
+ // ── Start the HTTP server ──────────────────────────────────────────────
103
+ const server = new HttpServer(this._kernel, {
104
+ onStart: cfg.onStart || undefined,
105
+ onShutdown: cfg.onShutdown || undefined,
106
+ });
107
+
108
+ await server.start();
109
+ }
110
+ }
111
+
112
+ // ── Internal ───────────────────────────────────────────────────────────────
113
+
114
+ _buildCoreProviders(cfg) {
115
+ const providers = [];
116
+ const load = (p) => {
117
+ try {
118
+ return require(p);
119
+ } catch {
120
+ return null;
121
+ }
122
+ };
123
+
124
+ if (cfg.logging !== false) {
125
+ const p = load('../providers/LogServiceProvider');
126
+ if (p) providers.push(p);
127
+ }
128
+ if (cfg.database !== false) {
129
+ const p = load('../providers/DatabaseServiceProvider');
130
+ if (p) providers.push(p);
131
+ }
132
+
133
+ if (cfg.cache !== false || cfg.storage !== false) {
134
+ const p = load('../providers/CacheStorageServiceProvider');
135
+ if (p) {
136
+ if (cfg.cache !== false && p.CacheServiceProvider) providers.push(p.CacheServiceProvider);
137
+ if (cfg.storage !== false && p.StorageServiceProvider) providers.push(p.StorageServiceProvider);
138
+ }
139
+ }
140
+
141
+ if (cfg.mail !== false) {
142
+ const p = load('../providers/MailServiceProvider');
143
+ if (p) providers.push(p);
144
+ }
145
+ if (cfg.queue !== false) {
146
+ const p = load('../providers/QueueServiceProvider');
147
+ if (p) providers.push(p);
148
+ }
149
+ if (cfg.events !== false) {
150
+ const p = load('../providers/EventServiceProvider');
151
+ if (p) providers.push(p);
152
+ }
153
+
154
+ return providers;
155
+ }
156
+ }
157
+
158
+ module.exports = AppInitializer;