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