ts-ioc-container 50.2.1 → 50.2.3
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 +3 -416
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,11 +36,9 @@ no global container objects.
|
|
|
36
36
|
- [Cheatsheet](#cheatsheet)
|
|
37
37
|
- [tsyringe alternative](https://igorbabkin.github.io/ts-ioc-container/tsyringe-alternative)
|
|
38
38
|
- [Inversify and Awilix alternative](https://igorbabkin.github.io/ts-ioc-container/inversify-awilix-alternative)
|
|
39
|
-
- [Recipes](#recipes)
|
|
40
39
|
- [Container](#container)
|
|
41
40
|
- [Basic usage](#basic-usage)
|
|
42
41
|
- [Scope](#scope) `tags`
|
|
43
|
-
- [Dynamic Tag Management](#dynamic-tag-management) `addTags`
|
|
44
42
|
- [Instances](#instances)
|
|
45
43
|
- [Dispose](#dispose)
|
|
46
44
|
- [Lazy](#lazy) `lazy`
|
|
@@ -153,208 +151,6 @@ describe('Quickstart', function () {
|
|
|
153
151
|
> for `R.fromValue(...)` and `R.fromFn(...)` (which have no class to decorate)
|
|
154
152
|
> or for third-party classes you don't own.
|
|
155
153
|
|
|
156
|
-
## Recipes
|
|
157
|
-
|
|
158
|
-
### Express/Next handler (per-request scope)
|
|
159
|
-
|
|
160
|
-
import 'reflect-metadata';
|
|
161
|
-
import { bindTo, Container, inject, register, Registration as R, singleton } from 'ts-ioc-container';
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Web Framework Integration - Per-Request Scope
|
|
165
|
-
*
|
|
166
|
-
* In Express/Next.js applications, each HTTP request typically gets its own
|
|
167
|
-
* scope. This ensures request-specific state (logger context, current user,
|
|
168
|
-
* correlation IDs) is isolated between concurrent requests.
|
|
169
|
-
*
|
|
170
|
-
* Scope hierarchy:
|
|
171
|
-
* Application (singleton services — live for entire app lifetime)
|
|
172
|
-
* └── Request (per-request services — created and disposed per request)
|
|
173
|
-
*/
|
|
174
|
-
|
|
175
|
-
@register(bindTo('ILogger'), singleton())
|
|
176
|
-
class Logger {
|
|
177
|
-
readonly messages: string[] = [];
|
|
178
|
-
|
|
179
|
-
log(message: string) {
|
|
180
|
-
this.messages.push(message);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
describe('Express/Next per-request scope', () => {
|
|
185
|
-
it('should give each request its own Logger instance', () => {
|
|
186
|
-
const app = new Container({ tags: ['application'] }).addRegistration(R.fromClass(Logger));
|
|
187
|
-
|
|
188
|
-
// Simulate two concurrent HTTP requests
|
|
189
|
-
const request1Scope = app.createScope({ tags: ['request'] });
|
|
190
|
-
const request2Scope = app.createScope({ tags: ['request'] });
|
|
191
|
-
|
|
192
|
-
const logger1 = request1Scope.resolve<Logger>('ILogger');
|
|
193
|
-
const logger2 = request2Scope.resolve<Logger>('ILogger');
|
|
194
|
-
|
|
195
|
-
logger1.log('req 1 started');
|
|
196
|
-
logger2.log('req 2 started');
|
|
197
|
-
|
|
198
|
-
// Each request has its own Logger — logs don't leak between requests
|
|
199
|
-
expect(logger1.messages).toEqual(['req 1 started']);
|
|
200
|
-
expect(logger2.messages).toEqual(['req 2 started']);
|
|
201
|
-
expect(logger1).not.toBe(logger2);
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it('should resolve the same Logger within a single request', () => {
|
|
205
|
-
const app = new Container({ tags: ['application'] }).addRegistration(R.fromClass(Logger));
|
|
206
|
-
|
|
207
|
-
const requestScope = app.createScope({ tags: ['request'] });
|
|
208
|
-
|
|
209
|
-
const logger1 = requestScope.resolve<Logger>('ILogger');
|
|
210
|
-
const logger2 = requestScope.resolve<Logger>('ILogger');
|
|
211
|
-
|
|
212
|
-
// Within one request, singleton is maintained
|
|
213
|
-
expect(logger1).toBe(logger2);
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
### Background worker (singleton client, transient jobs)
|
|
219
|
-
|
|
220
|
-
import 'reflect-metadata';
|
|
221
|
-
import { Container, inject, register, Registration as R, singleton } from 'ts-ioc-container';
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Background Worker - Singleton Client, Transient Jobs
|
|
225
|
-
*
|
|
226
|
-
* A queue worker typically needs:
|
|
227
|
-
* - A single shared QueueClient (expensive to create, holds connections)
|
|
228
|
-
* - A new JobHandler per job (stateful — holds job-specific data)
|
|
229
|
-
*
|
|
230
|
-
* singleton() on QueueClient ensures one shared connection pool.
|
|
231
|
-
* No singleton on JobHandler gives a fresh instance per resolve.
|
|
232
|
-
*/
|
|
233
|
-
|
|
234
|
-
@register(singleton())
|
|
235
|
-
class QueueClient {
|
|
236
|
-
readonly connected = true;
|
|
237
|
-
|
|
238
|
-
dequeue(): string {
|
|
239
|
-
return 'job-payload';
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
class JobHandler {
|
|
244
|
-
readonly result: string;
|
|
245
|
-
|
|
246
|
-
constructor(@inject('QueueClient') private queue: QueueClient) {
|
|
247
|
-
this.result = this.queue.dequeue();
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
describe('Background worker', () => {
|
|
252
|
-
function createWorker() {
|
|
253
|
-
return new Container({ tags: ['worker'] })
|
|
254
|
-
.addRegistration(R.fromClass(QueueClient))
|
|
255
|
-
.addRegistration(R.fromClass(JobHandler));
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
it('should share a single QueueClient across all job handlers', () => {
|
|
259
|
-
const worker = createWorker();
|
|
260
|
-
|
|
261
|
-
const handler1 = worker.resolve(JobHandler);
|
|
262
|
-
const handler2 = worker.resolve(JobHandler);
|
|
263
|
-
|
|
264
|
-
const client1 = worker.resolve<QueueClient>('QueueClient');
|
|
265
|
-
const client2 = worker.resolve<QueueClient>('QueueClient');
|
|
266
|
-
|
|
267
|
-
// QueueClient is a singleton — same connection shared everywhere
|
|
268
|
-
expect(client1).toBe(client2);
|
|
269
|
-
expect(client1.connected).toBe(true);
|
|
270
|
-
|
|
271
|
-
// JobHandler is transient — fresh instance per job
|
|
272
|
-
expect(handler1).not.toBe(handler2);
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
it('should inject the shared QueueClient into each JobHandler', () => {
|
|
276
|
-
const worker = createWorker();
|
|
277
|
-
|
|
278
|
-
const handler = worker.resolve(JobHandler);
|
|
279
|
-
|
|
280
|
-
expect(handler.result).toBe('job-payload');
|
|
281
|
-
});
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
### Frontend widget/page scope with lazy dependency
|
|
286
|
-
|
|
287
|
-
import 'reflect-metadata';
|
|
288
|
-
import { bindTo, Container, inject, register, Registration as R, select, singleton } from 'ts-ioc-container';
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Frontend Widget - Page Scope with Lazy Dependency
|
|
292
|
-
*
|
|
293
|
-
* In frontend applications, feature flags are fetched once per page load
|
|
294
|
-
* (singleton per page scope) but a widget may not need them on every render.
|
|
295
|
-
* Lazy injection defers instantiation until the widget actually reads the flags,
|
|
296
|
-
* avoiding unnecessary work for widgets that never display flag-gated content.
|
|
297
|
-
*
|
|
298
|
-
* Scope hierarchy:
|
|
299
|
-
* Application
|
|
300
|
-
* └── Page (singleton flags fetched once)
|
|
301
|
-
* └── Widget (lazy flag access)
|
|
302
|
-
*/
|
|
303
|
-
|
|
304
|
-
@register(bindTo('FeatureFlags'), singleton())
|
|
305
|
-
class FeatureFlags {
|
|
306
|
-
load(): Record<string, boolean> {
|
|
307
|
-
return { newDashboard: true };
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
class Widget {
|
|
312
|
-
constructor(@inject(select.token('FeatureFlags').lazy()) public flags: FeatureFlags) {}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
describe('Frontend widget/page scope with lazy dependency', () => {
|
|
316
|
-
function createPage() {
|
|
317
|
-
return new Container({ tags: ['page'] })
|
|
318
|
-
.addRegistration(R.fromClass(FeatureFlags))
|
|
319
|
-
.addRegistration(R.fromClass(Widget));
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
it('should not instantiate FeatureFlags until the widget actually accesses it', () => {
|
|
323
|
-
const page = createPage();
|
|
324
|
-
|
|
325
|
-
const widget = page.resolve(Widget);
|
|
326
|
-
|
|
327
|
-
// Widget is resolved, but FeatureFlags has not been instantiated yet
|
|
328
|
-
let instances = Array.from(page.getInstances()).filter((x) => x instanceof FeatureFlags);
|
|
329
|
-
expect(instances).toHaveLength(0);
|
|
330
|
-
|
|
331
|
-
// Accessing any property on the lazy proxy triggers instantiation
|
|
332
|
-
const _load = widget.flags.load;
|
|
333
|
-
expect(_load).toBeDefined();
|
|
334
|
-
|
|
335
|
-
instances = Array.from(page.getInstances()).filter((x) => x instanceof FeatureFlags);
|
|
336
|
-
expect(instances).toHaveLength(1);
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
it('should share the same FeatureFlags singleton across widgets on the same page', () => {
|
|
340
|
-
const page = createPage();
|
|
341
|
-
|
|
342
|
-
const widget1 = page.resolve(Widget);
|
|
343
|
-
const widget2 = page.resolve(Widget);
|
|
344
|
-
|
|
345
|
-
// Trigger instantiation through both widgets
|
|
346
|
-
const _load1 = widget1.flags.load;
|
|
347
|
-
const _load2 = widget2.flags.load;
|
|
348
|
-
expect(_load1).toBeDefined();
|
|
349
|
-
expect(_load2).toBeDefined();
|
|
350
|
-
|
|
351
|
-
// Only one FeatureFlags instance was created across the whole page scope
|
|
352
|
-
const instances = Array.from(page.getInstances()).filter((x) => x instanceof FeatureFlags);
|
|
353
|
-
expect(instances).toHaveLength(1);
|
|
354
|
-
});
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
|
|
358
154
|
## Container
|
|
359
155
|
|
|
360
156
|
`IContainer` consists of:
|
|
@@ -547,116 +343,6 @@ describe('Scopes', function () {
|
|
|
547
343
|
|
|
548
344
|
```
|
|
549
345
|
|
|
550
|
-
### Dynamic Tag Management
|
|
551
|
-
|
|
552
|
-
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.
|
|
553
|
-
|
|
554
|
-
- Tags can be added one at a time or multiple at once
|
|
555
|
-
- Useful for conditional configuration based on `NODE_ENV` or runtime flags
|
|
556
|
-
- Container can be configured incrementally as the application initializes
|
|
557
|
-
|
|
558
|
-
> [!WARNING]
|
|
559
|
-
> Tags must be added **before** registrations are applied. Scope matching happens at registration time, so adding tags later does not retroactively make providers available.
|
|
560
|
-
|
|
561
|
-
```typescript
|
|
562
|
-
import { bindTo, Container, register, Registration as R, scope } from 'ts-ioc-container';
|
|
563
|
-
|
|
564
|
-
describe('addTags', () => {
|
|
565
|
-
it('should dynamically add tags to enable environment-based registration', () => {
|
|
566
|
-
@register(bindTo('logger'), scope((s) => s.hasTag('development')))
|
|
567
|
-
class ConsoleLogger {
|
|
568
|
-
log(message: string) {
|
|
569
|
-
console.log(`[DEV] ${message}`);
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
@register(bindTo('logger'), scope((s) => s.hasTag('production')))
|
|
574
|
-
class FileLogger {
|
|
575
|
-
log(message: string) {
|
|
576
|
-
console.log(`[PROD] ${message}`);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
// Create container and configure for environment
|
|
581
|
-
const container = new Container();
|
|
582
|
-
const environment = 'development';
|
|
583
|
-
container.addTags(environment); // Add tag dynamically based on environment
|
|
584
|
-
|
|
585
|
-
// Register services after tag is set
|
|
586
|
-
container.addRegistration(R.fromClass(ConsoleLogger)).addRegistration(R.fromClass(FileLogger));
|
|
587
|
-
|
|
588
|
-
// Resolve logger - gets ConsoleLogger because 'development' tag was added
|
|
589
|
-
const logger = container.resolve<ConsoleLogger>('logger');
|
|
590
|
-
expect(logger).toBeInstanceOf(ConsoleLogger);
|
|
591
|
-
});
|
|
592
|
-
|
|
593
|
-
it('should add multiple tags for feature-based configuration', () => {
|
|
594
|
-
@register(bindTo('premiumFeature'), scope((s) => s.hasTag('premium')))
|
|
595
|
-
class PremiumFeature {}
|
|
596
|
-
|
|
597
|
-
@register(bindTo('betaFeature'), scope((s) => s.hasTag('beta')))
|
|
598
|
-
class BetaFeature {}
|
|
599
|
-
|
|
600
|
-
const container = new Container();
|
|
601
|
-
|
|
602
|
-
// Add multiple tags at once
|
|
603
|
-
container.addTags('premium', 'beta', 'experimental');
|
|
604
|
-
|
|
605
|
-
// Verify all tags are present
|
|
606
|
-
expect(container.hasTag('premium')).toBe(true);
|
|
607
|
-
expect(container.hasTag('beta')).toBe(true);
|
|
608
|
-
expect(container.hasTag('experimental')).toBe(true);
|
|
609
|
-
|
|
610
|
-
// Register features after tags are added
|
|
611
|
-
container.addRegistration(R.fromClass(PremiumFeature)).addRegistration(R.fromClass(BetaFeature));
|
|
612
|
-
|
|
613
|
-
// Both features are available because container has both tags
|
|
614
|
-
expect(container.resolve('premiumFeature')).toBeInstanceOf(PremiumFeature);
|
|
615
|
-
expect(container.resolve('betaFeature')).toBeInstanceOf(BetaFeature);
|
|
616
|
-
});
|
|
617
|
-
|
|
618
|
-
it('should affect child scope creation', () => {
|
|
619
|
-
@register(bindTo('service'), scope((s) => s.hasTag('api')))
|
|
620
|
-
class ApiService {
|
|
621
|
-
handleRequest() {
|
|
622
|
-
return 'API response';
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
const appContainer = new Container();
|
|
627
|
-
|
|
628
|
-
// Add tag to parent
|
|
629
|
-
appContainer.addTags('api');
|
|
630
|
-
appContainer.addRegistration(R.fromClass(ApiService));
|
|
631
|
-
|
|
632
|
-
// Create child scopes - they inherit parent's registrations
|
|
633
|
-
const requestScope1 = appContainer.createScope({ tags: ['request'] });
|
|
634
|
-
const requestScope2 = appContainer.createScope({ tags: ['request'] });
|
|
635
|
-
|
|
636
|
-
// Both scopes can access the ApiService from parent
|
|
637
|
-
expect(requestScope1.resolve<ApiService>('service').handleRequest()).toBe('API response');
|
|
638
|
-
expect(requestScope2.resolve<ApiService>('service').handleRequest()).toBe('API response');
|
|
639
|
-
});
|
|
640
|
-
|
|
641
|
-
it('should enable incremental tag addition', () => {
|
|
642
|
-
const container = new Container();
|
|
643
|
-
|
|
644
|
-
// Start with basic tags
|
|
645
|
-
container.addTags('application');
|
|
646
|
-
expect(container.hasTag('application')).toBe(true);
|
|
647
|
-
|
|
648
|
-
// Add more tags as needed
|
|
649
|
-
container.addTags('monitoring', 'logging');
|
|
650
|
-
expect(container.hasTag('monitoring')).toBe(true);
|
|
651
|
-
expect(container.hasTag('logging')).toBe(true);
|
|
652
|
-
|
|
653
|
-
// All tags are retained
|
|
654
|
-
expect(container.hasTag('application')).toBe(true);
|
|
655
|
-
});
|
|
656
|
-
});
|
|
657
|
-
|
|
658
|
-
```
|
|
659
|
-
|
|
660
346
|
### Instances
|
|
661
347
|
|
|
662
348
|
Sometimes you want to get all instances from container and its scopes. For example, when you want to dispose all instances of container.
|
|
@@ -742,108 +428,6 @@ describe('Instances', function () {
|
|
|
742
428
|
|
|
743
429
|
```
|
|
744
430
|
|
|
745
|
-
### Check Registration
|
|
746
|
-
|
|
747
|
-
Sometimes you want to check if a registration with a specific key exists in the container. This is useful for conditional registration logic, validation, and debugging.
|
|
748
|
-
|
|
749
|
-
- `hasRegistration(key)` checks if a registration exists in the current container or parent containers
|
|
750
|
-
- Checks both the current container's registrations and parent container registrations
|
|
751
|
-
- Works with string keys, symbol keys, and token keys
|
|
752
|
-
- Returns false after container disposal
|
|
753
|
-
|
|
754
|
-
```typescript
|
|
755
|
-
import { Container, Registration as R, bindTo, register, SingleToken } from 'ts-ioc-container';
|
|
756
|
-
|
|
757
|
-
/**
|
|
758
|
-
* Container Registration Checking - hasRegistration
|
|
759
|
-
*
|
|
760
|
-
* The `hasRegistration` method allows you to check if a registration with a specific key
|
|
761
|
-
* exists in the current container. This is useful for conditional registration logic,
|
|
762
|
-
* validation, and debugging.
|
|
763
|
-
*
|
|
764
|
-
* Key points:
|
|
765
|
-
* - Checks only the current container's registrations (not parent containers)
|
|
766
|
-
* - Works with string keys, symbol keys, and token keys
|
|
767
|
-
* - Returns false after container disposal
|
|
768
|
-
* - Useful for conditional registration patterns
|
|
769
|
-
*/
|
|
770
|
-
describe('hasRegistration', function () {
|
|
771
|
-
const createAppContainer = () => new Container({ tags: ['application'] });
|
|
772
|
-
|
|
773
|
-
it('should return true when registration exists with string key', function () {
|
|
774
|
-
const container = createAppContainer();
|
|
775
|
-
container.addRegistration(R.fromValue('production').bindToKey('Environment'));
|
|
776
|
-
|
|
777
|
-
expect(container.hasRegistration('Environment')).toBe(true);
|
|
778
|
-
});
|
|
779
|
-
|
|
780
|
-
it('should return false when registration does not exist', function () {
|
|
781
|
-
const container = createAppContainer();
|
|
782
|
-
|
|
783
|
-
expect(container.hasRegistration('NonExistentService')).toBe(false);
|
|
784
|
-
});
|
|
785
|
-
|
|
786
|
-
it('should work with symbol keys', function () {
|
|
787
|
-
const container = createAppContainer();
|
|
788
|
-
const serviceKey = Symbol('IService');
|
|
789
|
-
container.addRegistration(R.fromValue({ name: 'Service' }).bindToKey(serviceKey));
|
|
790
|
-
|
|
791
|
-
expect(container.hasRegistration(serviceKey)).toBe(true);
|
|
792
|
-
});
|
|
793
|
-
|
|
794
|
-
it('should work with token keys', function () {
|
|
795
|
-
const container = createAppContainer();
|
|
796
|
-
const loggerToken = new SingleToken<{ log: (msg: string) => void }>('ILogger');
|
|
797
|
-
container.addRegistration(R.fromValue({ log: () => {} }).bindTo(loggerToken));
|
|
798
|
-
|
|
799
|
-
expect(container.hasRegistration(loggerToken.token)).toBe(true);
|
|
800
|
-
});
|
|
801
|
-
|
|
802
|
-
it('should check current container and parent registrations', function () {
|
|
803
|
-
// Parent container has a registration
|
|
804
|
-
const parent = createAppContainer();
|
|
805
|
-
parent.addRegistration(R.fromValue('parent-config').bindToKey('Config'));
|
|
806
|
-
|
|
807
|
-
// Child scope does not have the registration
|
|
808
|
-
const child = parent.createScope();
|
|
809
|
-
child.addRegistration(R.fromValue('child-service').bindToKey('Service'));
|
|
810
|
-
|
|
811
|
-
// Child should see parent's registration (checks parent as well)
|
|
812
|
-
expect(child.hasRegistration('Config')).toBe(true);
|
|
813
|
-
// Child should see its own registration
|
|
814
|
-
expect(child.hasRegistration('Service')).toBe(true);
|
|
815
|
-
// Parent should see its own registration
|
|
816
|
-
expect(parent.hasRegistration('Config')).toBe(true);
|
|
817
|
-
});
|
|
818
|
-
|
|
819
|
-
it('should work with class-based registrations', function () {
|
|
820
|
-
@register(bindTo('ILogger'))
|
|
821
|
-
class Logger {}
|
|
822
|
-
|
|
823
|
-
const container = createAppContainer();
|
|
824
|
-
container.addRegistration(R.fromClass(Logger));
|
|
825
|
-
|
|
826
|
-
expect(container.hasRegistration('ILogger')).toBe(true);
|
|
827
|
-
});
|
|
828
|
-
|
|
829
|
-
it('should be useful for conditional registration patterns', function () {
|
|
830
|
-
const container = createAppContainer();
|
|
831
|
-
|
|
832
|
-
// Register a base service
|
|
833
|
-
container.addRegistration(R.fromValue('base-service').bindToKey('BaseService'));
|
|
834
|
-
|
|
835
|
-
// Conditionally register an extension only if base exists
|
|
836
|
-
if (container.hasRegistration('BaseService')) {
|
|
837
|
-
container.addRegistration(R.fromValue('extension-service').bindToKey('ExtensionService'));
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
expect(container.hasRegistration('BaseService')).toBe(true);
|
|
841
|
-
expect(container.hasRegistration('ExtensionService')).toBe(true);
|
|
842
|
-
});
|
|
843
|
-
});
|
|
844
|
-
|
|
845
|
-
```
|
|
846
|
-
|
|
847
431
|
### Dispose
|
|
848
432
|
|
|
849
433
|
Sometimes you want to dispose a container or scope. For example, when a request, page, widget, or other local lifecycle ends.
|
|
@@ -2576,6 +2160,9 @@ Sometimes you want to register provider with certain key. This is what `key` is
|
|
|
2576
2160
|
- by default, key is class name
|
|
2577
2161
|
- you can assign the same key to different registrations
|
|
2578
2162
|
|
|
2163
|
+
> [!TIP]
|
|
2164
|
+
> Prefer `SingleToken<T>` over plain string literals as registration keys. Tokens are type-safe, rename-friendly, and prevent typos that only surface at runtime.
|
|
2165
|
+
|
|
2579
2166
|
```typescript
|
|
2580
2167
|
import {
|
|
2581
2168
|
bindTo,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-ioc-container",
|
|
3
|
-
"version": "50.2.
|
|
3
|
+
"version": "50.2.3",
|
|
4
4
|
"description": "Fast, lightweight TypeScript dependency injection container with a clean API, scoped lifecycles, decorators, tokens, hooks, lazy injection, customizable providers, and no global container objects.",
|
|
5
5
|
"workspaces": [
|
|
6
6
|
"docs"
|