@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,368 @@
|
|
|
1
|
+
import { describe, expect, it, beforeEach, afterEach } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { Container } from '../container/container.mjs'
|
|
4
|
+
import { Injectable } from '../decorators/index.mjs'
|
|
5
|
+
import { InjectableScope } from '../enums/index.mjs'
|
|
6
|
+
import { inject, asyncInject } from '../utils/index.mjs'
|
|
7
|
+
|
|
8
|
+
describe('Concurrent Operations', () => {
|
|
9
|
+
let container: Container
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
container = new Container()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await container.dispose()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
describe('concurrent singleton resolution', () => {
|
|
20
|
+
it('should return same instance for concurrent requests', async () => {
|
|
21
|
+
let initCount = 0
|
|
22
|
+
|
|
23
|
+
@Injectable()
|
|
24
|
+
class SlowService {
|
|
25
|
+
id: number
|
|
26
|
+
|
|
27
|
+
constructor() {
|
|
28
|
+
initCount++
|
|
29
|
+
this.id = Math.random()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async onServiceInit() {
|
|
33
|
+
await new Promise((resolve) => setTimeout(resolve, 50))
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Start multiple concurrent resolutions
|
|
38
|
+
const promises = Array.from({ length: 10 }, () =>
|
|
39
|
+
container.get(SlowService),
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
const instances = await Promise.all(promises)
|
|
43
|
+
|
|
44
|
+
// All instances should be the same
|
|
45
|
+
const firstInstance = instances[0]
|
|
46
|
+
expect(instances.every((i) => i === firstInstance)).toBe(true)
|
|
47
|
+
|
|
48
|
+
// Should only have initialized once
|
|
49
|
+
expect(initCount).toBe(1)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('should handle race conditions without creating duplicate instances', async () => {
|
|
53
|
+
let creationCount = 0
|
|
54
|
+
|
|
55
|
+
@Injectable()
|
|
56
|
+
class RaceService {
|
|
57
|
+
createdAt = creationCount++
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Fire many concurrent requests
|
|
61
|
+
const promises = Array.from({ length: 100 }, () =>
|
|
62
|
+
container.get(RaceService),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
const instances = await Promise.all(promises)
|
|
66
|
+
|
|
67
|
+
// All should be the same instance
|
|
68
|
+
const first = instances[0]
|
|
69
|
+
expect(instances.every((i) => i === first)).toBe(true)
|
|
70
|
+
expect(creationCount).toBe(1)
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe('concurrent transient resolution', () => {
|
|
75
|
+
it('should create new instance for each concurrent request', async () => {
|
|
76
|
+
let instanceCount = 0
|
|
77
|
+
|
|
78
|
+
@Injectable({ scope: InjectableScope.Transient })
|
|
79
|
+
class TransientService {
|
|
80
|
+
id = ++instanceCount
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const promises = Array.from({ length: 10 }, () =>
|
|
84
|
+
container.get(TransientService),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
const instances = await Promise.all(promises)
|
|
88
|
+
|
|
89
|
+
// All instances should be different
|
|
90
|
+
const ids = instances.map((i) => i.id)
|
|
91
|
+
const uniqueIds = new Set(ids)
|
|
92
|
+
expect(uniqueIds.size).toBe(10)
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
describe('concurrent request-scoped resolution', () => {
|
|
97
|
+
it('should isolate instances per ScopedContainer', async () => {
|
|
98
|
+
@Injectable({ scope: InjectableScope.Request })
|
|
99
|
+
class RequestService {
|
|
100
|
+
id = Math.random()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Create multiple scoped containers concurrently
|
|
104
|
+
const scopedContainers = Array.from({ length: 5 }, (_, i) =>
|
|
105
|
+
container.beginRequest(`request-${i}`),
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
const promises = scopedContainers.map((sc) => sc.get(RequestService))
|
|
109
|
+
const instances = await Promise.all(promises)
|
|
110
|
+
|
|
111
|
+
// Each scoped container should have its own instance
|
|
112
|
+
const uniqueInstances = new Set(instances)
|
|
113
|
+
expect(uniqueInstances.size).toBe(5)
|
|
114
|
+
|
|
115
|
+
// Clean up
|
|
116
|
+
await Promise.all(scopedContainers.map((sc) => sc.endRequest()))
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('should return same instance within same ScopedContainer', async () => {
|
|
120
|
+
@Injectable({ scope: InjectableScope.Request })
|
|
121
|
+
class RequestService {
|
|
122
|
+
id = Math.random()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const scopedContainer = container.beginRequest('test-request')
|
|
126
|
+
|
|
127
|
+
// Multiple concurrent requests within same scoped container
|
|
128
|
+
const promises = Array.from({ length: 10 }, () =>
|
|
129
|
+
scopedContainer.get(RequestService),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
const instances = await Promise.all(promises)
|
|
133
|
+
|
|
134
|
+
// All should be the same instance
|
|
135
|
+
const first = instances[0]
|
|
136
|
+
expect(instances.every((i) => i === first)).toBe(true)
|
|
137
|
+
|
|
138
|
+
await scopedContainer.endRequest()
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
describe('concurrent resolution with dependencies', () => {
|
|
143
|
+
it('should correctly resolve dependency graph concurrently', async () => {
|
|
144
|
+
@Injectable()
|
|
145
|
+
class ServiceA {
|
|
146
|
+
id = Math.random()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@Injectable()
|
|
150
|
+
class ServiceB {
|
|
151
|
+
private a = inject(ServiceA)
|
|
152
|
+
id = Math.random()
|
|
153
|
+
|
|
154
|
+
getAId() {
|
|
155
|
+
return this.a.id
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
@Injectable()
|
|
160
|
+
class ServiceC {
|
|
161
|
+
private a = inject(ServiceA)
|
|
162
|
+
private b = inject(ServiceB)
|
|
163
|
+
|
|
164
|
+
getIds() {
|
|
165
|
+
return { a: this.a.id, b: this.b.id }
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Resolve ServiceC concurrently multiple times
|
|
170
|
+
const promises = Array.from({ length: 20 }, () =>
|
|
171
|
+
container.get(ServiceC),
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
const instances = await Promise.all(promises)
|
|
175
|
+
|
|
176
|
+
// All should be the same instance (singleton)
|
|
177
|
+
const first = instances[0]
|
|
178
|
+
expect(instances.every((i) => i === first)).toBe(true)
|
|
179
|
+
|
|
180
|
+
// All should reference the same A and B (verified by id)
|
|
181
|
+
const ids = first.getIds()
|
|
182
|
+
// ServiceB should reference the same ServiceA
|
|
183
|
+
expect(first.getIds().a).toBe(ids.a)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('should handle async dependencies concurrently', async () => {
|
|
187
|
+
@Injectable()
|
|
188
|
+
class AsyncDependency {
|
|
189
|
+
value = 'async-dep'
|
|
190
|
+
|
|
191
|
+
async onServiceInit() {
|
|
192
|
+
await new Promise((resolve) => setTimeout(resolve, 30))
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
@Injectable()
|
|
197
|
+
class DependentService {
|
|
198
|
+
private dep = asyncInject(AsyncDependency)
|
|
199
|
+
|
|
200
|
+
async getValue() {
|
|
201
|
+
const d = await this.dep
|
|
202
|
+
return d.value
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const promises = Array.from({ length: 5 }, () =>
|
|
207
|
+
container.get(DependentService),
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
const instances = await Promise.all(promises)
|
|
211
|
+
|
|
212
|
+
// All should be the same instance
|
|
213
|
+
expect(instances.every((i) => i === instances[0])).toBe(true)
|
|
214
|
+
|
|
215
|
+
// Should work correctly
|
|
216
|
+
const value = await instances[0].getValue()
|
|
217
|
+
expect(value).toBe('async-dep')
|
|
218
|
+
})
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
describe('concurrent disposal', () => {
|
|
222
|
+
it('should handle invalidation gracefully', async () => {
|
|
223
|
+
@Injectable()
|
|
224
|
+
class DisposableService {
|
|
225
|
+
value = 'active'
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const instance = await container.get(DisposableService)
|
|
229
|
+
expect(instance.value).toBe('active')
|
|
230
|
+
|
|
231
|
+
// invalidate takes the instance, not the class
|
|
232
|
+
await container.invalidate(instance)
|
|
233
|
+
|
|
234
|
+
// After invalidation, getting a new instance should work
|
|
235
|
+
const newInstance = await container.get(DisposableService)
|
|
236
|
+
expect(newInstance).not.toBe(instance)
|
|
237
|
+
})
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
describe('concurrent resolution during initialization', () => {
|
|
241
|
+
it('should wait for service to complete initialization', async () => {
|
|
242
|
+
let initCompleted = false
|
|
243
|
+
|
|
244
|
+
@Injectable()
|
|
245
|
+
class SlowInitService {
|
|
246
|
+
ready = false
|
|
247
|
+
|
|
248
|
+
async onServiceInit() {
|
|
249
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
250
|
+
this.ready = true
|
|
251
|
+
initCompleted = true
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Start resolution
|
|
256
|
+
const firstPromise = container.get(SlowInitService)
|
|
257
|
+
|
|
258
|
+
// Try to get it again before first resolution completes
|
|
259
|
+
await new Promise((resolve) => setTimeout(resolve, 20))
|
|
260
|
+
const secondPromise = container.get(SlowInitService)
|
|
261
|
+
|
|
262
|
+
const [first, second] = await Promise.all([firstPromise, secondPromise])
|
|
263
|
+
|
|
264
|
+
// Both should be the same instance and fully initialized
|
|
265
|
+
expect(first).toBe(second)
|
|
266
|
+
expect(first.ready).toBe(true)
|
|
267
|
+
expect(initCompleted).toBe(true)
|
|
268
|
+
})
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
describe('concurrent mixed scope resolution', () => {
|
|
272
|
+
it('should correctly handle mixed scopes concurrently', async () => {
|
|
273
|
+
@Injectable()
|
|
274
|
+
class SingletonService {
|
|
275
|
+
id = Math.random()
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
@Injectable({ scope: InjectableScope.Transient })
|
|
279
|
+
class TransientService {
|
|
280
|
+
private singleton = inject(SingletonService)
|
|
281
|
+
id = Math.random()
|
|
282
|
+
|
|
283
|
+
getSingletonId() {
|
|
284
|
+
return this.singleton.id
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Get transient services concurrently
|
|
289
|
+
const promises = Array.from({ length: 10 }, () =>
|
|
290
|
+
container.get(TransientService),
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
const instances = await Promise.all(promises)
|
|
294
|
+
|
|
295
|
+
// All transients should be different
|
|
296
|
+
const transientIds = new Set(instances.map((i) => i.id))
|
|
297
|
+
expect(transientIds.size).toBe(10)
|
|
298
|
+
|
|
299
|
+
// But they should all share the same singleton
|
|
300
|
+
const singletonId = instances[0].getSingletonId()
|
|
301
|
+
expect(instances.every((i) => i.getSingletonId() === singletonId)).toBe(
|
|
302
|
+
true,
|
|
303
|
+
)
|
|
304
|
+
})
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
describe('stress test', () => {
|
|
308
|
+
it('should handle high concurrency without errors', async () => {
|
|
309
|
+
@Injectable()
|
|
310
|
+
class StressService {
|
|
311
|
+
value = 'stress'
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Fire 200 concurrent requests
|
|
315
|
+
const promises = Array.from({ length: 200 }, () =>
|
|
316
|
+
container.get(StressService),
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
const instances = await Promise.all(promises)
|
|
320
|
+
|
|
321
|
+
// All should resolve successfully to the same instance
|
|
322
|
+
expect(instances.every((i) => i === instances[0])).toBe(true)
|
|
323
|
+
expect(instances[0].value).toBe('stress')
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
it('should handle rapid get/invalidate cycles', async () => {
|
|
327
|
+
@Injectable()
|
|
328
|
+
class CycleService {
|
|
329
|
+
value = Math.random()
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Get initial instance
|
|
333
|
+
const first = await container.get(CycleService)
|
|
334
|
+
const firstValue = first.value
|
|
335
|
+
|
|
336
|
+
// Invalidate takes instance, not class
|
|
337
|
+
await container.invalidate(first)
|
|
338
|
+
const second = await container.get(CycleService)
|
|
339
|
+
|
|
340
|
+
// New instance should be different
|
|
341
|
+
expect(second).not.toBe(first)
|
|
342
|
+
expect(second.value).not.toBe(firstValue)
|
|
343
|
+
})
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
describe('concurrent scoped container operations', () => {
|
|
347
|
+
it('should handle multiple scoped containers created and disposed concurrently', async () => {
|
|
348
|
+
@Injectable({ scope: InjectableScope.Request })
|
|
349
|
+
class RequestService {
|
|
350
|
+
id = Math.random()
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Create, use, and dispose many scoped containers sequentially
|
|
354
|
+
// (concurrent beginRequest with same IDs would throw)
|
|
355
|
+
const ids: number[] = []
|
|
356
|
+
for (let i = 0; i < 10; i++) {
|
|
357
|
+
const scoped = container.beginRequest(`request-${i}`)
|
|
358
|
+
const service = await scoped.get(RequestService)
|
|
359
|
+
ids.push(service.id)
|
|
360
|
+
await scoped.endRequest()
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// All should have unique IDs
|
|
364
|
+
const uniqueIds = new Set(ids)
|
|
365
|
+
expect(uniqueIds.size).toBe(10)
|
|
366
|
+
})
|
|
367
|
+
})
|
|
368
|
+
})
|
|
@@ -2,26 +2,32 @@
|
|
|
2
2
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
3
3
|
import { z } from 'zod/v4'
|
|
4
4
|
|
|
5
|
-
import type {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
5
|
+
import type { Factorable, FactorableWithArgs } from '../index.mjs'
|
|
6
|
+
import type { ServiceInitializationContext } from '../internal/context/service-initialization-context.mjs'
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
asyncInject,
|
|
10
|
+
Container,
|
|
11
|
+
Factory,
|
|
12
|
+
getInjectableToken,
|
|
13
|
+
getInjectors,
|
|
14
|
+
inject,
|
|
15
|
+
Injectable,
|
|
16
|
+
InjectableScope,
|
|
17
|
+
InjectionToken,
|
|
18
|
+
Registry,
|
|
19
|
+
} from '../index.mjs'
|
|
20
20
|
|
|
21
21
|
describe('Container', () => {
|
|
22
22
|
let container: Container
|
|
23
23
|
let registry: Registry
|
|
24
|
-
let mockLogger: Console
|
|
24
|
+
let mockLogger: Console & {
|
|
25
|
+
log: ReturnType<typeof vi.fn>
|
|
26
|
+
error: ReturnType<typeof vi.fn>
|
|
27
|
+
warn: ReturnType<typeof vi.fn>
|
|
28
|
+
info: ReturnType<typeof vi.fn>
|
|
29
|
+
debug: ReturnType<typeof vi.fn>
|
|
30
|
+
}
|
|
25
31
|
|
|
26
32
|
beforeEach(() => {
|
|
27
33
|
registry = new Registry()
|
|
@@ -41,21 +47,10 @@ describe('Container', () => {
|
|
|
41
47
|
expect(defaultContainer).toBeInstanceOf(Container)
|
|
42
48
|
})
|
|
43
49
|
|
|
44
|
-
it('should create container with custom registry and logger', () => {
|
|
45
|
-
expect(container).toBeInstanceOf(Container)
|
|
46
|
-
expect(container.getServiceLocator()).toBeInstanceOf(ServiceLocator)
|
|
47
|
-
})
|
|
48
|
-
|
|
49
50
|
it('should register itself in the container', async () => {
|
|
50
51
|
const selfInstance = await container.get(Container)
|
|
51
52
|
expect(selfInstance).toBe(container)
|
|
52
53
|
})
|
|
53
|
-
|
|
54
|
-
it('should return the same ServiceLocator instance', () => {
|
|
55
|
-
const serviceLocator1 = container.getServiceLocator()
|
|
56
|
-
const serviceLocator2 = container.getServiceLocator()
|
|
57
|
-
expect(serviceLocator1).toBe(serviceLocator2)
|
|
58
|
-
})
|
|
59
54
|
})
|
|
60
55
|
|
|
61
56
|
describe('Injectable decorator scenarios', () => {
|
|
@@ -283,7 +278,7 @@ describe('Container', () => {
|
|
|
283
278
|
it('should work with factory using context', async () => {
|
|
284
279
|
@Factory({ registry })
|
|
285
280
|
class ContextFactory implements Factorable<TestService> {
|
|
286
|
-
async create(ctx:
|
|
281
|
+
async create(ctx: ServiceInitializationContext) {
|
|
287
282
|
const container = await ctx.inject(Container)
|
|
288
283
|
return new TestService(container)
|
|
289
284
|
}
|
|
@@ -642,6 +637,12 @@ describe('Container', () => {
|
|
|
642
637
|
@Injectable({ registry })
|
|
643
638
|
class TestService {}
|
|
644
639
|
|
|
640
|
+
try {
|
|
641
|
+
await container.get(ErrorFactory)
|
|
642
|
+
} catch (error) {
|
|
643
|
+
console.log(error)
|
|
644
|
+
}
|
|
645
|
+
|
|
645
646
|
await expect(container.get(ErrorFactory)).rejects.toThrow('Factory error')
|
|
646
647
|
})
|
|
647
648
|
|
|
@@ -959,11 +960,12 @@ describe('Container', () => {
|
|
|
959
960
|
|
|
960
961
|
// Create 100 services
|
|
961
962
|
for (let i = 0; i < 100; i++) {
|
|
962
|
-
|
|
963
|
+
const token = InjectionToken.create<TestService>(`TestService${i}`)
|
|
964
|
+
@Injectable({ token, registry })
|
|
963
965
|
class TestService {
|
|
964
966
|
public id = i
|
|
965
967
|
}
|
|
966
|
-
services.push(
|
|
968
|
+
services.push(token)
|
|
967
969
|
}
|
|
968
970
|
|
|
969
971
|
// Get all services
|