@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.
Files changed (49) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/lib/{index-C0Sg16Eb.d.cts → index-3LcTrbxz.d.mts} +491 -52
  3. package/lib/index-3LcTrbxz.d.mts.map +1 -0
  4. package/lib/{index-DySQ6Dpd.d.mts → index-B14SekVb.d.cts} +491 -52
  5. package/lib/index-B14SekVb.d.cts.map +1 -0
  6. package/lib/index.cjs +18 -3
  7. package/lib/index.d.cts +2 -2
  8. package/lib/index.d.mts +2 -2
  9. package/lib/index.mjs +4 -4
  10. package/lib/legacy-compat/index.cjs +3 -3
  11. package/lib/legacy-compat/index.d.cts +1 -1
  12. package/lib/legacy-compat/index.d.mts +1 -1
  13. package/lib/legacy-compat/index.mjs +2 -2
  14. package/lib/{navios.factory-CO5MB_OK.cjs → navios.factory-CUrO_p6K.cjs} +461 -48
  15. package/lib/navios.factory-CUrO_p6K.cjs.map +1 -0
  16. package/lib/{navios.factory-D6Y94P9B.mjs → navios.factory-DjUOOVVL.mjs} +372 -49
  17. package/lib/navios.factory-DjUOOVVL.mjs.map +1 -0
  18. package/lib/testing/index.cjs +2 -2
  19. package/lib/testing/index.d.cts +1 -1
  20. package/lib/testing/index.d.mts +1 -1
  21. package/lib/testing/index.mjs +2 -2
  22. package/lib/{tokens-CWw9kyeD.cjs → tokens-BEuBMGGX.cjs} +18 -21
  23. package/lib/tokens-BEuBMGGX.cjs.map +1 -0
  24. package/lib/{tokens-4J9sredA.mjs → tokens-COyNGV1I.mjs} +17 -20
  25. package/lib/tokens-COyNGV1I.mjs.map +1 -0
  26. package/lib/{use-guards.decorator-BecoQSmE.mjs → use-guards.decorator-DLmRl2CV.mjs} +14 -17
  27. package/lib/use-guards.decorator-DLmRl2CV.mjs.map +1 -0
  28. package/lib/{use-guards.decorator-C4ml9XaT.cjs → use-guards.decorator-DhumFTk3.cjs} +15 -18
  29. package/lib/use-guards.decorator-DhumFTk3.cjs.map +1 -0
  30. package/package.json +2 -2
  31. package/src/interfaces/index.mts +3 -0
  32. package/src/interfaces/plugin-context.mts +104 -0
  33. package/src/interfaces/plugin-stage.mts +62 -0
  34. package/src/interfaces/plugin.interface.mts +42 -62
  35. package/src/interfaces/staged-plugin.interface.mts +209 -0
  36. package/src/metadata/controller.metadata.mts +29 -22
  37. package/src/metadata/module.metadata.mts +33 -25
  38. package/src/navios.application.mts +247 -53
  39. package/src/services/module-loader.service.mts +11 -8
  40. package/src/utils/define-plugin.mts +251 -0
  41. package/src/utils/index.mts +1 -0
  42. package/lib/index-C0Sg16Eb.d.cts.map +0 -1
  43. package/lib/index-DySQ6Dpd.d.mts.map +0 -1
  44. package/lib/navios.factory-CO5MB_OK.cjs.map +0 -1
  45. package/lib/navios.factory-D6Y94P9B.mjs.map +0 -1
  46. package/lib/tokens-4J9sredA.mjs.map +0 -1
  47. package/lib/tokens-CWw9kyeD.cjs.map +0 -1
  48. package/lib/use-guards.decorator-BecoQSmE.mjs.map +0 -1
  49. 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
- PluginContext,
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
- private plugins: PluginDefinition<any, any>[] = []
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
- if (this.environment.hasAdapterSetup()) {
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 a plugin to be initialized after modules are loaded.
204
+ * Registers one or more plugins for initialization during the application lifecycle.
180
205
  *
181
- * Plugins are initialized in the order they are registered,
182
- * after all modules are loaded but before the server starts listening.
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 definition - Plugin definition with options
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
- * import { defineOpenApiPlugin } from '@navios/openapi-fastify'
190
- *
191
- * app.usePlugin(defineOpenApiPlugin({
192
- * info: { title: 'My API', version: '1.0.0' },
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
- definition: PluginDefinition<TOptions, TAdapter>,
231
+ definitions:
232
+ | AnyPluginDefinition<TOptions, TAdapter>
233
+ | AnyPluginDefinition<TOptions, TAdapter>[],
198
234
  ): this {
199
- this.plugins.push(definition)
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
- * - Loads all modules and their dependencies
208
- * - Sets up the adapter if one is configured
209
- * - Calls onModuleInit hooks on all modules
210
- * - Initializes registered plugins
211
- * - Marks the application as initialized
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
- if (this.environment.hasAdapterSetup() && this.adapter) {
233
- await this.adapter.setupAdapter(this.options)
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
- await this.initPlugins()
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.ready()
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
- private async initModules() {
248
- const modules = this.moduleLoader.getAllModules()
249
- if (this.adapter) {
250
- await this.adapter.onModulesInit(modules)
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
- private async initPlugins() {
255
- if (this.plugins.length === 0) return
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 (!this.adapter) {
258
- throw new Error('Cannot initialize plugins without an adapter')
394
+ if (stage === PluginStages.PRE_MODULES_TRAVERSE) {
395
+ return baseContext
259
396
  }
260
397
 
261
- const context: PluginContext = {
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
- for (const { plugin, options } of this.plugins) {
269
- this.logger.debug(`Initializing plugin: ${plugin.name}`)
270
- await plugin.register(context, options)
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
- assertAdapterSupports(this.adapter, 'configure')
338
- this.adapter.configure(options)
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
- assertAdapterSupports(this.adapter, 'enableCors')
363
- this.adapter.enableCors(options)
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
- assertAdapterSupports(this.adapter, 'enableMultipart')
387
- this.adapter.enableMultipart(options)
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
- assertAdapterSupports(this.adapter, 'setGlobalPrefix')
404
- this.adapter.setGlobalPrefix(prefix)
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
- } else if (extension.controllers && extension.moduleName) {
84
- // Create synthetic module metadata for loose controllers
85
- await this.registerControllers(
86
- extension.controllers,
87
- extension.moduleName,
88
- )
89
- } else if (extension.controllers) {
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
+ }