ts-ioc-container 23.3.3 → 23.3.5
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 +55 -42
- package/README.template.md +453 -0
- package/cjs/container/AutoMockedContainer.js +15 -18
- package/cjs/container/AutoMockedContainer.js.map +1 -1
- package/cjs/container/Container.js +44 -130
- package/cjs/container/Container.js.map +1 -1
- package/cjs/container/ContainerDisposedError.js +8 -26
- package/cjs/container/ContainerDisposedError.js.map +1 -1
- package/cjs/container/DependencyNotFoundError.js +6 -24
- package/cjs/container/DependencyNotFoundError.js.map +1 -1
- package/cjs/container/EmptyContainer.js +20 -23
- package/cjs/container/EmptyContainer.js.map +1 -1
- package/cjs/container/MethodNotImplementedError.js +6 -24
- package/cjs/container/MethodNotImplementedError.js.map +1 -1
- package/cjs/injector/ProxyInjector.js +7 -25
- package/cjs/injector/ProxyInjector.js.map +1 -1
- package/cjs/injector/ReflectionInjector.js +15 -59
- package/cjs/injector/ReflectionInjector.js.map +1 -1
- package/cjs/injector/SimpleInjector.js +4 -36
- package/cjs/injector/SimpleInjector.js.map +1 -1
- package/cjs/provider/ArgsProvider.js +15 -67
- package/cjs/provider/ArgsProvider.js.map +1 -1
- package/cjs/provider/Provider.js +23 -70
- package/cjs/provider/Provider.js.map +1 -1
- package/cjs/provider/ProviderDecorator.js +12 -47
- package/cjs/provider/ProviderDecorator.js.map +1 -1
- package/cjs/provider/SingletonProvider.js +12 -60
- package/cjs/provider/SingletonProvider.js.map +1 -1
- package/cjs/provider/TaggedProvider.js +14 -36
- package/cjs/provider/TaggedProvider.js.map +1 -1
- package/cjs/reflection.js +5 -34
- package/cjs/reflection.js.map +1 -1
- package/cjs/registration/DependencyMissingKeyError.js +6 -24
- package/cjs/registration/DependencyMissingKeyError.js.map +1 -1
- package/cjs/registration/Registration.js +16 -47
- package/cjs/registration/Registration.js.map +1 -1
- package/cjs/utils.js +5 -29
- package/cjs/utils.js.map +1 -1
- package/package.json +8 -4
package/README.md
CHANGED
|
@@ -52,27 +52,30 @@ It consists of 2 main parts:
|
|
|
52
52
|
|
|
53
53
|
```typescript
|
|
54
54
|
import 'reflect-metadata';
|
|
55
|
-
import { Container } from
|
|
55
|
+
import { by, Container, inject, ReflectionInjector, Provider } from 'ts-ioc-container';
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
describe('Basic usage', function () {
|
|
58
|
+
it('should inject dependencies', function () {
|
|
59
|
+
class Logger {
|
|
60
|
+
name = 'Logger';
|
|
61
|
+
}
|
|
60
62
|
|
|
61
|
-
class App {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
63
|
+
class App {
|
|
64
|
+
constructor(@inject(by('ILogger')) public logger: Logger) {}
|
|
65
|
+
}
|
|
65
66
|
|
|
66
|
-
const container = new Container(new ReflectionInjector()).register('ILogger', Provider.fromClass(Logger));
|
|
67
|
-
|
|
67
|
+
const container = new Container(new ReflectionInjector()).register('ILogger', Provider.fromClass(Logger));
|
|
68
|
+
|
|
69
|
+
expect(container.resolve(App).logger.name).toBe('Logger');
|
|
70
|
+
});
|
|
71
|
+
});
|
|
68
72
|
|
|
69
|
-
container.dispose();
|
|
70
73
|
```
|
|
71
74
|
|
|
72
75
|
### Scopes
|
|
73
76
|
Sometimes you need to create a scope of container. For example, when you want to create a scope per request in web application.
|
|
74
77
|
|
|
75
|
-
- NOTICE: remember that when
|
|
78
|
+
- NOTICE: remember that when scope doesn't have dependency then it will be resolved from parent container
|
|
76
79
|
- 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.
|
|
77
80
|
|
|
78
81
|
```typescript
|
|
@@ -104,16 +107,23 @@ Sometimes you want to get all instances from container and its scopes. For examp
|
|
|
104
107
|
- you can get instances from container and scope which were created by injector
|
|
105
108
|
|
|
106
109
|
```typescript
|
|
107
|
-
import
|
|
110
|
+
import 'reflect-metadata';
|
|
111
|
+
import { Container, Provider, ReflectionInjector } from 'ts-ioc-container';
|
|
112
|
+
describe('Instances', function () {
|
|
113
|
+
it('should return injected instances', function () {
|
|
114
|
+
class Logger {}
|
|
108
115
|
|
|
109
|
-
const container = new Container(new ReflectionInjector()).register('ILogger', Provider.fromClass(Logger));
|
|
110
|
-
const scope = container.createScope();
|
|
116
|
+
const container = new Container(new ReflectionInjector()).register('ILogger', Provider.fromClass(Logger));
|
|
117
|
+
const scope = container.createScope();
|
|
111
118
|
|
|
112
|
-
const logger1 = container.resolve('ILogger');
|
|
113
|
-
const logger2 = scope.resolve('ILogger');
|
|
119
|
+
const logger1 = container.resolve('ILogger');
|
|
120
|
+
const logger2 = scope.resolve('ILogger');
|
|
121
|
+
|
|
122
|
+
expect(scope.getInstances().length).toBe(1);
|
|
123
|
+
expect(container.getInstances().length).toBe(2);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
114
126
|
|
|
115
|
-
expect(scope.getInstances().length).toBe(1);
|
|
116
|
-
expect(container.getInstances().length).toBe(2);
|
|
117
127
|
```
|
|
118
128
|
|
|
119
129
|
### Disposing
|
|
@@ -464,39 +474,42 @@ for (const instance of container.getInstances()) {
|
|
|
464
474
|
Sometimes you need to automatically mock all dependencies in container. This is what `AutoMockedContainer` is for.
|
|
465
475
|
|
|
466
476
|
```typescript
|
|
467
|
-
import {
|
|
468
|
-
|
|
469
|
-
Container,
|
|
470
|
-
DependencyKey,
|
|
471
|
-
} from "ts-ioc-container";
|
|
472
|
-
import { Mock } from "moq.ts";
|
|
477
|
+
import { AutoMockedContainer, Container, DependencyKey, ReflectionInjector } from 'ts-ioc-container';
|
|
478
|
+
import { IMock, Mock } from 'moq.ts';
|
|
473
479
|
|
|
474
480
|
export class MoqContainer extends AutoMockedContainer {
|
|
475
|
-
|
|
481
|
+
private mocks = new Map<DependencyKey, IMock<any>>();
|
|
476
482
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
483
|
+
resolve<T>(key: DependencyKey): T {
|
|
484
|
+
return this.resolveMock<T>(key).object();
|
|
485
|
+
}
|
|
480
486
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
487
|
+
resolveMock<T>(key: DependencyKey): IMock<T> {
|
|
488
|
+
if (!this.mocks.has(key)) {
|
|
489
|
+
this.mocks.set(key, new Mock());
|
|
490
|
+
}
|
|
491
|
+
return this.mocks.get(key) as IMock<T>;
|
|
484
492
|
}
|
|
485
|
-
return this.mocks.get(key) as IMock<T>;
|
|
486
|
-
}
|
|
487
493
|
}
|
|
488
494
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
495
|
+
interface IEngine {
|
|
496
|
+
getRegistrationNumber(): string;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
describe('Mocking', () => {
|
|
500
|
+
it('should auto-mock dependencies', () => {
|
|
501
|
+
const mockContainer = new MoqContainer();
|
|
502
|
+
const container = new Container(new ReflectionInjector(), { parent: mockContainer });
|
|
503
|
+
|
|
504
|
+
const engineMock = mockContainer.resolveMock<IEngine>('IEngine');
|
|
505
|
+
engineMock.setup((i) => i.getRegistrationNumber()).returns('123');
|
|
492
506
|
|
|
493
|
-
|
|
494
|
-
engineMock.setup(i => i.getRegistrationNumber()).return('123');
|
|
507
|
+
const engine = container.resolve<IEngine>('IEngine');
|
|
495
508
|
|
|
496
|
-
|
|
509
|
+
expect(engine.getRegistrationNumber()).toBe('123');
|
|
510
|
+
});
|
|
511
|
+
});
|
|
497
512
|
|
|
498
|
-
expect(engine.getRegistrationNumber()).toBe('123');
|
|
499
|
-
})
|
|
500
513
|
```
|
|
501
514
|
|
|
502
515
|
|
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
# Typescript IoC (Inversion Of Control) container
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
[](https://coveralls.io/github/IgorBabkin/ts-ioc-container?branch=master)
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## Advantages
|
|
10
|
+
- battle tested :boom:
|
|
11
|
+
- written on `typescript`
|
|
12
|
+
- simple and lightweight (roughly it's just one file of **~100 lines**) :heart:
|
|
13
|
+
- clean API :green_heart:
|
|
14
|
+
- supports `tagged scopes`
|
|
15
|
+
- fully test covered :100:
|
|
16
|
+
- can be used with decorators `@inject`
|
|
17
|
+
- composable and open to extend
|
|
18
|
+
- awesome for testing (auto mocking)
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
```shell script
|
|
22
|
+
npm install ts-ioc-container reflect-metadata
|
|
23
|
+
```
|
|
24
|
+
```shell script
|
|
25
|
+
yarn add ts-ioc-container reflect-metadata
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Setup
|
|
29
|
+
### reflect-metadata
|
|
30
|
+
Just put it in the main file of your project. It should be the first line of the code.
|
|
31
|
+
```typescript
|
|
32
|
+
import 'reflect-metadata';
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### tsconfig.json
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"compilerOptions": {
|
|
39
|
+
"experimentalDecorators": true,
|
|
40
|
+
"emitDecoratorMetadata": true
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Container `IContainer`
|
|
46
|
+
It consists of 2 main parts:
|
|
47
|
+
|
|
48
|
+
- Providers - describes how to create instances of dependencies
|
|
49
|
+
- Injector - describes how to inject dependencies to constructor
|
|
50
|
+
|
|
51
|
+
### Basic usage
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
{{{include_file './__tests__/readme/basic.spec.ts'}}}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Scopes
|
|
58
|
+
Sometimes you need to create a scope of container. For example, when you want to create a scope per request in web application.
|
|
59
|
+
|
|
60
|
+
- NOTICE: remember that when scope doesn't have dependency then it will be resolved from parent container
|
|
61
|
+
- 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.
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { Container, ReflectionInjector } from "ts-ioc-container";
|
|
65
|
+
|
|
66
|
+
const container = new Container(new ReflectionInjector());
|
|
67
|
+
const scope = container.createScope();
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Tags
|
|
71
|
+
Sometimes you want to mark some providers and resolve them only from certain scope. So you can assign tags to providers and create scopes with certain tags. For that reason every scope has method `hasTag` which we invoke from provider to check if it's valid for certain scope.
|
|
72
|
+
|
|
73
|
+
- tag - is a string that we assign to provider and scope/container
|
|
74
|
+
- every provider can be registered per certain tags and cannot be resolved from container with other tags. Only from parent one with certain tags.
|
|
75
|
+
- NOTICE: when you create a scope then we clone ONLY tags-matched providers.
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { Container, perTags, ReflectionInjector } from "ts-ioc-container";
|
|
79
|
+
|
|
80
|
+
const container = new Container(new ReflectionInjector(), { tags: ['root'] }).register('ILogger', Provider.fromClass(Logger).pipe(perTags('root')));
|
|
81
|
+
const scope = container.createScope(['child']);
|
|
82
|
+
|
|
83
|
+
scope.resolve('ILogger'); // it will be resolved from container, not from scope
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Instances
|
|
87
|
+
Sometimes you want to get all instances from container and its scopes. For example, when you want to dispose all instances of container.
|
|
88
|
+
|
|
89
|
+
- you can get instances from container and scope which were created by injector
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
{{{include_file './__tests__/readme/instances.spec.ts'}}}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Disposing
|
|
96
|
+
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.
|
|
97
|
+
|
|
98
|
+
- container can be disposed
|
|
99
|
+
- when container is disposed then all scopes are disposed too
|
|
100
|
+
- when container is disposed then it unregisters all providers and remove all instances
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { asSingleton, Container, perTags, ReflectionInjector, ContainerDisposedError } from "ts-ioc-container";
|
|
104
|
+
|
|
105
|
+
const container = new Container(new ReflectionInjector(), { tags: ['root'] }).register('ILogger', Provider.fromClass(Logger));
|
|
106
|
+
const scope = container.createScope(['child']);
|
|
107
|
+
|
|
108
|
+
const logger = scope.resolve('ILogger');
|
|
109
|
+
container.dispose();
|
|
110
|
+
|
|
111
|
+
expect(() => scope.resolve('ILogger')).toThrow(ContainerDisposedError);
|
|
112
|
+
expect(() => container.resolve('ILogger')).toThrow(ContainerDisposedError);
|
|
113
|
+
expect(container.getInstances().length).toBe(0);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Injectors `IInjector`
|
|
117
|
+
Injectors are used to describe how dependencies should be injected to constructor.
|
|
118
|
+
|
|
119
|
+
- `ReflectionInjector` - injects dependencies using `@inject` decorator
|
|
120
|
+
- `ProxyInjector` - injects dependencies as dictionary `Record<string, unknown>`
|
|
121
|
+
- `SimpleInjector` - just passes container to constructor with others arguments
|
|
122
|
+
|
|
123
|
+
### Reflection injector
|
|
124
|
+
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 `ReflectionInjector`.
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import { Container, IContainer, IInjector, Provider, by, inject, resolve } from "ts-ioc-container";
|
|
128
|
+
|
|
129
|
+
class Logger implements ILogger {
|
|
130
|
+
info(message: string) {
|
|
131
|
+
console.log(message);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
class App {
|
|
136
|
+
constructor(@inject((container, ...args) => container.resolve('ILogger', ...args)) private logger: ILogger) {
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// OR
|
|
140
|
+
// constructor(@inject(by('ILogger')) private logger: ILogger) {
|
|
141
|
+
// }
|
|
142
|
+
|
|
143
|
+
run() {
|
|
144
|
+
this.logger.info('Hello world');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const container = new Container(new ReflectionInjector())
|
|
149
|
+
.register('ILogger', Provider.fromClass(Logger));
|
|
150
|
+
|
|
151
|
+
const app = container.resolve(App);
|
|
152
|
+
app.run();
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Simple injector
|
|
156
|
+
This type of injector just passes container to constructor with others arguments.
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
import { SimpleInjector, IContainer } from "ts-ioc-container";
|
|
160
|
+
|
|
161
|
+
class Logger implements ILogger {
|
|
162
|
+
info(message: string) {
|
|
163
|
+
console.log(message);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
class App {
|
|
168
|
+
private logger: ILogger;
|
|
169
|
+
|
|
170
|
+
constructor(private container: IContainer) {
|
|
171
|
+
this.logger = container.resolve('ILogger');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
run() {
|
|
175
|
+
this.logger.info('Hello world');
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const container = new Container(new SimpleInjector())
|
|
180
|
+
.register('ILogger', Provider.fromClass(Logger));
|
|
181
|
+
|
|
182
|
+
const app = container.resolve(App);
|
|
183
|
+
app.run();
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Proxy injector
|
|
187
|
+
This type of injector injects dependencies as dictionary `Record<string, unknown>`.
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
import { ProxyInjector, IContainer } from "ts-ioc-container";
|
|
191
|
+
|
|
192
|
+
class Logger implements ILogger {
|
|
193
|
+
info(message: string) {
|
|
194
|
+
console.log(message);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
class App {
|
|
199
|
+
private logger: ILogger;
|
|
200
|
+
|
|
201
|
+
constructor({ logger }: { logger: ILogger }) {
|
|
202
|
+
this.logger = logger;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
run() {
|
|
206
|
+
this.logger.info('Hello world');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const container = new Container(new ProxyInjector())
|
|
211
|
+
.register('logger', Provider.fromClass(Logger));
|
|
212
|
+
|
|
213
|
+
const app = container.resolve(App);
|
|
214
|
+
app.run();
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Providers `IProvider<T>`
|
|
218
|
+
Providers are used to describe how instances should be created. It has next basic methods:
|
|
219
|
+
- `resolve` - creates instance with passed arguments
|
|
220
|
+
- `clone` - we invoke it when we create a scope. It clones provider to new scope.
|
|
221
|
+
- `isValid` - checks if provider can be resolved from container or cloned to container with certain tags
|
|
222
|
+
|
|
223
|
+
There are next types of providers:
|
|
224
|
+
- `Provider` - basic provider
|
|
225
|
+
- `SingletonProvider` - provider that creates only one instance in every scope where it's resolved
|
|
226
|
+
- `TaggedProvider` - provider that can be resolved only from container with certain tags and their sub scopes
|
|
227
|
+
- `ArgsProvider` - provider that encapsulates arguments to pass it to constructor.
|
|
228
|
+
|
|
229
|
+
### Provider
|
|
230
|
+
|
|
231
|
+
From function
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
import { Provider } from "ts-ioc-container";
|
|
235
|
+
|
|
236
|
+
container.register('ILogger', new Provider((container, ...args) => new Logger(container, ...args)));
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
From class
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
import { Provider } from "ts-ioc-container";
|
|
243
|
+
|
|
244
|
+
container.register('ILogger', Provider.fromClass(Logger));
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
From value
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
import { Provider } from "ts-ioc-container";
|
|
251
|
+
|
|
252
|
+
container.register('ILogger', Provider.fromValue(new Logger()));
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
`pipe` - decorates provider by other providers
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
import { asSingleton, perTags, Provider, SingletonProvider, TaggedProvider } from "ts-ioc-container";
|
|
259
|
+
|
|
260
|
+
container.register('ILogger', Provider.fromClass(Logger).pipe((provider) => new SingletonProvider(provider)), (provider) => new TaggedProvider(provider, ['root']));
|
|
261
|
+
|
|
262
|
+
// OR
|
|
263
|
+
container.register('ILogger', Provider.fromClass(Logger).pipe(asSingleton(), perTags('root')));
|
|
264
|
+
|
|
265
|
+
// OR
|
|
266
|
+
@provider(asSingleton(), perTags('root'))
|
|
267
|
+
class Logger {
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
container.register('ILogger', Provider.fromClass(Logger));
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Singleton provider
|
|
274
|
+
Sometimes you need to create only one instance of dependency per scope. For example, you want to create only one logger per scope.
|
|
275
|
+
|
|
276
|
+
- Singleton provider creates only one instance in every scope where it's resolved.
|
|
277
|
+
- NOTICE: if you create a scope 'A' of container 'root' then Logger of A !== Logger of root.
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
import { Provider, SingletonProvider, asSingleton } from "ts-ioc-container";
|
|
281
|
+
|
|
282
|
+
container.register('ILogger', Provider.fromClass(Logger).pipe((provider) => new SingletonProvider(provider)));
|
|
283
|
+
// OR
|
|
284
|
+
container.register('ILogger', Provider.fromClass(Logger).pipe(asSingleton()));
|
|
285
|
+
|
|
286
|
+
container.resolve('ILogger') === container.resolve('ILogger'); // true
|
|
287
|
+
|
|
288
|
+
const scope = container.createScope();
|
|
289
|
+
scope.resolve('ILogger') === scope.resolve('ILogger'); // true
|
|
290
|
+
container.resolve('ILogger') !== scope.resolve('ILogger'); // true. NOTICE: because every provider is cloned for every child scope from parent one
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Tagged provider
|
|
294
|
+
Sometimes you need to resolve provider only from container with certain tags and their sub scopes. Especially if you want to register dependency as singleton for some tags, for example `root`
|
|
295
|
+
- NOTICE: It doesn't make clones in not tagged-matched scopes. Usually it's used with `SingletonProvider`.
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
import { Provider, TaggedProvider, asSingleton, perTags } from "ts-ioc-container";
|
|
299
|
+
|
|
300
|
+
container.register('ILogger', Provider.fromClass(Logger).pipe((provider) => new TaggedProvider(provider, ['root'])));
|
|
301
|
+
// OR
|
|
302
|
+
container.register('ILogger', Provider.fromClass(Logger).pipe(perTags('root', 'parent')));
|
|
303
|
+
|
|
304
|
+
// with sigleton
|
|
305
|
+
container.register('ILogger', Provider.fromClass(Logger).pipe(perTags('root', 'parent')).pipe(asSingleton()));
|
|
306
|
+
container.resolve('ILogger') === container.resolve('ILogger'); // true
|
|
307
|
+
|
|
308
|
+
const scope = container.createScope();
|
|
309
|
+
scope.resolve('ILogger') === scope.resolve('ILogger'); // true
|
|
310
|
+
container.resolve('ILogger') === scope.resolve('ILogger'); // true
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Args provider
|
|
314
|
+
Sometimes you want to bind some arguments to provider. This is what `ArgsProvider` is for.
|
|
315
|
+
- NOTICE: args from this provider has higher priority than args from `resolve` method.
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
import { Provider, ArgsProvider, withArgs, withArgsFn } from "ts-ioc-container";
|
|
319
|
+
|
|
320
|
+
class Logger {
|
|
321
|
+
constructor(public type: string, public name: string) {
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
container.register('ILogger', Provider.fromClass(Logger).pipe((provider) => new ArgsProvider(provider, () => ['FileLogger'])));
|
|
326
|
+
|
|
327
|
+
// OR
|
|
328
|
+
container.register('ILogger', Provider.fromClass(Logger).pipe(withArgsFn(() => ['FileLogger'])));
|
|
329
|
+
// OR
|
|
330
|
+
container.register('ILogger', Provider.fromClass(Logger).pipe(withArgs('FileLogger')));
|
|
331
|
+
|
|
332
|
+
container.resolve('ILogger', 'Main').type === 'FileLogger'; // true
|
|
333
|
+
container.resolve('ILogger', 'Main').name === 'Main'; // true
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Container modules
|
|
337
|
+
Sometimes you want to encapsulate registration logic in separate module. This is what `IContainerModule` is for.
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
import { Registration } from "ts-ioc-container";
|
|
341
|
+
|
|
342
|
+
class Development implements IContainerModule {
|
|
343
|
+
applyTo(container: IContainer): void {
|
|
344
|
+
container.add(Registration.fromClass(DevLogger));
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
class Production implements IContainerModule {
|
|
349
|
+
applyTo(container: IContainer): void {
|
|
350
|
+
container.add(Registration.fromClass(ProdLogger));
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const container = new Container(injector, { tags: ['root'] })
|
|
355
|
+
.add(Registration.fromClass(Logger))
|
|
356
|
+
.add(process.env.NODE_ENV === 'production' ? new Production() : new Development());
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## Registration module (Provider + DependencyKey)
|
|
360
|
+
Sometimes you need to keep dependency key with class together. For example, you want to register class with key 'ILogger' and you want to keep this key with class. This is what `Registration` is for.
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
import { asSingleton, perTags, forKey, Registration, Provider } from "ts-ioc-container";
|
|
364
|
+
|
|
365
|
+
@forKey('ILogger')
|
|
366
|
+
@provider(asSingleton(), perTags('root'))
|
|
367
|
+
class Logger {
|
|
368
|
+
info(message: string) {
|
|
369
|
+
console.log(message);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
container.register(Registration.fromClass(Logger));
|
|
374
|
+
|
|
375
|
+
// OR
|
|
376
|
+
|
|
377
|
+
@provider(asSingleton(), perTags('root'))
|
|
378
|
+
class Logger {
|
|
379
|
+
info(message: string) {
|
|
380
|
+
console.log(message);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
container.register('ILogger', Provider.fromClass(Logger));
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Hooks
|
|
388
|
+
Sometimes you need to invoke methods after construct or dispose of class. This is what hooks are for.
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
import {
|
|
392
|
+
Container,
|
|
393
|
+
IInjector,
|
|
394
|
+
ContainerHook,
|
|
395
|
+
Injector,
|
|
396
|
+
Registration,
|
|
397
|
+
getHooks,
|
|
398
|
+
hook,
|
|
399
|
+
} from "ts-ioc-container";
|
|
400
|
+
|
|
401
|
+
class MyInjector implements IInjector {
|
|
402
|
+
private injector = new ReflectionInjector();
|
|
403
|
+
|
|
404
|
+
resolve<T>(container: IContainer, value: constructor<T>, ...deps: unknown[]): T {
|
|
405
|
+
const instance = this.injector.resolve(container, value, ...deps);
|
|
406
|
+
for (const h of getHooks(instance, 'onConstruct')) {
|
|
407
|
+
// @ts-ignore
|
|
408
|
+
instance[h]();
|
|
409
|
+
}
|
|
410
|
+
return instance;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
@forKey('ILogger')
|
|
415
|
+
class Logger {
|
|
416
|
+
@hook('onConstruct')
|
|
417
|
+
initialize() {
|
|
418
|
+
console.log('initialized');
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
@hook('onDispose')
|
|
422
|
+
dispose() {
|
|
423
|
+
console.log('disposed');
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const container = new Container(new MyInjector())
|
|
428
|
+
.add(Registration.fromClass(Logger));
|
|
429
|
+
const logger = container.resolve<ILogger>('ILogger'); // initialized
|
|
430
|
+
|
|
431
|
+
for (const instance of container.getInstances()) {
|
|
432
|
+
for (const h of getHooks(instance, 'onDispose')) {
|
|
433
|
+
// @ts-ignore
|
|
434
|
+
instance[h](); // disposed
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
## Mocking / Tests `AutoMockedContainer`
|
|
440
|
+
Sometimes you need to automatically mock all dependencies in container. This is what `AutoMockedContainer` is for.
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
{{{include_file '__tests__/readme/mocking.spec.ts'}}}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
## Errors
|
|
448
|
+
|
|
449
|
+
- [DependencyNotFoundError.ts](lib%2Fcontainer%2FDependencyNotFoundError.ts)
|
|
450
|
+
- [DependencyMissingKeyError.ts](lib%2Fregistration%2FDependencyMissingKeyError.ts)
|
|
451
|
+
- [MethodNotImplementedError.ts](lib%2FMethodNotImplementedError.ts)
|
|
452
|
+
- [ContainerDisposedError.ts](lib%2Fcontainer%2FContainerDisposedError.ts)
|
|
453
|
+
|
|
@@ -1,28 +1,25 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AutoMockedContainer = void 0;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
AutoMockedContainer.prototype.createScope = function () {
|
|
4
|
+
const index_1 = require("../index");
|
|
5
|
+
class AutoMockedContainer {
|
|
6
|
+
createScope() {
|
|
9
7
|
throw new index_1.MethodNotImplementedError();
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
}
|
|
9
|
+
dispose() { }
|
|
10
|
+
getProviders() {
|
|
13
11
|
return new Map();
|
|
14
|
-
}
|
|
15
|
-
|
|
12
|
+
}
|
|
13
|
+
register() {
|
|
16
14
|
throw new index_1.MethodNotImplementedError();
|
|
17
|
-
}
|
|
18
|
-
|
|
15
|
+
}
|
|
16
|
+
getInstances() {
|
|
19
17
|
return [];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
}
|
|
19
|
+
removeScope() { }
|
|
20
|
+
add() {
|
|
23
21
|
throw new index_1.MethodNotImplementedError();
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
}());
|
|
22
|
+
}
|
|
23
|
+
}
|
|
27
24
|
exports.AutoMockedContainer = AutoMockedContainer;
|
|
28
25
|
//# sourceMappingURL=AutoMockedContainer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutoMockedContainer.js","sourceRoot":"","sources":["../../lib/container/AutoMockedContainer.ts"],"names":[],"mappings":";;;AAAA,
|
|
1
|
+
{"version":3,"file":"AutoMockedContainer.js","sourceRoot":"","sources":["../../lib/container/AutoMockedContainer.ts"],"names":[],"mappings":";;;AAAA,oCAA2G;AAE3G,MAAsB,mBAAmB;IACrC,WAAW;QACP,MAAM,IAAI,iCAAyB,EAAE,CAAC;IAC1C,CAAC;IAID,OAAO,KAAU,CAAC;IAElB,YAAY;QACR,OAAO,IAAI,GAAG,EAAE,CAAC;IACrB,CAAC;IAED,QAAQ;QACJ,MAAM,IAAI,iCAAyB,EAAE,CAAC;IAC1C,CAAC;IAED,YAAY;QACR,OAAO,EAAE,CAAC;IACd,CAAC;IAED,WAAW,KAAU,CAAC;IAEtB,GAAG;QACC,MAAM,IAAI,iCAAyB,EAAE,CAAC;IAC1C,CAAC;CACJ;AA1BD,kDA0BC"}
|