@navios/core 1.0.0 → 1.1.0
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/CHANGELOG.md +44 -0
- package/lib/{index-C0Sg16Eb.d.cts → index-3LcTrbxz.d.mts} +491 -52
- package/lib/index-3LcTrbxz.d.mts.map +1 -0
- package/lib/{index-DySQ6Dpd.d.mts → index-B14SekVb.d.cts} +491 -52
- package/lib/index-B14SekVb.d.cts.map +1 -0
- package/lib/index.cjs +18 -3
- package/lib/index.d.cts +2 -2
- package/lib/index.d.mts +2 -2
- package/lib/index.mjs +4 -4
- package/lib/legacy-compat/index.cjs +3 -3
- package/lib/legacy-compat/index.d.cts +1 -1
- package/lib/legacy-compat/index.d.mts +1 -1
- package/lib/legacy-compat/index.mjs +2 -2
- package/lib/{navios.factory-CO5MB_OK.cjs → navios.factory-CUrO_p6K.cjs} +461 -48
- package/lib/navios.factory-CUrO_p6K.cjs.map +1 -0
- package/lib/{navios.factory-D6Y94P9B.mjs → navios.factory-DjUOOVVL.mjs} +372 -49
- package/lib/navios.factory-DjUOOVVL.mjs.map +1 -0
- package/lib/testing/index.cjs +2 -2
- package/lib/testing/index.d.cts +1 -1
- package/lib/testing/index.d.mts +1 -1
- package/lib/testing/index.mjs +2 -2
- package/lib/{tokens-CWw9kyeD.cjs → tokens-BEuBMGGX.cjs} +18 -21
- package/lib/tokens-BEuBMGGX.cjs.map +1 -0
- package/lib/{tokens-4J9sredA.mjs → tokens-COyNGV1I.mjs} +17 -20
- package/lib/tokens-COyNGV1I.mjs.map +1 -0
- package/lib/{use-guards.decorator-BecoQSmE.mjs → use-guards.decorator-DLmRl2CV.mjs} +14 -17
- package/lib/use-guards.decorator-DLmRl2CV.mjs.map +1 -0
- package/lib/{use-guards.decorator-C4ml9XaT.cjs → use-guards.decorator-DhumFTk3.cjs} +15 -18
- package/lib/use-guards.decorator-DhumFTk3.cjs.map +1 -0
- package/package.json +2 -2
- package/src/interfaces/index.mts +3 -0
- package/src/interfaces/plugin-context.mts +104 -0
- package/src/interfaces/plugin-stage.mts +62 -0
- package/src/interfaces/plugin.interface.mts +42 -62
- package/src/interfaces/staged-plugin.interface.mts +209 -0
- package/src/metadata/controller.metadata.mts +29 -22
- package/src/metadata/module.metadata.mts +33 -25
- package/src/navios.application.mts +247 -53
- package/src/services/module-loader.service.mts +11 -8
- package/src/utils/define-plugin.mts +251 -0
- package/src/utils/index.mts +1 -0
- package/lib/index-C0Sg16Eb.d.cts.map +0 -1
- package/lib/index-DySQ6Dpd.d.mts.map +0 -1
- package/lib/navios.factory-CO5MB_OK.cjs.map +0 -1
- package/lib/navios.factory-D6Y94P9B.mjs.map +0 -1
- package/lib/tokens-4J9sredA.mjs.map +0 -1
- package/lib/tokens-CWw9kyeD.cjs.map +0 -1
- package/lib/use-guards.decorator-BecoQSmE.mjs.map +0 -1
- package/lib/use-guards.decorator-C4ml9XaT.cjs.map +0 -1
|
@@ -19,15 +19,25 @@ import {
|
|
|
19
19
|
import type {
|
|
20
20
|
AbstractAdapterInterface,
|
|
21
21
|
AdapterEnvironment,
|
|
22
|
+
AnyPluginDefinition,
|
|
23
|
+
ContainerOnlyContext,
|
|
22
24
|
DefaultAdapterEnvironment,
|
|
25
|
+
FullPluginContext,
|
|
23
26
|
HttpAdapterEnvironment,
|
|
27
|
+
ModulesLoadedContext,
|
|
24
28
|
NaviosModule,
|
|
25
|
-
|
|
26
|
-
PluginDefinition,
|
|
29
|
+
PluginStage,
|
|
27
30
|
} from './interfaces/index.mjs'
|
|
28
31
|
import type { LoggerService, LogLevel } from './logger/index.mjs'
|
|
29
32
|
import type { NaviosEnvironmentOptions } from './navios.environment.mjs'
|
|
30
33
|
|
|
34
|
+
import {
|
|
35
|
+
PLUGIN_STAGES_ORDER,
|
|
36
|
+
PluginStageBase,
|
|
37
|
+
PluginStages,
|
|
38
|
+
postStage,
|
|
39
|
+
preStage,
|
|
40
|
+
} from './interfaces/index.mjs'
|
|
31
41
|
import { Logger } from './logger/index.mjs'
|
|
32
42
|
import { NaviosEnvironment } from './navios.environment.mjs'
|
|
33
43
|
import { ModuleLoaderService } from './services/index.mjs'
|
|
@@ -122,7 +132,23 @@ export class NaviosApplication<
|
|
|
122
132
|
private options: NaviosApplicationOptions = {
|
|
123
133
|
adapter: [],
|
|
124
134
|
}
|
|
125
|
-
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Plugin storage organized by stage for efficient execution.
|
|
138
|
+
* Each stage has a Set of plugin definitions.
|
|
139
|
+
*/
|
|
140
|
+
private plugins: Map<PluginStage, Set<AnyPluginDefinition>> = new Map(
|
|
141
|
+
PLUGIN_STAGES_ORDER.map((stage) => [stage, new Set()]),
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Queue of adapter configuration methods to apply after adapter resolution.
|
|
146
|
+
* Allows calling methods like enableCors() before init().
|
|
147
|
+
*/
|
|
148
|
+
private pendingAdapterCalls: Array<{
|
|
149
|
+
method: string
|
|
150
|
+
args: unknown[]
|
|
151
|
+
}> = []
|
|
126
152
|
|
|
127
153
|
/**
|
|
128
154
|
* Indicates whether the application has been initialized.
|
|
@@ -134,6 +160,9 @@ export class NaviosApplication<
|
|
|
134
160
|
* Sets up the application with the provided module and options.
|
|
135
161
|
* This is called automatically by NaviosFactory.create().
|
|
136
162
|
*
|
|
163
|
+
* Note: Adapter resolution has been moved to init() to allow
|
|
164
|
+
* plugins to modify the container/registry before adapter instantiation.
|
|
165
|
+
*
|
|
137
166
|
* @param appModule - The root application module
|
|
138
167
|
* @param options - Application configuration options
|
|
139
168
|
* @internal
|
|
@@ -146,11 +175,7 @@ export class NaviosApplication<
|
|
|
146
175
|
) {
|
|
147
176
|
this.appModule = appModule
|
|
148
177
|
this.options = options
|
|
149
|
-
|
|
150
|
-
this.adapter = (await this.container.get(
|
|
151
|
-
AdapterToken,
|
|
152
|
-
)) as Environment['adapter']
|
|
153
|
-
}
|
|
178
|
+
// Note: Adapter resolution moved to init() for plugin hooks
|
|
154
179
|
}
|
|
155
180
|
|
|
156
181
|
/**
|
|
@@ -176,39 +201,79 @@ export class NaviosApplication<
|
|
|
176
201
|
}
|
|
177
202
|
|
|
178
203
|
/**
|
|
179
|
-
* Registers
|
|
204
|
+
* Registers one or more plugins for initialization during the application lifecycle.
|
|
180
205
|
*
|
|
181
|
-
* Plugins
|
|
182
|
-
*
|
|
206
|
+
* Plugins can target specific stages or use the legacy interface (defaults to post:modules-init).
|
|
207
|
+
* Plugins are executed in stage order, and within a stage in registration order.
|
|
183
208
|
*
|
|
184
|
-
* @param
|
|
209
|
+
* @param definitions - Single plugin definition or array of definitions
|
|
185
210
|
* @returns this for method chaining
|
|
186
211
|
*
|
|
187
212
|
* @example
|
|
188
213
|
* ```typescript
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
214
|
+
* // Single plugin (legacy or staged)
|
|
215
|
+
* app.usePlugin(defineOpenApiPlugin({ info: { title: 'My API', version: '1.0.0' } }))
|
|
216
|
+
*
|
|
217
|
+
* // Multiple plugins in one call
|
|
218
|
+
* app.usePlugin([
|
|
219
|
+
* defineOtelPlugin({ serviceName: 'my-service' }),
|
|
220
|
+
* defineOpenApiPlugin({ info: { title: 'My API', version: '1.0.0' } }),
|
|
221
|
+
* ])
|
|
222
|
+
*
|
|
223
|
+
* // Staged plugin with explicit stage
|
|
224
|
+
* app.usePlugin(definePreAdapterResolvePlugin({
|
|
225
|
+
* name: 'early-setup',
|
|
226
|
+
* register: (ctx) => { ... },
|
|
227
|
+
* })({}))
|
|
194
228
|
* ```
|
|
195
229
|
*/
|
|
196
230
|
usePlugin<TOptions, TAdapter extends AbstractAdapterInterface>(
|
|
197
|
-
|
|
231
|
+
definitions:
|
|
232
|
+
| AnyPluginDefinition<TOptions, TAdapter>
|
|
233
|
+
| AnyPluginDefinition<TOptions, TAdapter>[],
|
|
198
234
|
): this {
|
|
199
|
-
|
|
235
|
+
const definitionsArray = Array.isArray(definitions)
|
|
236
|
+
? definitions
|
|
237
|
+
: [definitions]
|
|
238
|
+
|
|
239
|
+
for (const definition of definitionsArray) {
|
|
240
|
+
const stage = this.resolvePluginStage(definition)
|
|
241
|
+
const stageSet = this.plugins.get(stage)
|
|
242
|
+
|
|
243
|
+
if (!stageSet) {
|
|
244
|
+
throw new Error(`Unknown plugin stage: ${stage}`)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
stageSet.add(definition as AnyPluginDefinition)
|
|
248
|
+
this.logger.debug(
|
|
249
|
+
`Registered plugin "${definition.plugin.name}" for stage: ${stage}`,
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
|
|
200
253
|
return this
|
|
201
254
|
}
|
|
202
255
|
|
|
256
|
+
/**
|
|
257
|
+
* Resolves the stage for a plugin definition.
|
|
258
|
+
* Staged plugins use their explicit stage, legacy plugins default to post:modules-init.
|
|
259
|
+
*/
|
|
260
|
+
private resolvePluginStage(definition: AnyPluginDefinition): PluginStage {
|
|
261
|
+
if ('stage' in definition.plugin) {
|
|
262
|
+
return definition.plugin.stage
|
|
263
|
+
}
|
|
264
|
+
// Legacy plugins default to post:modules-init for backward compatibility
|
|
265
|
+
return PluginStages.POST_MODULES_INIT
|
|
266
|
+
}
|
|
267
|
+
|
|
203
268
|
/**
|
|
204
269
|
* Initializes the application.
|
|
205
270
|
*
|
|
206
|
-
* This method:
|
|
207
|
-
* -
|
|
208
|
-
* -
|
|
209
|
-
* -
|
|
210
|
-
* -
|
|
211
|
-
*
|
|
271
|
+
* This method executes the following lifecycle stages:
|
|
272
|
+
* 1. pre:modules-traverse → Load modules → post:modules-traverse
|
|
273
|
+
* 2. pre:adapter-resolve → Resolve adapter → post:adapter-resolve
|
|
274
|
+
* 3. pre:adapter-setup → Setup adapter → post:adapter-setup
|
|
275
|
+
* 4. pre:modules-init → Initialize modules → post:modules-init
|
|
276
|
+
* 5. pre:ready → Ready signal → post:ready
|
|
212
277
|
*
|
|
213
278
|
* Must be called before `listen()`.
|
|
214
279
|
*
|
|
@@ -227,48 +292,161 @@ export class NaviosApplication<
|
|
|
227
292
|
if (!this.appModule) {
|
|
228
293
|
throw new Error('App module is not set. Call setAppModule() first.')
|
|
229
294
|
}
|
|
230
|
-
await this.moduleLoader.loadModules(this.appModule)
|
|
231
295
|
|
|
232
|
-
|
|
233
|
-
|
|
296
|
+
// Stage 1: Load modules
|
|
297
|
+
await this.wrapStage(PluginStageBase.MODULES_TRAVERSE, () =>
|
|
298
|
+
this.moduleLoader.loadModules(this.appModule!),
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
// Stage 2: Resolve adapter (moved from setup())
|
|
302
|
+
// Note: If no adapter configured, adapter stages are silently skipped
|
|
303
|
+
if (this.environment.hasAdapterSetup()) {
|
|
304
|
+
await this.wrapStage(PluginStageBase.ADAPTER_RESOLVE, async () => {
|
|
305
|
+
this.adapter = (await this.container.get(
|
|
306
|
+
AdapterToken,
|
|
307
|
+
)) as Environment['adapter']
|
|
308
|
+
// Apply any configuration calls that were queued before adapter resolution
|
|
309
|
+
this.applyPendingAdapterCalls()
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
// Stage 3: Setup adapter
|
|
313
|
+
if (this.adapter) {
|
|
314
|
+
await this.wrapStage(PluginStageBase.ADAPTER_SETUP, () =>
|
|
315
|
+
this.adapter!.setupAdapter(this.options),
|
|
316
|
+
)
|
|
317
|
+
}
|
|
234
318
|
}
|
|
235
319
|
|
|
236
|
-
|
|
237
|
-
await this.initModules()
|
|
320
|
+
// Stage 4: Initialize modules (always runs)
|
|
321
|
+
await this.wrapStage(PluginStageBase.MODULES_INIT, () => this.initModules())
|
|
238
322
|
|
|
323
|
+
// Stage 5: Ready signal
|
|
239
324
|
if (this.adapter) {
|
|
240
|
-
await this.adapter
|
|
325
|
+
await this.wrapStage(PluginStageBase.READY, () => this.adapter!.ready())
|
|
241
326
|
}
|
|
242
327
|
|
|
243
328
|
this.isInitialized = true
|
|
244
329
|
this.logger.debug('Navios application initialized')
|
|
245
330
|
}
|
|
246
331
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
332
|
+
/**
|
|
333
|
+
* Wraps an operation with pre/post plugin stage execution.
|
|
334
|
+
*
|
|
335
|
+
* @param baseName - The base stage name (e.g., 'modules-traverse')
|
|
336
|
+
* @param operation - The operation to execute between pre/post stages
|
|
337
|
+
*/
|
|
338
|
+
private async wrapStage<T>(
|
|
339
|
+
baseName: PluginStageBase,
|
|
340
|
+
operation: () => Promise<T> | T,
|
|
341
|
+
): Promise<T> {
|
|
342
|
+
await this.executePluginStage(preStage(baseName))
|
|
343
|
+
const result = await operation()
|
|
344
|
+
await this.executePluginStage(postStage(baseName))
|
|
345
|
+
return result
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Executes all plugins registered for a specific stage.
|
|
350
|
+
*
|
|
351
|
+
* @param stage - The lifecycle stage to execute plugins for
|
|
352
|
+
*/
|
|
353
|
+
private async executePluginStage(stage: PluginStage): Promise<void> {
|
|
354
|
+
const stagePlugins = this.plugins.get(stage)
|
|
355
|
+
|
|
356
|
+
if (!stagePlugins || stagePlugins.size === 0) {
|
|
357
|
+
return
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const context = this.buildContextForStage(stage)
|
|
361
|
+
|
|
362
|
+
this.logger.debug(
|
|
363
|
+
`Executing ${stagePlugins.size} plugin(s) for stage: ${stage}`,
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
for (const { plugin, options } of stagePlugins) {
|
|
367
|
+
this.logger.debug(`Executing plugin: ${plugin.name} (stage: ${stage})`)
|
|
368
|
+
|
|
369
|
+
try {
|
|
370
|
+
await plugin.register(context as never, options)
|
|
371
|
+
} catch (error) {
|
|
372
|
+
this.logger.error(
|
|
373
|
+
`Plugin "${plugin.name}" failed at stage "${stage}"`,
|
|
374
|
+
error,
|
|
375
|
+
)
|
|
376
|
+
throw error
|
|
377
|
+
}
|
|
251
378
|
}
|
|
252
379
|
}
|
|
253
380
|
|
|
254
|
-
|
|
255
|
-
|
|
381
|
+
/**
|
|
382
|
+
* Builds the appropriate context object for a given stage.
|
|
383
|
+
*
|
|
384
|
+
* @param stage - The lifecycle stage
|
|
385
|
+
* @returns Context object with stage-appropriate properties
|
|
386
|
+
*/
|
|
387
|
+
private buildContextForStage(
|
|
388
|
+
stage: PluginStage,
|
|
389
|
+
): ContainerOnlyContext | ModulesLoadedContext | FullPluginContext {
|
|
390
|
+
const baseContext: ContainerOnlyContext = {
|
|
391
|
+
container: this.container,
|
|
392
|
+
}
|
|
256
393
|
|
|
257
|
-
if (
|
|
258
|
-
|
|
394
|
+
if (stage === PluginStages.PRE_MODULES_TRAVERSE) {
|
|
395
|
+
return baseContext
|
|
259
396
|
}
|
|
260
397
|
|
|
261
|
-
const
|
|
398
|
+
const modulesContext: ModulesLoadedContext = {
|
|
399
|
+
...baseContext,
|
|
262
400
|
modules: this.moduleLoader.getAllModules(),
|
|
263
|
-
adapter: this.adapter,
|
|
264
|
-
container: this.container,
|
|
265
401
|
moduleLoader: this.moduleLoader,
|
|
266
402
|
}
|
|
267
403
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
404
|
+
const isPreAdapterStage =
|
|
405
|
+
stage === PluginStages.POST_MODULES_TRAVERSE ||
|
|
406
|
+
stage === PluginStages.PRE_ADAPTER_RESOLVE
|
|
407
|
+
|
|
408
|
+
if (isPreAdapterStage) {
|
|
409
|
+
return modulesContext
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (!this.adapter) {
|
|
413
|
+
throw new Error(`Cannot execute stage "${stage}" without adapter`)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return {
|
|
417
|
+
...modulesContext,
|
|
418
|
+
adapter: this.adapter,
|
|
419
|
+
} as FullPluginContext
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
private async initModules() {
|
|
423
|
+
const modules = this.moduleLoader.getAllModules()
|
|
424
|
+
if (this.adapter) {
|
|
425
|
+
await this.adapter.onModulesInit(modules)
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Applies any pending adapter configuration calls that were queued
|
|
431
|
+
* before the adapter was resolved.
|
|
432
|
+
*/
|
|
433
|
+
private applyPendingAdapterCalls(): void {
|
|
434
|
+
if (!this.adapter || this.pendingAdapterCalls.length === 0) {
|
|
435
|
+
return
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
for (const { method, args } of this.pendingAdapterCalls) {
|
|
439
|
+
assertAdapterSupports(
|
|
440
|
+
this.adapter,
|
|
441
|
+
method as keyof AbstractAdapterInterface,
|
|
442
|
+
)
|
|
443
|
+
;(this.adapter as Record<string, (...args: unknown[]) => unknown>)[method](
|
|
444
|
+
...args,
|
|
445
|
+
)
|
|
271
446
|
}
|
|
447
|
+
|
|
448
|
+
// Clear the queue after applying
|
|
449
|
+
this.pendingAdapterCalls = []
|
|
272
450
|
}
|
|
273
451
|
|
|
274
452
|
/**
|
|
@@ -334,8 +512,12 @@ export class NaviosApplication<
|
|
|
334
512
|
if (this.isInitialized) {
|
|
335
513
|
throw new Error('configure() must be called before init()')
|
|
336
514
|
}
|
|
337
|
-
|
|
338
|
-
|
|
515
|
+
if (this.adapter) {
|
|
516
|
+
assertAdapterSupports(this.adapter, 'configure')
|
|
517
|
+
this.adapter.configure(options)
|
|
518
|
+
} else {
|
|
519
|
+
this.pendingAdapterCalls.push({ method: 'configure', args: [options] })
|
|
520
|
+
}
|
|
339
521
|
return this
|
|
340
522
|
}
|
|
341
523
|
|
|
@@ -359,8 +541,12 @@ export class NaviosApplication<
|
|
|
359
541
|
? Environment['corsOptions']
|
|
360
542
|
: never,
|
|
361
543
|
): void {
|
|
362
|
-
|
|
363
|
-
|
|
544
|
+
if (this.adapter) {
|
|
545
|
+
assertAdapterSupports(this.adapter, 'enableCors')
|
|
546
|
+
this.adapter.enableCors(options)
|
|
547
|
+
} else {
|
|
548
|
+
this.pendingAdapterCalls.push({ method: 'enableCors', args: [options] })
|
|
549
|
+
}
|
|
364
550
|
}
|
|
365
551
|
|
|
366
552
|
/**
|
|
@@ -383,8 +569,12 @@ export class NaviosApplication<
|
|
|
383
569
|
? Environment['multipartOptions']
|
|
384
570
|
: never,
|
|
385
571
|
): void {
|
|
386
|
-
|
|
387
|
-
|
|
572
|
+
if (this.adapter) {
|
|
573
|
+
assertAdapterSupports(this.adapter, 'enableMultipart')
|
|
574
|
+
this.adapter.enableMultipart(options)
|
|
575
|
+
} else {
|
|
576
|
+
this.pendingAdapterCalls.push({ method: 'enableMultipart', args: [options] })
|
|
577
|
+
}
|
|
388
578
|
}
|
|
389
579
|
|
|
390
580
|
/**
|
|
@@ -400,8 +590,12 @@ export class NaviosApplication<
|
|
|
400
590
|
* ```
|
|
401
591
|
*/
|
|
402
592
|
setGlobalPrefix(prefix: string): void {
|
|
403
|
-
|
|
404
|
-
|
|
593
|
+
if (this.adapter) {
|
|
594
|
+
assertAdapterSupports(this.adapter, 'setGlobalPrefix')
|
|
595
|
+
this.adapter.setGlobalPrefix(prefix)
|
|
596
|
+
} else {
|
|
597
|
+
this.pendingAdapterCalls.push({ method: 'setGlobalPrefix', args: [prefix] })
|
|
598
|
+
}
|
|
405
599
|
}
|
|
406
600
|
|
|
407
601
|
/**
|
|
@@ -78,19 +78,21 @@ export class ModuleLoaderService {
|
|
|
78
78
|
|
|
79
79
|
for (const extension of extensions) {
|
|
80
80
|
if (extension.module) {
|
|
81
|
-
// Process a full module with its imports and controllers
|
|
82
81
|
await this.traverseModules(extension.module)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
82
|
+
continue
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!extension.controllers) {
|
|
86
|
+
continue
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!extension.moduleName) {
|
|
90
90
|
throw new Error(
|
|
91
91
|
'moduleName is required when providing controllers without a module',
|
|
92
92
|
)
|
|
93
93
|
}
|
|
94
|
+
|
|
95
|
+
await this.registerControllers(extension.controllers, extension.moduleName)
|
|
94
96
|
}
|
|
95
97
|
}
|
|
96
98
|
|
|
@@ -114,6 +116,7 @@ export class ModuleLoaderService {
|
|
|
114
116
|
} else {
|
|
115
117
|
// Create new synthetic module metadata
|
|
116
118
|
const metadata: ModuleMetadata = {
|
|
119
|
+
name: moduleName,
|
|
117
120
|
controllers: new Set(controllers),
|
|
118
121
|
imports: new Set(),
|
|
119
122
|
guards: new Set(),
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import type { AbstractAdapterInterface } from '../interfaces/abstract-adapter.interface.mjs'
|
|
2
|
+
import type {
|
|
3
|
+
ContainerOnlyContext,
|
|
4
|
+
FullPluginContext,
|
|
5
|
+
ModulesLoadedContext,
|
|
6
|
+
} from '../interfaces/plugin-context.mjs'
|
|
7
|
+
import type { PluginStage } from '../interfaces/plugin-stage.mjs'
|
|
8
|
+
import type { StagedPluginDefinition } from '../interfaces/staged-plugin.interface.mjs'
|
|
9
|
+
|
|
10
|
+
import { PluginStages } from '../interfaces/plugin-stage.mjs'
|
|
11
|
+
|
|
12
|
+
// ============ Plugin Config Type ============
|
|
13
|
+
|
|
14
|
+
interface PluginConfig<TContext, TOptions> {
|
|
15
|
+
name: string
|
|
16
|
+
register: (context: TContext, options: TOptions) => Promise<void> | void
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ============ Internal Factory Creator ============
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates a curried plugin factory for a specific stage.
|
|
23
|
+
* Returns a function that takes config and returns a function that takes options.
|
|
24
|
+
*/
|
|
25
|
+
function createPluginFactory<TStage extends PluginStage, TContext>(
|
|
26
|
+
stage: TStage,
|
|
27
|
+
) {
|
|
28
|
+
return <
|
|
29
|
+
TOptions,
|
|
30
|
+
TAdapter extends AbstractAdapterInterface = AbstractAdapterInterface,
|
|
31
|
+
>(
|
|
32
|
+
config: PluginConfig<TContext, TOptions>,
|
|
33
|
+
) =>
|
|
34
|
+
(options: TOptions): StagedPluginDefinition<TStage, TOptions, TAdapter> => ({
|
|
35
|
+
plugin: {
|
|
36
|
+
name: config.name,
|
|
37
|
+
stage,
|
|
38
|
+
register: config.register as (
|
|
39
|
+
context: never,
|
|
40
|
+
options: TOptions,
|
|
41
|
+
) => Promise<void> | void,
|
|
42
|
+
},
|
|
43
|
+
options,
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ============ Pre-Adapter Stages (No adapter in context) ============
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Define a plugin that runs before modules are traversed.
|
|
51
|
+
*
|
|
52
|
+
* Context: container only
|
|
53
|
+
*
|
|
54
|
+
* Use this stage for early DI setup before any modules are loaded.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* export const defineEarlySetupPlugin = definePreModulesTraversePlugin({
|
|
59
|
+
* name: 'early-setup',
|
|
60
|
+
* register: (context, options: { key: string }) => {
|
|
61
|
+
* context.container.addInstance(SomeToken, options.key)
|
|
62
|
+
* },
|
|
63
|
+
* })
|
|
64
|
+
*
|
|
65
|
+
* // Usage
|
|
66
|
+
* app.usePlugin(defineEarlySetupPlugin({ key: 'value' }))
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export const definePreModulesTraversePlugin = createPluginFactory<
|
|
70
|
+
'pre:modules-traverse',
|
|
71
|
+
ContainerOnlyContext
|
|
72
|
+
>(PluginStages.PRE_MODULES_TRAVERSE)
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Define a plugin that runs after modules are traversed.
|
|
76
|
+
*
|
|
77
|
+
* Context: container + modules + moduleLoader
|
|
78
|
+
*
|
|
79
|
+
* Use this stage to inspect loaded modules or extend the module tree.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```typescript
|
|
83
|
+
* export const defineModuleInspectorPlugin = definePostModulesTraversePlugin({
|
|
84
|
+
* name: 'module-inspector',
|
|
85
|
+
* register: (context, options) => {
|
|
86
|
+
* console.log('Loaded modules:', context.modules.size)
|
|
87
|
+
* },
|
|
88
|
+
* })
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export const definePostModulesTraversePlugin = createPluginFactory<
|
|
92
|
+
'post:modules-traverse',
|
|
93
|
+
ModulesLoadedContext
|
|
94
|
+
>(PluginStages.POST_MODULES_TRAVERSE)
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Define a plugin that runs before adapter is resolved from container.
|
|
98
|
+
*
|
|
99
|
+
* Context: container + modules + moduleLoader (NO adapter yet!)
|
|
100
|
+
*
|
|
101
|
+
* Use this stage to modify registry/bindings before adapter instantiation.
|
|
102
|
+
* This is ideal for instrumentation, service wrapping, or changing DI bindings.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* export const defineOtelPlugin = definePreAdapterResolvePlugin({
|
|
107
|
+
* name: 'otel',
|
|
108
|
+
* register: (context, options: OtelOptions) => {
|
|
109
|
+
* const registry = context.container.getRegistry()
|
|
110
|
+
* // Modify registry before adapter is created
|
|
111
|
+
* },
|
|
112
|
+
* })
|
|
113
|
+
*
|
|
114
|
+
* // Usage
|
|
115
|
+
* app.usePlugin(defineOtelPlugin({ serviceName: 'my-app' }))
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
export const definePreAdapterResolvePlugin = createPluginFactory<
|
|
119
|
+
'pre:adapter-resolve',
|
|
120
|
+
ModulesLoadedContext
|
|
121
|
+
>(PluginStages.PRE_ADAPTER_RESOLVE)
|
|
122
|
+
|
|
123
|
+
// ============ Post-Adapter Stages (Adapter available) ============
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Define a plugin that runs after adapter is resolved.
|
|
127
|
+
*
|
|
128
|
+
* Context: full (container + modules + moduleLoader + adapter)
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```typescript
|
|
132
|
+
* export const defineAdapterConfigPlugin = definePostAdapterResolvePlugin<
|
|
133
|
+
* { prefix: string },
|
|
134
|
+
* BunApplicationServiceInterface
|
|
135
|
+
* >()({
|
|
136
|
+
* name: 'adapter-config',
|
|
137
|
+
* register: (context, options) => {
|
|
138
|
+
* context.adapter.setGlobalPrefix(options.prefix)
|
|
139
|
+
* },
|
|
140
|
+
* })
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
export function definePostAdapterResolvePlugin<
|
|
144
|
+
TAdapter extends AbstractAdapterInterface = AbstractAdapterInterface,
|
|
145
|
+
>() {
|
|
146
|
+
return createPluginFactory<'post:adapter-resolve', FullPluginContext<TAdapter>>(
|
|
147
|
+
PluginStages.POST_ADAPTER_RESOLVE,
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Define a plugin that runs before adapter setup.
|
|
153
|
+
*
|
|
154
|
+
* Context: full
|
|
155
|
+
*/
|
|
156
|
+
export function definePreAdapterSetupPlugin<
|
|
157
|
+
TAdapter extends AbstractAdapterInterface = AbstractAdapterInterface,
|
|
158
|
+
>() {
|
|
159
|
+
return createPluginFactory<'pre:adapter-setup', FullPluginContext<TAdapter>>(
|
|
160
|
+
PluginStages.PRE_ADAPTER_SETUP,
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Define a plugin that runs after adapter setup.
|
|
166
|
+
*
|
|
167
|
+
* Context: full
|
|
168
|
+
*/
|
|
169
|
+
export function definePostAdapterSetupPlugin<
|
|
170
|
+
TAdapter extends AbstractAdapterInterface = AbstractAdapterInterface,
|
|
171
|
+
>() {
|
|
172
|
+
return createPluginFactory<'post:adapter-setup', FullPluginContext<TAdapter>>(
|
|
173
|
+
PluginStages.POST_ADAPTER_SETUP,
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Define a plugin that runs before modules init (route registration).
|
|
179
|
+
*
|
|
180
|
+
* Context: full
|
|
181
|
+
*/
|
|
182
|
+
export function definePreModulesInitPlugin<
|
|
183
|
+
TAdapter extends AbstractAdapterInterface = AbstractAdapterInterface,
|
|
184
|
+
>() {
|
|
185
|
+
return createPluginFactory<'pre:modules-init', FullPluginContext<TAdapter>>(
|
|
186
|
+
PluginStages.PRE_MODULES_INIT,
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Define a plugin that runs after modules init (route registration).
|
|
192
|
+
*
|
|
193
|
+
* Context: full
|
|
194
|
+
*
|
|
195
|
+
* This is the default stage for legacy NaviosPlugin implementations.
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* ```typescript
|
|
199
|
+
* export const defineOpenApiPlugin = definePostModulesInitPlugin<
|
|
200
|
+
* BunApplicationServiceInterface
|
|
201
|
+
* >()({
|
|
202
|
+
* name: 'openapi',
|
|
203
|
+
* register: async (context, options: OpenApiOptions) => {
|
|
204
|
+
* // Routes are registered, can generate OpenAPI docs
|
|
205
|
+
* },
|
|
206
|
+
* })
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
export function definePostModulesInitPlugin<
|
|
210
|
+
TAdapter extends AbstractAdapterInterface = AbstractAdapterInterface,
|
|
211
|
+
>() {
|
|
212
|
+
return createPluginFactory<'post:modules-init', FullPluginContext<TAdapter>>(
|
|
213
|
+
PluginStages.POST_MODULES_INIT,
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Define a plugin that runs before adapter signals ready.
|
|
219
|
+
*
|
|
220
|
+
* Context: full
|
|
221
|
+
*/
|
|
222
|
+
export function definePreReadyPlugin<
|
|
223
|
+
TAdapter extends AbstractAdapterInterface = AbstractAdapterInterface,
|
|
224
|
+
>() {
|
|
225
|
+
return createPluginFactory<'pre:ready', FullPluginContext<TAdapter>>(
|
|
226
|
+
PluginStages.PRE_READY,
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Define a plugin that runs after adapter signals ready.
|
|
232
|
+
*
|
|
233
|
+
* Context: full - this is the final stage, app is fully initialized
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* ```typescript
|
|
237
|
+
* export const defineStartupLogPlugin = definePostReadyPlugin()({
|
|
238
|
+
* name: 'startup-log',
|
|
239
|
+
* register: (context) => {
|
|
240
|
+
* console.log('Application fully initialized!')
|
|
241
|
+
* },
|
|
242
|
+
* })
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
245
|
+
export function definePostReadyPlugin<
|
|
246
|
+
TAdapter extends AbstractAdapterInterface = AbstractAdapterInterface,
|
|
247
|
+
>() {
|
|
248
|
+
return createPluginFactory<'post:ready', FullPluginContext<TAdapter>>(
|
|
249
|
+
PluginStages.POST_READY,
|
|
250
|
+
)
|
|
251
|
+
}
|