@navios/core 0.7.1 → 0.9.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 (68) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/lib/{index-DW9EPAE6.d.mts → index-D9MNh6Tx.d.mts} +534 -342
  3. package/lib/index-D9MNh6Tx.d.mts.map +1 -0
  4. package/lib/{index-pHp-dIGt.d.cts → index-Db1d3cwD.d.cts} +534 -342
  5. package/lib/index-Db1d3cwD.d.cts.map +1 -0
  6. package/lib/index.cjs +12 -3
  7. package/lib/index.d.cts +2 -2
  8. package/lib/index.d.mts +2 -2
  9. package/lib/index.mjs +3 -3
  10. package/lib/legacy-compat/index.cjs +1 -1
  11. package/lib/legacy-compat/index.cjs.map +1 -1
  12. package/lib/legacy-compat/index.d.cts +3 -3
  13. package/lib/legacy-compat/index.d.cts.map +1 -1
  14. package/lib/legacy-compat/index.d.mts +3 -3
  15. package/lib/legacy-compat/index.d.mts.map +1 -1
  16. package/lib/legacy-compat/index.mjs +1 -1
  17. package/lib/legacy-compat/index.mjs.map +1 -1
  18. package/lib/{src-QnxR5b7c.cjs → src-BRPtJ9fG.cjs} +474 -53
  19. package/lib/src-BRPtJ9fG.cjs.map +1 -0
  20. package/lib/{src-DyvCDuKO.mjs → src-Bo23RIo-.mjs} +454 -51
  21. package/lib/src-Bo23RIo-.mjs.map +1 -0
  22. package/lib/testing/index.cjs +346 -29
  23. package/lib/testing/index.cjs.map +1 -1
  24. package/lib/testing/index.d.cts +299 -63
  25. package/lib/testing/index.d.cts.map +1 -1
  26. package/lib/testing/index.d.mts +299 -63
  27. package/lib/testing/index.d.mts.map +1 -1
  28. package/lib/testing/index.mjs +347 -31
  29. package/lib/testing/index.mjs.map +1 -1
  30. package/lib/{use-guards.decorator-B6q_N0sf.cjs → use-guards.decorator-Bs8oDHOi.cjs} +29 -99
  31. package/lib/use-guards.decorator-Bs8oDHOi.cjs.map +1 -0
  32. package/lib/{use-guards.decorator-kZ3lNK8v.mjs → use-guards.decorator-CzVXuLkz.mjs} +23 -99
  33. package/lib/use-guards.decorator-CzVXuLkz.mjs.map +1 -0
  34. package/package.json +4 -4
  35. package/src/__tests__/controller-resolver.spec.mts +229 -0
  36. package/src/__tests__/controller.spec.mts +1 -1
  37. package/src/__tests__/testing-module.spec.mts +459 -0
  38. package/src/__tests__/unit-testing-module.spec.mts +424 -0
  39. package/src/decorators/controller.decorator.mts +29 -7
  40. package/src/decorators/endpoint.decorator.mts +60 -12
  41. package/src/decorators/module.decorator.mts +23 -5
  42. package/src/decorators/multipart.decorator.mts +67 -24
  43. package/src/decorators/stream.decorator.mts +65 -24
  44. package/src/interfaces/abstract-http-handler-adapter.interface.mts +31 -1
  45. package/src/legacy-compat/__type-tests__/legacy-decorators.spec-d.mts +2 -6
  46. package/src/legacy-compat/decorators/endpoint.decorator.mts +1 -1
  47. package/src/legacy-compat/decorators/multipart.decorator.mts +5 -5
  48. package/src/legacy-compat/decorators/stream.decorator.mts +5 -5
  49. package/src/logger/logger.service.mts +0 -2
  50. package/src/navios.application.mts +23 -9
  51. package/src/navios.environment.mts +3 -1
  52. package/src/navios.factory.mts +19 -18
  53. package/src/services/guard-runner.service.mts +46 -9
  54. package/src/services/index.mts +1 -0
  55. package/src/services/instance-resolver.service.mts +187 -0
  56. package/src/services/module-loader.service.mts +3 -2
  57. package/src/stores/request-id.store.mts +45 -3
  58. package/src/testing/index.mts +1 -0
  59. package/src/testing/testing-module.mts +255 -93
  60. package/src/testing/unit-testing-module.mts +298 -0
  61. package/src/tokens/index.mts +1 -0
  62. package/src/tokens/navios-options.token.mts +6 -0
  63. package/lib/index-DW9EPAE6.d.mts.map +0 -1
  64. package/lib/index-pHp-dIGt.d.cts.map +0 -1
  65. package/lib/src-DyvCDuKO.mjs.map +0 -1
  66. package/lib/src-QnxR5b7c.cjs.map +0 -1
  67. package/lib/use-guards.decorator-B6q_N0sf.cjs.map +0 -1
  68. package/lib/use-guards.decorator-kZ3lNK8v.mjs.map +0 -1
@@ -0,0 +1,187 @@
1
+ import type { ClassType, ScopedContainer } from '@navios/di'
2
+
3
+ import {
4
+ Container,
5
+ getInjectableToken,
6
+ inject,
7
+ Injectable,
8
+ InjectableScope,
9
+ } from '@navios/di'
10
+
11
+ /**
12
+ * Result of instance resolution attempt.
13
+ * Contains either a cached singleton instance or a resolver function
14
+ * that can be used to get a fresh instance per request.
15
+ */
16
+ export interface InstanceResolution<T = any> {
17
+ /**
18
+ * Whether the instance was successfully cached as a singleton.
19
+ * If true, `instance` contains the cached instance.
20
+ * If false, the class has request-scoped dependencies and
21
+ * must be resolved per-request using `resolve()`.
22
+ */
23
+ cached: boolean
24
+
25
+ /**
26
+ * The cached instance (only available if `cached` is true).
27
+ */
28
+ instance: T | null
29
+
30
+ /**
31
+ * Resolves the instance from a scoped container.
32
+ * Use this when `cached` is false to get a fresh instance per request.
33
+ */
34
+ resolve: (scoped: ScopedContainer) => Promise<T>
35
+ }
36
+
37
+ /**
38
+ * Result of resolving multiple instances.
39
+ * Contains either all cached singleton instances or a resolver function.
40
+ */
41
+ export interface MultiInstanceResolution<T = any> {
42
+ /**
43
+ * Whether ALL instances were successfully cached as singletons.
44
+ * If true, `instances` contains all cached instances.
45
+ * If false, at least one class has request-scoped dependencies.
46
+ */
47
+ cached: boolean
48
+
49
+ /**
50
+ * The cached instances (only available if `cached` is true).
51
+ * Order matches the input array order.
52
+ */
53
+ instances: T[] | null
54
+
55
+ /**
56
+ * The original class types for dynamic resolution.
57
+ */
58
+ classTypes: ClassType[]
59
+
60
+ /**
61
+ * Resolves all instances from a scoped container.
62
+ * Use this when `cached` is false to get fresh instances per request.
63
+ */
64
+ resolve: (scoped: ScopedContainer) => Promise<T[]>
65
+ }
66
+
67
+ /**
68
+ * Service responsible for resolving class instances with automatic scope detection.
69
+ *
70
+ * This service attempts to resolve classes as singletons from the root container.
71
+ * If resolution fails (because the class has request-scoped dependencies),
72
+ * it automatically updates the class's scope to Request and provides a
73
+ * resolver function for per-request instantiation.
74
+ *
75
+ * This enables optimal performance:
76
+ * - Classes without request-scoped deps stay as singletons (faster)
77
+ * - Classes with request-scoped deps are automatically promoted to request scope
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * const resolution = await instanceResolver.resolve(UserController)
82
+ *
83
+ * if (resolution.cached) {
84
+ * // Use cached singleton
85
+ * return resolution.instance.handleRequest(req)
86
+ * } else {
87
+ * // Resolve per request
88
+ * const controller = await resolution.resolve(scopedContainer)
89
+ * return controller.handleRequest(req)
90
+ * }
91
+ * ```
92
+ */
93
+ @Injectable()
94
+ export class InstanceResolverService {
95
+ private container = inject(Container)
96
+
97
+ /**
98
+ * Attempts to resolve a class instance, automatically detecting if it needs
99
+ * request scope based on its dependencies.
100
+ *
101
+ * @param classType - The class to resolve
102
+ * @returns A resolution result containing either a cached instance or resolver function
103
+ */
104
+ async resolve<T>(classType: ClassType): Promise<InstanceResolution<T>> {
105
+ let cachedInstance: T | null = null
106
+
107
+ try {
108
+ cachedInstance = await this.container.get(classType)
109
+ } catch {
110
+ // Class has request-scoped dependencies, update its scope to Request
111
+ // so it will be resolved per-request from the scoped container
112
+ const token = getInjectableToken(classType)
113
+ this.container.getRegistry().updateScope(token, InjectableScope.Request)
114
+ }
115
+
116
+ return {
117
+ cached: cachedInstance !== null,
118
+ instance: cachedInstance,
119
+ resolve: (scoped: ScopedContainer) => scoped.get(classType) as Promise<T>,
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Attempts to resolve multiple class instances, automatically detecting if any need
125
+ * request scope based on their dependencies.
126
+ *
127
+ * Returns `cached: true` only if ALL classes can be resolved as singletons.
128
+ * If any class has request-scoped dependencies, returns `cached: false`.
129
+ *
130
+ * @param classTypes - The classes to resolve
131
+ * @returns A resolution result containing either all cached instances or resolver function
132
+ */
133
+ async resolveMany<T>(
134
+ classTypes: ClassType[],
135
+ ): Promise<MultiInstanceResolution<T>> {
136
+ if (classTypes.length === 0) {
137
+ return {
138
+ cached: true,
139
+ instances: [],
140
+ classTypes: [],
141
+ resolve: async () => [],
142
+ }
143
+ }
144
+
145
+ // Resolve all classes in parallel
146
+ const results = await Promise.all(
147
+ classTypes.map(async (classType) => {
148
+ try {
149
+ const instance = await this.container.get(classType)
150
+ return { success: true, instance: instance as T }
151
+ } catch {
152
+ // Class has request-scoped dependencies, update its scope to Request
153
+ const token = getInjectableToken(classType)
154
+ this.container
155
+ .getRegistry()
156
+ .updateScope(token, InjectableScope.Request)
157
+ return { success: false, instance: null }
158
+ }
159
+ }),
160
+ )
161
+
162
+ const allCached = results.every((r) => r.success)
163
+ const cachedInstances = allCached
164
+ ? results.map((r) => r.instance as T)
165
+ : null
166
+
167
+ return {
168
+ cached: allCached,
169
+ instances: cachedInstances,
170
+ classTypes,
171
+ resolve: (scoped: ScopedContainer) =>
172
+ Promise.all(
173
+ classTypes.map((classType) => scoped.get(classType) as Promise<T>),
174
+ ),
175
+ }
176
+ }
177
+ }
178
+
179
+ /**
180
+ * @deprecated Use InstanceResolverService instead
181
+ */
182
+ export const ControllerResolverService = InstanceResolverService
183
+
184
+ /**
185
+ * @deprecated Use InstanceResolution instead
186
+ */
187
+ export type ControllerResolution<T = any> = InstanceResolution<T>
@@ -1,6 +1,6 @@
1
1
  import type { ClassType, ClassTypeWithInstance } from '@navios/di'
2
2
 
3
- import { Container, inject, Injectable } from '@navios/di'
3
+ import { Container, getInjectableToken, inject, Injectable } from '@navios/di'
4
4
 
5
5
  import type { NaviosModule } from '../interfaces/index.mjs'
6
6
  import type { ModuleMetadata } from '../metadata/index.mjs'
@@ -135,7 +135,8 @@ export class ModuleLoaderService {
135
135
  if (parentMetadata) {
136
136
  this.mergeMetadata(metadata, parentMetadata)
137
137
  }
138
- const moduleName = module.name
138
+ const moduleToken = getInjectableToken(module)
139
+ const moduleName = moduleToken.id
139
140
  if (this.modulesMetadata.has(moduleName)) {
140
141
  return
141
142
  }
@@ -1,5 +1,17 @@
1
1
  import { AsyncLocalStorage } from 'node:async_hooks'
2
2
 
3
+ let requestCounter = 0
4
+
5
+ /**
6
+ * Generates a simple incremental request ID.
7
+ * Much faster than crypto.randomUUID() and sufficient for request tracking.
8
+ *
9
+ * @returns A unique request ID string (e.g., "req-1", "req-2", ...)
10
+ */
11
+ export function generateRequestId(): string {
12
+ return `req-${++requestCounter}`
13
+ }
14
+
3
15
  /**
4
16
  * AsyncLocalStorage store for the current request ID.
5
17
  *
@@ -20,17 +32,44 @@ import { AsyncLocalStorage } from 'node:async_hooks'
20
32
  * const currentId = getRequestId()
21
33
  * ```
22
34
  */
23
- export const requestIdStore = new AsyncLocalStorage<string>()
35
+ let requestIdStore: AsyncLocalStorage<string> | null = null
36
+
37
+ function getRequestIdStore(): AsyncLocalStorage<string> {
38
+ if (!requestIdStore) {
39
+ requestIdStore = new AsyncLocalStorage<string>()
40
+ }
41
+ return requestIdStore!
42
+ }
43
+ /**
44
+ * Whether request ID propagation is enabled.
45
+ * When disabled, runWithRequestId is a pass-through for better performance.
46
+ */
47
+ let requestIdEnabled = false
48
+
49
+ /**
50
+ * Enables or disables request ID propagation.
51
+ * Called by NaviosFactory based on the enableRequestId option.
52
+ *
53
+ * @param enabled - Whether to enable request ID propagation
54
+ */
55
+ export function setRequestIdEnabled(enabled: boolean): void {
56
+ requestIdEnabled = enabled
57
+ }
24
58
 
25
59
  /**
26
60
  * Runs a function with a request ID in the async local storage context.
61
+ * If request ID propagation is disabled, the function is called directly
62
+ * without AsyncLocalStorage overhead.
27
63
  *
28
64
  * @param requestId - The request ID to set for this context
29
65
  * @param fn - The function to run within this context
30
66
  * @returns The return value of the function
31
67
  */
32
68
  export function runWithRequestId<R>(requestId: string, fn: () => R): R {
33
- return requestIdStore.run(requestId, fn)
69
+ if (!requestIdEnabled) {
70
+ return fn()
71
+ }
72
+ return getRequestIdStore().run(requestId, fn)
34
73
  }
35
74
 
36
75
  /**
@@ -39,5 +78,8 @@ export function runWithRequestId<R>(requestId: string, fn: () => R): R {
39
78
  * @returns The current request ID, or undefined if not in a request context
40
79
  */
41
80
  export function getRequestId(): string | undefined {
42
- return requestIdStore.getStore()
81
+ if (!requestIdEnabled) {
82
+ return undefined
83
+ }
84
+ return getRequestIdStore().getStore()
43
85
  }
@@ -1,2 +1,3 @@
1
1
  export * from '@navios/di/testing'
2
2
  export * from './testing-module.mjs'
3
+ export * from './unit-testing-module.mjs'