New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

mesh-ioc

Package Overview
Dependencies
Maintainers
1
Versions
30
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mesh-ioc - npm Package Compare versions

Comparing version 0.2.1 to 0.3.0

15

out/main/bindings.d.ts
import { Mesh } from './mesh';
import { ServiceConstructor } from './types';
import { Constructor, ServiceConstructor } from './types';
export declare abstract class Binding<T> {

@@ -15,7 +15,18 @@ readonly mesh: Mesh;

export declare class ServiceBinding<T> extends Binding<T> {
readonly ctor: ServiceConstructor<T>;
ctor: ServiceConstructor<T>;
instance: T | undefined;
constructor(mesh: Mesh, key: string, ctor: ServiceConstructor<T>);
get(): T;
protected processClass(ctor: any): {
new (): {
[x: string]: any;
};
[x: string]: any;
};
}
export declare class ClassBinding<T extends Constructor<T>> extends Binding<T> {
readonly ctor: T;
constructor(mesh: Mesh, key: string, ctor: T);
get(): T;
}
export declare class ProxyBinding<T> extends Binding<T> {

@@ -22,0 +33,0 @@ readonly alias: string;

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProxyBinding = exports.ServiceBinding = exports.ConstantBinding = exports.Binding = void 0;
exports.ProxyBinding = exports.ClassBinding = exports.ServiceBinding = exports.ConstantBinding = exports.Binding = void 0;
class Binding {

@@ -24,12 +24,32 @@ constructor(mesh, key) {

super(mesh, key);
this.ctor = ctor;
this.ctor = this.processClass(ctor);
}
get() {
if (!this.instance) {
this.instance = this.mesh.connect(new this.ctor());
const inst = new this.ctor();
this.instance = this.mesh.connect(inst);
}
return this.instance;
}
processClass(ctor) {
// A fake derived class is created with Mesh attached to its prototype.
// This allows accessing deps in constructor whilst preserving instanceof.
const derived = class extends ctor {
};
Object.defineProperty(derived, 'name', { value: ctor.name });
this.mesh.injectRef(derived.prototype);
return derived;
}
}
exports.ServiceBinding = ServiceBinding;
class ClassBinding extends Binding {
constructor(mesh, key, ctor) {
super(mesh, key);
this.ctor = ctor;
}
get() {
return this.ctor;
}
}
exports.ClassBinding = ClassBinding;
class ProxyBinding extends Binding {

@@ -36,0 +56,0 @@ constructor(mesh, key, alias) {

2

out/main/errors.d.ts

@@ -17,5 +17,5 @@ declare class BaseError extends Error {

}
export declare class MeshInvalidServiceBinding extends BaseError {
export declare class MeshInvalidBinding extends BaseError {
constructor(key: string);
}
export {};
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MeshInvalidServiceBinding = exports.MeshServiceNotFound = exports.DepInstanceNotConnected = exports.DepKeyNotInferred = void 0;
exports.MeshInvalidBinding = exports.MeshServiceNotFound = exports.DepInstanceNotConnected = exports.DepKeyNotInferred = void 0;
class BaseError extends Error {

@@ -35,5 +35,5 @@ constructor() {

exports.MeshServiceNotFound = MeshServiceNotFound;
class MeshInvalidServiceBinding extends BaseError {
class MeshInvalidBinding extends BaseError {
constructor(key) {
super(`Invalid service binding "${key}". Valid bindings are: ` +
super(`Invalid binding "${key}". Valid bindings are: ` +
`string to constructor e.g. ("MyService", MyService) or ` +

@@ -44,2 +44,2 @@ `abstract class to constructor e.g. (MyService, MyServiceImpl) or` +

}
exports.MeshInvalidServiceBinding = MeshInvalidServiceBinding;
exports.MeshInvalidBinding = MeshInvalidBinding;

@@ -0,3 +1,4 @@

import { ClassBinding } from '.';
import { Binding } from './bindings';
import { AbstractService, Middleware, ServiceConstructor, ServiceKey } from './types';
import { AbstractClass, Constructor, Middleware, ServiceConstructor, ServiceKey } from './types';
export declare const MESH_REF: unique symbol;

@@ -11,6 +12,9 @@ export declare class Mesh {

bind<T>(impl: ServiceConstructor<T>): Binding<T>;
bind<T>(key: AbstractService<T> | string, impl: ServiceConstructor<T>): Binding<T>;
bind<T>(key: AbstractClass<T> | string, impl: ServiceConstructor<T>): Binding<T>;
protected _bindService<T>(k: string, impl: ServiceConstructor<T>): Binding<T>;
class<T extends Constructor<any>>(ctor: T): ClassBinding<T>;
class<T extends Constructor<any>>(key: AbstractClass<T> | string, ctor: T): Binding<T>;
protected _bindClass<T extends Constructor<any>>(k: string, ctor: T): ClassBinding<T>;
constant<T>(key: ServiceKey<T>, value: T): Binding<T>;
alias<T>(key: AbstractService<T> | string, referenceKey: AbstractService<T> | string): Binding<T>;
alias<T>(key: AbstractClass<T> | string, referenceKey: AbstractClass<T> | string): Binding<T>;
resolve<T>(key: ServiceKey<T>): T;

@@ -20,3 +24,3 @@ connect<T>(value: T): T;

protected applyMiddleware<T>(value: T): T;
protected addMeshRef(value: any): void;
injectRef(value: any): void;
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Mesh = exports.MESH_REF = void 0;
const _1 = require(".");
const bindings_1 = require("./bindings");

@@ -23,3 +24,3 @@ const errors_1 = require("./errors");

}
throw new errors_1.MeshInvalidServiceBinding(String(key));
throw new errors_1.MeshInvalidBinding(String(key));
}

@@ -31,2 +32,17 @@ _bindService(k, impl) {

}
class(key, ctor) {
const k = keyToString(key);
if (typeof ctor === 'function') {
return this._bindClass(k, ctor);
}
else if (typeof key === 'function') {
return this._bindClass(k, key);
}
throw new errors_1.MeshInvalidBinding(String(key));
}
_bindClass(k, ctor) {
const binding = new _1.ClassBinding(this, k, ctor);
this.bindings.set(k, binding);
return binding;
}
constant(key, value) {

@@ -58,3 +74,3 @@ const k = keyToString(key);

const res = this.applyMiddleware(value);
this.addMeshRef(res);
this.injectRef(res);
return res;

@@ -73,3 +89,3 @@ }

}
addMeshRef(value) {
injectRef(value) {
if (typeof value !== 'object') {

@@ -76,0 +92,0 @@ return;

@@ -7,7 +7,7 @@ export declare type Constructor<T> = {

};
export declare type AbstractService<T> = {
export declare type AbstractClass<T> = {
name: string;
prototype: T;
};
export declare type ServiceKey<T> = ServiceConstructor<T> | AbstractService<T> | string;
export declare type ServiceKey<T> = ServiceConstructor<T> | AbstractClass<T> | string;
export declare type Middleware = (instance: any) => any;
{
"name": "mesh-ioc",
"version": "0.2.1",
"version": "0.3.0",
"description": "Mesh: Powerful and Lightweight IoC Library",

@@ -5,0 +5,0 @@ "main": "out/main/index.js",

@@ -83,3 +83,6 @@ # Mesh IoC

this.redis = new RedisClient(/* ... */);
this.redis.on('connect', () => this.logger.log('Connected to Redis'));
this.redis.on('connect', () => {
// Logger can now be used by this class transparently
this.logger.log('Connected to Redis');
});
}

@@ -91,4 +94,4 @@ }

class AppMesh extends Mesh {
this.bind(Redis);
this.bind(Logger, ConsoleLogger);
this.bind(Redis);
}

@@ -116,1 +119,193 @@ ```

- Constant values can be bound to mesh. Those could be instances of other classes.
**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!
## Application Architecture Guide
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.
1. Identify the layers of your application. Oftentimes different components have different lifespans or, as we tend to refer to it, scopes:
- **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).
- **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.
- **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)
2. Build the mesh hierarchy, starting from application scope.
```ts
// app.ts
export class App {
// You can either inherit from Mesh or store it as a field.
// Name parameter is optional, but can be useful for debugging.
mesh = new Mesh('App');
// Define your application-scoped services
logger = mesh.bind(Logger, GlobalLogger);
database = mesh.bind(MyDatabase);
server = mesh.bind(MyServer);
// ...
start() {
// Define logic for application startup
// (e.g. connect to databases, start listening to servers, etc)
}
}
// session.ts
export class Session {
mesh: Mesh;
// A sample session scope will depend on Request and Response;
// Parent mesh is required so that session-scoped services
// can access application-scoped services
// (the other way around does not work, obviously)
constructor(parentMesh: Mesh, req: Request, res: Response) {
this.mesh = new Mesh('Session', parentMesh);
// Session dependencies can be bound as constants
this.mesh.constant(Request, req);
this.mesh.constant(Response, req);
// Bindings from parent can be overridden, so that session-scoped services
// could use more specialized versions
this.mesh.bind(Logger, SessionLogger);
// Define other session-scoped services
this.mesh.bind(SessionScopedService);
// ...
}
start() {
// Define what happens when session is established
}
}
```
3. Create an application entrypoint (advice: never mix modules that export classes with entrypoint modules!):
```ts
// bin/run.ts
const app = new App();
app.start();
```
4. Identify the proper component for session entrypoint:
```ts
export class MyServer {
// Note: Mesh is automatically available in all "connected" classes
@dep() mesh!: Mesh;
// The actual server (e.g. http server or web socket server)
server: Server;
constructor() {
this.server = new Server((req, res) => {
// This is the actual entrypoint of Session
const session = new Session(this.mesh, req, res);
// Note the similarity to application entrypoint
session.start();
});
}
}
```
5. Use `@dep()` to transparently inject dependencies in your services:
```ts
export class SessionScopedService {
@dep() database!: Database;
@dep() req!: Request;
@dep() res!: Request;
// ...
}
```
6. Come up with conventions and document them, for example:
- create different directories for services with different scopes
- separate entrypoints from the rest of the modules
- entrypoints only import, instantiate and invoke methods (think "runnable from CLI")
- all other modules only export stuff
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.
## Advanced
### Connecting "guest" instances
Mesh IoC allows connecting an arbitrary instance to the mesh, so that the `@dep` can be used in it.
For example:
```ts
// This entity class is not managed by Mesh directly, instead it's instantiated by UserService
class User {
@dep() database!: Database;
// Note: constructor can have arbitrary arguments in this case,
// because the instantiated isn't controlled by Mesh
constructor(
public firstName = '',
public lastName = '',
public email = '',
// ...
) {}
async save() {
await this.database.save(this);
}
}
class UserService {
@dep() mesh!: Mesh;
createUser(firstName = '', lastName = '', email = '', /*...*/) {
const user = new User();
// Now connect it to mesh, so that User can access its services via `@dep`
return this.mesh.connect(user);
}
}
```
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).
### Binding classes
Mesh typically connects the instances of services (again, services are classes with zero-arg constructors).
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.
Example:
```ts
// An example arbitrary class, unconnected to mesh
class Session {
constructor(readonly sessionId: number) {}
}
// A normal service connected to mesh
class SessionManager {
// Class constructor is injected (note: `key` is required because it cannot be deferred in this case)
@dep({ key: 'Session' }) Session!: typeof Session;
createSession(id: number): Session {
// Instantiate using injected constructor
return new this.Session(id);
}
}
// ...
const mesh = new Mesh();
mesh.bind(SessionManager);
mesh.class(Session); // This makes Session class available as a binding
// In tests this can be overridden, so SessionManager will transparently instantiate a different class
// (assuming constructor signatures match)
mesh.class(Session, MySession);
```
This approach can also be combined with `connect` so that the arbitrary class can also use `@dep`. Mix & match FTW!
## License
[ISC](https://en.wikipedia.org/wiki/ISC_license) © Boris Okunskiy
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc