@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.
- package/CHANGELOG.md +78 -0
- package/lib/{index-DW9EPAE6.d.mts → index-D9MNh6Tx.d.mts} +534 -342
- package/lib/index-D9MNh6Tx.d.mts.map +1 -0
- package/lib/{index-pHp-dIGt.d.cts → index-Db1d3cwD.d.cts} +534 -342
- package/lib/index-Db1d3cwD.d.cts.map +1 -0
- package/lib/index.cjs +12 -3
- package/lib/index.d.cts +2 -2
- package/lib/index.d.mts +2 -2
- package/lib/index.mjs +3 -3
- package/lib/legacy-compat/index.cjs +1 -1
- package/lib/legacy-compat/index.cjs.map +1 -1
- package/lib/legacy-compat/index.d.cts +3 -3
- package/lib/legacy-compat/index.d.cts.map +1 -1
- package/lib/legacy-compat/index.d.mts +3 -3
- package/lib/legacy-compat/index.d.mts.map +1 -1
- package/lib/legacy-compat/index.mjs +1 -1
- package/lib/legacy-compat/index.mjs.map +1 -1
- package/lib/{src-QnxR5b7c.cjs → src-BRPtJ9fG.cjs} +474 -53
- package/lib/src-BRPtJ9fG.cjs.map +1 -0
- package/lib/{src-DyvCDuKO.mjs → src-Bo23RIo-.mjs} +454 -51
- package/lib/src-Bo23RIo-.mjs.map +1 -0
- package/lib/testing/index.cjs +346 -29
- package/lib/testing/index.cjs.map +1 -1
- package/lib/testing/index.d.cts +299 -63
- package/lib/testing/index.d.cts.map +1 -1
- package/lib/testing/index.d.mts +299 -63
- package/lib/testing/index.d.mts.map +1 -1
- package/lib/testing/index.mjs +347 -31
- package/lib/testing/index.mjs.map +1 -1
- package/lib/{use-guards.decorator-B6q_N0sf.cjs → use-guards.decorator-Bs8oDHOi.cjs} +29 -99
- package/lib/use-guards.decorator-Bs8oDHOi.cjs.map +1 -0
- package/lib/{use-guards.decorator-kZ3lNK8v.mjs → use-guards.decorator-CzVXuLkz.mjs} +23 -99
- package/lib/use-guards.decorator-CzVXuLkz.mjs.map +1 -0
- package/package.json +4 -4
- package/src/__tests__/controller-resolver.spec.mts +229 -0
- package/src/__tests__/controller.spec.mts +1 -1
- package/src/__tests__/testing-module.spec.mts +459 -0
- package/src/__tests__/unit-testing-module.spec.mts +424 -0
- package/src/decorators/controller.decorator.mts +29 -7
- package/src/decorators/endpoint.decorator.mts +60 -12
- package/src/decorators/module.decorator.mts +23 -5
- package/src/decorators/multipart.decorator.mts +67 -24
- package/src/decorators/stream.decorator.mts +65 -24
- package/src/interfaces/abstract-http-handler-adapter.interface.mts +31 -1
- package/src/legacy-compat/__type-tests__/legacy-decorators.spec-d.mts +2 -6
- package/src/legacy-compat/decorators/endpoint.decorator.mts +1 -1
- package/src/legacy-compat/decorators/multipart.decorator.mts +5 -5
- package/src/legacy-compat/decorators/stream.decorator.mts +5 -5
- package/src/logger/logger.service.mts +0 -2
- package/src/navios.application.mts +23 -9
- package/src/navios.environment.mts +3 -1
- package/src/navios.factory.mts +19 -18
- package/src/services/guard-runner.service.mts +46 -9
- package/src/services/index.mts +1 -0
- package/src/services/instance-resolver.service.mts +187 -0
- package/src/services/module-loader.service.mts +3 -2
- package/src/stores/request-id.store.mts +45 -3
- package/src/testing/index.mts +1 -0
- package/src/testing/testing-module.mts +255 -93
- package/src/testing/unit-testing-module.mts +298 -0
- package/src/tokens/index.mts +1 -0
- package/src/tokens/navios-options.token.mts +6 -0
- package/lib/index-DW9EPAE6.d.mts.map +0 -1
- package/lib/index-pHp-dIGt.d.cts.map +0 -1
- package/lib/src-DyvCDuKO.mjs.map +0 -1
- package/lib/src-QnxR5b7c.cjs.map +0 -1
- package/lib/use-guards.decorator-B6q_N0sf.cjs.map +0 -1
- 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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
81
|
+
if (!requestIdEnabled) {
|
|
82
|
+
return undefined
|
|
83
|
+
}
|
|
84
|
+
return getRequestIdStore().getStore()
|
|
43
85
|
}
|
package/src/testing/index.mts
CHANGED