@navios/di 0.8.0 → 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 +87 -0
- package/README.md +117 -17
- package/lib/browser/container/abstract-container.d.mts +112 -0
- package/lib/browser/container/abstract-container.d.mts.map +1 -0
- package/lib/browser/container/abstract-container.mjs +100 -0
- package/lib/browser/container/abstract-container.mjs.map +1 -0
- package/lib/browser/container/container.d.mts +100 -0
- package/lib/browser/container/container.d.mts.map +1 -0
- package/lib/browser/container/container.mjs +424 -0
- package/lib/browser/container/container.mjs.map +1 -0
- package/lib/browser/container/scoped-container.d.mts +93 -0
- package/lib/browser/container/scoped-container.d.mts.map +1 -0
- package/lib/browser/container/scoped-container.mjs +119 -0
- package/lib/browser/container/scoped-container.mjs.map +1 -0
- package/lib/browser/decorators/factory.decorator.d.mts +26 -0
- package/lib/browser/decorators/factory.decorator.d.mts.map +1 -0
- package/lib/browser/decorators/factory.decorator.mjs +20 -0
- package/lib/browser/decorators/factory.decorator.mjs.map +1 -0
- package/lib/browser/decorators/injectable.decorator.d.mts +38 -0
- package/lib/browser/decorators/injectable.decorator.d.mts.map +1 -0
- package/lib/browser/decorators/injectable.decorator.mjs +21 -0
- package/lib/browser/decorators/injectable.decorator.mjs.map +1 -0
- package/lib/browser/enums/injectable-scope.enum.d.mts +18 -0
- package/lib/browser/enums/injectable-scope.enum.d.mts.map +1 -0
- package/lib/browser/enums/injectable-scope.enum.mjs +20 -0
- package/lib/browser/enums/injectable-scope.enum.mjs.map +1 -0
- package/lib/browser/enums/injectable-type.enum.d.mts +8 -0
- package/lib/browser/enums/injectable-type.enum.d.mts.map +1 -0
- package/lib/browser/enums/injectable-type.enum.mjs +10 -0
- package/lib/browser/enums/injectable-type.enum.mjs.map +1 -0
- package/lib/browser/errors/di-error.d.mts +43 -0
- package/lib/browser/errors/di-error.d.mts.map +1 -0
- package/lib/browser/errors/di-error.mjs +98 -0
- package/lib/browser/errors/di-error.mjs.map +1 -0
- package/lib/browser/event-emitter.d.mts +16 -0
- package/lib/browser/event-emitter.d.mts.map +1 -0
- package/lib/browser/event-emitter.mjs +320 -0
- package/lib/browser/event-emitter.mjs.map +1 -0
- package/lib/browser/index.d.mts +37 -1558
- package/lib/browser/index.mjs +29 -2749
- package/lib/browser/interfaces/container.interface.d.mts +59 -0
- package/lib/browser/interfaces/container.interface.d.mts.map +1 -0
- package/lib/browser/interfaces/factory.interface.d.mts +14 -0
- package/lib/browser/interfaces/factory.interface.d.mts.map +1 -0
- package/lib/browser/interfaces/on-service-destroy.interface.d.mts +7 -0
- package/lib/browser/interfaces/on-service-destroy.interface.d.mts.map +1 -0
- package/lib/browser/interfaces/on-service-init.interface.d.mts +7 -0
- package/lib/browser/interfaces/on-service-init.interface.d.mts.map +1 -0
- package/lib/browser/internal/context/async-local-storage.browser.mjs +20 -0
- package/lib/browser/internal/context/async-local-storage.browser.mjs.map +1 -0
- package/lib/browser/internal/context/async-local-storage.d.mts +9 -0
- package/lib/browser/internal/context/async-local-storage.d.mts.map +1 -0
- package/lib/browser/internal/context/async-local-storage.types.d.mts +11 -0
- package/lib/browser/internal/context/async-local-storage.types.d.mts.map +1 -0
- package/lib/browser/internal/context/factory-context.d.mts +23 -0
- package/lib/browser/internal/context/factory-context.d.mts.map +1 -0
- package/lib/browser/internal/context/resolution-context.d.mts +43 -0
- package/lib/browser/internal/context/resolution-context.d.mts.map +1 -0
- package/lib/browser/internal/context/resolution-context.mjs +56 -0
- package/lib/browser/internal/context/resolution-context.mjs.map +1 -0
- package/lib/browser/internal/context/service-initialization-context.d.mts +48 -0
- package/lib/browser/internal/context/service-initialization-context.d.mts.map +1 -0
- package/lib/browser/internal/context/sync-local-storage.mjs +53 -0
- package/lib/browser/internal/context/sync-local-storage.mjs.map +1 -0
- package/lib/browser/internal/core/instance-resolver.d.mts +119 -0
- package/lib/browser/internal/core/instance-resolver.d.mts.map +1 -0
- package/lib/browser/internal/core/instance-resolver.mjs +306 -0
- package/lib/browser/internal/core/instance-resolver.mjs.map +1 -0
- package/lib/browser/internal/core/name-resolver.d.mts +52 -0
- package/lib/browser/internal/core/name-resolver.d.mts.map +1 -0
- package/lib/browser/internal/core/name-resolver.mjs +118 -0
- package/lib/browser/internal/core/name-resolver.mjs.map +1 -0
- package/lib/browser/internal/core/scope-tracker.d.mts +65 -0
- package/lib/browser/internal/core/scope-tracker.d.mts.map +1 -0
- package/lib/browser/internal/core/scope-tracker.mjs +120 -0
- package/lib/browser/internal/core/scope-tracker.mjs.map +1 -0
- package/lib/browser/internal/core/service-initializer.d.mts +44 -0
- package/lib/browser/internal/core/service-initializer.d.mts.map +1 -0
- package/lib/browser/internal/core/service-initializer.mjs +109 -0
- package/lib/browser/internal/core/service-initializer.mjs.map +1 -0
- package/lib/browser/internal/core/service-invalidator.d.mts +81 -0
- package/lib/browser/internal/core/service-invalidator.d.mts.map +1 -0
- package/lib/browser/internal/core/service-invalidator.mjs +142 -0
- package/lib/browser/internal/core/service-invalidator.mjs.map +1 -0
- package/lib/browser/internal/core/token-resolver.d.mts +54 -0
- package/lib/browser/internal/core/token-resolver.d.mts.map +1 -0
- package/lib/browser/internal/core/token-resolver.mjs +77 -0
- package/lib/browser/internal/core/token-resolver.mjs.map +1 -0
- package/lib/browser/internal/holder/holder-storage.interface.d.mts +99 -0
- package/lib/browser/internal/holder/holder-storage.interface.d.mts.map +1 -0
- package/lib/browser/internal/holder/instance-holder.d.mts +101 -0
- package/lib/browser/internal/holder/instance-holder.d.mts.map +1 -0
- package/lib/browser/internal/holder/instance-holder.mjs +19 -0
- package/lib/browser/internal/holder/instance-holder.mjs.map +1 -0
- package/lib/browser/internal/holder/unified-storage.d.mts +53 -0
- package/lib/browser/internal/holder/unified-storage.d.mts.map +1 -0
- package/lib/browser/internal/holder/unified-storage.mjs +144 -0
- package/lib/browser/internal/holder/unified-storage.mjs.map +1 -0
- package/lib/browser/internal/lifecycle/circular-detector.d.mts +39 -0
- package/lib/browser/internal/lifecycle/circular-detector.d.mts.map +1 -0
- package/lib/browser/internal/lifecycle/circular-detector.mjs +55 -0
- package/lib/browser/internal/lifecycle/circular-detector.mjs.map +1 -0
- package/lib/browser/internal/lifecycle/lifecycle-event-bus.d.mts +18 -0
- package/lib/browser/internal/lifecycle/lifecycle-event-bus.d.mts.map +1 -0
- package/lib/browser/internal/lifecycle/lifecycle-event-bus.mjs +43 -0
- package/lib/browser/internal/lifecycle/lifecycle-event-bus.mjs.map +1 -0
- package/lib/browser/internal/stub-factory-class.d.mts +14 -0
- package/lib/browser/internal/stub-factory-class.d.mts.map +1 -0
- package/lib/browser/internal/stub-factory-class.mjs +18 -0
- package/lib/browser/internal/stub-factory-class.mjs.map +1 -0
- package/lib/browser/symbols/injectable-token.d.mts +5 -0
- package/lib/browser/symbols/injectable-token.d.mts.map +1 -0
- package/lib/browser/symbols/injectable-token.mjs +6 -0
- package/lib/browser/symbols/injectable-token.mjs.map +1 -0
- package/lib/browser/token/injection-token.d.mts +55 -0
- package/lib/browser/token/injection-token.d.mts.map +1 -0
- package/lib/browser/token/injection-token.mjs +100 -0
- package/lib/browser/token/injection-token.mjs.map +1 -0
- package/lib/browser/token/registry.d.mts +37 -0
- package/lib/browser/token/registry.d.mts.map +1 -0
- package/lib/browser/token/registry.mjs +86 -0
- package/lib/browser/token/registry.mjs.map +1 -0
- package/lib/browser/utils/default-injectors.d.mts +12 -0
- package/lib/browser/utils/default-injectors.d.mts.map +1 -0
- package/lib/browser/utils/default-injectors.mjs +13 -0
- package/lib/browser/utils/default-injectors.mjs.map +1 -0
- package/lib/browser/utils/get-injectable-token.d.mts +9 -0
- package/lib/browser/utils/get-injectable-token.d.mts.map +1 -0
- package/lib/browser/utils/get-injectable-token.mjs +13 -0
- package/lib/browser/utils/get-injectable-token.mjs.map +1 -0
- package/lib/browser/utils/get-injectors.d.mts +55 -0
- package/lib/browser/utils/get-injectors.d.mts.map +1 -0
- package/lib/browser/utils/get-injectors.mjs +121 -0
- package/lib/browser/utils/get-injectors.mjs.map +1 -0
- package/lib/browser/utils/types.d.mts +23 -0
- package/lib/browser/utils/types.d.mts.map +1 -0
- package/lib/{container-DAKOvAgr.mjs → container-8-z89TyQ.mjs} +1325 -1462
- package/lib/container-8-z89TyQ.mjs.map +1 -0
- package/lib/{container-Bp1W-pWJ.d.mts → container-CNiqesCL.d.mts} +598 -617
- package/lib/container-CNiqesCL.d.mts.map +1 -0
- package/lib/{container-DENMeJ87.cjs → container-CaY2fDuk.cjs} +1369 -1512
- package/lib/container-CaY2fDuk.cjs.map +1 -0
- package/lib/{container-YPwvmlK2.d.cts → container-D-0Ho3qL.d.cts} +598 -612
- package/lib/container-D-0Ho3qL.d.cts.map +1 -0
- package/lib/index.cjs +13 -15
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +58 -223
- package/lib/index.d.cts.map +1 -1
- package/lib/index.d.mts +62 -222
- package/lib/index.d.mts.map +1 -1
- package/lib/index.mjs +5 -6
- package/lib/index.mjs.map +1 -1
- package/lib/testing/index.cjs +569 -311
- package/lib/testing/index.cjs.map +1 -1
- package/lib/testing/index.d.cts +370 -41
- package/lib/testing/index.d.cts.map +1 -1
- package/lib/testing/index.d.mts +370 -41
- package/lib/testing/index.d.mts.map +1 -1
- package/lib/testing/index.mjs +568 -305
- package/lib/testing/index.mjs.map +1 -1
- package/package.json +2 -1
- package/src/__tests__/circular-detector.spec.mts +193 -0
- package/src/__tests__/concurrent.spec.mts +368 -0
- package/src/__tests__/container.spec.mts +32 -30
- package/src/__tests__/di-error.spec.mts +351 -0
- package/src/__tests__/e2e.browser.spec.mts +0 -4
- package/src/__tests__/e2e.spec.mts +10 -19
- package/src/__tests__/event-emitter.spec.mts +232 -109
- package/src/__tests__/get-injectors.spec.mts +250 -39
- package/src/__tests__/injection-token.spec.mts +293 -349
- package/src/__tests__/library-findings.spec.mts +8 -8
- package/src/__tests__/registry.spec.mts +358 -210
- package/src/__tests__/resolution-context.spec.mts +255 -0
- package/src/__tests__/scope-tracker.spec.mts +598 -0
- package/src/__tests__/scope-upgrade.spec.mts +808 -0
- package/src/__tests__/scoped-container.spec.mts +595 -0
- package/src/__tests__/test-container.spec.mts +293 -0
- package/src/__tests__/token-resolver.spec.mts +207 -0
- package/src/__tests__/unified-storage.spec.mts +535 -0
- package/src/__tests__/unit-test-container.spec.mts +405 -0
- package/src/__type-tests__/container.spec-d.mts +180 -0
- package/src/__type-tests__/factory.spec-d.mts +15 -3
- package/src/__type-tests__/inject.spec-d.mts +115 -20
- package/src/__type-tests__/injectable.spec-d.mts +69 -52
- package/src/__type-tests__/injection-token.spec-d.mts +176 -0
- package/src/__type-tests__/scoped-container.spec-d.mts +212 -0
- package/src/container/abstract-container.mts +327 -0
- package/src/container/container.mts +142 -170
- package/src/container/scoped-container.mts +126 -208
- package/src/decorators/factory.decorator.mts +16 -11
- package/src/decorators/injectable.decorator.mts +20 -16
- package/src/enums/index.mts +2 -2
- package/src/enums/injectable-scope.enum.mts +1 -0
- package/src/enums/injectable-type.enum.mts +1 -0
- package/src/errors/di-error.mts +96 -0
- package/src/event-emitter.mts +3 -27
- package/src/index.mts +6 -153
- package/src/interfaces/container.interface.mts +13 -0
- package/src/interfaces/factory.interface.mts +1 -1
- package/src/interfaces/index.mts +1 -1
- package/src/internal/context/async-local-storage.mts +3 -2
- package/src/internal/context/async-local-storage.types.mts +1 -0
- package/src/internal/context/factory-context.mts +1 -0
- package/src/internal/context/index.mts +3 -1
- package/src/internal/context/resolution-context.mts +1 -0
- package/src/internal/context/service-initialization-context.mts +43 -0
- package/src/internal/core/index.mts +5 -4
- package/src/internal/core/instance-resolver.mts +460 -302
- package/src/internal/core/name-resolver.mts +196 -0
- package/src/internal/core/scope-tracker.mts +242 -0
- package/src/internal/core/{instantiator.mts → service-initializer.mts} +51 -29
- package/src/internal/core/service-invalidator.mts +290 -0
- package/src/internal/core/token-resolver.mts +122 -0
- package/src/internal/holder/holder-storage.interface.mts +11 -5
- package/src/internal/holder/index.mts +2 -5
- package/src/internal/holder/instance-holder.mts +1 -3
- package/src/internal/holder/unified-storage.mts +245 -0
- package/src/internal/index.mts +2 -1
- package/src/internal/lifecycle/circular-detector.mts +1 -0
- package/src/internal/lifecycle/index.mts +1 -1
- package/src/internal/lifecycle/lifecycle-event-bus.mts +1 -0
- package/src/internal/stub-factory-class.mts +16 -0
- package/src/symbols/injectable-token.mts +3 -1
- package/src/testing/index.mts +2 -0
- package/src/testing/test-container.mts +546 -85
- package/src/testing/types.mts +117 -0
- package/src/testing/unit-test-container.mts +509 -0
- package/src/token/injection-token.mts +41 -4
- package/src/token/registry.mts +75 -9
- package/src/utils/default-injectors.mts +16 -0
- package/src/utils/get-injectable-token.mts +2 -3
- package/src/utils/get-injectors.mts +26 -15
- package/src/utils/index.mts +3 -1
- package/src/utils/types.mts +1 -0
- package/tsdown.config.mts +11 -1
- package/lib/browser/index.d.mts.map +0 -1
- package/lib/browser/index.mjs.map +0 -1
- package/lib/container-Bp1W-pWJ.d.mts.map +0 -1
- package/lib/container-DAKOvAgr.mjs.map +0 -1
- package/lib/container-DENMeJ87.cjs.map +0 -1
- package/lib/container-YPwvmlK2.d.cts.map +0 -1
- package/src/__tests__/async-local-storage.browser.spec.mts +0 -166
- package/src/__tests__/async-local-storage.spec.mts +0 -333
- package/src/__tests__/errors.spec.mts +0 -87
- package/src/__tests__/factory.spec.mts +0 -137
- package/src/__tests__/injectable.spec.mts +0 -246
- package/src/__tests__/request-scope.spec.mts +0 -416
- package/src/__tests__/service-instantiator.spec.mts +0 -410
- package/src/__tests__/service-locator-event-bus.spec.mts +0 -242
- package/src/__tests__/service-locator-manager.spec.mts +0 -300
- package/src/__tests__/service-locator.spec.mts +0 -966
- package/src/__tests__/unified-api.spec.mts +0 -130
- package/src/browser.mts +0 -11
- package/src/injectors.mts +0 -18
- package/src/internal/context/request-context.mts +0 -225
- package/src/internal/core/invalidator.mts +0 -437
- package/src/internal/core/service-locator.mts +0 -202
- package/src/internal/core/token-processor.mts +0 -252
- package/src/internal/holder/base-holder-manager.mts +0 -334
- package/src/internal/holder/holder-manager.mts +0 -85
- package/src/internal/holder/request-storage.mts +0 -127
- package/src/internal/holder/singleton-storage.mts +0 -92
- package/src/testing/README.md +0 -80
- package/src/testing/__tests__/test-container.spec.mts +0 -173
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { assertType, describe, test } from 'vitest'
|
|
2
|
+
import { z } from 'zod/v4'
|
|
3
|
+
|
|
4
|
+
import type { Factorable } from '../interfaces/index.mjs'
|
|
5
|
+
|
|
6
|
+
import { Container } from '../container/container.mjs'
|
|
7
|
+
import { Factory, Injectable } from '../decorators/index.mjs'
|
|
8
|
+
import { InjectionToken } from '../token/injection-token.mjs'
|
|
9
|
+
|
|
10
|
+
interface FooService {
|
|
11
|
+
makeFoo(): string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const simpleObjectSchema = z.object({
|
|
15
|
+
foo: z.string(),
|
|
16
|
+
})
|
|
17
|
+
const simpleOptionalObjectSchema = z
|
|
18
|
+
.object({
|
|
19
|
+
foo: z.string(),
|
|
20
|
+
})
|
|
21
|
+
.optional()
|
|
22
|
+
|
|
23
|
+
const typelessObjectToken = InjectionToken.create(
|
|
24
|
+
Symbol.for('Typeless object token'),
|
|
25
|
+
simpleObjectSchema,
|
|
26
|
+
)
|
|
27
|
+
const typelessOptionalObjectToken = InjectionToken.create(
|
|
28
|
+
Symbol.for('Typeless optional object token'),
|
|
29
|
+
simpleOptionalObjectSchema,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
const typedObjectToken = InjectionToken.create<
|
|
33
|
+
FooService,
|
|
34
|
+
typeof simpleObjectSchema
|
|
35
|
+
>(Symbol.for('Typed object token'), simpleObjectSchema)
|
|
36
|
+
const typedOptionalObjectToken = InjectionToken.create<
|
|
37
|
+
FooService,
|
|
38
|
+
typeof simpleOptionalObjectSchema
|
|
39
|
+
>(Symbol.for('Typed optional object token'), simpleOptionalObjectSchema)
|
|
40
|
+
|
|
41
|
+
const typedToken = InjectionToken.create<FooService>(Symbol.for('Typed token'))
|
|
42
|
+
|
|
43
|
+
describe('ScopedContainer.get', () => {
|
|
44
|
+
describe('#1 Classes', () => {
|
|
45
|
+
test('simple class', async () => {
|
|
46
|
+
@Injectable()
|
|
47
|
+
class Foo {
|
|
48
|
+
makeFoo() {
|
|
49
|
+
return 'foo'
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const container = new Container()
|
|
54
|
+
const scopedContainer = container.beginRequest('req-1')
|
|
55
|
+
assertType<Foo>(await scopedContainer.get(Foo))
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test('class with required argument', async () => {
|
|
59
|
+
@Injectable({
|
|
60
|
+
schema: simpleObjectSchema,
|
|
61
|
+
})
|
|
62
|
+
class Foo {
|
|
63
|
+
constructor(public arg: z.infer<typeof simpleObjectSchema>) {}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const container = new Container()
|
|
67
|
+
const scopedContainer = container.beginRequest('req-1')
|
|
68
|
+
assertType<Foo>(await scopedContainer.get(Foo, { foo: 'bar' }))
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('should fail if not compatible', async () => {
|
|
72
|
+
@Injectable({
|
|
73
|
+
schema: simpleObjectSchema,
|
|
74
|
+
})
|
|
75
|
+
class Foo {
|
|
76
|
+
constructor(public arg: z.infer<typeof simpleObjectSchema>) {}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const container = new Container()
|
|
80
|
+
const scopedContainer = container.beginRequest('req-1')
|
|
81
|
+
// @ts-expect-error Should fail if not compatible
|
|
82
|
+
await scopedContainer.get(Foo, { test: 'bar' })
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test('factory class returns unwrapped type', async () => {
|
|
86
|
+
@Factory()
|
|
87
|
+
class FooFactory implements Factorable<string> {
|
|
88
|
+
create() {
|
|
89
|
+
return 'created'
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const container = new Container()
|
|
94
|
+
const scopedContainer = container.beginRequest('req-1')
|
|
95
|
+
// When getting a Factorable class, we get the created type, not the factory
|
|
96
|
+
assertType<string>(await scopedContainer.get(FooFactory))
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('#2 Token with required Schema', async () => {
|
|
101
|
+
const container = new Container()
|
|
102
|
+
const scopedContainer = container.beginRequest('req-1')
|
|
103
|
+
|
|
104
|
+
const result = await scopedContainer.get(typelessObjectToken, {
|
|
105
|
+
foo: 'bar',
|
|
106
|
+
})
|
|
107
|
+
assertType<unknown>(result)
|
|
108
|
+
|
|
109
|
+
const result2 = await scopedContainer.get(typedObjectToken, { foo: 'bar' })
|
|
110
|
+
assertType<FooService>(result2)
|
|
111
|
+
|
|
112
|
+
// @ts-expect-error We show error when we pass the wrong type
|
|
113
|
+
await scopedContainer.get(typedObjectToken, undefined)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test('#3 Token with optional Schema', async () => {
|
|
117
|
+
const container = new Container()
|
|
118
|
+
const scopedContainer = container.beginRequest('req-1')
|
|
119
|
+
|
|
120
|
+
const result = await scopedContainer.get(typelessOptionalObjectToken)
|
|
121
|
+
assertType<unknown>(result)
|
|
122
|
+
|
|
123
|
+
const result2 = await scopedContainer.get(typedOptionalObjectToken)
|
|
124
|
+
assertType<FooService>(result2)
|
|
125
|
+
|
|
126
|
+
const result3 = await scopedContainer.get(typedObjectToken)
|
|
127
|
+
// Special case when we pass the token without args
|
|
128
|
+
// We can only return an error string
|
|
129
|
+
assertType<'Error: Your token requires args: foo'>(result3)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
test('#4 Token with no Schema', async () => {
|
|
133
|
+
const container = new Container()
|
|
134
|
+
const scopedContainer = container.beginRequest('req-1')
|
|
135
|
+
|
|
136
|
+
const result = await scopedContainer.get(typedToken)
|
|
137
|
+
assertType<FooService>(result)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test('#5 BoundInjectionToken', async () => {
|
|
141
|
+
const container = new Container()
|
|
142
|
+
const scopedContainer = container.beginRequest('req-1')
|
|
143
|
+
|
|
144
|
+
const boundToken = InjectionToken.bound(typedObjectToken, { foo: 'bar' })
|
|
145
|
+
const result = await scopedContainer.get(boundToken)
|
|
146
|
+
assertType<FooService>(result)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test('#6 FactoryInjectionToken', async () => {
|
|
150
|
+
const container = new Container()
|
|
151
|
+
const scopedContainer = container.beginRequest('req-1')
|
|
152
|
+
|
|
153
|
+
const factoryToken = InjectionToken.factory(typedObjectToken, async () => ({
|
|
154
|
+
foo: 'bar',
|
|
155
|
+
}))
|
|
156
|
+
const result = await scopedContainer.get(factoryToken)
|
|
157
|
+
assertType<FooService>(result)
|
|
158
|
+
})
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
describe('ScopedContainer methods', () => {
|
|
162
|
+
test('getRequestId returns string', () => {
|
|
163
|
+
const container = new Container()
|
|
164
|
+
const scopedContainer = container.beginRequest('req-1')
|
|
165
|
+
assertType<string>(scopedContainer.getRequestId())
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
test('getParent returns Container', () => {
|
|
169
|
+
const container = new Container()
|
|
170
|
+
const scopedContainer = container.beginRequest('req-1')
|
|
171
|
+
assertType<Container>(scopedContainer.getParent())
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
test('getMetadata returns any', () => {
|
|
175
|
+
const container = new Container()
|
|
176
|
+
const scopedContainer = container.beginRequest('req-1', { userId: '123' })
|
|
177
|
+
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
|
178
|
+
assertType<any | undefined>(scopedContainer.getMetadata('userId'))
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
test('setMetadata accepts any value', () => {
|
|
182
|
+
const container = new Container()
|
|
183
|
+
const scopedContainer = container.beginRequest('req-1')
|
|
184
|
+
scopedContainer.setMetadata('userId', '123')
|
|
185
|
+
scopedContainer.setMetadata('count', 42)
|
|
186
|
+
scopedContainer.setMetadata('data', { nested: true })
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
test('dispose returns Promise<void>', async () => {
|
|
190
|
+
const container = new Container()
|
|
191
|
+
const scopedContainer = container.beginRequest('req-1')
|
|
192
|
+
assertType<Promise<void>>(scopedContainer.dispose())
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
test('endRequest returns Promise<void>', async () => {
|
|
196
|
+
const container = new Container()
|
|
197
|
+
const scopedContainer = container.beginRequest('req-1')
|
|
198
|
+
assertType<Promise<void>>(scopedContainer.endRequest())
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
test('invalidate returns Promise<void>', async () => {
|
|
202
|
+
const container = new Container()
|
|
203
|
+
const scopedContainer = container.beginRequest('req-1')
|
|
204
|
+
assertType<Promise<void>>(scopedContainer.invalidate({}))
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
test('requestId property is string', () => {
|
|
208
|
+
const container = new Container()
|
|
209
|
+
const scopedContainer = container.beginRequest('req-1')
|
|
210
|
+
assertType<string>(scopedContainer.requestId)
|
|
211
|
+
})
|
|
212
|
+
})
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import type { z, ZodType } from 'zod/v4'
|
|
2
|
+
|
|
3
|
+
import type { IContainer } from '../interfaces/container.interface.mjs'
|
|
4
|
+
import type { Factorable } from '../interfaces/factory.interface.mjs'
|
|
5
|
+
import type { NameResolver } from '../internal/core/name-resolver.mjs'
|
|
6
|
+
import type { ServiceInvalidator } from '../internal/core/service-invalidator.mjs'
|
|
7
|
+
import type { TokenResolver } from '../internal/core/token-resolver.mjs'
|
|
8
|
+
import type {
|
|
9
|
+
ClassType,
|
|
10
|
+
ClassTypeWithArgument,
|
|
11
|
+
InjectionTokenSchemaType,
|
|
12
|
+
} from '../token/injection-token.mjs'
|
|
13
|
+
import type { Registry } from '../token/registry.mjs'
|
|
14
|
+
import type { Join, UnionToArray } from '../utils/types.mjs'
|
|
15
|
+
|
|
16
|
+
import { InjectableScope, InjectableType } from '../enums/index.mjs'
|
|
17
|
+
import { DIError, DIErrorCode } from '../errors/index.mjs'
|
|
18
|
+
import { InstanceStatus } from '../internal/holder/instance-holder.mjs'
|
|
19
|
+
import { UnifiedStorage } from '../internal/holder/unified-storage.mjs'
|
|
20
|
+
import { StubFactoryClass } from '../internal/index.mjs'
|
|
21
|
+
import {
|
|
22
|
+
BoundInjectionToken,
|
|
23
|
+
FactoryInjectionToken,
|
|
24
|
+
InjectionToken,
|
|
25
|
+
} from '../token/injection-token.mjs'
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Abstract base class for dependency injection containers.
|
|
29
|
+
*
|
|
30
|
+
* Provides shared implementation for common container operations.
|
|
31
|
+
* Both Container and ScopedContainer extend this class.
|
|
32
|
+
*/
|
|
33
|
+
export abstract class AbstractContainer implements IContainer {
|
|
34
|
+
/**
|
|
35
|
+
* The default scope used when adding instances without explicit registration.
|
|
36
|
+
*/
|
|
37
|
+
protected abstract readonly defaultScope: InjectableScope
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The request ID for scoped containers, undefined for root container.
|
|
41
|
+
*/
|
|
42
|
+
protected abstract readonly requestId: string | undefined
|
|
43
|
+
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// ABSTRACT METHODS - Must be implemented by subclasses
|
|
46
|
+
// ============================================================================
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Gets the storage for this container.
|
|
50
|
+
*/
|
|
51
|
+
abstract getStorage(): UnifiedStorage
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Gets the registry for this container.
|
|
55
|
+
*/
|
|
56
|
+
protected abstract getRegistry(): Registry
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Gets the token resolver.
|
|
60
|
+
*/
|
|
61
|
+
protected abstract getTokenResolver(): TokenResolver
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Gets the name resolver.
|
|
65
|
+
*/
|
|
66
|
+
protected abstract getNameResolver(): NameResolver
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Gets the service invalidator.
|
|
70
|
+
*/
|
|
71
|
+
protected abstract getServiceInvalidator(): ServiceInvalidator
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Gets an instance from the container.
|
|
75
|
+
*/
|
|
76
|
+
// #1 Simple class
|
|
77
|
+
abstract get<T extends ClassType>(
|
|
78
|
+
token: T,
|
|
79
|
+
): InstanceType<T> extends Factorable<infer R>
|
|
80
|
+
? Promise<R>
|
|
81
|
+
: Promise<InstanceType<T>>
|
|
82
|
+
// #1.1 Simple class with args
|
|
83
|
+
abstract get<T extends ClassTypeWithArgument<R>, R>(
|
|
84
|
+
token: T,
|
|
85
|
+
args: R,
|
|
86
|
+
): Promise<InstanceType<T>>
|
|
87
|
+
// #2 Token with required Schema
|
|
88
|
+
abstract get<T, S extends InjectionTokenSchemaType>(
|
|
89
|
+
token: InjectionToken<T, S>,
|
|
90
|
+
args: z.input<S>,
|
|
91
|
+
): Promise<T>
|
|
92
|
+
// #3 Token with optional Schema
|
|
93
|
+
abstract get<T, S extends InjectionTokenSchemaType, R extends boolean>(
|
|
94
|
+
token: InjectionToken<T, S, R>,
|
|
95
|
+
): R extends false
|
|
96
|
+
? Promise<T>
|
|
97
|
+
: S extends ZodType<infer Type>
|
|
98
|
+
? `Error: Your token requires args: ${Join<
|
|
99
|
+
UnionToArray<keyof Type>,
|
|
100
|
+
', '
|
|
101
|
+
>}`
|
|
102
|
+
: 'Error: Your token requires args'
|
|
103
|
+
// #4 Token with no Schema
|
|
104
|
+
abstract get<T>(token: InjectionToken<T, undefined>): Promise<T>
|
|
105
|
+
abstract get<T>(token: BoundInjectionToken<T, any>): Promise<T>
|
|
106
|
+
abstract get<T>(token: FactoryInjectionToken<T, any>): Promise<T>
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Invalidates a service and its dependencies.
|
|
110
|
+
*/
|
|
111
|
+
abstract invalidate(service: unknown): Promise<void>
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Disposes the container and cleans up all resources.
|
|
115
|
+
*/
|
|
116
|
+
abstract dispose(): Promise<void>
|
|
117
|
+
|
|
118
|
+
// ============================================================================
|
|
119
|
+
// SHARED IMPLEMENTATIONS
|
|
120
|
+
// ============================================================================
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Calculates the instance name for a given token and optional arguments.
|
|
124
|
+
*
|
|
125
|
+
* @internal
|
|
126
|
+
* @param token The class type, InjectionToken, BoundInjectionToken, or FactoryInjectionToken
|
|
127
|
+
* @param args Optional arguments (ignored for BoundInjectionToken which uses its bound value)
|
|
128
|
+
* @returns The calculated instance name string, or null if the token is a FactoryInjectionToken that is not yet resolved
|
|
129
|
+
*/
|
|
130
|
+
calculateInstanceName(
|
|
131
|
+
token:
|
|
132
|
+
| ClassType
|
|
133
|
+
| InjectionToken<any, any>
|
|
134
|
+
| BoundInjectionToken<any, any>
|
|
135
|
+
| FactoryInjectionToken<any, any>,
|
|
136
|
+
args?: unknown,
|
|
137
|
+
): string | null {
|
|
138
|
+
const tokenResolver = this.getTokenResolver()
|
|
139
|
+
|
|
140
|
+
// Use validateAndResolveTokenArgs to handle token normalization and arg resolution
|
|
141
|
+
const [err, { actualToken, validatedArgs }] =
|
|
142
|
+
tokenResolver.validateAndResolveTokenArgs(token, args)
|
|
143
|
+
|
|
144
|
+
if (err) {
|
|
145
|
+
// Return null if factory token is not resolved
|
|
146
|
+
if (
|
|
147
|
+
err instanceof DIError &&
|
|
148
|
+
err.code === DIErrorCode.FactoryTokenNotResolved
|
|
149
|
+
) {
|
|
150
|
+
return null
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Return null if validation fails (can't calculate name with invalid args)
|
|
154
|
+
if (
|
|
155
|
+
err instanceof DIError &&
|
|
156
|
+
err.code === DIErrorCode.TokenValidationError
|
|
157
|
+
) {
|
|
158
|
+
return null
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Get the real token for registry lookup to determine scope
|
|
163
|
+
const realToken = this.getTokenResolver().getRealToken(actualToken)
|
|
164
|
+
|
|
165
|
+
const registry = this.getRegistry()
|
|
166
|
+
|
|
167
|
+
// Get scope from registry, or use default scope if not registered
|
|
168
|
+
const scope = registry.has(realToken)
|
|
169
|
+
? registry.get(realToken).scope
|
|
170
|
+
: this.defaultScope
|
|
171
|
+
|
|
172
|
+
// Generate instance name using the name resolver with actual token and validated args
|
|
173
|
+
return this.getNameResolver().generateInstanceName(
|
|
174
|
+
actualToken,
|
|
175
|
+
validatedArgs,
|
|
176
|
+
scope === InjectableScope.Request ? this.requestId : undefined,
|
|
177
|
+
scope,
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Checks if a service is registered in the container.
|
|
183
|
+
*/
|
|
184
|
+
isRegistered(token: any): boolean {
|
|
185
|
+
const realToken = this.getTokenResolver().getRegistryToken(token)
|
|
186
|
+
return this.getRegistry().has(realToken)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Waits for all pending operations to complete.
|
|
191
|
+
*/
|
|
192
|
+
async ready(): Promise<void> {
|
|
193
|
+
await this.getServiceInvalidator().readyWithStorage(this.getStorage())
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* @internal
|
|
198
|
+
* Attempts to get an instance synchronously if it already exists.
|
|
199
|
+
*/
|
|
200
|
+
tryGetSync<T>(token: any, args?: any): T | null {
|
|
201
|
+
return this.tryGetSyncFromStorage(
|
|
202
|
+
token,
|
|
203
|
+
args,
|
|
204
|
+
this.getStorage(),
|
|
205
|
+
this.requestId,
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* @internal
|
|
211
|
+
* Internal method for getting instances synchronously with configurable storage.
|
|
212
|
+
*/
|
|
213
|
+
protected tryGetSyncFromStorage<T>(
|
|
214
|
+
token: any,
|
|
215
|
+
args: any,
|
|
216
|
+
storage: UnifiedStorage,
|
|
217
|
+
requestId?: string,
|
|
218
|
+
): T | null {
|
|
219
|
+
const tokenResolver = this.getTokenResolver()
|
|
220
|
+
const realToken = tokenResolver.getRegistryToken(token)
|
|
221
|
+
const registry = this.getRegistry()
|
|
222
|
+
const scope = registry.has(realToken)
|
|
223
|
+
? registry.get(realToken).scope
|
|
224
|
+
: InjectableScope.Singleton
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
const instanceName = this.getNameResolver().generateInstanceName(
|
|
228
|
+
tokenResolver.normalizeToken(token),
|
|
229
|
+
args,
|
|
230
|
+
requestId,
|
|
231
|
+
scope,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
const result = storage.get(instanceName)
|
|
235
|
+
if (result && result[0] === undefined && result[1]) {
|
|
236
|
+
const holder = result[1]
|
|
237
|
+
if (holder.status === InstanceStatus.Created) {
|
|
238
|
+
return holder.instance as T
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
} catch {
|
|
242
|
+
// Ignore error
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return null
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Adds an instance to the container.
|
|
250
|
+
* Accepts class types, InjectionTokens, and BoundInjectionTokens.
|
|
251
|
+
* Rejects InjectionTokens with required schemas (use BoundInjectionToken instead).
|
|
252
|
+
*
|
|
253
|
+
* @param token The class type, InjectionToken, or BoundInjectionToken to register the instance for
|
|
254
|
+
* @param instance The instance to store
|
|
255
|
+
*/
|
|
256
|
+
addInstance<T>(
|
|
257
|
+
token: ClassType | InjectionToken<T, any> | BoundInjectionToken<T, any>,
|
|
258
|
+
instance: T,
|
|
259
|
+
): void {
|
|
260
|
+
this.addInstanceToStorage(
|
|
261
|
+
token,
|
|
262
|
+
instance,
|
|
263
|
+
this.getStorage(),
|
|
264
|
+
this.defaultScope,
|
|
265
|
+
this.requestId,
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* @internal
|
|
271
|
+
* Internal method for adding instances with configurable scope and storage.
|
|
272
|
+
*/
|
|
273
|
+
protected addInstanceToStorage<T>(
|
|
274
|
+
token: ClassType | InjectionToken<T, any> | BoundInjectionToken<T, any>,
|
|
275
|
+
instance: T,
|
|
276
|
+
storage: UnifiedStorage,
|
|
277
|
+
scope: InjectableScope,
|
|
278
|
+
requestId?: string,
|
|
279
|
+
): void {
|
|
280
|
+
// Check if token is an InjectionToken with required schema
|
|
281
|
+
// BoundInjectionToken is allowed (it already has a value bound)
|
|
282
|
+
if (token instanceof InjectionToken) {
|
|
283
|
+
// Check if schema exists and is required (not optional)
|
|
284
|
+
if (token.schema) {
|
|
285
|
+
const schemaType = (token.schema as ZodType)?.def?.type
|
|
286
|
+
if (schemaType !== 'optional') {
|
|
287
|
+
throw DIError.tokenSchemaRequiredError(token.name)
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const tokenResolver = this.getTokenResolver()
|
|
293
|
+
const registry = this.getRegistry()
|
|
294
|
+
|
|
295
|
+
// Normalize the token
|
|
296
|
+
const normalizedToken = tokenResolver.normalizeToken(token)
|
|
297
|
+
const realToken = tokenResolver.getRegistryToken(token)
|
|
298
|
+
|
|
299
|
+
// If it's a class type and not registered, register it with the given scope
|
|
300
|
+
if (typeof token === 'function' && !registry.has(realToken)) {
|
|
301
|
+
registry.set(realToken, scope, token, InjectableType.Class)
|
|
302
|
+
} else if (!registry.has(realToken)) {
|
|
303
|
+
// Set a stub factory class to avoid errors when getting instances of unregistered factory tokens
|
|
304
|
+
registry.set(
|
|
305
|
+
realToken,
|
|
306
|
+
scope,
|
|
307
|
+
StubFactoryClass,
|
|
308
|
+
InjectableType.Class,
|
|
309
|
+
// Lowest priority to avoid conflicts with other registered tokens
|
|
310
|
+
-1,
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Generate instance name with the given scope
|
|
315
|
+
const instanceName = this.getNameResolver().generateInstanceName(
|
|
316
|
+
normalizedToken,
|
|
317
|
+
normalizedToken instanceof BoundInjectionToken
|
|
318
|
+
? normalizedToken.value
|
|
319
|
+
: undefined,
|
|
320
|
+
requestId,
|
|
321
|
+
scope,
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
// Store the instance
|
|
325
|
+
storage.storeInstance(instanceName, instance)
|
|
326
|
+
}
|
|
327
|
+
}
|