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