@navios/di 0.4.1 → 0.5.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.
Files changed (128) hide show
  1. package/README.md +211 -1
  2. package/coverage/clover.xml +1912 -1277
  3. package/coverage/coverage-final.json +37 -28
  4. package/coverage/docs/examples/basic-usage.mts.html +1 -1
  5. package/coverage/docs/examples/factory-pattern.mts.html +1 -1
  6. package/coverage/docs/examples/index.html +1 -1
  7. package/coverage/docs/examples/injection-tokens.mts.html +1 -1
  8. package/coverage/docs/examples/request-scope-example.mts.html +1 -1
  9. package/coverage/docs/examples/service-lifecycle.mts.html +1 -1
  10. package/coverage/index.html +71 -41
  11. package/coverage/lib/_tsup-dts-rollup.d.mts.html +682 -43
  12. package/coverage/lib/index.d.mts.html +7 -4
  13. package/coverage/lib/index.html +5 -5
  14. package/coverage/lib/testing/index.d.mts.html +91 -0
  15. package/coverage/lib/testing/index.html +116 -0
  16. package/coverage/src/base-instance-holder-manager.mts.html +589 -0
  17. package/coverage/src/container.mts.html +257 -74
  18. package/coverage/src/decorators/factory.decorator.mts.html +1 -1
  19. package/coverage/src/decorators/index.html +1 -1
  20. package/coverage/src/decorators/index.mts.html +1 -1
  21. package/coverage/src/decorators/injectable.decorator.mts.html +20 -20
  22. package/coverage/src/enums/index.html +1 -1
  23. package/coverage/src/enums/index.mts.html +1 -1
  24. package/coverage/src/enums/injectable-scope.enum.mts.html +1 -1
  25. package/coverage/src/enums/injectable-type.enum.mts.html +1 -1
  26. package/coverage/src/errors/di-error.mts.html +292 -0
  27. package/coverage/src/errors/errors.enum.mts.html +30 -21
  28. package/coverage/src/errors/factory-not-found.mts.html +31 -22
  29. package/coverage/src/errors/factory-token-not-resolved.mts.html +29 -26
  30. package/coverage/src/errors/index.html +56 -41
  31. package/coverage/src/errors/index.mts.html +15 -9
  32. package/coverage/src/errors/instance-destroying.mts.html +31 -22
  33. package/coverage/src/errors/instance-expired.mts.html +31 -22
  34. package/coverage/src/errors/instance-not-found.mts.html +31 -22
  35. package/coverage/src/errors/unknown-error.mts.html +31 -43
  36. package/coverage/src/event-emitter.mts.html +14 -14
  37. package/coverage/src/factory-context.mts.html +1 -1
  38. package/coverage/src/index.html +121 -46
  39. package/coverage/src/index.mts.html +7 -4
  40. package/coverage/src/injection-token.mts.html +28 -28
  41. package/coverage/src/injector.mts.html +1 -1
  42. package/coverage/src/instance-resolver.mts.html +1762 -0
  43. package/coverage/src/interfaces/factory.interface.mts.html +1 -1
  44. package/coverage/src/interfaces/index.html +1 -1
  45. package/coverage/src/interfaces/index.mts.html +1 -1
  46. package/coverage/src/interfaces/on-service-destroy.interface.mts.html +1 -1
  47. package/coverage/src/interfaces/on-service-init.interface.mts.html +1 -1
  48. package/coverage/src/registry.mts.html +28 -28
  49. package/coverage/src/request-context-holder.mts.html +183 -102
  50. package/coverage/src/request-context-manager.mts.html +532 -0
  51. package/coverage/src/service-instantiator.mts.html +49 -49
  52. package/coverage/src/service-invalidator.mts.html +1372 -0
  53. package/coverage/src/service-locator-event-bus.mts.html +48 -48
  54. package/coverage/src/service-locator-instance-holder.mts.html +2 -14
  55. package/coverage/src/service-locator-manager.mts.html +71 -335
  56. package/coverage/src/service-locator.mts.html +240 -2328
  57. package/coverage/src/symbols/index.html +1 -1
  58. package/coverage/src/symbols/index.mts.html +1 -1
  59. package/coverage/src/symbols/injectable-token.mts.html +1 -1
  60. package/coverage/src/testing/index.html +131 -0
  61. package/coverage/src/testing/index.mts.html +88 -0
  62. package/coverage/src/testing/test-container.mts.html +445 -0
  63. package/coverage/src/token-processor.mts.html +607 -0
  64. package/coverage/src/utils/defer.mts.html +28 -214
  65. package/coverage/src/utils/get-injectable-token.mts.html +7 -7
  66. package/coverage/src/utils/get-injectors.mts.html +99 -99
  67. package/coverage/src/utils/index.html +15 -15
  68. package/coverage/src/utils/index.mts.html +4 -7
  69. package/coverage/src/utils/types.mts.html +1 -1
  70. package/docs/injectable.md +51 -11
  71. package/docs/scopes.md +63 -29
  72. package/lib/_tsup-dts-rollup.d.mts +447 -212
  73. package/lib/_tsup-dts-rollup.d.ts +447 -212
  74. package/lib/chunk-44F3LXW5.mjs +2043 -0
  75. package/lib/chunk-44F3LXW5.mjs.map +1 -0
  76. package/lib/index.d.mts +6 -4
  77. package/lib/index.d.ts +6 -4
  78. package/lib/index.js +1199 -773
  79. package/lib/index.js.map +1 -1
  80. package/lib/index.mjs +4 -1599
  81. package/lib/index.mjs.map +1 -1
  82. package/lib/testing/index.d.mts +2 -0
  83. package/lib/testing/index.d.ts +2 -0
  84. package/lib/testing/index.js +2060 -0
  85. package/lib/testing/index.js.map +1 -0
  86. package/lib/testing/index.mjs +73 -0
  87. package/lib/testing/index.mjs.map +1 -0
  88. package/package.json +11 -1
  89. package/src/__tests__/container.spec.mts +47 -13
  90. package/src/__tests__/errors.spec.mts +53 -27
  91. package/src/__tests__/injectable.spec.mts +73 -0
  92. package/src/__tests__/request-scope.spec.mts +0 -2
  93. package/src/__tests__/service-locator-manager.spec.mts +12 -82
  94. package/src/__tests__/service-locator.spec.mts +1009 -1
  95. package/src/__type-tests__/inject.spec-d.mts +30 -7
  96. package/src/__type-tests__/injectable.spec-d.mts +76 -37
  97. package/src/base-instance-holder-manager.mts +2 -9
  98. package/src/container.mts +70 -10
  99. package/src/decorators/injectable.decorator.mts +29 -5
  100. package/src/errors/di-error.mts +69 -0
  101. package/src/errors/index.mts +9 -7
  102. package/src/injection-token.mts +1 -0
  103. package/src/injector.mts +2 -0
  104. package/src/instance-resolver.mts +559 -0
  105. package/src/request-context-holder.mts +0 -2
  106. package/src/request-context-manager.mts +149 -0
  107. package/src/service-invalidator.mts +429 -0
  108. package/src/service-locator-instance-holder.mts +0 -4
  109. package/src/service-locator-manager.mts +10 -40
  110. package/src/service-locator.mts +86 -782
  111. package/src/testing/README.md +80 -0
  112. package/src/testing/__tests__/test-container.spec.mts +173 -0
  113. package/src/testing/index.mts +1 -0
  114. package/src/testing/test-container.mts +120 -0
  115. package/src/token-processor.mts +174 -0
  116. package/src/utils/get-injectors.mts +161 -24
  117. package/src/utils/index.mts +0 -1
  118. package/src/utils/types.mts +12 -8
  119. package/tsup.config.mts +1 -1
  120. package/src/__tests__/defer.spec.mts +0 -166
  121. package/src/errors/errors.enum.mts +0 -8
  122. package/src/errors/factory-not-found.mts +0 -8
  123. package/src/errors/factory-token-not-resolved.mts +0 -10
  124. package/src/errors/instance-destroying.mts +0 -8
  125. package/src/errors/instance-expired.mts +0 -8
  126. package/src/errors/instance-not-found.mts +0 -8
  127. package/src/errors/unknown-error.mts +0 -15
  128. package/src/utils/defer.mts +0 -73
@@ -0,0 +1,80 @@
1
+ # Testing Infrastructure
2
+
3
+ The `@navios/di/testing` package provides a `TestContainer` class that extends the base `Container` with additional methods useful for testing.
4
+
5
+ ## Features
6
+
7
+ - **TestContainer**: A specialized container for testing with simplified binding methods
8
+ - **bind().toValue()**: Bind tokens to specific values (useful for mocks)
9
+ - **bind().toClass()**: Bind tokens to class constructors
10
+ - **clear()**: Clear all instances and bindings from the container
11
+ - **Convenience methods**: `bindValue()`, `bindClass()`, `createChild()`
12
+
13
+ ## Usage
14
+
15
+ ```typescript
16
+ import { Injectable, InjectionToken } from '@navios/di'
17
+ import { TestContainer } from '@navios/di/testing'
18
+
19
+ // Create a test container
20
+ const container = new TestContainer()
21
+
22
+ // Create injection tokens
23
+ const API_URL_TOKEN = InjectionToken.create<string>('api-url')
24
+ const HTTP_CLIENT_TOKEN = InjectionToken.create<HttpClient>('http-client')
25
+
26
+ // Mock implementations
27
+ class MockHttpClient implements HttpClient {
28
+ async get(url: string) {
29
+ return { data: 'mocked response' }
30
+ }
31
+ }
32
+
33
+ // Bind values for testing
34
+ container.bindValue(API_URL_TOKEN, 'https://test-api.com')
35
+ container.bindClass(HTTP_CLIENT_TOKEN, MockHttpClient)
36
+
37
+ // Or use the fluent API
38
+ container.bind(API_URL_TOKEN).toValue('https://test-api.com')
39
+
40
+ container.bind(HTTP_CLIENT_TOKEN).toClass(MockHttpClient)
41
+
42
+ // Bind a class to itself
43
+ @Injectable()
44
+ class UserService {
45
+ constructor(
46
+ @Inject(API_URL_TOKEN) private apiUrl: string,
47
+ @Inject(HTTP_CLIENT_TOKEN) private httpClient: HttpClient,
48
+ ) {}
49
+ }
50
+
51
+ container.bindSelf(UserService)
52
+
53
+ // Clear the container between tests
54
+ container.clear()
55
+
56
+ // Create isolated child containers
57
+ const childContainer = container.createChild()
58
+ ```
59
+
60
+ ## API Reference
61
+
62
+ ### TestContainer
63
+
64
+ #### Methods
65
+
66
+ - `bind<T>(token: InjectionToken<T, any>): TestBindingBuilder<T>` - Creates a binding builder
67
+ - `bind<T>(token: ClassType): TestBindingBuilder<T>` - Creates a binding builder
68
+ - `bindValue<T>(token: InjectionToken<T, any>, value: T): TestContainer` - Binds a value to a token
69
+ - `bindValue<T>(token: ClassType, value: T): TestContainer` - Binds a value to a token
70
+ - `bindClass<T>(token: InjectionToken<T, any>, target: ClassType): TestContainer` - Binds a class to a token
71
+ - `bindClass<T>(token: ClassType, target: ClassType): TestContainer` - Binds a class to a token
72
+ - `createChild(): TestContainer` - Creates a new isolated test container
73
+ - `clear(): void` - Clears all instances and bindings
74
+
75
+ ### TestBindingBuilder
76
+
77
+ #### Methods
78
+
79
+ - `toValue(value: T): TestContainer` - Binds the token to a specific value
80
+ - `toClass(target: ClassType): TestContainer` - Binds the token to a class constructor
@@ -0,0 +1,173 @@
1
+ import { beforeEach, describe, expect, it } from 'vitest'
2
+
3
+ import { Injectable } from '../../decorators/injectable.decorator.mjs'
4
+ import { InjectionToken } from '../../injection-token.mjs'
5
+ import { inject } from '../../injector.mjs'
6
+ import { TestContainer } from '../test-container.mjs'
7
+
8
+ describe('TestContainer', () => {
9
+ let container: TestContainer
10
+
11
+ beforeEach(() => {
12
+ container = new TestContainer()
13
+ })
14
+
15
+ describe('clear method', () => {
16
+ it('should clear all instances from the container', () => {
17
+ // This test verifies that the clear method exists and can be called
18
+ expect(() => container.clear()).not.toThrow()
19
+ })
20
+ })
21
+
22
+ describe('bind method', () => {
23
+ it('should create a TestBindingBuilder', () => {
24
+ const token = InjectionToken.create<string>('test-token')
25
+ const builder = container.bind(token)
26
+ expect(builder).toBeDefined()
27
+ expect(builder).toHaveProperty('toValue')
28
+ expect(builder).toHaveProperty('toClass')
29
+ })
30
+
31
+ it('should allow chaining bind operations', () => {
32
+ const token = InjectionToken.create<string>('test-token')
33
+ const result = container.bind(token).toValue('test-value')
34
+ expect(result).toBe(container)
35
+ })
36
+
37
+ it('should bind value and retrieve it correctly', async () => {
38
+ const token = InjectionToken.create<string>('test-token')
39
+ const testValue = 'test-value'
40
+
41
+ container.bind(token).toValue(testValue)
42
+
43
+ const retrievedValue = await container.get(token)
44
+ expect(retrievedValue).toBe(testValue)
45
+ })
46
+
47
+ it('should bind class and retrieve instance correctly', async () => {
48
+ @Injectable()
49
+ class TestService {
50
+ getValue() {
51
+ return 'test-service-value'
52
+ }
53
+ }
54
+
55
+ const token = InjectionToken.create<TestService>('test-service')
56
+
57
+ container.bind(token).toClass(TestService)
58
+
59
+ const instance = await container.get(token)
60
+ expect(instance).toBeInstanceOf(TestService)
61
+ expect(instance.getValue()).toBe('test-service-value')
62
+ })
63
+ })
64
+
65
+ describe('bindValue method', () => {
66
+ it('should bind a value to a token', () => {
67
+ const token = InjectionToken.create<string>('test-token')
68
+ const result = container.bindValue(token, 'test-value')
69
+ expect(result).toBe(container)
70
+ })
71
+
72
+ it('should bind value and retrieve it correctly', async () => {
73
+ const token = InjectionToken.create<number>('number-token')
74
+ const testValue = 42
75
+
76
+ container.bindValue(token, testValue)
77
+
78
+ const retrievedValue = await container.get(token)
79
+ expect(retrievedValue).toBe(testValue)
80
+ })
81
+
82
+ it('should bind object value and retrieve it correctly', async () => {
83
+ interface TestConfig {
84
+ apiUrl: string
85
+ timeout: number
86
+ }
87
+
88
+ const token = InjectionToken.create<TestConfig>('config-token')
89
+ const testConfig: TestConfig = {
90
+ apiUrl: 'https://api.example.com',
91
+ timeout: 5000,
92
+ }
93
+
94
+ container.bindValue(token, testConfig)
95
+
96
+ const retrievedConfig = await container.get(token)
97
+ expect(retrievedConfig).toEqual(testConfig)
98
+ expect(retrievedConfig.apiUrl).toBe('https://api.example.com')
99
+ expect(retrievedConfig.timeout).toBe(5000)
100
+ })
101
+ })
102
+
103
+ describe('bindClass method', () => {
104
+ it('should bind a class to a token', () => {
105
+ class TestClass {}
106
+ const token = InjectionToken.create<TestClass>('test-token')
107
+ const result = container.bindClass(token, TestClass)
108
+ expect(result).toBe(container)
109
+ })
110
+
111
+ it('should bind class and retrieve instance correctly', async () => {
112
+ @Injectable()
113
+ class UserService {
114
+ private users: string[] = ['alice', 'bob']
115
+
116
+ getUsers() {
117
+ return this.users
118
+ }
119
+
120
+ addUser(user: string) {
121
+ this.users.push(user)
122
+ }
123
+ }
124
+
125
+ const token = InjectionToken.create<UserService>('user-service')
126
+
127
+ container.bindClass(token, UserService)
128
+
129
+ const instance = await container.get(token)
130
+ expect(instance).toBeInstanceOf(UserService)
131
+ expect(instance.getUsers()).toEqual(['alice', 'bob'])
132
+
133
+ instance.addUser('charlie')
134
+ expect(instance.getUsers()).toEqual(['alice', 'bob', 'charlie'])
135
+ })
136
+
137
+ it('should bind class with dependencies and retrieve instance correctly', async () => {
138
+ @Injectable()
139
+ class DatabaseService {
140
+ connect() {
141
+ return 'connected'
142
+ }
143
+ }
144
+
145
+ @Injectable()
146
+ class UserRepository {
147
+ db = inject(DatabaseService)
148
+
149
+ findUser(id: string) {
150
+ return `User ${id} from ${this.db.connect()}`
151
+ }
152
+ }
153
+
154
+ const dbToken = InjectionToken.create<DatabaseService>('db-service')
155
+ const userRepoToken = InjectionToken.create<UserRepository>('user-repo')
156
+
157
+ container.bindClass(dbToken, DatabaseService)
158
+ container.bindClass(userRepoToken, UserRepository)
159
+
160
+ const userRepo = await container.get(userRepoToken)
161
+ expect(userRepo).toBeInstanceOf(UserRepository)
162
+ expect(userRepo.findUser('123')).toBe('User 123 from connected')
163
+ })
164
+ })
165
+
166
+ describe('createChild method', () => {
167
+ it('should create a new TestContainer instance', () => {
168
+ const child = container.createChild()
169
+ expect(child).toBeInstanceOf(TestContainer)
170
+ expect(child).not.toBe(container)
171
+ })
172
+ })
173
+ })
@@ -0,0 +1 @@
1
+ export * from './test-container.mjs'
@@ -0,0 +1,120 @@
1
+ import type { ClassType, InjectionToken } from '../injection-token.mjs'
2
+ import type { Registry } from '../registry.mjs'
3
+ import type { Injectors } from '../utils/index.mjs'
4
+
5
+ import { Container } from '../container.mjs'
6
+ import { Injectable } from '../decorators/injectable.decorator.mjs'
7
+ import { InjectableScope, InjectableType } from '../enums/index.mjs'
8
+ import { globalRegistry } from '../registry.mjs'
9
+ import { getInjectableToken } from '../utils/index.mjs'
10
+
11
+ /**
12
+ * A binding builder for the TestContainer that allows chaining binding operations.
13
+ */
14
+ export class TestBindingBuilder<T> {
15
+ constructor(
16
+ private readonly container: TestContainer,
17
+ private readonly token: InjectionToken<T, any>,
18
+ ) {}
19
+
20
+ /**
21
+ * Binds the token to a specific value.
22
+ * This is useful for testing with mock values or constants.
23
+ * @param value The value to bind to the token
24
+ */
25
+ toValue(value: T): TestContainer {
26
+ const instanceName = this.container
27
+ .getServiceLocator()
28
+ .getInstanceIdentifier(this.token)
29
+ this.container
30
+ .getServiceLocator()
31
+ .getManager()
32
+ .storeCreatedHolder(
33
+ instanceName,
34
+ value,
35
+ InjectableType.Class,
36
+ InjectableScope.Singleton,
37
+ )
38
+ return this.container
39
+ }
40
+
41
+ /**
42
+ * Binds the token to a class constructor.
43
+ * @param target The class constructor to bind to
44
+ */
45
+ toClass(target: ClassType): TestContainer {
46
+ this.container['registry'].set(
47
+ this.token,
48
+ InjectableScope.Singleton,
49
+ target,
50
+ InjectableType.Class,
51
+ )
52
+ return this.container
53
+ }
54
+ }
55
+
56
+ /**
57
+ * TestContainer extends the base Container with additional methods useful for testing.
58
+ * It provides a simplified API for binding values and classes during test setup.
59
+ */
60
+ @Injectable()
61
+ export class TestContainer extends Container {
62
+ constructor(
63
+ registry: Registry = globalRegistry,
64
+ logger: Console | null = null,
65
+ injectors: Injectors = undefined as any,
66
+ ) {
67
+ super(registry, logger, injectors)
68
+ }
69
+
70
+ /**
71
+ * Creates a binding builder for the given token.
72
+ * This allows chaining binding operations like bind(Token).toValue(value).
73
+ * @param token The injection token to bind
74
+ * @returns A TestBindingBuilder for chaining binding operations
75
+ */
76
+ bind<T>(token: ClassType): TestBindingBuilder<T>
77
+ bind<T>(token: InjectionToken<T, any>): TestBindingBuilder<T>
78
+ bind(token: any): TestBindingBuilder<any> {
79
+ let realToken = token
80
+ if (typeof token === 'function') {
81
+ realToken = getInjectableToken(token)
82
+ }
83
+ return new TestBindingBuilder(this, realToken)
84
+ }
85
+
86
+ /**
87
+ * Binds a value directly to a token.
88
+ * This is a convenience method equivalent to bind(token).toValue(value).
89
+ * @param token The injection token to bind
90
+ * @param value The value to bind to the token
91
+ * @returns The TestContainer instance for chaining
92
+ */
93
+ bindValue<T>(token: ClassType, value: T): TestContainer
94
+ bindValue<T>(token: InjectionToken<T, any>, value: T): TestContainer
95
+ bindValue(token: any, value: any): TestContainer {
96
+ return this.bind(token).toValue(value)
97
+ }
98
+
99
+ /**
100
+ * Binds a class to a token.
101
+ * This is a convenience method equivalent to bind(token).toClass(target).
102
+ * @param token The injection token to bind
103
+ * @param target The class constructor to bind to
104
+ * @returns The TestContainer instance for chaining
105
+ */
106
+ bindClass(token: ClassType, target: ClassType): TestContainer
107
+ bindClass<T>(token: InjectionToken<T, any>, target: ClassType): TestContainer
108
+ bindClass(token: any, target: any): TestContainer {
109
+ return this.bind(token).toClass(target)
110
+ }
111
+
112
+ /**
113
+ * Creates a new TestContainer instance with the same configuration.
114
+ * This is useful for creating isolated test containers.
115
+ * @returns A new TestContainer instance
116
+ */
117
+ createChild(): TestContainer {
118
+ return new TestContainer(this.registry, this.logger, this.injectors)
119
+ }
120
+ }
@@ -0,0 +1,174 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /* eslint-disable @typescript-eslint/no-empty-object-type */
3
+
4
+ import type { FactoryContext } from './factory-context.mjs'
5
+ import type {
6
+ AnyInjectableType,
7
+ InjectionTokenType,
8
+ } from './injection-token.mjs'
9
+ import type { RequestContextHolder } from './request-context-holder.mjs'
10
+ import type { ServiceLocator } from './service-locator.mjs'
11
+
12
+ import { DIError } from './errors/index.mjs'
13
+ import {
14
+ BoundInjectionToken,
15
+ FactoryInjectionToken,
16
+ InjectionToken,
17
+ } from './injection-token.mjs'
18
+ import { getInjectableToken } from './utils/index.mjs'
19
+
20
+ /**
21
+ * TokenProcessor handles token validation, resolution, and instance name generation.
22
+ * Extracted from ServiceLocator to improve separation of concerns.
23
+ */
24
+ export class TokenProcessor {
25
+ constructor(private readonly logger: Console | null = null) {}
26
+
27
+ /**
28
+ * Validates and resolves token arguments, handling factory token resolution and validation.
29
+ */
30
+ validateAndResolveTokenArgs(
31
+ token: AnyInjectableType,
32
+ args?: any,
33
+ ): [
34
+ DIError | undefined,
35
+ { actualToken: InjectionTokenType; validatedArgs?: any },
36
+ ] {
37
+ let actualToken = token as InjectionToken<any, any>
38
+ if (typeof token === 'function') {
39
+ actualToken = getInjectableToken(token)
40
+ }
41
+ let realArgs = args
42
+ if (actualToken instanceof BoundInjectionToken) {
43
+ realArgs = actualToken.value
44
+ } else if (actualToken instanceof FactoryInjectionToken) {
45
+ if (actualToken.resolved) {
46
+ realArgs = actualToken.value
47
+ } else {
48
+ return [DIError.factoryTokenNotResolved(token.name), { actualToken }]
49
+ }
50
+ }
51
+ if (!actualToken.schema) {
52
+ return [undefined, { actualToken, validatedArgs: realArgs }]
53
+ }
54
+ const validatedArgs = actualToken.schema?.safeParse(realArgs)
55
+ if (validatedArgs && !validatedArgs.success) {
56
+ this.logger?.error(
57
+ `[TokenProcessor]#validateAndResolveTokenArgs(): Error validating args for ${actualToken.name.toString()}`,
58
+ validatedArgs.error,
59
+ )
60
+ return [DIError.unknown(validatedArgs.error), { actualToken }]
61
+ }
62
+ return [undefined, { actualToken, validatedArgs: validatedArgs?.data }]
63
+ }
64
+
65
+ /**
66
+ * Generates a unique instance name based on token and arguments.
67
+ */
68
+ generateInstanceName(token: InjectionTokenType, args: any): string {
69
+ if (!args) {
70
+ return token.toString()
71
+ }
72
+
73
+ const formattedArgs = Object.entries(args)
74
+ .sort(([keyA], [keyB]) => keyA.localeCompare(keyB))
75
+ .map(([key, value]) => `${key}=${this.formatArgValue(value)}`)
76
+ .join(',')
77
+
78
+ return `${token.toString()}:${formattedArgs.replaceAll(/"/g, '').replaceAll(/:/g, '=')}`
79
+ }
80
+
81
+ /**
82
+ * Formats a single argument value for instance name generation.
83
+ */
84
+ formatArgValue(value: any): string {
85
+ if (typeof value === 'function') {
86
+ return `fn_${value.name}(${value.length})`
87
+ }
88
+ if (typeof value === 'symbol') {
89
+ return value.toString()
90
+ }
91
+ return JSON.stringify(value).slice(0, 40)
92
+ }
93
+
94
+ /**
95
+ * Creates a factory context for dependency injection during service instantiation.
96
+ * @param serviceLocator Reference to the service locator for dependency resolution
97
+ */
98
+ createFactoryContext(
99
+ serviceLocator: ServiceLocator, // ServiceLocator reference for dependency resolution
100
+ ): FactoryContext & {
101
+ getDestroyListeners: () => (() => void)[]
102
+ deps: Set<string>
103
+ } {
104
+ const destroyListeners = new Set<() => void>()
105
+ const deps = new Set<string>()
106
+
107
+ function addDestroyListener(listener: () => void) {
108
+ destroyListeners.add(listener)
109
+ }
110
+
111
+ function getDestroyListeners() {
112
+ return Array.from(destroyListeners)
113
+ }
114
+
115
+ return {
116
+ // @ts-expect-error This is correct type
117
+ async inject(token, args) {
118
+ // Fall back to normal resolution
119
+ const [error, instance] = await serviceLocator.getInstance(
120
+ token,
121
+ args,
122
+ ({ instanceName }: { instanceName: string }) => {
123
+ deps.add(instanceName)
124
+ },
125
+ )
126
+ if (error) {
127
+ throw error
128
+ }
129
+ return instance
130
+ },
131
+ addDestroyListener,
132
+ getDestroyListeners,
133
+ locator: serviceLocator,
134
+ deps,
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Tries to get a pre-prepared instance from request contexts.
140
+ */
141
+ tryGetPrePreparedInstance(
142
+ instanceName: string,
143
+ contextHolder: RequestContextHolder | undefined,
144
+ deps: Set<string>,
145
+ currentRequestContext: RequestContextHolder | null,
146
+ ): any {
147
+ // Check provided context holder first (if has higher priority)
148
+ if (contextHolder && contextHolder.priority > 0) {
149
+ const prePreparedInstance = contextHolder.get(instanceName)?.instance
150
+ if (prePreparedInstance !== undefined) {
151
+ this.logger?.debug(
152
+ `[TokenProcessor] Using pre-prepared instance ${instanceName} from request context ${contextHolder.requestId}`,
153
+ )
154
+ deps.add(instanceName)
155
+ return prePreparedInstance
156
+ }
157
+ }
158
+
159
+ // Check current request context (if different from provided contextHolder)
160
+ if (currentRequestContext && currentRequestContext !== contextHolder) {
161
+ const prePreparedInstance =
162
+ currentRequestContext.get(instanceName)?.instance
163
+ if (prePreparedInstance !== undefined) {
164
+ this.logger?.debug(
165
+ `[TokenProcessor] Using pre-prepared instance ${instanceName} from current request context ${currentRequestContext.requestId}`,
166
+ )
167
+ deps.add(instanceName)
168
+ return prePreparedInstance
169
+ }
170
+ }
171
+
172
+ return undefined
173
+ }
174
+ }