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