@tachybase/di 1.3.52 → 1.3.53
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/lib/index.d.ts +1 -0
- package/lib/index.js +3 -0
- package/package.json +2 -2
- package/src/__tests__/container-instance.test.ts +324 -0
- package/src/__tests__/container-registry.test.ts +279 -0
- package/src/__tests__/decorators.test.ts +178 -0
- package/src/__tests__/errors.test.ts +359 -0
- package/src/__tests__/integration.test.ts +123 -0
- package/src/__tests__/lifecycle.test.ts +475 -0
- package/src/__tests__/scopes.test.ts +284 -0
- package/src/__tests__/token-factory.test.ts +407 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { Container, ContainerInstance, Inject, Service } from '../index';
|
|
4
|
+
|
|
5
|
+
describe('Service Lifecycle and Cleanup', () => {
|
|
6
|
+
let container: ContainerInstance;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
container = new ContainerInstance(`test-container-${Math.random()}`);
|
|
10
|
+
Container.reset({ strategy: 'resetServices' });
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
container.reset({ strategy: 'resetServices' });
|
|
15
|
+
Container.reset({ strategy: 'resetServices' });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('Service Disposal', () => {
|
|
19
|
+
it('should call dispose method when removing service', () => {
|
|
20
|
+
let disposed = false;
|
|
21
|
+
|
|
22
|
+
class TestService {
|
|
23
|
+
dispose() {
|
|
24
|
+
disposed = true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
container.set({ type: TestService });
|
|
29
|
+
const instance = container.get(TestService);
|
|
30
|
+
|
|
31
|
+
container.remove(TestService);
|
|
32
|
+
|
|
33
|
+
expect(disposed).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should call dispose method with correct context', () => {
|
|
37
|
+
let disposedInstance: any = null;
|
|
38
|
+
|
|
39
|
+
class TestService {
|
|
40
|
+
public name = 'test';
|
|
41
|
+
|
|
42
|
+
dispose() {
|
|
43
|
+
disposedInstance = this;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
container.set({ type: TestService });
|
|
48
|
+
const instance = container.get(TestService);
|
|
49
|
+
|
|
50
|
+
container.remove(TestService);
|
|
51
|
+
|
|
52
|
+
expect(disposedInstance).toBe(instance);
|
|
53
|
+
expect(disposedInstance.name).toBe('test');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should handle service without dispose method', () => {
|
|
57
|
+
class TestService {
|
|
58
|
+
public name = 'test';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
container.set({ type: TestService });
|
|
62
|
+
const instance = container.get(TestService);
|
|
63
|
+
|
|
64
|
+
// Should not throw error
|
|
65
|
+
expect(() => container.remove(TestService)).not.toThrow();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should handle disposal errors gracefully', () => {
|
|
69
|
+
class TestService {
|
|
70
|
+
dispose() {
|
|
71
|
+
throw new Error('Disposal error');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
container.set({ type: TestService });
|
|
76
|
+
const instance = container.get(TestService);
|
|
77
|
+
|
|
78
|
+
// Should not throw error even if disposal fails
|
|
79
|
+
expect(() => container.remove(TestService)).not.toThrow();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should dispose service when resetting with resetValue strategy', () => {
|
|
83
|
+
let disposed = false;
|
|
84
|
+
|
|
85
|
+
class TestService {
|
|
86
|
+
dispose() {
|
|
87
|
+
disposed = true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
container.set({ type: TestService });
|
|
92
|
+
container.get(TestService);
|
|
93
|
+
|
|
94
|
+
container.reset({ strategy: 'resetValue' });
|
|
95
|
+
|
|
96
|
+
expect(disposed).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should dispose service when resetting with resetServices strategy', () => {
|
|
100
|
+
let disposed = false;
|
|
101
|
+
|
|
102
|
+
class TestService {
|
|
103
|
+
dispose() {
|
|
104
|
+
disposed = true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
container.set({ type: TestService });
|
|
109
|
+
container.get(TestService);
|
|
110
|
+
|
|
111
|
+
container.reset({ strategy: 'resetServices' });
|
|
112
|
+
|
|
113
|
+
expect(disposed).toBe(true);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should dispose service when disposing container', async () => {
|
|
117
|
+
let disposed = false;
|
|
118
|
+
|
|
119
|
+
class TestService {
|
|
120
|
+
dispose() {
|
|
121
|
+
disposed = true;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const testContainer = new ContainerInstance(`dispose-test-${Math.random()}`);
|
|
126
|
+
testContainer.set({ type: TestService });
|
|
127
|
+
testContainer.get(TestService);
|
|
128
|
+
|
|
129
|
+
await testContainer.dispose();
|
|
130
|
+
|
|
131
|
+
expect(disposed).toBe(true);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe('Service Lifecycle with Different Scopes', () => {
|
|
136
|
+
it('should dispose singleton service only once', () => {
|
|
137
|
+
let disposeCount = 0;
|
|
138
|
+
|
|
139
|
+
class TestService {
|
|
140
|
+
dispose() {
|
|
141
|
+
disposeCount++;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
container.set({ type: TestService, scope: 'singleton' });
|
|
146
|
+
const instance1 = container.get(TestService);
|
|
147
|
+
const instance2 = container.get(TestService);
|
|
148
|
+
|
|
149
|
+
container.remove(TestService);
|
|
150
|
+
|
|
151
|
+
expect(disposeCount).toBe(1);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should dispose transient service instances separately', () => {
|
|
155
|
+
let disposeCount = 0;
|
|
156
|
+
|
|
157
|
+
class TestService {
|
|
158
|
+
dispose() {
|
|
159
|
+
disposeCount++;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
container.set({ type: TestService, scope: 'transient' });
|
|
164
|
+
const instance1 = container.get(TestService);
|
|
165
|
+
const instance2 = container.get(TestService);
|
|
166
|
+
|
|
167
|
+
container.remove(TestService);
|
|
168
|
+
|
|
169
|
+
// Transient services don't store instances, so dispose is not called
|
|
170
|
+
expect(disposeCount).toBe(0);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should dispose container scope service only once', () => {
|
|
174
|
+
let disposeCount = 0;
|
|
175
|
+
|
|
176
|
+
class TestService {
|
|
177
|
+
dispose() {
|
|
178
|
+
disposeCount++;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
container.set({ type: TestService, scope: 'container' });
|
|
183
|
+
const instance1 = container.get(TestService);
|
|
184
|
+
const instance2 = container.get(TestService);
|
|
185
|
+
|
|
186
|
+
container.remove(TestService);
|
|
187
|
+
|
|
188
|
+
expect(disposeCount).toBe(1);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
describe('Multiple Services Disposal', () => {
|
|
193
|
+
it('should dispose all multiple services', () => {
|
|
194
|
+
let disposeCount = 0;
|
|
195
|
+
|
|
196
|
+
class TestService {
|
|
197
|
+
dispose() {
|
|
198
|
+
disposeCount++;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
container.set({ id: 'multi-service', type: TestService, multiple: true });
|
|
203
|
+
container.set({ id: 'multi-service', type: TestService, multiple: true });
|
|
204
|
+
|
|
205
|
+
const services = container.getMany('multi-service');
|
|
206
|
+
|
|
207
|
+
container.remove('multi-service');
|
|
208
|
+
|
|
209
|
+
expect(disposeCount).toBe(2);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should dispose multiple services with different types', () => {
|
|
213
|
+
let service1Disposed = false;
|
|
214
|
+
let service2Disposed = false;
|
|
215
|
+
|
|
216
|
+
class Service1 {
|
|
217
|
+
dispose() {
|
|
218
|
+
service1Disposed = true;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
class Service2 {
|
|
223
|
+
dispose() {
|
|
224
|
+
service2Disposed = true;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
container.set({ id: 'multi-service', type: Service1, multiple: true });
|
|
229
|
+
container.set({ id: 'multi-service', type: Service2, multiple: true });
|
|
230
|
+
|
|
231
|
+
container.remove('multi-service');
|
|
232
|
+
|
|
233
|
+
expect(service1Disposed).toBe(true);
|
|
234
|
+
expect(service2Disposed).toBe(true);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
describe('Container Disposal', () => {
|
|
239
|
+
it('should dispose all services when disposing container', async () => {
|
|
240
|
+
let service1Disposed = false;
|
|
241
|
+
let service2Disposed = false;
|
|
242
|
+
|
|
243
|
+
class Service1 {
|
|
244
|
+
dispose() {
|
|
245
|
+
service1Disposed = true;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
class Service2 {
|
|
250
|
+
dispose() {
|
|
251
|
+
service2Disposed = true;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const testContainer = new ContainerInstance(`dispose-test-${Math.random()}`);
|
|
256
|
+
testContainer.set({ type: Service1 });
|
|
257
|
+
testContainer.set({ type: Service2 });
|
|
258
|
+
|
|
259
|
+
testContainer.get(Service1);
|
|
260
|
+
testContainer.get(Service2);
|
|
261
|
+
|
|
262
|
+
await testContainer.dispose();
|
|
263
|
+
|
|
264
|
+
expect(service1Disposed).toBe(true);
|
|
265
|
+
expect(service2Disposed).toBe(true);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('should mark container as disposed', async () => {
|
|
269
|
+
const testContainer = new ContainerInstance(`dispose-test-${Math.random()}`);
|
|
270
|
+
testContainer.set({ id: 'test', value: 'test' });
|
|
271
|
+
|
|
272
|
+
await testContainer.dispose();
|
|
273
|
+
|
|
274
|
+
expect(testContainer['disposed']).toBe(true);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('should prevent operations after disposal', async () => {
|
|
278
|
+
const testContainer = new ContainerInstance(`dispose-test-${Math.random()}`);
|
|
279
|
+
await testContainer.dispose();
|
|
280
|
+
|
|
281
|
+
expect(() => testContainer.get('test')).toThrow('Cannot use container after it has been disposed.');
|
|
282
|
+
expect(() => testContainer.set({ id: 'test', value: 'test' })).toThrow(
|
|
283
|
+
'Cannot use container after it has been disposed.',
|
|
284
|
+
);
|
|
285
|
+
expect(() => testContainer.has('test')).toThrow('Cannot use container after it has been disposed.');
|
|
286
|
+
expect(() => testContainer.remove('test')).toThrow('Cannot use container after it has been disposed.');
|
|
287
|
+
expect(() => testContainer.reset()).toThrow('Cannot use container after it has been disposed.');
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
describe('Service Reset Strategies', () => {
|
|
292
|
+
it('should reset values but keep services with resetValue strategy', () => {
|
|
293
|
+
class TestService {
|
|
294
|
+
public id = Math.random();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
container.set({ type: TestService });
|
|
298
|
+
const instance1 = container.get(TestService);
|
|
299
|
+
|
|
300
|
+
container.reset({ strategy: 'resetValue' });
|
|
301
|
+
const instance2 = container.get(TestService);
|
|
302
|
+
|
|
303
|
+
expect(container.has(TestService)).toBe(true);
|
|
304
|
+
expect(instance1).not.toBe(instance2);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('should reset services completely with resetServices strategy', () => {
|
|
308
|
+
class TestService {
|
|
309
|
+
public name = 'test';
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
container.set({ type: TestService });
|
|
313
|
+
expect(container.has(TestService)).toBe(true);
|
|
314
|
+
|
|
315
|
+
container.reset({ strategy: 'resetServices' });
|
|
316
|
+
expect(container.has(TestService)).toBe(false);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('should dispose services when resetting', () => {
|
|
320
|
+
let disposed = false;
|
|
321
|
+
|
|
322
|
+
class TestService {
|
|
323
|
+
dispose() {
|
|
324
|
+
disposed = true;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
container.set({ type: TestService });
|
|
329
|
+
container.get(TestService);
|
|
330
|
+
|
|
331
|
+
container.reset({ strategy: 'resetServices' });
|
|
332
|
+
|
|
333
|
+
expect(disposed).toBe(true);
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
describe('Eager Services Lifecycle', () => {
|
|
338
|
+
it('should dispose eager services when removing', () => {
|
|
339
|
+
let disposed = false;
|
|
340
|
+
|
|
341
|
+
class TestService {
|
|
342
|
+
dispose() {
|
|
343
|
+
disposed = true;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
container.set({ type: TestService, eager: true });
|
|
348
|
+
|
|
349
|
+
container.remove(TestService);
|
|
350
|
+
|
|
351
|
+
expect(disposed).toBe(true);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it('should dispose eager services when resetting', () => {
|
|
355
|
+
let disposed = false;
|
|
356
|
+
|
|
357
|
+
class TestService {
|
|
358
|
+
dispose() {
|
|
359
|
+
disposed = true;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
container.set({ type: TestService, eager: true });
|
|
364
|
+
|
|
365
|
+
container.reset({ strategy: 'resetServices' });
|
|
366
|
+
|
|
367
|
+
expect(disposed).toBe(true);
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
describe('Factory Services Lifecycle', () => {
|
|
372
|
+
it('should dispose factory-created services', () => {
|
|
373
|
+
let disposed = false;
|
|
374
|
+
|
|
375
|
+
class TestService {
|
|
376
|
+
dispose() {
|
|
377
|
+
disposed = true;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const factory = () => new TestService();
|
|
382
|
+
|
|
383
|
+
container.set({ id: 'factory-service', factory });
|
|
384
|
+
container.get('factory-service');
|
|
385
|
+
|
|
386
|
+
container.remove('factory-service');
|
|
387
|
+
|
|
388
|
+
expect(disposed).toBe(true);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it('should dispose factory class-created services', () => {
|
|
392
|
+
let disposed = false;
|
|
393
|
+
|
|
394
|
+
class TestService {
|
|
395
|
+
dispose() {
|
|
396
|
+
disposed = true;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
class TestFactory {
|
|
401
|
+
create() {
|
|
402
|
+
return new TestService();
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
container.set({ id: 'factory-class-service', factory: [TestFactory, 'create'] });
|
|
407
|
+
container.get('factory-class-service');
|
|
408
|
+
|
|
409
|
+
container.remove('factory-class-service');
|
|
410
|
+
|
|
411
|
+
expect(disposed).toBe(true);
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
describe('Complex Lifecycle Scenarios', () => {
|
|
416
|
+
it('should handle service disposal with async operations', async () => {
|
|
417
|
+
let disposed = false;
|
|
418
|
+
|
|
419
|
+
class TestService {
|
|
420
|
+
async dispose() {
|
|
421
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
422
|
+
disposed = true;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
container.set({ type: TestService });
|
|
427
|
+
container.get(TestService);
|
|
428
|
+
|
|
429
|
+
container.remove(TestService);
|
|
430
|
+
|
|
431
|
+
// Wait a bit for async disposal
|
|
432
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
433
|
+
|
|
434
|
+
expect(disposed).toBe(true);
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
describe('Memory Management', () => {
|
|
439
|
+
it('should clear service references after disposal', async () => {
|
|
440
|
+
class TestService {
|
|
441
|
+
public data = new Array(1000).fill('data');
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const testContainer = new ContainerInstance(`dispose-test-${Math.random()}`);
|
|
445
|
+
testContainer.set({ type: TestService });
|
|
446
|
+
const instance = testContainer.get(TestService);
|
|
447
|
+
|
|
448
|
+
await testContainer.dispose();
|
|
449
|
+
|
|
450
|
+
// Service should be disposed and references cleared
|
|
451
|
+
expect(testContainer['disposed']).toBe(true);
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
it('should handle large number of services disposal', async () => {
|
|
455
|
+
const services: any[] = [];
|
|
456
|
+
const testContainer = new ContainerInstance(`dispose-test-${Math.random()}`);
|
|
457
|
+
|
|
458
|
+
for (let i = 0; i < 100; i++) {
|
|
459
|
+
class TestService {
|
|
460
|
+
public id = i;
|
|
461
|
+
dispose() {
|
|
462
|
+
// Disposal logic
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
testContainer.set({ id: `service-${i}`, type: TestService });
|
|
467
|
+
services.push(testContainer.get(`service-${i}`));
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
await testContainer.dispose();
|
|
471
|
+
|
|
472
|
+
expect(testContainer['disposed']).toBe(true);
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
});
|