@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.
- package/dist/Application.d.ts +85 -5
- package/dist/Application.d.ts.map +1 -1
- package/dist/Application.js +171 -21
- package/dist/Application.js.map +1 -1
- package/dist/ErrorBoundary.d.ts +1 -7
- package/dist/ErrorBoundary.d.ts.map +1 -1
- package/dist/ErrorBoundary.js +31 -31
- package/dist/ErrorBoundary.js.map +1 -1
- package/dist/Ignitor.d.ts +15 -46
- package/dist/Ignitor.d.ts.map +1 -1
- package/dist/Ignitor.js +178 -173
- package/dist/Ignitor.js.map +1 -1
- package/dist/env/Env.d.ts +6 -0
- package/dist/env/Env.d.ts.map +1 -1
- package/dist/env/Env.js +6 -0
- package/dist/env/Env.js.map +1 -1
- package/dist/events/Emitter.d.ts +2 -6
- package/dist/events/Emitter.d.ts.map +1 -1
- package/dist/events/Emitter.js +45 -42
- package/dist/events/Emitter.js.map +1 -1
- package/dist/events/native.d.ts +13 -11
- package/dist/events/native.d.ts.map +1 -1
- package/dist/events/native.js.map +1 -1
- package/dist/http/HttpContext.d.ts +1 -1
- package/dist/http/HttpContext.d.ts.map +1 -1
- package/dist/http/Request.js +0 -13
- package/dist/http/Request.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/native/generated.d.ts +243 -0
- package/dist/native/generated.d.ts.map +1 -0
- package/dist/native/generated.js +7 -0
- package/dist/native/generated.js.map +1 -0
- package/dist/session/drivers/CookieDriver.d.ts +1 -1
- package/dist/session/drivers/CookieDriver.d.ts.map +1 -1
- package/dist/session/drivers/CookieDriver.js +4 -4
- package/dist/session/drivers/CookieDriver.js.map +1 -1
- package/dist/types/index.d.ts +88 -5
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +8 -3
- package/dist/types/index.js.map +1 -1
- package/events.win32-x64-msvc.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +3 -2
- package/scheduler.win32-x64-msvc.node +0 -0
- package/scripts/build-napi-types.mjs +68 -0
- package/scripts/generate-napi-types.mjs +171 -0
- package/src/Application.ts +221 -21
- package/src/ErrorBoundary.ts +31 -31
- package/src/Ignitor.ts +185 -172
- package/src/env/Env.ts +6 -0
- package/src/events/Emitter.ts +45 -42
- package/src/events/native.ts +21 -14
- package/src/http/HttpContext.ts +1 -1
- package/src/http/Request.ts +0 -13
- package/src/index.ts +2 -2
- package/src/native/generated.ts +291 -0
- package/src/session/drivers/CookieDriver.ts +4 -4
- package/src/types/index.ts +92 -5
- package/dist/types/authenticators.d.ts +0 -24
- package/dist/types/authenticators.d.ts.map +0 -1
- package/dist/types/authenticators.js +0 -2
- package/dist/types/authenticators.js.map +0 -1
- package/dist/types/container.d.ts +0 -64
- package/dist/types/container.d.ts.map +0 -1
- package/dist/types/container.js +0 -23
- package/dist/types/container.js.map +0 -1
- package/src/types/authenticators.ts +0 -24
- package/src/types/container.ts +0 -66
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
|
-
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
|
243
|
-
this
|
|
249
|
+
this.#appRoot = appRootOrConfig
|
|
250
|
+
this.#config = config ?? {}
|
|
244
251
|
} else {
|
|
245
|
-
this
|
|
252
|
+
this.#config = appRootOrConfig ?? {}
|
|
246
253
|
}
|
|
247
254
|
|
|
248
|
-
this
|
|
249
|
-
this
|
|
250
|
-
this
|
|
251
|
-
this
|
|
252
|
-
this
|
|
253
|
-
this
|
|
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
|
|
257
|
-
this
|
|
258
|
-
this
|
|
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
|
|
263
|
-
this
|
|
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
|
|
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
|
|
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
|
|
278
|
-
const root = this
|
|
279
|
-
this
|
|
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
|
|
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
|
|
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
|
|
302
|
-
this
|
|
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
|
|
307
|
-
setRouter(this
|
|
308
|
-
setServer(this
|
|
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
|
|
325
|
+
callback(this.#app)
|
|
319
326
|
return this
|
|
320
327
|
}
|
|
321
328
|
|
|
322
|
-
/**
|
|
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.
|
|
337
|
+
this.#app.setEnvironment(env)
|
|
325
338
|
return this
|
|
326
339
|
}
|
|
327
340
|
|
|
328
341
|
getEnvironment(): AppEnvironment {
|
|
329
|
-
return this.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
419
|
+
if (this.#phase !== 'created') {
|
|
407
420
|
throw new ReamError(
|
|
408
421
|
'IGNITOR_ALREADY_STARTED',
|
|
409
|
-
`start() called while in phase '${this
|
|
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
|
|
416
|
-
await this
|
|
417
|
-
await this
|
|
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
|
|
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
|
|
451
|
-
loadEnvFiles(this
|
|
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
|
-
|
|
474
|
+
async #registerSessionTemplateTags(): Promise<void> {
|
|
462
475
|
for (const token of ['inker', 'view']) {
|
|
463
|
-
if (!this
|
|
464
|
-
const binding: unknown = await this
|
|
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
|
-
|
|
491
|
+
async #phaseRegister(): Promise<void> {
|
|
479
492
|
// Auto-load config/*.ts files into app.config
|
|
480
|
-
await this
|
|
493
|
+
await this.#autoloadConfig()
|
|
481
494
|
|
|
482
495
|
// Load providers from reamrc
|
|
483
|
-
if (this
|
|
484
|
-
for (const providerEntry of this
|
|
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.
|
|
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
|
|
495
|
-
this
|
|
496
|
-
this
|
|
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
|
|
502
|
-
const instance = factory(this
|
|
503
|
-
this
|
|
504
|
-
this
|
|
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
|
|
520
|
+
this.#phase = 'registered'
|
|
508
521
|
}
|
|
509
522
|
|
|
510
|
-
|
|
511
|
-
await this
|
|
512
|
-
this
|
|
523
|
+
async #phaseBoot(): Promise<void> {
|
|
524
|
+
await this.#app.boot()
|
|
525
|
+
this.#phase = 'booted'
|
|
513
526
|
}
|
|
514
527
|
|
|
515
|
-
|
|
528
|
+
async #phaseStart(): Promise<void> {
|
|
516
529
|
// Import preload files (routes.ts, kernel.ts, etc.)
|
|
517
|
-
if (this
|
|
518
|
-
for (const preloadEntry of this
|
|
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.
|
|
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
|
|
544
|
+
await this.#autoloadModules()
|
|
532
545
|
|
|
533
546
|
// Apply inline configuration
|
|
534
|
-
for (const mw of this
|
|
535
|
-
this
|
|
547
|
+
for (const mw of this.#inlineMiddleware) {
|
|
548
|
+
this.#middleware.use(mw)
|
|
536
549
|
}
|
|
537
|
-
for (const [name, mw] of this
|
|
538
|
-
this
|
|
550
|
+
for (const [name, mw] of this.#inlineNamedMiddleware) {
|
|
551
|
+
this.#middleware.register(name, mw)
|
|
539
552
|
}
|
|
540
|
-
if (this
|
|
541
|
-
this
|
|
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
|
|
558
|
+
for (const provider of this.#providers) {
|
|
546
559
|
await callProviderPhase(provider, 'start')
|
|
547
560
|
}
|
|
548
561
|
|
|
549
|
-
this
|
|
562
|
+
this.#phase = 'started'
|
|
550
563
|
}
|
|
551
564
|
|
|
552
|
-
|
|
565
|
+
async #phaseReady(): Promise<void> {
|
|
553
566
|
// Boot the Server (resolves lazy error handler etc.)
|
|
554
|
-
await this
|
|
567
|
+
await this.#server.boot()
|
|
555
568
|
|
|
556
569
|
// Start HTTP server if in web mode
|
|
557
|
-
if (this.
|
|
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
|
|
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
|
|
565
|
-
middleware: this
|
|
566
|
-
container: this
|
|
577
|
+
router: this.#router,
|
|
578
|
+
middleware: this.#middleware,
|
|
579
|
+
container: this.#app.container,
|
|
567
580
|
exceptionHandler:
|
|
568
|
-
this
|
|
569
|
-
serverMiddleware: this
|
|
570
|
-
routerMiddleware: this
|
|
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
|
|
585
|
+
this.#errorBoundary.serviceError('HttpKernel', error, ctx.id)
|
|
573
586
|
},
|
|
574
587
|
// AdonisJS `config/app.ts` → `http: { allowMethodSpoofing: true }`.
|
|
575
|
-
allowMethodSpoofing: this
|
|
588
|
+
allowMethodSpoofing: this.#app.config.get<boolean>('app.http.allowMethodSpoofing') === true,
|
|
576
589
|
streamBackend: () => {
|
|
577
|
-
const server = this
|
|
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
|
|
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
|
|
617
|
+
const availablePort = this.#app.inProduction
|
|
605
618
|
? desiredPort
|
|
606
619
|
: await findAvailablePort(desiredPort)
|
|
607
620
|
this.#host =
|
|
608
|
-
this
|
|
609
|
-
this
|
|
610
|
-
this
|
|
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
|
|
626
|
+
const trustedProxies = this.#server.getTrustedProxies()
|
|
614
627
|
if (
|
|
615
628
|
trustedProxies.length > 0 &&
|
|
616
|
-
typeof this
|
|
629
|
+
typeof this.#_httpServer.configureTrustedProxies === 'function'
|
|
617
630
|
) {
|
|
618
|
-
this
|
|
631
|
+
this.#_httpServer.configureTrustedProxies([...trustedProxies])
|
|
619
632
|
}
|
|
620
|
-
await this
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
666
|
+
await this.#registerSessionTemplateTags()
|
|
654
667
|
|
|
655
668
|
// Call ready() on providers
|
|
656
|
-
for (const provider of this
|
|
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
|
|
663
|
-
const bus = await this
|
|
664
|
-
bus.emit('app:ready', { environment: this.
|
|
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
|
|
680
|
-
this
|
|
692
|
+
const watchDirs = this.#config.watchDirs ?? ['app', 'start']
|
|
693
|
+
this.#hotReloadCleanup = startHotReload({
|
|
681
694
|
watchDirs,
|
|
682
695
|
onReload: () => {
|
|
683
|
-
this
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
719
|
-
? join(fileURLToPath(this
|
|
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
|
|
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
|
-
|
|
751
|
-
const modulesConfig = this
|
|
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
|
|
761
|
-
? join(fileURLToPath(this
|
|
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
|
|
835
|
+
if (this.#hotReloadCleanup) this.#hotReloadCleanup()
|
|
823
836
|
})
|
|
824
837
|
await attempt(async () => {
|
|
825
|
-
if (this
|
|
838
|
+
if (this.#_httpServer) await this.#_httpServer.close()
|
|
826
839
|
})
|
|
827
|
-
await attempt(() => this
|
|
828
|
-
await attempt(() => this
|
|
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
|
|
834
|
-
clearRouter(this
|
|
835
|
-
clearServer(this
|
|
846
|
+
clearApp(this.#app)
|
|
847
|
+
clearRouter(this.#router)
|
|
848
|
+
clearServer(this.#server)
|
|
836
849
|
|
|
837
|
-
this
|
|
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
|
|
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
|
|
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
|
|
882
|
+
return this.#appRoot
|
|
870
883
|
}
|
|
871
884
|
|
|
872
885
|
/** Has the application already been started? */
|
|
873
886
|
isStarted(): boolean {
|
|
874
|
-
return this
|
|
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
|
|
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
|
|
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
|
|
919
|
+
return this.#config.importer
|
|
907
920
|
}
|
|
908
921
|
|
|
909
922
|
getRouter(): Router {
|
|
910
|
-
return this
|
|
923
|
+
return this.#router
|
|
911
924
|
}
|
|
912
925
|
|
|
913
926
|
getServer(): Server {
|
|
914
|
-
return this
|
|
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
|
|
924
|
-
middleware: this
|
|
925
|
-
container: this
|
|
936
|
+
router: this.#router,
|
|
937
|
+
middleware: this.#middleware,
|
|
938
|
+
container: this.#app.container,
|
|
926
939
|
exceptionHandler:
|
|
927
|
-
this
|
|
928
|
-
serverMiddleware: this
|
|
929
|
-
routerMiddleware: this
|
|
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
|
|
950
|
+
return this.#app.inDev
|
|
938
951
|
}
|
|
939
952
|
|
|
940
953
|
getPhase(): string {
|
|
941
|
-
return this
|
|
954
|
+
return this.#phase
|
|
942
955
|
}
|
|
943
956
|
|
|
944
|
-
|
|
945
|
-
for (const listener of this
|
|
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
|
|
973
|
+
this.#handleError({
|
|
961
974
|
type: kind === 'error' ? 'system.error' : 'system.info',
|
|
962
975
|
source,
|
|
963
976
|
message,
|