diff --git a/.project b/.project
--- a/.project
+++ b/.project
@@ -14,4 +14,15 @@
org.eclipse.buildship.core.gradleprojectnature
+
+
+ 1599549685358
+
+ 30
+
+ org.eclipse.core.resources.regexFilterMatcher
+ node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__
+
+
+
diff --git a/.settings/org.eclipse.buildship.core.prefs b/.settings/org.eclipse.buildship.core.prefs
--- a/.settings/org.eclipse.buildship.core.prefs
+++ b/.settings/org.eclipse.buildship.core.prefs
@@ -1,2 +1,13 @@
+arguments=
+auto.sync=false
+build.scans.enabled=false
+connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
connection.project.dir=
eclipse.preferences.version=1
+gradle.user.home=
+java.home=/usr/lib64/jvm/java
+jvm.arguments=
+offline.mode=false
+override.workspace.settings=true
+show.console.view=true
+show.executions.view=true
diff --git a/build.gradle b/build.gradle
--- a/build.gradle
+++ b/build.gradle
@@ -1,5 +1,5 @@
plugins {
- id "org.implab.gradle-typescript" version "1.3.0"
+ id "org.implab.gradle-typescript" version "1.3.3"
id "org.implab.gradle-hg"
id "ivy-publish"
}
@@ -49,10 +49,14 @@ typescript {
compilerOptions {
types = []
declaration = true
+ experimentalDecorators = true
+ strict = true
+ // dojo-typings are sick
+ skipLibCheck = true
if(symbols != 'none') {
sourceMap = true
- sourceRoot = "_src"
+ sourceRoot = packageName
}
if (flavour == 'node') {
diff --git a/docs/ru/observable.md b/docs/ru/observable.md
--- a/docs/ru/observable.md
+++ b/docs/ru/observable.md
@@ -5,7 +5,7 @@
самостоятельных событий, например, связанных с действиями пользователя.
Является реализацией классического шаблона наблюдателя с возможность сообщить
-о коце потока событий. Данная реализация не содержит никаких дополнительных
+о конце потока событий. Данная реализация не содержит никаких дополнительных
функций, таких как фильтрация, канал с состоянием, преобразования сообщений и
т.п. Это сделано специально, чтобы реализация оставалась максимально простой.
@@ -20,7 +20,7 @@ var events = new Observable(async (notif
notify(i);
}
// по окончании последовательности информируем, что событий больше не будет
- compelte();
+ complete();
});
// создаем окно с отображением хода событий
@@ -49,9 +49,9 @@ let firstEvent = await events.next();
`Observable` можно создавать из событий другого объекта, например, виджета:
```ts
-// клсс
+// класс
class Canvas {
- readonly mouseMove: IObservable<[number,number]>
+ mouseMove: IObservable<[number,number]>;
postCreate() {
// превращаем события виджета в Observable
@@ -98,7 +98,7 @@ class PositionTracker implements IDestro
}
```
-Существует также несколько варинатов получения сообщений
+Существует также несколько вариантов получения сообщений
```ts
// регистрация метода для получений событий
@@ -128,7 +128,7 @@ class Map {
let evt = this.viewport.click.next(ct);
// преобразуем позицию на экране в координаты карты
- return this.clientToCoodinates([evt.clientx,evt.clientY]);
+ return this.clientToCoordinates([evt.clientX,evt.clientY]);
}
}
@@ -142,8 +142,8 @@ let coords = await map.peekCoordinates()
## Observable и последовательности
-Можно сичтать, что `Observable` это некоторая аналогия итератора только в
-парадигме событийного (или реактивного) программировния. Следует также понимать,
+Можно считать, что `Observable` это некоторая аналогия итератора только в
+парадигме событийного (или реактивного) программирования. Следует также понимать,
что при переходе от синхронного процедурного программирования к событийному так
же меняется и направление управления (Inverse Of Control), что означает
следующее:
@@ -153,7 +153,7 @@ let coords = await map.peekCoordinates()
* при работе с `Observable` клиенты вынуждены обрабатывать эти события по мере
их поступления и не могут на это повлиять.
-Последний пункт можно изменить применив, например, буффер или канал с
+Последний пункт можно изменить применив, например, буфер или канал с
состоянием, т.е. очередь, но данные механизмы выходят за рамки простого шаблона
наблюдателя.
@@ -179,4 +179,4 @@ events.on((data) => {
// будет вызван для всех сообщений
processEvent(data);
});
-```
\ No newline at end of file
+```
diff --git a/src/amd/js/data/StoreAdapter.js b/src/amd/js/data/StoreAdapter.js
--- a/src/amd/js/data/StoreAdapter.js
+++ b/src/amd/js/data/StoreAdapter.js
@@ -71,7 +71,6 @@ function(declare, safe, when, QueryResul
});
mapped.total = total;
var results = new QueryResults(mapped);
- console.log(results);
return results;
});
},
diff --git a/src/amd/ts/di/ResolverHelper.ts b/src/amd/ts/di/ResolverHelper.ts
--- a/src/amd/ts/di/ResolverHelper.ts
+++ b/src/amd/ts/di/ResolverHelper.ts
@@ -12,7 +12,7 @@ const trace = TraceSource.get(m.id);
trace.debug("globalRequire = {0}", globalRequire);
class ModuleResolver {
- _base: string;
+ _base: string | undefined;
_require: Require;
constructor(req: Require, base?: string) {
diff --git a/src/amd/ts/log/trace.ts b/src/amd/ts/log/trace.ts
--- a/src/amd/ts/log/trace.ts
+++ b/src/amd/ts/log/trace.ts
@@ -9,7 +9,7 @@ export = {
cb = filter;
filter = undefined;
}
- let test: Predicate;
+ let test: Predicate | undefined;
if (filter instanceof RegExp) {
test = chId => filter.test(chId);
} else if (filter instanceof Function) {
@@ -21,7 +21,7 @@ export = {
if (test) {
TraceSource.on(source => {
source.level = this.level;
- if (test(source.id))
+ if (test && test(source.id))
source.events.on(cb);
});
} else {
diff --git a/src/amd/ts/text/TemplateCompiler.ts b/src/amd/ts/text/TemplateCompiler.ts
--- a/src/amd/ts/text/TemplateCompiler.ts
+++ b/src/amd/ts/text/TemplateCompiler.ts
@@ -2,6 +2,7 @@ import * as format from "./format";
import { TraceSource, DebugLevel } from "../log/TraceSource";
import { ITemplateParser, TokenType } from "./TemplateParser";
import m = require("module");
+import { isKeyof } from "../safe";
const trace = TraceSource.get(m.id);
@@ -21,7 +22,7 @@ const htmlEscaper = /[&<>"'\/]/g;
// Escape a string for HTML interpolation.
function escapeHtml(string: any) {
- return ("" + string).replace(htmlEscaper, match => htmlEscapes[match]);
+ return ("" + string).replace(htmlEscaper, match => isKeyof(match, htmlEscapes) ? htmlEscapes[match] : "");
}
export class TemplateCompiler {
diff --git a/src/amd/ts/text/TemplateParser.ts b/src/amd/ts/text/TemplateParser.ts
--- a/src/amd/ts/text/TemplateParser.ts
+++ b/src/amd/ts/text/TemplateParser.ts
@@ -38,7 +38,7 @@ export class TemplateParser implements I
_tokens: string[];
_pos = -1;
_type: TokenType;
- _value: string;
+ _value: string | undefined;
constructor(text: string) {
argumentNotEmptyString(text, "text");
@@ -66,6 +66,8 @@ export class TemplateParser implements I
}
value() {
+ if (!this._value)
+ throw new Error("The current token doesn't have a value");
return this._value;
}
diff --git a/src/amd/ts/text/format.ts b/src/amd/ts/text/format.ts
--- a/src/amd/ts/text/format.ts
+++ b/src/amd/ts/text/format.ts
@@ -2,29 +2,32 @@ import { format as dojoFormatNumber } fr
import { format as dojoFormatDate } from "dojo/date/locale";
import { Formatter, compile as _compile } from "./StringFormat";
-import { isNumber, isNull } from "../safe";
+import { isNumber, isNull, get } from "../safe";
interface NumberFormatOptions {
round?: number;
pattern?: string;
}
-function convertNumber(value: any, pattern: string) {
+function convertNumber(value: any, _pattern?: string) {
if (isNumber(value)) {
const nopt = {} as NumberFormatOptions;
- if (pattern.indexOf("!") === 0) {
+ let pattern = _pattern;
+ if (pattern && pattern.indexOf("!") === 0) {
nopt.round = -1;
pattern = pattern.substr(1);
}
nopt.pattern = pattern;
return dojoFormatNumber(value, nopt);
+ } else {
+ return "";
}
}
-function convertDate(value: any, pattern: string) {
+function convertDate(value: any, pattern?: string) {
if (value instanceof Date) {
- const m = pattern.match(/^(\w+)-(\w+)$/);
+ const m = pattern && pattern.match(/^(\w+)-(\w+)$/);
if (m)
return dojoFormatDate(value, {
selector: m[2],
@@ -37,6 +40,8 @@ function convertDate(value: any, pattern
selector: "date",
datePattern: pattern
});
+ } else {
+ return "";
}
}
@@ -46,7 +51,7 @@ function format(msg: string, ...args: an
return _formatter.format(msg, ...args);
}
-function _convert(value: any, pattern: string) {
+function _convert(value: any, pattern?: string) {
return _formatter.convert(value, pattern);
}
@@ -55,9 +60,9 @@ namespace format {
export function compile(text: string) {
const template = _compile(text);
- return (...data) => {
+ return (...data: any[]) => {
return template((name, pattern) => {
- const value = data[name];
+ const value = get(name, data);
return !isNull(value) ? convert(value, pattern) : "";
});
};
diff --git a/src/amd/ts/text/template-compile.ts b/src/amd/ts/text/template-compile.ts
--- a/src/amd/ts/text/template-compile.ts
+++ b/src/amd/ts/text/template-compile.ts
@@ -34,7 +34,7 @@ compile.load = (id: string, require: Req
callback(cache[url]);
} else {
trace.debug("{0} -> {1}: load", id, url);
- request(url).then(compile).then((tc: TemplateFn) => {
+ request(url).then(compile).then((tc: TemplateFn) => {
trace.debug("{0}: compiled", url);
callback(cache[url] = tc);
}, (err: any) => {
diff --git a/src/cjs/ts/di/ResolverHelper.ts b/src/cjs/ts/di/ResolverHelper.ts
--- a/src/cjs/ts/di/ResolverHelper.ts
+++ b/src/cjs/ts/di/ResolverHelper.ts
@@ -4,10 +4,10 @@ import { TraceSource } from "../log/Trac
const trace = TraceSource.get(module.id);
const mainModule = require.main;
-const mainRequire = (id: string) => mainModule.require(id);
+const mainRequire = (id: string) => mainModule ? mainModule.require(id) : require;
class ModuleResolver {
- _base: string;
+ _base: string | undefined;
_require: NodeRequireFunction;
constructor(req: NodeRequireFunction, base?: string) {
diff --git a/src/main/ts/Cancellation.ts b/src/main/ts/Cancellation.ts
--- a/src/main/ts/Cancellation.ts
+++ b/src/main/ts/Cancellation.ts
@@ -3,7 +3,7 @@ import { argumentNotNull, destroyed } fr
export class Cancellation implements ICancellation {
private _reason: any;
- private _cbs: Array<(e: any) => void>;
+ private _cbs: Array<(e: any) => void> | undefined;
constructor(action: (cancel: (e?: any) => void) => void) {
argumentNotNull(action, "action");
@@ -44,7 +44,7 @@ export class Cancellation implements ICa
}
}
- private _unregister(cb) {
+ private _unregister(cb: any) {
if (this._cbs) {
const i = this._cbs.indexOf(cb);
if (i >= 0)
@@ -52,7 +52,7 @@ export class Cancellation implements ICa
}
}
- private _cancel(reason) {
+ private _cancel(reason: any) {
if (this._reason)
return;
@@ -60,7 +60,7 @@ export class Cancellation implements ICa
if (this._cbs) {
this._cbs.forEach(cb => cb(reason));
- this._cbs = null;
+ this._cbs = undefined;
}
}
diff --git a/src/main/ts/EventProvider.ts b/src/main/ts/EventProvider.ts
new file mode 100644
--- /dev/null
+++ b/src/main/ts/EventProvider.ts
@@ -0,0 +1,41 @@
+import { IDestroyable } from "./interfaces";
+import { Observable } from "./Observable";
+
+/**
+ * Event proviers are used to produce events, throug this object you can feed
+ * the Observable with input events. Once the EventProvider is destroyed the
+ * bound obsevable is disconnected and marked as 'done'.
+ */
+export class EventProvider implements IDestroyable {
+
+ _observable: Observable | undefined;
+
+ _next: ((evt: T) => void) | undefined;
+ _done: (() => void) | undefined;
+
+ constructor() {
+ this._observable = new Observable((next, _error, done) => {
+ this._next = next;
+ this._done = done;
+ });
+ }
+
+ destroy(): void {
+ if (this._observable) {
+ // break all references
+ this._observable = undefined;
+ this._next = undefined;
+ this._done = undefined;
+ }
+ }
+ post(event: T) {
+ return this._next && this._next(event);
+ }
+
+ getObservable() {
+ if (!this._observable)
+ throw new Error("The object is destroyed");
+
+ return this._observable;
+ }
+}
diff --git a/src/main/ts/Observable.ts b/src/main/ts/Observable.ts
--- a/src/main/ts/Observable.ts
+++ b/src/main/ts/Observable.ts
@@ -1,10 +1,10 @@
import { IObservable, IDestroyable, ICancellation, IObserver } from "./interfaces";
import { Cancellation } from "./Cancellation";
-import { argumentNotNull, destroyed } from "./safe";
+import { argumentNotNull } from "./safe";
type Handler = (x: T) => void;
-type Initializer = (notify: Handler, error?: (e: any) => void, complete?: () => void) => void;
+type Initializer = (notify: Handler, error: (e: any) => void, complete: () => void) => void;
const noop = () => { };
@@ -17,7 +17,7 @@ export class Observable implements IO
private _observers = new Array>();
- private _complete: boolean;
+ private _complete = false;
private _error: any;
@@ -46,7 +46,7 @@ export class Observable implements IO
const me = this;
const observer: IObserver & IDestroyable = {
- next,
+ next: next.bind(null),
error: error ? error.bind(null) : noop,
complete: complete ? complete.bind(null) : noop,
@@ -62,30 +62,21 @@ export class Observable implements IO
subscribe(next: IObserver | Handler, error?: Handler, complete?: () => void): IDestroyable {
if (isObserver(next)) {
- const me = this;
- const subscription = {
- destroy() {
- me._removeObserver(next);
- }
+ this._addObserver(next);
+ return {
+ destroy: () => this._removeObserver(next)
};
- this._addObserver(next);
- return subscription;
- } else if (next) {
+ } else {
const observer = {
- next,
- error,
- complete
+ next: next.bind(null),
+ error: error ? error.bind(null) : noop,
+ complete: complete ? complete.bind(null) : noop
};
- const me = this;
- const subscription = {
- destroy() {
- me._removeObserver(observer);
- }
+
+ this._addObserver(observer);
+ return {
+ destroy: () => this._removeObserver(observer)
};
- this._addObserver(observer);
- return subscription;
- } else {
- return destroyed;
}
}
diff --git a/src/main/ts/ObservableValue.ts b/src/main/ts/ObservableValue.ts
--- a/src/main/ts/ObservableValue.ts
+++ b/src/main/ts/ObservableValue.ts
@@ -17,8 +17,10 @@ export class ObservableValue extends
}
setValue(value: T) {
- this._value = value;
- this._notifyNext(value);
+ if (this._value !== value) {
+ this._value = value;
+ this._notifyNext(value);
+ }
}
on(next: Handler, error?: Handler, complete?: () => void): IDestroyable {
diff --git a/src/main/ts/Uuid.ts b/src/main/ts/Uuid.ts
--- a/src/main/ts/Uuid.ts
+++ b/src/main/ts/Uuid.ts
@@ -6,18 +6,25 @@
// Copyright (c) 2010-2012 Robert Kieffer
// MIT License - http://opensource.org/licenses/mit-license.php
+import { MapOf } from "./interfaces";
+
declare const window: any;
-declare const require;
-declare const Buffer;
+declare const require: any;
+declare const Buffer: any;
const _window: any = "undefined" !== typeof window ? window : null;
+interface WritableArrayLike {
+ length: number;
+ [n: number]: T;
+}
+
// Unique ID creation requires a high quality random # generator. We
// feature
// detect to determine the best RNG source, normalizing to a function
// that
// returns 128-bits of randomness, since that's what's usually required
-let _rng;
+let _rng: () => WritableArrayLike = () => [];
function setupBrowser() {
// Allow for MSIE11 msCrypto
@@ -43,9 +50,9 @@ function setupBrowser() {
// If all else fails, use Math.random(). It's fast, but is of
// unspecified
// quality.
- const _rnds = new Array(16);
+ const _rnds = new Array(16);
_rng = () => {
- for (let i = 0, r; i < 16; i++) {
+ for (let i = 0, r = 0; i < 16; i++) {
if ((i & 0x03) === 0) {
r = Math.random() * 0x100000000;
}
@@ -84,22 +91,22 @@ if (_window) {
const BufferClass = ("function" === typeof Buffer) ? Buffer : Array;
// Maps for number <-> hex string conversion
-const _byteToHex = [];
-const _hexToByte = {};
+const _byteToHex: string[] = [];
+const _hexToByte: MapOf = {};
for (let i = 0; i < 256; i++) {
_byteToHex[i] = (i + 0x100).toString(16).substr(1);
_hexToByte[_byteToHex[i]] = i;
}
// **`parse()` - Parse a UUID into it's component bytes**
-function _parse(s, buf?, offset?): Array {
+function _parse(s: string, buf: number[] = [], offset?: number): number[] {
const i = (buf && offset) || 0; let ii = 0;
- buf = buf || [];
s.toLowerCase().replace(/[0-9a-f]{2}/g, oct => {
if (ii < 16) { // Don't overflow!
buf[i + ii++] = _hexToByte[oct];
}
+ return "";
});
// Zero out remaining bytes if string was short
@@ -111,7 +118,7 @@ function _parse(s, buf?, offset?): Array
}
// **`unparse()` - Convert UUID byte array (ala parse()) into a string**
-function _unparse(buf, offset?): string {
+function _unparse(buf: ArrayLike, offset?: number): string {
let i = offset || 0; const bth = _byteToHex;
return bth[buf[i++]] + bth[buf[i++]] + bth[buf[i++]] +
bth[buf[i++]] + "-" + bth[buf[i++]] + bth[buf[i++]] + "-" +
@@ -145,13 +152,20 @@ let _clockseq = (_seedBytes[6] << 8 | _s
// Previous uuid creation time
let _lastMSecs = 0; let _lastNSecs = 0;
+interface V1Options {
+ clockseq?: number;
+ msecs?: number;
+ nsecs?: number;
+ node?: number[];
+}
+
// See https://github.com/broofa/node-uuid for API details
-function _v1(options?, buf?, offset?): string {
+function _v1(options?: V1Options): string;
+function _v1(options: V1Options, buf: number[], offset?: number): number[];
+function _v1(options: V1Options = {}, buf?: number[], offset?: number): string | number[] {
let i = buf && offset || 0;
const b = buf || [];
- options = options || {};
-
let clockseq = (options.clockseq != null) ? options.clockseq : _clockseq;
// UUID timestamps are 100 nano-second units since the Gregorian
@@ -228,19 +242,21 @@ function _v1(options?, buf?, offset?): s
return buf ? buf : _unparse(b);
}
+interface V4Opptions {
+ rng?: () => WritableArrayLike;
+
+ random?: number[];
+}
+
// **`v4()` - Generate random UUID**
// See https://github.com/broofa/node-uuid for API details
-function _v4(options?, buf?, offset?): string {
+function _v4(options?: V4Opptions): string;
+function _v4(options: V4Opptions, buf: number[], offset?: number): number[];
+function _v4(options: V4Opptions = {}, buf?: number[], offset?: number): string | number[] {
// Deprecated - 'format' argument, as supported in v1.2
const i = buf && offset || 0;
- if (typeof (options) === "string") {
- buf = (options === "binary") ? new BufferClass(16) : null;
- options = null;
- }
- options = options || {};
-
const rnds = options.random || (options.rng || _rng)();
// Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
diff --git a/src/main/ts/components/ActivatableMixin.ts b/src/main/ts/components/ActivatableMixin.ts
--- a/src/main/ts/components/ActivatableMixin.ts
+++ b/src/main/ts/components/ActivatableMixin.ts
@@ -9,15 +9,21 @@ const log = TraceSource.get("@implab/cor
export function ActivatableMixin>(Base: TBase) {
return class extends Base implements IActivatable {
- _controller: IActivationController;
+ _controller: IActivationController | undefined;
- _active: boolean;
+ _active = false;
isActive() {
return this._active;
}
+ hasActivationController() {
+ return !!this._controller;
+ }
+
getActivationController() {
+ if (!this._controller)
+ throw Error("Activation controller isn't set");
return this._controller;
}
diff --git a/src/main/ts/components/AsyncComponent.ts b/src/main/ts/components/AsyncComponent.ts
--- a/src/main/ts/components/AsyncComponent.ts
+++ b/src/main/ts/components/AsyncComponent.ts
@@ -2,8 +2,10 @@ import { Cancellation } from "../Cancell
import { IAsyncComponent, ICancellation, ICancellable, IDestroyable } from "../interfaces";
import { destroy } from "../safe";
+const noop = () => void (0);
+
export class AsyncComponent implements IAsyncComponent, ICancellable {
- _cancel: (e: any) => void;
+ _cancel: ((e: any) => void) = noop;
_completion: Promise = Promise.resolve();
@@ -26,7 +28,7 @@ export class AsyncComponent implements I
// after the operation is complete we need to cleanup the
// resources
destroy(h);
- this._cancel = null;
+ this._cancel = noop;
}
};
@@ -34,7 +36,6 @@ export class AsyncComponent implements I
}
cancel(reason: any) {
- if (this._cancel)
- this._cancel(reason);
+ this._cancel(reason);
}
}
diff --git a/src/main/ts/di/ActivationContext.ts b/src/main/ts/di/ActivationContext.ts
--- a/src/main/ts/di/ActivationContext.ts
+++ b/src/main/ts/di/ActivationContext.ts
@@ -1,7 +1,8 @@
import { TraceSource } from "../log/TraceSource";
-import { argumentNotNull, argumentNotEmptyString, isPrimitive, each, isNull } from "../safe";
-import { Descriptor, ServiceMap } from "./interfaces";
+import { argumentNotEmptyString } from "../safe";
+import { Descriptor, ContainerServiceMap, ContainerKeys, TypeOfService, ILifetime } from "./interfaces";
import { Container } from "./Container";
+import { MapOf } from "../interfaces";
const trace = TraceSource.get("@implab/core/di/ActivationContext");
@@ -10,50 +11,85 @@ export interface ActivationContextInfo {
service: string;
- scope: ServiceMap;
}
-export class ActivationContext {
- _cache: object;
+let nextId = 1;
- _services: ServiceMap;
+/** This class is created once per `Container.resolve` method call and used to
+ * cache dependencies and to track created instances. The activation context
+ * tracks services with `context` activation type.
+ */
+export class ActivationContext {
+ _cache: MapOf;
- _stack: ActivationContextInfo[];
+ _services: ContainerServiceMap;
- _visited: object;
+ _visited: MapOf;
_name: string;
- _localized: boolean;
+ _service: Descriptor;
- container: Container;
+ _container: Container;
+
+ _parent: ActivationContext | undefined;
- constructor(container: Container, services: ServiceMap, name?: string, cache?: object, visited?) {
- argumentNotNull(container, "container");
- argumentNotNull(services, "services");
-
+ /** Creates a new activation context with the specified parameters.
+ * @param container the container which starts the activation process
+ * @param services the initial service registrations
+ * @param name the name of the service being activated, this parameter is
+ * used for the debug purpose.
+ * @param service the service to activate, this parameter is used for the
+ * debug purpose.
+ */
+ constructor(container: Container, services: ContainerServiceMap, name: string, service: Descriptor) {
this._name = name;
- this._visited = visited || {};
- this._stack = [];
- this._cache = cache || {};
+ this._service = service;
+ this._visited = {};
+ this._cache = {};
this._services = services;
- this.container = container;
+ this._container = container;
}
+ /** the name of the current resolving dependency */
getName() {
return this._name;
}
- resolve(name, def?): any {
+ /** Returns the container for which 'resolve' method was called */
+ getContainer() {
+ return this._container;
+ }
+
+ /** Resolves the specified dependency in the current context
+ * @param name The name of the dependency being resolved
+ */
+ resolve>(name: K): TypeOfService;
+ /** Resolves the specified dependency with the specified default value if
+ * the dependency is missing.
+ *
+ * @param name The name of the dependency being resolved
+ * @param def A default value to return in case of the specified dependency
+ * is missing.
+ */
+ resolve, T>(name: K, def: T): TypeOfService | T;
+ /** Resolves the specified dependency and returns undefined in case if the
+ * dependency is missing.
+ *
+ * @param name The name of the dependency being resolved
+ */
+ resolve>(name: K, def: undefined): TypeOfService | undefined;
+ resolve, T>(name: K, def?: T): TypeOfService | T | undefined {
const d = this._services[name];
- if (!d)
+ if (d !== undefined) {
+ return this.activate(d, name.toString());
+ } else {
if (arguments.length > 1)
return def;
else
throw new Error(`Service ${name} not found`);
-
- return this.activate(d, name);
+ }
}
/**
@@ -62,41 +98,36 @@ export class ActivationContext {
* @name{string} the name of the service
* @service{string} the service descriptor to register
*/
- register(name: string, service: Descriptor) {
+ register(name: K, service: Descriptor) {
argumentNotEmptyString(name, "name");
- this._services[name] = service;
+ this._services[name] = service as any;
}
- clone() {
- return new ActivationContext(
- this.container,
- this._services,
- this._name,
- this._cache,
- this._visited
- );
+ createLifetime(): ILifetime {
+ const id = nextId++;
+ const me = this;
+ return {
+ initialize() {
+ },
+ has() {
+ return id in me._cache;
+ },
+ get() {
+ return me._cache[id];
+ },
+ store(item: any) {
+ me._cache[id] = item;
+ }
+ };
}
- has(id: string) {
- return id in this._cache;
- }
-
- get(id: string) {
- return this._cache[id];
- }
-
- store(id: string, value) {
- return (this._cache[id] = value);
- }
-
- activate(d: Descriptor, name: string) {
+ activate(d: Descriptor, name: string) {
if (trace.isLogEnabled())
trace.log(`enter ${name} ${d}`);
- this.enter(name, d.toString());
- const v = d.activate(this);
- this.leave();
+ const ctx = this.enter(d, name);
+ const v = d.activate(ctx);
if (trace.isLogEnabled())
trace.log(`leave ${name}`);
@@ -110,23 +141,30 @@ export class ActivationContext {
return count;
}
- getStack() {
- return this._stack.slice().reverse();
+ getStack(): ActivationContextInfo[] {
+ const stack = [{
+ name: this._name,
+ service: this._service.toString()
+ }];
+
+ return this._parent ?
+ stack.concat(this._parent.getStack()) :
+ stack;
}
- private enter(name: string, service: string) {
- this._stack.push({
- name,
- service,
- scope: this._services
- });
- this._name = name;
- this._services = Object.create(this._services);
+ private enter(service: Descriptor, name: string): this {
+ const clone = Object.create(this);
+ clone._name = name;
+ clone._services = Object.create(this._services);
+ clone._parent = this;
+ clone._service = service;
+ return clone;
}
- private leave() {
- const ctx = this._stack.pop();
- this._services = ctx.scope;
- this._name = ctx.name;
+ /** Creates a clone for the current context, used to protect it from modifications */
+ clone(): this {
+ const clone = Object.create(this);
+ clone._services = Object.create(this._services);
+ return clone;
}
}
diff --git a/src/main/ts/di/ActivationError.ts b/src/main/ts/di/ActivationError.ts
--- a/src/main/ts/di/ActivationError.ts
+++ b/src/main/ts/di/ActivationError.ts
@@ -1,7 +1,10 @@
-import { ActivationContextInfo } from "./ActivationContext";
+export interface ActivationItem {
+ name: string;
+ service: string;
+}
export class ActivationError {
- activationStack: ActivationContextInfo[];
+ activationStack: ActivationItem[];
service: string;
@@ -9,7 +12,7 @@ export class ActivationError {
message: string;
- constructor(service: string, activationStack: ActivationContextInfo[], innerException) {
+ constructor(service: string, activationStack: ActivationItem[], innerException: any) {
this.message = "Failed to activate the service";
this.activationStack = activationStack;
this.service = service;
diff --git a/src/main/ts/di/AggregateDescriptor.ts b/src/main/ts/di/AggregateDescriptor.ts
--- a/src/main/ts/di/AggregateDescriptor.ts
+++ b/src/main/ts/di/AggregateDescriptor.ts
@@ -1,37 +1,40 @@
-import { Descriptor, isDescriptor } from "./interfaces";
+import { Descriptor } from "./interfaces";
import { ActivationContext } from "./ActivationContext";
import { isPrimitive } from "../safe";
+import { isDescriptor } from "./traits";
-export class AggregateDescriptor implements Descriptor {
- _value: object;
+export class AggregateDescriptor implements Descriptor {
+ _value: any;
- constructor(value: object) {
+ constructor(value: any) {
this._value = value;
}
- activate(context: ActivationContext) {
+ activate(context: ActivationContext): T {
return this._parse(this._value, context, "$value");
}
- // TODO: make async
- _parse(value, context: ActivationContext, path: string) {
+ _parse(value: any, context: ActivationContext, path: string): any {
if (isPrimitive(value))
- return value;
+ return value as any;
if (isDescriptor(value))
return context.activate(value, path);
if (value instanceof Array)
- return value.map((x, i) => this._parse(x, context, `${path}[${i}]`));
+ return value.map((x, i) => this._parse(x, context, `${path}[${i}]`)) as any;
- const t = {};
- for (const p of Object.keys(value))
+ const t: any = {};
+ for (const p in value)
t[p] = this._parse(value[p], context, `${path}.${p}`);
return t;
-
}
toString() {
return "@walk";
}
+
+ clone() {
+ return this;
+ }
}
diff --git a/src/main/ts/di/ConfigError.ts b/src/main/ts/di/ConfigError.ts
--- a/src/main/ts/di/ConfigError.ts
+++ b/src/main/ts/di/ConfigError.ts
@@ -1,11 +1,11 @@
export class ConfigError extends Error {
- inner: any;
+ inner?: {};
- path: string;
+ path?: string;
- configName: string;
+ configName?: string;
- constructor(message: string, inner?: any) {
+ constructor(message: string, inner?: {}) {
super(message);
this.inner = inner;
}
diff --git a/src/main/ts/di/Configuration.ts b/src/main/ts/di/Configuration.ts
--- a/src/main/ts/di/Configuration.ts
+++ b/src/main/ts/di/Configuration.ts
@@ -1,19 +1,12 @@
import {
- ServiceRegistration,
- TypeRegistration,
- FactoryRegistration,
- ServiceMap,
- isDescriptor,
- isDependencyRegistration,
- DependencyRegistration,
- ValueRegistration,
+ PartialServiceMap,
ActivationType,
- isValueRegistration,
- isTypeRegistration,
- isFactoryRegistration
+ ContainerKeys,
+ TypeOfService,
+ ILifetime
} from "./interfaces";
-import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get } from "../safe";
+import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get, primitive } from "../safe";
import { AggregateDescriptor } from "./AggregateDescriptor";
import { ValueDescriptor } from "./ValueDescriptor";
import { Container } from "./Container";
@@ -25,10 +18,115 @@ import { ConfigError } from "./ConfigErr
import { Cancellation } from "../Cancellation";
import { makeResolver } from "./ResolverHelper";
import { ICancellation } from "../interfaces";
+import { isDescriptor } from "./traits";
+import { LazyReferenceDescriptor } from "./LazyReferenceDescriptor";
+import { LifetimeManager } from "./LifetimeManager";
+
+export interface RegistrationScope {
+
+ /** сервисы, которые регистрируются в контексте активации и таким образом
+ * могут переопределять ранее зарегистрированные сервисы. за это свойство
+ * нужно платить, кроме того порядок активации будет влиять на результат
+ * разрешения зависимостей.
+ */
+ services?: RegistrationMap;
+}
+
+/**
+ * Базовый интерфейс конфигурации сервисов
+ */
+export interface ServiceRegistration extends RegistrationScope {
+
+ activation?: ActivationType;
+
+ params?: any;
+
+ /** Специальный идентификатор используется при активации singleton, если
+ * не указан для TypeRegistration вычисляется как oid($type)
+ */
+ typeId?: string;
+
+ inject?: object | object[];
+
+ cleanup?: ((instance: T) => void) | string;
+}
+
+export interface TypeRegistration any, S extends object> extends ServiceRegistration, S> {
+ $type: string | C;
+ params?: Registration, S>;
+}
+
+export interface StrictTypeRegistration any, S extends object> extends ServiceRegistration, S> {
+ $type: C;
+ params?: Registration, S>;
+}
+
+export interface FactoryRegistration any, S extends object> extends ServiceRegistration, S> {
+ $factory: string | F;
+}
+
+export interface ValueRegistration {
+ $value: T;
+ parse?: boolean;
+}
+
+export interface DependencyRegistration = ContainerKeys> extends RegistrationScope {
+ $dependency: K;
+ lazy?: boolean;
+ optional?: boolean;
+ default?: TypeOfService;
+}
+
+export interface LazyDependencyRegistration = ContainerKeys> extends DependencyRegistration {
+ lazy: true;
+}
+
+export type Registration = T extends primitive ? T :
+ (
+ T |
+ { [k in keyof T]: Registration } |
+ TypeRegistration T, S> |
+ FactoryRegistration<(...args: any[]) => T, S> |
+ ValueRegistration |
+ DependencyRegistration
+ );
+
+export type RegistrationMap = {
+ [k in keyof S]?: Registration;
+};
+
+const _activationTypes: { [k in ActivationType]: number; } = {
+ singleton: 1,
+ container: 2,
+ hierarchy: 3,
+ context: 4,
+ call: 5
+};
+
+export function isTypeRegistration(x: any): x is TypeRegistration any, any> {
+ return (!isPrimitive(x)) && ("$type" in x);
+}
+
+export function isFactoryRegistration(x: any): x is FactoryRegistration<() => any, any> {
+ return (!isPrimitive(x)) && ("$factory" in x);
+}
+
+export function isValueRegistration(x: any): x is ValueRegistration {
+ return (!isPrimitive(x)) && ("$value" in x);
+}
+
+export function isDependencyRegistration(x: any): x is DependencyRegistration {
+ return (!isPrimitive(x)) && ("$dependency" in x);
+}
+
+export function isActivationType(x: string): x is ActivationType {
+ return typeof x === "string" && x in _activationTypes;
+}
const trace = TraceSource.get("@implab/core/di/Configuration");
-
-async function mapAll(data: object | any[], map?: (v, k) => any): Promise {
+async function mapAll(data: any[], map?: (v: any, k: number) => any): Promise;
+async function mapAll(data: any, map?: (v: any, k: string) => any): Promise;
+async function mapAll(data: any, map?: (v: any, k: any) => any): Promise {
if (data instanceof Array) {
return Promise.all(map ? data.map(map) : data);
} else {
@@ -47,21 +145,19 @@ async function mapAll(data: object | any
export type ModuleResolver = (moduleName: string, ct?: ICancellation) => any;
-type _key = string | number;
-
-export class Configuration {
+export class Configuration {
_hasInnerDescriptors = false;
- _container: Container;
+ readonly _container: Container;
- _path: Array<_key>;
+ _path: Array;
- _configName: string;
+ _configName: string | undefined;
- _require: ModuleResolver;
+ _require: ModuleResolver | undefined;
- constructor(container: Container) {
+ constructor(container: Container) {
argumentNotNull(container, "container");
this._container = container;
this._path = [];
@@ -78,7 +174,7 @@ export class Configuration {
this._configName = moduleName;
- const r = await makeResolver(null, contextRequire);
+ const r = await makeResolver(undefined, contextRequire);
const config = await r(moduleName, ct);
@@ -89,13 +185,14 @@ export class Configuration {
);
}
- async applyConfiguration(data: object, contextRequire?: any, ct = Cancellation.none) {
+ async applyConfiguration(data: RegistrationMap, opts: { contextRequire?: any; baseModule?: string }, ct = Cancellation.none) {
argumentNotNull(data, "data");
+ const _opts = opts || {};
- await this._applyConfiguration(data, await makeResolver(void (0), contextRequire), ct);
+ await this._applyConfiguration(data, await makeResolver(_opts.baseModule, _opts.contextRequire), ct);
}
- async _applyConfiguration(data: object, resolver?: ModuleResolver, ct = Cancellation.none) {
+ async _applyConfiguration(data: RegistrationMap, resolver?: ModuleResolver, ct = Cancellation.none) {
trace.log("applyConfiguration");
this._configName = "$";
@@ -103,7 +200,7 @@ export class Configuration {
if (resolver)
this._require = resolver;
- let services: ServiceMap;
+ let services: PartialServiceMap;
try {
services = await this._visitRegistrations(data, "$");
@@ -114,9 +211,9 @@ export class Configuration {
this._container.register(services);
}
- _makeError(inner) {
+ _makeError(inner: any) {
const e = new ConfigError("Failed to load configuration", inner);
- e.configName = this._configName;
+ e.configName = this._configName || "";
e.path = this._makePath();
return e;
}
@@ -135,7 +232,15 @@ export class Configuration {
trace.log("resolveType moduleName={0}, localName={1}", moduleName, localName);
try {
const m = await this._loadModule(moduleName);
- return localName ? get(localName, m) : m;
+ if (localName) {
+ return get(localName, m);
+ } else {
+ if (m instanceof Function)
+ return m;
+ if ("default" in m)
+ return m.default;
+ return m;
+ }
} catch (e) {
trace.error("Failed to resolve type moduleName={0}, localName={1}", moduleName, localName);
throw e;
@@ -144,32 +249,31 @@ export class Configuration {
_loadModule(moduleName: string) {
trace.debug("loadModule {0}", moduleName);
+ if (!this._require)
+ throw new Error("Module loader isn't specified");
return this._require(moduleName);
}
- async _visitRegistrations(data, name: _key) {
+ async _visitRegistrations(data: RegistrationMap, name: string) {
this._enter(name);
if (data.constructor &&
data.constructor.prototype !== Object.prototype)
throw new Error("Configuration must be a simple object");
- const o: ServiceMap = {};
- const keys = Object.keys(data);
-
const services = await mapAll(data, async (v, k) => {
- const d = await this._visit(v, k);
+ const d = await this._visit(v, k.toString());
return isDescriptor(d) ? d : new AggregateDescriptor(d);
- }) as ServiceMap;
+ }) as PartialServiceMap;
this._leave();
return services;
}
- _enter(name: _key) {
- this._path.push(name);
+ _enter(name: string) {
+ this._path.push(name.toString());
trace.debug(">{0}", name);
}
@@ -178,11 +282,13 @@ export class Configuration {
trace.debug("<{0}", name);
}
- async _visit(data, name: string) {
- if (isPrimitive(data) || isDescriptor(data))
- return data;
+ _visit(data: any, name: string): Promise {
+ if (isPrimitive(data))
+ return Promise.resolve(new ValueDescriptor(data));
+ if (isDescriptor(data))
+ return Promise.resolve(data);
- if (isDependencyRegistration(data)) {
+ if (isDependencyRegistration(data)) {
return this._visitDependencyRegistration(data, name);
} else if (isValueRegistration(data)) {
return this._visitValueRegistration(data, name);
@@ -197,7 +303,7 @@ export class Configuration {
return this._visitObject(data, name);
}
- async _visitObject(data: object, name: _key) {
+ async _visitObject(data: any, name: string) {
if (data.constructor &&
data.constructor.prototype !== Object.prototype)
return new ValueDescriptor(data);
@@ -220,7 +326,7 @@ export class Configuration {
return v;
}
- async _visitArray(data: any[], name: _key) {
+ async _visitArray(data: any[], name: string) {
if (data.constructor &&
data.constructor.prototype !== Array.prototype)
return new ValueDescriptor(data);
@@ -233,9 +339,8 @@ export class Configuration {
return v;
}
- _makeServiceParams(data: ServiceRegistration) {
+ _makeServiceParams(data: ServiceRegistration) {
const opts: any = {
- owner: this._container
};
if (data.services)
opts.services = this._visitRegistrations(data.services, "services");
@@ -257,30 +362,7 @@ export class Configuration {
this._visit(data.params, "params");
if (data.activation) {
- if (typeof (data.activation) === "string") {
- switch (data.activation.toLowerCase()) {
- case "singleton":
- opts.activation = ActivationType.Singleton;
- break;
- case "container":
- opts.activation = ActivationType.Container;
- break;
- case "hierarchy":
- opts.activation = ActivationType.Hierarchy;
- break;
- case "context":
- opts.activation = ActivationType.Context;
- break;
- case "call":
- opts.activation = ActivationType.Call;
- break;
- default:
- throw new Error("Unknown activation type: " +
- data.activation);
- }
- } else {
- opts.activation = Number(data.activation);
- }
+ opts.activation = this._getLifetimeManager(data.activation, data.typeId);
}
if (data.cleanup)
@@ -289,28 +371,28 @@ export class Configuration {
return opts;
}
- async _visitValueRegistration(data: ValueRegistration, name: _key) {
+ async _visitValueRegistration(data: ValueRegistration, name: string) {
this._enter(name);
const d = data.parse ? new AggregateDescriptor(data.$value) : new ValueDescriptor(data.$value);
this._leave();
return d;
}
- async _visitDependencyRegistration(data: DependencyRegistration, name: _key) {
+ async _visitDependencyRegistration(data: DependencyRegistration, name: string) {
argumentNotEmptyString(data && data.$dependency, "data.$dependency");
this._enter(name);
- const d = new ReferenceDescriptor({
+ const options = {
name: data.$dependency,
- lazy: data.lazy,
optional: data.optional,
default: data.default,
services: data.services && await this._visitRegistrations(data.services, "services")
- });
+ };
+ const d = data.lazy ? new LazyReferenceDescriptor(options) : new ReferenceDescriptor(options);
this._leave();
return d;
}
- async _visitTypeRegistration(data: TypeRegistration, name: _key) {
+ async _visitTypeRegistration(data: TypeRegistration any, S>, name: string) {
argumentNotNull(data.$type, "data.$type");
this._enter(name);
@@ -319,10 +401,14 @@ export class Configuration {
opts.type = data.$type;
} else {
const [moduleName, typeName] = data.$type.split(":", 2);
- opts.type = this._resolveType(moduleName, typeName);
+ opts.type = this._resolveType(moduleName, typeName).then(t => {
+ if (!(t instanceof Function))
+ throw Error("$type (" + data.$type + ") is not a constructable");
+ return t;
+ });
}
- const d = new TypeServiceDescriptor(
+ const d = new TypeServiceDescriptor(
await mapAll(opts)
);
@@ -331,18 +417,35 @@ export class Configuration {
return d;
}
- async _visitFactoryRegistration(data: FactoryRegistration, name: _key) {
+ async _visitFactoryRegistration(data: FactoryRegistration<() => any, S>, name: string) {
argumentOfType(data.$factory, Function, "data.$factory");
this._enter(name);
const opts = this._makeServiceParams(data);
opts.factory = data.$factory;
- const d = new FactoryServiceDescriptor(
+ const d = new FactoryServiceDescriptor(
await mapAll(opts)
);
this._leave();
return d;
}
+
+ _getLifetimeManager(activation: ActivationType, typeId: string | undefined): ILifetime {
+ switch (activation) {
+ case "container":
+ return LifetimeManager.containerLifetime(this._container);
+ case "hierarchy":
+ return LifetimeManager.hierarchyLifetime();
+ case "context":
+ return LifetimeManager.contextLifetime();
+ case "singleton":
+ if (typeId === undefined)
+ throw Error("The singleton activation requires a typeId");
+ return LifetimeManager.singletonLifetime(typeId);
+ default:
+ return LifetimeManager.empty();
+ }
+ }
}
diff --git a/src/main/ts/di/Container.ts b/src/main/ts/di/Container.ts
--- a/src/main/ts/di/Container.ts
+++ b/src/main/ts/di/Container.ts
@@ -1,31 +1,40 @@
import { ActivationContext } from "./ActivationContext";
import { ValueDescriptor } from "./ValueDescriptor";
import { ActivationError } from "./ActivationError";
-import { isDescriptor, ServiceMap } from "./interfaces";
+import { ServiceMap, Descriptor, PartialServiceMap, ServiceLocator, ContainerServiceMap, ContainerKeys, TypeOfService } from "./interfaces";
import { TraceSource } from "../log/TraceSource";
-import { Configuration } from "./Configuration";
+import { Configuration, RegistrationMap } from "./Configuration";
import { Cancellation } from "../Cancellation";
+import { MapOf, IDestroyable } from "../interfaces";
+import { isDescriptor } from "./traits";
+import { LifetimeManager } from "./LifetimeManager";
+import { each } from "../safe";
+import { FluentRegistrations } from "./fluent/interfaces";
+import { FluentConfiguration } from "./fluent/FluentConfiguration";
const trace = TraceSource.get("@implab/core/di/ActivationContext");
-export class Container {
- _services: ServiceMap;
+export class Container implements ServiceLocator, IDestroyable {
+ readonly _services: ContainerServiceMap;
- _cache: object;
+ readonly _lifetimeManager: LifetimeManager;
+
+ readonly _cleanup: (() => void)[];
- _cleanup: (() => void)[];
+ readonly _root: Container;
- _root: Container;
+ readonly _parent?: Container;
- _parent: Container;
+ _disposed: boolean;
- constructor(parent?: Container) {
+ constructor(parent?: Container) {
this._parent = parent;
this._services = parent ? Object.create(parent._services) : {};
- this._cache = {};
this._cleanup = [];
this._root = parent ? parent.getRootContainer() : this;
- this._services.container = new ValueDescriptor(this);
+ this._services.container = new ValueDescriptor(this) as any;
+ this._disposed = false;
+ this._lifetimeManager = new LifetimeManager();
}
getRootContainer() {
@@ -36,93 +45,105 @@ export class Container {
return this._parent;
}
- resolve(name: string, def?) {
+ getLifetimeManager() {
+ return this._lifetimeManager;
+ }
+
+ resolve>(name: K, def?: TypeOfService): TypeOfService {
trace.debug("resolve {0}", name);
const d = this._services[name];
if (d === undefined) {
- if (arguments.length > 1)
+ if (def !== undefined)
return def;
else
throw new Error("Service '" + name + "' isn't found");
- }
+ } else {
- const context = new ActivationContext(this, this._services);
- try {
- return context.activate(d, name);
- } catch (error) {
- throw new ActivationError(name, context.getStack(), error);
+ const context = new ActivationContext(this, this._services, String(name), d);
+ try {
+ return d.activate(context);
+ } catch (error) {
+ throw new ActivationError(name.toString(), context.getStack(), error);
+ }
}
}
/**
* @deprecated use resolve() method
*/
- getService() {
- return this.resolve.apply(this, arguments);
+ getService>(name: K, def?: TypeOfService) {
+ return this.resolve(name, def);
}
- register(nameOrCollection, service?) {
+ register(name: K, service: Descriptor): this;
+ register(services: PartialServiceMap): this;
+ register(nameOrCollection: K | ServiceMap, service?: Descriptor) {
if (arguments.length === 1) {
- const data = nameOrCollection;
- for (const name in data)
- this.register(name, data[name]);
+ const data = nameOrCollection as ServiceMap;
+
+ each(data, (v, k) => this.register(k, v));
} else {
if (!isDescriptor(service))
throw new Error("The service parameter must be a descriptor");
- this._services[nameOrCollection] = service;
+ this._services[nameOrCollection as K] = service as any;
}
return this;
}
- onDispose(callback) {
+ onDispose(callback: () => void) {
if (!(callback instanceof Function))
throw new Error("The callback must be a function");
this._cleanup.push(callback);
}
+ destroy() {
+ return this.dispose();
+ }
dispose() {
- if (this._cleanup) {
- for (const f of this._cleanup)
- f();
- this._cleanup = null;
- }
+ if (this._disposed)
+ return;
+ this._disposed = true;
+ for (const f of this._cleanup)
+ f();
}
/**
* @param{String|Object} config
- * The configuration of the contaier. Can be either a string or an object,
+ * The configuration of the container. Can be either a string or an object,
* if the configuration is an object it's treated as a collection of
- * services which will be registed in the contaier.
+ * services which will be registered in the container.
*
* @param{Function} opts.contextRequire
* The function which will be used to load a configuration or types for services.
*
*/
- async configure(config: string | object, opts?: any, ct = Cancellation.none) {
- const c = new Configuration(this);
+ async configure(config: string | RegistrationMap, opts?: { contextRequire: any; baseModule?: string }, ct = Cancellation.none) {
+ const _opts = Object.create(opts || null);
if (typeof (config) === "string") {
- return c.loadConfiguration(config, opts && opts.contextRequire, ct);
+ _opts.baseModule = config;
+
+ const module = await import(config);
+ if (module && module.default && typeof (module.default.apply) === "function")
+ return module.default.apply(this);
+ else
+ return this._applyLegacyConfig(module, _opts, ct);
} else {
- return c.applyConfiguration(config, opts && opts.contextRequire, ct);
+ return this._applyLegacyConfig(config, _opts, ct);
}
}
- createChildContainer() {
- return new Container(this);
- }
-
- has(id) {
- return id in this._cache;
+ async _applyLegacyConfig(config: RegistrationMap, opts: { contextRequire: any; baseModule?: string }, ct = Cancellation.none) {
+ return new Configuration(this).applyConfiguration(config, opts);
}
- get(id) {
- return this._cache[id];
+ async fluent(config: FluentRegistrations, ct = Cancellation.none): Promise {
+ await new FluentConfiguration().register(config).apply(this, ct);
+ return this;
}
- store(id, value) {
- return (this._cache[id] = value);
+ createChildContainer(): Container {
+ return new Container(this as any);
}
-
}
diff --git a/src/main/ts/di/FactoryServiceDescriptor.ts b/src/main/ts/di/FactoryServiceDescriptor.ts
--- a/src/main/ts/di/FactoryServiceDescriptor.ts
+++ b/src/main/ts/di/FactoryServiceDescriptor.ts
@@ -1,23 +1,18 @@
import { ServiceDescriptor, ServiceDescriptorParams } from "./ServiceDescriptor";
-import { Factory } from "../interfaces";
import { argumentNotNull, oid } from "../safe";
-import { ActivationType } from "./interfaces";
-export interface FactoryServiceDescriptorParams extends ServiceDescriptorParams {
- factory: Factory;
+export interface FactoryServiceDescriptorParams extends ServiceDescriptorParams {
+ factory: (...args: P) => T;
}
-export class FactoryServiceDescriptor extends ServiceDescriptor {
- constructor(opts: FactoryServiceDescriptorParams) {
+export class FactoryServiceDescriptor extends ServiceDescriptor {
+ constructor(opts: FactoryServiceDescriptorParams) {
super(opts);
argumentNotNull(opts && opts.factory, "opts.factory");
// bind to null
- this._factory = (...args) => opts.factory.apply(null, args);
+ this._factory = (...args) => opts.factory.apply(null, args as any);
- if (opts.activation === ActivationType.Singleton) {
- this._cacheId = oid(opts.factory);
- }
}
}
diff --git a/src/main/ts/di/LazyReferenceDescriptor.ts b/src/main/ts/di/LazyReferenceDescriptor.ts
new file mode 100644
--- /dev/null
+++ b/src/main/ts/di/LazyReferenceDescriptor.ts
@@ -0,0 +1,83 @@
+import { argumentNotEmptyString, each } from "../safe";
+import { ActivationContext } from "./ActivationContext";
+import { Descriptor, PartialServiceMap, TypeOfService, ContainerKeys } from "./interfaces";
+import { ActivationError } from "./ActivationError";
+
+export interface ReferenceDescriptorParams> {
+ name: K;
+ optional?: boolean;
+ default?: TypeOfService;
+ services?: PartialServiceMap;
+}
+
+export class LazyReferenceDescriptor = ContainerKeys>
+ implements Descriptor) => TypeOfService)> {
+
+ _name: K;
+
+ _optional = false;
+
+ _default: TypeOfService | undefined;
+
+ _services: PartialServiceMap;
+
+ constructor(opts: ReferenceDescriptorParams) {
+ argumentNotEmptyString(opts && opts.name, "opts.name");
+ this._name = opts.name;
+ this._optional = !!opts.optional;
+ this._default = opts.default;
+
+ this._services = (opts.services || {}) as PartialServiceMap;
+ }
+
+ activate(context: ActivationContext) {
+ // добавляем сервисы
+ if (this._services) {
+ each(this._services, (v, k) => context.register(k, v));
+ }
+
+ const saved = context.clone();
+
+ return (cfg?: PartialServiceMap): any => {
+ // защищаем контекст на случай исключения в процессе
+ // активации
+ const ct = cfg ? saved.clone() : saved;
+ try {
+ if (cfg) {
+ each(cfg, (v, k) => ct.register(k, v));
+ }
+
+ return this._optional ? ct.resolve(this._name, this._default) : ct
+ .resolve(this._name);
+ } catch (error) {
+ throw new ActivationError(this._name.toString(), ct.getStack(), error);
+ }
+ };
+ }
+
+ toString() {
+ const opts = [];
+ if (this._optional)
+ opts.push("optional");
+
+ opts.push("lazy");
+
+ const parts = [
+ "@ref "
+ ];
+ if (opts.length) {
+ parts.push("{");
+ parts.push(opts.join());
+ parts.push("} ");
+ }
+
+ parts.push(this._name.toString());
+
+ if (this._default !== undefined && this._default !== null) {
+ parts.push(" = ");
+ parts.push(String(this._default));
+ }
+
+ return parts.join("");
+ }
+}
diff --git a/src/main/ts/di/LifetimeManager.ts b/src/main/ts/di/LifetimeManager.ts
new file mode 100644
--- /dev/null
+++ b/src/main/ts/di/LifetimeManager.ts
@@ -0,0 +1,198 @@
+import { IDestroyable, MapOf } from "../interfaces";
+import { argumentNotNull, isDestroyable, primitive, isNull, argumentNotEmptyString } from "../safe";
+import { ILifetime } from "./interfaces";
+import { ActivationContext } from "./ActivationContext";
+import { Container } from "./Container";
+
+function safeCall(item: () => void) {
+ try {
+ item();
+ } catch {
+ // silence!
+ }
+}
+
+const emptyLifetime: ILifetime = Object.freeze({
+ has() {
+ return false;
+ },
+
+ initialize() {
+
+ },
+
+ get() {
+ throw new Error("The specified item isn't registered with this lifetime manager");
+ },
+
+ store() {
+ // does nothing
+ }
+
+});
+
+const unknownLifetime: ILifetime = Object.freeze({
+ has() {
+ return false;
+ },
+ initialize() {
+ throw new Error("Can't call initialize on the unknown lifetime object");
+ },
+ get() {
+ throw new Error("The lifetime object isn't initialized");
+ },
+ store() {
+ throw new Error("Can't store a value in the unknown lifetime object");
+ }
+});
+
+let nextId = 0;
+
+const singletons: { [k in keyof any]: any; } = {};
+
+export class LifetimeManager implements IDestroyable {
+ private _cleanup: (() => void)[] = [];
+ private _cache: MapOf = {};
+ private _destroyed = false;
+
+ private _pending: MapOf = {};
+
+ create(): ILifetime {
+ const self = this;
+ const id = ++nextId;
+ return {
+ has() {
+ return (id in self._cache);
+ },
+
+ get() {
+ const t = self._cache[id];
+ if (t === undefined)
+ throw new Error(`The item with with the key ${id} isn't found`);
+ return t;
+ },
+
+ initialize() {
+ if (self._pending[id])
+ throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`);
+ self._pending[id] = true;
+ },
+
+ store(item: any, cleanup?: (item: any) => void) {
+ argumentNotNull(id, "id");
+ argumentNotNull(item, "item");
+
+ if (this.has())
+ throw new Error(`The item with with the key ${id} already registered with this lifetime manager`);
+ delete self._pending[id];
+
+ self._cache[id] = item;
+
+ if (self._destroyed)
+ throw new Error("Lifetime manager is destroyed");
+ if (cleanup) {
+ self._cleanup.push(() => cleanup(item));
+ } else if (isDestroyable(item)) {
+ self._cleanup.push(() => item.destroy());
+ }
+ }
+ };
+ }
+
+ destroy() {
+ if (!this._destroyed) {
+ this._destroyed = true;
+ this._cleanup.forEach(safeCall);
+ this._cleanup.length = 0;
+ }
+ }
+
+ static empty(): ILifetime {
+ return emptyLifetime;
+ }
+
+ static hierarchyLifetime(): ILifetime {
+ let _lifetime = unknownLifetime;
+ return {
+ initialize(context: ActivationContext) {
+ if (_lifetime !== unknownLifetime)
+ throw new Error("Cyclic reference activation detected");
+
+ _lifetime = context.getContainer().getLifetimeManager().create();
+ },
+ get() {
+ return _lifetime.get();
+ },
+ has() {
+ return _lifetime.has();
+ },
+ store(item: any, cleanup?: (item: any) => void) {
+ return _lifetime.store(item, cleanup);
+ }
+ };
+ }
+
+ static contextLifetime(): ILifetime {
+ let _lifetime = unknownLifetime;
+ return {
+ initialize(context: ActivationContext) {
+ if (_lifetime !== unknownLifetime)
+ throw new Error("Cyclic reference detected");
+ _lifetime = context.createLifetime();
+ },
+ get() {
+ return _lifetime.get();
+ },
+ has() {
+ return _lifetime.has();
+ },
+ store(item: any) {
+ _lifetime.store(item);
+ }
+ };
+ }
+
+ static singletonLifetime(typeId: string): ILifetime {
+ argumentNotEmptyString(typeId, "typeId");
+ let pending = false;
+ return {
+ has() {
+ return typeId in singletons;
+ },
+ get() {
+ if (!this.has())
+ throw new Error(`The instance ${typeId} doesn't exists`);
+ return singletons[typeId];
+ },
+ initialize() {
+ if (pending)
+ throw new Error("Cyclic reference detected");
+ pending = true;
+ },
+ store(item: any) {
+ singletons[typeId] = item;
+ pending = false;
+ }
+ };
+ }
+
+ static containerLifetime(container: Container) {
+ let _lifetime = unknownLifetime;
+ return {
+ initialize(context: ActivationContext) {
+ if (_lifetime !== unknownLifetime)
+ throw new Error("Cyclic reference detected");
+ _lifetime = container.getLifetimeManager().create();
+ },
+ get() {
+ return _lifetime.get();
+ },
+ has() {
+ return _lifetime.has();
+ },
+ store(item: any) {
+ _lifetime.store(item);
+ }
+ };
+ }
+}
diff --git a/src/main/ts/di/ReferenceDescriptor.ts b/src/main/ts/di/ReferenceDescriptor.ts
--- a/src/main/ts/di/ReferenceDescriptor.ts
+++ b/src/main/ts/di/ReferenceDescriptor.ts
@@ -1,83 +1,71 @@
-import { isNull, argumentNotEmptyString, each } from "../safe";
+import { argumentNotEmptyString, each } from "../safe";
import { ActivationContext } from "./ActivationContext";
-import { ServiceMap, Descriptor } from "./interfaces";
-import { ActivationError } from "./ActivationError";
+import { Descriptor, PartialServiceMap, TypeOfService, ContainerKeys } from "./interfaces";
+
+export interface ReferenceDescriptorParams> {
+ /**
+ * The name of the descriptor
+ */
+ name: K;
-export interface ReferenceDescriptorParams {
- name: string;
- lazy?: boolean;
+ /**
+ * The flag that indicates that the referenced service isn't required to exist.
+ * If the reference is optional and the referenced service doesn't exist,
+ * the undefined or a default value will be returned.
+ */
optional?: boolean;
- default?;
- services?: ServiceMap;
+
+ /**
+ * a default value for the reference when the referenced service doesn't exist.
+ */
+ default?: TypeOfService;
+
+ /**
+ * The service overrides
+ */
+ services?: PartialServiceMap;
}
-export class ReferenceDescriptor implements Descriptor {
- _name: string;
+export class ReferenceDescriptor = ContainerKeys>
+ implements Descriptor> {
- _lazy = false;
+ _name: K;
_optional = false;
- _default: any;
+ _default: TypeOfService | undefined;
- _services: ServiceMap;
+ _services: PartialServiceMap;
- constructor(opts: ReferenceDescriptorParams) {
+ constructor(opts: ReferenceDescriptorParams) {
argumentNotEmptyString(opts && opts.name, "opts.name");
this._name = opts.name;
- this._lazy = !!opts.lazy;
this._optional = !!opts.optional;
this._default = opts.default;
- this._services = opts.services;
+
+ this._services = (opts.services || {}) as PartialServiceMap;
}
- activate(context: ActivationContext, name: string) {
+ /** This method activates the referenced service if one exists
+ * @param context activation context which is used during current activation
+ */
+ activate(context: ActivationContext): any {
// добавляем сервисы
if (this._services) {
- for (const p of Object.keys(this._services))
- context.register(p, this._services[p]);
+ each(this._services, (v, k) => context.register(k, v));
}
- if (this._lazy) {
- const saved = context.clone();
-
- return (cfg: ServiceMap) => {
- // защищаем контекст на случай исключения в процессе
- // активации
- const ct = saved.clone();
- try {
- if (cfg) {
- for (const k in cfg)
- ct.register(k, cfg[k]);
- }
+ const res = this._optional ?
+ context.resolve(this._name, this._default) :
+ context.resolve(this._name);
- return this._optional ? ct.resolve(this._name, this._default) : ct
- .resolve(this._name);
- } catch (error) {
- throw new ActivationError(this._name, ct.getStack(), error);
- }
- };
- } else {
- // добавляем сервисы
- if (this._services) {
- for (const p of Object.keys(this._services))
- context.register(p, this._services[p]);
- }
-
- const v = this._optional ?
- context.resolve(this._name, this._default) :
- context.resolve(this._name);
-
- return v;
- }
+ return res;
}
toString() {
const opts = [];
if (this._optional)
opts.push("optional");
- if (this._lazy)
- opts.push("lazy");
const parts = [
"@ref "
@@ -88,11 +76,11 @@ export class ReferenceDescriptor impleme
parts.push("} ");
}
- parts.push(this._name);
+ parts.push(this._name.toString());
- if (!isNull(this._default)) {
+ if (this._default !== undefined && this._default !== null) {
parts.push(" = ");
- parts.push(this._default);
+ parts.push(String(this._default));
}
return parts.join("");
diff --git a/src/main/ts/di/ServiceDescriptor.ts b/src/main/ts/di/ServiceDescriptor.ts
--- a/src/main/ts/di/ServiceDescriptor.ts
+++ b/src/main/ts/di/ServiceDescriptor.ts
@@ -1,16 +1,17 @@
import { ActivationContext } from "./ActivationContext";
-import { Descriptor, ActivationType, ServiceMap, isDescriptor } from "./interfaces";
-import { Container } from "./Container";
-import { argumentNotNull, isPrimitive } from "../safe";
+import { Descriptor, ServiceMap, PartialServiceMap, ILifetime } from "./interfaces";
+import { isPrimitive, keys, isNull } from "../safe";
import { TraceSource } from "../log/TraceSource";
-
-let cacheId = 0;
+import { isDescriptor } from "./traits";
+import { LifetimeManager } from "./LifetimeManager";
+import { MatchingMemberKeys } from "../interfaces";
const trace = TraceSource.get("@implab/core/di/ActivationContext");
-function injectMethod(target, method, context, args) {
+function injectMethod(target: T, method: M, context: ActivationContext, args: A) {
+
const m = target[method];
- if (!m)
+ if (!m || typeof m !== "function")
throw new Error("Method '" + method + "' not found");
if (args instanceof Array)
@@ -19,22 +20,22 @@ function injectMethod(target, method, co
return m.call(target, _parse(args, context, "." + method));
}
-function makeClenupCallback(target, method: ((instance) => void) | string) {
- if (typeof (method) === "string") {
- return () => {
- target[method]();
+function makeCleanupCallback(method: Cleaner) {
+ if (typeof (method) === "function") {
+ return (target: T) => {
+ method(target);
};
} else {
- return () => {
- method(target);
+ return (target: T) => {
+ const m = target[method] as any;
+ m.apply(target);
};
}
}
-// TODO: make async
-function _parse(value, context: ActivationContext, path: string) {
+function _parse(value: any, context: ActivationContext, path: string): any {
if (isPrimitive(value))
- return value;
+ return value as any;
trace.debug("parse {0}", path);
@@ -42,178 +43,92 @@ function _parse(value, context: Activati
return context.activate(value, path);
if (value instanceof Array)
- return value.map((x, i) => _parse(x, context, `${path}[${i}]`));
+ return value.map((x, i) => _parse(x, context, `${path}[${i}]`)) as any;
- const t = {};
- for (const p of Object.keys(value))
- t[p] = _parse(value[p], context, `${path}.${p}`);
+ const t: any = {};
+
+ keys(value).forEach(p => t[p] = _parse(value[p], context, `${path}.${p}`));
return t;
}
-export interface ServiceDescriptorParams {
- activation?: ActivationType;
+export type Cleaner = ((x: T) => void) | MatchingMemberKeys<() => void, T>;
- owner: Container;
+export type InjectionSpec = {
+ [m in keyof T]?: any;
+};
- params?;
+export interface ServiceDescriptorParams {
+ lifetime?: ILifetime;
- inject?: object[];
+ params?: P;
- services?: ServiceMap;
+ inject?: InjectionSpec[];
- cleanup?: ((x) => void) | string;
+ services?: PartialServiceMap;
+
+ cleanup?: Cleaner;
}
-export class ServiceDescriptor implements Descriptor {
- _instance;
-
- _hasInstance = false;
-
- _activationType = ActivationType.Call;
+export class ServiceDescriptor implements Descriptor {
+ _services: ServiceMap;
- _services: ServiceMap;
-
- _params;
+ _params: P | undefined;
- _inject: object[];
+ _inject: InjectionSpec[];
- _cleanup: ((x) => void) | string;
+ _cleanup: ((item: T) => void) | undefined;
- _cacheId: any;
+ _lifetime = LifetimeManager.empty();
- _owner: Container;
+ _objectLifetime: ILifetime | undefined;
- constructor(opts: ServiceDescriptorParams) {
- argumentNotNull(opts, "opts");
- argumentNotNull(opts.owner, "owner");
+ constructor(opts: ServiceDescriptorParams) {
- this._owner = opts.owner;
+ if (opts.lifetime)
+ this._lifetime = opts.lifetime;
- if ("activation" in opts)
- this._activationType = opts.activation;
-
- if ("params" in opts)
+ if (!isNull(opts.params))
this._params = opts.params;
- if (opts.inject)
- this._inject = opts.inject;
+ this._inject = opts.inject || [];
- if (opts.services)
- this._services = opts.services;
+ this._services = (opts.services || {}) as ServiceMap;
if (opts.cleanup) {
if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function))
throw new Error(
"The cleanup parameter must be either a function or a function name");
- this._cleanup = opts.cleanup;
+ this._cleanup = makeCleanupCallback(opts.cleanup);
}
}
- activate(context: ActivationContext) {
- // if we have a local service records, register them first
- let instance;
-
- // ensure we have a cache id
- if (!this._cacheId)
- this._cacheId = ++cacheId;
-
- switch (this._activationType) {
- case ActivationType.Singleton: // SINGLETON
- // if the value is cached return it
- if (this._hasInstance)
- return this._instance;
-
- // singletons are bound to the root container
- const container = context.container.getRootContainer();
-
- if (container.has(this._cacheId)) {
- instance = container.get(this._cacheId);
- } else {
- instance = this._create(context);
- container.store(this._cacheId, instance);
- if (this._cleanup)
- container.onDispose(
- makeClenupCallback(instance, this._cleanup));
- }
-
- this._hasInstance = true;
- return (this._instance = instance);
-
- case ActivationType.Container: // CONTAINER
- // return a cached value
-
- if (this._hasInstance)
- return this._instance;
+ activate(context: ActivationContext) {
+ const lifetime = this._lifetime;
- // create an instance
- instance = this._create(context);
-
- // the instance is bound to the container
- if (this._cleanup)
- this._owner.onDispose(
- makeClenupCallback(instance, this._cleanup));
-
- // cache and return the instance
- this._hasInstance = true;
- return (this._instance = instance);
- case ActivationType.Context: // CONTEXT
- // return a cached value if one exists
-
- if (context.has(this._cacheId))
- return context.get(this._cacheId);
- // context context activated instances are controlled by callers
- return context.store(this._cacheId, this._create(context));
- case ActivationType.Call: // CALL
- // per-call created instances are controlled by callers
- return this._create(context);
- case ActivationType.Hierarchy: // HIERARCHY
- // hierarchy activated instances are behave much like container activated
- // except they are created and bound to the child container
-
- // return a cached value
- if (context.container.has(this._cacheId))
- return context.container.get(this._cacheId);
-
- instance = this._create(context);
-
- if (this._cleanup)
- context.container.onDispose(makeClenupCallback(
- instance,
- this._cleanup));
-
- return context.container.store(this._cacheId, instance);
- default:
- throw new Error("Invalid activation type: " + this._activationType);
+ if (lifetime.has()) {
+ return lifetime.get();
+ } else {
+ lifetime.initialize(context);
+ const instance = this._create(context);
+ lifetime.store(instance, this._cleanup);
+ return instance;
}
}
- isInstanceCreated() {
- return this._hasInstance;
- }
-
- getInstance() {
- return this._instance;
- }
-
- _factory(...params: any[]): any {
+ _factory(...params: any[]): T {
throw Error("Not implemented");
}
- _create(context: ActivationContext) {
+ _create(context: ActivationContext) {
trace.debug(`constructing ${context._name}`);
- if (this._activationType !== ActivationType.Call &&
- context.visit(this._cacheId) > 0)
- throw new Error("Recursion detected");
-
if (this._services) {
- for (const p in this._services)
- context.register(p, this._services[p]);
+ keys(this._services).forEach(p => context.register(p, this._services[p]));
}
- let instance;
+ let instance: T;
if (this._params === undefined) {
instance = this._factory();
@@ -231,4 +146,9 @@ export class ServiceDescriptor implement
}
return instance;
}
+
+ clone() {
+ return Object.create(this);
+ }
+
}
diff --git a/src/main/ts/di/TypeServiceDescriptor.ts b/src/main/ts/di/TypeServiceDescriptor.ts
--- a/src/main/ts/di/TypeServiceDescriptor.ts
+++ b/src/main/ts/di/TypeServiceDescriptor.ts
@@ -1,15 +1,14 @@
import { ServiceDescriptor, ServiceDescriptorParams } from "./ServiceDescriptor";
-import { Constructor, Factory } from "../interfaces";
import { argumentNotNull, isPrimitive } from "../safe";
-export interface TypeServiceDescriptorParams extends ServiceDescriptorParams {
- type: Constructor;
+export interface TypeServiceDescriptorParams extends ServiceDescriptorParams {
+ type: new (...args: any[]) => T;
}
-export class TypeServiceDescriptor extends ServiceDescriptor {
- _type: Constructor;
+export class TypeServiceDescriptor extends ServiceDescriptor {
+ _type: new (...args: any[]) => T;
- constructor(opts: TypeServiceDescriptorParams) {
+ constructor(opts: TypeServiceDescriptorParams) {
super(opts);
argumentNotNull(opts && opts.type, "opts.type");
@@ -18,9 +17,10 @@ export class TypeServiceDescriptor exten
if (this._params) {
if (this._params.length) {
this._factory = (...args) => {
- const t = Object.create(ctor.prototype);
+ /*const t = Object.create(ctor.prototype);
const inst = ctor.apply(t, args);
- return isPrimitive(inst) ? t : inst;
+ return isPrimitive(inst) ? t : inst;*/
+ return new ctor(...args);
};
} else {
this._factory = arg => {
diff --git a/src/main/ts/di/ValueDescriptor.ts b/src/main/ts/di/ValueDescriptor.ts
--- a/src/main/ts/di/ValueDescriptor.ts
+++ b/src/main/ts/di/ValueDescriptor.ts
@@ -1,9 +1,9 @@
import { Descriptor } from "./interfaces";
-export class ValueDescriptor implements Descriptor {
- _value;
+export class ValueDescriptor implements Descriptor {
+ _value: T;
- constructor(value) {
+ constructor(value: T) {
this._value = value;
}
diff --git a/src/main/ts/di/fluent/DescriptorBuilder.ts b/src/main/ts/di/fluent/DescriptorBuilder.ts
new file mode 100644
--- /dev/null
+++ b/src/main/ts/di/fluent/DescriptorBuilder.ts
@@ -0,0 +1,147 @@
+import { Resolver, RegistrationBuilder } from "./interfaces";
+import { Container } from "../Container";
+import { Descriptor, ILifetime, ActivationType, PartialServiceMap } from "../interfaces";
+import { DescriptorImpl } from "./DescriptorImpl";
+import { LifetimeManager } from "../LifetimeManager";
+import { isString, each, isPrimitive, isPromise, oid } from "../../safe";
+
+export class DescriptorBuilder {
+ private readonly _container: Container;
+ private readonly _cb: (d: Descriptor) => void;
+
+ private readonly _eb: (err: any) => void;
+
+ private _lifetime = LifetimeManager.empty();
+
+ private _overrides?: PartialServiceMap;
+
+ private _cleanup?: (item: T) => void;
+
+ private _factory?: (resolve: Resolver) => T;
+
+ private _pending = 1;
+
+ private _failed = false;
+
+ constructor(container: Container, cb: (d: Descriptor) => void, eb: (err: any) => void) {
+ this._container = container;
+ this._cb = cb;
+ this._eb = eb;
+ }
+
+ build(): DescriptorBuilder {
+ this._defer();
+ return new DescriptorBuilder(this._container, () => this._complete(), err => this._fail(err));
+ }
+
+ override(name: K, builder: RegistrationBuilder): this;
+ override(services: { [name in K]: RegistrationBuilder }): this;
+ override(nameOrServices: K | { [name in K]: RegistrationBuilder }, builder?: RegistrationBuilder): this {
+ const overrides: PartialServiceMap = this._overrides ?
+ this._overrides :
+ (this._overrides = {});
+
+ const guard = (v: void | Promise) => {
+ if (isPromise(v))
+ v.catch(err => this._fail(err));
+ };
+
+ if (isPrimitive(nameOrServices)) {
+ if (builder) {
+ this._defer();
+ const d = new DescriptorBuilder(
+ this._container,
+ result => {
+ overrides[nameOrServices] = result;
+ this._complete();
+ },
+ err => this._fail(err)
+ );
+
+ try {
+ guard(builder(d));
+ } catch (err) {
+ this._fail(err);
+ }
+ }
+ } else {
+ each(nameOrServices, (v, k) => this.override(k, v));
+ }
+ return this;
+ }
+
+ lifetime(lifetime: "singleton", typeId: string): this;
+ lifetime(lifetime: ILifetime | Exclude): this;
+ lifetime(lifetime: ILifetime | ActivationType, typeId?: string): this {
+ if (isString(lifetime)) {
+ this._lifetime = this._resolveLifetime(lifetime, typeId);
+ } else {
+ this._lifetime = lifetime;
+ }
+ return this;
+ }
+
+ cleanup(cb: (item: T) => void): this {
+ this._cleanup = cb;
+ return this;
+ }
+
+ factory(f: (resolve: Resolver) => T): void {
+ this._factory = f;
+ this._complete();
+ }
+
+ value(v: T): void {
+ this._cb({
+ activate() {
+ return v;
+ }
+ });
+ }
+
+ _resolveLifetime(activation: ActivationType, typeId?: string | object) {
+ switch (activation) {
+ case "container":
+ return LifetimeManager.containerLifetime(this._container);
+ case "hierarchy":
+ return LifetimeManager.hierarchyLifetime();
+ case "context":
+ return LifetimeManager.contextLifetime();
+ case "singleton":
+ if (!typeId)
+ throw Error("The singleton activation requires a typeId");
+
+ const _oid = isString(typeId) ? typeId : oid(typeId);
+
+ return LifetimeManager.singletonLifetime(_oid);
+ default:
+ return LifetimeManager.empty();
+ }
+ }
+
+ _defer() {
+ this._pending++;
+ }
+
+ _complete() {
+ if (--this._pending === 0) {
+ if (!this._factory)
+ throw new Error("The factory must be specified");
+
+ this._cb(new DescriptorImpl({
+ lifetime: this._lifetime,
+ factory: this._factory,
+ overrides: this._overrides,
+ cleanup: this._cleanup
+ }));
+ }
+ }
+
+ _fail(err: any) {
+ if (!this._failed) {
+ this._failed = true;
+ this._eb.call(undefined, err);
+ }
+ }
+
+}
diff --git a/src/main/ts/di/fluent/DescriptorImpl.ts b/src/main/ts/di/fluent/DescriptorImpl.ts
new file mode 100644
--- /dev/null
+++ b/src/main/ts/di/fluent/DescriptorImpl.ts
@@ -0,0 +1,63 @@
+import { Descriptor, PartialServiceMap, ILifetime, ContainerKeys } from "../interfaces";
+import { ActivationContext } from "../ActivationContext";
+import { each } from "../../safe";
+import { DependencyOptions, LazyDependencyOptions, Resolver } from "./interfaces";
+
+export interface DescriptorImplArgs {
+ lifetime: ILifetime;
+
+ factory: (resolve: Resolver) => T;
+
+ cleanup?: (item: T) => void;
+
+ overrides?: PartialServiceMap;
+}
+
+export class DescriptorImpl implements Descriptor {
+
+ private readonly _overrides?: PartialServiceMap;
+
+ private readonly _lifetime: ILifetime;
+
+ private readonly _factory: (resolve: Resolver) => T;
+
+ private readonly _cleanup?: (item: T) => void;
+
+ constructor(args: DescriptorImplArgs) {
+ this._lifetime = args.lifetime;
+ this._factory = args.factory;
+ if (args.cleanup)
+ this._cleanup = args.cleanup;
+ if (args.overrides)
+ this._overrides = args.overrides;
+ }
+
+ activate(context: ActivationContext): T {
+
+ if (this._lifetime.has())
+ return this._lifetime.get();
+
+ this._lifetime.initialize(context);
+
+ if (this._overrides)
+ each(this._overrides, (v, k) => context.register(k, v));
+
+ const resolve = (name: ContainerKeys, opts?: DependencyOptions | LazyDependencyOptions) => {
+ if (opts && "lazy" in opts && opts.lazy) {
+ const c2 = context.clone();
+ return () => {
+ return opts.optional ? c2.resolve(name, opts.default) : c2.resolve(name);
+ };
+ } else {
+ return opts && opts.optional ? context.resolve(name, opts.default) : context.resolve(name);
+ }
+ };
+
+ const instance = this._factory.call(undefined, resolve);
+
+ this._lifetime.store(instance, this._cleanup);
+
+ return instance;
+ }
+
+}
diff --git a/src/main/ts/di/fluent/FluentConfiguration.ts b/src/main/ts/di/fluent/FluentConfiguration.ts
new file mode 100644
--- /dev/null
+++ b/src/main/ts/di/fluent/FluentConfiguration.ts
@@ -0,0 +1,60 @@
+import { Container } from "../Container";
+import { argumentNotNull, each, isPrimitive, isPromise } from "../../safe";
+import { DescriptorBuilder } from "./DescriptorBuilder";
+import { RegistrationBuilder, FluentRegistrations } from "./interfaces";
+import { Cancellation } from "../../Cancellation";
+
+export class FluentConfiguration {
+
+ _builders: { [k in keyof S]?: RegistrationBuilder } = {};
+
+ register(name: K, builder: RegistrationBuilder): FluentConfiguration>;
+ register(config: FluentRegistrations): FluentConfiguration>;
+ register(nameOrConfig: K | FluentRegistrations, builder?: RegistrationBuilder): FluentConfiguration> {
+ if (isPrimitive(nameOrConfig)) {
+ argumentNotNull(builder, "builder");
+ this._builders[nameOrConfig] = builder;
+ } else {
+ each(nameOrConfig, (v, k) => this.register(k, v));
+ }
+
+ return this;
+ }
+
+ apply(target: Container, ct = Cancellation.none) {
+
+ let pending = 1;
+
+ const _t2 = target as unknown as Container;
+
+ return new Promise>((resolve, reject) => {
+ function guard(v: void | Promise