##// END OF EJS Templates
added provided and configure methods to the fluent container configuration, added applyConfig method to the container
cin -
r142:be7edf08a115 v1.4.0-rc3 default
parent child
Show More
@@ -0,0 +1,10
1 import { fluent } from "../di/traits";
2 import { Bar } from "./Bar";
3 import { ChildServices, Services } from "./services";
4
5 export default fluent<ChildServices>()
6 .provided<keyof Services>()
7 .configure({
8 bar: it => it
9 .factory($ => new Bar({ foo: $("foo"), host: $("host") }, "bar"))
10 });
@@ -1,149 +1,172
1 import { ActivationContext } from "./ActivationContext";
1 import { ActivationContext } from "./ActivationContext";
2 import { ValueDescriptor } from "./ValueDescriptor";
2 import { ValueDescriptor } from "./ValueDescriptor";
3 import { ActivationError } from "./ActivationError";
3 import { ActivationError } from "./ActivationError";
4 import { ServiceMap, Descriptor, PartialServiceMap, ServiceLocator, ContainerServiceMap, ContainerKeys, TypeOfService } from "./interfaces";
4 import { ServiceMap, Descriptor, PartialServiceMap, ServiceLocator, ContainerServiceMap, ContainerKeys, TypeOfService } from "./interfaces";
5 import { TraceSource } from "../log/TraceSource";
5 import { TraceSource } from "../log/TraceSource";
6 import { Configuration, RegistrationMap } from "./Configuration";
6 import { Configuration, RegistrationMap } from "./Configuration";
7 import { Cancellation } from "../Cancellation";
7 import { Cancellation } from "../Cancellation";
8 import { MapOf, IDestroyable } from "../interfaces";
8 import { IDestroyable, PromiseOrValue, ICancellation } from "../interfaces";
9 import { isDescriptor } from "./traits";
9 import { isDescriptor } from "./traits";
10 import { LifetimeManager } from "./LifetimeManager";
10 import { LifetimeManager } from "./LifetimeManager";
11 import { each } from "../safe";
11 import { each, isString } from "../safe";
12 import { FluentRegistrations } from "./fluent/interfaces";
12 import { ContainerConfiguration, FluentRegistrations } from "./fluent/interfaces";
13 import { FluentConfiguration } from "./fluent/FluentConfiguration";
13 import { FluentConfiguration } from "./fluent/FluentConfiguration";
14
14
15 const trace = TraceSource.get("@implab/core/di/ActivationContext");
15 const trace = TraceSource.get("@implab/core/di/ActivationContext");
16
16
17 export class Container<S extends object = any> implements ServiceLocator<S>, IDestroyable {
17 export class Container<S extends object = any> implements ServiceLocator<S>, IDestroyable {
18 readonly _services: ContainerServiceMap<S>;
18 readonly _services: ContainerServiceMap<S>;
19
19
20 readonly _lifetimeManager: LifetimeManager;
20 readonly _lifetimeManager: LifetimeManager;
21
21
22 readonly _cleanup: (() => void)[];
22 readonly _cleanup: (() => void)[];
23
23
24 readonly _root: Container<S>;
24 readonly _root: Container<S>;
25
25
26 readonly _parent?: Container<S>;
26 readonly _parent?: Container<S>;
27
27
28 _disposed: boolean;
28 _disposed: boolean;
29
29
30 constructor(parent?: Container<S>) {
30 constructor(parent?: Container<S>) {
31 this._parent = parent;
31 this._parent = parent;
32 this._services = parent ? Object.create(parent._services) : {};
32 this._services = parent ? Object.create(parent._services) : {};
33 this._cleanup = [];
33 this._cleanup = [];
34 this._root = parent ? parent.getRootContainer() : this;
34 this._root = parent ? parent.getRootContainer() : this;
35 this._services.container = new ValueDescriptor(this) as any;
35 this._services.container = new ValueDescriptor(this) as any;
36 this._disposed = false;
36 this._disposed = false;
37 this._lifetimeManager = new LifetimeManager();
37 this._lifetimeManager = new LifetimeManager();
38 }
38 }
39
39
40 getRootContainer() {
40 getRootContainer() {
41 return this._root;
41 return this._root;
42 }
42 }
43
43
44 getParent() {
44 getParent() {
45 return this._parent;
45 return this._parent;
46 }
46 }
47
47
48 getLifetimeManager() {
48 getLifetimeManager() {
49 return this._lifetimeManager;
49 return this._lifetimeManager;
50 }
50 }
51
51
52 resolve<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>): TypeOfService<S, K> {
52 resolve<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>): TypeOfService<S, K> {
53 trace.debug("resolve {0}", name);
53 trace.debug("resolve {0}", name);
54 const d = this._services[name];
54 const d = this._services[name];
55 if (d === undefined) {
55 if (d === undefined) {
56 if (def !== undefined)
56 if (def !== undefined)
57 return def;
57 return def;
58 else
58 else
59 throw new Error("Service '" + name + "' isn't found");
59 throw new Error("Service '" + name + "' isn't found");
60 } else {
60 } else {
61
61
62 const context = new ActivationContext<S>(this, this._services, String(name), d);
62 const context = new ActivationContext<S>(this, this._services, String(name), d);
63 try {
63 try {
64 return d.activate(context);
64 return d.activate(context);
65 } catch (error) {
65 } catch (error) {
66 throw new ActivationError(name.toString(), context.getStack(), error);
66 throw new ActivationError(name.toString(), context.getStack(), error);
67 }
67 }
68 }
68 }
69 }
69 }
70
70
71 /**
71 /**
72 * @deprecated use resolve() method
72 * @deprecated use resolve() method
73 */
73 */
74 getService<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>) {
74 getService<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>) {
75 return this.resolve(name, def);
75 return this.resolve(name, def);
76 }
76 }
77
77
78 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>): this;
78 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>): this;
79 register(services: PartialServiceMap<S>): this;
79 register(services: PartialServiceMap<S>): this;
80 register<K extends keyof S>(nameOrCollection: K | ServiceMap<S>, service?: Descriptor<S, S[K]>) {
80 register<K extends keyof S>(nameOrCollection: K | ServiceMap<S>, service?: Descriptor<S, S[K]>) {
81 if (arguments.length === 1) {
81 if (arguments.length === 1) {
82 const data = nameOrCollection as ServiceMap<S>;
82 const data = nameOrCollection as ServiceMap<S>;
83
83
84 each(data, (v, k) => this.register(k, v));
84 each(data, (v, k) => this.register(k, v));
85 } else {
85 } else {
86 if (!isDescriptor(service))
86 if (!isDescriptor(service))
87 throw new Error("The service parameter must be a descriptor");
87 throw new Error("The service parameter must be a descriptor");
88
88
89 this._services[nameOrCollection as K] = service as any;
89 this._services[nameOrCollection as K] = service as any;
90 }
90 }
91 return this;
91 return this;
92 }
92 }
93
93
94 onDispose(callback: () => void) {
94 onDispose(callback: () => void) {
95 if (!(callback instanceof Function))
95 if (!(callback instanceof Function))
96 throw new Error("The callback must be a function");
96 throw new Error("The callback must be a function");
97 this._cleanup.push(callback);
97 this._cleanup.push(callback);
98 }
98 }
99
99
100 destroy() {
100 destroy() {
101 return this.dispose();
101 return this.dispose();
102 }
102 }
103 dispose() {
103 dispose() {
104 if (this._disposed)
104 if (this._disposed)
105 return;
105 return;
106 this._disposed = true;
106 this._disposed = true;
107 for (const f of this._cleanup)
107 for (const f of this._cleanup)
108 f();
108 f();
109 }
109 }
110
110
111 /**
111 /**
112 * @param{String|Object} config
112 * @param{String|Object} config
113 * The configuration of the container. Can be either a string or an object,
113 * The configuration of the container. Can be either a string or an object,
114 * if the configuration is an object it's treated as a collection of
114 * if the configuration is an object it's treated as a collection of
115 * services which will be registered in the container.
115 * services which will be registered in the container.
116 *
116 *
117 * @param{Function} opts.contextRequire
117 * @param{Function} opts.contextRequire
118 * The function which will be used to load a configuration or types for services.
118 * The function which will be used to load a configuration or types for services.
119 *
119 *
120 */
120 */
121 async configure(config: string | RegistrationMap<S>, opts?: { contextRequire: any; baseModule?: string }, ct = Cancellation.none) {
121 async configure(config: string | RegistrationMap<S>, opts?: { contextRequire: any; baseModule?: string }, ct = Cancellation.none) {
122 const _opts = Object.create(opts || null);
122 const _opts = Object.create(opts || null);
123
123
124 if (typeof (config) === "string") {
124 if (typeof (config) === "string") {
125 _opts.baseModule = config;
125 _opts.baseModule = config;
126
126
127 const module = await import(config);
127 const module = await import(config);
128 if (module && module.default && typeof (module.default.apply) === "function")
128 if (module && module.default && typeof (module.default.apply) === "function")
129 return module.default.apply(this);
129 return module.default.apply(this);
130 else
130 else
131 return this._applyLegacyConfig(module, _opts, ct);
131 return this._applyLegacyConfig(module, _opts, ct);
132 } else {
132 } else {
133 return this._applyLegacyConfig(config, _opts, ct);
133 return this._applyLegacyConfig(config, _opts, ct);
134 }
134 }
135 }
135 }
136
136
137 applyConfig<S2 extends object>(config: Promise<{ default: ContainerConfiguration<S2>; }>, ct?: ICancellation): Promise<Container<S & S2>>;
138 applyConfig<S2 extends object, P extends string>(config: Promise<{ [p in P]: ContainerConfiguration<S2>; }>, prop: P, ct?: ICancellation): Promise<Container<S & S2>>;
139 async applyConfig<S2 extends object, P extends string>(
140 config: Promise<{ [p in P | "default"]: ContainerConfiguration<S2>; }>,
141 propOrCt?: P | ICancellation,
142 ct?: ICancellation
143 ): Promise<Container<S & S2>> {
144 const mod = await config;
145
146 let _ct: ICancellation;
147 let _prop: P | "default";
148
149 if (isString(propOrCt)) {
150 _prop = propOrCt;
151 _ct = ct || Cancellation.none;
152 } else {
153 _ct = propOrCt || Cancellation.none;
154 _prop = "default";
155 }
156
157 return mod[_prop].apply(this, _ct);
158 }
159
137 async _applyLegacyConfig(config: RegistrationMap<S>, opts: { contextRequire: any; baseModule?: string }, ct = Cancellation.none) {
160 async _applyLegacyConfig(config: RegistrationMap<S>, opts: { contextRequire: any; baseModule?: string }, ct = Cancellation.none) {
138 return new Configuration<S>(this).applyConfiguration(config, opts);
161 return new Configuration<S>(this).applyConfiguration(config, opts);
139 }
162 }
140
163
141 async fluent<K extends keyof S>(config: FluentRegistrations<K, S>, ct = Cancellation.none): Promise<this> {
164 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);
165 await new FluentConfiguration<S>().register(config).apply(this, ct);
143 return this;
166 return this;
144 }
167 }
145
168
146 createChildContainer<S2 extends object = S>(): Container<S & S2> {
169 createChildContainer<S2 extends object = S>(): Container<S & S2> {
147 return new Container<S & S2>(this as any);
170 return new Container<S & S2>(this as any);
148 }
171 }
149 }
172 }
@@ -1,60 +1,68
1 import { Container } from "../Container";
1 import { Container } from "../Container";
2 import { argumentNotNull, each, isPrimitive, isPromise } from "../../safe";
2 import { argumentNotNull, each, isPrimitive, isPromise } from "../../safe";
3 import { DescriptorBuilder } from "./DescriptorBuilder";
3 import { DescriptorBuilder } from "./DescriptorBuilder";
4 import { RegistrationBuilder, FluentRegistrations } from "./interfaces";
4 import { RegistrationBuilder, FluentRegistrations, ContainerConfiguration } from "./interfaces";
5 import { Cancellation } from "../../Cancellation";
5 import { Cancellation } from "../../Cancellation";
6
6
7 export class FluentConfiguration<S extends object, Y extends keyof S = keyof S> {
7 export class FluentConfiguration<S extends object, Y extends keyof S = keyof S> {
8
8
9 _builders: { [k in keyof S]?: RegistrationBuilder<S, S[k]> } = {};
9 _builders: { [k in keyof S]?: RegistrationBuilder<S, S[k]> } = {};
10
10
11 provided<K extends Y>(): FluentConfiguration<S, Exclude<Y, K>> {
12 return this;
13 }
14
11 register<K extends Y>(name: K, builder: RegistrationBuilder<S, S[K]>): FluentConfiguration<S, Exclude<Y, K>>;
15 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>>;
16 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>> {
17 register<K extends Y>(nameOrConfig: K | FluentRegistrations<K, S>, builder?: RegistrationBuilder<S, S[K]>): FluentConfiguration<S, Exclude<Y, K>> {
14 if (isPrimitive(nameOrConfig)) {
18 if (isPrimitive(nameOrConfig)) {
15 argumentNotNull(builder, "builder");
19 argumentNotNull(builder, "builder");
16 this._builders[nameOrConfig] = builder;
20 this._builders[nameOrConfig] = builder;
17 } else {
21 } else {
18 each(nameOrConfig, (v, k) => this.register(k, v));
22 each(nameOrConfig, (v, k) => this.register(k, v));
19 }
23 }
20
24
21 return this;
25 return this;
22 }
26 }
23
27
28 configure(config: FluentRegistrations<Y, S>): ContainerConfiguration<S> {
29 return this.register(config);
30 }
31
24 apply<SC extends object>(target: Container<SC>, ct = Cancellation.none) {
32 apply<SC extends object>(target: Container<SC>, ct = Cancellation.none) {
25
33
26 let pending = 1;
34 let pending = 1;
27
35
28 const _t2 = target as unknown as Container<SC & S>;
36 const _t2 = target as unknown as Container<SC & S>;
29
37
30 return new Promise<Container<SC & S>>((resolve, reject) => {
38 return new Promise<Container<SC & S>>((resolve, reject) => {
31 function guard(v: void | Promise<void>) {
39 function guard(v: void | Promise<void>) {
32 if (isPromise(v))
40 if (isPromise(v))
33 v.catch(reject);
41 v.catch(reject);
34 }
42 }
35
43
36 function complete() {
44 function complete() {
37 if (!--pending)
45 if (!--pending)
38 resolve(_t2);
46 resolve(_t2);
39 }
47 }
40 each(this._builders, (v, k) => {
48 each(this._builders, (v, k) => {
41 pending++;
49 pending++;
42 const d = new DescriptorBuilder<SC & S, any>(_t2,
50 const d = new DescriptorBuilder<SC & S, any>(_t2,
43 result => {
51 result => {
44 _t2.register(k, result);
52 _t2.register(k, result);
45 complete();
53 complete();
46 },
54 },
47 reject
55 reject
48 );
56 );
49
57
50 try {
58 try {
51 guard(v(d, ct));
59 guard(v(d, ct));
52 } catch (e) {
60 } catch (e) {
53 reject(e);
61 reject(e);
54 }
62 }
55 });
63 });
56 complete();
64 complete();
57 });
65 });
58 }
66 }
59
67
60 }
68 }
@@ -1,52 +1,57
1 import { primitive } from "../../safe";
1 import { primitive } from "../../safe";
2 import { TypeOfService, ContainerKeys, ActivationType, ILifetime } from "../interfaces";
2 import { TypeOfService, ContainerKeys, ActivationType, ILifetime } from "../interfaces";
3 import { ICancellation } from "../../interfaces";
3 import { ICancellation } from "../../interfaces";
4 import { Container } from "../Container";
4
5
5 export interface DependencyOptions {
6 export interface DependencyOptions {
6 optional?: boolean;
7 optional?: boolean;
7 default?: any;
8 default?: any;
8 }
9 }
9
10
10 export interface LazyDependencyOptions extends DependencyOptions {
11 export interface LazyDependencyOptions extends DependencyOptions {
11 lazy: true;
12 lazy: true;
12 }
13 }
13
14
14 export type ExtractService<K, S> = K extends keyof S ? S[K] : never;
15 export type ExtractService<K, S> = K extends keyof S ? S[K] : never;
15
16
16 export type ExtractDependency<D, S> = D extends { $dependency: infer K } ?
17 export type ExtractDependency<D, S> = D extends { $dependency: infer K } ?
17 D extends { lazy: true } ? () => ExtractService<K, S> : ExtractService<K, S> :
18 D extends { lazy: true } ? () => ExtractService<K, S> : ExtractService<K, S> :
18 D extends { $type: new (...args: any[]) => infer I } ? I :
19 D extends { $type: new (...args: any[]) => infer I } ? I :
19 D extends { $factory: (...args: any[]) => infer R } ? R :
20 D extends { $factory: (...args: any[]) => infer R } ? R :
20 WalkDependencies<D, S>;
21 WalkDependencies<D, S>;
21
22
22 export type WalkDependencies<D, S> = D extends primitive ? D :
23 export type WalkDependencies<D, S> = D extends primitive ? D :
23 { [K in keyof D]: ExtractDependency<D[K], S> };
24 { [K in keyof D]: ExtractDependency<D[K], S> };
24
25
25 export type InferReferenceType<S extends object, K extends ContainerKeys<S>, O> = O extends { default: infer X } ? (TypeOfService<S, K> | X) :
26 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 O extends { optional: true } ? (TypeOfService<S, K> | undefined) :
27 TypeOfService<S, K>;
28 TypeOfService<S, K>;
28
29
29 export interface Resolver<S extends object> {
30 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 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 <K extends ContainerKeys<S>, O extends DependencyOptions>(this: void, name: K, opts?: O): InferReferenceType<S, K, O>;
32 }
33 }
33
34
34 export interface DescriptorBuilder<S extends object, T> {
35 export interface DescriptorBuilder<S extends object, T> {
35 factory(f: (resolve: Resolver<S>) => T): void;
36 factory(f: (resolve: Resolver<S>) => T): void;
36
37
37 build<T2>(): DescriptorBuilder<S, T2>;
38 build<T2>(): DescriptorBuilder<S, T2>;
38
39
39 override<K extends keyof S>(name: K, builder: RegistrationBuilder<S, S[K]>): this;
40 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 override<K extends keyof S>(services: { [name in K]: RegistrationBuilder<S, S[K]> }): this;
41
42
42 lifetime(lifetime: "singleton", typeId: any): this;
43 lifetime(lifetime: "singleton", typeId: any): this;
43 lifetime(lifetime: ILifetime | Exclude<ActivationType, "singleton">): this;
44 lifetime(lifetime: ILifetime | Exclude<ActivationType, "singleton">): this;
44
45
45 cleanup(cb: (item: T) => void): this;
46 cleanup(cb: (item: T) => void): this;
46
47
47 value(v: T): void;
48 value(v: T): void;
48 }
49 }
49
50
51 export interface ContainerConfiguration<S extends object> {
52 apply<S2 extends object>(target: Container<S2>, ct: ICancellation): Promise<Container<S2 & S>>;
53 }
54
50 export type RegistrationBuilder<S extends object, T> = (d: DescriptorBuilder<S, T>, ct?: ICancellation) => void | Promise<void>;
55 export type RegistrationBuilder<S extends object, T> = (d: DescriptorBuilder<S, T>, ct?: ICancellation) => void | Promise<void>;
51
56
52 export type FluentRegistrations<K extends keyof S, S extends object> = { [k in K]: RegistrationBuilder<S, S[k]> };
57 export type FluentRegistrations<K extends keyof S, S extends object> = { [k in K]: RegistrationBuilder<S, S[k]> };
@@ -1,502 +1,502
1 import { ICancellable, Constructor, IDestroyable, PromiseOrValue } from "./interfaces";
1 import { ICancellable, Constructor, IDestroyable, PromiseOrValue } from "./interfaces";
2 import { Cancellation } from "./Cancellation";
2 import { Cancellation } from "./Cancellation";
3
3
4 let _nextOid = 0;
4 let _nextOid = 0;
5 const _oid = typeof Symbol === "function" ?
5 const _oid = typeof Symbol === "function" ?
6 Symbol("__implab__oid__") :
6 Symbol("__implab__oid__") :
7 "__implab__oid__";
7 "__implab__oid__";
8
8
9 export function oid(instance: null | undefined): undefined;
9 export function oid(instance: null | undefined): undefined;
10 export function oid(instance: NonNullable<any>): string;
10 export function oid(instance: NonNullable<any>): string;
11 export function oid(instance: any): string | undefined {
11 export function oid(instance: any): string | undefined {
12 if (isNull(instance))
12 if (isNull(instance))
13 return undefined;
13 return undefined;
14
14
15 if (_oid in instance)
15 if (_oid in instance)
16 return instance[_oid];
16 return instance[_oid];
17 else
17 else
18 return (instance[_oid] = "oid_" + (++_nextOid));
18 return (instance[_oid] = "oid_" + (++_nextOid));
19 }
19 }
20
20
21 export function keys<T>(arg: T): (Extract<keyof T, string>)[] {
21 export function keys<T>(arg: T): (Extract<keyof T, string>)[] {
22 return isObject(arg) && arg ? Object.keys(arg) as (Extract<keyof T, string>)[] : [];
22 return isObject(arg) && arg ? Object.keys(arg) as (Extract<keyof T, string>)[] : [];
23 }
23 }
24
24
25 export function isKeyof<T>(k: string, target: T): k is Extract<keyof T, string> {
25 export function isKeyof<T>(k: string, target: T): k is Extract<keyof T, string> {
26 return target && typeof target === "object" && k in target;
26 return target && typeof target === "object" && k in target;
27 }
27 }
28
28
29 export function argumentNotNull(arg: any, name: string) {
29 export function argumentNotNull(arg: any, name: string) {
30 if (arg === null || arg === undefined)
30 if (arg === null || arg === undefined)
31 throw new Error("The argument " + name + " can't be null or undefined");
31 throw new Error("The argument " + name + " can't be null or undefined");
32 }
32 }
33
33
34 export function argumentNotEmptyString(arg: any, name: string) {
34 export function argumentNotEmptyString(arg: any, name: string) {
35 if (typeof (arg) !== "string" || !arg.length)
35 if (typeof (arg) !== "string" || !arg.length)
36 throw new Error("The argument '" + name + "' must be a not empty string");
36 throw new Error("The argument '" + name + "' must be a not empty string");
37 }
37 }
38
38
39 export function argumentNotEmptyArray(arg: any, name: string) {
39 export function argumentNotEmptyArray(arg: any, name: string) {
40 if (!(arg instanceof Array) || !arg.length)
40 if (!(arg instanceof Array) || !arg.length)
41 throw new Error("The argument '" + name + "' must be a not empty array");
41 throw new Error("The argument '" + name + "' must be a not empty array");
42 }
42 }
43
43
44 export function argumentOfType(arg: any, type: Constructor<{}>, name: string) {
44 export function argumentOfType(arg: any, type: Constructor<{}>, name: string) {
45 if (!(arg instanceof type))
45 if (!(arg instanceof type))
46 throw new Error("The argument '" + name + "' type doesn't match");
46 throw new Error("The argument '" + name + "' type doesn't match");
47 }
47 }
48
48
49 export function isObject(val: any): val is object {
49 export function isObject(val: any): val is object {
50 return typeof val === "object";
50 return typeof val === "object";
51 }
51 }
52
52
53 export function isNull(val: any): val is null | undefined {
53 export function isNull(val: any): val is null | undefined {
54 return (val === null || val === undefined);
54 return (val === null || val === undefined);
55 }
55 }
56
56
57 export type primitive = symbol | string | number | boolean | undefined | null;
57 export type primitive = symbol | string | number | boolean | undefined | null;
58
58
59 export function isPrimitive(val: any): val is primitive {
59 export function isPrimitive(val: any): val is primitive {
60 return (val === null || val === undefined || typeof (val) === "string" ||
60 return (val === null || val === undefined || typeof (val) === "string" ||
61 typeof (val) === "number" || typeof (val) === "boolean");
61 typeof (val) === "number" || typeof (val) === "boolean");
62 }
62 }
63
63
64 export function isInteger(val: any): val is number {
64 export function isInteger(val: any): val is number {
65 return parseInt(val, 10) === val;
65 return parseInt(val, 10) === val;
66 }
66 }
67
67
68 export function isNumber(val: any): val is number {
68 export function isNumber(val: any): val is number {
69 return parseFloat(val) === val;
69 return parseFloat(val) === val;
70 }
70 }
71
71
72 export function isString(val: any): val is string {
72 export function isString(val: any): val is string {
73 return typeof (val) === "string" || val instanceof String;
73 return typeof (val) === "string" || val instanceof String;
74 }
74 }
75
75
76 export function isPromise<T = any>(val: any): val is PromiseLike<T> {
76 export function isPromise<T = any>(val: any): val is PromiseLike<T> {
77 return val && typeof val.then === "function";
77 return val && typeof val.then === "function";
78 }
78 }
79
79
80 export function isCancellable(val: any): val is ICancellable {
80 export function isCancellable(val: any): val is ICancellable {
81 return val && typeof val.cancel === "function";
81 return val && typeof val.cancel === "function";
82 }
82 }
83
83
84 export function isNullOrEmptyString(val: any): val is ("" | null | undefined) {
84 export function isNullOrEmptyString(val: any): val is ("" | null | undefined) {
85 return (val === null || val === undefined ||
85 return (val === null || val === undefined ||
86 ((typeof (val) === "string" || val instanceof String) && val.length === 0));
86 ((typeof (val) === "string" || val instanceof String) && val.length === 0));
87 }
87 }
88
88
89 export function isNotEmptyArray<T = any>(arg: any): arg is T[] {
89 export function isNotEmptyArray<T = any>(arg: any): arg is T[] {
90 return (arg instanceof Array && arg.length > 0);
90 return (arg instanceof Array && arg.length > 0);
91 }
91 }
92
92
93 function _isStrictMode(this: any) {
93 function _isStrictMode(this: any) {
94 return !this;
94 return !this;
95 }
95 }
96
96
97 function _getNonStrictGlobal(this: any) {
97 function _getNonStrictGlobal(this: any) {
98 return this;
98 return this;
99 }
99 }
100
100
101 export function getGlobal() {
101 export function getGlobal() {
102 // in es3 we can't use indirect call to eval, since it will
102 // in es3 we can't use indirect call to eval, since it will
103 // be executed in the current call context.
103 // be executed in the current call context.
104 if (!_isStrictMode()) {
104 if (!_isStrictMode()) {
105 return _getNonStrictGlobal();
105 return _getNonStrictGlobal();
106 } else {
106 } else {
107 // tslint:disable-next-line:no-eval
107 // tslint:disable-next-line:no-eval
108 return eval.call(null, "this");
108 return eval.call(null, "this");
109 }
109 }
110 }
110 }
111
111
112 export function get(member: string, context?: object) {
112 export function get(member: string, context?: object) {
113 argumentNotEmptyString(member, "member");
113 argumentNotEmptyString(member, "member");
114 let that = context || getGlobal();
114 let that = context || getGlobal();
115 const parts = member.split(".");
115 const parts = member.split(".");
116 for (const m of parts) {
116 for (const m of parts) {
117 if (!m)
117 if (!m)
118 continue;
118 continue;
119 if (isNull(that = that[m]))
119 if (isNull(that = that[m]))
120 break;
120 break;
121 }
121 }
122 return that;
122 return that;
123 }
123 }
124
124
125 /**
125 /**
126 * Выполняет метод для каждого элемента массива, останавливается, когда
126 * Выполняет метод для каждого элемента массива, останавливается, когда
127 * либо достигнут конец массива, либо функция <c>cb</c> вернула
127 * либо достигнут конец массива, либо функция <c>cb</c> вернула
128 * значение.
128 * значение.
129 *
129 *
130 * @param {Array | Object} obj массив элементов для просмотра
130 * @param {Array | Object} obj массив элементов для просмотра
131 * @param {Function} cb функция, вызываемая для каждого элемента
131 * @param {Function} cb функция, вызываемая для каждого элемента
132 * @param {Object} thisArg значение, которое будет передано в качестве
132 * @param {Object} thisArg значение, которое будет передано в качестве
133 * <c>this</c> в <c>cb</c>.
133 * <c>this</c> в <c>cb</c>.
134 * @returns {void}
134 * @returns {void}
135 */
135 */
136 export function each<T>(obj: T, cb: <X extends keyof T>(v: NonNullable<T[X]>, k: X) => void): void;
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;
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;
138 export function each(obj: any, cb: any, thisArg?: any): any;
139 export function each(obj: any, cb: any, thisArg?: any) {
139 export function each(obj: any, cb: any, thisArg?: any) {
140 argumentNotNull(cb, "cb");
140 argumentNotNull(cb, "cb");
141 if (obj instanceof Array) {
141 if (obj instanceof Array) {
142 let v: any;
142 let v: any;
143 for (let i = 0; i < obj.length; i++) {
143 for (let i = 0; i < obj.length; i++) {
144 v = obj[i];
144 v = obj[i];
145 if (v !== undefined)
145 if (v !== undefined)
146 cb.call(thisArg, v, i);
146 cb.call(thisArg, v, i);
147 }
147 }
148 } else {
148 } else {
149 Object.keys(obj).forEach(k => obj[k] !== undefined && cb.call(thisArg, obj[k], k));
149 Object.keys(obj).forEach(k => obj[k] !== undefined && cb.call(thisArg, obj[k], k));
150 }
150 }
151 }
151 }
152
152
153 /** Copies property values from a source object to the destination and returns
153 /** Copies property values from a source object to the destination and returns
154 * the destination onject.
154 * the destination object.
155 *
155 *
156 * @param dest The destination object into which properties from the source
156 * @param dest The destination object into which properties from the source
157 * object will be copied.
157 * object will be copied.
158 * @param source The source of values which will be copied to the destination
158 * @param source The source of values which will be copied to the destination
159 * object.
159 * object.
160 * @param template An optional parameter specifies which properties should be
160 * @param template An optional parameter specifies which properties should be
161 * copied from the source and how to map them to the destination. If the
161 * copied from the source and how to map them to the destination. If the
162 * template is an array it contains the list of property names to copy from the
162 * template is an array it contains the list of property names to copy from the
163 * source to the destination. In case of object the templates contains the map
163 * source to the destination. In case of object the templates contains the map
164 * where keys are property names in the source and the values are property
164 * where keys are property names in the source and the values are property
165 * names in the destination object. If the template isn't specified then the
165 * names in the destination object. If the template isn't specified then the
166 * own properties of the source are entirely copied to the destination.
166 * own properties of the source are entirely copied to the destination.
167 *
167 *
168 */
168 */
169 export function mixin<T extends object, S extends object>(dest: T, source: S, template?: (keyof S)[]): 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;
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 {
171 export function mixin<T extends object, S extends object>(dest: T, source: S, template?: any): any {
172 argumentNotNull(dest, "dest");
172 argumentNotNull(dest, "dest");
173 const _res: any = dest as any;
173 const _res: any = dest as any;
174
174
175 if (isPrimitive(source))
175 if (isPrimitive(source))
176 return _res;
176 return _res;
177
177
178 if (template instanceof Array) {
178 if (template instanceof Array) {
179 template.forEach(p => {
179 template.forEach(p => {
180 if (isKeyof(p, source))
180 if (isKeyof(p, source))
181 _res[p] = source[p];
181 _res[p] = source[p];
182 });
182 });
183 } else if (template) {
183 } else if (template) {
184 keys(source).forEach(p => {
184 keys(source).forEach(p => {
185 if (isKeyof(p, template))
185 if (isKeyof(p, template))
186 _res[template[p]] = source[p];
186 _res[template[p]] = source[p];
187 });
187 });
188 } else {
188 } else {
189 keys(source).forEach(p => _res[p] = source[p]);
189 keys(source).forEach(p => _res[p] = source[p]);
190 }
190 }
191
191
192 return _res;
192 return _res;
193 }
193 }
194
194
195 /** Wraps the specified function to emulate an asynchronous execution.
195 /** Wraps the specified function to emulate an asynchronous execution.
196 * @param{Object} thisArg [Optional] Object which will be passed as 'this' to the function.
196 * @param{Object} thisArg [Optional] Object which will be passed as 'this' to the function.
197 * @param{Function|String} fn [Required] Function wich will be wrapped.
197 * @param{Function|String} fn [Required] Function wich will be wrapped.
198 */
198 */
199 export function async<T, F extends (...args: any[]) => T | PromiseLike<T>>(
199 export function async<T, F extends (...args: any[]) => T | PromiseLike<T>>(
200 fn: F,
200 fn: F,
201 thisArg?: ThisParameterType<F>
201 thisArg?: ThisParameterType<F>
202 ): (...args: Parameters<F>) => PromiseLike<T>;
202 ): (...args: Parameters<F>) => PromiseLike<T>;
203 export function async<T, M extends string, O extends { [m in M]?: (...args: any[]) => T | PromiseLike<T> }>(
203 export function async<T, M extends string, O extends { [m in M]?: (...args: any[]) => T | PromiseLike<T> }>(
204 fn: M,
204 fn: M,
205 thisArg: O
205 thisArg: O
206 ): (...args: Parameters<NonNullable<O[M]>>) => PromiseLike<T>;
206 ): (...args: Parameters<NonNullable<O[M]>>) => PromiseLike<T>;
207 export function async(_fn: any, thisArg: any): (...args: any[]) => PromiseLike<any> {
207 export function async(_fn: any, thisArg: any): (...args: any[]) => PromiseLike<any> {
208 let fn = _fn;
208 let fn = _fn;
209
209
210 if (arguments.length === 2 && !(fn instanceof Function))
210 if (arguments.length === 2 && !(fn instanceof Function))
211 fn = thisArg[fn];
211 fn = thisArg[fn];
212
212
213 if (fn == null)
213 if (fn == null)
214 throw new Error("The function must be specified");
214 throw new Error("The function must be specified");
215
215
216 function wrapresult(x: any, e?: any): PromiseLike<any> {
216 function wrapresult(x: any, e?: any): PromiseLike<any> {
217 if (e) {
217 if (e) {
218 return {
218 return {
219 then(cb, eb) {
219 then(cb, eb) {
220 try {
220 try {
221 return eb ? wrapresult(eb(e)) : this;
221 return eb ? wrapresult(eb(e)) : this;
222 } catch (e2) {
222 } catch (e2) {
223 return wrapresult(null, e2);
223 return wrapresult(null, e2);
224 }
224 }
225 }
225 }
226 };
226 };
227 } else {
227 } else {
228 if (x && x.then)
228 if (x && x.then)
229 return x;
229 return x;
230 return {
230 return {
231 then(cb) {
231 then(cb) {
232 try {
232 try {
233 return cb ? wrapresult(cb(x)) : this;
233 return cb ? wrapresult(cb(x)) : this;
234 } catch (e2) {
234 } catch (e2) {
235 return wrapresult(e2);
235 return wrapresult(e2);
236 }
236 }
237 }
237 }
238 };
238 };
239 }
239 }
240 }
240 }
241
241
242 return (...args) => {
242 return (...args) => {
243 try {
243 try {
244 return wrapresult(fn.apply(thisArg, args));
244 return wrapresult(fn.apply(thisArg, args));
245 } catch (e) {
245 } catch (e) {
246 return wrapresult(null, e);
246 return wrapresult(null, e);
247 }
247 }
248 };
248 };
249 }
249 }
250
250
251 export function delegate<T extends object, F extends (this: T, ...args: any[]) => any>(
251 export function delegate<T extends object, F extends (this: T, ...args: any[]) => any>(
252 target: T,
252 target: T,
253 method: F
253 method: F
254 ): OmitThisParameter<F>;
254 ): OmitThisParameter<F>;
255 export function delegate<M extends string, T extends { [m in M]?: (...args: any[]) => any; }>(
255 export function delegate<M extends string, T extends { [m in M]?: (...args: any[]) => any; }>(
256 target: T,
256 target: T,
257 method: M
257 method: M
258 ): OmitThisParameter<T[M]>;
258 ): OmitThisParameter<T[M]>;
259 export function delegate(target: any, _method: any): (...args: any[]) => any {
259 export function delegate(target: any, _method: any): (...args: any[]) => any {
260 let method: any;
260 let method: any;
261 if (!(_method instanceof Function)) {
261 if (!(_method instanceof Function)) {
262 argumentNotNull(target, "target");
262 argumentNotNull(target, "target");
263 method = target[_method];
263 method = target[_method];
264 if (!(method instanceof Function))
264 if (!(method instanceof Function))
265 throw new Error("'method' argument must be a Function or a method name");
265 throw new Error("'method' argument must be a Function or a method name");
266 } else {
266 } else {
267 method = _method;
267 method = _method;
268 }
268 }
269
269
270 return (...args) => {
270 return (...args) => {
271 return method.apply(target, args);
271 return method.apply(target, args);
272 };
272 };
273 }
273 }
274
274
275 export function delay(timeMs: number, ct = Cancellation.none) {
275 export function delay(timeMs: number, ct = Cancellation.none) {
276 ct.throwIfRequested();
276 ct.throwIfRequested();
277 return new Promise((resolve, reject) => {
277 return new Promise((resolve, reject) => {
278 const h = ct.register(e => {
278 const h = ct.register(e => {
279 clearTimeout(id);
279 clearTimeout(id);
280 reject(e);
280 reject(e);
281 // we don't nedd to unregister h, since ct is already disposed
281 // we don't nedd to unregister h, since ct is already disposed
282 });
282 });
283 const id = setTimeout(() => {
283 const id = setTimeout(() => {
284 h.destroy();
284 h.destroy();
285 resolve();
285 resolve();
286 }, timeMs);
286 }, timeMs);
287
287
288 });
288 });
289 }
289 }
290
290
291 /** Returns resolved promise, awaiting this method will cause the asynchronous
291 /** Returns resolved promise, awaiting this method will cause the asynchronous
292 * completion of the rest of the code.
292 * completion of the rest of the code.
293 */
293 */
294 export function fork() {
294 export function fork() {
295 return Promise.resolve();
295 return Promise.resolve();
296 }
296 }
297
297
298 /** Always throws Error, can be used as a stub for the methods which should be
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.
299 * assigned later and are required to be not null.
300 */
300 */
301 export function notImplemented(): never {
301 export function notImplemented(): never {
302 throw new Error("Not implemeted");
302 throw new Error("Not implemeted");
303 }
303 }
304 /**
304 /**
305 * Iterates over the specified array of items and calls the callback `cb`, if
305 * Iterates over the specified array of items and calls the callback `cb`, if
306 * the result of the callback is a promise the next item from the array will be
306 * the result of the callback is a promise the next item from the array will be
307 * proceeded after the promise is resolved.
307 * proceeded after the promise is resolved.
308 *
308 *
309 */
309 */
310 export function pmap<T, T2>(
310 export function pmap<T, T2>(
311 items: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
311 items: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
312 cb: (item: T, i: number) => T2 | PromiseLike<T2>
312 cb: (item: T, i: number) => T2 | PromiseLike<T2>
313 ): T2[] | PromiseLike<T2[]> {
313 ): T2[] | PromiseLike<T2[]> {
314 argumentNotNull(cb, "cb");
314 argumentNotNull(cb, "cb");
315
315
316 if (isPromise(items)) {
316 if (isPromise(items)) {
317 return items.then(data => pmap(data, cb));
317 return items.then(data => pmap(data, cb));
318 } else {
318 } else {
319
319
320 if (isNull(items) || !items.length)
320 if (isNull(items) || !items.length)
321 return [];
321 return [];
322
322
323 let i = 0;
323 let i = 0;
324 const result = new Array<T2>();
324 const result = new Array<T2>();
325
325
326 const next = (): any => {
326 const next = (): any => {
327 while (i < items.length) {
327 while (i < items.length) {
328 const r = cb(items[i], i);
328 const r = cb(items[i], i);
329 const ri = i;
329 const ri = i;
330 i++;
330 i++;
331 if (isPromise(r)) {
331 if (isPromise(r)) {
332 return r.then(x => {
332 return r.then(x => {
333 result[ri] = x;
333 result[ri] = x;
334 return next();
334 return next();
335 });
335 });
336 } else {
336 } else {
337 result[ri] = r;
337 result[ri] = r;
338 }
338 }
339 }
339 }
340 return result;
340 return result;
341 };
341 };
342
342
343 return next();
343 return next();
344 }
344 }
345 }
345 }
346
346
347 export function pfor<T>(
347 export function pfor<T>(
348 items: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
348 items: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
349 cb: (item: T, i: number) => any
349 cb: (item: T, i: number) => any
350 ): void | PromiseLike<void> {
350 ): void | PromiseLike<void> {
351 argumentNotNull(cb, "cb");
351 argumentNotNull(cb, "cb");
352
352
353 if (isPromise(items)) {
353 if (isPromise(items)) {
354 return items.then(data => pfor(data, cb));
354 return items.then(data => pfor(data, cb));
355 } else {
355 } else {
356 if (isNull(items) || !items.length)
356 if (isNull(items) || !items.length)
357 return;
357 return;
358
358
359 let i = 0;
359 let i = 0;
360
360
361 const next = (): any => {
361 const next = (): any => {
362 while (i < items.length) {
362 while (i < items.length) {
363 const r = cb(items[i], i);
363 const r = cb(items[i], i);
364 i++;
364 i++;
365 if (isPromise(r))
365 if (isPromise(r))
366 return r.then(next);
366 return r.then(next);
367 }
367 }
368 };
368 };
369
369
370 return next();
370 return next();
371 }
371 }
372 }
372 }
373
373
374 export function first<T>(sequence: ArrayLike<T>): T;
374 export function first<T>(sequence: ArrayLike<T>): T;
375 export function first<T>(sequence: PromiseLike<ArrayLike<T>>): PromiseLike<T>;
375 export function first<T>(sequence: PromiseLike<ArrayLike<T>>): PromiseLike<T>;
376 export function first<T>(
376 export function first<T>(
377 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
377 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
378 cb?: (x: T) => void,
378 cb?: (x: T) => void,
379 err?: (x: Error) => void
379 err?: (x: Error) => void
380 ): void;
380 ): void;
381 /**
381 /**
382 * Выбирает первый элемент из последовательности, или обещания, если в
382 * Выбирает первый элемент из последовательности, или обещания, если в
383 * качестве параметра используется обещание, оно должно вернуть массив.
383 * качестве параметра используется обещание, оно должно вернуть массив.
384 *
384 *
385 * @param {Function} cb обработчик результата, ему будет передан первый
385 * @param {Function} cb обработчик результата, ему будет передан первый
386 * элемент последовательности в случае успеха
386 * элемент последовательности в случае успеха
387 * @param {Function} err обработчик исключения, если массив пустой, либо
387 * @param {Function} err обработчик исключения, если массив пустой, либо
388 * не массив
388 * не массив
389 *
389 *
390 * @remarks Если не указаны ни cb ни err, тогда функция вернет либо
390 * @remarks Если не указаны ни cb ни err, тогда функция вернет либо
391 * обещание, либо первый элемент.
391 * обещание, либо первый элемент.
392 * @async
392 * @async
393 */
393 */
394 export function first<T>(
394 export function first<T>(
395 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
395 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
396 cb?: (x: T) => void,
396 cb?: (x: T) => void,
397 err?: (x: Error) => void
397 err?: (x: Error) => void
398 ) {
398 ) {
399 if (isPromise(sequence)) {
399 if (isPromise(sequence)) {
400 return sequence.then(res => first(res, cb as any /* force to pass undefined cb */, err));
400 return sequence.then(res => first(res, cb as any /* force to pass undefined cb */, err));
401 } else if (sequence && "length" in sequence) {
401 } else if (sequence && "length" in sequence) {
402 if (sequence.length === 0) {
402 if (sequence.length === 0) {
403 if (err)
403 if (err)
404 return err(new Error("The sequence is empty"));
404 return err(new Error("The sequence is empty"));
405 else
405 else
406 throw new Error("The sequence is empty");
406 throw new Error("The sequence is empty");
407 } else if (cb) {
407 } else if (cb) {
408 return cb(sequence[0]);
408 return cb(sequence[0]);
409 } else {
409 } else {
410 return sequence[0];
410 return sequence[0];
411 }
411 }
412 } else {
412 } else {
413 if (err)
413 if (err)
414 return err(new Error("The sequence is required"));
414 return err(new Error("The sequence is required"));
415 else
415 else
416 throw new Error("The sequence is required");
416 throw new Error("The sequence is required");
417 }
417 }
418 }
418 }
419
419
420 export function firstWhere<T>(
420 export function firstWhere<T>(
421 sequence: ArrayLike<T>,
421 sequence: ArrayLike<T>,
422 predicate: (x: T) => boolean
422 predicate: (x: T) => boolean
423 ): T;
423 ): T;
424 export function firstWhere<T>(
424 export function firstWhere<T>(
425 sequence: PromiseLike<ArrayLike<T>>,
425 sequence: PromiseLike<ArrayLike<T>>,
426 predicate: (x: T) => boolean
426 predicate: (x: T) => boolean
427 ): PromiseLike<T>;
427 ): PromiseLike<T>;
428 export function firstWhere<T>(
428 export function firstWhere<T>(
429 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
429 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
430 predicate: (x: T) => boolean,
430 predicate: (x: T) => boolean,
431 cb: (x: T) => void,
431 cb: (x: T) => void,
432 err?: (x: Error) => void
432 err?: (x: Error) => void
433 ): void;
433 ): void;
434
434
435 export function firstWhere<T>(
435 export function firstWhere<T>(
436 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
436 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
437 predicate?: (x: T) => boolean,
437 predicate?: (x: T) => boolean,
438 cb?: (x: T) => any,
438 cb?: (x: T) => any,
439 err?: (x: Error) => any
439 err?: (x: Error) => any
440 ) {
440 ) {
441 if (isPromise(sequence)) {
441 if (isPromise(sequence)) {
442 return sequence.then(res => firstWhere(
442 return sequence.then(res => firstWhere(
443 res,
443 res,
444 predicate as any /* force to pass undefined predicate */,
444 predicate as any /* force to pass undefined predicate */,
445 cb as any /* force to pass undefined cb */,
445 cb as any /* force to pass undefined cb */,
446 err)
446 err)
447 );
447 );
448 } else if (sequence && "length" in sequence) {
448 } else if (sequence && "length" in sequence) {
449 if (sequence.length === 0) {
449 if (sequence.length === 0) {
450 if (err)
450 if (err)
451 err(new Error("The sequence is empty"));
451 err(new Error("The sequence is empty"));
452 else
452 else
453 throw new Error("The sequence is empty");
453 throw new Error("The sequence is empty");
454 } else {
454 } else {
455 if (!predicate) {
455 if (!predicate) {
456 return cb ? cb(sequence[0]) && void (0) : sequence[0];
456 return cb ? cb(sequence[0]) && void (0) : sequence[0];
457 } else {
457 } else {
458 for (let i = 0; i < sequence.length; i++) {
458 for (let i = 0; i < sequence.length; i++) {
459 const v = sequence[i];
459 const v = sequence[i];
460 if (predicate(v))
460 if (predicate(v))
461 return cb ? cb(v) : v;
461 return cb ? cb(v) : v;
462 }
462 }
463 if (err)
463 if (err)
464 err(new Error("The sequence doesn't contain matching items"));
464 err(new Error("The sequence doesn't contain matching items"));
465 else
465 else
466 throw new Error("The sequence doesn't contain matching items");
466 throw new Error("The sequence doesn't contain matching items");
467 }
467 }
468 }
468 }
469 } else {
469 } else {
470 if (err)
470 if (err)
471 err(new Error("The sequence is required"));
471 err(new Error("The sequence is required"));
472 else
472 else
473 throw new Error("The sequence is required");
473 throw new Error("The sequence is required");
474 }
474 }
475 }
475 }
476
476
477 export function isDestroyable(d: any): d is IDestroyable {
477 export function isDestroyable(d: any): d is IDestroyable {
478 if (d && "destroy" in d && typeof (destroy) === "function")
478 if (d && "destroy" in d && typeof (destroy) === "function")
479 return true;
479 return true;
480 return false;
480 return false;
481 }
481 }
482
482
483 export function destroy(d: any) {
483 export function destroy(d: any) {
484 if (d && "destroy" in d)
484 if (d && "destroy" in d)
485 d.destroy();
485 d.destroy();
486 }
486 }
487
487
488 /**
488 /**
489 * Used to mark that the async operation isn't awaited intentionally.
489 * Used to mark that the async operation isn't awaited intentionally.
490 * @param p The promise which represents the async operation.
490 * @param p The promise which represents the async operation.
491 */
491 */
492 export function nowait(p: Promise<any>) {
492 export function nowait(p: Promise<any>) {
493 }
493 }
494
494
495 /** represents already destroyed object.
495 /** represents already destroyed object.
496 */
496 */
497 export const destroyed = {
497 export const destroyed = {
498 /** Calling to this method doesn't affect anything, noop.
498 /** Calling to this method doesn't affect anything, noop.
499 */
499 */
500 destroy() {
500 destroy() {
501 }
501 }
502 };
502 };
@@ -1,14 +1,14
1 import { Services } from "./services";
1 import { Services } from "./services";
2 import { fluent } from "../di/traits";
2 import { fluent } from "../di/traits";
3 import { Box } from "./Box";
3 import { Box } from "./Box";
4
4
5 export default fluent<Services>().register({
5 export default fluent<Services>().configure({
6 host: it => it.value("example.com"),
6 host: it => it.value("example.com"),
7
7
8 foo: it => import("./Foo").then(({ Foo }) => it
8 foo: it => import("./Foo").then(({ Foo }) => it
9 .factory(() => new Foo())
9 .factory(() => new Foo())
10 ),
10 ),
11
11
12 box: it => it
12 box: it => it
13 .factory($dependency => new Box($dependency("foo")))
13 .factory($dependency => new Box($dependency("foo")))
14 });
14 });
@@ -1,15 +1,22
1 import { Foo } from "./Foo";
1 import { Foo } from "./Foo";
2 import { Box } from "./Box";
2 import { Bar } from "./Bar";
3 import { Bar } from "./Bar";
3 import { Box } from "./Box";
4
4
5 /**
5 /**
6 * Сервисы доступные внутри контейнера
6 * Сервисы доступные внутри контейнера
7 */
7 */
8 export interface Services {
8 export interface Services {
9 foo: Foo;
9 foo: Foo;
10
10
11 box: Box<Foo>;
11 box: Box<Foo>;
12
12
13 host: string;
13 host: string;
14
14
15 }
15 }
16
17 export interface ChildServices extends Services {
18
19 foo2?: Foo;
20
21 bar: Bar;
22 }
@@ -1,63 +1,69
1 import { test } from "./TestTraits";
1 import { test } from "./TestTraits";
2 import { fluent } from "../di/traits";
2 import { fluent } from "../di/traits";
3 import { Bar } from "../mock/Bar";
3 import { Bar } from "../mock/Bar";
4 import { Container } from "../di/Container";
4 import { Container } from "../di/Container";
5 import { Foo } from "../mock/Foo";
5 import { Foo } from "../mock/Foo";
6 import { Box } from "../mock/Box";
6 import { Box } from "../mock/Box";
7 import { delay } from "../safe";
7 import { delay } from "../safe";
8 import { Services } from "../mock/services";
8 import { Services } from "../mock/services";
9
9
10 test("Simple fluent config", async t => {
10 test("Simple fluent config", async t => {
11 const config = fluent<{ host: string; bar: Bar; foo: Foo }>()
11 const config = fluent<{ host: string; bar: Bar; foo: Foo }>()
12 .register({
12 .register({
13 host: it => it.value("example.com"),
13 host: it => it.value("example.com"),
14 bar: it => it.factory(resolve => new Bar({ host: resolve("host") }, "s-bar")),
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()))
15 foo: it => import("../mock/Foo").then(m => it.lifetime("container").factory(() => new m.Foo()))
16 });
16 });
17
17
18 const c1 = new Container<{}>();
18 const c1 = new Container<{}>();
19 const container = await config.apply(c1);
19 const container = await config.apply(c1);
20
20
21 t.equal(container.resolve("host"), "example.com", "The value should be resolved");
21 t.equal(container.resolve("host"), "example.com", "The value should be resolved");
22 t.assert(container.resolve("bar"), "The service should de activated");
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");
23 t.equal(container.resolve("foo"), container.resolve("foo"), "The service should be activated once");
24 });
24 });
25
25
26 test("Nested async configuration", async t => {
26 test("Nested async configuration", async t => {
27 const container = await new Container<{
27 const container = await new Container<{
28 foo: Foo;
28 foo: Foo;
29 box: Box<Foo>
29 box: Box<Foo>
30 }>().fluent({
30 }>().fluent({
31 foo: it => delay(0).then(() => it.factory(() => new Foo())),
31 foo: it => delay(0).then(() => it.factory(() => new Foo())),
32 box: it => it.lifetime("context").factory($dependency => new Box($dependency("foo")))
32 box: it => it.lifetime("context").factory($dependency => new Box($dependency("foo")))
33 });
33 });
34
34
35 t.assert(container.resolve("box").getValue(), "The dependency should be set");
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")
36 t.equals(container.resolve("box").getValue(), container.resolve("box").getValue(), "The service should be activated once")
37 });
37 });
38
38
39 test("Bad fluent config", async t => {
39 test("Bad fluent config", async t => {
40 try {
40 try {
41 await new Container<{
41 await new Container<{
42 foo: Foo;
42 foo: Foo;
43 box: Box<Foo>
43 box: Box<Foo>
44 }>().fluent({
44 }>().fluent({
45 foo: it => delay(0).then(() => it.factory(() => new Foo())),
45 foo: it => delay(0).then(() => it.factory(() => new Foo())),
46 box: it => it.lifetime("context")
46 box: it => it.lifetime("context")
47 .override("foo", () => { throw new Error("bad override"); })
47 .override("foo", () => { throw new Error("bad override"); })
48 .factory($dependency => new Box($dependency("foo")))
48 .factory($dependency => new Box($dependency("foo")))
49 });
49 });
50 t.fail("Should throw");
50 t.fail("Should throw");
51 } catch (e) {
51 } catch (e) {
52 t.pass("The configuration should fail");
52 t.pass("The configuration should fail");
53 t.equal(e.message, "bad override", "the error should pass");
53 t.equal(e.message, "bad override", "the error should pass");
54 }
54 }
55 });
55 });
56
56
57 test("Load fluent config", async t => {
57 test("Load fluent config", async t => {
58 const container = new Container<Services>();
58 const container = new Container<Services>();
59
59
60 await container.configure("../mock/config", { contextRequire: require });
60 await container.configure("../mock/config", { contextRequire: require });
61
61
62 t.assert(container.resolve("host"), "Should resolve simple value");
62 t.assert(container.resolve("host"), "Should resolve simple value");
63 });
63 });
64
65 test("Container applyConfig", async t => {
66 const container = await new Container<{}>().applyConfig(import("../mock/config"));
67
68 t.assert(container.resolve("host"), "Should resolve simple value");
69 });
@@ -1,12 +1,12
1 {
1 {
2 "compilerOptions": {
2 "compilerOptions": {
3 "moduleResolution": "node",
3 "moduleResolution": "node",
4 "experimentalDecorators": true,
4 "experimentalDecorators": true,
5 "noEmitOnError": true,
5 "noEmitOnError": true,
6 "listFiles": true,
6 "listFiles": true,
7 "strict": true,
7 //"strict": true,
8 "types": [],
8 "types": [],
9 "target": "ES5",
9 "target": "ES5",
10 "lib": ["es5", "es2015.promise", "es2015.symbol", "es2015.iterable", "dom", "scripthost"]
10 "lib": ["es5", "es2015.promise", "es2015.symbol", "es2015.iterable", "dom", "scripthost"]
11 }
11 }
12 } No newline at end of file
12 }
General Comments 0
You need to be logged in to leave comments. Login now