@navios/di 0.5.0 → 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 +26 -4
- 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 +19 -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} +12 -5
- 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-44F3LXW5.mjs +0 -2043
- package/lib/chunk-44F3LXW5.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
|
@@ -1,53 +1,53 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
2
|
|
|
3
3
|
import { InjectableScope, InjectableType } from '../enums/index.mjs'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { DefaultRequestContext } from '../internal/context/request-context.mjs'
|
|
5
|
+
import { HolderManager } from '../internal/holder/holder-manager.mjs'
|
|
6
6
|
|
|
7
7
|
describe('Unified API', () => {
|
|
8
|
-
describe('Common methods between
|
|
8
|
+
describe('Common methods between HolderManager and RequestContext', () => {
|
|
9
9
|
it('should have the same basic API surface', () => {
|
|
10
|
-
const
|
|
11
|
-
const requestContext = new
|
|
10
|
+
const holderManager = new HolderManager()
|
|
11
|
+
const requestContext = new DefaultRequestContext(
|
|
12
12
|
'test-request',
|
|
13
13
|
100,
|
|
14
14
|
)
|
|
15
15
|
|
|
16
16
|
// Both should have the same common methods
|
|
17
|
-
expect(typeof
|
|
17
|
+
expect(typeof holderManager.size).toBe('function')
|
|
18
18
|
expect(typeof requestContext.size).toBe('function')
|
|
19
19
|
|
|
20
|
-
expect(typeof
|
|
20
|
+
expect(typeof holderManager.isEmpty).toBe('function')
|
|
21
21
|
expect(typeof requestContext.isEmpty).toBe('function')
|
|
22
22
|
|
|
23
|
-
expect(typeof
|
|
23
|
+
expect(typeof holderManager.filter).toBe('function')
|
|
24
24
|
expect(typeof requestContext.filter).toBe('function')
|
|
25
25
|
|
|
26
|
-
expect(typeof
|
|
26
|
+
expect(typeof holderManager.clear).toBe('function')
|
|
27
27
|
expect(typeof requestContext.clear).toBe('function')
|
|
28
28
|
|
|
29
|
-
expect(typeof
|
|
29
|
+
expect(typeof holderManager.delete).toBe('function')
|
|
30
30
|
expect(typeof requestContext.delete).toBe('function')
|
|
31
31
|
|
|
32
|
-
expect(typeof
|
|
32
|
+
expect(typeof holderManager.getAllNames).toBe('function')
|
|
33
33
|
expect(typeof requestContext.getAllNames).toBe('function')
|
|
34
34
|
|
|
35
|
-
expect(typeof
|
|
35
|
+
expect(typeof holderManager.getAllHolders).toBe('function')
|
|
36
36
|
expect(typeof requestContext.getAllHolders).toBe('function')
|
|
37
37
|
|
|
38
|
-
expect(typeof
|
|
38
|
+
expect(typeof holderManager.createCreatingHolder).toBe('function')
|
|
39
39
|
expect(typeof requestContext.createCreatingHolder).toBe('function')
|
|
40
40
|
})
|
|
41
41
|
|
|
42
42
|
it('should work with the same holder creation patterns', () => {
|
|
43
|
-
const
|
|
44
|
-
const requestContext = new
|
|
43
|
+
const holderManager = new HolderManager()
|
|
44
|
+
const requestContext = new DefaultRequestContext(
|
|
45
45
|
'test-request',
|
|
46
46
|
100,
|
|
47
47
|
)
|
|
48
48
|
|
|
49
49
|
// Both should be able to create holders the same way
|
|
50
|
-
const [deferred1, holder1] =
|
|
50
|
+
const [deferred1, holder1] = holderManager.createCreatingHolder(
|
|
51
51
|
'Service1',
|
|
52
52
|
InjectableType.Class,
|
|
53
53
|
InjectableScope.Singleton,
|
|
@@ -65,22 +65,22 @@ describe('Unified API', () => {
|
|
|
65
65
|
expect(holder2.scope).toBe(InjectableScope.Request)
|
|
66
66
|
|
|
67
67
|
// Both should be able to store holders
|
|
68
|
-
|
|
68
|
+
holderManager.set('Service1', holder1)
|
|
69
69
|
requestContext.set('Service2', holder2)
|
|
70
70
|
|
|
71
|
-
expect(
|
|
71
|
+
expect(holderManager.size()).toBe(1)
|
|
72
72
|
expect(requestContext.size()).toBe(1)
|
|
73
73
|
})
|
|
74
74
|
|
|
75
75
|
it('should support the same filtering patterns', () => {
|
|
76
|
-
const
|
|
77
|
-
const requestContext = new
|
|
76
|
+
const holderManager = new HolderManager()
|
|
77
|
+
const requestContext = new DefaultRequestContext(
|
|
78
78
|
'test-request',
|
|
79
79
|
100,
|
|
80
80
|
)
|
|
81
81
|
|
|
82
82
|
// Create and store different types of holders
|
|
83
|
-
const singletonHolder =
|
|
83
|
+
const singletonHolder = holderManager.storeCreatedHolder(
|
|
84
84
|
'Singleton',
|
|
85
85
|
{},
|
|
86
86
|
InjectableType.Class,
|
|
@@ -97,7 +97,7 @@ describe('Unified API', () => {
|
|
|
97
97
|
const transientHolder = transientHolderTemp
|
|
98
98
|
|
|
99
99
|
// Both should support the same filtering API
|
|
100
|
-
const singletons =
|
|
100
|
+
const singletons = holderManager.filter(
|
|
101
101
|
(holder) => holder.scope === InjectableScope.Singleton,
|
|
102
102
|
)
|
|
103
103
|
const transients = requestContext.filter(
|
|
@@ -111,17 +111,17 @@ describe('Unified API', () => {
|
|
|
111
111
|
})
|
|
112
112
|
|
|
113
113
|
it('should maintain their specific behaviors while sharing common API', () => {
|
|
114
|
-
const
|
|
115
|
-
const requestContext = new
|
|
114
|
+
const holderManager = new HolderManager()
|
|
115
|
+
const requestContext = new DefaultRequestContext(
|
|
116
116
|
'test-request',
|
|
117
117
|
100,
|
|
118
118
|
)
|
|
119
119
|
|
|
120
|
-
//
|
|
121
|
-
const [notFound] =
|
|
120
|
+
// HolderManager has specific error handling
|
|
121
|
+
const [notFound] = holderManager.get('NonExistent')
|
|
122
122
|
expect(notFound).toBeDefined()
|
|
123
123
|
|
|
124
|
-
//
|
|
124
|
+
// RequestContext has specific request features
|
|
125
125
|
expect(requestContext.requestId).toBe('test-request')
|
|
126
126
|
expect(requestContext.priority).toBe(100)
|
|
127
127
|
expect(requestContext.metadata).toBeInstanceOf(Map)
|
|
@@ -4,8 +4,8 @@ import type { Factorable, FactorableWithArgs } from '../interfaces/index.mjs'
|
|
|
4
4
|
|
|
5
5
|
import { Factory } from '../decorators/index.mjs'
|
|
6
6
|
import { InjectableScope } from '../enums/index.mjs'
|
|
7
|
-
import { InjectionToken } from '../injection-token.mjs'
|
|
8
|
-
import { Registry } from '../registry.mjs'
|
|
7
|
+
import { InjectionToken } from '../token/injection-token.mjs'
|
|
8
|
+
import { Registry } from '../token/registry.mjs'
|
|
9
9
|
|
|
10
10
|
// Test factory without arguments
|
|
11
11
|
@Factory()
|
|
@@ -2,8 +2,8 @@ import { assertType, describe, test } from 'vitest'
|
|
|
2
2
|
import { z } from 'zod/v4'
|
|
3
3
|
|
|
4
4
|
import { Injectable } from '../decorators/index.mjs'
|
|
5
|
-
import { InjectionToken } from '../injection-token.mjs'
|
|
6
|
-
import { asyncInject } from '../
|
|
5
|
+
import { InjectionToken } from '../token/injection-token.mjs'
|
|
6
|
+
import { asyncInject } from '../injectors.mjs'
|
|
7
7
|
|
|
8
8
|
interface FooService {
|
|
9
9
|
makeFoo(): string
|
|
@@ -3,7 +3,7 @@ import { expectTypeOf, test } from 'vitest'
|
|
|
3
3
|
import { z } from 'zod/v4'
|
|
4
4
|
|
|
5
5
|
import { Injectable } from '../decorators/index.mjs'
|
|
6
|
-
import { InjectionToken } from '../injection-token.mjs'
|
|
6
|
+
import { InjectionToken } from '../token/injection-token.mjs'
|
|
7
7
|
|
|
8
8
|
interface FooService {
|
|
9
9
|
makeFoo(): string
|
package/src/browser.mts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-specific entry point for @navios/di.
|
|
3
|
+
*
|
|
4
|
+
* This entry point forces the use of SyncLocalStorage instead of
|
|
5
|
+
* Node's AsyncLocalStorage, making it safe for browser environments.
|
|
6
|
+
*
|
|
7
|
+
* The browser build is automatically selected by bundlers that respect
|
|
8
|
+
* the "browser" condition in package.json exports.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Force sync mode before any other imports initialize the storage
|
|
12
|
+
import { __testing__ } from './internal/context/async-local-storage.mjs'
|
|
13
|
+
__testing__.forceSyncMode()
|
|
14
|
+
|
|
15
|
+
// Re-export everything from the main entry
|
|
16
|
+
export * from './index.mjs'
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import type { z, ZodType } from 'zod/v4'
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
ClassType,
|
|
5
|
+
ClassTypeWithArgument,
|
|
6
|
+
InjectionToken,
|
|
7
|
+
InjectionTokenSchemaType,
|
|
8
|
+
} from '../token/injection-token.mjs'
|
|
9
|
+
import type { IContainer } from '../interfaces/container.interface.mjs'
|
|
10
|
+
import type { Factorable } from '../interfaces/factory.interface.mjs'
|
|
11
|
+
import type { Registry } from '../token/registry.mjs'
|
|
12
|
+
import type { InstanceHolder } from '../internal/holder/instance-holder.mjs'
|
|
13
|
+
import type { Injectors } from '../utils/index.mjs'
|
|
14
|
+
import type { Join, UnionToArray } from '../utils/types.mjs'
|
|
15
|
+
|
|
16
|
+
import { Injectable } from '../decorators/injectable.decorator.mjs'
|
|
17
|
+
import { InjectableScope, InjectableType } from '../enums/index.mjs'
|
|
18
|
+
import { DIError } from '../errors/index.mjs'
|
|
19
|
+
import {
|
|
20
|
+
BoundInjectionToken,
|
|
21
|
+
FactoryInjectionToken,
|
|
22
|
+
} from '../token/injection-token.mjs'
|
|
23
|
+
import { defaultInjectors } from '../injectors.mjs'
|
|
24
|
+
import { globalRegistry } from '../token/registry.mjs'
|
|
25
|
+
import { ScopedContainer } from './scoped-container.mjs'
|
|
26
|
+
import { ServiceLocator } from '../internal/core/service-locator.mjs'
|
|
27
|
+
import { getInjectableToken } from '../utils/get-injectable-token.mjs'
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Main dependency injection container.
|
|
31
|
+
*
|
|
32
|
+
* Provides a simplified public API for dependency injection, wrapping
|
|
33
|
+
* a ServiceLocator instance. Handles singleton and transient services directly,
|
|
34
|
+
* while request-scoped services require using beginRequest() to create a ScopedContainer.
|
|
35
|
+
*/
|
|
36
|
+
@Injectable()
|
|
37
|
+
export class Container implements IContainer {
|
|
38
|
+
private readonly serviceLocator: ServiceLocator
|
|
39
|
+
private readonly activeRequestIds = new Set<string>()
|
|
40
|
+
|
|
41
|
+
constructor(
|
|
42
|
+
protected readonly registry: Registry = globalRegistry,
|
|
43
|
+
protected readonly logger: Console | null = null,
|
|
44
|
+
protected readonly injectors: Injectors = defaultInjectors,
|
|
45
|
+
) {
|
|
46
|
+
this.serviceLocator = new ServiceLocator(registry, logger, injectors)
|
|
47
|
+
this.registerSelf()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private registerSelf() {
|
|
51
|
+
const token = getInjectableToken(Container)
|
|
52
|
+
const instanceName = this.serviceLocator.getInstanceIdentifier(token)
|
|
53
|
+
this.serviceLocator
|
|
54
|
+
.getManager()
|
|
55
|
+
.storeCreatedHolder(
|
|
56
|
+
instanceName,
|
|
57
|
+
this,
|
|
58
|
+
InjectableType.Class,
|
|
59
|
+
InjectableScope.Singleton,
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Gets an instance from the container.
|
|
65
|
+
* This method has the same type signature as the inject method from get-injectors.mts
|
|
66
|
+
*
|
|
67
|
+
* NOTE: Request-scoped services cannot be resolved directly from Container.
|
|
68
|
+
* Use beginRequest() to create a ScopedContainer for request-scoped services.
|
|
69
|
+
*/
|
|
70
|
+
// #1 Simple class
|
|
71
|
+
get<T extends ClassType>(
|
|
72
|
+
token: T,
|
|
73
|
+
): InstanceType<T> extends Factorable<infer R>
|
|
74
|
+
? Promise<R>
|
|
75
|
+
: Promise<InstanceType<T>>
|
|
76
|
+
// #1.1 Simple class with args
|
|
77
|
+
get<T extends ClassTypeWithArgument<R>, R>(
|
|
78
|
+
token: T,
|
|
79
|
+
args: R,
|
|
80
|
+
): Promise<InstanceType<T>>
|
|
81
|
+
|
|
82
|
+
// #2 Token with required Schema
|
|
83
|
+
get<T, S extends InjectionTokenSchemaType>(
|
|
84
|
+
token: InjectionToken<T, S>,
|
|
85
|
+
args: z.input<S>,
|
|
86
|
+
): Promise<T>
|
|
87
|
+
// #3 Token with optional Schema
|
|
88
|
+
get<T, S extends InjectionTokenSchemaType, R extends boolean>(
|
|
89
|
+
token: InjectionToken<T, S, R>,
|
|
90
|
+
): R extends false
|
|
91
|
+
? Promise<T>
|
|
92
|
+
: S extends ZodType<infer Type>
|
|
93
|
+
? `Error: Your token requires args: ${Join<
|
|
94
|
+
UnionToArray<keyof Type>,
|
|
95
|
+
', '
|
|
96
|
+
>}`
|
|
97
|
+
: 'Error: Your token requires args'
|
|
98
|
+
// #4 Token with no Schema
|
|
99
|
+
get<T>(token: InjectionToken<T, undefined>): Promise<T>
|
|
100
|
+
get<T>(token: BoundInjectionToken<T, any>): Promise<T>
|
|
101
|
+
get<T>(token: FactoryInjectionToken<T, any>): Promise<T>
|
|
102
|
+
|
|
103
|
+
async get(
|
|
104
|
+
token:
|
|
105
|
+
| ClassType
|
|
106
|
+
| InjectionToken<any>
|
|
107
|
+
| BoundInjectionToken<any, any>
|
|
108
|
+
| FactoryInjectionToken<any, any>,
|
|
109
|
+
args?: unknown,
|
|
110
|
+
) {
|
|
111
|
+
// Check if this is a request-scoped service
|
|
112
|
+
// Use TokenProcessor for consistent token normalization
|
|
113
|
+
const tokenProcessor = this.serviceLocator.getTokenProcessor()
|
|
114
|
+
const realToken = tokenProcessor.getRegistryToken(token)
|
|
115
|
+
|
|
116
|
+
if (this.registry.has(realToken)) {
|
|
117
|
+
const record = this.registry.get(realToken)
|
|
118
|
+
if (record.scope === InjectableScope.Request) {
|
|
119
|
+
throw DIError.unknown(
|
|
120
|
+
`Cannot resolve request-scoped service "${String(realToken.name)}" from Container. ` +
|
|
121
|
+
`Use beginRequest() to create a ScopedContainer for request-scoped services.`,
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return this.serviceLocator.getOrThrowInstance(token, args as any, this)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Gets an instance with a specific container context.
|
|
131
|
+
* Used by ScopedContainer to delegate singleton/transient resolution
|
|
132
|
+
* while maintaining the correct container context for nested inject() calls.
|
|
133
|
+
*
|
|
134
|
+
* @internal
|
|
135
|
+
*/
|
|
136
|
+
async getWithContext(
|
|
137
|
+
token:
|
|
138
|
+
| ClassType
|
|
139
|
+
| InjectionToken<any>
|
|
140
|
+
| BoundInjectionToken<any, any>
|
|
141
|
+
| FactoryInjectionToken<any, any>,
|
|
142
|
+
args: unknown,
|
|
143
|
+
contextContainer: IContainer,
|
|
144
|
+
): Promise<any> {
|
|
145
|
+
return this.serviceLocator.getOrThrowInstance(
|
|
146
|
+
token,
|
|
147
|
+
args as any,
|
|
148
|
+
contextContainer,
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Resolves a request-scoped service for a ScopedContainer.
|
|
154
|
+
* The service will be stored in the ScopedContainer's request context.
|
|
155
|
+
*
|
|
156
|
+
* @internal
|
|
157
|
+
*/
|
|
158
|
+
async resolveForRequest(
|
|
159
|
+
token:
|
|
160
|
+
| ClassType
|
|
161
|
+
| InjectionToken<any>
|
|
162
|
+
| BoundInjectionToken<any, any>
|
|
163
|
+
| FactoryInjectionToken<any, any>,
|
|
164
|
+
args: unknown,
|
|
165
|
+
scopedContainer: ScopedContainer,
|
|
166
|
+
): Promise<any> {
|
|
167
|
+
return this.serviceLocator.resolveRequestScoped(
|
|
168
|
+
token,
|
|
169
|
+
args as any,
|
|
170
|
+
scopedContainer,
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Gets the underlying ServiceLocator instance for advanced usage
|
|
176
|
+
*/
|
|
177
|
+
getServiceLocator(): ServiceLocator {
|
|
178
|
+
return this.serviceLocator
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Gets the registry
|
|
183
|
+
*/
|
|
184
|
+
getRegistry(): Registry {
|
|
185
|
+
return this.registry
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Invalidates a service and its dependencies
|
|
190
|
+
*/
|
|
191
|
+
async invalidate(service: unknown): Promise<void> {
|
|
192
|
+
const holder = this.getHolderByInstance(service)
|
|
193
|
+
if (holder) {
|
|
194
|
+
await this.serviceLocator.invalidate(holder.name)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Gets a service holder by instance (reverse lookup)
|
|
200
|
+
*/
|
|
201
|
+
private getHolderByInstance(
|
|
202
|
+
instance: unknown,
|
|
203
|
+
): InstanceHolder | null {
|
|
204
|
+
const holderMap = Array.from(
|
|
205
|
+
this.serviceLocator
|
|
206
|
+
.getManager()
|
|
207
|
+
.filter((holder) => holder.instance === instance)
|
|
208
|
+
.values(),
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
return holderMap.length > 0 ? holderMap[0] : null
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Checks if a service is registered in the container
|
|
216
|
+
*/
|
|
217
|
+
isRegistered(token: any): boolean {
|
|
218
|
+
try {
|
|
219
|
+
return this.serviceLocator.getInstanceIdentifier(token) !== null
|
|
220
|
+
} catch {
|
|
221
|
+
return false
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Disposes the container and cleans up all resources
|
|
227
|
+
*/
|
|
228
|
+
async dispose(): Promise<void> {
|
|
229
|
+
await this.serviceLocator.clearAll()
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Waits for all pending operations to complete
|
|
234
|
+
*/
|
|
235
|
+
async ready(): Promise<void> {
|
|
236
|
+
await this.serviceLocator.ready()
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* @internal
|
|
241
|
+
* Attempts to get an instance synchronously if it already exists.
|
|
242
|
+
* Returns null if the instance doesn't exist or is not ready.
|
|
243
|
+
*/
|
|
244
|
+
tryGetSync<T>(token: any, args?: any): T | null {
|
|
245
|
+
return this.serviceLocator.getSyncInstance(token, args, this)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ============================================================================
|
|
249
|
+
// REQUEST CONTEXT MANAGEMENT
|
|
250
|
+
// ============================================================================
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Begins a new request context and returns a ScopedContainer.
|
|
254
|
+
*
|
|
255
|
+
* The ScopedContainer provides isolated request-scoped service resolution
|
|
256
|
+
* while delegating singleton and transient services to this Container.
|
|
257
|
+
*
|
|
258
|
+
* @param requestId Unique identifier for this request
|
|
259
|
+
* @param metadata Optional metadata for the request
|
|
260
|
+
* @param priority Priority for resolution (higher = more priority)
|
|
261
|
+
* @returns A ScopedContainer for this request
|
|
262
|
+
*/
|
|
263
|
+
beginRequest(
|
|
264
|
+
requestId: string,
|
|
265
|
+
metadata?: Record<string, any>,
|
|
266
|
+
priority: number = 100,
|
|
267
|
+
): ScopedContainer {
|
|
268
|
+
if (this.activeRequestIds.has(requestId)) {
|
|
269
|
+
throw DIError.unknown(
|
|
270
|
+
`Request context "${requestId}" already exists. Use a unique request ID.`,
|
|
271
|
+
)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
this.activeRequestIds.add(requestId)
|
|
275
|
+
|
|
276
|
+
this.logger?.log(`[Container] Started request context: ${requestId}`)
|
|
277
|
+
|
|
278
|
+
return new ScopedContainer(
|
|
279
|
+
this,
|
|
280
|
+
this.registry,
|
|
281
|
+
requestId,
|
|
282
|
+
metadata,
|
|
283
|
+
priority,
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Removes a request ID from the active set.
|
|
289
|
+
* Called by ScopedContainer when the request ends.
|
|
290
|
+
*
|
|
291
|
+
* @internal
|
|
292
|
+
*/
|
|
293
|
+
removeActiveRequest(requestId: string): void {
|
|
294
|
+
this.activeRequestIds.delete(requestId)
|
|
295
|
+
this.logger?.log(`[Container] Ended request context: ${requestId}`)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Gets the set of active request IDs.
|
|
300
|
+
*/
|
|
301
|
+
getActiveRequestIds(): ReadonlySet<string> {
|
|
302
|
+
return this.activeRequestIds
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Checks if a request ID is currently active.
|
|
307
|
+
*/
|
|
308
|
+
hasActiveRequest(requestId: string): boolean {
|
|
309
|
+
return this.activeRequestIds.has(requestId)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Clears all instances and bindings from the container.
|
|
314
|
+
* This is useful for testing or resetting the container state.
|
|
315
|
+
*/
|
|
316
|
+
clear(): Promise<void> {
|
|
317
|
+
return this.serviceLocator.clearAll()
|
|
318
|
+
}
|
|
319
|
+
}
|