##// END OF EJS Templates
Fixed container interfaces, separated ServiceContainer
cin -
r144:f97a113c3209 v1.4.0-rc4 default
parent child
Show More
@@ -1,170 +1,169
1 import { TraceSource } from "../log/TraceSource";
1 import { TraceSource } from "../log/TraceSource";
2 import { argumentNotEmptyString } from "../safe";
2 import { argumentNotEmptyString } from "../safe";
3 import { Descriptor, ContainerServiceMap, ContainerKeys, TypeOfService, ILifetime } from "./interfaces";
3 import { Descriptor, ContainerServiceMap, ContainerKeys, TypeOfService, ILifetime, ServiceContainer } from "./interfaces";
4 import { Container } from "./Container";
5 import { MapOf } from "../interfaces";
4 import { MapOf } from "../interfaces";
6
5
7 const trace = TraceSource.get("@implab/core/di/ActivationContext");
6 const trace = TraceSource.get("@implab/core/di/ActivationContext");
8
7
9 export interface ActivationContextInfo {
8 export interface ActivationContextInfo {
10 name: string;
9 name: string;
11
10
12 service: string;
11 service: string;
13
12
14 }
13 }
15
14
16 let nextId = 1;
15 let nextId = 1;
17
16
18 /** This class is created once per `Container.resolve` method call and used to
17 /** This class is created once per `Container.resolve` method call and used to
19 * cache dependencies and to track created instances. The activation context
18 * cache dependencies and to track created instances. The activation context
20 * tracks services with `context` activation type.
19 * tracks services with `context` activation type.
21 */
20 */
22 export class ActivationContext<S extends object> {
21 export class ActivationContext<S extends object> {
23 _cache: MapOf<any>;
22 _cache: MapOf<any>;
24
23
25 _services: ContainerServiceMap<S>;
24 _services: ContainerServiceMap<S>;
26
25
27 _visited: MapOf<any>;
26 _visited: MapOf<any>;
28
27
29 _name: string;
28 _name: string;
30
29
31 _service: Descriptor<S, any>;
30 _service: Descriptor<S, any>;
32
31
33 _container: Container<S>;
32 _container: ServiceContainer<S>;
34
33
35 _parent: ActivationContext<S> | undefined;
34 _parent: ActivationContext<S> | undefined;
36
35
37 /** Creates a new activation context with the specified parameters.
36 /** Creates a new activation context with the specified parameters.
38 * @param container the container which starts the activation process
37 * @param container the container which starts the activation process
39 * @param services the initial service registrations
38 * @param services the initial service registrations
40 * @param name the name of the service being activated, this parameter is
39 * @param name the name of the service being activated, this parameter is
41 * used for the debug purpose.
40 * used for the debug purpose.
42 * @param service the service to activate, this parameter is used for the
41 * @param service the service to activate, this parameter is used for the
43 * debug purpose.
42 * debug purpose.
44 */
43 */
45 constructor(container: Container<S>, services: ContainerServiceMap<S>, name: string, service: Descriptor<S, any>) {
44 constructor(container: ServiceContainer<S>, services: ContainerServiceMap<S>, name: string, service: Descriptor<S, any>) {
46 this._name = name;
45 this._name = name;
47 this._service = service;
46 this._service = service;
48 this._visited = {};
47 this._visited = {};
49 this._cache = {};
48 this._cache = {};
50 this._services = services;
49 this._services = services;
51 this._container = container;
50 this._container = container;
52 }
51 }
53
52
54 /** the name of the current resolving dependency */
53 /** the name of the current resolving dependency */
55 getName() {
54 getName() {
56 return this._name;
55 return this._name;
57 }
56 }
58
57
59 /** Returns the container for which 'resolve' method was called */
58 /** Returns the container for which 'resolve' method was called */
60 getContainer() {
59 getContainer() {
61 return this._container;
60 return this._container;
62 }
61 }
63
62
64 /** Resolves the specified dependency in the current context
63 /** Resolves the specified dependency in the current context
65 * @param name The name of the dependency being resolved
64 * @param name The name of the dependency being resolved
66 */
65 */
67 resolve<K extends ContainerKeys<S>>(name: K): TypeOfService<S, K>;
66 resolve<K extends ContainerKeys<S>>(name: K): TypeOfService<S, K>;
68 /** Resolves the specified dependency with the specified default value if
67 /** Resolves the specified dependency with the specified default value if
69 * the dependency is missing.
68 * the dependency is missing.
70 *
69 *
71 * @param name The name of the dependency being resolved
70 * @param name The name of the dependency being resolved
72 * @param def A default value to return in case of the specified dependency
71 * @param def A default value to return in case of the specified dependency
73 * is missing.
72 * is missing.
74 */
73 */
75 resolve<K extends ContainerKeys<S>, T>(name: K, def: T): TypeOfService<S, K> | T;
74 resolve<K extends ContainerKeys<S>, T>(name: K, def: T): TypeOfService<S, K> | T;
76 /** Resolves the specified dependency and returns undefined in case if the
75 /** Resolves the specified dependency and returns undefined in case if the
77 * dependency is missing.
76 * dependency is missing.
78 *
77 *
79 * @param name The name of the dependency being resolved
78 * @param name The name of the dependency being resolved
80 */
79 */
81 resolve<K extends ContainerKeys<S>>(name: K, def: undefined): TypeOfService<S, K> | undefined;
80 resolve<K extends ContainerKeys<S>>(name: K, def: undefined): TypeOfService<S, K> | undefined;
82 resolve<K extends ContainerKeys<S>, T>(name: K, def?: T): TypeOfService<S, K> | T | undefined {
81 resolve<K extends ContainerKeys<S>, T>(name: K, def?: T): TypeOfService<S, K> | T | undefined {
83 const d = this._services[name];
82 const d = this._services[name];
84
83
85 if (d !== undefined) {
84 if (d !== undefined) {
86 return this.activate(d, name.toString());
85 return this.activate(d, name.toString());
87 } else {
86 } else {
88 if (arguments.length > 1)
87 if (arguments.length > 1)
89 return def;
88 return def;
90 else
89 else
91 throw new Error(`Service ${name} not found`);
90 throw new Error(`Service ${name} not found`);
92 }
91 }
93 }
92 }
94
93
95 /**
94 /**
96 * registers services local to the the activation context
95 * registers services local to the the activation context
97 *
96 *
98 * @name{string} the name of the service
97 * @name{string} the name of the service
99 * @service{string} the service descriptor to register
98 * @service{string} the service descriptor to register
100 */
99 */
101 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>) {
100 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>) {
102 argumentNotEmptyString(name, "name");
101 argumentNotEmptyString(name, "name");
103
102
104 this._services[name] = service as any;
103 this._services[name] = service as any;
105 }
104 }
106
105
107 createLifetime(): ILifetime {
106 createLifetime(): ILifetime {
108 const id = nextId++;
107 const id = nextId++;
109 const me = this;
108 const me = this;
110 return {
109 return {
111 initialize() {
110 initialize() {
112 },
111 },
113 has() {
112 has() {
114 return id in me._cache;
113 return id in me._cache;
115 },
114 },
116 get() {
115 get() {
117 return me._cache[id];
116 return me._cache[id];
118 },
117 },
119 store(item: any) {
118 store(item: any) {
120 me._cache[id] = item;
119 me._cache[id] = item;
121 }
120 }
122 };
121 };
123 }
122 }
124
123
125 activate<T>(d: Descriptor<S, T>, name: string) {
124 activate<T>(d: Descriptor<S, T>, name: string) {
126 if (trace.isLogEnabled())
125 if (trace.isLogEnabled())
127 trace.log(`enter ${name} ${d}`);
126 trace.log(`enter ${name} ${d}`);
128
127
129 const ctx = this.enter(d, name);
128 const ctx = this.enter(d, name);
130 const v = d.activate(ctx);
129 const v = d.activate(ctx);
131
130
132 if (trace.isLogEnabled())
131 if (trace.isLogEnabled())
133 trace.log(`leave ${name}`);
132 trace.log(`leave ${name}`);
134
133
135 return v;
134 return v;
136 }
135 }
137
136
138 visit(id: string) {
137 visit(id: string) {
139 const count = this._visited[id] || 0;
138 const count = this._visited[id] || 0;
140 this._visited[id] = count + 1;
139 this._visited[id] = count + 1;
141 return count;
140 return count;
142 }
141 }
143
142
144 getStack(): ActivationContextInfo[] {
143 getStack(): ActivationContextInfo[] {
145 const stack = [{
144 const stack = [{
146 name: this._name,
145 name: this._name,
147 service: this._service.toString()
146 service: this._service.toString()
148 }];
147 }];
149
148
150 return this._parent ?
149 return this._parent ?
151 stack.concat(this._parent.getStack()) :
150 stack.concat(this._parent.getStack()) :
152 stack;
151 stack;
153 }
152 }
154
153
155 private enter(service: Descriptor<S, any>, name: string): this {
154 private enter(service: Descriptor<S, any>, name: string): this {
156 const clone = Object.create(this);
155 const clone = Object.create(this);
157 clone._name = name;
156 clone._name = name;
158 clone._services = Object.create(this._services);
157 clone._services = Object.create(this._services);
159 clone._parent = this;
158 clone._parent = this;
160 clone._service = service;
159 clone._service = service;
161 return clone;
160 return clone;
162 }
161 }
163
162
164 /** Creates a clone for the current context, used to protect it from modifications */
163 /** Creates a clone for the current context, used to protect it from modifications */
165 clone(): this {
164 clone(): this {
166 const clone = Object.create(this);
165 const clone = Object.create(this);
167 clone._services = Object.create(this._services);
166 clone._services = Object.create(this._services);
168 return clone;
167 return clone;
169 }
168 }
170 }
169 }
@@ -1,451 +1,450
1 import {
1 import {
2 PartialServiceMap,
2 PartialServiceMap,
3 ActivationType,
3 ActivationType,
4 ContainerKeys,
4 ContainerKeys,
5 TypeOfService,
5 TypeOfService,
6 ILifetime
6 ILifetime, ServiceContainer
7 } from "./interfaces";
7 } from "./interfaces";
8
8
9 import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get, primitive } from "../safe";
9 import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get, primitive } from "../safe";
10 import { AggregateDescriptor } from "./AggregateDescriptor";
10 import { AggregateDescriptor } from "./AggregateDescriptor";
11 import { ValueDescriptor } from "./ValueDescriptor";
11 import { ValueDescriptor } from "./ValueDescriptor";
12 import { Container } from "./Container";
13 import { ReferenceDescriptor } from "./ReferenceDescriptor";
12 import { ReferenceDescriptor } from "./ReferenceDescriptor";
14 import { TypeServiceDescriptor } from "./TypeServiceDescriptor";
13 import { TypeServiceDescriptor } from "./TypeServiceDescriptor";
15 import { FactoryServiceDescriptor } from "./FactoryServiceDescriptor";
14 import { FactoryServiceDescriptor } from "./FactoryServiceDescriptor";
16 import { TraceSource } from "../log/TraceSource";
15 import { TraceSource } from "../log/TraceSource";
17 import { ConfigError } from "./ConfigError";
16 import { ConfigError } from "./ConfigError";
18 import { Cancellation } from "../Cancellation";
17 import { Cancellation } from "../Cancellation";
19 import { makeResolver } from "./ResolverHelper";
18 import { makeResolver } from "./ResolverHelper";
20 import { ICancellation } from "../interfaces";
19 import { ICancellation } from "../interfaces";
21 import { isDescriptor } from "./traits";
20 import { isDescriptor } from "./traits";
22 import { LazyReferenceDescriptor } from "./LazyReferenceDescriptor";
21 import { LazyReferenceDescriptor } from "./LazyReferenceDescriptor";
23 import { LifetimeManager } from "./LifetimeManager";
22 import { LifetimeManager } from "./LifetimeManager";
24
23
25 export interface RegistrationScope<S extends object> {
24 export interface RegistrationScope<S extends object> {
26
25
27 /** сСрвисы, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Ρ€Π΅Π³ΠΈΡΡ‚Ρ€ΠΈΡ€ΡƒΡŽΡ‚ΡΡ Π² контСкстС Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ ΠΈ Ρ‚Π°ΠΊΠΈΠΌ ΠΎΠ±Ρ€Π°Π·ΠΎΠΌ
26 /** сСрвисы, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Ρ€Π΅Π³ΠΈΡΡ‚Ρ€ΠΈΡ€ΡƒΡŽΡ‚ΡΡ Π² контСкстС Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ ΠΈ Ρ‚Π°ΠΊΠΈΠΌ ΠΎΠ±Ρ€Π°Π·ΠΎΠΌ
28 * ΠΌΠΎΠ³ΡƒΡ‚ ΠΏΠ΅Ρ€Π΅ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΡΡ‚ΡŒ Ρ€Π°Π½Π΅Π΅ зарСгистрированныС сСрвисы. Π·Π° это свойство
27 * ΠΌΠΎΠ³ΡƒΡ‚ ΠΏΠ΅Ρ€Π΅ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΡΡ‚ΡŒ Ρ€Π°Π½Π΅Π΅ зарСгистрированныС сСрвисы. Π·Π° это свойство
29 * Π½ΡƒΠΆΠ½ΠΎ ΠΏΠ»Π°Ρ‚ΠΈΡ‚ΡŒ, ΠΊΡ€ΠΎΠΌΠ΅ Ρ‚ΠΎΠ³ΠΎ порядок Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ Π±ΡƒΠ΄Π΅Ρ‚ Π²Π»ΠΈΡΡ‚ΡŒ Π½Π° Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚
28 * Π½ΡƒΠΆΠ½ΠΎ ΠΏΠ»Π°Ρ‚ΠΈΡ‚ΡŒ, ΠΊΡ€ΠΎΠΌΠ΅ Ρ‚ΠΎΠ³ΠΎ порядок Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ Π±ΡƒΠ΄Π΅Ρ‚ Π²Π»ΠΈΡΡ‚ΡŒ Π½Π° Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚
30 * Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Π½ΠΈΡ зависимостСй.
29 * Ρ€Π°Π·Ρ€Π΅ΡˆΠ΅Π½ΠΈΡ зависимостСй.
31 */
30 */
32 services?: RegistrationMap<S>;
31 services?: RegistrationMap<S>;
33 }
32 }
34
33
35 /**
34 /**
36 * Π‘Π°Π·ΠΎΠ²Ρ‹ΠΉ интСрфСйс ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ сСрвисов
35 * Π‘Π°Π·ΠΎΠ²Ρ‹ΠΉ интСрфСйс ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ сСрвисов
37 */
36 */
38 export interface ServiceRegistration<T, S extends object> extends RegistrationScope<S> {
37 export interface ServiceRegistration<T, S extends object> extends RegistrationScope<S> {
39
38
40 activation?: ActivationType;
39 activation?: ActivationType;
41
40
42 params?: any;
41 params?: any;
43
42
44 /** Π‘ΠΏΠ΅Ρ†ΠΈΠ°Π»ΡŒΠ½Ρ‹ΠΉ ΠΈΠ΄Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ ΠΏΡ€ΠΈ Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ singleton, Ссли
43 /** Π‘ΠΏΠ΅Ρ†ΠΈΠ°Π»ΡŒΠ½Ρ‹ΠΉ ΠΈΠ΄Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ ΠΏΡ€ΠΈ Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ singleton, Ссли
45 * Π½Π΅ ΡƒΠΊΠ°Π·Π°Π½ для TypeRegistration вычисляСтся ΠΊΠ°ΠΊ oid($type)
44 * Π½Π΅ ΡƒΠΊΠ°Π·Π°Π½ для TypeRegistration вычисляСтся ΠΊΠ°ΠΊ oid($type)
46 */
45 */
47 typeId?: string;
46 typeId?: string;
48
47
49 inject?: object | object[];
48 inject?: object | object[];
50
49
51 cleanup?: ((instance: T) => void) | string;
50 cleanup?: ((instance: T) => void) | string;
52 }
51 }
53
52
54 export interface TypeRegistration<C extends new (...args: any[]) => any, S extends object> extends ServiceRegistration<InstanceType<C>, S> {
53 export interface TypeRegistration<C extends new (...args: any[]) => any, S extends object> extends ServiceRegistration<InstanceType<C>, S> {
55 $type: string | C;
54 $type: string | C;
56 params?: Registration<ConstructorParameters<C>, S>;
55 params?: Registration<ConstructorParameters<C>, S>;
57 }
56 }
58
57
59 export interface StrictTypeRegistration<C extends new (...args: any[]) => any, S extends object> extends ServiceRegistration<InstanceType<C>, S> {
58 export interface StrictTypeRegistration<C extends new (...args: any[]) => any, S extends object> extends ServiceRegistration<InstanceType<C>, S> {
60 $type: C;
59 $type: C;
61 params?: Registration<ConstructorParameters<C>, S>;
60 params?: Registration<ConstructorParameters<C>, S>;
62 }
61 }
63
62
64 export interface FactoryRegistration<F extends (...args: any[]) => any, S extends object> extends ServiceRegistration<ReturnType<F>, S> {
63 export interface FactoryRegistration<F extends (...args: any[]) => any, S extends object> extends ServiceRegistration<ReturnType<F>, S> {
65 $factory: string | F;
64 $factory: string | F;
66 }
65 }
67
66
68 export interface ValueRegistration<T> {
67 export interface ValueRegistration<T> {
69 $value: T;
68 $value: T;
70 parse?: boolean;
69 parse?: boolean;
71 }
70 }
72
71
73 export interface DependencyRegistration<S extends object, K extends ContainerKeys<S> = ContainerKeys<S>> extends RegistrationScope<S> {
72 export interface DependencyRegistration<S extends object, K extends ContainerKeys<S> = ContainerKeys<S>> extends RegistrationScope<S> {
74 $dependency: K;
73 $dependency: K;
75 lazy?: boolean;
74 lazy?: boolean;
76 optional?: boolean;
75 optional?: boolean;
77 default?: TypeOfService<S, K>;
76 default?: TypeOfService<S, K>;
78 }
77 }
79
78
80 export interface LazyDependencyRegistration<S extends object, K extends ContainerKeys<S> = ContainerKeys<S>> extends DependencyRegistration<S, K> {
79 export interface LazyDependencyRegistration<S extends object, K extends ContainerKeys<S> = ContainerKeys<S>> extends DependencyRegistration<S, K> {
81 lazy: true;
80 lazy: true;
82 }
81 }
83
82
84 export type Registration<T, S extends object> = T extends primitive ? T :
83 export type Registration<T, S extends object> = T extends primitive ? T :
85 (
84 (
86 T |
85 T |
87 { [k in keyof T]: Registration<T[k], S> } |
86 { [k in keyof T]: Registration<T[k], S> } |
88 TypeRegistration<new (...args: any[]) => T, S> |
87 TypeRegistration<new (...args: any[]) => T, S> |
89 FactoryRegistration<(...args: any[]) => T, S> |
88 FactoryRegistration<(...args: any[]) => T, S> |
90 ValueRegistration<any> |
89 ValueRegistration<any> |
91 DependencyRegistration<S, keyof S>
90 DependencyRegistration<S, keyof S>
92 );
91 );
93
92
94 export type RegistrationMap<S extends object> = {
93 export type RegistrationMap<S extends object> = {
95 [k in keyof S]?: Registration<S[k], S>;
94 [k in keyof S]?: Registration<S[k], S>;
96 };
95 };
97
96
98 const _activationTypes: { [k in ActivationType]: number; } = {
97 const _activationTypes: { [k in ActivationType]: number; } = {
99 singleton: 1,
98 singleton: 1,
100 container: 2,
99 container: 2,
101 hierarchy: 3,
100 hierarchy: 3,
102 context: 4,
101 context: 4,
103 call: 5
102 call: 5
104 };
103 };
105
104
106 export function isTypeRegistration(x: any): x is TypeRegistration<new () => any, any> {
105 export function isTypeRegistration(x: any): x is TypeRegistration<new () => any, any> {
107 return (!isPrimitive(x)) && ("$type" in x);
106 return (!isPrimitive(x)) && ("$type" in x);
108 }
107 }
109
108
110 export function isFactoryRegistration(x: any): x is FactoryRegistration<() => any, any> {
109 export function isFactoryRegistration(x: any): x is FactoryRegistration<() => any, any> {
111 return (!isPrimitive(x)) && ("$factory" in x);
110 return (!isPrimitive(x)) && ("$factory" in x);
112 }
111 }
113
112
114 export function isValueRegistration(x: any): x is ValueRegistration<any> {
113 export function isValueRegistration(x: any): x is ValueRegistration<any> {
115 return (!isPrimitive(x)) && ("$value" in x);
114 return (!isPrimitive(x)) && ("$value" in x);
116 }
115 }
117
116
118 export function isDependencyRegistration<S extends object>(x: any): x is DependencyRegistration<S, keyof S> {
117 export function isDependencyRegistration<S extends object>(x: any): x is DependencyRegistration<S, keyof S> {
119 return (!isPrimitive(x)) && ("$dependency" in x);
118 return (!isPrimitive(x)) && ("$dependency" in x);
120 }
119 }
121
120
122 export function isActivationType(x: string): x is ActivationType {
121 export function isActivationType(x: string): x is ActivationType {
123 return typeof x === "string" && x in _activationTypes;
122 return typeof x === "string" && x in _activationTypes;
124 }
123 }
125
124
126 const trace = TraceSource.get("@implab/core/di/Configuration");
125 const trace = TraceSource.get("@implab/core/di/Configuration");
127 async function mapAll(data: any[], map?: (v: any, k: number) => any): Promise<any[]>;
126 async function mapAll(data: any[], map?: (v: any, k: number) => any): Promise<any[]>;
128 async function mapAll(data: any, map?: (v: any, k: string) => any): Promise<any>;
127 async function mapAll(data: any, map?: (v: any, k: string) => any): Promise<any>;
129 async function mapAll(data: any, map?: (v: any, k: any) => any): Promise<any> {
128 async function mapAll(data: any, map?: (v: any, k: any) => any): Promise<any> {
130 if (data instanceof Array) {
129 if (data instanceof Array) {
131 return Promise.all(map ? data.map(map) : data);
130 return Promise.all(map ? data.map(map) : data);
132 } else {
131 } else {
133 const keys = Object.keys(data);
132 const keys = Object.keys(data);
134
133
135 const o: any = {};
134 const o: any = {};
136
135
137 await Promise.all(keys.map(async k => {
136 await Promise.all(keys.map(async k => {
138 const v = map ? map(data[k], k) : data[k];
137 const v = map ? map(data[k], k) : data[k];
139 o[k] = isPromise(v) ? await v : v;
138 o[k] = isPromise(v) ? await v : v;
140 }));
139 }));
141
140
142 return o;
141 return o;
143 }
142 }
144 }
143 }
145
144
146 export type ModuleResolver = (moduleName: string, ct?: ICancellation) => any;
145 export type ModuleResolver = (moduleName: string, ct?: ICancellation) => any;
147
146
148 export class Configuration<S extends object> {
147 export class Configuration<S extends object> {
149
148
150 _hasInnerDescriptors = false;
149 _hasInnerDescriptors = false;
151
150
152 readonly _container: Container<S>;
151 readonly _container: ServiceContainer<S>;
153
152
154 _path: Array<string>;
153 _path: Array<string>;
155
154
156 _configName: string | undefined;
155 _configName: string | undefined;
157
156
158 _require: ModuleResolver | undefined;
157 _require: ModuleResolver | undefined;
159
158
160 constructor(container: Container<S>) {
159 constructor(container: ServiceContainer<S>) {
161 argumentNotNull(container, "container");
160 argumentNotNull(container, "container");
162 this._container = container;
161 this._container = container;
163 this._path = [];
162 this._path = [];
164 }
163 }
165
164
166 async loadConfiguration(moduleName: string, contextRequire?: any, ct = Cancellation.none) {
165 async loadConfiguration(moduleName: string, contextRequire?: any, ct = Cancellation.none) {
167 argumentNotEmptyString(moduleName, "moduleName");
166 argumentNotEmptyString(moduleName, "moduleName");
168
167
169 trace.log(
168 trace.log(
170 "loadConfiguration moduleName={0}, contextRequire={1}",
169 "loadConfiguration moduleName={0}, contextRequire={1}",
171 moduleName,
170 moduleName,
172 contextRequire ? typeof (contextRequire) : "<nil>"
171 contextRequire ? typeof (contextRequire) : "<nil>"
173 );
172 );
174
173
175 this._configName = moduleName;
174 this._configName = moduleName;
176
175
177 const r = await makeResolver(undefined, contextRequire);
176 const r = await makeResolver(undefined, contextRequire);
178
177
179 const config = await r(moduleName, ct);
178 const config = await r(moduleName, ct);
180
179
181 await this._applyConfiguration(
180 await this._applyConfiguration(
182 config,
181 config,
183 await makeResolver(moduleName, contextRequire),
182 await makeResolver(moduleName, contextRequire),
184 ct
183 ct
185 );
184 );
186 }
185 }
187
186
188 async applyConfiguration(data: RegistrationMap<S>, opts: { contextRequire?: any; baseModule?: string }, ct = Cancellation.none) {
187 async applyConfiguration(data: RegistrationMap<S>, opts: { contextRequire?: any; baseModule?: string }, ct = Cancellation.none) {
189 argumentNotNull(data, "data");
188 argumentNotNull(data, "data");
190 const _opts = opts || {};
189 const _opts = opts || {};
191
190
192 await this._applyConfiguration(data, await makeResolver(_opts.baseModule, _opts.contextRequire), ct);
191 await this._applyConfiguration(data, await makeResolver(_opts.baseModule, _opts.contextRequire), ct);
193 }
192 }
194
193
195 async _applyConfiguration(data: RegistrationMap<S>, resolver?: ModuleResolver, ct = Cancellation.none) {
194 async _applyConfiguration(data: RegistrationMap<S>, resolver?: ModuleResolver, ct = Cancellation.none) {
196 trace.log("applyConfiguration");
195 trace.log("applyConfiguration");
197
196
198 this._configName = "$";
197 this._configName = "$";
199
198
200 if (resolver)
199 if (resolver)
201 this._require = resolver;
200 this._require = resolver;
202
201
203 let services: PartialServiceMap<S>;
202 let services: PartialServiceMap<S>;
204
203
205 try {
204 try {
206 services = await this._visitRegistrations(data, "$");
205 services = await this._visitRegistrations(data, "$");
207 } catch (e) {
206 } catch (e) {
208 throw this._makeError(e);
207 throw this._makeError(e);
209 }
208 }
210
209
211 this._container.register(services);
210 this._container.register(services);
212 }
211 }
213
212
214 _makeError(inner: any) {
213 _makeError(inner: any) {
215 const e = new ConfigError("Failed to load configuration", inner);
214 const e = new ConfigError("Failed to load configuration", inner);
216 e.configName = this._configName || "<inline>";
215 e.configName = this._configName || "<inline>";
217 e.path = this._makePath();
216 e.path = this._makePath();
218 return e;
217 return e;
219 }
218 }
220
219
221 _makePath() {
220 _makePath() {
222 return this._path
221 return this._path
223 .reduce(
222 .reduce(
224 (prev, cur) => typeof cur === "number" ?
223 (prev, cur) => typeof cur === "number" ?
225 `${prev}[${cur}]` :
224 `${prev}[${cur}]` :
226 `${prev}.${cur}`
225 `${prev}.${cur}`
227 )
226 )
228 .toString();
227 .toString();
229 }
228 }
230
229
231 async _resolveType(moduleName: string, localName: string) {
230 async _resolveType(moduleName: string, localName: string) {
232 trace.log("resolveType moduleName={0}, localName={1}", moduleName, localName);
231 trace.log("resolveType moduleName={0}, localName={1}", moduleName, localName);
233 try {
232 try {
234 const m = await this._loadModule(moduleName);
233 const m = await this._loadModule(moduleName);
235 if (localName) {
234 if (localName) {
236 return get(localName, m);
235 return get(localName, m);
237 } else {
236 } else {
238 if (m instanceof Function)
237 if (m instanceof Function)
239 return m;
238 return m;
240 if ("default" in m)
239 if ("default" in m)
241 return m.default;
240 return m.default;
242 return m;
241 return m;
243 }
242 }
244 } catch (e) {
243 } catch (e) {
245 trace.error("Failed to resolve type moduleName={0}, localName={1}", moduleName, localName);
244 trace.error("Failed to resolve type moduleName={0}, localName={1}", moduleName, localName);
246 throw e;
245 throw e;
247 }
246 }
248 }
247 }
249
248
250 _loadModule(moduleName: string) {
249 _loadModule(moduleName: string) {
251 trace.debug("loadModule {0}", moduleName);
250 trace.debug("loadModule {0}", moduleName);
252 if (!this._require)
251 if (!this._require)
253 throw new Error("Module loader isn't specified");
252 throw new Error("Module loader isn't specified");
254
253
255 return this._require(moduleName);
254 return this._require(moduleName);
256 }
255 }
257
256
258 async _visitRegistrations(data: RegistrationMap<S>, name: string) {
257 async _visitRegistrations(data: RegistrationMap<S>, name: string) {
259 this._enter(name);
258 this._enter(name);
260
259
261 if (data.constructor &&
260 if (data.constructor &&
262 data.constructor.prototype !== Object.prototype)
261 data.constructor.prototype !== Object.prototype)
263 throw new Error("Configuration must be a simple object");
262 throw new Error("Configuration must be a simple object");
264
263
265 const services = await mapAll(data, async (v, k) => {
264 const services = await mapAll(data, async (v, k) => {
266 const d = await this._visit(v, k.toString());
265 const d = await this._visit(v, k.toString());
267 return isDescriptor(d) ? d : new AggregateDescriptor(d);
266 return isDescriptor(d) ? d : new AggregateDescriptor(d);
268 }) as PartialServiceMap<S>;
267 }) as PartialServiceMap<S>;
269
268
270 this._leave();
269 this._leave();
271
270
272 return services;
271 return services;
273 }
272 }
274
273
275 _enter(name: string) {
274 _enter(name: string) {
276 this._path.push(name.toString());
275 this._path.push(name.toString());
277 trace.debug(">{0}", name);
276 trace.debug(">{0}", name);
278 }
277 }
279
278
280 _leave() {
279 _leave() {
281 const name = this._path.pop();
280 const name = this._path.pop();
282 trace.debug("<{0}", name);
281 trace.debug("<{0}", name);
283 }
282 }
284
283
285 _visit(data: any, name: string): Promise<any> {
284 _visit(data: any, name: string): Promise<any> {
286 if (isPrimitive(data))
285 if (isPrimitive(data))
287 return Promise.resolve(new ValueDescriptor(data));
286 return Promise.resolve(new ValueDescriptor(data));
288 if (isDescriptor(data))
287 if (isDescriptor(data))
289 return Promise.resolve(data);
288 return Promise.resolve(data);
290
289
291 if (isDependencyRegistration<S>(data)) {
290 if (isDependencyRegistration<S>(data)) {
292 return this._visitDependencyRegistration(data, name);
291 return this._visitDependencyRegistration(data, name);
293 } else if (isValueRegistration(data)) {
292 } else if (isValueRegistration(data)) {
294 return this._visitValueRegistration(data, name);
293 return this._visitValueRegistration(data, name);
295 } else if (isTypeRegistration(data)) {
294 } else if (isTypeRegistration(data)) {
296 return this._visitTypeRegistration(data, name);
295 return this._visitTypeRegistration(data, name);
297 } else if (isFactoryRegistration(data)) {
296 } else if (isFactoryRegistration(data)) {
298 return this._visitFactoryRegistration(data, name);
297 return this._visitFactoryRegistration(data, name);
299 } else if (data instanceof Array) {
298 } else if (data instanceof Array) {
300 return this._visitArray(data, name);
299 return this._visitArray(data, name);
301 }
300 }
302
301
303 return this._visitObject(data, name);
302 return this._visitObject(data, name);
304 }
303 }
305
304
306 async _visitObject(data: any, name: string) {
305 async _visitObject(data: any, name: string) {
307 if (data.constructor &&
306 if (data.constructor &&
308 data.constructor.prototype !== Object.prototype)
307 data.constructor.prototype !== Object.prototype)
309 return new ValueDescriptor(data);
308 return new ValueDescriptor(data);
310
309
311 this._enter(name);
310 this._enter(name);
312
311
313 const v = await mapAll(data, delegate(this, "_visit"));
312 const v = await mapAll(data, delegate(this, "_visit"));
314
313
315 // TODO: handle inline descriptors properly
314 // TODO: handle inline descriptors properly
316 // const ex = {
315 // const ex = {
317 // activate(ctx) {
316 // activate(ctx) {
318 // const value = ctx.activate(this.prop, "prop");
317 // const value = ctx.activate(this.prop, "prop");
319 // // some code
318 // // some code
320 // },
319 // },
321 // // will be turned to ReferenceDescriptor
320 // // will be turned to ReferenceDescriptor
322 // prop: { $dependency: "depName" }
321 // prop: { $dependency: "depName" }
323 // };
322 // };
324
323
325 this._leave();
324 this._leave();
326 return v;
325 return v;
327 }
326 }
328
327
329 async _visitArray(data: any[], name: string) {
328 async _visitArray(data: any[], name: string) {
330 if (data.constructor &&
329 if (data.constructor &&
331 data.constructor.prototype !== Array.prototype)
330 data.constructor.prototype !== Array.prototype)
332 return new ValueDescriptor(data);
331 return new ValueDescriptor(data);
333
332
334 this._enter(name);
333 this._enter(name);
335
334
336 const v = await mapAll(data, delegate(this, "_visit"));
335 const v = await mapAll(data, delegate(this, "_visit"));
337 this._leave();
336 this._leave();
338
337
339 return v;
338 return v;
340 }
339 }
341
340
342 _makeServiceParams(data: ServiceRegistration<any, S>) {
341 _makeServiceParams(data: ServiceRegistration<any, S>) {
343 const opts: any = {
342 const opts: any = {
344 };
343 };
345 if (data.services)
344 if (data.services)
346 opts.services = this._visitRegistrations(data.services, "services");
345 opts.services = this._visitRegistrations(data.services, "services");
347
346
348 if (data.inject) {
347 if (data.inject) {
349 this._enter("inject");
348 this._enter("inject");
350 opts.inject = mapAll(
349 opts.inject = mapAll(
351 data.inject instanceof Array ?
350 data.inject instanceof Array ?
352 data.inject :
351 data.inject :
353 [data.inject],
352 [data.inject],
354 delegate(this, "_visitObject")
353 delegate(this, "_visitObject")
355 );
354 );
356 this._leave();
355 this._leave();
357 }
356 }
358
357
359 if ("params" in data)
358 if ("params" in data)
360 opts.params = data.params instanceof Array ?
359 opts.params = data.params instanceof Array ?
361 this._visitArray(data.params, "params") :
360 this._visitArray(data.params, "params") :
362 this._visit(data.params, "params");
361 this._visit(data.params, "params");
363
362
364 if (data.activation) {
363 if (data.activation) {
365 opts.activation = this._getLifetimeManager(data.activation, data.typeId);
364 opts.activation = this._getLifetimeManager(data.activation, data.typeId);
366 }
365 }
367
366
368 if (data.cleanup)
367 if (data.cleanup)
369 opts.cleanup = data.cleanup;
368 opts.cleanup = data.cleanup;
370
369
371 return opts;
370 return opts;
372 }
371 }
373
372
374 async _visitValueRegistration<T>(data: ValueRegistration<T>, name: string) {
373 async _visitValueRegistration<T>(data: ValueRegistration<T>, name: string) {
375 this._enter(name);
374 this._enter(name);
376 const d = data.parse ? new AggregateDescriptor(data.$value) : new ValueDescriptor(data.$value);
375 const d = data.parse ? new AggregateDescriptor(data.$value) : new ValueDescriptor(data.$value);
377 this._leave();
376 this._leave();
378 return d;
377 return d;
379 }
378 }
380
379
381 async _visitDependencyRegistration<K extends keyof S>(data: DependencyRegistration<S, K>, name: string) {
380 async _visitDependencyRegistration<K extends keyof S>(data: DependencyRegistration<S, K>, name: string) {
382 argumentNotEmptyString(data && data.$dependency, "data.$dependency");
381 argumentNotEmptyString(data && data.$dependency, "data.$dependency");
383 this._enter(name);
382 this._enter(name);
384 const options = {
383 const options = {
385 name: data.$dependency,
384 name: data.$dependency,
386 optional: data.optional,
385 optional: data.optional,
387 default: data.default,
386 default: data.default,
388 services: data.services && await this._visitRegistrations(data.services, "services")
387 services: data.services && await this._visitRegistrations(data.services, "services")
389 };
388 };
390 const d = data.lazy ? new LazyReferenceDescriptor<S, K>(options) : new ReferenceDescriptor<S, K>(options);
389 const d = data.lazy ? new LazyReferenceDescriptor<S, K>(options) : new ReferenceDescriptor<S, K>(options);
391 this._leave();
390 this._leave();
392 return d;
391 return d;
393 }
392 }
394
393
395 async _visitTypeRegistration(data: TypeRegistration<new () => any, S>, name: string) {
394 async _visitTypeRegistration(data: TypeRegistration<new () => any, S>, name: string) {
396 argumentNotNull(data.$type, "data.$type");
395 argumentNotNull(data.$type, "data.$type");
397 this._enter(name);
396 this._enter(name);
398
397
399 const opts = this._makeServiceParams(data);
398 const opts = this._makeServiceParams(data);
400 if (data.$type instanceof Function) {
399 if (data.$type instanceof Function) {
401 opts.type = data.$type;
400 opts.type = data.$type;
402 } else {
401 } else {
403 const [moduleName, typeName] = data.$type.split(":", 2);
402 const [moduleName, typeName] = data.$type.split(":", 2);
404 opts.type = this._resolveType(moduleName, typeName).then(t => {
403 opts.type = this._resolveType(moduleName, typeName).then(t => {
405 if (!(t instanceof Function))
404 if (!(t instanceof Function))
406 throw Error("$type (" + data.$type + ") is not a constructable");
405 throw Error("$type (" + data.$type + ") is not a constructable");
407 return t;
406 return t;
408 });
407 });
409 }
408 }
410
409
411 const d = new TypeServiceDescriptor<S, any, any[]>(
410 const d = new TypeServiceDescriptor<S, any, any[]>(
412 await mapAll(opts)
411 await mapAll(opts)
413 );
412 );
414
413
415 this._leave();
414 this._leave();
416
415
417 return d;
416 return d;
418 }
417 }
419
418
420 async _visitFactoryRegistration(data: FactoryRegistration<() => any, S>, name: string) {
419 async _visitFactoryRegistration(data: FactoryRegistration<() => any, S>, name: string) {
421 argumentOfType(data.$factory, Function, "data.$factory");
420 argumentOfType(data.$factory, Function, "data.$factory");
422 this._enter(name);
421 this._enter(name);
423
422
424 const opts = this._makeServiceParams(data);
423 const opts = this._makeServiceParams(data);
425 opts.factory = data.$factory;
424 opts.factory = data.$factory;
426
425
427 const d = new FactoryServiceDescriptor<S, any, any[]>(
426 const d = new FactoryServiceDescriptor<S, any, any[]>(
428 await mapAll(opts)
427 await mapAll(opts)
429 );
428 );
430
429
431 this._leave();
430 this._leave();
432 return d;
431 return d;
433 }
432 }
434
433
435 _getLifetimeManager(activation: ActivationType, typeId: string | undefined): ILifetime {
434 _getLifetimeManager(activation: ActivationType, typeId: string | undefined): ILifetime {
436 switch (activation) {
435 switch (activation) {
437 case "container":
436 case "container":
438 return LifetimeManager.containerLifetime(this._container);
437 return LifetimeManager.containerLifetime(this._container);
439 case "hierarchy":
438 case "hierarchy":
440 return LifetimeManager.hierarchyLifetime();
439 return LifetimeManager.hierarchyLifetime();
441 case "context":
440 case "context":
442 return LifetimeManager.contextLifetime();
441 return LifetimeManager.contextLifetime();
443 case "singleton":
442 case "singleton":
444 if (typeId === undefined)
443 if (typeId === undefined)
445 throw Error("The singleton activation requires a typeId");
444 throw Error("The singleton activation requires a typeId");
446 return LifetimeManager.singletonLifetime(typeId);
445 return LifetimeManager.singletonLifetime(typeId);
447 default:
446 default:
448 return LifetimeManager.empty();
447 return LifetimeManager.empty();
449 }
448 }
450 }
449 }
451 }
450 }
@@ -1,172 +1,173
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, ServiceLocator, ContainerServiceMap, ContainerKeys, TypeOfService } from "./interfaces";
4 import { ServiceMap, Descriptor, PartialServiceMap, ContainerServiceMap, ContainerKeys, TypeOfService, ServiceContainer } from "./interfaces";
5 import { TraceSource } from "../log/TraceSource";
5 import { TraceSource } from "../log/TraceSource";
6 import { Configuration, RegistrationMap } from "./Configuration";
6 import { Configuration, RegistrationMap } from "./Configuration";
7 import { Cancellation } from "../Cancellation";
7 import { Cancellation } from "../Cancellation";
8 import { IDestroyable, PromiseOrValue, ICancellation } from "../interfaces";
8 import { IDestroyable, ICancellation } from "../interfaces";
9 import { isDescriptor } from "./traits";
9 import { isDescriptor } from "./traits";
10 import { LifetimeManager } from "./LifetimeManager";
10 import { LifetimeManager } from "./LifetimeManager";
11 import { each, isString } from "../safe";
11 import { each, isString } from "../safe";
12 import { ContainerConfiguration, FluentRegistrations } from "./fluent/interfaces";
12 import { ContainerConfiguration, FluentRegistrations } from "./fluent/interfaces";
13 import { FluentConfiguration } from "./fluent/FluentConfiguration";
13 import { FluentConfiguration } from "./fluent/FluentConfiguration";
14
14
15 const trace = TraceSource.get("@implab/core/di/ActivationContext");
15 const trace = TraceSource.get("@implab/core/di/ActivationContext");
16
16
17 export class Container<S extends object = any> implements ServiceLocator<S>, IDestroyable {
17 export class Container<S extends object = any> implements ServiceContainer<S>, IDestroyable {
18 readonly _services: ContainerServiceMap<S>;
18 readonly _services: ContainerServiceMap<S>;
19
19
20 readonly _lifetimeManager: LifetimeManager;
20 readonly _lifetimeManager: LifetimeManager;
21
21
22 readonly _cleanup: (() => void)[];
22 readonly _cleanup: (() => void)[];
23
23
24 readonly _root: Container<S>;
24 readonly _root: Container<S>;
25
25
26 readonly _parent?: Container<S>;
26 readonly _parent?: Container<S>;
27
27
28 _disposed: boolean;
28 _disposed: boolean;
29
29
30 constructor(parent?: Container<S>) {
30 constructor(parent?: Container<S>) {
31 this._parent = parent;
31 this._parent = parent;
32 this._services = parent ? Object.create(parent._services) : {};
32 this._services = parent ? Object.create(parent._services) : {};
33 this._cleanup = [];
33 this._cleanup = [];
34 this._root = parent ? parent.getRootContainer() : this;
34 this._root = parent ? parent.getRootContainer() : this;
35 this._services.container = new ValueDescriptor(this) as any;
35 this._services.container = new ValueDescriptor(this) as any;
36 this._disposed = false;
36 this._disposed = false;
37 this._lifetimeManager = new LifetimeManager();
37 this._lifetimeManager = new LifetimeManager();
38 }
38 }
39
39
40 getRootContainer() {
40 getRootContainer() {
41 return this._root;
41 return this._root;
42 }
42 }
43
43
44 getParent() {
44 getParent() {
45 return this._parent;
45 return this._parent;
46 }
46 }
47
47
48 getLifetimeManager() {
48 getLifetimeManager() {
49 return this._lifetimeManager;
49 return this._lifetimeManager;
50 }
50 }
51
51
52 resolve<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>): TypeOfService<S, K> {
52 resolve<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>): TypeOfService<S, K> {
53 trace.debug("resolve {0}", name);
53 trace.debug("resolve {0}", name);
54 const d = this._services[name];
54 const d = this._services[name];
55 if (d === undefined) {
55 if (d === undefined) {
56 if (def !== undefined)
56 if (def !== undefined)
57 return def;
57 return def;
58 else
58 else
59 throw new Error("Service '" + name + "' isn't found");
59 throw new Error("Service '" + name + "' isn't found");
60 } else {
60 } else {
61
61
62 const context = new ActivationContext<S>(this, this._services, String(name), d);
62 const context = new ActivationContext<S>(this, this._services, String(name), d);
63 try {
63 try {
64 return d.activate(context);
64 return d.activate(context);
65 } catch (error) {
65 } catch (error) {
66 throw new ActivationError(name.toString(), context.getStack(), error);
66 throw new ActivationError(name.toString(), context.getStack(), error);
67 }
67 }
68 }
68 }
69 }
69 }
70
70
71 /**
71 /**
72 * @deprecated use resolve() method
72 * @deprecated use resolve() method
73 */
73 */
74 getService<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>) {
74 getService<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>) {
75 return this.resolve(name, def);
75 return this.resolve(name, def);
76 }
76 }
77
77
78 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>): this;
78 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>): this;
79 register(services: PartialServiceMap<S>): this;
79 register(services: PartialServiceMap<S>): this;
80 register<K extends keyof S>(nameOrCollection: K | ServiceMap<S>, service?: Descriptor<S, S[K]>) {
80 register<K extends keyof S>(nameOrCollection: K | ServiceMap<S>, service?: Descriptor<S, S[K]>) {
81 if (arguments.length === 1) {
81 if (arguments.length === 1) {
82 const data = nameOrCollection as ServiceMap<S>;
82 const data = nameOrCollection as ServiceMap<S>;
83
83
84 each(data, (v, k) => this.register(k, v));
84 each(data, (v, k) => this.register(k, v));
85 } else {
85 } else {
86 if (!isDescriptor(service))
86 if (!isDescriptor(service))
87 throw new Error("The service parameter must be a descriptor");
87 throw new Error("The service parameter must be a descriptor");
88
88
89 this._services[nameOrCollection as K] = service as any;
89 this._services[nameOrCollection as K] = service as any;
90 }
90 }
91 return this;
91 return this;
92 }
92 }
93
93
94 /** @deprecated use getLifetimeManager() */
94 onDispose(callback: () => void) {
95 onDispose(callback: () => void) {
95 if (!(callback instanceof Function))
96 if (!(callback instanceof Function))
96 throw new Error("The callback must be a function");
97 throw new Error("The callback must be a function");
97 this._cleanup.push(callback);
98 this._cleanup.push(callback);
98 }
99 }
99
100
100 destroy() {
101 destroy() {
101 return this.dispose();
102 return this.dispose();
102 }
103 }
103 dispose() {
104 dispose() {
104 if (this._disposed)
105 if (this._disposed)
105 return;
106 return;
106 this._disposed = true;
107 this._disposed = true;
107 for (const f of this._cleanup)
108 for (const f of this._cleanup)
108 f();
109 f();
109 }
110 }
110
111
111 /**
112 /**
112 * @param{String|Object} config
113 * @param{String|Object} config
113 * The configuration of the container. Can be either a string or an object,
114 * The configuration of the container. Can be either a string or an object,
114 * if the configuration is an object it's treated as a collection of
115 * if the configuration is an object it's treated as a collection of
115 * services which will be registered in the container.
116 * services which will be registered in the container.
116 *
117 *
117 * @param{Function} opts.contextRequire
118 * @param{Function} opts.contextRequire
118 * The function which will be used to load a configuration or types for services.
119 * The function which will be used to load a configuration or types for services.
119 *
120 *
120 */
121 */
121 async configure(config: string | RegistrationMap<S>, opts?: { contextRequire: any; baseModule?: string }, ct = Cancellation.none) {
122 async configure(config: string | RegistrationMap<S>, opts?: { contextRequire: any; baseModule?: string }, ct = Cancellation.none) {
122 const _opts = Object.create(opts || null);
123 const _opts = Object.create(opts || null);
123
124
124 if (typeof (config) === "string") {
125 if (typeof (config) === "string") {
125 _opts.baseModule = config;
126 _opts.baseModule = config;
126
127
127 const module = await import(config);
128 const module = await import(config);
128 if (module && module.default && typeof (module.default.apply) === "function")
129 if (module && module.default && typeof (module.default.apply) === "function")
129 return module.default.apply(this);
130 return module.default.apply(this);
130 else
131 else
131 return this._applyLegacyConfig(module, _opts, ct);
132 return this._applyLegacyConfig(module, _opts, ct);
132 } else {
133 } else {
133 return this._applyLegacyConfig(config, _opts, ct);
134 return this._applyLegacyConfig(config, _opts, ct);
134 }
135 }
135 }
136 }
136
137
137 applyConfig<S2 extends object>(config: Promise<{ default: ContainerConfiguration<S2>; }>, ct?: ICancellation): Promise<Container<S & S2>>;
138 applyConfig<S2 extends object>(config: Promise<{ default: ContainerConfiguration<S2>; }>, ct?: ICancellation): Promise<ServiceContainer<S & S2>>;
138 applyConfig<S2 extends object, P extends string>(config: Promise<{ [p in P]: ContainerConfiguration<S2>; }>, prop: P, ct?: ICancellation): Promise<Container<S & S2>>;
139 applyConfig<S2 extends object, P extends string>(config: Promise<{ [p in P]: ContainerConfiguration<S2>; }>, prop: P, ct?: ICancellation): Promise<ServiceContainer<S & S2>>;
139 async applyConfig<S2 extends object, P extends string>(
140 async applyConfig<S2 extends object, P extends string>(
140 config: Promise<{ [p in P | "default"]: ContainerConfiguration<S2>; }>,
141 config: Promise<{ [p in P | "default"]: ContainerConfiguration<S2>; }>,
141 propOrCt?: P | ICancellation,
142 propOrCt?: P | ICancellation,
142 ct?: ICancellation
143 ct?: ICancellation
143 ): Promise<Container<S & S2>> {
144 ): Promise<ServiceContainer<S & S2>> {
144 const mod = await config;
145 const mod = await config;
145
146
146 let _ct: ICancellation;
147 let _ct: ICancellation;
147 let _prop: P | "default";
148 let _prop: P | "default";
148
149
149 if (isString(propOrCt)) {
150 if (isString(propOrCt)) {
150 _prop = propOrCt;
151 _prop = propOrCt;
151 _ct = ct || Cancellation.none;
152 _ct = ct || Cancellation.none;
152 } else {
153 } else {
153 _ct = propOrCt || Cancellation.none;
154 _ct = propOrCt || Cancellation.none;
154 _prop = "default";
155 _prop = "default";
155 }
156 }
156
157
157 return mod[_prop].apply(this, _ct);
158 return mod[_prop].apply(this, _ct);
158 }
159 }
159
160
160 async _applyLegacyConfig(config: RegistrationMap<S>, opts: { contextRequire: any; baseModule?: string }, ct = Cancellation.none) {
161 async _applyLegacyConfig(config: RegistrationMap<S>, opts: { contextRequire: any; baseModule?: string }, ct = Cancellation.none) {
161 return new Configuration<S>(this).applyConfiguration(config, opts);
162 return new Configuration<S>(this).applyConfiguration(config, opts);
162 }
163 }
163
164
164 async fluent<K extends keyof S>(config: FluentRegistrations<K, S>, ct = Cancellation.none): Promise<this> {
165 async fluent<K extends keyof S>(config: FluentRegistrations<K, S>, ct = Cancellation.none): Promise<this> {
165 await new FluentConfiguration<S>().register(config).apply(this, ct);
166 await new FluentConfiguration<S>().register(config).apply(this, ct);
166 return this;
167 return this;
167 }
168 }
168
169
169 createChildContainer<S2 extends object = S>(): Container<S & S2> {
170 createChildContainer<S2 extends object = S>(): Container<S & S2> {
170 return new Container<S & S2>(this as any);
171 return new Container<S & S2>(this as any);
171 }
172 }
172 }
173 }
@@ -1,198 +1,197
1 import { IDestroyable, MapOf } from "../interfaces";
1 import { IDestroyable, MapOf } from "../interfaces";
2 import { argumentNotNull, isDestroyable, primitive, isNull, argumentNotEmptyString } from "../safe";
2 import { argumentNotNull, isDestroyable, argumentNotEmptyString } from "../safe";
3 import { ILifetime } from "./interfaces";
3 import { ILifetime, ServiceContainer } from "./interfaces";
4 import { ActivationContext } from "./ActivationContext";
4 import { ActivationContext } from "./ActivationContext";
5 import { Container } from "./Container";
6
5
7 function safeCall(item: () => void) {
6 function safeCall(item: () => void) {
8 try {
7 try {
9 item();
8 item();
10 } catch {
9 } catch {
11 // silence!
10 // silence!
12 }
11 }
13 }
12 }
14
13
15 const emptyLifetime: ILifetime = Object.freeze({
14 const emptyLifetime: ILifetime = Object.freeze({
16 has() {
15 has() {
17 return false;
16 return false;
18 },
17 },
19
18
20 initialize() {
19 initialize() {
21
20
22 },
21 },
23
22
24 get() {
23 get() {
25 throw new Error("The specified item isn't registered with this lifetime manager");
24 throw new Error("The specified item isn't registered with this lifetime manager");
26 },
25 },
27
26
28 store() {
27 store() {
29 // does nothing
28 // does nothing
30 }
29 }
31
30
32 });
31 });
33
32
34 const unknownLifetime: ILifetime = Object.freeze({
33 const unknownLifetime: ILifetime = Object.freeze({
35 has() {
34 has() {
36 return false;
35 return false;
37 },
36 },
38 initialize() {
37 initialize() {
39 throw new Error("Can't call initialize on the unknown lifetime object");
38 throw new Error("Can't call initialize on the unknown lifetime object");
40 },
39 },
41 get() {
40 get() {
42 throw new Error("The lifetime object isn't initialized");
41 throw new Error("The lifetime object isn't initialized");
43 },
42 },
44 store() {
43 store() {
45 throw new Error("Can't store a value in the unknown lifetime object");
44 throw new Error("Can't store a value in the unknown lifetime object");
46 }
45 }
47 });
46 });
48
47
49 let nextId = 0;
48 let nextId = 0;
50
49
51 const singletons: { [k in keyof any]: any; } = {};
50 const singletons: any = {};
52
51
53 export class LifetimeManager implements IDestroyable {
52 export class LifetimeManager implements IDestroyable {
54 private _cleanup: (() => void)[] = [];
53 private _cleanup: (() => void)[] = [];
55 private _cache: MapOf<any> = {};
54 private _cache: MapOf<any> = {};
56 private _destroyed = false;
55 private _destroyed = false;
57
56
58 private _pending: MapOf<boolean> = {};
57 private _pending: MapOf<boolean> = {};
59
58
60 create(): ILifetime {
59 create(): ILifetime {
61 const self = this;
60 const self = this;
62 const id = ++nextId;
61 const id = ++nextId;
63 return {
62 return {
64 has() {
63 has() {
65 return (id in self._cache);
64 return (id in self._cache);
66 },
65 },
67
66
68 get() {
67 get() {
69 const t = self._cache[id];
68 const t = self._cache[id];
70 if (t === undefined)
69 if (t === undefined)
71 throw new Error(`The item with with the key ${id} isn't found`);
70 throw new Error(`The item with with the key ${id} isn't found`);
72 return t;
71 return t;
73 },
72 },
74
73
75 initialize() {
74 initialize() {
76 if (self._pending[id])
75 if (self._pending[id])
77 throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`);
76 throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`);
78 self._pending[id] = true;
77 self._pending[id] = true;
79 },
78 },
80
79
81 store(item: any, cleanup?: (item: any) => void) {
80 store(item: any, cleanup?: (item: any) => void) {
82 argumentNotNull(id, "id");
81 argumentNotNull(id, "id");
83 argumentNotNull(item, "item");
82 argumentNotNull(item, "item");
84
83
85 if (this.has())
84 if (this.has())
86 throw new Error(`The item with with the key ${id} already registered with this lifetime manager`);
85 throw new Error(`The item with with the key ${id} already registered with this lifetime manager`);
87 delete self._pending[id];
86 delete self._pending[id];
88
87
89 self._cache[id] = item;
88 self._cache[id] = item;
90
89
91 if (self._destroyed)
90 if (self._destroyed)
92 throw new Error("Lifetime manager is destroyed");
91 throw new Error("Lifetime manager is destroyed");
93 if (cleanup) {
92 if (cleanup) {
94 self._cleanup.push(() => cleanup(item));
93 self._cleanup.push(() => cleanup(item));
95 } else if (isDestroyable(item)) {
94 } else if (isDestroyable(item)) {
96 self._cleanup.push(() => item.destroy());
95 self._cleanup.push(() => item.destroy());
97 }
96 }
98 }
97 }
99 };
98 };
100 }
99 }
101
100
102 destroy() {
101 destroy() {
103 if (!this._destroyed) {
102 if (!this._destroyed) {
104 this._destroyed = true;
103 this._destroyed = true;
105 this._cleanup.forEach(safeCall);
104 this._cleanup.forEach(safeCall);
106 this._cleanup.length = 0;
105 this._cleanup.length = 0;
107 }
106 }
108 }
107 }
109
108
110 static empty(): ILifetime {
109 static empty(): ILifetime {
111 return emptyLifetime;
110 return emptyLifetime;
112 }
111 }
113
112
114 static hierarchyLifetime(): ILifetime {
113 static hierarchyLifetime(): ILifetime {
115 let _lifetime = unknownLifetime;
114 let _lifetime = unknownLifetime;
116 return {
115 return {
117 initialize(context: ActivationContext<any>) {
116 initialize(context: ActivationContext<any>) {
118 if (_lifetime !== unknownLifetime)
117 if (_lifetime !== unknownLifetime)
119 throw new Error("Cyclic reference activation detected");
118 throw new Error("Cyclic reference activation detected");
120
119
121 _lifetime = context.getContainer().getLifetimeManager().create();
120 _lifetime = context.getContainer().getLifetimeManager().create();
122 },
121 },
123 get() {
122 get() {
124 return _lifetime.get();
123 return _lifetime.get();
125 },
124 },
126 has() {
125 has() {
127 return _lifetime.has();
126 return _lifetime.has();
128 },
127 },
129 store(item: any, cleanup?: (item: any) => void) {
128 store(item: any, cleanup?: (item: any) => void) {
130 return _lifetime.store(item, cleanup);
129 return _lifetime.store(item, cleanup);
131 }
130 }
132 };
131 };
133 }
132 }
134
133
135 static contextLifetime(): ILifetime {
134 static contextLifetime(): ILifetime {
136 let _lifetime = unknownLifetime;
135 let _lifetime = unknownLifetime;
137 return {
136 return {
138 initialize(context: ActivationContext<any>) {
137 initialize(context: ActivationContext<any>) {
139 if (_lifetime !== unknownLifetime)
138 if (_lifetime !== unknownLifetime)
140 throw new Error("Cyclic reference detected");
139 throw new Error("Cyclic reference detected");
141 _lifetime = context.createLifetime();
140 _lifetime = context.createLifetime();
142 },
141 },
143 get() {
142 get() {
144 return _lifetime.get();
143 return _lifetime.get();
145 },
144 },
146 has() {
145 has() {
147 return _lifetime.has();
146 return _lifetime.has();
148 },
147 },
149 store(item: any) {
148 store(item: any) {
150 _lifetime.store(item);
149 _lifetime.store(item);
151 }
150 }
152 };
151 };
153 }
152 }
154
153
155 static singletonLifetime(typeId: string): ILifetime {
154 static singletonLifetime(typeId: string): ILifetime {
156 argumentNotEmptyString(typeId, "typeId");
155 argumentNotEmptyString(typeId, "typeId");
157 let pending = false;
156 let pending = false;
158 return {
157 return {
159 has() {
158 has() {
160 return typeId in singletons;
159 return typeId in singletons;
161 },
160 },
162 get() {
161 get() {
163 if (!this.has())
162 if (!this.has())
164 throw new Error(`The instance ${typeId} doesn't exists`);
163 throw new Error(`The instance ${typeId} doesn't exists`);
165 return singletons[typeId];
164 return singletons[typeId];
166 },
165 },
167 initialize() {
166 initialize() {
168 if (pending)
167 if (pending)
169 throw new Error("Cyclic reference detected");
168 throw new Error("Cyclic reference detected");
170 pending = true;
169 pending = true;
171 },
170 },
172 store(item: any) {
171 store(item: any) {
173 singletons[typeId] = item;
172 singletons[typeId] = item;
174 pending = false;
173 pending = false;
175 }
174 }
176 };
175 };
177 }
176 }
178
177
179 static containerLifetime(container: Container<any>) {
178 static containerLifetime(container: ServiceContainer<any>) {
180 let _lifetime = unknownLifetime;
179 let _lifetime = unknownLifetime;
181 return {
180 return {
182 initialize(context: ActivationContext<any>) {
181 initialize(context: ActivationContext<any>) {
183 if (_lifetime !== unknownLifetime)
182 if (_lifetime !== unknownLifetime)
184 throw new Error("Cyclic reference detected");
183 throw new Error("Cyclic reference detected");
185 _lifetime = container.getLifetimeManager().create();
184 _lifetime = container.getLifetimeManager().create();
186 },
185 },
187 get() {
186 get() {
188 return _lifetime.get();
187 return _lifetime.get();
189 },
188 },
190 has() {
189 has() {
191 return _lifetime.has();
190 return _lifetime.has();
192 },
191 },
193 store(item: any) {
192 store(item: any) {
194 _lifetime.store(item);
193 _lifetime.store(item);
195 }
194 }
196 };
195 };
197 }
196 }
198 }
197 }
@@ -1,147 +1,146
1 import { Resolver, RegistrationBuilder } from "./interfaces";
1 import { Resolver, RegistrationBuilder } from "./interfaces";
2 import { Container } from "../Container";
2 import { Descriptor, ILifetime, ActivationType, PartialServiceMap, ServiceContainer } from "../interfaces";
3 import { Descriptor, ILifetime, ActivationType, PartialServiceMap } from "../interfaces";
4 import { DescriptorImpl } from "./DescriptorImpl";
3 import { DescriptorImpl } from "./DescriptorImpl";
5 import { LifetimeManager } from "../LifetimeManager";
4 import { LifetimeManager } from "../LifetimeManager";
6 import { isString, each, isPrimitive, isPromise, oid } from "../../safe";
5 import { isString, each, isPrimitive, isPromise, oid } from "../../safe";
7
6
8 export class DescriptorBuilder<S extends object, T> {
7 export class DescriptorBuilder<S extends object, T> {
9 private readonly _container: Container<S>;
8 private readonly _container: ServiceContainer<S>;
10 private readonly _cb: (d: Descriptor<S, T>) => void;
9 private readonly _cb: (d: Descriptor<S, T>) => void;
11
10
12 private readonly _eb: (err: any) => void;
11 private readonly _eb: (err: any) => void;
13
12
14 private _lifetime = LifetimeManager.empty();
13 private _lifetime = LifetimeManager.empty();
15
14
16 private _overrides?: PartialServiceMap<S>;
15 private _overrides?: PartialServiceMap<S>;
17
16
18 private _cleanup?: (item: T) => void;
17 private _cleanup?: (item: T) => void;
19
18
20 private _factory?: (resolve: Resolver<S>) => T;
19 private _factory?: (resolve: Resolver<S>) => T;
21
20
22 private _pending = 1;
21 private _pending = 1;
23
22
24 private _failed = false;
23 private _failed = false;
25
24
26 constructor(container: Container<S>, cb: (d: Descriptor<S, T>) => void, eb: (err: any) => void) {
25 constructor(container: ServiceContainer<S>, cb: (d: Descriptor<S, T>) => void, eb: (err: any) => void) {
27 this._container = container;
26 this._container = container;
28 this._cb = cb;
27 this._cb = cb;
29 this._eb = eb;
28 this._eb = eb;
30 }
29 }
31
30
32 build<T2>(): DescriptorBuilder<S, T2> {
31 build<T2>(): DescriptorBuilder<S, T2> {
33 this._defer();
32 this._defer();
34 return new DescriptorBuilder<S, T2>(this._container, () => this._complete(), err => this._fail(err));
33 return new DescriptorBuilder<S, T2>(this._container, () => this._complete(), err => this._fail(err));
35 }
34 }
36
35
37 override<K extends keyof S>(name: K, builder: RegistrationBuilder<S, S[K]>): this;
36 override<K extends keyof S>(name: K, builder: RegistrationBuilder<S, S[K]>): this;
38 override<K extends keyof S>(services: { [name in K]: RegistrationBuilder<S, S[K]> }): this;
37 override<K extends keyof S>(services: { [name in K]: RegistrationBuilder<S, S[K]> }): this;
39 override<K extends keyof S>(nameOrServices: K | { [name in K]: RegistrationBuilder<S, S[K]> }, builder?: RegistrationBuilder<S, S[K]>): this {
38 override<K extends keyof S>(nameOrServices: K | { [name in K]: RegistrationBuilder<S, S[K]> }, builder?: RegistrationBuilder<S, S[K]>): this {
40 const overrides: PartialServiceMap<S> = this._overrides ?
39 const overrides: PartialServiceMap<S> = this._overrides ?
41 this._overrides :
40 this._overrides :
42 (this._overrides = {});
41 (this._overrides = {});
43
42
44 const guard = (v: void | Promise<void>) => {
43 const guard = (v: void | Promise<void>) => {
45 if (isPromise(v))
44 if (isPromise(v))
46 v.catch(err => this._fail(err));
45 v.catch(err => this._fail(err));
47 };
46 };
48
47
49 if (isPrimitive(nameOrServices)) {
48 if (isPrimitive(nameOrServices)) {
50 if (builder) {
49 if (builder) {
51 this._defer();
50 this._defer();
52 const d = new DescriptorBuilder<S, S[K]>(
51 const d = new DescriptorBuilder<S, S[K]>(
53 this._container,
52 this._container,
54 result => {
53 result => {
55 overrides[nameOrServices] = result;
54 overrides[nameOrServices] = result;
56 this._complete();
55 this._complete();
57 },
56 },
58 err => this._fail(err)
57 err => this._fail(err)
59 );
58 );
60
59
61 try {
60 try {
62 guard(builder(d));
61 guard(builder(d));
63 } catch (err) {
62 } catch (err) {
64 this._fail(err);
63 this._fail(err);
65 }
64 }
66 }
65 }
67 } else {
66 } else {
68 each(nameOrServices, (v, k) => this.override(k, v));
67 each(nameOrServices, (v, k) => this.override(k, v));
69 }
68 }
70 return this;
69 return this;
71 }
70 }
72
71
73 lifetime(lifetime: "singleton", typeId: string): this;
72 lifetime(lifetime: "singleton", typeId: string): this;
74 lifetime(lifetime: ILifetime | Exclude<ActivationType, "singleton">): this;
73 lifetime(lifetime: ILifetime | Exclude<ActivationType, "singleton">): this;
75 lifetime(lifetime: ILifetime | ActivationType, typeId?: string): this {
74 lifetime(lifetime: ILifetime | ActivationType, typeId?: string): this {
76 if (isString(lifetime)) {
75 if (isString(lifetime)) {
77 this._lifetime = this._resolveLifetime(lifetime, typeId);
76 this._lifetime = this._resolveLifetime(lifetime, typeId);
78 } else {
77 } else {
79 this._lifetime = lifetime;
78 this._lifetime = lifetime;
80 }
79 }
81 return this;
80 return this;
82 }
81 }
83
82
84 cleanup(cb: (item: T) => void): this {
83 cleanup(cb: (item: T) => void): this {
85 this._cleanup = cb;
84 this._cleanup = cb;
86 return this;
85 return this;
87 }
86 }
88
87
89 factory(f: (resolve: Resolver<S>) => T): void {
88 factory(f: (resolve: Resolver<S>) => T): void {
90 this._factory = f;
89 this._factory = f;
91 this._complete();
90 this._complete();
92 }
91 }
93
92
94 value(v: T): void {
93 value(v: T): void {
95 this._cb({
94 this._cb({
96 activate() {
95 activate() {
97 return v;
96 return v;
98 }
97 }
99 });
98 });
100 }
99 }
101
100
102 _resolveLifetime(activation: ActivationType, typeId?: string | object) {
101 _resolveLifetime(activation: ActivationType, typeId?: string | object) {
103 switch (activation) {
102 switch (activation) {
104 case "container":
103 case "container":
105 return LifetimeManager.containerLifetime(this._container);
104 return LifetimeManager.containerLifetime(this._container);
106 case "hierarchy":
105 case "hierarchy":
107 return LifetimeManager.hierarchyLifetime();
106 return LifetimeManager.hierarchyLifetime();
108 case "context":
107 case "context":
109 return LifetimeManager.contextLifetime();
108 return LifetimeManager.contextLifetime();
110 case "singleton":
109 case "singleton":
111 if (!typeId)
110 if (!typeId)
112 throw Error("The singleton activation requires a typeId");
111 throw Error("The singleton activation requires a typeId");
113
112
114 const _oid = isString(typeId) ? typeId : oid(typeId);
113 const _oid = isString(typeId) ? typeId : oid(typeId);
115
114
116 return LifetimeManager.singletonLifetime(_oid);
115 return LifetimeManager.singletonLifetime(_oid);
117 default:
116 default:
118 return LifetimeManager.empty();
117 return LifetimeManager.empty();
119 }
118 }
120 }
119 }
121
120
122 _defer() {
121 _defer() {
123 this._pending++;
122 this._pending++;
124 }
123 }
125
124
126 _complete() {
125 _complete() {
127 if (--this._pending === 0) {
126 if (--this._pending === 0) {
128 if (!this._factory)
127 if (!this._factory)
129 throw new Error("The factory must be specified");
128 throw new Error("The factory must be specified");
130
129
131 this._cb(new DescriptorImpl<S, T>({
130 this._cb(new DescriptorImpl<S, T>({
132 lifetime: this._lifetime,
131 lifetime: this._lifetime,
133 factory: this._factory,
132 factory: this._factory,
134 overrides: this._overrides,
133 overrides: this._overrides,
135 cleanup: this._cleanup
134 cleanup: this._cleanup
136 }));
135 }));
137 }
136 }
138 }
137 }
139
138
140 _fail(err: any) {
139 _fail(err: any) {
141 if (!this._failed) {
140 if (!this._failed) {
142 this._failed = true;
141 this._failed = true;
143 this._eb.call(undefined, err);
142 this._eb.call(undefined, err);
144 }
143 }
145 }
144 }
146
145
147 }
146 }
@@ -1,68 +1,68
1 import { Container } from "../Container";
2 import { argumentNotNull, each, isPrimitive, isPromise } from "../../safe";
1 import { argumentNotNull, each, isPrimitive, isPromise } from "../../safe";
3 import { DescriptorBuilder } from "./DescriptorBuilder";
2 import { DescriptorBuilder } from "./DescriptorBuilder";
4 import { RegistrationBuilder, FluentRegistrations, ContainerConfiguration } from "./interfaces";
3 import { RegistrationBuilder, FluentRegistrations, ContainerConfiguration } from "./interfaces";
5 import { Cancellation } from "../../Cancellation";
4 import { Cancellation } from "../../Cancellation";
5 import { ServiceContainer } from "../interfaces";
6
6
7 export class FluentConfiguration<S extends object, Y extends keyof S = keyof S> {
7 export class FluentConfiguration<S extends object, Y extends keyof S = keyof S> {
8
8
9 _builders: { [k in keyof S]?: RegistrationBuilder<S, S[k]> } = {};
9 _builders: { [k in keyof S]?: RegistrationBuilder<S, S[k]> } = {};
10
10
11 provided<K extends Y>(): FluentConfiguration<S, Exclude<Y, K>> {
11 provided<K extends Y>(): FluentConfiguration<S, Exclude<Y, K>> {
12 return this;
12 return this;
13 }
13 }
14
14
15 register<K extends Y>(name: K, builder: RegistrationBuilder<S, S[K]>): FluentConfiguration<S, Exclude<Y, K>>;
15 register<K extends Y>(name: K, builder: RegistrationBuilder<S, S[K]>): FluentConfiguration<S, Exclude<Y, K>>;
16 register<K extends Y>(config: FluentRegistrations<K, S>): FluentConfiguration<S, Exclude<Y, K>>;
16 register<K extends Y>(config: FluentRegistrations<K, S>): FluentConfiguration<S, Exclude<Y, K>>;
17 register<K extends Y>(nameOrConfig: K | FluentRegistrations<K, S>, builder?: RegistrationBuilder<S, S[K]>): FluentConfiguration<S, Exclude<Y, K>> {
17 register<K extends Y>(nameOrConfig: K | FluentRegistrations<K, S>, builder?: RegistrationBuilder<S, S[K]>): FluentConfiguration<S, Exclude<Y, K>> {
18 if (isPrimitive(nameOrConfig)) {
18 if (isPrimitive(nameOrConfig)) {
19 argumentNotNull(builder, "builder");
19 argumentNotNull(builder, "builder");
20 this._builders[nameOrConfig] = builder;
20 this._builders[nameOrConfig] = builder;
21 } else {
21 } else {
22 each(nameOrConfig, (v, k) => this.register(k, v));
22 each(nameOrConfig, (v, k) => this.register(k, v));
23 }
23 }
24
24
25 return this;
25 return this;
26 }
26 }
27
27
28 configure(config: FluentRegistrations<Y, S>): ContainerConfiguration<S> {
28 configure(config: FluentRegistrations<Y, S>): ContainerConfiguration<S> {
29 return this.register(config);
29 return this.register(config);
30 }
30 }
31
31
32 apply<SC extends object>(target: Container<SC>, ct = Cancellation.none) {
32 apply<S2 extends object>(target: ServiceContainer<S2>, ct = Cancellation.none) {
33
33
34 let pending = 1;
34 let pending = 1;
35
35
36 const _t2 = target as unknown as Container<SC & S>;
36 const _t2 = target as unknown as ServiceContainer<S2 & S>;
37
37
38 return new Promise<Container<SC & S>>((resolve, reject) => {
38 return new Promise<ServiceContainer<S2 & S>>((resolve, reject) => {
39 function guard(v: void | Promise<void>) {
39 function guard(v: void | Promise<void>) {
40 if (isPromise(v))
40 if (isPromise(v))
41 v.catch(reject);
41 v.catch(reject);
42 }
42 }
43
43
44 function complete() {
44 function complete() {
45 if (!--pending)
45 if (!--pending)
46 resolve(_t2);
46 resolve(_t2);
47 }
47 }
48 each(this._builders, (v, k) => {
48 each(this._builders, (v, k) => {
49 pending++;
49 pending++;
50 const d = new DescriptorBuilder<SC & S, any>(_t2,
50 const d = new DescriptorBuilder<S2 & S, any>(_t2,
51 result => {
51 result => {
52 _t2.register(k, result);
52 _t2.register(k, result);
53 complete();
53 complete();
54 },
54 },
55 reject
55 reject
56 );
56 );
57
57
58 try {
58 try {
59 guard(v(d, ct));
59 guard(v(d, ct));
60 } catch (e) {
60 } catch (e) {
61 reject(e);
61 reject(e);
62 }
62 }
63 });
63 });
64 complete();
64 complete();
65 });
65 });
66 }
66 }
67
67
68 }
68 }
@@ -1,57 +1,56
1 import { primitive } from "../../safe";
1 import { primitive } from "../../safe";
2 import { TypeOfService, ContainerKeys, ActivationType, ILifetime } from "../interfaces";
2 import { TypeOfService, ContainerKeys, ActivationType, ILifetime, ServiceContainer } from "../interfaces";
3 import { ICancellation } from "../../interfaces";
3 import { ICancellation } from "../../interfaces";
4 import { Container } from "../Container";
5
4
6 export interface DependencyOptions {
5 export interface DependencyOptions {
7 optional?: boolean;
6 optional?: boolean;
8 default?: any;
7 default?: any;
9 }
8 }
10
9
11 export interface LazyDependencyOptions extends DependencyOptions {
10 export interface LazyDependencyOptions extends DependencyOptions {
12 lazy: true;
11 lazy: true;
13 }
12 }
14
13
15 export type ExtractService<K, S> = K extends keyof S ? S[K] : never;
14 export type ExtractService<K, S> = K extends keyof S ? S[K] : never;
16
15
17 export type ExtractDependency<D, S> = D extends { $dependency: infer K } ?
16 export type ExtractDependency<D, S> = D extends { $dependency: infer K } ?
18 D extends { lazy: true } ? () => ExtractService<K, S> : ExtractService<K, S> :
17 D extends { lazy: true } ? () => ExtractService<K, S> : ExtractService<K, S> :
19 D extends { $type: new (...args: any[]) => infer I } ? I :
18 D extends { $type: new (...args: any[]) => infer I } ? I :
20 D extends { $factory: (...args: any[]) => infer R } ? R :
19 D extends { $factory: (...args: any[]) => infer R } ? R :
21 WalkDependencies<D, S>;
20 WalkDependencies<D, S>;
22
21
23 export type WalkDependencies<D, S> = D extends primitive ? D :
22 export type WalkDependencies<D, S> = D extends primitive ? D :
24 { [K in keyof D]: ExtractDependency<D[K], S> };
23 { [K in keyof D]: ExtractDependency<D[K], S> };
25
24
26 export type InferReferenceType<S extends object, K extends ContainerKeys<S>, O> = O extends { default: infer X } ? (TypeOfService<S, K> | X) :
25 export type InferReferenceType<S extends object, K extends ContainerKeys<S>, O> = O extends { default: infer X } ? (TypeOfService<S, K> | X) :
27 O extends { optional: true } ? (TypeOfService<S, K> | undefined) :
26 O extends { optional: true } ? (TypeOfService<S, K> | undefined) :
28 TypeOfService<S, K>;
27 TypeOfService<S, K>;
29
28
30 export interface Resolver<S extends object> {
29 export interface Resolver<S extends object> {
31 <K extends ContainerKeys<S>, O extends LazyDependencyOptions>(this: void, name: K, opts: O): () => InferReferenceType<S, K, O>;
30 <K extends ContainerKeys<S>, O extends LazyDependencyOptions>(this: void, name: K, opts: O): () => InferReferenceType<S, K, O>;
32 <K extends ContainerKeys<S>, O extends DependencyOptions>(this: void, name: K, opts?: O): InferReferenceType<S, K, O>;
31 <K extends ContainerKeys<S>, O extends DependencyOptions>(this: void, name: K, opts?: O): InferReferenceType<S, K, O>;
33 }
32 }
34
33
35 export interface DescriptorBuilder<S extends object, T> {
34 export interface DescriptorBuilder<S extends object, T> {
36 factory(f: (resolve: Resolver<S>) => T): void;
35 factory(f: (resolve: Resolver<S>) => T): void;
37
36
38 build<T2>(): DescriptorBuilder<S, T2>;
37 build<T2>(): DescriptorBuilder<S, T2>;
39
38
40 override<K extends keyof S>(name: K, builder: RegistrationBuilder<S, S[K]>): this;
39 override<K extends keyof S>(name: K, builder: RegistrationBuilder<S, S[K]>): this;
41 override<K extends keyof S>(services: { [name in K]: RegistrationBuilder<S, S[K]> }): this;
40 override<K extends keyof S>(services: { [name in K]: RegistrationBuilder<S, S[K]> }): this;
42
41
43 lifetime(lifetime: "singleton", typeId: any): this;
42 lifetime(lifetime: "singleton", typeId: any): this;
44 lifetime(lifetime: ILifetime | Exclude<ActivationType, "singleton">): this;
43 lifetime(lifetime: ILifetime | Exclude<ActivationType, "singleton">): this;
45
44
46 cleanup(cb: (item: T) => void): this;
45 cleanup(cb: (item: T) => void): this;
47
46
48 value(v: T): void;
47 value(v: T): void;
49 }
48 }
50
49
51 export interface ContainerConfiguration<S extends object> {
50 export interface ContainerConfiguration<S extends object> {
52 apply<S2 extends object>(target: Container<S2>, ct: ICancellation): Promise<Container<S2 & S>>;
51 apply<S2 extends object>(target: ServiceContainer<S2>, ct?: ICancellation): Promise<ServiceContainer<S2 & S>>;
53 }
52 }
54
53
55 export type RegistrationBuilder<S extends object, T> = (d: DescriptorBuilder<S, T>, ct?: ICancellation) => void | Promise<void>;
54 export type RegistrationBuilder<S extends object, T> = (d: DescriptorBuilder<S, T>, ct?: ICancellation) => void | Promise<void>;
56
55
57 export type FluentRegistrations<K extends keyof S, S extends object> = { [k in K]: RegistrationBuilder<S, S[k]> };
56 export type FluentRegistrations<K extends keyof S, S extends object> = { [k in K]: RegistrationBuilder<S, S[k]> };
@@ -1,53 +1,62
1 import { ActivationContext } from "./ActivationContext";
1 import { ActivationContext } from "./ActivationContext";
2 import { LifetimeManager } from "./LifetimeManager";
2
3
3 export interface Descriptor<S extends object = any, T = any> {
4 export interface Descriptor<S extends object = any, T = any> {
4 activate(context: ActivationContext<S>): T;
5 activate(context: ActivationContext<S>): T;
5 }
6 }
6
7
7 export type ServiceMap<S extends object> = {
8 export type ServiceMap<S extends object> = {
8 [k in keyof S]: Descriptor<S, S[k]>;
9 [k in keyof S]: Descriptor<S, S[k]>;
9 };
10 };
10
11
11 export type ContainerKeys<S extends object> = keyof S | keyof ContainerProvided<S>;
12 export type ContainerKeys<S extends object> = keyof S | keyof ContainerProvided<S>;
12
13
13 export type TypeOfService<S extends object, K> =
14 export type TypeOfService<S extends object, K> =
14 K extends keyof ContainerProvided<S> ? ContainerProvided<S>[K] :
15 K extends keyof ContainerProvided<S> ? ContainerProvided<S>[K] :
15 K extends keyof S ? S[K] : never;
16 K extends keyof S ? S[K] : never;
16
17
17 export type ContainerServiceMap<S extends object> = {
18 export type ContainerServiceMap<S extends object> = {
18 [K in ContainerKeys<S>]: Descriptor<S, TypeOfService<S, K>>;
19 [K in ContainerKeys<S>]: Descriptor<S, TypeOfService<S, K>>;
19 };
20 };
20
21
21 export type PartialServiceMap<S extends object> = {
22 export type PartialServiceMap<S extends object> = {
22 [k in keyof S]?: Descriptor<S, S[k]>;
23 [k in keyof S]?: Descriptor<S, S[k]>;
23 };
24 };
24
25
25 export interface ServiceLocator<S extends object> {
26 export interface ServiceLocator<S extends object> {
26 resolve<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>): TypeOfService<S, K>;
27 resolve<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>): TypeOfService<S, K>;
27 }
28 }
28
29
30 export interface ServiceContainer<S extends object> extends ServiceLocator<S> {
31 getLifetimeManager(): LifetimeManager;
32 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>): this;
33 register(services: PartialServiceMap<S>): this;
34
35 createChildContainer(): ServiceContainer<S>;
36 }
37
29 export interface ContainerProvided<S extends object> {
38 export interface ContainerProvided<S extends object> {
30 container: ServiceLocator<S>;
39 container: ServiceLocator<S>;
31 }
40 }
32
41
33 export type ContainerRegistered<S extends object> = /*{
42 export type ContainerRegistered<S extends object> = /*{
34 [K in Exclude<keyof S, keyof ContainerProvided<S>>]: S[K];
43 [K in Exclude<keyof S, keyof ContainerProvided<S>>]: S[K];
35 };*/
44 };*/
36 Exclude<S, ContainerProvided<S>>;
45 Exclude<S, ContainerProvided<S>>;
37
46
38 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
47 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
39
48
40 /**
49 /**
41 * Π˜Π½Ρ‚Π΅Ρ€Ρ„Π΅ΠΉΡ для управлСния Тизнью экзСмпляра ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π°. КаТдая рСгистрация ΠΈΠΌΠ΅Π΅Ρ‚
50 * Π˜Π½Ρ‚Π΅Ρ€Ρ„Π΅ΠΉΡ для управлСния Тизнью экзСмпляра ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π°. КаТдая рСгистрация ΠΈΠΌΠ΅Π΅Ρ‚
42 * свой собствСнный ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ `ILifetime`, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ создаСтся ΠΏΡ€ΠΈ ΠΏΠ΅Ρ€Π²ΠΎΠΉ Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ
51 * свой собствСнный ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ `ILifetime`, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ создаСтся ΠΏΡ€ΠΈ ΠΏΠ΅Ρ€Π²ΠΎΠΉ Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ
43 */
52 */
44 export interface ILifetime {
53 export interface ILifetime {
45 /** ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅Ρ‚, Ρ‡Ρ‚ΠΎ ΡƒΠΆΠ΅ создан экзСмпляр ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π° */
54 /** ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅Ρ‚, Ρ‡Ρ‚ΠΎ ΡƒΠΆΠ΅ создан экзСмпляр ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π° */
46 has(): boolean;
55 has(): boolean;
47
56
48 get(): any;
57 get(): any;
49
58
50 initialize(context: ActivationContext<any>): void;
59 initialize(context: ActivationContext<any>): void;
51
60
52 store(item: any, cleanup?: (item: any) => void): void;
61 store(item: any, cleanup?: (item: any) => void): void;
53 }
62 }
@@ -1,22 +1,26
1 import { Foo } from "./Foo";
1 import { Foo } from "./Foo";
2 import { Box } from "./Box";
2 import { Box } from "./Box";
3 import { Bar } from "./Bar";
3 import { Bar } from "./Bar";
4
4
5 /**
5 /**
6 * БСрвисы доступныС Π²Π½ΡƒΡ‚Ρ€ΠΈ ΠΊΠΎΠ½Ρ‚Π΅ΠΉΠ½Π΅Ρ€Π°
6 * БСрвисы доступныС Π²Π½ΡƒΡ‚Ρ€ΠΈ ΠΊΠΎΠ½Ρ‚Π΅ΠΉΠ½Π΅Ρ€Π°
7 */
7 */
8 export interface Services {
8 export interface Services {
9 foo: Foo;
9 foo: Foo;
10
10
11 box: Box<Foo>;
11 box: Box<Foo>;
12
12
13 host: string;
13 host: string;
14
14
15 }
15 }
16
16
17 export interface ChildServices extends Services {
17 export interface ChildServices extends Services {
18
18
19 foo2?: Foo;
19 foo2?: Foo;
20
20
21 bar: Bar;
21 bar: Bar;
22 }
22 }
23
24 export interface FooServices {
25 foo: Foo;
26 }
@@ -1,69 +1,80
1 import { test } from "./TestTraits";
1 import { test } from "./TestTraits";
2 import { fluent } from "../di/traits";
2 import { fluent } from "../di/traits";
3 import { Bar } from "../mock/Bar";
3 import { Bar } from "../mock/Bar";
4 import { Container } from "../di/Container";
4 import { Container } from "../di/Container";
5 import { Foo } from "../mock/Foo";
5 import { Foo } from "../mock/Foo";
6 import { Box } from "../mock/Box";
6 import { Box } from "../mock/Box";
7 import { delay } from "../safe";
7 import { delay } from "../safe";
8 import { Services } from "../mock/services";
8 import { FooServices, Services } from "../mock/services";
9 import { ContainerConfiguration } from "../di/fluent/interfaces";
9
10
10 test("Simple fluent config", async t => {
11 test("Simple fluent config", async t => {
11 const config = fluent<{ host: string; bar: Bar; foo: Foo }>()
12 const config = fluent<{ host: string; bar: Bar; foo: Foo }>()
12 .register({
13 .register({
13 host: it => it.value("example.com"),
14 host: it => it.value("example.com"),
14 bar: it => it.factory(resolve => new Bar({ host: resolve("host") }, "s-bar")),
15 bar: it => it.factory(resolve => new Bar({ host: resolve("host") }, "s-bar")),
15 foo: it => import("../mock/Foo").then(m => it.lifetime("container").factory(() => new m.Foo()))
16 foo: it => import("../mock/Foo").then(m => it.lifetime("container").factory(() => new m.Foo()))
16 });
17 });
17
18
18 const c1 = new Container<{}>();
19 const c1 = new Container<{}>();
19 const container = await config.apply(c1);
20 const container = await config.apply(c1);
20
21
21 t.equal(container.resolve("host"), "example.com", "The value should be resolved");
22 t.equal(container.resolve("host"), "example.com", "The value should be resolved");
22 t.assert(container.resolve("bar"), "The service should de activated");
23 t.assert(container.resolve("bar"), "The service should de activated");
23 t.equal(container.resolve("foo"), container.resolve("foo"), "The service should be activated once");
24 t.equal(container.resolve("foo"), container.resolve("foo"), "The service should be activated once");
24 });
25 });
25
26
26 test("Nested async configuration", async t => {
27 test("Nested async configuration", async t => {
27 const container = await new Container<{
28 const container = await new Container<{
28 foo: Foo;
29 foo: Foo;
29 box: Box<Foo>
30 box: Box<Foo>
30 }>().fluent({
31 }>().fluent({
31 foo: it => delay(0).then(() => it.factory(() => new Foo())),
32 foo: it => delay(0).then(() => it.factory(() => new Foo())),
32 box: it => it.lifetime("context").factory($dependency => new Box($dependency("foo")))
33 box: it => it.lifetime("context").factory($dependency => new Box($dependency("foo")))
33 });
34 });
34
35
35 t.assert(container.resolve("box").getValue(), "The dependency should be set");
36 t.assert(container.resolve("box").getValue(), "The dependency should be set");
36 t.equals(container.resolve("box").getValue(), container.resolve("box").getValue(), "The service should be activated once")
37 t.equals(container.resolve("box").getValue(), container.resolve("box").getValue(), "The service should be activated once")
37 });
38 });
38
39
39 test("Bad fluent config", async t => {
40 test("Bad fluent config", async t => {
40 try {
41 try {
41 await new Container<{
42 await new Container<{
42 foo: Foo;
43 foo: Foo;
43 box: Box<Foo>
44 box: Box<Foo>
44 }>().fluent({
45 }>().fluent({
45 foo: it => delay(0).then(() => it.factory(() => new Foo())),
46 foo: it => delay(0).then(() => it.factory(() => new Foo())),
46 box: it => it.lifetime("context")
47 box: it => it.lifetime("context")
47 .override("foo", () => { throw new Error("bad override"); })
48 .override("foo", () => { throw new Error("bad override"); })
48 .factory($dependency => new Box($dependency("foo")))
49 .factory($dependency => new Box($dependency("foo")))
49 });
50 });
50 t.fail("Should throw");
51 t.fail("Should throw");
51 } catch (e) {
52 } catch (e) {
52 t.pass("The configuration should fail");
53 t.pass("The configuration should fail");
53 t.equal(e.message, "bad override", "the error should pass");
54 t.equal(e.message, "bad override", "the error should pass");
54 }
55 }
55 });
56 });
56
57
57 test("Load fluent config", async t => {
58 test("Load fluent config", async t => {
58 const container = new Container<Services>();
59 const container = new Container<Services>();
59
60
60 await container.configure("../mock/config", { contextRequire: require });
61 await container.configure("../mock/config", { contextRequire: require });
61
62
62 t.assert(container.resolve("host"), "Should resolve simple value");
63 t.assert(container.resolve("host"), "Should resolve simple value");
63 });
64 });
64
65
65 test("Container applyConfig", async t => {
66 test("Container applyConfig", async t => {
66 const container = await new Container<{}>().applyConfig(import("../mock/config"));
67 const container = await new Container<{}>().applyConfig(import("../mock/config"));
67
68
68 t.assert(container.resolve("host"), "Should resolve simple value");
69 t.assert(container.resolve("host"), "Should resolve simple value");
69 });
70 });
71
72 test("Child container config", async t => {
73 const container = await new Container<{}>().applyConfig(import("../mock/config"));
74
75 const fooServices: ContainerConfiguration<FooServices> = (await import("../mock/config2")).default;
76
77 const child = await fooServices.apply(container.createChildContainer());
78
79 t.assert(child.resolve("foo"), "foo should be resolved");
80 });
General Comments 0
You need to be logged in to leave comments. Login now