ts-ioc-container 46.6.2 → 46.7.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 +108 -1
- package/cjm/container/Container.js +5 -0
- package/cjm/container/EmptyContainer.js +3 -0
- package/esm/container/Container.js +5 -0
- package/esm/container/EmptyContainer.js +3 -0
- package/package.json +1 -1
- package/typings/container/Container.d.ts +1 -0
- package/typings/container/EmptyContainer.d.ts +1 -0
- package/typings/container/IContainer.d.ts +1 -0
package/README.md
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
- [Container](#container)
|
|
29
29
|
- [Basic usage](#basic-usage)
|
|
30
30
|
- [Scope](#scope) `tags`
|
|
31
|
+
- [Dynamic Tag Management](#dynamic-tag-management) `addTags`
|
|
31
32
|
- [Instances](#instances)
|
|
32
33
|
- [Dispose](#dispose)
|
|
33
34
|
- [Lazy](#lazy) `lazy`
|
|
@@ -279,7 +280,6 @@ import {
|
|
|
279
280
|
*/
|
|
280
281
|
|
|
281
282
|
// SessionService is only available in request scope - not at application level
|
|
282
|
-
// This prevents accidental access to request-specific data from singletons
|
|
283
283
|
@register(bindTo('ISessionService'), scope((s) => s.hasTag('request')), singleton())
|
|
284
284
|
class SessionService {
|
|
285
285
|
private userId: string | null = null;
|
|
@@ -342,6 +342,113 @@ describe('Scopes', function () {
|
|
|
342
342
|
|
|
343
343
|
```
|
|
344
344
|
|
|
345
|
+
### Dynamic Tag Management
|
|
346
|
+
You can dynamically add tags to a container after it's been created using the `addTags()` method. This is useful for environment-based configuration, feature flags, and progressive container setup.
|
|
347
|
+
|
|
348
|
+
- Tags can be added one at a time or multiple at once
|
|
349
|
+
- Tags must be added **before** registrations are applied - scope matching happens at registration time
|
|
350
|
+
- Useful for conditional configuration based on `NODE_ENV` or runtime flags
|
|
351
|
+
- Container can be configured incrementally as the application initializes
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
import { bindTo, Container, register, Registration as R, scope } from 'ts-ioc-container';
|
|
355
|
+
|
|
356
|
+
describe('addTags', () => {
|
|
357
|
+
it('should dynamically add tags to enable environment-based registration', () => {
|
|
358
|
+
@register(bindTo('logger'), scope((s) => s.hasTag('development')))
|
|
359
|
+
class ConsoleLogger {
|
|
360
|
+
log(message: string) {
|
|
361
|
+
console.log(`[DEV] ${message}`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
@register(bindTo('logger'), scope((s) => s.hasTag('production')))
|
|
366
|
+
class FileLogger {
|
|
367
|
+
log(message: string) {
|
|
368
|
+
console.log(`[PROD] ${message}`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Create container and configure for environment
|
|
373
|
+
const container = new Container();
|
|
374
|
+
const environment = 'development';
|
|
375
|
+
container.addTags(environment); // Add tag dynamically based on environment
|
|
376
|
+
|
|
377
|
+
// Register services after tag is set
|
|
378
|
+
container.addRegistration(R.fromClass(ConsoleLogger)).addRegistration(R.fromClass(FileLogger));
|
|
379
|
+
|
|
380
|
+
// Resolve logger - gets ConsoleLogger because 'development' tag was added
|
|
381
|
+
const logger = container.resolve<ConsoleLogger>('logger');
|
|
382
|
+
expect(logger).toBeInstanceOf(ConsoleLogger);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it('should add multiple tags for feature-based configuration', () => {
|
|
386
|
+
@register(bindTo('premiumFeature'), scope((s) => s.hasTag('premium')))
|
|
387
|
+
class PremiumFeature {}
|
|
388
|
+
|
|
389
|
+
@register(bindTo('betaFeature'), scope((s) => s.hasTag('beta')))
|
|
390
|
+
class BetaFeature {}
|
|
391
|
+
|
|
392
|
+
const container = new Container();
|
|
393
|
+
|
|
394
|
+
// Add multiple tags at once
|
|
395
|
+
container.addTags('premium', 'beta', 'experimental');
|
|
396
|
+
|
|
397
|
+
// Verify all tags are present
|
|
398
|
+
expect(container.hasTag('premium')).toBe(true);
|
|
399
|
+
expect(container.hasTag('beta')).toBe(true);
|
|
400
|
+
expect(container.hasTag('experimental')).toBe(true);
|
|
401
|
+
|
|
402
|
+
// Register features after tags are added
|
|
403
|
+
container.addRegistration(R.fromClass(PremiumFeature)).addRegistration(R.fromClass(BetaFeature));
|
|
404
|
+
|
|
405
|
+
// Both features are available because container has both tags
|
|
406
|
+
expect(container.resolve('premiumFeature')).toBeInstanceOf(PremiumFeature);
|
|
407
|
+
expect(container.resolve('betaFeature')).toBeInstanceOf(BetaFeature);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it('should affect child scope creation', () => {
|
|
411
|
+
@register(bindTo('service'), scope((s) => s.hasTag('api')))
|
|
412
|
+
class ApiService {
|
|
413
|
+
handleRequest() {
|
|
414
|
+
return 'API response';
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const appContainer = new Container();
|
|
419
|
+
|
|
420
|
+
// Add tag to parent
|
|
421
|
+
appContainer.addTags('api');
|
|
422
|
+
appContainer.addRegistration(R.fromClass(ApiService));
|
|
423
|
+
|
|
424
|
+
// Create child scopes - they inherit parent's registrations
|
|
425
|
+
const requestScope1 = appContainer.createScope({ tags: ['request'] });
|
|
426
|
+
const requestScope2 = appContainer.createScope({ tags: ['request'] });
|
|
427
|
+
|
|
428
|
+
// Both scopes can access the ApiService from parent
|
|
429
|
+
expect(requestScope1.resolve<ApiService>('service').handleRequest()).toBe('API response');
|
|
430
|
+
expect(requestScope2.resolve<ApiService>('service').handleRequest()).toBe('API response');
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it('should enable incremental tag addition', () => {
|
|
434
|
+
const container = new Container();
|
|
435
|
+
|
|
436
|
+
// Start with basic tags
|
|
437
|
+
container.addTags('application');
|
|
438
|
+
expect(container.hasTag('application')).toBe(true);
|
|
439
|
+
|
|
440
|
+
// Add more tags as needed
|
|
441
|
+
container.addTags('monitoring', 'logging');
|
|
442
|
+
expect(container.hasTag('monitoring')).toBe(true);
|
|
443
|
+
expect(container.hasTag('logging')).toBe(true);
|
|
444
|
+
|
|
445
|
+
// All tags are retained
|
|
446
|
+
expect(container.hasTag('application')).toBe(true);
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
```
|
|
451
|
+
|
|
345
452
|
### Instances
|
|
346
453
|
Sometimes you want to get all instances from container and its scopes. For example, when you want to dispose all instances of container.
|
|
347
454
|
|
|
@@ -144,6 +144,11 @@ class Container {
|
|
|
144
144
|
hasTag(tag) {
|
|
145
145
|
return this.tags.has(tag);
|
|
146
146
|
}
|
|
147
|
+
addTags(...tags) {
|
|
148
|
+
for (const tag of tags) {
|
|
149
|
+
this.tags.add(tag);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
147
152
|
validateContainer() {
|
|
148
153
|
if (this.isDisposed) {
|
|
149
154
|
throw new ContainerDisposedError_1.ContainerDisposedError('Container is already disposed');
|
|
@@ -141,6 +141,11 @@ export class Container {
|
|
|
141
141
|
hasTag(tag) {
|
|
142
142
|
return this.tags.has(tag);
|
|
143
143
|
}
|
|
144
|
+
addTags(...tags) {
|
|
145
|
+
for (const tag of tags) {
|
|
146
|
+
this.tags.add(tag);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
144
149
|
validateContainer() {
|
|
145
150
|
if (this.isDisposed) {
|
|
146
151
|
throw new ContainerDisposedError('Container is already disposed');
|
package/package.json
CHANGED
|
@@ -39,6 +39,7 @@ export declare class Container implements IContainer {
|
|
|
39
39
|
getParent(): IContainer;
|
|
40
40
|
getInstances(cascade?: boolean): Instance<unknown>[];
|
|
41
41
|
hasTag(tag: Tag): boolean;
|
|
42
|
+
addTags(...tags: Tag[]): void;
|
|
42
43
|
private validateContainer;
|
|
43
44
|
private findProviderByKeyOrFail;
|
|
44
45
|
}
|
|
@@ -14,6 +14,7 @@ export declare class EmptyContainer implements IContainer {
|
|
|
14
14
|
dispose(): void;
|
|
15
15
|
register(key: DependencyKey, value: IProvider): this;
|
|
16
16
|
hasTag(tag: Tag): boolean;
|
|
17
|
+
addTags(...tags: Tag[]): void;
|
|
17
18
|
getRegistrations(): never[];
|
|
18
19
|
removeScope(): void;
|
|
19
20
|
useModule(module: IContainerModule): this;
|
|
@@ -17,6 +17,7 @@ type WithExcludedKeys = {
|
|
|
17
17
|
};
|
|
18
18
|
export interface Tagged {
|
|
19
19
|
hasTag(tag: Tag): boolean;
|
|
20
|
+
addTags(...tags: Tag[]): void;
|
|
20
21
|
}
|
|
21
22
|
export type ResolveOneOptions = ProviderOptions & Partial<WithChild>;
|
|
22
23
|
export type ResolveManyOptions = ResolveOneOptions & Partial<WithExcludedKeys>;
|