# HG changeset patch
# User cin
# Date 2018-11-29 22:46:28
# Node ID 58f57bdfc29545d2a2a1befd94034ffdd9b4841a
# Parent 1ef775e96c17d10406ba86479b6fc077657123ee
minor fixes, code cleanup
start porting IoC container to ts
diff --git a/.project b/.project
new file mode 100644
--- /dev/null
+++ b/.project
@@ -0,0 +1,17 @@
+
+
+ core
+ Project core created by Buildship.
+
+
+
+
+ org.eclipse.buildship.core.gradleprojectbuilder
+
+
+
+
+
+ org.eclipse.buildship.core.gradleprojectnature
+
+
diff --git a/.settings/org.eclipse.buildship.core.prefs b/.settings/org.eclipse.buildship.core.prefs
new file mode 100644
--- /dev/null
+++ b/.settings/org.eclipse.buildship.core.prefs
@@ -0,0 +1,2 @@
+connection.project.dir=
+eclipse.preferences.version=1
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,4 @@
+{
+ "java.configuration.updateBuildConfiguration": "disabled",
+ "tslint.enable": true
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
--- a/build.gradle
+++ b/build.gradle
@@ -40,7 +40,7 @@ task _buildTs(dependsOn: _npmInstall, ty
inputs.file('tsc.json')
outputs.dir(distDir)
- commandLine 'node_modules/.bin/tsc', '-p', 'tsc.json'
+ commandLine 'node_modules/.bin/tsc', '-p', 'tsconfig.json'
}
task _packageMeta(type: Copy) {
@@ -80,7 +80,7 @@ task buildTests(dependsOn: _localInstall
inputs.file('tsc.test.json')
outputs.dir(testDir)
- commandLine 'node_modules/.bin/tsc', '-p', 'tsc.test.json'
+ commandLine 'node_modules/.bin/tsc', '-p', 'tsconfig.test.json'
}
task test(dependsOn: [copyJsTests, buildTests], type: Exec) {
diff --git a/package-lock.json b/package-lock.json
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,7 +16,7 @@
"integrity": "sha512-xil0KO5wkPoixdBWGIGolPv9dekf6dVkjjJLAFYchfKcd4DICou67rgGCIO7wAh3i5Ff/6j9IDgZz+GU9cMaqQ==",
"dev": true,
"requires": {
- "@types/node": "10.5.1"
+ "@types/node": "*"
}
},
"balanced-match": {
@@ -31,7 +31,7 @@
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
- "balanced-match": "1.0.0",
+ "balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
@@ -59,8 +59,8 @@
"integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=",
"dev": true,
"requires": {
- "foreach": "2.0.5",
- "object-keys": "1.0.12"
+ "foreach": "^2.0.5",
+ "object-keys": "^1.0.8"
}
},
"defined": {
@@ -86,11 +86,11 @@
"integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==",
"dev": true,
"requires": {
- "es-to-primitive": "1.1.1",
- "function-bind": "1.1.1",
- "has": "1.0.3",
- "is-callable": "1.1.3",
- "is-regex": "1.0.4"
+ "es-to-primitive": "^1.1.1",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.1",
+ "is-callable": "^1.1.3",
+ "is-regex": "^1.0.4"
}
},
"es-to-primitive": {
@@ -99,9 +99,9 @@
"integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=",
"dev": true,
"requires": {
- "is-callable": "1.1.3",
- "is-date-object": "1.0.1",
- "is-symbol": "1.0.1"
+ "is-callable": "^1.1.1",
+ "is-date-object": "^1.0.1",
+ "is-symbol": "^1.0.1"
}
},
"faucet": {
@@ -111,12 +111,12 @@
"dev": true,
"requires": {
"defined": "0.0.0",
- "duplexer": "0.1.1",
+ "duplexer": "~0.1.1",
"minimist": "0.0.5",
- "sprintf": "0.1.5",
- "tap-parser": "0.4.3",
- "tape": "2.3.3",
- "through2": "0.2.3"
+ "sprintf": "~0.1.3",
+ "tap-parser": "~0.4.0",
+ "tape": "~2.3.2",
+ "through2": "~0.2.3"
},
"dependencies": {
"deep-equal": {
@@ -143,12 +143,12 @@
"integrity": "sha1-Lnzgox3wn41oUWZKcYQuDKUFevc=",
"dev": true,
"requires": {
- "deep-equal": "0.1.2",
- "defined": "0.0.0",
- "inherits": "2.0.3",
- "jsonify": "0.0.0",
- "resumer": "0.0.0",
- "through": "2.3.8"
+ "deep-equal": "~0.1.0",
+ "defined": "~0.0.0",
+ "inherits": "~2.0.1",
+ "jsonify": "~0.0.0",
+ "resumer": "~0.0.0",
+ "through": "~2.3.4"
}
}
}
@@ -159,7 +159,7 @@
"integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
"dev": true,
"requires": {
- "is-callable": "1.1.3"
+ "is-callable": "^1.1.3"
}
},
"foreach": {
@@ -186,12 +186,12 @@
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
"dev": true,
"requires": {
- "fs.realpath": "1.0.0",
- "inflight": "1.0.6",
- "inherits": "2.0.3",
- "minimatch": "3.0.4",
- "once": "1.4.0",
- "path-is-absolute": "1.0.1"
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
}
},
"has": {
@@ -200,7 +200,7 @@
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"requires": {
- "function-bind": "1.1.1"
+ "function-bind": "^1.1.1"
}
},
"inflight": {
@@ -209,8 +209,8 @@
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
- "once": "1.4.0",
- "wrappy": "1.0.2"
+ "once": "^1.3.0",
+ "wrappy": "1"
}
},
"inherits": {
@@ -237,7 +237,7 @@
"integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
"dev": true,
"requires": {
- "has": "1.0.3"
+ "has": "^1.0.1"
}
},
"is-symbol": {
@@ -264,7 +264,7 @@
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
- "brace-expansion": "1.1.11"
+ "brace-expansion": "^1.1.7"
}
},
"minimist": {
@@ -291,7 +291,7 @@
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
- "wrappy": "1.0.2"
+ "wrappy": "1"
}
},
"path-is-absolute": {
@@ -312,10 +312,10 @@
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"dev": true,
"requires": {
- "core-util-is": "1.0.2",
- "inherits": "2.0.3",
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.1",
"isarray": "0.0.1",
- "string_decoder": "0.10.31"
+ "string_decoder": "~0.10.x"
}
},
"requirejs": {
@@ -330,7 +330,7 @@
"integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==",
"dev": true,
"requires": {
- "path-parse": "1.0.5"
+ "path-parse": "^1.0.5"
}
},
"resumer": {
@@ -339,7 +339,7 @@
"integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=",
"dev": true,
"requires": {
- "through": "2.3.8"
+ "through": "~2.3.4"
}
},
"sprintf": {
@@ -354,9 +354,9 @@
"integrity": "sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=",
"dev": true,
"requires": {
- "define-properties": "1.1.2",
- "es-abstract": "1.12.0",
- "function-bind": "1.1.1"
+ "define-properties": "^1.1.2",
+ "es-abstract": "^1.5.0",
+ "function-bind": "^1.0.2"
}
},
"string_decoder": {
@@ -371,8 +371,8 @@
"integrity": "sha1-pOrhkMENdsehEZIf84u+TVjwnuo=",
"dev": true,
"requires": {
- "inherits": "2.0.3",
- "readable-stream": "1.1.14"
+ "inherits": "~2.0.1",
+ "readable-stream": "~1.1.11"
}
},
"tape": {
@@ -381,19 +381,19 @@
"integrity": "sha512-6fKIXknLpoe/Jp4rzHKFPpJUHDHDqn8jus99IfPnHIjyz78HYlefTGD3b5EkbQzuLfaEvmfPK3IolLgq2xT3kw==",
"dev": true,
"requires": {
- "deep-equal": "1.0.1",
- "defined": "1.0.0",
- "for-each": "0.3.3",
- "function-bind": "1.1.1",
- "glob": "7.1.2",
- "has": "1.0.3",
- "inherits": "2.0.3",
- "minimist": "1.2.0",
- "object-inspect": "1.6.0",
- "resolve": "1.7.1",
- "resumer": "0.0.0",
- "string.prototype.trim": "1.1.2",
- "through": "2.3.8"
+ "deep-equal": "~1.0.1",
+ "defined": "~1.0.0",
+ "for-each": "~0.3.3",
+ "function-bind": "~1.1.1",
+ "glob": "~7.1.2",
+ "has": "~1.0.3",
+ "inherits": "~2.0.3",
+ "minimist": "~1.2.0",
+ "object-inspect": "~1.6.0",
+ "resolve": "~1.7.1",
+ "resumer": "~0.0.0",
+ "string.prototype.trim": "~1.1.2",
+ "through": "~2.3.8"
}
},
"through": {
@@ -408,8 +408,8 @@
"integrity": "sha1-6zKE2k6jEbbMis42U3SKUqvyWj8=",
"dev": true,
"requires": {
- "readable-stream": "1.1.14",
- "xtend": "2.1.2"
+ "readable-stream": "~1.1.9",
+ "xtend": "~2.1.1"
}
},
"typescript": {
@@ -430,7 +430,7 @@
"integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=",
"dev": true,
"requires": {
- "object-keys": "0.4.0"
+ "object-keys": "~0.4.0"
},
"dependencies": {
"object-keys": {
diff --git a/src/js/Deferred.js b/src/js/Deferred.js
deleted file mode 100644
--- a/src/js/Deferred.js
+++ /dev/null
@@ -1,3 +0,0 @@
-define(["dojo/Deferred"], function(Deferred) {
- return Deferred;
-});
\ No newline at end of file
diff --git a/src/js/declare.js b/src/js/declare.js
deleted file mode 100644
--- a/src/js/declare.js
+++ /dev/null
@@ -1,6 +0,0 @@
-define([
- './declare/_load!'
-], function(declare) {
- 'use strict';
- return declare;
-});
\ No newline at end of file
diff --git a/src/ts/Uuid.ts b/src/ts/Uuid.ts
--- a/src/ts/Uuid.ts
+++ b/src/ts/Uuid.ts
@@ -8,7 +8,7 @@
declare var window: any;
-let _window : any = 'undefined' !== typeof window ? window : null;
+let _window: any = 'undefined' !== typeof window ? window : null;
// Unique ID creation requires a high quality random # generator. We
// feature
@@ -41,7 +41,7 @@ function setupBrowser() {
// If all else fails, use Math.random(). It's fast, but is of
// unspecified
// quality.
- let _rnds = new Array(16);
+ let _rnds = new Array(16);
_rng = function () {
for (var i = 0, r; i < 16; i++) {
if ((i & 0x03) === 0) {
@@ -92,8 +92,8 @@ for (let i = 0; i < 256; i++) {
}
// **`parse()` - Parse a UUID into it's component bytes**
-function parse(s, buf?, offset?) : Array {
- let i = (buf && offset) || 0, ii = 0;
+function _parse(s, buf?, offset?): Array {
+ let i = (buf && offset) || 0, ii = 0;
buf = buf || [];
s.toLowerCase().replace(/[0-9a-f]{2}/g, function (oct) {
@@ -111,8 +111,8 @@ function parse(s, buf?, offset?) : Array
}
// **`unparse()` - Convert UUID byte array (ala parse()) into a string**
-function unparse(buf, offset?) : string {
- let i = offset || 0, bth = _byteToHex;
+function _unparse(buf, offset?): string {
+ let i = offset || 0, bth = _byteToHex;
return bth[buf[i++]] + bth[buf[i++]] + bth[buf[i++]] +
bth[buf[i++]] + '-' + bth[buf[i++]] + bth[buf[i++]] + '-' +
bth[buf[i++]] + bth[buf[i++]] + '-' + bth[buf[i++]] +
@@ -143,10 +143,10 @@ let _nodeId = [
let _clockseq = (_seedBytes[6] << 8 | _seedBytes[7]) & 0x3fff;
// Previous uuid creation time
-let _lastMSecs = 0, _lastNSecs = 0;
+let _lastMSecs = 0, _lastNSecs = 0;
// See https://github.com/broofa/node-uuid for API details
-function v1(options?, buf?, offset?) : string {
+function _v1(options?, buf?, offset?): string {
let i = buf && offset || 0;
let b = buf || [];
@@ -225,13 +225,13 @@ function v1(options?, buf?, offset?) : s
b[i + n] = node[n];
}
- return buf ? buf : unparse(b);
+ return buf ? buf : _unparse(b);
}
// **`v4()` - Generate random UUID**
// See https://github.com/broofa/node-uuid for API details
-function v4(options?, buf?, offset?) : string {
+function _v4(options?, buf?, offset?): string {
// Deprecated - 'format' argument, as supported in v1.2
let i = buf && offset || 0;
@@ -254,29 +254,17 @@ function v4(options?, buf?, offset?) : s
}
}
- return buf || unparse(rnds);
+ return buf || _unparse(rnds);
+}
+
+export function Uuid() {
+
}
-// Export public API
-const empty = "00000000-0000-0000-0000-000000000000";
-
-interface uuid {
- (options?, buf?, offset?) : string;
- v1(options?, buf?, offset?) : string;
- v4(options?, buf?, offset?) : string;
- readonly empty: string;
- parse(s, buf?, offset?) : Array;
- unparse(buf, offset?) : string;
-}
-
-export = (() =>{
- var f : any = function(options?, buf?, offset?) : string {
- return v4(options, buf, offset);
- };
- f.v1 = v1;
- f.v4 = v4;
- f.empty = empty;
- f.parse = parse;
- f.unparse = unparse;
- return f;
-})();
\ No newline at end of file
+export namespace Uuid {
+ export const v4 = _v4;
+ export const v1 = _v1;
+ export const empty = "00000000-0000-0000-0000-000000000000";
+ export const parse = _parse;
+ export const unparse = _unparse;
+}
\ No newline at end of file
diff --git a/src/ts/di.ts b/src/ts/di.ts
new file mode 100644
--- /dev/null
+++ b/src/ts/di.ts
@@ -0,0 +1,126 @@
+import { TraceSource } from "./log/TraceSource";
+import { argumentNotNull, argumentNotEmptyString, isPrimitive, each, isNull } from "./safe";
+import { Uuid } from './Uuid';
+import {ActivationContext} from "./di/ActivationContext";
+
+let trace = TraceSource.get("di");
+
+export interface Descriptor {
+ activate(context: ActivationContext, name: string): any
+}
+
+export function isDescriptor(value: any): value is Descriptor {
+ return ("activate" in value);
+}
+
+export interface ServiceMap {
+ [s: string] : Descriptor
+}
+
+export class ActivationContextInfo {
+ name: string
+
+ service: string
+
+ scope: ServiceMap
+}
+
+export class ActivationError {
+ activationStack: ActivationContextInfo[]
+
+ service: string
+
+ innerException: any
+
+ message: string
+
+ constructor(service: string, activationStack: ActivationContextInfo[], innerException) {
+ this.message = "Failed to activate the service";
+ this.activationStack = activationStack;
+ this.service = service;
+ this.innerException = innerException;
+ }
+
+ toString() {
+ var parts = [this.message];
+ if (this.service)
+ parts.push("when activating: " + this.service.toString());
+
+ if (this.innerException)
+ parts.push("caused by: " + this.innerException.toString());
+
+ if (this.activationStack) {
+ parts.push("at");
+ this.activationStack.forEach(function (x) {
+ parts.push(" " + x.name + " " +
+ (x.service ? x.service.toString() : ""));
+ });
+ }
+
+ return parts.join("\n");
+ }
+}
+
+
+export enum ActivationType {
+ SINGLETON,
+ CONTAINER,
+ HIERARCHY,
+ CONTEXT,
+ CALL
+}
+
+
+
+interface ServiceDescriptorParams {
+
+}
+
+class ServiceDescriptor extends Descriptor {
+ constructor(opts: ServiceDescriptorParams) {
+ super();
+ }
+
+ activate(context: ActivationContext, name: string) {
+ throw new Error("Method not implemented.");
+ }
+ isInstanceCreated(): boolean {
+ throw new Error("Method not implemented.");
+ }
+ getInstance() {
+ throw new Error("Method not implemented.");
+ }
+}
+
+
+
+class AggregateDescriptor extends Descriptor {
+ constructor(value: T) {
+ super();
+ }
+
+ activate(context: ActivationContext, name: string) {
+ throw new Error("Method not implemented.");
+ }
+ isInstanceCreated(): boolean {
+ throw new Error("Method not implemented.");
+ }
+ getInstance(): T {
+ throw new Error("Method not implemented.");
+ }
+}
+
+class ValueDescriptor implements Descriptor {
+ activate(context: ActivationContext, name: string) {
+ throw new Error("Method not implemented.");
+ }
+ isInstanceCreated(): boolean {
+ throw new Error("Method not implemented.");
+ }
+ getInstance(): T {
+ throw new Error("Method not implemented.");
+ }
+ constructor(value: T) {
+
+ }
+}
\ No newline at end of file
diff --git a/src/ts/di/ActivationContext.ts b/src/ts/di/ActivationContext.ts
new file mode 100644
--- /dev/null
+++ b/src/ts/di/ActivationContext.ts
@@ -0,0 +1,132 @@
+import { TraceSource } from "../log/TraceSource";
+import { argumentNotNull, argumentNotEmptyString, isPrimitive, each, isNull } from "../safe";
+import { Uuid } from '../Uuid';
+import { Container, ActivationContextInfo, ServiceMap, Descriptor, isDescriptor } from "../di";
+
+let trace = TraceSource.get("di");
+
+
+export class ActivationContext {
+ _cache: object
+
+ _services: ServiceMap
+
+ _stack: ActivationContextInfo[]
+
+ _visited: any
+
+ container: any
+
+
+ constructor(container: Container, services: ServiceMap, cache?: object, visited?) {
+ argumentNotNull(container, "container");
+ argumentNotNull(services, "services");
+
+ this._visited = visited || {};
+ this._stack = [];
+ this._cache = cache || {};
+ this._services = services;
+ this.container = container;
+ }
+
+ getService(name, def?): any {
+ let d = this._services[name];
+
+ if (!d)
+ if (arguments.length > 1)
+ return def;
+ else
+ throw new Error("Service '" + name + "' not found");
+
+ return d.activate(this, name);
+ }
+
+ /**
+ * registers services local to the the activation context
+ *
+ * @name{string} the name of the service
+ * @service{string} the service descriptor to register
+ */
+ register(name: string, service: Descriptor) {
+ argumentNotEmptyString(name, "name");
+
+ this._services[name] = service;
+ }
+
+ clone() {
+ return new ActivationContext(
+ this.container,
+ Object.create(this._services),
+ this._cache,
+ this._visited
+ );
+
+ }
+
+ has(id) {
+ return id in this._cache;
+ }
+
+ get(id) {
+ return this._cache[id];
+ }
+
+ store(id, value) {
+ return (this._cache[id] = value);
+ }
+
+ parse(data: any, name) {
+ var me = this;
+ if (isPrimitive(data))
+ return data;
+
+ if (isDescriptor(data)) {
+ return data.activate(this, name);
+ } else if (data instanceof Array) {
+ me.enter(name);
+ var v = data.map(function (x, i) {
+ return me.parse(x, "." + i);
+ });
+ me.leave();
+ return v;
+ } else {
+ me.enter(name);
+ var result = {};
+ for (var p in data)
+ result[p] = me.parse(data[p], "." + p);
+ me.leave();
+ return result;
+ }
+ }
+
+ visit(id) {
+ var count = this._visited[id] || 0;
+ this._visited[id] = count + 1;
+ return count;
+ }
+
+ getStack() {
+ return this._stack.slice().reverse();
+ }
+
+ enter(name, d?, localize?) {
+ if (trace.isLogEnabled())
+ trace.log("enter " + name + " " + (d || "") +
+ (localize ? " localize" : ""));
+ this._stack.push({
+ name: name,
+ service: d,
+ scope: this._services
+ });
+ if (localize)
+ this._services = Object.create(this._services);
+ }
+
+ leave() {
+ var ctx = this._stack.pop();
+ this._services = ctx.scope;
+
+ if (trace.isLogEnabled())
+ trace.log("leave " + ctx.name + " " + (ctx.service || ""));
+ }
+}
\ No newline at end of file
diff --git a/src/ts/di/Container.ts b/src/ts/di/Container.ts
new file mode 100644
--- /dev/null
+++ b/src/ts/di/Container.ts
@@ -0,0 +1,282 @@
+import { Uuid } from "../Uuid";
+import { ActivationContext } from "./ActivationContext";
+import { ActivationError } from "../di";
+
+
+export class Container {
+ _services
+
+ _cache
+
+ _cleanup: any[]
+
+ _root: Container
+
+ _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);
+ }
+
+ getRootContainer() {
+ return this._root;
+ }
+
+ getParent() {
+ return this._parent;
+ }
+
+ getService(name: string, def?: T) {
+ let d = this._services[name];
+ if (!d)
+ if (arguments.length > 1)
+ return def;
+ else
+ throw new Error("Service '" + name + "' isn't found");
+
+ if (d.isInstanceCreated())
+ return d.getInstance();
+
+ var context = new ActivationContext(this, this._services);
+
+ try {
+ return d.activate(context, name);
+ } catch (error) {
+ throw new ActivationError(name, context.getStack(), error);
+ }
+ }
+
+ register(nameOrCollection, service?) {
+ if (arguments.length == 1) {
+ var data = nameOrCollection;
+ for (let name in data)
+ this.register(name, data[name]);
+ } else {
+ if (!(service instanceof Descriptor))
+ service = new ValueDescriptor(service);
+ this._services[nameOrCollection] = service;
+ }
+ return this;
+ }
+
+ onDispose(callback) {
+ if (!(callback instanceof Function))
+ throw new Error("The callback must be a function");
+ this._cleanup.push(callback);
+ }
+
+ dispose() {
+ if (this._cleanup) {
+ for (var i = 0; i < this._cleanup.length; i++)
+ this._cleanup[i].call(null);
+ this._cleanup = null;
+ }
+ }
+
+ /**
+ * @param{String|Object} config
+ * The configuration of the contaier. 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.
+ *
+ * @param{Function} opts.contextRequire
+ * The function which will be used to load a configuration or types for services.
+ *
+ */
+ async configure(config, opts) {
+ var me = this,
+ contextRequire = (opts && opts.contextRequire);
+
+ if (typeof (config) === "string") {
+ let args;
+ if (!contextRequire) {
+ var shim = [config, Uuid()].join(config.indexOf("/") != -1 ? "-" : "/");
+ args = await new Promise((resolve, reject) => {
+ define(shim, ["require", config], function (ctx, data) {
+ resolve([data, {
+ contextRequire: ctx
+ }]);
+ })
+ });
+ require([shim]);
+ } else {
+ // TODO how to get correct contextRequire for the relative config module?
+ args = await new Promise((resolve, reject) => {
+ contextRequire([config], function (data) {
+ resolve([data, {
+ contextRequire: contextRequire
+ }]);
+ });
+ });
+ }
+
+ return me._configure.apply(me, args);
+ } else {
+ return me._configure(config, opts);
+ }
+ }
+
+ createChildContainer() {
+ return new Container(this);
+ }
+
+ has(id) {
+ return id in this._cache;
+ }
+
+ get(id) {
+ return this._cache[id];
+ }
+
+ store(id, value) {
+ return (this._cache[id] = value);
+ }
+
+ async _configure(data, opts) {
+ var typemap = {},
+ me = this,
+ p,
+ contextRequire = (opts && opts.contextRequire) || require;
+
+ var services = {};
+
+ for (p in data) {
+ var service = me._parse(data[p], typemap);
+ if (!(service instanceof Descriptor))
+ service = new AggregateDescriptor(service);
+ services[p] = service;
+ }
+
+ me.register(services);
+
+ var names = [];
+
+ for (p in typemap)
+ names.push(p);
+
+ return new Promise((resolve, reject) => {
+ if (names.length) {
+ contextRequire(names, function () {
+ for (var i = 0; i < names.length; i++)
+ typemap[names[i]] = arguments[i];
+ resolve(me);
+ });
+ } else {
+ resolve(me);
+ }
+ });
+
+ }
+
+ _parse(data, typemap) {
+ if (isPrimitive(data) || data instanceof Descriptor)
+ return data;
+ if (data.$dependency) {
+ return new ReferenceDescriptor(
+ data.$dependency,
+ data.lazy,
+ data.optional,
+ data["default"],
+ data.services && this._parseObject(data.services, typemap));
+ } else if (data.$value) {
+ return !data.parse ?
+ new ValueDescriptor(data.$value) :
+ new AggregateDescriptor(this._parse(data.$value, typemap));
+ } else if (data.$type || data.$factory) {
+ return this._parseService(data, typemap);
+ } else if (data instanceof Array) {
+ return this._parseArray(data, typemap);
+ }
+
+ return this._parseObject(data, typemap);
+ }
+
+ _parseService(data, typemap) {
+ var me = this,
+ opts: any = {
+ owner: this
+ };
+ if (data.$type) {
+
+ opts.type = data.$type;
+
+ if (typeof (data.$type) === "string") {
+ typemap[data.$type] = null;
+ opts.typeMap = typemap;
+ }
+ }
+
+ if (data.$factory)
+ opts.factory = data.$factory;
+
+ if (data.services)
+ opts.services = me._parseObject(data.services, typemap);
+ if (data.inject)
+ opts.inject = data.inject instanceof Array ? data.inject.map(function (x) {
+ return me._parseObject(x, typemap);
+ }) : me._parseObject(data.inject, typemap);
+ if (data.params)
+ opts.params = me._parse(data.params, typemap);
+
+ 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);
+ }
+ }
+
+ if (data.cleanup)
+ opts.cleanup = data.cleanup;
+
+ return new ServiceDescriptor(opts);
+ }
+
+ _parseObject(data, typemap) {
+ if (data.constructor &&
+ data.constructor.prototype !== Object.prototype)
+ return new ValueDescriptor(data);
+
+ var o = {};
+
+ for (var p in data)
+ o[p] = this._parse(data[p], typemap);
+
+ return o;
+ }
+
+ _parseArray(data, typemap) {
+ if (data.constructor &&
+ data.constructor.prototype !== Array.prototype)
+ return new ValueDescriptor(data);
+
+ var me = this;
+ return data.map(function (x) {
+ return me._parse(x, typemap);
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/ts/di/Descriptor.ts b/src/ts/di/Descriptor.ts
new file mode 100644
--- /dev/null
+++ b/src/ts/di/Descriptor.ts
@@ -0,0 +1,11 @@
+import { ActivationContext } from "./ActivationContext";
+import { isNull } from "../safe";
+
+export interface Descriptor {
+ activate(context: ActivationContext, name?: string);
+}
+
+export function isDescriptor(instance): instance is Descriptor {
+ return (!isNull(instance)) &&
+ ('activate' in instance);
+}
\ No newline at end of file
diff --git a/src/ts/di/ReferenceDescriptor.ts b/src/ts/di/ReferenceDescriptor.ts
new file mode 100644
--- /dev/null
+++ b/src/ts/di/ReferenceDescriptor.ts
@@ -0,0 +1,98 @@
+import { isNull, argumentNotEmptyString, each } from "../safe";
+
+class ReferenceDescriptor extends Descriptor {
+ _name: string
+
+ _lazy = false
+
+ _optional = false
+
+ _default: any
+
+ _services: object
+
+ constructor(name: string, lazy: boolean, optional: boolean, def, services: object) {
+ super();
+ argumentNotEmptyString(name, "name");
+ this._name = name;
+ this._lazy = Boolean(lazy);
+ this._optional = Boolean(optional);
+ this._default = def;
+ this._services = services;
+ }
+
+ activate(context: ActivationContext, name: string) {
+ var me = this;
+
+ context.enter(name, this, true);
+
+ // добавляем сервисы
+ if (me._services) {
+ for (var p in me._services) {
+ var sv = me._services[p];
+ context.register(p, sv instanceof Descriptor ? sv : new AggregateDescriptor(sv));
+ }
+ }
+
+ if (me._lazy) {
+ // сохраняем контекст активации
+ context = context.clone();
+ return function (cfg) {
+ // защищаем контекст на случай исключения в процессе
+ // активации
+ var ct = context.clone();
+ try {
+ if (cfg)
+ each(cfg, function (v, k) {
+ ct.register(k, v instanceof Descriptor ? v : new AggregateDescriptor(v));
+ });
+ return me._optional ? ct.getService(me._name, me._default) : ct
+ .getService(me._name);
+ } catch (error) {
+ throw new ActivationError(me._name, ct.getStack(), error);
+ }
+ };
+ }
+
+ var v = me._optional ?
+ context.getService(me._name, me._default) :
+ context.getService(me._name);
+
+ context.leave();
+ return v;
+ }
+
+ isInstanceCreated() {
+ return false;
+ }
+
+ getInstance() {
+ throw new Error("The reference descriptor doesn't allowed to hold an instance");
+ }
+
+ toString() {
+ var opts = [];
+ if (this._optional)
+ opts.push("optional");
+ if (this._lazy)
+ opts.push("lazy");
+
+ var parts = [
+ "@ref "
+ ];
+ if (opts.length) {
+ parts.push("{");
+ parts.push(opts.join());
+ parts.push("} ");
+ }
+
+ parts.push(this._name);
+
+ if (!isNull(this._default)) {
+ parts.push(" = ");
+ parts.push(this._default);
+ }
+
+ return parts.join("");
+ }
+}
\ No newline at end of file
diff --git a/src/ts/safe.ts b/src/ts/safe.ts
--- a/src/ts/safe.ts
+++ b/src/ts/safe.ts
@@ -45,7 +45,7 @@ export function isNullOrEmptyString(str)
return true;
}
-export function isNotEmptyArray(arg) {
+export function isNotEmptyArray(arg): arg is Array {
return (arg instanceof Array && arg.length > 0);
}
@@ -61,7 +61,7 @@ export function isNotEmptyArray(arg) {
* @returns Результат вызова функции cb, либо undefined
* если достигнут конец массива.
*/
-export function each(obj, cb, thisArg) {
+export function each(obj, cb, thisArg?) {
argumentNotNull(cb, "cb");
var i, x;
if (obj instanceof Array) {
@@ -81,11 +81,55 @@ export function each(obj, cb, thisArg) {
}
}
+/** Copies property values from a source object to the destination and returns
+ * the destination onject.
+ *
+ * @param dest The destination object into which properties from the source
+ * object will be copied.
+ * @param source The source of values which will be copied to the destination
+ * object.
+ * @param template An optional parameter specifies which properties should be
+ * copied from the source and how to map them to the destination. If the
+ * template is an array it contains the list of property names to copy from the
+ * source to the destination. In case of object the templates contains the map
+ * where keys are property names in the source and the values are property
+ * names in the destination object. If the template isn't specified then the
+ * own properties of the source are entirely copied to the destination.
+ *
+ */
+export function mixin(dest: T, source: S, template?: string[] | object) : T & S {
+ argumentNotNull(dest, "to");
+ let _res = dest;
+
+ if (template instanceof Array) {
+ for(let i = 0; i < template.length; i++) {
+ let p = template[i];
+ if (p in source)
+ _res[p] = source[p];
+ }
+ } else if (template) {
+ let keys = Object.keys(source);
+ for(let i = 0; i < keys.length; i++) {
+ let p = keys[i];
+ if (p in template)
+ _res[template[p]] = source[p];
+ }
+ } else {
+ let keys = Object.keys(source);
+ for(let i = 0; i < keys.length; i++) {
+ let p = keys[i];
+ _res[p] = source[p];
+ }
+ }
+
+ return _res;
+}
+
/** Wraps the specified function to emulate an asynchronous execution.
* @param{Object} thisArg [Optional] Object which will be passed as 'this' to the function.
* @param{Function|String} fn [Required] Function wich will be wrapped.
*/
-export function async(_fn: (...args: any[]) => any, thisArg) : (...args: any[]) => PromiseLike {
+export function async(_fn: (...args: any[]) => any, thisArg): (...args: any[]) => PromiseLike {
let fn = _fn;
if (arguments.length == 2 && !(fn instanceof Function))
@@ -94,7 +138,7 @@ export function async(_fn: (...args: any
if (fn == null)
throw new Error("The function must be specified");
- function wrapresult(x, e?) : PromiseLike {
+ function wrapresult(x, e?): PromiseLike {
if (e) {
return {
then: function (cb, eb) {
@@ -129,19 +173,18 @@ export function async(_fn: (...args: any
};
}
-export function delegate(target, _method: (string | Function)) {
- let method : Function;
+export function delegate(target: T, _method: (K | Function)) {
+ let method;
if (!(_method instanceof Function)) {
argumentNotNull(target, "target");
method = target[_method];
+ if (!(method instanceof Function))
+ throw new Error("'method' argument must be a Function or a method name");
} else {
method = _method;
}
- if (!(method instanceof Function))
- throw new Error("'method' argument must be a Function or a method name");
-
return function () {
return method.apply(target, arguments);
};
diff --git a/tsc.json b/tsc.json
deleted file mode 100644
--- a/tsc.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "compilerOptions": {
- "target": "es5",
- "module": "amd",
- "sourceMap": true,
- "outDir" : "build/dist",
- "declaration": true,
- "lib": [
- "ES2015"
- ]
- },
- "include" : [
- "src/ts/**/*.ts"
- ]
-}
\ No newline at end of file
diff --git a/tsc.test.json b/tsc.test.json
deleted file mode 100644
--- a/tsc.test.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "compilerOptions": {
- "target": "es5",
- "module": "amd",
- "sourceMap": true,
- "outDir" : "build/test",
- "moduleResolution": "node",
- "lib": [
- "ES2015"
- ]
- },
- "include" : [
- "test/ts/**/*.ts"
- ]
-}
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "module": "amd",
+ "sourceMap": true,
+ "outDir" : "build/dist",
+ "declaration": true,
+ "lib": [
+ "es2015"
+ ]
+ },
+ "include" : [
+ "src/ts/**/*.ts"
+ ]
+}
\ No newline at end of file
diff --git a/tsconfig.test.json b/tsconfig.test.json
new file mode 100644
--- /dev/null
+++ b/tsconfig.test.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "module": "amd",
+ "sourceMap": true,
+ "outDir" : "build/test",
+ "moduleResolution": "node",
+ "lib": [
+ "ES2015"
+ ]
+ },
+ "include" : [
+ "test/ts/**/*.ts"
+ ]
+}
\ No newline at end of file