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