mesh-ioc 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -118
- package/out/main/index.d.ts +0 -1
- package/out/main/index.js +0 -1
- package/out/main/mesh.d.ts +15 -5
- package/out/main/mesh.js +47 -26
- package/package.json +2 -2
- package/out/main/bindings.d.ts +0 -14
- package/out/main/bindings.js +0 -2
- package/out/main/decorators/service.d.ts +0 -7
- package/out/main/decorators/service.js +0 -10
- package/out/main/decorators.d.ts +0 -5
- package/out/main/decorators.js +0 -34
- package/out/main/metadata.d.ts +0 -7
- package/out/main/metadata.js +0 -4
package/README.md
CHANGED
|
@@ -57,25 +57,6 @@ class User {
|
|
|
57
57
|
|
|
58
58
|
const user = mesh.connect(new User()); // now user.db will also be resolved by Mesh
|
|
59
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
60
|
// Middleware:
|
|
80
61
|
mesh.use(instance => {
|
|
81
62
|
// Allows modifying instances as they are created or connected
|
|
@@ -190,105 +171,6 @@ There are several aspects that differentiate Mesh IoC from the rest of the DI li
|
|
|
190
171
|
|
|
191
172
|
**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
173
|
|
|
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
174
|
## Advanced
|
|
293
175
|
|
|
294
176
|
### Connecting "guest" instances
|
package/out/main/index.d.ts
CHANGED
package/out/main/index.js
CHANGED
|
@@ -13,5 +13,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
13
13
|
__exportStar(require("./decorators/dep"), exports);
|
|
14
14
|
__exportStar(require("./errors"), exports);
|
|
15
15
|
__exportStar(require("./mesh"), exports);
|
|
16
|
-
__exportStar(require("./scope"), exports);
|
|
17
16
|
__exportStar(require("./types"), exports);
|
package/out/main/mesh.d.ts
CHANGED
|
@@ -1,23 +1,33 @@
|
|
|
1
|
-
import { Scope } from './scope';
|
|
2
1
|
import { AbstractClass, Binding, Middleware, ServiceConstructor, ServiceKey } from './types';
|
|
3
2
|
export declare const MESH_REF: unique symbol;
|
|
3
|
+
/**
|
|
4
|
+
* An IoC container.
|
|
5
|
+
*
|
|
6
|
+
* Encapsulates bindings — a map that allows to associate a _service key_ with a way to obtain an instance.
|
|
7
|
+
*
|
|
8
|
+
* Three binding types are supported via corresponding methods:
|
|
9
|
+
*
|
|
10
|
+
* - `service` — a zero-arg constructor mapping; these will be instantiated on demand and cached in this mesh
|
|
11
|
+
* - `constant` — an instance of a class (can be bound by using class as service name) or an arbitrary value bound by a string key
|
|
12
|
+
* - `alias` — a "redirect" mapping, useful in tests
|
|
13
|
+
*/
|
|
4
14
|
export declare class Mesh {
|
|
5
15
|
name: string;
|
|
6
16
|
parent: Mesh | undefined;
|
|
7
|
-
|
|
8
|
-
childScopes: Map<string, Scope>;
|
|
17
|
+
bindings: Map<string, Binding<any>>;
|
|
9
18
|
instances: Map<string, any>;
|
|
10
19
|
middlewares: Middleware[];
|
|
11
20
|
constructor(name?: string, parent?: Mesh | undefined);
|
|
21
|
+
[Symbol.iterator](): Generator<[string, Binding<any>], void, undefined>;
|
|
22
|
+
clone(): Mesh;
|
|
12
23
|
service<T>(impl: ServiceConstructor<T>): this;
|
|
13
24
|
service<T>(key: AbstractClass<T> | string, impl: ServiceConstructor<T>): this;
|
|
14
25
|
constant<T>(key: ServiceKey<T>, value: T): this;
|
|
15
26
|
alias<T>(key: AbstractClass<T> | string, referenceKey: AbstractClass<T> | string): this;
|
|
16
27
|
resolve<T>(key: ServiceKey<T>): T;
|
|
28
|
+
tryResolve<T>(key: ServiceKey<T>): T | undefined;
|
|
17
29
|
connect<T>(value: T): T;
|
|
18
30
|
use(fn: Middleware): this;
|
|
19
|
-
scope(scopeId: string): Scope;
|
|
20
|
-
createScope(scopeId: string, scopeName?: string): Mesh;
|
|
21
31
|
protected instantiate<T>(binding: Binding<T>): T;
|
|
22
32
|
protected applyMiddleware<T>(value: T): T;
|
|
23
33
|
protected injectRef(value: any): void;
|
package/out/main/mesh.js
CHANGED
|
@@ -2,38 +2,74 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Mesh = exports.MESH_REF = void 0;
|
|
4
4
|
const errors_1 = require("./errors");
|
|
5
|
-
const scope_1 = require("./scope");
|
|
6
5
|
const util_1 = require("./util");
|
|
7
6
|
exports.MESH_REF = Symbol.for('MESH_REF');
|
|
7
|
+
/**
|
|
8
|
+
* An IoC container.
|
|
9
|
+
*
|
|
10
|
+
* Encapsulates bindings — a map that allows to associate a _service key_ with a way to obtain an instance.
|
|
11
|
+
*
|
|
12
|
+
* Three binding types are supported via corresponding methods:
|
|
13
|
+
*
|
|
14
|
+
* - `service` — a zero-arg constructor mapping; these will be instantiated on demand and cached in this mesh
|
|
15
|
+
* - `constant` — an instance of a class (can be bound by using class as service name) or an arbitrary value bound by a string key
|
|
16
|
+
* - `alias` — a "redirect" mapping, useful in tests
|
|
17
|
+
*/
|
|
8
18
|
class Mesh {
|
|
9
19
|
constructor(name = 'default', parent = undefined) {
|
|
10
20
|
this.name = name;
|
|
11
21
|
this.parent = parent;
|
|
12
|
-
this.
|
|
22
|
+
this.bindings = new Map();
|
|
13
23
|
this.instances = new Map();
|
|
14
24
|
this.middlewares = [];
|
|
15
|
-
|
|
16
|
-
|
|
25
|
+
}
|
|
26
|
+
*[Symbol.iterator]() {
|
|
27
|
+
yield* this.bindings.entries();
|
|
28
|
+
}
|
|
29
|
+
clone() {
|
|
30
|
+
const clone = new Mesh();
|
|
31
|
+
clone.parent = this.parent;
|
|
32
|
+
clone.bindings = new Map(this.bindings);
|
|
33
|
+
return clone;
|
|
17
34
|
}
|
|
18
35
|
service(key, impl) {
|
|
19
|
-
|
|
20
|
-
|
|
36
|
+
const k = util_1.keyToString(key);
|
|
37
|
+
if (typeof impl === 'function') {
|
|
38
|
+
this.bindings.set(k, { type: 'service', class: impl });
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
else if (typeof key === 'function') {
|
|
42
|
+
this.bindings.set(k, { type: 'service', class: key });
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
throw new errors_1.MeshInvalidBinding(String(key));
|
|
21
46
|
}
|
|
22
47
|
constant(key, value) {
|
|
23
|
-
|
|
48
|
+
const k = util_1.keyToString(key);
|
|
49
|
+
this.bindings.set(k, { type: 'constant', value });
|
|
24
50
|
return this;
|
|
25
51
|
}
|
|
26
52
|
alias(key, referenceKey) {
|
|
27
|
-
|
|
53
|
+
const k = util_1.keyToString(key);
|
|
54
|
+
const refK = util_1.keyToString(referenceKey);
|
|
55
|
+
this.bindings.set(k, { type: 'alias', key: refK });
|
|
28
56
|
return this;
|
|
29
57
|
}
|
|
30
58
|
resolve(key) {
|
|
59
|
+
const instance = this.tryResolve(key);
|
|
60
|
+
if (instance === undefined) {
|
|
61
|
+
const k = util_1.keyToString(key);
|
|
62
|
+
throw new errors_1.MeshBindingNotFound(this.name, k);
|
|
63
|
+
}
|
|
64
|
+
return instance;
|
|
65
|
+
}
|
|
66
|
+
tryResolve(key) {
|
|
31
67
|
const k = util_1.keyToString(key);
|
|
32
68
|
let instance = this.instances.get(k);
|
|
33
69
|
if (instance) {
|
|
34
70
|
return instance;
|
|
35
71
|
}
|
|
36
|
-
const binding = this.
|
|
72
|
+
const binding = this.bindings.get(k);
|
|
37
73
|
if (binding) {
|
|
38
74
|
instance = this.instantiate(binding);
|
|
39
75
|
instance = this.connect(instance);
|
|
@@ -41,9 +77,9 @@ class Mesh {
|
|
|
41
77
|
return instance;
|
|
42
78
|
}
|
|
43
79
|
if (this.parent) {
|
|
44
|
-
return this.parent.
|
|
80
|
+
return this.parent.tryResolve(key);
|
|
45
81
|
}
|
|
46
|
-
|
|
82
|
+
return undefined;
|
|
47
83
|
}
|
|
48
84
|
connect(value) {
|
|
49
85
|
const res = this.applyMiddleware(value);
|
|
@@ -54,21 +90,6 @@ class Mesh {
|
|
|
54
90
|
this.middlewares.push(fn);
|
|
55
91
|
return this;
|
|
56
92
|
}
|
|
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
93
|
instantiate(binding) {
|
|
73
94
|
switch (binding.type) {
|
|
74
95
|
case 'alias':
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mesh-ioc",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Mesh: Powerful and Lightweight IoC Library",
|
|
5
5
|
"main": "out/main/index.js",
|
|
6
6
|
"types": "out/main/index.d.ts",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"test": "NODE_ENV=test mocha",
|
|
16
16
|
"preversion": "npm run lint",
|
|
17
17
|
"version": "npm run compile",
|
|
18
|
-
"postversion": "npm publish --access=public"
|
|
18
|
+
"postversion": "npm publish --access=public && git push origin main --tags"
|
|
19
19
|
},
|
|
20
20
|
"pre-commit": [
|
|
21
21
|
"lint"
|
package/out/main/bindings.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
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';
|
|
5
|
-
value: T;
|
|
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
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
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/decorators.d.ts
DELETED
package/out/main/decorators.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.dep = void 0;
|
|
4
|
-
require("reflect-metadata");
|
|
5
|
-
const errors_1 = require("./errors");
|
|
6
|
-
const mesh_1 = require("./mesh");
|
|
7
|
-
const metadata_1 = require("./metadata");
|
|
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
|
-
metadata_1.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;
|
package/out/main/metadata.d.ts
DELETED
package/out/main/metadata.js
DELETED