nano-injector 1.0.1
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/LICENSE +21 -0
- package/README.md +221 -0
- package/lib/Binder.js +81 -0
- package/lib/InjectingError.js +9 -0
- package/lib/Injector.js +126 -0
- package/lib/InjectorsStack.js +25 -0
- package/lib/Provider.js +62 -0
- package/lib/index.js +15 -0
- package/package.json +57 -0
- package/typings/Binder.d.ts +44 -0
- package/typings/InjectingError.d.ts +5 -0
- package/typings/Injector.d.ts +77 -0
- package/typings/InjectorsStack.d.ts +14 -0
- package/typings/Provider.d.ts +49 -0
- package/typings/index.d.ts +3 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Roman Pukhalskyi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# Nano-injector
|
|
2
|
+
|
|
3
|
+
Miniature dependency injection library for TypeScript and JavaScript.
|
|
4
|
+
|
|
5
|
+
# Motivation
|
|
6
|
+
|
|
7
|
+
There is a myriad of dependency injection libraries in typescript ecosystem. Most of them are built around decorators.
|
|
8
|
+
Decorators are an obvious solution for such kinds of tasks. But in terms of dependency injection, they have its
|
|
9
|
+
drawbacks - they are not type safe, which means that theoretically, it is possible to bind any value to the required type,
|
|
10
|
+
and they don't work well with interfaces and primitives, therefore some workaround solutions are required to overcome
|
|
11
|
+
this limitation.
|
|
12
|
+
Typically, injecting of an interface, using these libraries, looks like as follows:
|
|
13
|
+
```typescript
|
|
14
|
+
// binding value
|
|
15
|
+
injector.bind('IStorage').toValue(new DefStorage())
|
|
16
|
+
|
|
17
|
+
// injecting it
|
|
18
|
+
@Inject('IStorage')
|
|
19
|
+
private storage: IStorage
|
|
20
|
+
```
|
|
21
|
+
where string IStorage should be passed to Inject decorator in order to let the injector know which value should be injected
|
|
22
|
+
to the required property. Since it is just a string, any string can be passed, therefore any type can be injected,
|
|
23
|
+
and the compiler won't warn you about that. Also, there is type duplication, therefore the whole usage looks a bit ugly.
|
|
24
|
+
|
|
25
|
+
**Nano-injector** is addressing these issues, by doing dependency injection in a little bit different way - instead of using
|
|
26
|
+
decorators, it uses plain functions as dependencies providers. By doing so, the same workflow, using Nano-injector,
|
|
27
|
+
looks as follows:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
// defining provider
|
|
31
|
+
const $IStorage = createProvider<IStorage>()
|
|
32
|
+
|
|
33
|
+
// binding it to the value
|
|
34
|
+
injector.bindProvider($IStorage).toValue(new DefStorage())
|
|
35
|
+
|
|
36
|
+
// injecting it
|
|
37
|
+
private storage = $IStorage()
|
|
38
|
+
```
|
|
39
|
+
Here _**$IStorage**_ is the provider, which basically is a plain function, and which should be bound to desired value through
|
|
40
|
+
injector. Type of storage property compiler infers automatically, which reduces unneeded code duplication. Also, you can bind only
|
|
41
|
+
value of the type specified during provider creation, if not to do so, the compiler will warn you about that.
|
|
42
|
+
|
|
43
|
+
Also, unlike other libraries, Nano-injector is very small, with a very concise API.
|
|
44
|
+
More usage details you can find below, and in the example directory.
|
|
45
|
+
|
|
46
|
+
# Features
|
|
47
|
+
|
|
48
|
+
- Very simple
|
|
49
|
+
- Ultra lightweight
|
|
50
|
+
- Zero dependencies
|
|
51
|
+
- No decorators
|
|
52
|
+
- Type safe
|
|
53
|
+
- Injectable interfaces and primitives
|
|
54
|
+
|
|
55
|
+
# Installation
|
|
56
|
+
```bash
|
|
57
|
+
npm i nano-injector
|
|
58
|
+
```
|
|
59
|
+
# Usage
|
|
60
|
+
Importing dependencies:
|
|
61
|
+
```typescript
|
|
62
|
+
import { Injector, createProvider } from 'nano-injector'
|
|
63
|
+
```
|
|
64
|
+
Defining types and providers:
|
|
65
|
+
```typescript
|
|
66
|
+
// to distinguish providers from any other entities $ sign is used
|
|
67
|
+
const $Clock = createProvider<(cb: () => void, time: number) => number>()
|
|
68
|
+
const $ClockRate = createProvider<number>()
|
|
69
|
+
|
|
70
|
+
const $CPU = createProvider<CPU>()
|
|
71
|
+
interface CPU {
|
|
72
|
+
readonly model: string
|
|
73
|
+
readonly numCores: number
|
|
74
|
+
readonly frequency: number
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const $GPU = createProvider<GPU>()
|
|
78
|
+
interface GPU {
|
|
79
|
+
readonly model: string
|
|
80
|
+
readonly numRayTracingCores: number
|
|
81
|
+
readonly memorySize: number
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const $RAM = createProvider<RAM>()
|
|
85
|
+
interface RAM {
|
|
86
|
+
readonly capacity: number
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const $Motherboard = createProvider<Motherboard>()
|
|
90
|
+
interface Motherboard {
|
|
91
|
+
readonly cpu: CPU
|
|
92
|
+
readonly gpu: GPU
|
|
93
|
+
readonly ram: RAM
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
class GeForce911 implements GPU {
|
|
97
|
+
model = 'GeForce911'
|
|
98
|
+
numRayTracingCores = 100
|
|
99
|
+
memorySize = 2048
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
class DefMotherboard implements Motherboard {
|
|
103
|
+
constructor(
|
|
104
|
+
readonly model: string,
|
|
105
|
+
public cpu = $CPU(),
|
|
106
|
+
public gpu = $GPU(),
|
|
107
|
+
public ram = $RAM()
|
|
108
|
+
) {}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
class PC {
|
|
112
|
+
constructor(private name: string, private motherboard = $Motherboard()) {}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
Defining injector through which injection occurs:
|
|
116
|
+
```typescript
|
|
117
|
+
const injector = new Injector()
|
|
118
|
+
```
|
|
119
|
+
Binding providers:
|
|
120
|
+
```typescript
|
|
121
|
+
// binding $CPU provider to exact value
|
|
122
|
+
injector.bindProvider($CPU).toValue({ numCores: 4, model: 't7', frequency: 2000 })
|
|
123
|
+
|
|
124
|
+
// binding $GPU provider to class which conforms to provider's returning type
|
|
125
|
+
// calling asSingleton specifies that value should be created only once
|
|
126
|
+
injector.bindProvider($GPU).toConstructor(GeForce911).asSingleton()
|
|
127
|
+
|
|
128
|
+
// binding $RAM provider to the factory which creates value conforming to
|
|
129
|
+
// provider's returning type
|
|
130
|
+
injector.bindProvider($RAM).toFactory(() => ({
|
|
131
|
+
capacity: Math.floor(Math.random() * 1024),
|
|
132
|
+
}))
|
|
133
|
+
|
|
134
|
+
injector
|
|
135
|
+
.bindProvider($Motherboard)
|
|
136
|
+
.toFactory(
|
|
137
|
+
() =>
|
|
138
|
+
// only one required parameter is passed, the rest are initialized
|
|
139
|
+
// automatically through providers
|
|
140
|
+
new DefMotherboard('Asus ABC-123')
|
|
141
|
+
)
|
|
142
|
+
.asSingleton()
|
|
143
|
+
|
|
144
|
+
// without ignoring compiler would tell you about wrong value's type
|
|
145
|
+
// @ts-expect-error
|
|
146
|
+
injector.bindProvider($ClockRate).toValue('asd')
|
|
147
|
+
|
|
148
|
+
injector.bindProvider($ClockRate).toValue(1000)
|
|
149
|
+
|
|
150
|
+
injector.bindProvider($Clock).toValue(setTimeout)
|
|
151
|
+
```
|
|
152
|
+
Creating instances with all dependencies injected:
|
|
153
|
+
```typescript
|
|
154
|
+
// the first parameter of PC constructor is required, so it is passed into the
|
|
155
|
+
// construction method
|
|
156
|
+
injector.createInstance(PC, 'My pc')
|
|
157
|
+
```
|
|
158
|
+
Directly getting the bound to the providers values:
|
|
159
|
+
```typescript
|
|
160
|
+
injector.getValue($CPU)
|
|
161
|
+
injector.getValue($GPU)
|
|
162
|
+
injector.getValue($RAM)
|
|
163
|
+
|
|
164
|
+
injector.getValue($Clock)(() => console.log('Tick!'), 1000)
|
|
165
|
+
```
|
|
166
|
+
Manually creating instance with all its dependencies:
|
|
167
|
+
```typescript
|
|
168
|
+
new DefMotherboard(
|
|
169
|
+
'Asus xyz',
|
|
170
|
+
{ frequency: 123, model: 'Intel xyz', numCores: 1 },
|
|
171
|
+
{ model: '', memorySize: 0, numRayTracingCores: 1 },
|
|
172
|
+
{ capacity: 123 },
|
|
173
|
+
)
|
|
174
|
+
```
|
|
175
|
+
Calling function through injector:
|
|
176
|
+
```typescript
|
|
177
|
+
injector.callFunc(() => {
|
|
178
|
+
// inside function all providers return bound to them values
|
|
179
|
+
console.log('Inside function')
|
|
180
|
+
console.log('CPU:', $CPU())
|
|
181
|
+
console.log('GPU:', $GPU())
|
|
182
|
+
console.log('RAM:', $RAM())
|
|
183
|
+
})
|
|
184
|
+
```
|
|
185
|
+
It's also possible to bind few providers to the same value. The bound value should conform to
|
|
186
|
+
intersection of all providers' types:
|
|
187
|
+
```typescript
|
|
188
|
+
injector.bindProvider($RAM, $CPU, $GPU).toValue({
|
|
189
|
+
capacity: 10,
|
|
190
|
+
frequency: 100,
|
|
191
|
+
memorySize: 200,
|
|
192
|
+
model: 'ATB-21',
|
|
193
|
+
numCores: 2,
|
|
194
|
+
numRayTracingCores: 60,
|
|
195
|
+
})
|
|
196
|
+
```
|
|
197
|
+
Creating composition of injectors:
|
|
198
|
+
```typescript
|
|
199
|
+
const childInjector = new Injector({ parent: injector })
|
|
200
|
+
```
|
|
201
|
+
Overriding $CPU binding of parent injector inside child injector:
|
|
202
|
+
```typescript
|
|
203
|
+
childInjector.bindProvider($CPU).toValue({ numCores: 1, model: 'z2', frequency: 999 })
|
|
204
|
+
```
|
|
205
|
+
Injecting value to the existing instance:
|
|
206
|
+
```typescript
|
|
207
|
+
const newMotherboard = new class {
|
|
208
|
+
gpu: GPU
|
|
209
|
+
cpu: CPU
|
|
210
|
+
}
|
|
211
|
+
injector.injectValues(newMotherboard, { cpu: $CPU, gpu: $GPU })
|
|
212
|
+
```
|
|
213
|
+
# [Docs](https://protoukr.github.io/nano-injector/)
|
|
214
|
+
|
|
215
|
+
# Contributing
|
|
216
|
+
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
|
|
217
|
+
|
|
218
|
+
Please make sure to update tests as appropriate.
|
|
219
|
+
|
|
220
|
+
## License
|
|
221
|
+
[MIT](https://choosealicense.com/licenses/mit/)
|
package/lib/Binder.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Binder = void 0;
|
|
4
|
+
const InjectingError_1 = require("./InjectingError");
|
|
5
|
+
/**
|
|
6
|
+
* Class through which is defined how to create value for this binder
|
|
7
|
+
*/
|
|
8
|
+
class Binder {
|
|
9
|
+
constructor(injector) {
|
|
10
|
+
this.injector = injector;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Creates the new value if needed through previously defined method, and returns it
|
|
14
|
+
*/
|
|
15
|
+
getValue() {
|
|
16
|
+
if (this.value !== undefined) {
|
|
17
|
+
return this.value;
|
|
18
|
+
}
|
|
19
|
+
if (this.isSingleton) {
|
|
20
|
+
this.value = this.createValue();
|
|
21
|
+
return this.value;
|
|
22
|
+
}
|
|
23
|
+
return this.createValue();
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Directly defines value for this binder
|
|
27
|
+
* @param value
|
|
28
|
+
*/
|
|
29
|
+
toValue(value) {
|
|
30
|
+
this.value = value;
|
|
31
|
+
this.factory = undefined;
|
|
32
|
+
this.ctor = undefined;
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Defines constructor as a method for creating value. All previously defined methods
|
|
37
|
+
* will be ignored
|
|
38
|
+
* @param ctor
|
|
39
|
+
*/
|
|
40
|
+
toConstructor(ctor) {
|
|
41
|
+
this.ctor = ctor;
|
|
42
|
+
this.factory = undefined;
|
|
43
|
+
this.value = undefined;
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Defines factory as a method for creating value. All previously defined methods
|
|
48
|
+
* will be ignored
|
|
49
|
+
* @param factory
|
|
50
|
+
*/
|
|
51
|
+
toFactory(factory) {
|
|
52
|
+
this.factory = factory;
|
|
53
|
+
this.ctor = undefined;
|
|
54
|
+
this.value = undefined;
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Defines whether value should be a singleton. If yes the value will be created only once
|
|
59
|
+
* and the same instance will be returned forever
|
|
60
|
+
* @param value
|
|
61
|
+
*/
|
|
62
|
+
asSingleton(value = true) {
|
|
63
|
+
this.isSingleton = value;
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Creates value through previously defined method
|
|
68
|
+
* @private
|
|
69
|
+
*/
|
|
70
|
+
createValue() {
|
|
71
|
+
if (this.ctor != null) {
|
|
72
|
+
// eslint-disable-next-line new-cap
|
|
73
|
+
return new this.ctor();
|
|
74
|
+
}
|
|
75
|
+
if (this.factory != null) {
|
|
76
|
+
return this.factory(this.injector);
|
|
77
|
+
}
|
|
78
|
+
throw new InjectingError_1.InjectingError('Creation method isn\'t specified');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
exports.Binder = Binder;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InjectingError = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Just an error class, an instance of which is fired in some cases
|
|
6
|
+
*/
|
|
7
|
+
class InjectingError extends Error {
|
|
8
|
+
}
|
|
9
|
+
exports.InjectingError = InjectingError;
|
package/lib/Injector.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Injector = exports.$Injector = void 0;
|
|
4
|
+
const Binder_1 = require("./Binder");
|
|
5
|
+
const Provider_1 = require("./Provider");
|
|
6
|
+
const InjectorsStack_1 = require("./InjectorsStack");
|
|
7
|
+
const InjectingError_1 = require("./InjectingError");
|
|
8
|
+
/**
|
|
9
|
+
* Provider which every injector binds itself to
|
|
10
|
+
*/
|
|
11
|
+
exports.$Injector = (0, Provider_1.createProvider)();
|
|
12
|
+
/**
|
|
13
|
+
* Main entity in the library, which holds provider's bindings and through which
|
|
14
|
+
* dependencies are resolved
|
|
15
|
+
*/
|
|
16
|
+
class Injector {
|
|
17
|
+
/**
|
|
18
|
+
* @param params.name name of the injector which can be useful mainly for debugging purposes
|
|
19
|
+
* @param params.parent parent injector for the composition of injectors
|
|
20
|
+
* @param params.logger specific log function if the custom output is required
|
|
21
|
+
*/
|
|
22
|
+
constructor(params = {}) {
|
|
23
|
+
this.binders = new Map();
|
|
24
|
+
this.resolvingProviders = [];
|
|
25
|
+
const { name, parent, logger = console.warn } = params;
|
|
26
|
+
this.name = name;
|
|
27
|
+
this.parent = parent;
|
|
28
|
+
this.logger = logger;
|
|
29
|
+
this.bindProvider(exports.$Injector).toValue(this);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Creates new binder and joins it to the specified providers. If the provider
|
|
33
|
+
* is already bound, then overriding occurs
|
|
34
|
+
* @param providers providers which the binder should be joined to
|
|
35
|
+
*/
|
|
36
|
+
bindProvider(...providers) {
|
|
37
|
+
const binder = new Binder_1.Binder(this);
|
|
38
|
+
providers.forEach((provider) => this.binders.set(provider, binder));
|
|
39
|
+
return binder;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Resolves all providers to their values and assigns them to the specified instance
|
|
43
|
+
* @param instance
|
|
44
|
+
* @param providers
|
|
45
|
+
*/
|
|
46
|
+
injectValues(instance, providers) {
|
|
47
|
+
Object.entries(providers)
|
|
48
|
+
.filter(([, value]) => (0, Provider_1.isProvider)(value))
|
|
49
|
+
.forEach(([name, provider]) => {
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
51
|
+
instance[name] = this.getValue(provider);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Activates this injector and creates new instance of the type with the provided arguments
|
|
56
|
+
* @param type
|
|
57
|
+
* @param args
|
|
58
|
+
*/
|
|
59
|
+
createInstance(type, ...args) {
|
|
60
|
+
// eslint-disable-next-line new-cap
|
|
61
|
+
return this.activateAndCall(() => new type(...args));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Activates this injector and calls the function with provided arguments
|
|
65
|
+
* @param func function which should be called
|
|
66
|
+
* @param args args which should be passed to the called function
|
|
67
|
+
*/
|
|
68
|
+
callFunc(func, ...args) {
|
|
69
|
+
return this.activateAndCall(() => func(...args));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Returns bound to the specified provider value. If the value is not found
|
|
73
|
+
* undefined is returned
|
|
74
|
+
* @param provider
|
|
75
|
+
*/
|
|
76
|
+
getValue(provider) {
|
|
77
|
+
const binder = this.getBinder(provider);
|
|
78
|
+
if (binder == null) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
this.checkCircularDependency(provider);
|
|
82
|
+
this.resolvingProviders.push(provider);
|
|
83
|
+
const value = this.activateAndCall(() => binder.getValue());
|
|
84
|
+
this.resolvingProviders.pop();
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Finds binder for the specified provider recursively up to the root injector
|
|
89
|
+
* @param provider
|
|
90
|
+
* @private
|
|
91
|
+
*/
|
|
92
|
+
getBinder(provider) {
|
|
93
|
+
const binder = this.binders.get(provider);
|
|
94
|
+
if (binder == null && this.parent != null) {
|
|
95
|
+
return this.parent.getBinder(provider);
|
|
96
|
+
}
|
|
97
|
+
return binder;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Checks is there circular dependency and throws error if so
|
|
101
|
+
* @param provider
|
|
102
|
+
* @private
|
|
103
|
+
*/
|
|
104
|
+
checkCircularDependency(provider) {
|
|
105
|
+
const i = this.resolvingProviders.indexOf(provider);
|
|
106
|
+
if (i === -1) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const providers = [...this.resolvingProviders.slice(i), provider]
|
|
110
|
+
.map(Provider_1.getProviderName)
|
|
111
|
+
.map((name) => name !== null && name !== void 0 ? name : '?');
|
|
112
|
+
throw new InjectingError_1.InjectingError(`Circular dependency detected: ${providers.join('->')}`);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Temporary activates this injector calls provided function and returns its value
|
|
116
|
+
* @param func
|
|
117
|
+
* @private
|
|
118
|
+
*/
|
|
119
|
+
activateAndCall(func) {
|
|
120
|
+
InjectorsStack_1.InjectorsStack.push(this);
|
|
121
|
+
const value = func();
|
|
122
|
+
InjectorsStack_1.InjectorsStack.pop();
|
|
123
|
+
return value;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
exports.Injector = Injector;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InjectorsStack = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Private class for holding current active injector
|
|
6
|
+
*/
|
|
7
|
+
class _InjectorsStack {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.injectors = [];
|
|
10
|
+
}
|
|
11
|
+
get(provider) {
|
|
12
|
+
var _a;
|
|
13
|
+
return (_a = this.activeInjector) === null || _a === void 0 ? void 0 : _a.getValue(provider);
|
|
14
|
+
}
|
|
15
|
+
push(injector) {
|
|
16
|
+
if (this.activeInjector != null) {
|
|
17
|
+
this.injectors.push(this.activeInjector);
|
|
18
|
+
}
|
|
19
|
+
this.activeInjector = injector;
|
|
20
|
+
}
|
|
21
|
+
pop() {
|
|
22
|
+
this.activeInjector = this.injectors.pop();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.InjectorsStack = new _InjectorsStack();
|
package/lib/Provider.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getProviderName = exports.getProviderID = exports.isProvider = exports.createProvider = void 0;
|
|
4
|
+
const InjectorsStack_1 = require("./InjectorsStack");
|
|
5
|
+
const InjectingError_1 = require("./InjectingError");
|
|
6
|
+
/**
|
|
7
|
+
* Symbol used for storing the id of a provider
|
|
8
|
+
*/
|
|
9
|
+
const ID_SYMBOL = Symbol('id');
|
|
10
|
+
/**
|
|
11
|
+
* Symbol used for storing the name of a provider
|
|
12
|
+
*/
|
|
13
|
+
const NAME_SYMBOL = Symbol('name');
|
|
14
|
+
let PROVIDER_ID = 0;
|
|
15
|
+
/**
|
|
16
|
+
* Creates the new provider for some value with a specific type
|
|
17
|
+
* @param name name for this provider used mainly for debugging purposes
|
|
18
|
+
*/
|
|
19
|
+
function createProvider(name) {
|
|
20
|
+
function provider(...args) {
|
|
21
|
+
const value = InjectorsStack_1.InjectorsStack.get(provider);
|
|
22
|
+
if (value !== undefined) {
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
if (args.length !== 0) {
|
|
26
|
+
return args[0];
|
|
27
|
+
}
|
|
28
|
+
throw new InjectingError_1.InjectingError(`Value of ${name !== null && name !== void 0 ? name : 'unknown'} provider is not found`);
|
|
29
|
+
}
|
|
30
|
+
provider[NAME_SYMBOL] = name;
|
|
31
|
+
provider[ID_SYMBOL] = PROVIDER_ID++;
|
|
32
|
+
return provider;
|
|
33
|
+
}
|
|
34
|
+
exports.createProvider = createProvider;
|
|
35
|
+
/**
|
|
36
|
+
* Determines whether the received value is a provider
|
|
37
|
+
* @param value value which should be tested
|
|
38
|
+
*/
|
|
39
|
+
function isProvider(value) {
|
|
40
|
+
if (typeof value !== 'function') {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
const provider = value;
|
|
44
|
+
return typeof provider[ID_SYMBOL] !== 'undefined';
|
|
45
|
+
}
|
|
46
|
+
exports.isProvider = isProvider;
|
|
47
|
+
/**
|
|
48
|
+
* Returns the id of the specified provider
|
|
49
|
+
* @param provider
|
|
50
|
+
*/
|
|
51
|
+
function getProviderID(provider) {
|
|
52
|
+
return provider[ID_SYMBOL];
|
|
53
|
+
}
|
|
54
|
+
exports.getProviderID = getProviderID;
|
|
55
|
+
/**
|
|
56
|
+
* Returns the name of the specified provider
|
|
57
|
+
* @param provider
|
|
58
|
+
*/
|
|
59
|
+
function getProviderName(provider) {
|
|
60
|
+
return provider[NAME_SYMBOL];
|
|
61
|
+
}
|
|
62
|
+
exports.getProviderName = getProviderName;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
5
|
+
}) : (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
o[k2] = m[k];
|
|
8
|
+
}));
|
|
9
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
10
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
__exportStar(require("./Injector"), exports);
|
|
14
|
+
__exportStar(require("./Provider"), exports);
|
|
15
|
+
__exportStar(require("./InjectingError"), exports);
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nano-injector",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Miniature dependency injection library for TypeScript and JavaScript",
|
|
5
|
+
"main": "./lib/index.js",
|
|
6
|
+
"types": "./typings/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"clear": "rimraf typings docs lib",
|
|
9
|
+
"prepublishOnly": "npm run clear && npm test && npm run build",
|
|
10
|
+
"precommit": "npm run clear && npm run lint && npm test && npm run build && npm run docs",
|
|
11
|
+
"lint": "ts-standard",
|
|
12
|
+
"lintfix": "ts-standard --fix",
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"docs": "typedoc src/index.ts",
|
|
15
|
+
"test": "ts-mocha -p ./tsconfig.json ./test/*.test.ts"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+ssh://git@github.com/protoukr/nano-injector"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"lib",
|
|
23
|
+
"typings"
|
|
24
|
+
],
|
|
25
|
+
"keywords": [
|
|
26
|
+
"typescript",
|
|
27
|
+
"dependency injection",
|
|
28
|
+
"di",
|
|
29
|
+
"ioc",
|
|
30
|
+
"inversion of control"
|
|
31
|
+
],
|
|
32
|
+
"author": "Roman Pukhalskyi",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/protoukr/nano-injector/issues"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/protoukr/nano-injector",
|
|
38
|
+
"ts-standard": {
|
|
39
|
+
"ignore": [
|
|
40
|
+
"lib",
|
|
41
|
+
"docs",
|
|
42
|
+
"typings"
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/chai": "^4.2.22",
|
|
47
|
+
"@types/mocha": "^8.2.3",
|
|
48
|
+
"chai": "^4.3.4",
|
|
49
|
+
"mocha": "^8.4.0",
|
|
50
|
+
"rimraf": "^3.0.2",
|
|
51
|
+
"ts-mocha": "^8.0.0",
|
|
52
|
+
"ts-node": "^10.4.0",
|
|
53
|
+
"ts-standard": "^11.0.0",
|
|
54
|
+
"typedoc": "^0.22.10",
|
|
55
|
+
"typescript": "^4.4.3"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Injector } from './Injector';
|
|
2
|
+
/**
|
|
3
|
+
* Class through which is defined how to create value for this binder
|
|
4
|
+
*/
|
|
5
|
+
export declare class Binder<T> {
|
|
6
|
+
private readonly injector;
|
|
7
|
+
private value;
|
|
8
|
+
private ctor;
|
|
9
|
+
private factory;
|
|
10
|
+
private isSingleton;
|
|
11
|
+
constructor(injector: Injector);
|
|
12
|
+
/**
|
|
13
|
+
* Creates the new value if needed through previously defined method, and returns it
|
|
14
|
+
*/
|
|
15
|
+
getValue(): T;
|
|
16
|
+
/**
|
|
17
|
+
* Directly defines value for this binder
|
|
18
|
+
* @param value
|
|
19
|
+
*/
|
|
20
|
+
toValue(value: T): Binder<T>;
|
|
21
|
+
/**
|
|
22
|
+
* Defines constructor as a method for creating value. All previously defined methods
|
|
23
|
+
* will be ignored
|
|
24
|
+
* @param ctor
|
|
25
|
+
*/
|
|
26
|
+
toConstructor(ctor: new () => T): Binder<T>;
|
|
27
|
+
/**
|
|
28
|
+
* Defines factory as a method for creating value. All previously defined methods
|
|
29
|
+
* will be ignored
|
|
30
|
+
* @param factory
|
|
31
|
+
*/
|
|
32
|
+
toFactory(factory: (injector?: Injector) => T): Binder<T>;
|
|
33
|
+
/**
|
|
34
|
+
* Defines whether value should be a singleton. If yes the value will be created only once
|
|
35
|
+
* and the same instance will be returned forever
|
|
36
|
+
* @param value
|
|
37
|
+
*/
|
|
38
|
+
asSingleton(value?: boolean): Binder<T>;
|
|
39
|
+
/**
|
|
40
|
+
* Creates value through previously defined method
|
|
41
|
+
* @private
|
|
42
|
+
*/
|
|
43
|
+
private createValue;
|
|
44
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Binder } from './Binder';
|
|
2
|
+
import { Provider } from './Provider';
|
|
3
|
+
declare type UnionToIntersection<T> = (T extends any ? (k: T) => void : never) extends (k: infer R) => void ? R : never;
|
|
4
|
+
/**
|
|
5
|
+
* Provider which every injector binds itself to
|
|
6
|
+
*/
|
|
7
|
+
export declare const $Injector: Provider<Injector>;
|
|
8
|
+
/**
|
|
9
|
+
* Main entity in the library, which holds provider's bindings and through which
|
|
10
|
+
* dependencies are resolved
|
|
11
|
+
*/
|
|
12
|
+
export declare class Injector {
|
|
13
|
+
private readonly binders;
|
|
14
|
+
private readonly resolvingProviders;
|
|
15
|
+
private readonly name;
|
|
16
|
+
private readonly parent;
|
|
17
|
+
private readonly logger;
|
|
18
|
+
/**
|
|
19
|
+
* @param params.name name of the injector which can be useful mainly for debugging purposes
|
|
20
|
+
* @param params.parent parent injector for the composition of injectors
|
|
21
|
+
* @param params.logger specific log function if the custom output is required
|
|
22
|
+
*/
|
|
23
|
+
constructor(params?: {
|
|
24
|
+
name?: string;
|
|
25
|
+
parent?: Injector;
|
|
26
|
+
logger?: (msg: string) => unknown;
|
|
27
|
+
});
|
|
28
|
+
/**
|
|
29
|
+
* Creates new binder and joins it to the specified providers. If the provider
|
|
30
|
+
* is already bound, then overriding occurs
|
|
31
|
+
* @param providers providers which the binder should be joined to
|
|
32
|
+
*/
|
|
33
|
+
bindProvider<ProviderT extends Array<Provider<unknown>>, ValueT extends UnionToIntersection<ProviderT extends Array<Provider<infer R>> ? R : never>>(...providers: ProviderT): Binder<ValueT>;
|
|
34
|
+
/**
|
|
35
|
+
* Resolves all providers to their values and assigns them to the specified instance
|
|
36
|
+
* @param instance
|
|
37
|
+
* @param providers
|
|
38
|
+
*/
|
|
39
|
+
injectValues<T extends object, K extends keyof T>(instance: T, providers: Record<K, Provider<T[K]>>): void;
|
|
40
|
+
/**
|
|
41
|
+
* Activates this injector and creates new instance of the type with the provided arguments
|
|
42
|
+
* @param type
|
|
43
|
+
* @param args
|
|
44
|
+
*/
|
|
45
|
+
createInstance<ClassT extends new (...args: unknown[]) => unknown, CtorParamsT extends ConstructorParameters<ClassT>, InstT extends InstanceType<ClassT>>(type: ClassT, ...args: CtorParamsT): InstT;
|
|
46
|
+
/**
|
|
47
|
+
* Activates this injector and calls the function with provided arguments
|
|
48
|
+
* @param func function which should be called
|
|
49
|
+
* @param args args which should be passed to the called function
|
|
50
|
+
*/
|
|
51
|
+
callFunc<FuncT extends (...args: unknown[]) => unknown, ParamsT extends Parameters<FuncT>, ReturnT extends ReturnType<FuncT>>(func: FuncT, ...args: ParamsT): ReturnT;
|
|
52
|
+
/**
|
|
53
|
+
* Returns bound to the specified provider value. If the value is not found
|
|
54
|
+
* undefined is returned
|
|
55
|
+
* @param provider
|
|
56
|
+
*/
|
|
57
|
+
getValue<ProviderT extends Provider<unknown>, ValueT extends ProviderT extends Provider<infer R> ? R : never>(provider: ProviderT): ValueT | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* Finds binder for the specified provider recursively up to the root injector
|
|
60
|
+
* @param provider
|
|
61
|
+
* @private
|
|
62
|
+
*/
|
|
63
|
+
private getBinder;
|
|
64
|
+
/**
|
|
65
|
+
* Checks is there circular dependency and throws error if so
|
|
66
|
+
* @param provider
|
|
67
|
+
* @private
|
|
68
|
+
*/
|
|
69
|
+
private checkCircularDependency;
|
|
70
|
+
/**
|
|
71
|
+
* Temporary activates this injector calls provided function and returns its value
|
|
72
|
+
* @param func
|
|
73
|
+
* @private
|
|
74
|
+
*/
|
|
75
|
+
private activateAndCall;
|
|
76
|
+
}
|
|
77
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Injector } from './Injector';
|
|
2
|
+
import { Provider } from './Provider';
|
|
3
|
+
/**
|
|
4
|
+
* Private class for holding current active injector
|
|
5
|
+
*/
|
|
6
|
+
declare class _InjectorsStack {
|
|
7
|
+
private readonly injectors;
|
|
8
|
+
private activeInjector;
|
|
9
|
+
get<ProviderT extends Provider<unknown>, ValueT extends ProviderT extends Provider<infer R> ? R : never>(provider: ProviderT): ValueT | undefined;
|
|
10
|
+
push(injector: Injector): void;
|
|
11
|
+
pop(): void;
|
|
12
|
+
}
|
|
13
|
+
export declare const InjectorsStack: _InjectorsStack;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Symbol used for storing the id of a provider
|
|
3
|
+
*/
|
|
4
|
+
declare const ID_SYMBOL: unique symbol;
|
|
5
|
+
/**
|
|
6
|
+
* Symbol used for storing the name of a provider
|
|
7
|
+
*/
|
|
8
|
+
declare const NAME_SYMBOL: unique symbol;
|
|
9
|
+
/**
|
|
10
|
+
* Provider is a second main concept in the library. Basically just it is just a function
|
|
11
|
+
* through which binding with the specific value occurs.
|
|
12
|
+
*/
|
|
13
|
+
export interface Provider<T> {
|
|
14
|
+
readonly [ID_SYMBOL]: number;
|
|
15
|
+
readonly [NAME_SYMBOL]: string;
|
|
16
|
+
/**
|
|
17
|
+
* Returns bound to this provider value. Throws the exception if it's not found.
|
|
18
|
+
* If throwing the exception is not desired any value to the provider should
|
|
19
|
+
* be passed
|
|
20
|
+
*/
|
|
21
|
+
(): T;
|
|
22
|
+
/**
|
|
23
|
+
* Returns bound to this provider value. Returns default one if the bound value
|
|
24
|
+
* is not found
|
|
25
|
+
* @param defValue
|
|
26
|
+
*/
|
|
27
|
+
<DefValT>(defValue: DefValT): T | DefValT;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Creates the new provider for some value with a specific type
|
|
31
|
+
* @param name name for this provider used mainly for debugging purposes
|
|
32
|
+
*/
|
|
33
|
+
export declare function createProvider<T>(name?: string): Provider<T>;
|
|
34
|
+
/**
|
|
35
|
+
* Determines whether the received value is a provider
|
|
36
|
+
* @param value value which should be tested
|
|
37
|
+
*/
|
|
38
|
+
export declare function isProvider<T = unknown>(value: unknown): value is Provider<T>;
|
|
39
|
+
/**
|
|
40
|
+
* Returns the id of the specified provider
|
|
41
|
+
* @param provider
|
|
42
|
+
*/
|
|
43
|
+
export declare function getProviderID(provider: Provider<unknown>): number;
|
|
44
|
+
/**
|
|
45
|
+
* Returns the name of the specified provider
|
|
46
|
+
* @param provider
|
|
47
|
+
*/
|
|
48
|
+
export declare function getProviderName(provider: Provider<unknown>): string;
|
|
49
|
+
export {};
|