##// END OF EJS Templates
corrected code to support ts strict mode...
cin -
r115:691199f665e0 ioc ts support
parent child
Show More
@@ -0,0 +1,41
1 import { IDestroyable } from "./interfaces";
2 import { Observable } from "./Observable";
3
4 /**
5 * Event proviers are used to produce events, throug this object you can feed
6 * the Observable with input events. Once the EventProvider is destroyed the
7 * bound obsevable is disconnected and marked as 'done'.
8 */
9 export class EventProvider<T> implements IDestroyable {
10
11 _observable: Observable<T> | undefined;
12
13 _next: ((evt: T) => void) | undefined;
14 _done: (() => void) | undefined;
15
16 constructor() {
17 this._observable = new Observable<T>((next, _error, done) => {
18 this._next = next;
19 this._done = done;
20 });
21 }
22
23 destroy(): void {
24 if (this._observable) {
25 // break all references
26 this._observable = undefined;
27 this._next = undefined;
28 this._done = undefined;
29 }
30 }
31 post(event: T) {
32 return this._next && this._next(event);
33 }
34
35 getObservable() {
36 if (!this._observable)
37 throw new Error("The object is destroyed");
38
39 return this._observable;
40 }
41 }
@@ -1,236 +1,238
1 plugins {
1 plugins {
2 id "org.implab.gradle-typescript" version "1.3.3"
2 id "org.implab.gradle-typescript" version "1.3.3"
3 id "org.implab.gradle-hg"
3 id "org.implab.gradle-hg"
4 id "ivy-publish"
4 id "ivy-publish"
5 }
5 }
6
6
7 if (!symbols in ['local', 'pack', 'none'])
7 if (!symbols in ['local', 'pack', 'none'])
8 throw new Exception("The symbols property value is invalid: $symbols");
8 throw new Exception("The symbols property value is invalid: $symbols");
9
9
10 if (!flavour in ['browser', 'node'])
10 if (!flavour in ['browser', 'node'])
11 throw new Exception("The flavour property value is invalid: $flavour");
11 throw new Exception("The flavour property value is invalid: $flavour");
12
12
13 ext {
13 ext {
14 packageName = flavour == 'browser' ? "@$npmScope/$name-amd" : "@$npmScope/$name"
14 packageName = flavour == 'browser' ? "@$npmScope/$name-amd" : "@$npmScope/$name"
15 lint = project.hasProperty('lint') ? project.lint ?: true : false
15 lint = project.hasProperty('lint') ? project.lint ?: true : false
16 }
16 }
17
17
18 sources {
18 sources {
19 amd {
19 amd {
20 typings {
20 typings {
21 srcDir main.output.typingsDir
21 srcDir main.output.typingsDir
22 }
22 }
23 }
23 }
24
24
25 cjs {
25 cjs {
26 typings {
26 typings {
27 srcDir main.output.typingsDir
27 srcDir main.output.typingsDir
28 }
28 }
29 }
29 }
30
30
31 testAmd {
31 testAmd {
32 typings {
32 typings {
33 srcDir main.output.typingsDir
33 srcDir main.output.typingsDir
34 srcDir amd.output.typingsDir
34 srcDir amd.output.typingsDir
35 srcDir test.output.typingsDir
35 srcDir test.output.typingsDir
36 }
36 }
37 }
37 }
38
38
39 testCjs {
39 testCjs {
40 typings {
40 typings {
41 srcDir main.output.typingsDir
41 srcDir main.output.typingsDir
42 srcDir cjs.output.typingsDir
42 srcDir cjs.output.typingsDir
43 srcDir test.output.typingsDir
43 srcDir test.output.typingsDir
44 }
44 }
45 }
45 }
46 }
46 }
47
47
48 typescript {
48 typescript {
49 compilerOptions {
49 compilerOptions {
50 types = []
50 types = []
51 declaration = true
51 declaration = true
52 experimentalDecorators = true
52 experimentalDecorators = true
53 //strict = true
53 strict = true
54 // dojo-typings are sick
55 skipLibCheck = true
54
56
55 if(symbols != 'none') {
57 if(symbols != 'none') {
56 sourceMap = true
58 sourceMap = true
57 sourceRoot = packageName
59 sourceRoot = packageName
58 }
60 }
59
61
60 if (flavour == 'node') {
62 if (flavour == 'node') {
61 module = "commonjs"
63 module = "commonjs"
62 target = "es2017"
64 target = "es2017"
63 lib = ["es2017", "dom", "scripthost"]
65 lib = ["es2017", "dom", "scripthost"]
64 } else if (flavour == 'browser') {
66 } else if (flavour == 'browser') {
65 module = "amd"
67 module = "amd"
66 target = "es5"
68 target = "es5"
67 lib = ["es5", "dom", "scripthost", "es2015.promise", "es2015.symbol", "es2015.iterable" ]
69 lib = ["es5", "dom", "scripthost", "es2015.promise", "es2015.symbol", "es2015.iterable" ]
68 }
70 }
69 }
71 }
70 tscCmd = "$projectDir/node_modules/.bin/tsc"
72 tscCmd = "$projectDir/node_modules/.bin/tsc"
71 tsLintCmd = "$projectDir/node_modules/.bin/tslint"
73 tsLintCmd = "$projectDir/node_modules/.bin/tslint"
72 esLintCmd = "$projectDir/node_modules/.bin/eslint"
74 esLintCmd = "$projectDir/node_modules/.bin/eslint"
73 }
75 }
74
76
75 tasks.matching{ it.name =~ /^lint/ }.configureEach {
77 tasks.matching{ it.name =~ /^lint/ }.configureEach {
76 onlyIf { lint }
78 onlyIf { lint }
77 }
79 }
78
80
79 if (symbols == 'local') {
81 if (symbols == 'local') {
80 tasks.matching{ it.name =~ /^configureTs/ }.configureEach {
82 tasks.matching{ it.name =~ /^configureTs/ }.configureEach {
81 compilerOptions {
83 compilerOptions {
82 sourceRoot = "file://" + it.rootDir
84 sourceRoot = "file://" + it.rootDir
83 }
85 }
84 }
86 }
85 }
87 }
86
88
87 task printVersion {
89 task printVersion {
88 doLast {
90 doLast {
89 println "packageName: $packageName";
91 println "packageName: $packageName";
90 println "version: $version";
92 println "version: $version";
91 println "flavour: $flavour";
93 println "flavour: $flavour";
92 println "target: $typescript.compilerOptions.target";
94 println "target: $typescript.compilerOptions.target";
93 println "module: $typescript.compilerOptions.module";
95 println "module: $typescript.compilerOptions.module";
94 println "lint: $lint";
96 println "lint: $lint";
95 println "symbols: $symbols";
97 println "symbols: $symbols";
96 }
98 }
97 }
99 }
98
100
99 npmPackMeta {
101 npmPackMeta {
100 meta {
102 meta {
101 name = packageName
103 name = packageName
102 }
104 }
103 }
105 }
104
106
105 configureTsCjs {
107 configureTsCjs {
106 dependsOn sources.main.output
108 dependsOn sources.main.output
107 compilerOptions {
109 compilerOptions {
108 types += [ "node" ]
110 types += [ "node" ]
109 }
111 }
110 }
112 }
111
113
112 configureTsAmd {
114 configureTsAmd {
113 dependsOn sources.main.output
115 dependsOn sources.main.output
114 compilerOptions {
116 compilerOptions {
115 types += [ "requirejs", "dojo-typings" ]
117 types += [ "requirejs", "dojo-typings" ]
116 }
118 }
117 }
119 }
118
120
119 test {
121 test {
120 workingDir layout.buildDirectory.dir("test");
122 workingDir layout.buildDirectory.dir("test");
121 commandLine "node", "tests/index.js"
123 commandLine "node", "tests/index.js"
122 }
124 }
123
125
124 assemble {
126 assemble {
125 if (flavour == 'browser') {
127 if (flavour == 'browser') {
126 dependsOn sources.amd.output
128 dependsOn sources.amd.output
127 from sources.amd.output.compiledDir
129 from sources.amd.output.compiledDir
128 from sources.amd.resources
130 from sources.amd.resources
129 }
131 }
130 if (flavour == 'node') {
132 if (flavour == 'node') {
131 dependsOn sources.cjs.output
133 dependsOn sources.cjs.output
132 from sources.cjs.output.compiledDir
134 from sources.cjs.output.compiledDir
133 from sources.cjs.resources
135 from sources.cjs.resources
134 }
136 }
135 }
137 }
136
138
137 assembleTest {
139 assembleTest {
138 if (flavour == 'browser') {
140 if (flavour == 'browser') {
139 dependsOn sources.amd.output, sources.testAmd.output
141 dependsOn sources.amd.output, sources.testAmd.output
140
142
141 from sources.amd.output.compiledDir
143 from sources.amd.output.compiledDir
142 from sources.testAmd.output.compiledDir
144 from sources.testAmd.output.compiledDir
143 from sources.amd.resources
145 from sources.amd.resources
144 from sources.testAmd.resources
146 from sources.testAmd.resources
145 }
147 }
146 if (flavour == 'node') {
148 if (flavour == 'node') {
147 dependsOn sources.cjs.output, sources.testCjs.output
149 dependsOn sources.cjs.output, sources.testCjs.output
148
150
149 from sources.cjs.output.compiledDir
151 from sources.cjs.output.compiledDir
150 from sources.testCjs.output.compiledDir
152 from sources.testCjs.output.compiledDir
151 from sources.cjs.resources
153 from sources.cjs.resources
152 from sources.testCjs.resources
154 from sources.testCjs.resources
153 }
155 }
154 }
156 }
155
157
156 typings {
158 typings {
157 if (flavour == 'browser') {
159 if (flavour == 'browser') {
158 dependsOn sources.amd.output
160 dependsOn sources.amd.output
159 from sources.amd.output.typingsDir
161 from sources.amd.output.typingsDir
160 }
162 }
161 if (flavour == 'node') {
163 if (flavour == 'node') {
162 dependsOn sources.cjs.output
164 dependsOn sources.cjs.output
163 from sources.cjs.output.typingsDir
165 from sources.cjs.output.typingsDir
164 }
166 }
165 }
167 }
166
168
167 task npmPackTypings(type: Copy) {
169 task npmPackTypings(type: Copy) {
168 npmPackContents.dependsOn it
170 npmPackContents.dependsOn it
169 dependsOn sources.main.output
171 dependsOn sources.main.output
170
172
171 from sources.main.output.typingsDir
173 from sources.main.output.typingsDir
172
174
173 if (flavour == 'browser') {
175 if (flavour == 'browser') {
174 dependsOn sources.amd.output
176 dependsOn sources.amd.output
175 from sources.amd.output.typingsDir
177 from sources.amd.output.typingsDir
176 }
178 }
177 if (flavour == 'node') {
179 if (flavour == 'node') {
178 dependsOn sources.cjs.output
180 dependsOn sources.cjs.output
179 from sources.cjs.output.typingsDir
181 from sources.cjs.output.typingsDir
180 }
182 }
181
183
182 into npm.packageDir
184 into npm.packageDir
183 }
185 }
184
186
185 task npmPackSources(type: Copy) {
187 task npmPackSources(type: Copy) {
186 from sources.main.ts
188 from sources.main.ts
187 if (symbols == 'pack') {
189 if (symbols == 'pack') {
188 npmPackContents.dependsOn npmPackSources
190 npmPackContents.dependsOn npmPackSources
189 }
191 }
190
192
191 if (flavour == 'browser') {
193 if (flavour == 'browser') {
192 from sources.amd.ts
194 from sources.amd.ts
193 }
195 }
194 if (flavour == 'node') {
196 if (flavour == 'node') {
195 from sources.cjs.ts
197 from sources.cjs.ts
196 }
198 }
197
199
198 into npm.packageDir.dir("_src")
200 into npm.packageDir.dir("_src")
199 }
201 }
200
202
201
203
202
204
203 task packJsTar(type: Tar) {
205 task packJsTar(type: Tar) {
204 dependsOn assemble;
206 dependsOn assemble;
205
207
206 archiveBaseName = provider { packageName }
208 archiveBaseName = provider { packageName }
207
209
208 destinationDirectory = buildDir
210 destinationDirectory = buildDir
209 archiveClassifier = provider { typescript.compilerOptions.module }
211 archiveClassifier = provider { typescript.compilerOptions.module }
210 compression = Compression.GZIP
212 compression = Compression.GZIP
211
213
212 from(assemble.outputs)
214 from(assemble.outputs)
213
215
214 doLast {
216 doLast {
215 println archiveName;
217 println archiveName;
216 }
218 }
217 }
219 }
218
220
219 task packTypingsTar(type: Tar) {
221 task packTypingsTar(type: Tar) {
220 }
222 }
221
223
222 publishing {
224 publishing {
223 publications {
225 publications {
224 local(IvyPublication) {
226 local(IvyPublication) {
225 artifact(packJsTar) {
227 artifact(packJsTar) {
226 type = "js"
228 type = "js"
227 }
229 }
228 }
230 }
229 }
231 }
230
232
231 repositories {
233 repositories {
232 ivy {
234 ivy {
233 url "ivy-repo"
235 url "ivy-repo"
234 }
236 }
235 }
237 }
236 } No newline at end of file
238 }
@@ -1,45 +1,45
1 import { Uuid } from "../Uuid";
1 import { Uuid } from "../Uuid";
2 import { argumentNotEmptyString, getGlobal } from "../safe";
2 import { argumentNotEmptyString, getGlobal } from "../safe";
3 import { TraceSource } from "../log/TraceSource";
3 import { TraceSource } from "../log/TraceSource";
4 import m = require("module");
4 import m = require("module");
5
5
6 const sandboxId = Uuid();
6 const sandboxId = Uuid();
7 define(sandboxId, ["require"], (r: any) => r);
7 define(sandboxId, ["require"], (r: any) => r);
8
8
9 const globalRequire = getGlobal().require as Require || requirejs;
9 const globalRequire = getGlobal().require as Require || requirejs;
10
10
11 const trace = TraceSource.get(m.id);
11 const trace = TraceSource.get(m.id);
12 trace.debug("globalRequire = {0}", globalRequire);
12 trace.debug("globalRequire = {0}", globalRequire);
13
13
14 class ModuleResolver {
14 class ModuleResolver {
15 _base: string;
15 _base: string | undefined;
16 _require: Require;
16 _require: Require;
17
17
18 constructor(req: Require, base?: string) {
18 constructor(req: Require, base?: string) {
19 this._base = base;
19 this._base = base;
20 this._require = req;
20 this._require = req;
21 }
21 }
22
22
23 resolve(moduleName: string) {
23 resolve(moduleName: string) {
24 argumentNotEmptyString(moduleName, "moduleName");
24 argumentNotEmptyString(moduleName, "moduleName");
25 const resolvedName = moduleName[0] === "." && this._base ? [this._base, moduleName].join("/") : moduleName;
25 const resolvedName = moduleName[0] === "." && this._base ? [this._base, moduleName].join("/") : moduleName;
26 trace.debug(`${moduleName} -> ${resolvedName}`);
26 trace.debug(`${moduleName} -> ${resolvedName}`);
27
27
28 const req = this._require;
28 const req = this._require;
29
29
30 return new Promise<any>((cb, eb) => {
30 return new Promise<any>((cb, eb) => {
31 req([resolvedName], cb, eb);
31 req([resolvedName], cb, eb);
32 });
32 });
33 }
33 }
34 }
34 }
35
35
36 export function makeResolver(moduleName: string, contextRequire: Require) {
36 export function makeResolver(moduleName: string, contextRequire: Require) {
37 const base = moduleName && moduleName.split("/").slice(0, -1).join("/");
37 const base = moduleName && moduleName.split("/").slice(0, -1).join("/");
38
38
39 const req = contextRequire || globalRequire;
39 const req = contextRequire || globalRequire;
40 if (!req)
40 if (!req)
41 throw new Error("A global require isn't defined, the contextRequire parameter is mandatory");
41 throw new Error("A global require isn't defined, the contextRequire parameter is mandatory");
42
42
43 const resolver = new ModuleResolver(req, base);
43 const resolver = new ModuleResolver(req, base);
44 return (id: string) => resolver.resolve(id);
44 return (id: string) => resolver.resolve(id);
45 }
45 }
@@ -1,48 +1,48
1 import { TraceSource } from "./TraceSource";
1 import { TraceSource } from "./TraceSource";
2 import { Predicate } from "../interfaces";
2 import { Predicate } from "../interfaces";
3
3
4 export = {
4 export = {
5 level: 0,
5 level: 0,
6
6
7 on(filter: any, cb: any) {
7 on(filter: any, cb: any) {
8 if (arguments.length === 1) {
8 if (arguments.length === 1) {
9 cb = filter;
9 cb = filter;
10 filter = undefined;
10 filter = undefined;
11 }
11 }
12 let test: Predicate<string>;
12 let test: Predicate<string> | undefined;
13 if (filter instanceof RegExp) {
13 if (filter instanceof RegExp) {
14 test = chId => filter.test(chId);
14 test = chId => filter.test(chId);
15 } else if (filter instanceof Function) {
15 } else if (filter instanceof Function) {
16 test = filter;
16 test = filter;
17 } else if (filter) {
17 } else if (filter) {
18 test = chId => chId === filter;
18 test = chId => chId === filter;
19 }
19 }
20
20
21 if (test) {
21 if (test) {
22 TraceSource.on(source => {
22 TraceSource.on(source => {
23 source.level = this.level;
23 source.level = this.level;
24 if (test(source.id))
24 if (test && test(source.id))
25 source.events.on(cb);
25 source.events.on(cb);
26 });
26 });
27 } else {
27 } else {
28 TraceSource.on(source => {
28 TraceSource.on(source => {
29 source.level = this.level;
29 source.level = this.level;
30 source.events.on(cb);
30 source.events.on(cb);
31 });
31 });
32 }
32 }
33 },
33 },
34
34
35 load(id: string, require: any, cb: (trace: TraceSource) => void) {
35 load(id: string, require: any, cb: (trace: TraceSource) => void) {
36 if (id) {
36 if (id) {
37 cb(TraceSource.get(id));
37 cb(TraceSource.get(id));
38 } else if (require.module && require.module.mid) {
38 } else if (require.module && require.module.mid) {
39 cb(TraceSource.get(require.module.mid));
39 cb(TraceSource.get(require.module.mid));
40 } else {
40 } else {
41 require(["module"], (module: { id: any; }) => {
41 require(["module"], (module: { id: any; }) => {
42 cb(TraceSource.get(module && module.id));
42 cb(TraceSource.get(module && module.id));
43 });
43 });
44 }
44 }
45 },
45 },
46
46
47 dynamic: true
47 dynamic: true
48 };
48 };
@@ -1,135 +1,136
1 import * as format from "./format";
1 import * as format from "./format";
2 import { TraceSource, DebugLevel } from "../log/TraceSource";
2 import { TraceSource, DebugLevel } from "../log/TraceSource";
3 import { ITemplateParser, TokenType } from "./TemplateParser";
3 import { ITemplateParser, TokenType } from "./TemplateParser";
4 import m = require("module");
4 import m = require("module");
5 import { isKeyof } from "../safe";
5
6
6 const trace = TraceSource.get(m.id);
7 const trace = TraceSource.get(m.id);
7
8
8 type TemplateFn = (obj: object) => string;
9 type TemplateFn = (obj: object) => string;
9
10
10 const htmlEscapes = {
11 const htmlEscapes = {
11 "&": "&amp;",
12 "&": "&amp;",
12 "<": "&lt;",
13 "<": "&lt;",
13 ">": "&gt;",
14 ">": "&gt;",
14 '"': "&quot;",
15 '"': "&quot;",
15 "'": "&#x27;",
16 "'": "&#x27;",
16 "/": "&#x2F;"
17 "/": "&#x2F;"
17 };
18 };
18
19
19 // Regex containing the keys listed immediately above.
20 // Regex containing the keys listed immediately above.
20 const htmlEscaper = /[&<>"'\/]/g;
21 const htmlEscaper = /[&<>"'\/]/g;
21
22
22 // Escape a string for HTML interpolation.
23 // Escape a string for HTML interpolation.
23 function escapeHtml(string: any) {
24 function escapeHtml(string: any) {
24 return ("" + string).replace(htmlEscaper, match => htmlEscapes[match]);
25 return ("" + string).replace(htmlEscaper, match => isKeyof(match, htmlEscapes) ? htmlEscapes[match] : "");
25 }
26 }
26
27
27 export class TemplateCompiler {
28 export class TemplateCompiler {
28
29
29 _data: string[];
30 _data: string[];
30 _code: string[];
31 _code: string[];
31 _wrapWith = true;
32 _wrapWith = true;
32
33
33 constructor() {
34 constructor() {
34 this._code = [];
35 this._code = [];
35 this._data = [];
36 this._data = [];
36 }
37 }
37
38
38 compile(parser: ITemplateParser): TemplateFn {
39 compile(parser: ITemplateParser): TemplateFn {
39 this.preamble();
40 this.preamble();
40 this.visitTemplate(parser);
41 this.visitTemplate(parser);
41 this.postamble();
42 this.postamble();
42
43
43 const text = this._code.join("\n");
44 const text = this._code.join("\n");
44
45
45 try {
46 try {
46 // tslint:disable-next-line:function-constructor
47 // tslint:disable-next-line:function-constructor
47 const compiled = new Function("obj, format, $data, escapeHtml", text);
48 const compiled = new Function("obj, format, $data, escapeHtml", text);
48 /**
49 /**
49 * Функция форматирования по шаблону
50 * Функция форматирования по шаблону
50 *
51 *
51 * @type{Function}
52 * @type{Function}
52 * @param{Object} obj объект с параметрами для подстановки
53 * @param{Object} obj объект с параметрами для подстановки
53 */
54 */
54 return (obj: object) => compiled(obj || {}, format, this._data, escapeHtml);
55 return (obj: object) => compiled(obj || {}, format, this._data, escapeHtml);
55 } catch (e) {
56 } catch (e) {
56 trace.traceEvent(DebugLevel, [e, text, this._data]);
57 trace.traceEvent(DebugLevel, [e, text, this._data]);
57 throw e;
58 throw e;
58 }
59 }
59 }
60 }
60
61
61 preamble() {
62 preamble() {
62 this._code.push(
63 this._code.push(
63 "var $p = [];",
64 "var $p = [];",
64 "var print = function(){",
65 "var print = function(){",
65 " $p.push(format.apply(null,arguments));",
66 " $p.push(format.apply(null,arguments));",
66 "};"
67 "};"
67 );
68 );
68
69
69 if (this._wrapWith)
70 if (this._wrapWith)
70 this._code.push("with(obj){");
71 this._code.push("with(obj){");
71 }
72 }
72
73
73 postamble() {
74 postamble() {
74 if (this._wrapWith)
75 if (this._wrapWith)
75 this._code.push("}");
76 this._code.push("}");
76
77
77 this._code.push("return $p.join('');");
78 this._code.push("return $p.join('');");
78 }
79 }
79
80
80 visitTemplate(parser: ITemplateParser) {
81 visitTemplate(parser: ITemplateParser) {
81 while (parser.next()) {
82 while (parser.next()) {
82 switch (parser.token()) {
83 switch (parser.token()) {
83 case TokenType.OpenBlock:
84 case TokenType.OpenBlock:
84 this.visitCode(parser);
85 this.visitCode(parser);
85 break;
86 break;
86 case TokenType.OpenInlineBlock:
87 case TokenType.OpenInlineBlock:
87 this.visitInline(parser);
88 this.visitInline(parser);
88 break;
89 break;
89 case TokenType.OpenFilterBlock:
90 case TokenType.OpenFilterBlock:
90 this.visitFilter(parser);
91 this.visitFilter(parser);
91 break;
92 break;
92 default:
93 default:
93 this.visitTextFragment(parser);
94 this.visitTextFragment(parser);
94 break;
95 break;
95 }
96 }
96 }
97 }
97 }
98 }
98
99
99 visitInline(parser: ITemplateParser) {
100 visitInline(parser: ITemplateParser) {
100 const code = ["$p.push("];
101 const code = ["$p.push("];
101 while (parser.next()) {
102 while (parser.next()) {
102 if (parser.token() === TokenType.CloseBlock)
103 if (parser.token() === TokenType.CloseBlock)
103 break;
104 break;
104 code.push(parser.value());
105 code.push(parser.value());
105 }
106 }
106 code.push(");");
107 code.push(");");
107 this._code.push(code.join(""));
108 this._code.push(code.join(""));
108 }
109 }
109
110
110 visitFilter(parser: ITemplateParser) {
111 visitFilter(parser: ITemplateParser) {
111 const code = ["$p.push(escapeHtml("];
112 const code = ["$p.push(escapeHtml("];
112 while (parser.next()) {
113 while (parser.next()) {
113 if (parser.token() === TokenType.CloseBlock)
114 if (parser.token() === TokenType.CloseBlock)
114 break;
115 break;
115 code.push(parser.value());
116 code.push(parser.value());
116 }
117 }
117 code.push("));");
118 code.push("));");
118 this._code.push(code.join(""));
119 this._code.push(code.join(""));
119 }
120 }
120
121
121 visitCode(parser: ITemplateParser) {
122 visitCode(parser: ITemplateParser) {
122 const code = [];
123 const code = [];
123 while (parser.next()) {
124 while (parser.next()) {
124 if (parser.token() === TokenType.CloseBlock)
125 if (parser.token() === TokenType.CloseBlock)
125 break;
126 break;
126 code.push(parser.value());
127 code.push(parser.value());
127 }
128 }
128 this._code.push(code.join(""));
129 this._code.push(code.join(""));
129 }
130 }
130
131
131 visitTextFragment(parser: ITemplateParser) {
132 visitTextFragment(parser: ITemplateParser) {
132 const i = this._data.push(parser.value()) - 1;
133 const i = this._data.push(parser.value()) - 1;
133 this._code.push("$p.push($data[" + i + "]);");
134 this._code.push("$p.push($data[" + i + "]);");
134 }
135 }
135 }
136 }
@@ -1,72 +1,74
1 import { argumentNotEmptyString } from "../safe";
1 import { argumentNotEmptyString } from "../safe";
2 import { MapOf } from "../interfaces";
2 import { MapOf } from "../interfaces";
3 import { TraceSource, DebugLevel } from "../log/TraceSource";
3 import { TraceSource, DebugLevel } from "../log/TraceSource";
4 import m = require("module");
4 import m = require("module");
5
5
6 const trace = TraceSource.get(m.id);
6 const trace = TraceSource.get(m.id);
7
7
8 const splitRx = /(<%=|<%~|\[%~|\[%=|<%|\[%|%\]|%>)/;
8 const splitRx = /(<%=|<%~|\[%~|\[%=|<%|\[%|%\]|%>)/;
9
9
10 export enum TokenType {
10 export enum TokenType {
11 None,
11 None,
12 Text,
12 Text,
13 OpenInlineBlock,
13 OpenInlineBlock,
14 OpenFilterBlock,
14 OpenFilterBlock,
15 OpenBlock,
15 OpenBlock,
16 CloseBlock
16 CloseBlock
17 }
17 }
18
18
19 const tokenMap: MapOf<TokenType> = {
19 const tokenMap: MapOf<TokenType> = {
20 "<%": TokenType.OpenBlock,
20 "<%": TokenType.OpenBlock,
21 "[%": TokenType.OpenBlock,
21 "[%": TokenType.OpenBlock,
22 "<%=": TokenType.OpenInlineBlock,
22 "<%=": TokenType.OpenInlineBlock,
23 "[%=": TokenType.OpenInlineBlock,
23 "[%=": TokenType.OpenInlineBlock,
24 "<%~": TokenType.OpenFilterBlock,
24 "<%~": TokenType.OpenFilterBlock,
25 "[%~": TokenType.OpenFilterBlock,
25 "[%~": TokenType.OpenFilterBlock,
26 "%>": TokenType.CloseBlock,
26 "%>": TokenType.CloseBlock,
27 "%]": TokenType.CloseBlock
27 "%]": TokenType.CloseBlock
28 };
28 };
29
29
30 export interface ITemplateParser {
30 export interface ITemplateParser {
31 next(): boolean;
31 next(): boolean;
32 token(): TokenType;
32 token(): TokenType;
33 value(): string;
33 value(): string;
34 }
34 }
35
35
36 export class TemplateParser implements ITemplateParser {
36 export class TemplateParser implements ITemplateParser {
37
37
38 _tokens: string[];
38 _tokens: string[];
39 _pos = -1;
39 _pos = -1;
40 _type: TokenType;
40 _type: TokenType;
41 _value: string;
41 _value: string | undefined;
42
42
43 constructor(text: string) {
43 constructor(text: string) {
44 argumentNotEmptyString(text, "text");
44 argumentNotEmptyString(text, "text");
45
45
46 this._tokens = text.split(splitRx);
46 this._tokens = text.split(splitRx);
47 this._type = TokenType.None;
47 this._type = TokenType.None;
48 }
48 }
49
49
50 next() {
50 next() {
51 this._pos++;
51 this._pos++;
52 if (this._pos < this._tokens.length) {
52 if (this._pos < this._tokens.length) {
53 this._value = this._tokens[this._pos];
53 this._value = this._tokens[this._pos];
54 this._type = tokenMap[this._value] || TokenType.Text;
54 this._type = tokenMap[this._value] || TokenType.Text;
55
55
56 return true;
56 return true;
57 } else {
57 } else {
58 this._type = TokenType.None;
58 this._type = TokenType.None;
59 this._value = undefined;
59 this._value = undefined;
60 return false;
60 return false;
61 }
61 }
62 }
62 }
63
63
64 token() {
64 token() {
65 return this._type;
65 return this._type;
66 }
66 }
67
67
68 value() {
68 value() {
69 if (!this._value)
70 throw new Error("The current token doesn't have a value");
69 return this._value;
71 return this._value;
70 }
72 }
71
73
72 }
74 }
@@ -1,67 +1,72
1 import { format as dojoFormatNumber } from "dojo/number";
1 import { format as dojoFormatNumber } from "dojo/number";
2 import { format as dojoFormatDate } from "dojo/date/locale";
2 import { format as dojoFormatDate } from "dojo/date/locale";
3 import { Formatter, compile as _compile } from "./StringFormat";
3 import { Formatter, compile as _compile } from "./StringFormat";
4
4
5 import { isNumber, isNull } from "../safe";
5 import { isNumber, isNull, get } from "../safe";
6
6
7 interface NumberFormatOptions {
7 interface NumberFormatOptions {
8 round?: number;
8 round?: number;
9 pattern?: string;
9 pattern?: string;
10 }
10 }
11
11
12 function convertNumber(value: any, pattern: string) {
12 function convertNumber(value: any, _pattern?: string) {
13 if (isNumber(value)) {
13 if (isNumber(value)) {
14 const nopt = {} as NumberFormatOptions;
14 const nopt = {} as NumberFormatOptions;
15 if (pattern.indexOf("!") === 0) {
15 let pattern = _pattern;
16 if (pattern && pattern.indexOf("!") === 0) {
16 nopt.round = -1;
17 nopt.round = -1;
17 pattern = pattern.substr(1);
18 pattern = pattern.substr(1);
18 }
19 }
19 nopt.pattern = pattern;
20 nopt.pattern = pattern;
20
21
21 return dojoFormatNumber(value, nopt);
22 return dojoFormatNumber(value, nopt);
23 } else {
24 return "";
22 }
25 }
23 }
26 }
24
27
25 function convertDate(value: any, pattern: string) {
28 function convertDate(value: any, pattern?: string) {
26 if (value instanceof Date) {
29 if (value instanceof Date) {
27 const m = pattern.match(/^(\w+)-(\w+)$/);
30 const m = pattern && pattern.match(/^(\w+)-(\w+)$/);
28 if (m)
31 if (m)
29 return dojoFormatDate(value, {
32 return dojoFormatDate(value, {
30 selector: m[2],
33 selector: m[2],
31 formatLength: m[1]
34 formatLength: m[1]
32 });
35 });
33 else if (pattern === "iso")
36 else if (pattern === "iso")
34 return value.toISOString();
37 return value.toISOString();
35 else
38 else
36 return dojoFormatDate(value, {
39 return dojoFormatDate(value, {
37 selector: "date",
40 selector: "date",
38 datePattern: pattern
41 datePattern: pattern
39 });
42 });
43 } else {
44 return "";
40 }
45 }
41 }
46 }
42
47
43 const _formatter = new Formatter([convertNumber, convertDate]);
48 const _formatter = new Formatter([convertNumber, convertDate]);
44
49
45 function format(msg: string, ...args: any[]) {
50 function format(msg: string, ...args: any[]) {
46 return _formatter.format(msg, ...args);
51 return _formatter.format(msg, ...args);
47 }
52 }
48
53
49 function _convert(value: any, pattern: string) {
54 function _convert(value: any, pattern?: string) {
50 return _formatter.convert(value, pattern);
55 return _formatter.convert(value, pattern);
51 }
56 }
52
57
53 namespace format {
58 namespace format {
54 export const convert = _convert;
59 export const convert = _convert;
55 export function compile(text: string) {
60 export function compile(text: string) {
56 const template = _compile(text);
61 const template = _compile(text);
57
62
58 return (...data) => {
63 return (...data: any[]) => {
59 return template((name, pattern) => {
64 return template((name, pattern) => {
60 const value = data[name];
65 const value = get(name, data);
61 return !isNull(value) ? convert(value, pattern) : "";
66 return !isNull(value) ? convert(value, pattern) : "";
62 });
67 });
63 };
68 };
64 }
69 }
65 }
70 }
66
71
67 export = format;
72 export = format;
@@ -1,49 +1,49
1 import request = require("dojo/request");
1 import request = require("dojo/request");
2 import m = require("module");
2 import m = require("module");
3 import { TraceSource } from "../log/TraceSource";
3 import { TraceSource } from "../log/TraceSource";
4 import { TemplateCompiler } from "./TemplateCompiler";
4 import { TemplateCompiler } from "./TemplateCompiler";
5 import { TemplateParser } from "./TemplateParser";
5 import { TemplateParser } from "./TemplateParser";
6 import { isNullOrEmptyString } from "../safe";
6 import { isNullOrEmptyString } from "../safe";
7 import { MapOf } from "../interfaces";
7 import { MapOf } from "../interfaces";
8
8
9 type TemplateFn = (obj: object) => string;
9 type TemplateFn = (obj: object) => string;
10
10
11 const trace = TraceSource.get(m.id);
11 const trace = TraceSource.get(m.id);
12
12
13 function compile(str: string) {
13 function compile(str: string) {
14 if (isNullOrEmptyString(str))
14 if (isNullOrEmptyString(str))
15 return () => "";
15 return () => "";
16
16
17 const parser = new TemplateParser(str);
17 const parser = new TemplateParser(str);
18 const compiler = new TemplateCompiler();
18 const compiler = new TemplateCompiler();
19
19
20 return compiler.compile(parser);
20 return compiler.compile(parser);
21 }
21 }
22
22
23 const cache: MapOf<TemplateFn> = {};
23 const cache: MapOf<TemplateFn> = {};
24
24
25 interface OnLoadFn<T> {
25 interface OnLoadFn<T> {
26 (res: T): void;
26 (res: T): void;
27 error(e: any): void;
27 error(e: any): void;
28 }
28 }
29
29
30 compile.load = (id: string, require: Require, callback: OnLoadFn<TemplateFn>) => {
30 compile.load = (id: string, require: Require, callback: OnLoadFn<TemplateFn>) => {
31 const url = require.toUrl(id);
31 const url = require.toUrl(id);
32 if (url in cache) {
32 if (url in cache) {
33 trace.debug("{0} -> {1}: cached", id, url);
33 trace.debug("{0} -> {1}: cached", id, url);
34 callback(cache[url]);
34 callback(cache[url]);
35 } else {
35 } else {
36 trace.debug("{0} -> {1}: load", id, url);
36 trace.debug("{0} -> {1}: load", id, url);
37 request(url).then(compile).then((tc: TemplateFn) => {
37 request<string>(url).then(compile).then((tc: TemplateFn) => {
38 trace.debug("{0}: compiled", url);
38 trace.debug("{0}: compiled", url);
39 callback(cache[url] = tc);
39 callback(cache[url] = tc);
40 }, (err: any) => {
40 }, (err: any) => {
41 callback.error({
41 callback.error({
42 inner: err,
42 inner: err,
43 src: "@implab/core/text/template-compile"
43 src: "@implab/core/text/template-compile"
44 });
44 });
45 });
45 });
46 }
46 }
47 };
47 };
48
48
49 export = compile;
49 export = compile;
@@ -1,33 +1,33
1 import { argumentNotEmptyString } from "../safe";
1 import { argumentNotEmptyString } from "../safe";
2 import { TraceSource } from "../log/TraceSource";
2 import { TraceSource } from "../log/TraceSource";
3
3
4 const trace = TraceSource.get(module.id);
4 const trace = TraceSource.get(module.id);
5
5
6 const mainModule = require.main;
6 const mainModule = require.main;
7 const mainRequire = (id: string) => mainModule.require(id);
7 const mainRequire = (id: string) => mainModule ? mainModule.require(id) : require;
8
8
9 class ModuleResolver {
9 class ModuleResolver {
10 _base: string;
10 _base: string | undefined;
11 _require: NodeRequireFunction;
11 _require: NodeRequireFunction;
12
12
13 constructor(req: NodeRequireFunction, base?: string) {
13 constructor(req: NodeRequireFunction, base?: string) {
14 this._base = base;
14 this._base = base;
15 this._require = (req || mainRequire).bind(null);
15 this._require = (req || mainRequire).bind(null);
16 }
16 }
17
17
18 resolve(moduleName: string) {
18 resolve(moduleName: string) {
19 argumentNotEmptyString(moduleName, "moduleName");
19 argumentNotEmptyString(moduleName, "moduleName");
20 const resolvedName = moduleName[0] === "." && this._base ? [this._base, moduleName].join("/") : moduleName;
20 const resolvedName = moduleName[0] === "." && this._base ? [this._base, moduleName].join("/") : moduleName;
21
21
22 trace.debug(`${moduleName} -> ${resolvedName}`);
22 trace.debug(`${moduleName} -> ${resolvedName}`);
23
23
24 return this._require(resolvedName);
24 return this._require(resolvedName);
25 }
25 }
26 }
26 }
27
27
28 export function makeResolver(moduleName: string, contextRequire: NodeRequireFunction) {
28 export function makeResolver(moduleName: string, contextRequire: NodeRequireFunction) {
29 const base = moduleName && moduleName.split("/").slice(0, -1).join("/");
29 const base = moduleName && moduleName.split("/").slice(0, -1).join("/");
30
30
31 const resolver = new ModuleResolver(contextRequire, base);
31 const resolver = new ModuleResolver(contextRequire, base);
32 return (id: string) => resolver.resolve(id);
32 return (id: string) => resolver.resolve(id);
33 }
33 }
@@ -1,83 +1,83
1 import { ICancellation, IDestroyable } from "./interfaces";
1 import { ICancellation, IDestroyable } from "./interfaces";
2 import { argumentNotNull, destroyed } from "./safe";
2 import { argumentNotNull, destroyed } from "./safe";
3
3
4 export class Cancellation implements ICancellation {
4 export class Cancellation implements ICancellation {
5 private _reason: any;
5 private _reason: any;
6 private _cbs: Array<(e: any) => void>;
6 private _cbs: Array<(e: any) => void> | undefined;
7
7
8 constructor(action: (cancel: (e?: any) => void) => void) {
8 constructor(action: (cancel: (e?: any) => void) => void) {
9 argumentNotNull(action, "action");
9 argumentNotNull(action, "action");
10
10
11 action(this._cancel.bind(this));
11 action(this._cancel.bind(this));
12 }
12 }
13
13
14 isSupported(): boolean {
14 isSupported(): boolean {
15 return true;
15 return true;
16 }
16 }
17 throwIfRequested(): void {
17 throwIfRequested(): void {
18 if (this._reason)
18 if (this._reason)
19 throw this._reason;
19 throw this._reason;
20 }
20 }
21
21
22 isRequested(): boolean {
22 isRequested(): boolean {
23 return !!this._reason;
23 return !!this._reason;
24 }
24 }
25
25
26 register(cb: (e: any) => void): IDestroyable {
26 register(cb: (e: any) => void): IDestroyable {
27 argumentNotNull(cb, "cb");
27 argumentNotNull(cb, "cb");
28
28
29 if (this._reason) {
29 if (this._reason) {
30 cb(this._reason);
30 cb(this._reason);
31 return destroyed;
31 return destroyed;
32 } else {
32 } else {
33 if (!this._cbs)
33 if (!this._cbs)
34 this._cbs = [cb];
34 this._cbs = [cb];
35 else
35 else
36 this._cbs.push(cb);
36 this._cbs.push(cb);
37
37
38 const me = this;
38 const me = this;
39 return {
39 return {
40 destroy() {
40 destroy() {
41 me._unregister(cb);
41 me._unregister(cb);
42 }
42 }
43 };
43 };
44 }
44 }
45 }
45 }
46
46
47 private _unregister(cb) {
47 private _unregister(cb: any) {
48 if (this._cbs) {
48 if (this._cbs) {
49 const i = this._cbs.indexOf(cb);
49 const i = this._cbs.indexOf(cb);
50 if (i >= 0)
50 if (i >= 0)
51 this._cbs.splice(i, 1);
51 this._cbs.splice(i, 1);
52 }
52 }
53 }
53 }
54
54
55 private _cancel(reason) {
55 private _cancel(reason: any) {
56 if (this._reason)
56 if (this._reason)
57 return;
57 return;
58
58
59 this._reason = (reason = reason || new Error("Operation cancelled"));
59 this._reason = (reason = reason || new Error("Operation cancelled"));
60
60
61 if (this._cbs) {
61 if (this._cbs) {
62 this._cbs.forEach(cb => cb(reason));
62 this._cbs.forEach(cb => cb(reason));
63 this._cbs = null;
63 this._cbs = undefined;
64 }
64 }
65 }
65 }
66
66
67 static readonly none: ICancellation = {
67 static readonly none: ICancellation = {
68 isSupported(): boolean {
68 isSupported(): boolean {
69 return false;
69 return false;
70 },
70 },
71
71
72 throwIfRequested(): void {
72 throwIfRequested(): void {
73 },
73 },
74
74
75 isRequested(): boolean {
75 isRequested(): boolean {
76 return false;
76 return false;
77 },
77 },
78
78
79 register(_cb: (e: any) => void): IDestroyable {
79 register(_cb: (e: any) => void): IDestroyable {
80 return destroyed;
80 return destroyed;
81 }
81 }
82 };
82 };
83 }
83 }
@@ -1,208 +1,205
1 import { IObservable, IDestroyable, ICancellation, IObserver } from "./interfaces";
1 import { IObservable, IDestroyable, ICancellation, IObserver } from "./interfaces";
2 import { Cancellation } from "./Cancellation";
2 import { Cancellation } from "./Cancellation";
3 import { argumentNotNull, destroyed } from "./safe";
3 import { argumentNotNull, destroyed } from "./safe";
4
4
5 type Handler<T> = (x: T) => void;
5 type Handler<T> = (x: T) => void;
6
6
7 type Initializer<T> = (notify: Handler<T>, error?: (e: any) => void, complete?: () => void) => void;
7 type Initializer<T> = (notify: Handler<T>, error: (e: any) => void, complete: () => void) => void;
8
8
9 const noop = () => { };
9 const noop = () => { };
10
10
11 const nulObserver: IObserver<any> = Object.freeze({
12 next: noop,
13 error: noop,
14 complete: noop
15 });
16
11 function isObserver(val: any): val is IObserver<any> {
17 function isObserver(val: any): val is IObserver<any> {
12 return val && (typeof val.next === "function");
18 return val && (typeof val.next === "function");
13 }
19 }
14
20
15 export class Observable<T> implements IObservable<T> {
21 export class Observable<T> implements IObservable<T> {
16 private _once = new Array<IObserver<T>>();
22 private _once = new Array<IObserver<T>>();
17
23
18 private _observers = new Array<IObserver<T>>();
24 private _observers = new Array<IObserver<T>>();
19
25
20 private _complete: boolean;
26 private _complete = false;
21
27
22 private _error: any;
28 private _error: any;
23
29
24 constructor(func?: Initializer<T>) {
30 constructor(func?: Initializer<T>) {
25 if (func)
31 if (func)
26 func(
32 func(
27 this._notifyNext.bind(this),
33 this._notifyNext.bind(this),
28 this._notifyError.bind(this),
34 this._notifyError.bind(this),
29 this._notifyCompleted.bind(this)
35 this._notifyCompleted.bind(this)
30 );
36 );
31 }
37 }
32
38
33 /**
39 /**
34 * Registers handlers for the current observable object.
40 * Registers handlers for the current observable object.
35 *
41 *
36 * @param next the handler for events
42 * @param next the handler for events
37 * @param error the handler for a error
43 * @param error the handler for a error
38 * @param complete the handler for a completion
44 * @param complete the handler for a completion
39 * @returns {IDestroyable} the handler for the current subscription, this
45 * @returns {IDestroyable} the handler for the current subscription, this
40 * handler can be used to unsubscribe from events.
46 * handler can be used to unsubscribe from events.
41 *
47 *
42 */
48 */
43 on(next: Handler<T>, error?: Handler<any>, complete?: () => void): IDestroyable {
49 on(next: Handler<T>, error?: Handler<any>, complete?: () => void): IDestroyable {
44 argumentNotNull(next, "next");
50 argumentNotNull(next, "next");
45
51
46 const me = this;
52 const me = this;
47
53
48 const observer: IObserver<T> & IDestroyable = {
54 const observer: IObserver<T> & IDestroyable = {
49 next,
55 next: next.bind(null),
50 error: error ? error.bind(null) : noop,
56 error: error ? error.bind(null) : noop,
51 complete: complete ? complete.bind(null) : noop,
57 complete: complete ? complete.bind(null) : noop,
52
58
53 destroy() {
59 destroy() {
54 me._removeObserver(this);
60 me._removeObserver(this);
55 }
61 }
56 };
62 };
57
63
58 this._addObserver(observer);
64 this._addObserver(observer);
59
65
60 return observer;
66 return observer;
61 }
67 }
62
68
63 subscribe(next: IObserver<T> | Handler<T>, error?: Handler<any>, complete?: () => void): IDestroyable {
69 subscribe(next: IObserver<T> | Handler<T>, error?: Handler<any>, complete?: () => void): IDestroyable {
64 if (isObserver(next)) {
70 if (isObserver(next)) {
65 const me = this;
71 this._addObserver(next);
66 const subscription = {
72 return {
67 destroy() {
73 destroy: () => this._removeObserver(next)
68 me._removeObserver(next);
69 }
70 };
74 };
71 this._addObserver(next);
75 } else {
72 return subscription;
73 } else if (next) {
74 const observer = {
76 const observer = {
75 next,
77 next: next.bind(null),
76 error,
78 error: error ? error.bind(null) : noop,
77 complete
79 complete: complete ? complete.bind(null) : noop
78 };
80 };
79 const me = this;
81
80 const subscription = {
82 this._addObserver(observer);
81 destroy() {
83 return {
82 me._removeObserver(observer);
84 destroy: () => this._removeObserver(observer)
83 }
84 };
85 };
85 this._addObserver(observer);
86 return subscription;
87 } else {
88 return destroyed;
89 }
86 }
90 }
87 }
91
88
92 private _addObserver(observer: IObserver<T>) {
89 private _addObserver(observer: IObserver<T>) {
93 if (this._complete) {
90 if (this._complete) {
94 try {
91 try {
95 if (this._error)
92 if (this._error)
96 observer.error(this._error);
93 observer.error(this._error);
97 else
94 else
98 observer.complete();
95 observer.complete();
99 } catch (e) {
96 } catch (e) {
100 this.onObserverException(e);
97 this.onObserverException(e);
101 }
98 }
102 } else {
99 } else {
103 this._observers.push(observer);
100 this._observers.push(observer);
104 }
101 }
105 }
102 }
106
103
107 /**
104 /**
108 * Waits for the next event. This method can't be used to read messages
105 * Waits for the next event. This method can't be used to read messages
109 * as a sequence since it can skip some messages between calls.
106 * as a sequence since it can skip some messages between calls.
110 *
107 *
111 * @param ct a cancellation token
108 * @param ct a cancellation token
112 */
109 */
113 next(ct: ICancellation = Cancellation.none) {
110 next(ct: ICancellation = Cancellation.none) {
114 return new Promise<T>((resolve, reject) => {
111 return new Promise<T>((resolve, reject) => {
115 const observer: IObserver<T> = {
112 const observer: IObserver<T> = {
116 next: resolve,
113 next: resolve,
117 error: reject,
114 error: reject,
118 complete: () => reject("No more events are available")
115 complete: () => reject("No more events are available")
119 };
116 };
120
117
121 if (this._addOnce(observer) && ct.isSupported()) {
118 if (this._addOnce(observer) && ct.isSupported()) {
122 ct.register(e => {
119 ct.register(e => {
123 this._removeOnce(observer);
120 this._removeOnce(observer);
124 reject(e);
121 reject(e);
125 });
122 });
126 }
123 }
127 });
124 });
128 }
125 }
129
126
130 private _addOnce(observer: IObserver<T>) {
127 private _addOnce(observer: IObserver<T>) {
131 if (this._complete) {
128 if (this._complete) {
132 try {
129 try {
133 if (this._error)
130 if (this._error)
134 observer.error(this._error);
131 observer.error(this._error);
135 else
132 else
136 observer.complete();
133 observer.complete();
137 } catch (e) {
134 } catch (e) {
138 this.onObserverException(e);
135 this.onObserverException(e);
139 }
136 }
140 return false;
137 return false;
141 }
138 }
142
139
143 this._once.push(observer);
140 this._once.push(observer);
144 return true;
141 return true;
145 }
142 }
146
143
147 protected onObserverException(e: any) {
144 protected onObserverException(e: any) {
148 }
145 }
149
146
150 private _removeOnce(d: IObserver<T>) {
147 private _removeOnce(d: IObserver<T>) {
151 const i = this._once.indexOf(d);
148 const i = this._once.indexOf(d);
152 if (i >= 0)
149 if (i >= 0)
153 this._once.splice(i, 1);
150 this._once.splice(i, 1);
154 }
151 }
155
152
156 private _removeObserver(d: IObserver<T>) {
153 private _removeObserver(d: IObserver<T>) {
157 const i = this._observers.indexOf(d);
154 const i = this._observers.indexOf(d);
158 if (i >= 0)
155 if (i >= 0)
159 this._observers.splice(i, 1);
156 this._observers.splice(i, 1);
160 }
157 }
161
158
162 private _notify(guard: (observer: IObserver<T>) => void) {
159 private _notify(guard: (observer: IObserver<T>) => void) {
163 this._once.forEach(guard);
160 this._once.forEach(guard);
164 this._once = [];
161 this._once = [];
165
162
166 this._observers.forEach(guard);
163 this._observers.forEach(guard);
167 }
164 }
168
165
169 protected _notifyNext(evt: T) {
166 protected _notifyNext(evt: T) {
170 const guard = (observer: IObserver<T>) => {
167 const guard = (observer: IObserver<T>) => {
171 try {
168 try {
172 observer.next(evt);
169 observer.next(evt);
173 } catch (e) {
170 } catch (e) {
174 this.onObserverException(e);
171 this.onObserverException(e);
175 }
172 }
176 };
173 };
177
174
178 this._notify(guard);
175 this._notify(guard);
179 }
176 }
180
177
181 protected _notifyError(e: any) {
178 protected _notifyError(e: any) {
182 const guard = (observer: IObserver<T>) => {
179 const guard = (observer: IObserver<T>) => {
183 try {
180 try {
184 observer.error(e);
181 observer.error(e);
185 } catch (e) {
182 } catch (e) {
186 this.onObserverException(e);
183 this.onObserverException(e);
187 }
184 }
188 };
185 };
189
186
190 this._notify(guard);
187 this._notify(guard);
191 this._observers = [];
188 this._observers = [];
192 this._complete = true;
189 this._complete = true;
193 }
190 }
194
191
195 protected _notifyCompleted() {
192 protected _notifyCompleted() {
196 const guard = (observer: IObserver<T>) => {
193 const guard = (observer: IObserver<T>) => {
197 try {
194 try {
198 observer.complete();
195 observer.complete();
199 } catch (e) {
196 } catch (e) {
200 this.onObserverException(e);
197 this.onObserverException(e);
201 }
198 }
202 };
199 };
203
200
204 this._notify(guard);
201 this._notify(guard);
205 this._observers = [];
202 this._observers = [];
206 this._complete = true;
203 this._complete = true;
207 }
204 }
208 }
205 }
@@ -1,33 +1,35
1 import { Observable } from "./Observable";
1 import { Observable } from "./Observable";
2 import { IDestroyable } from "./interfaces";
2 import { IDestroyable } from "./interfaces";
3 import { argumentNotNull } from "./safe";
3 import { argumentNotNull } from "./safe";
4
4
5 type Handler<T> = (x: T) => void;
5 type Handler<T> = (x: T) => void;
6
6
7 export class ObservableValue<T> extends Observable<T> {
7 export class ObservableValue<T> extends Observable<T> {
8 private _value: T;
8 private _value: T;
9
9
10 constructor(initial: T) {
10 constructor(initial: T) {
11 super();
11 super();
12 this._value = initial;
12 this._value = initial;
13 }
13 }
14
14
15 getValue() {
15 getValue() {
16 return this._value;
16 return this._value;
17 }
17 }
18
18
19 setValue(value: T) {
19 setValue(value: T) {
20 this._value = value;
20 if (this._value !== value) {
21 this._notifyNext(value);
21 this._value = value;
22 this._notifyNext(value);
23 }
22 }
24 }
23
25
24 on(next: Handler<T>, error?: Handler<any>, complete?: () => void): IDestroyable {
26 on(next: Handler<T>, error?: Handler<any>, complete?: () => void): IDestroyable {
25 argumentNotNull(next, "next");
27 argumentNotNull(next, "next");
26 try {
28 try {
27 next(this._value);
29 next(this._value);
28 } catch {
30 } catch {
29 // suppress error
31 // suppress error
30 }
32 }
31 return super.on(next, error, complete);
33 return super.on(next, error, complete);
32 }
34 }
33 }
35 }
@@ -1,272 +1,288
1 // Typescript port of the uuid.js
1 // Typescript port of the uuid.js
2 // Copyright (c) 2018 Sergey Smirnov
2 // Copyright (c) 2018 Sergey Smirnov
3 // BSD-2-Clause License https://opensource.org/licenses/BSD-2-Clause
3 // BSD-2-Clause License https://opensource.org/licenses/BSD-2-Clause
4 //
4 //
5 // uuid.js
5 // uuid.js
6 // Copyright (c) 2010-2012 Robert Kieffer
6 // Copyright (c) 2010-2012 Robert Kieffer
7 // MIT License - http://opensource.org/licenses/mit-license.php
7 // MIT License - http://opensource.org/licenses/mit-license.php
8
8
9 import { MapOf } from "./interfaces";
10
9 declare const window: any;
11 declare const window: any;
10 declare const require;
12 declare const require: any;
11 declare const Buffer;
13 declare const Buffer: any;
12
14
13 const _window: any = "undefined" !== typeof window ? window : null;
15 const _window: any = "undefined" !== typeof window ? window : null;
14
16
17 interface WritableArrayLike<T> {
18 length: number;
19 [n: number]: T;
20 }
21
15 // Unique ID creation requires a high quality random # generator. We
22 // Unique ID creation requires a high quality random # generator. We
16 // feature
23 // feature
17 // detect to determine the best RNG source, normalizing to a function
24 // detect to determine the best RNG source, normalizing to a function
18 // that
25 // that
19 // returns 128-bits of randomness, since that's what's usually required
26 // returns 128-bits of randomness, since that's what's usually required
20 let _rng;
27 let _rng: () => WritableArrayLike<number> = () => [];
21
28
22 function setupBrowser() {
29 function setupBrowser() {
23 // Allow for MSIE11 msCrypto
30 // Allow for MSIE11 msCrypto
24 const _crypto = _window.crypto || _window.msCrypto;
31 const _crypto = _window.crypto || _window.msCrypto;
25
32
26 if (!_rng && _crypto && _crypto.getRandomValues) {
33 if (!_rng && _crypto && _crypto.getRandomValues) {
27 // WHATWG crypto-based RNG - http://wiki.whatwg.org/wiki/Crypto
34 // WHATWG crypto-based RNG - http://wiki.whatwg.org/wiki/Crypto
28 //
35 //
29 // Moderately fast, high quality
36 // Moderately fast, high quality
30 try {
37 try {
31 const _rnds8 = new Uint8Array(16);
38 const _rnds8 = new Uint8Array(16);
32 _rng = function whatwgRNG() {
39 _rng = function whatwgRNG() {
33 _crypto.getRandomValues(_rnds8);
40 _crypto.getRandomValues(_rnds8);
34 return _rnds8;
41 return _rnds8;
35 };
42 };
36 _rng();
43 _rng();
37 } catch (e) { /**/ }
44 } catch (e) { /**/ }
38 }
45 }
39
46
40 if (!_rng) {
47 if (!_rng) {
41 // Math.random()-based (RNG)
48 // Math.random()-based (RNG)
42 //
49 //
43 // If all else fails, use Math.random(). It's fast, but is of
50 // If all else fails, use Math.random(). It's fast, but is of
44 // unspecified
51 // unspecified
45 // quality.
52 // quality.
46 const _rnds = new Array(16);
53 const _rnds = new Array<number>(16);
47 _rng = () => {
54 _rng = () => {
48 for (let i = 0, r; i < 16; i++) {
55 for (let i = 0, r = 0; i < 16; i++) {
49 if ((i & 0x03) === 0) {
56 if ((i & 0x03) === 0) {
50 r = Math.random() * 0x100000000;
57 r = Math.random() * 0x100000000;
51 }
58 }
52 _rnds[i] = r >>> ((i & 0x03) << 3) & 0xff;
59 _rnds[i] = r >>> ((i & 0x03) << 3) & 0xff;
53 }
60 }
54
61
55 return _rnds;
62 return _rnds;
56 };
63 };
57 // if ("undefined" !== typeof console && console.warn) {
64 // if ("undefined" !== typeof console && console.warn) {
58 // console.warn("[SECURITY] node-uuid: crypto not usable, falling back to insecure Math.random()");
65 // console.warn("[SECURITY] node-uuid: crypto not usable, falling back to insecure Math.random()");
59 // }
66 // }
60 }
67 }
61 }
68 }
62
69
63 function setupNode() {
70 function setupNode() {
64 // Node.js crypto-based RNG -
71 // Node.js crypto-based RNG -
65 // http://nodejs.org/docs/v0.6.2/api/crypto.html
72 // http://nodejs.org/docs/v0.6.2/api/crypto.html
66 //
73 //
67 // Moderately fast, high quality
74 // Moderately fast, high quality
68 if ("function" === typeof require) {
75 if ("function" === typeof require) {
69 try {
76 try {
70 const _rb = require("crypto").randomBytes;
77 const _rb = require("crypto").randomBytes;
71 _rng = _rb && (() => _rb(16));
78 _rng = _rb && (() => _rb(16));
72 _rng();
79 _rng();
73 } catch (e) { /**/ }
80 } catch (e) { /**/ }
74 }
81 }
75 }
82 }
76
83
77 if (_window) {
84 if (_window) {
78 setupBrowser();
85 setupBrowser();
79 } else {
86 } else {
80 setupNode();
87 setupNode();
81 }
88 }
82
89
83 // Buffer class to use
90 // Buffer class to use
84 const BufferClass = ("function" === typeof Buffer) ? Buffer : Array;
91 const BufferClass = ("function" === typeof Buffer) ? Buffer : Array;
85
92
86 // Maps for number <-> hex string conversion
93 // Maps for number <-> hex string conversion
87 const _byteToHex = [];
94 const _byteToHex: string[] = [];
88 const _hexToByte = {};
95 const _hexToByte: MapOf<number> = {};
89 for (let i = 0; i < 256; i++) {
96 for (let i = 0; i < 256; i++) {
90 _byteToHex[i] = (i + 0x100).toString(16).substr(1);
97 _byteToHex[i] = (i + 0x100).toString(16).substr(1);
91 _hexToByte[_byteToHex[i]] = i;
98 _hexToByte[_byteToHex[i]] = i;
92 }
99 }
93
100
94 // **`parse()` - Parse a UUID into it's component bytes**
101 // **`parse()` - Parse a UUID into it's component bytes**
95 function _parse(s, buf?, offset?): Array<string> {
102 function _parse(s: string, buf: number[] = [], offset?: number): number[] {
96 const i = (buf && offset) || 0; let ii = 0;
103 const i = (buf && offset) || 0; let ii = 0;
97
104
98 buf = buf || [];
99 s.toLowerCase().replace(/[0-9a-f]{2}/g, oct => {
105 s.toLowerCase().replace(/[0-9a-f]{2}/g, oct => {
100 if (ii < 16) { // Don't overflow!
106 if (ii < 16) { // Don't overflow!
101 buf[i + ii++] = _hexToByte[oct];
107 buf[i + ii++] = _hexToByte[oct];
102 }
108 }
109 return "";
103 });
110 });
104
111
105 // Zero out remaining bytes if string was short
112 // Zero out remaining bytes if string was short
106 while (ii < 16) {
113 while (ii < 16) {
107 buf[i + ii++] = 0;
114 buf[i + ii++] = 0;
108 }
115 }
109
116
110 return buf;
117 return buf;
111 }
118 }
112
119
113 // **`unparse()` - Convert UUID byte array (ala parse()) into a string**
120 // **`unparse()` - Convert UUID byte array (ala parse()) into a string**
114 function _unparse(buf, offset?): string {
121 function _unparse(buf: ArrayLike<number>, offset?: number): string {
115 let i = offset || 0; const bth = _byteToHex;
122 let i = offset || 0; const bth = _byteToHex;
116 return bth[buf[i++]] + bth[buf[i++]] + bth[buf[i++]] +
123 return bth[buf[i++]] + bth[buf[i++]] + bth[buf[i++]] +
117 bth[buf[i++]] + "-" + bth[buf[i++]] + bth[buf[i++]] + "-" +
124 bth[buf[i++]] + "-" + bth[buf[i++]] + bth[buf[i++]] + "-" +
118 bth[buf[i++]] + bth[buf[i++]] + "-" + bth[buf[i++]] +
125 bth[buf[i++]] + bth[buf[i++]] + "-" + bth[buf[i++]] +
119 bth[buf[i++]] + "-" + bth[buf[i++]] + bth[buf[i++]] +
126 bth[buf[i++]] + "-" + bth[buf[i++]] + bth[buf[i++]] +
120 bth[buf[i++]] + bth[buf[i++]] + bth[buf[i++]] + bth[buf[i++]];
127 bth[buf[i++]] + bth[buf[i++]] + bth[buf[i++]] + bth[buf[i++]];
121 }
128 }
122
129
123 // **`v1()` - Generate time-based UUID**
130 // **`v1()` - Generate time-based UUID**
124 //
131 //
125 // Inspired by https://github.com/LiosK/UUID.js
132 // Inspired by https://github.com/LiosK/UUID.js
126 // and http://docs.python.org/library/uuid.html
133 // and http://docs.python.org/library/uuid.html
127
134
128 // random #'s we need to init node and clockseq
135 // random #'s we need to init node and clockseq
129 const _seedBytes = _rng();
136 const _seedBytes = _rng();
130
137
131 // Per 4.5, create and 48-bit node id, (47 random bits + multicast bit =
138 // Per 4.5, create and 48-bit node id, (47 random bits + multicast bit =
132 // 1)
139 // 1)
133 const _nodeId = [
140 const _nodeId = [
134 _seedBytes[0] | 0x01,
141 _seedBytes[0] | 0x01,
135 _seedBytes[1],
142 _seedBytes[1],
136 _seedBytes[2],
143 _seedBytes[2],
137 _seedBytes[3],
144 _seedBytes[3],
138 _seedBytes[4],
145 _seedBytes[4],
139 _seedBytes[5]
146 _seedBytes[5]
140 ];
147 ];
141
148
142 // Per 4.2.2, randomize (14 bit) clockseq
149 // Per 4.2.2, randomize (14 bit) clockseq
143 let _clockseq = (_seedBytes[6] << 8 | _seedBytes[7]) & 0x3fff;
150 let _clockseq = (_seedBytes[6] << 8 | _seedBytes[7]) & 0x3fff;
144
151
145 // Previous uuid creation time
152 // Previous uuid creation time
146 let _lastMSecs = 0; let _lastNSecs = 0;
153 let _lastMSecs = 0; let _lastNSecs = 0;
147
154
155 interface V1Options {
156 clockseq?: number;
157 msecs?: number;
158 nsecs?: number;
159 node?: number[];
160 }
161
148 // See https://github.com/broofa/node-uuid for API details
162 // See https://github.com/broofa/node-uuid for API details
149 function _v1(options?, buf?, offset?): string {
163 function _v1(options?: V1Options): string;
164 function _v1(options: V1Options, buf: number[], offset?: number): number[];
165 function _v1(options: V1Options = {}, buf?: number[], offset?: number): string | number[] {
150 let i = buf && offset || 0;
166 let i = buf && offset || 0;
151 const b = buf || [];
167 const b = buf || [];
152
168
153 options = options || {};
154
155 let clockseq = (options.clockseq != null) ? options.clockseq : _clockseq;
169 let clockseq = (options.clockseq != null) ? options.clockseq : _clockseq;
156
170
157 // UUID timestamps are 100 nano-second units since the Gregorian
171 // UUID timestamps are 100 nano-second units since the Gregorian
158 // epoch,
172 // epoch,
159 // (1582-10-15 00:00). JSNumbers aren't precise enough for this, so
173 // (1582-10-15 00:00). JSNumbers aren't precise enough for this, so
160 // time is handled internally as 'msecs' (integer milliseconds) and
174 // time is handled internally as 'msecs' (integer milliseconds) and
161 // 'nsecs'
175 // 'nsecs'
162 // (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01
176 // (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01
163 // 00:00.
177 // 00:00.
164 let msecs = (options.msecs != null) ? options.msecs : new Date()
178 let msecs = (options.msecs != null) ? options.msecs : new Date()
165 .getTime();
179 .getTime();
166
180
167 // Per 4.2.1.2, use count of uuid's generated during the current
181 // Per 4.2.1.2, use count of uuid's generated during the current
168 // clock
182 // clock
169 // cycle to simulate higher resolution clock
183 // cycle to simulate higher resolution clock
170 let nsecs = (options.nsecs != null) ? options.nsecs : _lastNSecs + 1;
184 let nsecs = (options.nsecs != null) ? options.nsecs : _lastNSecs + 1;
171
185
172 // Time since last uuid creation (in msecs)
186 // Time since last uuid creation (in msecs)
173 const dt = (msecs - _lastMSecs) + (nsecs - _lastNSecs) / 10000;
187 const dt = (msecs - _lastMSecs) + (nsecs - _lastNSecs) / 10000;
174
188
175 // Per 4.2.1.2, Bump clockseq on clock regression
189 // Per 4.2.1.2, Bump clockseq on clock regression
176 if (dt < 0 && options.clockseq == null) {
190 if (dt < 0 && options.clockseq == null) {
177 clockseq = clockseq + 1 & 0x3fff;
191 clockseq = clockseq + 1 & 0x3fff;
178 }
192 }
179
193
180 // Reset nsecs if clock regresses (new clockseq) or we've moved onto
194 // Reset nsecs if clock regresses (new clockseq) or we've moved onto
181 // a new
195 // a new
182 // time interval
196 // time interval
183 if ((dt < 0 || msecs > _lastMSecs) && options.nsecs == null) {
197 if ((dt < 0 || msecs > _lastMSecs) && options.nsecs == null) {
184 nsecs = 0;
198 nsecs = 0;
185 }
199 }
186
200
187 // Per 4.2.1.2 Throw error if too many uuids are requested
201 // Per 4.2.1.2 Throw error if too many uuids are requested
188 if (nsecs >= 10000) {
202 if (nsecs >= 10000) {
189 throw new Error(
203 throw new Error(
190 "uuid.v1(): Can't create more than 10M uuids/sec");
204 "uuid.v1(): Can't create more than 10M uuids/sec");
191 }
205 }
192
206
193 _lastMSecs = msecs;
207 _lastMSecs = msecs;
194 _lastNSecs = nsecs;
208 _lastNSecs = nsecs;
195 _clockseq = clockseq;
209 _clockseq = clockseq;
196
210
197 // Per 4.1.4 - Convert from unix epoch to Gregorian epoch
211 // Per 4.1.4 - Convert from unix epoch to Gregorian epoch
198 msecs += 12219292800000;
212 msecs += 12219292800000;
199
213
200 // `time_low`
214 // `time_low`
201 const tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000;
215 const tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000;
202 b[i++] = tl >>> 24 & 0xff;
216 b[i++] = tl >>> 24 & 0xff;
203 b[i++] = tl >>> 16 & 0xff;
217 b[i++] = tl >>> 16 & 0xff;
204 b[i++] = tl >>> 8 & 0xff;
218 b[i++] = tl >>> 8 & 0xff;
205 b[i++] = tl & 0xff;
219 b[i++] = tl & 0xff;
206
220
207 // `time_mid`
221 // `time_mid`
208 const tmh = (msecs / 0x100000000 * 10000) & 0xfffffff;
222 const tmh = (msecs / 0x100000000 * 10000) & 0xfffffff;
209 b[i++] = tmh >>> 8 & 0xff;
223 b[i++] = tmh >>> 8 & 0xff;
210 b[i++] = tmh & 0xff;
224 b[i++] = tmh & 0xff;
211
225
212 // `time_high_and_version`
226 // `time_high_and_version`
213 b[i++] = tmh >>> 24 & 0xf | 0x10; // include version
227 b[i++] = tmh >>> 24 & 0xf | 0x10; // include version
214 b[i++] = tmh >>> 16 & 0xff;
228 b[i++] = tmh >>> 16 & 0xff;
215
229
216 // `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant)
230 // `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant)
217 b[i++] = clockseq >>> 8 | 0x80;
231 b[i++] = clockseq >>> 8 | 0x80;
218
232
219 // `clock_seq_low`
233 // `clock_seq_low`
220 b[i++] = clockseq & 0xff;
234 b[i++] = clockseq & 0xff;
221
235
222 // `node`
236 // `node`
223 const node = options.node || _nodeId;
237 const node = options.node || _nodeId;
224 for (let n = 0; n < 6; n++) {
238 for (let n = 0; n < 6; n++) {
225 b[i + n] = node[n];
239 b[i + n] = node[n];
226 }
240 }
227
241
228 return buf ? buf : _unparse(b);
242 return buf ? buf : _unparse(b);
229 }
243 }
230
244
245 interface V4Opptions {
246 rng?: () => WritableArrayLike<number>;
247
248 random?: number[];
249 }
250
231 // **`v4()` - Generate random UUID**
251 // **`v4()` - Generate random UUID**
232
252
233 // See https://github.com/broofa/node-uuid for API details
253 // See https://github.com/broofa/node-uuid for API details
234 function _v4(options?, buf?, offset?): string {
254 function _v4(options?: V4Opptions): string;
255 function _v4(options: V4Opptions, buf: number[], offset?: number): number[];
256 function _v4(options: V4Opptions = {}, buf?: number[], offset?: number): string | number[] {
235 // Deprecated - 'format' argument, as supported in v1.2
257 // Deprecated - 'format' argument, as supported in v1.2
236 const i = buf && offset || 0;
258 const i = buf && offset || 0;
237
259
238 if (typeof (options) === "string") {
239 buf = (options === "binary") ? new BufferClass(16) : null;
240 options = null;
241 }
242 options = options || {};
243
244 const rnds = options.random || (options.rng || _rng)();
260 const rnds = options.random || (options.rng || _rng)();
245
261
246 // Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
262 // Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
247 rnds[6] = (rnds[6] & 0x0f) | 0x40;
263 rnds[6] = (rnds[6] & 0x0f) | 0x40;
248 rnds[8] = (rnds[8] & 0x3f) | 0x80;
264 rnds[8] = (rnds[8] & 0x3f) | 0x80;
249
265
250 // Copy bytes to buffer, if provided
266 // Copy bytes to buffer, if provided
251 if (buf) {
267 if (buf) {
252 for (let ii = 0; ii < 16; ii++) {
268 for (let ii = 0; ii < 16; ii++) {
253 buf[i + ii] = rnds[ii];
269 buf[i + ii] = rnds[ii];
254 }
270 }
255 }
271 }
256
272
257 return buf || _unparse(rnds);
273 return buf || _unparse(rnds);
258 }
274 }
259
275
260 function _Uuid() {
276 function _Uuid() {
261 return _v4();
277 return _v4();
262 }
278 }
263
279
264 namespace _Uuid {
280 namespace _Uuid {
265 export const v4 = _v4;
281 export const v4 = _v4;
266 export const v1 = _v1;
282 export const v1 = _v1;
267 export const empty = "00000000-0000-0000-0000-000000000000";
283 export const empty = "00000000-0000-0000-0000-000000000000";
268 export const parse = _parse;
284 export const parse = _parse;
269 export const Uuid = _v4;
285 export const Uuid = _v4;
270 }
286 }
271
287
272 export = _Uuid;
288 export = _Uuid;
@@ -1,83 +1,89
1 import { IActivationController, IActivatable, ICancellation } from "../interfaces";
1 import { IActivationController, IActivatable, ICancellation } from "../interfaces";
2 import { AsyncComponent } from "./AsyncComponent";
2 import { AsyncComponent } from "./AsyncComponent";
3 import { Cancellation } from "../Cancellation";
3 import { Cancellation } from "../Cancellation";
4 import { TraceSource } from "../log/TraceSource";
4 import { TraceSource } from "../log/TraceSource";
5
5
6 type Constructor<T = {}> = new (...args: any[]) => T;
6 type Constructor<T = {}> = new (...args: any[]) => T;
7
7
8 const log = TraceSource.get("@implab/core/components/ActivatableMixin");
8 const log = TraceSource.get("@implab/core/components/ActivatableMixin");
9
9
10 export function ActivatableMixin<TBase extends Constructor<AsyncComponent>>(Base: TBase) {
10 export function ActivatableMixin<TBase extends Constructor<AsyncComponent>>(Base: TBase) {
11 return class extends Base implements IActivatable {
11 return class extends Base implements IActivatable {
12 _controller: IActivationController;
12 _controller: IActivationController | undefined;
13
13
14 _active: boolean;
14 _active = false;
15
15
16 isActive() {
16 isActive() {
17 return this._active;
17 return this._active;
18 }
18 }
19
19
20 hasActivationController() {
21 return !!this._controller;
22 }
23
20 getActivationController() {
24 getActivationController() {
25 if (!this._controller)
26 throw Error("Activation controller isn't set");
21 return this._controller;
27 return this._controller;
22 }
28 }
23
29
24 setActivationController(controller: IActivationController) {
30 setActivationController(controller: IActivationController) {
25 this._controller = controller;
31 this._controller = controller;
26 }
32 }
27
33
28 async onActivating(ct: ICancellation) {
34 async onActivating(ct: ICancellation) {
29 if (this._controller)
35 if (this._controller)
30 await this._controller.activating(this, ct);
36 await this._controller.activating(this, ct);
31 }
37 }
32
38
33 async onActivated(ct: ICancellation) {
39 async onActivated(ct: ICancellation) {
34 if (this._controller)
40 if (this._controller)
35 await this._controller.activated(this, ct);
41 await this._controller.activated(this, ct);
36 }
42 }
37
43
38 activate(ct: ICancellation = Cancellation.none) {
44 activate(ct: ICancellation = Cancellation.none) {
39 return this.runOperation(this._activateAsync.bind(this), ct);
45 return this.runOperation(this._activateAsync.bind(this), ct);
40 }
46 }
41
47
42 async _activateAsync(ct: ICancellation) {
48 async _activateAsync(ct: ICancellation) {
43 if (this.isActive())
49 if (this.isActive())
44 return;
50 return;
45
51
46 await this.onActivating(ct);
52 await this.onActivating(ct);
47 this._active = true;
53 this._active = true;
48 try {
54 try {
49 await this.onActivated(ct);
55 await this.onActivated(ct);
50 } catch (e) {
56 } catch (e) {
51 log.error("Suppressed onActivated error: {0}", e);
57 log.error("Suppressed onActivated error: {0}", e);
52 }
58 }
53 }
59 }
54
60
55 async onDeactivating(ct: ICancellation) {
61 async onDeactivating(ct: ICancellation) {
56 if (this._controller)
62 if (this._controller)
57 await this._controller.deactivating(this, ct);
63 await this._controller.deactivating(this, ct);
58 }
64 }
59
65
60 async onDeactivated(ct: ICancellation) {
66 async onDeactivated(ct: ICancellation) {
61 if (this._controller)
67 if (this._controller)
62 await this._controller.deactivated(this, ct);
68 await this._controller.deactivated(this, ct);
63 }
69 }
64
70
65 deactivate(ct: ICancellation = Cancellation.none) {
71 deactivate(ct: ICancellation = Cancellation.none) {
66 return this.runOperation(this._deactivateAsync.bind(this), ct);
72 return this.runOperation(this._deactivateAsync.bind(this), ct);
67 }
73 }
68
74
69 async _deactivateAsync(ct: ICancellation) {
75 async _deactivateAsync(ct: ICancellation) {
70 if (!this.isActive())
76 if (!this.isActive())
71 return;
77 return;
72 await this.onDeactivating(ct);
78 await this.onDeactivating(ct);
73 this._active = false;
79 this._active = false;
74 try {
80 try {
75 await this.onDeactivated(ct);
81 await this.onDeactivated(ct);
76 } catch (e) {
82 } catch (e) {
77 log.error("Suppressed onDeactivated error: {0}", e);
83 log.error("Suppressed onDeactivated error: {0}", e);
78 }
84 }
79 }
85 }
80 };
86 };
81 }
87 }
82
88
83 export const traceSource = log;
89 export const traceSource = log;
@@ -1,40 +1,40
1 import { Cancellation } from "../Cancellation";
1 import { Cancellation } from "../Cancellation";
2 import { IAsyncComponent, ICancellation, ICancellable, IDestroyable } from "../interfaces";
2 import { IAsyncComponent, ICancellation, ICancellable, IDestroyable } from "../interfaces";
3 import { destroy } from "../safe";
3 import { destroy } from "../safe";
4
4
5 export class AsyncComponent implements IAsyncComponent, ICancellable {
5 export class AsyncComponent implements IAsyncComponent, ICancellable {
6 _cancel: (e: any) => void;
6 _cancel: ((e: any) => void) | undefined;
7
7
8 _completion: Promise<void> = Promise.resolve();
8 _completion: Promise<void> = Promise.resolve();
9
9
10 getCompletion() { return this._completion; }
10 getCompletion() { return this._completion; }
11
11
12 runOperation(op: (ct: ICancellation) => any, ct: ICancellation = Cancellation.none) {
12 runOperation(op: (ct: ICancellation) => any, ct: ICancellation = Cancellation.none) {
13 // create inner cancellation bound to the passed cancellation token
13 // create inner cancellation bound to the passed cancellation token
14 let h: IDestroyable;
14 let h: IDestroyable;
15 const inner = new Cancellation(cancel => {
15 const inner = new Cancellation(cancel => {
16
16
17 this._cancel = cancel;
17 this._cancel = cancel;
18 h = ct.register(cancel);
18 h = ct.register(cancel);
19 });
19 });
20
20
21 // TODO create cancellation source here
21 // TODO create cancellation source here
22 const guard = async () => {
22 const guard = async () => {
23 try {
23 try {
24 await op(inner);
24 await op(inner);
25 } finally {
25 } finally {
26 // after the operation is complete we need to cleanup the
26 // after the operation is complete we need to cleanup the
27 // resources
27 // resources
28 destroy(h);
28 destroy(h);
29 this._cancel = null;
29 this._cancel = undefined;
30 }
30 }
31 };
31 };
32
32
33 return this._completion = guard();
33 return this._completion = guard();
34 }
34 }
35
35
36 cancel(reason: any) {
36 cancel(reason: any) {
37 if (this._cancel)
37 if (this._cancel)
38 this._cancel(reason);
38 this._cancel(reason);
39 }
39 }
40 }
40 }
@@ -1,138 +1,138
1 import { TraceSource } from "../log/TraceSource";
1 import { TraceSource } from "../log/TraceSource";
2 import { argumentNotNull, argumentNotEmptyString, isPrimitive, each, isNull } from "../safe";
2 import { argumentNotNull, argumentNotEmptyString, isPrimitive, each, isNull } from "../safe";
3 import { Descriptor, ServiceMap } from "./interfaces";
3 import { Descriptor, ServiceMap, PartialServiceMap } from "./interfaces";
4 import { Container } from "./Container";
4 import { Container } from "./Container";
5 import { MapOf } from "../interfaces";
5 import { MapOf } from "../interfaces";
6
6
7 const trace = TraceSource.get("@implab/core/di/ActivationContext");
7 const trace = TraceSource.get("@implab/core/di/ActivationContext");
8
8
9 export interface ActivationContextInfo<S> {
9 export interface ActivationContextInfo<S> {
10 name: string;
10 name: string;
11
11
12 service: string;
12 service: string;
13
13
14 scope: ServiceMap<S>;
14 scope: PartialServiceMap<S>;
15 }
15 }
16
16
17 export class ActivationContext<S> {
17 export class ActivationContext<S> {
18 _cache: MapOf<any>;
18 _cache: MapOf<any>;
19
19
20 _services: ServiceMap<S>;
20 _services: PartialServiceMap<S>;
21
21
22 _stack: ActivationContextInfo<S>[];
22 _stack: ActivationContextInfo<S>[];
23
23
24 _visited: MapOf<any>;
24 _visited: MapOf<any>;
25
25
26 _name: string;
26 _name: string;
27
27
28 _localized: boolean = false;
28 _localized: boolean = false;
29
29
30 container: Container<S>;
30 container: Container<S>;
31
31
32 constructor(container: Container<S>, services: ServiceMap<S>, name?: string, cache?: object, visited?: MapOf<any>) {
32 constructor(container: Container<S>, services: PartialServiceMap<S>, name?: string, cache?: object, visited?: MapOf<any>) {
33 argumentNotNull(container, "container");
33 argumentNotNull(container, "container");
34 argumentNotNull(services, "services");
34 argumentNotNull(services, "services");
35
35
36 this._name = name || "<unnamed>";
36 this._name = name || "<unnamed>";
37 this._visited = visited || {};
37 this._visited = visited || {};
38 this._stack = [];
38 this._stack = [];
39 this._cache = cache || {};
39 this._cache = cache || {};
40 this._services = services;
40 this._services = services;
41 this.container = container;
41 this.container = container;
42 }
42 }
43
43
44 getName() {
44 getName() {
45 return this._name;
45 return this._name;
46 }
46 }
47
47
48 resolve<K extends keyof S, T extends S[K]>(name: K, def?: T) {
48 resolve<K extends keyof S, T extends S[K]>(name: K, def?: T): T {
49 const d = this._services[name];
49 const d = this._services[name];
50
50
51 if (d !== undefined) {
51 if (d !== undefined) {
52 return this.activate(d as Descriptor<T>, name.toString());
52 return this.activate(d as Descriptor<S, T>, name.toString());
53 } else {
53 } else {
54 if (def !== undefined && def !== null)
54 if (def !== undefined && def !== null)
55 return def;
55 return def;
56 else
56 else
57 throw new Error(`Service ${name} not found`);
57 throw new Error(`Service ${name} not found`);
58 }
58 }
59 }
59 }
60
60
61 /**
61 /**
62 * registers services local to the the activation context
62 * registers services local to the the activation context
63 *
63 *
64 * @name{string} the name of the service
64 * @name{string} the name of the service
65 * @service{string} the service descriptor to register
65 * @service{string} the service descriptor to register
66 */
66 */
67 register<K extends keyof S>(name: K, service: Descriptor<S[K]>) {
67 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>) {
68 argumentNotEmptyString(name, "name");
68 argumentNotEmptyString(name, "name");
69
69
70 this._services[name] = service;
70 this._services[name] = service;
71 }
71 }
72
72
73 clone() {
73 clone() {
74 return new ActivationContext(
74 return new ActivationContext<S>(
75 this.container,
75 this.container,
76 this._services,
76 this._services,
77 this._name,
77 this._name,
78 this._cache,
78 this._cache,
79 this._visited
79 this._visited
80 );
80 );
81 }
81 }
82
82
83 has(id: string) {
83 has(id: string) {
84 return id in this._cache;
84 return id in this._cache;
85 }
85 }
86
86
87 get<T>(id: string) {
87 get<T>(id: string) {
88 return this._cache[id];
88 return this._cache[id];
89 }
89 }
90
90
91 store(id: string, value: any) {
91 store(id: string, value: any) {
92 return (this._cache[id] = value);
92 return (this._cache[id] = value);
93 }
93 }
94
94
95 activate<T>(d: Descriptor<T>, name: string) {
95 activate<T>(d: Descriptor<S, T>, name: string) {
96 if (trace.isLogEnabled())
96 if (trace.isLogEnabled())
97 trace.log(`enter ${name} ${d}`);
97 trace.log(`enter ${name} ${d}`);
98
98
99 this.enter(name, d.toString());
99 this.enter(name, d.toString());
100 const v = d.activate<S>(this);
100 const v = d.activate(this);
101 this.leave();
101 this.leave();
102
102
103 if (trace.isLogEnabled())
103 if (trace.isLogEnabled())
104 trace.log(`leave ${name}`);
104 trace.log(`leave ${name}`);
105
105
106 return v;
106 return v;
107 }
107 }
108
108
109 visit(id: string) {
109 visit(id: string) {
110 const count = this._visited[id] || 0;
110 const count = this._visited[id] || 0;
111 this._visited[id] = count + 1;
111 this._visited[id] = count + 1;
112 return count;
112 return count;
113 }
113 }
114
114
115 getStack() {
115 getStack() {
116 return this._stack.slice().reverse();
116 return this._stack.slice().reverse();
117 }
117 }
118
118
119 private enter(name: string, service: string) {
119 private enter(name: string, service: string) {
120 this._stack.push({
120 this._stack.push({
121 name,
121 name,
122 service,
122 service,
123 scope: this._services
123 scope: this._services
124 });
124 });
125 this._name = name;
125 this._name = name;
126 this._services = Object.create(this._services);
126 this._services = Object.create(this._services);
127 }
127 }
128
128
129 private leave() {
129 private leave() {
130 const ctx = this._stack.pop();
130 const ctx = this._stack.pop();
131 if (ctx) {
131 if (ctx) {
132 this._services = ctx.scope;
132 this._services = ctx.scope;
133 this._name = ctx.name;
133 this._name = ctx.name;
134 } else {
134 } else {
135 trace.error("Trying to leave the last activation scope");
135 trace.error("Trying to leave the last activation scope");
136 }
136 }
137 }
137 }
138 }
138 }
@@ -1,39 +1,35
1 import { Descriptor, isDescriptor } from "./interfaces";
1 import { Descriptor, isDescriptor, Parse } from "./interfaces";
2 import { ActivationContext } from "./ActivationContext";
2 import { ActivationContext } from "./ActivationContext";
3 import { isPrimitive } from "../safe";
3 import { isPrimitive } from "../safe";
4
4
5 type Parse<T> = T extends Descriptor<infer V> ? V :
6 T extends {} ? { [K in keyof T]: Parse<T[K]> } :
7 T;
8
9 export class AggregateDescriptor<T> implements Descriptor<Parse<T>> {
5 export class AggregateDescriptor<T> implements Descriptor<Parse<T>> {
10 _value: T;
6 _value: T;
11
7
12 constructor(value: T) {
8 constructor(value: T) {
13 this._value = value;
9 this._value = value;
14 }
10 }
15
11
16 activate<S>(context: ActivationContext<S>) {
12 activate<S>(context: ActivationContext<S>) {
17 return this._parse(this._value, context, "$value");
13 return this._parse(this._value, context, "$value");
18 }
14 }
19
15
20 _parse<S, V>(value: V, context: ActivationContext<S>, path: string): Parse<V> {
16 _parse<S, V>(value: V, context: ActivationContext<S>, path: string): Parse<V> {
21 if (isPrimitive(value))
17 if (isPrimitive(value))
22 return value as any;
18 return value as any;
23
19
24 if (isDescriptor(value))
20 if (isDescriptor(value))
25 return context.activate(value, path);
21 return context.activate(value, path);
26
22
27 if (value instanceof Array)
23 if (value instanceof Array)
28 return value.map((x, i) => this._parse(x, context, `${path}[${i}]`)) as any;
24 return value.map((x, i) => this._parse(x, context, `${path}[${i}]`)) as any;
29
25
30 const t: any = {};
26 const t: any = {};
31 for (const p in value)
27 for (const p in value)
32 t[p] = this._parse(value[p], context, `${path}.${p}`);
28 t[p] = this._parse(value[p], context, `${path}.${p}`);
33 return t;
29 return t;
34 }
30 }
35
31
36 toString() {
32 toString() {
37 return "@walk";
33 return "@walk";
38 }
34 }
39 }
35 }
@@ -1,61 +1,58
1 import { Constructor } from "../interfaces";
1 import { Constructor } from "../interfaces";
2
2
3 export interface InjectOptions {
3 export interface InjectOptions {
4 lazy?: boolean;
4 lazy?: boolean;
5 }
5 }
6
6
7 interface Dependency<K extends keyof any> {
7 interface Dependency<K extends keyof any> {
8 $dependency: K;
8 $dependency: K;
9
9
10 lazy?: boolean;
10 lazy?: boolean;
11 }
11 }
12
12
13 interface Lazy<K extends keyof any> extends Dependency<K> {
13 interface Lazy<K extends keyof any> extends Dependency<K> {
14 lazy: true;
14 lazy: true;
15 }
15 }
16
16
17 type Setter<T = any> = (v: T) => void;
17 type Setter<T = any> = (v: T) => void;
18
18
19 type Compatible<T1, T2> = T1 extends T2 ? any : never;
19 type Compatible<T1, T2> = T1 extends T2 ? any : never;
20
20
21 type ExtractService<K, S> = K extends keyof S ? S[K] : K;
21 type ExtractService<K, S> = K extends keyof S ? S[K] : K;
22
22
23 type ExtractDependency<D, S> = D extends { $dependency: infer K } ? D extends { lazy: true } ? () => ExtractService<K, S> : ExtractService<K, S> : VisitDependency<D, S>;
23 type ExtractDependency<D, S> = D extends { $dependency: infer K } ? D extends { lazy: true } ? () => ExtractService<K, S> : ExtractService<K, S> : VisitDependency<D, S>;
24
24
25 type VisitDependency<D, S> = D extends {} ? { [K in keyof D]: ExtractDependency<D[K], S> } : D;
25 type VisitDependency<D, S> = D extends {} ? { [K in keyof D]: ExtractDependency<D[K], S> } : D;
26
26
27 interface Config<S> {
27 interface Config<S> {
28 dependency<K extends keyof S>(name: K): Dependency<K>;
28 dependency<K extends keyof S>(name: K): Dependency<K>;
29
29
30 lazy<K extends keyof S>(name: K): Lazy<K>;
30 lazy<K extends keyof S>(name: K): Lazy<K>;
31
31
32 build<T>(): Builder<T, S>;
32 build<T>(): Builder<T, S>;
33 }
33 }
34
34
35 export declare function services<S extends object>(): Config<S>;
35 export declare function services<S extends object>(): Config<S>;
36
36
37 export class Builder<T, S> {
37 export class Builder<T, S> {
38 consume<P extends any[]>(...args: P) {
38 consume<P extends any[]>(...args: P) {
39 return <C extends new (...args: ExtractDependency<P, S>) => T>(constructor: C) => {
39 return <C extends new (...args: ExtractDependency<P, S>) => T>(constructor: C) => {
40 return constructor;
41 };
40 };
42 }
41 }
43
42
44 inject<K extends keyof S>(dependency: K) {
43 inject<K extends keyof S>(dependency: K) {
45 // K = "bar"
44 // K = "bar"
46 // M = "setValue"
45 // M = "setValue"
47 // S[K] = Bar
46 // S[K] = Bar
48 // T[M] = (value: string) => void
47 // T[M] = (value: string) => void
49 // P[m] = (value: V) => void
48 // P[m] = (value: V) => void
50 return <P, M extends keyof (T | P)>(target: P, memberName: M, descriptor: TypedPropertyDescriptor<Compatible<T[M], Setter<S[K]>>>) => {
49 return <P, M extends keyof (T | P)>(target: P, memberName: M, descriptor: TypedPropertyDescriptor<Compatible<T[M], Setter<S[K]>>>) => {
51
50
52 };
51 };
53 }
52 }
54
53
55 cast<T2 extends T>(): Builder<T2, S> {
54 cast<T2 extends T>(): Builder<T2, S> {
56 return this as Builder<T2, S>;
55 return this as Builder<T2, S>;
57 }
56 }
58
57
59 }
58 }
60
61
@@ -1,348 +1,350
1 import {
1 import {
2 ServiceRegistration,
2 ServiceRegistration,
3 TypeRegistration,
3 TypeRegistration,
4 FactoryRegistration,
4 FactoryRegistration,
5 ServiceMap,
5 ServiceMap,
6 isDescriptor,
6 isDescriptor,
7 isDependencyRegistration,
7 isDependencyRegistration,
8 DependencyRegistration,
8 DependencyRegistration,
9 ValueRegistration,
9 ValueRegistration,
10 ActivationType,
10 ActivationType,
11 isValueRegistration,
11 isValueRegistration,
12 isTypeRegistration,
12 isTypeRegistration,
13 isFactoryRegistration
13 isFactoryRegistration,
14 PartialServiceMap
14 } from "./interfaces";
15 } from "./interfaces";
15
16
16 import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get } from "../safe";
17 import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get } from "../safe";
17 import { AggregateDescriptor } from "./AggregateDescriptor";
18 import { AggregateDescriptor } from "./AggregateDescriptor";
18 import { ValueDescriptor } from "./ValueDescriptor";
19 import { ValueDescriptor } from "./ValueDescriptor";
19 import { Container } from "./Container";
20 import { Container } from "./Container";
20 import { ReferenceDescriptor } from "./ReferenceDescriptor";
21 import { ReferenceDescriptor } from "./ReferenceDescriptor";
21 import { TypeServiceDescriptor } from "./TypeServiceDescriptor";
22 import { TypeServiceDescriptor } from "./TypeServiceDescriptor";
22 import { FactoryServiceDescriptor } from "./FactoryServiceDescriptor";
23 import { FactoryServiceDescriptor } from "./FactoryServiceDescriptor";
23 import { TraceSource } from "../log/TraceSource";
24 import { TraceSource } from "../log/TraceSource";
24 import { ConfigError } from "./ConfigError";
25 import { ConfigError } from "./ConfigError";
25 import { Cancellation } from "../Cancellation";
26 import { Cancellation } from "../Cancellation";
26 import { makeResolver } from "./ResolverHelper";
27 import { makeResolver } from "./ResolverHelper";
27 import { ICancellation } from "../interfaces";
28 import { ICancellation } from "../interfaces";
28
29
29 const trace = TraceSource.get("@implab/core/di/Configuration");
30 const trace = TraceSource.get("@implab/core/di/Configuration");
30
31 async function mapAll(data: any[], map?: (v: any, k: number) => any): Promise<any[]>;
31 async function mapAll(data: any | any[], map?: (v: any, k: number | string) => any): Promise<any> {
32 async function mapAll(data: any, map?: (v: any, k: string) => any): Promise<any>;
33 async function mapAll(data: any, map?: (v: any, k: any) => any): Promise<any> {
32 if (data instanceof Array) {
34 if (data instanceof Array) {
33 return Promise.all(map ? data.map(map) : data);
35 return Promise.all(map ? data.map(map) : data);
34 } else {
36 } else {
35 const keys = Object.keys(data);
37 const keys = Object.keys(data);
36
38
37 const o: any = {};
39 const o: any = {};
38
40
39 await Promise.all(keys.map(async k => {
41 await Promise.all(keys.map(async k => {
40 const v = map ? map(data[k], k) : data[k];
42 const v = map ? map(data[k], k) : data[k];
41 o[k] = isPromise(v) ? await v : v;
43 o[k] = isPromise(v) ? await v : v;
42 }));
44 }));
43
45
44 return o;
46 return o;
45 }
47 }
46 }
48 }
47
49
48 export type ModuleResolver = (moduleName: string, ct?: ICancellation) => any;
50 export type ModuleResolver = (moduleName: string, ct?: ICancellation) => any;
49
51
50 export class Configuration<S> {
52 export class Configuration<S> {
51
53
52 _hasInnerDescriptors = false;
54 _hasInnerDescriptors = false;
53
55
54 _container: Container<S>;
56 _container: Container<S>;
55
57
56 _path: Array<string>;
58 _path: Array<string>;
57
59
58 _configName: string | undefined;
60 _configName: string | undefined;
59
61
60 _require: ModuleResolver | undefined;
62 _require: ModuleResolver | undefined;
61
63
62 constructor(container: Container<S>) {
64 constructor(container: Container<S>) {
63 argumentNotNull(container, "container");
65 argumentNotNull(container, "container");
64 this._container = container;
66 this._container = container;
65 this._path = [];
67 this._path = [];
66 }
68 }
67
69
68 async loadConfiguration(moduleName: string, contextRequire?: any, ct = Cancellation.none) {
70 async loadConfiguration(moduleName: string, contextRequire?: any, ct = Cancellation.none) {
69 argumentNotEmptyString(moduleName, "moduleName");
71 argumentNotEmptyString(moduleName, "moduleName");
70
72
71 trace.log(
73 trace.log(
72 "loadConfiguration moduleName={0}, contextRequire={1}",
74 "loadConfiguration moduleName={0}, contextRequire={1}",
73 moduleName,
75 moduleName,
74 contextRequire ? typeof (contextRequire) : "<nil>"
76 contextRequire ? typeof (contextRequire) : "<nil>"
75 );
77 );
76
78
77 this._configName = moduleName;
79 this._configName = moduleName;
78
80
79 const r = await makeResolver(undefined, contextRequire);
81 const r = await makeResolver(undefined, contextRequire);
80
82
81 const config = await r(moduleName, ct);
83 const config = await r(moduleName, ct);
82
84
83 await this._applyConfiguration(
85 await this._applyConfiguration(
84 config,
86 config,
85 await makeResolver(moduleName, contextRequire),
87 await makeResolver(moduleName, contextRequire),
86 ct
88 ct
87 );
89 );
88 }
90 }
89
91
90 async applyConfiguration(data: object, contextRequire?: any, ct = Cancellation.none) {
92 async applyConfiguration(data: object, contextRequire?: any, ct = Cancellation.none) {
91 argumentNotNull(data, "data");
93 argumentNotNull(data, "data");
92
94
93 await this._applyConfiguration(data, await makeResolver(void (0), contextRequire), ct);
95 await this._applyConfiguration(data, await makeResolver(void (0), contextRequire), ct);
94 }
96 }
95
97
96 async _applyConfiguration(data: object, resolver?: ModuleResolver, ct = Cancellation.none) {
98 async _applyConfiguration(data: object, resolver?: ModuleResolver, ct = Cancellation.none) {
97 trace.log("applyConfiguration");
99 trace.log("applyConfiguration");
98
100
99 this._configName = "$";
101 this._configName = "$";
100
102
101 if (resolver)
103 if (resolver)
102 this._require = resolver;
104 this._require = resolver;
103
105
104 let services: ServiceMap;
106 let services: PartialServiceMap<S>;
105
107
106 try {
108 try {
107 services = await this._visitRegistrations(data, "$");
109 services = await this._visitRegistrations(data, "$");
108 } catch (e) {
110 } catch (e) {
109 throw this._makeError(e);
111 throw this._makeError(e);
110 }
112 }
111
113
112 this._container.register(services);
114 this._container.register(services);
113 }
115 }
114
116
115 _makeError(inner: any) {
117 _makeError(inner: any) {
116 const e = new ConfigError("Failed to load configuration", inner);
118 const e = new ConfigError("Failed to load configuration", inner);
117 e.configName = this._configName || "<inline>";
119 e.configName = this._configName || "<inline>";
118 e.path = this._makePath();
120 e.path = this._makePath();
119 return e;
121 return e;
120 }
122 }
121
123
122 _makePath() {
124 _makePath() {
123 return this._path
125 return this._path
124 .reduce(
126 .reduce(
125 (prev, cur) => typeof cur === "number" ?
127 (prev, cur) => typeof cur === "number" ?
126 `${prev}[${cur}]` :
128 `${prev}[${cur}]` :
127 `${prev}.${cur}`
129 `${prev}.${cur}`
128 )
130 )
129 .toString();
131 .toString();
130 }
132 }
131
133
132 async _resolveType(moduleName: string, localName: string) {
134 async _resolveType(moduleName: string, localName: string) {
133 trace.log("resolveType moduleName={0}, localName={1}", moduleName, localName);
135 trace.log("resolveType moduleName={0}, localName={1}", moduleName, localName);
134 try {
136 try {
135 const m = await this._loadModule(moduleName);
137 const m = await this._loadModule(moduleName);
136 return localName ? get(localName, m) : m;
138 return localName ? get(localName, m) : m;
137 } catch (e) {
139 } catch (e) {
138 trace.error("Failed to resolve type moduleName={0}, localName={1}", moduleName, localName);
140 trace.error("Failed to resolve type moduleName={0}, localName={1}", moduleName, localName);
139 throw e;
141 throw e;
140 }
142 }
141 }
143 }
142
144
143 _loadModule(moduleName: string) {
145 _loadModule(moduleName: string) {
144 trace.debug("loadModule {0}", moduleName);
146 trace.debug("loadModule {0}", moduleName);
145 if (!this._require)
147 if (!this._require)
146 throw new Error("Module loader isn't specified");
148 throw new Error("Module loader isn't specified");
147
149
148 return this._require(moduleName);
150 return this._require(moduleName);
149 }
151 }
150
152
151 async _visitRegistrations(data: any, name: string) {
153 async _visitRegistrations(data: any, name: string) {
152 this._enter(name);
154 this._enter(name);
153
155
154 if (data.constructor &&
156 if (data.constructor &&
155 data.constructor.prototype !== Object.prototype)
157 data.constructor.prototype !== Object.prototype)
156 throw new Error("Configuration must be a simple object");
158 throw new Error("Configuration must be a simple object");
157
159
158 const o: ServiceMap = {};
160 const o: PartialServiceMap<S> = {};
159 const keys = Object.keys(data);
161 const keys = Object.keys(data);
160
162
161 const services = await mapAll(data, async (v, k) => {
163 const services = await mapAll(data, async (v, k) => {
162 const d = await this._visit(v, k.toString());
164 const d = await this._visit(v, k.toString());
163 return isDescriptor(d) ? d : new AggregateDescriptor(d);
165 return isDescriptor(d) ? d : new AggregateDescriptor(d);
164 }) as ServiceMap;
166 }) as PartialServiceMap<S>;
165
167
166 this._leave();
168 this._leave();
167
169
168 return services;
170 return services;
169 }
171 }
170
172
171 _enter(name: keyof any) {
173 _enter(name: string) {
172 this._path.push(name.toString());
174 this._path.push(name.toString());
173 trace.debug(">{0}", name);
175 trace.debug(">{0}", name);
174 }
176 }
175
177
176 _leave() {
178 _leave() {
177 const name = this._path.pop();
179 const name = this._path.pop();
178 trace.debug("<{0}", name);
180 trace.debug("<{0}", name);
179 }
181 }
180
182
181 async _visit<T>(data: T, name: string) {
183 async _visit<T>(data: T, name: string): Promise<any> {
182 if (isPrimitive(data) || isDescriptor(data))
184 if (isPrimitive(data) || isDescriptor(data))
183 return data;
185 return data;
184
186
185 if (isDependencyRegistration<S>(data)) {
187 if (isDependencyRegistration<S>(data)) {
186 return this._visitDependencyRegistration(data, name);
188 return this._visitDependencyRegistration(data, name);
187 } else if (isValueRegistration(data)) {
189 } else if (isValueRegistration(data)) {
188 return this._visitValueRegistration(data, name);
190 return this._visitValueRegistration(data, name);
189 } else if (isTypeRegistration(data)) {
191 } else if (isTypeRegistration(data)) {
190 return this._visitTypeRegistration(data, name);
192 return this._visitTypeRegistration(data, name);
191 } else if (isFactoryRegistration(data)) {
193 } else if (isFactoryRegistration(data)) {
192 return this._visitFactoryRegistration(data, name);
194 return this._visitFactoryRegistration(data, name);
193 } else if (data instanceof Array) {
195 } else if (data instanceof Array) {
194 return this._visitArray(data, name);
196 return this._visitArray(data, name);
195 }
197 }
196
198
197 return this._visitObject(data as T & object, name);
199 return this._visitObject(data as T & object, name);
198 }
200 }
199
201
200 async _visitObject<T extends object>(data: T, name: string) {
202 async _visitObject<T extends object>(data: T, name: string) {
201 if (data.constructor &&
203 if (data.constructor &&
202 data.constructor.prototype !== Object.prototype)
204 data.constructor.prototype !== Object.prototype)
203 return new ValueDescriptor(data);
205 return new ValueDescriptor(data);
204
206
205 this._enter(name);
207 this._enter(name);
206
208
207 const v = await mapAll(data, delegate(this, "_visit"));
209 const v = await mapAll(data, delegate(this, "_visit"));
208
210
209 // TODO: handle inline descriptors properly
211 // TODO: handle inline descriptors properly
210 // const ex = {
212 // const ex = {
211 // activate(ctx) {
213 // activate(ctx) {
212 // const value = ctx.activate(this.prop, "prop");
214 // const value = ctx.activate(this.prop, "prop");
213 // // some code
215 // // some code
214 // },
216 // },
215 // // will be turned to ReferenceDescriptor
217 // // will be turned to ReferenceDescriptor
216 // prop: { $dependency: "depName" }
218 // prop: { $dependency: "depName" }
217 // };
219 // };
218
220
219 this._leave();
221 this._leave();
220 return v;
222 return v;
221 }
223 }
222
224
223 async _visitArray(data: any[], name: string) {
225 async _visitArray(data: any[], name: string) {
224 if (data.constructor &&
226 if (data.constructor &&
225 data.constructor.prototype !== Array.prototype)
227 data.constructor.prototype !== Array.prototype)
226 return new ValueDescriptor(data);
228 return new ValueDescriptor(data);
227
229
228 this._enter(name);
230 this._enter(name);
229
231
230 const v = await mapAll(data, delegate(this, "_visit"));
232 const v = await mapAll(data, delegate(this, "_visit"));
231 this._leave();
233 this._leave();
232
234
233 return v;
235 return v;
234 }
236 }
235
237
236 _makeServiceParams<T, P>(data: ServiceRegistration<T, P, S>) {
238 _makeServiceParams<T, P>(data: ServiceRegistration<T, P, S>) {
237 const opts: any = {
239 const opts: any = {
238 owner: this._container
240 owner: this._container
239 };
241 };
240 if (data.services)
242 if (data.services)
241 opts.services = this._visitRegistrations(data.services, "services");
243 opts.services = this._visitRegistrations(data.services, "services");
242
244
243 if (data.inject) {
245 if (data.inject) {
244 this._enter("inject");
246 this._enter("inject");
245 opts.inject = mapAll(
247 opts.inject = mapAll(
246 data.inject instanceof Array ?
248 data.inject instanceof Array ?
247 data.inject :
249 data.inject :
248 [data.inject],
250 [data.inject],
249 delegate(this, "_visitObject")
251 delegate(this, "_visitObject")
250 );
252 );
251 this._leave();
253 this._leave();
252 }
254 }
253
255
254 if ("params" in data)
256 if ("params" in data)
255 opts.params = data.params instanceof Array ?
257 opts.params = data.params instanceof Array ?
256 this._visitArray(data.params, "params") :
258 this._visitArray(data.params, "params") :
257 this._visit(data.params, "params");
259 this._visit(data.params, "params");
258
260
259 if (data.activation) {
261 if (data.activation) {
260 if (typeof (data.activation) === "string") {
262 if (typeof (data.activation) === "string") {
261 switch (data.activation.toLowerCase()) {
263 switch (data.activation.toLowerCase()) {
262 case "singleton":
264 case "singleton":
263 opts.activation = ActivationType.Singleton;
265 opts.activation = ActivationType.Singleton;
264 break;
266 break;
265 case "container":
267 case "container":
266 opts.activation = ActivationType.Container;
268 opts.activation = ActivationType.Container;
267 break;
269 break;
268 case "hierarchy":
270 case "hierarchy":
269 opts.activation = ActivationType.Hierarchy;
271 opts.activation = ActivationType.Hierarchy;
270 break;
272 break;
271 case "context":
273 case "context":
272 opts.activation = ActivationType.Context;
274 opts.activation = ActivationType.Context;
273 break;
275 break;
274 case "call":
276 case "call":
275 opts.activation = ActivationType.Call;
277 opts.activation = ActivationType.Call;
276 break;
278 break;
277 default:
279 default:
278 throw new Error("Unknown activation type: " +
280 throw new Error("Unknown activation type: " +
279 data.activation);
281 data.activation);
280 }
282 }
281 } else {
283 } else {
282 opts.activation = Number(data.activation);
284 opts.activation = Number(data.activation);
283 }
285 }
284 }
286 }
285
287
286 if (data.cleanup)
288 if (data.cleanup)
287 opts.cleanup = data.cleanup;
289 opts.cleanup = data.cleanup;
288
290
289 return opts;
291 return opts;
290 }
292 }
291
293
292 async _visitValueRegistration<T>(data: ValueRegistration<T>, name: string) {
294 async _visitValueRegistration<T>(data: ValueRegistration<T>, name: string) {
293 this._enter(name);
295 this._enter(name);
294 const d = data.parse ? new AggregateDescriptor(data.$value) : new ValueDescriptor(data.$value);
296 const d = data.parse ? new AggregateDescriptor(data.$value) : new ValueDescriptor(data.$value);
295 this._leave();
297 this._leave();
296 return d;
298 return d;
297 }
299 }
298
300
299 async _visitDependencyRegistration<K extends keyof S>(data: DependencyRegistration<S, K>, name: string) {
301 async _visitDependencyRegistration<K extends keyof S>(data: DependencyRegistration<S, K>, name: string) {
300 argumentNotEmptyString(data && data.$dependency, "data.$dependency");
302 argumentNotEmptyString(data && data.$dependency, "data.$dependency");
301 this._enter(name);
303 this._enter(name);
302 const d = new ReferenceDescriptor<S, K>({
304 const d = new ReferenceDescriptor<S, K>({
303 name: data.$dependency,
305 name: data.$dependency,
304 lazy: data.lazy,
306 lazy: data.lazy,
305 optional: data.optional,
307 optional: data.optional,
306 default: data.default,
308 default: data.default,
307 services: data.services && await this._visitRegistrations(data.services, "services")
309 services: data.services && await this._visitRegistrations(data.services, "services")
308 });
310 });
309 this._leave();
311 this._leave();
310 return d;
312 return d;
311 }
313 }
312
314
313 async _visitTypeRegistration<T, P>(data: TypeRegistration<T, P, S>, name: string) {
315 async _visitTypeRegistration<T, P>(data: TypeRegistration<T, P, S>, name: string) {
314 argumentNotNull(data.$type, "data.$type");
316 argumentNotNull(data.$type, "data.$type");
315 this._enter(name);
317 this._enter(name);
316
318
317 const opts = this._makeServiceParams(data);
319 const opts = this._makeServiceParams(data);
318 if (data.$type instanceof Function) {
320 if (data.$type instanceof Function) {
319 opts.type = data.$type;
321 opts.type = data.$type;
320 } else {
322 } else {
321 const [moduleName, typeName] = data.$type.split(":", 2);
323 const [moduleName, typeName] = data.$type.split(":", 2);
322 opts.type = this._resolveType(moduleName, typeName);
324 opts.type = this._resolveType(moduleName, typeName);
323 }
325 }
324
326
325 const d = new TypeServiceDescriptor(
327 const d = new TypeServiceDescriptor(
326 await mapAll(opts)
328 await mapAll(opts)
327 );
329 );
328
330
329 this._leave();
331 this._leave();
330
332
331 return d;
333 return d;
332 }
334 }
333
335
334 async _visitFactoryRegistration<T, P>(data: FactoryRegistration<T, P, S>, name: string) {
336 async _visitFactoryRegistration<T, P>(data: FactoryRegistration<T, P, S>, name: string) {
335 argumentOfType(data.$factory, Function, "data.$factory");
337 argumentOfType(data.$factory, Function, "data.$factory");
336 this._enter(name);
338 this._enter(name);
337
339
338 const opts = this._makeServiceParams(data);
340 const opts = this._makeServiceParams(data);
339 opts.factory = data.$factory;
341 opts.factory = data.$factory;
340
342
341 const d = new FactoryServiceDescriptor(
343 const d = new FactoryServiceDescriptor(
342 await mapAll(opts)
344 await mapAll(opts)
343 );
345 );
344
346
345 this._leave();
347 this._leave();
346 return d;
348 return d;
347 }
349 }
348 }
350 }
@@ -1,138 +1,139
1 import { ActivationContext } from "./ActivationContext";
1 import { ActivationContext } from "./ActivationContext";
2 import { ValueDescriptor } from "./ValueDescriptor";
2 import { ValueDescriptor } from "./ValueDescriptor";
3 import { ActivationError } from "./ActivationError";
3 import { ActivationError } from "./ActivationError";
4 import { isDescriptor, ServiceMap, Descriptor } from "./interfaces";
4 import { isDescriptor, ServiceMap, Descriptor, PartialServiceMap, ContainerServices, Resolver } from "./interfaces";
5 import { TraceSource } from "../log/TraceSource";
5 import { TraceSource } from "../log/TraceSource";
6 import { Configuration } from "./Configuration";
6 import { Configuration } from "./Configuration";
7 import { Cancellation } from "../Cancellation";
7 import { Cancellation } from "../Cancellation";
8 import { MapOf } from "../interfaces";
8 import { MapOf } from "../interfaces";
9
9
10 const trace = TraceSource.get("@implab/core/di/ActivationContext");
10 const trace = TraceSource.get("@implab/core/di/ActivationContext");
11
11
12 export class Container<S extends { container?: Container<S> }> {
12 export class Container<S = any> implements Resolver<S> {
13 readonly _services: ServiceMap<S>;
13 readonly _services: PartialServiceMap<S, ContainerServices<S>>;
14
14
15 readonly _cache: MapOf<any>;
15 readonly _cache: MapOf<any>;
16
16
17 readonly _cleanup: (() => void)[];
17 readonly _cleanup: (() => void)[];
18
18
19 readonly _root: Container<S>;
19 readonly _root: Container<S>;
20
20
21 readonly _parent?: Container<S>;
21 readonly _parent?: Container<S>;
22
22
23 _disposed: boolean;
23 _disposed: boolean;
24
24
25 constructor(parent?: Container<S>) {
25 constructor(parent?: Container<S>) {
26 this._parent = parent;
26 this._parent = parent;
27 this._services = parent ? Object.create(parent._services) : {};
27 this._services = parent ? Object.create(parent._services) : {};
28 this._cache = {};
28 this._cache = {};
29 this._cleanup = [];
29 this._cleanup = [];
30 this._root = parent ? parent.getRootContainer() : this;
30 this._root = parent ? parent.getRootContainer() : this;
31 this._services.container = new ValueDescriptor(this);
31 this._services.container = new ValueDescriptor(this) as any;
32 this._disposed = false;
32 this._disposed = false;
33 }
33 }
34
34
35 getRootContainer() {
35 getRootContainer() {
36 return this._root;
36 return this._root;
37 }
37 }
38
38
39 getParent() {
39 getParent() {
40 return this._parent;
40 return this._parent;
41 }
41 }
42
42
43 resolve<K extends keyof S, T extends S[K] = S[K]>(name: K, def?: T) {
43 resolve<K extends keyof ContainerServices<S>, T extends ContainerServices<S>[K] = ContainerServices<S>[K]>(name: K, def?: T): T {
44 trace.debug("resolve {0}", name);
44 trace.debug("resolve {0}", name);
45 const d = this._services[name];
45 const d = this._services[name];
46 if (d === undefined) {
46 if (d === undefined) {
47 if (def !== undefined)
47 if (def !== undefined)
48 return def;
48 return def;
49 else
49 else
50 throw new Error("Service '" + name + "' isn't found");
50 throw new Error("Service '" + name + "' isn't found");
51 } else {
51 } else {
52
52
53 const context = new ActivationContext<S>(this, this._services);
53 const context = new ActivationContext<S>(this, this._services);
54 try {
54 try {
55 return context.activate(d as Descriptor<T>, name.toString());
55 return context.activate(d as Descriptor<S, T>, name.toString());
56 } catch (error) {
56 } catch (error) {
57 throw new ActivationError(name.toString(), context.getStack(), error);
57 throw new ActivationError(name.toString(), context.getStack(), error);
58 }
58 }
59 }
59 }
60 }
60 }
61
61
62 /**
62 /**
63 * @deprecated use resolve() method
63 * @deprecated use resolve() method
64 */
64 */
65 getService<K extends keyof S>(name: K, def?: S[K]) {
65 getService<K extends keyof S, T extends ContainerServices<S>[K] = ContainerServices<S>[K]>(name: K, def?: T) {
66 return this.resolve(name, def);
66 return this.resolve(name, def);
67 }
67 }
68
68
69 register<K extends keyof S>(name: K, service: Descriptor<S[K]>): this;
69 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>): this;
70 register(services: ServiceMap<S>): this;
70 register(services: PartialServiceMap<S>): this;
71 register<K extends keyof S>(nameOrCollection: K | ServiceMap<S>, service?: Descriptor<S[K]>) {
71 register<K extends keyof S>(nameOrCollection: K | ServiceMap<S>, service?: Descriptor<S, S[K]>) {
72 if (arguments.length === 1) {
72 if (arguments.length === 1) {
73 const data = nameOrCollection as ServiceMap<S>;
73 const data = nameOrCollection as ServiceMap<S>;
74
74 for (const name in data) {
75 for (const name in data) {
75 if (Object.prototype.hasOwnProperty.call(data, name)) {
76 if (Object.prototype.hasOwnProperty.call(data, name)) {
76 this.register(name, data[name] as Descriptor<S[keyof S]>);
77 this.register(name, data[name] as Descriptor<S, S[keyof S]>);
77 }
78 }
78 }
79 }
79 } else {
80 } else {
80 if (!isDescriptor(service))
81 if (!isDescriptor(service))
81 throw new Error("The service parameter must be a descriptor");
82 throw new Error("The service parameter must be a descriptor");
82
83
83 this._services[nameOrCollection as K] = service;
84 this._services[nameOrCollection as K] = service as any;
84 }
85 }
85 return this;
86 return this;
86 }
87 }
87
88
88 onDispose(callback: () => void) {
89 onDispose(callback: () => void) {
89 if (!(callback instanceof Function))
90 if (!(callback instanceof Function))
90 throw new Error("The callback must be a function");
91 throw new Error("The callback must be a function");
91 this._cleanup.push(callback);
92 this._cleanup.push(callback);
92 }
93 }
93
94
94 dispose() {
95 dispose() {
95 if (this._disposed)
96 if (this._disposed)
96 return;
97 return;
97 this._disposed = true;
98 this._disposed = true;
98 for (const f of this._cleanup)
99 for (const f of this._cleanup)
99 f();
100 f();
100 }
101 }
101
102
102 /**
103 /**
103 * @param{String|Object} config
104 * @param{String|Object} config
104 * The configuration of the contaier. Can be either a string or an object,
105 * The configuration of the contaier. Can be either a string or an object,
105 * if the configuration is an object it's treated as a collection of
106 * if the configuration is an object it's treated as a collection of
106 * services which will be registed in the contaier.
107 * services which will be registed in the contaier.
107 *
108 *
108 * @param{Function} opts.contextRequire
109 * @param{Function} opts.contextRequire
109 * The function which will be used to load a configuration or types for services.
110 * The function which will be used to load a configuration or types for services.
110 *
111 *
111 */
112 */
112 async configure(config: string | object, opts?: any, ct = Cancellation.none) {
113 async configure(config: string | object, opts?: any, ct = Cancellation.none) {
113 const c = new Configuration(this);
114 const c = new Configuration<S>(this);
114
115
115 if (typeof (config) === "string") {
116 if (typeof (config) === "string") {
116 return c.loadConfiguration(config, opts && opts.contextRequire, ct);
117 return c.loadConfiguration(config, opts && opts.contextRequire, ct);
117 } else {
118 } else {
118 return c.applyConfiguration(config, opts && opts.contextRequire, ct);
119 return c.applyConfiguration(config, opts && opts.contextRequire, ct);
119 }
120 }
120 }
121 }
121
122
122 createChildContainer<S2 extends { container?: Container<S & S2> } = S>(): Container<S & S2> {
123 createChildContainer<S2 extends { container?: Container<S & S2> } = S>(): Container<S & S2> {
123 return new Container<S & S2>(this as any);
124 return new Container<S & S2>(this as any);
124 }
125 }
125
126
126 has(id: string | number) {
127 has(id: string | number) {
127 return id in this._cache;
128 return id in this._cache;
128 }
129 }
129
130
130 get(id: string | number) {
131 get(id: string | number) {
131 return this._cache[id];
132 return this._cache[id];
132 }
133 }
133
134
134 store(id: string | number, value: any) {
135 store(id: string | number, value: any) {
135 return (this._cache[id] = value);
136 return (this._cache[id] = value);
136 }
137 }
137
138
138 }
139 }
@@ -1,23 +1,22
1 import { ServiceDescriptor, ServiceDescriptorParams } from "./ServiceDescriptor";
1 import { ServiceDescriptor, ServiceDescriptorParams } from "./ServiceDescriptor";
2 import { Factory } from "../interfaces";
3 import { argumentNotNull, oid } from "../safe";
2 import { argumentNotNull, oid } from "../safe";
4 import { ActivationType } from "./interfaces";
3 import { ActivationType } from "./interfaces";
5
4
6 export interface FactoryServiceDescriptorParams extends ServiceDescriptorParams {
5 export interface FactoryServiceDescriptorParams<S, T, P extends any[]> extends ServiceDescriptorParams<S, T, P> {
7 factory: Factory;
6 factory: (...args: P) => T;
8 }
7 }
9
8
10 export class FactoryServiceDescriptor extends ServiceDescriptor {
9 export class FactoryServiceDescriptor<S, T, P extends any[]> extends ServiceDescriptor<S, T, P> {
11 constructor(opts: FactoryServiceDescriptorParams) {
10 constructor(opts: FactoryServiceDescriptorParams<S, T, P>) {
12 super(opts);
11 super(opts);
13
12
14 argumentNotNull(opts && opts.factory, "opts.factory");
13 argumentNotNull(opts && opts.factory, "opts.factory");
15
14
16 // bind to null
15 // bind to null
17 this._factory = (...args) => opts.factory.apply(null, args);
16 this._factory = (...args) => opts.factory.apply(null, args as any);
18
17
19 if (opts.activation === ActivationType.Singleton) {
18 if (opts.activation === ActivationType.Singleton) {
20 this._cacheId = oid(opts.factory);
19 this._cacheId = oid(opts.factory);
21 }
20 }
22 }
21 }
23 }
22 }
@@ -1,99 +1,99
1 import { isNull, argumentNotEmptyString, each, keys } from "../safe";
1 import { isNull, argumentNotEmptyString, each, keys } from "../safe";
2 import { ActivationContext } from "./ActivationContext";
2 import { ActivationContext } from "./ActivationContext";
3 import { ServiceMap, Descriptor } from "./interfaces";
3 import { ServiceMap, Descriptor, PartialServiceMap } from "./interfaces";
4 import { ActivationError } from "./ActivationError";
4 import { ActivationError } from "./ActivationError";
5
5
6 export interface ReferenceDescriptorParams<S, K extends keyof S> {
6 export interface ReferenceDescriptorParams<S, K extends keyof S> {
7 name: K;
7 name: K;
8 lazy?: boolean;
8 lazy?: boolean;
9 optional?: boolean;
9 optional?: boolean;
10 default?: S[K];
10 default?: S[K];
11 services?: ServiceMap;
11 services?: PartialServiceMap<S>;
12 }
12 }
13
13
14 function def<T>(v: T) {
14 function defined<T>(v: T | undefined) {
15 if (v === undefined)
15 if (v === undefined)
16 throw Error();
16 throw Error();
17 return v;
17 return v;
18 }
18 }
19
19
20 export class ReferenceDescriptor<S, K extends keyof S> implements Descriptor<S[K]> {
20 export class ReferenceDescriptor<S = any, K extends keyof S = keyof S> implements Descriptor<S, S[K] | ((args?: PartialServiceMap<S> ) => S[K])> {
21 _name: K;
21 _name: K;
22
22
23 _lazy = false;
23 _lazy = false;
24
24
25 _optional = false;
25 _optional = false;
26
26
27 _default: any;
27 _default: S[K] | undefined;
28
28
29 _services: ServiceMap<S>;
29 _services: ServiceMap<S>;
30
30
31 constructor(opts: ReferenceDescriptorParams<S, K>) {
31 constructor(opts: ReferenceDescriptorParams<S, K>) {
32 argumentNotEmptyString(opts && opts.name, "opts.name");
32 argumentNotEmptyString(opts && opts.name, "opts.name");
33 this._name = opts.name;
33 this._name = opts.name;
34 this._lazy = !!opts.lazy;
34 this._lazy = !!opts.lazy;
35 this._optional = !!opts.optional;
35 this._optional = !!opts.optional;
36 this._default = opts.default;
36 this._default = opts.default;
37
37
38 this._services = opts.services || {};
38 this._services = (opts.services || {}) as ServiceMap<S>;
39 }
39 }
40
40
41 activate(context: ActivationContext<S>) {
41 activate(context: ActivationContext<S>) {
42 // добавляем сервисы
42 // добавляем сервисы
43 if (this._services) {
43 if (this._services) {
44 each(this._services, (v, k) => context.register(k, def(v)));
44 each(this._services, (v, k) => context.register(k, v));
45 }
45 }
46
46
47 if (this._lazy) {
47 if (this._lazy) {
48 const saved = context.clone();
48 const saved = context.clone();
49
49
50 return (cfg: Partial<ServiceMap<S>>) => {
50 return (cfg?: PartialServiceMap<S>) => {
51 // защищаем контекст на случай исключения в процессе
51 // защищаем контекст на случай исключения в процессе
52 // активации
52 // активации
53 const ct = saved.clone();
53 const ct = saved.clone();
54 try {
54 try {
55 if (cfg) {
55 if (cfg) {
56 each(cfg, (v, k) => ct.register(k, v || {}));
56 each(cfg as ServiceMap<S>, (v, k) => ct.register(k, v));
57 }
57 }
58
58
59 return this._optional ? ct.resolve(this._name, this._default) : ct
59 return this._optional ? ct.resolve(this._name, this._default) : ct
60 .resolve(this._name);
60 .resolve(this._name);
61 } catch (error) {
61 } catch (error) {
62 throw new ActivationError(this._name.toString(), ct.getStack(), error);
62 throw new ActivationError(this._name.toString(), ct.getStack(), error);
63 }
63 }
64 };
64 };
65 } else {
65 } else {
66 const v = this._optional ?
66 const v = this._optional ?
67 context.resolve(this._name, this._default) :
67 context.resolve(this._name, this._default) :
68 context.resolve(this._name);
68 context.resolve(this._name);
69
69
70 return v;
70 return v;
71 }
71 }
72 }
72 }
73
73
74 toString() {
74 toString() {
75 const opts = [];
75 const opts = [];
76 if (this._optional)
76 if (this._optional)
77 opts.push("optional");
77 opts.push("optional");
78 if (this._lazy)
78 if (this._lazy)
79 opts.push("lazy");
79 opts.push("lazy");
80
80
81 const parts = [
81 const parts = [
82 "@ref "
82 "@ref "
83 ];
83 ];
84 if (opts.length) {
84 if (opts.length) {
85 parts.push("{");
85 parts.push("{");
86 parts.push(opts.join());
86 parts.push(opts.join());
87 parts.push("} ");
87 parts.push("} ");
88 }
88 }
89
89
90 parts.push(this._name.toString());
90 parts.push(this._name.toString());
91
91
92 if (!isNull(this._default)) {
92 if (this._default !== undefined && this._default !== null) {
93 parts.push(" = ");
93 parts.push(" = ");
94 parts.push(this._default);
94 parts.push(String(this._default));
95 }
95 }
96
96
97 return parts.join("");
97 return parts.join("");
98 }
98 }
99 }
99 }
@@ -1,234 +1,238
1 import { ActivationContext } from "./ActivationContext";
1 import { ActivationContext } from "./ActivationContext";
2 import { Descriptor, ActivationType, ServiceMap, isDescriptor } from "./interfaces";
2 import { Descriptor, ActivationType, ServiceMap, isDescriptor, Parse, PartialServiceMap } from "./interfaces";
3 import { Container } from "./Container";
3 import { Container } from "./Container";
4 import { argumentNotNull, isPrimitive } from "../safe";
4 import { argumentNotNull, isPrimitive, each, keys, isNull } from "../safe";
5 import { TraceSource } from "../log/TraceSource";
5 import { TraceSource } from "../log/TraceSource";
6
6
7 let cacheId = 0;
7 let cacheId = 0;
8
8
9 const trace = TraceSource.get("@implab/core/di/ActivationContext");
9 const trace = TraceSource.get("@implab/core/di/ActivationContext");
10
10
11 function injectMethod(target, method, context, args) {
11 function injectMethod<T, M extends keyof T, S, A>(target: T, method: M, context: ActivationContext<S>, args: A) {
12
12 const m = target[method];
13 const m = target[method];
13 if (!m)
14 if (!m || typeof m !== "function")
14 throw new Error("Method '" + method + "' not found");
15 throw new Error("Method '" + method + "' not found");
15
16
16 if (args instanceof Array)
17 if (args instanceof Array)
17 return m.apply(target, _parse(args, context, "." + method));
18 return m.apply(target, _parse(args, context, "." + method));
18 else
19 else
19 return m.call(target, _parse(args, context, "." + method));
20 return m.call(target, _parse(args, context, "." + method));
20 }
21 }
21
22
22 function makeClenupCallback(target, method: ((instance) => void) | string) {
23 function makeClenupCallback<T>(target: T, method: Cleaner<T>): () => void;
24 function makeClenupCallback(target: any, method: any) {
23 if (typeof (method) === "string") {
25 if (typeof (method) === "string") {
24 return () => {
26 return () => {
25 target[method]();
27 target[method]();
26 };
28 };
27 } else {
29 } else {
28 return () => {
30 return () => {
29 method(target);
31 method(target);
30 };
32 };
31 }
33 }
32 }
34 }
33
35
34 // TODO: make async
36 function _parse<T, S>(value: T, context: ActivationContext<S>, path: string): Parse<T> {
35 function _parse(value, context: ActivationContext, path: string) {
36 if (isPrimitive(value))
37 if (isPrimitive(value))
37 return value;
38 return value as any;
38
39
39 trace.debug("parse {0}", path);
40 trace.debug("parse {0}", path);
40
41
41 if (isDescriptor(value))
42 if (isDescriptor(value))
42 return context.activate(value, path);
43 return context.activate(value, path);
43
44
44 if (value instanceof Array)
45 if (value instanceof Array)
45 return value.map((x, i) => _parse(x, context, `${path}[${i}]`));
46 return value.map((x, i) => _parse(x, context, `${path}[${i}]`)) as any;
46
47
47 const t = {};
48 const t: any = {};
48 for (const p of Object.keys(value))
49
49 t[p] = _parse(value[p], context, `${path}.${p}`);
50 keys(value).forEach(p => t[p] = _parse(value[p], context, `${path}.${p}`));
50
51
51 return t;
52 return t;
52 }
53 }
53
54
54 export interface ServiceDescriptorParams {
55 export type Cleaner<T> = ((x: T) => void) | keyof Extract<T, { [M in keyof T]: () => void }>;
56
57 export type InjectionSpec<T> = {
58 [m in keyof T]?: any;
59 };
60
61 export interface ServiceDescriptorParams<S, T, P extends any[]> {
55 activation?: ActivationType;
62 activation?: ActivationType;
56
63
57 owner: Container;
64 owner: Container<S>;
58
65
59 params?;
66 params?: P;
60
67
61 inject?: object[];
68 inject?: InjectionSpec<T>[];
62
69
63 services?: ServiceMap;
70 services?: PartialServiceMap<S>;
64
71
65 cleanup?: ((x) => void) | string;
72 cleanup?: Cleaner<T>;
66 }
73 }
67
74
68 export class ServiceDescriptor implements Descriptor {
75 export class ServiceDescriptor<S, T, P extends any[]> implements Descriptor<S, T> {
69 _instance;
76 _instance: T | undefined;
70
77
71 _hasInstance = false;
78 _hasInstance = false;
72
79
73 _activationType = ActivationType.Call;
80 _activationType = ActivationType.Call;
74
81
75 _services: ServiceMap;
82 _services: ServiceMap<S>;
76
83
77 _params;
84 _params: P | undefined;
78
85
79 _inject: object[];
86 _inject: InjectionSpec<T>[];
80
87
81 _cleanup: ((x) => void) | string;
88 _cleanup: Cleaner<T> | undefined;
82
89
83 _cacheId: any;
90 _cacheId: any;
84
91
85 _owner: Container;
92 _owner: Container<S>;
86
93
87 constructor(opts: ServiceDescriptorParams) {
94 constructor(opts: ServiceDescriptorParams<S, T, P>) {
88 argumentNotNull(opts, "opts");
95 argumentNotNull(opts, "opts");
89 argumentNotNull(opts.owner, "owner");
96 argumentNotNull(opts.owner, "owner");
90
97
91 this._owner = opts.owner;
98 this._owner = opts.owner;
92
99
93 if ("activation" in opts)
100 if (!isNull(opts.activation))
94 this._activationType = opts.activation;
101 this._activationType = opts.activation;
95
102
96 if ("params" in opts)
103 if (!isNull(opts.params))
97 this._params = opts.params;
104 this._params = opts.params;
98
105
99 if (opts.inject)
106 this._inject = opts.inject || [];
100 this._inject = opts.inject;
101
107
102 if (opts.services)
108 this._services = (opts.services || {}) as ServiceMap<S>;
103 this._services = opts.services;
104
109
105 if (opts.cleanup) {
110 if (opts.cleanup) {
106 if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function))
111 if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function))
107 throw new Error(
112 throw new Error(
108 "The cleanup parameter must be either a function or a function name");
113 "The cleanup parameter must be either a function or a function name");
109
114
110 this._cleanup = opts.cleanup;
115 this._cleanup = opts.cleanup;
111 }
116 }
112 }
117 }
113
118
114 activate(context: ActivationContext) {
119 activate(context: ActivationContext<S>) {
115 // if we have a local service records, register them first
120 // if we have a local service records, register them first
116 let instance;
121 let instance: T;
117
122
118 // ensure we have a cache id
123 // ensure we have a cache id
119 if (!this._cacheId)
124 if (!this._cacheId)
120 this._cacheId = ++cacheId;
125 this._cacheId = ++cacheId;
121
126
122 switch (this._activationType) {
127 switch (this._activationType) {
123 case ActivationType.Singleton: // SINGLETON
128 case ActivationType.Singleton: // SINGLETON
124 // if the value is cached return it
129 // if the value is cached return it
125 if (this._hasInstance)
130 if (this._hasInstance)
126 return this._instance;
131 return this._instance;
127
132
128 // singletons are bound to the root container
133 // singletons are bound to the root container
129 const container = context.container.getRootContainer();
134 const container = context.container.getRootContainer();
130
135
131 if (container.has(this._cacheId)) {
136 if (container.has(this._cacheId)) {
132 instance = container.get(this._cacheId);
137 instance = container.get(this._cacheId);
133 } else {
138 } else {
134 instance = this._create(context);
139 instance = this._create(context);
135 container.store(this._cacheId, instance);
140 container.store(this._cacheId, instance);
136 if (this._cleanup)
141 if (this._cleanup)
137 container.onDispose(
142 container.onDispose(
138 makeClenupCallback(instance, this._cleanup));
143 makeClenupCallback(instance, this._cleanup));
139 }
144 }
140
145
141 this._hasInstance = true;
146 this._hasInstance = true;
142 return (this._instance = instance);
147 return (this._instance = instance);
143
148
144 case ActivationType.Container: // CONTAINER
149 case ActivationType.Container: // CONTAINER
145 // return a cached value
150 // return a cached value
146
151
147 if (this._hasInstance)
152 if (this._hasInstance)
148 return this._instance;
153 return this._instance;
149
154
150 // create an instance
155 // create an instance
151 instance = this._create(context);
156 instance = this._create(context);
152
157
153 // the instance is bound to the container
158 // the instance is bound to the container
154 if (this._cleanup)
159 if (this._cleanup)
155 this._owner.onDispose(
160 this._owner.onDispose(
156 makeClenupCallback(instance, this._cleanup));
161 makeClenupCallback(instance, this._cleanup));
157
162
158 // cache and return the instance
163 // cache and return the instance
159 this._hasInstance = true;
164 this._hasInstance = true;
160 return (this._instance = instance);
165 return (this._instance = instance);
161 case ActivationType.Context: // CONTEXT
166 case ActivationType.Context: // CONTEXT
162 // return a cached value if one exists
167 // return a cached value if one exists
163
168
164 if (context.has(this._cacheId))
169 if (context.has(this._cacheId))
165 return context.get(this._cacheId);
170 return context.get(this._cacheId);
166 // context context activated instances are controlled by callers
171 // context context activated instances are controlled by callers
167 return context.store(this._cacheId, this._create(context));
172 return context.store(this._cacheId, this._create(context));
168 case ActivationType.Call: // CALL
173 case ActivationType.Call: // CALL
169 // per-call created instances are controlled by callers
174 // per-call created instances are controlled by callers
170 return this._create(context);
175 return this._create(context);
171 case ActivationType.Hierarchy: // HIERARCHY
176 case ActivationType.Hierarchy: // HIERARCHY
172 // hierarchy activated instances are behave much like container activated
177 // hierarchy activated instances are behave much like container activated
173 // except they are created and bound to the child container
178 // except they are created and bound to the child container
174
179
175 // return a cached value
180 // return a cached value
176 if (context.container.has(this._cacheId))
181 if (context.container.has(this._cacheId))
177 return context.container.get(this._cacheId);
182 return context.container.get(this._cacheId);
178
183
179 instance = this._create(context);
184 instance = this._create(context);
180
185
181 if (this._cleanup)
186 if (this._cleanup)
182 context.container.onDispose(makeClenupCallback(
187 context.container.onDispose(makeClenupCallback(
183 instance,
188 instance,
184 this._cleanup));
189 this._cleanup));
185
190
186 return context.container.store(this._cacheId, instance);
191 return context.container.store(this._cacheId, instance);
187 default:
192 default:
188 throw new Error("Invalid activation type: " + this._activationType);
193 throw new Error("Invalid activation type: " + this._activationType);
189 }
194 }
190 }
195 }
191
196
192 isInstanceCreated() {
197 isInstanceCreated() {
193 return this._hasInstance;
198 return this._hasInstance;
194 }
199 }
195
200
196 getInstance() {
201 getInstance() {
197 return this._instance;
202 return this._instance;
198 }
203 }
199
204
200 _factory(...params: any[]): any {
205 _factory(...params: any[]): T {
201 throw Error("Not implemented");
206 throw Error("Not implemented");
202 }
207 }
203
208
204 _create(context: ActivationContext) {
209 _create(context: ActivationContext<S>) {
205 trace.debug(`constructing ${context._name}`);
210 trace.debug(`constructing ${context._name}`);
206
211
207 if (this._activationType !== ActivationType.Call &&
212 if (this._activationType !== ActivationType.Call &&
208 context.visit(this._cacheId) > 0)
213 context.visit(this._cacheId) > 0)
209 throw new Error("Recursion detected");
214 throw new Error("Recursion detected");
210
215
211 if (this._services) {
216 if (this._services) {
212 for (const p in this._services)
217 keys(this._services).forEach(p => context.register(p, this._services[p]));
213 context.register(p, this._services[p]);
214 }
218 }
215
219
216 let instance;
220 let instance: T;
217
221
218 if (this._params === undefined) {
222 if (this._params === undefined) {
219 instance = this._factory();
223 instance = this._factory();
220 } else if (this._params instanceof Array) {
224 } else if (this._params instanceof Array) {
221 instance = this._factory.apply(this, _parse(this._params, context, "args"));
225 instance = this._factory.apply(this, _parse(this._params, context, "args"));
222 } else {
226 } else {
223 instance = this._factory(_parse(this._params, context, "args"));
227 instance = this._factory(_parse(this._params, context, "args"));
224 }
228 }
225
229
226 if (this._inject) {
230 if (this._inject) {
227 this._inject.forEach(spec => {
231 this._inject.forEach(spec => {
228 for (const m in spec)
232 for (const m in spec)
229 injectMethod(instance, m, context, spec[m]);
233 injectMethod(instance, m, context, spec[m]);
230 });
234 });
231 }
235 }
232 return instance;
236 return instance;
233 }
237 }
234 }
238 }
@@ -1,42 +1,42
1 import { ServiceDescriptor, ServiceDescriptorParams } from "./ServiceDescriptor";
1 import { ServiceDescriptor, ServiceDescriptorParams } from "./ServiceDescriptor";
2 import { Constructor, Factory } from "../interfaces";
2 import { Constructor, Factory } from "../interfaces";
3 import { argumentNotNull, isPrimitive } from "../safe";
3 import { argumentNotNull, isPrimitive } from "../safe";
4
4
5 export interface TypeServiceDescriptorParams extends ServiceDescriptorParams {
5 export interface TypeServiceDescriptorParams<S, T extends object, P extends any[]> extends ServiceDescriptorParams<S, T, P> {
6 type: Constructor;
6 type: Constructor<T>;
7 }
7 }
8
8
9 export class TypeServiceDescriptor extends ServiceDescriptor {
9 export class TypeServiceDescriptor<S, T extends object, P extends any[]> extends ServiceDescriptor<S, T, P> {
10 _type: Constructor;
10 _type: Constructor;
11
11
12 constructor(opts: TypeServiceDescriptorParams) {
12 constructor(opts: TypeServiceDescriptorParams<S, T, P>) {
13 super(opts);
13 super(opts);
14 argumentNotNull(opts && opts.type, "opts.type");
14 argumentNotNull(opts && opts.type, "opts.type");
15
15
16 const ctor = this._type = opts.type;
16 const ctor = this._type = opts.type;
17
17
18 if (this._params) {
18 if (this._params) {
19 if (this._params.length) {
19 if (this._params.length) {
20 this._factory = (...args) => {
20 this._factory = (...args) => {
21 const t = Object.create(ctor.prototype);
21 const t = Object.create(ctor.prototype);
22 const inst = ctor.apply(t, args);
22 const inst = ctor.apply(t, args);
23 return isPrimitive(inst) ? t : inst;
23 return isPrimitive(inst) ? t : inst;
24 };
24 };
25 } else {
25 } else {
26 this._factory = arg => {
26 this._factory = arg => {
27 return new ctor(arg);
27 return new ctor(arg);
28 };
28 };
29 }
29 }
30 } else {
30 } else {
31 this._factory = () => {
31 this._factory = () => {
32 return new ctor();
32 return new ctor();
33 };
33 };
34 }
34 }
35
35
36 }
36 }
37
37
38 toString() {
38 toString() {
39 // @constructor {singleton} foo/bar/Baz
39 // @constructor {singleton} foo/bar/Baz
40 return ``;
40 return ``;
41 }
41 }
42 }
42 }
@@ -1,17 +1,17
1 import { Descriptor } from "./interfaces";
1 import { Descriptor } from "./interfaces";
2
2
3 export class ValueDescriptor<T> implements Descriptor<T> {
3 export class ValueDescriptor<T> implements Descriptor<any, T> {
4 _value: T;
4 _value: T;
5
5
6 constructor(value: T) {
6 constructor(value: T) {
7 this._value = value;
7 this._value = value;
8 }
8 }
9
9
10 activate() {
10 activate() {
11 return this._value;
11 return this._value;
12 }
12 }
13
13
14 toString() {
14 toString() {
15 return `@type=${typeof this._value}`;
15 return `@type=${typeof this._value}`;
16 }
16 }
17 }
17 }
@@ -1,75 +1,88
1 import { isPrimitive } from "../safe";
1 import { isPrimitive, primitive } from "../safe";
2 import { ActivationContext } from "./ActivationContext";
2 import { ActivationContext } from "./ActivationContext";
3 import { Constructor, Factory } from "../interfaces";
3 import { Constructor, Factory } from "../interfaces";
4
4
5 export interface Descriptor<T = any> {
5 export interface Descriptor<S = any, T = any> {
6 activate<S>(context: ActivationContext<S>): T;
6 activate(context: ActivationContext<S>): T;
7 }
7 }
8
8
9 export function isDescriptor(x: any): x is Descriptor {
9 export function isDescriptor(x: any): x is Descriptor {
10 return (!isPrimitive(x)) &&
10 return (!isPrimitive(x)) &&
11 (x.activate instanceof Function);
11 (x.activate instanceof Function);
12 }
12 }
13
13
14 export type ServiceMap<S = any> = {
14 export type ServiceMap<S, S2 extends S = S> = {
15 [k in keyof S]: Descriptor<S[k]>;
15 [k in keyof S2]: Descriptor<S, S2[k]>;
16 }
16 };
17
18 export type PartialServiceMap<S, S2 extends S = S> = Partial<ServiceMap<S, S2>>;
17
19
18 export enum ActivationType {
20 export enum ActivationType {
19 Singleton = 1,
21 Singleton = 1,
20 Container,
22 Container,
21 Hierarchy,
23 Hierarchy,
22 Context,
24 Context,
23 Call
25 Call
24 }
26 }
25
27
26 export interface RegistrationWithServices<S> {
28 export interface RegistrationWithServices<S> {
27 services?: ServiceMap<S>;
29 services?: ServiceMap<S>;
28 }
30 }
29
31
30 export interface ServiceRegistration<T, P, S> extends RegistrationWithServices<S> {
32 export interface ServiceRegistration<T, P, S> extends RegistrationWithServices<S> {
31
33
32 activation?: "singleton" | "container" | "hierarchy" | "context" | "call";
34 activation?: "singleton" | "container" | "hierarchy" | "context" | "call";
33
35
34 params?: P;
36 params?: P;
35
37
36 inject?: object | object[];
38 inject?: object | object[];
37
39
38 cleanup?: ((instance: T) => void) | string;
40 cleanup?: ((instance: T) => void) | string;
39 }
41 }
40
42
41 export interface TypeRegistration<T, P, S> extends ServiceRegistration<T, P, S> {
43 export interface TypeRegistration<T, P, S> extends ServiceRegistration<T, P, S> {
42 $type: string | Constructor<T>;
44 $type: string | Constructor<T>;
43 }
45 }
44
46
45 export interface FactoryRegistration<T, P, S> extends ServiceRegistration<T, P, S> {
47 export interface FactoryRegistration<T, P, S> extends ServiceRegistration<T, P, S> {
46 $factory: string | Factory<T>;
48 $factory: string | Factory<T>;
47 }
49 }
48
50
49 export interface ValueRegistration<T> {
51 export interface ValueRegistration<T> {
50 $value: T;
52 $value: T;
51 parse?: boolean;
53 parse?: boolean;
52 }
54 }
53
55
54 export interface DependencyRegistration<S, K extends keyof S> extends RegistrationWithServices<S> {
56 export interface DependencyRegistration<S, K extends keyof S> extends RegistrationWithServices<S> {
55 $dependency: K;
57 $dependency: K;
56 lazy?: boolean;
58 lazy?: boolean;
57 optional?: boolean;
59 optional?: boolean;
58 default?: S[K];
60 default?: S[K];
59 }
61 }
60
62
63 export type Parse<T> = T extends primitive ? T:
64 T extends Descriptor<infer V> ? V :
65 { [K in keyof T]: Parse<T[K]> };
66
67 export interface Resolver<S> {
68 resolve<K extends keyof ContainerServices<S>, T extends ContainerServices<S>[K] = ContainerServices<S>[K]>(name: K, def?: T): T;
69 }
70 export type ContainerServices<S> = S & {
71 container: Resolver<S>;
72 };
73
61 export function isTypeRegistration(x: any): x is TypeRegistration<any, any, any> {
74 export function isTypeRegistration(x: any): x is TypeRegistration<any, any, any> {
62 return (!isPrimitive(x)) && ("$type" in x);
75 return (!isPrimitive(x)) && ("$type" in x);
63 }
76 }
64
77
65 export function isFactoryRegistration(x: any): x is FactoryRegistration<any, any, any> {
78 export function isFactoryRegistration(x: any): x is FactoryRegistration<any, any, any> {
66 return (!isPrimitive(x)) && ("$factory" in x);
79 return (!isPrimitive(x)) && ("$factory" in x);
67 }
80 }
68
81
69 export function isValueRegistration(x: any): x is ValueRegistration<any> {
82 export function isValueRegistration(x: any): x is ValueRegistration<any> {
70 return (!isPrimitive(x)) && ("$value" in x);
83 return (!isPrimitive(x)) && ("$value" in x);
71 }
84 }
72
85
73 export function isDependencyRegistration<S>(x: any): x is DependencyRegistration<S, keyof S> {
86 export function isDependencyRegistration<S>(x: any): x is DependencyRegistration<S, keyof S> {
74 return (!isPrimitive(x)) && ("$dependency" in x);
87 return (!isPrimitive(x)) && ("$dependency" in x);
75 }
88 }
@@ -1,111 +1,116
1 export interface Constructor<T = {}> {
1 export interface Constructor<T = {}> {
2 new(...args: any[]): T;
2 new(...args: any[]): T;
3 prototype: T;
3 prototype: T;
4 }
4 }
5
5
6 export type Factory<T = {}> = (...args: any[]) => T;
6 export type Factory<T = {}> = (...args: any[]) => T;
7
7
8 export type Predicate<T = any> = (x: T) => boolean;
8 export type Predicate<T = any> = (x: T) => boolean;
9
9
10 export interface MapOf<T> {
10 export interface MapOf<T> {
11 [key: string]: T;
11 [key: string]: T;
12 }
12 }
13
13
14 export interface IDestroyable {
14 export interface IDestroyable {
15 destroy(): void;
15 destroy(): void;
16 }
16 }
17
17
18 export interface IRemovable {
18 export interface IRemovable {
19 remove(): void;
19 remove(): void;
20 }
20 }
21
21
22 export interface ICancellation {
22 export interface ICancellation {
23 throwIfRequested(): void;
23 throwIfRequested(): void;
24 isRequested(): boolean;
24 isRequested(): boolean;
25 isSupported(): boolean;
25 isSupported(): boolean;
26 register(cb: (e: any) => void): IDestroyable;
26 register(cb: (e: any) => void): IDestroyable;
27 }
27 }
28
28
29 /**
29 /**
30 * Интерфейс поддерживающий асинхронную активацию
30 * Интерфейс поддерживающий асинхронную активацию
31 */
31 */
32 export interface IActivatable {
32 export interface IActivatable {
33 /**
33 /**
34 * @returns Boolean indicates the current state
34 * @returns Boolean indicates the current state
35 */
35 */
36 isActive(): boolean;
36 isActive(): boolean;
37
37
38 /**
38 /**
39 * Starts the component activation
39 * Starts the component activation
40 * @param ct cancellation token for this operation
40 * @param ct cancellation token for this operation
41 */
41 */
42 activate(ct?: ICancellation): Promise<void>;
42 activate(ct?: ICancellation): Promise<void>;
43
43
44 /**
44 /**
45 * Starts the component deactivation
45 * Starts the component deactivation
46 * @param ct cancellation token for this operation
46 * @param ct cancellation token for this operation
47 */
47 */
48 deactivate(ct?: ICancellation): Promise<void>;
48 deactivate(ct?: ICancellation): Promise<void>;
49
49
50 /**
50 /**
51 * Sets the activation controller for this component
51 * Sets the activation controller for this component
52 * @param controller The activation controller
52 * @param controller The activation controller
53 *
53 *
54 * Activation controller checks whether this component
54 * Activation controller checks whether this component
55 * can be activated and manages the active state of the
55 * can be activated and manages the active state of the
56 * component
56 * component
57 */
57 */
58 setActivationController(controller: IActivationController): void;
58 setActivationController(controller: IActivationController): void;
59
59
60 /** Indicates whether this component has an activation controller */
61 hasActivationController(): boolean;
62
60 /**
63 /**
61 * Gets the current activation controller for this component
64 * Gets the current activation controller for this component
62 */
65 */
63 getActivationController(): IActivationController;
66 getActivationController(): IActivationController;
64 }
67 }
65
68
66 export interface IActivationController {
69 export interface IActivationController {
67 activating(component: IActivatable, ct?: ICancellation): Promise<void>;
70 activating(component: IActivatable, ct?: ICancellation): Promise<void>;
68
71
69 activated(component: IActivatable, ct?: ICancellation): Promise<void>;
72 activated(component: IActivatable, ct?: ICancellation): Promise<void>;
70
73
71 deactivating(component: IActivatable, ct?: ICancellation): Promise<void>;
74 deactivating(component: IActivatable, ct?: ICancellation): Promise<void>;
72
75
73 deactivated(component: IActivatable, ct?: ICancellation): Promise<void>;
76 deactivated(component: IActivatable, ct?: ICancellation): Promise<void>;
74
77
75 deactivate(ct?: ICancellation): Promise<void>;
78 deactivate(ct?: ICancellation): Promise<void>;
76
79
77 activate(component: IActivatable, ct?: ICancellation): Promise<void>;
80 activate(component: IActivatable, ct?: ICancellation): Promise<void>;
78
81
82 hasActive(): boolean;
83
79 getActive(): IActivatable;
84 getActive(): IActivatable;
80 }
85 }
81
86
82 export interface IAsyncComponent {
87 export interface IAsyncComponent {
83 getCompletion(): Promise<void>;
88 getCompletion(): Promise<void>;
84 }
89 }
85
90
86 export interface ICancellable {
91 export interface ICancellable {
87 cancel(reason?: any): void;
92 cancel(reason?: any): void;
88 }
93 }
89
94
90 export interface IObservable<T> {
95 export interface IObservable<T> {
91 on(next: (x: T) => void, error?: (e: any) => void, complete?: () => void): IDestroyable;
96 on(next: (x: T) => void, error?: (e: any) => void, complete?: () => void): IDestroyable;
92 next(ct?: ICancellation): Promise<T>;
97 next(ct?: ICancellation): Promise<T>;
93 }
98 }
94
99
95 export interface IObserver<T> {
100 export interface IObserver<T> {
96 next(event: T): void;
101 next(event: T): void;
97
102
98 error(e: any): void;
103 error(e: any): void;
99
104
100 complete(): void;
105 complete(): void;
101 }
106 }
102
107
103 export interface TextWriter {
108 export interface TextWriter {
104 write(obj: any): void;
109 write(obj: any): void;
105 write(format: string, ...args: any[]): void;
110 write(format: string, ...args: any[]): void;
106
111
107 writeLine(obj?: any): void;
112 writeLine(obj?: any): void;
108 writeLine(format: string, ...args: any[]): void;
113 writeLine(format: string, ...args: any[]): void;
109
114
110 writeValue(value: any, spec?: string): void;
115 writeValue(value: any, spec?: string): void;
111 }
116 }
@@ -1,55 +1,55
1 import { TraceSource } from "./TraceSource";
1 import { TraceSource } from "./TraceSource";
2 import { argumentNotNull } from "../safe";
2 import { argumentNotNull } from "../safe";
3 import { IDestroyable } from "../interfaces";
3 import { IDestroyable, MapOf } from "../interfaces";
4
4
5 export class Registry {
5 export class Registry {
6 static readonly instance = new Registry();
6 static readonly instance = new Registry();
7
7
8 private _registry: object = new Object();
8 private _registry: MapOf<TraceSource> = {};
9 private _listeners: object = new Object();
9 private _listeners: MapOf<(source: TraceSource) => void> = {};
10 private _nextCookie: number = 1;
10 private _nextCookie: number = 1;
11
11
12 get(id: any): TraceSource {
12 get(id: string): TraceSource {
13 argumentNotNull(id, "id");
13 argumentNotNull(id, "id");
14
14
15 if (this._registry[id])
15 if (this._registry[id])
16 return this._registry[id];
16 return this._registry[id];
17
17
18 const source = new TraceSource(id);
18 const source = new TraceSource(id);
19 this._registry[id] = source;
19 this._registry[id] = source;
20 this._onNewSource(source);
20 this._onNewSource(source);
21
21
22 return source;
22 return source;
23 }
23 }
24
24
25 add(id: any, source: TraceSource) {
25 add(id: any, source: TraceSource) {
26 argumentNotNull(id, "id");
26 argumentNotNull(id, "id");
27 argumentNotNull(source, "source");
27 argumentNotNull(source, "source");
28
28
29 this._registry[id] = source;
29 this._registry[id] = source;
30 this._onNewSource(source);
30 this._onNewSource(source);
31 }
31 }
32
32
33 _onNewSource(source: TraceSource) {
33 _onNewSource(source: TraceSource) {
34 for (const i in this._listeners)
34 for (const i in this._listeners)
35 this._listeners[i].call(null, source);
35 this._listeners[i].call(null, source);
36 }
36 }
37
37
38 on(handler: (source: TraceSource) => void): IDestroyable {
38 on(handler: (source: TraceSource) => void): IDestroyable {
39 argumentNotNull(handler, "handler");
39 argumentNotNull(handler, "handler");
40 const me = this;
40 const me = this;
41
41
42 const cookie = this._nextCookie++;
42 const cookie = this._nextCookie++;
43
43
44 this._listeners[cookie] = handler;
44 this._listeners[cookie] = handler;
45
45
46 for (const i in this._registry)
46 for (const i in this._registry)
47 handler(this._registry[i]);
47 handler(this._registry[i]);
48
48
49 return {
49 return {
50 destroy() {
50 destroy() {
51 delete me._listeners[cookie];
51 delete me._listeners[cookie];
52 }
52 }
53 };
53 };
54 }
54 }
55 }
55 }
@@ -1,20 +1,20
1 import { TraceEvent, TraceSource } from "./TraceSource";
1 import { TraceEvent, TraceSource } from "./TraceSource";
2 import { format } from "../text/StringBuilder";
2 import { format } from "../text/StringBuilder";
3
3
4 export class TraceEventData implements TraceEvent {
4 export class TraceEventData implements TraceEvent {
5 source: TraceSource;
5 source: TraceSource;
6 level: number;
6 level: number;
7 message: any;
7 message: any;
8 args?: any[];
8 args: any[];
9
9
10 constructor(source: TraceSource, level: number, message: any, args: any[]) {
10 constructor(source: TraceSource, level: number, message: any, args: any[]) {
11 this.source = source;
11 this.source = source;
12 this.level = level;
12 this.level = level;
13 this.message = message;
13 this.message = message;
14 this.args = args;
14 this.args = args;
15 }
15 }
16
16
17 toString() {
17 toString() {
18 return format(this.message, ...this.args);
18 return format(this.message, ...this.args);
19 }
19 }
20 }
20 }
@@ -1,141 +1,141
1 import { Observable } from "../Observable";
1 import { Observable } from "../Observable";
2 import { Registry } from "./Registry";
2 import { Registry } from "./Registry";
3 import { TraceEventData } from "./TraceEventData";
3 import { TraceEventData } from "./TraceEventData";
4 import { EventProvider } from "../EventProvider";
4
5
5 export const DebugLevel = 400;
6 export const DebugLevel = 400;
6
7
7 export const LogLevel = 300;
8 export const LogLevel = 300;
8
9
9 export const WarnLevel = 200;
10 export const WarnLevel = 200;
10
11
11 export const ErrorLevel = 100;
12 export const ErrorLevel = 100;
12
13
13 export const SilentLevel = 0;
14 export const SilentLevel = 0;
14
15
15 export interface TraceEvent {
16 export interface TraceEvent {
16 readonly source: TraceSource;
17 readonly source: TraceSource;
17
18
18 readonly level: number;
19 readonly level: number;
19
20
20 readonly message: any;
21 readonly message: any;
21
22
22 readonly args?: any[];
23 readonly args?: any[];
23 }
24 }
24
25
25 export class TraceSource {
26 export class TraceSource {
26 readonly id: any;
27 readonly id: any;
27
28
28 level: number;
29 level = 0;
29
30
30 readonly events: Observable<TraceEvent>;
31 readonly events: Observable<TraceEvent>;
31
32
32 _notifyNext: (arg: TraceEvent) => void;
33 private readonly _provider: EventProvider<TraceEvent>;
33
34
34 constructor(id: any) {
35 constructor(id?: any) {
35
36
36 this.id = id || new Object();
37 this.id = id || new Object();
37 this.events = new Observable(next => {
38 this._provider = new EventProvider();
38 this._notifyNext = next;
39 this.events = this._provider.getObservable();
39 });
40 }
40 }
41
41
42 protected emit(level: number, message: any, args?: any[]) {
42 protected emit(level: number, message: any, args: any[]) {
43 this._notifyNext(new TraceEventData(this, level, message, args));
43 this._provider.post(new TraceEventData(this, level, message, args));
44 }
44 }
45
45
46 isDebugEnabled() {
46 isDebugEnabled() {
47 return this.level >= DebugLevel;
47 return this.level >= DebugLevel;
48 }
48 }
49
49
50 debug(data: any): void;
50 debug(data: any): void;
51 debug(msg: string, ...args: any[]): void;
51 debug(msg: string, ...args: any[]): void;
52 debug(msg: string, ...args: any[]) {
52 debug(msg: string, ...args: any[]) {
53 if (this.isEnabled(DebugLevel))
53 if (this.isEnabled(DebugLevel))
54 this.emit(DebugLevel, msg, args);
54 this.emit(DebugLevel, msg, args);
55 }
55 }
56
56
57 isLogEnabled() {
57 isLogEnabled() {
58 return this.level >= LogLevel;
58 return this.level >= LogLevel;
59 }
59 }
60
60
61 log(data: any): void;
61 log(data: any): void;
62 log(msg: string, ...args: any[]): void;
62 log(msg: string, ...args: any[]): void;
63 log(msg: string, ...args: any[]) {
63 log(msg: string, ...args: any[]) {
64 if (this.isEnabled(LogLevel))
64 if (this.isEnabled(LogLevel))
65 this.emit(LogLevel, msg, args);
65 this.emit(LogLevel, msg, args);
66 }
66 }
67
67
68 isWarnEnabled() {
68 isWarnEnabled() {
69 return this.level >= WarnLevel;
69 return this.level >= WarnLevel;
70 }
70 }
71
71
72 warn(data: any): void;
72 warn(data: any): void;
73 warn(msg: string, ...args: any[]): void;
73 warn(msg: string, ...args: any[]): void;
74 warn(msg: string, ...args: any[]) {
74 warn(msg: string, ...args: any[]) {
75 if (this.isEnabled(WarnLevel))
75 if (this.isEnabled(WarnLevel))
76 this.emit(WarnLevel, msg, args);
76 this.emit(WarnLevel, msg, args);
77 }
77 }
78
78
79 /**
79 /**
80 * returns true if errors will be recorded.
80 * returns true if errors will be recorded.
81 */
81 */
82 isErrorEnabled() {
82 isErrorEnabled() {
83 return this.level >= ErrorLevel;
83 return this.level >= ErrorLevel;
84 }
84 }
85
85
86 /** Traces a error
86 /** Traces a error
87 * @param data The object which will be passed to the underlying listeners
87 * @param data The object which will be passed to the underlying listeners
88 */
88 */
89 error(data: any): void;
89 error(data: any): void;
90 /**
90 /**
91 * Traces a error.
91 * Traces a error.
92 *
92 *
93 * @param msg the message.
93 * @param msg the message.
94 * @param args parameters which will be substituted in the message.
94 * @param args parameters which will be substituted in the message.
95 */
95 */
96 error(msg: string, ...args: any[]): void;
96 error(msg: string, ...args: any[]): void;
97 error(msg: string, ...args: any[]) {
97 error(msg: string, ...args: any[]) {
98 if (this.isEnabled(ErrorLevel))
98 if (this.isEnabled(ErrorLevel))
99 this.emit(ErrorLevel, msg, args);
99 this.emit(ErrorLevel, msg, args);
100 }
100 }
101
101
102 /**
102 /**
103 * Checks whether the specified level is enabled for this
103 * Checks whether the specified level is enabled for this
104 * trace source.
104 * trace source.
105 *
105 *
106 * @param level the trace level which should be checked.
106 * @param level the trace level which should be checked.
107 */
107 */
108 isEnabled(level: number) {
108 isEnabled(level: number) {
109 return (this.level >= level);
109 return (this.level >= level);
110 }
110 }
111
111
112 /**
112 /**
113 * Traces a raw event, passing data as it is to the underlying listeners
113 * Traces a raw event, passing data as it is to the underlying listeners
114 *
114 *
115 * @param level the level of the event
115 * @param level the level of the event
116 * @param msg the data of the event, can be a simple string or any object.
116 * @param msg the data of the event, can be a simple string or any object.
117 */
117 */
118 traceEvent(level: number, msg: any, ...args: any[]) {
118 traceEvent(level: number, msg: any, ...args: any[]) {
119 if (this.isEnabled(level))
119 if (this.isEnabled(level))
120 this.emit(level, msg, args);
120 this.emit(level, msg, args);
121 }
121 }
122
122
123 /**
123 /**
124 * Register the specified handler to be called for every new and already
124 * Register the specified handler to be called for every new and already
125 * created trace source.
125 * created trace source.
126 *
126 *
127 * @param handler the handler which will be called for each trace source
127 * @param handler the handler which will be called for each trace source
128 */
128 */
129 static on(handler: (source: TraceSource) => void) {
129 static on(handler: (source: TraceSource) => void) {
130 return Registry.instance.on(handler);
130 return Registry.instance.on(handler);
131 }
131 }
132
132
133 /**
133 /**
134 * Creates or returns already created trace source for the specified id.
134 * Creates or returns already created trace source for the specified id.
135 *
135 *
136 * @param id the id for the trace source
136 * @param id the id for the trace source
137 */
137 */
138 static get(id: any) {
138 static get(id: any) {
139 return Registry.instance.get(id);
139 return Registry.instance.get(id);
140 }
140 }
141 }
141 }
@@ -1,41 +1,45
1 import { IObservable, IDestroyable, ICancellation } from "../../interfaces";
1 import { IObservable, IDestroyable, ICancellation } from "../../interfaces";
2 import { TraceEvent, LogLevel, WarnLevel, DebugLevel } from "../TraceSource";
2 import { TraceEvent, LogLevel, WarnLevel, DebugLevel } from "../TraceSource";
3 import { Cancellation } from "../../Cancellation";
3 import { Cancellation } from "../../Cancellation";
4 import { destroy, argumentNotNull } from "../../safe";
4 import { destroy, argumentNotNull } from "../../safe";
5 import { ConsoleWriter } from "../ConsoleWriter";
5 import { ConsoleWriter } from "../ConsoleWriter";
6
6
7 export class ConsoleLogger implements IDestroyable {
7 export class ConsoleLogger implements IDestroyable {
8 private readonly _subscriptions = new Array<IDestroyable>();
8 private readonly _subscriptions = new Array<IDestroyable>();
9 private readonly _writer: ConsoleWriter;
9 private readonly _writer: ConsoleWriter;
10
10
11 constructor(writer = ConsoleWriter.default) {
11 constructor(writer = ConsoleWriter.default) {
12 argumentNotNull(writer, "writer");
12 argumentNotNull(writer, "writer");
13 this._writer = writer;
13 this._writer = writer;
14 }
14 }
15
15
16 writeEvents(source: IObservable<TraceEvent>, ct: ICancellation = Cancellation.none) {
16 writeEvents(source: IObservable<TraceEvent>, ct: ICancellation = Cancellation.none) {
17 const subscription = source.on(this.writeEvent.bind(this));
17 const subscription = source.on(this.writeEvent.bind(this));
18 if (ct.isSupported()) {
18 if (ct.isSupported()) {
19 ct.register(subscription.destroy.bind(subscription));
19 ct.register(subscription.destroy.bind(subscription));
20 }
20 }
21 this._subscriptions.push(subscription);
21 this._subscriptions.push(subscription);
22 }
22 }
23
23
24 writeEvent(next: TraceEvent) {
24 writeEvent(next: TraceEvent) {
25 if (next.level >= DebugLevel) {
25 if (next.level >= DebugLevel) {
26 this._writer.setLogLevel("debug");
26 this._writer.setLogLevel("debug");
27 } else if (next.level >= LogLevel) {
27 } else if (next.level >= LogLevel) {
28 this._writer.setLogLevel("log");
28 this._writer.setLogLevel("log");
29 } else if (next.level >= WarnLevel) {
29 } else if (next.level >= WarnLevel) {
30 this._writer.setLogLevel("warn");
30 this._writer.setLogLevel("warn");
31 } else {
31 } else {
32 this._writer.setLogLevel("error");
32 this._writer.setLogLevel("error");
33 }
33 }
34 this._writer.write("{0}: ", next.source.id);
34 this._writer.write("{0}: ", next.source.id);
35 this._writer.writeLine(next.message, ...next.args);
35
36 if (next.args)
37 this._writer.writeLine(next.message, ...next.args);
38 else
39 this._writer.writeLine(next.message);
36 }
40 }
37
41
38 destroy() {
42 destroy() {
39 this._subscriptions.forEach(destroy);
43 this._subscriptions.forEach(destroy);
40 }
44 }
41 }
45 }
@@ -1,461 +1,499
1 import { ICancellable, Constructor } from "./interfaces";
1 import { ICancellable, Constructor } from "./interfaces";
2 import { Cancellation } from "./Cancellation";
2 import { Cancellation } from "./Cancellation";
3
3
4 let _nextOid = 0;
4 let _nextOid = 0;
5 const _oid = typeof Symbol === "function" ?
5 const _oid = typeof Symbol === "function" ?
6 Symbol("__implab__oid__") :
6 Symbol("__implab__oid__") :
7 "__implab__oid__";
7 "__implab__oid__";
8
8
9 export function oid(instance: any): string | undefined {
9 export function oid(instance: any): string | undefined {
10 if (isNull(instance))
10 if (isNull(instance))
11 return undefined;
11 return undefined;
12
12
13 if (_oid in instance)
13 if (_oid in instance)
14 return instance[_oid];
14 return instance[_oid];
15 else
15 else
16 return (instance[_oid] = "oid_" + (++_nextOid));
16 return (instance[_oid] = "oid_" + (++_nextOid));
17 }
17 }
18
18
19 export function keys<T>(arg: T): (Extract<keyof T, string>)[] {
19 export function keys<T>(arg: T): (Extract<keyof T, string>)[] {
20 return isObject(arg) && arg ? Object.keys(arg) as (Extract<keyof T, string>)[] : [];
20 return isObject(arg) && arg ? Object.keys(arg) as (Extract<keyof T, string>)[] : [];
21 }
21 }
22
22
23 export function isKeyof<T>(k: string, target: T): k is Extract<keyof T, string> {
24 return target && typeof target === "object" && k in target;
25 }
26
23 export function argumentNotNull(arg: any, name: string) {
27 export function argumentNotNull(arg: any, name: string) {
24 if (arg === null || arg === undefined)
28 if (arg === null || arg === undefined)
25 throw new Error("The argument " + name + " can't be null or undefined");
29 throw new Error("The argument " + name + " can't be null or undefined");
26 }
30 }
27
31
28 export function argumentNotEmptyString(arg: any, name: string) {
32 export function argumentNotEmptyString(arg: any, name: string) {
29 if (typeof (arg) !== "string" || !arg.length)
33 if (typeof (arg) !== "string" || !arg.length)
30 throw new Error("The argument '" + name + "' must be a not empty string");
34 throw new Error("The argument '" + name + "' must be a not empty string");
31 }
35 }
32
36
33 export function argumentNotEmptyArray(arg: any, name: string) {
37 export function argumentNotEmptyArray(arg: any, name: string) {
34 if (!(arg instanceof Array) || !arg.length)
38 if (!(arg instanceof Array) || !arg.length)
35 throw new Error("The argument '" + name + "' must be a not empty array");
39 throw new Error("The argument '" + name + "' must be a not empty array");
36 }
40 }
37
41
38 export function argumentOfType(arg: any, type: Constructor<{}>, name: string) {
42 export function argumentOfType(arg: any, type: Constructor<{}>, name: string) {
39 if (!(arg instanceof type))
43 if (!(arg instanceof type))
40 throw new Error("The argument '" + name + "' type doesn't match");
44 throw new Error("The argument '" + name + "' type doesn't match");
41 }
45 }
42
46
43 export function isObject(val: any): val is object {
47 export function isObject(val: any): val is object {
44 return typeof val === "object";
48 return typeof val === "object";
45 }
49 }
46
50
47 export function isNull(val: any) {
51 export function isNull(val: any): val is null | undefined {
48 return (val === null || val === undefined);
52 return (val === null || val === undefined);
49 }
53 }
50
54
51 export function isPrimitive(val: any): val is string | number | boolean | undefined | null {
55 export type primitive = symbol | string | number | boolean | undefined | null;
56
57 export function isPrimitive(val: any): val is primitive {
52 return (val === null || val === undefined || typeof (val) === "string" ||
58 return (val === null || val === undefined || typeof (val) === "string" ||
53 typeof (val) === "number" || typeof (val) === "boolean");
59 typeof (val) === "number" || typeof (val) === "boolean");
54 }
60 }
55
61
56 export function isInteger(val: any): val is number {
62 export function isInteger(val: any): val is number {
57 return parseInt(val, 10) === val;
63 return parseInt(val, 10) === val;
58 }
64 }
59
65
60 export function isNumber(val: any): val is number {
66 export function isNumber(val: any): val is number {
61 return parseFloat(val) === val;
67 return parseFloat(val) === val;
62 }
68 }
63
69
64 export function isString(val: any): val is string {
70 export function isString(val: any): val is string {
65 return typeof (val) === "string" || val instanceof String;
71 return typeof (val) === "string" || val instanceof String;
66 }
72 }
67
73
68 export function isPromise(val: any): val is PromiseLike<any> {
74 export function isPromise<T = any>(val: any): val is PromiseLike<T> {
69 return val && typeof val.then === "function";
75 return val && typeof val.then === "function";
70 }
76 }
71
77
72 export function isCancellable(val: any): val is ICancellable {
78 export function isCancellable(val: any): val is ICancellable {
73 return val && typeof val.cancel === "function";
79 return val && typeof val.cancel === "function";
74 }
80 }
75
81
76 export function isNullOrEmptyString(val: any): val is (string | null | undefined) {
82 export function isNullOrEmptyString(val: any): val is ("" | null | undefined) {
77 return (val === null || val === undefined ||
83 return (val === null || val === undefined ||
78 ((typeof (val) === "string" || val instanceof String) && val.length === 0))
84 ((typeof (val) === "string" || val instanceof String) && val.length === 0));
79 }
85 }
80
86
81 export function isNotEmptyArray(arg: any): arg is Array<any> {
87 export function isNotEmptyArray<T = any>(arg: any): arg is T[] {
82 return (arg instanceof Array && arg.length > 0);
88 return (arg instanceof Array && arg.length > 0);
83 }
89 }
84
90
85 function _isStrictMode(this: any) {
91 function _isStrictMode(this: any) {
86 return !this;
92 return !this;
87 }
93 }
88
94
89 function _getNonStrictGlobal(this: any) {
95 function _getNonStrictGlobal(this: any) {
90 return this;
96 return this;
91 }
97 }
92
98
93 export function getGlobal() {
99 export function getGlobal() {
94 // in es3 we can't use indirect call to eval, since it will
100 // in es3 we can't use indirect call to eval, since it will
95 // be executed in the current call context.
101 // be executed in the current call context.
96 if (!_isStrictMode()) {
102 if (!_isStrictMode()) {
97 return _getNonStrictGlobal();
103 return _getNonStrictGlobal();
98 } else {
104 } else {
99 // tslint:disable-next-line:no-eval
105 // tslint:disable-next-line:no-eval
100 return eval.call(null, "this");
106 return eval.call(null, "this");
101 }
107 }
102 }
108 }
103
109
104 export function get(member: string, context?: object) {
110 export function get(member: string, context?: object) {
105 argumentNotEmptyString(member, "member");
111 argumentNotEmptyString(member, "member");
106 let that = context || getGlobal();
112 let that = context || getGlobal();
107 const parts = member.split(".");
113 const parts = member.split(".");
108 for (const m of parts) {
114 for (const m of parts) {
109 if (!m)
115 if (!m)
110 continue;
116 continue;
111 if (isNull(that = that[m]))
117 if (isNull(that = that[m]))
112 break;
118 break;
113 }
119 }
114 return that;
120 return that;
115 }
121 }
116
122
117 /**
123 /**
118 * Выполняет метод для каждого элемента массива, останавливается, когда
124 * Выполняет метод для каждого элемента массива, останавливается, когда
119 * либо достигнут конец массива, либо функция <c>cb</c> вернула
125 * либо достигнут конец массива, либо функция <c>cb</c> вернула
120 * значение.
126 * значение.
121 *
127 *
122 * @param {Array | Object} obj массив элементов для просмотра
128 * @param {Array | Object} obj массив элементов для просмотра
123 * @param {Function} cb функция, вызываемая для каждого элемента
129 * @param {Function} cb функция, вызываемая для каждого элемента
124 * @param {Object} thisArg значение, которое будет передано в качестве
130 * @param {Object} thisArg значение, которое будет передано в качестве
125 * <c>this</c> в <c>cb</c>.
131 * <c>this</c> в <c>cb</c>.
126 * @returns Результат вызова функции <c>cb</c>, либо <c>undefined</c>
132 * @returns Результат вызова функции <c>cb</c>, либо <c>undefined</c>
127 * если достигнут конец массива.
133 * если достигнут конец массива.
128 */
134 */
129 export function each<T>(obj: T, cb: (v: T[keyof T], k: keyof T) => void): void;
135 export function each<T>(obj: T, cb: <X extends keyof T>(v: NonNullable<T[X]>, k: X) => void): void;
136 export function each<T>(array: T[], cb: (v: T, i: number) => void): void;
130 export function each(obj: any, cb: any, thisArg?: any): any;
137 export function each(obj: any, cb: any, thisArg?: any): any;
131 export function each(obj: any, cb: any, thisArg?: any) {
138 export function each(obj: any, cb: any, thisArg?: any) {
132 argumentNotNull(cb, "cb");
139 argumentNotNull(cb, "cb");
133 if (obj instanceof Array) {
140 if (obj instanceof Array) {
134 for (let i = 0; i < obj.length; i++) {
141 for (let i = 0; i < obj.length; i++) {
135 const x = cb.call(thisArg, obj[i], i);
142 const x = cb.call(thisArg, obj[i], i);
136 if (x !== undefined)
143 if (x !== undefined)
137 return x;
144 return x;
138 }
145 }
139 } else {
146 } else {
140 const _keys = Object.keys(obj);
147 const _keys = Object.keys(obj);
141 for (const k of _keys) {
148 for (const k of _keys) {
142 const x = cb.call(thisArg, obj[k], k);
149 const x = cb.call(thisArg, obj[k], k);
143 if (x !== undefined)
150 if (x !== undefined)
144 return x;
151 return x;
145 }
152 }
146 }
153 }
147 }
154 }
148
155
149 /** Copies property values from a source object to the destination and returns
156 /** Copies property values from a source object to the destination and returns
150 * the destination onject.
157 * the destination onject.
151 *
158 *
152 * @param dest The destination object into which properties from the source
159 * @param dest The destination object into which properties from the source
153 * object will be copied.
160 * object will be copied.
154 * @param source The source of values which will be copied to the destination
161 * @param source The source of values which will be copied to the destination
155 * object.
162 * object.
156 * @param template An optional parameter specifies which properties should be
163 * @param template An optional parameter specifies which properties should be
157 * copied from the source and how to map them to the destination. If the
164 * copied from the source and how to map them to the destination. If the
158 * template is an array it contains the list of property names to copy from the
165 * template is an array it contains the list of property names to copy from the
159 * source to the destination. In case of object the templates contains the map
166 * source to the destination. In case of object the templates contains the map
160 * where keys are property names in the source and the values are property
167 * where keys are property names in the source and the values are property
161 * names in the destination object. If the template isn't specified then the
168 * names in the destination object. If the template isn't specified then the
162 * own properties of the source are entirely copied to the destination.
169 * own properties of the source are entirely copied to the destination.
163 *
170 *
164 */
171 */
165 export function mixin<T extends object, S extends object>(dest: T, source: S, template?: string[] | object): T & S {
172 export function mixin<T extends object, S extends object>(dest: T, source: S, template?: keyof S[]): T & S;
166 argumentNotNull(dest, "to");
173 export function mixin<T extends object, S extends object, R extends object = T>(dest: T, source: S, template: { [p in keyof S]?: keyof R; }): T & R;
167 const _res = dest as T & S;
174 export function mixin<T extends object, S extends object>(dest: T, source: S, template?: any): any {
175 argumentNotNull(dest, "dest");
176 const _res: any = dest as any;
168
177
169 if (isPrimitive(source))
178 if (isPrimitive(source))
170 return _res;
179 return _res;
171
180
172 if (template instanceof Array) {
181 if (template instanceof Array) {
173 for (const p of template) {
182 template.forEach(p => {
174 if (p in source)
183 if (isKeyof(p, source))
175 _res[p] = source[p];
184 _res[p] = source[p];
176 }
185 });
177 } else if (template) {
186 } else if (template) {
178 const _keys = Object.keys(source);
187 keys(source).forEach(p => {
179 for (const p of _keys) {
188 if (isKeyof(p, template))
180 if (p in template)
181 _res[template[p]] = source[p];
189 _res[template[p]] = source[p];
182 }
190 });
183 } else {
191 } else {
184 const keys = Object.keys(source);
192 keys(source).forEach(p => _res[p] = source[p]);
185 for (const p of keys)
186 _res[p] = source[p];
187 }
193 }
188
194
189 return _res;
195 return _res;
190 }
196 }
191
197
192 /** Wraps the specified function to emulate an asynchronous execution.
198 /** Wraps the specified function to emulate an asynchronous execution.
193 * @param{Object} thisArg [Optional] Object which will be passed as 'this' to the function.
199 * @param{Object} thisArg [Optional] Object which will be passed as 'this' to the function.
194 * @param{Function|String} fn [Required] Function wich will be wrapped.
200 * @param{Function|String} fn [Required] Function wich will be wrapped.
195 */
201 */
196 export function async(_fn: (...args: any[]) => any, thisArg: any): (...args: any[]) => PromiseLike<any> {
202 export function async<T, F extends (...args: any[]) => T | PromiseLike<T>>(
203 fn: F,
204 thisArg?: ThisParameterType<F>
205 ): (...args: Parameters<F>) => PromiseLike<T>;
206 export function async<T, M extends string, O extends { [m in M]?: (...args: any[]) => T | PromiseLike<T> }>(
207 fn: M,
208 thisArg: O
209 ): (...args: Parameters<NonNullable<O[M]>>) => PromiseLike<T>;
210 export function async(_fn: any, thisArg: any): (...args: any[]) => PromiseLike<any> {
197 let fn = _fn;
211 let fn = _fn;
198
212
199 if (arguments.length === 2 && !(fn instanceof Function))
213 if (arguments.length === 2 && !(fn instanceof Function))
200 fn = thisArg[fn];
214 fn = thisArg[fn];
201
215
202 if (fn == null)
216 if (fn == null)
203 throw new Error("The function must be specified");
217 throw new Error("The function must be specified");
204
218
205 function wrapresult(x, e?): PromiseLike<any> {
219 function wrapresult(x: any, e?: any): PromiseLike<any> {
206 if (e) {
220 if (e) {
207 return {
221 return {
208 then(cb, eb) {
222 then(cb, eb) {
209 try {
223 try {
210 return eb ? wrapresult(eb(e)) : this;
224 return eb ? wrapresult(eb(e)) : this;
211 } catch (e2) {
225 } catch (e2) {
212 return wrapresult(null, e2);
226 return wrapresult(null, e2);
213 }
227 }
214 }
228 }
215 };
229 };
216 } else {
230 } else {
217 if (x && x.then)
231 if (x && x.then)
218 return x;
232 return x;
219 return {
233 return {
220 then(cb) {
234 then(cb) {
221 try {
235 try {
222 return cb ? wrapresult(cb(x)) : this;
236 return cb ? wrapresult(cb(x)) : this;
223 } catch (e2) {
237 } catch (e2) {
224 return wrapresult(e2);
238 return wrapresult(e2);
225 }
239 }
226 }
240 }
227 };
241 };
228 }
242 }
229 }
243 }
230
244
231 return (...args) => {
245 return (...args) => {
232 try {
246 try {
233 return wrapresult(fn.apply(thisArg, args));
247 return wrapresult(fn.apply(thisArg, args));
234 } catch (e) {
248 } catch (e) {
235 return wrapresult(null, e);
249 return wrapresult(null, e);
236 }
250 }
237 };
251 };
238 }
252 }
239
253
240 type _AnyFn = (...args) => any;
254 export function delegate<T extends object, F extends (this: T, ...args: any[]) => any>(
241
255 target: T,
242 export function delegate<T, K extends keyof T>(target: T, _method: (K | _AnyFn)) {
256 method: F
243 let method;
257 ): OmitThisParameter<F>;
258 export function delegate<M extends string, T extends { [m in M]?: (...args: any[]) => any; }>(
259 target: T,
260 method: M
261 ): OmitThisParameter<T[M]>;
262 export function delegate(target: any, _method: any): (...args: any[]) => any {
263 let method: any;
244 if (!(_method instanceof Function)) {
264 if (!(_method instanceof Function)) {
245 argumentNotNull(target, "target");
265 argumentNotNull(target, "target");
246 method = target[_method];
266 method = target[_method];
247 if (!(method instanceof Function))
267 if (!(method instanceof Function))
248 throw new Error("'method' argument must be a Function or a method name");
268 throw new Error("'method' argument must be a Function or a method name");
249 } else {
269 } else {
250 method = _method;
270 method = _method;
251 }
271 }
252
272
253 return (...args) => {
273 return (...args) => {
254 return method.apply(target, args);
274 return method.apply(target, args);
255 };
275 };
256 }
276 }
257
277
258 export function delay(timeMs: number, ct = Cancellation.none) {
278 export function delay(timeMs: number, ct = Cancellation.none) {
259 ct.throwIfRequested();
279 ct.throwIfRequested();
260 return new Promise((resolve, reject) => {
280 return new Promise((resolve, reject) => {
261 const h = ct.register(e => {
281 const h = ct.register(e => {
262 clearTimeout(id);
282 clearTimeout(id);
263 reject(e);
283 reject(e);
264 // we don't nedd to unregister h, since ct is already disposed
284 // we don't nedd to unregister h, since ct is already disposed
265 });
285 });
266 const id = setTimeout(() => {
286 const id = setTimeout(() => {
267 h.destroy();
287 h.destroy();
268 resolve();
288 resolve();
269 }, timeMs);
289 }, timeMs);
270
290
271 });
291 });
272 }
292 }
273
293
294 /** Returns resolved promise, awaiting this method will cause the asynchronous
295 * completion of the rest of the code.
296 */
297 export function fork() {
298 return Promise.resolve();
299 }
300
301 /** Always throws Error, can be used as a stub for the methods which should be
302 * assigned later and are required to be not null.
303 */
304 export function notImplemented(): never {
305 throw new Error("Not implemeted");
306 }
274 /**
307 /**
275 * Iterates over the specified array of items and calls the callback `cb`, if
308 * Iterates over the specified array of items and calls the callback `cb`, if
276 * the result of the callback is a promise the next item from the array will be
309 * the result of the callback is a promise the next item from the array will be
277 * proceeded after the promise is resolved.
310 * proceeded after the promise is resolved.
278 *
311 *
279 */
312 */
280 export function pmap<T, T2>(
313 export function pmap<T, T2>(
281 items: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
314 items: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
282 cb: (item: T, i: number) => T2 | PromiseLike<T2>
315 cb: (item: T, i: number) => T2 | PromiseLike<T2>
283 ): T2[] | PromiseLike<T2[]> {
316 ): T2[] | PromiseLike<T2[]> {
284 argumentNotNull(cb, "cb");
317 argumentNotNull(cb, "cb");
285
318
286 if (isPromise(items)) {
319 if (isPromise(items)) {
287 return items.then(data => pmap(data, cb));
320 return items.then(data => pmap(data, cb));
288 } else {
321 } else {
289
322
290 if (isNull(items) || !items.length)
323 if (isNull(items) || !items.length)
291 return [];
324 return [];
292
325
293 let i = 0;
326 let i = 0;
294 const result = new Array<T2>();
327 const result = new Array<T2>();
295
328
296 const next = (): any => {
329 const next = (): any => {
297 while (i < items.length) {
330 while (i < items.length) {
298 const r = cb(items[i], i);
331 const r = cb(items[i], i);
299 const ri = i;
332 const ri = i;
300 i++;
333 i++;
301 if (isPromise(r)) {
334 if (isPromise(r)) {
302 return r.then(x => {
335 return r.then(x => {
303 result[ri] = x;
336 result[ri] = x;
304 return next();
337 return next();
305 });
338 });
306 } else {
339 } else {
307 result[ri] = r;
340 result[ri] = r;
308 }
341 }
309 }
342 }
310 return result;
343 return result;
311 };
344 };
312
345
313 return next();
346 return next();
314 }
347 }
315 }
348 }
316
349
317 export function pfor<T>(
350 export function pfor<T>(
318 items: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
351 items: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
319 cb: (item: T, i: number) => any
352 cb: (item: T, i: number) => any
320 ): void | PromiseLike<void> {
353 ): void | PromiseLike<void> {
321 argumentNotNull(cb, "cb");
354 argumentNotNull(cb, "cb");
322
355
323 if (isPromise(items)) {
356 if (isPromise(items)) {
324 return items.then(data => pfor(data, cb));
357 return items.then(data => pfor(data, cb));
325 } else {
358 } else {
326 if (isNull(items) || !items.length)
359 if (isNull(items) || !items.length)
327 return;
360 return;
328
361
329 let i = 0;
362 let i = 0;
330
363
331 const next = (): any => {
364 const next = (): any => {
332 while (i < items.length) {
365 while (i < items.length) {
333 const r = cb(items[i], i);
366 const r = cb(items[i], i);
334 i++;
367 i++;
335 if (isPromise(r))
368 if (isPromise(r))
336 return r.then(next);
369 return r.then(next);
337 }
370 }
338 };
371 };
339
372
340 return next();
373 return next();
341 }
374 }
342 }
375 }
343
376
344 export function first<T>(sequence: ArrayLike<T>): T;
377 export function first<T>(sequence: ArrayLike<T>): T;
345 export function first<T>(sequence: PromiseLike<ArrayLike<T>>): PromiseLike<T>;
378 export function first<T>(sequence: PromiseLike<ArrayLike<T>>): PromiseLike<T>;
346 export function first<T>(
379 export function first<T>(
347 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
380 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
348 cb: (x: T) => void,
381 cb?: (x: T) => void,
349 err?: (x: Error) => void
382 err?: (x: Error) => void
350 ): void;
383 ): void;
351 /**
384 /**
352 * Выбирает первый элемент из последовательности, или обещания, если в
385 * Выбирает первый элемент из последовательности, или обещания, если в
353 * качестве параметра используется обещание, оно должно вернуть массив.
386 * качестве параметра используется обещание, оно должно вернуть массив.
354 *
387 *
355 * @param {Function} cb обработчик результата, ему будет передан первый
388 * @param {Function} cb обработчик результата, ему будет передан первый
356 * элемент последовательности в случае успеха
389 * элемент последовательности в случае успеха
357 * @param {Function} err обработчик исключения, если массив пустой, либо
390 * @param {Function} err обработчик исключения, если массив пустой, либо
358 * не массив
391 * не массив
359 *
392 *
360 * @remarks Если не указаны ни cb ни err, тогда функция вернет либо
393 * @remarks Если не указаны ни cb ни err, тогда функция вернет либо
361 * обещание, либо первый элемент.
394 * обещание, либо первый элемент.
362 * @async
395 * @async
363 */
396 */
364 export function first<T>(
397 export function first<T>(
365 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
398 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
366 cb?: (x: T) => void,
399 cb?: (x: T) => void,
367 err?: (x: Error) => void
400 err?: (x: Error) => void
368 ) {
401 ) {
369 if (isPromise(sequence)) {
402 if (isPromise(sequence)) {
370 return sequence.then(res => first(res, cb, err));
403 return sequence.then(res => first(res, cb as any /* force to pass undefined cb */, err));
371 } else if (sequence && "length" in sequence) {
404 } else if (sequence && "length" in sequence) {
372 if (sequence.length === 0) {
405 if (sequence.length === 0) {
373 if (err)
406 if (err)
374 return err(new Error("The sequence is empty"));
407 return err(new Error("The sequence is empty"));
375 else
408 else
376 throw new Error("The sequence is empty");
409 throw new Error("The sequence is empty");
377 } else if (cb) {
410 } else if (cb) {
378 return cb(sequence[0]);
411 return cb(sequence[0]);
379 } else {
412 } else {
380 return sequence[0];
413 return sequence[0];
381 }
414 }
382 } else {
415 } else {
383 if (err)
416 if (err)
384 return err(new Error("The sequence is required"));
417 return err(new Error("The sequence is required"));
385 else
418 else
386 throw new Error("The sequence is required");
419 throw new Error("The sequence is required");
387 }
420 }
388 }
421 }
389
422
390 export function firstWhere<T>(
423 export function firstWhere<T>(
391 sequence: ArrayLike<T>,
424 sequence: ArrayLike<T>,
392 predicate: (x: T) => boolean
425 predicate: (x: T) => boolean
393 ): T;
426 ): T;
394 export function firstWhere<T>(
427 export function firstWhere<T>(
395 sequence: PromiseLike<ArrayLike<T>>,
428 sequence: PromiseLike<ArrayLike<T>>,
396 predicate: (x: T) => boolean
429 predicate: (x: T) => boolean
397 ): PromiseLike<T>;
430 ): PromiseLike<T>;
398 export function firstWhere<T>(
431 export function firstWhere<T>(
399 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
432 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
400 predicate: (x: T) => boolean,
433 predicate: (x: T) => boolean,
401 cb: (x: T) => void,
434 cb: (x: T) => void,
402 err?: (x: Error) => void
435 err?: (x: Error) => void
403 ): void;
436 ): void;
404
437
405 export function firstWhere<T>(
438 export function firstWhere<T>(
406 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
439 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
407 predicate?: (x: T) => boolean,
440 predicate?: (x: T) => boolean,
408 cb?: (x: T) => any,
441 cb?: (x: T) => any,
409 err?: (x: Error) => any
442 err?: (x: Error) => any
410 ) {
443 ) {
411 if (isPromise(sequence)) {
444 if (isPromise(sequence)) {
412 return sequence.then(res => firstWhere(res, predicate, cb, err));
445 return sequence.then(res => firstWhere(
446 res,
447 predicate as any /* force to pass undefined predicate */,
448 cb as any /* force to pass undefined cb */,
449 err)
450 );
413 } else if (sequence && "length" in sequence) {
451 } else if (sequence && "length" in sequence) {
414 if (sequence.length === 0) {
452 if (sequence.length === 0) {
415 if (err)
453 if (err)
416 err(new Error("The sequence is empty"));
454 err(new Error("The sequence is empty"));
417 else
455 else
418 throw new Error("The sequence is empty");
456 throw new Error("The sequence is empty");
419 } else {
457 } else {
420 if (!predicate) {
458 if (!predicate) {
421 return cb ? cb(sequence[0]) && void (0) : sequence[0];
459 return cb ? cb(sequence[0]) && void (0) : sequence[0];
422 } else {
460 } else {
423 for (let i = 0; i < sequence.length; i++) {
461 for (let i = 0; i < sequence.length; i++) {
424 const v = sequence[i];
462 const v = sequence[i];
425 if (predicate(v))
463 if (predicate(v))
426 return cb ? cb(v) : v;
464 return cb ? cb(v) : v;
427 }
465 }
428 if (err)
466 if (err)
429 err(new Error("The sequence doesn't contain matching items"));
467 err(new Error("The sequence doesn't contain matching items"));
430 else
468 else
431 throw new Error("The sequence doesn't contain matching items");
469 throw new Error("The sequence doesn't contain matching items");
432 }
470 }
433 }
471 }
434 } else {
472 } else {
435 if (err)
473 if (err)
436 err(new Error("The sequence is required"));
474 err(new Error("The sequence is required"));
437 else
475 else
438 throw new Error("The sequence is required");
476 throw new Error("The sequence is required");
439 }
477 }
440 }
478 }
441
479
442 export function destroy(d: any) {
480 export function destroy(d: any) {
443 if (d && "destroy" in d)
481 if (d && "destroy" in d)
444 d.destroy();
482 d.destroy();
445 }
483 }
446
484
447 /**
485 /**
448 * Used to mark that the async operation isn't awaited intentionally.
486 * Used to mark that the async operation isn't awaited intentionally.
449 * @param p The promise which represents the async operation.
487 * @param p The promise which represents the async operation.
450 */
488 */
451 export function nowait(p: Promise<any>) {
489 export function nowait(p: Promise<any>) {
452 }
490 }
453
491
454 /** represents already destroyed object.
492 /** represents already destroyed object.
455 */
493 */
456 export const destroyed = {
494 export const destroyed = {
457 /** Calling to this method doesn't affect anything, noop.
495 /** Calling to this method doesn't affect anything, noop.
458 */
496 */
459 destroy() {
497 destroy() {
460 }
498 }
461 };
499 };
@@ -1,30 +1,30
1 import { isPrimitive, isNull } from "../safe";
1 import { isPrimitive, isNull } from "../safe";
2
2
3 export class Converter {
3 export class Converter {
4 static readonly default = new Converter();
4 static readonly default = new Converter();
5
5
6 convert(value: any, pattern: string) {
6 convert(value: any, pattern?: string) {
7 if (pattern && pattern.toLocaleLowerCase() === "json") {
7 if (pattern && pattern.toLocaleLowerCase() === "json") {
8 const seen = [];
8 const seen: any[] = [];
9 return JSON.stringify(value, (k, v) => {
9 return JSON.stringify(value, (k, v) => {
10 if (!isPrimitive(v)) {
10 if (!isPrimitive(v)) {
11 const id = seen.indexOf(v);
11 const id = seen.indexOf(v);
12 if (id >= 0)
12 if (id >= 0)
13 return "@ref-" + id;
13 return "@ref-" + id;
14 else {
14 else {
15 seen.push(v);
15 seen.push(v);
16 return v;
16 return v;
17 }
17 }
18 } else {
18 } else {
19 return v;
19 return v;
20 }
20 }
21 }, 2);
21 }, 2);
22 } else if (isNull(value)) {
22 } else if (isNull(value)) {
23 return "";
23 return "";
24 } else if (value instanceof Date) {
24 } else if (value instanceof Date) {
25 return value.toISOString();
25 return value.toISOString();
26 } else {
26 } else {
27 return value.toString();
27 return value.toString();
28 }
28 }
29 }
29 }
30 }
30 }
@@ -1,126 +1,134
1 import { FormatScanner, TokeType } from "./FormatScanner";
1 import { FormatScanner, TokeType } from "./FormatScanner";
2 import { isNullOrEmptyString, isPrimitive, get } from "../safe";
2 import { isNullOrEmptyString, isPrimitive, get } from "../safe";
3 import { TextWriter, MapOf } from "../interfaces";
3 import { TextWriter, MapOf } from "../interfaces";
4
4
5 type CompiledPattern = (writer: TextWriter, args: any) => void;
5 type CompiledPattern = (writer: TextWriter, args: any) => void;
6
6
7 export class FormatCompiler {
7 export class FormatCompiler {
8 _scanner: FormatScanner;
8 _scanner: FormatScanner;
9 _cache: MapOf<CompiledPattern> = {};
9 static _cache: MapOf<CompiledPattern> = {};
10
11 _parts: Array<string | { name: string; format?: string; }>;
12
13 constructor(scanner: FormatScanner) {
14 this._scanner = scanner;
15 this._parts = [];
16 }
10
17
11 _parts: Array<string | { name: string; format: string; }>;
18 compile() {
19 this.visitText();
20 const parts = this._parts;
12
21
13 compile(pattern: string) {
22 return (writer: TextWriter, args: any) => {
23 parts.forEach(x => {
24 if (isPrimitive(x))
25 writer.writeValue(x);
26 else
27 writer.writeValue(get(x.name, args), x.format);
28 });
29 };
30 }
31
32 static compile(pattern: string) {
14 let compiledPattern = this._cache && this._cache[pattern];
33 let compiledPattern = this._cache && this._cache[pattern];
15 if (!compiledPattern) {
34 if (!compiledPattern) {
16 this._scanner = new FormatScanner(pattern);
35 const compiler = new this(new FormatScanner(pattern));
17 this._parts = [];
18
19 this.visitText();
20 const parts = this._parts;
21
36
22 compiledPattern = (writer: TextWriter, args: any) => {
37 compiledPattern = compiler.compile();
23 parts.forEach(x => {
38
24 if (isPrimitive(x))
39 this._cache[pattern] = compiledPattern;
25 writer.writeValue(x);
26 else
27 writer.writeValue(get(x.name, args), x.format);
28 });
29 };
30 if (this._cache)
31 this._cache[pattern] = compiledPattern;
32 }
40 }
33 return compiledPattern;
41 return compiledPattern;
34 }
42 }
35
43
36 visitText() {
44 visitText() {
37 while (this._scanner.next()) {
45 while (this._scanner.next()) {
38 // console.log(this._scanner.getTokenType(), this._scanner.getTokenValue());
46 // console.log(this._scanner.getTokenType(), this._scanner.getTokenValue());
39 switch (this._scanner.getTokenType()) {
47 switch (this._scanner.getTokenType()) {
40 case TokeType.CurlOpen:
48 case TokeType.CurlOpen:
41 this.visitCurlOpen();
49 this.visitCurlOpen();
42 break;
50 break;
43 case TokeType.CurlClose:
51 case TokeType.CurlClose:
44 this.visitCurlClose();
52 this.visitCurlClose();
45 break;
53 break;
46 default:
54 default:
47 this.pushText(this._scanner.getTokenValue());
55 this.pushText(this._scanner.getTokenValue());
48 }
56 }
49 }
57 }
50 }
58 }
51
59
52 visitCurlClose() {
60 visitCurlClose() {
53 if (!this._scanner.next())
61 if (!this._scanner.next())
54 this.dieUnexpectedEnd("}");
62 this.dieUnexpectedEnd("}");
55 if (this._scanner.getTokenType() !== TokeType.CurlClose)
63 if (this._scanner.getTokenType() !== TokeType.CurlClose)
56 this.dieUnexpectedToken("}");
64 this.dieUnexpectedToken("}");
57 this.pushText("}");
65 this.pushText("}");
58 }
66 }
59
67
60 visitCurlOpen() {
68 visitCurlOpen() {
61 if (!this._scanner.next())
69 if (!this._scanner.next())
62 this.dieUnexpectedEnd("{ | TEXT");
70 this.dieUnexpectedEnd("{ | TEXT");
63
71
64 if (this._scanner.getTokenType() === TokeType.CurlOpen)
72 if (this._scanner.getTokenType() === TokeType.CurlOpen)
65 this.pushText("{");
73 this.pushText("{");
66 else
74 else
67 this.visitTemplateSubst();
75 this.visitTemplateSubst();
68
76
69 }
77 }
70
78
71 visitTemplateSubst() {
79 visitTemplateSubst() {
72 if (this._scanner.getTokenType() !== TokeType.Text)
80 if (this._scanner.getTokenType() !== TokeType.Text)
73 this.dieUnexpectedToken("TEXT");
81 this.dieUnexpectedToken("TEXT");
74
82
75 const fieldName = this._scanner.getTokenValue();
83 const fieldName = this._scanner.getTokenValue();
76 const filedFormat = this.readColon() ? this.readFieldFormat() : null;
84 const filedFormat = this.readColon() ? this.readFieldFormat() : undefined;
77
85
78 if (this._scanner.getTokenType() !== TokeType.CurlClose)
86 if (this._scanner.getTokenType() !== TokeType.CurlClose)
79 this.dieUnexpectedToken("}");
87 this.dieUnexpectedToken("}");
80
88
81 this.pushSubst(fieldName, filedFormat);
89 this.pushSubst(fieldName, filedFormat);
82 }
90 }
83
91
84 readFieldFormat() {
92 readFieldFormat() {
85 const parts = new Array<string>();
93 const parts = new Array<string>();
86 do {
94 do {
87 if (this._scanner.getTokenType() === TokeType.CurlClose) {
95 if (this._scanner.getTokenType() === TokeType.CurlClose) {
88 return parts.join("");
96 return parts.join("");
89 } else {
97 } else {
90 parts.push(this._scanner.getTokenValue());
98 parts.push(this._scanner.getTokenValue());
91 }
99 }
92 } while (this._scanner.next());
100 } while (this._scanner.next());
93
101
94 this.dieUnexpectedEnd("}");
102 this.dieUnexpectedEnd("}");
95 }
103 }
96
104
97 readColon() {
105 readColon() {
98 if (!this._scanner.next())
106 if (!this._scanner.next())
99 this.dieUnexpectedEnd();
107 this.dieUnexpectedEnd();
100 if (this._scanner.getTokenType() !== TokeType.Colon)
108 if (this._scanner.getTokenType() !== TokeType.Colon)
101 return false;
109 return false;
102 if (!this._scanner.next())
110 if (!this._scanner.next())
103 this.dieUnexpectedEnd();
111 this.dieUnexpectedEnd();
104 return true;
112 return true;
105 }
113 }
106
114
107 pushSubst(fieldName: string, filedFormat: string) {
115 pushSubst(fieldName: string, filedFormat?: string) {
108 // console.log("pushSubst ", fieldName, filedFormat);
116 // console.log("pushSubst ", fieldName, filedFormat);
109 this._parts.push({ name: fieldName, format: filedFormat });
117 this._parts.push({ name: fieldName, format: filedFormat });
110 }
118 }
111
119
112 pushText(text: string) {
120 pushText(text: string) {
113 this._parts.push(text);
121 this._parts.push(text);
114 }
122 }
115
123
116 dieUnexpectedToken(expected?: string) {
124 dieUnexpectedToken(expected?: string): never {
117 throw new Error(isNullOrEmptyString(expected) ?
125 throw new Error(isNullOrEmptyString(expected) ?
118 `Unexpected token ${this._scanner.getTokenValue()}` :
126 `Unexpected token ${this._scanner.getTokenValue()}` :
119 `Unexpected token ${this._scanner.getTokenValue()}, expected ${expected}`
127 `Unexpected token ${this._scanner.getTokenValue()}, expected ${expected}`
120 );
128 );
121 }
129 }
122
130
123 dieUnexpectedEnd(expected?: string) {
131 dieUnexpectedEnd(expected?: string): never {
124 throw new Error(isNullOrEmptyString(expected) ? "Unexpected end of the string" : `Unexpected end of the string, expected ${expected}`);
132 throw new Error(isNullOrEmptyString(expected) ? "Unexpected end of the string" : `Unexpected end of the string, expected ${expected}`);
125 }
133 }
126 }
134 }
@@ -1,46 +1,54
1 import { argumentNotEmptyString } from "../safe";
1 import { argumentNotEmptyString } from "../safe";
2 import { MapOf } from "../interfaces";
2 import { MapOf } from "../interfaces";
3
3
4 export const enum TokeType {
4 export const enum TokeType {
5 CurlOpen = 1,
5 CurlOpen = 1,
6 CurlClose = 2,
6 CurlClose = 2,
7 Colon = 3,
7 Colon = 3,
8 Text = 4
8 Text = 4
9 }
9 }
10
10
11 const typeMap = {
11 const typeMap = {
12 "{": TokeType.CurlOpen,
12 "{": TokeType.CurlOpen,
13 "}": TokeType.CurlClose,
13 "}": TokeType.CurlClose,
14 ":": TokeType.Colon
14 ":": TokeType.Colon
15 } as MapOf<TokeType>;
15 } as MapOf<TokeType>;
16
16
17 export class FormatScanner {
17 export class FormatScanner {
18 private _text: string;
18 private _text: string;
19 private _tokenType: TokeType;
19 private _tokenType: TokeType | undefined;
20 private _tokenValue: string;
20 private _tokenValue: string | undefined;
21 private _rx = /[^{}:]+|(.)/g;
21 private _rx = /[^{}:]+|(.)/g;
22
22
23 constructor(text: string) {
23 constructor(text: string) {
24 argumentNotEmptyString(text, text);
24 argumentNotEmptyString(text, text);
25 this._text = text;
25 this._text = text;
26 }
26 }
27
27
28 next() {
28 next() {
29 if (this._rx.lastIndex >= this._text.length)
29 if (this._rx.lastIndex >= this._text.length)
30 return false;
30 return false;
31
31
32 const match = this._rx.exec(this._text);
32 const match = this._rx.exec(this._text);
33 if (match === null)
34 return false;
35
33 this._tokenType = typeMap[match[1]] || TokeType.Text;
36 this._tokenType = typeMap[match[1]] || TokeType.Text;
34 this._tokenValue = match[0];
37 this._tokenValue = match[0];
35
38
36 return true;
39 return true;
37 }
40 }
38
41
39 getTokenValue() {
42 getTokenValue() {
43 if (this._tokenValue === undefined)
44 throw new Error("The scanner is before the first element");
40 return this._tokenValue;
45 return this._tokenValue;
41 }
46 }
42
47
43 getTokenType() {
48 getTokenType() {
49
50 if (this._tokenType === undefined)
51 throw new Error("The scanner is before the first element");
44 return this._tokenType;
52 return this._tokenType;
45 }
53 }
46 }
54 }
@@ -1,31 +1,31
1 import { TextWriterBase } from "./TextWriterBase";
1 import { TextWriterBase } from "./TextWriterBase";
2 import { Converter } from "./Converter";
2 import { Converter } from "./Converter";
3
3
4 export class StringBuilder extends TextWriterBase {
4 export class StringBuilder extends TextWriterBase {
5 private _data = new Array<string>();
5 private _data = new Array<string>();
6
6
7 constructor(converter = Converter.default) {
7 constructor(converter = Converter.default) {
8 super(converter);
8 super(converter);
9 }
9 }
10
10
11 writeText(text: string) {
11 writeText(text: string) {
12 this._data.push(text);
12 this._data.push(text);
13 }
13 }
14
14
15 toString() {
15 toString() {
16 return this._data.join("");
16 return this._data.join("");
17 }
17 }
18
18
19 clear() {
19 clear() {
20 this._data.length = 0;
20 this._data.length = 0;
21 }
21 }
22 }
22 }
23
23
24 const sb = new StringBuilder();
24 const sb = new StringBuilder();
25
25
26 export function format(format: string, ...args: any): string;
26 export function format(format: string, ...args: any): string;
27 export function format() {
27 export function format() {
28 sb.clear();
28 sb.clear();
29 sb.write.apply(sb, arguments);
29 sb.write.apply<StringBuilder, any, void>(sb, arguments);
30 return sb.toString();
30 return sb.toString();
31 }
31 }
@@ -1,177 +1,178
1 import { isPrimitive, isNull, each } from "../safe";
1 import { isPrimitive, isNull, each, isKeyof, get } from "../safe";
2 import { MapOf } from "../interfaces";
2 import { MapOf } from "../interfaces";
3
3
4 type SubstFn = (name: string, format?: string) => string;
4 type SubstFn = (name: string, format?: string) => string;
5 type TemplateFn = (subst: SubstFn) => string;
5 type TemplateFn = (subst: SubstFn) => string | undefined;
6 type ConvertFn = (value: any, format?: string) => string;
6 type ConvertFn = (value: any, format?: string) => string;
7
7
8 const map = {
8 const map = {
9 "\\{": "&curlopen;",
9 "\\{": "&curlopen;",
10 "\\}": "&curlclose;",
10 "\\}": "&curlclose;",
11 "&": "&amp;",
11 "&": "&amp;",
12 "\\:": "&colon;"
12 "\\:": "&colon;"
13 };
13 };
14
14
15 const rev = {
15 const rev = {
16 curlopen: "{",
16 curlopen: "{",
17 curlclose: "}",
17 curlclose: "}",
18 amp: "&",
18 amp: "&",
19 colon: ":"
19 colon: ":"
20 };
20 };
21
21
22 function espaceString(s: string) {
22 function espaceString(s: string) {
23 if (!s)
23 if (!s)
24 return s;
24 return s;
25 return "'" + s.replace(/('|\\)/g, "\\$1").replace("\n", "\\n") + "'";
25 return "'" + s.replace(/('|\\)/g, "\\$1").replace("\n", "\\n") + "'";
26 }
26 }
27
27
28 function encode(s: string) {
28 function encode(s: string) {
29 if (!s)
29 if (!s)
30 return s;
30 return s;
31 return s.replace(/\\{|\\}|&|\\:|\n/g, m => map[m] || m);
31 return s.replace(/\\{|\\}|&|\\:|\n/g, m => isKeyof(m, map) ? map[m] : m);
32 }
32 }
33
33
34 function decode(s: string) {
34 function decode(s: string) {
35 if (!s)
35 if (!s)
36 return s;
36 return s;
37 return s.replace(/&(\w+);/g, (m, $1) => rev[$1] || m);
37 return s.replace(/&(\w+);/g, (m, $1) => isKeyof($1, rev) ? rev[$1] : m);
38 }
38 }
39
39
40 function subst(s: string) {
40 function subst(s: string) {
41 const i = s.indexOf(":");
41 const i = s.indexOf(":");
42 let name: string;
42 let name: string;
43 let pattern: string;
43 let pattern: string | undefined;
44 if (i >= 0) {
44 if (i >= 0) {
45 name = s.substr(0, i);
45 name = s.substr(0, i);
46 pattern = s.substr(i + 1);
46 pattern = s.substr(i + 1);
47 } else {
47 } else {
48 name = s;
48 name = s;
49 }
49 }
50
50
51 if (pattern)
51 if (pattern)
52 return [
52 return [
53 espaceString(decode(name)),
53 espaceString(decode(name)),
54 espaceString(decode(pattern))];
54 espaceString(decode(pattern))
55 ];
55 else
56 else
56 return [espaceString(decode(name))];
57 return [espaceString(decode(name))];
57 }
58 }
58
59
59 function _compile(str: string) {
60 function _compile(str: string) {
60 if (!str)
61 if (!str)
61 return () => void 0;
62 return () => void 0;
62
63
63 const chunks = encode(str).split("{");
64 const chunks = encode(str).split("{");
64 let chunk: string;
65 let chunk: string;
65
66
66 const code = ["var result=[];"];
67 const code = ["var result=[];"];
67
68
68 for (let i = 0; i < chunks.length; i++) {
69 for (let i = 0; i < chunks.length; i++) {
69 chunk = chunks[i];
70 chunk = chunks[i];
70
71
71 if (i === 0) {
72 if (i === 0) {
72 if (chunk)
73 if (chunk)
73 code.push("result.push(" + espaceString(decode(chunk)) +
74 code.push("result.push(" + espaceString(decode(chunk)) +
74 ");");
75 ");");
75 } else {
76 } else {
76 const len = chunk.indexOf("}");
77 const len = chunk.indexOf("}");
77 if (len < 0)
78 if (len < 0)
78 throw new Error("Unbalanced substitution #" + i);
79 throw new Error("Unbalanced substitution #" + i);
79
80
80 code.push("result.push(subst(" +
81 code.push("result.push(subst(" +
81 subst(chunk.substr(0, len)).join(",") + "));");
82 subst(chunk.substr(0, len)).join(",") + "));");
82 if (chunk.length > len + 1)
83 if (chunk.length > len + 1)
83 code.push("result.push(" +
84 code.push("result.push(" +
84 espaceString(decode(chunk.substr(len + 1))) + ");");
85 espaceString(decode(chunk.substr(len + 1))) + ");");
85 }
86 }
86 }
87 }
87
88
88 code.push("return result.join('');");
89 code.push("return result.join('');");
89
90
90 // the code for this function is generated from the template
91 // the code for this function is generated from the template
91 // tslint:disable-next-line:function-constructor
92 // tslint:disable-next-line:function-constructor
92 return new Function("subst", code.join("\n")) as TemplateFn;
93 return new Function("subst", code.join("\n")) as TemplateFn;
93 }
94 }
94
95
95 const cache: MapOf<TemplateFn> = {};
96 const cache: MapOf<TemplateFn> = {};
96
97
97 export function compile(template: string) {
98 export function compile(template: string) {
98 let compiled = cache[template];
99 let compiled = cache[template];
99 if (!compiled) {
100 if (!compiled) {
100 compiled = _compile(template);
101 compiled = _compile(template);
101 cache[template] = compiled;
102 cache[template] = compiled;
102 }
103 }
103 return compiled;
104 return compiled;
104 }
105 }
105
106
106 function defaultConverter(value: any, pattern: string) {
107 function defaultConverter(value: any, pattern?: string) {
107 if (pattern && pattern.toLocaleLowerCase() === "json") {
108 if (pattern && pattern.toLocaleLowerCase() === "json") {
108 const seen = [];
109 const seen: any = [];
109 return JSON.stringify(value, (k, v) => {
110 return JSON.stringify(value, (k, v) => {
110 if (!isPrimitive(v)) {
111 if (!isPrimitive(v)) {
111 const id = seen.indexOf(v);
112 const id = seen.indexOf(v);
112 if (id >= 0)
113 if (id >= 0)
113 return "@ref-" + id;
114 return "@ref-" + id;
114 else {
115 else {
115 seen.push(v);
116 seen.push(v);
116 return v;
117 return v.toString() as string;
117 }
118 }
118 } else {
119 } else {
119 return v;
120 return isNull(v) ? "" : v.toString();
120 }
121 }
121 }, 2);
122 }, 2);
122 } else if (isNull(value)) {
123 } else if (isNull(value)) {
123 return "";
124 return "";
124 } else if (value instanceof Date) {
125 } else if (value instanceof Date) {
125 return value.toISOString();
126 return value.toISOString();
126 } else {
127 } else {
127 return value.toString();
128 return value.toString() as string;
128 }
129 }
129 }
130 }
130
131
131 export class Formatter {
132 export class Formatter {
132 _converters: ConvertFn[];
133 _converters: ConvertFn[];
133
134
134 constructor(converters?: ConvertFn[]) {
135 constructor(converters?: ConvertFn[]) {
135 this._converters = converters || [];
136 this._converters = converters || [];
136 this._converters.push(defaultConverter);
137 this._converters.push(defaultConverter);
137 }
138 }
138
139
139 convert(value: any, pattern: string) {
140 convert(value: any, pattern?: string) {
140 for (const c of this._converters) {
141 for (const c of this._converters) {
141 const res = c(value, pattern);
142 const res = c(value, pattern);
142 if (!isNull(res))
143 if (!isNull(res))
143 return res;
144 return res;
144 }
145 }
145 return "";
146 return "";
146 }
147 }
147
148
148 format(msg: string, ...args: any[]) {
149 format(msg: string, ...args: any[]) {
149 const template = compile(msg);
150 const template = compile(msg);
150
151
151 return template((name, pattern) => {
152 return template((name, pattern) => {
152 const value = args[name];
153 const value = get(name, args);
153 return !isNull(value) ? this.convert(value, pattern) : "";
154 return !isNull(value) ? this.convert(value, pattern) : "";
154 });
155 });
155
156
156 }
157 }
157
158
158 compile(msg: string) {
159 compile(msg: string) {
159 const template = compile(msg);
160 const template = compile(msg);
160 return (...args: any[]) => {
161 return (...args: any[]) => {
161 return template((name, pattern) => {
162 return template((name, pattern) => {
162 const value = args[name];
163 const value = get(name, args);
163 return !isNull(value) ? this.convert(value, pattern) : "";
164 return !isNull(value) ? this.convert(value, pattern) : "";
164 });
165 });
165 };
166 };
166 }
167 }
167 }
168 }
168
169
169 const _default = new Formatter();
170 const _default = new Formatter();
170
171
171 export function format(msg: string, ...args: any[]) {
172 export function format(msg: string, ...args: any[]) {
172 return _default.format(msg, ...args);
173 return _default.format(msg, ...args);
173 }
174 }
174
175
175 export function convert(value: any, pattern: string) {
176 export function convert(value: any, pattern: string) {
176 return _default.format(value, pattern);
177 return _default.format(value, pattern);
177 }
178 }
@@ -1,48 +1,46
1 import { TextWriter } from "../interfaces";
1 import { TextWriter } from "../interfaces";
2 import { FormatCompiler } from "./FormatCompiler";
2 import { FormatCompiler } from "./FormatCompiler";
3 import { isString, argumentNotNull } from "../safe";
3 import { isString, argumentNotNull } from "../safe";
4 import { Converter } from "./Converter";
4 import { Converter } from "./Converter";
5
5
6 const compiler = new FormatCompiler();
7
8 export abstract class TextWriterBase implements TextWriter {
6 export abstract class TextWriterBase implements TextWriter {
9 private _converter: Converter;
7 private _converter: Converter;
10
8
11 constructor(converter = Converter.default) {
9 constructor(converter = Converter.default) {
12 argumentNotNull(converter, "converter");
10 argumentNotNull(converter, "converter");
13 this._converter = converter;
11 this._converter = converter;
14 }
12 }
15
13
16 writeNewLine() {
14 writeNewLine() {
17 this.writeValue("\n");
15 this.writeValue("\n");
18 }
16 }
19
17
20 write(obj: any): void;
18 write(obj: any): void;
21 write(format: string, ...args: any[]): void;
19 write(format: string, ...args: any[]): void;
22 write(format: any, ...args: any[]): void {
20 write(format: any, ...args: any[]): void {
23 if (args.length) {
21 if (args.length) {
24 const compiled = compiler.compile(format);
22 const compiled = FormatCompiler.compile(format);
25 compiled(this, args);
23 compiled(this, args);
26 } else {
24 } else {
27 this.writeValue(format);
25 this.writeValue(format);
28 }
26 }
29 }
27 }
30
28
31 writeLine(obj?: any): void;
29 writeLine(obj?: any): void;
32 writeLine(format: string, ...args: any[]): void;
30 writeLine(format: string, ...args: any[]): void;
33 writeLine(): void {
31 writeLine(): void {
34 if (arguments.length)
32 if (arguments.length)
35 this.write.apply(this, arguments);
33 this.write.apply<this, any, void>(this, arguments);
36 this.writeNewLine();
34 this.writeNewLine();
37 }
35 }
38
36
39 writeValue(value: any, spec?: string): void {
37 writeValue(value: any, spec?: string) {
40 this.writeText(
38 this.writeText(
41 isString(value) ?
39 isString(value) ?
42 value :
40 value :
43 this._converter.convert(value, spec)
41 this._converter.convert(value, spec)
44 );
42 );
45 }
43 }
46
44
47 abstract writeText(text: string);
45 abstract writeText(text: string): void;
48 }
46 }
@@ -1,33 +1,33
1 import { Foo } from "./Foo";
1 import { Foo } from "./Foo";
2 import { config } from "./config";
2 // import { config } from "./config";
3
3
4 const service = config.build("bar");
4 // const service = config.build("bar");
5
5
6 @service.consume({
6 // @service.consume({
7 f: config.dependency("foo"),
7 // f: config.dependency("foo"),
8 nested: {
8 // nested: {
9 lazy: config.lazy("foo")
9 // lazy: config.lazy("foo")
10 }
10 // }
11 })
11 // })
12 export class Bar {
12 export class Bar {
13 barName = "bar";
13 barName = "bar";
14
14
15 _v: Foo | undefined;
15 _v: Foo | undefined;
16
16
17 constructor(_opts: {
17 constructor(_opts: {
18 f: Foo;
18 foo: Foo;
19 nested: {
19 nested: {
20 lazy: () => Foo
20 lazy: () => Foo
21 }
21 }
22 }) {
22 }) {
23
23
24 if (_opts && _opts.f)
24 if (_opts && _opts.foo)
25 this._v = _opts.f;
25 this._v = _opts.foo;
26 }
26 }
27
27
28 getFoo() {
28 getFoo() {
29 if (this._v === undefined)
29 if (this._v === undefined)
30 throw new Error("The foo isn't set");
30 throw new Error("The foo isn't set");
31 return this._v;
31 return this._v;
32 }
32 }
33 }
33 }
@@ -1,35 +1,35
1 import { services } from "../di/Annotations";
1 // import { services } from "../di/Annotations";
2 import { Bar } from "./Bar";
2 // import { Bar } from "./Bar";
3
3
4 // declare required dependencies
4 // declare required dependencies
5 const config = services<{
5 // const config = services<{
6 bar: Bar;
6 // bar: Bar;
7 }>();
7 // }>();
8
8
9 // export service descriptor
9 // export service descriptor
10 export const service = config.build<Box<Bar>>();
10 // export const service = config.build<Box<Bar>>();
11
11
12 @service.consume(config.dependency("bar"))
12 // @service.consume(config.dependency("bar"))
13 export class Box<T> {
13 export class Box<T> {
14 private _value: T | undefined;
14 private _value: T | undefined;
15
15
16 constructor(value: T) {
16 constructor(value: T) {
17 this._value = value;
17 this._value = value;
18 }
18 }
19
19
20 @service.inject("bar")
20 // @service.inject("bar")
21 setValue(value: T) {
21 setValue(value: T) {
22 this._value = value;
22 this._value = value;
23 }
23 }
24
24
25 setObj(value: object) {
25 setObj(value: object) {
26
26
27 }
27 }
28
28
29 getValue() {
29 getValue() {
30 if (this._value === undefined)
30 if (this._value === undefined)
31 throw new Error("Trying to get a value from the empty box");
31 throw new Error("Trying to get a value from the empty box");
32
32
33 return this._value;
33 return this._value;
34 }
34 }
35 }
35 }
@@ -1,43 +1,49
1 import { IActivatable, ICancellation, IActivationController } from "../interfaces";
1 import { IActivatable, ICancellation, IActivationController } from "../interfaces";
2 import { Cancellation } from "../Cancellation";
2 import { Cancellation } from "../Cancellation";
3
3
4 export class MockActivationController implements IActivationController {
4 export class MockActivationController implements IActivationController {
5
5
6 _active: IActivatable = null;
6 _active: IActivatable | null = null;
7
8 hasActive() {
9 return !!this._active;
10 }
7
11
8 getActive(): IActivatable {
12 getActive(): IActivatable {
13 if (!this._active)
14 throw new Error("No active component is set");
9 return this._active;
15 return this._active;
10 }
16 }
11
17
12 async deactivate() {
18 async deactivate() {
13 if (this._active)
19 if (this._active)
14 await this._active.deactivate();
20 await this._active.deactivate();
15 this._active = null;
21 this._active = null;
16 }
22 }
17
23
18 async activate(component: IActivatable) {
24 async activate(component: IActivatable) {
19 if (!component || component.isActive())
25 if (!component || component.isActive())
20 return;
26 return;
21 component.setActivationController(this);
27 component.setActivationController(this);
22
28
23 await component.activate();
29 await component.activate();
24 }
30 }
25
31
26 async activating(component: IActivatable, ct: ICancellation = Cancellation.none) {
32 async activating(component: IActivatable, ct: ICancellation = Cancellation.none) {
27 if (component !== this._active)
33 if (component !== this._active)
28 await this.deactivate();
34 await this.deactivate();
29 }
35 }
30
36
31 async activated(component: IActivatable, ct: ICancellation = Cancellation.none) {
37 async activated(component: IActivatable, ct: ICancellation = Cancellation.none) {
32 this._active = component;
38 this._active = component;
33 }
39 }
34
40
35 async deactivating(component: IActivatable, ct: ICancellation = Cancellation.none) {
41 async deactivating(component: IActivatable, ct: ICancellation = Cancellation.none) {
36
42
37 }
43 }
38
44
39 async deactivated(component: IActivatable, ct: ICancellation = Cancellation.none) {
45 async deactivated(component: IActivatable, ct: ICancellation = Cancellation.none) {
40 if (this._active === component)
46 if (this._active === component)
41 this._active = null;
47 this._active = null;
42 }
48 }
43 }
49 }
@@ -1,68 +1,64
1 import { Foo } from "./Foo";
1 import { Foo } from "./Foo";
2 import { Bar } from "./Bar";
2 import { Bar } from "./Bar";
3 import { ActivationType } from "../di/interfaces";
3 import { ActivationType } from "../di/interfaces";
4 import { Builder } from "../di/Annotations";
4 import { Builder } from "../di/Annotations";
5 import { Box } from "./Box";
5 import { Box } from "./Box";
6
6
7 interface RegistrationOptions {
7 interface RegistrationOptions {
8 activation?: ActivationType;
8 activation?: ActivationType;
9 }
9 }
10
10
11 interface Dependency<K extends keyof any> {
11 interface Dependency<K extends keyof any> {
12 $dependency: K;
12 $dependency: K;
13
13
14 lazy?: boolean;
14 lazy?: boolean;
15 }
15 }
16
16
17 interface Lazy<K extends keyof any> extends Dependency<K> {
17 interface Lazy<K extends keyof any> extends Dependency<K> {
18 lazy: true;
18 lazy: true;
19 }
19 }
20
20
21 type PromiseOrValue<T> = T | PromiseLike<T>;
21 type PromiseOrValue<T> = T | PromiseLike<T>;
22
22
23 interface ConfigBuilder<S> {
23 interface ConfigBuilder<S> {
24 build<K extends keyof S, T = S[K]>(name: K): Builder<T, S>;
24 build<K extends keyof S, T = S[K]>(name: K): Builder<T, S>;
25
25
26 dependency<K extends keyof S>(name: K): Dependency<K>;
26 dependency<K extends keyof S>(name: K): Dependency<K>;
27
27
28 lazy<K extends keyof S>(name: K): Lazy<K>;
28 lazy<K extends keyof S>(name: K): Lazy<K>;
29
29
30 mapTo<K extends keyof S>(name: K, ctor: () => PromiseOrValue<new (...args: any[]) => S[K]>): ConfigBuilder<S>;
30 mapTo<K extends keyof S>(name: K, ctor: () => PromiseOrValue<new (...args: any[]) => S[K]>): ConfigBuilder<S>;
31
31
32 }
32 }
33
33
34 interface ContainerServices {
34 interface ContainerServices {
35 barBox: Box<Bar>;
35 barBox: Box<Bar>;
36
36
37 foo: Foo;
37 foo: Foo;
38
38
39 bar: Bar;
39 bar: Bar;
40
40
41 password: string;
41 password: string;
42
42
43 user: string;
43 user: string;
44
44
45 timeout: number;
45 timeout: number;
46 }
46 }
47
47
48 declare function load<M, C extends keyof M>(m: PromiseLike<M>, name: C): () => PromiseLike<M[C]>;
48 declare function load<M, C extends keyof M>(m: PromiseLike<M>, name: C): () => PromiseLike<M[C]>;
49
49
50 const t = {
50 const t = {
51 barBox: load(import("./Box"), "Box"),
51 barBox: load(import("./Box"), "Box"),
52
52
53 foo: async () => (await import("./Bar")).Bar,
53 foo: async () => (await import("./Bar")).Bar,
54
54
55 bar: Bar,
55 bar: Bar,
56
56
57 password: String,
57 password: String,
58
58
59 user: String,
59 user: String,
60
60
61 timeout: Number
61 timeout: Number
62 };
62 };
63
63
64 declare const bc: typeof Box;
65
66 const x = new bc();
67
68 export declare const config: ConfigBuilder<ContainerServices>;
64 export declare const config: ConfigBuilder<ContainerServices>;
@@ -1,54 +1,65
1 import { MockActivationController } from "../mock/MockActivationController";
1 import { MockActivationController } from "../mock/MockActivationController";
2 import { SimpleActivatable } from "../mock/SimpleActivatable";
2 import { SimpleActivatable } from "../mock/SimpleActivatable";
3 import { test } from "./TestTraits";
3 import { test } from "./TestTraits";
4
4
5 test("simple activation", async t => {
5 test("simple activation", async t => {
6
6
7 const a = new SimpleActivatable();
7 const a = new SimpleActivatable();
8 t.false(a.isActive());
8 t.false(a.isActive());
9
9
10 await a.activate();
10 await a.activate();
11 t.true(a.isActive());
11 t.true(a.isActive());
12
12
13 await a.deactivate();
13 await a.deactivate();
14 t.false(a.isActive());
14 t.false(a.isActive());
15 });
15 });
16
16
17 test("controller activation", async t => {
17 test("controller activation", async t => {
18
18
19 const a = new SimpleActivatable();
19 const a = new SimpleActivatable();
20 const c = new MockActivationController();
20 const c = new MockActivationController();
21
21
22 t.false(a.isActive(), "the component is not active by default");
22 t.false(a.isActive(), "the component is not active by default");
23 t.assert(c.getActive() == null, "the activation controller doesn't have an active component by default");
23 t.false(c.hasActive(), "the activation controller doesn't have an active component by default");
24 t.assert(a.getActivationController() == null, "the component doesn't have an activation controller by default");
24 try {
25 c.getActive();
26 t.fail("Should fail when no active component is set");
27 } catch (e) {
28 }
29
30 t.false(a.hasActivationController(), "the component doesn't have an activation controller by default");
31 try {
32 a.getActivationController();
33 t.fail("Should fail when no activation controller is set");
34 } catch (e) {
35 }
25
36
26 t.comment("Active the component through the controller");
37 t.comment("Active the component through the controller");
27 await c.activate(a);
38 await c.activate(a);
28 t.true(a.isActive(), "The component should successfully activate");
39 t.true(a.isActive(), "The component should successfully activate");
29 t.equal(c.getActive(), a, "The controller should point to the activated component");
40 t.equal(c.getActive(), a, "The controller should point to the activated component");
30 t.equal(a.getActivationController(), c, "The component should point to the controller");
41 t.equal(a.getActivationController(), c, "The component should point to the controller");
31
42
32 t.comment("Deactive the component throug the controller");
43 t.comment("Deactive the component throug the controller");
33 await c.deactivate();
44 await c.deactivate();
34
45
35 t.false(a.isActive(), "The component should successfully deactivate");
46 t.false(a.isActive(), "The component should successfully deactivate");
36 t.equal(c.getActive(), null, "The controller shouldn't point to any component");
47 t.false(c.hasActive(), "The controller shouldn't point to any component");
37 t.equal(a.getActivationController(), c, "The componet should point to it's controller");
48 t.equal(a.getActivationController(), c, "The componet should point to it's controller");
38 });
49 });
39
50
40 test("handle error in onActivating", async t => {
51 test("handle error in onActivating", async t => {
41 const a = new SimpleActivatable();
52 const a = new SimpleActivatable();
42
53
43 a.onActivating = async () => {
54 a.onActivating = async () => {
44 throw new Error("Should fail");
55 throw new Error("Should fail");
45 };
56 };
46
57
47 try {
58 try {
48 await a.activate();
59 await a.activate();
49 t.fail("activation should fail");
60 t.fail("activation should fail");
50 } catch {
61 } catch {
51 }
62 }
52
63
53 t.false(a.isActive(), "the component should remain inactive");
64 t.false(a.isActive(), "the component should remain inactive");
54 });
65 });
@@ -1,88 +1,88
1 import { Cancellation } from "../Cancellation";
1 import { Cancellation } from "../Cancellation";
2 import { delay } from "../safe";
2 import { delay, notImplemented } from "../safe";
3 import { test } from "./TestTraits";
3 import { test } from "./TestTraits";
4
4
5 test("standalone cancellation", async t => {
5 test("standalone cancellation", async t => {
6
6
7 let doCancel: (e) => void;
7 let doCancel: (e: any) => void = notImplemented;
8
8
9 const ct = new Cancellation(cancel => {
9 const ct = new Cancellation(cancel => {
10 doCancel = cancel;
10 doCancel = cancel;
11 });
11 });
12
12
13 let counter = 0;
13 let counter = 0;
14 const reason = "BILL";
14 const reason = "BILL";
15
15
16 t.true(ct.isSupported(), "Cancellation must be supported");
16 t.true(ct.isSupported(), "Cancellation must be supported");
17 t.false(ct.isRequested(), "Cancellation shouldn't be requested");
17 t.false(ct.isRequested(), "Cancellation shouldn't be requested");
18 ct.throwIfRequested();
18 ct.throwIfRequested();
19 t.pass("The exception shouldn't be thrown unless the cancellation is requested");
19 t.pass("The exception shouldn't be thrown unless the cancellation is requested");
20
20
21 ct.register(() => counter++);
21 ct.register(() => counter++);
22 t.equals(counter, 0, "counter should be zero");
22 t.equals(counter, 0, "counter should be zero");
23
23
24 ct.register(() => counter++).destroy();
24 ct.register(() => counter++).destroy();
25
25
26 doCancel(reason);
26 doCancel(reason);
27
27
28 t.true(ct.isRequested(), "Cancellation should be requested");
28 t.true(ct.isRequested(), "Cancellation should be requested");
29 t.equals(counter, 1, "The registered callback should be triggered");
29 t.equals(counter, 1, "The registered callback should be triggered");
30
30
31 ct.register(() => counter++);
31 ct.register(() => counter++);
32 t.equals(counter, 2, "The callback should be triggered immediately");
32 t.equals(counter, 2, "The callback should be triggered immediately");
33
33
34 let msg;
34 let msg;
35 ct.register(e => msg = e);
35 ct.register(e => msg = e);
36 t.equals(msg, reason, "The cancellation reason should be passed to callback");
36 t.equals(msg, reason, "The cancellation reason should be passed to callback");
37
37
38 try {
38 try {
39 msg = null;
39 msg = null;
40 ct.throwIfRequested();
40 ct.throwIfRequested();
41 t.fail("The exception should be thrown");
41 t.fail("The exception should be thrown");
42 } catch (e) {
42 } catch (e) {
43 msg = e;
43 msg = e;
44 }
44 }
45 t.equals(msg, reason, "The cancellation reason should be catched");
45 t.equals(msg, reason, "The cancellation reason should be catched");
46 });
46 });
47
47
48 test("async cancellation", async t => {
48 test("async cancellation", async t => {
49
49
50 const ct = new Cancellation(cancel => {
50 const ct = new Cancellation(cancel => {
51 cancel("STOP!");
51 cancel("STOP!");
52 });
52 });
53
53
54 try {
54 try {
55 await delay(0, ct);
55 await delay(0, ct);
56 t.fail("Should thow the exception");
56 t.fail("Should thow the exception");
57 } catch (e) {
57 } catch (e) {
58 t.equals(e, "STOP!", "Should throw the cancellation reason");
58 t.equals(e, "STOP!", "Should throw the cancellation reason");
59 }
59 }
60 });
60 });
61
61
62 test("cancel with external event", async t => {
62 test("cancel with external event", async t => {
63 const ct = new Cancellation(cancel => {
63 const ct = new Cancellation(cancel => {
64 setTimeout(x => cancel("STOP!"), 0);
64 setTimeout(x => cancel("STOP!"), 0);
65 });
65 });
66
66
67 try {
67 try {
68 await delay(10000, ct);
68 await delay(10000, ct);
69 t.fail("Should thow the exception");
69 t.fail("Should thow the exception");
70 } catch (e) {
70 } catch (e) {
71 t.equals(e, "STOP!", "Should throw the cancellation reason");
71 t.equals(e, "STOP!", "Should throw the cancellation reason");
72 }
72 }
73 });
73 });
74
74
75 test("operation normal flow", async t => {
75 test("operation normal flow", async t => {
76
76
77 let htimeout;
77 let htimeout;
78 const ct = new Cancellation(cancel => {
78 const ct = new Cancellation(cancel => {
79 htimeout = setTimeout(() => cancel("STOP!"), 1000);
79 htimeout = setTimeout(() => cancel("STOP!"), 1000);
80 });
80 });
81
81
82 try {
82 try {
83 await delay(0, ct);
83 await delay(0, ct);
84 t.pass("Should pass");
84 t.pass("Should pass");
85 } finally {
85 } finally {
86 clearTimeout(htimeout);
86 clearTimeout(htimeout);
87 }
87 }
88 });
88 });
@@ -1,93 +1,94
1 import { test } from "./TestTraits";
1 import { test } from "./TestTraits";
2 import { Container } from "../di/Container";
2 import { Container } from "../di/Container";
3 import { ReferenceDescriptor } from "../di/ReferenceDescriptor";
3 import { ReferenceDescriptor } from "../di/ReferenceDescriptor";
4 import { AggregateDescriptor } from "../di/AggregateDescriptor";
4 import { AggregateDescriptor } from "../di/AggregateDescriptor";
5 import { ValueDescriptor } from "../di/ValueDescriptor";
5 import { ValueDescriptor } from "../di/ValueDescriptor";
6 import { Foo } from "../mock/Foo";
6 import { Foo } from "../mock/Foo";
7 import { Bar } from "../mock/Bar";
7 import { Bar } from "../mock/Bar";
8 import { isNull } from "../safe";
8 import { isNull } from "../safe";
9 import { Descriptor } from "../di/interfaces";
9
10
10 test("Container register/resolve tests", async t => {
11 test("Container register/resolve tests", async t => {
11 const container = new Container();
12 const container = new Container();
12
13
13 const connection1 = "db://localhost";
14 const connection1 = "db://localhost";
14
15
15 t.throws(
16 t.throws(
16 () => container.register("bla-bla", "bla-bla"),
17 () => container.register("bla-bla", "bla-bla" as any),
17 "Do not allow to register anything other than descriptors"
18 "Do not allow to register anything other than descriptors"
18 );
19 );
19
20
20 t.doesNotThrow(
21 t.doesNotThrow(
21 () => container.register("connection", new ValueDescriptor(connection1)),
22 () => container.register("connection", new ValueDescriptor(connection1)),
22 "register ValueDescriptor"
23 "register ValueDescriptor"
23 );
24 );
24
25
25 t.equals(container.resolve("connection"), connection1, "resolve string value");
26 t.equals(container.resolve("connection"), connection1, "resolve string value");
26
27
27 t.doesNotThrow(
28 t.doesNotThrow(
28 () => container.register(
29 () => container.register(
29 "dbParams",
30 "dbParams",
30 new AggregateDescriptor({
31 new AggregateDescriptor({
31 timeout: 10,
32 timeout: 10,
32 connection: new ReferenceDescriptor({ name: "connection" })
33 connection: new ReferenceDescriptor({ name: "connection" })
33 })
34 })
34 ),
35 ),
35 "register AggregateDescriptor"
36 "register AggregateDescriptor"
36 );
37 );
37
38
38 const dbParams = container.resolve("dbParams");
39 const dbParams = container.resolve("dbParams");
39 t.equals(dbParams.connection, connection1, "should get string value 'dbParams.connection'");
40 t.equals(dbParams.connection, connection1, "should get string value 'dbParams.connection'");
40 });
41 });
41
42
42 test("Container configure/resolve tests", async t => {
43 test("Container configure/resolve tests", async t => {
43
44
44 const container = new Container();
45 const container = new Container();
45
46
46 await container.configure({
47 await container.configure({
47 foo: {
48 foo: {
48 $type: Foo
49 $type: Foo
49 },
50 },
50
51
51 box: {
52 box: {
52 $type: Bar,
53 $type: Bar,
53 params: {
54 params: {
54 $dependency: "foo"
55 $dependency: "foo"
55 }
56 }
56 },
57 },
57
58
58 bar: {
59 bar: {
59 $type: Bar,
60 $type: Bar,
60 params: {
61 params: {
61 db: {
62 db: {
62 provider: {
63 provider: {
63 $dependency: "db"
64 $dependency: "db"
64 }
65 }
65 }
66 }
66 }
67 }
67 }
68 }
68 });
69 });
69 t.pass("should configure from js object");
70 t.pass("should configure from js object");
70
71
71 const f1 = container.resolve("foo");
72 const f1 = container.resolve("foo");
72
73
73 t.assert(!isNull(f1), "foo should be not null");
74 t.assert(!isNull(f1), "foo should be not null");
74
75
75 t.throws(() => container.resolve("bar"), "should not resolve dependency 'db'");
76 t.throws(() => container.resolve("bar"), "should not resolve dependency 'db'");
76
77
77 });
78 });
78
79
79 test("Load configuration from module", async t => {
80 test("Load configuration from module", async t => {
80 const container = new Container();
81 const container = new Container();
81
82
82 await container.configure("../mock/config1", { contextRequire: require });
83 await container.configure("../mock/config1", { contextRequire: require });
83 t.pass("The configuration should load");
84 t.pass("The configuration should load");
84
85
85 const f1 = container.resolve("foo");
86 const f1 = container.resolve("foo");
86
87
87 t.assert(!isNull(f1), "foo should be not null");
88 t.assert(!isNull(f1), "foo should be not null");
88
89
89 const b1 = container.resolve("bar") as Bar;
90 const b1 = container.resolve("bar") as Bar;
90
91
91 t.assert(!isNull(b1), "bar should not be null");
92 t.assert(!isNull(b1), "bar should not be null");
92 t.assert(!isNull(b1._v), "bar.foo should not be null");
93 t.assert(!isNull(b1._v), "bar.foo should not be null");
93 });
94 });
@@ -1,69 +1,74
1 import { TraceSource } from "../log/TraceSource";
1 import { TraceSource } from "../log/TraceSource";
2 import { Observable } from "../Observable";
2 import { Observable } from "../Observable";
3 import { IObservable } from "../interfaces";
3 import { IObservable } from "../interfaces";
4 import { delay } from "../safe";
4 import { delay, fork } from "../safe";
5 import { test } from "./TestTraits";
5 import { test } from "./TestTraits";
6
6
7 const trace = TraceSource.get("ObservableTests");
7 const trace = TraceSource.get("ObservableTests");
8
8
9 test("events sequence example", async t => {
9 test("events sequence example", async t => {
10
10
11 let events: IObservable<number>;
11 let events: IObservable<number> | undefined;
12
12
13 const done = new Promise<void>(resolve => {
13 const done = new Promise<void>(resolve => {
14 events = new Observable<number>(async (notify, fail, finish) => {
14 events = new Observable<number>(async (notify, fail, finish) => {
15 for (let i = 0; i < 10; i++) {
15 for (let i = 0; i < 10; i++) {
16 await delay(0);
16 await fork();
17 notify(i);
17 notify(i);
18 }
18 }
19 finish();
19 finish();
20 resolve();
20 resolve();
21 });
21 });
22 });
22 });
23
23
24 let count = 0;
24 let count = 0;
25 let complete = false;
25 let complete = false;
26 events.on(x => count = count + x, null, () => complete = true);
26 if (!events)
27 throw new Error("events === undefined");
28 events.on(x => count = count + x, undefined, () => complete = true);
27
29
28 const first = await events.next();
30 const first = await events.next();
29
31
30 t.equals(first, 0, "the first event");
32 t.equals(first, 0, "the first event");
31 t.false(complete, "the sequence is not complete");
33 t.false(complete, "the sequence is not complete");
32
34
33 await done;
35 await done;
34
36
35 t.equals(count, 45, "the summ of the evetns");
37 t.equals(count, 45, "the summ of the evetns");
36 t.true(complete, "the sequence is complete");
38 t.true(complete, "the sequence is complete");
37 });
39 });
38
40
39 test("event sequence termination", async t => {
41 test("event sequence termination", async t => {
40 let events: IObservable<number>;
42 let events: IObservable<number> | undefined;
41
43
42 const done = new Promise<void>(resolve => {
44 const done = new Promise<void>(resolve => {
43 events = new Observable<number>(async (notify, fail, complete) => {
45 events = new Observable<number>(async (notify, fail, complete) => {
44 await delay(0);
46 await fork();
45 notify(1);
47 notify(1);
46 complete();
48 complete();
47 notify(2);
49 notify(2);
48 complete();
50 complete();
49 fail("Sequence terminated");
51 fail("Sequence terminated");
50 resolve();
52 resolve();
51 });
53 });
52 });
54 });
53
55
56 if (!events)
57 throw new Error("events === undefined");
58
54 let count = 0;
59 let count = 0;
55 events.on(() => {}, e => count++, () => count++);
60 events.on(() => {}, e => count++, () => count++);
56
61
57 const first = await events.next();
62 const first = await events.next();
58 t.equals(first, 1, "the first message");
63 t.equals(first, 1, "the first message");
59 try {
64 try {
60 await events.next();
65 await events.next();
61 t.fail("shoud throw an exception");
66 t.fail("shoud throw an exception");
62 } catch (e) {
67 } catch (e) {
63 t.pass("the sequence is terminated");
68 t.pass("the sequence is terminated");
64 }
69 }
65
70
66 await done;
71 await done;
67
72
68 t.equals(count, 1, "the sequence must be terminated once");
73 t.equals(count, 1, "the sequence must be terminated once");
69 });
74 });
@@ -1,95 +1,95
1 import { Cancellation } from "../Cancellation";
1 import { Cancellation } from "../Cancellation";
2 import { first, isPromise, firstWhere, delay, nowait } from "../safe";
2 import { first, isPromise, firstWhere, delay, nowait, notImplemented } from "../safe";
3 import { test } from "./TestTraits";
3 import { test } from "./TestTraits";
4
4
5 test("await delay test", async t => {
5 test("await delay test", async t => {
6 // schedule delay
6 // schedule delay
7 let resolved = false;
7 let resolved = false;
8 let res = delay(0).then(() => resolved = true);
8 let res = delay(0).then(() => resolved = true);
9
9
10 t.false(resolved, "the delay should be async");
10 t.false(resolved, "the delay should be async");
11
11
12 await res;
12 await res;
13 t.pass("await delay");
13 t.pass("await delay");
14
14
15 // create cancellation token
15 // create cancellation token
16 let cancel: (e?: any) => void;
16 let cancel: (e?: any) => void = notImplemented;
17 const ct = new Cancellation(c => cancel = c);
17 const ct = new Cancellation(c => cancel = c);
18
18
19 // schedule delay
19 // schedule delay
20 resolved = false;
20 resolved = false;
21 res = delay(0, ct).then(() => resolved = true);
21 res = delay(0, ct).then(() => resolved = true);
22
22
23 t.false(resolved, "created delay with ct");
23 t.false(resolved, "created delay with ct");
24
24
25 // cancel
25 // cancel
26 cancel();
26 cancel();
27
27
28 try {
28 try {
29 await res;
29 await res;
30 t.fail("the delay should fail when it is cancelled");
30 t.fail("the delay should fail when it is cancelled");
31 } catch {
31 } catch {
32 t.pass("the delay is cancelled");
32 t.pass("the delay is cancelled");
33 }
33 }
34
34
35 t.throws(() => {
35 t.throws(() => {
36 // try schedule delay after the cancellation is requested
36 // try schedule delay after the cancellation is requested
37 nowait(delay(0, ct));
37 nowait(delay(0, ct));
38 }, "Should throw if cancelled before start");
38 }, "Should throw if cancelled before start");
39 });
39 });
40
40
41 test("sequemce test", async t => {
41 test("sequemce test", async t => {
42 const sequence = ["a", "b", "c"];
42 const sequence = ["a", "b", "c"];
43 const empty = [];
43 const empty: string[] = [];
44
44
45 // synchronous tests
45 // synchronous tests
46 t.equals(first(sequence), "a", "Should return the first element");
46 t.equals(first(sequence), "a", "Should return the first element");
47 t.equals(firstWhere(sequence, x => x === "b"), "b", "Should get the second element");
47 t.equals(firstWhere(sequence, x => x === "b"), "b", "Should get the second element");
48
48
49 let v: string;
49 let v: string | undefined;
50 let e: Error;
50 let e: Error | undefined;
51 first(sequence, x => v = x);
51 first(sequence, x => v = x);
52 t.equal(v, "a", "The callback should be called for the first element");
52 t.equal(v, "a", "The callback should be called for the first element");
53 firstWhere(sequence, x => x === "b", x => v = x);
53 firstWhere(sequence, x => x === "b", x => v = x);
54 t.equal(v, "b", "The callback should be called for the second element");
54 t.equal(v, "b", "The callback should be called for the second element");
55
55
56 t.throws(() => {
56 t.throws(() => {
57 first(empty);
57 first(empty);
58 }, "Should throw when the sequence is empty");
58 }, "Should throw when the sequence is empty");
59
59
60 t.throws(() => {
60 t.throws(() => {
61 firstWhere(empty, x => x === "b");
61 firstWhere(empty, x => x === "b");
62 }, "Should throw when the sequence is empty");
62 }, "Should throw when the sequence is empty");
63
63
64 t.throws(() => {
64 t.throws(() => {
65 first(empty, x => v = x);
65 first(empty, x => v = x);
66 }, "Should throw when the sequence is empty");
66 }, "Should throw when the sequence is empty");
67
67
68 t.throws(() => {
68 t.throws(() => {
69 firstWhere(empty, x => x === "b", x => v = x);
69 firstWhere(empty, x => x === "b", x => v = x);
70 }, "Should throw when the sequence is empty");
70 }, "Should throw when the sequence is empty");
71
71
72 t.throws(() => {
72 t.throws(() => {
73 firstWhere(sequence, x => x === "z");
73 firstWhere(sequence, x => x === "z");
74 }, "Should throw when the element isn't found");
74 }, "Should throw when the element isn't found");
75
75
76 t.throws(() => {
76 t.throws(() => {
77 firstWhere(sequence, x => x === "z", x => v = x);
77 firstWhere(sequence, x => x === "z", x => v = x);
78 }, "Should throw when the element isn't found");
78 }, "Should throw when the element isn't found");
79
79
80 first(empty, null, x => e = x);
80 first(empty, undefined, x => e = x);
81 t.true(e, "The errorback should be called for the empty sequence");
81 t.true(e, "The errorback should be called for the empty sequence");
82
82
83 // async tests
83 // async tests
84 const asyncSequence = Promise.resolve(sequence);
84 const asyncSequence = Promise.resolve(sequence);
85 const asyncEmptySequence = Promise.resolve(empty);
85 const asyncEmptySequence = Promise.resolve(empty);
86
86
87 const promise = first(asyncSequence);
87 const promise = first(asyncSequence);
88 t.true(isPromise(promise), "Should return promise");
88 t.true(isPromise(promise), "Should return promise");
89
89
90 v = await promise;
90 v = await promise;
91 t.equal(v, "a", "Should return the first element");
91 t.equal(v, "a", "Should return the first element");
92
92
93 v = await new Promise(resolve => first(asyncSequence, resolve));
93 v = await new Promise(resolve => first(asyncSequence, resolve));
94 t.equal(v, "a", "The callback should be called for the first element");
94 t.equal(v, "a", "The callback should be called for the first element");
95 });
95 });
@@ -1,74 +1,77
1 import { IObservable, ICancellation, IDestroyable } from "../interfaces";
1 import { IObservable, ICancellation, IDestroyable } from "../interfaces";
2 import { Cancellation } from "../Cancellation";
2 import { Cancellation } from "../Cancellation";
3 import { TraceEvent, LogLevel, WarnLevel, DebugLevel, TraceSource } from "../log/TraceSource";
3 import { TraceEvent, LogLevel, WarnLevel, DebugLevel, TraceSource } from "../log/TraceSource";
4 import * as tape from "tape";
4 import * as tape from "tape";
5 import { argumentNotNull, destroy } from "../safe";
5 import { argumentNotNull, destroy } from "../safe";
6
6
7 export class TapeWriter implements IDestroyable {
7 export class TapeWriter implements IDestroyable {
8 private readonly _tape: tape.Test;
8 private readonly _tape: tape.Test;
9
9
10 private readonly _subscriptions = new Array<IDestroyable>();
10 private readonly _subscriptions = new Array<IDestroyable>();
11 private _destroyed;
11 private _destroyed = false;
12
12
13 constructor(t: tape.Test) {
13 constructor(t: tape.Test) {
14 argumentNotNull(t, "tape");
14 argumentNotNull(t, "tape");
15 this._tape = t;
15 this._tape = t;
16 }
16 }
17
17
18 writeEvents(source: IObservable<TraceEvent>, ct: ICancellation = Cancellation.none) {
18 writeEvents(source: IObservable<TraceEvent>, ct: ICancellation = Cancellation.none) {
19 if (!this._destroyed) {
19 if (!this._destroyed) {
20 const subscription = source.on(this.writeEvent.bind(this));
20 const subscription = source.on(this.writeEvent.bind(this));
21 if (ct.isSupported()) {
21 if (ct.isSupported()) {
22 ct.register(subscription.destroy.bind(subscription));
22 ct.register(subscription.destroy.bind(subscription));
23 }
23 }
24 this._subscriptions.push(subscription);
24 this._subscriptions.push(subscription);
25 }
25 }
26 }
26 }
27
27
28 writeEvent(next: TraceEvent) {
28 writeEvent(next: TraceEvent) {
29 if (next.level >= DebugLevel) {
29 if (next.level >= DebugLevel) {
30 this._tape.comment(`DEBUG ${next.source.id} ${next}`);
30 this._tape.comment(`DEBUG ${next.source.id} ${next}`);
31 } else if (next.level >= LogLevel) {
31 } else if (next.level >= LogLevel) {
32 this._tape.comment(`LOG ${next.source.id} ${next}`);
32 this._tape.comment(`LOG ${next.source.id} ${next}`);
33 } else if (next.level >= WarnLevel) {
33 } else if (next.level >= WarnLevel) {
34 this._tape.comment(`WARN ${next.source.id} ${next}`);
34 this._tape.comment(`WARN ${next.source.id} ${next}`);
35 } else {
35 } else {
36 this._tape.comment(`ERROR ${next.source.id} ${next}`);
36 this._tape.comment(`ERROR ${next.source.id} ${next}`);
37 }
37 }
38 }
38 }
39
39
40 destroy() {
40 destroy() {
41 if (this._destroyed)
42 return;
43 this._destroyed = true;
41 this._subscriptions.forEach(destroy);
44 this._subscriptions.forEach(destroy);
42 }
45 }
43 }
46 }
44
47
45 export function test(name: string, cb: (t: tape.Test, trace: TraceSource) => any) {
48 export function test(name: string, cb: (t: tape.Test, trace: TraceSource) => any) {
46 tape(name, async t => {
49 tape(name, async t => {
47 const writer = new TapeWriter(t);
50 const writer = new TapeWriter(t);
48
51
49 // this trace is not announced through the TraceSource global registry
52 // this trace is not announced through the TraceSource global registry
50 const trace = new TraceSource(name);
53 const trace = new TraceSource(name);
51 trace.level = DebugLevel;
54 trace.level = DebugLevel;
52 writer.writeEvents(trace.events);
55 writer.writeEvents(trace.events);
53
56
54 const h = TraceSource.on(ts => {
57 const h = TraceSource.on(ts => {
55 ts.level = DebugLevel;
58 ts.level = DebugLevel;
56 writer.writeEvents(ts.events);
59 writer.writeEvents(ts.events);
57 });
60 });
58
61
59 try {
62 try {
60 await cb(t, trace);
63 await cb(t, trace);
61 } catch (e) {
64 } catch (e) {
62
65
63 // verbose error information
66 // verbose error information
64 // tslint:disable-next-line
67 // tslint:disable-next-line
65 console.error(e);
68 console.error(e);
66 t.fail(e);
69 t.fail(e);
67
70
68 } finally {
71 } finally {
69 t.end();
72 t.end();
70 destroy(writer);
73 destroy(writer);
71 destroy(h);
74 destroy(h);
72 }
75 }
73 });
76 });
74 }
77 }
General Comments 0
You need to be logged in to leave comments. Login now