mesh-ioc 0.2.0 → 1.1.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 +225 -3
- package/out/main/bindings.d.ts +12 -22
- package/out/main/bindings.js +0 -43
- 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 +2 -2
- package/out/main/errors.js +7 -7
- package/out/main/index.d.ts +3 -3
- package/out/main/index.js +3 -3
- package/out/main/mesh.d.ts +15 -11
- package/out/main/mesh.js +64 -35
- package/out/main/scope.d.ts +11 -0
- package/out/main/scope.js +41 -0
- package/out/main/types.d.ts +26 -2
- package/out/main/util.d.ts +2 -0
- package/out/main/util.js +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,6 +13,78 @@ Mesh IoC solves the problem of dependency management of application services. It
|
|
|
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.
|
|
@@ -80,15 +152,19 @@ class Redis {
|
|
|
80
152
|
|
|
81
153
|
constructor() {
|
|
82
154
|
this.redis = new RedisClient(/* ... */);
|
|
83
|
-
this.redis.on('connect', () =>
|
|
155
|
+
this.redis.on('connect', () => {
|
|
156
|
+
// Logger can now be used by this class transparently
|
|
157
|
+
this.logger.log('Connected to Redis');
|
|
158
|
+
});
|
|
84
159
|
}
|
|
85
160
|
}
|
|
86
161
|
|
|
87
162
|
// app.ts
|
|
88
163
|
|
|
89
164
|
class AppMesh extends Mesh {
|
|
90
|
-
|
|
91
|
-
this.
|
|
165
|
+
// List all services so that mesh connects them together
|
|
166
|
+
this.service(Redis);
|
|
167
|
+
this.service(Logger, ConsoleLogger);
|
|
92
168
|
}
|
|
93
169
|
```
|
|
94
170
|
|
|
@@ -111,3 +187,149 @@ There are several aspects that differentiate Mesh IoC from the rest of the DI li
|
|
|
111
187
|
- Mesh can handle circular dependencies, all thanks to on-demand resolution. However, due to how Node.js module loading works, `@dep` will be unable to infer _one_ of the service keys (depending on which module happened to load first). It's a good practice to use explicit service keys on both sides of circular dependencies.
|
|
112
188
|
|
|
113
189
|
- Constant values can be bound to mesh. Those could be instances of other classes.
|
|
190
|
+
|
|
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!
|
|
192
|
+
|
|
193
|
+
## Application Architecture Guide
|
|
194
|
+
|
|
195
|
+
This short guide briefly explains the basic concepts of a good application architecture where all components are loosely coupled, dependencies are easy to reason about and are not mixed with the actual data arguments.
|
|
196
|
+
|
|
197
|
+
1. Identify the layers of your application. Oftentimes different components have different lifespans or, as we tend to refer to it, scopes:
|
|
198
|
+
|
|
199
|
+
- **Application scope**: things like database connection pools, servers and other global components are scoped to entire application; their instances are effectively singletons (i.e. you don't want to establish a new database connection each time you query it).
|
|
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.
|
|
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)
|
|
202
|
+
|
|
203
|
+
2. Build the mesh hierarchy, starting from application scope, descending into more granular scopes.
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
// app.ts
|
|
207
|
+
export class App {
|
|
208
|
+
// You can either inherit from Mesh or store it as a field.
|
|
209
|
+
// Name parameter is optional, but can be useful for debugging.
|
|
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
|
+
}
|
|
224
|
+
|
|
225
|
+
start() {
|
|
226
|
+
// Define logic for application startup
|
|
227
|
+
// (e.g. connect to databases, start listening to servers, etc)
|
|
228
|
+
this.mesh.resolve(MyServer).listen();
|
|
229
|
+
}
|
|
230
|
+
|
|
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();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
3. Create an application entrypoint (advice: never mix modules that export classes with entrypoint modules!):
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
// bin/run.ts
|
|
247
|
+
|
|
248
|
+
const app = new App();
|
|
249
|
+
app.start();
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
4. Identify the proper component for session entrypoint:
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
export class MyServer {
|
|
256
|
+
@dep() app!: App;
|
|
257
|
+
|
|
258
|
+
// The actual server (e.g. http server or web socket server)
|
|
259
|
+
server: Server;
|
|
260
|
+
|
|
261
|
+
constructor() {
|
|
262
|
+
this.server = new Server((req, res) => {
|
|
263
|
+
// This is the actual entrypoint of Session
|
|
264
|
+
app.startSession(req, res);
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
5. Use `@dep()` to transparently inject dependencies in your services:
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
export class SessionScopedService {
|
|
274
|
+
|
|
275
|
+
@dep() database!: Database;
|
|
276
|
+
@dep() req!: Request;
|
|
277
|
+
@dep() res!: Response;
|
|
278
|
+
|
|
279
|
+
// ...
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
6. Come up with conventions and document them, for example:
|
|
284
|
+
|
|
285
|
+
- create different directories for services with different scopes
|
|
286
|
+
- separate entrypoints from the rest of the modules
|
|
287
|
+
- entrypoints only import, instantiate and invoke methods (think "runnable from CLI")
|
|
288
|
+
- all other modules only export stuff
|
|
289
|
+
|
|
290
|
+
You can take those further and adapt to your own needs. Meshes are composable and the underlying mechanics are quite simple. Start using it and you'll get a better understanding of how to adapt it to the needs of your particular case.
|
|
291
|
+
|
|
292
|
+
## Advanced
|
|
293
|
+
|
|
294
|
+
### Connecting "guest" instances
|
|
295
|
+
|
|
296
|
+
Mesh IoC allows connecting an arbitrary instance to the mesh, so that the `@dep` can be used in it.
|
|
297
|
+
|
|
298
|
+
For example:
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
// This entity class is not managed by Mesh directly, instead it's instantiated by UserService
|
|
302
|
+
class User {
|
|
303
|
+
@dep() database!: Database;
|
|
304
|
+
|
|
305
|
+
// Note: constructor can have arbitrary arguments in this case,
|
|
306
|
+
// because the instantiation isn't controlled by Mesh
|
|
307
|
+
constructor(
|
|
308
|
+
public firstName = '',
|
|
309
|
+
public lastName = '',
|
|
310
|
+
public email = '',
|
|
311
|
+
// ...
|
|
312
|
+
) {}
|
|
313
|
+
|
|
314
|
+
async save() {
|
|
315
|
+
await this.database.save(this);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
class UserService {
|
|
320
|
+
// Note: Mesh is automatically available in all connected services
|
|
321
|
+
@dep() mesh!: Mesh;
|
|
322
|
+
|
|
323
|
+
createUser(firstName = '', lastName = '', email = '', /*...*/) {
|
|
324
|
+
const user = new User();
|
|
325
|
+
// Now connect it to mesh, so that User can access its services via `@dep`
|
|
326
|
+
return this.mesh.connect(user);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
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).
|
|
332
|
+
|
|
333
|
+
## License
|
|
334
|
+
|
|
335
|
+
[ISC](https://en.wikipedia.org/wiki/ISC_license) © Boris Okunskiy
|
package/out/main/bindings.d.ts
CHANGED
|
@@ -1,24 +1,14 @@
|
|
|
1
|
-
import { Mesh } from './mesh';
|
|
2
1
|
import { ServiceConstructor } from './types';
|
|
3
|
-
export declare
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
constructor(mesh: Mesh, key: string);
|
|
7
|
-
abstract get(): T;
|
|
8
|
-
}
|
|
9
|
-
export declare class ConstantBinding<T> extends Binding<T> {
|
|
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
|
-
export declare class ProxyBinding<T> extends Binding<T> {
|
|
21
|
-
readonly alias: string;
|
|
22
|
-
constructor(mesh: Mesh, key: string, alias: string);
|
|
23
|
-
get(): T;
|
|
24
|
-
}
|
|
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,45 +1,2 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ProxyBinding = 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.applyMiddleware(value);
|
|
15
|
-
this.mesh.connect(this.value);
|
|
16
|
-
}
|
|
17
|
-
get() {
|
|
18
|
-
return this.value;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
exports.ConstantBinding = ConstantBinding;
|
|
22
|
-
class ServiceBinding extends Binding {
|
|
23
|
-
constructor(mesh, key, ctor) {
|
|
24
|
-
super(mesh, key);
|
|
25
|
-
this.ctor = ctor;
|
|
26
|
-
}
|
|
27
|
-
get() {
|
|
28
|
-
if (!this.instance) {
|
|
29
|
-
this.instance = this.mesh.applyMiddleware(new this.ctor());
|
|
30
|
-
this.mesh.connect(this.instance);
|
|
31
|
-
}
|
|
32
|
-
return this.instance;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
exports.ServiceBinding = ServiceBinding;
|
|
36
|
-
class ProxyBinding extends Binding {
|
|
37
|
-
constructor(mesh, key, alias) {
|
|
38
|
-
super(mesh, key);
|
|
39
|
-
this.alias = alias;
|
|
40
|
-
}
|
|
41
|
-
get() {
|
|
42
|
-
return this.mesh.resolve(this.alias);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
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.svcMetadata = void 0;
|
|
4
|
+
exports.svcMetadata = [];
|
|
5
|
+
function service(options = {}) {
|
|
6
|
+
return function (target) {
|
|
7
|
+
exports.svcMetadata.push(Object.assign({ class: target }, options));
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
exports.service = service;
|
package/out/main/errors.d.ts
CHANGED
|
@@ -11,10 +11,10 @@ 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
|
-
export declare class
|
|
17
|
+
export declare class MeshInvalidBinding extends BaseError {
|
|
18
18
|
constructor(key: string);
|
|
19
19
|
}
|
|
20
20
|
export {};
|
package/out/main/errors.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
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,18 +26,18 @@ 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.
|
|
35
|
-
class
|
|
34
|
+
exports.MeshBindingNotFound = MeshBindingNotFound;
|
|
35
|
+
class MeshInvalidBinding extends BaseError {
|
|
36
36
|
constructor(key) {
|
|
37
|
-
super(`Invalid
|
|
37
|
+
super(`Invalid binding "${key}". Valid bindings are: ` +
|
|
38
38
|
`string to constructor e.g. ("MyService", MyService) or ` +
|
|
39
39
|
`abstract class to constructor e.g. (MyService, MyServiceImpl) or` +
|
|
40
40
|
`constructor to self e.g. (MyService)`);
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
-
exports.
|
|
43
|
+
exports.MeshInvalidBinding = MeshInvalidBinding;
|
package/out/main/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export * from './
|
|
2
|
-
export * from './decorators';
|
|
1
|
+
export * from './decorators/dep';
|
|
2
|
+
export * from './decorators/service';
|
|
3
3
|
export * from './errors';
|
|
4
4
|
export * from './mesh';
|
|
5
|
-
export * from './
|
|
5
|
+
export * from './scope';
|
|
6
6
|
export * from './types';
|
package/out/main/index.js
CHANGED
|
@@ -10,9 +10,9 @@ 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);
|
|
14
|
+
__exportStar(require("./decorators/service"), exports);
|
|
15
15
|
__exportStar(require("./errors"), exports);
|
|
16
16
|
__exportStar(require("./mesh"), exports);
|
|
17
|
-
__exportStar(require("./
|
|
17
|
+
__exportStar(require("./scope"), exports);
|
|
18
18
|
__exportStar(require("./types"), exports);
|
package/out/main/mesh.d.ts
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Scope } from './scope';
|
|
2
|
+
import { AbstractClass, Binding, Middleware, ServiceConstructor, ServiceKey } from './types';
|
|
3
3
|
export declare const MESH_REF: unique symbol;
|
|
4
4
|
export declare class Mesh {
|
|
5
5
|
name: string;
|
|
6
6
|
parent: Mesh | undefined;
|
|
7
|
-
|
|
7
|
+
currentScope: Scope;
|
|
8
|
+
childScopes: Map<string, Scope>;
|
|
9
|
+
instances: Map<string, any>;
|
|
8
10
|
middlewares: Middleware[];
|
|
9
11
|
constructor(name?: string, parent?: Mesh | undefined);
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
alias<T>(key: AbstractService<T> | string, referenceKey: AbstractService<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;
|
|
15
16
|
resolve<T>(key: ServiceKey<T>): T;
|
|
16
|
-
connect(value:
|
|
17
|
+
connect<T>(value: T): T;
|
|
17
18
|
use(fn: Middleware): this;
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
scope(scopeId: string): Scope;
|
|
20
|
+
createScope(scopeId: string, scopeName?: string): Mesh;
|
|
21
|
+
protected instantiate<T>(binding: Binding<T>): T;
|
|
22
|
+
protected applyMiddleware<T>(value: T): T;
|
|
23
|
+
protected injectRef(value: any): void;
|
|
20
24
|
}
|
package/out/main/mesh.js
CHANGED
|
@@ -1,71 +1,103 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Mesh = exports.MESH_REF = void 0;
|
|
4
|
-
const bindings_1 = require("./bindings");
|
|
5
4
|
const errors_1 = require("./errors");
|
|
5
|
+
const scope_1 = require("./scope");
|
|
6
|
+
const util_1 = require("./util");
|
|
6
7
|
exports.MESH_REF = Symbol.for('MESH_REF');
|
|
7
8
|
class Mesh {
|
|
8
9
|
constructor(name = 'default', parent = undefined) {
|
|
9
10
|
this.name = name;
|
|
10
11
|
this.parent = parent;
|
|
11
|
-
this.
|
|
12
|
+
this.childScopes = new Map();
|
|
13
|
+
this.instances = new Map();
|
|
12
14
|
this.middlewares = [];
|
|
13
|
-
this.
|
|
15
|
+
this.currentScope = new scope_1.Scope(name);
|
|
16
|
+
this.currentScope.constant('Mesh', this);
|
|
14
17
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return this._bindService(k, impl);
|
|
19
|
-
}
|
|
20
|
-
else if (typeof key === 'function') {
|
|
21
|
-
return this._bindService(k, key);
|
|
22
|
-
}
|
|
23
|
-
throw new errors_1.MeshInvalidServiceBinding(String(key));
|
|
24
|
-
}
|
|
25
|
-
_bindService(k, impl) {
|
|
26
|
-
const binding = new bindings_1.ServiceBinding(this, k, impl);
|
|
27
|
-
this.bindings.set(k, binding);
|
|
28
|
-
return new bindings_1.ProxyBinding(this, k, k);
|
|
18
|
+
service(key, impl) {
|
|
19
|
+
this.currentScope.service(key, impl);
|
|
20
|
+
return this;
|
|
29
21
|
}
|
|
30
22
|
constant(key, value) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
this.bindings.set(k, binding);
|
|
34
|
-
return new bindings_1.ProxyBinding(this, k, k);
|
|
23
|
+
this.currentScope.constant(key, value);
|
|
24
|
+
return this;
|
|
35
25
|
}
|
|
36
26
|
alias(key, referenceKey) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const binding = new bindings_1.ProxyBinding(this, k, refK);
|
|
40
|
-
this.bindings.set(k, binding);
|
|
41
|
-
return binding;
|
|
27
|
+
this.currentScope.alias(key, referenceKey);
|
|
28
|
+
return this;
|
|
42
29
|
}
|
|
43
30
|
resolve(key) {
|
|
44
|
-
const k = keyToString(key);
|
|
45
|
-
|
|
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);
|
|
46
37
|
if (binding) {
|
|
47
|
-
|
|
38
|
+
instance = this.instantiate(binding);
|
|
39
|
+
instance = this.connect(instance);
|
|
40
|
+
this.instances.set(k, instance);
|
|
41
|
+
return instance;
|
|
48
42
|
}
|
|
49
43
|
if (this.parent) {
|
|
50
44
|
return this.parent.resolve(key);
|
|
51
45
|
}
|
|
52
|
-
throw new errors_1.
|
|
46
|
+
throw new errors_1.MeshBindingNotFound(this.name, k);
|
|
53
47
|
}
|
|
54
48
|
connect(value) {
|
|
55
|
-
this.
|
|
49
|
+
const res = this.applyMiddleware(value);
|
|
50
|
+
this.injectRef(res);
|
|
51
|
+
return res;
|
|
56
52
|
}
|
|
57
53
|
use(fn) {
|
|
58
54
|
this.middlewares.push(fn);
|
|
59
55
|
return this;
|
|
60
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
|
+
}
|
|
61
90
|
applyMiddleware(value) {
|
|
62
91
|
let res = value;
|
|
63
92
|
for (const middleware of this.middlewares) {
|
|
64
93
|
res = middleware(res);
|
|
65
94
|
}
|
|
95
|
+
if (this.parent) {
|
|
96
|
+
res = this.parent.applyMiddleware(res);
|
|
97
|
+
}
|
|
66
98
|
return res;
|
|
67
99
|
}
|
|
68
|
-
|
|
100
|
+
injectRef(value) {
|
|
69
101
|
if (typeof value !== 'object') {
|
|
70
102
|
return;
|
|
71
103
|
}
|
|
@@ -77,6 +109,3 @@ class Mesh {
|
|
|
77
109
|
}
|
|
78
110
|
}
|
|
79
111
|
exports.Mesh = Mesh;
|
|
80
|
-
function keyToString(key) {
|
|
81
|
-
return typeof key === 'string' ? key : key.name;
|
|
82
|
-
}
|
|
@@ -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
|
@@ -4,9 +4,33 @@ export declare type Constructor<T> = {
|
|
|
4
4
|
export declare type ServiceConstructor<T> = {
|
|
5
5
|
new (): T;
|
|
6
6
|
};
|
|
7
|
-
export declare type
|
|
7
|
+
export declare type AbstractClass<T> = {
|
|
8
8
|
name: string;
|
|
9
9
|
prototype: T;
|
|
10
10
|
};
|
|
11
|
-
export declare type ServiceKey<T> = ServiceConstructor<T> |
|
|
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