##// END OF EJS Templates
Merge with ioc ts support
cin -
r138:a2fb9af6341c merge v1.4.0-rc1 default
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 }
@@ -0,0 +1,83
1 import { argumentNotEmptyString, each } from "../safe";
2 import { ActivationContext } from "./ActivationContext";
3 import { Descriptor, PartialServiceMap, TypeOfService, ContainerKeys } from "./interfaces";
4 import { ActivationError } from "./ActivationError";
5
6 export interface ReferenceDescriptorParams<S extends object, K extends ContainerKeys<S>> {
7 name: K;
8 optional?: boolean;
9 default?: TypeOfService<S, K>;
10 services?: PartialServiceMap<S>;
11 }
12
13 export class LazyReferenceDescriptor<S extends object = any, K extends ContainerKeys<S> = ContainerKeys<S>>
14 implements Descriptor<S, ((args?: PartialServiceMap<S>) => TypeOfService<S, K>)> {
15
16 _name: K;
17
18 _optional = false;
19
20 _default: TypeOfService<S, K> | undefined;
21
22 _services: PartialServiceMap<S>;
23
24 constructor(opts: ReferenceDescriptorParams<S, K>) {
25 argumentNotEmptyString(opts && opts.name, "opts.name");
26 this._name = opts.name;
27 this._optional = !!opts.optional;
28 this._default = opts.default;
29
30 this._services = (opts.services || {}) as PartialServiceMap<S>;
31 }
32
33 activate(context: ActivationContext<S>) {
34 // добавляем сервисы
35 if (this._services) {
36 each(this._services, (v, k) => context.register(k, v));
37 }
38
39 const saved = context.clone();
40
41 return (cfg?: PartialServiceMap<S>): any => {
42 // защищаем контекст на случай исключения в процессе
43 // активации
44 const ct = cfg ? saved.clone() : saved;
45 try {
46 if (cfg) {
47 each(cfg, (v, k) => ct.register(k, v));
48 }
49
50 return this._optional ? ct.resolve(this._name, this._default) : ct
51 .resolve(this._name);
52 } catch (error) {
53 throw new ActivationError(this._name.toString(), ct.getStack(), error);
54 }
55 };
56 }
57
58 toString() {
59 const opts = [];
60 if (this._optional)
61 opts.push("optional");
62
63 opts.push("lazy");
64
65 const parts = [
66 "@ref "
67 ];
68 if (opts.length) {
69 parts.push("{");
70 parts.push(opts.join());
71 parts.push("} ");
72 }
73
74 parts.push(this._name.toString());
75
76 if (this._default !== undefined && this._default !== null) {
77 parts.push(" = ");
78 parts.push(String(this._default));
79 }
80
81 return parts.join("");
82 }
83 }
@@ -0,0 +1,198
1 import { IDestroyable, MapOf } from "../interfaces";
2 import { argumentNotNull, isDestroyable, primitive, isNull, argumentNotEmptyString } from "../safe";
3 import { ILifetime } from "./interfaces";
4 import { ActivationContext } from "./ActivationContext";
5 import { Container } from "./Container";
6
7 function safeCall(item: () => void) {
8 try {
9 item();
10 } catch {
11 // silence!
12 }
13 }
14
15 const emptyLifetime: ILifetime = Object.freeze({
16 has() {
17 return false;
18 },
19
20 initialize() {
21
22 },
23
24 get() {
25 throw new Error("The specified item isn't registered with this lifetime manager");
26 },
27
28 store() {
29 // does nothing
30 }
31
32 });
33
34 const unknownLifetime: ILifetime = Object.freeze({
35 has() {
36 return false;
37 },
38 initialize() {
39 throw new Error("Can't call initialize on the unknown lifetime object");
40 },
41 get() {
42 throw new Error("The lifetime object isn't initialized");
43 },
44 store() {
45 throw new Error("Can't store a value in the unknown lifetime object");
46 }
47 });
48
49 let nextId = 0;
50
51 const singletons: { [k in keyof any]: any; } = {};
52
53 export class LifetimeManager implements IDestroyable {
54 private _cleanup: (() => void)[] = [];
55 private _cache: MapOf<any> = {};
56 private _destroyed = false;
57
58 private _pending: MapOf<boolean> = {};
59
60 create(): ILifetime {
61 const self = this;
62 const id = ++nextId;
63 return {
64 has() {
65 return (id in self._cache);
66 },
67
68 get() {
69 const t = self._cache[id];
70 if (t === undefined)
71 throw new Error(`The item with with the key ${id} isn't found`);
72 return t;
73 },
74
75 initialize() {
76 if (self._pending[id])
77 throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`);
78 self._pending[id] = true;
79 },
80
81 store(item: any, cleanup?: (item: any) => void) {
82 argumentNotNull(id, "id");
83 argumentNotNull(item, "item");
84
85 if (this.has())
86 throw new Error(`The item with with the key ${id} already registered with this lifetime manager`);
87 delete self._pending[id];
88
89 self._cache[id] = item;
90
91 if (self._destroyed)
92 throw new Error("Lifetime manager is destroyed");
93 if (cleanup) {
94 self._cleanup.push(() => cleanup(item));
95 } else if (isDestroyable(item)) {
96 self._cleanup.push(() => item.destroy());
97 }
98 }
99 };
100 }
101
102 destroy() {
103 if (!this._destroyed) {
104 this._destroyed = true;
105 this._cleanup.forEach(safeCall);
106 this._cleanup.length = 0;
107 }
108 }
109
110 static empty(): ILifetime {
111 return emptyLifetime;
112 }
113
114 static hierarchyLifetime(): ILifetime {
115 let _lifetime = unknownLifetime;
116 return {
117 initialize(context: ActivationContext<any>) {
118 if (_lifetime !== unknownLifetime)
119 throw new Error("Cyclic reference activation detected");
120
121 _lifetime = context.getContainer().getLifetimeManager().create();
122 },
123 get() {
124 return _lifetime.get();
125 },
126 has() {
127 return _lifetime.has();
128 },
129 store(item: any, cleanup?: (item: any) => void) {
130 return _lifetime.store(item, cleanup);
131 }
132 };
133 }
134
135 static contextLifetime(): ILifetime {
136 let _lifetime = unknownLifetime;
137 return {
138 initialize(context: ActivationContext<any>) {
139 if (_lifetime !== unknownLifetime)
140 throw new Error("Cyclic reference detected");
141 _lifetime = context.createLifetime();
142 },
143 get() {
144 return _lifetime.get();
145 },
146 has() {
147 return _lifetime.has();
148 },
149 store(item: any) {
150 _lifetime.store(item);
151 }
152 };
153 }
154
155 static singletonLifetime(typeId: string): ILifetime {
156 argumentNotEmptyString(typeId, "typeId");
157 let pending = false;
158 return {
159 has() {
160 return typeId in singletons;
161 },
162 get() {
163 if (!this.has())
164 throw new Error(`The instance ${typeId} doesn't exists`);
165 return singletons[typeId];
166 },
167 initialize() {
168 if (pending)
169 throw new Error("Cyclic reference detected");
170 pending = true;
171 },
172 store(item: any) {
173 singletons[typeId] = item;
174 pending = false;
175 }
176 };
177 }
178
179 static containerLifetime(container: Container<any>) {
180 let _lifetime = unknownLifetime;
181 return {
182 initialize(context: ActivationContext<any>) {
183 if (_lifetime !== unknownLifetime)
184 throw new Error("Cyclic reference detected");
185 _lifetime = container.getLifetimeManager().create();
186 },
187 get() {
188 return _lifetime.get();
189 },
190 has() {
191 return _lifetime.has();
192 },
193 store(item: any) {
194 _lifetime.store(item);
195 }
196 };
197 }
198 }
@@ -0,0 +1,147
1 import { Resolver, RegistrationBuilder } from "./interfaces";
2 import { Container } from "../Container";
3 import { Descriptor, ILifetime, ActivationType, PartialServiceMap } from "../interfaces";
4 import { DescriptorImpl } from "./DescriptorImpl";
5 import { LifetimeManager } from "../LifetimeManager";
6 import { isString, each, isPrimitive, isPromise, oid } from "../../safe";
7
8 export class DescriptorBuilder<S extends object, T> {
9 private readonly _container: Container<S>;
10 private readonly _cb: (d: Descriptor<S, T>) => void;
11
12 private readonly _eb: (err: any) => void;
13
14 private _lifetime = LifetimeManager.empty();
15
16 private _overrides?: PartialServiceMap<S>;
17
18 private _cleanup?: (item: T) => void;
19
20 private _factory?: (resolve: Resolver<S>) => T;
21
22 private _pending = 1;
23
24 private _failed = false;
25
26 constructor(container: Container<S>, cb: (d: Descriptor<S, T>) => void, eb: (err: any) => void) {
27 this._container = container;
28 this._cb = cb;
29 this._eb = eb;
30 }
31
32 build<T2>(): DescriptorBuilder<S, T2> {
33 this._defer();
34 return new DescriptorBuilder<S, T2>(this._container, () => this._complete(), err => this._fail(err));
35 }
36
37 override<K extends keyof S>(name: K, builder: RegistrationBuilder<S, S[K]>): this;
38 override<K extends keyof S>(services: { [name in K]: RegistrationBuilder<S, S[K]> }): this;
39 override<K extends keyof S>(nameOrServices: K | { [name in K]: RegistrationBuilder<S, S[K]> }, builder?: RegistrationBuilder<S, S[K]>): this {
40 const overrides: PartialServiceMap<S> = this._overrides ?
41 this._overrides :
42 (this._overrides = {});
43
44 const guard = (v: void | Promise<void>) => {
45 if (isPromise(v))
46 v.catch(err => this._fail(err));
47 };
48
49 if (isPrimitive(nameOrServices)) {
50 if (builder) {
51 this._defer();
52 const d = new DescriptorBuilder<S, S[K]>(
53 this._container,
54 result => {
55 overrides[nameOrServices] = result;
56 this._complete();
57 },
58 err => this._fail(err)
59 );
60
61 try {
62 guard(builder(d));
63 } catch (err) {
64 this._fail(err);
65 }
66 }
67 } else {
68 each(nameOrServices, (v, k) => this.override(k, v));
69 }
70 return this;
71 }
72
73 lifetime(lifetime: "singleton", typeId: string): this;
74 lifetime(lifetime: ILifetime | Exclude<ActivationType, "singleton">): this;
75 lifetime(lifetime: ILifetime | ActivationType, typeId?: string): this {
76 if (isString(lifetime)) {
77 this._lifetime = this._resolveLifetime(lifetime, typeId);
78 } else {
79 this._lifetime = lifetime;
80 }
81 return this;
82 }
83
84 cleanup(cb: (item: T) => void): this {
85 this._cleanup = cb;
86 return this;
87 }
88
89 factory(f: (resolve: Resolver<S>) => T): void {
90 this._factory = f;
91 this._complete();
92 }
93
94 value(v: T): void {
95 this._cb({
96 activate() {
97 return v;
98 }
99 });
100 }
101
102 _resolveLifetime(activation: ActivationType, typeId?: string | object) {
103 switch (activation) {
104 case "container":
105 return LifetimeManager.containerLifetime(this._container);
106 case "hierarchy":
107 return LifetimeManager.hierarchyLifetime();
108 case "context":
109 return LifetimeManager.contextLifetime();
110 case "singleton":
111 if (!typeId)
112 throw Error("The singleton activation requires a typeId");
113
114 const _oid = isString(typeId) ? typeId : oid(typeId);
115
116 return LifetimeManager.singletonLifetime(_oid);
117 default:
118 return LifetimeManager.empty();
119 }
120 }
121
122 _defer() {
123 this._pending++;
124 }
125
126 _complete() {
127 if (--this._pending === 0) {
128 if (!this._factory)
129 throw new Error("The factory must be specified");
130
131 this._cb(new DescriptorImpl<S, T>({
132 lifetime: this._lifetime,
133 factory: this._factory,
134 overrides: this._overrides,
135 cleanup: this._cleanup
136 }));
137 }
138 }
139
140 _fail(err: any) {
141 if (!this._failed) {
142 this._failed = true;
143 this._eb.call(undefined, err);
144 }
145 }
146
147 }
@@ -0,0 +1,63
1 import { Descriptor, PartialServiceMap, ILifetime, ContainerKeys } from "../interfaces";
2 import { ActivationContext } from "../ActivationContext";
3 import { each } from "../../safe";
4 import { DependencyOptions, LazyDependencyOptions, Resolver } from "./interfaces";
5
6 export interface DescriptorImplArgs<S extends object, T> {
7 lifetime: ILifetime;
8
9 factory: (resolve: Resolver<S>) => T;
10
11 cleanup?: (item: T) => void;
12
13 overrides?: PartialServiceMap<S>;
14 }
15
16 export class DescriptorImpl<S extends object, T> implements Descriptor<S, T> {
17
18 private readonly _overrides?: PartialServiceMap<S>;
19
20 private readonly _lifetime: ILifetime;
21
22 private readonly _factory: (resolve: Resolver<S>) => T;
23
24 private readonly _cleanup?: (item: T) => void;
25
26 constructor(args: DescriptorImplArgs<S, T>) {
27 this._lifetime = args.lifetime;
28 this._factory = args.factory;
29 if (args.cleanup)
30 this._cleanup = args.cleanup;
31 if (args.overrides)
32 this._overrides = args.overrides;
33 }
34
35 activate(context: ActivationContext<S>): T {
36
37 if (this._lifetime.has())
38 return this._lifetime.get();
39
40 this._lifetime.initialize(context);
41
42 if (this._overrides)
43 each(this._overrides, (v, k) => context.register(k, v));
44
45 const resolve = (name: ContainerKeys<S>, opts?: DependencyOptions | LazyDependencyOptions) => {
46 if (opts && "lazy" in opts && opts.lazy) {
47 const c2 = context.clone();
48 return () => {
49 return opts.optional ? c2.resolve(name, opts.default) : c2.resolve(name);
50 };
51 } else {
52 return opts && opts.optional ? context.resolve(name, opts.default) : context.resolve(name);
53 }
54 };
55
56 const instance = this._factory.call(undefined, resolve);
57
58 this._lifetime.store(instance, this._cleanup);
59
60 return instance;
61 }
62
63 }
@@ -0,0 +1,60
1 import { Container } from "../Container";
2 import { argumentNotNull, each, isPrimitive, isPromise } from "../../safe";
3 import { DescriptorBuilder } from "./DescriptorBuilder";
4 import { RegistrationBuilder, FluentRegistrations } from "./interfaces";
5 import { Cancellation } from "../../Cancellation";
6
7 export class FluentConfiguration<S extends object, Y extends keyof S = keyof S> {
8
9 _builders: { [k in keyof S]?: RegistrationBuilder<S, S[k]> } = {};
10
11 register<K extends Y>(name: K, builder: RegistrationBuilder<S, S[K]>): FluentConfiguration<S, Exclude<Y, K>>;
12 register<K extends Y>(config: FluentRegistrations<K, S>): FluentConfiguration<S, Exclude<Y, K>>;
13 register<K extends Y>(nameOrConfig: K | FluentRegistrations<K, S>, builder?: RegistrationBuilder<S, S[K]>): FluentConfiguration<S, Exclude<Y, K>> {
14 if (isPrimitive(nameOrConfig)) {
15 argumentNotNull(builder, "builder");
16 this._builders[nameOrConfig] = builder;
17 } else {
18 each(nameOrConfig, (v, k) => this.register(k, v));
19 }
20
21 return this;
22 }
23
24 apply<SC extends object>(target: Container<SC>, ct = Cancellation.none) {
25
26 let pending = 1;
27
28 const _t2 = target as unknown as Container<SC & S>;
29
30 return new Promise<Container<SC & S>>((resolve, reject) => {
31 function guard(v: void | Promise<void>) {
32 if (isPromise(v))
33 v.catch(reject);
34 }
35
36 function complete() {
37 if (!--pending)
38 resolve(_t2);
39 }
40 each(this._builders, (v, k) => {
41 pending++;
42 const d = new DescriptorBuilder<SC & S, any>(_t2,
43 result => {
44 _t2.register(k, result);
45 complete();
46 },
47 reject
48 );
49
50 try {
51 guard(v(d, ct));
52 } catch (e) {
53 reject(e);
54 }
55 });
56 complete();
57 });
58 }
59
60 }
@@ -0,0 +1,52
1 import { primitive } from "../../safe";
2 import { TypeOfService, ContainerKeys, ActivationType, ILifetime } from "../interfaces";
3 import { ICancellation } from "../../interfaces";
4
5 export interface DependencyOptions {
6 optional?: boolean;
7 default?: any;
8 }
9
10 export interface LazyDependencyOptions extends DependencyOptions {
11 lazy: true;
12 }
13
14 export type ExtractService<K, S> = K extends keyof S ? S[K] : never;
15
16 export type ExtractDependency<D, S> = D extends { $dependency: infer K } ?
17 D extends { lazy: true } ? () => ExtractService<K, S> : ExtractService<K, S> :
18 D extends { $type: new (...args: any[]) => infer I } ? I :
19 D extends { $factory: (...args: any[]) => infer R } ? R :
20 WalkDependencies<D, S>;
21
22 export type WalkDependencies<D, S> = D extends primitive ? D :
23 { [K in keyof D]: ExtractDependency<D[K], S> };
24
25 export type InferReferenceType<S extends object, K extends ContainerKeys<S>, O> = O extends { default: infer X } ? (TypeOfService<S, K> | X) :
26 O extends { optional: true } ? (TypeOfService<S, K> | undefined) :
27 TypeOfService<S, K>;
28
29 export interface Resolver<S extends object> {
30 <K extends ContainerKeys<S>, O extends LazyDependencyOptions>(this: void, name: K, opts: O): () => InferReferenceType<S, K, O>;
31 <K extends ContainerKeys<S>, O extends DependencyOptions>(this: void, name: K, opts?: O): InferReferenceType<S, K, O>;
32 }
33
34 export interface DescriptorBuilder<S extends object, T> {
35 factory(f: (resolve: Resolver<S>) => T): void;
36
37 build<T2>(): DescriptorBuilder<S, T2>;
38
39 override<K extends keyof S>(name: K, builder: RegistrationBuilder<S, S[K]>): this;
40 override<K extends keyof S>(services: { [name in K]: RegistrationBuilder<S, S[K]> }): this;
41
42 lifetime(lifetime: "singleton", typeId: any): this;
43 lifetime(lifetime: ILifetime | Exclude<ActivationType, "singleton">): this;
44
45 cleanup(cb: (item: T) => void): this;
46
47 value(v: T): void;
48 }
49
50 export type RegistrationBuilder<S extends object, T> = (d: DescriptorBuilder<S, T>, ct?: ICancellation) => void | Promise<void>;
51
52 export type FluentRegistrations<K extends keyof S, S extends object> = { [k in K]: RegistrationBuilder<S, S[k]> };
@@ -0,0 +1,11
1 import { isPrimitive } from "../safe";
2 import { Descriptor } from "./interfaces";
3 import { FluentConfiguration } from "./fluent/FluentConfiguration";
4 export function isDescriptor(x: any): x is Descriptor {
5 return (!isPrimitive(x)) &&
6 (x.activate instanceof Function);
7 }
8
9 export function fluent<S extends object>() {
10 return new FluentConfiguration<S>();
11 }
@@ -0,0 +1,28
1 import { Bar } from "./Bar";
2
3 // export service descriptor
4 // через service передается информация о типе зависимости
5 // даже если это шаблон.
6 // export const service = annotate<Box<Bar>>();
7
8 // @service.wire()
9 export class Box<T> {
10 private _value: T | undefined;
11
12 constructor(value?: T) {
13 this._value = value;
14 }
15
16 // @service.inject(dependency("bar"))
17 setValue(value: T) {
18 this._value = value;
19 return value;
20 }
21
22 getValue() {
23 if (this._value === undefined)
24 throw new Error("Trying to get a value from the empty box");
25
26 return this._value;
27 }
28 }
@@ -0,0 +1,14
1 import { Services } from "./services";
2 import { fluent } from "../di/traits";
3 import { Box } from "./Box";
4
5 export default fluent<Services>().register({
6 host: it => it.value("example.com"),
7
8 foo: it => import("./Foo").then(({ Foo }) => it
9 .factory(() => new Foo())
10 ),
11
12 box: it => it
13 .factory($dependency => new Box($dependency("foo")))
14 });
@@ -0,0 +1,15
1 import { Foo } from "./Foo";
2 import { Bar } from "./Bar";
3 import { Box } from "./Box";
4
5 /**
6 * Сервисы доступные внутри контейнера
7 */
8 export interface Services {
9 foo: Foo;
10
11 box: Box<Foo>;
12
13 host: string;
14
15 }
@@ -0,0 +1,63
1 import { test } from "./TestTraits";
2 import { fluent } from "../di/traits";
3 import { Bar } from "../mock/Bar";
4 import { Container } from "../di/Container";
5 import { Foo } from "../mock/Foo";
6 import { Box } from "../mock/Box";
7 import { delay } from "../safe";
8 import { Services } from "../mock/services";
9
10 test("Simple fluent config", async t => {
11 const config = fluent<{ host: string; bar: Bar; foo: Foo }>()
12 .register({
13 host: it => it.value("example.com"),
14 bar: it => it.factory(resolve => new Bar({ host: resolve("host") }, "s-bar")),
15 foo: it => import("../mock/Foo").then(m => it.lifetime("container").factory(() => new m.Foo()))
16 });
17
18 const c1 = new Container<{}>();
19 const container = await config.apply(c1);
20
21 t.equal(container.resolve("host"), "example.com", "The value should be resolved");
22 t.assert(container.resolve("bar"), "The service should de activated");
23 t.equal(container.resolve("foo"), container.resolve("foo"), "The service should be activated once");
24 });
25
26 test("Nested async configuration", async t => {
27 const container = await new Container<{
28 foo: Foo;
29 box: Box<Foo>
30 }>().fluent({
31 foo: it => delay(0).then(() => it.factory(() => new Foo())),
32 box: it => it.lifetime("context").factory($dependency => new Box($dependency("foo")))
33 });
34
35 t.assert(container.resolve("box").getValue(), "The dependency should be set");
36 t.equals(container.resolve("box").getValue(), container.resolve("box").getValue(), "The service should be activated once")
37 });
38
39 test("Bad fluent config", async t => {
40 try {
41 await new Container<{
42 foo: Foo;
43 box: Box<Foo>
44 }>().fluent({
45 foo: it => delay(0).then(() => it.factory(() => new Foo())),
46 box: it => it.lifetime("context")
47 .override("foo", () => { throw new Error("bad override"); })
48 .factory($dependency => new Box($dependency("foo")))
49 });
50 t.fail("Should throw");
51 } catch (e) {
52 t.pass("The configuration should fail");
53 t.equal(e.message, "bad override", "the error should pass");
54 }
55 });
56
57 test("Load fluent config", async t => {
58 const container = new Container<Services>();
59
60 await container.configure("../mock/config", { contextRequire: require });
61
62 t.assert(container.resolve("host"), "Should resolve simple value");
63 });
@@ -1,17 +1,28
1 <?xml version="1.0" encoding="UTF-8"?>
1 <?xml version="1.0" encoding="UTF-8"?>
2 <projectDescription>
2 <projectDescription>
3 <name>core</name>
3 <name>core</name>
4 <comment>Project implabjs-core created by Buildship.</comment>
4 <comment>Project implabjs-core created by Buildship.</comment>
5 <projects>
5 <projects>
6 </projects>
6 </projects>
7 <buildSpec>
7 <buildSpec>
8 <buildCommand>
8 <buildCommand>
9 <name>org.eclipse.buildship.core.gradleprojectbuilder</name>
9 <name>org.eclipse.buildship.core.gradleprojectbuilder</name>
10 <arguments>
10 <arguments>
11 </arguments>
11 </arguments>
12 </buildCommand>
12 </buildCommand>
13 </buildSpec>
13 </buildSpec>
14 <natures>
14 <natures>
15 <nature>org.eclipse.buildship.core.gradleprojectnature</nature>
15 <nature>org.eclipse.buildship.core.gradleprojectnature</nature>
16 </natures>
16 </natures>
17 <filteredResources>
18 <filter>
19 <id>1599549685358</id>
20 <name></name>
21 <type>30</type>
22 <matcher>
23 <id>org.eclipse.core.resources.regexFilterMatcher</id>
24 <arguments>node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
25 </matcher>
26 </filter>
27 </filteredResources>
17 </projectDescription>
28 </projectDescription>
@@ -1,2 +1,13
1 arguments=
2 auto.sync=false
3 build.scans.enabled=false
4 connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
1 connection.project.dir=
5 connection.project.dir=
2 eclipse.preferences.version=1
6 eclipse.preferences.version=1
7 gradle.user.home=
8 java.home=/usr/lib64/jvm/java
9 jvm.arguments=
10 offline.mode=false
11 override.workspace.settings=true
12 show.console.view=true
13 show.executions.view=true
@@ -1,234 +1,238
1 plugins {
1 plugins {
2 id "org.implab.gradle-typescript" version "1.3.0"
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
53 strict = true
54 // dojo-typings are sick
55 skipLibCheck = true
52
56
53 if(symbols != 'none') {
57 if(symbols != 'none') {
54 sourceMap = true
58 sourceMap = true
55 sourceRoot = "_src"
59 sourceRoot = packageName
56 }
60 }
57
61
58 if (flavour == 'node') {
62 if (flavour == 'node') {
59 module = "commonjs"
63 module = "commonjs"
60 target = "es2017"
64 target = "es2017"
61 lib = ["es2017", "dom", "scripthost"]
65 lib = ["es2017", "dom", "scripthost"]
62 } else if (flavour == 'browser') {
66 } else if (flavour == 'browser') {
63 module = "amd"
67 module = "amd"
64 target = "es5"
68 target = "es5"
65 lib = ["es5", "dom", "scripthost", "es2015.promise", "es2015.symbol", "es2015.iterable" ]
69 lib = ["es5", "dom", "scripthost", "es2015.promise", "es2015.symbol", "es2015.iterable" ]
66 }
70 }
67 }
71 }
68 tscCmd = "$projectDir/node_modules/.bin/tsc"
72 tscCmd = "$projectDir/node_modules/.bin/tsc"
69 tsLintCmd = "$projectDir/node_modules/.bin/tslint"
73 tsLintCmd = "$projectDir/node_modules/.bin/tslint"
70 esLintCmd = "$projectDir/node_modules/.bin/eslint"
74 esLintCmd = "$projectDir/node_modules/.bin/eslint"
71 }
75 }
72
76
73 tasks.matching{ it.name =~ /^lint/ }.configureEach {
77 tasks.matching{ it.name =~ /^lint/ }.configureEach {
74 onlyIf { lint }
78 onlyIf { lint }
75 }
79 }
76
80
77 if (symbols == 'local') {
81 if (symbols == 'local') {
78 tasks.matching{ it.name =~ /^configureTs/ }.configureEach {
82 tasks.matching{ it.name =~ /^configureTs/ }.configureEach {
79 compilerOptions {
83 compilerOptions {
80 sourceRoot = "file://" + it.rootDir
84 sourceRoot = "file://" + it.rootDir
81 }
85 }
82 }
86 }
83 }
87 }
84
88
85 task printVersion {
89 task printVersion {
86 doLast {
90 doLast {
87 println "packageName: $packageName";
91 println "packageName: $packageName";
88 println "version: $version";
92 println "version: $version";
89 println "flavour: $flavour";
93 println "flavour: $flavour";
90 println "target: $typescript.compilerOptions.target";
94 println "target: $typescript.compilerOptions.target";
91 println "module: $typescript.compilerOptions.module";
95 println "module: $typescript.compilerOptions.module";
92 println "lint: $lint";
96 println "lint: $lint";
93 println "symbols: $symbols";
97 println "symbols: $symbols";
94 }
98 }
95 }
99 }
96
100
97 npmPackMeta {
101 npmPackMeta {
98 meta {
102 meta {
99 name = packageName
103 name = packageName
100 }
104 }
101 }
105 }
102
106
103 configureTsCjs {
107 configureTsCjs {
104 dependsOn sources.main.output
108 dependsOn sources.main.output
105 compilerOptions {
109 compilerOptions {
106 types += [ "node" ]
110 types += [ "node" ]
107 }
111 }
108 }
112 }
109
113
110 configureTsAmd {
114 configureTsAmd {
111 dependsOn sources.main.output
115 dependsOn sources.main.output
112 compilerOptions {
116 compilerOptions {
113 types += [ "requirejs", "dojo-typings" ]
117 types += [ "requirejs", "dojo-typings" ]
114 }
118 }
115 }
119 }
116
120
117 test {
121 test {
118 workingDir layout.buildDirectory.dir("test");
122 workingDir layout.buildDirectory.dir("test");
119 commandLine "node", "tests/index.js"
123 commandLine "node", "tests/index.js"
120 }
124 }
121
125
122 assemble {
126 assemble {
123 if (flavour == 'browser') {
127 if (flavour == 'browser') {
124 dependsOn sources.amd.output
128 dependsOn sources.amd.output
125 from sources.amd.output.compiledDir
129 from sources.amd.output.compiledDir
126 from sources.amd.resources
130 from sources.amd.resources
127 }
131 }
128 if (flavour == 'node') {
132 if (flavour == 'node') {
129 dependsOn sources.cjs.output
133 dependsOn sources.cjs.output
130 from sources.cjs.output.compiledDir
134 from sources.cjs.output.compiledDir
131 from sources.cjs.resources
135 from sources.cjs.resources
132 }
136 }
133 }
137 }
134
138
135 assembleTest {
139 assembleTest {
136 if (flavour == 'browser') {
140 if (flavour == 'browser') {
137 dependsOn sources.amd.output, sources.testAmd.output
141 dependsOn sources.amd.output, sources.testAmd.output
138
142
139 from sources.amd.output.compiledDir
143 from sources.amd.output.compiledDir
140 from sources.testAmd.output.compiledDir
144 from sources.testAmd.output.compiledDir
141 from sources.amd.resources
145 from sources.amd.resources
142 from sources.testAmd.resources
146 from sources.testAmd.resources
143 }
147 }
144 if (flavour == 'node') {
148 if (flavour == 'node') {
145 dependsOn sources.cjs.output, sources.testCjs.output
149 dependsOn sources.cjs.output, sources.testCjs.output
146
150
147 from sources.cjs.output.compiledDir
151 from sources.cjs.output.compiledDir
148 from sources.testCjs.output.compiledDir
152 from sources.testCjs.output.compiledDir
149 from sources.cjs.resources
153 from sources.cjs.resources
150 from sources.testCjs.resources
154 from sources.testCjs.resources
151 }
155 }
152 }
156 }
153
157
154 typings {
158 typings {
155 if (flavour == 'browser') {
159 if (flavour == 'browser') {
156 dependsOn sources.amd.output
160 dependsOn sources.amd.output
157 from sources.amd.output.typingsDir
161 from sources.amd.output.typingsDir
158 }
162 }
159 if (flavour == 'node') {
163 if (flavour == 'node') {
160 dependsOn sources.cjs.output
164 dependsOn sources.cjs.output
161 from sources.cjs.output.typingsDir
165 from sources.cjs.output.typingsDir
162 }
166 }
163 }
167 }
164
168
165 task npmPackTypings(type: Copy) {
169 task npmPackTypings(type: Copy) {
166 npmPackContents.dependsOn it
170 npmPackContents.dependsOn it
167 dependsOn sources.main.output
171 dependsOn sources.main.output
168
172
169 from sources.main.output.typingsDir
173 from sources.main.output.typingsDir
170
174
171 if (flavour == 'browser') {
175 if (flavour == 'browser') {
172 dependsOn sources.amd.output
176 dependsOn sources.amd.output
173 from sources.amd.output.typingsDir
177 from sources.amd.output.typingsDir
174 }
178 }
175 if (flavour == 'node') {
179 if (flavour == 'node') {
176 dependsOn sources.cjs.output
180 dependsOn sources.cjs.output
177 from sources.cjs.output.typingsDir
181 from sources.cjs.output.typingsDir
178 }
182 }
179
183
180 into npm.packageDir
184 into npm.packageDir
181 }
185 }
182
186
183 task npmPackSources(type: Copy) {
187 task npmPackSources(type: Copy) {
184 from sources.main.ts
188 from sources.main.ts
185 if (symbols == 'pack') {
189 if (symbols == 'pack') {
186 npmPackContents.dependsOn npmPackSources
190 npmPackContents.dependsOn npmPackSources
187 }
191 }
188
192
189 if (flavour == 'browser') {
193 if (flavour == 'browser') {
190 from sources.amd.ts
194 from sources.amd.ts
191 }
195 }
192 if (flavour == 'node') {
196 if (flavour == 'node') {
193 from sources.cjs.ts
197 from sources.cjs.ts
194 }
198 }
195
199
196 into npm.packageDir.dir("_src")
200 into npm.packageDir.dir("_src")
197 }
201 }
198
202
199
203
200
204
201 task packJsTar(type: Tar) {
205 task packJsTar(type: Tar) {
202 dependsOn assemble;
206 dependsOn assemble;
203
207
204 archiveBaseName = provider { packageName }
208 archiveBaseName = provider { packageName }
205
209
206 destinationDirectory = buildDir
210 destinationDirectory = buildDir
207 archiveClassifier = provider { typescript.compilerOptions.module }
211 archiveClassifier = provider { typescript.compilerOptions.module }
208 compression = Compression.GZIP
212 compression = Compression.GZIP
209
213
210 from(assemble.outputs)
214 from(assemble.outputs)
211
215
212 doLast {
216 doLast {
213 println archiveName;
217 println archiveName;
214 }
218 }
215 }
219 }
216
220
217 task packTypingsTar(type: Tar) {
221 task packTypingsTar(type: Tar) {
218 }
222 }
219
223
220 publishing {
224 publishing {
221 publications {
225 publications {
222 local(IvyPublication) {
226 local(IvyPublication) {
223 artifact(packJsTar) {
227 artifact(packJsTar) {
224 type = "js"
228 type = "js"
225 }
229 }
226 }
230 }
227 }
231 }
228
232
229 repositories {
233 repositories {
230 ivy {
234 ivy {
231 url "ivy-repo"
235 url "ivy-repo"
232 }
236 }
233 }
237 }
234 } No newline at end of file
238 }
@@ -1,182 +1,182
1 # Observable
1 # Observable
2
2
3 Универсальный способ организации потока сообщений. Данный механизм может
3 Универсальный способ организации потока сообщений. Данный механизм может
4 использоваться для оповещения об изменениях состояний объектов или для доставки
4 использоваться для оповещения об изменениях состояний объектов или для доставки
5 самостоятельных событий, например, связанных с действиями пользователя.
5 самостоятельных событий, например, связанных с действиями пользователя.
6
6
7 Является реализацией классического шаблона наблюдателя с возможность сообщить
7 Является реализацией классического шаблона наблюдателя с возможность сообщить
8 о коце потока событий. Данная реализация не содержит никаких дополнительных
8 о конце потока событий. Данная реализация не содержит никаких дополнительных
9 функций, таких как фильтрация, канал с состоянием, преобразования сообщений и
9 функций, таких как фильтрация, канал с состоянием, преобразования сообщений и
10 т.п. Это сделано специально, чтобы реализация оставалась максимально простой.
10 т.п. Это сделано специально, чтобы реализация оставалась максимально простой.
11
11
12 Пример того, как можно создать последовательность из 10 событий:
12 Пример того, как можно создать последовательность из 10 событий:
13
13
14 ```ts
14 ```ts
15 var events = new Observable(async (notify, error, complete) => {
15 var events = new Observable(async (notify, error, complete) => {
16 // цикл в котором возникает событие
16 // цикл в котором возникает событие
17 for(let i = 0; i < 10; i++) {
17 for(let i = 0; i < 10; i++) {
18 await delay(1000);
18 await delay(1000);
19 // в качестве данных передается номер события
19 // в качестве данных передается номер события
20 notify(i);
20 notify(i);
21 }
21 }
22 // по окончании последовательности информируем, что событий больше не будет
22 // по окончании последовательности информируем, что событий больше не будет
23 compelte();
23 complete();
24 });
24 });
25
25
26 // создаем окно с отображением хода событий
26 // создаем окно с отображением хода событий
27 var progress = showProgress({ min: 0, max: 9, current: 0});
27 var progress = showProgress({ min: 0, max: 9, current: 0});
28
28
29 // подписываемся на события
29 // подписываемся на события
30 events.on(
30 events.on(
31 // обработчик очередного события
31 // обработчик очередного события
32 msg => {
32 msg => {
33 progress.setValue(msg);
33 progress.setValue(msg);
34 }.
34 }.
35 // обработчик ошибки
35 // обработчик ошибки
36 e => {
36 e => {
37 progress.showError(e);
37 progress.showError(e);
38 },
38 },
39 // обработчик конца потока
39 // обработчик конца потока
40 () => {
40 () => {
41 progress.close();
41 progress.close();
42 }
42 }
43 );
43 );
44
44
45 // ожидание следующего события
45 // ожидание следующего события
46 let firstEvent = await events.next();
46 let firstEvent = await events.next();
47 ```
47 ```
48
48
49 `Observable` можно создавать из событий другого объекта, например, виджета:
49 `Observable` можно создавать из событий другого объекта, например, виджета:
50
50
51 ```ts
51 ```ts
52 // клсс
52 // класс
53 class Canvas {
53 class Canvas {
54 readonly mouseMove: IObservable<[number,number]>
54 mouseMove: IObservable<[number,number]>;
55
55
56 postCreate() {
56 postCreate() {
57 // превращаем события виджета в Observable
57 // превращаем события виджета в Observable
58 this.mouseMove = new Observable<[number,number]>((notify) => {
58 this.mouseMove = new Observable<[number,number]>((notify) => {
59 this.mousePad.on('mousemove',(e) => notify([e.clientX, e.clientY]) );
59 this.mousePad.on('mousemove',(e) => notify([e.clientX, e.clientY]) );
60 });
60 });
61 }
61 }
62 }
62 }
63
63
64 ```
64 ```
65
65
66 Если объект инкапсулирует в себе `Observable`, он также может сохранить методы
66 Если объект инкапсулирует в себе `Observable`, он также может сохранить методы
67 для оповещения подписчиков для дальнейшего их использования внутри класса.
67 для оповещения подписчиков для дальнейшего их использования внутри класса.
68
68
69 ```ts
69 ```ts
70 // класс, который будет генерировать события местоположения
70 // класс, который будет генерировать события местоположения
71 class PositionTracker implements IDestroyable {
71 class PositionTracker implements IDestroyable {
72 // _nextPosition и _complete будут связаны с position при создании
72 // _nextPosition и _complete будут связаны с position при создании
73 // экземпляра PositionTracker.
73 // экземпляра PositionTracker.
74 _nextPosition: (pos: Position) => void
74 _nextPosition: (pos: Position) => void
75 _complete: () => void
75 _complete: () => void
76
76
77 readonly position: IObservable<Position>
77 readonly position: IObservable<Position>
78
78
79 // конструктор
79 // конструктор
80 constructor(...args: any[]) {
80 constructor(...args: any[]) {
81 super(args);
81 super(args);
82
82
83 // создаем Observable
83 // создаем Observable
84 this.position = new Observable<Position>((notify, error, complete) => {
84 this.position = new Observable<Position>((notify, error, complete) => {
85 // сохраняем методы для оповещения о новом местоположении
85 // сохраняем методы для оповещения о новом местоположении
86 this._nextPosition = notify;
86 this._nextPosition = notify;
87 // метод об оповещении конца потока событий
87 // метод об оповещении конца потока событий
88 this._complete = complete
88 this._complete = complete
89 });
89 });
90 }
90 }
91
91
92 // метод для очистки ресурсов
92 // метод для очистки ресурсов
93 destroy() {
93 destroy() {
94 this._complete();
94 this._complete();
95
95
96 super();
96 super();
97 }
97 }
98 }
98 }
99 ```
99 ```
100
100
101 Существует также несколько варинатов получения сообщений
101 Существует также несколько вариантов получения сообщений
102
102
103 ```ts
103 ```ts
104 // регистрация метода для получений событий
104 // регистрация метода для получений событий
105 let subscription = pushEvents.on((msg) => {
105 let subscription = pushEvents.on((msg) => {
106 displayPopup(msg);
106 displayPopup(msg);
107 });
107 });
108
108
109 // подписку можно отменить, после чего обработчики больше не будут вызываться
109 // подписку можно отменить, после чего обработчики больше не будут вызываться
110 subcription.destroy();
110 subcription.destroy();
111
111
112 // если требуется получить только одно сообщение можно использовать
112 // если требуется получить только одно сообщение можно использовать
113 // асинхронный метод next(ct?: ICancellation)
113 // асинхронный метод next(ct?: ICancellation)
114
114
115 let msg = await pushEvents.next();
115 let msg = await pushEvents.next();
116
116
117 // пример метода для получения координат с карты, который использует
117 // пример метода для получения координат с карты, который использует
118 // событие нажатия мышью для определения координат.
118 // событие нажатия мышью для определения координат.
119
119
120 class Map {
120 class Map {
121 /**
121 /**
122 Получает координаты по щелчку мыши.
122 Получает координаты по щелчку мыши.
123 @async
123 @async
124 @returns [lon,lat]
124 @returns [lon,lat]
125 */
125 */
126 async peekCoordinates(ct: ICancellation = Cancellation.none) {
126 async peekCoordinates(ct: ICancellation = Cancellation.none) {
127 // получаем событие клика
127 // получаем событие клика
128 let evt = this.viewport.click.next(ct);
128 let evt = this.viewport.click.next(ct);
129
129
130 // преобразуем позицию на экране в координаты карты
130 // преобразуем позицию на экране в координаты карты
131 return this.clientToCoodinates([evt.clientx,evt.clientY]);
131 return this.clientToCoordinates([evt.clientX,evt.clientY]);
132 }
132 }
133 }
133 }
134
134
135
135
136 let map : Map; // где-то объявлено
136 let map : Map; // где-то объявлено
137
137
138 // пример получения координат с карты
138 // пример получения координат с карты
139 let coords = await map.peekCoordinates();
139 let coords = await map.peekCoordinates();
140
140
141 ```
141 ```
142
142
143 ## Observable и последовательности
143 ## Observable и последовательности
144
144
145 Можно сичтать, что `Observable` это некоторая аналогия итератора только в
145 Можно считать, что `Observable` это некоторая аналогия итератора только в
146 парадигме событийного (или реактивного) программировния. Следует также понимать,
146 парадигме событийного (или реактивного) программирования. Следует также понимать,
147 что при переходе от синхронного процедурного программирования к событийному так
147 что при переходе от синхронного процедурного программирования к событийному так
148 же меняется и направление управления (Inverse Of Control), что означает
148 же меняется и направление управления (Inverse Of Control), что означает
149 следующее:
149 следующее:
150
150
151 * при работе с итераторами клиенты сами определяют момент чтения следующего
151 * при работе с итераторами клиенты сами определяют момент чтения следующего
152 элемента последовательности.
152 элемента последовательности.
153 * при работе с `Observable` клиенты вынуждены обрабатывать эти события по мере
153 * при работе с `Observable` клиенты вынуждены обрабатывать эти события по мере
154 их поступления и не могут на это повлиять.
154 их поступления и не могут на это повлиять.
155
155
156 Последний пункт можно изменить применив, например, буффер или канал с
156 Последний пункт можно изменить применив, например, буфер или канал с
157 состоянием, т.е. очередь, но данные механизмы выходят за рамки простого шаблона
157 состоянием, т.е. очередь, но данные механизмы выходят за рамки простого шаблона
158 наблюдателя.
158 наблюдателя.
159
159
160 ```ts
160 ```ts
161 // обработка в цикле не гарантирует получения всех сообщений
161 // обработка в цикле не гарантирует получения всех сообщений
162 while(1) {
162 while(1) {
163 // ожидаем следующее событие, по сути это подписка только на одно событие
163 // ожидаем следующее событие, по сути это подписка только на одно событие
164 let next = await events.next();
164 let next = await events.next();
165
165
166 // такой цикл может пропускать сообщения, поскольку асинхронная операция
166 // такой цикл может пропускать сообщения, поскольку асинхронная операция
167 // позволит возобновить создание новых событий, на которые мы не подписаны
167 // позволит возобновить создание новых событий, на которые мы не подписаны
168 await processEvent(next);
168 await processEvent(next);
169
169
170 // не только асинхронные операции могут привести к пропуску события
170 // не только асинхронные операции могут привести к пропуску события
171 // например вызов метода, который приводит к созданию события так же
171 // например вызов метода, который приводит к созданию события так же
172 // приведет к тому, что созданное событие не будет обработано в текущем
172 // приведет к тому, что созданное событие не будет обработано в текущем
173 // цикле
173 // цикле
174 doSmthAndRiseEvent();
174 doSmthAndRiseEvent();
175 }
175 }
176
176
177 // для получения всех сообщений нужно регистрировать подписчика
177 // для получения всех сообщений нужно регистрировать подписчика
178 events.on((data) => {
178 events.on((data) => {
179 // будет вызван для всех сообщений
179 // будет вызван для всех сообщений
180 processEvent(data);
180 processEvent(data);
181 });
181 });
182 ``` No newline at end of file
182 ```
@@ -1,110 +1,109
1 define([
1 define([
2 "dojo/_base/declare",
2 "dojo/_base/declare",
3 "../safe",
3 "../safe",
4 "dojo/when",
4 "dojo/when",
5 "dojo/store/util/QueryResults" ],
5 "dojo/store/util/QueryResults" ],
6
6
7 function(declare, safe, when, QueryResults) {
7 function(declare, safe, when, QueryResults) {
8
8
9 "use strict";
9 "use strict";
10
10
11 /**
11 /**
12 * Обертка вокруг произвольного хранилища, только для чтения. Используется
12 * Обертка вокруг произвольного хранилища, только для чтения. Используется
13 * для преобразования данных, например, отображения в списках элементов
13 * для преобразования данных, например, отображения в списках элементов
14 * пространственных данных.
14 * пространственных данных.
15 */
15 */
16 return declare(null, {
16 return declare(null, {
17 /**
17 /**
18 * @type{String} Свойство, хранящее идентификатор
18 * @type{String} Свойство, хранящее идентификатор
19 */
19 */
20 idProperty : null,
20 idProperty : null,
21
21
22 _store : null,
22 _store : null,
23
23
24 /**
24 /**
25 * @param{String} opts.idProperty Имя свойства, в которое будет записан
25 * @param{String} opts.idProperty Имя свойства, в которое будет записан
26 * идентификатор, если не указан, то идентификатор будет
26 * идентификатор, если не указан, то идентификатор будет
27 * взят из родительского хранилища или использоваться
27 * взят из родительского хранилища или использоваться
28 * строка <code>id</code>
28 * строка <code>id</code>
29 * @param{dojo.store} opts.store Родительское хранилище
29 * @param{dojo.store} opts.store Родительское хранилище
30 */
30 */
31 constructor : function(opts) {
31 constructor : function(opts) {
32 safe.argumentNotNull(opts, "opts");
32 safe.argumentNotNull(opts, "opts");
33 safe.argumentNotNull(opts.store, "opts.store");
33 safe.argumentNotNull(opts.store, "opts.store");
34
34
35 this._store = opts.store;
35 this._store = opts.store;
36 delete opts.store;
36 delete opts.store;
37 declare.safeMixin(this, opts);
37 declare.safeMixin(this, opts);
38 this.idProperty = opts.idProperty || this._store.idProperty || "id";
38 this.idProperty = opts.idProperty || this._store.idProperty || "id";
39 },
39 },
40
40
41 getParentStore : function() {
41 getParentStore : function() {
42 return this._store;
42 return this._store;
43 },
43 },
44
44
45 get : function(id) {
45 get : function(id) {
46 var me = this;
46 var me = this;
47 return when(me._store.get(id), function(x) {
47 return when(me._store.get(id), function(x) {
48 var m = me.mapItem(x);
48 var m = me.mapItem(x);
49 if (!(me.idProperty in m))
49 if (!(me.idProperty in m))
50 m[me.idProperty] = id;
50 m[me.idProperty] = id;
51 return m;
51 return m;
52 });
52 });
53 },
53 },
54
54
55 /**
55 /**
56 * Выполняет запрос в родительском хранилище, для этого используется
56 * Выполняет запрос в родительском хранилище, для этого используется
57 * <code>translateQuery</code> для подготовки запроса, затем,
57 * <code>translateQuery</code> для подготовки запроса, затем,
58 * <code>mapItem</code> для преобразования результатов.
58 * <code>mapItem</code> для преобразования результатов.
59 */
59 */
60 query : function(q, options) {
60 query : function(q, options) {
61 var me = this, store = this._store;
61 var me = this, store = this._store;
62 return when(store.query(me.translateQuery(q), me
62 return when(store.query(me.translateQuery(q), me
63 .translateOptions(options)), function(res) {
63 .translateOptions(options)), function(res) {
64 var total = res.total;
64 var total = res.total;
65 var mapped = res.map(function(x) {
65 var mapped = res.map(function(x) {
66 var m = me.mapItem(x);
66 var m = me.mapItem(x);
67 if (!(me.idProperty in m))
67 if (!(me.idProperty in m))
68 m[me.idProperty] = store.getIdentity &&
68 m[me.idProperty] = store.getIdentity &&
69 store.getIdentity(x);
69 store.getIdentity(x);
70 return m;
70 return m;
71 });
71 });
72 mapped.total = total;
72 mapped.total = total;
73 var results = new QueryResults(mapped);
73 var results = new QueryResults(mapped);
74 console.log(results);
75 return results;
74 return results;
76 });
75 });
77 },
76 },
78
77
79 getIdentity : function(obj) {
78 getIdentity : function(obj) {
80 return obj && obj[this.idProperty];
79 return obj && obj[this.idProperty];
81 },
80 },
82
81
83 /**
82 /**
84 * Преобразование запроса в формат родительского хранилища.
83 * Преобразование запроса в формат родительского хранилища.
85 *
84 *
86 * @param{Object} q Запрос в формате текущего хранилища
85 * @param{Object} q Запрос в формате текущего хранилища
87 * @returns{Object} Запрос в формате родительского хранилища
86 * @returns{Object} Запрос в формате родительского хранилища
88 */
87 */
89 translateQuery : function(q) {
88 translateQuery : function(q) {
90 return q;
89 return q;
91 },
90 },
92
91
93 translateOptions : function(options) {
92 translateOptions : function(options) {
94 return options;
93 return options;
95 },
94 },
96
95
97 /**
96 /**
98 * Преобразование объекта из родительского хранилища. При преобразовании
97 * Преобразование объекта из родительского хранилища. При преобразовании
99 * в объекте можно задать идентификатор, иначе идентификатор будет
98 * в объекте можно задать идентификатор, иначе идентификатор будет
100 * автоматически получен и присвоен из родительского хранилища
99 * автоматически получен и присвоен из родительского хранилища
101 *
100 *
102 * @param{Object} item Объект из родительского хранилища
101 * @param{Object} item Объект из родительского хранилища
103 * @returns{Object} результат преобразования
102 * @returns{Object} результат преобразования
104 */
103 */
105 mapItem : function(item) {
104 mapItem : function(item) {
106 return item;
105 return item;
107 }
106 }
108 });
107 });
109
108
110 }); No newline at end of file
109 });
@@ -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,199
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 } 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 function isObserver(val: any): val is IObserver<any> {
11 function isObserver(val: any): val is IObserver<any> {
12 return val && (typeof val.next === "function");
12 return val && (typeof val.next === "function");
13 }
13 }
14
14
15 export class Observable<T> implements IObservable<T> {
15 export class Observable<T> implements IObservable<T> {
16 private _once = new Array<IObserver<T>>();
16 private _once = new Array<IObserver<T>>();
17
17
18 private _observers = new Array<IObserver<T>>();
18 private _observers = new Array<IObserver<T>>();
19
19
20 private _complete: boolean;
20 private _complete = false;
21
21
22 private _error: any;
22 private _error: any;
23
23
24 constructor(func?: Initializer<T>) {
24 constructor(func?: Initializer<T>) {
25 if (func)
25 if (func)
26 func(
26 func(
27 this._notifyNext.bind(this),
27 this._notifyNext.bind(this),
28 this._notifyError.bind(this),
28 this._notifyError.bind(this),
29 this._notifyCompleted.bind(this)
29 this._notifyCompleted.bind(this)
30 );
30 );
31 }
31 }
32
32
33 /**
33 /**
34 * Registers handlers for the current observable object.
34 * Registers handlers for the current observable object.
35 *
35 *
36 * @param next the handler for events
36 * @param next the handler for events
37 * @param error the handler for a error
37 * @param error the handler for a error
38 * @param complete the handler for a completion
38 * @param complete the handler for a completion
39 * @returns {IDestroyable} the handler for the current subscription, this
39 * @returns {IDestroyable} the handler for the current subscription, this
40 * handler can be used to unsubscribe from events.
40 * handler can be used to unsubscribe from events.
41 *
41 *
42 */
42 */
43 on(next: Handler<T>, error?: Handler<any>, complete?: () => void): IDestroyable {
43 on(next: Handler<T>, error?: Handler<any>, complete?: () => void): IDestroyable {
44 argumentNotNull(next, "next");
44 argumentNotNull(next, "next");
45
45
46 const me = this;
46 const me = this;
47
47
48 const observer: IObserver<T> & IDestroyable = {
48 const observer: IObserver<T> & IDestroyable = {
49 next,
49 next: next.bind(null),
50 error: error ? error.bind(null) : noop,
50 error: error ? error.bind(null) : noop,
51 complete: complete ? complete.bind(null) : noop,
51 complete: complete ? complete.bind(null) : noop,
52
52
53 destroy() {
53 destroy() {
54 me._removeObserver(this);
54 me._removeObserver(this);
55 }
55 }
56 };
56 };
57
57
58 this._addObserver(observer);
58 this._addObserver(observer);
59
59
60 return observer;
60 return observer;
61 }
61 }
62
62
63 subscribe(next: IObserver<T> | Handler<T>, error?: Handler<any>, complete?: () => void): IDestroyable {
63 subscribe(next: IObserver<T> | Handler<T>, error?: Handler<any>, complete?: () => void): IDestroyable {
64 if (isObserver(next)) {
64 if (isObserver(next)) {
65 const me = this;
65 this._addObserver(next);
66 const subscription = {
66 return {
67 destroy() {
67 destroy: () => this._removeObserver(next)
68 me._removeObserver(next);
69 }
70 };
68 };
71 this._addObserver(next);
69 } else {
72 return subscription;
73 } else if (next) {
74 const observer = {
70 const observer = {
75 next,
71 next: next.bind(null),
76 error,
72 error: error ? error.bind(null) : noop,
77 complete
73 complete: complete ? complete.bind(null) : noop
78 };
74 };
79 const me = this;
75
80 const subscription = {
76 this._addObserver(observer);
81 destroy() {
77 return {
82 me._removeObserver(observer);
78 destroy: () => this._removeObserver(observer)
83 }
84 };
79 };
85 this._addObserver(observer);
86 return subscription;
87 } else {
88 return destroyed;
89 }
80 }
90 }
81 }
91
82
92 private _addObserver(observer: IObserver<T>) {
83 private _addObserver(observer: IObserver<T>) {
93 if (this._complete) {
84 if (this._complete) {
94 try {
85 try {
95 if (this._error)
86 if (this._error)
96 observer.error(this._error);
87 observer.error(this._error);
97 else
88 else
98 observer.complete();
89 observer.complete();
99 } catch (e) {
90 } catch (e) {
100 this.onObserverException(e);
91 this.onObserverException(e);
101 }
92 }
102 } else {
93 } else {
103 this._observers.push(observer);
94 this._observers.push(observer);
104 }
95 }
105 }
96 }
106
97
107 /**
98 /**
108 * Waits for the next event. This method can't be used to read messages
99 * 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.
100 * as a sequence since it can skip some messages between calls.
110 *
101 *
111 * @param ct a cancellation token
102 * @param ct a cancellation token
112 */
103 */
113 next(ct: ICancellation = Cancellation.none) {
104 next(ct: ICancellation = Cancellation.none) {
114 return new Promise<T>((resolve, reject) => {
105 return new Promise<T>((resolve, reject) => {
115 const observer: IObserver<T> = {
106 const observer: IObserver<T> = {
116 next: resolve,
107 next: resolve,
117 error: reject,
108 error: reject,
118 complete: () => reject("No more events are available")
109 complete: () => reject("No more events are available")
119 };
110 };
120
111
121 if (this._addOnce(observer) && ct.isSupported()) {
112 if (this._addOnce(observer) && ct.isSupported()) {
122 ct.register(e => {
113 ct.register(e => {
123 this._removeOnce(observer);
114 this._removeOnce(observer);
124 reject(e);
115 reject(e);
125 });
116 });
126 }
117 }
127 });
118 });
128 }
119 }
129
120
130 private _addOnce(observer: IObserver<T>) {
121 private _addOnce(observer: IObserver<T>) {
131 if (this._complete) {
122 if (this._complete) {
132 try {
123 try {
133 if (this._error)
124 if (this._error)
134 observer.error(this._error);
125 observer.error(this._error);
135 else
126 else
136 observer.complete();
127 observer.complete();
137 } catch (e) {
128 } catch (e) {
138 this.onObserverException(e);
129 this.onObserverException(e);
139 }
130 }
140 return false;
131 return false;
141 }
132 }
142
133
143 this._once.push(observer);
134 this._once.push(observer);
144 return true;
135 return true;
145 }
136 }
146
137
147 protected onObserverException(e: any) {
138 protected onObserverException(e: any) {
148 }
139 }
149
140
150 private _removeOnce(d: IObserver<T>) {
141 private _removeOnce(d: IObserver<T>) {
151 const i = this._once.indexOf(d);
142 const i = this._once.indexOf(d);
152 if (i >= 0)
143 if (i >= 0)
153 this._once.splice(i, 1);
144 this._once.splice(i, 1);
154 }
145 }
155
146
156 private _removeObserver(d: IObserver<T>) {
147 private _removeObserver(d: IObserver<T>) {
157 const i = this._observers.indexOf(d);
148 const i = this._observers.indexOf(d);
158 if (i >= 0)
149 if (i >= 0)
159 this._observers.splice(i, 1);
150 this._observers.splice(i, 1);
160 }
151 }
161
152
162 private _notify(guard: (observer: IObserver<T>) => void) {
153 private _notify(guard: (observer: IObserver<T>) => void) {
163 this._once.forEach(guard);
154 this._once.forEach(guard);
164 this._once = [];
155 this._once = [];
165
156
166 this._observers.forEach(guard);
157 this._observers.forEach(guard);
167 }
158 }
168
159
169 protected _notifyNext(evt: T) {
160 protected _notifyNext(evt: T) {
170 const guard = (observer: IObserver<T>) => {
161 const guard = (observer: IObserver<T>) => {
171 try {
162 try {
172 observer.next(evt);
163 observer.next(evt);
173 } catch (e) {
164 } catch (e) {
174 this.onObserverException(e);
165 this.onObserverException(e);
175 }
166 }
176 };
167 };
177
168
178 this._notify(guard);
169 this._notify(guard);
179 }
170 }
180
171
181 protected _notifyError(e: any) {
172 protected _notifyError(e: any) {
182 const guard = (observer: IObserver<T>) => {
173 const guard = (observer: IObserver<T>) => {
183 try {
174 try {
184 observer.error(e);
175 observer.error(e);
185 } catch (e) {
176 } catch (e) {
186 this.onObserverException(e);
177 this.onObserverException(e);
187 }
178 }
188 };
179 };
189
180
190 this._notify(guard);
181 this._notify(guard);
191 this._observers = [];
182 this._observers = [];
192 this._complete = true;
183 this._complete = true;
193 }
184 }
194
185
195 protected _notifyCompleted() {
186 protected _notifyCompleted() {
196 const guard = (observer: IObserver<T>) => {
187 const guard = (observer: IObserver<T>) => {
197 try {
188 try {
198 observer.complete();
189 observer.complete();
199 } catch (e) {
190 } catch (e) {
200 this.onObserverException(e);
191 this.onObserverException(e);
201 }
192 }
202 };
193 };
203
194
204 this._notify(guard);
195 this._notify(guard);
205 this._observers = [];
196 this._observers = [];
206 this._complete = true;
197 this._complete = true;
207 }
198 }
208 }
199 }
@@ -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 if (this._value !== value) {
20 this._value = value;
21 this._value = value;
21 this._notifyNext(value);
22 this._notifyNext(value);
22 }
23 }
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,41
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 const noop = () => void (0);
6
5 export class AsyncComponent implements IAsyncComponent, ICancellable {
7 export class AsyncComponent implements IAsyncComponent, ICancellable {
6 _cancel: (e: any) => void;
8 _cancel: ((e: any) => void) = noop;
7
9
8 _completion: Promise<void> = Promise.resolve();
10 _completion: Promise<void> = Promise.resolve();
9
11
10 getCompletion() { return this._completion; }
12 getCompletion() { return this._completion; }
11
13
12 runOperation(op: (ct: ICancellation) => any, ct: ICancellation = Cancellation.none) {
14 runOperation(op: (ct: ICancellation) => any, ct: ICancellation = Cancellation.none) {
13 // create inner cancellation bound to the passed cancellation token
15 // create inner cancellation bound to the passed cancellation token
14 let h: IDestroyable;
16 let h: IDestroyable;
15 const inner = new Cancellation(cancel => {
17 const inner = new Cancellation(cancel => {
16
18
17 this._cancel = cancel;
19 this._cancel = cancel;
18 h = ct.register(cancel);
20 h = ct.register(cancel);
19 });
21 });
20
22
21 // TODO create cancellation source here
23 // TODO create cancellation source here
22 const guard = async () => {
24 const guard = async () => {
23 try {
25 try {
24 await op(inner);
26 await op(inner);
25 } finally {
27 } finally {
26 // after the operation is complete we need to cleanup the
28 // after the operation is complete we need to cleanup the
27 // resources
29 // resources
28 destroy(h);
30 destroy(h);
29 this._cancel = null;
31 this._cancel = noop;
30 }
32 }
31 };
33 };
32
34
33 return this._completion = guard();
35 return this._completion = guard();
34 }
36 }
35
37
36 cancel(reason: any) {
38 cancel(reason: any) {
37 if (this._cancel)
38 this._cancel(reason);
39 this._cancel(reason);
39 }
40 }
40 }
41 }
@@ -1,132 +1,170
1 import { TraceSource } from "../log/TraceSource";
1 import { TraceSource } from "../log/TraceSource";
2 import { argumentNotNull, argumentNotEmptyString, isPrimitive, each, isNull } from "../safe";
2 import { argumentNotEmptyString } from "../safe";
3 import { Descriptor, ServiceMap } from "./interfaces";
3 import { Descriptor, ContainerServiceMap, ContainerKeys, TypeOfService, ILifetime } from "./interfaces";
4 import { Container } from "./Container";
4 import { Container } from "./Container";
5 import { MapOf } from "../interfaces";
5
6
6 const trace = TraceSource.get("@implab/core/di/ActivationContext");
7 const trace = TraceSource.get("@implab/core/di/ActivationContext");
7
8
8 export interface ActivationContextInfo {
9 export interface ActivationContextInfo {
9 name: string;
10 name: string;
10
11
11 service: string;
12 service: string;
12
13
13 scope: ServiceMap;
14 }
14 }
15
15
16 export class ActivationContext {
16 let nextId = 1;
17 _cache: object;
18
17
19 _services: ServiceMap;
18 /** This class is created once per `Container.resolve` method call and used to
19 * cache dependencies and to track created instances. The activation context
20 * tracks services with `context` activation type.
21 */
22 export class ActivationContext<S extends object> {
23 _cache: MapOf<any>;
20
24
21 _stack: ActivationContextInfo[];
25 _services: ContainerServiceMap<S>;
22
26
23 _visited: object;
27 _visited: MapOf<any>;
24
28
25 _name: string;
29 _name: string;
26
30
27 _localized: boolean;
31 _service: Descriptor<S, any>;
28
32
29 container: Container;
33 _container: Container<S>;
34
35 _parent: ActivationContext<S> | undefined;
30
36
31 constructor(container: Container, services: ServiceMap, name?: string, cache?: object, visited?) {
37 /** Creates a new activation context with the specified parameters.
32 argumentNotNull(container, "container");
38 * @param container the container which starts the activation process
33 argumentNotNull(services, "services");
39 * @param services the initial service registrations
34
40 * @param name the name of the service being activated, this parameter is
41 * used for the debug purpose.
42 * @param service the service to activate, this parameter is used for the
43 * debug purpose.
44 */
45 constructor(container: Container<S>, services: ContainerServiceMap<S>, name: string, service: Descriptor<S, any>) {
35 this._name = name;
46 this._name = name;
36 this._visited = visited || {};
47 this._service = service;
37 this._stack = [];
48 this._visited = {};
38 this._cache = cache || {};
49 this._cache = {};
39 this._services = services;
50 this._services = services;
40 this.container = container;
51 this._container = container;
41 }
52 }
42
53
54 /** the name of the current resolving dependency */
43 getName() {
55 getName() {
44 return this._name;
56 return this._name;
45 }
57 }
46
58
47 resolve(name, def?): any {
59 /** Returns the container for which 'resolve' method was called */
60 getContainer() {
61 return this._container;
62 }
63
64 /** Resolves the specified dependency in the current context
65 * @param name The name of the dependency being resolved
66 */
67 resolve<K extends ContainerKeys<S>>(name: K): TypeOfService<S, K>;
68 /** Resolves the specified dependency with the specified default value if
69 * the dependency is missing.
70 *
71 * @param name The name of the dependency being resolved
72 * @param def A default value to return in case of the specified dependency
73 * is missing.
74 */
75 resolve<K extends ContainerKeys<S>, T>(name: K, def: T): TypeOfService<S, K> | T;
76 /** Resolves the specified dependency and returns undefined in case if the
77 * dependency is missing.
78 *
79 * @param name The name of the dependency being resolved
80 */
81 resolve<K extends ContainerKeys<S>>(name: K, def: undefined): TypeOfService<S, K> | undefined;
82 resolve<K extends ContainerKeys<S>, T>(name: K, def?: T): TypeOfService<S, K> | T | undefined {
48 const d = this._services[name];
83 const d = this._services[name];
49
84
50 if (!d)
85 if (d !== undefined) {
86 return this.activate(d, name.toString());
87 } else {
51 if (arguments.length > 1)
88 if (arguments.length > 1)
52 return def;
89 return def;
53 else
90 else
54 throw new Error(`Service ${name} not found`);
91 throw new Error(`Service ${name} not found`);
55
92 }
56 return this.activate(d, name);
57 }
93 }
58
94
59 /**
95 /**
60 * registers services local to the the activation context
96 * registers services local to the the activation context
61 *
97 *
62 * @name{string} the name of the service
98 * @name{string} the name of the service
63 * @service{string} the service descriptor to register
99 * @service{string} the service descriptor to register
64 */
100 */
65 register(name: string, service: Descriptor) {
101 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>) {
66 argumentNotEmptyString(name, "name");
102 argumentNotEmptyString(name, "name");
67
103
68 this._services[name] = service;
104 this._services[name] = service as any;
69 }
105 }
70
106
71 clone() {
107 createLifetime(): ILifetime {
72 return new ActivationContext(
108 const id = nextId++;
73 this.container,
109 const me = this;
74 this._services,
110 return {
75 this._name,
111 initialize() {
76 this._cache,
112 },
77 this._visited
113 has() {
78 );
114 return id in me._cache;
115 },
116 get() {
117 return me._cache[id];
118 },
119 store(item: any) {
120 me._cache[id] = item;
121 }
122 };
79 }
123 }
80
124
81 has(id: string) {
125 activate<T>(d: Descriptor<S, T>, name: string) {
82 return id in this._cache;
83 }
84
85 get(id: string) {
86 return this._cache[id];
87 }
88
89 store(id: string, value) {
90 return (this._cache[id] = value);
91 }
92
93 activate(d: Descriptor, name: string) {
94 if (trace.isLogEnabled())
126 if (trace.isLogEnabled())
95 trace.log(`enter ${name} ${d}`);
127 trace.log(`enter ${name} ${d}`);
96
128
97 this.enter(name, d.toString());
129 const ctx = this.enter(d, name);
98 const v = d.activate(this);
130 const v = d.activate(ctx);
99 this.leave();
100
131
101 if (trace.isLogEnabled())
132 if (trace.isLogEnabled())
102 trace.log(`leave ${name}`);
133 trace.log(`leave ${name}`);
103
134
104 return v;
135 return v;
105 }
136 }
106
137
107 visit(id: string) {
138 visit(id: string) {
108 const count = this._visited[id] || 0;
139 const count = this._visited[id] || 0;
109 this._visited[id] = count + 1;
140 this._visited[id] = count + 1;
110 return count;
141 return count;
111 }
142 }
112
143
113 getStack() {
144 getStack(): ActivationContextInfo[] {
114 return this._stack.slice().reverse();
145 const stack = [{
146 name: this._name,
147 service: this._service.toString()
148 }];
149
150 return this._parent ?
151 stack.concat(this._parent.getStack()) :
152 stack;
115 }
153 }
116
154
117 private enter(name: string, service: string) {
155 private enter(service: Descriptor<S, any>, name: string): this {
118 this._stack.push({
156 const clone = Object.create(this);
119 name,
157 clone._name = name;
120 service,
158 clone._services = Object.create(this._services);
121 scope: this._services
159 clone._parent = this;
122 });
160 clone._service = service;
123 this._name = name;
161 return clone;
124 this._services = Object.create(this._services);
125 }
162 }
126
163
127 private leave() {
164 /** Creates a clone for the current context, used to protect it from modifications */
128 const ctx = this._stack.pop();
165 clone(): this {
129 this._services = ctx.scope;
166 const clone = Object.create(this);
130 this._name = ctx.name;
167 clone._services = Object.create(this._services);
168 return clone;
131 }
169 }
132 }
170 }
@@ -1,36 +1,39
1 import { ActivationContextInfo } from "./ActivationContext";
1 export interface ActivationItem {
2 name: string;
3 service: string;
4 }
2
5
3 export class ActivationError {
6 export class ActivationError {
4 activationStack: ActivationContextInfo[];
7 activationStack: ActivationItem[];
5
8
6 service: string;
9 service: string;
7
10
8 innerException: any;
11 innerException: any;
9
12
10 message: string;
13 message: string;
11
14
12 constructor(service: string, activationStack: ActivationContextInfo[], innerException) {
15 constructor(service: string, activationStack: ActivationItem[], innerException: any) {
13 this.message = "Failed to activate the service";
16 this.message = "Failed to activate the service";
14 this.activationStack = activationStack;
17 this.activationStack = activationStack;
15 this.service = service;
18 this.service = service;
16 this.innerException = innerException;
19 this.innerException = innerException;
17 }
20 }
18
21
19 toString() {
22 toString() {
20 const parts = [this.message];
23 const parts = [this.message];
21 if (this.service)
24 if (this.service)
22 parts.push("when activating: " + this.service.toString());
25 parts.push("when activating: " + this.service.toString());
23
26
24 if (this.innerException)
27 if (this.innerException)
25 parts.push("caused by: " + this.innerException.toString());
28 parts.push("caused by: " + this.innerException.toString());
26
29
27 if (this.activationStack) {
30 if (this.activationStack) {
28 parts.push("at");
31 parts.push("at");
29 this.activationStack
32 this.activationStack
30 .forEach(x => parts.push(` ${x.name} ${x.service}`));
33 .forEach(x => parts.push(` ${x.name} ${x.service}`));
31
34
32 }
35 }
33
36
34 return parts.join("\n");
37 return parts.join("\n");
35 }
38 }
36 }
39 }
@@ -1,37 +1,40
1 import { Descriptor, isDescriptor } from "./interfaces";
1 import { Descriptor } from "./interfaces";
2 import { ActivationContext } from "./ActivationContext";
2 import { ActivationContext } from "./ActivationContext";
3 import { isPrimitive } from "../safe";
3 import { isPrimitive } from "../safe";
4 import { isDescriptor } from "./traits";
4
5
5 export class AggregateDescriptor implements Descriptor {
6 export class AggregateDescriptor<S extends object, T> implements Descriptor<S, T> {
6 _value: object;
7 _value: any;
7
8
8 constructor(value: object) {
9 constructor(value: any) {
9 this._value = value;
10 this._value = value;
10 }
11 }
11
12
12 activate(context: ActivationContext) {
13 activate(context: ActivationContext<S>): T {
13 return this._parse(this._value, context, "$value");
14 return this._parse(this._value, context, "$value");
14 }
15 }
15
16
16 // TODO: make async
17 _parse(value: any, context: ActivationContext<S>, path: string): any {
17 _parse(value, context: ActivationContext, path: string) {
18 if (isPrimitive(value))
18 if (isPrimitive(value))
19 return value;
19 return value as any;
20
20
21 if (isDescriptor(value))
21 if (isDescriptor(value))
22 return context.activate(value, path);
22 return context.activate(value, path);
23
23
24 if (value instanceof Array)
24 if (value instanceof Array)
25 return value.map((x, i) => this._parse(x, context, `${path}[${i}]`));
25 return value.map((x, i) => this._parse(x, context, `${path}[${i}]`)) as any;
26
26
27 const t = {};
27 const t: any = {};
28 for (const p of Object.keys(value))
28 for (const p in value)
29 t[p] = this._parse(value[p], context, `${path}.${p}`);
29 t[p] = this._parse(value[p], context, `${path}.${p}`);
30 return t;
30 return t;
31
32 }
31 }
33
32
34 toString() {
33 toString() {
35 return "@walk";
34 return "@walk";
36 }
35 }
36
37 clone() {
38 return this;
37 }
39 }
40 }
@@ -1,12 +1,12
1 export class ConfigError extends Error {
1 export class ConfigError extends Error {
2 inner: any;
2 inner?: {};
3
3
4 path: string;
4 path?: string;
5
5
6 configName: string;
6 configName?: string;
7
7
8 constructor(message: string, inner?: any) {
8 constructor(message: string, inner?: {}) {
9 super(message);
9 super(message);
10 this.inner = inner;
10 this.inner = inner;
11 }
11 }
12 }
12 }
@@ -1,348 +1,451
1 import {
1 import {
2 ServiceRegistration,
2 PartialServiceMap,
3 TypeRegistration,
4 FactoryRegistration,
5 ServiceMap,
6 isDescriptor,
7 isDependencyRegistration,
8 DependencyRegistration,
9 ValueRegistration,
10 ActivationType,
3 ActivationType,
11 isValueRegistration,
4 ContainerKeys,
12 isTypeRegistration,
5 TypeOfService,
13 isFactoryRegistration
6 ILifetime
14 } from "./interfaces";
7 } from "./interfaces";
15
8
16 import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get } from "../safe";
9 import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get, primitive } from "../safe";
17 import { AggregateDescriptor } from "./AggregateDescriptor";
10 import { AggregateDescriptor } from "./AggregateDescriptor";
18 import { ValueDescriptor } from "./ValueDescriptor";
11 import { ValueDescriptor } from "./ValueDescriptor";
19 import { Container } from "./Container";
12 import { Container } from "./Container";
20 import { ReferenceDescriptor } from "./ReferenceDescriptor";
13 import { ReferenceDescriptor } from "./ReferenceDescriptor";
21 import { TypeServiceDescriptor } from "./TypeServiceDescriptor";
14 import { TypeServiceDescriptor } from "./TypeServiceDescriptor";
22 import { FactoryServiceDescriptor } from "./FactoryServiceDescriptor";
15 import { FactoryServiceDescriptor } from "./FactoryServiceDescriptor";
23 import { TraceSource } from "../log/TraceSource";
16 import { TraceSource } from "../log/TraceSource";
24 import { ConfigError } from "./ConfigError";
17 import { ConfigError } from "./ConfigError";
25 import { Cancellation } from "../Cancellation";
18 import { Cancellation } from "../Cancellation";
26 import { makeResolver } from "./ResolverHelper";
19 import { makeResolver } from "./ResolverHelper";
27 import { ICancellation } from "../interfaces";
20 import { ICancellation } from "../interfaces";
21 import { isDescriptor } from "./traits";
22 import { LazyReferenceDescriptor } from "./LazyReferenceDescriptor";
23 import { LifetimeManager } from "./LifetimeManager";
24
25 export interface RegistrationScope<S extends object> {
26
27 /** сервисы, которые регистрируются в контексте активации и таким образом
28 * могут переопределять ранее зарегистрированные сервисы. за это свойство
29 * нужно платить, кроме того порядок активации будет влиять на результат
30 * разрешения зависимостей.
31 */
32 services?: RegistrationMap<S>;
33 }
34
35 /**
36 * Базовый интерфейс конфигурации сервисов
37 */
38 export interface ServiceRegistration<T, S extends object> extends RegistrationScope<S> {
39
40 activation?: ActivationType;
41
42 params?: any;
43
44 /** Специальный идентификатор используется при активации singleton, если
45 * не указан для TypeRegistration вычисляется как oid($type)
46 */
47 typeId?: string;
48
49 inject?: object | object[];
50
51 cleanup?: ((instance: T) => void) | string;
52 }
53
54 export interface TypeRegistration<C extends new (...args: any[]) => any, S extends object> extends ServiceRegistration<InstanceType<C>, S> {
55 $type: string | C;
56 params?: Registration<ConstructorParameters<C>, S>;
57 }
58
59 export interface StrictTypeRegistration<C extends new (...args: any[]) => any, S extends object> extends ServiceRegistration<InstanceType<C>, S> {
60 $type: C;
61 params?: Registration<ConstructorParameters<C>, S>;
62 }
63
64 export interface FactoryRegistration<F extends (...args: any[]) => any, S extends object> extends ServiceRegistration<ReturnType<F>, S> {
65 $factory: string | F;
66 }
67
68 export interface ValueRegistration<T> {
69 $value: T;
70 parse?: boolean;
71 }
72
73 export interface DependencyRegistration<S extends object, K extends ContainerKeys<S> = ContainerKeys<S>> extends RegistrationScope<S> {
74 $dependency: K;
75 lazy?: boolean;
76 optional?: boolean;
77 default?: TypeOfService<S, K>;
78 }
79
80 export interface LazyDependencyRegistration<S extends object, K extends ContainerKeys<S> = ContainerKeys<S>> extends DependencyRegistration<S, K> {
81 lazy: true;
82 }
83
84 export type Registration<T, S extends object> = T extends primitive ? T :
85 (
86 T |
87 { [k in keyof T]: Registration<T[k], S> } |
88 TypeRegistration<new (...args: any[]) => T, S> |
89 FactoryRegistration<(...args: any[]) => T, S> |
90 ValueRegistration<any> |
91 DependencyRegistration<S, keyof S>
92 );
93
94 export type RegistrationMap<S extends object> = {
95 [k in keyof S]?: Registration<S[k], S>;
96 };
97
98 const _activationTypes: { [k in ActivationType]: number; } = {
99 singleton: 1,
100 container: 2,
101 hierarchy: 3,
102 context: 4,
103 call: 5
104 };
105
106 export function isTypeRegistration(x: any): x is TypeRegistration<new () => any, any> {
107 return (!isPrimitive(x)) && ("$type" in x);
108 }
109
110 export function isFactoryRegistration(x: any): x is FactoryRegistration<() => any, any> {
111 return (!isPrimitive(x)) && ("$factory" in x);
112 }
113
114 export function isValueRegistration(x: any): x is ValueRegistration<any> {
115 return (!isPrimitive(x)) && ("$value" in x);
116 }
117
118 export function isDependencyRegistration<S extends object>(x: any): x is DependencyRegistration<S, keyof S> {
119 return (!isPrimitive(x)) && ("$dependency" in x);
120 }
121
122 export function isActivationType(x: string): x is ActivationType {
123 return typeof x === "string" && x in _activationTypes;
124 }
28
125
29 const trace = TraceSource.get("@implab/core/di/Configuration");
126 const trace = TraceSource.get("@implab/core/di/Configuration");
30
127 async function mapAll(data: any[], map?: (v: any, k: number) => any): Promise<any[]>;
31 async function mapAll(data: object | any[], map?: (v, k) => any): Promise<any> {
128 async function mapAll(data: any, map?: (v: any, k: string) => any): Promise<any>;
129 async function mapAll(data: any, map?: (v: any, k: any) => any): Promise<any> {
32 if (data instanceof Array) {
130 if (data instanceof Array) {
33 return Promise.all(map ? data.map(map) : data);
131 return Promise.all(map ? data.map(map) : data);
34 } else {
132 } else {
35 const keys = Object.keys(data);
133 const keys = Object.keys(data);
36
134
37 const o: any = {};
135 const o: any = {};
38
136
39 await Promise.all(keys.map(async k => {
137 await Promise.all(keys.map(async k => {
40 const v = map ? map(data[k], k) : data[k];
138 const v = map ? map(data[k], k) : data[k];
41 o[k] = isPromise(v) ? await v : v;
139 o[k] = isPromise(v) ? await v : v;
42 }));
140 }));
43
141
44 return o;
142 return o;
45 }
143 }
46 }
144 }
47
145
48 export type ModuleResolver = (moduleName: string, ct?: ICancellation) => any;
146 export type ModuleResolver = (moduleName: string, ct?: ICancellation) => any;
49
147
50 type _key = string | number;
148 export class Configuration<S extends object> {
51
52 export class Configuration {
53
149
54 _hasInnerDescriptors = false;
150 _hasInnerDescriptors = false;
55
151
56 _container: Container;
152 readonly _container: Container<S>;
57
153
58 _path: Array<_key>;
154 _path: Array<string>;
59
155
60 _configName: string;
156 _configName: string | undefined;
61
157
62 _require: ModuleResolver;
158 _require: ModuleResolver | undefined;
63
159
64 constructor(container: Container) {
160 constructor(container: Container<S>) {
65 argumentNotNull(container, "container");
161 argumentNotNull(container, "container");
66 this._container = container;
162 this._container = container;
67 this._path = [];
163 this._path = [];
68 }
164 }
69
165
70 async loadConfiguration(moduleName: string, contextRequire?: any, ct = Cancellation.none) {
166 async loadConfiguration(moduleName: string, contextRequire?: any, ct = Cancellation.none) {
71 argumentNotEmptyString(moduleName, "moduleName");
167 argumentNotEmptyString(moduleName, "moduleName");
72
168
73 trace.log(
169 trace.log(
74 "loadConfiguration moduleName={0}, contextRequire={1}",
170 "loadConfiguration moduleName={0}, contextRequire={1}",
75 moduleName,
171 moduleName,
76 contextRequire ? typeof (contextRequire) : "<nil>"
172 contextRequire ? typeof (contextRequire) : "<nil>"
77 );
173 );
78
174
79 this._configName = moduleName;
175 this._configName = moduleName;
80
176
81 const r = await makeResolver(null, contextRequire);
177 const r = await makeResolver(undefined, contextRequire);
82
178
83 const config = await r(moduleName, ct);
179 const config = await r(moduleName, ct);
84
180
85 await this._applyConfiguration(
181 await this._applyConfiguration(
86 config,
182 config,
87 await makeResolver(moduleName, contextRequire),
183 await makeResolver(moduleName, contextRequire),
88 ct
184 ct
89 );
185 );
90 }
186 }
91
187
92 async applyConfiguration(data: object, contextRequire?: any, ct = Cancellation.none) {
188 async applyConfiguration(data: RegistrationMap<S>, opts: { contextRequire?: any; baseModule?: string }, ct = Cancellation.none) {
93 argumentNotNull(data, "data");
189 argumentNotNull(data, "data");
190 const _opts = opts || {};
94
191
95 await this._applyConfiguration(data, await makeResolver(void (0), contextRequire), ct);
192 await this._applyConfiguration(data, await makeResolver(_opts.baseModule, _opts.contextRequire), ct);
96 }
193 }
97
194
98 async _applyConfiguration(data: object, resolver?: ModuleResolver, ct = Cancellation.none) {
195 async _applyConfiguration(data: RegistrationMap<S>, resolver?: ModuleResolver, ct = Cancellation.none) {
99 trace.log("applyConfiguration");
196 trace.log("applyConfiguration");
100
197
101 this._configName = "$";
198 this._configName = "$";
102
199
103 if (resolver)
200 if (resolver)
104 this._require = resolver;
201 this._require = resolver;
105
202
106 let services: ServiceMap;
203 let services: PartialServiceMap<S>;
107
204
108 try {
205 try {
109 services = await this._visitRegistrations(data, "$");
206 services = await this._visitRegistrations(data, "$");
110 } catch (e) {
207 } catch (e) {
111 throw this._makeError(e);
208 throw this._makeError(e);
112 }
209 }
113
210
114 this._container.register(services);
211 this._container.register(services);
115 }
212 }
116
213
117 _makeError(inner) {
214 _makeError(inner: any) {
118 const e = new ConfigError("Failed to load configuration", inner);
215 const e = new ConfigError("Failed to load configuration", inner);
119 e.configName = this._configName;
216 e.configName = this._configName || "<inline>";
120 e.path = this._makePath();
217 e.path = this._makePath();
121 return e;
218 return e;
122 }
219 }
123
220
124 _makePath() {
221 _makePath() {
125 return this._path
222 return this._path
126 .reduce(
223 .reduce(
127 (prev, cur) => typeof cur === "number" ?
224 (prev, cur) => typeof cur === "number" ?
128 `${prev}[${cur}]` :
225 `${prev}[${cur}]` :
129 `${prev}.${cur}`
226 `${prev}.${cur}`
130 )
227 )
131 .toString();
228 .toString();
132 }
229 }
133
230
134 async _resolveType(moduleName: string, localName: string) {
231 async _resolveType(moduleName: string, localName: string) {
135 trace.log("resolveType moduleName={0}, localName={1}", moduleName, localName);
232 trace.log("resolveType moduleName={0}, localName={1}", moduleName, localName);
136 try {
233 try {
137 const m = await this._loadModule(moduleName);
234 const m = await this._loadModule(moduleName);
138 return localName ? get(localName, m) : m;
235 if (localName) {
236 return get(localName, m);
237 } else {
238 if (m instanceof Function)
239 return m;
240 if ("default" in m)
241 return m.default;
242 return m;
243 }
139 } catch (e) {
244 } catch (e) {
140 trace.error("Failed to resolve type moduleName={0}, localName={1}", moduleName, localName);
245 trace.error("Failed to resolve type moduleName={0}, localName={1}", moduleName, localName);
141 throw e;
246 throw e;
142 }
247 }
143 }
248 }
144
249
145 _loadModule(moduleName: string) {
250 _loadModule(moduleName: string) {
146 trace.debug("loadModule {0}", moduleName);
251 trace.debug("loadModule {0}", moduleName);
252 if (!this._require)
253 throw new Error("Module loader isn't specified");
147
254
148 return this._require(moduleName);
255 return this._require(moduleName);
149 }
256 }
150
257
151 async _visitRegistrations(data, name: _key) {
258 async _visitRegistrations(data: RegistrationMap<S>, name: string) {
152 this._enter(name);
259 this._enter(name);
153
260
154 if (data.constructor &&
261 if (data.constructor &&
155 data.constructor.prototype !== Object.prototype)
262 data.constructor.prototype !== Object.prototype)
156 throw new Error("Configuration must be a simple object");
263 throw new Error("Configuration must be a simple object");
157
264
158 const o: ServiceMap = {};
159 const keys = Object.keys(data);
160
161 const services = await mapAll(data, async (v, k) => {
265 const services = await mapAll(data, async (v, k) => {
162 const d = await this._visit(v, k);
266 const d = await this._visit(v, k.toString());
163 return isDescriptor(d) ? d : new AggregateDescriptor(d);
267 return isDescriptor(d) ? d : new AggregateDescriptor(d);
164 }) as ServiceMap;
268 }) as PartialServiceMap<S>;
165
269
166 this._leave();
270 this._leave();
167
271
168 return services;
272 return services;
169 }
273 }
170
274
171 _enter(name: _key) {
275 _enter(name: string) {
172 this._path.push(name);
276 this._path.push(name.toString());
173 trace.debug(">{0}", name);
277 trace.debug(">{0}", name);
174 }
278 }
175
279
176 _leave() {
280 _leave() {
177 const name = this._path.pop();
281 const name = this._path.pop();
178 trace.debug("<{0}", name);
282 trace.debug("<{0}", name);
179 }
283 }
180
284
181 async _visit(data, name: string) {
285 _visit(data: any, name: string): Promise<any> {
182 if (isPrimitive(data) || isDescriptor(data))
286 if (isPrimitive(data))
183 return data;
287 return Promise.resolve(new ValueDescriptor(data));
288 if (isDescriptor(data))
289 return Promise.resolve(data);
184
290
185 if (isDependencyRegistration(data)) {
291 if (isDependencyRegistration<S>(data)) {
186 return this._visitDependencyRegistration(data, name);
292 return this._visitDependencyRegistration(data, name);
187 } else if (isValueRegistration(data)) {
293 } else if (isValueRegistration(data)) {
188 return this._visitValueRegistration(data, name);
294 return this._visitValueRegistration(data, name);
189 } else if (isTypeRegistration(data)) {
295 } else if (isTypeRegistration(data)) {
190 return this._visitTypeRegistration(data, name);
296 return this._visitTypeRegistration(data, name);
191 } else if (isFactoryRegistration(data)) {
297 } else if (isFactoryRegistration(data)) {
192 return this._visitFactoryRegistration(data, name);
298 return this._visitFactoryRegistration(data, name);
193 } else if (data instanceof Array) {
299 } else if (data instanceof Array) {
194 return this._visitArray(data, name);
300 return this._visitArray(data, name);
195 }
301 }
196
302
197 return this._visitObject(data, name);
303 return this._visitObject(data, name);
198 }
304 }
199
305
200 async _visitObject(data: object, name: _key) {
306 async _visitObject(data: any, name: string) {
201 if (data.constructor &&
307 if (data.constructor &&
202 data.constructor.prototype !== Object.prototype)
308 data.constructor.prototype !== Object.prototype)
203 return new ValueDescriptor(data);
309 return new ValueDescriptor(data);
204
310
205 this._enter(name);
311 this._enter(name);
206
312
207 const v = await mapAll(data, delegate(this, "_visit"));
313 const v = await mapAll(data, delegate(this, "_visit"));
208
314
209 // TODO: handle inline descriptors properly
315 // TODO: handle inline descriptors properly
210 // const ex = {
316 // const ex = {
211 // activate(ctx) {
317 // activate(ctx) {
212 // const value = ctx.activate(this.prop, "prop");
318 // const value = ctx.activate(this.prop, "prop");
213 // // some code
319 // // some code
214 // },
320 // },
215 // // will be turned to ReferenceDescriptor
321 // // will be turned to ReferenceDescriptor
216 // prop: { $dependency: "depName" }
322 // prop: { $dependency: "depName" }
217 // };
323 // };
218
324
219 this._leave();
325 this._leave();
220 return v;
326 return v;
221 }
327 }
222
328
223 async _visitArray(data: any[], name: _key) {
329 async _visitArray(data: any[], name: string) {
224 if (data.constructor &&
330 if (data.constructor &&
225 data.constructor.prototype !== Array.prototype)
331 data.constructor.prototype !== Array.prototype)
226 return new ValueDescriptor(data);
332 return new ValueDescriptor(data);
227
333
228 this._enter(name);
334 this._enter(name);
229
335
230 const v = await mapAll(data, delegate(this, "_visit"));
336 const v = await mapAll(data, delegate(this, "_visit"));
231 this._leave();
337 this._leave();
232
338
233 return v;
339 return v;
234 }
340 }
235
341
236 _makeServiceParams(data: ServiceRegistration) {
342 _makeServiceParams(data: ServiceRegistration<any, S>) {
237 const opts: any = {
343 const opts: any = {
238 owner: this._container
239 };
344 };
240 if (data.services)
345 if (data.services)
241 opts.services = this._visitRegistrations(data.services, "services");
346 opts.services = this._visitRegistrations(data.services, "services");
242
347
243 if (data.inject) {
348 if (data.inject) {
244 this._enter("inject");
349 this._enter("inject");
245 opts.inject = mapAll(
350 opts.inject = mapAll(
246 data.inject instanceof Array ?
351 data.inject instanceof Array ?
247 data.inject :
352 data.inject :
248 [data.inject],
353 [data.inject],
249 delegate(this, "_visitObject")
354 delegate(this, "_visitObject")
250 );
355 );
251 this._leave();
356 this._leave();
252 }
357 }
253
358
254 if ("params" in data)
359 if ("params" in data)
255 opts.params = data.params instanceof Array ?
360 opts.params = data.params instanceof Array ?
256 this._visitArray(data.params, "params") :
361 this._visitArray(data.params, "params") :
257 this._visit(data.params, "params");
362 this._visit(data.params, "params");
258
363
259 if (data.activation) {
364 if (data.activation) {
260 if (typeof (data.activation) === "string") {
365 opts.activation = this._getLifetimeManager(data.activation, data.typeId);
261 switch (data.activation.toLowerCase()) {
262 case "singleton":
263 opts.activation = ActivationType.Singleton;
264 break;
265 case "container":
266 opts.activation = ActivationType.Container;
267 break;
268 case "hierarchy":
269 opts.activation = ActivationType.Hierarchy;
270 break;
271 case "context":
272 opts.activation = ActivationType.Context;
273 break;
274 case "call":
275 opts.activation = ActivationType.Call;
276 break;
277 default:
278 throw new Error("Unknown activation type: " +
279 data.activation);
280 }
281 } else {
282 opts.activation = Number(data.activation);
283 }
284 }
366 }
285
367
286 if (data.cleanup)
368 if (data.cleanup)
287 opts.cleanup = data.cleanup;
369 opts.cleanup = data.cleanup;
288
370
289 return opts;
371 return opts;
290 }
372 }
291
373
292 async _visitValueRegistration(data: ValueRegistration, name: _key) {
374 async _visitValueRegistration<T>(data: ValueRegistration<T>, name: string) {
293 this._enter(name);
375 this._enter(name);
294 const d = data.parse ? new AggregateDescriptor(data.$value) : new ValueDescriptor(data.$value);
376 const d = data.parse ? new AggregateDescriptor(data.$value) : new ValueDescriptor(data.$value);
295 this._leave();
377 this._leave();
296 return d;
378 return d;
297 }
379 }
298
380
299 async _visitDependencyRegistration(data: DependencyRegistration, name: _key) {
381 async _visitDependencyRegistration<K extends keyof S>(data: DependencyRegistration<S, K>, name: string) {
300 argumentNotEmptyString(data && data.$dependency, "data.$dependency");
382 argumentNotEmptyString(data && data.$dependency, "data.$dependency");
301 this._enter(name);
383 this._enter(name);
302 const d = new ReferenceDescriptor({
384 const options = {
303 name: data.$dependency,
385 name: data.$dependency,
304 lazy: data.lazy,
305 optional: data.optional,
386 optional: data.optional,
306 default: data.default,
387 default: data.default,
307 services: data.services && await this._visitRegistrations(data.services, "services")
388 services: data.services && await this._visitRegistrations(data.services, "services")
308 });
389 };
390 const d = data.lazy ? new LazyReferenceDescriptor<S, K>(options) : new ReferenceDescriptor<S, K>(options);
309 this._leave();
391 this._leave();
310 return d;
392 return d;
311 }
393 }
312
394
313 async _visitTypeRegistration(data: TypeRegistration, name: _key) {
395 async _visitTypeRegistration(data: TypeRegistration<new () => any, S>, name: string) {
314 argumentNotNull(data.$type, "data.$type");
396 argumentNotNull(data.$type, "data.$type");
315 this._enter(name);
397 this._enter(name);
316
398
317 const opts = this._makeServiceParams(data);
399 const opts = this._makeServiceParams(data);
318 if (data.$type instanceof Function) {
400 if (data.$type instanceof Function) {
319 opts.type = data.$type;
401 opts.type = data.$type;
320 } else {
402 } else {
321 const [moduleName, typeName] = data.$type.split(":", 2);
403 const [moduleName, typeName] = data.$type.split(":", 2);
322 opts.type = this._resolveType(moduleName, typeName);
404 opts.type = this._resolveType(moduleName, typeName).then(t => {
405 if (!(t instanceof Function))
406 throw Error("$type (" + data.$type + ") is not a constructable");
407 return t;
408 });
323 }
409 }
324
410
325 const d = new TypeServiceDescriptor(
411 const d = new TypeServiceDescriptor<S, any, any[]>(
326 await mapAll(opts)
412 await mapAll(opts)
327 );
413 );
328
414
329 this._leave();
415 this._leave();
330
416
331 return d;
417 return d;
332 }
418 }
333
419
334 async _visitFactoryRegistration(data: FactoryRegistration, name: _key) {
420 async _visitFactoryRegistration(data: FactoryRegistration<() => any, S>, name: string) {
335 argumentOfType(data.$factory, Function, "data.$factory");
421 argumentOfType(data.$factory, Function, "data.$factory");
336 this._enter(name);
422 this._enter(name);
337
423
338 const opts = this._makeServiceParams(data);
424 const opts = this._makeServiceParams(data);
339 opts.factory = data.$factory;
425 opts.factory = data.$factory;
340
426
341 const d = new FactoryServiceDescriptor(
427 const d = new FactoryServiceDescriptor<S, any, any[]>(
342 await mapAll(opts)
428 await mapAll(opts)
343 );
429 );
344
430
345 this._leave();
431 this._leave();
346 return d;
432 return d;
347 }
433 }
434
435 _getLifetimeManager(activation: ActivationType, typeId: string | undefined): ILifetime {
436 switch (activation) {
437 case "container":
438 return LifetimeManager.containerLifetime(this._container);
439 case "hierarchy":
440 return LifetimeManager.hierarchyLifetime();
441 case "context":
442 return LifetimeManager.contextLifetime();
443 case "singleton":
444 if (typeId === undefined)
445 throw Error("The singleton activation requires a typeId");
446 return LifetimeManager.singletonLifetime(typeId);
447 default:
448 return LifetimeManager.empty();
348 }
449 }
450 }
451 }
@@ -1,128 +1,149
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 } from "./interfaces";
4 import { ServiceMap, Descriptor, PartialServiceMap, ServiceLocator, ContainerServiceMap, ContainerKeys, TypeOfService } from "./interfaces";
5 import { TraceSource } from "../log/TraceSource";
5 import { TraceSource } from "../log/TraceSource";
6 import { Configuration } from "./Configuration";
6 import { Configuration, RegistrationMap } from "./Configuration";
7 import { Cancellation } from "../Cancellation";
7 import { Cancellation } from "../Cancellation";
8 import { MapOf, IDestroyable } from "../interfaces";
9 import { isDescriptor } from "./traits";
10 import { LifetimeManager } from "./LifetimeManager";
11 import { each } from "../safe";
12 import { FluentRegistrations } from "./fluent/interfaces";
13 import { FluentConfiguration } from "./fluent/FluentConfiguration";
8
14
9 const trace = TraceSource.get("@implab/core/di/ActivationContext");
15 const trace = TraceSource.get("@implab/core/di/ActivationContext");
10
16
11 export class Container {
17 export class Container<S extends object = any> implements ServiceLocator<S>, IDestroyable {
12 _services: ServiceMap;
18 readonly _services: ContainerServiceMap<S>;
13
19
14 _cache: object;
20 readonly _lifetimeManager: LifetimeManager;
21
22 readonly _cleanup: (() => void)[];
15
23
16 _cleanup: (() => void)[];
24 readonly _root: Container<S>;
17
25
18 _root: Container;
26 readonly _parent?: Container<S>;
19
27
20 _parent: Container;
28 _disposed: boolean;
21
29
22 constructor(parent?: Container) {
30 constructor(parent?: Container<S>) {
23 this._parent = parent;
31 this._parent = parent;
24 this._services = parent ? Object.create(parent._services) : {};
32 this._services = parent ? Object.create(parent._services) : {};
25 this._cache = {};
26 this._cleanup = [];
33 this._cleanup = [];
27 this._root = parent ? parent.getRootContainer() : this;
34 this._root = parent ? parent.getRootContainer() : this;
28 this._services.container = new ValueDescriptor(this);
35 this._services.container = new ValueDescriptor(this) as any;
36 this._disposed = false;
37 this._lifetimeManager = new LifetimeManager();
29 }
38 }
30
39
31 getRootContainer() {
40 getRootContainer() {
32 return this._root;
41 return this._root;
33 }
42 }
34
43
35 getParent() {
44 getParent() {
36 return this._parent;
45 return this._parent;
37 }
46 }
38
47
39 resolve(name: string, def?) {
48 getLifetimeManager() {
49 return this._lifetimeManager;
50 }
51
52 resolve<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>): TypeOfService<S, K> {
40 trace.debug("resolve {0}", name);
53 trace.debug("resolve {0}", name);
41 const d = this._services[name];
54 const d = this._services[name];
42 if (d === undefined) {
55 if (d === undefined) {
43 if (arguments.length > 1)
56 if (def !== undefined)
44 return def;
57 return def;
45 else
58 else
46 throw new Error("Service '" + name + "' isn't found");
59 throw new Error("Service '" + name + "' isn't found");
47 }
60 } else {
48
61
49 const context = new ActivationContext(this, this._services);
62 const context = new ActivationContext<S>(this, this._services, String(name), d);
50 try {
63 try {
51 return context.activate(d, name);
64 return d.activate(context);
52 } catch (error) {
65 } catch (error) {
53 throw new ActivationError(name, context.getStack(), error);
66 throw new ActivationError(name.toString(), context.getStack(), error);
67 }
54 }
68 }
55 }
69 }
56
70
57 /**
71 /**
58 * @deprecated use resolve() method
72 * @deprecated use resolve() method
59 */
73 */
60 getService() {
74 getService<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>) {
61 return this.resolve.apply(this, arguments);
75 return this.resolve(name, def);
62 }
76 }
63
77
64 register(nameOrCollection, service?) {
78 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>): this;
79 register(services: PartialServiceMap<S>): this;
80 register<K extends keyof S>(nameOrCollection: K | ServiceMap<S>, service?: Descriptor<S, S[K]>) {
65 if (arguments.length === 1) {
81 if (arguments.length === 1) {
66 const data = nameOrCollection;
82 const data = nameOrCollection as ServiceMap<S>;
67 for (const name in data)
83
68 this.register(name, data[name]);
84 each(data, (v, k) => this.register(k, v));
69 } else {
85 } else {
70 if (!isDescriptor(service))
86 if (!isDescriptor(service))
71 throw new Error("The service parameter must be a descriptor");
87 throw new Error("The service parameter must be a descriptor");
72
88
73 this._services[nameOrCollection] = service;
89 this._services[nameOrCollection as K] = service as any;
74 }
90 }
75 return this;
91 return this;
76 }
92 }
77
93
78 onDispose(callback) {
94 onDispose(callback: () => void) {
79 if (!(callback instanceof Function))
95 if (!(callback instanceof Function))
80 throw new Error("The callback must be a function");
96 throw new Error("The callback must be a function");
81 this._cleanup.push(callback);
97 this._cleanup.push(callback);
82 }
98 }
83
99
100 destroy() {
101 return this.dispose();
102 }
84 dispose() {
103 dispose() {
85 if (this._cleanup) {
104 if (this._disposed)
105 return;
106 this._disposed = true;
86 for (const f of this._cleanup)
107 for (const f of this._cleanup)
87 f();
108 f();
88 this._cleanup = null;
89 }
90 }
109 }
91
110
92 /**
111 /**
93 * @param{String|Object} config
112 * @param{String|Object} config
94 * The configuration of the contaier. Can be either a string or an object,
113 * The configuration of the container. Can be either a string or an object,
95 * if the configuration is an object it's treated as a collection of
114 * if the configuration is an object it's treated as a collection of
96 * services which will be registed in the contaier.
115 * services which will be registered in the container.
97 *
116 *
98 * @param{Function} opts.contextRequire
117 * @param{Function} opts.contextRequire
99 * The function which will be used to load a configuration or types for services.
118 * The function which will be used to load a configuration or types for services.
100 *
119 *
101 */
120 */
102 async configure(config: string | object, opts?: any, ct = Cancellation.none) {
121 async configure(config: string | RegistrationMap<S>, opts?: { contextRequire: any; baseModule?: string }, ct = Cancellation.none) {
103 const c = new Configuration(this);
122 const _opts = Object.create(opts || null);
104
123
105 if (typeof (config) === "string") {
124 if (typeof (config) === "string") {
106 return c.loadConfiguration(config, opts && opts.contextRequire, ct);
125 _opts.baseModule = config;
126
127 const module = await import(config);
128 if (module && module.default && typeof (module.default.apply) === "function")
129 return module.default.apply(this);
130 else
131 return this._applyLegacyConfig(module, _opts, ct);
107 } else {
132 } else {
108 return c.applyConfiguration(config, opts && opts.contextRequire, ct);
133 return this._applyLegacyConfig(config, _opts, ct);
109 }
134 }
110 }
135 }
111
136
112 createChildContainer() {
137 async _applyLegacyConfig(config: RegistrationMap<S>, opts: { contextRequire: any; baseModule?: string }, ct = Cancellation.none) {
113 return new Container(this);
138 return new Configuration<S>(this).applyConfiguration(config, opts);
114 }
115
116 has(id) {
117 return id in this._cache;
118 }
139 }
119
140
120 get(id) {
141 async fluent<K extends keyof S>(config: FluentRegistrations<K, S>, ct = Cancellation.none): Promise<this> {
121 return this._cache[id];
142 await new FluentConfiguration<S>().register(config).apply(this, ct);
143 return this;
122 }
144 }
123
145
124 store(id, value) {
146 createChildContainer<S2 extends object = S>(): Container<S & S2> {
125 return (this._cache[id] = value);
147 return new Container<S & S2>(this as any);
126 }
148 }
127
128 }
149 }
@@ -1,23 +1,18
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";
5
3
6 export interface FactoryServiceDescriptorParams extends ServiceDescriptorParams {
4 export interface FactoryServiceDescriptorParams<S extends object, T, P extends any[]> extends ServiceDescriptorParams<S, T, P> {
7 factory: Factory;
5 factory: (...args: P) => T;
8 }
6 }
9
7
10 export class FactoryServiceDescriptor extends ServiceDescriptor {
8 export class FactoryServiceDescriptor<S extends object, T, P extends any[]> extends ServiceDescriptor<S, T, P> {
11 constructor(opts: FactoryServiceDescriptorParams) {
9 constructor(opts: FactoryServiceDescriptorParams<S, T, P>) {
12 super(opts);
10 super(opts);
13
11
14 argumentNotNull(opts && opts.factory, "opts.factory");
12 argumentNotNull(opts && opts.factory, "opts.factory");
15
13
16 // bind to null
14 // bind to null
17 this._factory = (...args) => opts.factory.apply(null, args);
15 this._factory = (...args) => opts.factory.apply(null, args as any);
18
16
19 if (opts.activation === ActivationType.Singleton) {
20 this._cacheId = oid(opts.factory);
21 }
17 }
22 }
18 }
23 }
@@ -1,100 +1,88
1 import { isNull, argumentNotEmptyString, each } from "../safe";
1 import { argumentNotEmptyString, each } from "../safe";
2 import { ActivationContext } from "./ActivationContext";
2 import { ActivationContext } from "./ActivationContext";
3 import { ServiceMap, Descriptor } from "./interfaces";
3 import { Descriptor, PartialServiceMap, TypeOfService, ContainerKeys } from "./interfaces";
4 import { ActivationError } from "./ActivationError";
4
5 export interface ReferenceDescriptorParams<S extends object, K extends ContainerKeys<S>> {
6 /**
7 * The name of the descriptor
8 */
9 name: K;
5
10
6 export interface ReferenceDescriptorParams {
11 /**
7 name: string;
12 * The flag that indicates that the referenced service isn't required to exist.
8 lazy?: boolean;
13 * If the reference is optional and the referenced service doesn't exist,
14 * the undefined or a default value will be returned.
15 */
9 optional?: boolean;
16 optional?: boolean;
10 default?;
17
11 services?: ServiceMap;
18 /**
19 * a default value for the reference when the referenced service doesn't exist.
20 */
21 default?: TypeOfService<S, K>;
22
23 /**
24 * The service overrides
25 */
26 services?: PartialServiceMap<S>;
12 }
27 }
13
28
14 export class ReferenceDescriptor implements Descriptor {
29 export class ReferenceDescriptor<S extends object = any, K extends ContainerKeys<S> = ContainerKeys<S>>
15 _name: string;
30 implements Descriptor<S, TypeOfService<S, K>> {
16
31
17 _lazy = false;
32 _name: K;
18
33
19 _optional = false;
34 _optional = false;
20
35
21 _default: any;
36 _default: TypeOfService<S, K> | undefined;
22
37
23 _services: ServiceMap;
38 _services: PartialServiceMap<S>;
24
39
25 constructor(opts: ReferenceDescriptorParams) {
40 constructor(opts: ReferenceDescriptorParams<S, K>) {
26 argumentNotEmptyString(opts && opts.name, "opts.name");
41 argumentNotEmptyString(opts && opts.name, "opts.name");
27 this._name = opts.name;
42 this._name = opts.name;
28 this._lazy = !!opts.lazy;
29 this._optional = !!opts.optional;
43 this._optional = !!opts.optional;
30 this._default = opts.default;
44 this._default = opts.default;
31 this._services = opts.services;
32 }
33
45
34 activate(context: ActivationContext, name: string) {
46 this._services = (opts.services || {}) as PartialServiceMap<S>;
35 // добавляем сервисы
36 if (this._services) {
37 for (const p of Object.keys(this._services))
38 context.register(p, this._services[p]);
39 }
47 }
40
48
41 if (this._lazy) {
49 /** This method activates the referenced service if one exists
42 const saved = context.clone();
50 * @param context activation context which is used during current activation
43
51 */
44 return (cfg: ServiceMap) => {
52 activate(context: ActivationContext<S>): any {
45 // защищаем контекст на случай исключения в процессе
53 // добавляем сервисы
46 // активации
54 if (this._services) {
47 const ct = saved.clone();
55 each(this._services, (v, k) => context.register(k, v));
48 try {
49 if (cfg) {
50 for (const k in cfg)
51 ct.register(k, cfg[k]);
52 }
56 }
53
57
54 return this._optional ? ct.resolve(this._name, this._default) : ct
58 const res = this._optional ?
55 .resolve(this._name);
56 } catch (error) {
57 throw new ActivationError(this._name, ct.getStack(), error);
58 }
59 };
60 } else {
61 // добавляем сервисы
62 if (this._services) {
63 for (const p of Object.keys(this._services))
64 context.register(p, this._services[p]);
65 }
66
67 const v = this._optional ?
68 context.resolve(this._name, this._default) :
59 context.resolve(this._name, this._default) :
69 context.resolve(this._name);
60 context.resolve(this._name);
70
61
71 return v;
62 return res;
72 }
73 }
63 }
74
64
75 toString() {
65 toString() {
76 const opts = [];
66 const opts = [];
77 if (this._optional)
67 if (this._optional)
78 opts.push("optional");
68 opts.push("optional");
79 if (this._lazy)
80 opts.push("lazy");
81
69
82 const parts = [
70 const parts = [
83 "@ref "
71 "@ref "
84 ];
72 ];
85 if (opts.length) {
73 if (opts.length) {
86 parts.push("{");
74 parts.push("{");
87 parts.push(opts.join());
75 parts.push(opts.join());
88 parts.push("} ");
76 parts.push("} ");
89 }
77 }
90
78
91 parts.push(this._name);
79 parts.push(this._name.toString());
92
80
93 if (!isNull(this._default)) {
81 if (this._default !== undefined && this._default !== null) {
94 parts.push(" = ");
82 parts.push(" = ");
95 parts.push(this._default);
83 parts.push(String(this._default));
96 }
84 }
97
85
98 return parts.join("");
86 return parts.join("");
99 }
87 }
100 }
88 }
@@ -1,234 +1,154
1 import { ActivationContext } from "./ActivationContext";
1 import { ActivationContext } from "./ActivationContext";
2 import { Descriptor, ActivationType, ServiceMap, isDescriptor } from "./interfaces";
2 import { Descriptor, ServiceMap, PartialServiceMap, ILifetime } from "./interfaces";
3 import { Container } from "./Container";
3 import { isPrimitive, keys, isNull } from "../safe";
4 import { argumentNotNull, isPrimitive } from "../safe";
5 import { TraceSource } from "../log/TraceSource";
4 import { TraceSource } from "../log/TraceSource";
6
5 import { isDescriptor } from "./traits";
7 let cacheId = 0;
6 import { LifetimeManager } from "./LifetimeManager";
7 import { MatchingMemberKeys } from "../interfaces";
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 extends object, 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 makeCleanupCallback<T>(method: Cleaner<T>) {
23 if (typeof (method) === "string") {
24 if (typeof (method) === "function") {
24 return () => {
25 return (target: T) => {
25 target[method]();
26 method(target);
26 };
27 };
27 } else {
28 } else {
28 return () => {
29 return (target: T) => {
29 method(target);
30 const m = target[method] as any;
31 m.apply(target);
30 };
32 };
31 }
33 }
32 }
34 }
33
35
34 // TODO: make async
36 function _parse(value: any, context: ActivationContext<any>, path: string): any {
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) | MatchingMemberKeys<() => void, T>;
55 activation?: ActivationType;
56
56
57 owner: Container;
57 export type InjectionSpec<T> = {
58 [m in keyof T]?: any;
59 };
58
60
59 params?;
61 export interface ServiceDescriptorParams<S extends object, T, P extends any[]> {
62 lifetime?: ILifetime;
60
63
61 inject?: object[];
64 params?: P;
62
65
63 services?: ServiceMap;
66 inject?: InjectionSpec<T>[];
64
67
65 cleanup?: ((x) => void) | string;
68 services?: PartialServiceMap<S>;
69
70 cleanup?: Cleaner<T>;
66 }
71 }
67
72
68 export class ServiceDescriptor implements Descriptor {
73 export class ServiceDescriptor<S extends object, T, P extends any[]> implements Descriptor<S, T> {
69 _instance;
74 _services: ServiceMap<S>;
70
71 _hasInstance = false;
72
73 _activationType = ActivationType.Call;
74
75
75 _services: ServiceMap;
76 _params: P | undefined;
76
77 _params;
78
77
79 _inject: object[];
78 _inject: InjectionSpec<T>[];
80
79
81 _cleanup: ((x) => void) | string;
80 _cleanup: ((item: T) => void) | undefined;
82
81
83 _cacheId: any;
82 _lifetime = LifetimeManager.empty();
84
83
85 _owner: Container;
84 _objectLifetime: ILifetime | undefined;
86
85
87 constructor(opts: ServiceDescriptorParams) {
86 constructor(opts: ServiceDescriptorParams<S, T, P>) {
88 argumentNotNull(opts, "opts");
89 argumentNotNull(opts.owner, "owner");
90
87
91 this._owner = opts.owner;
88 if (opts.lifetime)
89 this._lifetime = opts.lifetime;
92
90
93 if ("activation" in opts)
91 if (!isNull(opts.params))
94 this._activationType = opts.activation;
95
96 if ("params" in opts)
97 this._params = opts.params;
92 this._params = opts.params;
98
93
99 if (opts.inject)
94 this._inject = opts.inject || [];
100 this._inject = opts.inject;
101
95
102 if (opts.services)
96 this._services = (opts.services || {}) as ServiceMap<S>;
103 this._services = opts.services;
104
97
105 if (opts.cleanup) {
98 if (opts.cleanup) {
106 if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function))
99 if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function))
107 throw new Error(
100 throw new Error(
108 "The cleanup parameter must be either a function or a function name");
101 "The cleanup parameter must be either a function or a function name");
109
102
110 this._cleanup = opts.cleanup;
103 this._cleanup = makeCleanupCallback(opts.cleanup);
111 }
104 }
112 }
105 }
113
106
114 activate(context: ActivationContext) {
107 activate(context: ActivationContext<S>) {
115 // if we have a local service records, register them first
108 const lifetime = this._lifetime;
116 let instance;
117
118 // ensure we have a cache id
119 if (!this._cacheId)
120 this._cacheId = ++cacheId;
121
122 switch (this._activationType) {
123 case ActivationType.Singleton: // SINGLETON
124 // if the value is cached return it
125 if (this._hasInstance)
126 return this._instance;
127
128 // singletons are bound to the root container
129 const container = context.container.getRootContainer();
130
131 if (container.has(this._cacheId)) {
132 instance = container.get(this._cacheId);
133 } else {
134 instance = this._create(context);
135 container.store(this._cacheId, instance);
136 if (this._cleanup)
137 container.onDispose(
138 makeClenupCallback(instance, this._cleanup));
139 }
140
141 this._hasInstance = true;
142 return (this._instance = instance);
143
144 case ActivationType.Container: // CONTAINER
145 // return a cached value
146
147 if (this._hasInstance)
148 return this._instance;
149
109
150 // create an instance
110 if (lifetime.has()) {
151 instance = this._create(context);
111 return lifetime.get();
152
112 } else {
153 // the instance is bound to the container
113 lifetime.initialize(context);
154 if (this._cleanup)
114 const instance = this._create(context);
155 this._owner.onDispose(
115 lifetime.store(instance, this._cleanup);
156 makeClenupCallback(instance, this._cleanup));
116 return instance;
157
158 // cache and return the instance
159 this._hasInstance = true;
160 return (this._instance = instance);
161 case ActivationType.Context: // CONTEXT
162 // return a cached value if one exists
163
164 if (context.has(this._cacheId))
165 return context.get(this._cacheId);
166 // context context activated instances are controlled by callers
167 return context.store(this._cacheId, this._create(context));
168 case ActivationType.Call: // CALL
169 // per-call created instances are controlled by callers
170 return this._create(context);
171 case ActivationType.Hierarchy: // HIERARCHY
172 // hierarchy activated instances are behave much like container activated
173 // except they are created and bound to the child container
174
175 // return a cached value
176 if (context.container.has(this._cacheId))
177 return context.container.get(this._cacheId);
178
179 instance = this._create(context);
180
181 if (this._cleanup)
182 context.container.onDispose(makeClenupCallback(
183 instance,
184 this._cleanup));
185
186 return context.container.store(this._cacheId, instance);
187 default:
188 throw new Error("Invalid activation type: " + this._activationType);
189 }
117 }
190 }
118 }
191
119
192 isInstanceCreated() {
120 _factory(...params: any[]): T {
193 return this._hasInstance;
194 }
195
196 getInstance() {
197 return this._instance;
198 }
199
200 _factory(...params: any[]): any {
201 throw Error("Not implemented");
121 throw Error("Not implemented");
202 }
122 }
203
123
204 _create(context: ActivationContext) {
124 _create(context: ActivationContext<S>) {
205 trace.debug(`constructing ${context._name}`);
125 trace.debug(`constructing ${context._name}`);
206
126
207 if (this._activationType !== ActivationType.Call &&
208 context.visit(this._cacheId) > 0)
209 throw new Error("Recursion detected");
210
211 if (this._services) {
127 if (this._services) {
212 for (const p in this._services)
128 keys(this._services).forEach(p => context.register(p, this._services[p]));
213 context.register(p, this._services[p]);
214 }
129 }
215
130
216 let instance;
131 let instance: T;
217
132
218 if (this._params === undefined) {
133 if (this._params === undefined) {
219 instance = this._factory();
134 instance = this._factory();
220 } else if (this._params instanceof Array) {
135 } else if (this._params instanceof Array) {
221 instance = this._factory.apply(this, _parse(this._params, context, "args"));
136 instance = this._factory.apply(this, _parse(this._params, context, "args"));
222 } else {
137 } else {
223 instance = this._factory(_parse(this._params, context, "args"));
138 instance = this._factory(_parse(this._params, context, "args"));
224 }
139 }
225
140
226 if (this._inject) {
141 if (this._inject) {
227 this._inject.forEach(spec => {
142 this._inject.forEach(spec => {
228 for (const m in spec)
143 for (const m in spec)
229 injectMethod(instance, m, context, spec[m]);
144 injectMethod(instance, m, context, spec[m]);
230 });
145 });
231 }
146 }
232 return instance;
147 return instance;
233 }
148 }
149
150 clone() {
151 return Object.create(this);
234 }
152 }
153
154 }
@@ -1,42 +1,42
1 import { ServiceDescriptor, ServiceDescriptorParams } from "./ServiceDescriptor";
1 import { ServiceDescriptor, ServiceDescriptorParams } from "./ServiceDescriptor";
2 import { Constructor, Factory } from "../interfaces";
3 import { argumentNotNull, isPrimitive } from "../safe";
2 import { argumentNotNull, isPrimitive } from "../safe";
4
3
5 export interface TypeServiceDescriptorParams extends ServiceDescriptorParams {
4 export interface TypeServiceDescriptorParams<S extends object, T extends object, P extends any[]> extends ServiceDescriptorParams<S, T, P> {
6 type: Constructor;
5 type: new (...args: any[]) => T;
7 }
6 }
8
7
9 export class TypeServiceDescriptor extends ServiceDescriptor {
8 export class TypeServiceDescriptor<S extends object, T extends object, P extends any[]> extends ServiceDescriptor<S, T, P> {
10 _type: Constructor;
9 _type: new (...args: any[]) => T;
11
10
12 constructor(opts: TypeServiceDescriptorParams) {
11 constructor(opts: TypeServiceDescriptorParams<S, T, P>) {
13 super(opts);
12 super(opts);
14 argumentNotNull(opts && opts.type, "opts.type");
13 argumentNotNull(opts && opts.type, "opts.type");
15
14
16 const ctor = this._type = opts.type;
15 const ctor = this._type = opts.type;
17
16
18 if (this._params) {
17 if (this._params) {
19 if (this._params.length) {
18 if (this._params.length) {
20 this._factory = (...args) => {
19 this._factory = (...args) => {
21 const t = Object.create(ctor.prototype);
20 /*const t = Object.create(ctor.prototype);
22 const inst = ctor.apply(t, args);
21 const inst = ctor.apply(t, args);
23 return isPrimitive(inst) ? t : inst;
22 return isPrimitive(inst) ? t : inst;*/
23 return new ctor(...args);
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 implements Descriptor {
3 export class ValueDescriptor<T> implements Descriptor<any, T> {
4 _value;
4 _value: T;
5
5
6 constructor(value) {
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,53
1 import { isNull, isPrimitive } from "../safe";
2 import { ActivationContext } from "./ActivationContext";
1 import { ActivationContext } from "./ActivationContext";
3 import { Constructor, Factory } from "../interfaces";
4
5 export interface Descriptor {
6 activate(context: ActivationContext, name?: string);
7 }
8
9 export function isDescriptor(x): x is Descriptor {
10 return (!isPrimitive(x)) &&
11 (x.activate instanceof Function);
12 }
13
2
14 export interface ServiceMap {
3 export interface Descriptor<S extends object = any, T = any> {
15 [s: string]: Descriptor;
4 activate(context: ActivationContext<S>): T;
16 }
17
18 export enum ActivationType {
19 Singleton = 1,
20 Container,
21 Hierarchy,
22 Context,
23 Call
24 }
25
26 export interface RegistrationWithServices {
27 services?: object;
28 }
5 }
29
6
30 export interface ServiceRegistration extends RegistrationWithServices {
7 export type ServiceMap<S extends object> = {
31
8 [k in keyof S]: Descriptor<S, S[k]>;
32 activation?: "singleton" | "container" | "hierarchy" | "context" | "call";
9 };
33
10
34 params?;
11 export type ContainerKeys<S extends object> = keyof S | keyof ContainerProvided<S>;
35
12
36 inject?: object | object[];
13 export type TypeOfService<S extends object, K> =
14 K extends keyof ContainerProvided<S> ? ContainerProvided<S>[K] :
15 K extends keyof S ? S[K] : never;
37
16
38 cleanup?: (instance) => void | string;
17 export type ContainerServiceMap<S extends object> = {
39 }
18 [K in ContainerKeys<S>]: Descriptor<S, TypeOfService<S, K>>;
19 };
40
20
41 export interface TypeRegistration extends ServiceRegistration {
21 export type PartialServiceMap<S extends object> = {
42 $type: string | Constructor;
22 [k in keyof S]?: Descriptor<S, S[k]>;
43 }
23 };
44
24
45 export interface FactoryRegistration extends ServiceRegistration {
25 export interface ServiceLocator<S extends object> {
46 $factory: string | Factory;
26 resolve<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>): TypeOfService<S, K>;
47 }
27 }
48
28
49 export interface ValueRegistration {
29 export interface ContainerProvided<S extends object> {
50 $value;
30 container: ServiceLocator<S>;
51 parse?: boolean;
52 }
53
54 export interface DependencyRegistration extends RegistrationWithServices {
55 $dependency: string;
56 lazy?: boolean;
57 optional?: boolean;
58 default?;
59 }
31 }
60
32
61 export function isTypeRegistration(x): x is TypeRegistration {
33 export type ContainerRegistered<S extends object> = /*{
62 return (!isPrimitive(x)) && ("$type" in x);
34 [K in Exclude<keyof S, keyof ContainerProvided<S>>]: S[K];
63 }
35 };*/
36 Exclude<S, ContainerProvided<S>>;
64
37
65 export function isFactoryRegistration(x): x is FactoryRegistration {
38 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
66 return (!isPrimitive(x)) && ("$factory" in x);
67 }
68
39
69 export function isValueRegistration(x): x is ValueRegistration {
40 /**
70 return (!isPrimitive(x)) && ("$value" in x);
41 * Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет
71 }
42 * свой собственный объект `ILifetime`, который создается при первой активации
43 */
44 export interface ILifetime {
45 /** Проверяет, что уже создан экземпляр объекта */
46 has(): boolean;
72
47
73 export function isDependencyRegistration(x): x is DependencyRegistration {
48 get(): any;
74 return (!isPrimitive(x)) && ("$dependency" in x);
49
50 initialize(context: ActivationContext<any>): void;
51
52 store(item: any, cleanup?: (item: any) => void): void;
75 }
53 }
@@ -1,111 +1,126
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 PromiseOrValue<T> = T | PromiseLike<T>;
7
6 export type Factory<T = {}> = (...args: any[]) => T;
8 export type Factory<T = {}> = (...args: any[]) => T;
7
9
8 export type Predicate<T = any> = (x: T) => boolean;
10 export type Predicate<T = any> = (x: T) => boolean;
9
11
12 export type MatchingMemberKeys<T, From> = { [K in keyof From]: From[K] extends T ? K : never}[keyof From];
13
14 export type NotMatchingMemberKeys<T, From> = { [K in keyof From]: From[K] extends T ? never : K}[keyof From];
15
16 export type ExtractMembers<T, From> = Pick<From, MatchingMemberKeys<T, From>>;
17
18 export type ExcludeMembers<T, From> = Pick<From, NotMatchingMemberKeys<T, From>>;
19
10 export interface MapOf<T> {
20 export interface MapOf<T> {
11 [key: string]: T;
21 [key: string]: T;
12 }
22 }
13
23
14 export interface IDestroyable {
24 export interface IDestroyable {
15 destroy(): void;
25 destroy(): void;
16 }
26 }
17
27
18 export interface IRemovable {
28 export interface IRemovable {
19 remove(): void;
29 remove(): void;
20 }
30 }
21
31
22 export interface ICancellation {
32 export interface ICancellation {
23 throwIfRequested(): void;
33 throwIfRequested(): void;
24 isRequested(): boolean;
34 isRequested(): boolean;
25 isSupported(): boolean;
35 isSupported(): boolean;
26 register(cb: (e: any) => void): IDestroyable;
36 register(cb: (e: any) => void): IDestroyable;
27 }
37 }
28
38
29 /**
39 /**
30 * Интерфейс поддерживающий асинхронную активацию
40 * Интерфейс поддерживающий асинхронную активацию
31 */
41 */
32 export interface IActivatable {
42 export interface IActivatable {
33 /**
43 /**
34 * @returns Boolean indicates the current state
44 * @returns Boolean indicates the current state
35 */
45 */
36 isActive(): boolean;
46 isActive(): boolean;
37
47
38 /**
48 /**
39 * Starts the component activation
49 * Starts the component activation
40 * @param ct cancellation token for this operation
50 * @param ct cancellation token for this operation
41 */
51 */
42 activate(ct?: ICancellation): Promise<void>;
52 activate(ct?: ICancellation): Promise<void>;
43
53
44 /**
54 /**
45 * Starts the component deactivation
55 * Starts the component deactivation
46 * @param ct cancellation token for this operation
56 * @param ct cancellation token for this operation
47 */
57 */
48 deactivate(ct?: ICancellation): Promise<void>;
58 deactivate(ct?: ICancellation): Promise<void>;
49
59
50 /**
60 /**
51 * Sets the activation controller for this component
61 * Sets the activation controller for this component
52 * @param controller The activation controller
62 * @param controller The activation controller
53 *
63 *
54 * Activation controller checks whether this component
64 * Activation controller checks whether this component
55 * can be activated and manages the active state of the
65 * can be activated and manages the active state of the
56 * component
66 * component
57 */
67 */
58 setActivationController(controller: IActivationController);
68 setActivationController(controller: IActivationController): void;
69
70 /** Indicates whether this component has an activation controller */
71 hasActivationController(): boolean;
59
72
60 /**
73 /**
61 * Gets the current activation controller for this component
74 * Gets the current activation controller for this component
62 */
75 */
63 getActivationController(): IActivationController;
76 getActivationController(): IActivationController;
64 }
77 }
65
78
66 export interface IActivationController {
79 export interface IActivationController {
67 activating(component: IActivatable, ct?: ICancellation): Promise<void>;
80 activating(component: IActivatable, ct?: ICancellation): Promise<void>;
68
81
69 activated(component: IActivatable, ct?: ICancellation): Promise<void>;
82 activated(component: IActivatable, ct?: ICancellation): Promise<void>;
70
83
71 deactivating(component: IActivatable, ct?: ICancellation): Promise<void>;
84 deactivating(component: IActivatable, ct?: ICancellation): Promise<void>;
72
85
73 deactivated(component: IActivatable, ct?: ICancellation): Promise<void>;
86 deactivated(component: IActivatable, ct?: ICancellation): Promise<void>;
74
87
75 deactivate(ct?: ICancellation): Promise<void>;
88 deactivate(ct?: ICancellation): Promise<void>;
76
89
77 activate(component: IActivatable, ct?: ICancellation): Promise<void>;
90 activate(component: IActivatable, ct?: ICancellation): Promise<void>;
78
91
92 hasActive(): boolean;
93
79 getActive(): IActivatable;
94 getActive(): IActivatable;
80 }
95 }
81
96
82 export interface IAsyncComponent {
97 export interface IAsyncComponent {
83 getCompletion(): Promise<void>;
98 getCompletion(): Promise<void>;
84 }
99 }
85
100
86 export interface ICancellable {
101 export interface ICancellable {
87 cancel(reason?: any): void;
102 cancel(reason?: any): void;
88 }
103 }
89
104
90 export interface IObservable<T> {
105 export interface IObservable<T> {
91 on(next: (x: T) => void, error?: (e: any) => void, complete?: () => void): IDestroyable;
106 on(next: (x: T) => void, error?: (e: any) => void, complete?: () => void): IDestroyable;
92 next(ct?: ICancellation): Promise<T>;
107 next(ct?: ICancellation): Promise<T>;
93 }
108 }
94
109
95 export interface IObserver<T> {
110 export interface IObserver<T> {
96 next(event: T): void;
111 next(event: T): void;
97
112
98 error(e: any): void;
113 error(e: any): void;
99
114
100 complete(): void;
115 complete(): void;
101 }
116 }
102
117
103 export interface TextWriter {
118 export interface TextWriter {
104 write(obj: any): void;
119 write(obj: any): void;
105 write(format: string, ...args: any[]): void;
120 write(format: string, ...args: any[]): void;
106
121
107 writeLine(obj?: any): void;
122 writeLine(obj?: any): void;
108 writeLine(format: string, ...args: any[]): void;
123 writeLine(format: string, ...args: any[]): void;
109
124
110 writeValue(value: any, spec?: string): void;
125 writeValue(value: any, spec?: string): void;
111 }
126 }
@@ -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
36 if (next.args)
35 this._writer.writeLine(next.message, ...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,1 +1,1
1 export { ConsoleLogger as ConsoleWriter } from "./ConsoleLogger"; No newline at end of file
1 export { ConsoleLogger as ConsoleWriter } from "./ConsoleLogger";
@@ -1,452 +1,502
1 import { ICancellable, Constructor } from "./interfaces";
1 import { ICancellable, Constructor, IDestroyable, PromiseOrValue } 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: object): string {
9 export function oid(instance: null | undefined): undefined;
10 export function oid(instance: NonNullable<any>): string;
11 export function oid(instance: any): string | undefined {
10 if (isNull(instance))
12 if (isNull(instance))
11 return null;
13 return undefined;
12
14
13 if (_oid in instance)
15 if (_oid in instance)
14 return instance[_oid];
16 return instance[_oid];
15 else
17 else
16 return (instance[_oid] = "oid_" + (++_nextOid));
18 return (instance[_oid] = "oid_" + (++_nextOid));
17 }
19 }
18
20
21 export function keys<T>(arg: T): (Extract<keyof T, string>)[] {
22 return isObject(arg) && arg ? Object.keys(arg) as (Extract<keyof T, string>)[] : [];
23 }
24
25 export function isKeyof<T>(k: string, target: T): k is Extract<keyof T, string> {
26 return target && typeof target === "object" && k in target;
27 }
28
19 export function argumentNotNull(arg: any, name: string) {
29 export function argumentNotNull(arg: any, name: string) {
20 if (arg === null || arg === undefined)
30 if (arg === null || arg === undefined)
21 throw new Error("The argument " + name + " can't be null or undefined");
31 throw new Error("The argument " + name + " can't be null or undefined");
22 }
32 }
23
33
24 export function argumentNotEmptyString(arg: any, name: string) {
34 export function argumentNotEmptyString(arg: any, name: string) {
25 if (typeof (arg) !== "string" || !arg.length)
35 if (typeof (arg) !== "string" || !arg.length)
26 throw new Error("The argument '" + name + "' must be a not empty string");
36 throw new Error("The argument '" + name + "' must be a not empty string");
27 }
37 }
28
38
29 export function argumentNotEmptyArray(arg: any, name: string) {
39 export function argumentNotEmptyArray(arg: any, name: string) {
30 if (!(arg instanceof Array) || !arg.length)
40 if (!(arg instanceof Array) || !arg.length)
31 throw new Error("The argument '" + name + "' must be a not empty array");
41 throw new Error("The argument '" + name + "' must be a not empty array");
32 }
42 }
33
43
34 export function argumentOfType(arg: any, type: Constructor<{}>, name: string) {
44 export function argumentOfType(arg: any, type: Constructor<{}>, name: string) {
35 if (!(arg instanceof type))
45 if (!(arg instanceof type))
36 throw new Error("The argument '" + name + "' type doesn't match");
46 throw new Error("The argument '" + name + "' type doesn't match");
37 }
47 }
38
48
39 export function isNull(val: any) {
49 export function isObject(val: any): val is object {
50 return typeof val === "object";
51 }
52
53 export function isNull(val: any): val is null | undefined {
40 return (val === null || val === undefined);
54 return (val === null || val === undefined);
41 }
55 }
42
56
43 export function isPrimitive(val: any): val is string | number | boolean | undefined | null {
57 export type primitive = symbol | string | number | boolean | undefined | null;
58
59 export function isPrimitive(val: any): val is primitive {
44 return (val === null || val === undefined || typeof (val) === "string" ||
60 return (val === null || val === undefined || typeof (val) === "string" ||
45 typeof (val) === "number" || typeof (val) === "boolean");
61 typeof (val) === "number" || typeof (val) === "boolean");
46 }
62 }
47
63
48 export function isInteger(val: any): val is number {
64 export function isInteger(val: any): val is number {
49 return parseInt(val, 10) === val;
65 return parseInt(val, 10) === val;
50 }
66 }
51
67
52 export function isNumber(val: any): val is number {
68 export function isNumber(val: any): val is number {
53 return parseFloat(val) === val;
69 return parseFloat(val) === val;
54 }
70 }
55
71
56 export function isString(val: any): val is string {
72 export function isString(val: any): val is string {
57 return typeof (val) === "string" || val instanceof String;
73 return typeof (val) === "string" || val instanceof String;
58 }
74 }
59
75
60 export function isPromise(val: any): val is PromiseLike<any> {
76 export function isPromise<T = any>(val: any): val is PromiseLike<T> {
61 return val && typeof val.then === "function";
77 return val && typeof val.then === "function";
62 }
78 }
63
79
64 export function isCancellable(val: any): val is ICancellable {
80 export function isCancellable(val: any): val is ICancellable {
65 return val && typeof val.cancel === "function";
81 return val && typeof val.cancel === "function";
66 }
82 }
67
83
68 export function isNullOrEmptyString(val: any): val is string | null | undefined {
84 export function isNullOrEmptyString(val: any): val is ("" | null | undefined) {
69 if (val === null || val === undefined ||
85 return (val === null || val === undefined ||
70 ((typeof (val) === "string" || val instanceof String) && val.length === 0))
86 ((typeof (val) === "string" || val instanceof String) && val.length === 0));
71 return true;
72 }
87 }
73
88
74 export function isNotEmptyArray(arg: any): arg is Array<any> {
89 export function isNotEmptyArray<T = any>(arg: any): arg is T[] {
75 return (arg instanceof Array && arg.length > 0);
90 return (arg instanceof Array && arg.length > 0);
76 }
91 }
77
92
78 function _isStrictMode() {
93 function _isStrictMode(this: any) {
79 return !this;
94 return !this;
80 }
95 }
81
96
82 function _getNonStrictGlobal() {
97 function _getNonStrictGlobal(this: any) {
83 return this;
98 return this;
84 }
99 }
85
100
86 export function getGlobal() {
101 export function getGlobal() {
87 // in es3 we can't use indirect call to eval, since it will
102 // in es3 we can't use indirect call to eval, since it will
88 // be executed in the current call context.
103 // be executed in the current call context.
89 if (!_isStrictMode()) {
104 if (!_isStrictMode()) {
90 return _getNonStrictGlobal();
105 return _getNonStrictGlobal();
91 } else {
106 } else {
92 // tslint:disable-next-line:no-eval
107 // tslint:disable-next-line:no-eval
93 return eval.call(null, "this");
108 return eval.call(null, "this");
94 }
109 }
95 }
110 }
96
111
97 export function get(member: string, context?: object) {
112 export function get(member: string, context?: object) {
98 argumentNotEmptyString(member, "member");
113 argumentNotEmptyString(member, "member");
99 let that = context || getGlobal();
114 let that = context || getGlobal();
100 const parts = member.split(".");
115 const parts = member.split(".");
101 for (const m of parts) {
116 for (const m of parts) {
102 if (!m)
117 if (!m)
103 continue;
118 continue;
104 if (isNull(that = that[m]))
119 if (isNull(that = that[m]))
105 break;
120 break;
106 }
121 }
107 return that;
122 return that;
108 }
123 }
109
124
110 /**
125 /**
111 * Выполняет метод для каждого элемента массива, останавливается, когда
126 * Выполняет метод для каждого элемента массива, останавливается, когда
112 * либо достигнут конец массива, либо функция <c>cb</c> вернула
127 * либо достигнут конец массива, либо функция <c>cb</c> вернула
113 * значение.
128 * значение.
114 *
129 *
115 * @param {Array | Object} obj массив элементов для просмотра
130 * @param {Array | Object} obj массив элементов для просмотра
116 * @param {Function} cb функция, вызываемая для каждого элемента
131 * @param {Function} cb функция, вызываемая для каждого элемента
117 * @param {Object} thisArg значение, которое будет передано в качестве
132 * @param {Object} thisArg значение, которое будет передано в качестве
118 * <c>this</c> в <c>cb</c>.
133 * <c>this</c> в <c>cb</c>.
119 * @returns Результат вызова функции <c>cb</c>, либо <c>undefined</c>
134 * @returns {void}
120 * если достигнут конец массива.
121 */
135 */
122 export function each(obj, cb, thisArg?) {
136 export function each<T>(obj: T, cb: <X extends keyof T>(v: NonNullable<T[X]>, k: X) => void): void;
137 export function each<T>(array: T[], cb: (v: T, i: number) => void): void;
138 export function each(obj: any, cb: any, thisArg?: any): any;
139 export function each(obj: any, cb: any, thisArg?: any) {
123 argumentNotNull(cb, "cb");
140 argumentNotNull(cb, "cb");
124 if (obj instanceof Array) {
141 if (obj instanceof Array) {
142 let v: any;
125 for (let i = 0; i < obj.length; i++) {
143 for (let i = 0; i < obj.length; i++) {
126 const x = cb.call(thisArg, obj[i], i);
144 v = obj[i];
127 if (x !== undefined)
145 if (v !== undefined)
128 return x;
146 cb.call(thisArg, v, i);
129 }
147 }
130 } else {
148 } else {
131 const keys = Object.keys(obj);
149 Object.keys(obj).forEach(k => obj[k] !== undefined && cb.call(thisArg, obj[k], k));
132 for (const k of keys) {
133 const x = cb.call(thisArg, obj[k], k);
134 if (x !== undefined)
135 return x;
136 }
137 }
150 }
138 }
151 }
139
152
140 /** Copies property values from a source object to the destination and returns
153 /** Copies property values from a source object to the destination and returns
141 * the destination onject.
154 * the destination onject.
142 *
155 *
143 * @param dest The destination object into which properties from the source
156 * @param dest The destination object into which properties from the source
144 * object will be copied.
157 * object will be copied.
145 * @param source The source of values which will be copied to the destination
158 * @param source The source of values which will be copied to the destination
146 * object.
159 * object.
147 * @param template An optional parameter specifies which properties should be
160 * @param template An optional parameter specifies which properties should be
148 * copied from the source and how to map them to the destination. If the
161 * copied from the source and how to map them to the destination. If the
149 * template is an array it contains the list of property names to copy from the
162 * template is an array it contains the list of property names to copy from the
150 * source to the destination. In case of object the templates contains the map
163 * source to the destination. In case of object the templates contains the map
151 * where keys are property names in the source and the values are property
164 * where keys are property names in the source and the values are property
152 * names in the destination object. If the template isn't specified then the
165 * names in the destination object. If the template isn't specified then the
153 * own properties of the source are entirely copied to the destination.
166 * own properties of the source are entirely copied to the destination.
154 *
167 *
155 */
168 */
156 export function mixin<T extends object, S extends object>(dest: T, source: S, template?: string[] | object): T & S {
169 export function mixin<T extends object, S extends object>(dest: T, source: S, template?: keyof S[]): T & S;
157 argumentNotNull(dest, "to");
170 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;
158 const _res = dest as T & S;
171 export function mixin<T extends object, S extends object>(dest: T, source: S, template?: any): any {
172 argumentNotNull(dest, "dest");
173 const _res: any = dest as any;
159
174
160 if (isPrimitive(source))
175 if (isPrimitive(source))
161 return _res;
176 return _res;
162
177
163 if (template instanceof Array) {
178 if (template instanceof Array) {
164 for (const p of template) {
179 template.forEach(p => {
165 if (p in source)
180 if (isKeyof(p, source))
166 _res[p] = source[p];
181 _res[p] = source[p];
167 }
182 });
168 } else if (template) {
183 } else if (template) {
169 const keys = Object.keys(source);
184 keys(source).forEach(p => {
170 for (const p of keys) {
185 if (isKeyof(p, template))
171 if (p in template)
172 _res[template[p]] = source[p];
186 _res[template[p]] = source[p];
173 }
187 });
174 } else {
188 } else {
175 const keys = Object.keys(source);
189 keys(source).forEach(p => _res[p] = source[p]);
176 for (const p of keys)
177 _res[p] = source[p];
178 }
190 }
179
191
180 return _res;
192 return _res;
181 }
193 }
182
194
183 /** Wraps the specified function to emulate an asynchronous execution.
195 /** Wraps the specified function to emulate an asynchronous execution.
184 * @param{Object} thisArg [Optional] Object which will be passed as 'this' to the function.
196 * @param{Object} thisArg [Optional] Object which will be passed as 'this' to the function.
185 * @param{Function|String} fn [Required] Function wich will be wrapped.
197 * @param{Function|String} fn [Required] Function wich will be wrapped.
186 */
198 */
187 export function async(_fn: (...args: any[]) => any, thisArg): (...args: any[]) => PromiseLike<any> {
199 export function async<T, F extends (...args: any[]) => T | PromiseLike<T>>(
200 fn: F,
201 thisArg?: ThisParameterType<F>
202 ): (...args: Parameters<F>) => PromiseLike<T>;
203 export function async<T, M extends string, O extends { [m in M]?: (...args: any[]) => T | PromiseLike<T> }>(
204 fn: M,
205 thisArg: O
206 ): (...args: Parameters<NonNullable<O[M]>>) => PromiseLike<T>;
207 export function async(_fn: any, thisArg: any): (...args: any[]) => PromiseLike<any> {
188 let fn = _fn;
208 let fn = _fn;
189
209
190 if (arguments.length === 2 && !(fn instanceof Function))
210 if (arguments.length === 2 && !(fn instanceof Function))
191 fn = thisArg[fn];
211 fn = thisArg[fn];
192
212
193 if (fn == null)
213 if (fn == null)
194 throw new Error("The function must be specified");
214 throw new Error("The function must be specified");
195
215
196 function wrapresult(x, e?): PromiseLike<any> {
216 function wrapresult(x: any, e?: any): PromiseLike<any> {
197 if (e) {
217 if (e) {
198 return {
218 return {
199 then(cb, eb) {
219 then(cb, eb) {
200 try {
220 try {
201 return eb ? wrapresult(eb(e)) : this;
221 return eb ? wrapresult(eb(e)) : this;
202 } catch (e2) {
222 } catch (e2) {
203 return wrapresult(null, e2);
223 return wrapresult(null, e2);
204 }
224 }
205 }
225 }
206 };
226 };
207 } else {
227 } else {
208 if (x && x.then)
228 if (x && x.then)
209 return x;
229 return x;
210 return {
230 return {
211 then(cb) {
231 then(cb) {
212 try {
232 try {
213 return cb ? wrapresult(cb(x)) : this;
233 return cb ? wrapresult(cb(x)) : this;
214 } catch (e2) {
234 } catch (e2) {
215 return wrapresult(e2);
235 return wrapresult(e2);
216 }
236 }
217 }
237 }
218 };
238 };
219 }
239 }
220 }
240 }
221
241
222 return (...args) => {
242 return (...args) => {
223 try {
243 try {
224 return wrapresult(fn.apply(thisArg, args));
244 return wrapresult(fn.apply(thisArg, args));
225 } catch (e) {
245 } catch (e) {
226 return wrapresult(null, e);
246 return wrapresult(null, e);
227 }
247 }
228 };
248 };
229 }
249 }
230
250
231 type _AnyFn = (...args) => any;
251 export function delegate<T extends object, F extends (this: T, ...args: any[]) => any>(
232
252 target: T,
233 export function delegate<T, K extends keyof T>(target: T, _method: (K | _AnyFn)) {
253 method: F
234 let method;
254 ): OmitThisParameter<F>;
255 export function delegate<M extends string, T extends { [m in M]?: (...args: any[]) => any; }>(
256 target: T,
257 method: M
258 ): OmitThisParameter<T[M]>;
259 export function delegate(target: any, _method: any): (...args: any[]) => any {
260 let method: any;
235 if (!(_method instanceof Function)) {
261 if (!(_method instanceof Function)) {
236 argumentNotNull(target, "target");
262 argumentNotNull(target, "target");
237 method = target[_method];
263 method = target[_method];
238 if (!(method instanceof Function))
264 if (!(method instanceof Function))
239 throw new Error("'method' argument must be a Function or a method name");
265 throw new Error("'method' argument must be a Function or a method name");
240 } else {
266 } else {
241 method = _method;
267 method = _method;
242 }
268 }
243
269
244 return (...args) => {
270 return (...args) => {
245 return method.apply(target, args);
271 return method.apply(target, args);
246 };
272 };
247 }
273 }
248
274
249 export function delay(timeMs: number, ct = Cancellation.none) {
275 export function delay(timeMs: number, ct = Cancellation.none) {
250 ct.throwIfRequested();
276 ct.throwIfRequested();
251 return new Promise((resolve, reject) => {
277 return new Promise((resolve, reject) => {
252 const h = ct.register(e => {
278 const h = ct.register(e => {
253 clearTimeout(id);
279 clearTimeout(id);
254 reject(e);
280 reject(e);
255 // we don't nedd to unregister h, since ct is already disposed
281 // we don't nedd to unregister h, since ct is already disposed
256 });
282 });
257 const id = setTimeout(() => {
283 const id = setTimeout(() => {
258 h.destroy();
284 h.destroy();
259 resolve();
285 resolve();
260 }, timeMs);
286 }, timeMs);
261
287
262 });
288 });
263 }
289 }
264
290
291 /** Returns resolved promise, awaiting this method will cause the asynchronous
292 * completion of the rest of the code.
293 */
294 export function fork() {
295 return Promise.resolve();
296 }
297
298 /** Always throws Error, can be used as a stub for the methods which should be
299 * assigned later and are required to be not null.
300 */
301 export function notImplemented(): never {
302 throw new Error("Not implemeted");
303 }
265 /**
304 /**
266 * Iterates over the specified array of items and calls the callback `cb`, if
305 * Iterates over the specified array of items and calls the callback `cb`, if
267 * the result of the callback is a promise the next item from the array will be
306 * the result of the callback is a promise the next item from the array will be
268 * proceeded after the promise is resolved.
307 * proceeded after the promise is resolved.
269 *
308 *
270 */
309 */
271 export function pmap<T, T2>(
310 export function pmap<T, T2>(
272 items: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
311 items: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
273 cb: (item: T, i: number) => T2 | PromiseLike<T2>
312 cb: (item: T, i: number) => T2 | PromiseLike<T2>
274 ): T2[] | PromiseLike<T2[]> {
313 ): T2[] | PromiseLike<T2[]> {
275 argumentNotNull(cb, "cb");
314 argumentNotNull(cb, "cb");
276
315
277 if (isPromise(items)) {
316 if (isPromise(items)) {
278 return items.then(data => pmap(data, cb));
317 return items.then(data => pmap(data, cb));
279 } else {
318 } else {
280
319
281 if (isNull(items) || !items.length)
320 if (isNull(items) || !items.length)
282 return [];
321 return [];
283
322
284 let i = 0;
323 let i = 0;
285 const result = new Array<T2>();
324 const result = new Array<T2>();
286
325
287 const next = () => {
326 const next = (): any => {
288 while (i < items.length) {
327 while (i < items.length) {
289 const r = cb(items[i], i);
328 const r = cb(items[i], i);
290 const ri = i;
329 const ri = i;
291 i++;
330 i++;
292 if (isPromise(r)) {
331 if (isPromise(r)) {
293 return r.then(x => {
332 return r.then(x => {
294 result[ri] = x;
333 result[ri] = x;
295 return next();
334 return next();
296 });
335 });
297 } else {
336 } else {
298 result[ri] = r;
337 result[ri] = r;
299 }
338 }
300 }
339 }
301 return result;
340 return result;
302 };
341 };
303
342
304 return next();
343 return next();
305 }
344 }
306 }
345 }
307
346
308 export function pfor<T>(
347 export function pfor<T>(
309 items: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
348 items: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
310 cb: (item: T, i: number) => any
349 cb: (item: T, i: number) => any
311 ): void | PromiseLike<void> {
350 ): void | PromiseLike<void> {
312 argumentNotNull(cb, "cb");
351 argumentNotNull(cb, "cb");
313
352
314 if (isPromise(items)) {
353 if (isPromise(items)) {
315 return items.then(data => pfor(data, cb));
354 return items.then(data => pfor(data, cb));
316 } else {
355 } else {
317 if (isNull(items) || !items.length)
356 if (isNull(items) || !items.length)
318 return;
357 return;
319
358
320 let i = 0;
359 let i = 0;
321
360
322 const next = () => {
361 const next = (): any => {
323 while (i < items.length) {
362 while (i < items.length) {
324 const r = cb(items[i], i);
363 const r = cb(items[i], i);
325 i++;
364 i++;
326 if (isPromise(r))
365 if (isPromise(r))
327 return r.then(next);
366 return r.then(next);
328 }
367 }
329 };
368 };
330
369
331 return next();
370 return next();
332 }
371 }
333 }
372 }
334
373
335 export function first<T>(sequence: ArrayLike<T>): T;
374 export function first<T>(sequence: ArrayLike<T>): T;
336 export function first<T>(sequence: PromiseLike<ArrayLike<T>>): PromiseLike<T>;
375 export function first<T>(sequence: PromiseLike<ArrayLike<T>>): PromiseLike<T>;
337 export function first<T>(
376 export function first<T>(
338 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
377 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
339 cb: (x: T) => void,
378 cb?: (x: T) => void,
340 err?: (x: Error) => void
379 err?: (x: Error) => void
341 ): void;
380 ): void;
342 /**
381 /**
343 * Выбирает первый элемент из последовательности, или обещания, если в
382 * Выбирает первый элемент из последовательности, или обещания, если в
344 * качестве параметра используется обещание, оно должно вернуть массив.
383 * качестве параметра используется обещание, оно должно вернуть массив.
345 *
384 *
346 * @param {Function} cb обработчик результата, ему будет передан первый
385 * @param {Function} cb обработчик результата, ему будет передан первый
347 * элемент последовательности в случае успеха
386 * элемент последовательности в случае успеха
348 * @param {Function} err обработчик исключения, если массив пустой, либо
387 * @param {Function} err обработчик исключения, если массив пустой, либо
349 * не массив
388 * не массив
350 *
389 *
351 * @remarks Если не указаны ни cb ни err, тогда функция вернет либо
390 * @remarks Если не указаны ни cb ни err, тогда функция вернет либо
352 * обещание, либо первый элемент.
391 * обещание, либо первый элемент.
353 * @async
392 * @async
354 */
393 */
355 export function first<T>(
394 export function first<T>(
356 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
395 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
357 cb?: (x: T) => void,
396 cb?: (x: T) => void,
358 err?: (x: Error) => void
397 err?: (x: Error) => void
359 ) {
398 ) {
360 if (isPromise(sequence)) {
399 if (isPromise(sequence)) {
361 return sequence.then(res => first(res, cb, err));
400 return sequence.then(res => first(res, cb as any /* force to pass undefined cb */, err));
362 } else if (sequence && "length" in sequence) {
401 } else if (sequence && "length" in sequence) {
363 if (sequence.length === 0) {
402 if (sequence.length === 0) {
364 if (err)
403 if (err)
365 return err(new Error("The sequence is empty"));
404 return err(new Error("The sequence is empty"));
366 else
405 else
367 throw new Error("The sequence is empty");
406 throw new Error("The sequence is empty");
368 } else if (cb) {
407 } else if (cb) {
369 return cb(sequence[0]);
408 return cb(sequence[0]);
370 } else {
409 } else {
371 return sequence[0];
410 return sequence[0];
372 }
411 }
373 } else {
412 } else {
374 if (err)
413 if (err)
375 return err(new Error("The sequence is required"));
414 return err(new Error("The sequence is required"));
376 else
415 else
377 throw new Error("The sequence is required");
416 throw new Error("The sequence is required");
378 }
417 }
379 }
418 }
380
419
381 export function firstWhere<T>(
420 export function firstWhere<T>(
382 sequence: ArrayLike<T>,
421 sequence: ArrayLike<T>,
383 predicate: (x: T) => boolean
422 predicate: (x: T) => boolean
384 ): T;
423 ): T;
385 export function firstWhere<T>(
424 export function firstWhere<T>(
386 sequence: PromiseLike<ArrayLike<T>>,
425 sequence: PromiseLike<ArrayLike<T>>,
387 predicate: (x: T) => boolean
426 predicate: (x: T) => boolean
388 ): PromiseLike<T>;
427 ): PromiseLike<T>;
389 export function firstWhere<T>(
428 export function firstWhere<T>(
390 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
429 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
391 predicate: (x: T) => boolean,
430 predicate: (x: T) => boolean,
392 cb: (x: T) => void,
431 cb: (x: T) => void,
393 err?: (x: Error) => void
432 err?: (x: Error) => void
394 ): void;
433 ): void;
395
434
396 export function firstWhere<T>(
435 export function firstWhere<T>(
397 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
436 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
398 predicate?: (x: T) => boolean,
437 predicate?: (x: T) => boolean,
399 cb?: (x: T) => any,
438 cb?: (x: T) => any,
400 err?: (x: Error) => any
439 err?: (x: Error) => any
401 ) {
440 ) {
402 if (isPromise(sequence)) {
441 if (isPromise(sequence)) {
403 return sequence.then(res => firstWhere(res, predicate, cb, err));
442 return sequence.then(res => firstWhere(
443 res,
444 predicate as any /* force to pass undefined predicate */,
445 cb as any /* force to pass undefined cb */,
446 err)
447 );
404 } else if (sequence && "length" in sequence) {
448 } else if (sequence && "length" in sequence) {
405 if (sequence.length === 0) {
449 if (sequence.length === 0) {
406 if (err)
450 if (err)
407 err(new Error("The sequence is empty"));
451 err(new Error("The sequence is empty"));
408 else
452 else
409 throw new Error("The sequence is empty");
453 throw new Error("The sequence is empty");
410 } else {
454 } else {
411 if (!predicate) {
455 if (!predicate) {
412 return cb ? cb(sequence[0]) && void (0) : sequence[0];
456 return cb ? cb(sequence[0]) && void (0) : sequence[0];
413 } else {
457 } else {
414 for (let i = 0; i < sequence.length; i++) {
458 for (let i = 0; i < sequence.length; i++) {
415 const v = sequence[i];
459 const v = sequence[i];
416 if (predicate(v))
460 if (predicate(v))
417 return cb ? cb(v) : v;
461 return cb ? cb(v) : v;
418 }
462 }
419 if (err)
463 if (err)
420 err(new Error("The sequence doesn't contain matching items"));
464 err(new Error("The sequence doesn't contain matching items"));
421 else
465 else
422 throw new Error("The sequence doesn't contain matching items");
466 throw new Error("The sequence doesn't contain matching items");
423 }
467 }
424 }
468 }
425 } else {
469 } else {
426 if (err)
470 if (err)
427 err(new Error("The sequence is required"));
471 err(new Error("The sequence is required"));
428 else
472 else
429 throw new Error("The sequence is required");
473 throw new Error("The sequence is required");
430 }
474 }
431 }
475 }
432
476
477 export function isDestroyable(d: any): d is IDestroyable {
478 if (d && "destroy" in d && typeof (destroy) === "function")
479 return true;
480 return false;
481 }
482
433 export function destroy(d: any) {
483 export function destroy(d: any) {
434 if (d && "destroy" in d)
484 if (d && "destroy" in d)
435 d.destroy();
485 d.destroy();
436 }
486 }
437
487
438 /**
488 /**
439 * Used to mark that the async operation isn't awaited intentionally.
489 * Used to mark that the async operation isn't awaited intentionally.
440 * @param p The promise which represents the async operation.
490 * @param p The promise which represents the async operation.
441 */
491 */
442 export function nowait(p: Promise<any>) {
492 export function nowait(p: Promise<any>) {
443 }
493 }
444
494
445 /** represents already destroyed object.
495 /** represents already destroyed object.
446 */
496 */
447 export const destroyed = {
497 export const destroyed = {
448 /** Calling to this method doesn't affect anything, noop.
498 /** Calling to this method doesn't affect anything, noop.
449 */
499 */
450 destroy() {
500 destroy() {
451 }
501 }
452 };
502 };
@@ -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
10
11 _parts: Array<string | { name: string; format: string; }>;
11 _parts: Array<string | { name: string; format?: string; }>;
12
12
13 compile(pattern: string) {
13 constructor(scanner: FormatScanner) {
14 let compiledPattern = this._cache && this._cache[pattern];
14 this._scanner = scanner;
15 if (!compiledPattern) {
16 this._scanner = new FormatScanner(pattern);
17 this._parts = [];
15 this._parts = [];
16 }
18
17
18 compile() {
19 this.visitText();
19 this.visitText();
20 const parts = this._parts;
20 const parts = this._parts;
21
21
22 compiledPattern = (writer: TextWriter, args: any) => {
22 return (writer: TextWriter, args: any) => {
23 parts.forEach(x => {
23 parts.forEach(x => {
24 if (isPrimitive(x))
24 if (isPrimitive(x))
25 writer.writeValue(x);
25 writer.writeValue(x);
26 else
26 else
27 writer.writeValue(get(x.name, args), x.format);
27 writer.writeValue(get(x.name, args), x.format);
28 });
28 });
29 };
29 };
30 if (this._cache)
30 }
31
32 static compile(pattern: string) {
33 let compiledPattern = this._cache && this._cache[pattern];
34 if (!compiledPattern) {
35 const compiler = new this(new FormatScanner(pattern));
36
37 compiledPattern = compiler.compile();
38
31 this._cache[pattern] = compiledPattern;
39 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, 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,12 +1,43
1 import { Foo } from "./Foo";
1 import { Foo } from "./Foo";
2
2
3 /* export const service = annotate<Bar>();
4
5 @service.wire({
6 foo: dependency("foo"),
7 nested: {
8 lazy: dependency("foo", { lazy: true })
9 },
10 host: dependency("host")
11 }, "") */
3 export class Bar {
12 export class Bar {
4 name = "bar";
13 barName = "Twister";
14
15 _v: Foo | undefined;
5
16
6 foo: Foo;
17 constructor(
18 _opts: {
19 foo?: Foo;
20 nested?: {
21 lazy: () => Foo
22 },
23 host: string
24 },
25 s: string
26 ) {
7
27
8 constructor(_opts) {
9 if (_opts && _opts.foo)
28 if (_opts && _opts.foo)
10 this.foo = _opts.foo;
29 this._v = _opts.foo;
30 if (s)
31 this.barName = s;
32 }
33
34 setName(name: string) {
35
36 }
37
38 getFoo() {
39 if (this._v === undefined)
40 throw new Error("The foo isn't set");
41 return this._v;
11 }
42 }
12 }
43 }
@@ -1,3 +1,3
1 export class Foo {
1 export class Foo {
2 name = "foo";
2 fooName = "foo";
3 }
3 }
@@ -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,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,106
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
9
10 test("Container register/resolve tests", async t => {
10 test("Container register/resolve tests", async t => {
11 const container = new Container();
11 const container = new Container<{
12 "bla-bla": string;
13 "connection": string;
14 "dbParams": {
15 timeout: number;
16 connection: string;
17 }
18 }>();
12
19
13 const connection1 = "db://localhost";
20 const connection1 = "db://localhost";
14
21
15 t.throws(
22 t.throws(
16 () => container.register("bla-bla", "bla-bla"),
23 () => container.register("bla-bla", "bla-bla" as any),
17 "Do not allow to register anything other than descriptors"
24 "Do not allow to register anything other than descriptors"
18 );
25 );
19
26
20 t.doesNotThrow(
27 t.doesNotThrow(
21 () => container.register("connection", new ValueDescriptor(connection1)),
28 () => container.register("connection", new ValueDescriptor(connection1)),
22 "register ValueDescriptor"
29 "register ValueDescriptor"
23 );
30 );
24
31
25 t.equals(container.resolve("connection"), connection1, "resolve string value");
32 t.equals(container.resolve("connection"), connection1, "resolve string value");
26
33
27 t.doesNotThrow(
34 t.doesNotThrow(
28 () => container.register(
35 () => container.register(
29 "dbParams",
36 "dbParams",
30 new AggregateDescriptor({
37 new AggregateDescriptor({
31 timeout: 10,
38 timeout: 10,
32 connection: new ReferenceDescriptor({ name: "connection" })
39 connection: new ReferenceDescriptor({ name: "connection" })
33 })
40 })
34 ),
41 ),
35 "register AggregateDescriptor"
42 "register AggregateDescriptor"
36 );
43 );
37
44
38 const dbParams = container.resolve("dbParams");
45 const dbParams = container.resolve("dbParams");
39 t.equals(dbParams.connection, connection1, "should get string value 'dbParams.connection'");
46 t.equals(dbParams.connection, connection1, "should get string value 'dbParams.connection'");
40 });
47 });
41
48
42 test("Container configure/resolve tests", async t => {
49 test("Container configure/resolve tests", async t => {
43
50
44 const container = new Container();
51 const container = new Container<{
52 foo: Foo;
53 box: Bar;
54 bar: Bar;
55 db: any;
56 }>();
45
57
46 await container.configure({
58 await container.configure({
47 foo: {
59 foo: {
48 $type: Foo
60 $type: Foo
49 },
61 },
50
62
51 box: {
63 box: {
52 $type: Bar,
64 $type: Bar,
53 params: {
65 params: {
54 $dependency: "foo"
66 $dependency: "foo"
55 }
67 }
56 },
68 },
57
69
58 bar: {
70 bar: {
59 $type: Bar,
71 $type: Bar,
60 params: {
72 params: [{
61 db: {
73 db: {
62 provider: {
74 provider: {
63 $dependency: "db"
75 $dependency: "db"
64 }
76 }
65 }
77 }
66 }
78 }]
67 }
79 }
68 });
80 });
69 t.pass("should configure from js object");
81 t.pass("should configure from js object");
70
82
71 const f1 = container.resolve("foo");
83 const f1 = container.resolve("foo");
72
84
73 t.assert(!isNull(f1), "foo should be not null");
85 t.assert(!isNull(f1), "foo should be not null");
74
86
75 t.throws(() => container.resolve("bar"), "should not resolve dependency 'db'");
87 t.throws(() => container.resolve("bar"), "should not resolve dependency 'db'");
76
88
77 });
89 });
78
90
79 test("Load configuration from module", async t => {
91 test("Load configuration from module", async t => {
80 const container = new Container();
92 const container = new Container();
81
93
82 await container.configure("../mock/config1", { contextRequire: require });
94 await container.configure("../mock/config1", { contextRequire: require });
83 t.pass("The configuration should load");
95 t.pass("The configuration should load");
84
96
85 const f1 = container.resolve("foo");
97 const f1 = container.resolve("foo");
86
98
87 t.assert(!isNull(f1), "foo should be not null");
99 t.assert(!isNull(f1), "foo should be not null");
88
100
89 const b1 = container.resolve("bar") as Bar;
101 const b1 = container.resolve("bar") as Bar;
90
102
91 t.assert(!isNull(b1), "bar should not be null");
103 t.assert(!isNull(b1), "bar should not be null");
92 t.assert(!isNull(b1.foo), "bar.foo should not be null");
104 t.assert(!isNull(b1._v), "bar.foo should not be null");
105
93 });
106 });
@@ -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 }
@@ -1,14 +1,12
1 {
1 {
2 "extends": "../tsconfig",
2 "extends": "../tsconfig",
3 "compilerOptions": {
3 "compilerOptions": {
4 "rootDir": "ts",
5 "baseUrl": ".",
6 "rootDirs": [
4 "rootDirs": [
7 "ts",
5 "ts",
8 "../main/ts"
6 "../main/ts"
9 ]
7 ]
10 },
8 },
11 "include" : [
9 "include" : [
12 "ts/**/*.ts"
10 "ts/**/*.ts"
13 ]
11 ]
14 } No newline at end of file
12 }
@@ -1,10 +1,11
1 define([
1 define([
2 "./ActivatableTests",
2 "./ActivatableTests",
3 "./trace-test",
3 "./trace-test",
4 "./TraceSourceTests",
4 "./TraceSourceTests",
5 "./CancellationTests",
5 "./CancellationTests",
6 "./ObservableTests",
6 "./ObservableTests",
7 "./ContainerTests",
7 "./ContainerTests",
8 "./SafeTests",
8 "./SafeTests",
9 "./TextTests"
9 "./TextTests",
10 "./FluentContainerTests"
10 ]); No newline at end of file
11 ]);
@@ -1,7 +1,8
1 import "./ActivatableTests";
1 import "./ActivatableTests";
2 import "./TraceSourceTests";
2 import "./TraceSourceTests";
3 import "./CancellationTests";
3 import "./CancellationTests";
4 import "./ObservableTests";
4 import "./ObservableTests";
5 import "./ContainerTests";
5 import "./ContainerTests";
6 import "./SafeTests";
6 import "./SafeTests";
7 import "./TextTests";
7 import "./TextTests";
8 import "./FluentContainerTests";
@@ -1,9 +1,12
1 {
1 {
2 "compilerOptions": {
2 "compilerOptions": {
3 "moduleResolution": "node",
3 "moduleResolution": "node",
4 "experimentalDecorators": true,
4 "noEmitOnError": true,
5 "noEmitOnError": true,
5 "listFiles": true,
6 "listFiles": true,
7 "strict": true,
6 "types": [],
8 "types": [],
9 "target": "ES5",
7 "lib": ["es5", "es2015.promise", "es2015.symbol", "es2015.iterable", "dom", "scripthost"]
10 "lib": ["es5", "es2015.promise", "es2015.symbol", "es2015.iterable", "dom", "scripthost"]
8 }
11 }
9 } No newline at end of file
12 }
General Comments 0
You need to be logged in to leave comments. Login now