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