@navios/di 0.7.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +110 -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 -1508
- package/lib/browser/index.mjs +29 -2650
- 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-Pb_Y4Z4x.mjs → container-8-z89TyQ.mjs} +1269 -1305
- package/lib/container-8-z89TyQ.mjs.map +1 -0
- package/lib/{container-BuAutHGg.d.mts → container-CNiqesCL.d.mts} +600 -569
- package/lib/container-CNiqesCL.d.mts.map +1 -0
- package/lib/{container-DnzgpfBe.cjs → container-CaY2fDuk.cjs} +1287 -1329
- package/lib/container-CaY2fDuk.cjs.map +1 -0
- package/lib/{container-oGTgX2iX.d.cts → container-D-0Ho3qL.d.cts} +601 -565
- 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 +461 -292
- 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-processor.mts → token-resolver.mts} +17 -88
- 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-BuAutHGg.d.mts.map +0 -1
- package/lib/container-DnzgpfBe.cjs.map +0 -1
- package/lib/container-Pb_Y4Z4x.mjs.map +0 -1
- package/lib/container-oGTgX2iX.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 -214
- package/src/internal/core/invalidator.mts +0 -437
- package/src/internal/core/service-locator.mts +0 -202
- package/src/internal/holder/base-holder-manager.mts +0 -238
- package/src/internal/holder/holder-manager.mts +0 -85
- package/src/internal/holder/request-storage.mts +0 -134
- package/src/internal/holder/singleton-storage.mts +0 -105
- package/src/testing/README.md +0 -80
- package/src/testing/__tests__/test-container.spec.mts +0 -173
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comprehensive tests for ScopedContainer
|
|
3
|
+
*
|
|
4
|
+
* These tests cover:
|
|
5
|
+
* 1. Basic ScopedContainer functionality
|
|
6
|
+
* 2. Request-scoped service resolution
|
|
7
|
+
* 3. addInstance method with various token types
|
|
8
|
+
* 4. Error handling and validation
|
|
9
|
+
* 5. Integration with parent Container
|
|
10
|
+
* 6. Disposal and cleanup
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
14
|
+
import { z } from 'zod/v4'
|
|
15
|
+
|
|
16
|
+
import { Container } from '../container/container.mjs'
|
|
17
|
+
import { Injectable } from '../decorators/injectable.decorator.mjs'
|
|
18
|
+
import { InjectableScope } from '../enums/index.mjs'
|
|
19
|
+
import { DIError, DIErrorCode } from '../errors/di-error.mjs'
|
|
20
|
+
import { InjectionToken } from '../token/injection-token.mjs'
|
|
21
|
+
import { Registry } from '../token/registry.mjs'
|
|
22
|
+
import { getInjectors } from '../utils/get-injectors.mjs'
|
|
23
|
+
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// TEST UTILITIES
|
|
26
|
+
// ============================================================================
|
|
27
|
+
|
|
28
|
+
function createTestSetup() {
|
|
29
|
+
const registry = new Registry()
|
|
30
|
+
const injectors = getInjectors()
|
|
31
|
+
const container = new Container(registry, null, injectors)
|
|
32
|
+
|
|
33
|
+
return { registry, injectors, container }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// SECTION 1: BASIC FUNCTIONALITY
|
|
38
|
+
// ============================================================================
|
|
39
|
+
|
|
40
|
+
describe('ScopedContainer: Basic Functionality', () => {
|
|
41
|
+
let registry: Registry
|
|
42
|
+
let container: Container
|
|
43
|
+
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
const setup = createTestSetup()
|
|
46
|
+
registry = setup.registry
|
|
47
|
+
container = setup.container
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
afterEach(async () => {
|
|
51
|
+
await container.dispose()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
describe('Container creation and lifecycle', () => {
|
|
55
|
+
it('should create a scoped container with request ID', () => {
|
|
56
|
+
const scoped = container.beginRequest('test-request-1')
|
|
57
|
+
expect(scoped).toBeDefined()
|
|
58
|
+
expect(scoped.getRequestId()).toBe('test-request-1')
|
|
59
|
+
expect(scoped.getParent()).toBe(container)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('should track active request IDs', async () => {
|
|
63
|
+
const scoped1 = container.beginRequest('request-1')
|
|
64
|
+
const scoped2 = container.beginRequest('request-2')
|
|
65
|
+
|
|
66
|
+
expect(container.hasActiveRequest('request-1')).toBe(true)
|
|
67
|
+
expect(container.hasActiveRequest('request-2')).toBe(true)
|
|
68
|
+
expect(container.getActiveRequestIds().size).toBe(2)
|
|
69
|
+
|
|
70
|
+
await scoped1.endRequest()
|
|
71
|
+
expect(container.hasActiveRequest('request-1')).toBe(false)
|
|
72
|
+
expect(container.hasActiveRequest('request-2')).toBe(true)
|
|
73
|
+
|
|
74
|
+
await scoped2.endRequest()
|
|
75
|
+
expect(container.getActiveRequestIds().size).toBe(0)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('should prevent duplicate request IDs', () => {
|
|
79
|
+
container.beginRequest('duplicate-id')
|
|
80
|
+
expect(() => container.beginRequest('duplicate-id')).toThrow()
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('should support metadata', () => {
|
|
84
|
+
const metadata = { userId: '123', sessionId: 'abc' }
|
|
85
|
+
const scoped = container.beginRequest('request-1', metadata)
|
|
86
|
+
|
|
87
|
+
expect(scoped.getMetadata('userId')).toBe('123')
|
|
88
|
+
expect(scoped.getMetadata('sessionId')).toBe('abc')
|
|
89
|
+
expect(scoped.getMetadata('nonExistent')).toBeUndefined()
|
|
90
|
+
|
|
91
|
+
scoped.setMetadata('newKey', 'newValue')
|
|
92
|
+
expect(scoped.getMetadata('newKey')).toBe('newValue')
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
describe('Request-scoped service resolution', () => {
|
|
97
|
+
it('should resolve request-scoped services from scoped container', async () => {
|
|
98
|
+
let instanceCount = 0
|
|
99
|
+
|
|
100
|
+
@Injectable({ scope: InjectableScope.Request, registry })
|
|
101
|
+
class RequestService {
|
|
102
|
+
id = ++instanceCount
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const scoped = container.beginRequest('request-1')
|
|
106
|
+
const instance1 = await scoped.get(RequestService)
|
|
107
|
+
const instance2 = await scoped.get(RequestService)
|
|
108
|
+
|
|
109
|
+
expect(instance1).toBeInstanceOf(RequestService)
|
|
110
|
+
expect(instance1).toBe(instance2) // Same instance within request
|
|
111
|
+
expect(instance1.id).toBe(1)
|
|
112
|
+
|
|
113
|
+
await scoped.endRequest()
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('should create different instances for different requests', async () => {
|
|
117
|
+
let instanceCount = 0
|
|
118
|
+
|
|
119
|
+
@Injectable({ scope: InjectableScope.Request, registry })
|
|
120
|
+
class RequestService {
|
|
121
|
+
id = ++instanceCount
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const scoped1 = container.beginRequest('request-1')
|
|
125
|
+
const instance1 = await scoped1.get(RequestService)
|
|
126
|
+
|
|
127
|
+
const scoped2 = container.beginRequest('request-2')
|
|
128
|
+
const instance2 = await scoped2.get(RequestService)
|
|
129
|
+
|
|
130
|
+
expect(instance1).not.toBe(instance2)
|
|
131
|
+
expect(instance1.id).toBe(1)
|
|
132
|
+
expect(instance2.id).toBe(2)
|
|
133
|
+
|
|
134
|
+
await scoped1.endRequest()
|
|
135
|
+
await scoped2.endRequest()
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('should delegate singleton services to parent', async () => {
|
|
139
|
+
@Injectable({ scope: InjectableScope.Singleton, registry })
|
|
140
|
+
class SingletonService {
|
|
141
|
+
id = Math.random()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const scoped = container.beginRequest('request-1')
|
|
145
|
+
const instance1 = await scoped.get(SingletonService)
|
|
146
|
+
const instance2 = await container.get(SingletonService)
|
|
147
|
+
|
|
148
|
+
expect(instance1).toBe(instance2) // Same singleton instance
|
|
149
|
+
|
|
150
|
+
await scoped.endRequest()
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('should delegate transient services to parent', async () => {
|
|
154
|
+
@Injectable({ scope: InjectableScope.Transient, registry })
|
|
155
|
+
class TransientService {
|
|
156
|
+
id = Math.random()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const scoped = container.beginRequest('request-1')
|
|
160
|
+
const instance1 = await scoped.get(TransientService)
|
|
161
|
+
const instance2 = await scoped.get(TransientService)
|
|
162
|
+
|
|
163
|
+
expect(instance1).not.toBe(instance2) // Different instances
|
|
164
|
+
|
|
165
|
+
await scoped.endRequest()
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
describe('Disposal and cleanup', () => {
|
|
170
|
+
it('should clean up request-scoped services on endRequest', async () => {
|
|
171
|
+
let destroyCount = 0
|
|
172
|
+
|
|
173
|
+
@Injectable({ scope: InjectableScope.Request, registry })
|
|
174
|
+
class RequestService {
|
|
175
|
+
onServiceDestroy() {
|
|
176
|
+
destroyCount++
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const scoped = container.beginRequest('request-1')
|
|
181
|
+
await scoped.get(RequestService)
|
|
182
|
+
await scoped.endRequest()
|
|
183
|
+
|
|
184
|
+
expect(destroyCount).toBe(1)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
it('should prevent operations after disposal', async () => {
|
|
188
|
+
const scoped = container.beginRequest('request-1')
|
|
189
|
+
await scoped.endRequest()
|
|
190
|
+
|
|
191
|
+
await expect(scoped.get(Container)).rejects.toThrow(
|
|
192
|
+
'ScopedContainer has been disposed',
|
|
193
|
+
)
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
it('should support dispose alias', async () => {
|
|
197
|
+
const scoped = container.beginRequest('request-1')
|
|
198
|
+
await scoped.dispose()
|
|
199
|
+
|
|
200
|
+
await expect(scoped.get(Container)).rejects.toThrow(
|
|
201
|
+
'ScopedContainer has been disposed',
|
|
202
|
+
)
|
|
203
|
+
})
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
// ============================================================================
|
|
208
|
+
// SECTION 2: addInstance METHOD TESTS
|
|
209
|
+
// ============================================================================
|
|
210
|
+
|
|
211
|
+
describe('ScopedContainer: addInstance Method', () => {
|
|
212
|
+
let registry: Registry
|
|
213
|
+
let container: Container
|
|
214
|
+
|
|
215
|
+
beforeEach(() => {
|
|
216
|
+
const setup = createTestSetup()
|
|
217
|
+
registry = setup.registry
|
|
218
|
+
container = setup.container
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
afterEach(async () => {
|
|
222
|
+
await container.dispose()
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
describe('addInstance with class types', () => {
|
|
226
|
+
it('should throw an error for unregistered class type', () => {
|
|
227
|
+
class UnregisteredService {
|
|
228
|
+
value = 'test'
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const scoped = container.beginRequest('request-1')
|
|
232
|
+
const instance = new UnregisteredService()
|
|
233
|
+
expect(() => scoped.addInstance(UnregisteredService, instance)).toThrow(
|
|
234
|
+
DIError,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
scoped.endRequest()
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
it('should add instance for registered class type', async () => {
|
|
241
|
+
@Injectable({ scope: InjectableScope.Request, registry })
|
|
242
|
+
class RegisteredService {
|
|
243
|
+
value = 'default'
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const scoped = container.beginRequest('request-1')
|
|
247
|
+
const customInstance = new RegisteredService()
|
|
248
|
+
customInstance.value = 'custom'
|
|
249
|
+
|
|
250
|
+
scoped.addInstance(RegisteredService, customInstance)
|
|
251
|
+
|
|
252
|
+
// Should retrieve the added instance, not create a new one
|
|
253
|
+
const retrieved = await scoped.get(RegisteredService)
|
|
254
|
+
expect(retrieved).toBe(customInstance)
|
|
255
|
+
expect(retrieved.value).toBe('custom')
|
|
256
|
+
|
|
257
|
+
scoped.endRequest()
|
|
258
|
+
})
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
describe('addInstance with InjectionToken (no schema)', () => {
|
|
262
|
+
it('should add instance for InjectionToken without schema', async () => {
|
|
263
|
+
interface TestService {
|
|
264
|
+
value: string
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const token = InjectionToken.create<TestService>('TestService')
|
|
268
|
+
const instance: TestService = { value: 'test' }
|
|
269
|
+
|
|
270
|
+
const scoped = container.beginRequest('request-1')
|
|
271
|
+
scoped.addInstance(token, instance)
|
|
272
|
+
|
|
273
|
+
const retrieved = await scoped.get(token)
|
|
274
|
+
expect(retrieved).toBe(instance)
|
|
275
|
+
expect(retrieved.value).toBe('test')
|
|
276
|
+
|
|
277
|
+
scoped.endRequest()
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
it('should add instance for InjectionToken with optional schema', async () => {
|
|
281
|
+
interface TestService {
|
|
282
|
+
value: string
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const optionalSchema = z
|
|
286
|
+
.object({
|
|
287
|
+
name: z.string(),
|
|
288
|
+
})
|
|
289
|
+
.optional()
|
|
290
|
+
|
|
291
|
+
const token = InjectionToken.create<TestService, typeof optionalSchema>(
|
|
292
|
+
'TestService',
|
|
293
|
+
optionalSchema,
|
|
294
|
+
)
|
|
295
|
+
const instance: TestService = { value: 'test' }
|
|
296
|
+
|
|
297
|
+
const scoped = container.beginRequest('request-1')
|
|
298
|
+
scoped.addInstance(token, instance)
|
|
299
|
+
|
|
300
|
+
const retrieved = await scoped.get(token)
|
|
301
|
+
expect(retrieved).toBe(instance)
|
|
302
|
+
|
|
303
|
+
scoped.endRequest()
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it('should reject InjectionToken with required schema', () => {
|
|
307
|
+
interface TestService {
|
|
308
|
+
value: string
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const requiredSchema = z.object({
|
|
312
|
+
name: z.string(),
|
|
313
|
+
age: z.number(),
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
const token = InjectionToken.create<TestService, typeof requiredSchema>(
|
|
317
|
+
'TestService',
|
|
318
|
+
requiredSchema,
|
|
319
|
+
)
|
|
320
|
+
const instance: TestService = { value: 'test' }
|
|
321
|
+
|
|
322
|
+
const scoped = container.beginRequest('request-1')
|
|
323
|
+
|
|
324
|
+
expect(() => {
|
|
325
|
+
scoped.addInstance(token, instance)
|
|
326
|
+
}).toThrow(DIError)
|
|
327
|
+
|
|
328
|
+
const error = (() => {
|
|
329
|
+
try {
|
|
330
|
+
scoped.addInstance(token, instance)
|
|
331
|
+
} catch (e) {
|
|
332
|
+
return e
|
|
333
|
+
}
|
|
334
|
+
})() as DIError
|
|
335
|
+
|
|
336
|
+
expect(error.code).toBe(DIErrorCode.TokenSchemaRequiredError)
|
|
337
|
+
expect(error.message).toContain('requires schema arguments')
|
|
338
|
+
|
|
339
|
+
scoped.endRequest()
|
|
340
|
+
})
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
describe('addInstance with BoundInjectionToken', () => {
|
|
344
|
+
it('should add instance for BoundInjectionToken with required schema', async () => {
|
|
345
|
+
interface TestService {
|
|
346
|
+
value: string
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const requiredSchema = z.object({
|
|
350
|
+
name: z.string(),
|
|
351
|
+
age: z.number(),
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
const token = InjectionToken.create<TestService, typeof requiredSchema>(
|
|
355
|
+
'TestService',
|
|
356
|
+
requiredSchema,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
const boundToken = InjectionToken.bound(token, {
|
|
360
|
+
name: 'John',
|
|
361
|
+
age: 30,
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
const instance: TestService = { value: 'test' }
|
|
365
|
+
|
|
366
|
+
const scoped = container.beginRequest('request-1')
|
|
367
|
+
scoped.addInstance(boundToken, instance)
|
|
368
|
+
console.log(scoped.getStorage().getAllNames())
|
|
369
|
+
|
|
370
|
+
const retrieved = await scoped.get(boundToken)
|
|
371
|
+
expect(retrieved).toBe(instance)
|
|
372
|
+
expect(retrieved.value).toBe('test')
|
|
373
|
+
|
|
374
|
+
scoped.endRequest()
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
it('should use bound value for instance name generation', async () => {
|
|
378
|
+
interface TestService {
|
|
379
|
+
value: string
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const schema = z.object({
|
|
383
|
+
id: z.string(),
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
const token = InjectionToken.create<TestService, typeof schema>(
|
|
387
|
+
'TestService',
|
|
388
|
+
schema,
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
const boundToken1 = InjectionToken.bound(token, { id: '1' })
|
|
392
|
+
const boundToken2 = InjectionToken.bound(token, { id: '2' })
|
|
393
|
+
|
|
394
|
+
const instance1: TestService = { value: 'instance1' }
|
|
395
|
+
const instance2: TestService = { value: 'instance2' }
|
|
396
|
+
|
|
397
|
+
const scoped = container.beginRequest('request-1')
|
|
398
|
+
scoped.addInstance(boundToken1, instance1)
|
|
399
|
+
scoped.addInstance(boundToken2, instance2)
|
|
400
|
+
|
|
401
|
+
const retrieved1 = await scoped.get(boundToken1)
|
|
402
|
+
const retrieved2 = await scoped.get(boundToken2)
|
|
403
|
+
|
|
404
|
+
expect(retrieved1).toBe(instance1)
|
|
405
|
+
expect(retrieved2).toBe(instance2)
|
|
406
|
+
expect(retrieved1.value).toBe('instance1')
|
|
407
|
+
expect(retrieved2.value).toBe('instance2')
|
|
408
|
+
|
|
409
|
+
scoped.endRequest()
|
|
410
|
+
})
|
|
411
|
+
})
|
|
412
|
+
|
|
413
|
+
describe('addInstance error cases', () => {
|
|
414
|
+
it('should reject addInstance on disposed container', async () => {
|
|
415
|
+
class TestService {
|
|
416
|
+
value = 'test'
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const scoped = container.beginRequest('request-1')
|
|
420
|
+
await scoped.endRequest()
|
|
421
|
+
|
|
422
|
+
const instance = new TestService()
|
|
423
|
+
expect(() => {
|
|
424
|
+
scoped.addInstance(TestService, instance)
|
|
425
|
+
}).toThrow('ScopedContainer has been disposed')
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
it('should handle multiple addInstance calls for same token', async () => {
|
|
429
|
+
@Injectable({ scope: InjectableScope.Request, registry })
|
|
430
|
+
class TestService {
|
|
431
|
+
value = 'test'
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const scoped = container.beginRequest('request-1')
|
|
435
|
+
const instance1 = new TestService()
|
|
436
|
+
instance1.value = 'first'
|
|
437
|
+
scoped.addInstance(TestService, instance1)
|
|
438
|
+
|
|
439
|
+
// Second addInstance should overwrite (or throw if storage prevents it)
|
|
440
|
+
const instance2 = new TestService()
|
|
441
|
+
instance2.value = 'second'
|
|
442
|
+
|
|
443
|
+
// Storage.storeInstance throws if instance already exists
|
|
444
|
+
expect(() => {
|
|
445
|
+
scoped.addInstance(TestService, instance2)
|
|
446
|
+
}).toThrow()
|
|
447
|
+
|
|
448
|
+
scoped.endRequest()
|
|
449
|
+
})
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
describe('addInstance integration with get', () => {
|
|
453
|
+
it('should retrieve added instance via get method', async () => {
|
|
454
|
+
@Injectable({ scope: InjectableScope.Request, registry })
|
|
455
|
+
class TestService {
|
|
456
|
+
value = 'custom'
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const scoped = container.beginRequest('request-1')
|
|
460
|
+
const instance = new TestService()
|
|
461
|
+
scoped.addInstance(TestService, instance)
|
|
462
|
+
|
|
463
|
+
const retrieved = await scoped.get(TestService)
|
|
464
|
+
expect(retrieved).toBe(instance)
|
|
465
|
+
expect(retrieved.value).toBe('custom')
|
|
466
|
+
|
|
467
|
+
scoped.endRequest()
|
|
468
|
+
})
|
|
469
|
+
})
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
// ============================================================================
|
|
473
|
+
// SECTION 3: COMPLEX SCENARIOS
|
|
474
|
+
// ============================================================================
|
|
475
|
+
|
|
476
|
+
describe('ScopedContainer: Complex Scenarios', () => {
|
|
477
|
+
let registry: Registry
|
|
478
|
+
let container: Container
|
|
479
|
+
let injectors: ReturnType<typeof getInjectors>
|
|
480
|
+
|
|
481
|
+
beforeEach(() => {
|
|
482
|
+
const setup = createTestSetup()
|
|
483
|
+
registry = setup.registry
|
|
484
|
+
container = setup.container
|
|
485
|
+
injectors = setup.injectors
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
afterEach(async () => {
|
|
489
|
+
await container.dispose()
|
|
490
|
+
})
|
|
491
|
+
|
|
492
|
+
describe('Mixed scopes with addInstance', () => {
|
|
493
|
+
it('should handle singleton depending on request-scoped added instance', async () => {
|
|
494
|
+
@Injectable({ scope: InjectableScope.Singleton, registry })
|
|
495
|
+
class SingletonService {
|
|
496
|
+
public requestService = injectors.inject(RequestService)
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
@Injectable({ scope: InjectableScope.Request, registry })
|
|
500
|
+
class RequestService {
|
|
501
|
+
value = 'request'
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const scoped = container.beginRequest('request-1')
|
|
505
|
+
const requestInstance = new RequestService()
|
|
506
|
+
scoped.addInstance(RequestService, requestInstance)
|
|
507
|
+
|
|
508
|
+
// Singleton should be able to get the request-scoped instance
|
|
509
|
+
const singleton = await scoped.get(SingletonService)
|
|
510
|
+
expect(singleton.requestService).toBe(requestInstance)
|
|
511
|
+
|
|
512
|
+
scoped.endRequest()
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
it('should handle multiple addInstance calls with different token types', async () => {
|
|
516
|
+
@Injectable({ registry, scope: InjectableScope.Request })
|
|
517
|
+
class ClassService {
|
|
518
|
+
value = 'class'
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
interface TokenService {
|
|
522
|
+
value: string
|
|
523
|
+
}
|
|
524
|
+
const token = InjectionToken.create<TokenService>('TokenService')
|
|
525
|
+
|
|
526
|
+
const schema = z.object({ id: z.string() })
|
|
527
|
+
interface BoundService {
|
|
528
|
+
value: string
|
|
529
|
+
}
|
|
530
|
+
const boundToken = InjectionToken.bound(
|
|
531
|
+
InjectionToken.create<BoundService, typeof schema>(
|
|
532
|
+
'BoundService',
|
|
533
|
+
schema,
|
|
534
|
+
),
|
|
535
|
+
{ id: '123' },
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
const scoped = container.beginRequest('request-1')
|
|
539
|
+
|
|
540
|
+
const classInstance = new ClassService()
|
|
541
|
+
scoped.addInstance(ClassService, classInstance)
|
|
542
|
+
|
|
543
|
+
const tokenInstance: TokenService = { value: 'token' }
|
|
544
|
+
scoped.addInstance(token, tokenInstance)
|
|
545
|
+
|
|
546
|
+
const boundInstance: BoundService = { value: 'bound' }
|
|
547
|
+
scoped.addInstance(boundToken, boundInstance)
|
|
548
|
+
|
|
549
|
+
expect(await scoped.get(ClassService)).toBe(classInstance)
|
|
550
|
+
expect(await scoped.get(token)).toBe(tokenInstance)
|
|
551
|
+
expect(await scoped.get(boundToken)).toBe(boundInstance)
|
|
552
|
+
|
|
553
|
+
scoped.endRequest()
|
|
554
|
+
})
|
|
555
|
+
})
|
|
556
|
+
|
|
557
|
+
describe('Invalidation with addInstance', () => {
|
|
558
|
+
it('should invalidate added instances', async () => {
|
|
559
|
+
let destroyCount = 0
|
|
560
|
+
|
|
561
|
+
@Injectable({ scope: InjectableScope.Request, registry })
|
|
562
|
+
class TestService {
|
|
563
|
+
onServiceDestroy() {
|
|
564
|
+
destroyCount++
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const scoped = container.beginRequest('request-1')
|
|
569
|
+
const instance = new TestService()
|
|
570
|
+
scoped.addInstance(TestService, instance)
|
|
571
|
+
|
|
572
|
+
await scoped.invalidate(instance)
|
|
573
|
+
expect(destroyCount).toBe(1)
|
|
574
|
+
|
|
575
|
+
scoped.endRequest()
|
|
576
|
+
})
|
|
577
|
+
})
|
|
578
|
+
|
|
579
|
+
describe('Ready state with addInstance', () => {
|
|
580
|
+
it('should wait for ready state after adding instances', async () => {
|
|
581
|
+
@Injectable({ scope: InjectableScope.Request, registry })
|
|
582
|
+
class TestService {
|
|
583
|
+
value = 'test'
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const scoped = container.beginRequest('request-1')
|
|
587
|
+
const instance = new TestService()
|
|
588
|
+
scoped.addInstance(TestService, instance)
|
|
589
|
+
|
|
590
|
+
await scoped.ready() // Should complete without hanging
|
|
591
|
+
|
|
592
|
+
scoped.endRequest()
|
|
593
|
+
})
|
|
594
|
+
})
|
|
595
|
+
})
|