##// END OF EJS Templates
working on fluent configuration
cin -
r132:0866c6259285 ioc ts support
parent child
Show More
@@ -0,0 +1,17
1 import { Container } from "../Container";
2 import { ExtractDependency, ServiceRecordBuilder } from "./interfaces";
3
4 export class ConfigBuilder<S extends object, Y extends keyof S = keyof S> {
5 register<K extends Y>(name: K, builder: (t: ServiceRecordBuilder<S[K], S>) => void | Promise<void>): ConfigBuilder<S, Exclude<Y, K>>;
6 register<K extends Y, V>(name: S[K] extends ExtractDependency<V, S> ? K : never, value: V): ConfigBuilder<S, Exclude<Y, K>>;
7 register<K extends Y>(name: K, value: S[K], raw: true): ConfigBuilder<S, Exclude<Y, K>>;
8 register<K extends Y>(name: K, value: any, raw = false): ConfigBuilder<S, Exclude<Y, K>> {
9
10 return this;
11 }
12
13 apply(container: Container<S>): PromiseLike<void> {
14
15 return Promise.resolve();
16 }
17 }
@@ -0,0 +1,17
1 import { RegistrationBuilder } from "./RegistrationBuilder";
2
3 export class ConstructorBuilder<C extends new (...args: any[]) => any, S extends object>
4 extends RegistrationBuilder<InstanceType<C>, S> {
5
6 $type: C;
7
8 _params: any;
9
10 constructor(target: C, params: any) {
11 super();
12 this.$type = target;
13
14 this._params = params;
15 }
16
17 }
@@ -0,0 +1,14
1 import { RegistrationBuilder } from "./RegistrationBuilder";
2
3 export class FactoryBuilder<F extends (...args: any[]) => any, S extends object> extends RegistrationBuilder<ReturnType<F>, S> {
4 $factory: F;
5
6 _params: any;
7
8 constructor(target: F, params: any) {
9 super();
10
11 this.$factory = target;
12 this._params = params;
13 }
14 }
1 NO CONTENT: new file 100644
@@ -1,123 +1,130
1 1 import { TraceSource } from "../log/TraceSource";
2 2 import { argumentNotNull, argumentNotEmptyString } from "../safe";
3 3 import { Descriptor, ContainerServiceMap, ContainerKeys, ContainerResolve } from "./interfaces";
4 4 import { Container } from "./Container";
5 5 import { MapOf } from "../interfaces";
6 6
7 7 const trace = TraceSource.get("@implab/core/di/ActivationContext");
8 8
9 9 export interface ActivationContextInfo {
10 10 name: string;
11 11
12 12 service: string;
13 13
14 14 }
15 15
16 16 export class ActivationContext<S extends object> {
17 17 _cache: MapOf<any>;
18 18
19 19 _services: ContainerServiceMap<S>;
20 20
21 21 _visited: MapOf<any>;
22 22
23 23 _name: string;
24 24
25 25 _service: Descriptor<S, any>;
26 26
27 27 _container: Container<S>;
28 28
29 29 _parent: ActivationContext<S> | undefined;
30 30
31 31 constructor(container: Container<S>, services: ContainerServiceMap<S>, name: string, service: Descriptor<S, any>) {
32 32 this._name = name;
33 33 this._service = service;
34 34 this._visited = {};
35 35 this._cache = {};
36 36 this._services = services;
37 37 this._container = container;
38 38 }
39 39
40 40 getName() {
41 41 return this._name;
42 42 }
43 43
44 44 getContainer() {
45 45 return this._container;
46 46 }
47 47
48 48 resolve<K extends ContainerKeys<S>>(name: K, def?: ContainerResolve<S, K>) {
49 49 const d = this._services[name];
50 50
51 51 if (d !== undefined) {
52 52 return this.activate(d, name.toString());
53 53 } else {
54 54 if (def !== undefined && def !== null)
55 55 return def;
56 56 else
57 57 throw new Error(`Service ${name} not found`);
58 58 }
59 59 }
60 60
61 61 /**
62 62 * registers services local to the the activation context
63 63 *
64 64 * @name{string} the name of the service
65 65 * @service{string} the service descriptor to register
66 66 */
67 67 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>) {
68 68 argumentNotEmptyString(name, "name");
69 69
70 70 this._services[name] = service as any;
71 71 }
72 72
73 73 has(id: string) {
74 74 return id in this._cache;
75 75 }
76 76
77 77 get<T>(id: string) {
78 78 return this._cache[id];
79 79 }
80 80
81 81 store(id: string, value: any) {
82 82 return (this._cache[id] = value);
83 83 }
84 84
85 85 activate<T>(d: Descriptor<S, T>, name: string) {
86 86 if (trace.isLogEnabled())
87 87 trace.log(`enter ${name} ${d}`);
88 88
89 89 const ctx = this.enter(d, name);
90 90 const v = d.activate(ctx);
91 91
92 92 if (trace.isLogEnabled())
93 93 trace.log(`leave ${name}`);
94 94
95 95 return v;
96 96 }
97 97
98 98 visit(id: string) {
99 99 const count = this._visited[id] || 0;
100 100 this._visited[id] = count + 1;
101 101 return count;
102 102 }
103 103
104 104 getStack(): ActivationContextInfo[] {
105 105 const stack = [{
106 106 name: this._name,
107 107 service: this._service.toString()
108 108 }];
109 109
110 110 return this._parent ?
111 111 stack.concat(this._parent.getStack()) :
112 112 stack;
113 113 }
114 114
115 115 private enter(service: Descriptor<S, any>, name: string): this {
116 116 const clone = Object.create(this);
117 117 clone._name = name;
118 118 clone._services = Object.create(this._services);
119 119 clone._parent = this;
120 120 clone._service = service;
121 121 return clone;
122 122 }
123
124 /** Creates a clone for the current context, used to protect it from modifications */
125 clone(): this {
126 const clone = Object.create(this);
127 clone._services = Object.create(this._services);
128 return clone;
129 }
123 130 }
@@ -1,25 +1,30
1 1 import { TypeRegistration } from "./Configuration";
2 2 import { ExtractDependency } from "./fluent/interfaces";
3 import { RegistrationBuilder } from "./fluent/RegistrationBuilder";
3 4
4 5 export class AnnotaionBuilder<T, S extends object> {
5 6 wire<P extends any[]>(...args: P) {
6 7 return <C extends new (...args: ExtractDependency<P, S>) => T>(constructor: C) => {
7 8
8 9 };
9 10 }
10 11
11 12 inject<P extends any[]>(...args: P) {
12 13 return <X extends { [m in M]: (...args: any) => any }, M extends keyof (T | X)>(
13 14 target: X,
14 15 memberName: M,
15 16 descriptor: TypedPropertyDescriptor< T[M] extends ((...args: ExtractDependency<P, S>) => any) ? any : never >
16 17 ) => {
17 18
18 19 };
19 20 }
20 21
21 22 getDescriptor(): TypeRegistration<new () => T, S> {
22 23 throw new Error();
23 24 }
24 25
26 getRegistrationBuilder(): RegistrationBuilder<T, S> {
27 throw new Error();
28 }
29
25 30 }
@@ -1,415 +1,428
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 import { LifetimeManager } from "./LifetimeManager";
22 23
23 24 export interface RegistrationScope<S extends object> {
24 25
25 26 /** сервисы, которые регистрируются в контексте активации и таким образом
26 27 * могут переопределять ранее зарегистрированные сервисы. за это свойство
27 28 * нужно платить, кроме того порядок активации будет влиять на результат
28 29 * разрешения зависимостей.
29 30 */
30 31 services?: RegistrationMap<S>;
31 32 }
32 33
33 34 /**
34 35 * Базовый интефейс конфигурации сервисов
35 36 */
36 37 export interface ServiceRegistration<T, S extends object> extends RegistrationScope<S> {
37 38
38 39 activation?: ActivationType;
39 40
40 41 params?: any;
41 42
42 43 inject?: object | object[];
43 44
44 45 cleanup?: ((instance: T) => void) | string;
45 46 }
46 47
47 48 export interface TypeRegistration<C extends new (...args: any[]) => any, S extends object> extends ServiceRegistration<InstanceType<C>, S> {
48 49 $type: string | C;
49 50 params?: Registration<ConstructorParameters<C>, S>;
50 51 }
51 52
52 53 export interface StrictTypeRegistration<C extends new (...args: any[]) => any, S extends object> extends ServiceRegistration<InstanceType<C>, S> {
53 54 $type: C;
54 55 params?: Registration<ConstructorParameters<C>, S>;
55 56 }
56 57
57 58 export interface FactoryRegistration<F extends (...args: any[]) => any, S extends object> extends ServiceRegistration<ReturnType<F>, S> {
58 59 $factory: string | F;
59 60 }
60 61
61 62 export interface ValueRegistration<T> {
62 63 $value: T;
63 64 parse?: boolean;
64 65 }
65 66
66 67 export interface DependencyRegistration<S extends object, K extends ContainerKeys<S> = ContainerKeys<S>> extends RegistrationScope<S> {
67 68 $dependency: K;
68 69 lazy?: boolean;
69 70 optional?: boolean;
70 71 default?: ContainerResolve<S, K>;
71 72 }
72 73
73 74 export interface LazyDependencyRegistration<S extends object, K extends ContainerKeys<S> = ContainerKeys<S>> extends DependencyRegistration<S, K> {
74 75 lazy: true;
75 76 }
76 77
77 type OfType<K extends keyof S, S, T> = Extract<{ [k in K]: T}, S>;
78
79 78 export type Registration<T, S extends object> = T extends primitive ? T :
80 79 (
81 80 T |
82 81 { [k in keyof T]: Registration<T[k], S> } |
83 82 TypeRegistration<new (...args: any[]) => T, S> |
84 83 FactoryRegistration<(...args: any[]) => T, S> |
85 84 ValueRegistration<any> |
86 85 DependencyRegistration<S, keyof S>
87 86 );
88 87
89 88 export type RegistrationMap<S extends object> = {
90 89 [k in keyof S]?: Registration<S[k], S>;
91 90 };
92 91
93 92 const _activationTypes: { [k in ActivationType]: number; } = {
94 93 singleton: 1,
95 94 container: 2,
96 95 hierarchy: 3,
97 96 context: 4,
98 97 call: 5
99 98 };
100 99
101 100 export function isTypeRegistration(x: any): x is TypeRegistration<new () => any, any> {
102 101 return (!isPrimitive(x)) && ("$type" in x);
103 102 }
104 103
105 104 export function isFactoryRegistration(x: any): x is FactoryRegistration<() => any, any> {
106 105 return (!isPrimitive(x)) && ("$factory" in x);
107 106 }
108 107
109 108 export function isValueRegistration(x: any): x is ValueRegistration<any> {
110 109 return (!isPrimitive(x)) && ("$value" in x);
111 110 }
112 111
113 112 export function isDependencyRegistration<S extends object>(x: any): x is DependencyRegistration<S, keyof S> {
114 113 return (!isPrimitive(x)) && ("$dependency" in x);
115 114 }
116 115
117 116 export function isActivationType(x: string): x is ActivationType {
118 117 return typeof x === "string" && x in _activationTypes;
119 118 }
120 119
121 120 const trace = TraceSource.get("@implab/core/di/Configuration");
122 121 async function mapAll(data: any[], map?: (v: any, k: number) => any): Promise<any[]>;
123 122 async function mapAll(data: any, map?: (v: any, k: string) => any): Promise<any>;
124 123 async function mapAll(data: any, map?: (v: any, k: any) => any): Promise<any> {
125 124 if (data instanceof Array) {
126 125 return Promise.all(map ? data.map(map) : data);
127 126 } else {
128 127 const keys = Object.keys(data);
129 128
130 129 const o: any = {};
131 130
132 131 await Promise.all(keys.map(async k => {
133 132 const v = map ? map(data[k], k) : data[k];
134 133 o[k] = isPromise(v) ? await v : v;
135 134 }));
136 135
137 136 return o;
138 137 }
139 138 }
140 139
141 140 export type ModuleResolver = (moduleName: string, ct?: ICancellation) => any;
142 141
143 142 export class Configuration<S extends object> {
144 143
145 144 _hasInnerDescriptors = false;
146 145
147 146 readonly _container: Container<S>;
148 147
149 148 _path: Array<string>;
150 149
151 150 _configName: string | undefined;
152 151
153 152 _require: ModuleResolver | undefined;
154 153
155 154 constructor(container: Container<S>) {
156 155 argumentNotNull(container, "container");
157 156 this._container = container;
158 157 this._path = [];
159 158 }
160 159
161 160 async loadConfiguration(moduleName: string, contextRequire?: any, ct = Cancellation.none) {
162 161 argumentNotEmptyString(moduleName, "moduleName");
163 162
164 163 trace.log(
165 164 "loadConfiguration moduleName={0}, contextRequire={1}",
166 165 moduleName,
167 166 contextRequire ? typeof (contextRequire) : "<nil>"
168 167 );
169 168
170 169 this._configName = moduleName;
171 170
172 171 const r = await makeResolver(undefined, contextRequire);
173 172
174 173 const config = await r(moduleName, ct);
175 174
176 175 await this._applyConfiguration(
177 176 config,
178 177 await makeResolver(moduleName, contextRequire),
179 178 ct
180 179 );
181 180 }
182 181
183 182 async applyConfiguration(data: RegistrationMap<S>, contextRequire?: any, ct = Cancellation.none) {
184 183 argumentNotNull(data, "data");
185 184
186 185 await this._applyConfiguration(data, await makeResolver(void (0), contextRequire), ct);
187 186 }
188 187
189 188 async _applyConfiguration(data: RegistrationMap<S>, resolver?: ModuleResolver, ct = Cancellation.none) {
190 189 trace.log("applyConfiguration");
191 190
192 191 this._configName = "$";
193 192
194 193 if (resolver)
195 194 this._require = resolver;
196 195
197 196 let services: PartialServiceMap<S>;
198 197
199 198 try {
200 199 services = await this._visitRegistrations(data, "$");
201 200 } catch (e) {
202 201 throw this._makeError(e);
203 202 }
204 203
205 204 this._container.register(services);
206 205 }
207 206
208 207 _makeError(inner: any) {
209 208 const e = new ConfigError("Failed to load configuration", inner);
210 209 e.configName = this._configName || "<inline>";
211 210 e.path = this._makePath();
212 211 return e;
213 212 }
214 213
215 214 _makePath() {
216 215 return this._path
217 216 .reduce(
218 217 (prev, cur) => typeof cur === "number" ?
219 218 `${prev}[${cur}]` :
220 219 `${prev}.${cur}`
221 220 )
222 221 .toString();
223 222 }
224 223
225 224 async _resolveType(moduleName: string, localName: string) {
226 225 trace.log("resolveType moduleName={0}, localName={1}", moduleName, localName);
227 226 try {
228 227 const m = await this._loadModule(moduleName);
229 228 return localName ? get(localName, m) : m;
230 229 } catch (e) {
231 230 trace.error("Failed to resolve type moduleName={0}, localName={1}", moduleName, localName);
232 231 throw e;
233 232 }
234 233 }
235 234
236 235 _loadModule(moduleName: string) {
237 236 trace.debug("loadModule {0}", moduleName);
238 237 if (!this._require)
239 238 throw new Error("Module loader isn't specified");
240 239
241 240 return this._require(moduleName);
242 241 }
243 242
244 243 async _visitRegistrations(data: RegistrationMap<S>, name: string) {
245 244 this._enter(name);
246 245
247 246 if (data.constructor &&
248 247 data.constructor.prototype !== Object.prototype)
249 248 throw new Error("Configuration must be a simple object");
250 249
251 250 const services = await mapAll(data, async (v, k) => {
252 251 const d = await this._visit(v, k.toString());
253 252 return isDescriptor(d) ? d : new AggregateDescriptor(d);
254 253 }) as PartialServiceMap<S>;
255 254
256 255 this._leave();
257 256
258 257 return services;
259 258 }
260 259
261 260 _enter(name: string) {
262 261 this._path.push(name.toString());
263 262 trace.debug(">{0}", name);
264 263 }
265 264
266 265 _leave() {
267 266 const name = this._path.pop();
268 267 trace.debug("<{0}", name);
269 268 }
270 269
271 270 async _visit(data: any, name: string): Promise<any> {
272 271 if (isPrimitive(data) || isDescriptor(data))
273 272 return data;
274 273
275 274 if (isDependencyRegistration<S>(data)) {
276 275 return this._visitDependencyRegistration(data, name);
277 276 } else if (isValueRegistration(data)) {
278 277 return this._visitValueRegistration(data, name);
279 278 } else if (isTypeRegistration(data)) {
280 279 return this._visitTypeRegistration(data, name);
281 280 } else if (isFactoryRegistration(data)) {
282 281 return this._visitFactoryRegistration(data, name);
283 282 } else if (data instanceof Array) {
284 283 return this._visitArray(data, name);
285 284 }
286 285
287 286 return this._visitObject(data, name);
288 287 }
289 288
290 289 async _visitObject(data: any, name: string) {
291 290 if (data.constructor &&
292 291 data.constructor.prototype !== Object.prototype)
293 292 return new ValueDescriptor(data);
294 293
295 294 this._enter(name);
296 295
297 296 const v = await mapAll(data, delegate(this, "_visit"));
298 297
299 298 // TODO: handle inline descriptors properly
300 299 // const ex = {
301 300 // activate(ctx) {
302 301 // const value = ctx.activate(this.prop, "prop");
303 302 // // some code
304 303 // },
305 304 // // will be turned to ReferenceDescriptor
306 305 // prop: { $dependency: "depName" }
307 306 // };
308 307
309 308 this._leave();
310 309 return v;
311 310 }
312 311
313 312 async _visitArray(data: any[], name: string) {
314 313 if (data.constructor &&
315 314 data.constructor.prototype !== Array.prototype)
316 315 return new ValueDescriptor(data);
317 316
318 317 this._enter(name);
319 318
320 319 const v = await mapAll(data, delegate(this, "_visit"));
321 320 this._leave();
322 321
323 322 return v;
324 323 }
325 324
326 325 _makeServiceParams(data: ServiceRegistration<any, S>) {
327 326 const opts: any = {
328 owner: this._container
329 327 };
330 328 if (data.services)
331 329 opts.services = this._visitRegistrations(data.services, "services");
332 330
333 331 if (data.inject) {
334 332 this._enter("inject");
335 333 opts.inject = mapAll(
336 334 data.inject instanceof Array ?
337 335 data.inject :
338 336 [data.inject],
339 337 delegate(this, "_visitObject")
340 338 );
341 339 this._leave();
342 340 }
343 341
344 342 if ("params" in data)
345 343 opts.params = data.params instanceof Array ?
346 344 this._visitArray(data.params, "params") :
347 345 this._visit(data.params, "params");
348 346
349 347 if (data.activation) {
350 opts.activation = data.activation;
348 opts.activation = this._getLifetimeManager(data.activation);
351 349 }
352 350
353 351 if (data.cleanup)
354 352 opts.cleanup = data.cleanup;
355 353
356 354 return opts;
357 355 }
358 356
359 357 async _visitValueRegistration<T>(data: ValueRegistration<T>, name: string) {
360 358 this._enter(name);
361 359 const d = data.parse ? new AggregateDescriptor(data.$value) : new ValueDescriptor(data.$value);
362 360 this._leave();
363 361 return d;
364 362 }
365 363
366 364 async _visitDependencyRegistration<K extends keyof S>(data: DependencyRegistration<S, K>, name: string) {
367 365 argumentNotEmptyString(data && data.$dependency, "data.$dependency");
368 366 this._enter(name);
369 367 const options = {
370 368 name: data.$dependency,
371 369 optional: data.optional,
372 370 default: data.default,
373 371 services: data.services && await this._visitRegistrations(data.services, "services")
374 372 };
375 373 const d = data.lazy ? new LazyReferenceDescriptor<S, K>(options) : new ReferenceDescriptor<S, K>(options);
376 374 this._leave();
377 375 return d;
378 376 }
379 377
380 378 async _visitTypeRegistration(data: TypeRegistration<new () => any, S>, name: string) {
381 379 argumentNotNull(data.$type, "data.$type");
382 380 this._enter(name);
383 381
384 382 const opts = this._makeServiceParams(data);
385 383 if (data.$type instanceof Function) {
386 384 opts.type = data.$type;
387 385 } else {
388 386 const [moduleName, typeName] = data.$type.split(":", 2);
389 387 opts.type = this._resolveType(moduleName, typeName);
390 388 }
391 389
392 390 const d = new TypeServiceDescriptor<S, any, any[]>(
393 391 await mapAll(opts)
394 392 );
395 393
396 394 this._leave();
397 395
398 396 return d;
399 397 }
400 398
401 399 async _visitFactoryRegistration(data: FactoryRegistration<() => any, S>, name: string) {
402 400 argumentOfType(data.$factory, Function, "data.$factory");
403 401 this._enter(name);
404 402
405 403 const opts = this._makeServiceParams(data);
406 404 opts.factory = data.$factory;
407 405
408 406 const d = new FactoryServiceDescriptor<S, any, any[]>(
409 407 await mapAll(opts)
410 408 );
411 409
412 410 this._leave();
413 411 return d;
414 412 }
413
414 _getLifetimeManager(activation: ActivationType) {
415 switch (activation) {
416 case "container":
417 return this._container.getLifetimeManager();
418 case "hierarchy":
419 return LifetimeManager.hierarchyLifetime;
420 case "context":
421 return LifetimeManager.contextLifetime;
422 case "singleton":
423 return LifetimeManager.singletonLifetime;
424 default:
425 return LifetimeManager.empty;
426 }
427 }
415 428 }
@@ -1,84 +1,84
1 1 import { argumentNotEmptyString, each } from "../safe";
2 2 import { ActivationContext } from "./ActivationContext";
3 3 import { Descriptor, PartialServiceMap, ContainerResolve, ContainerKeys } from "./interfaces";
4 4 import { ActivationError } from "./ActivationError";
5 5
6 6 export interface ReferenceDescriptorParams<S extends object, K extends ContainerKeys<S>> {
7 7 name: K;
8 8 optional?: boolean;
9 9 default?: ContainerResolve<S, K>;
10 10 services?: PartialServiceMap<S>;
11 11 }
12 12
13 13 export class LazyReferenceDescriptor<S extends object = any, K extends ContainerKeys<S> = ContainerKeys<S>>
14 14 implements Descriptor<S, ((args?: PartialServiceMap<S>) => ContainerResolve<S, K>)> {
15 15
16 16 _name: K;
17 17
18 18 _optional = false;
19 19
20 20 _default: ContainerResolve<S, K> | undefined;
21 21
22 22 _services: PartialServiceMap<S>;
23 23
24 24 constructor(opts: ReferenceDescriptorParams<S, K>) {
25 25 argumentNotEmptyString(opts && opts.name, "opts.name");
26 26 this._name = opts.name;
27 27 this._optional = !!opts.optional;
28 28 this._default = opts.default;
29 29
30 30 this._services = (opts.services || {}) as PartialServiceMap<S>;
31 31 }
32 32
33 33 activate(context: ActivationContext<S>) {
34 34 // добавляем сервисы
35 35 if (this._services) {
36 36 each(this._services, (v, k) => context.register(k, v));
37 37 }
38 38
39 39 const saved = context.clone();
40 40
41 41 return (cfg?: PartialServiceMap<S>) => {
42 42 // защищаем контекст на случай исключения в процессе
43 43 // активации
44 const ct = saved.clone();
44 const ct = cfg ? saved.clone() : saved;
45 45 try {
46 46 if (cfg) {
47 47 each(cfg, (v, k) => ct.register(k, v));
48 48 }
49 49
50 50 return this._optional ? ct.resolve(this._name, this._default) : ct
51 51 .resolve(this._name);
52 52 } catch (error) {
53 53 throw new ActivationError(this._name.toString(), ct.getStack(), error);
54 54 }
55 55 };
56 56
57 57 }
58 58
59 59 toString() {
60 60 const opts = [];
61 61 if (this._optional)
62 62 opts.push("optional");
63 63
64 64 opts.push("lazy");
65 65
66 66 const parts = [
67 67 "@ref "
68 68 ];
69 69 if (opts.length) {
70 70 parts.push("{");
71 71 parts.push(opts.join());
72 72 parts.push("} ");
73 73 }
74 74
75 75 parts.push(this._name.toString());
76 76
77 77 if (this._default !== undefined && this._default !== null) {
78 78 parts.push(" = ");
79 79 parts.push(String(this._default));
80 80 }
81 81
82 82 return parts.join("");
83 83 }
84 84 }
@@ -1,141 +1,142
1 1 import { IDestroyable, MapOf } from "../interfaces";
2 2 import { argumentNotNull, isDestroyable } from "../safe";
3 3 import { ILifetimeManager, ILifetime } from "./interfaces";
4 4 import { ActivationContext } from "./ActivationContext";
5 5
6 6 function safeCall(item: () => void) {
7 7 try {
8 8 item();
9 9 } catch {
10 10 // silence!
11 11 }
12 12 }
13 13
14 14 const emptyLifetime: ILifetime = {
15 15 has() {
16 16 return false;
17 17 },
18 18
19 19 enter() {
20 20
21 21 },
22 22
23 23 get() {
24 24 throw new Error("The specified item isn't registered with this lifetime manager");
25 25 },
26 26
27 27 store() {
28 28 // does nothing
29 29 }
30 30
31 31 };
32 32
33 33 export class LifetimeManager implements IDestroyable, ILifetimeManager {
34 34 private _cleanup: (() => void)[] = [];
35 35 private _cache: MapOf<any> = {};
36 36 private _destroyed = false;
37 37
38 private _pending: MapOf<boolean> = {};
39
38 40 initialize(id: string): ILifetime {
39 41 const self = this;
40 let pending = false;
41 42 return {
42 43 has() {
43 44 return (id in self._cache);
44 45 },
45 46
46 47 get() {
47 48 const t = self._cache[id];
48 49 if (t === undefined)
49 50 throw new Error(`The item with with the key ${id} isn't found`);
50 51 return t;
51 52 },
52 53
53 54 enter() {
54 if (pending)
55 if (self._pending[id])
55 56 throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`);
56 pending = true;
57 self._pending[id] = true;
57 58 },
58 59
59 60 store(item: any, cleanup?: (item: any) => void) {
60 61 argumentNotNull(id, "id");
61 62 argumentNotNull(item, "item");
62 63
63 64 if (this.has())
64 65 throw new Error(`The item with with the key ${id} already registered with this lifetime manager`);
65 pending = false;
66 delete self._pending[id];
66 67
67 68 self._cache[id] = item;
68 69
69 70 if (self._destroyed)
70 71 throw new Error("Lifetime manager is destroyed");
71 72 if (cleanup) {
72 73 self._cleanup.push(() => cleanup(item));
73 74 } else if (isDestroyable(item)) {
74 75 self._cleanup.push(() => item.destroy());
75 76 }
76 77 }
77 78 };
78 79 }
79 80
80 81 destroy() {
81 82 if (!this._destroyed) {
82 83 this._destroyed = true;
83 84 this._cleanup.forEach(safeCall);
84 85 this._cleanup.length = 0;
85 86 }
86 87 }
87 88
88 89 static readonly empty: ILifetimeManager = {
89 90 initialize(): ILifetime {
90 91 return emptyLifetime;
91 92 },
92 93 destroy() {
93 94 throw new Error("Trying to destroy empty lifetime manager, this is a bug.");
94 95 }
95 96
96 97 };
97 98
98 99 static readonly hierarchyLifetime: ILifetimeManager = {
99 100 initialize(id: string, context: ActivationContext<any>): ILifetime {
100 101 return context.getContainer().getLifetimeManager().initialize(id, context);
101 102 },
102 103 destroy() {
103 104 throw new Error("Trying to destroy hierarchy lifetime manager, this is a bug.");
104 105 }
105 106 };
106 107
107 108 static readonly singletonLifetime: ILifetimeManager = {
108 109 initialize(id: string): ILifetime {
109 110 return singletonLifetimeManager.initialize(id);
110 111 },
111 112 destroy() {
112 113 throw new Error("Trying to destroy singleton lifetime manager, this is a bug.");
113 114 }
114 115 };
115 116
116 117 static readonly contextLifetime: ILifetimeManager = {
117 118 initialize(id: string, context: ActivationContext<any>): ILifetime {
118 119 return {
119 120 enter() {
120 121 if (context.visit(id))
121 122 throw new Error("Cyclic reference detected");
122 123 },
123 124 get() {
124 125 return context.get(id);
125 126 },
126 127 has() {
127 128 return context.has(id);
128 129 },
129 130 store(item: any) {
130 131 context.store(id, item);
131 132 }
132 133
133 134 };
134 135 },
135 136 destroy() {
136 137 throw new Error("Trying to destroy empty lifetime manager, this is a bug.");
137 138 }
138 139 };
139 140 }
140 141
141 142 const singletonLifetimeManager = new LifetimeManager();
@@ -1,164 +1,157
1 1 import { ActivationContext } from "./ActivationContext";
2 2 import { Descriptor, ServiceMap, PartialServiceMap, ILifetimeManager } from "./interfaces";
3 3 import { argumentNotNull, isPrimitive, keys, isNull } from "../safe";
4 4 import { TraceSource } from "../log/TraceSource";
5 5 import { isDescriptor } from "./traits";
6 6 import { LifetimeManager } from "./LifetimeManager";
7 7 import { MatchingMemberKeys } from "../interfaces";
8 8 import { Container } from "./Container";
9 9
10 10 let cacheId = 0;
11 11
12 12 const trace = TraceSource.get("@implab/core/di/ActivationContext");
13 13
14 14 function injectMethod<T, M extends keyof T, S extends object, A>(target: T, method: M, context: ActivationContext<S>, args: A) {
15 15
16 16 const m = target[method];
17 17 if (!m || typeof m !== "function")
18 18 throw new Error("Method '" + method + "' not found");
19 19
20 20 if (args instanceof Array)
21 21 return m.apply(target, _parse(args, context, "." + method));
22 22 else
23 23 return m.call(target, _parse(args, context, "." + method));
24 24 }
25 25
26 26 function makeCleanupCallback<T>(method: Cleaner<T>) {
27 27 if (typeof (method) === "function") {
28 28 return (target: T) => {
29 29 method(target);
30 30 };
31 31 } else {
32 32 return (target: T) => {
33 33 const m = target[method] as any;
34 34 m.apply(target);
35 35 };
36 36 }
37 37 }
38 38
39 39 function _parse(value: any, context: ActivationContext<any>, path: string): any {
40 40 if (isPrimitive(value))
41 41 return value as any;
42 42
43 43 trace.debug("parse {0}", path);
44 44
45 45 if (isDescriptor(value))
46 46 return context.activate(value, path);
47 47
48 48 if (value instanceof Array)
49 49 return value.map((x, i) => _parse(x, context, `${path}[${i}]`)) as any;
50 50
51 51 const t: any = {};
52 52
53 53 keys(value).forEach(p => t[p] = _parse(value[p], context, `${path}.${p}`));
54 54
55 55 return t;
56 56 }
57 57
58 58 export type Cleaner<T> = ((x: T) => void) | MatchingMemberKeys<() => void, T>;
59 59
60 60 export type InjectionSpec<T> = {
61 61 [m in keyof T]?: any;
62 62 };
63 63
64 64 export interface ServiceDescriptorParams<S extends object, T, P extends any[]> {
65 owner: Container<S>;
66
67 65 lifetime?: ILifetimeManager;
68 66
69 67 params?: P;
70 68
71 69 inject?: InjectionSpec<T>[];
72 70
73 71 services?: PartialServiceMap<S>;
74 72
75 73 cleanup?: Cleaner<T>;
76 74 }
77 75
78 76 export class ServiceDescriptor<S extends object, T, P extends any[]> implements Descriptor<S, T> {
79 77 _services: ServiceMap<S>;
80 78
81 79 _params: P | undefined;
82 80
83 81 _inject: InjectionSpec<T>[];
84 82
85 83 _cleanup: ((item: T) => void) | undefined;
86 84
87 85 _cacheId = String(++cacheId);
88 86
89 87 _lifetime = LifetimeManager.empty;
90 88
91 _owner: Container<S>;
92
93 89 constructor(opts: ServiceDescriptorParams<S, T, P>) {
94 argumentNotNull(opts && opts.owner, "opts.owner");
95
96 this._owner = opts.owner;
97 90
98 91 if (opts.lifetime)
99 92 this._lifetime = opts.lifetime;
100 93
101 94 if (!isNull(opts.params))
102 95 this._params = opts.params;
103 96
104 97 this._inject = opts.inject || [];
105 98
106 99 this._services = (opts.services || {}) as ServiceMap<S>;
107 100
108 101 if (opts.cleanup) {
109 102 if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function))
110 103 throw new Error(
111 104 "The cleanup parameter must be either a function or a function name");
112 105
113 106 this._cleanup = makeCleanupCallback(opts.cleanup);
114 107 }
115 108 }
116 109
117 110 activate(context: ActivationContext<S>) {
118 111 const lifetime = this._lifetime.initialize(this._cacheId, context);
119 112
120 113 if (lifetime.has()) {
121 114 return lifetime.get();
122 115 } else {
123 116 lifetime.enter();
124 117 const instance = this._create(context);
125 118 lifetime.store(this._cacheId, this._cleanup);
126 119 return instance;
127 120 }
128 121 }
129 122
130 123 _factory(...params: any[]): T {
131 124 throw Error("Not implemented");
132 125 }
133 126
134 127 _create(context: ActivationContext<S>) {
135 128 trace.debug(`constructing ${context._name}`);
136 129
137 130 if (this._services) {
138 131 keys(this._services).forEach(p => context.register(p, this._services[p]));
139 132 }
140 133
141 134 let instance: T;
142 135
143 136 if (this._params === undefined) {
144 137 instance = this._factory();
145 138 } else if (this._params instanceof Array) {
146 139 instance = this._factory.apply(this, _parse(this._params, context, "args"));
147 140 } else {
148 141 instance = this._factory(_parse(this._params, context, "args"));
149 142 }
150 143
151 144 if (this._inject) {
152 145 this._inject.forEach(spec => {
153 146 for (const m in spec)
154 147 injectMethod(instance, m, context, spec[m]);
155 148 });
156 149 }
157 150 return instance;
158 151 }
159 152
160 153 clone() {
161 154 return Object.create(this);
162 155 }
163 156
164 157 }
@@ -1,42 +1,42
1 1 import { ServiceDescriptor, ServiceDescriptorParams } from "./ServiceDescriptor";
2 import { Constructor, Factory } from "../interfaces";
3 2 import { argumentNotNull, isPrimitive } from "../safe";
4 3
5 4 export interface TypeServiceDescriptorParams<S extends object, T extends object, P extends any[]> extends ServiceDescriptorParams<S, T, P> {
6 type: Constructor<T>;
5 type: new (...args: any[]) => T;
7 6 }
8 7
9 8 export class TypeServiceDescriptor<S extends object, T extends object, P extends any[]> extends ServiceDescriptor<S, T, P> {
10 _type: Constructor;
9 _type: new (...args: any[]) => T;
11 10
12 11 constructor(opts: TypeServiceDescriptorParams<S, T, P>) {
13 12 super(opts);
14 13 argumentNotNull(opts && opts.type, "opts.type");
15 14
16 15 const ctor = this._type = opts.type;
17 16
18 17 if (this._params) {
19 18 if (this._params.length) {
20 19 this._factory = (...args) => {
21 const t = Object.create(ctor.prototype);
20 /*const t = Object.create(ctor.prototype);
22 21 const inst = ctor.apply(t, args);
23 return isPrimitive(inst) ? t : inst;
22 return isPrimitive(inst) ? t : inst;*/
23 return new ctor(...args);
24 24 };
25 25 } else {
26 26 this._factory = arg => {
27 27 return new ctor(arg);
28 28 };
29 29 }
30 30 } else {
31 31 this._factory = () => {
32 32 return new ctor();
33 33 };
34 34 }
35 35
36 36 }
37 37
38 38 toString() {
39 39 // @constructor {singleton} foo/bar/Baz
40 40 return ``;
41 41 }
42 42 }
@@ -1,37 +1,37
1 import { ServiceRecordBuilder, ExtractDependency, RegistrationVisitor } from "./interfaces";
1 import { ServiceRecordBuilder, ExtractDependency, RegistrationVisitor, ServiceRegistration } from "./interfaces";
2 2 import { ActivationType } from "../interfaces";
3 3
4 export class RegistrationBuilder<T, S extends object> {
4 export class RegistrationBuilder<T, S extends object> implements ServiceRegistration {
5 5 private _activationType: ActivationType = "call";
6 6
7 7 private _overrides: { [m in keyof S]?: (...args: any) => void } | undefined;
8 8
9 9
10 10 override<K extends keyof S>(name: K, builder: S[K], raw: true): this;
11 11 override<K extends keyof S>(name: K, builder: (t: ServiceRecordBuilder<S[K], S>) => void): this;
12 12 override<K extends keyof S, V>(name: S[K] extends ExtractDependency<V, S> ? K : never, value: V): this;
13 13 override<K extends keyof S>(name: K, builder: S[K] | ((t: ServiceRecordBuilder<S[K], S>) => void), raw: boolean = false) {
14 14 if (!this._overrides)
15 15 this._overrides = {};
16 16 if (raw) {
17 17
18 18 } else if (builder instanceof Function) {
19 19
20 20 } else {
21 21
22 22 }
23 23 return this;
24 24 }
25 25
26 26 activate(activation: ActivationType) {
27 27 this._activationType = activation;
28 28 return this;
29 29 }
30 30 inject<M extends keyof T, P extends any[]>(member: T[M] extends (...params: ExtractDependency<P, S>) => any ? M : never, ...params: P) {
31 31 return this;
32 32 }
33 33
34 visit(visitor: RegistrationVisitor<S>) {
34 visit(visitor: RegistrationVisitor) {
35 35
36 36 }
37 } No newline at end of file
37 }
@@ -1,88 +1,71
1 1 import { primitive } from "../../safe";
2 2 import { ActivationType } from "../interfaces";
3 3 import { AnnotaionBuilder } from "../Annotations";
4 4 import { LazyDependencyRegistration, DependencyRegistration } from "../Configuration";
5 5 import { Container } from "../Container";
6 6
7 7 export interface DependencyOptions<T> {
8 8 optional?: boolean;
9 9 default?: T;
10 10 }
11 11
12 12 export interface LazyDependencyOptions<T> extends DependencyOptions<T> {
13 13 lazy: true;
14 14 }
15 15
16 16 export type ExtractService<K, S> = K extends keyof S ? S[K] : K;
17 17
18 18 export type ExtractDependency<D, S> = D extends { $dependency: infer K } ?
19 19 D extends { lazy: true } ? () => ExtractService<K, S> : ExtractService<K, S> :
20 20 D extends { $type: new (...args: any[]) => infer I } ? I :
21 21 D extends { $factory: (...args: any[]) => infer R } ? R :
22 22 WalkDependencies<D, S>;
23 23
24 24 export type WalkDependencies<D, S> = D extends primitive ? D :
25 25 { [K in keyof D]: ExtractDependency<D[K], S> };
26 26
27 27 export type ServiceModule<T, S extends object, M extends keyof any = "service"> = {
28 28 [m in M]: AnnotaionBuilder<T, S>;
29 29 };
30 30
31 31 export interface ServiceRecordBuilder<T, S extends object> {
32 32 type<P extends any[], C extends new (...args: ExtractDependency<P, S>) => T>(
33 33 target: C, ...params: P): ConstructorBuilder<C, S>;
34 34 factory<P extends any[], F extends (...args: ExtractDependency<P, S>) => T>(
35 35 target: F, ...params: P): FactoryBuilder<F, S>;
36 36 wired<M extends keyof any>(module: ServiceModule<T, S, M>, m: M): RegistrationBuilder<T, S>;
37 37 wired(module: ServiceModule<T, S>): RegistrationBuilder<T, S>;
38 38 }
39 39
40 export interface RegistrationVisitor<S extends object> {
40 export interface RegistrationVisitor {
41 41 visitDependency(): void;
42 42
43 43 visitObject(): void;
44 44
45 45 visitTypeRegistration(): void;
46 46
47 47 visitFactoryRegistration(): void;
48 48
49 49 }
50 50
51 export interface RegistrationBuilder<T, S extends object> {
52 override<K extends keyof S>(name: K, builder: S[K], raw: true): this;
53 override<K extends keyof S>(name: K, builder: (t: ServiceRecordBuilder<S[K], S>) => void): this;
54 override<K extends keyof S, V>(name: S[K] extends ExtractDependency<V, S> ? K : never, value: V): this;
55
56 activate(activation: ActivationType): this;
57 inject<M extends keyof T, P extends any[]>(member: T[M] extends (...params: ExtractDependency<P, S>) => any ? M : never, ...params: P): this;
58
59 visit(visitor: RegistrationVisitor<S>): void;
60 }
61
62 export interface ConstructorBuilder<C extends new (...args: any[]) => any, S extends object> extends RegistrationBuilder<InstanceType<C>, S> {
63 $type: C;
64 }
65
66 export interface FactoryBuilder<F extends (...args: any[]) => any, S extends object> extends RegistrationBuilder<ReturnType<F>, S> {
67 $factory: F;
51 export interface ServiceRegistration {
52 visit(visitor: RegistrationVisitor): void;
68 53 }
69 54
70 55 export interface ConfigBuilder<S extends object, Y extends keyof S = keyof S> {
71 56 register<K extends Y>(name: K, builder: (t: ServiceRecordBuilder<S[K], S>) => void | Promise<void>): ConfigBuilder<S, Exclude<Y, K>>;
72 57 register<K extends Y, V>(name: S[K] extends ExtractDependency<V, S> ? K : never, value: V): ConfigBuilder<S, Exclude<Y, K>>;
73 58 register<K extends Y>(name: K, value: S[K], raw: true): ConfigBuilder<S, Exclude<Y, K>>;
74 59
75 60 apply(container: Container<S>): Promise<void>;
76 61 }
77 62
78 interface ServicesDeclaration<S extends object> {
63 export interface ServicesDeclaration<S extends object> {
79 64 build<T>(this: void): ServiceRecordBuilder<T, S>;
80 65 annotate<T>(this: void): AnnotaionBuilder<T, S>;
81 66
82 67 dependency<K extends keyof S>(this: void, name: K, opts: LazyDependencyOptions<S[K]>): LazyDependencyRegistration<S, K>;
83 68 dependency<K extends keyof S>(this: void, name: K, opts?: DependencyOptions<S[K]>): DependencyRegistration<S, K>;
84 69
85 70 configure(): ConfigBuilder<S>;
86 71 }
87
88 export declare function declare<S extends object>(): ServicesDeclaration<S>;
@@ -1,53 +1,53
1 1 import { ActivationContext } from "./ActivationContext";
2 2 import { IDestroyable } from "../interfaces";
3 3
4 4 export interface Descriptor<S extends object = any, T = any> {
5 5 activate(context: ActivationContext<S>): T;
6 6 }
7 7
8 8 export type ServiceMap<S extends object> = {
9 9 [k in keyof S]: Descriptor<S, S[k]>;
10 10 };
11 11
12 12 export type ContainerKeys<S extends object> = keyof S | keyof ContainerProvided<S>;
13 13
14 14 export type ContainerResolve<S extends object, K> =
15 15 K extends keyof ContainerProvided<S> ? ContainerProvided<S>[K] :
16 16 K extends keyof S ? S[K] : never;
17 17
18 18 export type ContainerServiceMap<S extends object> = {
19 19 [K in ContainerKeys<S>]: Descriptor<S, ContainerResolve<S, K>>;
20 20 };
21 21
22 22 export type PartialServiceMap<S extends object> = {
23 23 [k in keyof S]?: Descriptor<S, S[k]>;
24 24 };
25 25
26 26 export interface Resolver<S extends object> {
27 27 resolve<K extends ContainerKeys<S>>(name: K, def?: ContainerResolve<S, K>): ContainerResolve<S, K>;
28 28 }
29 29
30 30 export interface ContainerProvided<S extends object> {
31 31 container: Resolver<S>;
32 32 }
33 33
34 34 export type ContainerRegistered<S extends object> = /*{
35 35 [K in Exclude<keyof S, keyof ContainerProvided<S>>]: S[K];
36 36 };*/
37 37 Exclude<S, ContainerProvided<S>>;
38 38
39 39 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
40 40
41 41 export interface ILifetimeManager extends IDestroyable {
42 42 initialize(id: string, context: ActivationContext<any>): ILifetime;
43 43 }
44 44
45 45 export interface ILifetime {
46 46 has(): boolean;
47 47
48 48 get(): any;
49 49
50 50 enter(): void;
51 51
52 52 store(item: any, cleanup?: (item: any) => void): void;
53 } No newline at end of file
53 }
@@ -1,7 +1,50
1 1 import { isPrimitive } from "../safe";
2 2 import { Descriptor } from "./interfaces";
3 import { ServicesDeclaration, ServiceRecordBuilder, ServiceModule, RegistrationBuilder, ExtractDependency } from "./fluent/interfaces";
4 import { AnnotaionBuilder } from "./Annotations";
5 import { FactoryBuilder } from "./fluent/FactoryBuilder";
6 import { ConstructorBuilder } from "./fluent/ConstructorBuiler";
3 7
4 8 export function isDescriptor(x: any): x is Descriptor {
5 9 return (!isPrimitive(x)) &&
6 10 (x.activate instanceof Function);
7 } No newline at end of file
11 }
12
13 export function declare<S extends object>(): ServicesDeclaration<S> {
14 return {
15 annotate<T>() {
16 return new AnnotaionBuilder<T, S>();
17 },
18 build<T>(): ServiceRecordBuilder<T, S> {
19 return {
20 factory<P extends any[], F extends (...args: ExtractDependency<P, S>) => T>(
21 target: F,
22 ...params: P
23 ): FactoryBuilder<F, S> {
24 return new FactoryBuilder(target, params);
25 },
26
27 type<P extends any[], C extends new (...args: ExtractDependency<P, S>) => T>(
28 target: C, ...params: P
29 ): ConstructorBuilder<C, S> {
30 return new ConstructorBuilder(target, params);
31 },
32
33 wired<M extends keyof any>(module: ServiceModule<T, S, M>, m?: M): RegistrationBuilder<T, S> {
34 const service = m ?
35 module[m] :
36 (module as ServiceModule<T, S>).service;
37 if (!service)
38 throw new Error("The specified module doen's provides a service annotation");
39 return service.getRegistrationBuilder();
40 }
41 };
42 },
43 configure() {
44 throw new Error();
45 },
46 dependency() {
47 throw new Error();
48 }
49 };
50 }
@@ -1,1 +1,1
1 export { ConsoleLogger as ConsoleWriter } from "./ConsoleLogger"; No newline at end of file
1 export { ConsoleLogger as ConsoleWriter } from "./ConsoleLogger";
General Comments 0
You need to be logged in to leave comments. Login now