ts-ioc-container 47.5.0 → 48.0.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
@@ -1,4 +1,4 @@
1
- # Typescript IoC (Inversion Of Control) container
1
+ # TypeScript Dependency Injection Container
2
2
 
3
3
  ![NPM version:latest](https://img.shields.io/npm/v/ts-ioc-container/latest.svg?style=flat-square)
4
4
  ![npm downloads](https://img.shields.io/npm/dt/ts-ioc-container.svg?style=flat-square)
@@ -7,51 +7,68 @@
7
7
  ![License](https://img.shields.io/npm/l/ts-ioc-container)
8
8
  [![semantic-release](https://img.shields.io/badge/%20%20%20FLO%20-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
9
9
 
10
+ `ts-ioc-container` is a fast, lightweight TypeScript dependency injection
11
+ container for applications that need more than basic constructor injection:
12
+ scoped lifecycles, decorators, typed tokens, lazy dependencies, lifecycle hooks,
13
+ provider pipelines, aliases, and custom injector strategies.
14
+
15
+ Use it as a `tsyringe` alternative when you want a clear API with more flexible
16
+ customization for real application architecture. Use it as an Inversify or
17
+ Awilix alternative when you want a thinner, cleaner API with explicit scopes and
18
+ no global container objects.
19
+
10
20
  ## Advantages
11
- - battle tested :boom:
12
- - written on `typescript`
13
- - simple and lightweight :heart:
14
- - clean API :green_heart:
15
- - supports `tagged scopes`
16
- - fully test covered :100:
17
- - can be used with decorators `@inject`
21
+
22
+ - fast TypeScript dependency resolution
23
+ - lightweight and dependency-minimal
24
+ - clean API for classes, keys, tokens, aliases, and scopes
25
+ - no global container object; pass containers and scopes explicitly
26
+ - supports tagged application, request, transaction, page, and widget scopes
27
+ - decorator support with `@register`, `@inject`, `@onConstruct`, and `@onDispose`
18
28
  - can [inject properties](#inject-property)
19
29
  - can inject [lazy dependencies](#lazy)
20
- - composable and open to extend
30
+ - composable provider and registration pipelines
31
+ - custom injectors, hooks, and provider behavior
32
+ - product behavior covered by executable specs
21
33
 
22
34
  ## Content
23
35
 
24
36
  - [Setup](#setup)
25
37
  - [Quickstart](#quickstart)
26
38
  - [Cheatsheet](#cheatsheet)
39
+ - [Spec-driven workflow](#spec-driven-workflow)
40
+ - [Product capability map](#product-capability-map)
41
+ - [Acceptance specs](#acceptance-specs)
42
+ - [tsyringe alternative](https://igorbabkin.github.io/ts-ioc-container/tsyringe-alternative)
43
+ - [Inversify and Awilix alternative](https://igorbabkin.github.io/ts-ioc-container/inversify-awilix-alternative)
27
44
  - [Recipes](#recipes)
28
45
  - [Container](#container)
29
- - [Basic usage](#basic-usage)
30
- - [Scope](#scope) `tags`
31
- - [Dynamic Tag Management](#dynamic-tag-management) `addTags`
32
- - [Instances](#instances)
33
- - [Dispose](#dispose)
34
- - [Lazy](#lazy) `lazy`
35
- - [Lazy with registerPipe](#lazy-with-registerpipe) `lazy()`
46
+ - [Basic usage](#basic-usage)
47
+ - [Scope](#scope) `tags`
48
+ - [Dynamic Tag Management](#dynamic-tag-management) `addTags`
49
+ - [Instances](#instances)
50
+ - [Dispose](#dispose)
51
+ - [Lazy](#lazy) `lazy`
52
+ - [Lazy with registerPipe](#lazy-with-registerpipe) `lazy()`
36
53
  - [Injector](#injector)
37
- - [Metadata](#metadata) `@inject`
38
- - [Simple](#simple)
39
- - [Proxy](#proxy)
54
+ - [Metadata](#metadata) `@inject`
55
+ - [Simple](#simple)
56
+ - [Proxy](#proxy)
40
57
  - [Provider](#provider) `provider`
41
- - [Singleton](#singleton) `singleton`
42
- - [Arguments](#arguments) `setArgs` `setArgsFn`
43
- - [Visibility](#visibility) `visible`
44
- - [Alias](#alias) `asAlias`
45
- - [Decorator](#decorator) `decorate`
58
+ - [Singleton](#singleton) `singleton`
59
+ - [Arguments](#arguments) `setArgs` `setArgsFn`
60
+ - [Visibility](#visibility) `visible`
61
+ - [Alias](#alias) `asAlias`
62
+ - [Decorator](#decorator) `decorate`
46
63
  - [Registration](#registration) `@register`
47
- - [Key](#key) `asKey`
48
- - [Scope](#scope) `scope`
64
+ - [Key](#key) `asKey`
65
+ - [Scope](#scope) `scope`
49
66
  - [Module](#module)
50
67
  - [Hook](#hook) `@hook`
51
- - [OnConstruct](#onconstruct) `@onConstruct`
52
- - [OnDispose](#ondispose) `@onDispose`
53
- - [Inject Property](#inject-property)
54
- - [Inject Method](#inject-method)
68
+ - [OnConstruct](#onconstruct) `@onConstruct`
69
+ - [OnDispose](#ondispose) `@onDispose`
70
+ - [Inject Property](#inject-property)
71
+ - [Inject Method](#inject-method)
55
72
  - [Mock](#mock)
56
73
  - [Error](#error)
57
74
 
@@ -60,16 +77,19 @@
60
77
  ```shell script
61
78
  npm install ts-ioc-container reflect-metadata
62
79
  ```
80
+
63
81
  ```shell script
64
82
  yarn add ts-ioc-container reflect-metadata
65
83
  ```
66
84
 
67
85
  Just put it in the entrypoint file of your project. It should be the first line of the code.
86
+
68
87
  ```typescript
69
88
  import 'reflect-metadata';
70
89
  ```
71
90
 
72
91
  And `tsconfig.json` should have next options:
92
+
73
93
  ```json
74
94
  {
75
95
  "compilerOptions": {
@@ -105,8 +125,9 @@ container.resolve(App).start();
105
125
 
106
126
  ## Cheatsheet
107
127
 
108
- - Register class with key: `@register(bindTo('Key')) class Service {}`
128
+ - Register class with key (preferred): `@register(bindTo('Key')) class Service {}` then `container.addRegistration(R.fromClass(Service))`
109
129
  - Register value: `R.fromValue(config).bindToKey('Config')`
130
+ - Register factory: `R.fromFn((c) => createX(c)).bindToKey('X')`
110
131
  - Singleton: `@register(singleton())`
111
132
  - Scoped registration: `@register(scope((s) => s.hasTag('request')))`
112
133
  - Resolve by alias: `container.resolveByAlias('Alias')`
@@ -115,12 +136,56 @@ container.resolve(App).start();
115
136
  - Inject decorator: `@inject('Key')`
116
137
  - Property inject: `injectProp(target, 'propName', select.token('Key'))`
117
138
 
139
+ > [!TIP]
140
+ > For classes, prefer the `@register(bindTo('Key'))` decorator over the fluent
141
+ > `R.fromClass(Class).bindToKey('Key')` chain. The decorator co-locates the binding
142
+ > with the class and reads consistently with other registration pipes
143
+ > (`scope`, `singleton`, `setArgsFn`, ...). Use the fluent `bindToKey` chain only
144
+ > for `R.fromValue(...)` and `R.fromFn(...)` (which have no class to decorate)
145
+ > or for third-party classes you don't own.
146
+
147
+ ## Spec-driven workflow
148
+
149
+ Public behavior is described as product capabilities before it is implemented.
150
+ The repository keeps the same chain visible in specs, tests, docs, and this
151
+ README:
152
+
153
+ ```text
154
+ Business capability -> user story -> acceptance criteria -> executable spec test -> docs and ADRs
155
+ ```
156
+
157
+ - ADRs in `docs/adr/` explain durable architecture and process decisions.
158
+ - Specs in `specs/` describe epics, stories, use cases, and acceptance criteria.
159
+ - Acceptance tests in `__tests__/specs/` execute the public contract.
160
+ - README examples in `__tests__/readme/` stay executable and are rendered from `.readme.hbs.md`.
161
+
162
+ ### Product capability map
163
+
164
+ | Capability | User outcome | Spec | Acceptance test |
165
+ | ----------------------- | -------------------------------------------------------------------------- | ---------------------------------------- | ------------------------------------------------- |
166
+ | Dependency resolution | Resolve dependencies by key, class, token, or constructor injection. | `specs/epics/dependency-resolution.md` | `__tests__/specs/dependency-resolution.spec.ts` |
167
+ | Scoped lifecycle | Isolate application, request, transaction, page, or widget lifecycles. | `specs/epics/scoped-lifecycle.md` | `__tests__/specs/scoped-lifecycle.spec.ts` |
168
+ | Dependency registration | Describe classes, values, factories, keys, aliases, and scoped services. | `specs/epics/dependency-registration.md` | `__tests__/specs/dependency-registration.spec.ts` |
169
+ | Provider behavior | Cache, decorate, delay, restrict, or parameterize dependency creation. | `specs/epics/provider-behavior.md` | `__tests__/specs/provider-behavior.spec.ts` |
170
+ | Token-based injection | Make dependency requests explicit, typed, reusable, and composable. | `specs/epics/token-based-injection.md` | `__tests__/specs/token-based-injection.spec.ts` |
171
+ | Injector strategies | Support metadata, simple container, proxy, and custom injection styles. | `specs/epics/injector-strategies.md` | `__tests__/specs/injector-strategies.spec.ts` |
172
+ | Lifecycle hooks | Run initialization, cleanup, property injection, and custom hook behavior. | `specs/epics/lifecycle-hooks.md` | `__tests__/specs/lifecycle-hooks.spec.ts` |
173
+ | Container modules | Package container configuration by feature, environment, or lifecycle. | `specs/epics/container-modules.md` | `__tests__/specs/container-modules.spec.ts` |
174
+ | Metadata utilities | Attach labels, tags, and reusable method behavior to application code. | `specs/epics/metadata-utilities.md` | `__tests__/specs/metadata-utilities.spec.ts` |
175
+ | Errors and boundaries | Make misconfiguration and unsupported usage diagnosable. | `specs/epics/errors-and-boundaries.md` | `__tests__/specs/errors-and-boundaries.spec.ts` |
176
+
177
+ ### Acceptance specs
178
+
179
+ Use `pnpm run test:spec` to run only the executable acceptance specs. These
180
+ tests are intentionally product-facing; lower-level regression and implementation
181
+ tests stay in the feature folders under `__tests__/`.
182
+
118
183
  ## Recipes
119
184
 
120
185
  ### Express/Next handler (per-request scope)
186
+
121
187
  ```typescript
122
- const app = new Container({ tags: ['application'] })
123
- .addRegistration(R.fromClass(Logger).pipe(singleton()));
188
+ const app = new Container({ tags: ['application'] }).addRegistration(R.fromClass(Logger).pipe(singleton()));
124
189
 
125
190
  function handleRequest() {
126
191
  const requestScope = app.createScope({ tags: ['request'] });
@@ -130,6 +195,7 @@ function handleRequest() {
130
195
  ```
131
196
 
132
197
  ### Background worker (singleton client, transient jobs)
198
+
133
199
  ```typescript
134
200
  @register(singleton())
135
201
  class QueueClient {}
@@ -144,10 +210,13 @@ const worker = new Container({ tags: ['worker'] })
144
210
  ```
145
211
 
146
212
  ### Frontend widget/page scope with lazy dependency
213
+
147
214
  ```typescript
148
215
  @register(bindTo('FeatureFlags'), singleton())
149
216
  class FeatureFlags {
150
- load() { /* fetch flags */ }
217
+ load() {
218
+ /* fetch flags */
219
+ }
151
220
  }
152
221
 
153
222
  class Widget {
@@ -160,6 +229,7 @@ const page = new Container({ tags: ['page'] })
160
229
  ```
161
230
 
162
231
  ## Container
232
+
163
233
  `IContainer` consists of:
164
234
 
165
235
  - Provider is dependency factory which creates dependency
@@ -170,7 +240,7 @@ const page = new Container({ tags: ['page'] })
170
240
 
171
241
  ```typescript
172
242
  import 'reflect-metadata';
173
- import { Container, type IContainer, inject, Registration as R, select } from 'ts-ioc-container';
243
+ import { bindTo, Container, type IContainer, inject, register, Registration as R, select } from 'ts-ioc-container';
174
244
 
175
245
  /**
176
246
  * User Management Domain - Basic Dependency Injection
@@ -193,6 +263,7 @@ describe('Basic usage', function () {
193
263
  }
194
264
 
195
265
  // Concrete implementation
266
+ @register(bindTo('IUserRepository'))
196
267
  class UserRepository implements IUserRepository {
197
268
  private users: User[] = [{ id: '1', email: 'admin@example.com', passwordHash: 'hashed_password' }];
198
269
 
@@ -213,7 +284,7 @@ describe('Basic usage', function () {
213
284
  }
214
285
 
215
286
  // Wire up the container
216
- const container = new Container().addRegistration(R.fromClass(UserRepository).bindTo('IUserRepository'));
287
+ const container = new Container().addRegistration(R.fromClass(UserRepository));
217
288
 
218
289
  // Resolve AuthService - UserRepository is automatically injected
219
290
  const authService = container.resolve(AuthService);
@@ -246,6 +317,7 @@ describe('Basic usage', function () {
246
317
  ```
247
318
 
248
319
  ### Scope
320
+
249
321
  Sometimes you need to create a scope of container. For example, when you want to create a scope per request in web application. You can assign tags to scope and provider and resolve dependencies only from certain scope.
250
322
 
251
323
  > [!IMPORTANT]
@@ -349,6 +421,7 @@ describe('Scopes', function () {
349
421
  ```
350
422
 
351
423
  ### Dynamic Tag Management
424
+
352
425
  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.
353
426
 
354
427
  - Tags can be added one at a time or multiple at once
@@ -458,6 +531,7 @@ describe('addTags', () => {
458
531
  ```
459
532
 
460
533
  ### Instances
534
+
461
535
  Sometimes you want to get all instances from container and its scopes. For example, when you want to dispose all instances of container.
462
536
 
463
537
  - you can get instances from container and scope which were created by injector
@@ -542,6 +616,7 @@ describe('Instances', function () {
542
616
  ```
543
617
 
544
618
  ### Check Registration
619
+
545
620
  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.
546
621
 
547
622
  - `hasRegistration(key)` checks if a registration exists in the current container or parent containers
@@ -643,6 +718,7 @@ describe('hasRegistration', function () {
643
718
  ```
644
719
 
645
720
  ### Dispose
721
+
646
722
  Sometimes you want to dispose a container or scope. For example, when a request, page, widget, or other local lifecycle ends.
647
723
 
648
724
  - container can be disposed
@@ -653,7 +729,7 @@ Sometimes you want to dispose a container or scope. For example, when a request,
653
729
 
654
730
  ```typescript
655
731
  import 'reflect-metadata';
656
- import { Container, ContainerDisposedError, Registration as R, select } from 'ts-ioc-container';
732
+ import { bindTo, Container, ContainerDisposedError, register, Registration as R, select } from 'ts-ioc-container';
657
733
 
658
734
  /**
659
735
  * User Management Domain - Resource Cleanup
@@ -672,6 +748,7 @@ import { Container, ContainerDisposedError, Registration as R, select } from 'ts
672
748
  */
673
749
 
674
750
  // Simulates a database connection that must be closed
751
+ @register(bindTo('IDatabase'))
675
752
  class DatabaseConnection {
676
753
  isClosed = false;
677
754
 
@@ -689,9 +766,7 @@ class DatabaseConnection {
689
766
 
690
767
  describe('Disposing', function () {
691
768
  it('should dispose container and prevent further usage', function () {
692
- const appContainer = new Container({ tags: ['application'] }).addRegistration(
693
- R.fromClass(DatabaseConnection).bindTo('IDatabase'),
694
- );
769
+ const appContainer = new Container({ tags: ['application'] }).addRegistration(R.fromClass(DatabaseConnection));
695
770
 
696
771
  // Create a request scope with a database connection
697
772
  const requestScope = appContainer.createScope({ tags: ['request'] });
@@ -714,9 +789,7 @@ describe('Disposing', function () {
714
789
  });
715
790
 
716
791
  it('should clean up request-scoped resources on request end', function () {
717
- const appContainer = new Container({ tags: ['application'] }).addRegistration(
718
- R.fromClass(DatabaseConnection).bindTo('IDatabase'),
719
- );
792
+ const appContainer = new Container({ tags: ['application'] }).addRegistration(R.fromClass(DatabaseConnection));
720
793
 
721
794
  // Simulate Express.js request lifecycle
722
795
  function handleRequest(): { connection: DatabaseConnection; scope: Container } {
@@ -751,6 +824,7 @@ describe('Disposing', function () {
751
824
  ```
752
825
 
753
826
  ### Lazy
827
+
754
828
  Sometimes you want to create dependency only when somebody want to invoke it's method or property. This is what `lazy` is for.
755
829
 
756
830
  - Lazy class instances are wrapped in a JavaScript `Proxy`; the real class instance is created on first property or method access.
@@ -886,9 +960,11 @@ describe('lazy provider', () => {
886
960
  ```
887
961
 
888
962
  ### Lazy with registerPipe
963
+
889
964
  The `lazy()` registerPipe can be used in two ways: with the `@register` decorator or directly on the `Provider` pipe. This allows you to defer expensive class instance initialization until first access.
890
965
 
891
966
  **Use cases:**
967
+
892
968
  - Defer expensive initialization (database connections, SMTP, external APIs)
893
969
  - Conditional features that may not be used
894
970
  - Breaking circular dependencies
@@ -901,7 +977,7 @@ The `lazy()` registerPipe can be used in two ways: with the `@register` decorato
901
977
 
902
978
  ```typescript
903
979
  import 'reflect-metadata';
904
- import { bindTo, Container, inject, lazy, Provider, register, Registration as R, singleton } from 'ts-ioc-container';
980
+ import { args, bindTo, Container, inject, lazy, Provider, register, Registration as R, singleton } from 'ts-ioc-container';
905
981
 
906
982
  /**
907
983
  * Lazy Loading with registerPipe
@@ -1073,13 +1149,9 @@ describe('lazy registerPipe', () => {
1073
1149
  it('should allow selective lazy loading - email lazy, SMS eager', () => {
1074
1150
  const container = new Container()
1075
1151
  // EmailService is lazy - won't connect to SMTP until used
1076
- .addRegistration(
1077
- R.fromClass(EmailService)
1078
- .bindToKey('EmailService')
1079
- .pipe(singleton(), (p) => p.lazy()),
1080
- )
1152
+ .addRegistration(R.fromClass(EmailService).pipe(singleton(), (p) => p.lazy()))
1081
1153
  // SmsService is eager - connects to gateway immediately
1082
- .addRegistration(R.fromClass(SmsService).bindToKey('SmsService').pipe(singleton()))
1154
+ .addRegistration(R.fromClass(SmsService).pipe(singleton()))
1083
1155
  .addRegistration(R.fromClass(NotificationService));
1084
1156
 
1085
1157
  // Resolve NotificationService
@@ -1096,12 +1168,8 @@ describe('lazy registerPipe', () => {
1096
1168
 
1097
1169
  it('should initialize lazy email service when first accessed', () => {
1098
1170
  const container = new Container()
1099
- .addRegistration(
1100
- R.fromClass(EmailService)
1101
- .bindToKey('EmailService')
1102
- .pipe(singleton(), (p) => p.lazy()),
1103
- )
1104
- .addRegistration(R.fromClass(SmsService).bindToKey('SmsService').pipe(singleton()))
1171
+ .addRegistration(R.fromClass(EmailService).pipe(singleton(), (p) => p.lazy()))
1172
+ .addRegistration(R.fromClass(SmsService).pipe(singleton()))
1105
1173
  .addRegistration(R.fromClass(NotificationService));
1106
1174
 
1107
1175
  const notifications = container.resolve<NotificationService>(NotificationService);
@@ -1116,16 +1184,8 @@ describe('lazy registerPipe', () => {
1116
1184
  it('should work with multiple lazy providers', () => {
1117
1185
  const container = new Container()
1118
1186
  // Both services are lazy
1119
- .addRegistration(
1120
- R.fromClass(EmailService)
1121
- .bindToKey('EmailService')
1122
- .pipe(singleton(), (p) => p.lazy()),
1123
- )
1124
- .addRegistration(
1125
- R.fromClass(SmsService)
1126
- .bindToKey('SmsService')
1127
- .pipe(singleton(), (p) => p.lazy()),
1128
- )
1187
+ .addRegistration(R.fromClass(EmailService).pipe(singleton(), (p) => p.lazy()))
1188
+ .addRegistration(R.fromClass(SmsService).pipe(singleton(), (p) => p.lazy()))
1129
1189
  .addRegistration(R.fromClass(NotificationService));
1130
1190
 
1131
1191
  const notifications = container.resolve<NotificationService>(NotificationService);
@@ -1214,10 +1274,11 @@ describe('lazy registerPipe', () => {
1214
1274
  * lazy() works seamlessly with other provider transformations.
1215
1275
  */
1216
1276
  describe('combining with other pipes', () => {
1277
+ @register(bindTo('Config'))
1217
1278
  class ConfigService {
1218
1279
  constructor(
1219
- public apiUrl: string,
1220
- public timeout: number,
1280
+ @inject(args(0)) public apiUrl: string,
1281
+ @inject(args(1)) public timeout: number,
1221
1282
  ) {
1222
1283
  initLog.push(`ConfigService initialized with ${apiUrl}`);
1223
1284
  }
@@ -1226,7 +1287,6 @@ describe('lazy registerPipe', () => {
1226
1287
  it('should combine lazy with args and singleton', () => {
1227
1288
  const container = new Container().addRegistration(
1228
1289
  R.fromClass(ConfigService)
1229
- .bindToKey('Config')
1230
1290
  .pipe(
1231
1291
  (p) => p.setArgs(() => ['https://api.example.com', 5000]),
1232
1292
  (p) => p.lazy(),
@@ -1263,6 +1323,7 @@ describe('lazy registerPipe', () => {
1263
1323
  * - Report generators
1264
1324
  */
1265
1325
  describe('real-world example - feature flags', () => {
1326
+ @register(singleton())
1266
1327
  class FeatureFlagService {
1267
1328
  constructor() {
1268
1329
  initLog.push('FeatureFlagService initialized');
@@ -1302,7 +1363,7 @@ describe('lazy registerPipe', () => {
1302
1363
 
1303
1364
  it('should not initialize premium features for standard users', () => {
1304
1365
  const container = new Container()
1305
- .addRegistration(R.fromClass(FeatureFlagService).bindToKey('FeatureFlagService').pipe(singleton()))
1366
+ .addRegistration(R.fromClass(FeatureFlagService))
1306
1367
  .addRegistration(R.fromClass(PremiumFeature))
1307
1368
  .addRegistration(R.fromClass(Application));
1308
1369
 
@@ -1316,7 +1377,7 @@ describe('lazy registerPipe', () => {
1316
1377
 
1317
1378
  it('should initialize premium features only for premium users', () => {
1318
1379
  const container = new Container()
1319
- .addRegistration(R.fromClass(FeatureFlagService).bindToKey('FeatureFlagService').pipe(singleton()))
1380
+ .addRegistration(R.fromClass(FeatureFlagService))
1320
1381
  .addRegistration(R.fromClass(PremiumFeature))
1321
1382
  .addRegistration(R.fromClass(Application));
1322
1383
 
@@ -1333,6 +1394,7 @@ describe('lazy registerPipe', () => {
1333
1394
  ```
1334
1395
 
1335
1396
  ## Injector
1397
+
1336
1398
  `IInjector` is used to describe how dependencies should be injected to constructor.
1337
1399
 
1338
1400
  - `MetadataInjector` - injects dependencies using `@inject` decorator
@@ -1340,11 +1402,12 @@ describe('lazy registerPipe', () => {
1340
1402
  - `SimpleInjector` - just passes container to constructor with others arguments
1341
1403
 
1342
1404
  ### Metadata
1405
+
1343
1406
  This type of injector uses `@inject` decorator to mark where dependencies should be injected. It's bases on `reflect-metadata` package. That's why I call it `MetadataInjector`.
1344
1407
  Also you can [inject property.](#inject-property)
1345
1408
 
1346
1409
  ```typescript
1347
- import { Container, inject, Registration as R } from 'ts-ioc-container';
1410
+ import { bindTo, Container, inject, register, Registration as R } from 'ts-ioc-container';
1348
1411
 
1349
1412
  /**
1350
1413
  * User Management Domain - Metadata Injection
@@ -1361,6 +1424,7 @@ import { Container, inject, Registration as R } from 'ts-ioc-container';
1361
1424
  * Requires: "experimentalDecorators" and "emitDecoratorMetadata" in tsconfig.
1362
1425
  */
1363
1426
 
1427
+ @register(bindTo('ILogger'))
1364
1428
  class Logger {
1365
1429
  name = 'Logger';
1366
1430
  }
@@ -1379,9 +1443,7 @@ class App {
1379
1443
 
1380
1444
  describe('Metadata Injector', function () {
1381
1445
  it('should inject dependencies using @inject decorator', function () {
1382
- const container = new Container({ tags: ['application'] }).addRegistration(
1383
- R.fromClass(Logger).bindToKey('ILogger'),
1384
- );
1446
+ const container = new Container({ tags: ['application'] }).addRegistration(R.fromClass(Logger));
1385
1447
 
1386
1448
  // Container reads @inject metadata and resolves 'ILogger' for the logger parameter
1387
1449
  const app = container.resolve(App);
@@ -1393,10 +1455,11 @@ describe('Metadata Injector', function () {
1393
1455
  ```
1394
1456
 
1395
1457
  ### Simple
1458
+
1396
1459
  This type of injector just passes container to constructor with others arguments.
1397
1460
 
1398
1461
  ```typescript
1399
- import { Container, type IContainer, Registration as R, SimpleInjector } from 'ts-ioc-container';
1462
+ import { bindTo, Container, type IContainer, register, Registration as R, SimpleInjector } from 'ts-ioc-container';
1400
1463
 
1401
1464
  /**
1402
1465
  * Command Pattern - Simple Injector
@@ -1424,6 +1487,7 @@ class CreateUserCommand implements ICommand {
1424
1487
  constructor(readonly username: string) {}
1425
1488
  }
1426
1489
 
1490
+ @register(bindTo('HandlerCreateUser'))
1427
1491
  class CreateUserHandler implements ICommandHandler {
1428
1492
  handle(command: CreateUserCommand): string {
1429
1493
  return `User ${command.username} created`;
@@ -1433,6 +1497,7 @@ class CreateUserHandler implements ICommandHandler {
1433
1497
  describe('SimpleInjector', function () {
1434
1498
  it('should inject container to allow dynamic resolution (Service Locator pattern)', function () {
1435
1499
  // Dispatcher needs the container to find handlers dynamically based on command type
1500
+ @register(bindTo('Dispatcher'))
1436
1501
  class CommandDispatcher {
1437
1502
  constructor(private container: IContainer) {}
1438
1503
 
@@ -1445,8 +1510,8 @@ describe('SimpleInjector', function () {
1445
1510
  }
1446
1511
 
1447
1512
  const container = new Container({ injector: new SimpleInjector() })
1448
- .addRegistration(R.fromClass(CommandDispatcher).bindToKey('Dispatcher'))
1449
- .addRegistration(R.fromClass(CreateUserHandler).bindToKey('HandlerCreateUser'));
1513
+ .addRegistration(R.fromClass(CommandDispatcher))
1514
+ .addRegistration(R.fromClass(CreateUserHandler));
1450
1515
 
1451
1516
  const dispatcher = container.resolve<CommandDispatcher>('Dispatcher');
1452
1517
  const result = dispatcher.dispatch(new CreateUserCommand('alice'));
@@ -1467,9 +1532,7 @@ describe('SimpleInjector', function () {
1467
1532
  }
1468
1533
  }
1469
1534
 
1470
- const container = new Container({ injector: new SimpleInjector() }).addRegistration(
1471
- R.fromClass(WidgetFactory).bindToKey('WidgetFactory'),
1472
- );
1535
+ const container = new Container({ injector: new SimpleInjector() }).addRegistration(R.fromClass(WidgetFactory));
1473
1536
 
1474
1537
  // Pass "dark" as the theme argument
1475
1538
  const factory = container.resolve<WidgetFactory>('WidgetFactory', { args: ['dark'] });
@@ -1481,16 +1544,18 @@ describe('SimpleInjector', function () {
1481
1544
  ```
1482
1545
 
1483
1546
  ### Proxy
1547
+
1484
1548
  This type of injector injects dependencies as dictionary `Record<string, unknown>`.
1485
1549
 
1486
1550
  - **`args` reserved keyword**: accessing `deps.args` returns the raw `args[]` array passed at resolve time
1487
1551
  - **Alias convention**: any property name containing `"alias"` (case-insensitive) is resolved via `resolveByAlias` instead of `resolve`
1488
1552
 
1489
1553
  ```typescript
1490
- import { Container, ProxyInjector, Registration as R } from 'ts-ioc-container';
1554
+ import { bindTo, Container, ProxyInjector, register, Registration as R } from 'ts-ioc-container';
1491
1555
 
1492
1556
  describe('ProxyInjector', function () {
1493
1557
  it('should inject dependencies as a props object', function () {
1558
+ @register(bindTo('logger'))
1494
1559
  class Logger {
1495
1560
  log(msg: string) {
1496
1561
  return `Logged: ${msg}`;
@@ -1517,9 +1582,9 @@ describe('ProxyInjector', function () {
1517
1582
  }
1518
1583
 
1519
1584
  const container = new Container({ injector: new ProxyInjector() })
1520
- .addRegistration(R.fromClass(Logger).bindToKey('logger'))
1585
+ .addRegistration(R.fromClass(Logger))
1521
1586
  .addRegistration(R.fromValue('USER:').bindToKey('prefix'))
1522
- .addRegistration(R.fromClass(UserController).bindToKey('UserController'));
1587
+ .addRegistration(R.fromClass(UserController));
1523
1588
 
1524
1589
  const controller = container.resolve<UserController>('UserController');
1525
1590
 
@@ -1527,6 +1592,7 @@ describe('ProxyInjector', function () {
1527
1592
  });
1528
1593
 
1529
1594
  it('should expose runtime args through the reserved "args" property', function () {
1595
+ @register(bindTo('database'))
1530
1596
  class Database {}
1531
1597
 
1532
1598
  class ReportGenerator {
@@ -1544,8 +1610,8 @@ describe('ProxyInjector', function () {
1544
1610
  }
1545
1611
 
1546
1612
  const container = new Container({ injector: new ProxyInjector() })
1547
- .addRegistration(R.fromClass(Database).bindToKey('database'))
1548
- .addRegistration(R.fromClass(ReportGenerator).bindToKey('ReportGenerator'));
1613
+ .addRegistration(R.fromClass(Database))
1614
+ .addRegistration(R.fromClass(ReportGenerator));
1549
1615
 
1550
1616
  const generator = container.resolve<ReportGenerator>('ReportGenerator', {
1551
1617
  args: ['PDF'],
@@ -1582,6 +1648,7 @@ describe('ProxyInjector', function () {
1582
1648
  ```
1583
1649
 
1584
1650
  ## Provider
1651
+
1585
1652
  Provider is dependency factory which creates dependency.
1586
1653
 
1587
1654
  - `Provider.fromClass(Logger)`
@@ -1590,10 +1657,12 @@ Provider is dependency factory which creates dependency.
1590
1657
 
1591
1658
  ```typescript
1592
1659
  import {
1660
+ args,
1593
1661
  setArgs,
1594
1662
  setArgsFn,
1595
1663
  bindTo,
1596
1664
  Container,
1665
+ inject,
1597
1666
  lazy,
1598
1667
  Provider,
1599
1668
  register,
@@ -1667,7 +1736,7 @@ describe('Provider', () => {
1667
1736
 
1668
1737
  it('supports args decorator for providing extra arguments', () => {
1669
1738
  class FileService {
1670
- constructor(readonly basePath: string) {}
1739
+ constructor(@inject(args(0)) readonly basePath: string) {}
1671
1740
  }
1672
1741
 
1673
1742
  const container = new Container().register(
@@ -1681,7 +1750,7 @@ describe('Provider', () => {
1681
1750
 
1682
1751
  it('supports argsFn decorator for dynamic arguments', () => {
1683
1752
  class Database {
1684
- constructor(readonly connectionString: string) {}
1753
+ constructor(@inject(args(0)) readonly connectionString: string) {}
1685
1754
  }
1686
1755
 
1687
1756
  const container = new Container().register('DbPath', Provider.fromValue('localhost:5432')).register(
@@ -1735,6 +1804,7 @@ describe('Provider', () => {
1735
1804
  ```
1736
1805
 
1737
1806
  ### Singleton
1807
+
1738
1808
  Sometimes you need to create only one instance of dependency per scope. For example, you want to create only one logger per scope.
1739
1809
 
1740
1810
  - Singleton provider creates only one instance in every scope where it's resolved.
@@ -1827,23 +1897,30 @@ describe('Singleton', function () {
1827
1897
  ```
1828
1898
 
1829
1899
  ### Arguments
1900
+
1830
1901
  Sometimes you want to bind some arguments to provider. This is what `ArgsProvider` is for.
1902
+
1831
1903
  - `provider(setArgs('someArgument'))`
1832
1904
  - `provider(setArgsFn((container) => [container.resolve(Logger), 'someValue']))`
1833
1905
  - `Provider.fromClass(Logger).pipe(setArgs('someArgument'))`
1834
- - Use `setArgsFn(resolveByArgs)` to resolve `InjectionToken` args at resolution time (tokens are resolved, primitives pass through)
1835
1906
 
1836
1907
  ### Token as argument
1837
- When passing args via `token.args(...)`, if an argument is an `InjectionToken` and the provider uses `setArgsFn(resolveByArgs)`, the token is resolved to its value rather than wrapped as a constant.
1908
+
1909
+ When you pass an `InjectionToken` via `token.args(...)`, the container resolves it before the value reaches the constructor. Bare constructors are **not** auto-resolved — wrap a class in `ClassToken` to opt into resolution.
1910
+
1838
1911
  - `ServiceToken.args(ValueToken)` — `ValueToken` is resolved from the container, its value is passed as arg
1912
+ - `ServiceToken.args(new ClassToken(SomeService))` — `SomeService` is constructed by the container
1839
1913
  - `ServiceToken.args('literal')` — literal value passed directly
1840
1914
 
1841
1915
  ### Positional arg injection with `args(index)`
1842
- Use `@inject(args(index))` to explicitly bind a constructor parameter to a positional argument from `ProviderOptions`. This is useful when combining `setArgsFn(resolveByArgs)` with token-based arg passing.
1916
+
1917
+ Constructor parameters that should pick up positional args from `ProviderOptions` must be annotated with `@inject(args(index))`. Parameters without `@inject` resolve to `undefined`.
1918
+
1843
1919
  - `@inject(args(0))` — resolves the first element of the `args` array passed at resolution time
1844
1920
  - Works together with `token.args(...)` to pass typed dependencies through the args context
1845
1921
 
1846
1922
  ### Immutable token chaining
1923
+
1847
1924
  `token.args(...)`, `token.argsFn(...)`, and `token.lazy()` all return **new token instances** — the parent token is never mutated. This allows the same token to be specialized in multiple independent ways (one-way linked list: parent → many children).
1848
1925
 
1849
1926
  ```typescript
@@ -1860,10 +1937,10 @@ import {
1860
1937
  bindTo,
1861
1938
  Container,
1862
1939
  inject,
1940
+ args,
1863
1941
  MultiCache,
1864
1942
  register,
1865
1943
  Registration as R,
1866
- resolveByArgs,
1867
1944
  singleton,
1868
1945
  SingleToken,
1869
1946
  } from 'ts-ioc-container';
@@ -1886,7 +1963,7 @@ describe('ArgsProvider', function () {
1886
1963
  describe('Static Arguments', () => {
1887
1964
  it('can pass static arguments to constructor', function () {
1888
1965
  class FileLogger {
1889
- constructor(public filename: string) {}
1966
+ constructor(@inject(args(0)) public filename: string) {}
1890
1967
  }
1891
1968
 
1892
1969
  // Pre-configure the logger with a filename
@@ -1899,7 +1976,7 @@ describe('ArgsProvider', function () {
1899
1976
 
1900
1977
  it('prioritizes provided args over resolve args', function () {
1901
1978
  class Logger {
1902
- constructor(public context: string) {}
1979
+ constructor(@inject(args(0)) public context: string) {}
1903
1980
  }
1904
1981
 
1905
1982
  // 'FixedContext' wins over any runtime args
@@ -1920,7 +1997,7 @@ describe('ArgsProvider', function () {
1920
1997
  }
1921
1998
 
1922
1999
  class Service {
1923
- constructor(public env: string) {}
2000
+ constructor(@inject(args(0)) public env: string) {}
1924
2001
  }
1925
2002
 
1926
2003
  const root = createContainer()
@@ -1960,17 +2037,18 @@ describe('ArgsProvider', function () {
1960
2037
  name = 'TodoRepository';
1961
2038
  }
1962
2039
 
1963
- // EntityManager is generic - it works with ANY repository
1964
- // We use argsFn(resolveByArgs) to tell it to look at the arguments passed to .args()
2040
+ // EntityManager is generic - it works with ANY repository.
2041
+ // The repository is the first arg passed via `EntityManagerToken.args(...)`.
2042
+ // `@inject(args(0))` reads it; the container auto-resolves InjectionToken args
2043
+ // before they reach the constructor.
1965
2044
  const EntityManagerToken = new SingleToken<EntityManager>('EntityManager');
1966
2045
 
1967
2046
  @register(
1968
2047
  bindTo(EntityManagerToken),
1969
- setArgsFn(resolveByArgs), // <--- Key magic: resolves dependencies based on arguments passed to token
1970
2048
  singleton(MultiCache.fromFirstArg), // Cache unique instance per repository type
1971
2049
  )
1972
2050
  class EntityManager {
1973
- constructor(public repository: IRepository) {}
2051
+ constructor(@inject(args(0)) public repository: IRepository) {}
1974
2052
  }
1975
2053
 
1976
2054
  class App {
@@ -2022,6 +2100,7 @@ describe('ArgsProvider', function () {
2022
2100
  ```
2023
2101
 
2024
2102
  ### Visibility
2103
+
2025
2104
  Sometimes you want to hide dependency if somebody wants to resolve it from certain scope. This uses `ScopeAccessRule` to control access.
2026
2105
 
2027
2106
  > [!IMPORTANT]
@@ -2122,7 +2201,9 @@ describe('Visibility', function () {
2122
2201
  ```
2123
2202
 
2124
2203
  ### Alias
2204
+
2125
2205
  Alias is needed to group keys
2206
+
2126
2207
  - `@register(asAlias('logger'))` helper assigns `logger` alias to registration.
2127
2208
  - `by.aliases((it) => it.has('logger') || it.has('a'))` resolves dependencies which have `logger` or `a` aliases
2128
2209
  - `Provider.fromClass(Logger).pipe(alias('logger'))`
@@ -2267,11 +2348,14 @@ describe('alias', () => {
2267
2348
  ```
2268
2349
 
2269
2350
  ### Decorator
2351
+
2270
2352
  Sometimes you want to decorate you class with some logic. This is what `DecoratorProvider` is for.
2353
+
2271
2354
  - `provider(decorate((instance, container) => new LoggerDecorator(instance)))`
2272
2355
 
2273
2356
  ```typescript
2274
2357
  import {
2358
+ args,
2275
2359
  bindTo,
2276
2360
  Container,
2277
2361
  decorate,
@@ -2325,7 +2409,7 @@ describe('Decorator Pattern', () => {
2325
2409
  // Decorator: Wraps any IRepository with logging behavior
2326
2410
  class LoggingRepository implements IRepository {
2327
2411
  constructor(
2328
- private repository: IRepository,
2412
+ @inject(args(0)) private repository: IRepository,
2329
2413
  @inject(s.token('Logger').lazy()) private logger: Logger,
2330
2414
  ) {}
2331
2415
 
@@ -2381,7 +2465,9 @@ describe('Decorator Pattern', () => {
2381
2465
  ```
2382
2466
 
2383
2467
  ## Registration
2468
+
2384
2469
  Registration is provider factory which registers provider in container.
2470
+
2385
2471
  - `@register(asKey('logger'))`
2386
2472
  - `Registration.fromClass(Logger).to('logger')`
2387
2473
  - `Registration.fromClass(Logger)`
@@ -2389,6 +2475,7 @@ Registration is provider factory which registers provider in container.
2389
2475
  - `Registration.fromFn((container, ...args) => container.resolve(Logger, {args}))`
2390
2476
 
2391
2477
  ### Key
2478
+
2392
2479
  Sometimes you want to register provider with certain key. This is what `key` is for.
2393
2480
 
2394
2481
  - by default, key is class name
@@ -2481,7 +2568,9 @@ describe('Registration module', function () {
2481
2568
  ```
2482
2569
 
2483
2570
  ### Scope
2571
+
2484
2572
  Sometimes you need to register provider only in scope which matches to certain condition and their sub scopes. Especially if you want to register dependency as singleton for some tags, for example `root`. This uses `ScopeMatchRule` to determine which scopes should have the provider.
2573
+
2485
2574
  - `@register(scope((container) => container.hasTag('root'))` - register provider only in root scope
2486
2575
  - `Registration.fromClass(Logger).when((container) => container.hasTag('root'))`
2487
2576
 
@@ -2523,6 +2612,7 @@ describe('ScopeProvider', function () {
2523
2612
  ```
2524
2613
 
2525
2614
  ## Module
2615
+
2526
2616
  Sometimes you want to encapsulate registration logic in separate module. This is what `IContainerModule` is for.
2527
2617
 
2528
2618
  ```typescript
@@ -2646,9 +2736,11 @@ describe('Container Modules', function () {
2646
2736
  ```
2647
2737
 
2648
2738
  ## Hook
2739
+
2649
2740
  Sometimes you need to invoke methods after construct or dispose of class. This is what hooks are for.
2650
2741
 
2651
2742
  ### OnConstruct
2743
+
2652
2744
  ```typescript
2653
2745
  import {
2654
2746
  AddOnConstructHookModule,
@@ -2709,6 +2801,7 @@ describe('onConstruct', function () {
2709
2801
  ```
2710
2802
 
2711
2803
  ### OnDispose
2804
+
2712
2805
  ```typescript
2713
2806
  import {
2714
2807
  AddOnDisposeHookModule,
@@ -2823,6 +2916,10 @@ describe('inject property', () => {
2823
2916
 
2824
2917
  ## Error
2825
2918
 
2919
+ The product-facing error contract is described in
2920
+ `specs/epics/errors-and-boundaries.md` and executed by
2921
+ `__tests__/specs/errors-and-boundaries.spec.ts`.
2922
+
2826
2923
  - [DependencyNotFoundError.ts](..%2F..%2Flib%2Ferrors%2FDependencyNotFoundError.ts)
2827
2924
  - [MethodNotImplementedError.ts](..%2F..%2Flib%2Ferrors%2FMethodNotImplementedError.ts)
2828
2925
  - [DependencyMissingKeyError.ts](..%2F..%2Flib%2Ferrors%2FDependencyMissingKeyError.ts)
package/cjm/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SingleAliasToken = exports.toGroupAlias = exports.GroupAliasToken = exports.InjectionToken = exports.HooksRunner = exports.AddOnDisposeHookModule = exports.onDispose = exports.onDisposeHooksRunner = exports.AddOnConstructHookModule = exports.onConstruct = exports.onConstructHooksRunner = exports.injectProp = exports.createHookContext = exports.createHookContextFactory = exports.HookContext = exports.hasHooks = exports.hook = exports.getHooks = exports.UnexpectedHookResultError = exports.ContainerDisposedError = exports.MethodNotImplementedError = exports.DependencyMissingKeyError = exports.DependencyNotFoundError = exports.Registration = exports.bindTo = exports.register = exports.scope = exports.decorate = exports.MultiCache = exports.multiCache = exports.SingletonProvider = exports.singleton = exports.Provider = exports.resolveByArgs = exports.ProviderDecorator = exports.setArgs = exports.setArgsFn = exports.lazy = exports.scopeAccess = exports.ProxyInjector = exports.SimpleInjector = exports.MetadataInjector = exports.Injector = exports.argsFn = exports.args = exports.resolveArgs = exports.inject = exports.EmptyContainer = exports.Container = exports.isDependencyKey = void 0;
4
- exports.resolveConstructor = exports.Is = exports.pipe = exports.select = exports.once = exports.shallowCache = exports.debounce = exports.throttle = exports.handleAsyncError = exports.handleError = exports.getMethodTags = exports.methodTag = exports.getMethodLabels = exports.methodLabel = exports.getMethodMeta = exports.methodMeta = exports.getParamTags = exports.paramTag = exports.getParamLabels = exports.paramLabel = exports.getParamMeta = exports.paramMeta = exports.getClassTags = exports.classTag = exports.getClassLabels = exports.classLabel = exports.getClassMeta = exports.classMeta = exports.GroupInstanceToken = exports.ConstantToken = exports.FunctionToken = exports.SingleToken = exports.ClassToken = exports.toSingleAlias = void 0;
3
+ exports.toSingleAlias = exports.SingleAliasToken = exports.toGroupAlias = exports.GroupAliasToken = exports.InjectionToken = exports.HooksRunner = exports.AddOnDisposeHookModule = exports.onDispose = exports.onDisposeHooksRunner = exports.AddOnConstructHookModule = exports.onConstruct = exports.onConstructHooksRunner = exports.injectProp = exports.createHookContext = exports.createHookContextFactory = exports.HookContext = exports.hasHooks = exports.hook = exports.getHooks = exports.UnexpectedHookResultError = exports.ContainerDisposedError = exports.MethodNotImplementedError = exports.DependencyMissingKeyError = exports.DependencyNotFoundError = exports.Registration = exports.bindTo = exports.register = exports.scope = exports.decorate = exports.MultiCache = exports.multiCache = exports.SingletonProvider = exports.singleton = exports.Provider = exports.ProviderDecorator = exports.setArgs = exports.setArgsFn = exports.lazy = exports.scopeAccess = exports.ProxyInjector = exports.SimpleInjector = exports.MetadataInjector = exports.Injector = exports.argsFn = exports.args = exports.resolveArgs = exports.inject = exports.EmptyContainer = exports.Container = exports.isDependencyKey = void 0;
4
+ exports.resolveConstructor = exports.Is = exports.pipe = exports.select = exports.once = exports.shallowCache = exports.debounce = exports.throttle = exports.handleAsyncError = exports.handleError = exports.getMethodTags = exports.methodTag = exports.getMethodLabels = exports.methodLabel = exports.getMethodMeta = exports.methodMeta = exports.getParamTags = exports.paramTag = exports.getParamLabels = exports.paramLabel = exports.getParamMeta = exports.paramMeta = exports.getClassTags = exports.classTag = exports.getClassLabels = exports.classLabel = exports.getClassMeta = exports.classMeta = exports.GroupInstanceToken = exports.ConstantToken = exports.FunctionToken = exports.SingleToken = exports.ClassToken = void 0;
5
5
  // Containers
6
6
  var IContainer_1 = require("./container/IContainer");
7
7
  Object.defineProperty(exports, "isDependencyKey", { enumerable: true, get: function () { return IContainer_1.isDependencyKey; } });
@@ -30,7 +30,6 @@ Object.defineProperty(exports, "lazy", { enumerable: true, get: function () { re
30
30
  Object.defineProperty(exports, "setArgsFn", { enumerable: true, get: function () { return IProvider_1.setArgsFn; } });
31
31
  Object.defineProperty(exports, "setArgs", { enumerable: true, get: function () { return IProvider_1.setArgs; } });
32
32
  Object.defineProperty(exports, "ProviderDecorator", { enumerable: true, get: function () { return IProvider_1.ProviderDecorator; } });
33
- Object.defineProperty(exports, "resolveByArgs", { enumerable: true, get: function () { return IProvider_1.resolveByArgs; } });
34
33
  var Provider_1 = require("./provider/Provider");
35
34
  Object.defineProperty(exports, "Provider", { enumerable: true, get: function () { return Provider_1.Provider; } });
36
35
  var SingletonProvider_1 = require("./provider/SingletonProvider");
@@ -4,7 +4,6 @@ exports.resolveArgs = exports.argsFn = exports.args = exports.inject = void 0;
4
4
  const InjectionToken_1 = require("../token/InjectionToken");
5
5
  const ConstantToken_1 = require("../token/ConstantToken");
6
6
  const toToken_1 = require("../token/toToken");
7
- const array_1 = require("../utils/array");
8
7
  const basic_1 = require("../utils/basic");
9
8
  const parameter_1 = require("../metadata/parameter");
10
9
  const hookMetaKey = (methodName = 'constructor') => `inject:${methodName}`;
@@ -18,14 +17,11 @@ const args = (index) => (c, { args = [] }) => {
18
17
  exports.args = args;
19
18
  const argsFn = (fn) => (c, options) => fn(options.args ?? []);
20
19
  exports.argsFn = argsFn;
20
+ const resolveTokens = (scope, deps) => deps.map((v) => ((0, InjectionToken_1.isInjectionToken)(v) ? v : new ConstantToken_1.ConstantToken(v))).map((t) => t.resolve(scope));
21
21
  const resolveArgs = (Target, methodName) => {
22
- const argsTokens = (0, parameter_1.getParamMeta)(hookMetaKey(methodName), Target);
22
+ const argsMetaTokens = (0, parameter_1.getParamMeta)(hookMetaKey(methodName), Target);
23
23
  return (scope, { args = [], lazy }) => {
24
- const depsTokens = args.map((v) => {
25
- return !(0, InjectionToken_1.isInjectionToken)(v) ? new ConstantToken_1.ConstantToken(v) : v;
26
- });
27
- const allTokens = (0, array_1.fillEmptyIndexes)(argsTokens, depsTokens);
28
- return allTokens.map((fn) => fn.resolve(scope, { args, lazy }));
24
+ return argsMetaTokens.map((fn) => fn.resolve(scope, { args: resolveTokens(scope, args), lazy }));
29
25
  };
30
26
  };
31
27
  exports.resolveArgs = resolveArgs;
@@ -1,23 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ProviderDecorator = exports.lazy = exports.scopeAccess = exports.resolveByArgs = exports.setArgsFn = exports.setArgs = void 0;
3
+ exports.ProviderDecorator = exports.lazy = exports.scopeAccess = exports.setArgsFn = exports.setArgs = void 0;
4
4
  const ProviderPipe_1 = require("./ProviderPipe");
5
- const InjectionToken_1 = require("../token/InjectionToken");
6
- const basic_1 = require("../utils/basic");
7
5
  const setArgs = (...extraArgs) => (0, ProviderPipe_1.registerPipe)((p) => p.setArgs(() => extraArgs));
8
6
  exports.setArgs = setArgs;
9
7
  const setArgsFn = (fn) => (0, ProviderPipe_1.registerPipe)((p) => p.setArgs(fn));
10
8
  exports.setArgsFn = setArgsFn;
11
- const resolveByArgs = (s, { args = [] } = {}) => args.map((d) => {
12
- if (d instanceof InjectionToken_1.InjectionToken) {
13
- return d.resolve(s);
14
- }
15
- if (basic_1.Is.constructor(d)) {
16
- return s.resolve(d);
17
- }
18
- return d;
19
- });
20
- exports.resolveByArgs = resolveByArgs;
21
9
  const scopeAccess = (rule) => (0, ProviderPipe_1.registerPipe)((p) => p.setAccessRule(rule));
22
10
  exports.scopeAccess = scopeAccess;
23
11
  const lazy = () => (0, ProviderPipe_1.registerPipe)((p) => p.lazy());
@@ -1,20 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Filter = void 0;
4
- exports.fillEmptyIndexes = fillEmptyIndexes;
5
4
  exports.Filter = {
6
5
  exclude: (arr) => {
7
6
  const excludeSet = arr instanceof Array ? new Set(arr) : arr;
8
7
  return (v) => !excludeSet.has(v);
9
8
  },
10
9
  };
11
- function fillEmptyIndexes(baseArr, insertArr) {
12
- const a = [...baseArr];
13
- const b = [...insertArr];
14
- for (let i = 0; i < a.length; i++) {
15
- if (a[i] === undefined) {
16
- a[i] = b.shift();
17
- }
18
- }
19
- return a.concat(b);
20
- }
package/esm/index.js CHANGED
@@ -9,7 +9,7 @@ export { MetadataInjector } from './injector/MetadataInjector';
9
9
  export { SimpleInjector } from './injector/SimpleInjector';
10
10
  export { ProxyInjector } from './injector/ProxyInjector';
11
11
  // Providers
12
- export { scopeAccess, lazy, setArgsFn, setArgs, ProviderDecorator, resolveByArgs, } from './provider/IProvider';
12
+ export { scopeAccess, lazy, setArgsFn, setArgs, ProviderDecorator, } from './provider/IProvider';
13
13
  export { Provider } from './provider/Provider';
14
14
  export { singleton, SingletonProvider } from './provider/SingletonProvider';
15
15
  export { multiCache, MultiCache } from './provider/Cache';
@@ -1,7 +1,6 @@
1
1
  import { isInjectionToken } from '../token/InjectionToken';
2
2
  import { ConstantToken } from '../token/ConstantToken';
3
3
  import { toToken } from '../token/toToken';
4
- import { fillEmptyIndexes } from '../utils/array';
5
4
  import { Is } from '../utils/basic';
6
5
  import { getParamMeta, paramMeta } from '../metadata/parameter';
7
6
  const hookMetaKey = (methodName = 'constructor') => `inject:${methodName}`;
@@ -12,13 +11,10 @@ export const args = (index) => (c, { args = [] }) => {
12
11
  return args[index];
13
12
  };
14
13
  export const argsFn = (fn) => (c, options) => fn(options.args ?? []);
14
+ const resolveTokens = (scope, deps) => deps.map((v) => (isInjectionToken(v) ? v : new ConstantToken(v))).map((t) => t.resolve(scope));
15
15
  export const resolveArgs = (Target, methodName) => {
16
- const argsTokens = getParamMeta(hookMetaKey(methodName), Target);
16
+ const argsMetaTokens = getParamMeta(hookMetaKey(methodName), Target);
17
17
  return (scope, { args = [], lazy }) => {
18
- const depsTokens = args.map((v) => {
19
- return !isInjectionToken(v) ? new ConstantToken(v) : v;
20
- });
21
- const allTokens = fillEmptyIndexes(argsTokens, depsTokens);
22
- return allTokens.map((fn) => fn.resolve(scope, { args, lazy }));
18
+ return argsMetaTokens.map((fn) => fn.resolve(scope, { args: resolveTokens(scope, args), lazy }));
23
19
  };
24
20
  };
@@ -1,17 +1,6 @@
1
1
  import { isProviderPipe, registerPipe } from './ProviderPipe';
2
- import { InjectionToken } from '../token/InjectionToken';
3
- import { Is } from '../utils/basic';
4
2
  export const setArgs = (...extraArgs) => registerPipe((p) => p.setArgs(() => extraArgs));
5
3
  export const setArgsFn = (fn) => registerPipe((p) => p.setArgs(fn));
6
- export const resolveByArgs = (s, { args = [] } = {}) => args.map((d) => {
7
- if (d instanceof InjectionToken) {
8
- return d.resolve(s);
9
- }
10
- if (Is.constructor(d)) {
11
- return s.resolve(d);
12
- }
13
- return d;
14
- });
15
4
  export const scopeAccess = (rule) => registerPipe((p) => p.setAccessRule(rule));
16
5
  export const lazy = () => registerPipe((p) => p.lazy());
17
6
  export class ProviderDecorator {
@@ -4,13 +4,3 @@ export const Filter = {
4
4
  return (v) => !excludeSet.has(v);
5
5
  },
6
6
  };
7
- export function fillEmptyIndexes(baseArr, insertArr) {
8
- const a = [...baseArr];
9
- const b = [...insertArr];
10
- for (let i = 0; i < a.length; i++) {
11
- if (a[i] === undefined) {
12
- a[i] = b.shift();
13
- }
14
- }
15
- return a.concat(b);
16
- }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ts-ioc-container",
3
- "version": "47.5.0",
4
- "description": "Typescript IoC container",
3
+ "version": "48.0.0",
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"
7
7
  ],
@@ -21,10 +21,24 @@
21
21
  "inversion-of-control",
22
22
  "container",
23
23
  "typescript",
24
+ "typescript-di",
25
+ "typescript-ioc",
26
+ "dependency-injection-container",
24
27
  "ioc",
25
28
  "di",
29
+ "ioc-container",
30
+ "tsyringe-alternative",
31
+ "inversify-alternative",
32
+ "awilix-alternative",
33
+ "clean-api",
34
+ "no-global-container",
26
35
  "scope",
36
+ "scoped-dependencies",
27
37
  "hook",
38
+ "lifecycle-hooks",
39
+ "decorators",
40
+ "lazy-injection",
41
+ "service-container",
28
42
  "inject"
29
43
  ],
30
44
  "directories": {
@@ -47,12 +61,14 @@
47
61
  "generate:docs": "scripts/generate-readme/generate-readme.ts && git add README.md",
48
62
  "build": "npm run build:cjm && npm run build:esm && npm run build:types",
49
63
  "test": "vitest run",
64
+ "test:spec": "vitest run __tests__/specs",
65
+ "bench:spec": "vitest bench __benchmarks__/specs/*.bench.ts",
50
66
  "test:coverage": "vitest run --coverage --coverage.reporter=lcov",
51
67
  "type-check": "tsc --noEmit",
52
68
  "type-check:watch": "tsc --noEmit --watch",
53
69
  "commit": "cz",
54
70
  "format": "prettier --write \"**/*.ts\"",
55
- "lint": "eslint lib/**/*.ts __tests__/**/*.ts scripts/**/*.ts",
71
+ "lint": "eslint lib/**/*.ts __tests__/**/*.ts __benchmarks__/**/*.ts scripts/**/*.ts",
56
72
  "lint:fix": "npm run lint --fix",
57
73
  "audit": "npm audit",
58
74
  "prepare": "husky",
@@ -90,6 +106,7 @@
90
106
  "rimraf": "6.1.3",
91
107
  "semantic-release": "^25.0.2",
92
108
  "tsx": "^4.21.0",
109
+ "tsyringe": "^4.10.0",
93
110
  "typescript": "5.8.3",
94
111
  "unplugin-swc": "^1.5.9",
95
112
  "vite": "^6.4.1",
@@ -6,7 +6,7 @@ export { type IInjector, type InjectOptions, type IInjectFnResolver, Injector }
6
6
  export { MetadataInjector } from './injector/MetadataInjector';
7
7
  export { SimpleInjector } from './injector/SimpleInjector';
8
8
  export { ProxyInjector } from './injector/ProxyInjector';
9
- export { type ResolveDependency, type IProvider, scopeAccess, lazy, setArgsFn, setArgs, type ArgsFn, ProviderDecorator, resolveByArgs, type IMapper, type ProviderOptions, } from './provider/IProvider';
9
+ export { type ResolveDependency, type IProvider, scopeAccess, lazy, setArgsFn, setArgs, type ArgsFn, ProviderDecorator, type IMapper, type ProviderOptions, } from './provider/IProvider';
10
10
  export { Provider } from './provider/Provider';
11
11
  export { singleton, SingletonProvider } from './provider/SingletonProvider';
12
12
  export { type Cache, multiCache, MultiCache } from './provider/Cache';
@@ -26,7 +26,6 @@ export interface IProvider<T = any> {
26
26
  }
27
27
  export declare const setArgs: <T>(...extraArgs: unknown[]) => ProviderPipe<T>;
28
28
  export declare const setArgsFn: <T>(fn: ArgsFn) => ProviderPipe<T>;
29
- export declare const resolveByArgs: ArgsFn;
30
29
  export declare const scopeAccess: <T>(rule: ScopeAccessRule) => ProviderPipe<T>;
31
30
  export declare const lazy: <T>() => ProviderPipe<T>;
32
31
  export declare abstract class ProviderDecorator<T> implements IProvider<T> {
@@ -1,4 +1,3 @@
1
1
  export declare const Filter: {
2
2
  exclude: <T>(arr: Set<T> | T[]) => (v: T) => boolean;
3
3
  };
4
- export declare function fillEmptyIndexes<T>(baseArr: (T | undefined)[], insertArr: T[]): T[];