@navios/core 0.6.0 → 0.7.1

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 (102) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/README.md +18 -1
  3. package/docs/README.md +1 -0
  4. package/docs/legacy-compat.md +320 -0
  5. package/docs/testing.md +140 -17
  6. package/lib/index-DW9EPAE6.d.mts +2156 -0
  7. package/lib/index-DW9EPAE6.d.mts.map +1 -0
  8. package/lib/index-pHp-dIGt.d.cts +2156 -0
  9. package/lib/index-pHp-dIGt.d.cts.map +1 -0
  10. package/lib/index.cjs +157 -0
  11. package/lib/index.d.cts +3 -0
  12. package/lib/index.d.mts +3 -190
  13. package/lib/index.mjs +4 -1459
  14. package/lib/legacy-compat/index.cjs +315 -0
  15. package/lib/legacy-compat/index.cjs.map +1 -0
  16. package/lib/legacy-compat/index.d.cts +219 -0
  17. package/lib/legacy-compat/index.d.cts.map +1 -0
  18. package/lib/legacy-compat/index.d.mts +219 -0
  19. package/lib/legacy-compat/index.d.mts.map +1 -0
  20. package/lib/legacy-compat/index.mjs +308 -0
  21. package/lib/legacy-compat/index.mjs.map +1 -0
  22. package/lib/src-DyvCDuKO.mjs +5443 -0
  23. package/lib/src-DyvCDuKO.mjs.map +1 -0
  24. package/lib/src-QnxR5b7c.cjs +5800 -0
  25. package/lib/src-QnxR5b7c.cjs.map +1 -0
  26. package/lib/testing/index.cjs +106 -0
  27. package/lib/testing/index.cjs.map +1 -0
  28. package/lib/testing/index.d.cts +156 -0
  29. package/lib/testing/index.d.cts.map +1 -0
  30. package/lib/testing/index.d.mts +156 -0
  31. package/lib/testing/index.d.mts.map +1 -0
  32. package/lib/testing/index.mjs +100 -0
  33. package/lib/testing/index.mjs.map +1 -0
  34. package/lib/use-guards.decorator-B6q_N0sf.cjs +622 -0
  35. package/lib/use-guards.decorator-B6q_N0sf.cjs.map +1 -0
  36. package/lib/use-guards.decorator-kZ3lNK8v.mjs +454 -0
  37. package/lib/use-guards.decorator-kZ3lNK8v.mjs.map +1 -0
  38. package/package.json +28 -8
  39. package/project.json +2 -2
  40. package/src/attribute.factory.mts +154 -0
  41. package/src/config/config-service.interface.mts +31 -0
  42. package/src/config/config.provider.mts +36 -0
  43. package/src/config/config.service.mts +94 -4
  44. package/src/decorators/controller.decorator.mts +28 -0
  45. package/src/decorators/endpoint.decorator.mts +76 -0
  46. package/src/decorators/header.decorator.mts +19 -0
  47. package/src/decorators/http-code.decorator.mts +20 -0
  48. package/src/decorators/module.decorator.mts +34 -0
  49. package/src/decorators/multipart.decorator.mts +41 -0
  50. package/src/decorators/stream.decorator.mts +33 -0
  51. package/src/decorators/use-guards.decorator.mts +29 -0
  52. package/src/exceptions/bad-request.exception.mts +21 -0
  53. package/src/exceptions/conflict.exception.mts +24 -0
  54. package/src/exceptions/forbidden.exception.mts +23 -0
  55. package/src/exceptions/http.exception.mts +26 -0
  56. package/src/exceptions/internal-server-error.exception.mts +26 -0
  57. package/src/exceptions/not-found.exception.mts +23 -0
  58. package/src/exceptions/unauthorized.exception.mts +23 -0
  59. package/src/index.mts +1 -0
  60. package/src/interfaces/abstract-execution-context.inteface.mts +35 -0
  61. package/src/interfaces/abstract-http-adapter.interface.mts +52 -0
  62. package/src/interfaces/abstract-http-handler-adapter.interface.mts +2 -2
  63. package/src/interfaces/can-activate.mts +31 -0
  64. package/src/interfaces/index.mts +1 -0
  65. package/src/interfaces/navios-module.mts +25 -0
  66. package/src/interfaces/plugin.interface.mts +105 -0
  67. package/src/legacy-compat/__type-tests__/legacy-decorators.spec-d.mts +420 -0
  68. package/src/legacy-compat/__type-tests__/tsconfig.json +15 -0
  69. package/src/legacy-compat/context-compat.mts +93 -0
  70. package/src/legacy-compat/decorators/controller.decorator.mts +31 -0
  71. package/src/legacy-compat/decorators/endpoint.decorator.mts +99 -0
  72. package/src/legacy-compat/decorators/header.decorator.mts +42 -0
  73. package/src/legacy-compat/decorators/http-code.decorator.mts +38 -0
  74. package/src/legacy-compat/decorators/index.mts +9 -0
  75. package/src/legacy-compat/decorators/module.decorator.mts +37 -0
  76. package/src/legacy-compat/decorators/multipart.decorator.mts +93 -0
  77. package/src/legacy-compat/decorators/stream.decorator.mts +76 -0
  78. package/src/legacy-compat/decorators/use-guards.decorator.mts +80 -0
  79. package/src/legacy-compat/index.mts +40 -0
  80. package/src/logger/console-logger.service.mts +15 -2
  81. package/src/logger/log-levels.mts +9 -0
  82. package/src/logger/logger.service.mts +21 -0
  83. package/src/logger/logger.tokens.mts +23 -0
  84. package/src/navios.application.mts +228 -4
  85. package/src/navios.factory.mts +60 -1
  86. package/src/services/guard-runner.service.mts +12 -11
  87. package/src/services/module-loader.service.mts +118 -12
  88. package/src/stores/index.mts +1 -0
  89. package/src/stores/request-id.store.mts +43 -0
  90. package/src/testing/index.mts +2 -0
  91. package/src/testing/testing-module.mts +231 -0
  92. package/tsconfig.lib.json +1 -1
  93. package/tsconfig.spec.json +3 -0
  94. package/tsdown.config.mts +35 -0
  95. package/vitest.config.mts +6 -0
  96. package/lib/_tsup-dts-rollup.d.mts +0 -1365
  97. package/lib/_tsup-dts-rollup.d.ts +0 -1365
  98. package/lib/index.d.ts +0 -190
  99. package/lib/index.js +0 -1540
  100. package/lib/index.js.map +0 -1
  101. package/lib/index.mjs.map +0 -1
  102. package/tsup.config.mts +0 -13
@@ -1,4 +1,4 @@
1
- import type { ClassTypeWithInstance } from '@navios/di'
1
+ import type { ClassType, ClassTypeWithInstance } from '@navios/di'
2
2
 
3
3
  import { Container, inject, Injectable } from '@navios/di'
4
4
 
@@ -8,6 +8,30 @@ import type { ModuleMetadata } from '../metadata/index.mjs'
8
8
  import { Logger } from '../logger/index.mjs'
9
9
  import { extractModuleMetadata } from '../metadata/index.mjs'
10
10
 
11
+ /**
12
+ * Extension definition for dynamically adding to the module tree.
13
+ * Used by plugins to inject controllers or entire modules.
14
+ */
15
+ export interface ModuleExtension {
16
+ /**
17
+ * Module class to add. If provided, the module and all its
18
+ * controllers/imports will be processed.
19
+ */
20
+ module?: ClassTypeWithInstance<NaviosModule>
21
+
22
+ /**
23
+ * Controllers to add directly without a wrapper module.
24
+ * Will be registered under a synthetic module named after the plugin.
25
+ */
26
+ controllers?: ClassType[]
27
+
28
+ /**
29
+ * Name for the synthetic module when using controllers directly.
30
+ * Required if `controllers` is provided without `module`.
31
+ */
32
+ moduleName?: string
33
+ }
34
+
11
35
  @Injectable()
12
36
  export class ModuleLoaderService {
13
37
  private logger = inject(Logger, {
@@ -26,6 +50,83 @@ export class ModuleLoaderService {
26
50
  this.initialized = true
27
51
  }
28
52
 
53
+ /**
54
+ * Extends the module tree with additional modules or controllers.
55
+ *
56
+ * This method is designed to be called by plugins during registration,
57
+ * which happens after initial module loading but before route registration.
58
+ *
59
+ * @param extensions - Array of module extensions to add
60
+ * @throws Error if not initialized (loadModules must be called first)
61
+ *
62
+ * @example
63
+ * ```typescript
64
+ * // In plugin registration
65
+ * const moduleLoader = await context.container.get(ModuleLoaderService)
66
+ * await moduleLoader.extendModules([{
67
+ * controllers: [OpenApiJsonController, OpenApiYamlController],
68
+ * moduleName: 'OpenApiBunModule',
69
+ * }])
70
+ * ```
71
+ */
72
+ async extendModules(extensions: ModuleExtension[]): Promise<void> {
73
+ if (!this.initialized) {
74
+ throw new Error(
75
+ 'ModuleLoaderService must be initialized before extending. Call loadModules() first.',
76
+ )
77
+ }
78
+
79
+ for (const extension of extensions) {
80
+ if (extension.module) {
81
+ // Process a full module with its imports and controllers
82
+ 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) {
90
+ throw new Error(
91
+ 'moduleName is required when providing controllers without a module',
92
+ )
93
+ }
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Registers controllers under a synthetic module.
99
+ * Used when plugins want to add controllers without a full module class.
100
+ */
101
+ private async registerControllers(
102
+ controllers: ClassType[],
103
+ moduleName: string,
104
+ ): Promise<void> {
105
+ if (this.modulesMetadata.has(moduleName)) {
106
+ // Merge controllers into existing module
107
+ const existing = this.modulesMetadata.get(moduleName)!
108
+ for (const controller of controllers) {
109
+ existing.controllers.add(controller)
110
+ }
111
+ this.logger.debug(
112
+ `Extended module ${moduleName} with ${controllers.length} controllers`,
113
+ )
114
+ } else {
115
+ // Create new synthetic module metadata
116
+ const metadata: ModuleMetadata = {
117
+ controllers: new Set(controllers),
118
+ imports: new Set(),
119
+ guards: new Set(),
120
+ customAttributes: new Map(),
121
+ }
122
+ this.modulesMetadata.set(moduleName, metadata)
123
+
124
+ this.logger.debug(
125
+ `Created module ${moduleName} with ${controllers.length} controllers`,
126
+ )
127
+ }
128
+ }
129
+
29
130
  private async traverseModules(
30
131
  module: ClassTypeWithInstance<NaviosModule>,
31
132
  parentMetadata?: ModuleMetadata,
@@ -38,18 +139,23 @@ export class ModuleLoaderService {
38
139
  if (this.modulesMetadata.has(moduleName)) {
39
140
  return
40
141
  }
41
- this.modulesMetadata.set(moduleName, metadata)
42
- const imports = metadata.imports ?? new Set()
43
- const loadingPromises = Array.from(imports).map(async (importedModule) =>
44
- this.traverseModules(importedModule, metadata),
45
- )
46
- await Promise.all(loadingPromises)
47
- const instance = await this.container.get(module)
48
- if (instance.onModuleInit) {
49
- await instance.onModuleInit()
142
+ try {
143
+ this.modulesMetadata.set(moduleName, metadata)
144
+ const imports = metadata.imports ?? new Set()
145
+ const loadingPromises = Array.from(imports).map(async (importedModule) =>
146
+ this.traverseModules(importedModule, metadata),
147
+ )
148
+ await Promise.all(loadingPromises)
149
+ const instance = await this.container.get(module)
150
+ if (instance.onModuleInit) {
151
+ await instance.onModuleInit()
152
+ }
153
+ this.logger.debug(`Module ${moduleName} loaded`)
154
+ this.loadedModules.set(moduleName, instance)
155
+ } catch (error) {
156
+ this.logger.error(`Error loading module ${moduleName}`, error)
157
+ throw error
50
158
  }
51
- this.logger.debug(`Module ${moduleName} loaded`)
52
- this.loadedModules.set(moduleName, instance)
53
159
  }
54
160
 
55
161
  private mergeMetadata(
@@ -0,0 +1 @@
1
+ export * from './request-id.store.mjs'
@@ -0,0 +1,43 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks'
2
+
3
+ /**
4
+ * AsyncLocalStorage store for the current request ID.
5
+ *
6
+ * This allows logging and other services to access the current request ID
7
+ * without explicitly passing it through the call stack.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { requestIdStore, runWithRequestId, getRequestId } from '@navios/core'
12
+ *
13
+ * // Run code with a request ID in context
14
+ * runWithRequestId('req-123', () => {
15
+ * // Inside this callback, getRequestId() returns 'req-123'
16
+ * logger.log('Processing request') // Will include request ID if logger is configured
17
+ * })
18
+ *
19
+ * // Get current request ID (returns undefined if not in a request context)
20
+ * const currentId = getRequestId()
21
+ * ```
22
+ */
23
+ export const requestIdStore = new AsyncLocalStorage<string>()
24
+
25
+ /**
26
+ * Runs a function with a request ID in the async local storage context.
27
+ *
28
+ * @param requestId - The request ID to set for this context
29
+ * @param fn - The function to run within this context
30
+ * @returns The return value of the function
31
+ */
32
+ export function runWithRequestId<R>(requestId: string, fn: () => R): R {
33
+ return requestIdStore.run(requestId, fn)
34
+ }
35
+
36
+ /**
37
+ * Gets the current request ID from the async local storage context.
38
+ *
39
+ * @returns The current request ID, or undefined if not in a request context
40
+ */
41
+ export function getRequestId(): string | undefined {
42
+ return requestIdStore.getStore()
43
+ }
@@ -0,0 +1,2 @@
1
+ export * from '@navios/di/testing'
2
+ export * from './testing-module.mjs'
@@ -0,0 +1,231 @@
1
+ import type { ClassType, ClassTypeWithInstance, InjectionToken } from '@navios/di'
2
+ import type { NaviosModule } from '../interfaces/index.mjs'
3
+ import type { NaviosApplicationOptions } from '../navios.application.mjs'
4
+
5
+ import { TestContainer } from '@navios/di/testing'
6
+
7
+ import { NaviosApplication } from '../navios.application.mjs'
8
+ import { NaviosFactory } from '../navios.factory.mjs'
9
+
10
+ /**
11
+ * Configuration for overriding a provider in the testing module.
12
+ *
13
+ * @typeParam T - The type of the provider being overridden
14
+ */
15
+ export interface TestingModuleOverride<T = any> {
16
+ /**
17
+ * The injection token or class to override.
18
+ */
19
+ token: ClassType | InjectionToken<T, any>
20
+ /**
21
+ * Value to use instead of the original provider.
22
+ */
23
+ useValue?: T
24
+ /**
25
+ * Class to use instead of the original provider.
26
+ */
27
+ useClass?: ClassType
28
+ }
29
+
30
+ /**
31
+ * Options for creating a testing module.
32
+ *
33
+ * Extends NaviosApplicationOptions but excludes the container option,
34
+ * as TestingModule manages its own TestContainer.
35
+ */
36
+ export interface TestingModuleOptions
37
+ extends Omit<NaviosApplicationOptions, 'container'> {
38
+ /**
39
+ * Initial provider overrides to apply when creating the testing module.
40
+ *
41
+ * You can also use `overrideProvider()` method for a fluent API.
42
+ */
43
+ overrides?: TestingModuleOverride[]
44
+ }
45
+
46
+ /**
47
+ * A testing-optimized wrapper around NaviosApplication.
48
+ * Provides utilities for setting up test environments with mock dependencies.
49
+ */
50
+ export class TestingModule {
51
+ private app: NaviosApplication | null = null
52
+
53
+ constructor(
54
+ private readonly appModule: ClassTypeWithInstance<NaviosModule>,
55
+ private readonly container: TestContainer,
56
+ private readonly options: TestingModuleOptions,
57
+ ) {}
58
+
59
+ /**
60
+ * Compiles the testing module and returns the NaviosApplication.
61
+ * Call this after setting up all overrides.
62
+ */
63
+ async compile(): Promise<NaviosApplication> {
64
+ this.app = await NaviosFactory.create(this.appModule, {
65
+ ...this.options,
66
+ container: this.container,
67
+ })
68
+ return this.app
69
+ }
70
+
71
+ /**
72
+ * Initializes the application (loads modules, sets up HTTP if configured).
73
+ * This is equivalent to calling app.init() on the compiled application.
74
+ */
75
+ async init(): Promise<NaviosApplication> {
76
+ if (!this.app) {
77
+ await this.compile()
78
+ }
79
+ await this.app!.init()
80
+ return this.app!
81
+ }
82
+
83
+ /**
84
+ * Gets the underlying TestContainer for direct manipulation.
85
+ */
86
+ getContainer(): TestContainer {
87
+ return this.container
88
+ }
89
+
90
+ /**
91
+ * Gets the compiled application. Throws if not yet compiled.
92
+ */
93
+ getApplication(): NaviosApplication {
94
+ if (!this.app) {
95
+ throw new Error(
96
+ 'TestingModule not compiled. Call compile() or init() first.',
97
+ )
98
+ }
99
+ return this.app
100
+ }
101
+
102
+ /**
103
+ * Override a provider with a mock value.
104
+ */
105
+ overrideProvider<T>(token: ClassType | InjectionToken<T, any>): {
106
+ useValue: (value: T) => TestingModule
107
+ useClass: (target: ClassType) => TestingModule
108
+ } {
109
+ return {
110
+ useValue: (value: T) => {
111
+ this.container.bind(token as any).toValue(value)
112
+ return this
113
+ },
114
+ useClass: (target: ClassType) => {
115
+ this.container.bind(token as any).toClass(target)
116
+ return this
117
+ },
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Gets an instance from the container.
123
+ */
124
+ async get<T>(token: ClassTypeWithInstance<T> | InjectionToken<T, any>): Promise<T> {
125
+ return this.container.get(token as any)
126
+ }
127
+
128
+ /**
129
+ * Disposes the testing module and cleans up resources.
130
+ */
131
+ async close(): Promise<void> {
132
+ if (this.app) {
133
+ await this.app.close()
134
+ }
135
+ await this.container.dispose()
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Fluent builder interface for TestingModule.
141
+ *
142
+ * Provides a chainable API for configuring and using a testing module.
143
+ */
144
+ export interface TestingModuleBuilder {
145
+ /**
146
+ * Override a provider with a mock value or class.
147
+ *
148
+ * @param token - The injection token or class to override
149
+ * @returns An object with `useValue` and `useClass` methods for chaining
150
+ *
151
+ * @example
152
+ * ```typescript
153
+ * const testingModule = await createTestingModule(AppModule)
154
+ * .overrideProvider(DatabaseService)
155
+ * .useValue(mockDatabaseService)
156
+ * .compile()
157
+ * ```
158
+ */
159
+ overrideProvider<T>(token: ClassType | InjectionToken<T, any>): {
160
+ useValue: (value: T) => TestingModuleBuilder
161
+ useClass: (target: ClassType) => TestingModuleBuilder
162
+ }
163
+
164
+ /**
165
+ * Compiles the testing module and returns the NaviosApplication.
166
+ *
167
+ * This creates the application instance but does not initialize it.
168
+ * Call `init()` if you need the application to be fully initialized.
169
+ */
170
+ compile(): Promise<NaviosApplication>
171
+
172
+ /**
173
+ * Initializes the application (loads modules, sets up HTTP if configured).
174
+ *
175
+ * This is equivalent to calling `compile()` followed by `app.init()`.
176
+ */
177
+ init(): Promise<NaviosApplication>
178
+
179
+ /**
180
+ * Gets the underlying TestContainer for direct manipulation.
181
+ */
182
+ getContainer(): TestContainer
183
+
184
+ /**
185
+ * Gets an instance from the container.
186
+ *
187
+ * @typeParam T - The type of the instance to retrieve
188
+ * @param token - The injection token or class
189
+ * @returns The resolved instance
190
+ */
191
+ get<T>(token: ClassTypeWithInstance<T> | InjectionToken<T, any>): Promise<T>
192
+
193
+ /**
194
+ * Disposes the testing module and cleans up resources.
195
+ */
196
+ close(): Promise<void>
197
+ }
198
+
199
+ /**
200
+ * Creates a testing module for the given app module.
201
+ * This is the main entry point for setting up tests.
202
+ *
203
+ * @example
204
+ * ```typescript
205
+ * const testingModule = await createTestingModule(AppModule, {
206
+ * adapter: [],
207
+ * })
208
+ * .overrideProvider(DatabaseService)
209
+ * .useValue(mockDatabaseService)
210
+ * .compile()
211
+ * ```
212
+ */
213
+ export function createTestingModule(
214
+ appModule: ClassTypeWithInstance<NaviosModule>,
215
+ options: TestingModuleOptions = { adapter: [] },
216
+ ): TestingModule {
217
+ const container = new TestContainer()
218
+
219
+ // Apply initial overrides if provided
220
+ if (options.overrides) {
221
+ for (const override of options.overrides) {
222
+ if (override.useValue !== undefined) {
223
+ container.bind(override.token as any).toValue(override.useValue)
224
+ } else if (override.useClass) {
225
+ container.bind(override.token as any).toClass(override.useClass)
226
+ }
227
+ }
228
+ }
229
+
230
+ return new TestingModule(appModule, container, options)
231
+ }
package/tsconfig.lib.json CHANGED
@@ -4,5 +4,5 @@
4
4
  "outDir": "./dist"
5
5
  },
6
6
  "include": ["src/**/*"],
7
- "exclude": ["src/**/*.spec.mts"]
7
+ "exclude": ["src/**/*.spec.mts", "src/**/*.spec-d.mts"]
8
8
  }
@@ -7,6 +7,9 @@
7
7
  "references": [
8
8
  {
9
9
  "path": "./tsconfig.lib.json"
10
+ },
11
+ {
12
+ "path": "./src/legacy-compat/__type-tests__/tsconfig.json"
10
13
  }
11
14
  ]
12
15
  }
@@ -0,0 +1,35 @@
1
+ import { withFilter } from 'rolldown/filter'
2
+ import { defineConfig } from 'tsdown'
3
+ import swc from 'unplugin-swc'
4
+
5
+ export default defineConfig({
6
+ entry: ['src/index.mts', 'src/testing/index.mts', 'src/legacy-compat/index.mts'],
7
+ outDir: 'lib',
8
+ format: ['esm', 'cjs'],
9
+ clean: true,
10
+ tsconfig: 'tsconfig.lib.json',
11
+ treeshake: true,
12
+ sourcemap: true,
13
+ platform: 'node',
14
+ external: ['@navios/di'],
15
+ dts: true,
16
+ target: 'es2022',
17
+ plugins: [
18
+ withFilter(
19
+ swc.rolldown({
20
+ jsc: {
21
+ target: 'es2022',
22
+ parser: {
23
+ syntax: 'typescript',
24
+ decorators: true,
25
+ },
26
+ transform: {
27
+ decoratorVersion: '2022-03',
28
+ },
29
+ },
30
+ }),
31
+ // Only run this transform if the file contains a decorator.
32
+ { transform: { code: '@' } },
33
+ ),
34
+ ],
35
+ })
package/vitest.config.mts CHANGED
@@ -4,6 +4,12 @@ export default defineProject({
4
4
  test: {
5
5
  typecheck: {
6
6
  enabled: true,
7
+ tsconfig: './tsconfig.lib.json',
7
8
  },
9
+ include: [
10
+ 'src/**/*.spec.mts',
11
+ 'src/**/*.spec-d.mts',
12
+ 'src/legacy-compat/__type-tests__/**/*.spec-d.mts',
13
+ ],
8
14
  },
9
15
  })