millas 0.2.11 → 0.2.12-beta-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/package.json +6 -5
  2. package/src/auth/Auth.js +13 -8
  3. package/src/auth/AuthController.js +45 -134
  4. package/src/auth/AuthMiddleware.js +12 -23
  5. package/src/auth/AuthUser.js +98 -0
  6. package/src/auth/RoleMiddleware.js +7 -17
  7. package/src/cli.js +1 -1
  8. package/src/commands/migrate.js +46 -31
  9. package/src/commands/serve.js +238 -38
  10. package/src/container/AppInitializer.js +158 -0
  11. package/src/container/Application.js +288 -183
  12. package/src/container/HttpServer.js +156 -0
  13. package/src/container/MillasApp.js +23 -280
  14. package/src/container/MillasConfig.js +163 -0
  15. package/src/controller/Controller.js +79 -300
  16. package/src/core/auth.js +9 -0
  17. package/src/core/db.js +8 -0
  18. package/src/core/foundation.js +67 -0
  19. package/src/core/http.js +11 -0
  20. package/src/core/mail.js +6 -0
  21. package/src/core/queue.js +7 -0
  22. package/src/core/validation.js +29 -0
  23. package/src/errors/ErrorRenderer.js +640 -0
  24. package/src/facades/Admin.js +49 -0
  25. package/src/facades/Auth.js +29 -0
  26. package/src/facades/Cache.js +28 -0
  27. package/src/facades/Database.js +43 -0
  28. package/src/facades/Events.js +25 -0
  29. package/src/facades/Facade.js +197 -0
  30. package/src/facades/Http.js +51 -0
  31. package/src/facades/Log.js +32 -0
  32. package/src/facades/Mail.js +35 -0
  33. package/src/facades/Queue.js +30 -0
  34. package/src/facades/Storage.js +25 -0
  35. package/src/facades/Url.js +53 -0
  36. package/src/http/HttpClient.js +673 -0
  37. package/src/http/MillasRequest.js +253 -0
  38. package/src/http/MillasResponse.js +196 -0
  39. package/src/http/RequestContext.js +176 -0
  40. package/src/http/ResponseDispatcher.js +51 -0
  41. package/src/http/UrlGenerator.js +375 -0
  42. package/src/http/WelcomePage.js +273 -0
  43. package/src/http/adapters/ExpressAdapter.js +315 -0
  44. package/src/http/adapters/HttpAdapter.js +168 -0
  45. package/src/http/adapters/index.js +9 -0
  46. package/src/http/helpers.js +164 -0
  47. package/src/http/index.js +13 -0
  48. package/src/index.js +5 -91
  49. package/src/logger/formatters/PrettyFormatter.js +15 -5
  50. package/src/logger/internal.js +76 -0
  51. package/src/logger/patchConsole.js +145 -0
  52. package/src/middleware/CorsMiddleware.js +22 -30
  53. package/src/middleware/LogMiddleware.js +27 -59
  54. package/src/middleware/Middleware.js +24 -15
  55. package/src/middleware/MiddlewarePipeline.js +30 -67
  56. package/src/middleware/MiddlewareRegistry.js +106 -0
  57. package/src/middleware/ThrottleMiddleware.js +22 -26
  58. package/src/orm/fields/index.js +124 -56
  59. package/src/orm/migration/ModelInspector.js +339 -336
  60. package/src/orm/model/Model.js +96 -6
  61. package/src/orm/query/QueryBuilder.js +141 -3
  62. package/src/providers/AuthServiceProvider.js +9 -5
  63. package/src/providers/CacheStorageServiceProvider.js +3 -1
  64. package/src/providers/EventServiceProvider.js +2 -1
  65. package/src/providers/LogServiceProvider.js +88 -17
  66. package/src/providers/MailServiceProvider.js +3 -2
  67. package/src/providers/ProviderRegistry.js +14 -1
  68. package/src/providers/QueueServiceProvider.js +3 -2
  69. package/src/providers/ServiceProvider.js +40 -8
  70. package/src/router/Router.js +121 -222
  71. package/src/scaffold/maker.js +24 -59
  72. package/src/scaffold/templates.js +21 -19
  73. package/src/validation/BaseValidator.js +193 -0
  74. package/src/validation/Validator.js +680 -0
@@ -6,17 +6,15 @@ const fs = require('fs-extra');
6
6
 
7
7
  module.exports = function (program) {
8
8
 
9
- // ── makemigrations ────────────────────────────────────────────────────────
10
-
9
+ // ── makemigrations ──────────────────────────────────────────────────────────
11
10
  program
12
11
  .command('makemigrations')
13
12
  .description('Scan model files, detect schema changes, generate migration files')
14
13
  .action(async () => {
15
14
  try {
16
- const ctx = getProjectContext();
17
- // Fixed: was incorrectly destructured as { ModelInspector }
15
+ const ctx = getProjectContext();
18
16
  const ModelInspector = require('../orm/migration/ModelInspector');
19
- const inspector = new ModelInspector(
17
+ const inspector = new ModelInspector(
20
18
  ctx.modelsPath,
21
19
  ctx.migrationsPath,
22
20
  ctx.snapshotPath,
@@ -31,14 +29,12 @@ module.exports = function (program) {
31
29
  console.log(chalk.gray('\n Run: millas migrate to apply these migrations.\n'));
32
30
  }
33
31
  } catch (err) {
34
- console.error(chalk.red(`\n ✖ makemigrations failed: ${err.message}\n`));
35
- if (process.env.DEBUG) console.error(err.stack);
36
- process.exit(1);
32
+ bail('makemigrations', err);
37
33
  }
34
+ // makemigrations doesn't open a DB connection so no closeDb() needed
38
35
  });
39
36
 
40
- // ── migrate ───────────────────────────────────────────────────────────────
41
-
37
+ // ── migrate ─────────────────────────────────────────────────────────────────
42
38
  program
43
39
  .command('migrate')
44
40
  .description('Run all pending migrations')
@@ -46,14 +42,15 @@ module.exports = function (program) {
46
42
  try {
47
43
  const runner = await getRunner();
48
44
  const result = await runner.migrate();
49
- printResult(result, 'Ran');
45
+ printMigrationResult(result, 'Ran');
50
46
  } catch (err) {
51
47
  bail('migrate', err);
48
+ } finally {
49
+ await closeDb();
52
50
  }
53
51
  });
54
52
 
55
- // ── migrate:fresh ─────────────────────────────────────────────────────────
56
-
53
+ // ── migrate:fresh ────────────────────────────────────────────────────────────
57
54
  program
58
55
  .command('migrate:fresh')
59
56
  .description('Drop ALL tables then re-run every migration from scratch')
@@ -62,14 +59,15 @@ module.exports = function (program) {
62
59
  console.log(chalk.yellow('\n ⚠ Dropping all tables…\n'));
63
60
  const runner = await getRunner();
64
61
  const result = await runner.fresh();
65
- printResult(result, 'Ran');
62
+ printMigrationResult(result, 'Ran');
66
63
  } catch (err) {
67
64
  bail('migrate:fresh', err);
65
+ } finally {
66
+ await closeDb();
68
67
  }
69
68
  });
70
69
 
71
- // ── migrate:rollback ──────────────────────────────────────────────────────
72
-
70
+ // ── migrate:rollback ─────────────────────────────────────────────────────────
73
71
  program
74
72
  .command('migrate:rollback')
75
73
  .description('Rollback the last batch of migrations')
@@ -78,14 +76,15 @@ module.exports = function (program) {
78
76
  try {
79
77
  const runner = await getRunner();
80
78
  const result = await runner.rollback(Number(options.steps));
81
- printResult(result, 'Rolled back');
79
+ printMigrationResult(result, 'Rolled back');
82
80
  } catch (err) {
83
81
  bail('migrate:rollback', err);
82
+ } finally {
83
+ await closeDb();
84
84
  }
85
85
  });
86
86
 
87
- // ── migrate:reset ─────────────────────────────────────────────────────────
88
-
87
+ // ── migrate:reset ────────────────────────────────────────────────────────────
89
88
  program
90
89
  .command('migrate:reset')
91
90
  .description('Rollback ALL migrations')
@@ -93,14 +92,15 @@ module.exports = function (program) {
93
92
  try {
94
93
  const runner = await getRunner();
95
94
  const result = await runner.reset();
96
- printResult(result, 'Rolled back');
95
+ printMigrationResult(result, 'Rolled back');
97
96
  } catch (err) {
98
97
  bail('migrate:reset', err);
98
+ } finally {
99
+ await closeDb();
99
100
  }
100
101
  });
101
102
 
102
- // ── migrate:refresh ───────────────────────────────────────────────────────
103
-
103
+ // ── migrate:refresh ──────────────────────────────────────────────────────────
104
104
  program
105
105
  .command('migrate:refresh')
106
106
  .description('Rollback all then re-run all migrations')
@@ -108,14 +108,15 @@ module.exports = function (program) {
108
108
  try {
109
109
  const runner = await getRunner();
110
110
  const result = await runner.refresh();
111
- printResult(result, 'Ran');
111
+ printMigrationResult(result, 'Ran');
112
112
  } catch (err) {
113
113
  bail('migrate:refresh', err);
114
+ } finally {
115
+ await closeDb();
114
116
  }
115
117
  });
116
118
 
117
- // ── migrate:status ────────────────────────────────────────────────────────
118
-
119
+ // ── migrate:status ───────────────────────────────────────────────────────────
119
120
  program
120
121
  .command('migrate:status')
121
122
  .description('Show the status of all migration files')
@@ -143,11 +144,12 @@ module.exports = function (program) {
143
144
  console.log();
144
145
  } catch (err) {
145
146
  bail('migrate:status', err);
147
+ } finally {
148
+ await closeDb();
146
149
  }
147
150
  });
148
151
 
149
- // ── db:seed ───────────────────────────────────────────────────────────────
150
-
152
+ // ── db:seed ──────────────────────────────────────────────────────────────────
151
153
  program
152
154
  .command('db:seed')
153
155
  .description('Run all database seeders')
@@ -180,6 +182,8 @@ module.exports = function (program) {
180
182
  console.log();
181
183
  } catch (err) {
182
184
  bail('db:seed', err);
185
+ } finally {
186
+ await closeDb();
183
187
  }
184
188
  });
185
189
  };
@@ -208,14 +212,25 @@ async function getDbConnection() {
208
212
  }
209
213
 
210
214
  async function getRunner() {
211
- // Fixed: was incorrectly destructured as { MigrationRunner }
212
215
  const MigrationRunner = require('../orm/migration/MigrationRunner');
213
216
  const ctx = getProjectContext();
214
217
  const db = await getDbConnection();
215
218
  return new MigrationRunner(db, ctx.migrationsPath);
216
219
  }
217
220
 
218
- function printResult(result, verb) {
221
+ /**
222
+ * Destroy all open knex connection pools so the CLI process exits cleanly.
223
+ * Without this, knex keeps the event loop alive indefinitely after the
224
+ * command finishes, causing the terminal to appear to hang.
225
+ */
226
+ async function closeDb() {
227
+ try {
228
+ const DatabaseManager = require('../orm/drivers/DatabaseManager');
229
+ await DatabaseManager.closeAll();
230
+ } catch { /* already closed or never opened — safe to ignore */ }
231
+ }
232
+
233
+ function printMigrationResult(result, verb) {
219
234
  const list = result.ran || result.rolledBack || [];
220
235
  if (list.length === 0) {
221
236
  console.log(chalk.yellow(`\n ${result.message}\n`));
@@ -223,7 +238,7 @@ function printResult(result, verb) {
223
238
  }
224
239
  console.log(chalk.green(`\n ✔ ${result.message}`));
225
240
  list.forEach(f =>
226
- console.log(chalk.cyan(` ${verb === 'Ran' ? '+' : '-'} ${f}`)),
241
+ console.log(chalk.cyan(` ${verb === 'Ran' ? '+' : '-'} ${f}`))
227
242
  );
228
243
  console.log();
229
244
  }
@@ -231,5 +246,5 @@ function printResult(result, verb) {
231
246
  function bail(cmd, err) {
232
247
  console.error(chalk.red(`\n ✖ ${cmd} failed: ${err.message}\n`));
233
248
  if (process.env.DEBUG) console.error(err.stack);
234
- process.exit(1);
249
+ closeDb().finally(() => process.exit(1));
235
250
  }
@@ -3,48 +3,248 @@
3
3
  const chalk = require('chalk');
4
4
  const path = require('path');
5
5
  const fs = require('fs-extra');
6
+ const fsnative = require('fs');
7
+ const {fork} = require('child_process');
8
+ const chokidar = require('chokidar');
9
+ const patchConsole = require("../logger/patchConsole");
10
+ const Logger = require("../logger/internal");
11
+ // ── ASCII banner ───────────────────────────────────────────────────────────────
12
+
13
+ const BANNER_LINES = [
14
+ ' ███╗ ███╗██╗██╗ ██╗ █████╗ ███████╗',
15
+ ' ████╗ ████║██║██║ ██║ ██╔══██╗██╔════╝',
16
+ ' ██╔████╔██║██║██║ ██║ ███████║███████╗',
17
+ ' ██║╚██╔╝██║██║██║ ██║ ██╔══██║╚════██║',
18
+ ' ██║ ╚═╝ ██║██║███████╗███████╗██║ ██║███████║',
19
+ ' ╚═╝ ╚═╝╚═╝╚══════╝╚══════╝╚═╝ ╚═╝╚══════╝',
20
+ ];
21
+
22
+ function printBanner(host, port) {
23
+ const env = process.env.NODE_ENV || 'development';
24
+ const ver = 'v' + (require('../../package.json').version || '0.1.0');
25
+ const url = `http://${host}:${port}`;
26
+ const hr = chalk.dim(' ' + '─'.repeat(54));
27
+ const envColour = env === 'production' ? chalk.red
28
+ : env === 'staging' ? chalk.yellow
29
+ : chalk.green;
30
+
31
+ process.stdout.write('\n');
32
+ for (const line of BANNER_LINES) {
33
+ process.stdout.write(chalk.bold.cyan(line) + '\n');
34
+ }
35
+ process.stdout.write('\n' + hr + '\n');
36
+ process.stdout.write(
37
+ ' ' + chalk.dim(ver.padEnd(8)) +
38
+ chalk.dim('│') + ' ' + envColour('⬤ ' + env) + ' ' +
39
+ chalk.dim('│') + ' ' + chalk.bold.white(url) + '\n'
40
+ );
41
+ process.stdout.write(hr + '\n\n');
42
+ }
43
+
44
+ // ── Constants ─────────────────────────────────────────────────────────────────
45
+
46
+ const WATCH_DIRS = ['app', 'routes', 'config', 'bootstrap', 'providers', 'middleware'];
47
+ const WATCH_EXTS = new Set(['.js', '.mjs', '.cjs', '.json', '.njk', '.env']);
48
+ const DEBOUNCE_MS = 250;
49
+
50
+
51
+ // ── HotReloader ───────────────────────────────────────────────────────────────
52
+
53
+ class HotReloader {
54
+ constructor(bootstrapPath, publicPort, publicHost) {
55
+ this._bootstrap = bootstrapPath;
56
+ this._initialised = false
57
+
58
+ this._child = null;
59
+ this._starting = false;
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;
66
+ }
67
+
68
+ start() {
69
+ this._spawnChild();
70
+ this._watch();
71
+ this._handleSignals();
72
+ }
73
+
74
+ _spawnChild() {
75
+ if (this._starting) {
76
+ return;
77
+ }
78
+ this._starting = true;
79
+ const extra = {}
80
+ if (!this._initialised) {
81
+ extra["MILLAS_START_UP"] = true
82
+ this._initialised = true
83
+ }
84
+ this._child = fork(this._bootstrap, [], {
85
+ env: {
86
+ ...extra,
87
+ MILLAS_CHILD: '1',
88
+ },
89
+ stdio: 'inherit',
90
+ });
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
+ //
99
+ this._child.on('exit', (code, signal) => {
100
+ this._starting = false;
101
+
102
+ if (code !== 0 && signal !== 'SIGTERM' && signal !== 'SIGKILL') {
103
+ console.error(chalk.red('✖ App crashed') +
104
+ chalk.dim(` (exit ${code ?? signal})`) +
105
+ chalk.dim(' — fix the error, file watcher will reload…')
106
+ );
107
+ }
108
+ });
109
+ //
110
+ this._child.on('error', err => {
111
+ this._starting = false;
112
+ console.error(chalk.red(`✖ ${err.message}`));
113
+ });
114
+ }
115
+
116
+ _killChild(cb) {
117
+ if (!this._child || this._child.exitCode !== null) return cb();
118
+ this._child.once('exit', cb);
119
+ this._child.kill('SIGTERM');
120
+ }
121
+
122
+ _restart(changedFile) {
123
+ const link = changedFile
124
+ ? `\x1b]8;;file://${changedFile}\x07${path.relative(process.cwd(), changedFile)}\x1b]8;;\x07`
125
+ : '';
126
+ console.warn(
127
+ chalk.yellow('↺') + ' ' +
128
+ chalk.white('Reloading') +
129
+ (link ? chalk.blueBright(' ' + link) : '')
130
+ );
131
+
132
+ this._restarts++;
133
+ this._killChild(() => this._spawnChild());
134
+ }
135
+
136
+ // ── Watcher ───────────────────────────────────────────────────────────────
137
+
138
+ _watch() {
139
+ console.log(chalk.green('✔') + ' ' +
140
+ chalk.dim('Watching for changes…')
141
+ );
142
+ const cwd = process.cwd();
143
+ const watchPaths = [
144
+ ...WATCH_DIRS.map(d => path.join(cwd, d)),
145
+ path.join(cwd, '.env'),
146
+ path.join(cwd, '.env.local'),
147
+ ];
148
+
149
+ const watcher = chokidar.watch(watchPaths, {
150
+ persistent: true,
151
+ ignoreInitial: true,
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},
156
+ });
157
+
158
+ watcher.on('all', (event, filePath) => {
159
+ if (WATCH_EXTS.has(path.extname(filePath))) {
160
+ this._scheduleRestart(filePath);
161
+ }
162
+ });
163
+
164
+ this._watchers.push(watcher);
165
+ }
166
+
167
+ _scheduleRestart(changedFile) {
168
+ clearTimeout(this._timer);
169
+ this._timer = setTimeout(() => this._restart(changedFile), DEBOUNCE_MS);
170
+ }
171
+
172
+ _stopWatching() {
173
+ for (const w of this._watchers) {
174
+ try {
175
+ w.close();
176
+ } catch {
177
+ }
178
+ }
179
+ this._watchers = [];
180
+ }
181
+
182
+ // ── Signals ───────────────────────────────────────────────────────────────
183
+
184
+ _handleSignals() {
185
+ const cleanup = () => {
186
+ clearTimeout(this._timer);
187
+ this._stopWatching();
188
+ if (this._child) this._child.kill('SIGTERM');
189
+ process.exit(0);
190
+ };
191
+ process.once('SIGINT', cleanup);
192
+ process.once('SIGTERM', cleanup);
193
+ }
194
+ }
195
+
196
+ // ── Command ────────────────────────────────────────────────────────────────────
6
197
 
7
198
  module.exports = function (program) {
8
- program
9
- .command('serve')
10
- .description('Start the development server')
11
- .option('-p, --port <port>', 'Port to listen on', '3000')
12
- .option('-h, --host <host>', 'Host to bind to', 'localhost')
13
- .action(async (options) => {
14
- const appBootstrap = path.resolve(process.cwd(), 'bootstrap/app.js');
15
-
16
- if (!fs.existsSync(appBootstrap)) {
17
- console.error(chalk.red('\n ✖ No Millas project found in current directory.\n'));
18
- console.log(` Make sure you are inside a Millas project and bootstrap/app.js exists.\n`);
19
- process.exit(1);
20
- }
199
+ program
200
+ .command('serve')
201
+ .description('Start the development server with hot reload')
202
+ .option('-p, --port <port>', 'Port to listen on', '3000')
203
+ .option('-h, --host <host>', 'Host to bind to', 'localhost')
204
+ .option('--no-reload', 'Disable hot reload (run once, like production)')
205
+ .action((options) => {
21
206
 
22
- console.log();
23
- console.log(chalk.cyan(' ⚡ Millas Dev Server'));
24
- console.log(chalk.gray(` Starting on http://${options.host}:${options.port}\n`));
207
+ const restoreAfterPatch = patchConsole(Logger,"SystemOut")
208
+ const appBootstrap = path.resolve(process.cwd(), 'bootstrap/app.js');
25
209
 
26
- process.env.MILLAS_PORT = options.port;
27
- process.env.MILLAS_HOST = options.host;
28
- process.env.NODE_ENV = process.env.NODE_ENV || 'development';
210
+ if (!fs.existsSync(appBootstrap)) {
211
+ process.stderr.write(chalk.red('\n ✖ No Millas project found here.\n'));
212
+ process.stderr.write(chalk.dim(' Make sure bootstrap/app.js exists.\n\n'));
213
+ process.exit(1);
214
+ }
29
215
 
30
- try {
31
- require(appBootstrap);
32
- } catch (err) {
33
- console.error(chalk.red(`\n ✖ Failed to start server: ${err.message}\n`));
34
- console.error(err.stack);
35
- process.exit(1);
36
- }
37
- });
38
- };
39
216
 
40
- // Exported so queue:work can import the pattern
41
- module.exports.requireProject = function(command) {
42
- const path = require('path');
43
- const fs = require('fs-extra');
44
- const bootstrapPath = path.resolve(process.cwd(), 'bootstrap/app.js');
45
- if (!fs.existsSync(bootstrapPath)) {
46
- const chalk = require('chalk');
47
- console.error(chalk.red(`\n ✖ Not inside a Millas project (${command}).\n`));
48
- process.exit(1);
49
- }
217
+ const publicPort = parseInt(options.port, 10) || 3000;
218
+ const publicHost = options.host || 'localhost';
219
+
220
+ const env = {
221
+ NODE_ENV: process.env.APP_ENV || 'development',
222
+ MILLERS_NODE_ENV: true,
223
+ MILLAS_HOST: publicHost,
224
+ MILLAS_PORT: String(publicPort),
225
+ };
226
+
227
+ Object.assign(process.env, env);
228
+ printBanner(publicHost, publicPort);
229
+
230
+ if (options.reload !== false) {
231
+
232
+ new HotReloader(appBootstrap, publicPort, publicHost).start();
233
+ } else {
234
+ try {
235
+ require(appBootstrap);
236
+ } catch (e) {
237
+ console.log("Error starting server: ", +e)
238
+ }
239
+ }
240
+ });
241
+
50
242
  };
243
+
244
+ module.exports.requireProject = function (command) {
245
+ const bootstrapPath = path.resolve(process.cwd(), 'bootstrap/app.js');
246
+ if (!fsnative.existsSync(bootstrapPath)) {
247
+ process.stderr.write(chalk.red(`\n ✖ Not inside a Millas project (${command}).\n\n`));
248
+ process.exit(1);
249
+ }
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;