@c9up/ream 0.2.4 → 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 (50) 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 +0 -6
  18. package/dist/events/Emitter.d.ts.map +1 -1
  19. package/dist/events/Emitter.js +42 -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/index.d.ts +3 -2
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +1 -2
  27. package/dist/index.js.map +1 -1
  28. package/dist/native/generated.d.ts +243 -0
  29. package/dist/native/generated.d.ts.map +1 -0
  30. package/dist/native/generated.js +7 -0
  31. package/dist/native/generated.js.map +1 -0
  32. package/dist/session/drivers/CookieDriver.d.ts +1 -1
  33. package/dist/session/drivers/CookieDriver.d.ts.map +1 -1
  34. package/dist/session/drivers/CookieDriver.js +4 -4
  35. package/dist/session/drivers/CookieDriver.js.map +1 -1
  36. package/events.win32-x64-msvc.node +0 -0
  37. package/index.win32-x64-msvc.node +0 -0
  38. package/package.json +3 -2
  39. package/scheduler.win32-x64-msvc.node +0 -0
  40. package/scripts/build-napi-types.mjs +68 -0
  41. package/scripts/generate-napi-types.mjs +171 -0
  42. package/src/Application.ts +221 -21
  43. package/src/ErrorBoundary.ts +31 -31
  44. package/src/Ignitor.ts +185 -172
  45. package/src/env/Env.ts +6 -0
  46. package/src/events/Emitter.ts +42 -42
  47. package/src/events/native.ts +21 -14
  48. package/src/index.ts +2 -2
  49. package/src/native/generated.ts +291 -0
  50. package/src/session/drivers/CookieDriver.ts +4 -4
package/src/Ignitor.ts CHANGED
@@ -46,7 +46,15 @@ import { clearServer, setServer } from './services/server.js'
46
46
  import { registerSessionTemplateTags } from './session/templateTags.js'
47
47
 
48
48
  /** Application environment. */
49
- export type AppEnvironment = 'web' | 'console' | 'test' | 'unknown'
49
+ /**
50
+ * Re-exported from `Application`, which is where it is declared: the
51
+ * environment belongs to the application (a provider reads
52
+ * `app.getEnvironment()`), and the Ignitor is only where the entry point
53
+ * declares it. Two declarations would be two types that happen to look alike.
54
+ */
55
+ export type { AppEnvironment } from './Application.js'
56
+
57
+ import type { AppEnvironment } from './Application.js'
50
58
 
51
59
  /**
52
60
  * Reamrc config — like AdonisJS adonisrc.ts with defineConfig().
@@ -202,31 +210,30 @@ export interface IgnitorConfig {
202
210
  * Lifecycle: register → boot → start → ready → shutdown
203
211
  */
204
212
  export class Ignitor {
205
- private app: Application
206
- private router: Router
207
- private server: Server
208
- private middleware: MiddlewareRegistry
213
+ #app: Application
214
+ #router: Router
215
+ #server: Server
216
+ #middleware: MiddlewareRegistry
209
217
  /** Where each data package registers its migration runner. See `MigrationRegistry`. */
210
- private migrations: MigrationRegistry
211
- private errorBoundary: ErrorBoundary
212
- private _httpServer?: HyperServerLike
213
- private config: IgnitorConfig
214
- private appRoot?: URL
215
- private environment: AppEnvironment = 'unknown'
216
- private reamrc?: ReamrcConfig
217
- private providers: ProviderContract[] = []
218
- private errorListeners: Array<(event: ErrorEvent) => void> = []
219
- private phase: 'created' | 'registered' | 'booted' | 'started' | 'ready' | 'shutdown' = 'created'
220
- private hotReloadCleanup?: () => void
218
+ #migrations: MigrationRegistry
219
+ #errorBoundary: ErrorBoundary
220
+ #_httpServer?: HyperServerLike
221
+ #config: IgnitorConfig
222
+ #appRoot?: URL
223
+ #reamrc?: ReamrcConfig
224
+ #providers: ProviderContract[] = []
225
+ #errorListeners: Array<(event: ErrorEvent) => void> = []
226
+ #phase: 'created' | 'registered' | 'booted' | 'started' | 'ready' | 'shutdown' = 'created'
227
+ #hotReloadCleanup?: () => void
221
228
  #shutdownHandle?: ShutdownHandle
222
229
  #host?: string
223
230
  #console?: Console
224
231
 
225
232
  // Inline configuration (for simple use or testing)
226
- private inlineRoutes?: (router: Router) => void
227
- private inlineMiddleware: MiddlewareFunction[] = []
228
- private inlineNamedMiddleware: Array<[string, MiddlewareFunction]> = []
229
- private inlineProviderFactories: Array<(app: Application) => ProviderContract> = []
233
+ #inlineRoutes?: (router: Router) => void
234
+ #inlineMiddleware: MiddlewareFunction[] = []
235
+ #inlineNamedMiddleware: Array<[string, MiddlewareFunction]> = []
236
+ #inlineProviderFactories: Array<(app: Application) => ProviderContract> = []
230
237
 
231
238
  /**
232
239
  * Create the Ignitor.
@@ -239,47 +246,47 @@ export class Ignitor {
239
246
  */
240
247
  constructor(appRootOrConfig?: URL | IgnitorConfig, config?: IgnitorConfig) {
241
248
  if (appRootOrConfig instanceof URL) {
242
- this.appRoot = appRootOrConfig
243
- this.config = config ?? {}
249
+ this.#appRoot = appRootOrConfig
250
+ this.#config = config ?? {}
244
251
  } else {
245
- this.config = appRootOrConfig ?? {}
252
+ this.#config = appRootOrConfig ?? {}
246
253
  }
247
254
 
248
- this.app = new Application()
249
- this.router = new Router()
250
- this.server = new Server(this.router)
251
- this.middleware = new MiddlewareRegistry()
252
- this.migrations = new MigrationRegistry()
253
- this.errorBoundary = new ErrorBoundary((event) => this.handleError(event), this.isDevMode())
255
+ this.#app = new Application()
256
+ this.#router = new Router()
257
+ this.#server = new Server(this.#router)
258
+ this.#middleware = new MiddlewareRegistry()
259
+ this.#migrations = new MigrationRegistry()
260
+ this.#errorBoundary = new ErrorBoundary((event) => this.#handleError(event), this.isDevMode())
254
261
 
255
262
  // Register framework services in container
256
- this.app.container.singleton('router', () => this.router)
257
- this.app.container.singleton('server', () => this.server)
258
- this.app.container.singleton('middleware', () => this.middleware)
263
+ this.#app.container.singleton('router', () => this.#router)
264
+ this.#app.container.singleton('server', () => this.#server)
265
+ this.#app.container.singleton('middleware', () => this.#middleware)
259
266
  // Always bound, even with no data package: an EMPTY registry is what lets
260
267
  // the CLI tell "this app has no migrations" from "this app predates the
261
268
  // registry" and say something useful in each case.
262
- this.app.container.singleton('migrations', () => this.migrations)
263
- this.app.container.singleton('app', () => this.app)
269
+ this.#app.container.singleton('migrations', () => this.#migrations)
270
+ this.#app.container.singleton('app', () => this.#app)
264
271
  // AdonisJS binds the config store too, so code that only needs settings can
265
272
  // resolve `config` instead of taking the whole application.
266
- this.app.container.singleton('config', () => this.app.config)
273
+ this.#app.container.singleton('config', () => this.#app.config)
267
274
  // The HttpContext CLASS, so an integration provider can extend it the way
268
275
  // AdonisJS does — `HttpContext.getter('view', …)` is how the view layer
269
276
  // attaches `ctx.view`. Exported from the barrel too, but a provider that
270
277
  // must stay agnostic reaches it through the container instead of importing
271
278
  // ream at runtime.
272
- this.app.container.singleton('HttpContext', () => HttpContext)
279
+ this.#app.container.singleton('HttpContext', () => HttpContext)
273
280
  // `appRoot` is the URL passed to `new Ignitor(new URL('../', import.meta.url))`.
274
281
  // Providers resolve it through the container so they can interpret
275
282
  // relative paths in config files (e.g. `pages.root: './resources/pages'`)
276
283
  // against the project root — same convention `modules.path` uses.
277
- if (this.appRoot) {
278
- const root = this.appRoot
279
- this.app.container.singleton('appRoot', () => root)
284
+ if (this.#appRoot) {
285
+ const root = this.#appRoot
286
+ this.#app.container.singleton('appRoot', () => root)
280
287
  // Also expose it on the Application for the AdonisJS-style path helpers
281
288
  // (`app.makePath`, `app.configPath`, `app.migrationsPath`, …).
282
- this.app.setAppRoot(root)
289
+ this.#app.setAppRoot(root)
283
290
  }
284
291
 
285
292
  // Encryption / cookie-signing service (AdonisJS `APP_KEY` idiom). Registered
@@ -294,18 +301,18 @@ export class Ignitor {
294
301
  )
295
302
  }
296
303
  const signer = new CookieSigner(appKey)
297
- this.app.container.singleton('encryption', () => signer)
304
+ this.#app.container.singleton('encryption', () => signer)
298
305
  // Signed-URL helper (same APP_KEY): the router signs via makeSignedUrl,
299
306
  // HttpContext hands it to the request so hasValidSignature() can verify.
300
307
  const signedUrl = new SignedUrl({ secret: appKey })
301
- this.app.container.singleton('signedUrl', () => signedUrl)
302
- this.router.setSignedUrl(signedUrl)
308
+ this.#app.container.singleton('signedUrl', () => signedUrl)
309
+ this.#router.setSignedUrl(signedUrl)
303
310
  }
304
311
 
305
312
  // Set service singletons so route/kernel files can import them
306
- setApp(this.app)
307
- setRouter(this.router)
308
- setServer(this.server)
313
+ setApp(this.#app)
314
+ setRouter(this.#router)
315
+ setServer(this.#server)
309
316
  }
310
317
 
311
318
  // ─── Configuration ────────────────────────────────────────
@@ -315,28 +322,34 @@ export class Ignitor {
315
322
  * Like AdonisJS: .tap((app) => { app.booting(...) })
316
323
  */
317
324
  tap(callback: (app: Application) => void): this {
318
- callback(this.app)
325
+ callback(this.#app)
319
326
  return this
320
327
  }
321
328
 
322
- /** Set the application environment. */
329
+ /**
330
+ * Set the application environment.
331
+ *
332
+ * Stored on the Application, which is where a provider reads it
333
+ * (`app.getEnvironment()`, the AdonisJS shape). The Ignitor keeps the setter
334
+ * because that is where the entry point declares it.
335
+ */
323
336
  setEnvironment(env: AppEnvironment): this {
324
- this.environment = env
337
+ this.#app.setEnvironment(env)
325
338
  return this
326
339
  }
327
340
 
328
341
  getEnvironment(): AppEnvironment {
329
- return this.environment
342
+ return this.#app.getEnvironment()
330
343
  }
331
344
 
332
345
  /**
333
346
  * Load the reamrc config (equivalent to adonisrc.ts).
334
347
  */
335
348
  useRcFile(reamrc: ReamrcConfig): this {
336
- this.reamrc = reamrc
349
+ this.#reamrc = reamrc
337
350
  // Hand the app its directory overrides now rather than at boot: a path
338
351
  // helper called from a provider's register() must already see them.
339
- this.app.rcContents(reamrc)
352
+ this.#app.rcContents(reamrc)
340
353
  return this
341
354
  }
342
355
 
@@ -344,37 +357,37 @@ export class Ignitor {
344
357
 
345
358
  /** Define routes inline (simple mode). */
346
359
  routes(callback: (router: Router) => void): this {
347
- this.inlineRoutes = callback
360
+ this.#inlineRoutes = callback
348
361
  return this
349
362
  }
350
363
 
351
364
  /** Add global middleware inline. */
352
365
  use(mw: MiddlewareFunction): this {
353
- this.inlineMiddleware.push(mw)
366
+ this.#inlineMiddleware.push(mw)
354
367
  return this
355
368
  }
356
369
 
357
370
  /** Register a named middleware inline. */
358
371
  named(name: string, mw: MiddlewareFunction): this {
359
- this.inlineNamedMiddleware.push([name, mw])
372
+ this.#inlineNamedMiddleware.push([name, mw])
360
373
  return this
361
374
  }
362
375
 
363
376
  /** Register a provider inline (for testing or simple apps). */
364
377
  provider(factory: (app: Application) => ProviderContract): this {
365
- this.inlineProviderFactories.push(factory)
378
+ this.#inlineProviderFactories.push(factory)
366
379
  return this
367
380
  }
368
381
 
369
382
  /** Listen for error events. */
370
383
  onError(listener: (event: ErrorEvent) => void): this {
371
- this.errorListeners.push(listener)
384
+ this.#errorListeners.push(listener)
372
385
  return this
373
386
  }
374
387
 
375
388
  /** Set a config value. */
376
389
  configure(key: string, value: unknown): this {
377
- this.app.config.set(key, value)
390
+ this.#app.config.set(key, value)
378
391
  return this
379
392
  }
380
393
 
@@ -382,19 +395,19 @@ export class Ignitor {
382
395
 
383
396
  /** Configure for HTTP server mode. */
384
397
  httpServer(): this {
385
- this.environment = 'web'
398
+ this.#app.setEnvironment('web')
386
399
  return this
387
400
  }
388
401
 
389
402
  /** Configure for CLI/console mode. Returns a ConsoleKernel for dispatching commands. */
390
403
  console(): ConsoleKernel {
391
- this.environment = 'console'
404
+ this.#app.setEnvironment('console')
392
405
  return new ConsoleKernel(this)
393
406
  }
394
407
 
395
408
  /** Configure for test mode. */
396
409
  testMode(): this {
397
- this.environment = 'test'
410
+ this.#app.setEnvironment('test')
398
411
  return this
399
412
  }
400
413
 
@@ -403,25 +416,25 @@ export class Ignitor {
403
416
  async start(): Promise<Ignitor> {
404
417
  // Double-start guard: a second start() would re-run phaseRegister and
405
418
  // instantiate + register every reamrc provider a second time.
406
- if (this.phase !== 'created') {
419
+ if (this.#phase !== 'created') {
407
420
  throw new ReamError(
408
421
  'IGNITOR_ALREADY_STARTED',
409
- `start() called while in phase '${this.phase}' — an Ignitor boots once`,
422
+ `start() called while in phase '${this.#phase}' — an Ignitor boots once`,
410
423
  { hint: 'Create a new Ignitor instance instead of restarting this one.' },
411
424
  )
412
425
  }
413
426
  this.#loadEnvironmentFiles()
414
427
  try {
415
- await this.phaseRegister()
416
- await this.phaseBoot()
417
- await this.phaseStart()
428
+ await this.#phaseRegister()
429
+ await this.#phaseBoot()
430
+ await this.#phaseStart()
418
431
  // Console documents its `consoleApp` service as available once the application has
419
432
  // booted, so the locator is installed here rather than on first use —
420
433
  // `import consoleApp from '@c9up/ream/services/console'` must not throw in a running
421
434
  // app. Only the façade is built (three small modules); loading the
422
435
  // commands themselves stays lazy, inside `consoleApp.boot()`.
423
436
  await this.consoleApp()
424
- await this.phaseReady()
437
+ await this.#phaseReady()
425
438
  } catch (err) {
426
439
  // A throw mid-boot (e.g. a provider ready() failing AFTER the HTTP port
427
440
  // is bound) must release everything the partial boot opened — port,
@@ -447,8 +460,8 @@ export class Ignitor {
447
460
  * `process.env` win, so the shell / CI always overrides the files.
448
461
  */
449
462
  #loadEnvironmentFiles(): void {
450
- if (!this.appRoot) return
451
- loadEnvFiles(this.appRoot, { skipEnvLocal: this.environment === 'test' })
463
+ if (!this.#appRoot) return
464
+ loadEnvFiles(this.#appRoot, { skipEnvLocal: this.#app.getEnvironment() === 'test' })
452
465
  }
453
466
 
454
467
  /**
@@ -458,10 +471,10 @@ export class Ignitor {
458
471
  * The engine is duck-typed out of the container so ream carries no dependency
459
472
  * on it, and an app with no template layer simply skips this.
460
473
  */
461
- private async registerSessionTemplateTags(): Promise<void> {
474
+ async #registerSessionTemplateTags(): Promise<void> {
462
475
  for (const token of ['inker', 'view']) {
463
- if (!this.app.container.has(token)) continue
464
- const binding: unknown = await this.app.container.resolve(token)
476
+ if (!this.#app.container.has(token)) continue
477
+ const binding: unknown = await this.#app.container.resolve(token)
465
478
  for (const candidate of [binding, Reflect.get(Object(binding), '_templates')]) {
466
479
  const register = Reflect.get(Object(candidate), 'registerTag')
467
480
  if (typeof register !== 'function') continue
@@ -475,106 +488,106 @@ export class Ignitor {
475
488
  }
476
489
  }
477
490
 
478
- private async phaseRegister(): Promise<void> {
491
+ async #phaseRegister(): Promise<void> {
479
492
  // Auto-load config/*.ts files into app.config
480
- await this.autoloadConfig()
493
+ await this.#autoloadConfig()
481
494
 
482
495
  // Load providers from reamrc
483
- if (this.reamrc?.providers) {
484
- for (const providerEntry of this.reamrc.providers) {
496
+ if (this.#reamrc?.providers) {
497
+ for (const providerEntry of this.#reamrc.providers) {
485
498
  const providerImport =
486
499
  typeof providerEntry === 'function' ? providerEntry : providerEntry.file
487
500
  const env = typeof providerEntry === 'function' ? undefined : providerEntry.environment
488
501
 
489
502
  // Skip providers not matching current environment
490
- if (env && !env.includes(this.environment)) continue
503
+ if (env && !env.includes(this.#app.getEnvironment())) continue
491
504
 
492
505
  const mod = await providerImport()
493
506
  const ProviderClass = mod.default
494
- const instance = new ProviderClass(this.app)
495
- this.providers.push(instance)
496
- this.app.register(instance)
507
+ const instance = new ProviderClass(this.#app)
508
+ this.#providers.push(instance)
509
+ this.#app.register(instance)
497
510
  }
498
511
  }
499
512
 
500
513
  // Register inline providers
501
- for (const factory of this.inlineProviderFactories) {
502
- const instance = factory(this.app)
503
- this.providers.push(instance)
504
- this.app.register(instance)
514
+ for (const factory of this.#inlineProviderFactories) {
515
+ const instance = factory(this.#app)
516
+ this.#providers.push(instance)
517
+ this.#app.register(instance)
505
518
  }
506
519
 
507
- this.phase = 'registered'
520
+ this.#phase = 'registered'
508
521
  }
509
522
 
510
- private async phaseBoot(): Promise<void> {
511
- await this.app.boot()
512
- this.phase = 'booted'
523
+ async #phaseBoot(): Promise<void> {
524
+ await this.#app.boot()
525
+ this.#phase = 'booted'
513
526
  }
514
527
 
515
- private async phaseStart(): Promise<void> {
528
+ async #phaseStart(): Promise<void> {
516
529
  // Import preload files (routes.ts, kernel.ts, etc.)
517
- if (this.reamrc?.preloads) {
518
- for (const preloadEntry of this.reamrc.preloads) {
530
+ if (this.#reamrc?.preloads) {
531
+ for (const preloadEntry of this.#reamrc.preloads) {
519
532
  const preloadImport = typeof preloadEntry === 'function' ? preloadEntry : preloadEntry.file
520
533
  const env =
521
534
  typeof preloadEntry === 'function'
522
535
  ? undefined
523
536
  : (preloadEntry as { environment?: string[] }).environment
524
537
 
525
- if (env && !env.includes(this.environment)) continue
538
+ if (env && !env.includes(this.#app.getEnvironment())) continue
526
539
  await preloadImport()
527
540
  }
528
541
  }
529
542
 
530
543
  // Auto-load module files (routes.ts, etc.) from modules directory
531
- await this.autoloadModules()
544
+ await this.#autoloadModules()
532
545
 
533
546
  // Apply inline configuration
534
- for (const mw of this.inlineMiddleware) {
535
- this.middleware.use(mw)
547
+ for (const mw of this.#inlineMiddleware) {
548
+ this.#middleware.use(mw)
536
549
  }
537
- for (const [name, mw] of this.inlineNamedMiddleware) {
538
- this.middleware.register(name, mw)
550
+ for (const [name, mw] of this.#inlineNamedMiddleware) {
551
+ this.#middleware.register(name, mw)
539
552
  }
540
- if (this.inlineRoutes) {
541
- this.inlineRoutes(this.router)
553
+ if (this.#inlineRoutes) {
554
+ this.#inlineRoutes(this.#router)
542
555
  }
543
556
 
544
557
  // Call start() on providers
545
- for (const provider of this.providers) {
558
+ for (const provider of this.#providers) {
546
559
  await callProviderPhase(provider, 'start')
547
560
  }
548
561
 
549
- this.phase = 'started'
562
+ this.#phase = 'started'
550
563
  }
551
564
 
552
- private async phaseReady(): Promise<void> {
565
+ async #phaseReady(): Promise<void> {
553
566
  // Boot the Server (resolves lazy error handler etc.)
554
- await this.server.boot()
567
+ await this.#server.boot()
555
568
 
556
569
  // Start HTTP server if in web mode
557
- if (this.environment === 'web' && this.config.serverFactory) {
570
+ if (this.#app.getEnvironment() === 'web' && this.#config.serverFactory) {
558
571
  // Build the HttpKernel with server middleware + router middleware.
559
572
  // The streaming backend is resolved lazily — the HyperServer is
560
573
  // created a few lines down, after the kernel — so the kernel
561
- // closes over the `this._httpServer` slot and reads it on every
574
+ // closes over the `this.#_httpServer` slot and reads it on every
562
575
  // request through the factory.
563
576
  const kernel = createHttpKernel({
564
- router: this.router,
565
- middleware: this.middleware,
566
- container: this.app.container,
577
+ router: this.#router,
578
+ middleware: this.#middleware,
579
+ container: this.#app.container,
567
580
  exceptionHandler:
568
- this.server.getErrorHandler() ?? new ExceptionHandler(!this.app.inProduction),
569
- serverMiddleware: this.server.getServerMiddleware(),
570
- routerMiddleware: this.router.getRouterMiddleware(),
581
+ this.#server.getErrorHandler() ?? new ExceptionHandler(!this.#app.inProduction),
582
+ serverMiddleware: this.#server.getServerMiddleware(),
583
+ routerMiddleware: this.#router.getRouterMiddleware(),
571
584
  onError: (error, ctx) => {
572
- this.errorBoundary.serviceError('HttpKernel', error, ctx.id)
585
+ this.#errorBoundary.serviceError('HttpKernel', error, ctx.id)
573
586
  },
574
587
  // AdonisJS `config/app.ts` → `http: { allowMethodSpoofing: true }`.
575
- allowMethodSpoofing: this.app.config.get<boolean>('app.http.allowMethodSpoofing') === true,
588
+ allowMethodSpoofing: this.#app.config.get<boolean>('app.http.allowMethodSpoofing') === true,
576
589
  streamBackend: () => {
577
- const server = this._httpServer
590
+ const server = this.#_httpServer
578
591
  if (
579
592
  server &&
580
593
  typeof server.registerStream === 'function' &&
@@ -596,28 +609,28 @@ export class Ignitor {
596
609
  },
597
610
  })
598
611
 
599
- const desiredPort = this.config.port ?? 3000
612
+ const desiredPort = this.#config.port ?? 3000
600
613
  // Port-scan fallback (+1..+19) is a DEV convenience only. In production
601
614
  // a silent drift to :3001 while the LB targets :3000 is an outage —
602
615
  // bind the configured port and let EADDRINUSE fail loudly. The probe
603
616
  // also has an inherent TOCTOU; acceptable in dev, not in prod.
604
- const availablePort = this.app.inProduction
617
+ const availablePort = this.#app.inProduction
605
618
  ? desiredPort
606
619
  : await findAvailablePort(desiredPort)
607
620
  this.#host =
608
- this.config.host ?? process.env.HOST ?? (this.app.inProduction ? '0.0.0.0' : 'localhost')
609
- this._httpServer = this.config.serverFactory(availablePort, this.#host)
610
- this._httpServer.onRequest(kernel)
621
+ this.#config.host ?? process.env.HOST ?? (this.#app.inProduction ? '0.0.0.0' : 'localhost')
622
+ this.#_httpServer = this.#config.serverFactory(availablePort, this.#host)
623
+ this.#_httpServer.onRequest(kernel)
611
624
  // Pre-resolve client IPs in Rust from the trusted-proxy CIDRs before
612
625
  // listen. Security filtering itself lives in @c9up/blackhole.
613
- const trustedProxies = this.server.getTrustedProxies()
626
+ const trustedProxies = this.#server.getTrustedProxies()
614
627
  if (
615
628
  trustedProxies.length > 0 &&
616
- typeof this._httpServer.configureTrustedProxies === 'function'
629
+ typeof this.#_httpServer.configureTrustedProxies === 'function'
617
630
  ) {
618
- this._httpServer.configureTrustedProxies([...trustedProxies])
631
+ this.#_httpServer.configureTrustedProxies([...trustedProxies])
619
632
  }
620
- await this._httpServer.listen()
633
+ await this.#_httpServer.listen()
621
634
 
622
635
  // Wire OS-signal graceful shutdown. Without it the process never closes
623
636
  // the HTTP server on SIGTERM/SIGINT, so live keep-alive / SSE sockets keep
@@ -625,7 +638,7 @@ export class Ignitor {
625
638
  // force-kill, and an orchestrator's rolling deploy drops in-flight work.
626
639
  // onShutdown = this.stop(), which closes the port (aborting connections),
627
640
  // shuts providers down (DB pools), and releases the locators.
628
- if (this.config.gracefulShutdown !== false) {
641
+ if (this.#config.gracefulShutdown !== false) {
629
642
  this.#shutdownHandle = installGracefulShutdown({
630
643
  onShutdown: () => this.stop(),
631
644
  logger: {
@@ -634,7 +647,7 @@ export class Ignitor {
634
647
  },
635
648
  })
636
649
  }
637
- } else if (this.environment === 'web' && !this.config.serverFactory) {
650
+ } else if (this.#app.getEnvironment() === 'web' && !this.#config.serverFactory) {
638
651
  throw new ReamError(
639
652
  'IGNITOR_NO_SERVER_FACTORY',
640
653
  'httpServer() requires a serverFactory in config',
@@ -645,23 +658,23 @@ export class Ignitor {
645
658
  }
646
659
 
647
660
  // Install error boundary
648
- this.errorBoundary.install()
661
+ this.#errorBoundary.install()
649
662
 
650
663
  // Publish the flash-message tags to the template engine, if one is
651
664
  // installed. After boot(), so the engine's own provider has bound it;
652
665
  // before ready(), so a provider that pre-renders finds the tags in place.
653
- await this.registerSessionTemplateTags()
666
+ await this.#registerSessionTemplateTags()
654
667
 
655
668
  // Call ready() on providers
656
- for (const provider of this.providers) {
669
+ for (const provider of this.#providers) {
657
670
  await callProviderPhase(provider, 'ready')
658
671
  }
659
672
 
660
673
  // Core domain event: the app finished booting (all providers ready).
661
674
  // Emitted once through the bus when events are wired — zero hot-path cost.
662
- if (this.app.container.has('events')) {
663
- const bus = await this.app.container.resolve<Emitter>('events')
664
- bus.emit('app:ready', { environment: this.environment })
675
+ if (this.#app.container.has('events')) {
676
+ const bus = await this.#app.container.resolve<Emitter>('events')
677
+ bus.emit('app:ready', { environment: this.#app.getEnvironment() })
665
678
  }
666
679
 
667
680
  // Dev-mode change watcher. IMPORTANT: this does NOT attempt an in-process
@@ -676,11 +689,11 @@ export class Ignitor {
676
689
  // the Ignitor without a supervisor. A future true HMR needs a loader-hook
677
690
  // (hot-hook style) that can invalidate the ESM cache — plug it in here.
678
691
  if (this.isDevMode()) {
679
- const watchDirs = this.config.watchDirs ?? ['app', 'start']
680
- this.hotReloadCleanup = startHotReload({
692
+ const watchDirs = this.#config.watchDirs ?? ['app', 'start']
693
+ this.#hotReloadCleanup = startHotReload({
681
694
  watchDirs,
682
695
  onReload: () => {
683
- this.handleError({
696
+ this.#handleError({
684
697
  type: 'system.info',
685
698
  source: 'HotReload',
686
699
  message:
@@ -691,7 +704,7 @@ export class Ignitor {
691
704
  },
692
705
  logger: {
693
706
  info: (msg) =>
694
- this.handleError({
707
+ this.#handleError({
695
708
  type: 'system.info',
696
709
  source: 'HotReload',
697
710
  message: msg,
@@ -702,7 +715,7 @@ export class Ignitor {
702
715
  })
703
716
  }
704
717
 
705
- this.phase = 'ready'
718
+ this.#phase = 'ready'
706
719
  }
707
720
 
708
721
  /**
@@ -710,13 +723,13 @@ export class Ignitor {
710
723
  * Each file's default export is stored under its filename (without extension).
711
724
  * e.g. config/database.ts → app.config.get('database')
712
725
  */
713
- private async autoloadConfig(): Promise<void> {
726
+ async #autoloadConfig(): Promise<void> {
714
727
  const { readdirSync, existsSync } = await import('node:fs')
715
728
  const { join, basename } = await import('node:path')
716
729
  const { fileURLToPath, pathToFileURL } = await import('node:url')
717
730
 
718
- const configDir = this.appRoot
719
- ? join(fileURLToPath(this.appRoot), 'config')
731
+ const configDir = this.#appRoot
732
+ ? join(fileURLToPath(this.#appRoot), 'config')
720
733
  : join(process.cwd(), 'config')
721
734
 
722
735
  if (!existsSync(configDir)) return
@@ -739,7 +752,7 @@ export class Ignitor {
739
752
  // is what AdonisJS does (`fsImportAll`). Spread rather than stored as-is:
740
753
  // an ESM namespace is sealed and null-prototype, so a later merge into
741
754
  // this entry would fail on it.
742
- this.app.config.set(key, 'default' in mod ? mod.default : { ...mod })
755
+ this.#app.config.set(key, 'default' in mod ? mod.default : { ...mod })
743
756
  }
744
757
  }
745
758
 
@@ -747,8 +760,8 @@ export class Ignitor {
747
760
  * Auto-load module files (routes.ts, etc.) from the modules directory.
748
761
  * Scans modules.path for subdirectories and imports matching files.
749
762
  */
750
- private async autoloadModules(): Promise<void> {
751
- const modulesConfig = this.reamrc?.modules
763
+ async #autoloadModules(): Promise<void> {
764
+ const modulesConfig = this.#reamrc?.modules
752
765
  if (!modulesConfig?.path) return
753
766
 
754
767
  const { readdirSync, existsSync } = await import('node:fs')
@@ -757,8 +770,8 @@ export class Ignitor {
757
770
  const { pathToFileURL } = await import('node:url')
758
771
 
759
772
  // Resolve modules path relative to app root or cwd
760
- const basePath = this.appRoot
761
- ? join(fileURLToPath(this.appRoot), modulesConfig.path)
773
+ const basePath = this.#appRoot
774
+ ? join(fileURLToPath(this.#appRoot), modulesConfig.path)
762
775
  : resolve(modulesConfig.path)
763
776
 
764
777
  if (!existsSync(basePath)) return
@@ -819,22 +832,22 @@ export class Ignitor {
819
832
  this.#shutdownHandle = undefined
820
833
  })
821
834
  await attempt(() => {
822
- if (this.hotReloadCleanup) this.hotReloadCleanup()
835
+ if (this.#hotReloadCleanup) this.#hotReloadCleanup()
823
836
  })
824
837
  await attempt(async () => {
825
- if (this._httpServer) await this._httpServer.close()
838
+ if (this.#_httpServer) await this.#_httpServer.close()
826
839
  })
827
- await attempt(() => this.errorBoundary.uninstall())
828
- await attempt(() => this.app.shutdown())
840
+ await attempt(() => this.#errorBoundary.uninstall())
841
+ await attempt(() => this.#app.shutdown())
829
842
 
830
843
  // Release the process-wide service locators (ownership-guarded): a stopped
831
844
  // app must not keep its container/router reachable through the module
832
845
  // singletons, and must not clobber a newer Ignitor's bindings.
833
- clearApp(this.app)
834
- clearRouter(this.router)
835
- clearServer(this.server)
846
+ clearApp(this.#app)
847
+ clearRouter(this.#router)
848
+ clearServer(this.#server)
836
849
 
837
- this.phase = 'shutdown'
850
+ this.#phase = 'shutdown'
838
851
  if (errors.length === 1) throw errors[0]
839
852
  if (errors.length > 1) {
840
853
  throw new AggregateError(errors, 'Ignitor.stop() completed with errors')
@@ -844,7 +857,7 @@ export class Ignitor {
844
857
  // ─── Accessors ────────────────────────────────────────────
845
858
 
846
859
  async port(): Promise<number> {
847
- return this._httpServer ? this._httpServer.port() : 0
860
+ return this.#_httpServer ? this.#_httpServer.port() : 0
848
861
  }
849
862
 
850
863
  /**
@@ -858,7 +871,7 @@ export class Ignitor {
858
871
  }
859
872
 
860
873
  getApp(): Application {
861
- return this.app
874
+ return this.#app
862
875
  }
863
876
 
864
877
  /**
@@ -866,12 +879,12 @@ export class Ignitor {
866
879
  * Undefined for inline/test ignitors created without one.
867
880
  */
868
881
  getAppRoot(): URL | undefined {
869
- return this.appRoot
882
+ return this.#appRoot
870
883
  }
871
884
 
872
885
  /** Has the application already been started? */
873
886
  isStarted(): boolean {
874
- return this.phase === 'started' || this.phase === 'ready'
887
+ return this.#phase === 'started' || this.#phase === 'ready'
875
888
  }
876
889
 
877
890
  /**
@@ -892,26 +905,26 @@ export class Ignitor {
892
905
 
893
906
  this.#console = consoleApp
894
907
  setConsole(consoleApp)
895
- this.app.container.singleton('console', () => consoleApp)
908
+ this.#app.container.singleton('console', () => consoleApp)
896
909
  return consoleApp
897
910
  }
898
911
 
899
912
  /** The loaded rc file, if `useRcFile()` was called. */
900
913
  getRcFile(): ReamrcConfig | undefined {
901
- return this.reamrc
914
+ return this.#reamrc
902
915
  }
903
916
 
904
917
  /** The custom module importer, when the app provided one. */
905
918
  getImporter(): ((filePath: string) => Promise<unknown>) | undefined {
906
- return this.config.importer
919
+ return this.#config.importer
907
920
  }
908
921
 
909
922
  getRouter(): Router {
910
- return this.router
923
+ return this.#router
911
924
  }
912
925
 
913
926
  getServer(): Server {
914
- return this.server
927
+ return this.#server
915
928
  }
916
929
 
917
930
  /**
@@ -920,13 +933,13 @@ export class Ignitor {
920
933
  */
921
934
  getKernel(): (request: HttpKernelRequest) => Promise<HttpKernelResponse> {
922
935
  this.#kernel ??= createHttpKernel({
923
- router: this.router,
924
- middleware: this.middleware,
925
- container: this.app.container,
936
+ router: this.#router,
937
+ middleware: this.#middleware,
938
+ container: this.#app.container,
926
939
  exceptionHandler:
927
- this.server.getErrorHandler() ?? new ExceptionHandler(!this.app.inProduction),
928
- serverMiddleware: this.server.getServerMiddleware(),
929
- routerMiddleware: this.router.getRouterMiddleware(),
940
+ this.#server.getErrorHandler() ?? new ExceptionHandler(!this.#app.inProduction),
941
+ serverMiddleware: this.#server.getServerMiddleware(),
942
+ routerMiddleware: this.#router.getRouterMiddleware(),
930
943
  })
931
944
  return this.#kernel
932
945
  }
@@ -934,15 +947,15 @@ export class Ignitor {
934
947
  #kernel?: (request: HttpKernelRequest) => Promise<HttpKernelResponse>
935
948
 
936
949
  isDevMode(): boolean {
937
- return this.app.inDev
950
+ return this.#app.inDev
938
951
  }
939
952
 
940
953
  getPhase(): string {
941
- return this.phase
954
+ return this.#phase
942
955
  }
943
956
 
944
- private handleError(event: ErrorEvent): void {
945
- for (const listener of this.errorListeners) {
957
+ #handleError(event: ErrorEvent): void {
958
+ for (const listener of this.#errorListeners) {
946
959
  try {
947
960
  listener(event)
948
961
  } catch {
@@ -957,7 +970,7 @@ export class Ignitor {
957
970
  * shutdown logger so drain progress / timeouts are observable.
958
971
  */
959
972
  #emitSystemInfo(source: string, message: string, kind: 'info' | 'error'): void {
960
- this.handleError({
973
+ this.#handleError({
961
974
  type: kind === 'error' ? 'system.error' : 'system.info',
962
975
  source,
963
976
  message,