##// END OF EJS Templates
configuration interfaces moved to di/Configuration module...
cin -
r118:6738ac4c3072 ioc ts support
parent child
Show More
@@ -0,0 +1,7
1 import { isPrimitive } from "../safe";
2 import { Descriptor } from "./interfaces";
3
4 export function isDescriptor(x: any): x is Descriptor {
5 return (!isPrimitive(x)) &&
6 (x.activate instanceof Function);
7 }
@@ -1,35 +1,36
1 import { Descriptor, isDescriptor, Parse } from "./interfaces";
1 import { Descriptor } from "./interfaces";
2 import { ActivationContext } from "./ActivationContext";
2 import { ActivationContext } from "./ActivationContext";
3 import { isPrimitive } from "../safe";
3 import { isPrimitive } from "../safe";
4 import { isDescriptor } from "./traits";
4
5
5 export class AggregateDescriptor<T> implements Descriptor<Parse<T>> {
6 export class AggregateDescriptor<S, T> implements Descriptor<S, T> {
6 _value: T;
7 _value: any;
7
8
8 constructor(value: T) {
9 constructor(value: any) {
9 this._value = value;
10 this._value = value;
10 }
11 }
11
12
12 activate<S>(context: ActivationContext<S>) {
13 activate(context: ActivationContext<S>): T {
13 return this._parse(this._value, context, "$value");
14 return this._parse(this._value, context, "$value");
14 }
15 }
15
16
16 _parse<S, V>(value: V, context: ActivationContext<S>, path: string): Parse<V> {
17 _parse(value: any, context: ActivationContext<S>, path: string): any {
17 if (isPrimitive(value))
18 if (isPrimitive(value))
18 return value as any;
19 return value as any;
19
20
20 if (isDescriptor(value))
21 if (isDescriptor(value))
21 return context.activate(value, path);
22 return context.activate(value, path);
22
23
23 if (value instanceof Array)
24 if (value instanceof Array)
24 return value.map((x, i) => this._parse(x, context, `${path}[${i}]`)) as any;
25 return value.map((x, i) => this._parse(x, context, `${path}[${i}]`)) as any;
25
26
26 const t: any = {};
27 const t: any = {};
27 for (const p in value)
28 for (const p in value)
28 t[p] = this._parse(value[p], context, `${path}.${p}`);
29 t[p] = this._parse(value[p], context, `${path}.${p}`);
29 return t;
30 return t;
30 }
31 }
31
32
32 toString() {
33 toString() {
33 return "@walk";
34 return "@walk";
34 }
35 }
35 }
36 }
@@ -1,58 +1,75
1 import { Constructor } from "../interfaces";
1 import { Constructor } from "../interfaces";
2 import { primitive } from "../safe";
2
3
3 export interface InjectOptions {
4 export interface InjectOptions {
4 lazy?: boolean;
5 lazy?: boolean;
5 }
6 }
6
7
7 interface Dependency<K extends keyof any> {
8 interface Dependency<K extends keyof any> {
8 $dependency: K;
9 $dependency: K;
9
10
10 lazy?: boolean;
11 lazy?: boolean;
11 }
12 }
12
13
13 interface Lazy<K extends keyof any> extends Dependency<K> {
14 interface Lazy<K extends keyof any> extends Dependency<K> {
14 lazy: true;
15 lazy: true;
15 }
16 }
16
17
17 type Setter<T = any> = (v: T) => void;
18
19 type Compatible<T1, T2> = T1 extends T2 ? any : never;
18 type Compatible<T1, T2> = T1 extends T2 ? any : never;
20
19
21 type ExtractService<K, S> = K extends keyof S ? S[K] : K;
20 type ExtractService<K, S> = K extends keyof S ? S[K] : K;
22
21
23 type ExtractDependency<D, S> = D extends { $dependency: infer K } ? D extends { lazy: true } ? () => ExtractService<K, S> : ExtractService<K, S> : VisitDependency<D, S>;
22 type ExtractDependency<D, S> = D extends { $dependency: infer K } ?
23 D extends { lazy: true } ? () => ExtractService<K, S> : ExtractService<K, S> :
24 WalkDependencies<D, S>;
24
25
25 type VisitDependency<D, S> = D extends {} ? { [K in keyof D]: ExtractDependency<D[K], S> } : D;
26 type WalkDependencies<D, S> = D extends primitive ? D :
27 { [K in keyof D]: ExtractDependency<D[K], S> };
26
28
27 interface Config<S> {
29 interface Services<S> {
28 dependency<K extends keyof S>(name: K): Dependency<K>;
30 get<K extends keyof S>(name: K): Dependency<K>;
29
31
30 lazy<K extends keyof S>(name: K): Lazy<K>;
32 lazy<K extends keyof S>(name: K): Lazy<K>;
31
33
32 build<T>(): Builder<T, S>;
34 build<T extends object>(): Builder<T, S>;
33 }
35 }
34
36
35 export declare function services<S extends object>(): Config<S>;
37 export declare function services<S extends object>(): Services<S>;
38
39 export declare function build<T = never, S = any>(): Builder<T, S>;
36
40
37 export class Builder<T, S> {
41 export class Builder<T, S> {
38 consume<P extends any[]>(...args: P) {
42 consume<P extends any[]>(...args: P) {
39 return <C extends new (...args: ExtractDependency<P, S>) => T>(constructor: C) => {
43 return <C extends new (...args: ExtractDependency<P, S>) => T>(constructor: C) => {
40 };
44 };
41 }
45 }
42
46
43 inject<K extends keyof S>(dependency: K) {
47 inject<P extends any[]>(...args: P) {
44 // K = "bar"
48 // K = "bar"
45 // M = "setValue"
49 // M = "setValue"
46 // S[K] = Bar
50 // S[K] = Bar
47 // T[M] = (value: string) => void
51 // T[M] = (value: string) => void
48 // P[m] = (value: V) => void
52 // P[m] = (value: V) => void
49 return <P, M extends keyof (T | P)>(target: P, memberName: M, descriptor: TypedPropertyDescriptor<Compatible<T[M], Setter<S[K]>>>) => {
53 return <X extends { [m in M]: (...args: any) => any }, M extends keyof (T | X)>(
54 target: X,
55 memberName: M,
56 descriptor: TypedPropertyDescriptor<Compatible<(...args: ExtractDependency<P, S>) => any, T[M]>>
57 ) => {
50
58
51 };
59 };
52 }
60 }
53
61
54 cast<T2 extends T>(): Builder<T2, S> {
62 cast<T2 extends T>(): Builder<T2, S> {
55 return this as Builder<T2, S>;
63 return this as Builder<T2, S>;
56 }
64 }
57
65
66 get<K extends keyof S>(name: K): Dependency<K> {
67 throw new Error();
58 }
68 }
69
70 lazy<K extends keyof S>(name: K): Lazy<K> {
71 throw new Error();
72 }
73
74
75 }
@@ -1,350 +1,390
1 import {
1 import {
2 ServiceRegistration,
2 PartialServiceMap,
3 TypeRegistration,
3 ActivationType
4 FactoryRegistration,
5 ServiceMap,
6 isDescriptor,
7 isDependencyRegistration,
8 DependencyRegistration,
9 ValueRegistration,
10 ActivationType,
11 isValueRegistration,
12 isTypeRegistration,
13 isFactoryRegistration,
14 PartialServiceMap
15 } from "./interfaces";
4 } from "./interfaces";
16
5
17 import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get } from "../safe";
6 import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get } from "../safe";
18 import { AggregateDescriptor } from "./AggregateDescriptor";
7 import { AggregateDescriptor } from "./AggregateDescriptor";
19 import { ValueDescriptor } from "./ValueDescriptor";
8 import { ValueDescriptor } from "./ValueDescriptor";
20 import { Container } from "./Container";
9 import { Container } from "./Container";
21 import { ReferenceDescriptor } from "./ReferenceDescriptor";
10 import { ReferenceDescriptor } from "./ReferenceDescriptor";
22 import { TypeServiceDescriptor } from "./TypeServiceDescriptor";
11 import { TypeServiceDescriptor } from "./TypeServiceDescriptor";
23 import { FactoryServiceDescriptor } from "./FactoryServiceDescriptor";
12 import { FactoryServiceDescriptor } from "./FactoryServiceDescriptor";
24 import { TraceSource } from "../log/TraceSource";
13 import { TraceSource } from "../log/TraceSource";
25 import { ConfigError } from "./ConfigError";
14 import { ConfigError } from "./ConfigError";
26 import { Cancellation } from "../Cancellation";
15 import { Cancellation } from "../Cancellation";
27 import { makeResolver } from "./ResolverHelper";
16 import { makeResolver } from "./ResolverHelper";
28 import { ICancellation } from "../interfaces";
17 import { ICancellation, Constructor, Factory } from "../interfaces";
18 import { isDescriptor } from "./traits";
19
20 export interface RegistrationScope<S> {
21
22 /** сСрвисы, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Ρ€Π΅Π³ΠΈΡΡ‚Ρ€ΠΈΡ€ΡƒΡŽΡ‚ΡΡ Π² контСкстС Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ ΠΈ Ρ‚Π°ΠΊΠΈΠΌ ΠΎΠ±Ρ€Π°Π·ΠΎΠΌ
23 * ΠΌΠΎΠ³ΡƒΡ‚ ΠΏΠ΅Ρ€Π΅ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΡΡ‚ΡŒ Ρ€Π°Π½Π΅Π΅ зарСгистрированныС сСрвисы. Π·Π° это свойство
24 * Π½ΡƒΠΆΠ½ΠΎ ΠΏΠ»Π°Ρ‚ΠΈΡ‚ΡŒ, ΠΊΡ€ΠΎΠΌΠ΅ Ρ‚ΠΎΠ³ΠΎ порядок Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ Π±ΡƒΠ΄Π΅Ρ‚ Π²Π»ΠΈΡΡ‚ΡŒ Π½Π° Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚
25 * Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Π½ΠΈΡ зависимостСй.
26 */
27 services?: PartialServiceMap<S>;
28 }
29
30 /**
31 * Π‘Π°Π·ΠΎΠ²Ρ‹ΠΉ интСфСйс ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ сСрвисов
32 */
33 export interface ServiceRegistration<T, P, S> extends RegistrationScope<S> {
34
35 activation?: ActivationType;
36
37 params?: P;
38
39 inject?: object | object[];
40
41 cleanup?: ((instance: T) => void) | string;
42 }
43
44 export interface TypeRegistration<T, P extends any[], S> extends ServiceRegistration<T, P, S> {
45 $type: string | (new (...params: P) => T);
46
47 }
48
49 export interface FactoryRegistration<T, P extends any[], S> extends ServiceRegistration<T, P, S> {
50 $factory: string | ( (...params: P) => T);
51 }
52
53 export interface ValueRegistration<T> {
54 $value: T;
55 parse?: boolean;
56 }
57
58 export interface DependencyRegistration<S, K extends keyof S> extends RegistrationScope<S> {
59 $dependency: K;
60 lazy?: boolean;
61 optional?: boolean;
62 default?: S[K];
63 }
64
65 const _activationTypes: { [k in ActivationType]: number; } = {
66 singleton: 1,
67 container: 2,
68 hierarchy: 3,
69 context: 4,
70 call: 5
71 };
72
73 export function isTypeRegistration(x: any): x is TypeRegistration<any, any, any> {
74 return (!isPrimitive(x)) && ("$type" in x);
75 }
76
77 export function isFactoryRegistration(x: any): x is FactoryRegistration<any, any, any> {
78 return (!isPrimitive(x)) && ("$factory" in x);
79 }
80
81 export function isValueRegistration(x: any): x is ValueRegistration<any> {
82 return (!isPrimitive(x)) && ("$value" in x);
83 }
84
85 export function isDependencyRegistration<S>(x: any): x is DependencyRegistration<S, keyof S> {
86 return (!isPrimitive(x)) && ("$dependency" in x);
87 }
88
89 export function isActivationType(x: string): x is ActivationType {
90 return typeof x === "string" && x in _activationTypes;
91 }
29
92
30 const trace = TraceSource.get("@implab/core/di/Configuration");
93 const trace = TraceSource.get("@implab/core/di/Configuration");
31 async function mapAll(data: any[], map?: (v: any, k: number) => any): Promise<any[]>;
94 async function mapAll(data: any[], map?: (v: any, k: number) => any): Promise<any[]>;
32 async function mapAll(data: any, map?: (v: any, k: string) => any): Promise<any>;
95 async function mapAll(data: any, map?: (v: any, k: string) => any): Promise<any>;
33 async function mapAll(data: any, map?: (v: any, k: any) => any): Promise<any> {
96 async function mapAll(data: any, map?: (v: any, k: any) => any): Promise<any> {
34 if (data instanceof Array) {
97 if (data instanceof Array) {
35 return Promise.all(map ? data.map(map) : data);
98 return Promise.all(map ? data.map(map) : data);
36 } else {
99 } else {
37 const keys = Object.keys(data);
100 const keys = Object.keys(data);
38
101
39 const o: any = {};
102 const o: any = {};
40
103
41 await Promise.all(keys.map(async k => {
104 await Promise.all(keys.map(async k => {
42 const v = map ? map(data[k], k) : data[k];
105 const v = map ? map(data[k], k) : data[k];
43 o[k] = isPromise(v) ? await v : v;
106 o[k] = isPromise(v) ? await v : v;
44 }));
107 }));
45
108
46 return o;
109 return o;
47 }
110 }
48 }
111 }
49
112
50 export type ModuleResolver = (moduleName: string, ct?: ICancellation) => any;
113 export type ModuleResolver = (moduleName: string, ct?: ICancellation) => any;
51
114
52 export class Configuration<S> {
115 export class Configuration<S> {
53
116
54 _hasInnerDescriptors = false;
117 _hasInnerDescriptors = false;
55
118
56 _container: Container<S>;
119 _container: Container<S>;
57
120
58 _path: Array<string>;
121 _path: Array<string>;
59
122
60 _configName: string | undefined;
123 _configName: string | undefined;
61
124
62 _require: ModuleResolver | undefined;
125 _require: ModuleResolver | undefined;
63
126
64 constructor(container: Container<S>) {
127 constructor(container: Container<S>) {
65 argumentNotNull(container, "container");
128 argumentNotNull(container, "container");
66 this._container = container;
129 this._container = container;
67 this._path = [];
130 this._path = [];
68 }
131 }
69
132
70 async loadConfiguration(moduleName: string, contextRequire?: any, ct = Cancellation.none) {
133 async loadConfiguration(moduleName: string, contextRequire?: any, ct = Cancellation.none) {
71 argumentNotEmptyString(moduleName, "moduleName");
134 argumentNotEmptyString(moduleName, "moduleName");
72
135
73 trace.log(
136 trace.log(
74 "loadConfiguration moduleName={0}, contextRequire={1}",
137 "loadConfiguration moduleName={0}, contextRequire={1}",
75 moduleName,
138 moduleName,
76 contextRequire ? typeof (contextRequire) : "<nil>"
139 contextRequire ? typeof (contextRequire) : "<nil>"
77 );
140 );
78
141
79 this._configName = moduleName;
142 this._configName = moduleName;
80
143
81 const r = await makeResolver(undefined, contextRequire);
144 const r = await makeResolver(undefined, contextRequire);
82
145
83 const config = await r(moduleName, ct);
146 const config = await r(moduleName, ct);
84
147
85 await this._applyConfiguration(
148 await this._applyConfiguration(
86 config,
149 config,
87 await makeResolver(moduleName, contextRequire),
150 await makeResolver(moduleName, contextRequire),
88 ct
151 ct
89 );
152 );
90 }
153 }
91
154
92 async applyConfiguration(data: object, contextRequire?: any, ct = Cancellation.none) {
155 async applyConfiguration(data: object, contextRequire?: any, ct = Cancellation.none) {
93 argumentNotNull(data, "data");
156 argumentNotNull(data, "data");
94
157
95 await this._applyConfiguration(data, await makeResolver(void (0), contextRequire), ct);
158 await this._applyConfiguration(data, await makeResolver(void (0), contextRequire), ct);
96 }
159 }
97
160
98 async _applyConfiguration(data: object, resolver?: ModuleResolver, ct = Cancellation.none) {
161 async _applyConfiguration(data: object, resolver?: ModuleResolver, ct = Cancellation.none) {
99 trace.log("applyConfiguration");
162 trace.log("applyConfiguration");
100
163
101 this._configName = "$";
164 this._configName = "$";
102
165
103 if (resolver)
166 if (resolver)
104 this._require = resolver;
167 this._require = resolver;
105
168
106 let services: PartialServiceMap<S>;
169 let services: PartialServiceMap<S>;
107
170
108 try {
171 try {
109 services = await this._visitRegistrations(data, "$");
172 services = await this._visitRegistrations(data, "$");
110 } catch (e) {
173 } catch (e) {
111 throw this._makeError(e);
174 throw this._makeError(e);
112 }
175 }
113
176
114 this._container.register(services);
177 this._container.register(services);
115 }
178 }
116
179
117 _makeError(inner: any) {
180 _makeError(inner: any) {
118 const e = new ConfigError("Failed to load configuration", inner);
181 const e = new ConfigError("Failed to load configuration", inner);
119 e.configName = this._configName || "<inline>";
182 e.configName = this._configName || "<inline>";
120 e.path = this._makePath();
183 e.path = this._makePath();
121 return e;
184 return e;
122 }
185 }
123
186
124 _makePath() {
187 _makePath() {
125 return this._path
188 return this._path
126 .reduce(
189 .reduce(
127 (prev, cur) => typeof cur === "number" ?
190 (prev, cur) => typeof cur === "number" ?
128 `${prev}[${cur}]` :
191 `${prev}[${cur}]` :
129 `${prev}.${cur}`
192 `${prev}.${cur}`
130 )
193 )
131 .toString();
194 .toString();
132 }
195 }
133
196
134 async _resolveType(moduleName: string, localName: string) {
197 async _resolveType(moduleName: string, localName: string) {
135 trace.log("resolveType moduleName={0}, localName={1}", moduleName, localName);
198 trace.log("resolveType moduleName={0}, localName={1}", moduleName, localName);
136 try {
199 try {
137 const m = await this._loadModule(moduleName);
200 const m = await this._loadModule(moduleName);
138 return localName ? get(localName, m) : m;
201 return localName ? get(localName, m) : m;
139 } catch (e) {
202 } catch (e) {
140 trace.error("Failed to resolve type moduleName={0}, localName={1}", moduleName, localName);
203 trace.error("Failed to resolve type moduleName={0}, localName={1}", moduleName, localName);
141 throw e;
204 throw e;
142 }
205 }
143 }
206 }
144
207
145 _loadModule(moduleName: string) {
208 _loadModule(moduleName: string) {
146 trace.debug("loadModule {0}", moduleName);
209 trace.debug("loadModule {0}", moduleName);
147 if (!this._require)
210 if (!this._require)
148 throw new Error("Module loader isn't specified");
211 throw new Error("Module loader isn't specified");
149
212
150 return this._require(moduleName);
213 return this._require(moduleName);
151 }
214 }
152
215
153 async _visitRegistrations(data: any, name: string) {
216 async _visitRegistrations(data: any, name: string) {
154 this._enter(name);
217 this._enter(name);
155
218
156 if (data.constructor &&
219 if (data.constructor &&
157 data.constructor.prototype !== Object.prototype)
220 data.constructor.prototype !== Object.prototype)
158 throw new Error("Configuration must be a simple object");
221 throw new Error("Configuration must be a simple object");
159
222
160 const o: PartialServiceMap<S> = {};
223 const o: PartialServiceMap<S> = {};
161 const keys = Object.keys(data);
224 const keys = Object.keys(data);
162
225
163 const services = await mapAll(data, async (v, k) => {
226 const services = await mapAll(data, async (v, k) => {
164 const d = await this._visit(v, k.toString());
227 const d = await this._visit(v, k.toString());
165 return isDescriptor(d) ? d : new AggregateDescriptor(d);
228 return isDescriptor(d) ? d : new AggregateDescriptor(d);
166 }) as PartialServiceMap<S>;
229 }) as PartialServiceMap<S>;
167
230
168 this._leave();
231 this._leave();
169
232
170 return services;
233 return services;
171 }
234 }
172
235
173 _enter(name: string) {
236 _enter(name: string) {
174 this._path.push(name.toString());
237 this._path.push(name.toString());
175 trace.debug(">{0}", name);
238 trace.debug(">{0}", name);
176 }
239 }
177
240
178 _leave() {
241 _leave() {
179 const name = this._path.pop();
242 const name = this._path.pop();
180 trace.debug("<{0}", name);
243 trace.debug("<{0}", name);
181 }
244 }
182
245
183 async _visit<T>(data: T, name: string): Promise<any> {
246 async _visit(data: any, name: string): Promise<any> {
184 if (isPrimitive(data) || isDescriptor(data))
247 if (isPrimitive(data) || isDescriptor(data))
185 return data;
248 return data;
186
249
187 if (isDependencyRegistration<S>(data)) {
250 if (isDependencyRegistration<S>(data)) {
188 return this._visitDependencyRegistration(data, name);
251 return this._visitDependencyRegistration(data, name);
189 } else if (isValueRegistration(data)) {
252 } else if (isValueRegistration(data)) {
190 return this._visitValueRegistration(data, name);
253 return this._visitValueRegistration(data, name);
191 } else if (isTypeRegistration(data)) {
254 } else if (isTypeRegistration(data)) {
192 return this._visitTypeRegistration(data, name);
255 return this._visitTypeRegistration(data, name);
193 } else if (isFactoryRegistration(data)) {
256 } else if (isFactoryRegistration(data)) {
194 return this._visitFactoryRegistration(data, name);
257 return this._visitFactoryRegistration(data, name);
195 } else if (data instanceof Array) {
258 } else if (data instanceof Array) {
196 return this._visitArray(data, name);
259 return this._visitArray(data, name);
197 }
260 }
198
261
199 return this._visitObject(data as T & object, name);
262 return this._visitObject(data, name);
200 }
263 }
201
264
202 async _visitObject<T extends object>(data: T, name: string) {
265 async _visitObject(data: any, name: string) {
203 if (data.constructor &&
266 if (data.constructor &&
204 data.constructor.prototype !== Object.prototype)
267 data.constructor.prototype !== Object.prototype)
205 return new ValueDescriptor(data);
268 return new ValueDescriptor(data);
206
269
207 this._enter(name);
270 this._enter(name);
208
271
209 const v = await mapAll(data, delegate(this, "_visit"));
272 const v = await mapAll(data, delegate(this, "_visit"));
210
273
211 // TODO: handle inline descriptors properly
274 // TODO: handle inline descriptors properly
212 // const ex = {
275 // const ex = {
213 // activate(ctx) {
276 // activate(ctx) {
214 // const value = ctx.activate(this.prop, "prop");
277 // const value = ctx.activate(this.prop, "prop");
215 // // some code
278 // // some code
216 // },
279 // },
217 // // will be turned to ReferenceDescriptor
280 // // will be turned to ReferenceDescriptor
218 // prop: { $dependency: "depName" }
281 // prop: { $dependency: "depName" }
219 // };
282 // };
220
283
221 this._leave();
284 this._leave();
222 return v;
285 return v;
223 }
286 }
224
287
225 async _visitArray(data: any[], name: string) {
288 async _visitArray(data: any[], name: string) {
226 if (data.constructor &&
289 if (data.constructor &&
227 data.constructor.prototype !== Array.prototype)
290 data.constructor.prototype !== Array.prototype)
228 return new ValueDescriptor(data);
291 return new ValueDescriptor(data);
229
292
230 this._enter(name);
293 this._enter(name);
231
294
232 const v = await mapAll(data, delegate(this, "_visit"));
295 const v = await mapAll(data, delegate(this, "_visit"));
233 this._leave();
296 this._leave();
234
297
235 return v;
298 return v;
236 }
299 }
237
300
238 _makeServiceParams<T, P>(data: ServiceRegistration<T, P, S>) {
301 _makeServiceParams<T, P>(data: ServiceRegistration<T, P, S>) {
239 const opts: any = {
302 const opts: any = {
240 owner: this._container
303 owner: this._container
241 };
304 };
242 if (data.services)
305 if (data.services)
243 opts.services = this._visitRegistrations(data.services, "services");
306 opts.services = this._visitRegistrations(data.services, "services");
244
307
245 if (data.inject) {
308 if (data.inject) {
246 this._enter("inject");
309 this._enter("inject");
247 opts.inject = mapAll(
310 opts.inject = mapAll(
248 data.inject instanceof Array ?
311 data.inject instanceof Array ?
249 data.inject :
312 data.inject :
250 [data.inject],
313 [data.inject],
251 delegate(this, "_visitObject")
314 delegate(this, "_visitObject")
252 );
315 );
253 this._leave();
316 this._leave();
254 }
317 }
255
318
256 if ("params" in data)
319 if ("params" in data)
257 opts.params = data.params instanceof Array ?
320 opts.params = data.params instanceof Array ?
258 this._visitArray(data.params, "params") :
321 this._visitArray(data.params, "params") :
259 this._visit(data.params, "params");
322 this._visit(data.params, "params");
260
323
261 if (data.activation) {
324 if (data.activation) {
262 if (typeof (data.activation) === "string") {
325 opts.activation = data.activation;
263 switch (data.activation.toLowerCase()) {
264 case "singleton":
265 opts.activation = ActivationType.Singleton;
266 break;
267 case "container":
268 opts.activation = ActivationType.Container;
269 break;
270 case "hierarchy":
271 opts.activation = ActivationType.Hierarchy;
272 break;
273 case "context":
274 opts.activation = ActivationType.Context;
275 break;
276 case "call":
277 opts.activation = ActivationType.Call;
278 break;
279 default:
280 throw new Error("Unknown activation type: " +
281 data.activation);
282 }
283 } else {
284 opts.activation = Number(data.activation);
285 }
286 }
326 }
287
327
288 if (data.cleanup)
328 if (data.cleanup)
289 opts.cleanup = data.cleanup;
329 opts.cleanup = data.cleanup;
290
330
291 return opts;
331 return opts;
292 }
332 }
293
333
294 async _visitValueRegistration<T>(data: ValueRegistration<T>, name: string) {
334 async _visitValueRegistration<T>(data: ValueRegistration<T>, name: string) {
295 this._enter(name);
335 this._enter(name);
296 const d = data.parse ? new AggregateDescriptor(data.$value) : new ValueDescriptor(data.$value);
336 const d = data.parse ? new AggregateDescriptor(data.$value) : new ValueDescriptor(data.$value);
297 this._leave();
337 this._leave();
298 return d;
338 return d;
299 }
339 }
300
340
301 async _visitDependencyRegistration<K extends keyof S>(data: DependencyRegistration<S, K>, name: string) {
341 async _visitDependencyRegistration<K extends keyof S>(data: DependencyRegistration<S, K>, name: string) {
302 argumentNotEmptyString(data && data.$dependency, "data.$dependency");
342 argumentNotEmptyString(data && data.$dependency, "data.$dependency");
303 this._enter(name);
343 this._enter(name);
304 const d = new ReferenceDescriptor<S, K>({
344 const d = new ReferenceDescriptor<S, K>({
305 name: data.$dependency,
345 name: data.$dependency,
306 lazy: data.lazy,
346 lazy: data.lazy,
307 optional: data.optional,
347 optional: data.optional,
308 default: data.default,
348 default: data.default,
309 services: data.services && await this._visitRegistrations(data.services, "services")
349 services: data.services && await this._visitRegistrations(data.services, "services")
310 });
350 });
311 this._leave();
351 this._leave();
312 return d;
352 return d;
313 }
353 }
314
354
315 async _visitTypeRegistration<T, P>(data: TypeRegistration<T, P, S>, name: string) {
355 async _visitTypeRegistration(data: TypeRegistration<any, any, S>, name: string) {
316 argumentNotNull(data.$type, "data.$type");
356 argumentNotNull(data.$type, "data.$type");
317 this._enter(name);
357 this._enter(name);
318
358
319 const opts = this._makeServiceParams(data);
359 const opts = this._makeServiceParams(data);
320 if (data.$type instanceof Function) {
360 if (data.$type instanceof Function) {
321 opts.type = data.$type;
361 opts.type = data.$type;
322 } else {
362 } else {
323 const [moduleName, typeName] = data.$type.split(":", 2);
363 const [moduleName, typeName] = data.$type.split(":", 2);
324 opts.type = this._resolveType(moduleName, typeName);
364 opts.type = this._resolveType(moduleName, typeName);
325 }
365 }
326
366
327 const d = new TypeServiceDescriptor(
367 const d = new TypeServiceDescriptor<S, any, any[]>(
328 await mapAll(opts)
368 await mapAll(opts)
329 );
369 );
330
370
331 this._leave();
371 this._leave();
332
372
333 return d;
373 return d;
334 }
374 }
335
375
336 async _visitFactoryRegistration<T, P>(data: FactoryRegistration<T, P, S>, name: string) {
376 async _visitFactoryRegistration(data: FactoryRegistration<any, any, S>, name: string) {
337 argumentOfType(data.$factory, Function, "data.$factory");
377 argumentOfType(data.$factory, Function, "data.$factory");
338 this._enter(name);
378 this._enter(name);
339
379
340 const opts = this._makeServiceParams(data);
380 const opts = this._makeServiceParams(data);
341 opts.factory = data.$factory;
381 opts.factory = data.$factory;
342
382
343 const d = new FactoryServiceDescriptor(
383 const d = new FactoryServiceDescriptor<S, any, any[]>(
344 await mapAll(opts)
384 await mapAll(opts)
345 );
385 );
346
386
347 this._leave();
387 this._leave();
348 return d;
388 return d;
349 }
389 }
350 }
390 }
@@ -1,139 +1,140
1 import { ActivationContext } from "./ActivationContext";
1 import { ActivationContext } from "./ActivationContext";
2 import { ValueDescriptor } from "./ValueDescriptor";
2 import { ValueDescriptor } from "./ValueDescriptor";
3 import { ActivationError } from "./ActivationError";
3 import { ActivationError } from "./ActivationError";
4 import { isDescriptor, ServiceMap, Descriptor, PartialServiceMap, ContainerServices, Resolver } from "./interfaces";
4 import { ServiceMap, Descriptor, PartialServiceMap, ContainerServices, Resolver } from "./interfaces";
5 import { TraceSource } from "../log/TraceSource";
5 import { TraceSource } from "../log/TraceSource";
6 import { Configuration } from "./Configuration";
6 import { Configuration } from "./Configuration";
7 import { Cancellation } from "../Cancellation";
7 import { Cancellation } from "../Cancellation";
8 import { MapOf } from "../interfaces";
8 import { MapOf } from "../interfaces";
9 import { isDescriptor } from "./traits";
9
10
10 const trace = TraceSource.get("@implab/core/di/ActivationContext");
11 const trace = TraceSource.get("@implab/core/di/ActivationContext");
11
12
12 export class Container<S = any> implements Resolver<S> {
13 export class Container<S = any> implements Resolver<S> {
13 readonly _services: PartialServiceMap<S, ContainerServices<S>>;
14 readonly _services: PartialServiceMap<ContainerServices<S>>;
14
15
15 readonly _cache: MapOf<any>;
16 readonly _cache: MapOf<any>;
16
17
17 readonly _cleanup: (() => void)[];
18 readonly _cleanup: (() => void)[];
18
19
19 readonly _root: Container<S>;
20 readonly _root: Container<S>;
20
21
21 readonly _parent?: Container<S>;
22 readonly _parent?: Container<S>;
22
23
23 _disposed: boolean;
24 _disposed: boolean;
24
25
25 constructor(parent?: Container<S>) {
26 constructor(parent?: Container<S>) {
26 this._parent = parent;
27 this._parent = parent;
27 this._services = parent ? Object.create(parent._services) : {};
28 this._services = parent ? Object.create(parent._services) : {};
28 this._cache = {};
29 this._cache = {};
29 this._cleanup = [];
30 this._cleanup = [];
30 this._root = parent ? parent.getRootContainer() : this;
31 this._root = parent ? parent.getRootContainer() : this;
31 this._services.container = new ValueDescriptor(this) as any;
32 this._services.container = new ValueDescriptor(this) as any;
32 this._disposed = false;
33 this._disposed = false;
33 }
34 }
34
35
35 getRootContainer() {
36 getRootContainer() {
36 return this._root;
37 return this._root;
37 }
38 }
38
39
39 getParent() {
40 getParent() {
40 return this._parent;
41 return this._parent;
41 }
42 }
42
43
43 resolve<K extends keyof ContainerServices<S>, T extends ContainerServices<S>[K] = ContainerServices<S>[K]>(name: K, def?: T): T {
44 resolve<K extends keyof ContainerServices<S>, T extends ContainerServices<S>[K] = ContainerServices<S>[K]>(name: K, def?: T): T {
44 trace.debug("resolve {0}", name);
45 trace.debug("resolve {0}", name);
45 const d = this._services[name];
46 const d = this._services[name];
46 if (d === undefined) {
47 if (d === undefined) {
47 if (def !== undefined)
48 if (def !== undefined)
48 return def;
49 return def;
49 else
50 else
50 throw new Error("Service '" + name + "' isn't found");
51 throw new Error("Service '" + name + "' isn't found");
51 } else {
52 } else {
52
53
53 const context = new ActivationContext<S>(this, this._services);
54 const context = new ActivationContext<S>(this, this._services);
54 try {
55 try {
55 return context.activate(d as Descriptor<S, T>, name.toString());
56 return context.activate(d as Descriptor<S, T>, name.toString());
56 } catch (error) {
57 } catch (error) {
57 throw new ActivationError(name.toString(), context.getStack(), error);
58 throw new ActivationError(name.toString(), context.getStack(), error);
58 }
59 }
59 }
60 }
60 }
61 }
61
62
62 /**
63 /**
63 * @deprecated use resolve() method
64 * @deprecated use resolve() method
64 */
65 */
65 getService<K extends keyof S, T extends ContainerServices<S>[K] = ContainerServices<S>[K]>(name: K, def?: T) {
66 getService<K extends keyof S, T extends ContainerServices<S>[K] = ContainerServices<S>[K]>(name: K, def?: T) {
66 return this.resolve(name, def);
67 return this.resolve(name, def);
67 }
68 }
68
69
69 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>): this;
70 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>): this;
70 register(services: PartialServiceMap<S>): this;
71 register(services: PartialServiceMap<S>): this;
71 register<K extends keyof S>(nameOrCollection: K | ServiceMap<S>, service?: Descriptor<S, S[K]>) {
72 register<K extends keyof S>(nameOrCollection: K | ServiceMap<S>, service?: Descriptor<S, S[K]>) {
72 if (arguments.length === 1) {
73 if (arguments.length === 1) {
73 const data = nameOrCollection as ServiceMap<S>;
74 const data = nameOrCollection as ServiceMap<S>;
74
75
75 for (const name in data) {
76 for (const name in data) {
76 if (Object.prototype.hasOwnProperty.call(data, name)) {
77 if (Object.prototype.hasOwnProperty.call(data, name)) {
77 this.register(name, data[name] as Descriptor<S, S[keyof S]>);
78 this.register(name, data[name] as Descriptor<S, S[keyof S]>);
78 }
79 }
79 }
80 }
80 } else {
81 } else {
81 if (!isDescriptor(service))
82 if (!isDescriptor(service))
82 throw new Error("The service parameter must be a descriptor");
83 throw new Error("The service parameter must be a descriptor");
83
84
84 this._services[nameOrCollection as K] = service as any;
85 this._services[nameOrCollection as K] = service as any;
85 }
86 }
86 return this;
87 return this;
87 }
88 }
88
89
89 onDispose(callback: () => void) {
90 onDispose(callback: () => void) {
90 if (!(callback instanceof Function))
91 if (!(callback instanceof Function))
91 throw new Error("The callback must be a function");
92 throw new Error("The callback must be a function");
92 this._cleanup.push(callback);
93 this._cleanup.push(callback);
93 }
94 }
94
95
95 dispose() {
96 dispose() {
96 if (this._disposed)
97 if (this._disposed)
97 return;
98 return;
98 this._disposed = true;
99 this._disposed = true;
99 for (const f of this._cleanup)
100 for (const f of this._cleanup)
100 f();
101 f();
101 }
102 }
102
103
103 /**
104 /**
104 * @param{String|Object} config
105 * @param{String|Object} config
105 * The configuration of the contaier. Can be either a string or an object,
106 * The configuration of the contaier. Can be either a string or an object,
106 * if the configuration is an object it's treated as a collection of
107 * if the configuration is an object it's treated as a collection of
107 * services which will be registed in the contaier.
108 * services which will be registed in the contaier.
108 *
109 *
109 * @param{Function} opts.contextRequire
110 * @param{Function} opts.contextRequire
110 * The function which will be used to load a configuration or types for services.
111 * The function which will be used to load a configuration or types for services.
111 *
112 *
112 */
113 */
113 async configure(config: string | object, opts?: any, ct = Cancellation.none) {
114 async configure(config: string | object, opts?: any, ct = Cancellation.none) {
114 const c = new Configuration<S>(this);
115 const c = new Configuration<S>(this);
115
116
116 if (typeof (config) === "string") {
117 if (typeof (config) === "string") {
117 return c.loadConfiguration(config, opts && opts.contextRequire, ct);
118 return c.loadConfiguration(config, opts && opts.contextRequire, ct);
118 } else {
119 } else {
119 return c.applyConfiguration(config, opts && opts.contextRequire, ct);
120 return c.applyConfiguration(config, opts && opts.contextRequire, ct);
120 }
121 }
121 }
122 }
122
123
123 createChildContainer<S2 extends { container?: Container<S & S2> } = S>(): Container<S & S2> {
124 createChildContainer<S2 extends { container?: Container<S & S2> } = S>(): Container<S & S2> {
124 return new Container<S & S2>(this as any);
125 return new Container<S & S2>(this as any);
125 }
126 }
126
127
127 has(id: string | number) {
128 has(id: string | number) {
128 return id in this._cache;
129 return id in this._cache;
129 }
130 }
130
131
131 get(id: string | number) {
132 get(id: string | number) {
132 return this._cache[id];
133 return this._cache[id];
133 }
134 }
134
135
135 store(id: string | number, value: any) {
136 store(id: string | number, value: any) {
136 return (this._cache[id] = value);
137 return (this._cache[id] = value);
137 }
138 }
138
139
139 }
140 }
@@ -1,22 +1,21
1 import { ServiceDescriptor, ServiceDescriptorParams } from "./ServiceDescriptor";
1 import { ServiceDescriptor, ServiceDescriptorParams } from "./ServiceDescriptor";
2 import { argumentNotNull, oid } from "../safe";
2 import { argumentNotNull, oid } from "../safe";
3 import { ActivationType } from "./interfaces";
4
3
5 export interface FactoryServiceDescriptorParams<S, T, P extends any[]> extends ServiceDescriptorParams<S, T, P> {
4 export interface FactoryServiceDescriptorParams<S, T, P extends any[]> extends ServiceDescriptorParams<S, T, P> {
6 factory: (...args: P) => T;
5 factory: (...args: P) => T;
7 }
6 }
8
7
9 export class FactoryServiceDescriptor<S, T, P extends any[]> extends ServiceDescriptor<S, T, P> {
8 export class FactoryServiceDescriptor<S, T, P extends any[]> extends ServiceDescriptor<S, T, P> {
10 constructor(opts: FactoryServiceDescriptorParams<S, T, P>) {
9 constructor(opts: FactoryServiceDescriptorParams<S, T, P>) {
11 super(opts);
10 super(opts);
12
11
13 argumentNotNull(opts && opts.factory, "opts.factory");
12 argumentNotNull(opts && opts.factory, "opts.factory");
14
13
15 // bind to null
14 // bind to null
16 this._factory = (...args) => opts.factory.apply(null, args as any);
15 this._factory = (...args) => opts.factory.apply(null, args as any);
17
16
18 if (opts.activation === ActivationType.Singleton) {
17 if (opts.activation === "singleton") {
19 this._cacheId = oid(opts.factory);
18 this._cacheId = oid(opts.factory);
20 }
19 }
21 }
20 }
22 }
21 }
@@ -1,99 +1,99
1 import { isNull, argumentNotEmptyString, each, keys } from "../safe";
1 import { isNull, argumentNotEmptyString, each, keys } from "../safe";
2 import { ActivationContext } from "./ActivationContext";
2 import { ActivationContext } from "./ActivationContext";
3 import { ServiceMap, Descriptor, PartialServiceMap } from "./interfaces";
3 import { ServiceMap, Descriptor, PartialServiceMap } from "./interfaces";
4 import { ActivationError } from "./ActivationError";
4 import { ActivationError } from "./ActivationError";
5
5
6 export interface ReferenceDescriptorParams<S, K extends keyof S> {
6 export interface ReferenceDescriptorParams<S, K extends keyof S> {
7 name: K;
7 name: K;
8 lazy?: boolean;
8 lazy?: boolean;
9 optional?: boolean;
9 optional?: boolean;
10 default?: S[K];
10 default?: S[K];
11 services?: PartialServiceMap<S>;
11 services?: PartialServiceMap<S>;
12 }
12 }
13
13
14 function defined<T>(v: T | undefined) {
14 function defined<T>(v: T | undefined) {
15 if (v === undefined)
15 if (v === undefined)
16 throw Error();
16 throw Error();
17 return v;
17 return v;
18 }
18 }
19
19
20 export class ReferenceDescriptor<S = any, K extends keyof S = keyof S> implements Descriptor<S, S[K] | ((args?: PartialServiceMap<S> ) => S[K])> {
20 export class ReferenceDescriptor<S = any, K extends keyof S = keyof S> implements Descriptor<S, S[K] | ((args?: PartialServiceMap<S> ) => S[K])> {
21 _name: K;
21 _name: K;
22
22
23 _lazy = false;
23 _lazy = false;
24
24
25 _optional = false;
25 _optional = false;
26
26
27 _default: S[K] | undefined;
27 _default: S[K] | undefined;
28
28
29 _services: ServiceMap<S>;
29 _services: PartialServiceMap<S>;
30
30
31 constructor(opts: ReferenceDescriptorParams<S, K>) {
31 constructor(opts: ReferenceDescriptorParams<S, K>) {
32 argumentNotEmptyString(opts && opts.name, "opts.name");
32 argumentNotEmptyString(opts && opts.name, "opts.name");
33 this._name = opts.name;
33 this._name = opts.name;
34 this._lazy = !!opts.lazy;
34 this._lazy = !!opts.lazy;
35 this._optional = !!opts.optional;
35 this._optional = !!opts.optional;
36 this._default = opts.default;
36 this._default = opts.default;
37
37
38 this._services = (opts.services || {}) as ServiceMap<S>;
38 this._services = (opts.services || {}) as ServiceMap<S>;
39 }
39 }
40
40
41 activate(context: ActivationContext<S>) {
41 activate(context: ActivationContext<S>) {
42 // добавляСм сСрвисы
42 // добавляСм сСрвисы
43 if (this._services) {
43 if (this._services) {
44 each(this._services, (v, k) => context.register(k, v));
44 each(this._services, (v, k) => context.register(k, v));
45 }
45 }
46
46
47 if (this._lazy) {
47 if (this._lazy) {
48 const saved = context.clone();
48 const saved = context.clone();
49
49
50 return (cfg?: PartialServiceMap<S>) => {
50 return (cfg?: PartialServiceMap<S>) => {
51 // Π·Π°Ρ‰ΠΈΡ‰Π°Π΅ΠΌ контСкст Π½Π° случай ΠΈΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ Π² процСссС
51 // Π·Π°Ρ‰ΠΈΡ‰Π°Π΅ΠΌ контСкст Π½Π° случай ΠΈΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ Π² процСссС
52 // Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ
52 // Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ
53 const ct = saved.clone();
53 const ct = saved.clone();
54 try {
54 try {
55 if (cfg) {
55 if (cfg) {
56 each(cfg as ServiceMap<S>, (v, k) => ct.register(k, v));
56 each(cfg, (v, k) => ct.register(k, v));
57 }
57 }
58
58
59 return this._optional ? ct.resolve(this._name, this._default) : ct
59 return this._optional ? ct.resolve(this._name, this._default) : ct
60 .resolve(this._name);
60 .resolve(this._name);
61 } catch (error) {
61 } catch (error) {
62 throw new ActivationError(this._name.toString(), ct.getStack(), error);
62 throw new ActivationError(this._name.toString(), ct.getStack(), error);
63 }
63 }
64 };
64 };
65 } else {
65 } else {
66 const v = this._optional ?
66 const v = this._optional ?
67 context.resolve(this._name, this._default) :
67 context.resolve(this._name, this._default) :
68 context.resolve(this._name);
68 context.resolve(this._name);
69
69
70 return v;
70 return v;
71 }
71 }
72 }
72 }
73
73
74 toString() {
74 toString() {
75 const opts = [];
75 const opts = [];
76 if (this._optional)
76 if (this._optional)
77 opts.push("optional");
77 opts.push("optional");
78 if (this._lazy)
78 if (this._lazy)
79 opts.push("lazy");
79 opts.push("lazy");
80
80
81 const parts = [
81 const parts = [
82 "@ref "
82 "@ref "
83 ];
83 ];
84 if (opts.length) {
84 if (opts.length) {
85 parts.push("{");
85 parts.push("{");
86 parts.push(opts.join());
86 parts.push(opts.join());
87 parts.push("} ");
87 parts.push("} ");
88 }
88 }
89
89
90 parts.push(this._name.toString());
90 parts.push(this._name.toString());
91
91
92 if (this._default !== undefined && this._default !== null) {
92 if (this._default !== undefined && this._default !== null) {
93 parts.push(" = ");
93 parts.push(" = ");
94 parts.push(String(this._default));
94 parts.push(String(this._default));
95 }
95 }
96
96
97 return parts.join("");
97 return parts.join("");
98 }
98 }
99 }
99 }
@@ -1,238 +1,239
1 import { ActivationContext } from "./ActivationContext";
1 import { ActivationContext } from "./ActivationContext";
2 import { Descriptor, ActivationType, ServiceMap, isDescriptor, Parse, PartialServiceMap } from "./interfaces";
2 import { Descriptor, ServiceMap, PartialServiceMap, ActivationType } from "./interfaces";
3 import { Container } from "./Container";
3 import { Container } from "./Container";
4 import { argumentNotNull, isPrimitive, each, keys, isNull } from "../safe";
4 import { argumentNotNull, isPrimitive, keys, isNull } from "../safe";
5 import { TraceSource } from "../log/TraceSource";
5 import { TraceSource } from "../log/TraceSource";
6 import { isDescriptor } from "./traits";
6
7
7 let cacheId = 0;
8 let cacheId = 0;
8
9
9 const trace = TraceSource.get("@implab/core/di/ActivationContext");
10 const trace = TraceSource.get("@implab/core/di/ActivationContext");
10
11
11 function injectMethod<T, M extends keyof T, S, A>(target: T, method: M, context: ActivationContext<S>, args: A) {
12 function injectMethod<T, M extends keyof T, S, A>(target: T, method: M, context: ActivationContext<S>, args: A) {
12
13
13 const m = target[method];
14 const m = target[method];
14 if (!m || typeof m !== "function")
15 if (!m || typeof m !== "function")
15 throw new Error("Method '" + method + "' not found");
16 throw new Error("Method '" + method + "' not found");
16
17
17 if (args instanceof Array)
18 if (args instanceof Array)
18 return m.apply(target, _parse(args, context, "." + method));
19 return m.apply(target, _parse(args, context, "." + method));
19 else
20 else
20 return m.call(target, _parse(args, context, "." + method));
21 return m.call(target, _parse(args, context, "." + method));
21 }
22 }
22
23
23 function makeClenupCallback<T>(target: T, method: Cleaner<T>): () => void;
24 function makeClenupCallback<T>(target: T, method: Cleaner<T>): () => void;
24 function makeClenupCallback(target: any, method: any) {
25 function makeClenupCallback(target: any, method: any) {
25 if (typeof (method) === "string") {
26 if (typeof (method) === "string") {
26 return () => {
27 return () => {
27 target[method]();
28 target[method]();
28 };
29 };
29 } else {
30 } else {
30 return () => {
31 return () => {
31 method(target);
32 method(target);
32 };
33 };
33 }
34 }
34 }
35 }
35
36
36 function _parse<T, S>(value: T, context: ActivationContext<S>, path: string): Parse<T> {
37 function _parse(value: any, context: ActivationContext<any>, path: string): any {
37 if (isPrimitive(value))
38 if (isPrimitive(value))
38 return value as any;
39 return value as any;
39
40
40 trace.debug("parse {0}", path);
41 trace.debug("parse {0}", path);
41
42
42 if (isDescriptor(value))
43 if (isDescriptor(value))
43 return context.activate(value, path);
44 return context.activate(value, path);
44
45
45 if (value instanceof Array)
46 if (value instanceof Array)
46 return value.map((x, i) => _parse(x, context, `${path}[${i}]`)) as any;
47 return value.map((x, i) => _parse(x, context, `${path}[${i}]`)) as any;
47
48
48 const t: any = {};
49 const t: any = {};
49
50
50 keys(value).forEach(p => t[p] = _parse(value[p], context, `${path}.${p}`));
51 keys(value).forEach(p => t[p] = _parse(value[p], context, `${path}.${p}`));
51
52
52 return t;
53 return t;
53 }
54 }
54
55
55 export type Cleaner<T> = ((x: T) => void) | keyof Extract<T, { [M in keyof T]: () => void }>;
56 export type Cleaner<T> = ((x: T) => void) | keyof Extract<T, { [M in keyof T]: () => void }>;
56
57
57 export type InjectionSpec<T> = {
58 export type InjectionSpec<T> = {
58 [m in keyof T]?: any;
59 [m in keyof T]?: any;
59 };
60 };
60
61
61 export interface ServiceDescriptorParams<S, T, P extends any[]> {
62 export interface ServiceDescriptorParams<S, T, P extends any[]> {
62 activation?: ActivationType;
63 activation?: ActivationType;
63
64
64 owner: Container<S>;
65 owner: Container<S>;
65
66
66 params?: P;
67 params?: P;
67
68
68 inject?: InjectionSpec<T>[];
69 inject?: InjectionSpec<T>[];
69
70
70 services?: PartialServiceMap<S>;
71 services?: PartialServiceMap<S>;
71
72
72 cleanup?: Cleaner<T>;
73 cleanup?: Cleaner<T>;
73 }
74 }
74
75
75 export class ServiceDescriptor<S, T, P extends any[]> implements Descriptor<S, T> {
76 export class ServiceDescriptor<S, T, P extends any[]> implements Descriptor<S, T> {
76 _instance: T | undefined;
77 _instance: T | undefined;
77
78
78 _hasInstance = false;
79 _hasInstance = false;
79
80
80 _activationType = ActivationType.Call;
81 _activationType: ActivationType = "call";
81
82
82 _services: ServiceMap<S>;
83 _services: ServiceMap<S>;
83
84
84 _params: P | undefined;
85 _params: P | undefined;
85
86
86 _inject: InjectionSpec<T>[];
87 _inject: InjectionSpec<T>[];
87
88
88 _cleanup: Cleaner<T> | undefined;
89 _cleanup: Cleaner<T> | undefined;
89
90
90 _cacheId: any;
91 _cacheId: any;
91
92
92 _owner: Container<S>;
93 _owner: Container<S>;
93
94
94 constructor(opts: ServiceDescriptorParams<S, T, P>) {
95 constructor(opts: ServiceDescriptorParams<S, T, P>) {
95 argumentNotNull(opts, "opts");
96 argumentNotNull(opts, "opts");
96 argumentNotNull(opts.owner, "owner");
97 argumentNotNull(opts.owner, "owner");
97
98
98 this._owner = opts.owner;
99 this._owner = opts.owner;
99
100
100 if (!isNull(opts.activation))
101 if (!isNull(opts.activation))
101 this._activationType = opts.activation;
102 this._activationType = opts.activation;
102
103
103 if (!isNull(opts.params))
104 if (!isNull(opts.params))
104 this._params = opts.params;
105 this._params = opts.params;
105
106
106 this._inject = opts.inject || [];
107 this._inject = opts.inject || [];
107
108
108 this._services = (opts.services || {}) as ServiceMap<S>;
109 this._services = (opts.services || {}) as ServiceMap<S>;
109
110
110 if (opts.cleanup) {
111 if (opts.cleanup) {
111 if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function))
112 if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function))
112 throw new Error(
113 throw new Error(
113 "The cleanup parameter must be either a function or a function name");
114 "The cleanup parameter must be either a function or a function name");
114
115
115 this._cleanup = opts.cleanup;
116 this._cleanup = opts.cleanup;
116 }
117 }
117 }
118 }
118
119
119 activate(context: ActivationContext<S>) {
120 activate(context: ActivationContext<S>) {
120 // if we have a local service records, register them first
121 // if we have a local service records, register them first
121 let instance: T;
122 let instance: T;
122
123
123 // ensure we have a cache id
124 // ensure we have a cache id
124 if (!this._cacheId)
125 if (!this._cacheId)
125 this._cacheId = ++cacheId;
126 this._cacheId = ++cacheId;
126
127
127 switch (this._activationType) {
128 switch (this._activationType) {
128 case ActivationType.Singleton: // SINGLETON
129 case "singleton": // SINGLETON
129 // if the value is cached return it
130 // if the value is cached return it
130 if (this._hasInstance)
131 if (this._hasInstance)
131 return this._instance;
132 return this._instance;
132
133
133 // singletons are bound to the root container
134 // singletons are bound to the root container
134 const container = context.container.getRootContainer();
135 const container = context.container.getRootContainer();
135
136
136 if (container.has(this._cacheId)) {
137 if (container.has(this._cacheId)) {
137 instance = container.get(this._cacheId);
138 instance = container.get(this._cacheId);
138 } else {
139 } else {
139 instance = this._create(context);
140 instance = this._create(context);
140 container.store(this._cacheId, instance);
141 container.store(this._cacheId, instance);
141 if (this._cleanup)
142 if (this._cleanup)
142 container.onDispose(
143 container.onDispose(
143 makeClenupCallback(instance, this._cleanup));
144 makeClenupCallback(instance, this._cleanup));
144 }
145 }
145
146
146 this._hasInstance = true;
147 this._hasInstance = true;
147 return (this._instance = instance);
148 return (this._instance = instance);
148
149
149 case ActivationType.Container: // CONTAINER
150 case "container": // CONTAINER
150 // return a cached value
151 // return a cached value
151
152
152 if (this._hasInstance)
153 if (this._hasInstance)
153 return this._instance;
154 return this._instance;
154
155
155 // create an instance
156 // create an instance
156 instance = this._create(context);
157 instance = this._create(context);
157
158
158 // the instance is bound to the container
159 // the instance is bound to the container
159 if (this._cleanup)
160 if (this._cleanup)
160 this._owner.onDispose(
161 this._owner.onDispose(
161 makeClenupCallback(instance, this._cleanup));
162 makeClenupCallback(instance, this._cleanup));
162
163
163 // cache and return the instance
164 // cache and return the instance
164 this._hasInstance = true;
165 this._hasInstance = true;
165 return (this._instance = instance);
166 return (this._instance = instance);
166 case ActivationType.Context: // CONTEXT
167 case "context": // CONTEXT
167 // return a cached value if one exists
168 // return a cached value if one exists
168
169
169 if (context.has(this._cacheId))
170 if (context.has(this._cacheId))
170 return context.get(this._cacheId);
171 return context.get(this._cacheId);
171 // context context activated instances are controlled by callers
172 // context context activated instances are controlled by callers
172 return context.store(this._cacheId, this._create(context));
173 return context.store(this._cacheId, this._create(context));
173 case ActivationType.Call: // CALL
174 case "call": // CALL
174 // per-call created instances are controlled by callers
175 // per-call created instances are controlled by callers
175 return this._create(context);
176 return this._create(context);
176 case ActivationType.Hierarchy: // HIERARCHY
177 case "hierarchy": // HIERARCHY
177 // hierarchy activated instances are behave much like container activated
178 // hierarchy activated instances are behave much like container activated
178 // except they are created and bound to the child container
179 // except they are created and bound to the child container
179
180
180 // return a cached value
181 // return a cached value
181 if (context.container.has(this._cacheId))
182 if (context.container.has(this._cacheId))
182 return context.container.get(this._cacheId);
183 return context.container.get(this._cacheId);
183
184
184 instance = this._create(context);
185 instance = this._create(context);
185
186
186 if (this._cleanup)
187 if (this._cleanup)
187 context.container.onDispose(makeClenupCallback(
188 context.container.onDispose(makeClenupCallback(
188 instance,
189 instance,
189 this._cleanup));
190 this._cleanup));
190
191
191 return context.container.store(this._cacheId, instance);
192 return context.container.store(this._cacheId, instance);
192 default:
193 default:
193 throw new Error("Invalid activation type: " + this._activationType);
194 throw new Error("Invalid activation type: " + this._activationType);
194 }
195 }
195 }
196 }
196
197
197 isInstanceCreated() {
198 isInstanceCreated() {
198 return this._hasInstance;
199 return this._hasInstance;
199 }
200 }
200
201
201 getInstance() {
202 getInstance() {
202 return this._instance;
203 return this._instance;
203 }
204 }
204
205
205 _factory(...params: any[]): T {
206 _factory(...params: any[]): T {
206 throw Error("Not implemented");
207 throw Error("Not implemented");
207 }
208 }
208
209
209 _create(context: ActivationContext<S>) {
210 _create(context: ActivationContext<S>) {
210 trace.debug(`constructing ${context._name}`);
211 trace.debug(`constructing ${context._name}`);
211
212
212 if (this._activationType !== ActivationType.Call &&
213 if (this._activationType !== "call" &&
213 context.visit(this._cacheId) > 0)
214 context.visit(this._cacheId) > 0)
214 throw new Error("Recursion detected");
215 throw new Error("Recursion detected");
215
216
216 if (this._services) {
217 if (this._services) {
217 keys(this._services).forEach(p => context.register(p, this._services[p]));
218 keys(this._services).forEach(p => context.register(p, this._services[p]));
218 }
219 }
219
220
220 let instance: T;
221 let instance: T;
221
222
222 if (this._params === undefined) {
223 if (this._params === undefined) {
223 instance = this._factory();
224 instance = this._factory();
224 } else if (this._params instanceof Array) {
225 } else if (this._params instanceof Array) {
225 instance = this._factory.apply(this, _parse(this._params, context, "args"));
226 instance = this._factory.apply(this, _parse(this._params, context, "args"));
226 } else {
227 } else {
227 instance = this._factory(_parse(this._params, context, "args"));
228 instance = this._factory(_parse(this._params, context, "args"));
228 }
229 }
229
230
230 if (this._inject) {
231 if (this._inject) {
231 this._inject.forEach(spec => {
232 this._inject.forEach(spec => {
232 for (const m in spec)
233 for (const m in spec)
233 injectMethod(instance, m, context, spec[m]);
234 injectMethod(instance, m, context, spec[m]);
234 });
235 });
235 }
236 }
236 return instance;
237 return instance;
237 }
238 }
238 }
239 }
@@ -1,88 +1,21
1 import { isPrimitive, primitive } from "../safe";
2 import { ActivationContext } from "./ActivationContext";
1 import { ActivationContext } from "./ActivationContext";
3 import { Constructor, Factory } from "../interfaces";
4
2
5 export interface Descriptor<S = any, T = any> {
3 export interface Descriptor<S = any, T = any> {
6 activate(context: ActivationContext<S>): T;
4 activate(context: ActivationContext<S>): T;
7 }
5 }
8
6
9 export function isDescriptor(x: any): x is Descriptor {
7 export type ServiceMap<S> = {
10 return (!isPrimitive(x)) &&
8 [k in keyof S]: Descriptor<S, S[k]>;
11 (x.activate instanceof Function);
12 }
13
14 export type ServiceMap<S, S2 extends S = S> = {
15 [k in keyof S2]: Descriptor<S, S2[k]>;
16 };
9 };
17
10
18 export type PartialServiceMap<S, S2 extends S = S> = Partial<ServiceMap<S, S2>>;
11 export type PartialServiceMap<S> = {
19
12 [k in keyof S]?: Descriptor<S, S[k]>;
20 export enum ActivationType {
13 };
21 Singleton = 1,
22 Container,
23 Hierarchy,
24 Context,
25 Call
26 }
27
28 export interface RegistrationWithServices<S> {
29 services?: ServiceMap<S>;
30 }
31
32 export interface ServiceRegistration<T, P, S> extends RegistrationWithServices<S> {
33
34 activation?: "singleton" | "container" | "hierarchy" | "context" | "call";
35
36 params?: P;
37
38 inject?: object | object[];
39
40 cleanup?: ((instance: T) => void) | string;
41 }
42
43 export interface TypeRegistration<T, P, S> extends ServiceRegistration<T, P, S> {
44 $type: string | Constructor<T>;
45 }
46
47 export interface FactoryRegistration<T, P, S> extends ServiceRegistration<T, P, S> {
48 $factory: string | Factory<T>;
49 }
50
51 export interface ValueRegistration<T> {
52 $value: T;
53 parse?: boolean;
54 }
55
56 export interface DependencyRegistration<S, K extends keyof S> extends RegistrationWithServices<S> {
57 $dependency: K;
58 lazy?: boolean;
59 optional?: boolean;
60 default?: S[K];
61 }
62
63 export type Parse<T> = T extends primitive ? T:
64 T extends Descriptor<infer V> ? V :
65 { [K in keyof T]: Parse<T[K]> };
66
14
67 export interface Resolver<S> {
15 export interface Resolver<S> {
68 resolve<K extends keyof ContainerServices<S>, T extends ContainerServices<S>[K] = ContainerServices<S>[K]>(name: K, def?: T): T;
16 resolve<K extends keyof ContainerServices<S>, T extends ContainerServices<S>[K] = ContainerServices<S>[K]>(name: K, def?: T): T;
69 }
17 }
70 export type ContainerServices<S> = S & {
18 export type ContainerServices<S> = S & {
71 container: Resolver<S>;
19 container: Resolver<S>;
72 };
20 };
73
21 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
74 export function isTypeRegistration(x: any): x is TypeRegistration<any, any, any> {
75 return (!isPrimitive(x)) && ("$type" in x);
76 }
77
78 export function isFactoryRegistration(x: any): x is FactoryRegistration<any, any, any> {
79 return (!isPrimitive(x)) && ("$factory" in x);
80 }
81
82 export function isValueRegistration(x: any): x is ValueRegistration<any> {
83 return (!isPrimitive(x)) && ("$value" in x);
84 }
85
86 export function isDependencyRegistration<S>(x: any): x is DependencyRegistration<S, keyof S> {
87 return (!isPrimitive(x)) && ("$dependency" in x);
88 }
@@ -1,33 +1,33
1 import { Foo } from "./Foo";
1 import { Foo } from "./Foo";
2 // import { config } from "./config";
2 import { build } from "./config";
3
3
4 // const service = config.build("bar");
4 const service = build<Bar>();
5
5
6 // @service.consume({
6 @service.consume({
7 // f: config.dependency("foo"),
7 foo: service.get("foo"),
8 // nested: {
8 nested: {
9 // lazy: config.lazy("foo")
9 lazy: service.lazy("foo")
10 // }
10 }
11 // })
11 })
12 export class Bar {
12 export class Bar {
13 barName = "bar";
13 barName = "bar";
14
14
15 _v: Foo | undefined;
15 _v: Foo | undefined;
16
16
17 constructor(_opts: {
17 constructor(_opts?: {
18 foo: Foo;
18 foo?: Foo;
19 nested: {
19 nested?: {
20 lazy: () => Foo
20 lazy: () => Foo
21 }
21 }
22 }) {
22 }) {
23
23
24 if (_opts && _opts.foo)
24 if (_opts && _opts.foo)
25 this._v = _opts.foo;
25 this._v = _opts.foo;
26 }
26 }
27
27
28 getFoo() {
28 getFoo() {
29 if (this._v === undefined)
29 if (this._v === undefined)
30 throw new Error("The foo isn't set");
30 throw new Error("The foo isn't set");
31 return this._v;
31 return this._v;
32 }
32 }
33 }
33 }
@@ -1,35 +1,38
1 import { services } from "../di/Annotations";
1 import { services } from "../di/Annotations";
2 import { Bar } from "./Bar";
2 import { Bar } from "./Bar";
3 import { Foo } from "./Foo";
3
4
4 // declare required dependencies
5 // declare required dependencies
5 const config = services<{
6 const config = services<{
6 bar: Bar;
7 bar: Bar;
8 foo: Foo;
7 }>();
9 }>();
8
10
9 // export service descriptor
11 // export service descriptor
10 export const service = config.build<Box<Bar>>();
12 export const service = config.build<Box<Bar>>();
11
13
12 @service.consume(config.dependency("bar"))
14 @service.consume(config.get("bar"))
13 export class Box<T> {
15 export class Box<T> {
14 private _value: T | undefined;
16 private _value: T | undefined;
15
17
16 constructor(value: T) {
18 constructor(value: T) {
17 this._value = value;
19 this._value = value;
18 }
20 }
19
21
20 // @service.inject("bar")
22 @service.inject( config.get("bar"))
21 setValue(value: T) {
23 setValue(value?: T) {
22 this._value = value;
24 this._value = value;
25 return value;
23 }
26 }
24
27
25 setObj(value: object) {
28 setObj(value: object) {
26
29
27 }
30 }
28
31
29 getValue() {
32 getValue() {
30 if (this._value === undefined)
33 if (this._value === undefined)
31 throw new Error("Trying to get a value from the empty box");
34 throw new Error("Trying to get a value from the empty box");
32
35
33 return this._value;
36 return this._value;
34 }
37 }
35 }
38 }
@@ -1,38 +1,27
1 import { Foo } from "./Foo";
1 import { Foo } from "./Foo";
2 import { Bar } from "./Bar";
2 import { Bar } from "./Bar";
3 import { Box } from "./Box";
3 import { Box } from "./Box";
4 import { primitive } from "../safe";
4 import { Builder } from "../di/Annotations";
5 import { Constructor } from "../interfaces";
6
5
7 interface Services {
6 interface Services {
8 foo: Foo;
7 foo: Foo;
9
8
10 bar: Bar;
9 bar: Bar;
11
10
12 box: Box<Foo>;
11 box: Box<Foo>;
13
12
14 host: string;
13 host: string;
15
14
16 }
15 }
17
16
18 interface TypeDescriptor<T, C extends Constructor<T>> {
17 const services = {
19 $type: C;
18 build: <T>() => {
19 return new Builder<T, Services>();
20 }
21 };
20
22
21 params: Wrap<ConstructorParameters<C>>;
23 namespace services {
22 }
23
24
24 function typeRegistration<T, C extends Constructor<T>>(target: C, params: Wrap<ConstructorParameters<C>>): TypeDescriptor<T, C> {
25 throw new Error();
26 }
25 }
27
26
28 declare function register<T>(): { type<C extends Constructor<T>>(target: C, params: Wrap<ConstructorParameters<C>>): TypeDescriptor<T, C>};
27 export = services;
29
30 type Wrap<T> = T extends primitive ? T :
31 { [k in keyof T]: Wrap<T[k]> } | TypeDescriptor<T, Constructor<T>>;
32
33 const config: Wrap<Services> = {
34 foo: typeRegistration(Foo, []),
35 bar: typeRegistration(Bar, [{ foo: null as any, nested: null as any }]),
36 box: register<Box<Foo>>().type(Box, [{ $type: Bar, params: [] }]),
37 host: ""
38 };
@@ -1,94 +1,95
1 import { test } from "./TestTraits";
1 import { test } from "./TestTraits";
2 import { Container } from "../di/Container";
2 import { Container } from "../di/Container";
3 import { ReferenceDescriptor } from "../di/ReferenceDescriptor";
3 import { ReferenceDescriptor } from "../di/ReferenceDescriptor";
4 import { AggregateDescriptor } from "../di/AggregateDescriptor";
4 import { AggregateDescriptor } from "../di/AggregateDescriptor";
5 import { ValueDescriptor } from "../di/ValueDescriptor";
5 import { ValueDescriptor } from "../di/ValueDescriptor";
6 import { Foo } from "../mock/Foo";
6 import { Foo } from "../mock/Foo";
7 import { Bar } from "../mock/Bar";
7 import { Bar } from "../mock/Bar";
8 import { isNull } from "../safe";
8 import { isNull } from "../safe";
9 import { Descriptor } from "../di/interfaces";
9 import { Descriptor } from "../di/interfaces";
10
10
11 test("Container register/resolve tests", async t => {
11 test("Container register/resolve tests", async t => {
12 const container = new Container();
12 const container = new Container();
13
13
14 const connection1 = "db://localhost";
14 const connection1 = "db://localhost";
15
15
16 t.throws(
16 t.throws(
17 () => container.register("bla-bla", "bla-bla" as any),
17 () => container.register("bla-bla", "bla-bla" as any),
18 "Do not allow to register anything other than descriptors"
18 "Do not allow to register anything other than descriptors"
19 );
19 );
20
20
21 t.doesNotThrow(
21 t.doesNotThrow(
22 () => container.register("connection", new ValueDescriptor(connection1)),
22 () => container.register("connection", new ValueDescriptor(connection1)),
23 "register ValueDescriptor"
23 "register ValueDescriptor"
24 );
24 );
25
25
26 t.equals(container.resolve("connection"), connection1, "resolve string value");
26 t.equals(container.resolve("connection"), connection1, "resolve string value");
27
27
28 t.doesNotThrow(
28 t.doesNotThrow(
29 () => container.register(
29 () => container.register(
30 "dbParams",
30 "dbParams",
31 new AggregateDescriptor({
31 new AggregateDescriptor({
32 timeout: 10,
32 timeout: 10,
33 connection: new ReferenceDescriptor({ name: "connection" })
33 connection: new ReferenceDescriptor({ name: "connection" })
34 })
34 })
35 ),
35 ),
36 "register AggregateDescriptor"
36 "register AggregateDescriptor"
37 );
37 );
38
38
39 const dbParams = container.resolve("dbParams");
39 const dbParams = container.resolve("dbParams");
40 t.equals(dbParams.connection, connection1, "should get string value 'dbParams.connection'");
40 t.equals(dbParams.connection, connection1, "should get string value 'dbParams.connection'");
41 });
41 });
42
42
43 test("Container configure/resolve tests", async t => {
43 test("Container configure/resolve tests", async t => {
44
44
45 const container = new Container();
45 const container = new Container();
46
46
47 await container.configure({
47 await container.configure({
48 foo: {
48 foo: {
49 $type: Foo
49 $type: Foo
50 },
50 },
51
51
52 box: {
52 box: {
53 $type: Bar,
53 $type: Bar,
54 params: {
54 params: {
55 $dependency: "foo"
55 $dependency: "foo"
56 }
56 }
57 },
57 },
58
58
59 bar: {
59 bar: {
60 $type: Bar,
60 $type: Bar,
61 params: {
61 params: {
62 db: {
62 db: {
63 provider: {
63 provider: {
64 $dependency: "db"
64 $dependency: "db"
65 }
65 }
66 }
66 }
67 }
67 }
68 }
68 }
69 });
69 });
70 t.pass("should configure from js object");
70 t.pass("should configure from js object");
71
71
72 const f1 = container.resolve("foo");
72 const f1 = container.resolve("foo");
73
73
74 t.assert(!isNull(f1), "foo should be not null");
74 t.assert(!isNull(f1), "foo should be not null");
75
75
76 t.throws(() => container.resolve("bar"), "should not resolve dependency 'db'");
76 t.throws(() => container.resolve("bar"), "should not resolve dependency 'db'");
77
77
78 });
78 });
79
79
80 test("Load configuration from module", async t => {
80 test("Load configuration from module", async t => {
81 const container = new Container();
81 const container = new Container();
82
82
83 await container.configure("../mock/config1", { contextRequire: require });
83 await container.configure("../mock/config1", { contextRequire: require });
84 t.pass("The configuration should load");
84 t.pass("The configuration should load");
85
85
86 const f1 = container.resolve("foo");
86 const f1 = container.resolve("foo");
87
87
88 t.assert(!isNull(f1), "foo should be not null");
88 t.assert(!isNull(f1), "foo should be not null");
89
89
90 const b1 = container.resolve("bar") as Bar;
90 const b1 = container.resolve("bar") as Bar;
91
91
92 t.assert(!isNull(b1), "bar should not be null");
92 t.assert(!isNull(b1), "bar should not be null");
93 t.assert(!isNull(b1._v), "bar.foo should not be null");
93 t.assert(!isNull(b1._v), "bar.foo should not be null");
94
94 });
95 });
General Comments 0
You need to be logged in to leave comments. Login now