ts-ioc-container 47.4.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.
Files changed (45) hide show
  1. package/README.md +260 -145
  2. package/cjm/container/Container.js +1 -1
  3. package/cjm/index.js +2 -2
  4. package/cjm/injector/ProxyInjector.js +5 -12
  5. package/cjm/injector/inject.js +7 -6
  6. package/cjm/provider/IProvider.js +1 -13
  7. package/cjm/provider/Provider.js +2 -2
  8. package/cjm/registration/Registration.js +2 -2
  9. package/cjm/token/ClassToken.js +27 -17
  10. package/cjm/token/FunctionToken.js +25 -15
  11. package/cjm/token/GroupAliasToken.js +18 -17
  12. package/cjm/token/InjectionToken.js +5 -0
  13. package/cjm/token/SingleAliasToken.js +18 -17
  14. package/cjm/token/SingleToken.js +20 -16
  15. package/cjm/utils/array.js +0 -11
  16. package/esm/container/Container.js +1 -1
  17. package/esm/index.js +2 -2
  18. package/esm/injector/ProxyInjector.js +5 -12
  19. package/esm/injector/inject.js +5 -5
  20. package/esm/provider/IProvider.js +0 -11
  21. package/esm/provider/Provider.js +2 -2
  22. package/esm/registration/Registration.js +2 -2
  23. package/esm/token/ClassToken.js +28 -18
  24. package/esm/token/FunctionToken.js +25 -15
  25. package/esm/token/GroupAliasToken.js +19 -18
  26. package/esm/token/InjectionToken.js +4 -0
  27. package/esm/token/SingleAliasToken.js +19 -18
  28. package/esm/token/SingleToken.js +21 -17
  29. package/esm/utils/array.js +0 -10
  30. package/package.json +20 -3
  31. package/typings/hooks/hook.d.ts +1 -1
  32. package/typings/index.d.ts +2 -2
  33. package/typings/injector/IInjector.d.ts +1 -2
  34. package/typings/injector/ProxyInjector.d.ts +1 -1
  35. package/typings/injector/inject.d.ts +2 -1
  36. package/typings/provider/IProvider.d.ts +1 -2
  37. package/typings/provider/Provider.d.ts +1 -1
  38. package/typings/registration/Registration.d.ts +3 -1
  39. package/typings/token/ClassToken.d.ts +13 -8
  40. package/typings/token/FunctionToken.d.ts +10 -7
  41. package/typings/token/GroupAliasToken.d.ts +13 -8
  42. package/typings/token/InjectionToken.d.ts +4 -8
  43. package/typings/token/SingleAliasToken.d.ts +13 -8
  44. package/typings/token/SingleToken.d.ts +14 -9
  45. package/typings/utils/array.d.ts +0 -1
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,11 +317,17 @@ 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
- - NOTICE: remember that when scope doesn't have dependency then it will be resolved from parent container
252
- - NOTICE: when you create a scope of container then all providers are cloned to new scope. For that reason every provider has methods `clone` and `isValid` to clone itself and check if it's valid for certain scope accordingly.
253
- - NOTICE: when you create a scope then we clone ONLY tags-matched providers.
323
+ > [!IMPORTANT]
324
+ > Scope creation is snapshot-like. Existing parent registrations are applied to the child scope when `createScope()` is called, and when a scope doesn't have a dependency it resolves from the parent container.
325
+
326
+ > [!WARNING]
327
+ > Registrations added to a parent after a child scope has already been created are not automatically applied to that existing child. Create a new scope or add the registration to the child explicitly.
328
+
329
+ > [!WARNING]
330
+ > Scope matching happens when a registration is applied to a container. Only registrations whose scope rules match that container are registered there.
254
331
 
255
332
  ```typescript
256
333
  import 'reflect-metadata';
@@ -344,13 +421,16 @@ describe('Scopes', function () {
344
421
  ```
345
422
 
346
423
  ### Dynamic Tag Management
424
+
347
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.
348
426
 
349
427
  - Tags can be added one at a time or multiple at once
350
- - Tags must be added **before** registrations are applied - scope matching happens at registration time
351
428
  - Useful for conditional configuration based on `NODE_ENV` or runtime flags
352
429
  - Container can be configured incrementally as the application initializes
353
430
 
431
+ > [!WARNING]
432
+ > Tags must be added **before** registrations are applied. Scope matching happens at registration time, so adding tags later does not retroactively make providers available.
433
+
354
434
  ```typescript
355
435
  import { bindTo, Container, register, Registration as R, scope } from 'ts-ioc-container';
356
436
 
@@ -451,6 +531,7 @@ describe('addTags', () => {
451
531
  ```
452
532
 
453
533
  ### Instances
534
+
454
535
  Sometimes you want to get all instances from container and its scopes. For example, when you want to dispose all instances of container.
455
536
 
456
537
  - you can get instances from container and scope which were created by injector
@@ -535,6 +616,7 @@ describe('Instances', function () {
535
616
  ```
536
617
 
537
618
  ### Check Registration
619
+
538
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.
539
621
 
540
622
  - `hasRegistration(key)` checks if a registration exists in the current container or parent containers
@@ -636,15 +718,18 @@ describe('hasRegistration', function () {
636
718
  ```
637
719
 
638
720
  ### Dispose
639
- Sometimes you want to dispose container and all its scopes. For example, when you want to prevent memory leaks. Or you want to ensure that nobody can use container after it was disposed.
721
+
722
+ Sometimes you want to dispose a container or scope. For example, when a request, page, widget, or other local lifecycle ends.
640
723
 
641
724
  - container can be disposed
642
- - when container is disposed then all scopes are disposed too
643
- - when container is disposed then it unregisters all providers and remove all instances
725
+ - when container is disposed then it runs its `onDispose` hooks, unregisters its providers, removes its local instances, and detaches from its parent
726
+
727
+ > [!IMPORTANT]
728
+ > Dispose is local to the container being disposed. Child scopes are not disposed automatically; dispose them explicitly when their own lifecycle ends.
644
729
 
645
730
  ```typescript
646
731
  import 'reflect-metadata';
647
- 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';
648
733
 
649
734
  /**
650
735
  * User Management Domain - Resource Cleanup
@@ -663,6 +748,7 @@ import { Container, ContainerDisposedError, Registration as R, select } from 'ts
663
748
  */
664
749
 
665
750
  // Simulates a database connection that must be closed
751
+ @register(bindTo('IDatabase'))
666
752
  class DatabaseConnection {
667
753
  isClosed = false;
668
754
 
@@ -680,9 +766,7 @@ class DatabaseConnection {
680
766
 
681
767
  describe('Disposing', function () {
682
768
  it('should dispose container and prevent further usage', function () {
683
- const appContainer = new Container({ tags: ['application'] }).addRegistration(
684
- R.fromClass(DatabaseConnection).bindTo('IDatabase'),
685
- );
769
+ const appContainer = new Container({ tags: ['application'] }).addRegistration(R.fromClass(DatabaseConnection));
686
770
 
687
771
  // Create a request scope with a database connection
688
772
  const requestScope = appContainer.createScope({ tags: ['request'] });
@@ -705,9 +789,7 @@ describe('Disposing', function () {
705
789
  });
706
790
 
707
791
  it('should clean up request-scoped resources on request end', function () {
708
- const appContainer = new Container({ tags: ['application'] }).addRegistration(
709
- R.fromClass(DatabaseConnection).bindTo('IDatabase'),
710
- );
792
+ const appContainer = new Container({ tags: ['application'] }).addRegistration(R.fromClass(DatabaseConnection));
711
793
 
712
794
  // Simulate Express.js request lifecycle
713
795
  function handleRequest(): { connection: DatabaseConnection; scope: Container } {
@@ -742,8 +824,17 @@ describe('Disposing', function () {
742
824
  ```
743
825
 
744
826
  ### Lazy
827
+
745
828
  Sometimes you want to create dependency only when somebody want to invoke it's method or property. This is what `lazy` is for.
746
829
 
830
+ - Lazy class instances are wrapped in a JavaScript `Proxy`; the real class instance is created on first property or method access.
831
+
832
+ > [!IMPORTANT]
833
+ > `lazy` is designed only for class instances resolved from class providers.
834
+
835
+ > [!WARNING]
836
+ > Do not use `lazy` for primitive values, plain values, functions, or non-class provider results.
837
+
747
838
  ```typescript
748
839
  import 'reflect-metadata';
749
840
  import { Container, inject, register, Registration as R, select as s, singleton } from 'ts-ioc-container';
@@ -869,9 +960,11 @@ describe('lazy provider', () => {
869
960
  ```
870
961
 
871
962
  ### Lazy with registerPipe
872
- 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 service initialization until first access.
963
+
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.
873
965
 
874
966
  **Use cases:**
967
+
875
968
  - Defer expensive initialization (database connections, SMTP, external APIs)
876
969
  - Conditional features that may not be used
877
970
  - Breaking circular dependencies
@@ -884,7 +977,7 @@ The `lazy()` registerPipe can be used in two ways: with the `@register` decorato
884
977
 
885
978
  ```typescript
886
979
  import 'reflect-metadata';
887
- 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';
888
981
 
889
982
  /**
890
983
  * Lazy Loading with registerPipe
@@ -1056,13 +1149,9 @@ describe('lazy registerPipe', () => {
1056
1149
  it('should allow selective lazy loading - email lazy, SMS eager', () => {
1057
1150
  const container = new Container()
1058
1151
  // EmailService is lazy - won't connect to SMTP until used
1059
- .addRegistration(
1060
- R.fromClass(EmailService)
1061
- .bindToKey('EmailService')
1062
- .pipe(singleton(), (p) => p.lazy()),
1063
- )
1152
+ .addRegistration(R.fromClass(EmailService).pipe(singleton(), (p) => p.lazy()))
1064
1153
  // SmsService is eager - connects to gateway immediately
1065
- .addRegistration(R.fromClass(SmsService).bindToKey('SmsService').pipe(singleton()))
1154
+ .addRegistration(R.fromClass(SmsService).pipe(singleton()))
1066
1155
  .addRegistration(R.fromClass(NotificationService));
1067
1156
 
1068
1157
  // Resolve NotificationService
@@ -1079,12 +1168,8 @@ describe('lazy registerPipe', () => {
1079
1168
 
1080
1169
  it('should initialize lazy email service when first accessed', () => {
1081
1170
  const container = new Container()
1082
- .addRegistration(
1083
- R.fromClass(EmailService)
1084
- .bindToKey('EmailService')
1085
- .pipe(singleton(), (p) => p.lazy()),
1086
- )
1087
- .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()))
1088
1173
  .addRegistration(R.fromClass(NotificationService));
1089
1174
 
1090
1175
  const notifications = container.resolve<NotificationService>(NotificationService);
@@ -1099,16 +1184,8 @@ describe('lazy registerPipe', () => {
1099
1184
  it('should work with multiple lazy providers', () => {
1100
1185
  const container = new Container()
1101
1186
  // Both services are lazy
1102
- .addRegistration(
1103
- R.fromClass(EmailService)
1104
- .bindToKey('EmailService')
1105
- .pipe(singleton(), (p) => p.lazy()),
1106
- )
1107
- .addRegistration(
1108
- R.fromClass(SmsService)
1109
- .bindToKey('SmsService')
1110
- .pipe(singleton(), (p) => p.lazy()),
1111
- )
1187
+ .addRegistration(R.fromClass(EmailService).pipe(singleton(), (p) => p.lazy()))
1188
+ .addRegistration(R.fromClass(SmsService).pipe(singleton(), (p) => p.lazy()))
1112
1189
  .addRegistration(R.fromClass(NotificationService));
1113
1190
 
1114
1191
  const notifications = container.resolve<NotificationService>(NotificationService);
@@ -1197,10 +1274,11 @@ describe('lazy registerPipe', () => {
1197
1274
  * lazy() works seamlessly with other provider transformations.
1198
1275
  */
1199
1276
  describe('combining with other pipes', () => {
1277
+ @register(bindTo('Config'))
1200
1278
  class ConfigService {
1201
1279
  constructor(
1202
- public apiUrl: string,
1203
- public timeout: number,
1280
+ @inject(args(0)) public apiUrl: string,
1281
+ @inject(args(1)) public timeout: number,
1204
1282
  ) {
1205
1283
  initLog.push(`ConfigService initialized with ${apiUrl}`);
1206
1284
  }
@@ -1209,7 +1287,6 @@ describe('lazy registerPipe', () => {
1209
1287
  it('should combine lazy with args and singleton', () => {
1210
1288
  const container = new Container().addRegistration(
1211
1289
  R.fromClass(ConfigService)
1212
- .bindToKey('Config')
1213
1290
  .pipe(
1214
1291
  (p) => p.setArgs(() => ['https://api.example.com', 5000]),
1215
1292
  (p) => p.lazy(),
@@ -1246,6 +1323,7 @@ describe('lazy registerPipe', () => {
1246
1323
  * - Report generators
1247
1324
  */
1248
1325
  describe('real-world example - feature flags', () => {
1326
+ @register(singleton())
1249
1327
  class FeatureFlagService {
1250
1328
  constructor() {
1251
1329
  initLog.push('FeatureFlagService initialized');
@@ -1285,7 +1363,7 @@ describe('lazy registerPipe', () => {
1285
1363
 
1286
1364
  it('should not initialize premium features for standard users', () => {
1287
1365
  const container = new Container()
1288
- .addRegistration(R.fromClass(FeatureFlagService).bindToKey('FeatureFlagService').pipe(singleton()))
1366
+ .addRegistration(R.fromClass(FeatureFlagService))
1289
1367
  .addRegistration(R.fromClass(PremiumFeature))
1290
1368
  .addRegistration(R.fromClass(Application));
1291
1369
 
@@ -1299,7 +1377,7 @@ describe('lazy registerPipe', () => {
1299
1377
 
1300
1378
  it('should initialize premium features only for premium users', () => {
1301
1379
  const container = new Container()
1302
- .addRegistration(R.fromClass(FeatureFlagService).bindToKey('FeatureFlagService').pipe(singleton()))
1380
+ .addRegistration(R.fromClass(FeatureFlagService))
1303
1381
  .addRegistration(R.fromClass(PremiumFeature))
1304
1382
  .addRegistration(R.fromClass(Application));
1305
1383
 
@@ -1316,6 +1394,7 @@ describe('lazy registerPipe', () => {
1316
1394
  ```
1317
1395
 
1318
1396
  ## Injector
1397
+
1319
1398
  `IInjector` is used to describe how dependencies should be injected to constructor.
1320
1399
 
1321
1400
  - `MetadataInjector` - injects dependencies using `@inject` decorator
@@ -1323,11 +1402,12 @@ describe('lazy registerPipe', () => {
1323
1402
  - `SimpleInjector` - just passes container to constructor with others arguments
1324
1403
 
1325
1404
  ### Metadata
1405
+
1326
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`.
1327
1407
  Also you can [inject property.](#inject-property)
1328
1408
 
1329
1409
  ```typescript
1330
- import { Container, inject, Registration as R } from 'ts-ioc-container';
1410
+ import { bindTo, Container, inject, register, Registration as R } from 'ts-ioc-container';
1331
1411
 
1332
1412
  /**
1333
1413
  * User Management Domain - Metadata Injection
@@ -1344,6 +1424,7 @@ import { Container, inject, Registration as R } from 'ts-ioc-container';
1344
1424
  * Requires: "experimentalDecorators" and "emitDecoratorMetadata" in tsconfig.
1345
1425
  */
1346
1426
 
1427
+ @register(bindTo('ILogger'))
1347
1428
  class Logger {
1348
1429
  name = 'Logger';
1349
1430
  }
@@ -1362,9 +1443,7 @@ class App {
1362
1443
 
1363
1444
  describe('Metadata Injector', function () {
1364
1445
  it('should inject dependencies using @inject decorator', function () {
1365
- const container = new Container({ tags: ['application'] }).addRegistration(
1366
- R.fromClass(Logger).bindToKey('ILogger'),
1367
- );
1446
+ const container = new Container({ tags: ['application'] }).addRegistration(R.fromClass(Logger));
1368
1447
 
1369
1448
  // Container reads @inject metadata and resolves 'ILogger' for the logger parameter
1370
1449
  const app = container.resolve(App);
@@ -1376,10 +1455,11 @@ describe('Metadata Injector', function () {
1376
1455
  ```
1377
1456
 
1378
1457
  ### Simple
1458
+
1379
1459
  This type of injector just passes container to constructor with others arguments.
1380
1460
 
1381
1461
  ```typescript
1382
- 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';
1383
1463
 
1384
1464
  /**
1385
1465
  * Command Pattern - Simple Injector
@@ -1407,6 +1487,7 @@ class CreateUserCommand implements ICommand {
1407
1487
  constructor(readonly username: string) {}
1408
1488
  }
1409
1489
 
1490
+ @register(bindTo('HandlerCreateUser'))
1410
1491
  class CreateUserHandler implements ICommandHandler {
1411
1492
  handle(command: CreateUserCommand): string {
1412
1493
  return `User ${command.username} created`;
@@ -1416,6 +1497,7 @@ class CreateUserHandler implements ICommandHandler {
1416
1497
  describe('SimpleInjector', function () {
1417
1498
  it('should inject container to allow dynamic resolution (Service Locator pattern)', function () {
1418
1499
  // Dispatcher needs the container to find handlers dynamically based on command type
1500
+ @register(bindTo('Dispatcher'))
1419
1501
  class CommandDispatcher {
1420
1502
  constructor(private container: IContainer) {}
1421
1503
 
@@ -1428,8 +1510,8 @@ describe('SimpleInjector', function () {
1428
1510
  }
1429
1511
 
1430
1512
  const container = new Container({ injector: new SimpleInjector() })
1431
- .addRegistration(R.fromClass(CommandDispatcher).bindToKey('Dispatcher'))
1432
- .addRegistration(R.fromClass(CreateUserHandler).bindToKey('HandlerCreateUser'));
1513
+ .addRegistration(R.fromClass(CommandDispatcher))
1514
+ .addRegistration(R.fromClass(CreateUserHandler));
1433
1515
 
1434
1516
  const dispatcher = container.resolve<CommandDispatcher>('Dispatcher');
1435
1517
  const result = dispatcher.dispatch(new CreateUserCommand('alice'));
@@ -1450,9 +1532,7 @@ describe('SimpleInjector', function () {
1450
1532
  }
1451
1533
  }
1452
1534
 
1453
- const container = new Container({ injector: new SimpleInjector() }).addRegistration(
1454
- R.fromClass(WidgetFactory).bindToKey('WidgetFactory'),
1455
- );
1535
+ const container = new Container({ injector: new SimpleInjector() }).addRegistration(R.fromClass(WidgetFactory));
1456
1536
 
1457
1537
  // Pass "dark" as the theme argument
1458
1538
  const factory = container.resolve<WidgetFactory>('WidgetFactory', { args: ['dark'] });
@@ -1464,39 +1544,29 @@ describe('SimpleInjector', function () {
1464
1544
  ```
1465
1545
 
1466
1546
  ### Proxy
1547
+
1467
1548
  This type of injector injects dependencies as dictionary `Record<string, unknown>`.
1468
1549
 
1469
- ```typescript
1470
- import { Container, ProxyInjector, Registration as R } from 'ts-ioc-container';
1550
+ - **`args` reserved keyword**: accessing `deps.args` returns the raw `args[]` array passed at resolve time
1551
+ - **Alias convention**: any property name containing `"alias"` (case-insensitive) is resolved via `resolveByAlias` instead of `resolve`
1471
1552
 
1472
- /**
1473
- * Clean Architecture - Proxy Injector
1474
- *
1475
- * The ProxyInjector injects dependencies as a single object (props/options pattern).
1476
- * This is popular in modern JavaScript/TypeScript (like React props or destructuring).
1477
- *
1478
- * Advantages:
1479
- * - Named parameters are more readable than positional arguments
1480
- * - Order of arguments doesn't matter
1481
- * - Easy to add/remove dependencies without breaking inheritance chains
1482
- * - Works well with "Clean Architecture" adapters
1483
- */
1553
+ ```typescript
1554
+ import { bindTo, Container, ProxyInjector, register, Registration as R } from 'ts-ioc-container';
1484
1555
 
1485
1556
  describe('ProxyInjector', function () {
1486
1557
  it('should inject dependencies as a props object', function () {
1558
+ @register(bindTo('logger'))
1487
1559
  class Logger {
1488
1560
  log(msg: string) {
1489
1561
  return `Logged: ${msg}`;
1490
1562
  }
1491
1563
  }
1492
1564
 
1493
- // Dependencies defined as an interface
1494
1565
  interface UserControllerDeps {
1495
1566
  logger: Logger;
1496
1567
  prefix: string;
1497
1568
  }
1498
1569
 
1499
- // Controller receives all dependencies in a single object
1500
1570
  class UserController {
1501
1571
  private logger: Logger;
1502
1572
  private prefix: string;
@@ -1512,53 +1582,51 @@ describe('ProxyInjector', function () {
1512
1582
  }
1513
1583
 
1514
1584
  const container = new Container({ injector: new ProxyInjector() })
1515
- .addRegistration(R.fromClass(Logger).bindToKey('logger'))
1585
+ .addRegistration(R.fromClass(Logger))
1516
1586
  .addRegistration(R.fromValue('USER:').bindToKey('prefix'))
1517
- .addRegistration(R.fromClass(UserController).bindToKey('UserController'));
1587
+ .addRegistration(R.fromClass(UserController));
1518
1588
 
1519
1589
  const controller = container.resolve<UserController>('UserController');
1520
1590
 
1521
1591
  expect(controller.createUser('bob')).toBe('Logged: USER: bob');
1522
1592
  });
1523
1593
 
1524
- it('should support mixing injected dependencies with runtime arguments', function () {
1594
+ it('should expose runtime args through the reserved "args" property', function () {
1595
+ @register(bindTo('database'))
1525
1596
  class Database {}
1526
1597
 
1527
- interface ReportGeneratorDeps {
1598
+ class ReportGenerator {
1528
1599
  database: Database;
1529
- format: string; // Runtime argument
1530
- }
1600
+ format: string;
1531
1601
 
1532
- class ReportGenerator {
1533
- constructor(public deps: ReportGeneratorDeps) {}
1602
+ constructor({ database, args }: { database: Database; args: string[] }) {
1603
+ this.database = database;
1604
+ this.format = args[0];
1605
+ }
1534
1606
 
1535
1607
  generate(): string {
1536
- return `Report in ${this.deps.format}`;
1608
+ return `Report in ${this.format}`;
1537
1609
  }
1538
1610
  }
1539
1611
 
1540
1612
  const container = new Container({ injector: new ProxyInjector() })
1541
- .addRegistration(R.fromClass(Database).bindToKey('database'))
1542
- .addRegistration(R.fromClass(ReportGenerator).bindToKey('ReportGenerator'));
1613
+ .addRegistration(R.fromClass(Database))
1614
+ .addRegistration(R.fromClass(ReportGenerator));
1543
1615
 
1544
- // "format" is passed at resolution time
1545
1616
  const generator = container.resolve<ReportGenerator>('ReportGenerator', {
1546
- args: [{ format: 'PDF' }],
1617
+ args: ['PDF'],
1547
1618
  });
1548
1619
 
1549
- expect(generator.deps.database).toBeInstanceOf(Database);
1620
+ expect(generator.database).toBeInstanceOf(Database);
1550
1621
  expect(generator.generate()).toBe('Report in PDF');
1551
1622
  });
1552
1623
 
1553
- it('should resolve array dependencies by alias (convention over configuration)', function () {
1554
- // If a property is named "loggersArray", it looks for alias "loggersArray"
1555
- // and resolves it as an array of all matches.
1556
-
1624
+ it('should resolve dependencies by alias when property name contains "alias"', function () {
1557
1625
  class FileLogger {}
1558
1626
  class ConsoleLogger {}
1559
1627
 
1560
1628
  interface AppDeps {
1561
- loggersArray: any[]; // Injected as array of all loggers
1629
+ loggersAlias: any[];
1562
1630
  }
1563
1631
 
1564
1632
  class App {
@@ -1567,23 +1635,20 @@ describe('ProxyInjector', function () {
1567
1635
 
1568
1636
  const container = new Container({ injector: new ProxyInjector() });
1569
1637
 
1570
- // Mocking the behavior for this specific test as ProxyInjector uses resolveByAlias
1571
- // which delegates to the container.
1572
- // In a real scenario, you'd register multiple loggers with the same alias.
1573
1638
  const mockLoggers = [new FileLogger(), new ConsoleLogger()];
1574
-
1575
1639
  container.resolveByAlias = vi.fn().mockReturnValue(mockLoggers);
1576
1640
 
1577
1641
  const app = container.resolve(App);
1578
1642
 
1579
- expect(app.deps.loggersArray).toBe(mockLoggers);
1580
- expect(container.resolveByAlias).toHaveBeenCalledWith('loggersArray');
1643
+ expect(app.deps.loggersAlias).toBe(mockLoggers);
1644
+ expect(container.resolveByAlias).toHaveBeenCalledWith('loggersAlias');
1581
1645
  });
1582
1646
  });
1583
1647
 
1584
1648
  ```
1585
1649
 
1586
1650
  ## Provider
1651
+
1587
1652
  Provider is dependency factory which creates dependency.
1588
1653
 
1589
1654
  - `Provider.fromClass(Logger)`
@@ -1592,10 +1657,12 @@ Provider is dependency factory which creates dependency.
1592
1657
 
1593
1658
  ```typescript
1594
1659
  import {
1660
+ args,
1595
1661
  setArgs,
1596
1662
  setArgsFn,
1597
1663
  bindTo,
1598
1664
  Container,
1665
+ inject,
1599
1666
  lazy,
1600
1667
  Provider,
1601
1668
  register,
@@ -1669,7 +1736,7 @@ describe('Provider', () => {
1669
1736
 
1670
1737
  it('supports args decorator for providing extra arguments', () => {
1671
1738
  class FileService {
1672
- constructor(readonly basePath: string) {}
1739
+ constructor(@inject(args(0)) readonly basePath: string) {}
1673
1740
  }
1674
1741
 
1675
1742
  const container = new Container().register(
@@ -1683,7 +1750,7 @@ describe('Provider', () => {
1683
1750
 
1684
1751
  it('supports argsFn decorator for dynamic arguments', () => {
1685
1752
  class Database {
1686
- constructor(readonly connectionString: string) {}
1753
+ constructor(@inject(args(0)) readonly connectionString: string) {}
1687
1754
  }
1688
1755
 
1689
1756
  const container = new Container().register('DbPath', Provider.fromValue('localhost:5432')).register(
@@ -1737,10 +1804,13 @@ describe('Provider', () => {
1737
1804
  ```
1738
1805
 
1739
1806
  ### Singleton
1807
+
1740
1808
  Sometimes you need to create only one instance of dependency per scope. For example, you want to create only one logger per scope.
1741
1809
 
1742
1810
  - Singleton provider creates only one instance in every scope where it's resolved.
1743
- - NOTICE: if you create a scope 'A' of container 'root' then Logger of A !== Logger of root.
1811
+
1812
+ > [!IMPORTANT]
1813
+ > Singleton means one instance per scope. If you create a scope `A` of container `root`, then `Logger` of `A` !== `Logger` of `root`.
1744
1814
 
1745
1815
  ```typescript
1746
1816
  import 'reflect-metadata';
@@ -1827,17 +1897,39 @@ 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
- - NOTICE: args from this provider has higher priority than args from `resolve` method.
1906
+
1907
+ ### Token as argument
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
+
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
1913
+ - `ServiceToken.args('literal')` — literal value passed directly
1835
1914
 
1836
1915
  ### Positional arg injection with `args(index)`
1837
- 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
+
1838
1919
  - `@inject(args(0))` — resolves the first element of the `args` array passed at resolution time
1839
1920
  - Works together with `token.args(...)` to pass typed dependencies through the args context
1840
1921
 
1922
+ ### Immutable token chaining
1923
+
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).
1925
+
1926
+ ```typescript
1927
+ const ApiToken = new SingleToken<IApiClient>('IApiClient');
1928
+ // Independent children — ApiToken is unchanged
1929
+ const dataToken = ApiToken.args('https://data.api.com', 5000);
1930
+ const userToken = ApiToken.args('https://users.api.com', 1000);
1931
+ ```
1932
+
1841
1933
  ```typescript
1842
1934
  import {
1843
1935
  setArgs,
@@ -1845,10 +1937,10 @@ import {
1845
1937
  bindTo,
1846
1938
  Container,
1847
1939
  inject,
1940
+ args,
1848
1941
  MultiCache,
1849
1942
  register,
1850
1943
  Registration as R,
1851
- resolveByArgs,
1852
1944
  singleton,
1853
1945
  SingleToken,
1854
1946
  } from 'ts-ioc-container';
@@ -1871,7 +1963,7 @@ describe('ArgsProvider', function () {
1871
1963
  describe('Static Arguments', () => {
1872
1964
  it('can pass static arguments to constructor', function () {
1873
1965
  class FileLogger {
1874
- constructor(public filename: string) {}
1966
+ constructor(@inject(args(0)) public filename: string) {}
1875
1967
  }
1876
1968
 
1877
1969
  // Pre-configure the logger with a filename
@@ -1884,7 +1976,7 @@ describe('ArgsProvider', function () {
1884
1976
 
1885
1977
  it('prioritizes provided args over resolve args', function () {
1886
1978
  class Logger {
1887
- constructor(public context: string) {}
1979
+ constructor(@inject(args(0)) public context: string) {}
1888
1980
  }
1889
1981
 
1890
1982
  // 'FixedContext' wins over any runtime args
@@ -1905,7 +1997,7 @@ describe('ArgsProvider', function () {
1905
1997
  }
1906
1998
 
1907
1999
  class Service {
1908
- constructor(public env: string) {}
2000
+ constructor(@inject(args(0)) public env: string) {}
1909
2001
  }
1910
2002
 
1911
2003
  const root = createContainer()
@@ -1945,17 +2037,18 @@ describe('ArgsProvider', function () {
1945
2037
  name = 'TodoRepository';
1946
2038
  }
1947
2039
 
1948
- // EntityManager is generic - it works with ANY repository
1949
- // 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.
1950
2044
  const EntityManagerToken = new SingleToken<EntityManager>('EntityManager');
1951
2045
 
1952
2046
  @register(
1953
2047
  bindTo(EntityManagerToken),
1954
- setArgsFn(resolveByArgs), // <--- Key magic: resolves dependencies based on arguments passed to token
1955
2048
  singleton(MultiCache.fromFirstArg), // Cache unique instance per repository type
1956
2049
  )
1957
2050
  class EntityManager {
1958
- constructor(public repository: IRepository) {}
2051
+ constructor(@inject(args(0)) public repository: IRepository) {}
1959
2052
  }
1960
2053
 
1961
2054
  class App {
@@ -2007,7 +2100,12 @@ describe('ArgsProvider', function () {
2007
2100
  ```
2008
2101
 
2009
2102
  ### Visibility
2103
+
2010
2104
  Sometimes you want to hide dependency if somebody wants to resolve it from certain scope. This uses `ScopeAccessRule` to control access.
2105
+
2106
+ > [!IMPORTANT]
2107
+ > Use `scope()` to decide where a provider is registered. Use `scopeAccess()` to decide which invocation scopes can see an already registered provider.
2108
+
2011
2109
  - `provider(scopeAccess(({ invocationScope, providerScope }) => invocationScope === providerScope))` - dependency will be accessible only from the scope where it's registered
2012
2110
  - `Provider.fromClass(Logger).pipe(scopeAccess(({ invocationScope, providerScope }) => invocationScope === providerScope))`
2013
2111
 
@@ -2103,7 +2201,9 @@ describe('Visibility', function () {
2103
2201
  ```
2104
2202
 
2105
2203
  ### Alias
2204
+
2106
2205
  Alias is needed to group keys
2206
+
2107
2207
  - `@register(asAlias('logger'))` helper assigns `logger` alias to registration.
2108
2208
  - `by.aliases((it) => it.has('logger') || it.has('a'))` resolves dependencies which have `logger` or `a` aliases
2109
2209
  - `Provider.fromClass(Logger).pipe(alias('logger'))`
@@ -2248,11 +2348,14 @@ describe('alias', () => {
2248
2348
  ```
2249
2349
 
2250
2350
  ### Decorator
2351
+
2251
2352
  Sometimes you want to decorate you class with some logic. This is what `DecoratorProvider` is for.
2353
+
2252
2354
  - `provider(decorate((instance, container) => new LoggerDecorator(instance)))`
2253
2355
 
2254
2356
  ```typescript
2255
2357
  import {
2358
+ args,
2256
2359
  bindTo,
2257
2360
  Container,
2258
2361
  decorate,
@@ -2306,7 +2409,7 @@ describe('Decorator Pattern', () => {
2306
2409
  // Decorator: Wraps any IRepository with logging behavior
2307
2410
  class LoggingRepository implements IRepository {
2308
2411
  constructor(
2309
- private repository: IRepository,
2412
+ @inject(args(0)) private repository: IRepository,
2310
2413
  @inject(s.token('Logger').lazy()) private logger: Logger,
2311
2414
  ) {}
2312
2415
 
@@ -2362,7 +2465,9 @@ describe('Decorator Pattern', () => {
2362
2465
  ```
2363
2466
 
2364
2467
  ## Registration
2468
+
2365
2469
  Registration is provider factory which registers provider in container.
2470
+
2366
2471
  - `@register(asKey('logger'))`
2367
2472
  - `Registration.fromClass(Logger).to('logger')`
2368
2473
  - `Registration.fromClass(Logger)`
@@ -2370,6 +2475,7 @@ Registration is provider factory which registers provider in container.
2370
2475
  - `Registration.fromFn((container, ...args) => container.resolve(Logger, {args}))`
2371
2476
 
2372
2477
  ### Key
2478
+
2373
2479
  Sometimes you want to register provider with certain key. This is what `key` is for.
2374
2480
 
2375
2481
  - by default, key is class name
@@ -2462,7 +2568,9 @@ describe('Registration module', function () {
2462
2568
  ```
2463
2569
 
2464
2570
  ### Scope
2571
+
2465
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
+
2466
2574
  - `@register(scope((container) => container.hasTag('root'))` - register provider only in root scope
2467
2575
  - `Registration.fromClass(Logger).when((container) => container.hasTag('root'))`
2468
2576
 
@@ -2504,6 +2612,7 @@ describe('ScopeProvider', function () {
2504
2612
  ```
2505
2613
 
2506
2614
  ## Module
2615
+
2507
2616
  Sometimes you want to encapsulate registration logic in separate module. This is what `IContainerModule` is for.
2508
2617
 
2509
2618
  ```typescript
@@ -2627,9 +2736,11 @@ describe('Container Modules', function () {
2627
2736
  ```
2628
2737
 
2629
2738
  ## Hook
2739
+
2630
2740
  Sometimes you need to invoke methods after construct or dispose of class. This is what hooks are for.
2631
2741
 
2632
2742
  ### OnConstruct
2743
+
2633
2744
  ```typescript
2634
2745
  import {
2635
2746
  AddOnConstructHookModule,
@@ -2690,6 +2801,7 @@ describe('onConstruct', function () {
2690
2801
  ```
2691
2802
 
2692
2803
  ### OnDispose
2804
+
2693
2805
  ```typescript
2694
2806
  import {
2695
2807
  AddOnDisposeHookModule,
@@ -2804,8 +2916,11 @@ describe('inject property', () => {
2804
2916
 
2805
2917
  ## Error
2806
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
+
2807
2923
  - [DependencyNotFoundError.ts](..%2F..%2Flib%2Ferrors%2FDependencyNotFoundError.ts)
2808
2924
  - [MethodNotImplementedError.ts](..%2F..%2Flib%2Ferrors%2FMethodNotImplementedError.ts)
2809
2925
  - [DependencyMissingKeyError.ts](..%2F..%2Flib%2Ferrors%2FDependencyMissingKeyError.ts)
2810
2926
  - [ContainerDisposedError.ts](..%2F..%2Flib%2Ferrors%2FContainerDisposedError.ts)
2811
-