mesh-ioc 0.3.0 β 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -79
- package/out/main/bindings.d.ts +13 -34
- package/out/main/bindings.js +0 -61
- package/out/main/decorators/dep.d.ts +7 -0
- package/out/main/decorators/dep.js +34 -0
- package/out/main/decorators/service.d.ts +7 -0
- package/out/main/decorators/service.js +10 -0
- package/out/main/errors.d.ts +1 -1
- package/out/main/errors.js +4 -4
- package/out/main/index.d.ts +2 -3
- package/out/main/index.js +2 -3
- package/out/main/mesh.d.ts +13 -13
- package/out/main/mesh.js +60 -49
- package/out/main/scope.d.ts +11 -0
- package/out/main/scope.js +41 -0
- package/out/main/types.d.ts +24 -0
- package/out/main/util.d.ts +2 -0
- package/out/main/util.js +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,13 +6,85 @@ Mesh IoC solves the problem of dependency management of application services. It
|
|
|
6
6
|
|
|
7
7
|
## Key features
|
|
8
8
|
|
|
9
|
-
- π Very slim β about
|
|
9
|
+
- π Very slim β about 2KB minified, few hundred bytes gzipped
|
|
10
10
|
- β‘οΈ Blazing fast
|
|
11
11
|
- π§© Flexible and composable
|
|
12
12
|
- πΏ Ergonomic
|
|
13
13
|
- π π₯ Tolerates circular dependencies
|
|
14
14
|
- π΅οΈββοΈ Provides APIs for dependency analysis
|
|
15
15
|
|
|
16
|
+
## Quick API Cheatsheet
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
// Mesh is an IoC container that stores service bindings and instantiated objects
|
|
20
|
+
const mesh = new Mesh('someName');
|
|
21
|
+
|
|
22
|
+
// Bindings:
|
|
23
|
+
|
|
24
|
+
// 1. Service (zero-arg constructor) to itself
|
|
25
|
+
mesh.service(SomeDatabase);
|
|
26
|
+
// 2. Abstract class to service implementation
|
|
27
|
+
mesh.service(Logger, SomeLogger);
|
|
28
|
+
// 3. String key to service (π because of limited type support)
|
|
29
|
+
mesh.service('Something', Something);
|
|
30
|
+
// 4. Service to instance
|
|
31
|
+
mesh.constant(SomeService, someServiceInstance);
|
|
32
|
+
// 5. String key to arbitrary constant
|
|
33
|
+
mesh.constant('SessionId', 42);
|
|
34
|
+
// 6. Alias (key to another key)
|
|
35
|
+
mesh.alias('DB', SomeDatabase);
|
|
36
|
+
|
|
37
|
+
// Methods can also be chained
|
|
38
|
+
mesh.service(MyFoo)
|
|
39
|
+
.alias(Foo, MyFoo)
|
|
40
|
+
.constant('Secret', 'supersecret');
|
|
41
|
+
|
|
42
|
+
// Declarative bindings in services:
|
|
43
|
+
|
|
44
|
+
class SomeDatabase {
|
|
45
|
+
@dep() logger!: Logger; // auto-resolved by Mesh
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Manual resolution:
|
|
49
|
+
|
|
50
|
+
const db = mesh.resolve(SomeDatabase);
|
|
51
|
+
|
|
52
|
+
// Connect instances:
|
|
53
|
+
|
|
54
|
+
class User {
|
|
55
|
+
@dep() db!: MyDatabase;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const user = mesh.connect(new User()); // now user.db will also be resolved by Mesh
|
|
59
|
+
|
|
60
|
+
// Scopes:
|
|
61
|
+
|
|
62
|
+
// Declare scoped bindings
|
|
63
|
+
mesh.scope('request')
|
|
64
|
+
.service(UsersRouter)
|
|
65
|
+
.service(OrdersRouter);
|
|
66
|
+
|
|
67
|
+
// Create and use scope
|
|
68
|
+
const request = mesh.createScope('request')
|
|
69
|
+
// Bind scope-specific data
|
|
70
|
+
.constant(Request, req)
|
|
71
|
+
.constant(Response, res);
|
|
72
|
+
|
|
73
|
+
// Scoped services can use scope-specific data
|
|
74
|
+
class UsersRouter {
|
|
75
|
+
@dep() req!: Request;
|
|
76
|
+
@dep() res!: Response;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Middleware:
|
|
80
|
+
mesh.use(instance => {
|
|
81
|
+
// Allows modifying instances as they are created or connected
|
|
82
|
+
// (useful for logging, stats or replacing instances with proxies)
|
|
83
|
+
console.log('Instantiated', instance);
|
|
84
|
+
return instance;
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
16
88
|
## IoC Recap
|
|
17
89
|
|
|
18
90
|
[IoC](https://en.wikipedia.org/wiki/Inversion_of_control) when applied to class dependency management states that each component should not resolve their dependencies β instead the dependencies should be provided to each component.
|
|
@@ -90,8 +162,9 @@ class Redis {
|
|
|
90
162
|
// app.ts
|
|
91
163
|
|
|
92
164
|
class AppMesh extends Mesh {
|
|
93
|
-
|
|
94
|
-
this.
|
|
165
|
+
// List all services so that mesh connects them together
|
|
166
|
+
this.service(Redis);
|
|
167
|
+
this.service(Logger, ConsoleLogger);
|
|
95
168
|
}
|
|
96
169
|
```
|
|
97
170
|
|
|
@@ -115,7 +188,7 @@ There are several aspects that differentiate Mesh IoC from the rest of the DI li
|
|
|
115
188
|
|
|
116
189
|
- Constant values can be bound to mesh. Those could be instances of other classes.
|
|
117
190
|
|
|
118
|
-
**Important!** Mesh should be used to track _services_. We defined services as classes with **zero-argument constructors** (this is also enforced by TypeScript). However, there are multiple patterns to support
|
|
191
|
+
**Important!** Mesh should be used to track _services_. We defined services as classes with **zero-argument constructors** (this is also enforced by TypeScript). However, there are multiple patterns to support constructor arguments, read on!
|
|
119
192
|
|
|
120
193
|
## Application Architecture Guide
|
|
121
194
|
|
|
@@ -127,49 +200,42 @@ This short guide briefly explains the basic concepts of a good application archi
|
|
|
127
200
|
- **Request/session scope**: things like traditional HTTP routers will depend on `request` and `response` objects; the same reasoning can be applied to other scenarios, for example, web socket server may need functionality per each connected client βΒ such components will depend on client socket.
|
|
128
201
|
- **Short-lived per-instance scope**: if you use "fat" classes (e.g. Active Record pattern) then each entity instances should be conceptually "connected" to the rest of the application (e.g. `instance.save()` should somehow know about the database)
|
|
129
202
|
|
|
130
|
-
2. Build the mesh hierarchy, starting from application scope.
|
|
203
|
+
2. Build the mesh hierarchy, starting from application scope, descending into more granular scopes.
|
|
131
204
|
|
|
132
205
|
```ts
|
|
133
206
|
// app.ts
|
|
134
207
|
export class App {
|
|
135
208
|
// You can either inherit from Mesh or store it as a field.
|
|
136
209
|
// Name parameter is optional, but can be useful for debugging.
|
|
137
|
-
mesh
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
210
|
+
mesh: Mesh;
|
|
211
|
+
|
|
212
|
+
constructor() {
|
|
213
|
+
this.mesh = new Mesh('App')
|
|
214
|
+
// Define your application-scoped services
|
|
215
|
+
.constant(App, this)
|
|
216
|
+
.service(Logger, GlobalLogger);
|
|
217
|
+
.service(MyDatabase);
|
|
218
|
+
.service(MyServer);
|
|
219
|
+
// Define your session-scoped services
|
|
220
|
+
.scope('session')
|
|
221
|
+
.service(Logger, SessionLogger)
|
|
222
|
+
.service(SessionScopedService);
|
|
223
|
+
}
|
|
143
224
|
|
|
144
225
|
start() {
|
|
145
226
|
// Define logic for application startup
|
|
146
227
|
// (e.g. connect to databases, start listening to servers, etc)
|
|
228
|
+
this.mesh.resolve(MyServer).listen();
|
|
147
229
|
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// session.ts
|
|
151
|
-
export class Session {
|
|
152
|
-
mesh: Mesh;
|
|
153
230
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
this.mesh.constant(Response, req);
|
|
163
|
-
// Bindings from parent can be overridden, so that session-scoped services
|
|
164
|
-
// could use more specialized versions
|
|
165
|
-
this.mesh.bind(Logger, SessionLogger);
|
|
166
|
-
// Define other session-scoped services
|
|
167
|
-
this.mesh.bind(SessionScopedService);
|
|
168
|
-
// ...
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
start() {
|
|
172
|
-
// Define what happens when session is established
|
|
231
|
+
startSession(req: Request, res: Response) {
|
|
232
|
+
// Bind session-specific data (e.g. request, response, client web socket, session id, etc)
|
|
233
|
+
const sessionMesh = this.mesh.createScope('session')
|
|
234
|
+
// Session-specific data can be bound by session-scoped services
|
|
235
|
+
.constant(Request, req)
|
|
236
|
+
.constant(Response, res);
|
|
237
|
+
// Define logic for session initialisation
|
|
238
|
+
sessionMesh.resolve(SessionScopedService).doStuff();
|
|
173
239
|
}
|
|
174
240
|
}
|
|
175
241
|
```
|
|
@@ -187,8 +253,7 @@ This short guide briefly explains the basic concepts of a good application archi
|
|
|
187
253
|
|
|
188
254
|
```ts
|
|
189
255
|
export class MyServer {
|
|
190
|
-
|
|
191
|
-
@dep() mesh!: Mesh;
|
|
256
|
+
@dep() app!: App;
|
|
192
257
|
|
|
193
258
|
// The actual server (e.g. http server or web socket server)
|
|
194
259
|
server: Server;
|
|
@@ -196,9 +261,7 @@ This short guide briefly explains the basic concepts of a good application archi
|
|
|
196
261
|
constructor() {
|
|
197
262
|
this.server = new Server((req, res) => {
|
|
198
263
|
// This is the actual entrypoint of Session
|
|
199
|
-
|
|
200
|
-
// Note the similarity to application entrypoint
|
|
201
|
-
session.start();
|
|
264
|
+
app.startSession(req, res);
|
|
202
265
|
});
|
|
203
266
|
}
|
|
204
267
|
}
|
|
@@ -211,7 +274,7 @@ This short guide briefly explains the basic concepts of a good application archi
|
|
|
211
274
|
|
|
212
275
|
@dep() database!: Database;
|
|
213
276
|
@dep() req!: Request;
|
|
214
|
-
@dep() res!:
|
|
277
|
+
@dep() res!: Response;
|
|
215
278
|
|
|
216
279
|
// ...
|
|
217
280
|
}
|
|
@@ -240,7 +303,7 @@ class User {
|
|
|
240
303
|
@dep() database!: Database;
|
|
241
304
|
|
|
242
305
|
// Note: constructor can have arbitrary arguments in this case,
|
|
243
|
-
// because the
|
|
306
|
+
// because the instantiation isn't controlled by Mesh
|
|
244
307
|
constructor(
|
|
245
308
|
public firstName = '',
|
|
246
309
|
public lastName = '',
|
|
@@ -254,6 +317,7 @@ class User {
|
|
|
254
317
|
}
|
|
255
318
|
|
|
256
319
|
class UserService {
|
|
320
|
+
// Note: Mesh is automatically available in all connected services
|
|
257
321
|
@dep() mesh!: Mesh;
|
|
258
322
|
|
|
259
323
|
createUser(firstName = '', lastName = '', email = '', /*...*/) {
|
|
@@ -266,43 +330,6 @@ class UserService {
|
|
|
266
330
|
|
|
267
331
|
Note: the important limitation of this approach is that `@dep` are not available in entity constructors (e.g. `database` cannot be resolved in `User` constructor, because by the time the instance is instantiated it's not yet connected to the mesh).
|
|
268
332
|
|
|
269
|
-
### Binding classes
|
|
270
|
-
|
|
271
|
-
Mesh typically connects the instances of services (again, services are classes with zero-arg constructors).
|
|
272
|
-
|
|
273
|
-
However, in some cases you may need to instantiate other classes, for example, third-party or with non-zero-arg constructors. You can always do it directly, however, a level of indirection can be introduced by defining a class on Mesh. This can be especially useful in tests where classes can be substituted on Mesh level without changing the implementation.
|
|
274
|
-
|
|
275
|
-
Example:
|
|
276
|
-
|
|
277
|
-
```ts
|
|
278
|
-
// An example arbitrary class, unconnected to mesh
|
|
279
|
-
class Session {
|
|
280
|
-
constructor(readonly sessionId: number) {}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// A normal service connected to mesh
|
|
284
|
-
class SessionManager {
|
|
285
|
-
// Class constructor is injected (note: `key` is required because it cannot be deferred in this case)
|
|
286
|
-
@dep({ key: 'Session' }) Session!: typeof Session;
|
|
287
|
-
|
|
288
|
-
createSession(id: number): Session {
|
|
289
|
-
// Instantiate using injected constructor
|
|
290
|
-
return new this.Session(id);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// ...
|
|
295
|
-
const mesh = new Mesh();
|
|
296
|
-
mesh.bind(SessionManager);
|
|
297
|
-
mesh.class(Session); // This makes Session class available as a binding
|
|
298
|
-
|
|
299
|
-
// In tests this can be overridden, so SessionManager will transparently instantiate a different class
|
|
300
|
-
// (assuming constructor signatures match)
|
|
301
|
-
mesh.class(Session, MySession);
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
This approach can also be combined with `connect` so that the arbitrary class can also use `@dep`. Mix & match FTW!
|
|
305
|
-
|
|
306
333
|
## License
|
|
307
334
|
|
|
308
335
|
[ISC](https://en.wikipedia.org/wiki/ISC_license) Β© Boris Okunskiy
|
package/out/main/bindings.d.ts
CHANGED
|
@@ -1,35 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export declare
|
|
4
|
-
|
|
5
|
-
readonly key: string;
|
|
6
|
-
constructor(mesh: Mesh, key: string);
|
|
7
|
-
abstract get(): T;
|
|
8
|
-
}
|
|
9
|
-
export declare class ConstantBinding<T> extends Binding<T> {
|
|
1
|
+
import { ServiceConstructor } from './types';
|
|
2
|
+
export declare type Binding<T> = ConstantBinding<T> | ServiceBinding<T> | AliasBinding;
|
|
3
|
+
export declare type ConstantBinding<T> = {
|
|
4
|
+
type: 'constant';
|
|
10
5
|
value: T;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
new (): {
|
|
21
|
-
[x: string]: any;
|
|
22
|
-
};
|
|
23
|
-
[x: string]: any;
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
export declare class ClassBinding<T extends Constructor<T>> extends Binding<T> {
|
|
27
|
-
readonly ctor: T;
|
|
28
|
-
constructor(mesh: Mesh, key: string, ctor: T);
|
|
29
|
-
get(): T;
|
|
30
|
-
}
|
|
31
|
-
export declare class ProxyBinding<T> extends Binding<T> {
|
|
32
|
-
readonly alias: string;
|
|
33
|
-
constructor(mesh: Mesh, key: string, alias: string);
|
|
34
|
-
get(): T;
|
|
35
|
-
}
|
|
6
|
+
};
|
|
7
|
+
export declare type ServiceBinding<T> = {
|
|
8
|
+
type: 'service';
|
|
9
|
+
class: ServiceConstructor<T>;
|
|
10
|
+
};
|
|
11
|
+
export declare type AliasBinding = {
|
|
12
|
+
type: 'alias';
|
|
13
|
+
key: string;
|
|
14
|
+
};
|
package/out/main/bindings.js
CHANGED
|
@@ -1,63 +1,2 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ProxyBinding = exports.ClassBinding = exports.ServiceBinding = exports.ConstantBinding = exports.Binding = void 0;
|
|
4
|
-
class Binding {
|
|
5
|
-
constructor(mesh, key) {
|
|
6
|
-
this.mesh = mesh;
|
|
7
|
-
this.key = key;
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
exports.Binding = Binding;
|
|
11
|
-
class ConstantBinding extends Binding {
|
|
12
|
-
constructor(mesh, key, value) {
|
|
13
|
-
super(mesh, key);
|
|
14
|
-
this.value = this.mesh.connect(value);
|
|
15
|
-
}
|
|
16
|
-
get() {
|
|
17
|
-
return this.value;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
exports.ConstantBinding = ConstantBinding;
|
|
21
|
-
class ServiceBinding extends Binding {
|
|
22
|
-
constructor(mesh, key, ctor) {
|
|
23
|
-
super(mesh, key);
|
|
24
|
-
this.ctor = this.processClass(ctor);
|
|
25
|
-
}
|
|
26
|
-
get() {
|
|
27
|
-
if (!this.instance) {
|
|
28
|
-
const inst = new this.ctor();
|
|
29
|
-
this.instance = this.mesh.connect(inst);
|
|
30
|
-
}
|
|
31
|
-
return this.instance;
|
|
32
|
-
}
|
|
33
|
-
processClass(ctor) {
|
|
34
|
-
// A fake derived class is created with Mesh attached to its prototype.
|
|
35
|
-
// This allows accessing deps in constructor whilst preserving instanceof.
|
|
36
|
-
const derived = class extends ctor {
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(derived, 'name', { value: ctor.name });
|
|
39
|
-
this.mesh.injectRef(derived.prototype);
|
|
40
|
-
return derived;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
exports.ServiceBinding = ServiceBinding;
|
|
44
|
-
class ClassBinding extends Binding {
|
|
45
|
-
constructor(mesh, key, ctor) {
|
|
46
|
-
super(mesh, key);
|
|
47
|
-
this.ctor = ctor;
|
|
48
|
-
}
|
|
49
|
-
get() {
|
|
50
|
-
return this.ctor;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
exports.ClassBinding = ClassBinding;
|
|
54
|
-
class ProxyBinding extends Binding {
|
|
55
|
-
constructor(mesh, key, alias) {
|
|
56
|
-
super(mesh, key);
|
|
57
|
-
this.alias = alias;
|
|
58
|
-
}
|
|
59
|
-
get() {
|
|
60
|
-
return this.mesh.resolve(this.alias);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
exports.ProxyBinding = ProxyBinding;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { DepMetadata } from '../types';
|
|
3
|
+
export declare const depMetadata: DepMetadata[];
|
|
4
|
+
export interface DepOptions {
|
|
5
|
+
key?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function dep(options?: DepOptions): (target: any, propertyName: string) => void;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.dep = exports.depMetadata = void 0;
|
|
4
|
+
require("reflect-metadata");
|
|
5
|
+
const errors_1 = require("../errors");
|
|
6
|
+
const mesh_1 = require("../mesh");
|
|
7
|
+
exports.depMetadata = [];
|
|
8
|
+
function dep(options = {}) {
|
|
9
|
+
return function (target, propertyName) {
|
|
10
|
+
var _a;
|
|
11
|
+
const className = target.constructor.name;
|
|
12
|
+
const designType = Reflect.getMetadata('design:type', target, propertyName);
|
|
13
|
+
const key = (_a = options.key) !== null && _a !== void 0 ? _a : designType === null || designType === void 0 ? void 0 : designType.name;
|
|
14
|
+
if (!key) {
|
|
15
|
+
throw new errors_1.DepKeyNotInferred(className, propertyName);
|
|
16
|
+
}
|
|
17
|
+
exports.depMetadata.push({
|
|
18
|
+
className,
|
|
19
|
+
propertyName,
|
|
20
|
+
designTypeName: designType.name,
|
|
21
|
+
key,
|
|
22
|
+
});
|
|
23
|
+
Object.defineProperty(target, propertyName, {
|
|
24
|
+
get() {
|
|
25
|
+
const mesh = this[mesh_1.MESH_REF];
|
|
26
|
+
if (!mesh) {
|
|
27
|
+
throw new errors_1.DepInstanceNotConnected(className, propertyName);
|
|
28
|
+
}
|
|
29
|
+
return mesh.resolve(key);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
exports.dep = dep;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.service = exports.serviceMetadata = void 0;
|
|
4
|
+
exports.serviceMetadata = [];
|
|
5
|
+
function service(options = {}) {
|
|
6
|
+
return function (target) {
|
|
7
|
+
exports.serviceMetadata.push(Object.assign({ class: target }, options));
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
exports.service = service;
|
package/out/main/errors.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export declare class DepInstanceNotConnected extends BaseError {
|
|
|
11
11
|
propertyName: string;
|
|
12
12
|
constructor(className: string, propertyName: string);
|
|
13
13
|
}
|
|
14
|
-
export declare class
|
|
14
|
+
export declare class MeshBindingNotFound extends BaseError {
|
|
15
15
|
constructor(meshName: string, serviceKey: string);
|
|
16
16
|
}
|
|
17
17
|
export declare class MeshInvalidBinding extends BaseError {
|
package/out/main/errors.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MeshInvalidBinding = exports.
|
|
3
|
+
exports.MeshInvalidBinding = exports.MeshBindingNotFound = exports.DepInstanceNotConnected = exports.DepKeyNotInferred = void 0;
|
|
4
4
|
class BaseError extends Error {
|
|
5
5
|
constructor() {
|
|
6
6
|
super(...arguments);
|
|
@@ -26,12 +26,12 @@ class DepInstanceNotConnected extends BaseError {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
exports.DepInstanceNotConnected = DepInstanceNotConnected;
|
|
29
|
-
class
|
|
29
|
+
class MeshBindingNotFound extends BaseError {
|
|
30
30
|
constructor(meshName, serviceKey) {
|
|
31
|
-
super(`
|
|
31
|
+
super(`"${serviceKey}" not found in Mesh "${meshName}"`);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
-
exports.
|
|
34
|
+
exports.MeshBindingNotFound = MeshBindingNotFound;
|
|
35
35
|
class MeshInvalidBinding extends BaseError {
|
|
36
36
|
constructor(key) {
|
|
37
37
|
super(`Invalid binding "${key}". Valid bindings are: ` +
|
package/out/main/index.d.ts
CHANGED
package/out/main/index.js
CHANGED
|
@@ -10,9 +10,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
10
10
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
11
11
|
};
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
-
__exportStar(require("./
|
|
14
|
-
__exportStar(require("./decorators"), exports);
|
|
13
|
+
__exportStar(require("./decorators/dep"), exports);
|
|
15
14
|
__exportStar(require("./errors"), exports);
|
|
16
15
|
__exportStar(require("./mesh"), exports);
|
|
17
|
-
__exportStar(require("./
|
|
16
|
+
__exportStar(require("./scope"), exports);
|
|
18
17
|
__exportStar(require("./types"), exports);
|
package/out/main/mesh.d.ts
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Binding } from './
|
|
3
|
-
import { AbstractClass, Constructor, Middleware, ServiceConstructor, ServiceKey } from './types';
|
|
1
|
+
import { Scope } from './scope';
|
|
2
|
+
import { AbstractClass, Binding, Middleware, ServiceConstructor, ServiceKey } from './types';
|
|
4
3
|
export declare const MESH_REF: unique symbol;
|
|
5
4
|
export declare class Mesh {
|
|
6
5
|
name: string;
|
|
7
6
|
parent: Mesh | undefined;
|
|
8
|
-
|
|
7
|
+
currentScope: Scope;
|
|
8
|
+
childScopes: Map<string, Scope>;
|
|
9
|
+
instances: Map<string, any>;
|
|
9
10
|
middlewares: Middleware[];
|
|
10
11
|
constructor(name?: string, parent?: Mesh | undefined);
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class<T extends Constructor<any>>(key: AbstractClass<T> | string, ctor: T): Binding<T>;
|
|
16
|
-
protected _bindClass<T extends Constructor<any>>(k: string, ctor: T): ClassBinding<T>;
|
|
17
|
-
constant<T>(key: ServiceKey<T>, value: T): Binding<T>;
|
|
18
|
-
alias<T>(key: AbstractClass<T> | string, referenceKey: AbstractClass<T> | string): Binding<T>;
|
|
12
|
+
service<T>(impl: ServiceConstructor<T>): this;
|
|
13
|
+
service<T>(key: AbstractClass<T> | string, impl: ServiceConstructor<T>): this;
|
|
14
|
+
constant<T>(key: ServiceKey<T>, value: T): this;
|
|
15
|
+
alias<T>(key: AbstractClass<T> | string, referenceKey: AbstractClass<T> | string): this;
|
|
19
16
|
resolve<T>(key: ServiceKey<T>): T;
|
|
20
17
|
connect<T>(value: T): T;
|
|
21
18
|
use(fn: Middleware): this;
|
|
19
|
+
scope(scopeId: string): Scope;
|
|
20
|
+
createScope(scopeId: string, scopeName?: string): Mesh;
|
|
21
|
+
protected instantiate<T>(binding: Binding<T>): T;
|
|
22
22
|
protected applyMiddleware<T>(value: T): T;
|
|
23
|
-
injectRef(value: any): void;
|
|
23
|
+
protected injectRef(value: any): void;
|
|
24
24
|
}
|
package/out/main/mesh.js
CHANGED
|
@@ -1,71 +1,49 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Mesh = exports.MESH_REF = void 0;
|
|
4
|
-
const _1 = require(".");
|
|
5
|
-
const bindings_1 = require("./bindings");
|
|
6
4
|
const errors_1 = require("./errors");
|
|
5
|
+
const scope_1 = require("./scope");
|
|
6
|
+
const util_1 = require("./util");
|
|
7
7
|
exports.MESH_REF = Symbol.for('MESH_REF');
|
|
8
8
|
class Mesh {
|
|
9
9
|
constructor(name = 'default', parent = undefined) {
|
|
10
10
|
this.name = name;
|
|
11
11
|
this.parent = parent;
|
|
12
|
-
this.
|
|
12
|
+
this.childScopes = new Map();
|
|
13
|
+
this.instances = new Map();
|
|
13
14
|
this.middlewares = [];
|
|
14
|
-
this.
|
|
15
|
+
this.currentScope = new scope_1.Scope(name);
|
|
16
|
+
this.currentScope.constant('Mesh', this);
|
|
15
17
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return this._bindService(k, impl);
|
|
20
|
-
}
|
|
21
|
-
else if (typeof key === 'function') {
|
|
22
|
-
return this._bindService(k, key);
|
|
23
|
-
}
|
|
24
|
-
throw new errors_1.MeshInvalidBinding(String(key));
|
|
25
|
-
}
|
|
26
|
-
_bindService(k, impl) {
|
|
27
|
-
const binding = new bindings_1.ServiceBinding(this, k, impl);
|
|
28
|
-
this.bindings.set(k, binding);
|
|
29
|
-
return new bindings_1.ProxyBinding(this, k, k);
|
|
30
|
-
}
|
|
31
|
-
class(key, ctor) {
|
|
32
|
-
const k = keyToString(key);
|
|
33
|
-
if (typeof ctor === 'function') {
|
|
34
|
-
return this._bindClass(k, ctor);
|
|
35
|
-
}
|
|
36
|
-
else if (typeof key === 'function') {
|
|
37
|
-
return this._bindClass(k, key);
|
|
38
|
-
}
|
|
39
|
-
throw new errors_1.MeshInvalidBinding(String(key));
|
|
40
|
-
}
|
|
41
|
-
_bindClass(k, ctor) {
|
|
42
|
-
const binding = new _1.ClassBinding(this, k, ctor);
|
|
43
|
-
this.bindings.set(k, binding);
|
|
44
|
-
return binding;
|
|
18
|
+
service(key, impl) {
|
|
19
|
+
this.currentScope.service(key, impl);
|
|
20
|
+
return this;
|
|
45
21
|
}
|
|
46
22
|
constant(key, value) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
this.bindings.set(k, binding);
|
|
50
|
-
return new bindings_1.ProxyBinding(this, k, k);
|
|
23
|
+
this.currentScope.constant(key, value);
|
|
24
|
+
return this;
|
|
51
25
|
}
|
|
52
26
|
alias(key, referenceKey) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const binding = new bindings_1.ProxyBinding(this, k, refK);
|
|
56
|
-
this.bindings.set(k, binding);
|
|
57
|
-
return binding;
|
|
27
|
+
this.currentScope.alias(key, referenceKey);
|
|
28
|
+
return this;
|
|
58
29
|
}
|
|
59
30
|
resolve(key) {
|
|
60
|
-
const k = keyToString(key);
|
|
61
|
-
|
|
31
|
+
const k = util_1.keyToString(key);
|
|
32
|
+
let instance = this.instances.get(k);
|
|
33
|
+
if (instance) {
|
|
34
|
+
return instance;
|
|
35
|
+
}
|
|
36
|
+
const binding = this.currentScope.bindings.get(k);
|
|
62
37
|
if (binding) {
|
|
63
|
-
|
|
38
|
+
instance = this.instantiate(binding);
|
|
39
|
+
instance = this.connect(instance);
|
|
40
|
+
this.instances.set(k, instance);
|
|
41
|
+
return instance;
|
|
64
42
|
}
|
|
65
43
|
if (this.parent) {
|
|
66
44
|
return this.parent.resolve(key);
|
|
67
45
|
}
|
|
68
|
-
throw new errors_1.
|
|
46
|
+
throw new errors_1.MeshBindingNotFound(this.name, k);
|
|
69
47
|
}
|
|
70
48
|
connect(value) {
|
|
71
49
|
const res = this.applyMiddleware(value);
|
|
@@ -76,11 +54,47 @@ class Mesh {
|
|
|
76
54
|
this.middlewares.push(fn);
|
|
77
55
|
return this;
|
|
78
56
|
}
|
|
57
|
+
scope(scopeId) {
|
|
58
|
+
let scope = this.childScopes.get(scopeId);
|
|
59
|
+
if (!scope) {
|
|
60
|
+
scope = new scope_1.Scope(scopeId);
|
|
61
|
+
this.childScopes.set(scopeId, scope);
|
|
62
|
+
}
|
|
63
|
+
return scope;
|
|
64
|
+
}
|
|
65
|
+
createScope(scopeId, scopeName = scopeId) {
|
|
66
|
+
const childScope = this.childScopes.get(scopeId);
|
|
67
|
+
const newScope = new scope_1.Scope(scopeName, childScope !== null && childScope !== void 0 ? childScope : []);
|
|
68
|
+
const mesh = new Mesh(scopeId, this);
|
|
69
|
+
mesh.currentScope = newScope;
|
|
70
|
+
return mesh;
|
|
71
|
+
}
|
|
72
|
+
instantiate(binding) {
|
|
73
|
+
switch (binding.type) {
|
|
74
|
+
case 'alias':
|
|
75
|
+
return this.resolve(binding.key);
|
|
76
|
+
case 'service': {
|
|
77
|
+
// A fake derived class is created with Mesh attached to its prototype.
|
|
78
|
+
// This allows accessing deps in constructor whilst preserving instanceof.
|
|
79
|
+
const ctor = binding.class;
|
|
80
|
+
const derived = class extends ctor {
|
|
81
|
+
};
|
|
82
|
+
Object.defineProperty(derived, 'name', { value: ctor.name });
|
|
83
|
+
this.injectRef(derived.prototype);
|
|
84
|
+
return new derived();
|
|
85
|
+
}
|
|
86
|
+
case 'constant':
|
|
87
|
+
return binding.value;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
79
90
|
applyMiddleware(value) {
|
|
80
91
|
let res = value;
|
|
81
92
|
for (const middleware of this.middlewares) {
|
|
82
93
|
res = middleware(res);
|
|
83
94
|
}
|
|
95
|
+
if (this.parent) {
|
|
96
|
+
res = this.parent.applyMiddleware(res);
|
|
97
|
+
}
|
|
84
98
|
return res;
|
|
85
99
|
}
|
|
86
100
|
injectRef(value) {
|
|
@@ -95,6 +109,3 @@ class Mesh {
|
|
|
95
109
|
}
|
|
96
110
|
}
|
|
97
111
|
exports.Mesh = Mesh;
|
|
98
|
-
function keyToString(key) {
|
|
99
|
-
return typeof key === 'string' ? key : key.name;
|
|
100
|
-
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { AbstractClass, Binding, ServiceConstructor, ServiceKey } from './types';
|
|
2
|
+
export declare class Scope {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
bindings: Map<string, Binding<any>>;
|
|
5
|
+
constructor(name: string, bindings?: Iterable<[string, Binding<any>]>);
|
|
6
|
+
[Symbol.iterator](): Generator<[string, Binding<any>], void, undefined>;
|
|
7
|
+
service<T>(impl: ServiceConstructor<T>): this;
|
|
8
|
+
service<T>(key: AbstractClass<T> | string, impl: ServiceConstructor<T>): this;
|
|
9
|
+
constant<T>(key: ServiceKey<T>, value: T): this;
|
|
10
|
+
alias<T>(key: AbstractClass<T> | string, referenceKey: AbstractClass<T> | string): this;
|
|
11
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Scope = void 0;
|
|
4
|
+
const errors_1 = require("./errors");
|
|
5
|
+
const util_1 = require("./util");
|
|
6
|
+
class Scope {
|
|
7
|
+
constructor(name, bindings = []) {
|
|
8
|
+
this.name = name;
|
|
9
|
+
this.bindings = new Map();
|
|
10
|
+
for (const [k, v] of bindings) {
|
|
11
|
+
this.bindings.set(k, v);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
*[Symbol.iterator]() {
|
|
15
|
+
yield* this.bindings.entries();
|
|
16
|
+
}
|
|
17
|
+
service(key, impl) {
|
|
18
|
+
const k = util_1.keyToString(key);
|
|
19
|
+
if (typeof impl === 'function') {
|
|
20
|
+
this.bindings.set(k, { type: 'service', class: impl });
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
else if (typeof key === 'function') {
|
|
24
|
+
this.bindings.set(k, { type: 'service', class: key });
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
throw new errors_1.MeshInvalidBinding(String(key));
|
|
28
|
+
}
|
|
29
|
+
constant(key, value) {
|
|
30
|
+
const k = util_1.keyToString(key);
|
|
31
|
+
this.bindings.set(k, { type: 'constant', value });
|
|
32
|
+
return this;
|
|
33
|
+
}
|
|
34
|
+
alias(key, referenceKey) {
|
|
35
|
+
const k = util_1.keyToString(key);
|
|
36
|
+
const refK = util_1.keyToString(referenceKey);
|
|
37
|
+
this.bindings.set(k, { type: 'alias', key: refK });
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.Scope = Scope;
|
package/out/main/types.d.ts
CHANGED
|
@@ -10,3 +10,27 @@ export declare type AbstractClass<T> = {
|
|
|
10
10
|
};
|
|
11
11
|
export declare type ServiceKey<T> = ServiceConstructor<T> | AbstractClass<T> | string;
|
|
12
12
|
export declare type Middleware = (instance: any) => any;
|
|
13
|
+
export declare type Binding<T> = ConstantBinding<T> | ServiceBinding<T> | AliasBinding;
|
|
14
|
+
export declare type ConstantBinding<T> = {
|
|
15
|
+
type: 'constant';
|
|
16
|
+
value: T;
|
|
17
|
+
};
|
|
18
|
+
export declare type ServiceBinding<T> = {
|
|
19
|
+
type: 'service';
|
|
20
|
+
class: ServiceConstructor<T>;
|
|
21
|
+
};
|
|
22
|
+
export declare type AliasBinding = {
|
|
23
|
+
type: 'alias';
|
|
24
|
+
key: string;
|
|
25
|
+
};
|
|
26
|
+
export interface DepMetadata {
|
|
27
|
+
className: string;
|
|
28
|
+
propertyName: string;
|
|
29
|
+
designTypeName: string;
|
|
30
|
+
key: string;
|
|
31
|
+
}
|
|
32
|
+
export interface ServiceMetadata {
|
|
33
|
+
class: ServiceConstructor<any>;
|
|
34
|
+
alias?: string;
|
|
35
|
+
metadata?: any;
|
|
36
|
+
}
|
package/out/main/util.js
ADDED