- Instance dependencies — Define service dependencies that already exist.
- Class dependencies — Define service dependency classes that are instantiated on demand.
- Optional dependencies — Define service dependencies that may be
null
if not in the container. - n-ary dependencies — Define multiple dependencies that may be resolved for a single
ServiceIdentifier
. - Lazy dependencies — Create proxies for services that may have circularities.
- Constructor dependencies — Define dependencies that are injected as constructor arguments.
- Field dependencies — Define dependencies that are injected as fields after an dependency has been instantiated.
- Hierarchical dependency trees — Seperate reusable services with longer lifetimes from transient services.
npm install service-composition
NOTE: The following examples use TypeScript, but its possible to use
service-composition
in JavaScript (without decorators) as well. See below for examples
ihelloservice.ts
import { ServiceIdentifier } from "service-composition";
const const IHelloService = ServiceIdentifier.create<IHelloService>("IHelloService");
export interface IHelloService {
sayHello(name: string): string;
}
itranslateservice.ts
import { ServiceIdentifier } from "service-composition";
const const ITranslateService = ServiceIdentifier.create<ITranslateService>("ITranslateService");
export interface ITranslateService {
format(message: string, args: Record<string, any>): string;
}
helloservice.ts
import { IHelloService } from "./ihelloservice";
import { ITranslateService } from "./itranslateservice";
export class HelloService implements IHelloService {
constructor(@ITranslateService private translate: ITranslateService) {
}
sayHello(name: str) {
return this.translate.format("hello, {name}", { name });
}
}
app.ts
import { ServiceCollection } from "service-composition";
// service interfaces
import { IHelloService } from "./ihelloservice";
import { ITranslateService } from "./itranslateservice";
// concrete services (or mock services for testing)
import { HelloService } from "./helloservice";
const serviceCollection = new ServiceCollection();
// define factories
serviceCollection.setClass(IHelloService, HelloService);
// define instances
serviceCollection.setInstance(ITranslateService, {
format(message: string, args: Record<string, any>) {
return message.replace(/\{([\w\d_$]+)\}/g, (_, key) => key in args ? args[key] : _);
}
});
// create container
const serviceProvider = serviceCollection.createContainer();
// resolve dependencies and get instances
const helloService = serviceProvider.getService(IHelloService);
console.log(helloService.sayHello("Alice")); // prints: hello, Alice
ihelloservice.js
const { ServiceIdentifier } = require("service-composition");
/** @type {ServiceIdentifier<IHelloService>} */
exports.IHelloService = ServiceIdentifier.create("IHelloService");
/**
* @typedef IHelloService
* @property {(name: string) => string} sayHello
*/
itranslateservice.js
const { ServiceIdentifier } = require("service-composition");
/** @type {ServiceIdentifier<ITranslateService>} */
exports.ITranslateService = ServiceIdentifier.create("ITranslateService");
/**
* @typedef ITranslateService
* @property {(message: string, args: Record<string, any>) => string} format
*/
helloservice.js
const { ITranslateService } = require("./itranslateservice.js")
class HelloService {
/**
* @param {import("./translateservice").ITranslateService} translate
*/
constructor(translate) {
this.translate = translate;
}
/**
* @param {string} name
*/
sayHello(name) {
return this.translate.format("hello, {name}", { name });
}
}
// Call decorator directly
ITranslateService(HelloService, undefined, /*parameterIndex*/ 0);
exports.HelloService = HelloService;
app.js
const { ServiceCollection } = require("service-composition");
// service interfaces
const { IHelloService } = require("./ihelloservice");
const { ITranslateService } = require("./itranslateservice");
// concrete services (or mock services for testing)
const { HelloService } = require("./helloservice");
const serviceCollection = new ServiceCollection();
// define factories
serviceCollection.setClass(IHelloService, HelloService);
// define instances
serviceCollection.setInstance(ITranslateService, {
format(message, args) {
return message.replace(/\{([\w\d_$]+)\}/g, (_, key) => key in args ? args[key] : _);
}
});
// create container
const serviceProvider = serviceCollection.createContainer();
// resolve dependencies and get instances
const helloService = serviceProvider.getService(IHelloService);
console.log(helloService.sayHello("Alice")); // prints: hello, Alice
interface ServiceIdentifier
— Identifies a service within the composition graph, and is a decorator that indicates the provided dependency is required.serviceName
— Gets the (optional) name of the service.
namespace ServiceIdentifier
— Methods used to get/createServiceIdentifier
instances.create(name?: string | symbol)
— Creates a unique service identifier, which can act as a parameter or field decorator.
function optional(id)
— A decorator that indicates the providedServiceIdentifier
is an optional dependency.function many(id)
— A decorator that indicates the providedServiceIdentifier
is an n-ary dependency.enum ServiceDependencyCardinality
— Describes the cardinality of aServiceDependency
:ZeroOrOne
— The dependency may have at most one service.ExactlyOne
— The dependency may have exactly one service.ZeroOrMore
— The dependency may have any number of services.
interface ServiceDependency
— Describes a dependency on a class.id
— TheServiceIdentifier
that satisfies this dependency.parameterIndex
— The constructor parameter this dependency satisfies, if this describes a parameter.propertyName
— The name of the property (i.e., field) this dependency satisfies, if this describes a property.cardinality
— The cardinality of the dependency.
namespace ServiceDependency
:store(id, target, parameterIndex, cardinality)
— Stores information about aServiceDependency
for a constructor parameter.store(id, target, propertyName, cardinality)
— Stores information about aServiceDependency
for a property/field on an instance.get(target)
— Gets theServiceDependencies
for a class.isParameterDependency(dependency)
— Tests whether aServiceDependency
is for a constructor parameter.isPropertyDependency(dependency)
— Tests whether aServiceDependency
is for an instance field/property.
class ServiceDescriptor
— Describes how a service should be activated.static forClass(ctor, staticArguments, supportsDelayedInstantiation)
— Creates aClassDescriptor
for a class with the provided bound arguments and whether the result can be created as aProxy
for circular dependencies.static forInstance(value)
— Creates anInstanceDescriptor
for a value.abstract activate(dependencies)
— Instantiates a service from the descriptor.
class ClassDescriptor
— AServiceDescriptor
describing a class.bind(...args)
— Binds additional static arguments to aClassDescriptor
.activate(dependencies)
— Instantiates a service from the descriptor.
class InstanceDescriptor
— AServiceDescriptor
describing an instance.activate(dependencies)
— Instantiates a service from the descriptor.
class ServiceCollection
— A catalog that maps aServiceIdentifier
to aServiceDescriptor
.new ServiceCollection(entries?)
— Creates a newServiceCollection
.get size()
— Returns the number of entries in the collection.has(id)
— Returns whether aServiceIdentifier
is present in the collection.get(id)
— Gets theServiceDescriptor
associated with aServiceIdentifier
.set(id, descriptor)
— Sets theServiceDescriptor
or an array ofServiceDescriptor
objects to use for aServiceIdentifier
.setInstance(id, value)
— Shorthand forcol.set(id, ServiceDescriptor.forInstance(value))
.setClass(id, ctor, staticArguments, supportsDelayedInstantiation?)
— Shorthand forcol.set(id, ServiceDescriptor.forClass(ctor, staticArguments, supportsDelayedInstantiation))
.add(id, descriptor)
— Adds aServiceDescriptor
or an array ofServiceDescriptor
objects to use for aServiceIdentifier
(similar tocol.set
, except it appends to the list of descriptors).addInstance(id, value)
— Shorthand forcol.add(id, ServiceDescriptor.forInstance(value))
.addClass(id, ctor, staticArguments, supportsDelayedInstantiation?)
— Shorthand forcol.add(id, ServiceDescriptor.forClass(ctor, staticArguments, supportsDelayedInstantiation))
.createContainer(parent?)
— Creates aServiceContainer
with an optional parent.
interface IServiceProvider
— Describes the shape of a service provider.const IServiceProvider
— AServiceIdentifier
for anIServiceProvider
.class ServiceContainer
— Instantiates and holds references to services described in aServiceCollection
. EveryServiceContainer
has a singleIServiceProvider
dependency of itself.new ServiceContainer(services)
— Creates a newServiceContainer
from aServiceCollection
.createInstance(descriptor, ...args)
— Instantiates aClassDescriptor
or class with the provided static arguments.hasService(serviceId)
— Tests whether the providedServiceIdentifier
can be instantiated by the container.getServices(serviceId)
— Instantiates services for the providedServiceIdentifier
(when not already instantiated) and returns an array of all instantiated services.getService(serviceId)
— Instantiates the service for the providedServiceIdentifier
(when not already instantiated) and returns it. Throws an error if there is not exactly one service for the providedServiceIdentifier
.tryGetService(serviceId)
— Instantiates the service for the providedServiceIdentifier
(when not already instantiated) and returns it if it was defined. Throws an error if there is more than one service for the providedServiceIdentifier
.createChild(services)
— Creates a child container for this container's services and the providedServiceCollection
.
This package depends on the following packages at runtime:
@esfx/iter-fn
- For enhanced iteration.@esfx/lazy
- For lazy initialization of classes.graphmodel
- For managing the composition graph.tslib
- For TypeScript runtime helpers.
This package is licensed under the MIT License.
This package is partially based on the dependency injection system used by VS Code, but with substantial changes in implementation. Please review THIRD_PARTY_NOTICES for more information.