mesh-ioc 1.3.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 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
@@ -1,5 +1,4 @@
1
1
  export * from './decorators/dep';
2
2
  export * from './errors';
3
3
  export * from './mesh';
4
- export * from './scope';
5
4
  export * from './types';
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);
@@ -1,14 +1,25 @@
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
- currentScope: Scope;
8
- childScopes: Map<string, Scope>;
17
+ bindings: Map<string, Binding<any>>;
9
18
  instances: Map<string, any>;
10
19
  middlewares: Middleware[];
11
- constructor(name?: string, parent?: Mesh | undefined, scope?: Scope);
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;
@@ -17,8 +28,6 @@ export declare class Mesh {
17
28
  tryResolve<T>(key: ServiceKey<T>): T | undefined;
18
29
  connect<T>(value: T): T;
19
30
  use(fn: Middleware): this;
20
- scope(scopeId: string): Scope;
21
- createScope(scopeId: string, scopeName?: string): Mesh;
22
31
  protected instantiate<T>(binding: Binding<T>): T;
23
32
  protected applyMiddleware<T>(value: T): T;
24
33
  protected injectRef(value: any): void;
package/out/main/mesh.js CHANGED
@@ -2,29 +2,57 @@
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
- constructor(name = 'default', parent = undefined, scope) {
19
+ constructor(name = 'default', parent = undefined) {
10
20
  this.name = name;
11
21
  this.parent = parent;
12
- this.childScopes = new Map();
22
+ this.bindings = new Map();
13
23
  this.instances = new Map();
14
24
  this.middlewares = [];
15
- this.currentScope = scope !== null && scope !== void 0 ? scope : new scope_1.Scope(name);
16
- this.currentScope.constant('Mesh', this);
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
- this.currentScope.service(key, impl);
20
- return this;
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
- this.currentScope.constant(key, value);
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
- this.currentScope.alias(key, referenceKey);
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) {
@@ -41,7 +69,7 @@ class Mesh {
41
69
  if (instance) {
42
70
  return instance;
43
71
  }
44
- const binding = this.currentScope.bindings.get(k);
72
+ const binding = this.bindings.get(k);
45
73
  if (binding) {
46
74
  instance = this.instantiate(binding);
47
75
  instance = this.connect(instance);
@@ -62,20 +90,6 @@ class Mesh {
62
90
  this.middlewares.push(fn);
63
91
  return this;
64
92
  }
65
- scope(scopeId) {
66
- let scope = this.childScopes.get(scopeId);
67
- if (!scope) {
68
- scope = new scope_1.Scope(scopeId);
69
- this.childScopes.set(scopeId, scope);
70
- }
71
- return scope;
72
- }
73
- createScope(scopeId, scopeName = scopeId) {
74
- const childScope = this.childScopes.get(scopeId);
75
- const newScope = new scope_1.Scope(scopeName, childScope !== null && childScope !== void 0 ? childScope : []);
76
- const mesh = new Mesh(scopeId, this, newScope);
77
- return mesh;
78
- }
79
93
  instantiate(binding) {
80
94
  switch (binding.type) {
81
95
  case 'alias':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mesh-ioc",
3
- "version": "1.3.0",
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",
@@ -1,7 +0,0 @@
1
- import { ServiceMetadata } from '../types';
2
- export declare const serviceMetadata: ServiceMetadata[];
3
- export interface SvcOptions {
4
- alias?: string;
5
- metadata?: any;
6
- }
7
- export declare function service(options?: SvcOptions): (target: any) => void;
@@ -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;