##// END OF EJS Templates
fixed bug in applying a lifetime while processing the json configuration...
cin -
r152:8bee6a6d5f46 v1.4.0-rc10 default
parent child
Show More
@@ -1,74 +1,74
1 1 import { argumentNotEmptyString } from "../safe";
2 2 import { MapOf } from "../interfaces";
3 3 import { TraceSource, DebugLevel } from "../log/TraceSource";
4 4 import m = require("module");
5 5
6 6 const trace = TraceSource.get(m.id);
7 7
8 8 const splitRx = /(<%=|<%~|\[%~|\[%=|<%|\[%|%\]|%>)/;
9 9
10 10 export enum TokenType {
11 11 None,
12 12 Text,
13 13 OpenInlineBlock,
14 14 OpenFilterBlock,
15 15 OpenBlock,
16 16 CloseBlock
17 17 }
18 18
19 19 const tokenMap: MapOf<TokenType> = {
20 20 "<%": TokenType.OpenBlock,
21 21 "[%": TokenType.OpenBlock,
22 22 "<%=": TokenType.OpenInlineBlock,
23 23 "[%=": TokenType.OpenInlineBlock,
24 24 "<%~": TokenType.OpenFilterBlock,
25 25 "[%~": TokenType.OpenFilterBlock,
26 26 "%>": TokenType.CloseBlock,
27 27 "%]": TokenType.CloseBlock
28 28 };
29 29
30 30 export interface ITemplateParser {
31 31 next(): boolean;
32 32 token(): TokenType;
33 33 value(): string;
34 34 }
35 35
36 36 export class TemplateParser implements ITemplateParser {
37 37
38 38 _tokens: string[];
39 39 _pos = -1;
40 40 _type: TokenType;
41 41 _value: string | undefined;
42 42
43 43 constructor(text: string) {
44 44 argumentNotEmptyString(text, "text");
45 45
46 46 this._tokens = text.split(splitRx);
47 47 this._type = TokenType.None;
48 48 }
49 49
50 50 next() {
51 51 this._pos++;
52 52 if (this._pos < this._tokens.length) {
53 53 this._value = this._tokens[this._pos];
54 54 this._type = tokenMap[this._value] || TokenType.Text;
55 55
56 56 return true;
57 57 } else {
58 58 this._type = TokenType.None;
59 59 this._value = undefined;
60 60 return false;
61 61 }
62 62 }
63 63
64 64 token() {
65 65 return this._type;
66 66 }
67 67
68 68 value() {
69 if (!this._value)
69 if (this._value === undefined)
70 70 throw new Error("The current token doesn't have a value");
71 71 return this._value;
72 72 }
73 73
74 74 }
@@ -1,49 +1,56
1 1 import request = require("dojo/request");
2 2 import m = require("module");
3 3 import { TraceSource } from "../log/TraceSource";
4 4 import { TemplateCompiler } from "./TemplateCompiler";
5 5 import { TemplateParser } from "./TemplateParser";
6 6 import { isNullOrEmptyString } from "../safe";
7 7 import { MapOf } from "../interfaces";
8 8
9 9 type TemplateFn = (obj: object) => string;
10 10
11 11 const trace = TraceSource.get(m.id);
12 12
13 13 function compile(str: string) {
14 14 if (isNullOrEmptyString(str))
15 15 return () => "";
16 16
17 17 const parser = new TemplateParser(str);
18 18 const compiler = new TemplateCompiler();
19 19
20 20 return compiler.compile(parser);
21 21 }
22 22
23 23 const cache: MapOf<TemplateFn> = {};
24 24
25 25 interface OnLoadFn<T> {
26 26 (res: T): void;
27 27 error(e: any): void;
28 28 }
29 29
30 30 compile.load = (id: string, require: Require, callback: OnLoadFn<TemplateFn>) => {
31 31 const url = require.toUrl(id);
32 32 if (url in cache) {
33 33 trace.debug("{0} -> {1}: cached", id, url);
34 34 callback(cache[url]);
35 35 } else {
36 36 trace.debug("{0} -> {1}: load", id, url);
37 37 request<string>(url).then(compile).then((tc: TemplateFn) => {
38 38 trace.debug("{0}: compiled", url);
39 39 callback(cache[url] = tc);
40 40 }, (err: any) => {
41 if (callback.error)
41 42 callback.error({
42 43 inner: err,
43 src: "@implab/core/text/template-compile"
44 from: "@implab/core/text/template-compile"
45 });
46 else
47 trace.error({
48 message: `Failed to load: ${url}`,
49 error: err,
50 from: "@implab/core/text/template-compile"
44 51 });
45 52 });
46 53 }
47 54 };
48 55
49 56 export = compile;
@@ -1,12 +1,14
1 export class ConfigError extends Error {
1 export class ConfigError {
2 2 inner?: {};
3 3
4 message: string;
5
4 6 path?: string;
5 7
6 8 configName?: string;
7 9
8 10 constructor(message: string, inner?: {}) {
9 super(message);
11 this.message = message;
10 12 this.inner = inner;
11 13 }
12 14 }
@@ -1,450 +1,459
1 1 import {
2 2 PartialServiceMap,
3 3 ActivationType,
4 4 ContainerKeys,
5 5 TypeOfService,
6 6 ILifetime, ServiceContainer
7 7 } from "./interfaces";
8 8
9 import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get, primitive } from "../safe";
9 import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get, primitive, oid, mixin } from "../safe";
10 10 import { AggregateDescriptor } from "./AggregateDescriptor";
11 11 import { ValueDescriptor } from "./ValueDescriptor";
12 12 import { ReferenceDescriptor } from "./ReferenceDescriptor";
13 13 import { TypeServiceDescriptor } from "./TypeServiceDescriptor";
14 14 import { FactoryServiceDescriptor } from "./FactoryServiceDescriptor";
15 15 import { TraceSource } from "../log/TraceSource";
16 16 import { ConfigError } from "./ConfigError";
17 17 import { Cancellation } from "../Cancellation";
18 18 import { makeResolver } from "./ResolverHelper";
19 19 import { ICancellation } from "../interfaces";
20 20 import { isDescriptor } from "./traits";
21 21 import { LazyReferenceDescriptor } from "./LazyReferenceDescriptor";
22 22 import { LifetimeManager } from "./LifetimeManager";
23 import { ServiceDescriptorParams } from "./ServiceDescriptor";
23 24
24 25 export interface RegistrationScope<S extends object> {
25 26
26 27 /** сСрвисы, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Ρ€Π΅Π³ΠΈΡΡ‚Ρ€ΠΈΡ€ΡƒΡŽΡ‚ΡΡ Π² контСкстС Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ ΠΈ Ρ‚Π°ΠΊΠΈΠΌ ΠΎΠ±Ρ€Π°Π·ΠΎΠΌ
27 28 * ΠΌΠΎΠ³ΡƒΡ‚ ΠΏΠ΅Ρ€Π΅ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΡΡ‚ΡŒ Ρ€Π°Π½Π΅Π΅ зарСгистрированныС сСрвисы. Π·Π° это свойство
28 29 * Π½ΡƒΠΆΠ½ΠΎ ΠΏΠ»Π°Ρ‚ΠΈΡ‚ΡŒ, ΠΊΡ€ΠΎΠΌΠ΅ Ρ‚ΠΎΠ³ΠΎ порядок Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ Π±ΡƒΠ΄Π΅Ρ‚ Π²Π»ΠΈΡΡ‚ΡŒ Π½Π° Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚
29 30 * Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Π½ΠΈΡ зависимостСй.
30 31 */
31 32 services?: RegistrationMap<S>;
32 33 }
33 34
34 35 /**
35 36 * Π‘Π°Π·ΠΎΠ²Ρ‹ΠΉ интСрфСйс ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ сСрвисов
36 37 */
37 38 export interface ServiceRegistration<T, S extends object> extends RegistrationScope<S> {
38 39
39 40 activation?: ActivationType;
40 41
41 42 params?: any;
42 43
43 44 /** Π‘ΠΏΠ΅Ρ†ΠΈΠ°Π»ΡŒΠ½Ρ‹ΠΉ ΠΈΠ΄Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ ΠΏΡ€ΠΈ Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ singleton, Ссли
44 45 * Π½Π΅ ΡƒΠΊΠ°Π·Π°Π½ для TypeRegistration вычисляСтся ΠΊΠ°ΠΊ oid($type)
45 46 */
46 47 typeId?: string;
47 48
48 49 inject?: object | object[];
49 50
50 51 cleanup?: ((instance: T) => void) | string;
51 52 }
52 53
53 54 export interface TypeRegistration<C extends new (...args: any[]) => any, S extends object> extends ServiceRegistration<InstanceType<C>, S> {
54 55 $type: string | C;
55 56 params?: Registration<ConstructorParameters<C>, S>;
56 57 }
57 58
58 59 export interface StrictTypeRegistration<C extends new (...args: any[]) => any, S extends object> extends ServiceRegistration<InstanceType<C>, S> {
59 60 $type: C;
60 61 params?: Registration<ConstructorParameters<C>, S>;
61 62 }
62 63
63 64 export interface FactoryRegistration<F extends (...args: any[]) => any, S extends object> extends ServiceRegistration<ReturnType<F>, S> {
64 65 $factory: string | F;
65 66 }
66 67
67 68 export interface ValueRegistration<T> {
68 69 $value: T;
69 70 parse?: boolean;
70 71 }
71 72
72 73 export interface DependencyRegistration<S extends object, K extends ContainerKeys<S> = ContainerKeys<S>> extends RegistrationScope<S> {
73 74 $dependency: K;
74 75 lazy?: boolean;
75 76 optional?: boolean;
76 77 default?: TypeOfService<S, K>;
77 78 }
78 79
79 80 export interface LazyDependencyRegistration<S extends object, K extends ContainerKeys<S> = ContainerKeys<S>> extends DependencyRegistration<S, K> {
80 81 lazy: true;
81 82 }
82 83
83 84 export type Registration<T, S extends object> = T extends primitive ? T :
84 85 (
85 86 T |
86 87 { [k in keyof T]: Registration<T[k], S> } |
87 88 TypeRegistration<new (...args: any[]) => T, S> |
88 89 FactoryRegistration<(...args: any[]) => T, S> |
89 90 ValueRegistration<any> |
90 91 DependencyRegistration<S, keyof S>
91 92 );
92 93
93 94 export type RegistrationMap<S extends object> = {
94 95 [k in keyof S]?: Registration<S[k], S>;
95 96 };
96 97
97 98 const _activationTypes: { [k in ActivationType]: number; } = {
98 99 singleton: 1,
99 100 container: 2,
100 101 hierarchy: 3,
101 102 context: 4,
102 103 call: 5
103 104 };
104 105
105 106 export function isTypeRegistration(x: any): x is TypeRegistration<new () => any, any> {
106 107 return (!isPrimitive(x)) && ("$type" in x);
107 108 }
108 109
109 110 export function isFactoryRegistration(x: any): x is FactoryRegistration<() => any, any> {
110 111 return (!isPrimitive(x)) && ("$factory" in x);
111 112 }
112 113
113 114 export function isValueRegistration(x: any): x is ValueRegistration<any> {
114 115 return (!isPrimitive(x)) && ("$value" in x);
115 116 }
116 117
117 118 export function isDependencyRegistration<S extends object>(x: any): x is DependencyRegistration<S, keyof S> {
118 119 return (!isPrimitive(x)) && ("$dependency" in x);
119 120 }
120 121
121 122 export function isActivationType(x: string): x is ActivationType {
122 123 return typeof x === "string" && x in _activationTypes;
123 124 }
124 125
125 126 const trace = TraceSource.get("@implab/core/di/Configuration");
126 127 async function mapAll(data: any[], map?: (v: any, k: number) => any): Promise<any[]>;
127 128 async function mapAll(data: any, map?: (v: any, k: string) => any): Promise<any>;
128 129 async function mapAll(data: any, map?: (v: any, k: any) => any): Promise<any> {
129 130 if (data instanceof Array) {
130 131 return Promise.all(map ? data.map(map) : data);
131 132 } else {
132 133 const keys = Object.keys(data);
133 134
134 135 const o: any = {};
135 136
136 137 await Promise.all(keys.map(async k => {
137 138 const v = map ? map(data[k], k) : data[k];
138 139 o[k] = isPromise(v) ? await v : v;
139 140 }));
140 141
141 142 return o;
142 143 }
143 144 }
144 145
145 146 export type ModuleResolver = (moduleName: string, ct?: ICancellation) => any;
146 147
147 148 export class Configuration<S extends object> {
148 149
149 150 _hasInnerDescriptors = false;
150 151
151 152 readonly _container: ServiceContainer<S>;
152 153
153 154 _path: Array<string>;
154 155
155 156 _configName: string | undefined;
156 157
157 158 _require: ModuleResolver | undefined;
158 159
159 160 constructor(container: ServiceContainer<S>) {
160 161 argumentNotNull(container, "container");
161 162 this._container = container;
162 163 this._path = [];
163 164 }
164 165
165 166 async loadConfiguration(moduleName: string, contextRequire?: any, ct = Cancellation.none) {
166 167 argumentNotEmptyString(moduleName, "moduleName");
167 168
168 169 trace.log(
169 170 "loadConfiguration moduleName={0}, contextRequire={1}",
170 171 moduleName,
171 172 contextRequire ? typeof (contextRequire) : "<nil>"
172 173 );
173 174
174 175 this._configName = moduleName;
175 176
176 177 const r = await makeResolver(undefined, contextRequire);
177 178
178 179 const config = await r(moduleName, ct);
179 180
180 181 await this._applyConfiguration(
181 182 config,
182 183 await makeResolver(moduleName, contextRequire),
183 184 ct
184 185 );
185 186 }
186 187
187 188 async applyConfiguration(data: RegistrationMap<S>, opts: { contextRequire?: any; baseModule?: string }, ct = Cancellation.none) {
188 189 argumentNotNull(data, "data");
189 190 const _opts = opts || {};
190 191
191 192 await this._applyConfiguration(data, await makeResolver(_opts.baseModule, _opts.contextRequire), ct);
192 193 }
193 194
194 195 async _applyConfiguration(data: RegistrationMap<S>, resolver?: ModuleResolver, ct = Cancellation.none) {
195 196 trace.log("applyConfiguration");
196 197
197 198 this._configName = "$";
198 199
199 200 if (resolver)
200 201 this._require = resolver;
201 202
202 203 let services: PartialServiceMap<S>;
203 204
204 205 try {
205 206 services = await this._visitRegistrations(data, "$");
206 207 } catch (e) {
207 208 throw this._makeError(e);
208 209 }
209 210
210 211 this._container.register(services);
211 212 }
212 213
213 214 _makeError(inner: any) {
214 215 const e = new ConfigError("Failed to load configuration", inner);
215 216 e.configName = this._configName || "<inline>";
216 217 e.path = this._makePath();
217 218 return e;
218 219 }
219 220
220 221 _makePath() {
221 222 return this._path
222 223 .reduce(
223 224 (prev, cur) => typeof cur === "number" ?
224 225 `${prev}[${cur}]` :
225 226 `${prev}.${cur}`
226 227 )
227 228 .toString();
228 229 }
229 230
230 231 async _resolveType(moduleName: string, localName: string) {
231 232 trace.log("resolveType moduleName={0}, localName={1}", moduleName, localName);
232 233 try {
233 234 const m = await this._loadModule(moduleName);
234 235 if (localName) {
235 236 return get(localName, m);
236 237 } else {
237 238 if (m instanceof Function)
238 239 return m;
239 240 if ("default" in m)
240 241 return m.default;
241 242 return m;
242 243 }
243 244 } catch (e) {
244 245 trace.error("Failed to resolve type moduleName={0}, localName={1}", moduleName, localName);
245 246 throw e;
246 247 }
247 248 }
248 249
249 250 _loadModule(moduleName: string) {
250 251 trace.debug("loadModule {0}", moduleName);
251 252 if (!this._require)
252 253 throw new Error("Module loader isn't specified");
253 254
254 255 return this._require(moduleName);
255 256 }
256 257
257 258 async _visitRegistrations(data: RegistrationMap<S>, name: string) {
258 259 this._enter(name);
259 260
260 261 if (data.constructor &&
261 262 data.constructor.prototype !== Object.prototype)
262 263 throw new Error("Configuration must be a simple object");
263 264
264 265 const services = await mapAll(data, async (v, k) => {
265 266 const d = await this._visit(v, k.toString());
266 267 return isDescriptor(d) ? d : new AggregateDescriptor(d);
267 268 }) as PartialServiceMap<S>;
268 269
269 270 this._leave();
270 271
271 272 return services;
272 273 }
273 274
274 275 _enter(name: string) {
275 276 this._path.push(name.toString());
276 277 trace.debug(">{0}", name);
277 278 }
278 279
279 280 _leave() {
280 281 const name = this._path.pop();
281 282 trace.debug("<{0}", name);
282 283 }
283 284
284 285 _visit(data: any, name: string): Promise<any> {
285 286 if (isPrimitive(data))
286 287 return Promise.resolve(new ValueDescriptor(data));
287 288 if (isDescriptor(data))
288 289 return Promise.resolve(data);
289 290
290 291 if (isDependencyRegistration<S>(data)) {
291 292 return this._visitDependencyRegistration(data, name);
292 293 } else if (isValueRegistration(data)) {
293 294 return this._visitValueRegistration(data, name);
294 295 } else if (isTypeRegistration(data)) {
295 296 return this._visitTypeRegistration(data, name);
296 297 } else if (isFactoryRegistration(data)) {
297 298 return this._visitFactoryRegistration(data, name);
298 299 } else if (data instanceof Array) {
299 300 return this._visitArray(data, name);
300 301 }
301 302
302 303 return this._visitObject(data, name);
303 304 }
304 305
305 306 async _visitObject(data: any, name: string) {
306 307 if (data.constructor &&
307 308 data.constructor.prototype !== Object.prototype)
308 309 return new ValueDescriptor(data);
309 310
310 311 this._enter(name);
311 312
312 313 const v = await mapAll(data, delegate(this, "_visit"));
313 314
314 315 // TODO: handle inline descriptors properly
315 316 // const ex = {
316 317 // activate(ctx) {
317 318 // const value = ctx.activate(this.prop, "prop");
318 319 // // some code
319 320 // },
320 321 // // will be turned to ReferenceDescriptor
321 322 // prop: { $dependency: "depName" }
322 323 // };
323 324
324 325 this._leave();
325 326 return v;
326 327 }
327 328
328 329 async _visitArray(data: any[], name: string) {
329 330 if (data.constructor &&
330 331 data.constructor.prototype !== Array.prototype)
331 332 return new ValueDescriptor(data);
332 333
333 334 this._enter(name);
334 335
335 336 const v = await mapAll(data, delegate(this, "_visit"));
336 337 this._leave();
337 338
338 339 return v;
339 340 }
340 341
341 342 _makeServiceParams(data: ServiceRegistration<any, S>) {
342 const opts: any = {
343 };
343 const opts: any = {};
344
344 345 if (data.services)
345 346 opts.services = this._visitRegistrations(data.services, "services");
346 347
347 348 if (data.inject) {
348 349 this._enter("inject");
349 350 opts.inject = mapAll(
350 351 data.inject instanceof Array ?
351 352 data.inject :
352 353 [data.inject],
353 354 delegate(this, "_visitObject")
354 355 );
355 356 this._leave();
356 357 }
357 358
358 359 if ("params" in data)
359 360 opts.params = data.params instanceof Array ?
360 361 this._visitArray(data.params, "params") :
361 362 this._visit(data.params, "params");
362 363
363 364 if (data.activation) {
364 opts.activation = this._getLifetimeManager(data.activation, data.typeId);
365 opts.lifetime = this._getLifetimeManager(data.activation, data.typeId);
365 366 }
366 367
367 368 if (data.cleanup)
368 369 opts.cleanup = data.cleanup;
369 370
370 371 return opts;
371 372 }
372 373
373 374 async _visitValueRegistration<T>(data: ValueRegistration<T>, name: string) {
374 375 this._enter(name);
375 376 const d = data.parse ? new AggregateDescriptor(data.$value) : new ValueDescriptor(data.$value);
376 377 this._leave();
377 378 return d;
378 379 }
379 380
380 381 async _visitDependencyRegistration<K extends keyof S>(data: DependencyRegistration<S, K>, name: string) {
381 382 argumentNotEmptyString(data && data.$dependency, "data.$dependency");
382 383 this._enter(name);
383 384 const options = {
384 385 name: data.$dependency,
385 386 optional: data.optional,
386 387 default: data.default,
387 388 services: data.services && await this._visitRegistrations(data.services, "services")
388 389 };
389 390 const d = data.lazy ? new LazyReferenceDescriptor<S, K>(options) : new ReferenceDescriptor<S, K>(options);
390 391 this._leave();
391 392 return d;
392 393 }
393 394
394 395 async _visitTypeRegistration(data: TypeRegistration<new () => any, S>, name: string) {
395 396 argumentNotNull(data.$type, "data.$type");
396 397 this._enter(name);
397 398
398 const opts = this._makeServiceParams(data);
399 const opts = {} as any;
399 400 if (data.$type instanceof Function) {
400 401 opts.type = data.$type;
401 402 } else {
402 403 const [moduleName, typeName] = data.$type.split(":", 2);
403 404 opts.type = this._resolveType(moduleName, typeName).then(t => {
404 405 if (!(t instanceof Function))
405 406 throw Error("$type (" + data.$type + ") is not a constructable");
406 407 return t;
407 408 });
408 409 }
409 410
411 if (!data.typeId && data.activation === "singleton")
412 data.typeId = oid(opts.type);
413
414 mixin(opts, this._makeServiceParams(data));
415
410 416 const d = new TypeServiceDescriptor<S, any, any[]>(
411 417 await mapAll(opts)
412 418 );
413 419
414 420 this._leave();
415 421
416 422 return d;
417 423 }
418 424
419 425 async _visitFactoryRegistration(data: FactoryRegistration<() => any, S>, name: string) {
420 426 argumentOfType(data.$factory, Function, "data.$factory");
421 427 this._enter(name);
422 428
429 if (!data.typeId && data.activation === "singleton")
430 data.typeId = oid(data.$factory);
431
423 432 const opts = this._makeServiceParams(data);
424 433 opts.factory = data.$factory;
425 434
426 435 const d = new FactoryServiceDescriptor<S, any, any[]>(
427 436 await mapAll(opts)
428 437 );
429 438
430 439 this._leave();
431 440 return d;
432 441 }
433 442
434 443 _getLifetimeManager(activation: ActivationType, typeId: string | undefined): ILifetime {
435 444 switch (activation) {
436 445 case "container":
437 446 return LifetimeManager.containerLifetime(this._container);
438 447 case "hierarchy":
439 448 return LifetimeManager.hierarchyLifetime();
440 449 case "context":
441 450 return LifetimeManager.contextLifetime();
442 451 case "singleton":
443 452 if (typeId === undefined)
444 453 throw Error("The singleton activation requires a typeId");
445 454 return LifetimeManager.singletonLifetime(typeId);
446 455 default:
447 456 return LifetimeManager.empty();
448 457 }
449 458 }
450 459 }
@@ -1,518 +1,518
1 1 import { ICancellable, Constructor, IDestroyable, ICancellation } from "./interfaces";
2 2
3 3 let _nextOid = 0;
4 4 const _oid = typeof Symbol === "function" ?
5 5 Symbol("__implab__oid__") :
6 6 "__implab__oid__";
7 7
8 8 export function oid(instance: null | undefined): undefined;
9 9 export function oid(instance: NonNullable<any>): string;
10 10 export function oid(instance: any): string | undefined {
11 11 if (isNull(instance))
12 12 return undefined;
13 13
14 14 if (_oid in instance)
15 15 return instance[_oid];
16 16 else
17 17 return (instance[_oid] = "oid_" + (++_nextOid));
18 18 }
19 19
20 20 const cancellationNone: ICancellation = {
21 21 isSupported(): boolean {
22 22 return false;
23 23 },
24 24
25 25 throwIfRequested(): void {
26 26 },
27 27
28 28 isRequested(): boolean {
29 29 return false;
30 30 },
31 31
32 32 register(_cb: (e: any) => void): IDestroyable {
33 33 return destroyed;
34 34 }
35 35 };
36 36
37 37 export function keys<T>(arg: T): (Extract<keyof T, string>)[] {
38 38 return isObject(arg) && arg ? Object.keys(arg) as (Extract<keyof T, string>)[] : [];
39 39 }
40 40
41 41 export function isKeyof<T>(k: string, target: T): k is Extract<keyof T, string> {
42 42 return target && typeof target === "object" && k in target;
43 43 }
44 44
45 45 export function argumentNotNull(arg: any, name: string) {
46 46 if (arg === null || arg === undefined)
47 47 throw new Error("The argument " + name + " can't be null or undefined");
48 48 }
49 49
50 50 export function argumentNotEmptyString(arg: any, name: string) {
51 51 if (typeof (arg) !== "string" || !arg.length)
52 52 throw new Error("The argument '" + name + "' must be a not empty string");
53 53 }
54 54
55 55 export function argumentNotEmptyArray(arg: any, name: string) {
56 56 if (!(arg instanceof Array) || !arg.length)
57 57 throw new Error("The argument '" + name + "' must be a not empty array");
58 58 }
59 59
60 60 export function argumentOfType(arg: any, type: Constructor<{}>, name: string) {
61 61 if (!(arg instanceof type))
62 62 throw new Error("The argument '" + name + "' type doesn't match");
63 63 }
64 64
65 65 export function isObject(val: any): val is object {
66 66 return typeof val === "object";
67 67 }
68 68
69 69 export function isNull(val: any): val is null | undefined {
70 70 return (val === null || val === undefined);
71 71 }
72 72
73 73 export type primitive = symbol | string | number | boolean | undefined | null;
74 74
75 75 export function isPrimitive(val: any): val is primitive {
76 76 return (val === null || val === undefined || typeof (val) === "string" ||
77 77 typeof (val) === "number" || typeof (val) === "boolean");
78 78 }
79 79
80 80 export function isInteger(val: any): val is number {
81 81 return parseInt(val, 10) === val;
82 82 }
83 83
84 84 export function isNumber(val: any): val is number {
85 85 return parseFloat(val) === val;
86 86 }
87 87
88 88 export function isString(val: any): val is string {
89 89 return typeof (val) === "string" || val instanceof String;
90 90 }
91 91
92 92 export function isPromise<T = any>(val: any): val is PromiseLike<T> {
93 93 return val && typeof val.then === "function";
94 94 }
95 95
96 96 export function isCancellable(val: any): val is ICancellable {
97 97 return val && typeof val.cancel === "function";
98 98 }
99 99
100 100 export function isNullOrEmptyString(val: any): val is ("" | null | undefined) {
101 101 return (val === null || val === undefined ||
102 102 ((typeof (val) === "string" || val instanceof String) && val.length === 0));
103 103 }
104 104
105 105 export function isNotEmptyArray<T = any>(arg: any): arg is T[] {
106 106 return (arg instanceof Array && arg.length > 0);
107 107 }
108 108
109 109 function _isStrictMode(this: any) {
110 110 return !this;
111 111 }
112 112
113 113 function _getNonStrictGlobal(this: any) {
114 114 return this;
115 115 }
116 116
117 117 export function getGlobal() {
118 118 // in es3 we can't use indirect call to eval, since it will
119 119 // be executed in the current call context.
120 120 if (!_isStrictMode()) {
121 121 return _getNonStrictGlobal();
122 122 } else {
123 123 // tslint:disable-next-line:no-eval
124 124 return eval.call(null, "this");
125 125 }
126 126 }
127 127
128 128 export function get(member: string, context?: object) {
129 129 argumentNotEmptyString(member, "member");
130 130 let that = context || getGlobal();
131 131 const parts = member.split(".");
132 132 for (const m of parts) {
133 133 if (!m)
134 134 continue;
135 135 if (isNull(that = that[m]))
136 136 break;
137 137 }
138 138 return that;
139 139 }
140 140
141 141 /**
142 142 * ВыполняСт ΠΌΠ΅Ρ‚ΠΎΠ΄ для ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ элСмСнта массива, останавливаСтся, ΠΊΠΎΠ³Π΄Π°
143 143 * Π»ΠΈΠ±ΠΎ достигнут ΠΊΠΎΠ½Π΅Ρ† массива, Π»ΠΈΠ±ΠΎ функция <c>cb</c> Π²Π΅Ρ€Π½ΡƒΠ»Π°
144 144 * Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅.
145 145 *
146 146 * @param {Array | Object} obj массив элСмСнтов для просмотра
147 147 * @param {Function} cb функция, вызываСмая для ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ элСмСнта
148 148 * @param {Object} thisArg Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅, ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ΅ Π±ΡƒΠ΄Π΅Ρ‚ ΠΏΠ΅Ρ€Π΅Π΄Π°Π½ΠΎ Π² качСствС
149 149 * <c>this</c> Π² <c>cb</c>.
150 150 * @returns {void}
151 151 */
152 export function each<T>(obj: T, cb: <X extends keyof T>(v: NonNullable<T[X]>, k: X) => void): void;
152 export function each<T>(obj: T, cb: <X extends Extract<keyof T, string>>(v: NonNullable<T[X]>, k: X) => void): void;
153 153 export function each<T>(array: T[], cb: (v: T, i: number) => void): void;
154 154 export function each(obj: any, cb: any, thisArg?: any): any;
155 155 export function each(obj: any, cb: any, thisArg?: any) {
156 156 argumentNotNull(cb, "cb");
157 157 if (obj instanceof Array) {
158 158 let v: any;
159 159 for (let i = 0; i < obj.length; i++) {
160 160 v = obj[i];
161 161 if (v !== undefined)
162 162 cb.call(thisArg, v, i);
163 163 }
164 164 } else {
165 165 Object.keys(obj).forEach(k => obj[k] !== undefined && cb.call(thisArg, obj[k], k));
166 166 }
167 167 }
168 168
169 169 /** Copies property values from a source object to the destination and returns
170 170 * the destination object.
171 171 *
172 172 * @param dest The destination object into which properties from the source
173 173 * object will be copied.
174 174 * @param source The source of values which will be copied to the destination
175 175 * object.
176 176 * @param template An optional parameter specifies which properties should be
177 177 * copied from the source and how to map them to the destination. If the
178 178 * template is an array it contains the list of property names to copy from the
179 179 * source to the destination. In case of object the templates contains the map
180 180 * where keys are property names in the source and the values are property
181 181 * names in the destination object. If the template isn't specified then the
182 182 * own properties of the source are entirely copied to the destination.
183 183 *
184 184 */
185 185 export function mixin<T extends object, S extends object>(dest: T, source: S, template?: (keyof S)[]): T & S;
186 186 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;
187 187 export function mixin<T extends object, S extends object>(dest: T, source: S, template?: any): any {
188 188 argumentNotNull(dest, "dest");
189 189 const _res: any = dest as any;
190 190
191 191 if (isPrimitive(source))
192 192 return _res;
193 193
194 194 if (template instanceof Array) {
195 195 template.forEach(p => {
196 196 if (isKeyof(p, source))
197 197 _res[p] = source[p];
198 198 });
199 199 } else if (template) {
200 200 keys(source).forEach(p => {
201 201 if (isKeyof(p, template))
202 202 _res[template[p]] = source[p];
203 203 });
204 204 } else {
205 205 keys(source).forEach(p => _res[p] = source[p]);
206 206 }
207 207
208 208 return _res;
209 209 }
210 210
211 211 /** Wraps the specified function to emulate an asynchronous execution.
212 212 * @param{Object} thisArg [Optional] Object which will be passed as 'this' to the function.
213 213 * @param{Function|String} fn [Required] Function wich will be wrapped.
214 214 */
215 215 export function async<T, F extends (...args: any[]) => T | PromiseLike<T>>(
216 216 fn: F,
217 217 thisArg?: ThisParameterType<F>
218 218 ): (...args: Parameters<F>) => PromiseLike<T>;
219 219 export function async<T, M extends string, O extends { [m in M]?: (...args: any[]) => T | PromiseLike<T> }>(
220 220 fn: M,
221 221 thisArg: O
222 222 ): (...args: Parameters<NonNullable<O[M]>>) => PromiseLike<T>;
223 223 export function async(_fn: any, thisArg: any): (...args: any[]) => PromiseLike<any> {
224 224 let fn = _fn;
225 225
226 226 if (arguments.length === 2 && !(fn instanceof Function))
227 227 fn = thisArg[fn];
228 228
229 229 if (fn == null)
230 230 throw new Error("The function must be specified");
231 231
232 232 function wrapresult(x: any, e?: any): PromiseLike<any> {
233 233 if (e) {
234 234 return {
235 235 then(cb, eb) {
236 236 try {
237 237 return eb ? wrapresult(eb(e)) : this;
238 238 } catch (e2) {
239 239 return wrapresult(null, e2);
240 240 }
241 241 }
242 242 };
243 243 } else {
244 244 if (x && x.then)
245 245 return x;
246 246 return {
247 247 then(cb) {
248 248 try {
249 249 return cb ? wrapresult(cb(x)) : this;
250 250 } catch (e2) {
251 251 return wrapresult(e2);
252 252 }
253 253 }
254 254 };
255 255 }
256 256 }
257 257
258 258 return (...args) => {
259 259 try {
260 260 return wrapresult(fn.apply(thisArg, args));
261 261 } catch (e) {
262 262 return wrapresult(null, e);
263 263 }
264 264 };
265 265 }
266 266
267 267 export function delegate<T extends object, F extends (this: T, ...args: any[]) => any>(
268 268 target: T,
269 269 method: F
270 270 ): OmitThisParameter<F>;
271 271 export function delegate<M extends string, T extends { [m in M]?: (...args: any[]) => any; }>(
272 272 target: T,
273 273 method: M
274 274 ): OmitThisParameter<T[M]>;
275 275 export function delegate(target: any, _method: any): (...args: any[]) => any {
276 276 let method: any;
277 277 if (!(_method instanceof Function)) {
278 278 argumentNotNull(target, "target");
279 279 method = target[_method];
280 280 if (!(method instanceof Function))
281 281 throw new Error("'method' argument must be a Function or a method name");
282 282 } else {
283 283 method = _method;
284 284 }
285 285
286 286 return (...args) => {
287 287 return method.apply(target, args);
288 288 };
289 289 }
290 290
291 291 export function delay(timeMs: number, ct = cancellationNone) {
292 292 ct.throwIfRequested();
293 293 return new Promise((resolve, reject) => {
294 294 const h = ct.register(e => {
295 295 clearTimeout(id);
296 296 reject(e);
297 297 // we don't nedd to unregister h, since ct is already disposed
298 298 });
299 299 const id = setTimeout(() => {
300 300 h.destroy();
301 301 resolve();
302 302 }, timeMs);
303 303
304 304 });
305 305 }
306 306
307 307 /** Returns resolved promise, awaiting this method will cause the asynchronous
308 308 * completion of the rest of the code.
309 309 */
310 310 export function fork() {
311 311 return Promise.resolve();
312 312 }
313 313
314 314 /** Always throws Error, can be used as a stub for the methods which should be
315 315 * assigned later and are required to be not null.
316 316 */
317 317 export function notImplemented(): never {
318 318 throw new Error("Not implemeted");
319 319 }
320 320 /**
321 321 * Iterates over the specified array of items and calls the callback `cb`, if
322 322 * the result of the callback is a promise the next item from the array will be
323 323 * proceeded after the promise is resolved.
324 324 *
325 325 */
326 326 export function pmap<T, T2>(
327 327 items: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
328 328 cb: (item: T, i: number) => T2 | PromiseLike<T2>
329 329 ): T2[] | PromiseLike<T2[]> {
330 330 argumentNotNull(cb, "cb");
331 331
332 332 if (isPromise(items)) {
333 333 return items.then(data => pmap(data, cb));
334 334 } else {
335 335
336 336 if (isNull(items) || !items.length)
337 337 return [];
338 338
339 339 let i = 0;
340 340 const result = new Array<T2>();
341 341
342 342 const next = (): any => {
343 343 while (i < items.length) {
344 344 const r = cb(items[i], i);
345 345 const ri = i;
346 346 i++;
347 347 if (isPromise(r)) {
348 348 return r.then(x => {
349 349 result[ri] = x;
350 350 return next();
351 351 });
352 352 } else {
353 353 result[ri] = r;
354 354 }
355 355 }
356 356 return result;
357 357 };
358 358
359 359 return next();
360 360 }
361 361 }
362 362
363 363 export function pfor<T>(
364 364 items: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
365 365 cb: (item: T, i: number) => any
366 366 ): void | PromiseLike<void> {
367 367 argumentNotNull(cb, "cb");
368 368
369 369 if (isPromise(items)) {
370 370 return items.then(data => pfor(data, cb));
371 371 } else {
372 372 if (isNull(items) || !items.length)
373 373 return;
374 374
375 375 let i = 0;
376 376
377 377 const next = (): any => {
378 378 while (i < items.length) {
379 379 const r = cb(items[i], i);
380 380 i++;
381 381 if (isPromise(r))
382 382 return r.then(next);
383 383 }
384 384 };
385 385
386 386 return next();
387 387 }
388 388 }
389 389
390 390 export function first<T>(sequence: ArrayLike<T>): T;
391 391 export function first<T>(sequence: PromiseLike<ArrayLike<T>>): PromiseLike<T>;
392 392 export function first<T>(
393 393 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
394 394 cb?: (x: T) => void,
395 395 err?: (x: Error) => void
396 396 ): void;
397 397 /**
398 398 * Π’Ρ‹Π±ΠΈΡ€Π°Π΅Ρ‚ ΠΏΠ΅Ρ€Π²Ρ‹ΠΉ элСмСнт ΠΈΠ· ΠΏΠΎΡΠ»Π΅Π΄ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ, ΠΈΠ»ΠΈ обСщания, Ссли Π²
399 399 * качСствС ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Π° ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ ΠΎΠ±Π΅Ρ‰Π°Π½ΠΈΠ΅, ΠΎΠ½ΠΎ Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π²Π΅Ρ€Π½ΡƒΡ‚ΡŒ массив.
400 400 *
401 401 * @param {Function} cb ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Π°, Π΅ΠΌΡƒ Π±ΡƒΠ΄Π΅Ρ‚ ΠΏΠ΅Ρ€Π΅Π΄Π°Π½ ΠΏΠ΅Ρ€Π²Ρ‹ΠΉ
402 402 * элСмСнт ΠΏΠΎΡΠ»Π΅Π΄ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ Π² случаС успСха
403 403 * @param {Function} err ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ ΠΈΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ, Ссли массив пустой, Π»ΠΈΠ±ΠΎ
404 404 * нС массив
405 405 *
406 406 * @remarks Если Π½Π΅ ΡƒΠΊΠ°Π·Π°Π½Ρ‹ Π½ΠΈ cb Π½ΠΈ err, Ρ‚ΠΎΠ³Π΄Π° функция Π²Π΅Ρ€Π½Π΅Ρ‚ Π»ΠΈΠ±ΠΎ
407 407 * ΠΎΠ±Π΅Ρ‰Π°Π½ΠΈΠ΅, Π»ΠΈΠ±ΠΎ ΠΏΠ΅Ρ€Π²Ρ‹ΠΉ элСмСнт.
408 408 * @async
409 409 */
410 410 export function first<T>(
411 411 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
412 412 cb?: (x: T) => void,
413 413 err?: (x: Error) => void
414 414 ) {
415 415 if (isPromise(sequence)) {
416 416 return sequence.then(res => first(res, cb as any /* force to pass undefined cb */, err));
417 417 } else if (sequence && "length" in sequence) {
418 418 if (sequence.length === 0) {
419 419 if (err)
420 420 return err(new Error("The sequence is empty"));
421 421 else
422 422 throw new Error("The sequence is empty");
423 423 } else if (cb) {
424 424 return cb(sequence[0]);
425 425 } else {
426 426 return sequence[0];
427 427 }
428 428 } else {
429 429 if (err)
430 430 return err(new Error("The sequence is required"));
431 431 else
432 432 throw new Error("The sequence is required");
433 433 }
434 434 }
435 435
436 436 export function firstWhere<T>(
437 437 sequence: ArrayLike<T>,
438 438 predicate: (x: T) => boolean
439 439 ): T;
440 440 export function firstWhere<T>(
441 441 sequence: PromiseLike<ArrayLike<T>>,
442 442 predicate: (x: T) => boolean
443 443 ): PromiseLike<T>;
444 444 export function firstWhere<T>(
445 445 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
446 446 predicate: (x: T) => boolean,
447 447 cb: (x: T) => void,
448 448 err?: (x: Error) => void
449 449 ): void;
450 450
451 451 export function firstWhere<T>(
452 452 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
453 453 predicate?: (x: T) => boolean,
454 454 cb?: (x: T) => any,
455 455 err?: (x: Error) => any
456 456 ) {
457 457 if (isPromise(sequence)) {
458 458 return sequence.then(res => firstWhere(
459 459 res,
460 460 predicate as any /* force to pass undefined predicate */,
461 461 cb as any /* force to pass undefined cb */,
462 462 err)
463 463 );
464 464 } else if (sequence && "length" in sequence) {
465 465 if (sequence.length === 0) {
466 466 if (err)
467 467 err(new Error("The sequence is empty"));
468 468 else
469 469 throw new Error("The sequence is empty");
470 470 } else {
471 471 if (!predicate) {
472 472 return cb ? cb(sequence[0]) && void (0) : sequence[0];
473 473 } else {
474 474 for (let i = 0; i < sequence.length; i++) {
475 475 const v = sequence[i];
476 476 if (predicate(v))
477 477 return cb ? cb(v) : v;
478 478 }
479 479 if (err)
480 480 err(new Error("The sequence doesn't contain matching items"));
481 481 else
482 482 throw new Error("The sequence doesn't contain matching items");
483 483 }
484 484 }
485 485 } else {
486 486 if (err)
487 487 err(new Error("The sequence is required"));
488 488 else
489 489 throw new Error("The sequence is required");
490 490 }
491 491 }
492 492
493 493 export function isDestroyable(d: any): d is IDestroyable {
494 494 if (d && "destroy" in d && typeof (destroy) === "function")
495 495 return true;
496 496 return false;
497 497 }
498 498
499 499 export function destroy(d: any) {
500 500 if (d && "destroy" in d)
501 501 d.destroy();
502 502 }
503 503
504 504 /**
505 505 * Used to mark that the async operation isn't awaited intentionally.
506 506 * @param p The promise which represents the async operation.
507 507 */
508 508 export function nowait(p: Promise<any>) {
509 509 }
510 510
511 511 /** represents already destroyed object.
512 512 */
513 513 export const destroyed = {
514 514 /** Calling to this method doesn't affect anything, noop.
515 515 */
516 516 destroy() {
517 517 }
518 518 };
General Comments 0
You need to be logged in to leave comments. Login now