@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
|
@@ -1,416 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { Container } from '../container/container.mjs'
|
|
4
|
-
import { Injectable } from '../decorators/injectable.decorator.mjs'
|
|
5
|
-
import { InjectableScope } from '../enums/index.mjs'
|
|
6
|
-
import { inject } from '../index.mjs'
|
|
7
|
-
import { InjectionToken } from '../token/injection-token.mjs'
|
|
8
|
-
import { Registry } from '../token/registry.mjs'
|
|
9
|
-
import { ScopedContainer } from '../container/scoped-container.mjs'
|
|
10
|
-
|
|
11
|
-
describe('Request Scope', () => {
|
|
12
|
-
let container: Container
|
|
13
|
-
let registry: Registry
|
|
14
|
-
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
registry = new Registry()
|
|
17
|
-
container = new Container(registry)
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
describe('Request-scoped services with ScopedContainer', () => {
|
|
21
|
-
it('should create different instances for different requests', async () => {
|
|
22
|
-
@Injectable({ registry, scope: InjectableScope.Request })
|
|
23
|
-
class RequestService {
|
|
24
|
-
public readonly requestId = Math.random().toString(36)
|
|
25
|
-
public readonly createdAt = new Date()
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Start first request - get a ScopedContainer
|
|
29
|
-
const scoped1 = container.beginRequest('request-1')
|
|
30
|
-
const instance1a = await scoped1.get(RequestService)
|
|
31
|
-
const instance1b = await scoped1.get(RequestService)
|
|
32
|
-
|
|
33
|
-
// Start second request - get another ScopedContainer
|
|
34
|
-
const scoped2 = container.beginRequest('request-2')
|
|
35
|
-
const instance2a = await scoped2.get(RequestService)
|
|
36
|
-
const instance2b = await scoped2.get(RequestService)
|
|
37
|
-
|
|
38
|
-
// Within same request, instances should be the same
|
|
39
|
-
expect(instance1a).toBe(instance1b)
|
|
40
|
-
expect(instance2a).toBe(instance2b)
|
|
41
|
-
|
|
42
|
-
// Between different requests, instances should be different
|
|
43
|
-
expect(instance1a).not.toBe(instance2a)
|
|
44
|
-
expect(instance1a.requestId).not.toBe(instance2a.requestId)
|
|
45
|
-
|
|
46
|
-
// Clean up
|
|
47
|
-
await scoped1.endRequest()
|
|
48
|
-
await scoped2.endRequest()
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('should handle request context lifecycle correctly', async () => {
|
|
52
|
-
@Injectable({ registry, scope: InjectableScope.Request })
|
|
53
|
-
class RequestService {
|
|
54
|
-
public readonly requestId = Math.random().toString(36)
|
|
55
|
-
public destroyed = false
|
|
56
|
-
|
|
57
|
-
async onServiceDestroy() {
|
|
58
|
-
this.destroyed = true
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Start request - get a ScopedContainer
|
|
63
|
-
const scoped = container.beginRequest('test-request')
|
|
64
|
-
|
|
65
|
-
const instance = await scoped.get(RequestService)
|
|
66
|
-
expect(instance.destroyed).toBe(false)
|
|
67
|
-
|
|
68
|
-
// End request should trigger cleanup
|
|
69
|
-
await scoped.endRequest()
|
|
70
|
-
expect(instance.destroyed).toBe(true)
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
it('should support request metadata', async () => {
|
|
74
|
-
const requestId = 'test-request'
|
|
75
|
-
const metadata = { userId: 'user123', sessionId: 'session456' }
|
|
76
|
-
|
|
77
|
-
const scoped = container.beginRequest(requestId, metadata)
|
|
78
|
-
|
|
79
|
-
// Verify metadata is accessible from the scoped container
|
|
80
|
-
expect(scoped.getRequestId()).toBe(requestId)
|
|
81
|
-
expect(scoped.getMetadata('userId')).toBe('user123')
|
|
82
|
-
expect(scoped.getMetadata('sessionId')).toBe('session456')
|
|
83
|
-
|
|
84
|
-
await scoped.endRequest()
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
it('should handle pre-prepared instances', async () => {
|
|
88
|
-
const token = InjectionToken.create<{ value: string }>('PrePrepared')
|
|
89
|
-
|
|
90
|
-
@Injectable({ registry, scope: InjectableScope.Request, token })
|
|
91
|
-
class RequestService {
|
|
92
|
-
constructor(public readonly value: string = 'default') {}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const scoped = container.beginRequest('test-request')
|
|
96
|
-
|
|
97
|
-
// Add a pre-prepared instance
|
|
98
|
-
const prePreparedInstance = { value: 'pre-prepared' }
|
|
99
|
-
scoped.addInstance(token, prePreparedInstance)
|
|
100
|
-
|
|
101
|
-
// Getting the service should return the pre-prepared instance
|
|
102
|
-
const instance = await scoped.get(token)
|
|
103
|
-
expect(instance).toBe(prePreparedInstance)
|
|
104
|
-
|
|
105
|
-
await scoped.endRequest()
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
it('should throw error when resolving request-scoped service from Container directly', async () => {
|
|
109
|
-
@Injectable({ registry, scope: InjectableScope.Request })
|
|
110
|
-
class RequestService {
|
|
111
|
-
public readonly requestId = Math.random().toString(36)
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Trying to resolve request-scoped service from Container should throw
|
|
115
|
-
await expect(container.get(RequestService)).rejects.toThrow(
|
|
116
|
-
/Cannot resolve request-scoped service/,
|
|
117
|
-
)
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
it('should throw error when creating duplicate request ID', () => {
|
|
121
|
-
container.beginRequest('request-1')
|
|
122
|
-
|
|
123
|
-
// Creating another request with the same ID should throw
|
|
124
|
-
expect(() => container.beginRequest('request-1')).toThrow(
|
|
125
|
-
/Request context "request-1" already exists/,
|
|
126
|
-
)
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
it('should allow reusing request ID after ending the request', async () => {
|
|
130
|
-
const scoped1 = container.beginRequest('request-1')
|
|
131
|
-
await scoped1.endRequest()
|
|
132
|
-
|
|
133
|
-
// Should be able to create a new request with the same ID
|
|
134
|
-
const scoped2 = container.beginRequest('request-1')
|
|
135
|
-
expect(scoped2).toBeInstanceOf(ScopedContainer)
|
|
136
|
-
await scoped2.endRequest()
|
|
137
|
-
})
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
describe('ScopedContainer delegation', () => {
|
|
141
|
-
it('should delegate singleton resolution to parent Container', async () => {
|
|
142
|
-
@Injectable({ registry })
|
|
143
|
-
class SingletonService {
|
|
144
|
-
public readonly id = Math.random()
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const scoped = container.beginRequest('test-request')
|
|
148
|
-
|
|
149
|
-
// Get singleton from scoped container
|
|
150
|
-
const instance1 = await scoped.get(SingletonService)
|
|
151
|
-
|
|
152
|
-
// Get singleton from main container
|
|
153
|
-
const instance2 = await container.get(SingletonService)
|
|
154
|
-
|
|
155
|
-
// Should be the same instance
|
|
156
|
-
expect(instance1).toBe(instance2)
|
|
157
|
-
|
|
158
|
-
await scoped.endRequest()
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
it('should delegate transient resolution to parent Container', async () => {
|
|
162
|
-
@Injectable({ registry, scope: InjectableScope.Transient })
|
|
163
|
-
class TransientService {
|
|
164
|
-
public readonly id = Math.random()
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const scoped = container.beginRequest('test-request')
|
|
168
|
-
|
|
169
|
-
// Each get should create a new instance
|
|
170
|
-
const instance1 = await scoped.get(TransientService)
|
|
171
|
-
const instance2 = await scoped.get(TransientService)
|
|
172
|
-
|
|
173
|
-
expect(instance1).not.toBe(instance2)
|
|
174
|
-
|
|
175
|
-
await scoped.endRequest()
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
it('should allow request-scoped services to depend on singletons', async () => {
|
|
179
|
-
@Injectable({ registry })
|
|
180
|
-
class SingletonService {
|
|
181
|
-
public readonly id = Math.random()
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
@Injectable({ registry, scope: InjectableScope.Request })
|
|
185
|
-
class RequestService {
|
|
186
|
-
singleton = inject(SingletonService)
|
|
187
|
-
public readonly id = Math.random()
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const scoped = container.beginRequest('test-request')
|
|
191
|
-
|
|
192
|
-
const requestInstance = await scoped.get(RequestService)
|
|
193
|
-
const singletonFromRequest = requestInstance.singleton
|
|
194
|
-
const singletonDirect = await container.get(SingletonService)
|
|
195
|
-
|
|
196
|
-
// The singleton injected into the request service should be the same
|
|
197
|
-
// as the one from the main container
|
|
198
|
-
expect(singletonFromRequest).toBe(singletonDirect)
|
|
199
|
-
|
|
200
|
-
await scoped.endRequest()
|
|
201
|
-
})
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
describe('Request isolation (race condition prevention)', () => {
|
|
205
|
-
it('should prevent duplicate initialization during concurrent resolution within same request', async () => {
|
|
206
|
-
let initializationCount = 0
|
|
207
|
-
|
|
208
|
-
@Injectable({ registry, scope: InjectableScope.Request })
|
|
209
|
-
class RequestService {
|
|
210
|
-
public readonly instanceId: string
|
|
211
|
-
|
|
212
|
-
constructor() {
|
|
213
|
-
initializationCount++
|
|
214
|
-
this.instanceId = Math.random().toString(36)
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
async onServiceInit() {
|
|
218
|
-
// Simulate async initialization that takes time
|
|
219
|
-
await new Promise((resolve) => setTimeout(resolve, 50))
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const scoped = container.beginRequest('test-request')
|
|
224
|
-
|
|
225
|
-
// Fire multiple concurrent resolution requests for the same service
|
|
226
|
-
const [instance1, instance2, instance3] = await Promise.all([
|
|
227
|
-
scoped.get(RequestService),
|
|
228
|
-
scoped.get(RequestService),
|
|
229
|
-
scoped.get(RequestService),
|
|
230
|
-
])
|
|
231
|
-
|
|
232
|
-
// All instances should be the same (no duplicate initialization)
|
|
233
|
-
expect(instance1).toBe(instance2)
|
|
234
|
-
expect(instance2).toBe(instance3)
|
|
235
|
-
expect(initializationCount).toBe(1) // Only initialized once
|
|
236
|
-
|
|
237
|
-
await scoped.endRequest()
|
|
238
|
-
})
|
|
239
|
-
|
|
240
|
-
it('should return correct instance when waiting for in-progress creation', async () => {
|
|
241
|
-
let creationOrder: string[] = []
|
|
242
|
-
|
|
243
|
-
@Injectable({ registry, scope: InjectableScope.Request })
|
|
244
|
-
class SlowService {
|
|
245
|
-
public readonly id: string
|
|
246
|
-
|
|
247
|
-
constructor() {
|
|
248
|
-
this.id = Math.random().toString(36)
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
async onServiceInit() {
|
|
252
|
-
creationOrder.push('init-start')
|
|
253
|
-
// Simulate slow async initialization
|
|
254
|
-
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
255
|
-
creationOrder.push('init-end')
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
const scoped = container.beginRequest('test-request')
|
|
260
|
-
|
|
261
|
-
// Start first resolution (will start creating)
|
|
262
|
-
const promise1 = scoped.get(SlowService)
|
|
263
|
-
creationOrder.push('promise1-started')
|
|
264
|
-
|
|
265
|
-
// Start second resolution while first is still in progress
|
|
266
|
-
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
267
|
-
creationOrder.push('promise2-starting')
|
|
268
|
-
const promise2 = scoped.get(SlowService)
|
|
269
|
-
|
|
270
|
-
const [instance1, instance2] = await Promise.all([promise1, promise2])
|
|
271
|
-
|
|
272
|
-
// Both should be the same instance
|
|
273
|
-
expect(instance1).toBe(instance2)
|
|
274
|
-
expect(instance1.id).toBe(instance2.id)
|
|
275
|
-
|
|
276
|
-
// Verify the initialization only happened once
|
|
277
|
-
expect(creationOrder.filter((x) => x === 'init-start').length).toBe(1)
|
|
278
|
-
expect(creationOrder.filter((x) => x === 'init-end').length).toBe(1)
|
|
279
|
-
|
|
280
|
-
await scoped.endRequest()
|
|
281
|
-
})
|
|
282
|
-
|
|
283
|
-
it('should isolate request contexts during concurrent async operations', async () => {
|
|
284
|
-
@Injectable({ registry, scope: InjectableScope.Request })
|
|
285
|
-
class RequestService {
|
|
286
|
-
public readonly requestId: string
|
|
287
|
-
|
|
288
|
-
constructor() {
|
|
289
|
-
this.requestId = Math.random().toString(36)
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// Start two requests concurrently
|
|
294
|
-
const scoped1 = container.beginRequest('request-1')
|
|
295
|
-
const scoped2 = container.beginRequest('request-2')
|
|
296
|
-
|
|
297
|
-
// Simulate concurrent async operations
|
|
298
|
-
const [instance1, instance2] = await Promise.all([
|
|
299
|
-
scoped1.get(RequestService),
|
|
300
|
-
scoped2.get(RequestService),
|
|
301
|
-
])
|
|
302
|
-
|
|
303
|
-
// Each request should have its own instance
|
|
304
|
-
expect(instance1.requestId).not.toBe(instance2.requestId)
|
|
305
|
-
|
|
306
|
-
// Verify they're still accessible after concurrent resolution
|
|
307
|
-
const instance1Again = await scoped1.get(RequestService)
|
|
308
|
-
const instance2Again = await scoped2.get(RequestService)
|
|
309
|
-
|
|
310
|
-
expect(instance1).toBe(instance1Again)
|
|
311
|
-
expect(instance2).toBe(instance2Again)
|
|
312
|
-
|
|
313
|
-
await scoped1.endRequest()
|
|
314
|
-
await scoped2.endRequest()
|
|
315
|
-
})
|
|
316
|
-
|
|
317
|
-
it('should maintain correct context during interleaved async operations', async () => {
|
|
318
|
-
@Injectable({ registry, scope: InjectableScope.Request })
|
|
319
|
-
class RequestService {
|
|
320
|
-
public readonly requestId: string
|
|
321
|
-
public value = 0
|
|
322
|
-
|
|
323
|
-
constructor() {
|
|
324
|
-
this.requestId = Math.random().toString(36)
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
async asyncOperation(delay: number): Promise<void> {
|
|
328
|
-
await new Promise((resolve) => setTimeout(resolve, delay))
|
|
329
|
-
this.value++
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
const scoped1 = container.beginRequest('request-1')
|
|
334
|
-
const scoped2 = container.beginRequest('request-2')
|
|
335
|
-
|
|
336
|
-
const instance1 = await scoped1.get(RequestService)
|
|
337
|
-
const instance2 = await scoped2.get(RequestService)
|
|
338
|
-
|
|
339
|
-
// Start async operations with different delays
|
|
340
|
-
await Promise.all([
|
|
341
|
-
instance1.asyncOperation(50),
|
|
342
|
-
instance2.asyncOperation(25),
|
|
343
|
-
instance1.asyncOperation(10),
|
|
344
|
-
instance2.asyncOperation(75),
|
|
345
|
-
])
|
|
346
|
-
|
|
347
|
-
// Each instance should have been modified independently
|
|
348
|
-
expect(instance1.value).toBe(2)
|
|
349
|
-
expect(instance2.value).toBe(2)
|
|
350
|
-
|
|
351
|
-
await scoped1.endRequest()
|
|
352
|
-
await scoped2.endRequest()
|
|
353
|
-
})
|
|
354
|
-
})
|
|
355
|
-
|
|
356
|
-
describe('ScopedContainer API', () => {
|
|
357
|
-
it('should implement IContainer interface', async () => {
|
|
358
|
-
const scoped = container.beginRequest('test-request')
|
|
359
|
-
|
|
360
|
-
// Check that all IContainer methods exist
|
|
361
|
-
expect(typeof scoped.get).toBe('function')
|
|
362
|
-
expect(typeof scoped.invalidate).toBe('function')
|
|
363
|
-
expect(typeof scoped.isRegistered).toBe('function')
|
|
364
|
-
expect(typeof scoped.dispose).toBe('function')
|
|
365
|
-
expect(typeof scoped.ready).toBe('function')
|
|
366
|
-
expect(typeof scoped.tryGetSync).toBe('function')
|
|
367
|
-
|
|
368
|
-
await scoped.endRequest()
|
|
369
|
-
})
|
|
370
|
-
|
|
371
|
-
it('should track active request IDs in Container', async () => {
|
|
372
|
-
expect(container.hasActiveRequest('request-1')).toBe(false)
|
|
373
|
-
|
|
374
|
-
const scoped1 = container.beginRequest('request-1')
|
|
375
|
-
expect(container.hasActiveRequest('request-1')).toBe(true)
|
|
376
|
-
|
|
377
|
-
const scoped2 = container.beginRequest('request-2')
|
|
378
|
-
expect(container.hasActiveRequest('request-2')).toBe(true)
|
|
379
|
-
|
|
380
|
-
expect(container.getActiveRequestIds().size).toBe(2)
|
|
381
|
-
|
|
382
|
-
await scoped1.endRequest()
|
|
383
|
-
expect(container.hasActiveRequest('request-1')).toBe(false)
|
|
384
|
-
expect(container.hasActiveRequest('request-2')).toBe(true)
|
|
385
|
-
|
|
386
|
-
await scoped2.endRequest()
|
|
387
|
-
expect(container.getActiveRequestIds().size).toBe(0)
|
|
388
|
-
})
|
|
389
|
-
|
|
390
|
-
it('should return parent Container from ScopedContainer', async () => {
|
|
391
|
-
const scoped = container.beginRequest('test-request')
|
|
392
|
-
expect(scoped.getParent()).toBe(container)
|
|
393
|
-
await scoped.endRequest()
|
|
394
|
-
})
|
|
395
|
-
|
|
396
|
-
it('dispose() should be an alias for endRequest()', async () => {
|
|
397
|
-
@Injectable({ registry, scope: InjectableScope.Request })
|
|
398
|
-
class RequestService {
|
|
399
|
-
public destroyed = false
|
|
400
|
-
|
|
401
|
-
async onServiceDestroy() {
|
|
402
|
-
this.destroyed = true
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
const scoped = container.beginRequest('test-request')
|
|
407
|
-
const instance = await scoped.get(RequestService)
|
|
408
|
-
|
|
409
|
-
// Use dispose() instead of endRequest()
|
|
410
|
-
await scoped.dispose()
|
|
411
|
-
|
|
412
|
-
expect(instance.destroyed).toBe(true)
|
|
413
|
-
expect(container.hasActiveRequest('test-request')).toBe(false)
|
|
414
|
-
})
|
|
415
|
-
})
|
|
416
|
-
})
|