@navios/di 0.5.1 → 0.6.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 +146 -0
- package/README.md +196 -219
- package/docs/README.md +69 -11
- package/docs/api-reference.md +281 -117
- package/docs/container.md +220 -56
- package/docs/examples/request-scope-example.mts +2 -2
- package/docs/factory.md +3 -8
- package/docs/getting-started.md +37 -8
- package/docs/migration.md +318 -37
- package/docs/request-contexts.md +263 -175
- package/docs/scopes.md +79 -42
- package/lib/browser/index.d.mts +1577 -0
- package/lib/browser/index.d.mts.map +1 -0
- package/lib/browser/index.mjs +3012 -0
- package/lib/browser/index.mjs.map +1 -0
- package/lib/index-S_qX2VLI.d.mts +1211 -0
- package/lib/index-S_qX2VLI.d.mts.map +1 -0
- package/lib/index-fKPuT65j.d.cts +1206 -0
- package/lib/index-fKPuT65j.d.cts.map +1 -0
- package/lib/index.cjs +389 -0
- package/lib/index.cjs.map +1 -0
- package/lib/index.d.cts +376 -0
- package/lib/index.d.cts.map +1 -0
- package/lib/index.d.mts +371 -78
- package/lib/index.d.mts.map +1 -0
- package/lib/index.mjs +325 -63
- package/lib/index.mjs.map +1 -1
- package/lib/testing/index.cjs +9 -0
- package/lib/testing/index.d.cts +2 -0
- package/lib/testing/index.d.mts +2 -2
- package/lib/testing/index.mjs +2 -72
- package/lib/testing-BMGmmxH7.cjs +2895 -0
- package/lib/testing-BMGmmxH7.cjs.map +1 -0
- package/lib/testing-DCXz8AJD.mjs +2655 -0
- package/lib/testing-DCXz8AJD.mjs.map +1 -0
- package/package.json +23 -1
- package/project.json +2 -2
- package/src/__tests__/async-local-storage.browser.spec.mts +240 -0
- package/src/__tests__/async-local-storage.spec.mts +333 -0
- package/src/__tests__/container.spec.mts +30 -25
- package/src/__tests__/e2e.browser.spec.mts +790 -0
- package/src/__tests__/e2e.spec.mts +1222 -0
- package/src/__tests__/errors.spec.mts +6 -6
- package/src/__tests__/factory.spec.mts +1 -1
- package/src/__tests__/get-injectors.spec.mts +1 -1
- package/src/__tests__/injectable.spec.mts +1 -1
- package/src/__tests__/injection-token.spec.mts +1 -1
- package/src/__tests__/library-findings.spec.mts +563 -0
- package/src/__tests__/registry.spec.mts +2 -2
- package/src/__tests__/request-scope.spec.mts +266 -274
- package/src/__tests__/service-instantiator.spec.mts +18 -17
- package/src/__tests__/service-locator-event-bus.spec.mts +9 -9
- package/src/__tests__/service-locator-manager.spec.mts +15 -15
- package/src/__tests__/service-locator.spec.mts +167 -244
- package/src/__tests__/unified-api.spec.mts +27 -27
- package/src/__type-tests__/factory.spec-d.mts +2 -2
- package/src/__type-tests__/inject.spec-d.mts +2 -2
- package/src/__type-tests__/injectable.spec-d.mts +1 -1
- package/src/browser.mts +16 -0
- package/src/container/container.mts +319 -0
- package/src/container/index.mts +2 -0
- package/src/container/scoped-container.mts +350 -0
- package/src/decorators/factory.decorator.mts +4 -4
- package/src/decorators/injectable.decorator.mts +5 -5
- package/src/errors/di-error.mts +13 -7
- package/src/errors/index.mts +0 -8
- package/src/index.mts +156 -15
- package/src/interfaces/container.interface.mts +82 -0
- package/src/interfaces/factory.interface.mts +2 -2
- package/src/interfaces/index.mts +1 -0
- package/src/internal/context/async-local-storage.mts +120 -0
- package/src/internal/context/factory-context.mts +18 -0
- package/src/internal/context/index.mts +3 -0
- package/src/{request-context-holder.mts → internal/context/request-context.mts} +40 -27
- package/src/internal/context/resolution-context.mts +63 -0
- package/src/internal/context/sync-local-storage.mts +51 -0
- package/src/internal/core/index.mts +5 -0
- package/src/internal/core/instance-resolver.mts +641 -0
- package/src/{service-instantiator.mts → internal/core/instantiator.mts} +31 -27
- package/src/internal/core/invalidator.mts +437 -0
- package/src/internal/core/service-locator.mts +202 -0
- package/src/{token-processor.mts → internal/core/token-processor.mts} +79 -60
- package/src/{base-instance-holder-manager.mts → internal/holder/base-holder-manager.mts} +91 -21
- package/src/internal/holder/holder-manager.mts +85 -0
- package/src/internal/holder/holder-storage.interface.mts +116 -0
- package/src/internal/holder/index.mts +6 -0
- package/src/internal/holder/instance-holder.mts +109 -0
- package/src/internal/holder/request-storage.mts +134 -0
- package/src/internal/holder/singleton-storage.mts +105 -0
- package/src/internal/index.mts +4 -0
- package/src/internal/lifecycle/circular-detector.mts +77 -0
- package/src/internal/lifecycle/index.mts +2 -0
- package/src/{service-locator-event-bus.mts → internal/lifecycle/lifecycle-event-bus.mts} +11 -4
- package/src/testing/__tests__/test-container.spec.mts +2 -2
- package/src/testing/test-container.mts +4 -4
- package/src/token/index.mts +2 -0
- package/src/{injection-token.mts → token/injection-token.mts} +1 -1
- package/src/{registry.mts → token/registry.mts} +1 -1
- package/src/utils/get-injectable-token.mts +1 -1
- package/src/utils/get-injectors.mts +32 -15
- package/src/utils/types.mts +1 -1
- package/tsdown.config.mts +67 -0
- package/lib/_tsup-dts-rollup.d.mts +0 -1283
- package/lib/_tsup-dts-rollup.d.ts +0 -1283
- package/lib/chunk-2M576LCC.mjs +0 -2043
- package/lib/chunk-2M576LCC.mjs.map +0 -1
- package/lib/index.d.ts +0 -78
- package/lib/index.js +0 -2127
- package/lib/index.js.map +0 -1
- package/lib/testing/index.d.ts +0 -2
- package/lib/testing/index.js +0 -2060
- package/lib/testing/index.js.map +0 -1
- package/lib/testing/index.mjs.map +0 -1
- package/src/container.mts +0 -227
- package/src/factory-context.mts +0 -8
- package/src/instance-resolver.mts +0 -559
- package/src/request-context-manager.mts +0 -149
- package/src/service-invalidator.mts +0 -429
- package/src/service-locator-instance-holder.mts +0 -70
- package/src/service-locator-manager.mts +0 -85
- package/src/service-locator.mts +0 -246
- package/tsup.config.mts +0 -12
- /package/src/{injector.mts → injectors.mts} +0 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { z, ZodType } from 'zod/v4'
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
BoundInjectionToken,
|
|
5
|
+
ClassType,
|
|
6
|
+
ClassTypeWithArgument,
|
|
7
|
+
FactoryInjectionToken,
|
|
8
|
+
InjectionToken,
|
|
9
|
+
InjectionTokenSchemaType,
|
|
10
|
+
} from '../token/injection-token.mjs'
|
|
11
|
+
import type { Join, UnionToArray } from '../utils/types.mjs'
|
|
12
|
+
import type { Factorable } from './factory.interface.mjs'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Interface for dependency injection containers.
|
|
16
|
+
* Both Container and ScopedContainer implement this interface,
|
|
17
|
+
* allowing them to be used interchangeably in factory contexts.
|
|
18
|
+
*/
|
|
19
|
+
export interface IContainer {
|
|
20
|
+
/**
|
|
21
|
+
* Gets an instance from the container.
|
|
22
|
+
*/
|
|
23
|
+
// #1 Simple class
|
|
24
|
+
get<T extends ClassType>(
|
|
25
|
+
token: T,
|
|
26
|
+
): InstanceType<T> extends Factorable<infer R>
|
|
27
|
+
? Promise<R>
|
|
28
|
+
: Promise<InstanceType<T>>
|
|
29
|
+
// #1.1 Simple class with args
|
|
30
|
+
get<T extends ClassTypeWithArgument<R>, R>(
|
|
31
|
+
token: T,
|
|
32
|
+
args: R,
|
|
33
|
+
): Promise<InstanceType<T>>
|
|
34
|
+
// #2 Token with required Schema
|
|
35
|
+
get<T, S extends InjectionTokenSchemaType>(
|
|
36
|
+
token: InjectionToken<T, S>,
|
|
37
|
+
args: z.input<S>,
|
|
38
|
+
): Promise<T>
|
|
39
|
+
// #3 Token with optional Schema
|
|
40
|
+
get<T, S extends InjectionTokenSchemaType, R extends boolean>(
|
|
41
|
+
token: InjectionToken<T, S, R>,
|
|
42
|
+
): R extends false
|
|
43
|
+
? Promise<T>
|
|
44
|
+
: S extends ZodType<infer Type>
|
|
45
|
+
? `Error: Your token requires args: ${Join<
|
|
46
|
+
UnionToArray<keyof Type>,
|
|
47
|
+
', '
|
|
48
|
+
>}`
|
|
49
|
+
: 'Error: Your token requires args'
|
|
50
|
+
// #4 Token with no Schema
|
|
51
|
+
get<T>(token: InjectionToken<T, undefined>): Promise<T>
|
|
52
|
+
get<T>(token: BoundInjectionToken<T, any>): Promise<T>
|
|
53
|
+
get<T>(token: FactoryInjectionToken<T, any>): Promise<T>
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Invalidates a service and its dependencies.
|
|
57
|
+
*/
|
|
58
|
+
invalidate(service: unknown): Promise<void>
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Checks if a service is registered in the container.
|
|
62
|
+
*/
|
|
63
|
+
isRegistered(token: any): boolean
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Disposes the container and cleans up all resources.
|
|
67
|
+
*/
|
|
68
|
+
dispose(): Promise<void>
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Waits for all pending operations to complete.
|
|
72
|
+
*/
|
|
73
|
+
ready(): Promise<void>
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @internal
|
|
77
|
+
* Attempts to get an instance synchronously if it already exists.
|
|
78
|
+
* Returns null if the instance doesn't exist or is not ready.
|
|
79
|
+
* Used internally by the inject system for synchronous property initialization.
|
|
80
|
+
*/
|
|
81
|
+
tryGetSync<T>(token: any, args?: any): T | null
|
|
82
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod/v4'
|
|
2
2
|
|
|
3
|
-
import type { FactoryContext } from '../factory-context.mjs'
|
|
4
|
-
import type { InjectionTokenSchemaType } from '../injection-token.mjs'
|
|
3
|
+
import type { FactoryContext } from '../internal/context/factory-context.mjs'
|
|
4
|
+
import type { InjectionTokenSchemaType } from '../token/injection-token.mjs'
|
|
5
5
|
|
|
6
6
|
export interface Factorable<T> {
|
|
7
7
|
create(ctx?: FactoryContext): Promise<T> | T
|
package/src/interfaces/index.mts
CHANGED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform AsyncLocalStorage wrapper.
|
|
3
|
+
*
|
|
4
|
+
* Provides AsyncLocalStorage on Node.js/Bun and falls back to
|
|
5
|
+
* a synchronous-only polyfill in browser environments.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { SyncLocalStorage } from './sync-local-storage.mjs'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Interface matching the subset of AsyncLocalStorage API we use.
|
|
12
|
+
*/
|
|
13
|
+
export interface IAsyncLocalStorage<T> {
|
|
14
|
+
run<R>(store: T, fn: () => R): R
|
|
15
|
+
getStore(): T | undefined
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Detects if we're running in a Node.js-like environment with async_hooks support.
|
|
20
|
+
*/
|
|
21
|
+
function hasAsyncHooksSupport(): boolean {
|
|
22
|
+
// Check for Node.js
|
|
23
|
+
if (
|
|
24
|
+
typeof process !== 'undefined' &&
|
|
25
|
+
process.versions &&
|
|
26
|
+
process.versions.node
|
|
27
|
+
) {
|
|
28
|
+
return true
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Check for Bun
|
|
32
|
+
if (typeof process !== 'undefined' && process.versions && 'bun' in process.versions) {
|
|
33
|
+
return true
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Check for Deno (also supports async_hooks via node compat)
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
+
if (typeof (globalThis as any).Deno !== 'undefined') {
|
|
39
|
+
return true
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Cache for the AsyncLocalStorage class
|
|
46
|
+
let AsyncLocalStorageClass: (new <T>() => IAsyncLocalStorage<T>) | null = null
|
|
47
|
+
let initialized = false
|
|
48
|
+
let forceSyncMode = false
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Gets the appropriate AsyncLocalStorage implementation for the current environment.
|
|
52
|
+
*
|
|
53
|
+
* - On Node.js/Bun/Deno: Returns the native AsyncLocalStorage
|
|
54
|
+
* - On browsers: Returns SyncLocalStorage polyfill
|
|
55
|
+
*/
|
|
56
|
+
function getAsyncLocalStorageClass(): new <T>() => IAsyncLocalStorage<T> {
|
|
57
|
+
if (initialized) {
|
|
58
|
+
return AsyncLocalStorageClass!
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
initialized = true
|
|
62
|
+
|
|
63
|
+
if (!forceSyncMode && hasAsyncHooksSupport()) {
|
|
64
|
+
try {
|
|
65
|
+
// Dynamic require to avoid bundler issues
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
67
|
+
const asyncHooks = require('node:async_hooks')
|
|
68
|
+
AsyncLocalStorageClass = asyncHooks.AsyncLocalStorage
|
|
69
|
+
} catch {
|
|
70
|
+
// Fallback if require fails (shouldn't happen in Node/Bun)
|
|
71
|
+
AsyncLocalStorageClass = SyncLocalStorage as any
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
AsyncLocalStorageClass = SyncLocalStorage as any
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return AsyncLocalStorageClass!
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Creates a new AsyncLocalStorage instance appropriate for the current environment.
|
|
82
|
+
*/
|
|
83
|
+
export function createAsyncLocalStorage<T>(): IAsyncLocalStorage<T> {
|
|
84
|
+
const StorageClass = getAsyncLocalStorageClass()
|
|
85
|
+
return new StorageClass<T>()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Returns true if we're using the real AsyncLocalStorage (Node/Bun/Deno).
|
|
90
|
+
* Returns false if we're using the sync-only polyfill (browser).
|
|
91
|
+
*/
|
|
92
|
+
export function isUsingNativeAsyncLocalStorage(): boolean {
|
|
93
|
+
getAsyncLocalStorageClass() // Ensure initialized
|
|
94
|
+
return AsyncLocalStorageClass !== (SyncLocalStorage as any)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Testing utilities for forcing specific modes.
|
|
99
|
+
* Only exported for testing purposes.
|
|
100
|
+
*/
|
|
101
|
+
export const __testing__ = {
|
|
102
|
+
/**
|
|
103
|
+
* Resets the initialization state and forces sync mode.
|
|
104
|
+
* Call this before creating new storage instances in tests.
|
|
105
|
+
*/
|
|
106
|
+
forceSyncMode: () => {
|
|
107
|
+
initialized = false
|
|
108
|
+
forceSyncMode = true
|
|
109
|
+
AsyncLocalStorageClass = null
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Resets to default behavior (auto-detect environment).
|
|
114
|
+
*/
|
|
115
|
+
reset: () => {
|
|
116
|
+
initialized = false
|
|
117
|
+
forceSyncMode = false
|
|
118
|
+
AsyncLocalStorageClass = null
|
|
119
|
+
},
|
|
120
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { IContainer } from '../../interfaces/container.interface.mjs'
|
|
2
|
+
import type { Injectors } from '../../utils/index.mjs'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Context provided to factory functions during service instantiation.
|
|
6
|
+
*
|
|
7
|
+
* Provides access to dependency injection (via inject), the container,
|
|
8
|
+
* and lifecycle hooks for cleanup.
|
|
9
|
+
*/
|
|
10
|
+
export interface FactoryContext {
|
|
11
|
+
inject: Injectors['asyncInject']
|
|
12
|
+
/**
|
|
13
|
+
* The container instance for dependency resolution.
|
|
14
|
+
* This may be either a Container or ScopedContainer.
|
|
15
|
+
*/
|
|
16
|
+
container: IContainer
|
|
17
|
+
addDestroyListener: (listener: () => void) => void
|
|
18
|
+
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { InstanceHolder } from '../holder/instance-holder.mjs'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { InjectableScope, InjectableType } from '
|
|
5
|
-
import { InjectionToken } from '
|
|
3
|
+
import { BaseHolderManager } from '../holder/base-holder-manager.mjs'
|
|
4
|
+
import { InjectableScope, InjectableType } from '../../enums/index.mjs'
|
|
5
|
+
import { InjectionToken } from '../../token/injection-token.mjs'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Interface for request context that manages pre-prepared instances for a specific request.
|
|
9
|
+
*
|
|
10
|
+
* Provides isolated storage for request-scoped services, enabling efficient
|
|
11
|
+
* instantiation and cleanup within the lifecycle of a single request.
|
|
10
12
|
*/
|
|
11
|
-
export interface
|
|
13
|
+
export interface RequestContext {
|
|
12
14
|
/**
|
|
13
15
|
* Unique identifier for this request context.
|
|
14
16
|
*/
|
|
@@ -17,7 +19,7 @@ export interface RequestContextHolder {
|
|
|
17
19
|
/**
|
|
18
20
|
* Instance holders for request-scoped services.
|
|
19
21
|
*/
|
|
20
|
-
readonly holders: Map<string,
|
|
22
|
+
readonly holders: Map<string, InstanceHolder>
|
|
21
23
|
|
|
22
24
|
/**
|
|
23
25
|
* Priority for resolution in FactoryContext.inject method.
|
|
@@ -41,7 +43,7 @@ export interface RequestContextHolder {
|
|
|
41
43
|
addInstance(
|
|
42
44
|
instanceName: string,
|
|
43
45
|
instance: any,
|
|
44
|
-
holder:
|
|
46
|
+
holder: InstanceHolder,
|
|
45
47
|
): void
|
|
46
48
|
|
|
47
49
|
/**
|
|
@@ -52,7 +54,12 @@ export interface RequestContextHolder {
|
|
|
52
54
|
/**
|
|
53
55
|
* Gets an instance holder from this context.
|
|
54
56
|
*/
|
|
55
|
-
get(instanceName: string):
|
|
57
|
+
get(instanceName: string): InstanceHolder | undefined
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Sets an instance holder by name.
|
|
61
|
+
*/
|
|
62
|
+
set(instanceName: string, holder: InstanceHolder): void
|
|
56
63
|
|
|
57
64
|
/**
|
|
58
65
|
* Checks if this context has a pre-prepared instance.
|
|
@@ -74,16 +81,16 @@ export interface RequestContextHolder {
|
|
|
74
81
|
*/
|
|
75
82
|
setMetadata(key: string, value: any): void
|
|
76
83
|
|
|
77
|
-
// Methods inherited from
|
|
84
|
+
// Methods inherited from BaseHolderManager
|
|
78
85
|
/**
|
|
79
86
|
* Filters holders based on a predicate function.
|
|
80
87
|
*/
|
|
81
88
|
filter(
|
|
82
89
|
predicate: (
|
|
83
|
-
value:
|
|
90
|
+
value: InstanceHolder<any>,
|
|
84
91
|
key: string,
|
|
85
92
|
) => boolean,
|
|
86
|
-
): Map<string,
|
|
93
|
+
): Map<string, InstanceHolder>
|
|
87
94
|
|
|
88
95
|
/**
|
|
89
96
|
* Deletes a holder by name.
|
|
@@ -101,12 +108,18 @@ export interface RequestContextHolder {
|
|
|
101
108
|
isEmpty(): boolean
|
|
102
109
|
}
|
|
103
110
|
|
|
111
|
+
/** @deprecated Use RequestContext instead */
|
|
112
|
+
export type RequestContextHolder = RequestContext
|
|
113
|
+
|
|
104
114
|
/**
|
|
105
|
-
* Default implementation of
|
|
115
|
+
* Default implementation of RequestContext.
|
|
116
|
+
*
|
|
117
|
+
* Extends BaseHolderManager to provide holder management functionality
|
|
118
|
+
* with request-specific metadata and lifecycle support.
|
|
106
119
|
*/
|
|
107
|
-
export class
|
|
108
|
-
extends
|
|
109
|
-
implements
|
|
120
|
+
export class DefaultRequestContext
|
|
121
|
+
extends BaseHolderManager
|
|
122
|
+
implements RequestContext
|
|
110
123
|
{
|
|
111
124
|
public readonly metadata = new Map<string, any>()
|
|
112
125
|
public readonly createdAt = Date.now()
|
|
@@ -116,7 +129,7 @@ export class DefaultRequestContextHolder
|
|
|
116
129
|
public readonly priority: number = 100,
|
|
117
130
|
initialMetadata?: Record<string, any>,
|
|
118
131
|
) {
|
|
119
|
-
super(null) //
|
|
132
|
+
super(null) // RequestContext doesn't need logging
|
|
120
133
|
if (initialMetadata) {
|
|
121
134
|
Object.entries(initialMetadata).forEach(([key, value]) => {
|
|
122
135
|
this.metadata.set(key, value)
|
|
@@ -127,21 +140,21 @@ export class DefaultRequestContextHolder
|
|
|
127
140
|
/**
|
|
128
141
|
* Public getter for holders to maintain interface compatibility.
|
|
129
142
|
*/
|
|
130
|
-
get holders(): Map<string,
|
|
143
|
+
get holders(): Map<string, InstanceHolder> {
|
|
131
144
|
return this._holders
|
|
132
145
|
}
|
|
133
146
|
|
|
134
147
|
/**
|
|
135
|
-
* Gets a holder by name. For
|
|
148
|
+
* Gets a holder by name. For RequestContext, this is a simple lookup.
|
|
136
149
|
*/
|
|
137
|
-
get(name: string):
|
|
150
|
+
get(name: string): InstanceHolder | undefined {
|
|
138
151
|
return this._holders.get(name)
|
|
139
152
|
}
|
|
140
153
|
|
|
141
154
|
/**
|
|
142
155
|
* Sets a holder by name.
|
|
143
156
|
*/
|
|
144
|
-
set(name: string, holder:
|
|
157
|
+
set(name: string, holder: InstanceHolder): void {
|
|
145
158
|
this._holders.set(name, holder)
|
|
146
159
|
}
|
|
147
160
|
|
|
@@ -155,7 +168,7 @@ export class DefaultRequestContextHolder
|
|
|
155
168
|
addInstance(
|
|
156
169
|
instanceName: string | InjectionToken<any, undefined>,
|
|
157
170
|
instance: any,
|
|
158
|
-
holder?:
|
|
171
|
+
holder?: InstanceHolder,
|
|
159
172
|
): void {
|
|
160
173
|
if (instanceName instanceof InjectionToken) {
|
|
161
174
|
const name = instanceName.toString()
|
|
@@ -190,12 +203,12 @@ export class DefaultRequestContextHolder
|
|
|
190
203
|
}
|
|
191
204
|
|
|
192
205
|
/**
|
|
193
|
-
* Creates a new request context
|
|
206
|
+
* Creates a new request context with the given parameters.
|
|
194
207
|
*/
|
|
195
|
-
export function
|
|
208
|
+
export function createRequestContext(
|
|
196
209
|
requestId: string,
|
|
197
210
|
priority: number = 100,
|
|
198
211
|
initialMetadata?: Record<string, any>,
|
|
199
|
-
):
|
|
200
|
-
return new
|
|
212
|
+
): RequestContext {
|
|
213
|
+
return new DefaultRequestContext(requestId, priority, initialMetadata)
|
|
201
214
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { InstanceHolder } from '../holder/instance-holder.mjs'
|
|
2
|
+
|
|
3
|
+
import { createAsyncLocalStorage } from './async-local-storage.mjs'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Data stored in the resolution context during service instantiation.
|
|
7
|
+
*/
|
|
8
|
+
export interface ResolutionContextData {
|
|
9
|
+
/** The holder that is currently being instantiated */
|
|
10
|
+
waiterHolder: InstanceHolder
|
|
11
|
+
/** Function to get a holder by name (for cycle detection) */
|
|
12
|
+
getHolder: (name: string) => InstanceHolder | undefined
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* AsyncLocalStorage for tracking the current resolution context.
|
|
17
|
+
*
|
|
18
|
+
* This allows tracking which service is being instantiated even across
|
|
19
|
+
* async boundaries (like when inject() is called inside a constructor).
|
|
20
|
+
* Essential for circular dependency detection.
|
|
21
|
+
*/
|
|
22
|
+
export const resolutionContext = createAsyncLocalStorage<ResolutionContextData>()
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Runs a function within a resolution context.
|
|
26
|
+
*
|
|
27
|
+
* The context tracks which holder is currently being instantiated,
|
|
28
|
+
* allowing circular dependency detection to work correctly.
|
|
29
|
+
*
|
|
30
|
+
* @param waiterHolder The holder being instantiated
|
|
31
|
+
* @param getHolder Function to retrieve holders by name
|
|
32
|
+
* @param fn The function to run within the context
|
|
33
|
+
*/
|
|
34
|
+
export function withResolutionContext<T>(
|
|
35
|
+
waiterHolder: InstanceHolder,
|
|
36
|
+
getHolder: (name: string) => InstanceHolder | undefined,
|
|
37
|
+
fn: () => T,
|
|
38
|
+
): T {
|
|
39
|
+
return resolutionContext.run({ waiterHolder, getHolder }, fn)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Gets the current resolution context, if any.
|
|
44
|
+
*
|
|
45
|
+
* Returns undefined if we're not inside a resolution context
|
|
46
|
+
* (e.g., when resolving a top-level service that has no parent).
|
|
47
|
+
*/
|
|
48
|
+
export function getCurrentResolutionContext(): ResolutionContextData | undefined {
|
|
49
|
+
return resolutionContext.getStore()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Runs a function outside any resolution context.
|
|
54
|
+
*
|
|
55
|
+
* This is useful for async injections that should not participate
|
|
56
|
+
* in circular dependency detection since they don't block.
|
|
57
|
+
*
|
|
58
|
+
* @param fn The function to run without resolution context
|
|
59
|
+
*/
|
|
60
|
+
export function withoutResolutionContext<T>(fn: () => T): T {
|
|
61
|
+
// Run with undefined context to clear any current context
|
|
62
|
+
return resolutionContext.run(undefined as any, fn)
|
|
63
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A synchronous-only polyfill for AsyncLocalStorage.
|
|
3
|
+
*
|
|
4
|
+
* This provides the same API as Node's AsyncLocalStorage but only works
|
|
5
|
+
* for synchronous code paths. It uses a simple stack-based approach.
|
|
6
|
+
*
|
|
7
|
+
* Limitations:
|
|
8
|
+
* - Context does NOT propagate across async boundaries (setTimeout, promises, etc.)
|
|
9
|
+
* - Only suitable for environments where DI resolution is synchronous
|
|
10
|
+
*
|
|
11
|
+
* This is acceptable for browser environments where:
|
|
12
|
+
* 1. Constructors are typically synchronous
|
|
13
|
+
* 2. Circular dependency detection mainly needs sync tracking
|
|
14
|
+
*/
|
|
15
|
+
export class SyncLocalStorage<T> {
|
|
16
|
+
private stack: T[] = []
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Runs a function within the given store context.
|
|
20
|
+
* The context is only available synchronously within the function.
|
|
21
|
+
*/
|
|
22
|
+
run<R>(store: T, fn: () => R): R {
|
|
23
|
+
this.stack.push(store)
|
|
24
|
+
try {
|
|
25
|
+
return fn()
|
|
26
|
+
} finally {
|
|
27
|
+
this.stack.pop()
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Gets the current store value, or undefined if not in a context.
|
|
33
|
+
*/
|
|
34
|
+
getStore(): T | undefined {
|
|
35
|
+
return this.stack.length > 0 ? this.stack[this.stack.length - 1] : undefined
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Exits the current context and runs the function without any store.
|
|
40
|
+
* This matches AsyncLocalStorage.exit() behavior.
|
|
41
|
+
*/
|
|
42
|
+
exit<R>(fn: () => R): R {
|
|
43
|
+
const savedStack = this.stack
|
|
44
|
+
this.stack = []
|
|
45
|
+
try {
|
|
46
|
+
return fn()
|
|
47
|
+
} finally {
|
|
48
|
+
this.stack = savedStack
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|