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