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 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');
@@ -29,6 +29,9 @@ class EmptyContainer {
29
29
  hasTag(tag) {
30
30
  throw new MethodNotImplementedError_1.MethodNotImplementedError();
31
31
  }
32
+ addTags(...tags) {
33
+ throw new MethodNotImplementedError_1.MethodNotImplementedError();
34
+ }
32
35
  getRegistrations() {
33
36
  return [];
34
37
  }
@@ -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');
@@ -26,6 +26,9 @@ export class EmptyContainer {
26
26
  hasTag(tag) {
27
27
  throw new MethodNotImplementedError();
28
28
  }
29
+ addTags(...tags) {
30
+ throw new MethodNotImplementedError();
31
+ }
29
32
  getRegistrations() {
30
33
  return [];
31
34
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-ioc-container",
3
- "version": "46.6.2",
3
+ "version": "46.7.0",
4
4
  "description": "Typescript IoC container",
5
5
  "workspaces": [
6
6
  "docs"
@@ -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>;