@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,117 @@
|
|
|
1
|
+
import type { InjectionToken } from '../token/injection-token.mjs'
|
|
2
|
+
import type { Registry } from '../token/registry.mjs'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Tracks method calls on a mocked service.
|
|
6
|
+
*/
|
|
7
|
+
export interface MethodCallRecord {
|
|
8
|
+
method: string
|
|
9
|
+
args: unknown[]
|
|
10
|
+
timestamp: number
|
|
11
|
+
result?: unknown
|
|
12
|
+
error?: Error
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Tracks lifecycle events for a service.
|
|
17
|
+
*/
|
|
18
|
+
export interface LifecycleRecord {
|
|
19
|
+
event: 'created' | 'initialized' | 'destroyed'
|
|
20
|
+
timestamp: number
|
|
21
|
+
instanceName: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Statistics about a mocked service.
|
|
26
|
+
*/
|
|
27
|
+
export interface MockServiceStats {
|
|
28
|
+
instanceCount: number
|
|
29
|
+
methodCalls: MethodCallRecord[]
|
|
30
|
+
lifecycleEvents: LifecycleRecord[]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A node in the dependency graph.
|
|
35
|
+
*/
|
|
36
|
+
export interface DependencyNode {
|
|
37
|
+
token: string
|
|
38
|
+
instanceName: string
|
|
39
|
+
scope: string
|
|
40
|
+
dependencies: string[]
|
|
41
|
+
dependents: string[]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Serializable dependency graph for snapshot testing.
|
|
46
|
+
*/
|
|
47
|
+
export interface DependencyGraph {
|
|
48
|
+
nodes: Record<string, DependencyNode>
|
|
49
|
+
rootTokens: string[]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Binding configuration for TestContainer.
|
|
54
|
+
*/
|
|
55
|
+
export interface BindingBuilder<T> {
|
|
56
|
+
/**
|
|
57
|
+
* Bind to a concrete value.
|
|
58
|
+
*/
|
|
59
|
+
toValue(value: T): void
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Bind to a class implementation.
|
|
63
|
+
*/
|
|
64
|
+
toClass<C extends new (...args: any[]) => T>(cls: C): void
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Bind to a factory function.
|
|
68
|
+
*/
|
|
69
|
+
toFactory(factory: () => T | Promise<T>): void
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Provider configuration for UnitTestContainer.
|
|
74
|
+
*/
|
|
75
|
+
export interface ProviderConfig<T = any> {
|
|
76
|
+
token: InjectionToken<T, any> | (new (...args: any[]) => T)
|
|
77
|
+
useValue?: T
|
|
78
|
+
useClass?: new (...args: any[]) => T
|
|
79
|
+
useFactory?: () => T | Promise<T>
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Options for TestContainer.
|
|
84
|
+
*/
|
|
85
|
+
export interface TestContainerOptions {
|
|
86
|
+
/**
|
|
87
|
+
* Parent registry. Defaults to globalRegistry.
|
|
88
|
+
* Pass `null` to create a completely isolated container.
|
|
89
|
+
*/
|
|
90
|
+
parentRegistry?: Registry | null
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Logger for debugging.
|
|
94
|
+
*/
|
|
95
|
+
logger?: Console | null
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Options for UnitTestContainer.
|
|
100
|
+
*/
|
|
101
|
+
export interface UnitTestContainerOptions {
|
|
102
|
+
/**
|
|
103
|
+
* List of providers to register. Only these services can be resolved.
|
|
104
|
+
*/
|
|
105
|
+
providers: ProviderConfig[]
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* If true, unregistered dependencies will be auto-mocked instead of throwing.
|
|
109
|
+
* Default: false (throws on unregistered dependencies)
|
|
110
|
+
*/
|
|
111
|
+
allowUnregistered?: boolean
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Logger for debugging.
|
|
115
|
+
*/
|
|
116
|
+
logger?: Console | null
|
|
117
|
+
}
|
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
import type { LifecycleRecord, MethodCallRecord, MockServiceStats, ProviderConfig, UnitTestContainerOptions } from './types.mjs'
|
|
2
|
+
|
|
3
|
+
import { Container } from '../container/container.mjs'
|
|
4
|
+
import { InjectableScope, InjectableType } from '../enums/index.mjs'
|
|
5
|
+
import { DIError } from '../errors/index.mjs'
|
|
6
|
+
import { InjectionToken } from '../token/injection-token.mjs'
|
|
7
|
+
import { Registry } from '../token/registry.mjs'
|
|
8
|
+
import { getInjectableToken } from '../utils/get-injectable-token.mjs'
|
|
9
|
+
import { defaultInjectors } from '../utils/index.mjs'
|
|
10
|
+
|
|
11
|
+
type AnyToken = InjectionToken<any, any> | (new (...args: any[]) => any)
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Creates a tracking proxy that records method calls.
|
|
15
|
+
*/
|
|
16
|
+
function createTrackingProxy<T extends object>(
|
|
17
|
+
target: T,
|
|
18
|
+
tokenId: string,
|
|
19
|
+
methodCalls: Map<string, MethodCallRecord[]>,
|
|
20
|
+
): T {
|
|
21
|
+
return new Proxy(target, {
|
|
22
|
+
get(obj, prop) {
|
|
23
|
+
const value = Reflect.get(obj, prop)
|
|
24
|
+
|
|
25
|
+
if (typeof value === 'function' && typeof prop === 'string') {
|
|
26
|
+
return function (this: unknown, ...args: unknown[]) {
|
|
27
|
+
const calls = methodCalls.get(tokenId) || []
|
|
28
|
+
const record: MethodCallRecord = {
|
|
29
|
+
method: prop,
|
|
30
|
+
args,
|
|
31
|
+
timestamp: Date.now(),
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const result = value.apply(this === undefined ? obj : this, args)
|
|
36
|
+
|
|
37
|
+
if (result instanceof Promise) {
|
|
38
|
+
return result
|
|
39
|
+
.then((res) => {
|
|
40
|
+
record.result = res
|
|
41
|
+
calls.push(record)
|
|
42
|
+
methodCalls.set(tokenId, calls)
|
|
43
|
+
return res
|
|
44
|
+
})
|
|
45
|
+
.catch((err) => {
|
|
46
|
+
record.error = err
|
|
47
|
+
calls.push(record)
|
|
48
|
+
methodCalls.set(tokenId, calls)
|
|
49
|
+
throw err
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
record.result = result
|
|
54
|
+
calls.push(record)
|
|
55
|
+
methodCalls.set(tokenId, calls)
|
|
56
|
+
return result
|
|
57
|
+
} catch (err) {
|
|
58
|
+
record.error = err as Error
|
|
59
|
+
calls.push(record)
|
|
60
|
+
methodCalls.set(tokenId, calls)
|
|
61
|
+
throw err
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return value
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Creates an auto-mock proxy that throws on method access.
|
|
73
|
+
*/
|
|
74
|
+
function createAutoMockProxy(tokenId: string): object {
|
|
75
|
+
return new Proxy(
|
|
76
|
+
{},
|
|
77
|
+
{
|
|
78
|
+
get(_, prop) {
|
|
79
|
+
if (prop === 'then' || prop === 'catch' || prop === 'finally') {
|
|
80
|
+
return undefined
|
|
81
|
+
}
|
|
82
|
+
if (typeof prop === 'symbol') {
|
|
83
|
+
return undefined
|
|
84
|
+
}
|
|
85
|
+
throw new Error(
|
|
86
|
+
`[UnitTestContainer] Attempted to access '${prop}' on auto-mocked service '${tokenId}'. ` +
|
|
87
|
+
`This service was not provided in the providers list. ` +
|
|
88
|
+
`Add it to providers or use allowUnregistered: false to catch this earlier.`,
|
|
89
|
+
)
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* UnitTestContainer for isolated unit testing.
|
|
97
|
+
*
|
|
98
|
+
* Only services explicitly listed in `providers` can be resolved.
|
|
99
|
+
* All method calls are automatically tracked via proxies.
|
|
100
|
+
* Unregistered dependencies throw by default, or can be auto-mocked.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```ts
|
|
104
|
+
* const container = new UnitTestContainer({
|
|
105
|
+
* providers: [
|
|
106
|
+
* { token: UserService, useClass: MockUserService },
|
|
107
|
+
* { token: ConfigToken, useValue: { apiUrl: 'test' } },
|
|
108
|
+
* ],
|
|
109
|
+
* })
|
|
110
|
+
*
|
|
111
|
+
* const service = await container.get(UserService)
|
|
112
|
+
*
|
|
113
|
+
* // All method calls are automatically tracked
|
|
114
|
+
* await service.findUser('123')
|
|
115
|
+
*
|
|
116
|
+
* container.expectCalled(UserService, 'findUser')
|
|
117
|
+
* container.expectCalledWith(UserService, 'findUser', ['123'])
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export class UnitTestContainer extends Container {
|
|
121
|
+
private readonly testRegistry: Registry
|
|
122
|
+
private readonly methodCalls = new Map<string, MethodCallRecord[]>()
|
|
123
|
+
private readonly lifecycleEvents = new Map<string, LifecycleRecord[]>()
|
|
124
|
+
private readonly instanceCounts = new Map<string, number>()
|
|
125
|
+
private readonly registeredTokenIds = new Set<string>()
|
|
126
|
+
private readonly autoMockedTokenIds = new Set<string>()
|
|
127
|
+
private allowUnregistered: boolean
|
|
128
|
+
|
|
129
|
+
constructor(options: UnitTestContainerOptions) {
|
|
130
|
+
const testRegistry = new Registry()
|
|
131
|
+
super(testRegistry, options.logger ?? null, defaultInjectors)
|
|
132
|
+
this.testRegistry = testRegistry
|
|
133
|
+
this.allowUnregistered = options.allowUnregistered ?? false
|
|
134
|
+
|
|
135
|
+
// Register all providers
|
|
136
|
+
for (const provider of options.providers) {
|
|
137
|
+
this.registerProvider(provider)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Enables auto-mocking for unregistered dependencies.
|
|
143
|
+
* Call this to switch from strict mode to auto-mock mode.
|
|
144
|
+
*/
|
|
145
|
+
enableAutoMocking(): this {
|
|
146
|
+
this.allowUnregistered = true
|
|
147
|
+
return this
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Disables auto-mocking (strict mode).
|
|
152
|
+
* Unregistered dependencies will throw.
|
|
153
|
+
*/
|
|
154
|
+
disableAutoMocking(): this {
|
|
155
|
+
this.allowUnregistered = false
|
|
156
|
+
return this
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Override get to wrap instances in tracking proxies.
|
|
161
|
+
*/
|
|
162
|
+
override async get(token: any, args?: unknown): Promise<any> {
|
|
163
|
+
const realToken = this.resolveToken(token)
|
|
164
|
+
|
|
165
|
+
// Check if this is a registered provider
|
|
166
|
+
if (!this.registeredTokenIds.has(realToken.id)) {
|
|
167
|
+
if (!this.allowUnregistered) {
|
|
168
|
+
throw DIError.factoryNotFound(
|
|
169
|
+
`${realToken.toString()} is not in the providers list. ` +
|
|
170
|
+
`Add it to providers or enable allowUnregistered.`,
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Auto-mock unregistered dependency
|
|
175
|
+
if (!this.autoMockedTokenIds.has(realToken.id)) {
|
|
176
|
+
this.autoMockedTokenIds.add(realToken.id)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return createAutoMockProxy(realToken.id)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const instance = await super.get(token, args)
|
|
183
|
+
|
|
184
|
+
// Wrap in tracking proxy if it's an object
|
|
185
|
+
if (instance && typeof instance === 'object') {
|
|
186
|
+
return createTrackingProxy(instance, realToken.id, this.methodCalls)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return instance
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Clears all state and disposes the container.
|
|
194
|
+
*/
|
|
195
|
+
async clear(): Promise<void> {
|
|
196
|
+
await this.dispose()
|
|
197
|
+
this.methodCalls.clear()
|
|
198
|
+
this.lifecycleEvents.clear()
|
|
199
|
+
this.instanceCounts.clear()
|
|
200
|
+
this.autoMockedTokenIds.clear()
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ============================================================================
|
|
204
|
+
// ASSERTION HELPERS
|
|
205
|
+
// ============================================================================
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Asserts that a service has been resolved at least once.
|
|
209
|
+
*/
|
|
210
|
+
expectResolved(token: AnyToken): void {
|
|
211
|
+
const realToken = this.resolveToken(token)
|
|
212
|
+
const storage = this.getStorage()
|
|
213
|
+
const names = storage.getAllNames()
|
|
214
|
+
const found = names.some((name) => name.includes(realToken.id))
|
|
215
|
+
|
|
216
|
+
if (!found) {
|
|
217
|
+
throw new Error(`Expected ${realToken.toString()} to be resolved, but it was not`)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Asserts that a service has NOT been resolved.
|
|
223
|
+
*/
|
|
224
|
+
expectNotResolved(token: AnyToken): void {
|
|
225
|
+
const realToken = this.resolveToken(token)
|
|
226
|
+
const storage = this.getStorage()
|
|
227
|
+
const names = storage.getAllNames()
|
|
228
|
+
const found = names.some((name) => name.includes(realToken.id))
|
|
229
|
+
|
|
230
|
+
if (found) {
|
|
231
|
+
throw new Error(`Expected ${realToken.toString()} to NOT be resolved, but it was`)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Asserts that a service was auto-mocked (not in providers list).
|
|
237
|
+
*/
|
|
238
|
+
expectAutoMocked(token: AnyToken): void {
|
|
239
|
+
const realToken = this.resolveToken(token)
|
|
240
|
+
|
|
241
|
+
if (!this.autoMockedTokenIds.has(realToken.id)) {
|
|
242
|
+
throw new Error(
|
|
243
|
+
`Expected ${realToken.toString()} to be auto-mocked, but it was not. ` +
|
|
244
|
+
`Either it's in the providers list or hasn't been resolved.`,
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Asserts that a service was NOT auto-mocked (is in providers list).
|
|
251
|
+
*/
|
|
252
|
+
expectNotAutoMocked(token: AnyToken): void {
|
|
253
|
+
const realToken = this.resolveToken(token)
|
|
254
|
+
|
|
255
|
+
if (this.autoMockedTokenIds.has(realToken.id)) {
|
|
256
|
+
throw new Error(
|
|
257
|
+
`Expected ${realToken.toString()} to NOT be auto-mocked, but it was.`,
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ============================================================================
|
|
263
|
+
// LIFECYCLE ASSERTIONS
|
|
264
|
+
// ============================================================================
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Records a lifecycle event for tracking.
|
|
268
|
+
*/
|
|
269
|
+
recordLifecycleEvent(token: AnyToken, event: 'created' | 'initialized' | 'destroyed', instanceName: string): void {
|
|
270
|
+
const realToken = this.resolveToken(token)
|
|
271
|
+
const events = this.lifecycleEvents.get(realToken.id) || []
|
|
272
|
+
events.push({
|
|
273
|
+
event,
|
|
274
|
+
timestamp: Date.now(),
|
|
275
|
+
instanceName,
|
|
276
|
+
})
|
|
277
|
+
this.lifecycleEvents.set(realToken.id, events)
|
|
278
|
+
|
|
279
|
+
if (event === 'created') {
|
|
280
|
+
const count = this.instanceCounts.get(realToken.id) || 0
|
|
281
|
+
this.instanceCounts.set(realToken.id, count + 1)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Asserts that a service's onServiceInit was called.
|
|
287
|
+
*/
|
|
288
|
+
expectInitialized(token: AnyToken): void {
|
|
289
|
+
const realToken = this.resolveToken(token)
|
|
290
|
+
const events = this.lifecycleEvents.get(realToken.id) || []
|
|
291
|
+
const initialized = events.some((e) => e.event === 'initialized')
|
|
292
|
+
|
|
293
|
+
if (!initialized) {
|
|
294
|
+
throw new Error(`Expected ${realToken.toString()} to be initialized, but onServiceInit was not called`)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Asserts that a service's onServiceDestroy was called.
|
|
300
|
+
*/
|
|
301
|
+
expectDestroyed(token: AnyToken): void {
|
|
302
|
+
const realToken = this.resolveToken(token)
|
|
303
|
+
const events = this.lifecycleEvents.get(realToken.id) || []
|
|
304
|
+
const destroyed = events.some((e) => e.event === 'destroyed')
|
|
305
|
+
|
|
306
|
+
if (!destroyed) {
|
|
307
|
+
throw new Error(`Expected ${realToken.toString()} to be destroyed, but onServiceDestroy was not called`)
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Asserts that a service has NOT been destroyed.
|
|
313
|
+
*/
|
|
314
|
+
expectNotDestroyed(token: AnyToken): void {
|
|
315
|
+
const realToken = this.resolveToken(token)
|
|
316
|
+
const events = this.lifecycleEvents.get(realToken.id) || []
|
|
317
|
+
const destroyed = events.some((e) => e.event === 'destroyed')
|
|
318
|
+
|
|
319
|
+
if (destroyed) {
|
|
320
|
+
throw new Error(`Expected ${realToken.toString()} to NOT be destroyed, but onServiceDestroy was called`)
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ============================================================================
|
|
325
|
+
// CALL TRACKING (AUTO-TRACKED VIA PROXY)
|
|
326
|
+
// ============================================================================
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Asserts that a method was called on a service.
|
|
330
|
+
*/
|
|
331
|
+
expectCalled(token: AnyToken, method: string): void {
|
|
332
|
+
const realToken = this.resolveToken(token)
|
|
333
|
+
const calls = this.methodCalls.get(realToken.id) || []
|
|
334
|
+
const found = calls.some((c) => c.method === method)
|
|
335
|
+
|
|
336
|
+
if (!found) {
|
|
337
|
+
throw new Error(`Expected ${realToken.toString()}.${method}() to be called, but it was not`)
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Asserts that a method was NOT called on a service.
|
|
343
|
+
*/
|
|
344
|
+
expectNotCalled(token: AnyToken, method: string): void {
|
|
345
|
+
const realToken = this.resolveToken(token)
|
|
346
|
+
const calls = this.methodCalls.get(realToken.id) || []
|
|
347
|
+
const found = calls.some((c) => c.method === method)
|
|
348
|
+
|
|
349
|
+
if (found) {
|
|
350
|
+
throw new Error(`Expected ${realToken.toString()}.${method}() to NOT be called, but it was`)
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Asserts that a method was called with specific arguments.
|
|
356
|
+
*/
|
|
357
|
+
expectCalledWith(token: AnyToken, method: string, expectedArgs: unknown[]): void {
|
|
358
|
+
const realToken = this.resolveToken(token)
|
|
359
|
+
const calls = this.methodCalls.get(realToken.id) || []
|
|
360
|
+
const found = calls.some(
|
|
361
|
+
(c) => c.method === method && this.argsMatch(c.args, expectedArgs),
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
if (!found) {
|
|
365
|
+
const methodCalls = calls.filter((c) => c.method === method)
|
|
366
|
+
const actualArgs = methodCalls.map((c) => JSON.stringify(c.args)).join(', ')
|
|
367
|
+
throw new Error(
|
|
368
|
+
`Expected ${realToken.toString()}.${method}() to be called with ${JSON.stringify(expectedArgs)}. ` +
|
|
369
|
+
`Actual calls: [${actualArgs}]`,
|
|
370
|
+
)
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Asserts that a method was called a specific number of times.
|
|
376
|
+
*/
|
|
377
|
+
expectCallCount(token: AnyToken, method: string, count: number): void {
|
|
378
|
+
const realToken = this.resolveToken(token)
|
|
379
|
+
const calls = this.methodCalls.get(realToken.id) || []
|
|
380
|
+
const actualCount = calls.filter((c) => c.method === method).length
|
|
381
|
+
|
|
382
|
+
if (actualCount !== count) {
|
|
383
|
+
throw new Error(
|
|
384
|
+
`Expected ${realToken.toString()}.${method}() to be called ${count} times, but was called ${actualCount} times`,
|
|
385
|
+
)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Gets all recorded method calls for a service.
|
|
391
|
+
*/
|
|
392
|
+
getMethodCalls(token: AnyToken): MethodCallRecord[] {
|
|
393
|
+
const realToken = this.resolveToken(token)
|
|
394
|
+
return this.methodCalls.get(realToken.id) || []
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Gets statistics about a service.
|
|
399
|
+
*/
|
|
400
|
+
getServiceStats(token: AnyToken): MockServiceStats {
|
|
401
|
+
const realToken = this.resolveToken(token)
|
|
402
|
+
return {
|
|
403
|
+
instanceCount: this.instanceCounts.get(realToken.id) || 0,
|
|
404
|
+
methodCalls: this.methodCalls.get(realToken.id) || [],
|
|
405
|
+
lifecycleEvents: this.lifecycleEvents.get(realToken.id) || [],
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Clears all recorded method calls.
|
|
411
|
+
*/
|
|
412
|
+
clearMethodCalls(): void {
|
|
413
|
+
this.methodCalls.clear()
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Gets list of all registered provider token IDs.
|
|
418
|
+
*/
|
|
419
|
+
getRegisteredTokenIds(): ReadonlySet<string> {
|
|
420
|
+
return this.registeredTokenIds
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Gets list of all auto-mocked token IDs.
|
|
425
|
+
*/
|
|
426
|
+
getAutoMockedTokenIds(): ReadonlySet<string> {
|
|
427
|
+
return this.autoMockedTokenIds
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// ============================================================================
|
|
431
|
+
// INTERNAL HELPERS
|
|
432
|
+
// ============================================================================
|
|
433
|
+
|
|
434
|
+
private resolveToken(token: AnyToken): InjectionToken<any, any> {
|
|
435
|
+
if (typeof token === 'function') {
|
|
436
|
+
try {
|
|
437
|
+
return getInjectableToken(token)
|
|
438
|
+
} catch {
|
|
439
|
+
// Class doesn't have @Injectable, create a token for it
|
|
440
|
+
return InjectionToken.create(token)
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return token
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
private registerProvider<T>(provider: ProviderConfig<T>): void {
|
|
447
|
+
const realToken = this.resolveToken(provider.token as AnyToken)
|
|
448
|
+
this.registeredTokenIds.add(realToken.id)
|
|
449
|
+
|
|
450
|
+
if (provider.useValue !== undefined) {
|
|
451
|
+
this.registerValueBinding(realToken, provider.useValue)
|
|
452
|
+
} else if (provider.useClass) {
|
|
453
|
+
this.registerClassBinding(realToken, provider.useClass)
|
|
454
|
+
} else if (provider.useFactory) {
|
|
455
|
+
this.registerFactoryBinding(realToken, provider.useFactory)
|
|
456
|
+
} else {
|
|
457
|
+
// Just the token - register as itself
|
|
458
|
+
if (typeof provider.token === 'function') {
|
|
459
|
+
this.testRegistry.set(realToken, InjectableScope.Singleton, provider.token, InjectableType.Class)
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
private registerValueBinding<T>(token: InjectionToken<T, any>, value: T): void {
|
|
465
|
+
const ValueHolder = class {
|
|
466
|
+
static instance = value
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
this.testRegistry.set(token, InjectableScope.Singleton, ValueHolder, InjectableType.Class)
|
|
470
|
+
|
|
471
|
+
const nameResolver = this.getNameResolver()
|
|
472
|
+
const instanceName = nameResolver.generateInstanceName(
|
|
473
|
+
token,
|
|
474
|
+
undefined,
|
|
475
|
+
undefined,
|
|
476
|
+
InjectableScope.Singleton,
|
|
477
|
+
)
|
|
478
|
+
this.getStorage().storeInstance(instanceName, value)
|
|
479
|
+
this.recordLifecycleEvent(token, 'created', instanceName)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
private registerClassBinding<T>(token: InjectionToken<T, any>, cls: new (...args: any[]) => T): void {
|
|
483
|
+
this.testRegistry.set(token, InjectableScope.Singleton, cls, InjectableType.Class)
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
private registerFactoryBinding<T>(token: InjectionToken<T, any>, factory: () => T | Promise<T>): void {
|
|
487
|
+
const FactoryWrapper = class {
|
|
488
|
+
static factory = factory
|
|
489
|
+
async create(): Promise<T> {
|
|
490
|
+
return await factory()
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
this.testRegistry.set(token, InjectableScope.Singleton, FactoryWrapper, InjectableType.Factory)
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
private argsMatch(actual: unknown[], expected: unknown[]): boolean {
|
|
498
|
+
if (actual.length !== expected.length) {
|
|
499
|
+
return false
|
|
500
|
+
}
|
|
501
|
+
return actual.every((arg, index) => {
|
|
502
|
+
const exp = expected[index]
|
|
503
|
+
if (typeof exp === 'object' && exp !== null) {
|
|
504
|
+
return JSON.stringify(arg) === JSON.stringify(exp)
|
|
505
|
+
}
|
|
506
|
+
return arg === exp
|
|
507
|
+
})
|
|
508
|
+
}
|
|
509
|
+
}
|
|
@@ -23,6 +23,39 @@ export type InjectionTokenSchemaType =
|
|
|
23
23
|
| BaseInjectionTokenSchemaType
|
|
24
24
|
| OptionalInjectionTokenSchemaType
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Simple hash function for deterministic ID generation
|
|
28
|
+
*/
|
|
29
|
+
function simpleHash(str: string): string {
|
|
30
|
+
let hash = 0
|
|
31
|
+
for (let i = 0; i < str.length; i++) {
|
|
32
|
+
const char = str.charCodeAt(i)
|
|
33
|
+
hash = (hash << 5) - hash + char
|
|
34
|
+
hash = hash & hash // Convert to 32-bit integer
|
|
35
|
+
}
|
|
36
|
+
return Math.abs(hash).toString(36)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Generate deterministic ID from token name
|
|
41
|
+
*/
|
|
42
|
+
function generateTokenId(name: string | symbol | ClassType, customId?: string): string {
|
|
43
|
+
if (customId) {
|
|
44
|
+
return customId
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let base: string
|
|
48
|
+
if (typeof name === 'function') {
|
|
49
|
+
base = `${name.name}_${name.toString()}`
|
|
50
|
+
} else if (typeof name === 'symbol') {
|
|
51
|
+
base = `symbol_${name.toString()}`
|
|
52
|
+
} else {
|
|
53
|
+
base = `token_${name}`
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return `${base.split('_')[0]}_${simpleHash(base)}`
|
|
57
|
+
}
|
|
58
|
+
|
|
26
59
|
export class InjectionToken<
|
|
27
60
|
// oxlint-disable-next-line no-unused-vars
|
|
28
61
|
T,
|
|
@@ -38,13 +71,16 @@ export class InjectionToken<
|
|
|
38
71
|
? true
|
|
39
72
|
: false,
|
|
40
73
|
> {
|
|
41
|
-
public id
|
|
74
|
+
public readonly id: string
|
|
42
75
|
private formattedName: string | null = null
|
|
43
76
|
|
|
44
77
|
constructor(
|
|
45
78
|
public readonly name: string | symbol | ClassType,
|
|
46
79
|
public readonly schema: ZodObject | undefined,
|
|
47
|
-
|
|
80
|
+
customId?: string,
|
|
81
|
+
) {
|
|
82
|
+
this.id = generateTokenId(name, customId)
|
|
83
|
+
}
|
|
48
84
|
|
|
49
85
|
static create<T extends ClassType>(
|
|
50
86
|
name: T,
|
|
@@ -60,9 +96,9 @@ export class InjectionToken<
|
|
|
60
96
|
name: string | any,
|
|
61
97
|
schema: Schema,
|
|
62
98
|
): InjectionToken<T, Schema>
|
|
63
|
-
static create(name: string | symbol, schema?: unknown) {
|
|
99
|
+
static create(name: string | symbol, schema?: unknown, customId?: string) {
|
|
64
100
|
// @ts-expect-error
|
|
65
|
-
return new InjectionToken(name, schema)
|
|
101
|
+
return new InjectionToken(name, schema, customId)
|
|
66
102
|
}
|
|
67
103
|
|
|
68
104
|
static bound<T, S extends InjectionTokenSchemaType>(
|
|
@@ -161,3 +197,4 @@ export type InjectionTokenType =
|
|
|
161
197
|
| InjectionToken<any, any>
|
|
162
198
|
| BoundInjectionToken<any, any>
|
|
163
199
|
| FactoryInjectionToken<any, any>
|
|
200
|
+
|