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 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 4KB minified, even less gzipped
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
- this.bind(Redis);
94
- this.bind(Logger, ConsoleLogger);
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 construtor arguments, read on!
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 = new Mesh('App');
138
- // Define your application-scoped services
139
- logger = mesh.bind(Logger, GlobalLogger);
140
- database = mesh.bind(MyDatabase);
141
- server = mesh.bind(MyServer);
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
- // A sample session scope will depend on Request and Response;
155
- // Parent mesh is required so that session-scoped services
156
- // can access application-scoped services
157
- // (the other way around does not work, obviously)
158
- constructor(parentMesh: Mesh, req: Request, res: Response) {
159
- this.mesh = new Mesh('Session', parentMesh);
160
- // Session dependencies can be bound as constants
161
- this.mesh.constant(Request, req);
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
- // Note: Mesh is automatically available in all "connected" classes
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
- const session = new Session(this.mesh, req, res);
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!: Request;
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 instantiated isn't controlled by Mesh
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
@@ -1,35 +1,14 @@
1
- import { Mesh } from './mesh';
2
- import { Constructor, ServiceConstructor } from './types';
3
- export declare abstract class Binding<T> {
4
- readonly mesh: Mesh;
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
- constructor(mesh: Mesh, key: string, value: T);
12
- get(): T;
13
- }
14
- export declare class ServiceBinding<T> extends Binding<T> {
15
- ctor: ServiceConstructor<T>;
16
- instance: T | undefined;
17
- constructor(mesh: Mesh, key: string, ctor: ServiceConstructor<T>);
18
- get(): T;
19
- protected processClass(ctor: any): {
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
+ };
@@ -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,7 @@
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;
@@ -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;
@@ -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 MeshServiceNotFound extends BaseError {
14
+ export declare class MeshBindingNotFound extends BaseError {
15
15
  constructor(meshName: string, serviceKey: string);
16
16
  }
17
17
  export declare class MeshInvalidBinding extends BaseError {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MeshInvalidBinding = exports.MeshServiceNotFound = exports.DepInstanceNotConnected = exports.DepKeyNotInferred = void 0;
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 MeshServiceNotFound extends BaseError {
29
+ class MeshBindingNotFound extends BaseError {
30
30
  constructor(meshName, serviceKey) {
31
- super(`Service "${serviceKey}" not found in Mesh "${meshName}"`);
31
+ super(`"${serviceKey}" not found in Mesh "${meshName}"`);
32
32
  }
33
33
  }
34
- exports.MeshServiceNotFound = MeshServiceNotFound;
34
+ exports.MeshBindingNotFound = MeshBindingNotFound;
35
35
  class MeshInvalidBinding extends BaseError {
36
36
  constructor(key) {
37
37
  super(`Invalid binding "${key}". Valid bindings are: ` +
@@ -1,6 +1,5 @@
1
- export * from './bindings';
2
- export * from './decorators';
1
+ export * from './decorators/dep';
3
2
  export * from './errors';
4
3
  export * from './mesh';
5
- export * from './metadata';
4
+ export * from './scope';
6
5
  export * from './types';
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("./bindings"), exports);
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("./metadata"), exports);
16
+ __exportStar(require("./scope"), exports);
18
17
  __exportStar(require("./types"), exports);
@@ -1,24 +1,24 @@
1
- import { ClassBinding } from '.';
2
- import { Binding } from './bindings';
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
- bindings: Map<string, Binding<any>>;
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
- bind<T>(impl: ServiceConstructor<T>): Binding<T>;
12
- bind<T>(key: AbstractClass<T> | string, impl: ServiceConstructor<T>): Binding<T>;
13
- protected _bindService<T>(k: string, impl: ServiceConstructor<T>): Binding<T>;
14
- class<T extends Constructor<any>>(ctor: T): ClassBinding<T>;
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.bindings = new Map();
12
+ this.childScopes = new Map();
13
+ this.instances = new Map();
13
14
  this.middlewares = [];
14
- this.constant('Mesh', this);
15
+ this.currentScope = new scope_1.Scope(name);
16
+ this.currentScope.constant('Mesh', this);
15
17
  }
16
- bind(key, impl) {
17
- const k = keyToString(key);
18
- if (typeof impl === 'function') {
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
- const k = keyToString(key);
48
- const binding = new bindings_1.ConstantBinding(this, k, value);
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
- const k = keyToString(key);
54
- const refK = typeof referenceKey === 'string' ? referenceKey : referenceKey.name;
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
- const binding = this.bindings.get(k);
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
- return binding.get();
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.MeshServiceNotFound(this.name, k);
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;
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ import { ServiceKey } from './types';
2
+ export declare function keyToString<T>(key: ServiceKey<T>): string;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.keyToString = void 0;
4
+ function keyToString(key) {
5
+ return typeof key === 'string' ? key : key.name;
6
+ }
7
+ exports.keyToString = keyToString;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mesh-ioc",
3
- "version": "0.3.0",
3
+ "version": "1.2.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",