@navios/di 0.3.0 → 0.4.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/README.md +67 -6
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/clover.xml +2659 -0
- package/coverage/coverage-final.json +46 -0
- package/coverage/docs/examples/basic-usage.mts.html +376 -0
- package/coverage/docs/examples/factory-pattern.mts.html +1039 -0
- package/coverage/docs/examples/index.html +176 -0
- package/coverage/docs/examples/injection-tokens.mts.html +760 -0
- package/coverage/docs/examples/request-scope-example.mts.html +847 -0
- package/coverage/docs/examples/service-lifecycle.mts.html +1162 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +236 -0
- package/coverage/lib/_tsup-dts-rollup.d.mts.html +2806 -0
- package/coverage/lib/index.d.mts.html +310 -0
- package/coverage/lib/index.html +131 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +196 -0
- package/coverage/src/container.mts.html +586 -0
- package/coverage/src/decorators/factory.decorator.mts.html +322 -0
- package/coverage/src/decorators/index.html +146 -0
- package/coverage/src/decorators/index.mts.html +91 -0
- package/coverage/src/decorators/injectable.decorator.mts.html +394 -0
- package/coverage/src/enums/index.html +146 -0
- package/coverage/src/enums/index.mts.html +91 -0
- package/coverage/src/enums/injectable-scope.enum.mts.html +127 -0
- package/coverage/src/enums/injectable-type.enum.mts.html +97 -0
- package/coverage/src/errors/errors.enum.mts.html +109 -0
- package/coverage/src/errors/factory-not-found.mts.html +109 -0
- package/coverage/src/errors/factory-token-not-resolved.mts.html +115 -0
- package/coverage/src/errors/index.html +221 -0
- package/coverage/src/errors/index.mts.html +106 -0
- package/coverage/src/errors/instance-destroying.mts.html +109 -0
- package/coverage/src/errors/instance-expired.mts.html +109 -0
- package/coverage/src/errors/instance-not-found.mts.html +109 -0
- package/coverage/src/errors/unknown-error.mts.html +130 -0
- package/coverage/src/event-emitter.mts.html +400 -0
- package/coverage/src/factory-context.mts.html +109 -0
- package/coverage/src/index.html +296 -0
- package/coverage/src/index.mts.html +139 -0
- package/coverage/src/injection-token.mts.html +571 -0
- package/coverage/src/injector.mts.html +133 -0
- package/coverage/src/interfaces/factory.interface.mts.html +121 -0
- package/coverage/src/interfaces/index.html +161 -0
- package/coverage/src/interfaces/index.mts.html +94 -0
- package/coverage/src/interfaces/on-service-destroy.interface.mts.html +94 -0
- package/coverage/src/interfaces/on-service-init.interface.mts.html +94 -0
- package/coverage/src/registry.mts.html +247 -0
- package/coverage/src/request-context-holder.mts.html +607 -0
- package/coverage/src/service-instantiator.mts.html +559 -0
- package/coverage/src/service-locator-event-bus.mts.html +289 -0
- package/coverage/src/service-locator-instance-holder.mts.html +307 -0
- package/coverage/src/service-locator-manager.mts.html +604 -0
- package/coverage/src/service-locator.mts.html +2911 -0
- package/coverage/src/symbols/index.html +131 -0
- package/coverage/src/symbols/index.mts.html +88 -0
- package/coverage/src/symbols/injectable-token.mts.html +88 -0
- package/coverage/src/utils/defer.mts.html +304 -0
- package/coverage/src/utils/get-injectable-token.mts.html +142 -0
- package/coverage/src/utils/get-injectors.mts.html +691 -0
- package/coverage/src/utils/index.html +176 -0
- package/coverage/src/utils/index.mts.html +97 -0
- package/coverage/src/utils/types.mts.html +241 -0
- package/docs/README.md +5 -2
- package/docs/api-reference.md +38 -0
- package/docs/container.md +75 -0
- package/docs/getting-started.md +4 -3
- package/docs/injectable.md +4 -3
- package/docs/migration.md +177 -0
- package/docs/request-contexts.md +364 -0
- package/lib/_tsup-dts-rollup.d.mts +180 -35
- package/lib/_tsup-dts-rollup.d.ts +180 -35
- package/lib/index.d.mts +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +485 -279
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +485 -280
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/defer.spec.mts +166 -0
- package/src/__tests__/errors.spec.mts +61 -0
- package/src/__tests__/event-emitter.spec.mts +163 -0
- package/src/__tests__/get-injectors.spec.mts +70 -0
- package/src/__tests__/registry.spec.mts +335 -0
- package/src/__tests__/request-scope.spec.mts +167 -4
- package/src/__tests__/service-instantiator.spec.mts +408 -0
- package/src/__tests__/service-locator-event-bus.spec.mts +242 -0
- package/src/__tests__/service-locator-manager.spec.mts +370 -0
- package/src/__tests__/unified-api.spec.mts +130 -0
- package/src/base-instance-holder-manager.mts +175 -0
- package/src/index.mts +1 -0
- package/src/request-context-holder.mts +85 -27
- package/src/service-locator-manager.mts +12 -70
- package/src/service-locator.mts +421 -226
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { InjectableScope, InjectableType } from '../enums/index.mjs'
|
|
4
|
+
import {
|
|
5
|
+
InstanceDestroying,
|
|
6
|
+
InstanceExpired,
|
|
7
|
+
InstanceNotFound,
|
|
8
|
+
} from '../errors/index.mjs'
|
|
9
|
+
import { ServiceLocatorInstanceHolderStatus } from '../service-locator-instance-holder.mjs'
|
|
10
|
+
import { ServiceLocatorManager } from '../service-locator-manager.mjs'
|
|
11
|
+
|
|
12
|
+
describe('ServiceLocatorManager', () => {
|
|
13
|
+
let manager: ServiceLocatorManager
|
|
14
|
+
let mockLogger: Console
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
mockLogger = {
|
|
18
|
+
log: vi.fn(),
|
|
19
|
+
} as any as Console
|
|
20
|
+
manager = new ServiceLocatorManager(mockLogger)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
describe('constructor', () => {
|
|
24
|
+
it('should create manager without logger', () => {
|
|
25
|
+
const managerWithoutLogger = new ServiceLocatorManager()
|
|
26
|
+
expect(managerWithoutLogger).toBeDefined()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('should create manager with logger', () => {
|
|
30
|
+
expect(manager).toBeDefined()
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
describe('get', () => {
|
|
35
|
+
it('should return InstanceNotFound for non-existent instance', () => {
|
|
36
|
+
const result = manager.get('non-existent')
|
|
37
|
+
|
|
38
|
+
expect(result).toHaveLength(1)
|
|
39
|
+
expect(result[0]).toBeInstanceOf(InstanceNotFound)
|
|
40
|
+
expect(mockLogger.log).toHaveBeenCalledWith(
|
|
41
|
+
'[ServiceLocatorManager]#getInstanceHolder() Instance non-existent not found',
|
|
42
|
+
)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('should return valid holder for existing instance', () => {
|
|
46
|
+
const holder = manager.storeCreatedHolder(
|
|
47
|
+
'test-instance',
|
|
48
|
+
{ value: 'test' },
|
|
49
|
+
InjectableType.Class,
|
|
50
|
+
InjectableScope.Singleton,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
const result = manager.get('test-instance')
|
|
54
|
+
|
|
55
|
+
expect(result).toHaveLength(2)
|
|
56
|
+
expect(result[0]).toBeUndefined()
|
|
57
|
+
expect(result[1]).toBe(holder)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('should return InstanceExpired for expired instance with TTL', () => {
|
|
61
|
+
const holder = manager.storeCreatedHolder(
|
|
62
|
+
'expired-instance',
|
|
63
|
+
{ value: 'test' },
|
|
64
|
+
InjectableType.Class,
|
|
65
|
+
InjectableScope.Singleton,
|
|
66
|
+
new Set(),
|
|
67
|
+
100, // 100ms TTL
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
// Manually set creation time to past
|
|
71
|
+
holder.createdAt = Date.now() - 200
|
|
72
|
+
|
|
73
|
+
const result = manager.get('expired-instance')
|
|
74
|
+
|
|
75
|
+
expect(result).toHaveLength(2)
|
|
76
|
+
expect(result[0]).toBeInstanceOf(InstanceExpired)
|
|
77
|
+
expect(result[1]).toBe(holder)
|
|
78
|
+
expect(mockLogger.log).toHaveBeenCalledWith(
|
|
79
|
+
'[ServiceLocatorManager]#getInstanceHolder() TTL expired for expired-instance',
|
|
80
|
+
)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('should return InstanceDestroying for destroying instance', () => {
|
|
84
|
+
const holder = manager.storeCreatedHolder(
|
|
85
|
+
'destroying-instance',
|
|
86
|
+
{ value: 'test' },
|
|
87
|
+
InjectableType.Class,
|
|
88
|
+
InjectableScope.Singleton,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
// Manually set status to destroying
|
|
92
|
+
holder.status = ServiceLocatorInstanceHolderStatus.Destroying
|
|
93
|
+
|
|
94
|
+
const result = manager.get('destroying-instance')
|
|
95
|
+
|
|
96
|
+
expect(result).toHaveLength(2)
|
|
97
|
+
expect(result[0]).toBeInstanceOf(InstanceDestroying)
|
|
98
|
+
expect(result[1]).toBe(holder)
|
|
99
|
+
expect(mockLogger.log).toHaveBeenCalledWith(
|
|
100
|
+
'[ServiceLocatorManager]#getInstanceHolder() Instance destroying-instance is destroying',
|
|
101
|
+
)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('should return error for instance in error state', () => {
|
|
105
|
+
const holder = manager.storeCreatedHolder(
|
|
106
|
+
'error-instance',
|
|
107
|
+
{ value: 'test' },
|
|
108
|
+
InjectableType.Class,
|
|
109
|
+
InjectableScope.Singleton,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
// Manually set status to error with an error instance
|
|
113
|
+
holder.status = ServiceLocatorInstanceHolderStatus.Error
|
|
114
|
+
const errorInstance = new InstanceNotFound('error-instance')
|
|
115
|
+
holder.instance = errorInstance
|
|
116
|
+
|
|
117
|
+
const result = manager.get('error-instance')
|
|
118
|
+
|
|
119
|
+
expect(result).toHaveLength(2)
|
|
120
|
+
expect(result[0]).toBeInstanceOf(InstanceNotFound)
|
|
121
|
+
expect(result[1]).toBe(holder)
|
|
122
|
+
expect(mockLogger.log).toHaveBeenCalledWith(
|
|
123
|
+
'[ServiceLocatorManager]#getInstanceHolder() Instance error-instance is in error state',
|
|
124
|
+
)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('should handle instance with infinite TTL correctly', () => {
|
|
128
|
+
const holder = manager.storeCreatedHolder(
|
|
129
|
+
'infinite-ttl-instance',
|
|
130
|
+
{ value: 'test' },
|
|
131
|
+
InjectableType.Class,
|
|
132
|
+
InjectableScope.Singleton,
|
|
133
|
+
new Set(),
|
|
134
|
+
Infinity,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
const result = manager.get('infinite-ttl-instance')
|
|
138
|
+
|
|
139
|
+
expect(result).toHaveLength(2)
|
|
140
|
+
expect(result[0]).toBeUndefined()
|
|
141
|
+
expect(result[1]).toBe(holder)
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
describe('set', () => {
|
|
146
|
+
it('should store holder with given name', () => {
|
|
147
|
+
const holder = manager.storeCreatedHolder(
|
|
148
|
+
'test-instance',
|
|
149
|
+
{ value: 'test' },
|
|
150
|
+
InjectableType.Class,
|
|
151
|
+
InjectableScope.Singleton,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
manager.set('new-name', holder)
|
|
155
|
+
|
|
156
|
+
const result = manager.get('new-name')
|
|
157
|
+
expect(result).toHaveLength(2)
|
|
158
|
+
expect(result[0]).toBeUndefined()
|
|
159
|
+
expect(result[1]).toBe(holder)
|
|
160
|
+
})
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
describe('has', () => {
|
|
164
|
+
it('should return false for non-existent instance', () => {
|
|
165
|
+
const result = manager.has('non-existent')
|
|
166
|
+
|
|
167
|
+
expect(result).toHaveLength(2)
|
|
168
|
+
expect(result[0]).toBeUndefined()
|
|
169
|
+
expect(result[1]).toBe(false)
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('should return true for existing instance', () => {
|
|
173
|
+
manager.storeCreatedHolder(
|
|
174
|
+
'test-instance',
|
|
175
|
+
{ value: 'test' },
|
|
176
|
+
InjectableType.Class,
|
|
177
|
+
InjectableScope.Singleton,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
const result = manager.has('test-instance')
|
|
181
|
+
|
|
182
|
+
expect(result).toHaveLength(2)
|
|
183
|
+
expect(result[0]).toBeUndefined()
|
|
184
|
+
expect(result[1]).toBe(true)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
it('should return error for expired instance', () => {
|
|
188
|
+
const holder = manager.storeCreatedHolder(
|
|
189
|
+
'expired-instance',
|
|
190
|
+
{ value: 'test' },
|
|
191
|
+
InjectableType.Class,
|
|
192
|
+
InjectableScope.Singleton,
|
|
193
|
+
new Set(),
|
|
194
|
+
100,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
holder.createdAt = Date.now() - 200
|
|
198
|
+
|
|
199
|
+
const result = manager.has('expired-instance')
|
|
200
|
+
|
|
201
|
+
expect(result).toHaveLength(1)
|
|
202
|
+
expect(result[0]).toBeInstanceOf(InstanceExpired)
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
it('should return error for destroying instance', () => {
|
|
206
|
+
const holder = manager.storeCreatedHolder(
|
|
207
|
+
'destroying-instance',
|
|
208
|
+
{ value: 'test' },
|
|
209
|
+
InjectableType.Class,
|
|
210
|
+
InjectableScope.Singleton,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
holder.status = ServiceLocatorInstanceHolderStatus.Destroying
|
|
214
|
+
|
|
215
|
+
const result = manager.has('destroying-instance')
|
|
216
|
+
|
|
217
|
+
expect(result).toHaveLength(1)
|
|
218
|
+
expect(result[0]).toBeInstanceOf(InstanceDestroying)
|
|
219
|
+
})
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
describe('delete', () => {
|
|
223
|
+
it('should delete existing instance and return true', () => {
|
|
224
|
+
manager.storeCreatedHolder(
|
|
225
|
+
'test-instance',
|
|
226
|
+
{ value: 'test' },
|
|
227
|
+
InjectableType.Class,
|
|
228
|
+
InjectableScope.Singleton,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
const result = manager.delete('test-instance')
|
|
232
|
+
expect(result).toBe(true)
|
|
233
|
+
|
|
234
|
+
const getResult = manager.get('test-instance')
|
|
235
|
+
expect(getResult).toHaveLength(1)
|
|
236
|
+
expect(getResult[0]).toBeInstanceOf(InstanceNotFound)
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it('should return false for non-existent instance', () => {
|
|
240
|
+
const result = manager.delete('non-existent')
|
|
241
|
+
expect(result).toBe(false)
|
|
242
|
+
})
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
describe('filter', () => {
|
|
246
|
+
it('should filter instances by predicate', () => {
|
|
247
|
+
manager.storeCreatedHolder(
|
|
248
|
+
'instance1',
|
|
249
|
+
{ value: 'test1' },
|
|
250
|
+
InjectableType.Class,
|
|
251
|
+
InjectableScope.Singleton,
|
|
252
|
+
)
|
|
253
|
+
manager.storeCreatedHolder(
|
|
254
|
+
'instance2',
|
|
255
|
+
{ value: 'test2' },
|
|
256
|
+
InjectableType.Factory,
|
|
257
|
+
InjectableScope.Transient,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
const result = manager.filter(
|
|
261
|
+
(holder) => holder.type === InjectableType.Class,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
expect(result.size).toBe(1)
|
|
265
|
+
expect(result.has('instance1')).toBe(true)
|
|
266
|
+
expect(result.has('instance2')).toBe(false)
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
it('should return empty map when no instances match', () => {
|
|
270
|
+
manager.storeCreatedHolder(
|
|
271
|
+
'instance1',
|
|
272
|
+
{ value: 'test1' },
|
|
273
|
+
InjectableType.Class,
|
|
274
|
+
InjectableScope.Singleton,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
const result = manager.filter(
|
|
278
|
+
(holder) => holder.type === InjectableType.Factory,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
expect(result.size).toBe(0)
|
|
282
|
+
})
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
describe('createCreatingHolder', () => {
|
|
286
|
+
it('should create holder with Creating status and deferred promise', () => {
|
|
287
|
+
const [deferred, holder] = manager.createCreatingHolder(
|
|
288
|
+
'test-instance',
|
|
289
|
+
InjectableType.Class,
|
|
290
|
+
InjectableScope.Singleton,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
expect(deferred).toBeDefined()
|
|
294
|
+
expect(deferred.promise).toBeInstanceOf(Promise)
|
|
295
|
+
expect(holder.status).toBe(ServiceLocatorInstanceHolderStatus.Creating)
|
|
296
|
+
expect(holder.name).toBe('test-instance')
|
|
297
|
+
expect(holder.instance).toBeNull()
|
|
298
|
+
expect(holder.creationPromise).toBe(deferred.promise)
|
|
299
|
+
expect(holder.type).toBe(InjectableType.Class)
|
|
300
|
+
expect(holder.scope).toBe(InjectableScope.Singleton)
|
|
301
|
+
expect(holder.deps).toEqual(new Set())
|
|
302
|
+
expect(holder.ttl).toBe(Infinity)
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
it('should create holder with custom dependencies and TTL', () => {
|
|
306
|
+
const deps = new Set(['dep1', 'dep2'])
|
|
307
|
+
const ttl = 5000
|
|
308
|
+
|
|
309
|
+
const [deferred, holder] = manager.createCreatingHolder(
|
|
310
|
+
'test-instance',
|
|
311
|
+
InjectableType.Factory,
|
|
312
|
+
InjectableScope.Request,
|
|
313
|
+
deps,
|
|
314
|
+
ttl,
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
expect(holder.deps).toBe(deps)
|
|
318
|
+
expect(holder.ttl).toBe(ttl)
|
|
319
|
+
expect(holder.type).toBe(InjectableType.Factory)
|
|
320
|
+
expect(holder.scope).toBe(InjectableScope.Request)
|
|
321
|
+
})
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
describe('storeCreatedHolder', () => {
|
|
325
|
+
it('should create and store holder with Created status', () => {
|
|
326
|
+
const instance = { value: 'test' }
|
|
327
|
+
const holder = manager.storeCreatedHolder(
|
|
328
|
+
'test-instance',
|
|
329
|
+
instance,
|
|
330
|
+
InjectableType.Class,
|
|
331
|
+
InjectableScope.Singleton,
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
expect(holder.status).toBe(ServiceLocatorInstanceHolderStatus.Created)
|
|
335
|
+
expect(holder.name).toBe('test-instance')
|
|
336
|
+
expect(holder.instance).toBe(instance)
|
|
337
|
+
expect(holder.creationPromise).toBeNull()
|
|
338
|
+
expect(holder.type).toBe(InjectableType.Class)
|
|
339
|
+
expect(holder.scope).toBe(InjectableScope.Singleton)
|
|
340
|
+
expect(holder.deps).toEqual(new Set())
|
|
341
|
+
expect(holder.ttl).toBe(Infinity)
|
|
342
|
+
|
|
343
|
+
// Verify it's stored
|
|
344
|
+
const getResult = manager.get('test-instance')
|
|
345
|
+
expect(getResult).toHaveLength(2)
|
|
346
|
+
expect(getResult[0]).toBeUndefined()
|
|
347
|
+
expect(getResult[1]).toBe(holder)
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
it('should create holder with custom dependencies and TTL', () => {
|
|
351
|
+
const deps = new Set(['dep1', 'dep2'])
|
|
352
|
+
const ttl = 5000
|
|
353
|
+
const instance = { value: 'test' }
|
|
354
|
+
|
|
355
|
+
const holder = manager.storeCreatedHolder(
|
|
356
|
+
'test-instance',
|
|
357
|
+
instance,
|
|
358
|
+
InjectableType.Factory,
|
|
359
|
+
InjectableScope.Request,
|
|
360
|
+
deps,
|
|
361
|
+
ttl,
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
expect(holder.deps).toBe(deps)
|
|
365
|
+
expect(holder.ttl).toBe(ttl)
|
|
366
|
+
expect(holder.type).toBe(InjectableType.Factory)
|
|
367
|
+
expect(holder.scope).toBe(InjectableScope.Request)
|
|
368
|
+
})
|
|
369
|
+
})
|
|
370
|
+
})
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { InjectableScope, InjectableType } from '../enums/index.mjs'
|
|
4
|
+
import { DefaultRequestContextHolder } from '../request-context-holder.mjs'
|
|
5
|
+
import { ServiceLocatorManager } from '../service-locator-manager.mjs'
|
|
6
|
+
|
|
7
|
+
describe('Unified API', () => {
|
|
8
|
+
describe('Common methods between ServiceLocatorManager and RequestContextHolder', () => {
|
|
9
|
+
it('should have the same basic API surface', () => {
|
|
10
|
+
const serviceManager = new ServiceLocatorManager()
|
|
11
|
+
const requestContext = new DefaultRequestContextHolder(
|
|
12
|
+
'test-request',
|
|
13
|
+
100,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
// Both should have the same common methods
|
|
17
|
+
expect(typeof serviceManager.size).toBe('function')
|
|
18
|
+
expect(typeof requestContext.size).toBe('function')
|
|
19
|
+
|
|
20
|
+
expect(typeof serviceManager.isEmpty).toBe('function')
|
|
21
|
+
expect(typeof requestContext.isEmpty).toBe('function')
|
|
22
|
+
|
|
23
|
+
expect(typeof serviceManager.filter).toBe('function')
|
|
24
|
+
expect(typeof requestContext.filter).toBe('function')
|
|
25
|
+
|
|
26
|
+
expect(typeof serviceManager.clear).toBe('function')
|
|
27
|
+
expect(typeof requestContext.clear).toBe('function')
|
|
28
|
+
|
|
29
|
+
expect(typeof serviceManager.delete).toBe('function')
|
|
30
|
+
expect(typeof requestContext.delete).toBe('function')
|
|
31
|
+
|
|
32
|
+
expect(typeof serviceManager.getAllNames).toBe('function')
|
|
33
|
+
expect(typeof requestContext.getAllNames).toBe('function')
|
|
34
|
+
|
|
35
|
+
expect(typeof serviceManager.getAllHolders).toBe('function')
|
|
36
|
+
expect(typeof requestContext.getAllHolders).toBe('function')
|
|
37
|
+
|
|
38
|
+
expect(typeof serviceManager.createCreatingHolder).toBe('function')
|
|
39
|
+
expect(typeof requestContext.createCreatingHolder).toBe('function')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('should work with the same holder creation patterns', () => {
|
|
43
|
+
const serviceManager = new ServiceLocatorManager()
|
|
44
|
+
const requestContext = new DefaultRequestContextHolder(
|
|
45
|
+
'test-request',
|
|
46
|
+
100,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
// Both should be able to create holders the same way
|
|
50
|
+
const [deferred1, holder1] = serviceManager.createCreatingHolder(
|
|
51
|
+
'Service1',
|
|
52
|
+
InjectableType.Class,
|
|
53
|
+
InjectableScope.Singleton,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
const [deferred2, holder2] = requestContext.createCreatingHolder(
|
|
57
|
+
'Service2',
|
|
58
|
+
InjectableType.Class,
|
|
59
|
+
InjectableScope.Request,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
expect(holder1.name).toBe('Service1')
|
|
63
|
+
expect(holder1.scope).toBe(InjectableScope.Singleton)
|
|
64
|
+
expect(holder2.name).toBe('Service2')
|
|
65
|
+
expect(holder2.scope).toBe(InjectableScope.Request)
|
|
66
|
+
|
|
67
|
+
// Both should be able to store holders
|
|
68
|
+
serviceManager.set('Service1', holder1)
|
|
69
|
+
requestContext.set('Service2', holder2)
|
|
70
|
+
|
|
71
|
+
expect(serviceManager.size()).toBe(1)
|
|
72
|
+
expect(requestContext.size()).toBe(1)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('should support the same filtering patterns', () => {
|
|
76
|
+
const serviceManager = new ServiceLocatorManager()
|
|
77
|
+
const requestContext = new DefaultRequestContextHolder(
|
|
78
|
+
'test-request',
|
|
79
|
+
100,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
// Create and store different types of holders
|
|
83
|
+
const singletonHolder = serviceManager.storeCreatedHolder(
|
|
84
|
+
'Singleton',
|
|
85
|
+
{},
|
|
86
|
+
InjectableType.Class,
|
|
87
|
+
InjectableScope.Singleton,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
// Create a holder first, then add it to the request context
|
|
91
|
+
const [, transientHolderTemp] = requestContext.createCreatingHolder(
|
|
92
|
+
'Transient',
|
|
93
|
+
InjectableType.Class,
|
|
94
|
+
InjectableScope.Transient,
|
|
95
|
+
)
|
|
96
|
+
requestContext.set('Transient', transientHolderTemp)
|
|
97
|
+
const transientHolder = transientHolderTemp
|
|
98
|
+
|
|
99
|
+
// Both should support the same filtering API
|
|
100
|
+
const singletons = serviceManager.filter(
|
|
101
|
+
(holder) => holder.scope === InjectableScope.Singleton,
|
|
102
|
+
)
|
|
103
|
+
const transients = requestContext.filter(
|
|
104
|
+
(holder) => holder.scope === InjectableScope.Transient,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
expect(singletons.size).toBe(1)
|
|
108
|
+
expect(transients.size).toBe(1)
|
|
109
|
+
expect(singletons.get('Singleton')).toBe(singletonHolder)
|
|
110
|
+
expect(transients.get('Transient')).toBe(transientHolder)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('should maintain their specific behaviors while sharing common API', () => {
|
|
114
|
+
const serviceManager = new ServiceLocatorManager()
|
|
115
|
+
const requestContext = new DefaultRequestContextHolder(
|
|
116
|
+
'test-request',
|
|
117
|
+
100,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
// ServiceLocatorManager has specific error handling
|
|
121
|
+
const [notFound] = serviceManager.get('NonExistent')
|
|
122
|
+
expect(notFound).toBeDefined()
|
|
123
|
+
|
|
124
|
+
// RequestContextHolder has specific request features
|
|
125
|
+
expect(requestContext.requestId).toBe('test-request')
|
|
126
|
+
expect(requestContext.priority).toBe(100)
|
|
127
|
+
expect(requestContext.metadata).toBeInstanceOf(Map)
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
})
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import type { ServiceLocatorInstanceHolder } from './service-locator-instance-holder.mjs'
|
|
2
|
+
|
|
3
|
+
import { InjectableScope, InjectableType } from './enums/index.mjs'
|
|
4
|
+
import { ServiceLocatorInstanceHolderStatus } from './service-locator-instance-holder.mjs'
|
|
5
|
+
import { createDeferred } from './utils/defer.mjs'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Abstract base class that provides common functionality for managing ServiceLocatorInstanceHolder objects.
|
|
9
|
+
* This class contains shared patterns used by both RequestContextHolder and ServiceLocatorManager.
|
|
10
|
+
*/
|
|
11
|
+
export abstract class BaseInstanceHolderManager {
|
|
12
|
+
protected readonly _holders: Map<string, ServiceLocatorInstanceHolder>
|
|
13
|
+
|
|
14
|
+
constructor(protected readonly logger: Console | null = null) {
|
|
15
|
+
this._holders = new Map()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Protected getter for accessing the holders map from subclasses.
|
|
20
|
+
*/
|
|
21
|
+
protected get holders(): Map<string, ServiceLocatorInstanceHolder> {
|
|
22
|
+
return this._holders
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Abstract method to get a holder by name. Each implementation defines its own return type
|
|
27
|
+
* based on their specific error handling and validation needs.
|
|
28
|
+
*/
|
|
29
|
+
abstract get(name: string): any
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Abstract method to set a holder by name. Each implementation may have different validation logic.
|
|
33
|
+
*/
|
|
34
|
+
abstract set(name: string, holder: ServiceLocatorInstanceHolder): void
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Abstract method to check if a holder exists. Each implementation may have different validation logic.
|
|
38
|
+
*/
|
|
39
|
+
abstract has(name: string): any
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Deletes a holder by name.
|
|
43
|
+
* @param name The name of the holder to delete
|
|
44
|
+
* @returns true if the holder was deleted, false if it didn't exist
|
|
45
|
+
*/
|
|
46
|
+
delete(name: string): boolean {
|
|
47
|
+
return this._holders.delete(name)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Filters holders based on a predicate function.
|
|
52
|
+
* @param predicate Function to test each holder
|
|
53
|
+
* @returns A new Map containing only the holders that match the predicate
|
|
54
|
+
*/
|
|
55
|
+
filter(
|
|
56
|
+
predicate: (
|
|
57
|
+
value: ServiceLocatorInstanceHolder<any>,
|
|
58
|
+
key: string,
|
|
59
|
+
) => boolean,
|
|
60
|
+
): Map<string, ServiceLocatorInstanceHolder> {
|
|
61
|
+
return new Map(
|
|
62
|
+
[...this._holders].filter(([key, value]) => predicate(value, key)),
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Clears all holders from this manager.
|
|
68
|
+
*/
|
|
69
|
+
clear(): void {
|
|
70
|
+
this._holders.clear()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Gets the number of holders currently managed.
|
|
75
|
+
*/
|
|
76
|
+
size(): number {
|
|
77
|
+
return this._holders.size
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Creates a new holder with Creating status and a deferred creation promise.
|
|
82
|
+
* This is useful for creating placeholder holders that can be fulfilled later.
|
|
83
|
+
* @param name The name of the instance
|
|
84
|
+
* @param type The injectable type
|
|
85
|
+
* @param scope The injectable scope
|
|
86
|
+
* @param deps Optional set of dependencies
|
|
87
|
+
* @param ttl Optional time-to-live in milliseconds (defaults to Infinity)
|
|
88
|
+
* @returns A tuple containing the deferred promise and the holder
|
|
89
|
+
*/
|
|
90
|
+
createCreatingHolder<Instance>(
|
|
91
|
+
name: string,
|
|
92
|
+
type: InjectableType,
|
|
93
|
+
scope: InjectableScope,
|
|
94
|
+
deps: Set<string> = new Set(),
|
|
95
|
+
ttl: number = Infinity,
|
|
96
|
+
): [
|
|
97
|
+
ReturnType<typeof createDeferred<[undefined, Instance]>>,
|
|
98
|
+
ServiceLocatorInstanceHolder<Instance>,
|
|
99
|
+
] {
|
|
100
|
+
const deferred = createDeferred<[undefined, Instance]>()
|
|
101
|
+
|
|
102
|
+
const holder: ServiceLocatorInstanceHolder<Instance> = {
|
|
103
|
+
status: ServiceLocatorInstanceHolderStatus.Creating,
|
|
104
|
+
name,
|
|
105
|
+
instance: null,
|
|
106
|
+
creationPromise: deferred.promise,
|
|
107
|
+
destroyPromise: null,
|
|
108
|
+
type,
|
|
109
|
+
scope,
|
|
110
|
+
deps,
|
|
111
|
+
destroyListeners: [],
|
|
112
|
+
createdAt: Date.now(),
|
|
113
|
+
ttl,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return [deferred, holder]
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Creates a new holder with Created status and an actual instance.
|
|
121
|
+
* This is useful for creating holders that already have their instance ready.
|
|
122
|
+
* @param name The name of the instance
|
|
123
|
+
* @param instance The actual instance to store
|
|
124
|
+
* @param type The injectable type
|
|
125
|
+
* @param scope The injectable scope
|
|
126
|
+
* @param deps Optional set of dependencies
|
|
127
|
+
* @param ttl Optional time-to-live in milliseconds (defaults to Infinity)
|
|
128
|
+
* @returns The created holder
|
|
129
|
+
*/
|
|
130
|
+
protected createCreatedHolder<Instance>(
|
|
131
|
+
name: string,
|
|
132
|
+
instance: Instance,
|
|
133
|
+
type: InjectableType,
|
|
134
|
+
scope: InjectableScope,
|
|
135
|
+
deps: Set<string> = new Set(),
|
|
136
|
+
ttl: number = Infinity,
|
|
137
|
+
): ServiceLocatorInstanceHolder<Instance> {
|
|
138
|
+
const holder: ServiceLocatorInstanceHolder<Instance> = {
|
|
139
|
+
status: ServiceLocatorInstanceHolderStatus.Created,
|
|
140
|
+
name,
|
|
141
|
+
instance,
|
|
142
|
+
creationPromise: null,
|
|
143
|
+
destroyPromise: null,
|
|
144
|
+
type,
|
|
145
|
+
scope,
|
|
146
|
+
deps,
|
|
147
|
+
destroyListeners: [],
|
|
148
|
+
createdAt: Date.now(),
|
|
149
|
+
ttl,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return holder
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Gets all holder names currently managed.
|
|
157
|
+
*/
|
|
158
|
+
getAllNames(): string[] {
|
|
159
|
+
return Array.from(this._holders.keys())
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Gets all holders currently managed.
|
|
164
|
+
*/
|
|
165
|
+
getAllHolders(): ServiceLocatorInstanceHolder[] {
|
|
166
|
+
return Array.from(this._holders.values())
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Checks if this manager has any holders.
|
|
171
|
+
*/
|
|
172
|
+
isEmpty(): boolean {
|
|
173
|
+
return this._holders.size === 0
|
|
174
|
+
}
|
|
175
|
+
}
|
package/src/index.mts
CHANGED
|
@@ -3,6 +3,7 @@ export * from './enums/index.mjs'
|
|
|
3
3
|
export * from './errors/index.mjs'
|
|
4
4
|
export * from './interfaces/index.mjs'
|
|
5
5
|
export * from './utils/index.mjs'
|
|
6
|
+
export * from './base-instance-holder-manager.mjs'
|
|
6
7
|
export * from './event-emitter.mjs'
|
|
7
8
|
export * from './factory-context.mjs'
|
|
8
9
|
export * from './injection-token.mjs'
|