@c9up/ream 0.2.3 → 0.2.5

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 (71) hide show
  1. package/dist/Application.d.ts +85 -5
  2. package/dist/Application.d.ts.map +1 -1
  3. package/dist/Application.js +171 -21
  4. package/dist/Application.js.map +1 -1
  5. package/dist/ErrorBoundary.d.ts +1 -7
  6. package/dist/ErrorBoundary.d.ts.map +1 -1
  7. package/dist/ErrorBoundary.js +31 -31
  8. package/dist/ErrorBoundary.js.map +1 -1
  9. package/dist/Ignitor.d.ts +15 -46
  10. package/dist/Ignitor.d.ts.map +1 -1
  11. package/dist/Ignitor.js +178 -173
  12. package/dist/Ignitor.js.map +1 -1
  13. package/dist/env/Env.d.ts +6 -0
  14. package/dist/env/Env.d.ts.map +1 -1
  15. package/dist/env/Env.js +6 -0
  16. package/dist/env/Env.js.map +1 -1
  17. package/dist/events/Emitter.d.ts +2 -6
  18. package/dist/events/Emitter.d.ts.map +1 -1
  19. package/dist/events/Emitter.js +45 -42
  20. package/dist/events/Emitter.js.map +1 -1
  21. package/dist/events/native.d.ts +13 -11
  22. package/dist/events/native.d.ts.map +1 -1
  23. package/dist/events/native.js.map +1 -1
  24. package/dist/http/HttpContext.d.ts +1 -1
  25. package/dist/http/HttpContext.d.ts.map +1 -1
  26. package/dist/http/Request.js +0 -13
  27. package/dist/http/Request.js.map +1 -1
  28. package/dist/index.d.ts +3 -2
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +1 -2
  31. package/dist/index.js.map +1 -1
  32. package/dist/native/generated.d.ts +243 -0
  33. package/dist/native/generated.d.ts.map +1 -0
  34. package/dist/native/generated.js +7 -0
  35. package/dist/native/generated.js.map +1 -0
  36. package/dist/session/drivers/CookieDriver.d.ts +1 -1
  37. package/dist/session/drivers/CookieDriver.d.ts.map +1 -1
  38. package/dist/session/drivers/CookieDriver.js +4 -4
  39. package/dist/session/drivers/CookieDriver.js.map +1 -1
  40. package/dist/types/index.d.ts +88 -5
  41. package/dist/types/index.d.ts.map +1 -1
  42. package/dist/types/index.js +8 -3
  43. package/dist/types/index.js.map +1 -1
  44. package/events.win32-x64-msvc.node +0 -0
  45. package/index.win32-x64-msvc.node +0 -0
  46. package/package.json +3 -2
  47. package/scheduler.win32-x64-msvc.node +0 -0
  48. package/scripts/build-napi-types.mjs +68 -0
  49. package/scripts/generate-napi-types.mjs +171 -0
  50. package/src/Application.ts +221 -21
  51. package/src/ErrorBoundary.ts +31 -31
  52. package/src/Ignitor.ts +185 -172
  53. package/src/env/Env.ts +6 -0
  54. package/src/events/Emitter.ts +45 -42
  55. package/src/events/native.ts +21 -14
  56. package/src/http/HttpContext.ts +1 -1
  57. package/src/http/Request.ts +0 -13
  58. package/src/index.ts +2 -2
  59. package/src/native/generated.ts +291 -0
  60. package/src/session/drivers/CookieDriver.ts +4 -4
  61. package/src/types/index.ts +92 -5
  62. package/dist/types/authenticators.d.ts +0 -24
  63. package/dist/types/authenticators.d.ts.map +0 -1
  64. package/dist/types/authenticators.js +0 -2
  65. package/dist/types/authenticators.js.map +0 -1
  66. package/dist/types/container.d.ts +0 -64
  67. package/dist/types/container.d.ts.map +0 -1
  68. package/dist/types/container.js +0 -23
  69. package/dist/types/container.js.map +0 -1
  70. package/src/types/authenticators.ts +0 -24
  71. package/src/types/container.ts +0 -66
package/dist/Ignitor.js CHANGED
@@ -44,30 +44,29 @@ export function defineConfig(config) {
44
44
  * Lifecycle: register → boot → start → ready → shutdown
45
45
  */
46
46
  export class Ignitor {
47
- app;
48
- router;
49
- server;
50
- middleware;
47
+ #app;
48
+ #router;
49
+ #server;
50
+ #middleware;
51
51
  /** Where each data package registers its migration runner. See `MigrationRegistry`. */
52
- migrations;
53
- errorBoundary;
54
- _httpServer;
55
- config;
56
- appRoot;
57
- environment = 'unknown';
58
- reamrc;
59
- providers = [];
60
- errorListeners = [];
61
- phase = 'created';
62
- hotReloadCleanup;
52
+ #migrations;
53
+ #errorBoundary;
54
+ #_httpServer;
55
+ #config;
56
+ #appRoot;
57
+ #reamrc;
58
+ #providers = [];
59
+ #errorListeners = [];
60
+ #phase = 'created';
61
+ #hotReloadCleanup;
63
62
  #shutdownHandle;
64
63
  #host;
65
64
  #console;
66
65
  // Inline configuration (for simple use or testing)
67
- inlineRoutes;
68
- inlineMiddleware = [];
69
- inlineNamedMiddleware = [];
70
- inlineProviderFactories = [];
66
+ #inlineRoutes;
67
+ #inlineMiddleware = [];
68
+ #inlineNamedMiddleware = [];
69
+ #inlineProviderFactories = [];
71
70
  /**
72
71
  * Create the Ignitor.
73
72
  *
@@ -79,46 +78,46 @@ export class Ignitor {
79
78
  */
80
79
  constructor(appRootOrConfig, config) {
81
80
  if (appRootOrConfig instanceof URL) {
82
- this.appRoot = appRootOrConfig;
83
- this.config = config ?? {};
81
+ this.#appRoot = appRootOrConfig;
82
+ this.#config = config ?? {};
84
83
  }
85
84
  else {
86
- this.config = appRootOrConfig ?? {};
85
+ this.#config = appRootOrConfig ?? {};
87
86
  }
88
- this.app = new Application();
89
- this.router = new Router();
90
- this.server = new Server(this.router);
91
- this.middleware = new MiddlewareRegistry();
92
- this.migrations = new MigrationRegistry();
93
- this.errorBoundary = new ErrorBoundary((event) => this.handleError(event), this.isDevMode());
87
+ this.#app = new Application();
88
+ this.#router = new Router();
89
+ this.#server = new Server(this.#router);
90
+ this.#middleware = new MiddlewareRegistry();
91
+ this.#migrations = new MigrationRegistry();
92
+ this.#errorBoundary = new ErrorBoundary((event) => this.#handleError(event), this.isDevMode());
94
93
  // Register framework services in container
95
- this.app.container.singleton('router', () => this.router);
96
- this.app.container.singleton('server', () => this.server);
97
- this.app.container.singleton('middleware', () => this.middleware);
94
+ this.#app.container.singleton('router', () => this.#router);
95
+ this.#app.container.singleton('server', () => this.#server);
96
+ this.#app.container.singleton('middleware', () => this.#middleware);
98
97
  // Always bound, even with no data package: an EMPTY registry is what lets
99
98
  // the CLI tell "this app has no migrations" from "this app predates the
100
99
  // registry" and say something useful in each case.
101
- this.app.container.singleton('migrations', () => this.migrations);
102
- this.app.container.singleton('app', () => this.app);
100
+ this.#app.container.singleton('migrations', () => this.#migrations);
101
+ this.#app.container.singleton('app', () => this.#app);
103
102
  // AdonisJS binds the config store too, so code that only needs settings can
104
103
  // resolve `config` instead of taking the whole application.
105
- this.app.container.singleton('config', () => this.app.config);
104
+ this.#app.container.singleton('config', () => this.#app.config);
106
105
  // The HttpContext CLASS, so an integration provider can extend it the way
107
106
  // AdonisJS does — `HttpContext.getter('view', …)` is how the view layer
108
107
  // attaches `ctx.view`. Exported from the barrel too, but a provider that
109
108
  // must stay agnostic reaches it through the container instead of importing
110
109
  // ream at runtime.
111
- this.app.container.singleton('HttpContext', () => HttpContext);
110
+ this.#app.container.singleton('HttpContext', () => HttpContext);
112
111
  // `appRoot` is the URL passed to `new Ignitor(new URL('../', import.meta.url))`.
113
112
  // Providers resolve it through the container so they can interpret
114
113
  // relative paths in config files (e.g. `pages.root: './resources/pages'`)
115
114
  // against the project root — same convention `modules.path` uses.
116
- if (this.appRoot) {
117
- const root = this.appRoot;
118
- this.app.container.singleton('appRoot', () => root);
115
+ if (this.#appRoot) {
116
+ const root = this.#appRoot;
117
+ this.#app.container.singleton('appRoot', () => root);
119
118
  // Also expose it on the Application for the AdonisJS-style path helpers
120
119
  // (`app.makePath`, `app.configPath`, `app.migrationsPath`, …).
121
- this.app.setAppRoot(root);
120
+ this.#app.setAppRoot(root);
122
121
  }
123
122
  // Encryption / cookie-signing service (AdonisJS `APP_KEY` idiom). Registered
124
123
  // only when APP_KEY is set; HttpContext hands it to Response/Request so
@@ -130,17 +129,17 @@ export class Ignitor {
130
129
  throw new Error('APP_KEY is too short — use at least a 16-character (ideally 32-byte) random key for cookie signing/encryption.');
131
130
  }
132
131
  const signer = new CookieSigner(appKey);
133
- this.app.container.singleton('encryption', () => signer);
132
+ this.#app.container.singleton('encryption', () => signer);
134
133
  // Signed-URL helper (same APP_KEY): the router signs via makeSignedUrl,
135
134
  // HttpContext hands it to the request so hasValidSignature() can verify.
136
135
  const signedUrl = new SignedUrl({ secret: appKey });
137
- this.app.container.singleton('signedUrl', () => signedUrl);
138
- this.router.setSignedUrl(signedUrl);
136
+ this.#app.container.singleton('signedUrl', () => signedUrl);
137
+ this.#router.setSignedUrl(signedUrl);
139
138
  }
140
139
  // Set service singletons so route/kernel files can import them
141
- setApp(this.app);
142
- setRouter(this.router);
143
- setServer(this.server);
140
+ setApp(this.#app);
141
+ setRouter(this.#router);
142
+ setServer(this.#server);
144
143
  }
145
144
  // ─── Configuration ────────────────────────────────────────
146
145
  /**
@@ -148,93 +147,99 @@ export class Ignitor {
148
147
  * Like AdonisJS: .tap((app) => { app.booting(...) })
149
148
  */
150
149
  tap(callback) {
151
- callback(this.app);
150
+ callback(this.#app);
152
151
  return this;
153
152
  }
154
- /** Set the application environment. */
153
+ /**
154
+ * Set the application environment.
155
+ *
156
+ * Stored on the Application, which is where a provider reads it
157
+ * (`app.getEnvironment()`, the AdonisJS shape). The Ignitor keeps the setter
158
+ * because that is where the entry point declares it.
159
+ */
155
160
  setEnvironment(env) {
156
- this.environment = env;
161
+ this.#app.setEnvironment(env);
157
162
  return this;
158
163
  }
159
164
  getEnvironment() {
160
- return this.environment;
165
+ return this.#app.getEnvironment();
161
166
  }
162
167
  /**
163
168
  * Load the reamrc config (equivalent to adonisrc.ts).
164
169
  */
165
170
  useRcFile(reamrc) {
166
- this.reamrc = reamrc;
171
+ this.#reamrc = reamrc;
167
172
  // Hand the app its directory overrides now rather than at boot: a path
168
173
  // helper called from a provider's register() must already see them.
169
- this.app.rcContents(reamrc);
174
+ this.#app.rcContents(reamrc);
170
175
  return this;
171
176
  }
172
177
  // === Inline configuration (simple mode / testing) ===
173
178
  /** Define routes inline (simple mode). */
174
179
  routes(callback) {
175
- this.inlineRoutes = callback;
180
+ this.#inlineRoutes = callback;
176
181
  return this;
177
182
  }
178
183
  /** Add global middleware inline. */
179
184
  use(mw) {
180
- this.inlineMiddleware.push(mw);
185
+ this.#inlineMiddleware.push(mw);
181
186
  return this;
182
187
  }
183
188
  /** Register a named middleware inline. */
184
189
  named(name, mw) {
185
- this.inlineNamedMiddleware.push([name, mw]);
190
+ this.#inlineNamedMiddleware.push([name, mw]);
186
191
  return this;
187
192
  }
188
193
  /** Register a provider inline (for testing or simple apps). */
189
194
  provider(factory) {
190
- this.inlineProviderFactories.push(factory);
195
+ this.#inlineProviderFactories.push(factory);
191
196
  return this;
192
197
  }
193
198
  /** Listen for error events. */
194
199
  onError(listener) {
195
- this.errorListeners.push(listener);
200
+ this.#errorListeners.push(listener);
196
201
  return this;
197
202
  }
198
203
  /** Set a config value. */
199
204
  configure(key, value) {
200
- this.app.config.set(key, value);
205
+ this.#app.config.set(key, value);
201
206
  return this;
202
207
  }
203
208
  // ─── Mode selection ───────────────────────────────────────
204
209
  /** Configure for HTTP server mode. */
205
210
  httpServer() {
206
- this.environment = 'web';
211
+ this.#app.setEnvironment('web');
207
212
  return this;
208
213
  }
209
214
  /** Configure for CLI/console mode. Returns a ConsoleKernel for dispatching commands. */
210
215
  console() {
211
- this.environment = 'console';
216
+ this.#app.setEnvironment('console');
212
217
  return new ConsoleKernel(this);
213
218
  }
214
219
  /** Configure for test mode. */
215
220
  testMode() {
216
- this.environment = 'test';
221
+ this.#app.setEnvironment('test');
217
222
  return this;
218
223
  }
219
224
  // ─── Lifecycle ────────────────────────────────────────────
220
225
  async start() {
221
226
  // Double-start guard: a second start() would re-run phaseRegister and
222
227
  // instantiate + register every reamrc provider a second time.
223
- if (this.phase !== 'created') {
224
- throw new ReamError('IGNITOR_ALREADY_STARTED', `start() called while in phase '${this.phase}' — an Ignitor boots once`, { hint: 'Create a new Ignitor instance instead of restarting this one.' });
228
+ if (this.#phase !== 'created') {
229
+ throw new ReamError('IGNITOR_ALREADY_STARTED', `start() called while in phase '${this.#phase}' — an Ignitor boots once`, { hint: 'Create a new Ignitor instance instead of restarting this one.' });
225
230
  }
226
231
  this.#loadEnvironmentFiles();
227
232
  try {
228
- await this.phaseRegister();
229
- await this.phaseBoot();
230
- await this.phaseStart();
233
+ await this.#phaseRegister();
234
+ await this.#phaseBoot();
235
+ await this.#phaseStart();
231
236
  // Console documents its `consoleApp` service as available once the application has
232
237
  // booted, so the locator is installed here rather than on first use —
233
238
  // `import consoleApp from '@c9up/ream/services/console'` must not throw in a running
234
239
  // app. Only the façade is built (three small modules); loading the
235
240
  // commands themselves stays lazy, inside `consoleApp.boot()`.
236
241
  await this.consoleApp();
237
- await this.phaseReady();
242
+ await this.#phaseReady();
238
243
  }
239
244
  catch (err) {
240
245
  // A throw mid-boot (e.g. a provider ready() failing AFTER the HTTP port
@@ -261,9 +266,9 @@ export class Ignitor {
261
266
  * `process.env` win, so the shell / CI always overrides the files.
262
267
  */
263
268
  #loadEnvironmentFiles() {
264
- if (!this.appRoot)
269
+ if (!this.#appRoot)
265
270
  return;
266
- loadEnvFiles(this.appRoot, { skipEnvLocal: this.environment === 'test' });
271
+ loadEnvFiles(this.#appRoot, { skipEnvLocal: this.#app.getEnvironment() === 'test' });
267
272
  }
268
273
  /**
269
274
  * Give the template engine `@error` / `@errors` / `@inputError` /
@@ -272,11 +277,11 @@ export class Ignitor {
272
277
  * The engine is duck-typed out of the container so ream carries no dependency
273
278
  * on it, and an app with no template layer simply skips this.
274
279
  */
275
- async registerSessionTemplateTags() {
280
+ async #registerSessionTemplateTags() {
276
281
  for (const token of ['inker', 'view']) {
277
- if (!this.app.container.has(token))
282
+ if (!this.#app.container.has(token))
278
283
  continue;
279
- const binding = await this.app.container.resolve(token);
284
+ const binding = await this.#app.container.resolve(token);
280
285
  for (const candidate of [binding, Reflect.get(Object(binding), '_templates')]) {
281
286
  const register = Reflect.get(Object(candidate), 'registerTag');
282
287
  if (typeof register !== 'function')
@@ -290,91 +295,91 @@ export class Ignitor {
290
295
  }
291
296
  }
292
297
  }
293
- async phaseRegister() {
298
+ async #phaseRegister() {
294
299
  // Auto-load config/*.ts files into app.config
295
- await this.autoloadConfig();
300
+ await this.#autoloadConfig();
296
301
  // Load providers from reamrc
297
- if (this.reamrc?.providers) {
298
- for (const providerEntry of this.reamrc.providers) {
302
+ if (this.#reamrc?.providers) {
303
+ for (const providerEntry of this.#reamrc.providers) {
299
304
  const providerImport = typeof providerEntry === 'function' ? providerEntry : providerEntry.file;
300
305
  const env = typeof providerEntry === 'function' ? undefined : providerEntry.environment;
301
306
  // Skip providers not matching current environment
302
- if (env && !env.includes(this.environment))
307
+ if (env && !env.includes(this.#app.getEnvironment()))
303
308
  continue;
304
309
  const mod = await providerImport();
305
310
  const ProviderClass = mod.default;
306
- const instance = new ProviderClass(this.app);
307
- this.providers.push(instance);
308
- this.app.register(instance);
311
+ const instance = new ProviderClass(this.#app);
312
+ this.#providers.push(instance);
313
+ this.#app.register(instance);
309
314
  }
310
315
  }
311
316
  // Register inline providers
312
- for (const factory of this.inlineProviderFactories) {
313
- const instance = factory(this.app);
314
- this.providers.push(instance);
315
- this.app.register(instance);
317
+ for (const factory of this.#inlineProviderFactories) {
318
+ const instance = factory(this.#app);
319
+ this.#providers.push(instance);
320
+ this.#app.register(instance);
316
321
  }
317
- this.phase = 'registered';
322
+ this.#phase = 'registered';
318
323
  }
319
- async phaseBoot() {
320
- await this.app.boot();
321
- this.phase = 'booted';
324
+ async #phaseBoot() {
325
+ await this.#app.boot();
326
+ this.#phase = 'booted';
322
327
  }
323
- async phaseStart() {
328
+ async #phaseStart() {
324
329
  // Import preload files (routes.ts, kernel.ts, etc.)
325
- if (this.reamrc?.preloads) {
326
- for (const preloadEntry of this.reamrc.preloads) {
330
+ if (this.#reamrc?.preloads) {
331
+ for (const preloadEntry of this.#reamrc.preloads) {
327
332
  const preloadImport = typeof preloadEntry === 'function' ? preloadEntry : preloadEntry.file;
328
333
  const env = typeof preloadEntry === 'function'
329
334
  ? undefined
330
335
  : preloadEntry.environment;
331
- if (env && !env.includes(this.environment))
336
+ if (env && !env.includes(this.#app.getEnvironment()))
332
337
  continue;
333
338
  await preloadImport();
334
339
  }
335
340
  }
336
341
  // Auto-load module files (routes.ts, etc.) from modules directory
337
- await this.autoloadModules();
342
+ await this.#autoloadModules();
338
343
  // Apply inline configuration
339
- for (const mw of this.inlineMiddleware) {
340
- this.middleware.use(mw);
344
+ for (const mw of this.#inlineMiddleware) {
345
+ this.#middleware.use(mw);
341
346
  }
342
- for (const [name, mw] of this.inlineNamedMiddleware) {
343
- this.middleware.register(name, mw);
347
+ for (const [name, mw] of this.#inlineNamedMiddleware) {
348
+ this.#middleware.register(name, mw);
344
349
  }
345
- if (this.inlineRoutes) {
346
- this.inlineRoutes(this.router);
350
+ if (this.#inlineRoutes) {
351
+ this.#inlineRoutes(this.#router);
347
352
  }
348
353
  // Call start() on providers
349
- for (const provider of this.providers) {
354
+ for (const provider of this.#providers) {
350
355
  await callProviderPhase(provider, 'start');
351
356
  }
352
- this.phase = 'started';
357
+ this.#phase = 'started';
353
358
  }
354
- async phaseReady() {
359
+ async #phaseReady() {
355
360
  // Boot the Server (resolves lazy error handler etc.)
356
- await this.server.boot();
361
+ await this.#server.boot();
357
362
  // Start HTTP server if in web mode
358
- if (this.environment === 'web' && this.config.serverFactory) {
363
+ if (this.#app.getEnvironment() === 'web' && this.#config.serverFactory) {
359
364
  // Build the HttpKernel with server middleware + router middleware.
360
365
  // The streaming backend is resolved lazily — the HyperServer is
361
366
  // created a few lines down, after the kernel — so the kernel
362
- // closes over the `this._httpServer` slot and reads it on every
367
+ // closes over the `this.#_httpServer` slot and reads it on every
363
368
  // request through the factory.
364
369
  const kernel = createHttpKernel({
365
- router: this.router,
366
- middleware: this.middleware,
367
- container: this.app.container,
368
- exceptionHandler: this.server.getErrorHandler() ?? new ExceptionHandler(!this.app.inProduction),
369
- serverMiddleware: this.server.getServerMiddleware(),
370
- routerMiddleware: this.router.getRouterMiddleware(),
370
+ router: this.#router,
371
+ middleware: this.#middleware,
372
+ container: this.#app.container,
373
+ exceptionHandler: this.#server.getErrorHandler() ?? new ExceptionHandler(!this.#app.inProduction),
374
+ serverMiddleware: this.#server.getServerMiddleware(),
375
+ routerMiddleware: this.#router.getRouterMiddleware(),
371
376
  onError: (error, ctx) => {
372
- this.errorBoundary.serviceError('HttpKernel', error, ctx.id);
377
+ this.#errorBoundary.serviceError('HttpKernel', error, ctx.id);
373
378
  },
374
379
  // AdonisJS `config/app.ts` → `http: { allowMethodSpoofing: true }`.
375
- allowMethodSpoofing: this.app.config.get('app.http.allowMethodSpoofing') === true,
380
+ allowMethodSpoofing: this.#app.config.get('app.http.allowMethodSpoofing') === true,
376
381
  streamBackend: () => {
377
- const server = this._httpServer;
382
+ const server = this.#_httpServer;
378
383
  if (server &&
379
384
  typeof server.registerStream === 'function' &&
380
385
  typeof server.writeStream === 'function' &&
@@ -393,33 +398,33 @@ export class Ignitor {
393
398
  return undefined;
394
399
  },
395
400
  });
396
- const desiredPort = this.config.port ?? 3000;
401
+ const desiredPort = this.#config.port ?? 3000;
397
402
  // Port-scan fallback (+1..+19) is a DEV convenience only. In production
398
403
  // a silent drift to :3001 while the LB targets :3000 is an outage —
399
404
  // bind the configured port and let EADDRINUSE fail loudly. The probe
400
405
  // also has an inherent TOCTOU; acceptable in dev, not in prod.
401
- const availablePort = this.app.inProduction
406
+ const availablePort = this.#app.inProduction
402
407
  ? desiredPort
403
408
  : await findAvailablePort(desiredPort);
404
409
  this.#host =
405
- this.config.host ?? process.env.HOST ?? (this.app.inProduction ? '0.0.0.0' : 'localhost');
406
- this._httpServer = this.config.serverFactory(availablePort, this.#host);
407
- this._httpServer.onRequest(kernel);
410
+ this.#config.host ?? process.env.HOST ?? (this.#app.inProduction ? '0.0.0.0' : 'localhost');
411
+ this.#_httpServer = this.#config.serverFactory(availablePort, this.#host);
412
+ this.#_httpServer.onRequest(kernel);
408
413
  // Pre-resolve client IPs in Rust from the trusted-proxy CIDRs before
409
414
  // listen. Security filtering itself lives in @c9up/blackhole.
410
- const trustedProxies = this.server.getTrustedProxies();
415
+ const trustedProxies = this.#server.getTrustedProxies();
411
416
  if (trustedProxies.length > 0 &&
412
- typeof this._httpServer.configureTrustedProxies === 'function') {
413
- this._httpServer.configureTrustedProxies([...trustedProxies]);
417
+ typeof this.#_httpServer.configureTrustedProxies === 'function') {
418
+ this.#_httpServer.configureTrustedProxies([...trustedProxies]);
414
419
  }
415
- await this._httpServer.listen();
420
+ await this.#_httpServer.listen();
416
421
  // Wire OS-signal graceful shutdown. Without it the process never closes
417
422
  // the HTTP server on SIGTERM/SIGINT, so live keep-alive / SSE sockets keep
418
423
  // the event loop alive — a `ream dev` restart or Ctrl+C ends in a watcher
419
424
  // force-kill, and an orchestrator's rolling deploy drops in-flight work.
420
425
  // onShutdown = this.stop(), which closes the port (aborting connections),
421
426
  // shuts providers down (DB pools), and releases the locators.
422
- if (this.config.gracefulShutdown !== false) {
427
+ if (this.#config.gracefulShutdown !== false) {
423
428
  this.#shutdownHandle = installGracefulShutdown({
424
429
  onShutdown: () => this.stop(),
425
430
  logger: {
@@ -429,26 +434,26 @@ export class Ignitor {
429
434
  });
430
435
  }
431
436
  }
432
- else if (this.environment === 'web' && !this.config.serverFactory) {
437
+ else if (this.#app.getEnvironment() === 'web' && !this.#config.serverFactory) {
433
438
  throw new ReamError('IGNITOR_NO_SERVER_FACTORY', 'httpServer() requires a serverFactory in config', {
434
439
  hint: 'Example: new Ignitor({ serverFactory: (port, host) => new HyperServer(port, host) })',
435
440
  });
436
441
  }
437
442
  // Install error boundary
438
- this.errorBoundary.install();
443
+ this.#errorBoundary.install();
439
444
  // Publish the flash-message tags to the template engine, if one is
440
445
  // installed. After boot(), so the engine's own provider has bound it;
441
446
  // before ready(), so a provider that pre-renders finds the tags in place.
442
- await this.registerSessionTemplateTags();
447
+ await this.#registerSessionTemplateTags();
443
448
  // Call ready() on providers
444
- for (const provider of this.providers) {
449
+ for (const provider of this.#providers) {
445
450
  await callProviderPhase(provider, 'ready');
446
451
  }
447
452
  // Core domain event: the app finished booting (all providers ready).
448
453
  // Emitted once through the bus when events are wired — zero hot-path cost.
449
- if (this.app.container.has('events')) {
450
- const bus = await this.app.container.resolve('events');
451
- bus.emit('app:ready', { environment: this.environment });
454
+ if (this.#app.container.has('events')) {
455
+ const bus = await this.#app.container.resolve('events');
456
+ bus.emit('app:ready', { environment: this.#app.getEnvironment() });
452
457
  }
453
458
  // Dev-mode change watcher. IMPORTANT: this does NOT attempt an in-process
454
459
  // reload. The previous implementation cleared the router + service registry
@@ -462,11 +467,11 @@ export class Ignitor {
462
467
  // the Ignitor without a supervisor. A future true HMR needs a loader-hook
463
468
  // (hot-hook style) that can invalidate the ESM cache — plug it in here.
464
469
  if (this.isDevMode()) {
465
- const watchDirs = this.config.watchDirs ?? ['app', 'start'];
466
- this.hotReloadCleanup = startHotReload({
470
+ const watchDirs = this.#config.watchDirs ?? ['app', 'start'];
471
+ this.#hotReloadCleanup = startHotReload({
467
472
  watchDirs,
468
473
  onReload: () => {
469
- this.handleError({
474
+ this.#handleError({
470
475
  type: 'system.info',
471
476
  source: 'HotReload',
472
477
  message: 'File change detected — a process restart is required to apply it (`ream dev` restarts automatically).',
@@ -475,7 +480,7 @@ export class Ignitor {
475
480
  });
476
481
  },
477
482
  logger: {
478
- info: (msg) => this.handleError({
483
+ info: (msg) => this.#handleError({
479
484
  type: 'system.info',
480
485
  source: 'HotReload',
481
486
  message: msg,
@@ -485,19 +490,19 @@ export class Ignitor {
485
490
  },
486
491
  });
487
492
  }
488
- this.phase = 'ready';
493
+ this.#phase = 'ready';
489
494
  }
490
495
  /**
491
496
  * Auto-load config/*.ts files into app.config store.
492
497
  * Each file's default export is stored under its filename (without extension).
493
498
  * e.g. config/database.ts → app.config.get('database')
494
499
  */
495
- async autoloadConfig() {
500
+ async #autoloadConfig() {
496
501
  const { readdirSync, existsSync } = await import('node:fs');
497
502
  const { join, basename } = await import('node:path');
498
503
  const { fileURLToPath, pathToFileURL } = await import('node:url');
499
- const configDir = this.appRoot
500
- ? join(fileURLToPath(this.appRoot), 'config')
504
+ const configDir = this.#appRoot
505
+ ? join(fileURLToPath(this.#appRoot), 'config')
501
506
  : join(process.cwd(), 'config');
502
507
  if (!existsSync(configDir))
503
508
  return;
@@ -518,15 +523,15 @@ export class Ignitor {
518
523
  // is what AdonisJS does (`fsImportAll`). Spread rather than stored as-is:
519
524
  // an ESM namespace is sealed and null-prototype, so a later merge into
520
525
  // this entry would fail on it.
521
- this.app.config.set(key, 'default' in mod ? mod.default : { ...mod });
526
+ this.#app.config.set(key, 'default' in mod ? mod.default : { ...mod });
522
527
  }
523
528
  }
524
529
  /**
525
530
  * Auto-load module files (routes.ts, etc.) from the modules directory.
526
531
  * Scans modules.path for subdirectories and imports matching files.
527
532
  */
528
- async autoloadModules() {
529
- const modulesConfig = this.reamrc?.modules;
533
+ async #autoloadModules() {
534
+ const modulesConfig = this.#reamrc?.modules;
530
535
  if (!modulesConfig?.path)
531
536
  return;
532
537
  const { readdirSync, existsSync } = await import('node:fs');
@@ -534,8 +539,8 @@ export class Ignitor {
534
539
  const { fileURLToPath } = await import('node:url');
535
540
  const { pathToFileURL } = await import('node:url');
536
541
  // Resolve modules path relative to app root or cwd
537
- const basePath = this.appRoot
538
- ? join(fileURLToPath(this.appRoot), modulesConfig.path)
542
+ const basePath = this.#appRoot
543
+ ? join(fileURLToPath(this.#appRoot), modulesConfig.path)
539
544
  : resolve(modulesConfig.path);
540
545
  if (!existsSync(basePath))
541
546
  return;
@@ -592,22 +597,22 @@ export class Ignitor {
592
597
  this.#shutdownHandle = undefined;
593
598
  });
594
599
  await attempt(() => {
595
- if (this.hotReloadCleanup)
596
- this.hotReloadCleanup();
600
+ if (this.#hotReloadCleanup)
601
+ this.#hotReloadCleanup();
597
602
  });
598
603
  await attempt(async () => {
599
- if (this._httpServer)
600
- await this._httpServer.close();
604
+ if (this.#_httpServer)
605
+ await this.#_httpServer.close();
601
606
  });
602
- await attempt(() => this.errorBoundary.uninstall());
603
- await attempt(() => this.app.shutdown());
607
+ await attempt(() => this.#errorBoundary.uninstall());
608
+ await attempt(() => this.#app.shutdown());
604
609
  // Release the process-wide service locators (ownership-guarded): a stopped
605
610
  // app must not keep its container/router reachable through the module
606
611
  // singletons, and must not clobber a newer Ignitor's bindings.
607
- clearApp(this.app);
608
- clearRouter(this.router);
609
- clearServer(this.server);
610
- this.phase = 'shutdown';
612
+ clearApp(this.#app);
613
+ clearRouter(this.#router);
614
+ clearServer(this.#server);
615
+ this.#phase = 'shutdown';
611
616
  if (errors.length === 1)
612
617
  throw errors[0];
613
618
  if (errors.length > 1) {
@@ -616,7 +621,7 @@ export class Ignitor {
616
621
  }
617
622
  // ─── Accessors ────────────────────────────────────────────
618
623
  async port() {
619
- return this._httpServer ? this._httpServer.port() : 0;
624
+ return this.#_httpServer ? this.#_httpServer.port() : 0;
620
625
  }
621
626
  /**
622
627
  * The address the HTTP server was bound to. Resolved at start; undefined
@@ -628,18 +633,18 @@ export class Ignitor {
628
633
  return this.#host;
629
634
  }
630
635
  getApp() {
631
- return this.app;
636
+ return this.#app;
632
637
  }
633
638
  /**
634
639
  * The application root passed to `new Ignitor(new URL('../', import.meta.url))`.
635
640
  * Undefined for inline/test ignitors created without one.
636
641
  */
637
642
  getAppRoot() {
638
- return this.appRoot;
643
+ return this.#appRoot;
639
644
  }
640
645
  /** Has the application already been started? */
641
646
  isStarted() {
642
- return this.phase === 'started' || this.phase === 'ready';
647
+ return this.#phase === 'started' || this.#phase === 'ready';
643
648
  }
644
649
  /**
645
650
  * The `consoleApp` façade — the programmatic console.
@@ -658,22 +663,22 @@ export class Ignitor {
658
663
  const consoleApp = new Console({ kernel, load });
659
664
  this.#console = consoleApp;
660
665
  setConsole(consoleApp);
661
- this.app.container.singleton('console', () => consoleApp);
666
+ this.#app.container.singleton('console', () => consoleApp);
662
667
  return consoleApp;
663
668
  }
664
669
  /** The loaded rc file, if `useRcFile()` was called. */
665
670
  getRcFile() {
666
- return this.reamrc;
671
+ return this.#reamrc;
667
672
  }
668
673
  /** The custom module importer, when the app provided one. */
669
674
  getImporter() {
670
- return this.config.importer;
675
+ return this.#config.importer;
671
676
  }
672
677
  getRouter() {
673
- return this.router;
678
+ return this.#router;
674
679
  }
675
680
  getServer() {
676
- return this.server;
681
+ return this.#server;
677
682
  }
678
683
  /**
679
684
  * Get the kernel callback (for serverless / testing). Memoized — building a
@@ -681,24 +686,24 @@ export class Ignitor {
681
686
  */
682
687
  getKernel() {
683
688
  this.#kernel ??= createHttpKernel({
684
- router: this.router,
685
- middleware: this.middleware,
686
- container: this.app.container,
687
- exceptionHandler: this.server.getErrorHandler() ?? new ExceptionHandler(!this.app.inProduction),
688
- serverMiddleware: this.server.getServerMiddleware(),
689
- routerMiddleware: this.router.getRouterMiddleware(),
689
+ router: this.#router,
690
+ middleware: this.#middleware,
691
+ container: this.#app.container,
692
+ exceptionHandler: this.#server.getErrorHandler() ?? new ExceptionHandler(!this.#app.inProduction),
693
+ serverMiddleware: this.#server.getServerMiddleware(),
694
+ routerMiddleware: this.#router.getRouterMiddleware(),
690
695
  });
691
696
  return this.#kernel;
692
697
  }
693
698
  #kernel;
694
699
  isDevMode() {
695
- return this.app.inDev;
700
+ return this.#app.inDev;
696
701
  }
697
702
  getPhase() {
698
- return this.phase;
703
+ return this.#phase;
699
704
  }
700
- handleError(event) {
701
- for (const listener of this.errorListeners) {
705
+ #handleError(event) {
706
+ for (const listener of this.#errorListeners) {
702
707
  try {
703
708
  listener(event);
704
709
  }
@@ -713,7 +718,7 @@ export class Ignitor {
713
718
  * shutdown logger so drain progress / timeouts are observable.
714
719
  */
715
720
  #emitSystemInfo(source, message, kind) {
716
- this.handleError({
721
+ this.#handleError({
717
722
  type: kind === 'error' ? 'system.error' : 'system.info',
718
723
  source,
719
724
  message,