##// END OF EJS Templates
Added LazyReferenceDescriptor, removed lazy behaviour from ReferenceDescriptor.
cin -
r122:118478dfa903 ioc ts support
parent child
Show More
@@ -0,0 +1,84
1 import { argumentNotEmptyString, each } from "../safe";
2 import { ActivationContext } from "./ActivationContext";
3 import { Descriptor, PartialServiceMap, ContainerResolve, ContainerKeys } from "./interfaces";
4 import { ActivationError } from "./ActivationError";
5
6 export interface ReferenceDescriptorParams<S extends object, K extends ContainerKeys<S>> {
7 name: K;
8 optional?: boolean;
9 default?: ContainerResolve<S, K>;
10 services?: PartialServiceMap<S>;
11 }
12
13 export class LazyReferenceDescriptor<S extends object = any, K extends ContainerKeys<S> = ContainerKeys<S>>
14 implements Descriptor<S, ((args?: PartialServiceMap<S>) => ContainerResolve<S, K>)> {
15
16 _name: K;
17
18 _optional = false;
19
20 _default: ContainerResolve<S, K> | undefined;
21
22 _services: PartialServiceMap<S>;
23
24 constructor(opts: ReferenceDescriptorParams<S, K>) {
25 argumentNotEmptyString(opts && opts.name, "opts.name");
26 this._name = opts.name;
27 this._optional = !!opts.optional;
28 this._default = opts.default;
29
30 this._services = (opts.services || {}) as PartialServiceMap<S>;
31 }
32
33 activate(context: ActivationContext<S>) {
34 // добавляем сервисы
35 if (this._services) {
36 each(this._services, (v, k) => context.register(k, v));
37 }
38
39 const saved = context.clone();
40
41 return (cfg?: PartialServiceMap<S>) => {
42 // защищаем контекст на случай исключения в процессе
43 // активации
44 const ct = saved.clone();
45 try {
46 if (cfg) {
47 each(cfg, (v, k) => ct.register(k, v));
48 }
49
50 return this._optional ? ct.resolve(this._name, this._default) : ct
51 .resolve(this._name);
52 } catch (error) {
53 throw new ActivationError(this._name.toString(), ct.getStack(), error);
54 }
55 };
56
57 }
58
59 toString() {
60 const opts = [];
61 if (this._optional)
62 opts.push("optional");
63
64 opts.push("lazy");
65
66 const parts = [
67 "@ref "
68 ];
69 if (opts.length) {
70 parts.push("{");
71 parts.push(opts.join());
72 parts.push("} ");
73 }
74
75 parts.push(this._name.toString());
76
77 if (this._default !== undefined && this._default !== null) {
78 parts.push(" = ");
79 parts.push(String(this._default));
80 }
81
82 return parts.join("");
83 }
84 }
@@ -1,82 +1,84
1 1 import { primitive } from "../safe";
2 2 import { TypeRegistration } from "./Configuration";
3 3
4 4 export interface InjectOptions {
5 5 lazy?: boolean;
6 6 }
7 7
8 8 export interface Dependency<K extends keyof any> {
9 9 $dependency: K;
10 10
11 11 lazy?: boolean;
12 12
13 13 }
14 14
15 15 export interface Lazy<K extends keyof any> extends Dependency<K> {
16 16 lazy: true;
17 17 }
18 18
19 19 type Compatible<T1, T2> = T2 extends T1 ? any : never;
20 20
21 21 type ExtractService<K, S> = K extends keyof S ? S[K] : K;
22 22
23 23 type ExtractDependency<D, S> = D extends { $dependency: infer K } ?
24 24 D extends { lazy: true } ? () => ExtractService<K, S> : ExtractService<K, S> :
25 25 WalkDependencies<D, S>;
26 26
27 27 type WalkDependencies<D, S> = D extends primitive ? D :
28 28 { [K in keyof D]: ExtractDependency<D[K], S> };
29 29
30 30 export class Builder<T, S extends object> {
31 31 declare<P extends any[]>(...args: P) {
32 32 return <C extends new (...args: ExtractDependency<P, S>) => T>(constructor: C) => {
33 33
34 34 };
35 35 }
36 36
37 37 inject<P extends any[]>(...args: P) {
38 38 return <X extends { [m in M]: (...args: any) => any }, M extends keyof (T | X)>(
39 39 target: X,
40 40 memberName: M,
41 41 descriptor: TypedPropertyDescriptor<Compatible<(...args: ExtractDependency<P, S>) => any, T[M]>>
42 42 ) => {
43 43
44 44 };
45 45 }
46 46
47 47 getDescriptor(): TypeRegistration<new () => T, S> {
48 48 throw new Error();
49 49 }
50 50
51 51 }
52 52
53 53 export interface DependencyOptions<T> {
54 54 optional?: boolean;
55 55 default?: T;
56 56 }
57 57
58 58 export interface LazyDependencyOptions<T> extends DependencyOptions<T> {
59 59 lazy: true;
60 60 }
61 61
62 62 interface Declaration<S extends object> {
63 63 define<T>(): Builder<T, S>;
64 64
65 65 dependency<K extends keyof S>(name: K, opts: LazyDependencyOptions<S[K]>): Lazy<K>;
66 66 dependency<K extends keyof S>(name: K, opts?: DependencyOptions<S[K]>): Dependency<K>;
67 67
68 68 config(): Config<S>;
69 69 }
70 70
71 71 type ServiceModule<T, S extends object, M extends string = "service"> = {
72 72 [m in M]: Builder<T, S>;
73 73 };
74 74
75 type PromiseOrValue<T> = PromiseLike<T> | T;
76
75 77 export interface Config<S extends object, Y extends keyof S = keyof S> {
76 78 register<K extends Y>(name: K, builder: Builder<S[K], S>): Config<S, Exclude<Y, K>>;
77 79 register<K extends Y>(name: K, m: Promise<ServiceModule<S[K], S>>): Config<S, Exclude<Y, K>>;
78 80 register<K extends Y, M extends string>(name: K, m: Promise<ServiceModule<S[K], S, M>>, x: M): Config<S, Exclude<Y, K>>;
79 81
80 82 }
81 83
82 84 export declare function declare<S extends object>(): Declaration<S>;
@@ -1,406 +1,407
1 1 import {
2 2 PartialServiceMap,
3 3 ActivationType,
4 4 ContainerKeys,
5 5 ContainerResolve
6 6 } from "./interfaces";
7 7
8 8 import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get, primitive } from "../safe";
9 9 import { AggregateDescriptor } from "./AggregateDescriptor";
10 10 import { ValueDescriptor } from "./ValueDescriptor";
11 11 import { Container } from "./Container";
12 12 import { ReferenceDescriptor } from "./ReferenceDescriptor";
13 13 import { TypeServiceDescriptor } from "./TypeServiceDescriptor";
14 14 import { FactoryServiceDescriptor } from "./FactoryServiceDescriptor";
15 15 import { TraceSource } from "../log/TraceSource";
16 16 import { ConfigError } from "./ConfigError";
17 17 import { Cancellation } from "../Cancellation";
18 18 import { makeResolver } from "./ResolverHelper";
19 19 import { ICancellation } from "../interfaces";
20 20 import { isDescriptor } from "./traits";
21 import { LazyReferenceDescriptor } from "./LazyReferenceDescriptor";
21 22
22 23 export interface RegistrationScope<S extends object> {
23 24
24 25 /** сервисы, которые регистрируются в контексте активации и таким образом
25 26 * могут переопределять ранее зарегистрированные сервисы. за это свойство
26 27 * нужно платить, кроме того порядок активации будет влиять на результат
27 28 * разрешения зависимостей.
28 29 */
29 30 services?: RegistrationMap<S>;
30 31 }
31 32
32 33 /**
33 34 * Базовый интефейс конфигурации сервисов
34 35 */
35 36 export interface ServiceRegistration<T, S extends object> extends RegistrationScope<S> {
36 37
37 38 activation?: ActivationType;
38 39
39 40 params?: any;
40 41
41 42 inject?: object | object[];
42 43
43 44 cleanup?: ((instance: T) => void) | string;
44 45 }
45 46
46 47 export interface TypeRegistration<C extends new () => any, S extends object> extends ServiceRegistration<InstanceType<C>, S> {
47 48 $type: string | C;
48 49 }
49 50
50 51 export interface FactoryRegistration<F extends () => any, S extends object> extends ServiceRegistration<ReturnType<F>, S> {
51 52 $factory: string | F;
52 53 }
53 54
54 55 export interface ValueRegistration<T> {
55 56 $value: T;
56 57 parse?: boolean;
57 58 }
58 59
59 60 export interface DependencyRegistration<S extends object, K extends ContainerKeys<S> = ContainerKeys<S>> extends RegistrationScope<S> {
60 61 $dependency: K;
61 62 lazy?: boolean;
62 63 optional?: boolean;
63 64 default?: ContainerResolve<S, K>;
64 65 }
65 66
66 67 export interface LazyDependencyRegistration<S extends object, K extends ContainerKeys<S> = ContainerKeys<S>> extends DependencyRegistration<S, K> {
67 68 lazy: true;
68 69 }
69 70
70 71 export type Registration<T, S extends object> = T extends primitive ? T :
71 72 (
72 73 T |
73 74 { [k in keyof T]: Registration<T[k], S> } |
74 75 TypeRegistration<new () => T, S> |
75 76 FactoryRegistration<() => T, S> |
76 77 ValueRegistration<any> |
77 78 DependencyRegistration<S, keyof S>
78 79 );
79 80
80 81 export type RegistrationMap<S extends object> = {
81 82 [k in keyof S]?: Registration<S[k], S>;
82 83 };
83 84
84 85 const _activationTypes: { [k in ActivationType]: number; } = {
85 86 singleton: 1,
86 87 container: 2,
87 88 hierarchy: 3,
88 89 context: 4,
89 90 call: 5
90 91 };
91 92
92 93 export function isTypeRegistration(x: any): x is TypeRegistration<new () => any, any> {
93 94 return (!isPrimitive(x)) && ("$type" in x);
94 95 }
95 96
96 97 export function isFactoryRegistration(x: any): x is FactoryRegistration<() => any, any> {
97 98 return (!isPrimitive(x)) && ("$factory" in x);
98 99 }
99 100
100 101 export function isValueRegistration(x: any): x is ValueRegistration<any> {
101 102 return (!isPrimitive(x)) && ("$value" in x);
102 103 }
103 104
104 105 export function isDependencyRegistration<S extends object>(x: any): x is DependencyRegistration<S, keyof S> {
105 106 return (!isPrimitive(x)) && ("$dependency" in x);
106 107 }
107 108
108 109 export function isActivationType(x: string): x is ActivationType {
109 110 return typeof x === "string" && x in _activationTypes;
110 111 }
111 112
112 113 const trace = TraceSource.get("@implab/core/di/Configuration");
113 114 async function mapAll(data: any[], map?: (v: any, k: number) => any): Promise<any[]>;
114 115 async function mapAll(data: any, map?: (v: any, k: string) => any): Promise<any>;
115 116 async function mapAll(data: any, map?: (v: any, k: any) => any): Promise<any> {
116 117 if (data instanceof Array) {
117 118 return Promise.all(map ? data.map(map) : data);
118 119 } else {
119 120 const keys = Object.keys(data);
120 121
121 122 const o: any = {};
122 123
123 124 await Promise.all(keys.map(async k => {
124 125 const v = map ? map(data[k], k) : data[k];
125 126 o[k] = isPromise(v) ? await v : v;
126 127 }));
127 128
128 129 return o;
129 130 }
130 131 }
131 132
132 133 export type ModuleResolver = (moduleName: string, ct?: ICancellation) => any;
133 134
134 135 export class Configuration<S extends object> {
135 136
136 137 _hasInnerDescriptors = false;
137 138
138 139 readonly _container: Container<S>;
139 140
140 141 _path: Array<string>;
141 142
142 143 _configName: string | undefined;
143 144
144 145 _require: ModuleResolver | undefined;
145 146
146 147 constructor(container: Container<S>) {
147 148 argumentNotNull(container, "container");
148 149 this._container = container;
149 150 this._path = [];
150 151 }
151 152
152 153 async loadConfiguration(moduleName: string, contextRequire?: any, ct = Cancellation.none) {
153 154 argumentNotEmptyString(moduleName, "moduleName");
154 155
155 156 trace.log(
156 157 "loadConfiguration moduleName={0}, contextRequire={1}",
157 158 moduleName,
158 159 contextRequire ? typeof (contextRequire) : "<nil>"
159 160 );
160 161
161 162 this._configName = moduleName;
162 163
163 164 const r = await makeResolver(undefined, contextRequire);
164 165
165 166 const config = await r(moduleName, ct);
166 167
167 168 await this._applyConfiguration(
168 169 config,
169 170 await makeResolver(moduleName, contextRequire),
170 171 ct
171 172 );
172 173 }
173 174
174 175 async applyConfiguration(data: RegistrationMap<S>, contextRequire?: any, ct = Cancellation.none) {
175 176 argumentNotNull(data, "data");
176 177
177 178 await this._applyConfiguration(data, await makeResolver(void (0), contextRequire), ct);
178 179 }
179 180
180 181 async _applyConfiguration(data: RegistrationMap<S>, resolver?: ModuleResolver, ct = Cancellation.none) {
181 182 trace.log("applyConfiguration");
182 183
183 184 this._configName = "$";
184 185
185 186 if (resolver)
186 187 this._require = resolver;
187 188
188 189 let services: PartialServiceMap<S>;
189 190
190 191 try {
191 192 services = await this._visitRegistrations(data, "$");
192 193 } catch (e) {
193 194 throw this._makeError(e);
194 195 }
195 196
196 197 this._container.register(services);
197 198 }
198 199
199 200 _makeError(inner: any) {
200 201 const e = new ConfigError("Failed to load configuration", inner);
201 202 e.configName = this._configName || "<inline>";
202 203 e.path = this._makePath();
203 204 return e;
204 205 }
205 206
206 207 _makePath() {
207 208 return this._path
208 209 .reduce(
209 210 (prev, cur) => typeof cur === "number" ?
210 211 `${prev}[${cur}]` :
211 212 `${prev}.${cur}`
212 213 )
213 214 .toString();
214 215 }
215 216
216 217 async _resolveType(moduleName: string, localName: string) {
217 218 trace.log("resolveType moduleName={0}, localName={1}", moduleName, localName);
218 219 try {
219 220 const m = await this._loadModule(moduleName);
220 221 return localName ? get(localName, m) : m;
221 222 } catch (e) {
222 223 trace.error("Failed to resolve type moduleName={0}, localName={1}", moduleName, localName);
223 224 throw e;
224 225 }
225 226 }
226 227
227 228 _loadModule(moduleName: string) {
228 229 trace.debug("loadModule {0}", moduleName);
229 230 if (!this._require)
230 231 throw new Error("Module loader isn't specified");
231 232
232 233 return this._require(moduleName);
233 234 }
234 235
235 236 async _visitRegistrations(data: RegistrationMap<S>, name: string) {
236 237 this._enter(name);
237 238
238 239 if (data.constructor &&
239 240 data.constructor.prototype !== Object.prototype)
240 241 throw new Error("Configuration must be a simple object");
241 242
242 243 const services = await mapAll(data, async (v, k) => {
243 244 const d = await this._visit(v, k.toString());
244 245 return isDescriptor(d) ? d : new AggregateDescriptor(d);
245 246 }) as PartialServiceMap<S>;
246 247
247 248 this._leave();
248 249
249 250 return services;
250 251 }
251 252
252 253 _enter(name: string) {
253 254 this._path.push(name.toString());
254 255 trace.debug(">{0}", name);
255 256 }
256 257
257 258 _leave() {
258 259 const name = this._path.pop();
259 260 trace.debug("<{0}", name);
260 261 }
261 262
262 263 async _visit(data: any, name: string): Promise<any> {
263 264 if (isPrimitive(data) || isDescriptor(data))
264 265 return data;
265 266
266 267 if (isDependencyRegistration<S>(data)) {
267 268 return this._visitDependencyRegistration(data, name);
268 269 } else if (isValueRegistration(data)) {
269 270 return this._visitValueRegistration(data, name);
270 271 } else if (isTypeRegistration(data)) {
271 272 return this._visitTypeRegistration(data, name);
272 273 } else if (isFactoryRegistration(data)) {
273 274 return this._visitFactoryRegistration(data, name);
274 275 } else if (data instanceof Array) {
275 276 return this._visitArray(data, name);
276 277 }
277 278
278 279 return this._visitObject(data, name);
279 280 }
280 281
281 282 async _visitObject(data: any, name: string) {
282 283 if (data.constructor &&
283 284 data.constructor.prototype !== Object.prototype)
284 285 return new ValueDescriptor(data);
285 286
286 287 this._enter(name);
287 288
288 289 const v = await mapAll(data, delegate(this, "_visit"));
289 290
290 291 // TODO: handle inline descriptors properly
291 292 // const ex = {
292 293 // activate(ctx) {
293 294 // const value = ctx.activate(this.prop, "prop");
294 295 // // some code
295 296 // },
296 297 // // will be turned to ReferenceDescriptor
297 298 // prop: { $dependency: "depName" }
298 299 // };
299 300
300 301 this._leave();
301 302 return v;
302 303 }
303 304
304 305 async _visitArray(data: any[], name: string) {
305 306 if (data.constructor &&
306 307 data.constructor.prototype !== Array.prototype)
307 308 return new ValueDescriptor(data);
308 309
309 310 this._enter(name);
310 311
311 312 const v = await mapAll(data, delegate(this, "_visit"));
312 313 this._leave();
313 314
314 315 return v;
315 316 }
316 317
317 318 _makeServiceParams(data: ServiceRegistration<any, S>) {
318 319 const opts: any = {
319 320 owner: this._container
320 321 };
321 322 if (data.services)
322 323 opts.services = this._visitRegistrations(data.services, "services");
323 324
324 325 if (data.inject) {
325 326 this._enter("inject");
326 327 opts.inject = mapAll(
327 328 data.inject instanceof Array ?
328 329 data.inject :
329 330 [data.inject],
330 331 delegate(this, "_visitObject")
331 332 );
332 333 this._leave();
333 334 }
334 335
335 336 if ("params" in data)
336 337 opts.params = data.params instanceof Array ?
337 338 this._visitArray(data.params, "params") :
338 339 this._visit(data.params, "params");
339 340
340 341 if (data.activation) {
341 342 opts.activation = data.activation;
342 343 }
343 344
344 345 if (data.cleanup)
345 346 opts.cleanup = data.cleanup;
346 347
347 348 return opts;
348 349 }
349 350
350 351 async _visitValueRegistration<T>(data: ValueRegistration<T>, name: string) {
351 352 this._enter(name);
352 353 const d = data.parse ? new AggregateDescriptor(data.$value) : new ValueDescriptor(data.$value);
353 354 this._leave();
354 355 return d;
355 356 }
356 357
357 358 async _visitDependencyRegistration<K extends keyof S>(data: DependencyRegistration<S, K>, name: string) {
358 359 argumentNotEmptyString(data && data.$dependency, "data.$dependency");
359 360 this._enter(name);
360 const d = new ReferenceDescriptor<S, K>({
361 const options = {
361 362 name: data.$dependency,
362 lazy: data.lazy,
363 363 optional: data.optional,
364 364 default: data.default,
365 365 services: data.services && await this._visitRegistrations(data.services, "services")
366 });
366 };
367 const d = data.lazy ? new LazyReferenceDescriptor<S, K>(options) : new ReferenceDescriptor<S, K>(options);
367 368 this._leave();
368 369 return d;
369 370 }
370 371
371 372 async _visitTypeRegistration(data: TypeRegistration<new () => any, S>, name: string) {
372 373 argumentNotNull(data.$type, "data.$type");
373 374 this._enter(name);
374 375
375 376 const opts = this._makeServiceParams(data);
376 377 if (data.$type instanceof Function) {
377 378 opts.type = data.$type;
378 379 } else {
379 380 const [moduleName, typeName] = data.$type.split(":", 2);
380 381 opts.type = this._resolveType(moduleName, typeName);
381 382 }
382 383
383 384 const d = new TypeServiceDescriptor<S, any, any[]>(
384 385 await mapAll(opts)
385 386 );
386 387
387 388 this._leave();
388 389
389 390 return d;
390 391 }
391 392
392 393 async _visitFactoryRegistration(data: FactoryRegistration<() => any, S>, name: string) {
393 394 argumentOfType(data.$factory, Function, "data.$factory");
394 395 this._enter(name);
395 396
396 397 const opts = this._makeServiceParams(data);
397 398 opts.factory = data.$factory;
398 399
399 400 const d = new FactoryServiceDescriptor<S, any, any[]>(
400 401 await mapAll(opts)
401 402 );
402 403
403 404 this._leave();
404 405 return d;
405 406 }
406 407 }
@@ -1,95 +1,68
1 1 import { argumentNotEmptyString, each } from "../safe";
2 2 import { ActivationContext } from "./ActivationContext";
3 3 import { Descriptor, PartialServiceMap, ContainerResolve, ContainerKeys } from "./interfaces";
4 import { ActivationError } from "./ActivationError";
5 4
6 5 export interface ReferenceDescriptorParams<S extends object, K extends ContainerKeys<S>> {
7 6 name: K;
8 lazy?: boolean;
9 7 optional?: boolean;
10 8 default?: ContainerResolve<S, K>;
11 9 services?: PartialServiceMap<S>;
12 10 }
13 11
14 12 export class ReferenceDescriptor<S extends object = any, K extends ContainerKeys<S> = ContainerKeys<S>>
15 implements Descriptor<S, ContainerResolve<S, K> | ((args?: PartialServiceMap<S>) => ContainerResolve<S, K>)> {
13 implements Descriptor<S, ContainerResolve<S, K>> {
16 14
17 15 _name: K;
18 16
19 _lazy = false;
20
21 17 _optional = false;
22 18
23 19 _default: ContainerResolve<S, K> | undefined;
24 20
25 21 _services: PartialServiceMap<S>;
26 22
27 23 constructor(opts: ReferenceDescriptorParams<S, K>) {
28 24 argumentNotEmptyString(opts && opts.name, "opts.name");
29 25 this._name = opts.name;
30 this._lazy = !!opts.lazy;
31 26 this._optional = !!opts.optional;
32 27 this._default = opts.default;
33 28
34 29 this._services = (opts.services || {}) as PartialServiceMap<S>;
35 30 }
36 31
37 32 activate(context: ActivationContext<S>) {
38 33 // добавляем сервисы
39 34 if (this._services) {
40 35 each(this._services, (v, k) => context.register(k, v));
41 36 }
42 37
43 if (this._lazy) {
44 const saved = context.clone();
45
46 return (cfg?: PartialServiceMap<S>) => {
47 // защищаем контекст на случай исключения в процессе
48 // активации
49 const ct = saved.clone();
50 try {
51 if (cfg) {
52 each(cfg, (v, k) => ct.register(k, v));
53 }
38 const res = this._optional ?
39 context.resolve(this._name, this._default) :
40 context.resolve(this._name);
54 41
55 return this._optional ? ct.resolve(this._name, this._default) : ct
56 .resolve(this._name);
57 } catch (error) {
58 throw new ActivationError(this._name.toString(), ct.getStack(), error);
59 }
60 };
61 } else {
62 const v = this._optional ?
63 context.resolve(this._name, this._default) :
64 context.resolve(this._name);
65
66 return v;
67 }
42 return res;
68 43 }
69 44
70 45 toString() {
71 46 const opts = [];
72 47 if (this._optional)
73 48 opts.push("optional");
74 if (this._lazy)
75 opts.push("lazy");
76 49
77 50 const parts = [
78 51 "@ref "
79 52 ];
80 53 if (opts.length) {
81 54 parts.push("{");
82 55 parts.push(opts.join());
83 56 parts.push("} ");
84 57 }
85 58
86 59 parts.push(this._name.toString());
87 60
88 61 if (this._default !== undefined && this._default !== null) {
89 62 parts.push(" = ");
90 63 parts.push(String(this._default));
91 64 }
92 65
93 66 return parts.join("");
94 67 }
95 68 }
@@ -1,6 +1,6
1 1 import { config } from "./services";
2 2
3 3 config()
4 4 .register("bar", import("./Bar"))
5 5 .register("box", import("./Box"), "service");
6 //.register("foo", import("./Foo"), "Foo");
6 // .register("foo", import("./Foo"), "Foo");
General Comments 0
You need to be logged in to leave comments. Login now