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