##// END OF EJS Templates
Added test: Optional dependency with child container...
cin -
r158:7e27596a76a8 default
parent child
Show More
@@ -1,169 +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, ServiceContainer } from "./interfaces";
3 import { Descriptor, ContainerServiceMap, ContainerKeys, TypeOfService, ILifetime, ServiceContainer } from "./interfaces";
4 import { MapOf } from "../interfaces";
4 import { MapOf } from "../interfaces";
5
5
6 const trace = TraceSource.get("@implab/core/di/ActivationContext");
6 const trace = TraceSource.get("@implab/core/di/ActivationContext");
7
7
8 export interface ActivationContextInfo {
8 export interface ActivationContextInfo {
9 name: string;
9 name: string;
10
10
11 service: string;
11 service: string;
12
12
13 }
13 }
14
14
15 let nextId = 1;
15 let nextId = 1;
16
16
17 /** 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
18 * cache dependencies and to track created instances. The activation context
18 * cache dependencies and to track created instances. The activation context
19 * tracks services with `context` activation type.
19 * tracks services with `context` activation type.
20 */
20 */
21 export class ActivationContext<S extends object> {
21 export class ActivationContext<S extends object> {
22 _cache: MapOf<any>;
22 _cache: MapOf<any>;
23
23
24 _services: ContainerServiceMap<S>;
24 _services: ContainerServiceMap<S>;
25
25
26 _visited: MapOf<any>;
26 _visited: MapOf<any>;
27
27
28 _name: string;
28 _name: string;
29
29
30 _service: Descriptor<S, any>;
30 _service: Descriptor<S, any>;
31
31
32 _container: ServiceContainer<S>;
32 _container: ServiceContainer<S>;
33
33
34 _parent: ActivationContext<S> | undefined;
34 _parent: ActivationContext<S> | undefined;
35
35
36 /** Creates a new activation context with the specified parameters.
36 /** Creates a new activation context with the specified parameters.
37 * @param container the container which starts the activation process
37 * @param container the container which starts the activation process
38 * @param services the initial service registrations
38 * @param services the initial service registrations
39 * @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
40 * used for the debug purpose.
40 * used for the debug purpose.
41 * @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
42 * debug purpose.
42 * debug purpose.
43 */
43 */
44 constructor(container: ServiceContainer<S>, services: ContainerServiceMap<S>, name: string, service: Descriptor<S, any>) {
44 constructor(container: ServiceContainer<S>, services: ContainerServiceMap<S>, name: string, service: Descriptor<S, any>) {
45 this._name = name;
45 this._name = name;
46 this._service = service;
46 this._service = service;
47 this._visited = {};
47 this._visited = {};
48 this._cache = {};
48 this._cache = {};
49 this._services = services;
49 this._services = services;
50 this._container = container;
50 this._container = container;
51 }
51 }
52
52
53 /** the name of the current resolving dependency */
53 /** the name of the current resolving dependency */
54 getName() {
54 getName() {
55 return this._name;
55 return this._name;
56 }
56 }
57
57
58 /** Returns the container for which 'resolve' method was called */
58 /** Returns the container for which 'resolve' method was called */
59 getContainer() {
59 getContainer() {
60 return this._container;
60 return this._container;
61 }
61 }
62
62
63 /** Resolves the specified dependency in the current context
63 /** Resolves the specified dependency in the current context
64 * @param name The name of the dependency being resolved
64 * @param name The name of the dependency being resolved
65 */
65 */
66 resolve<K extends ContainerKeys<S>>(name: K): TypeOfService<S, K>;
66 resolve<K extends ContainerKeys<S>>(name: K): TypeOfService<S, K>;
67 /** Resolves the specified dependency with the specified default value if
67 /** Resolves the specified dependency with the specified default value if
68 * the dependency is missing.
68 * the dependency is missing.
69 *
69 *
70 * @param name The name of the dependency being resolved
70 * @param name The name of the dependency being resolved
71 * @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
72 * is missing.
72 * is missing.
73 */
73 */
74 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;
75 /** Resolves the specified dependency and returns undefined in case if the
75 /** Resolves the specified dependency and returns undefined in case if the
76 * dependency is missing.
76 * dependency is missing.
77 *
77 *
78 * @param name The name of the dependency being resolved
78 * @param name The name of the dependency being resolved
79 */
79 */
80 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;
81 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 {
82 const d = this._services[name];
82 const d = this._services[name];
83
83
84 if (d !== undefined) {
84 if (d !== undefined) {
85 return this.activate(d, name.toString());
85 return this.activate(d, name.toString());
86 } else {
86 } else {
87 if (arguments.length > 1)
87 if (arguments.length > 1)
88 return def;
88 return def;
89 else
89 else
90 throw new Error(`Service ${name} not found`);
90 throw new Error(`Service ${name} not found`);
91 }
91 }
92 }
92 }
93
93
94 /**
94 /**
95 * registers services local to the the activation context
95 * registers services local to the the activation context
96 *
96 *
97 * @name{string} the name of the service
97 * @name{string} the name of the service
98 * @service{string} the service descriptor to register
98 * @service{string} the service descriptor to register
99 */
99 */
100 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]>) {
101 argumentNotEmptyString(name, "name");
101 argumentNotEmptyString(name, "name");
102
102
103 this._services[name] = service as any;
103 this._services[name] = service as any;
104 }
104 }
105
105
106 createLifetime(): ILifetime {
106 createLifetime(): ILifetime {
107 const id = nextId++;
107 const id = nextId++;
108 const me = this;
108 const me = this;
109 return {
109 return {
110 initialize() {
110 initialize() {
111 },
111 },
112 has() {
112 has() {
113 return id in me._cache;
113 return id in me._cache;
114 },
114 },
115 get() {
115 get() {
116 return me._cache[id];
116 return me._cache[id];
117 },
117 },
118 store(item: any) {
118 store(item: any) {
119 me._cache[id] = item;
119 me._cache[id] = item;
120 }
120 }
121 };
121 };
122 }
122 }
123
123
124 activate<T>(d: Descriptor<S, T>, name: string) {
124 activate<T>(d: Descriptor<S, T>, name: string) {
125 if (trace.isLogEnabled())
125 if (trace.isLogEnabled())
126 trace.log(`enter ${name} ${d}`);
126 trace.log("enter {0} {1}", name, d);
127
127
128 const ctx = this.enter(d, name);
128 const ctx = this.enter(d, name);
129 const v = d.activate(ctx);
129 const v = d.activate(ctx);
130
130
131 if (trace.isLogEnabled())
131 if (trace.isLogEnabled())
132 trace.log(`leave ${name}`);
132 trace.log(`leave ${name}`);
133
133
134 return v;
134 return v;
135 }
135 }
136
136
137 visit(id: string) {
137 visit(id: string) {
138 const count = this._visited[id] || 0;
138 const count = this._visited[id] || 0;
139 this._visited[id] = count + 1;
139 this._visited[id] = count + 1;
140 return count;
140 return count;
141 }
141 }
142
142
143 getStack(): ActivationContextInfo[] {
143 getStack(): ActivationContextInfo[] {
144 const stack = [{
144 const stack = [{
145 name: this._name,
145 name: this._name,
146 service: this._service.toString()
146 service: this._service.toString()
147 }];
147 }];
148
148
149 return this._parent ?
149 return this._parent ?
150 stack.concat(this._parent.getStack()) :
150 stack.concat(this._parent.getStack()) :
151 stack;
151 stack;
152 }
152 }
153
153
154 private enter(service: Descriptor<S, any>, name: string): this {
154 private enter(service: Descriptor<S, any>, name: string): this {
155 const clone = Object.create(this);
155 const clone = Object.create(this);
156 clone._name = name;
156 clone._name = name;
157 clone._services = Object.create(this._services);
157 clone._services = Object.create(this._services);
158 clone._parent = this;
158 clone._parent = this;
159 clone._service = service;
159 clone._service = service;
160 return clone;
160 return clone;
161 }
161 }
162
162
163 /** 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 */
164 clone(): this {
164 clone(): this {
165 const clone = Object.create(this);
165 const clone = Object.create(this);
166 clone._services = Object.create(this._services);
166 clone._services = Object.create(this._services);
167 return clone;
167 return clone;
168 }
168 }
169 }
169 }
@@ -1,199 +1,218
1 import { IDestroyable, MapOf } from "../interfaces";
1 import { IDestroyable, MapOf } from "../interfaces";
2 import { argumentNotNull, isDestroyable, argumentNotEmptyString, isRemovable } from "../safe";
2 import { argumentNotNull, isDestroyable, argumentNotEmptyString, isRemovable } from "../safe";
3 import { ILifetime, ServiceContainer } from "./interfaces";
3 import { ILifetime, ServiceContainer } from "./interfaces";
4 import { ActivationContext } from "./ActivationContext";
4 import { ActivationContext } from "./ActivationContext";
5
5
6 function safeCall(item: () => void) {
6 function safeCall(item: () => void) {
7 try {
7 try {
8 item();
8 item();
9 } catch {
9 } catch {
10 // silence!
10 // silence!
11 }
11 }
12 }
12 }
13
13
14 const emptyLifetime: ILifetime = Object.freeze({
14 const emptyLifetime: ILifetime = Object.freeze({
15 has() {
15 has() {
16 return false;
16 return false;
17 },
17 },
18
18
19 initialize() {
19 initialize() {
20
20
21 },
21 },
22
22
23 get() {
23 get() {
24 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");
25 },
25 },
26
26
27 store() {
27 store() {
28 // does nothing
28 // does nothing
29 },
30
31 toString() {
32 return `[object EmptyLifetime]`;
29 }
33 }
30
34
31 });
35 });
32
36
33 const unknownLifetime: ILifetime = Object.freeze({
37 const unknownLifetime: ILifetime = Object.freeze({
34 has() {
38 has() {
35 return false;
39 return false;
36 },
40 },
37 initialize() {
41 initialize() {
38 throw new Error("Can't call initialize on the unknown lifetime object");
42 throw new Error("Can't call initialize on the unknown lifetime object");
39 },
43 },
40 get() {
44 get() {
41 throw new Error("The lifetime object isn't initialized");
45 throw new Error("The lifetime object isn't initialized");
42 },
46 },
43 store() {
47 store() {
44 throw new Error("Can't store a value in the unknown lifetime object");
48 throw new Error("Can't store a value in the unknown lifetime object");
49 },
50 toString() {
51 return `[object UnknownLifetime]`;
45 }
52 }
46 });
53 });
47
54
48 let nextId = 0;
55 let nextId = 0;
49
56
50 const singletons: any = {};
57 const singletons: any = {};
51
58
52 export class LifetimeManager implements IDestroyable {
59 export class LifetimeManager implements IDestroyable {
53 private _cleanup: (() => void)[] = [];
60 private _cleanup: (() => void)[] = [];
54 private _cache: MapOf<any> = {};
61 private _cache: MapOf<any> = {};
55 private _destroyed = false;
62 private _destroyed = false;
56
63
57 private _pending: MapOf<boolean> = {};
64 private _pending: MapOf<boolean> = {};
58
65
59 create(): ILifetime {
66 create(): ILifetime {
60 const self = this;
67 const self = this;
61 const id = ++nextId;
68 const id = ++nextId;
62 return {
69 return {
63 has() {
70 has() {
64 return (id in self._cache);
71 return (id in self._cache);
65 },
72 },
66
73
67 get() {
74 get() {
68 const t = self._cache[id];
75 const t = self._cache[id];
69 if (t === undefined)
76 if (t === undefined)
70 throw new Error(`The item with with the key ${id} isn't found`);
77 throw new Error(`The item with with the key ${id} isn't found`);
71 return t;
78 return t;
72 },
79 },
73
80
74 initialize() {
81 initialize() {
75 if (self._pending[id])
82 if (self._pending[id])
76 throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`);
83 throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`);
77 self._pending[id] = true;
84 self._pending[id] = true;
78 },
85 },
79
86
80 store(item: any, cleanup?: (item: any) => void) {
87 store(item: any, cleanup?: (item: any) => void) {
81 argumentNotNull(id, "id");
88 argumentNotNull(id, "id");
82 argumentNotNull(item, "item");
89 argumentNotNull(item, "item");
83
90
84 if (this.has())
91 if (this.has())
85 throw new Error(`The item with with the key ${id} already registered with this lifetime manager`);
92 throw new Error(`The item with with the key ${id} already registered with this lifetime manager`);
86 delete self._pending[id];
93 delete self._pending[id];
87
94
88 self._cache[id] = item;
95 self._cache[id] = item;
89
96
90 if (self._destroyed)
97 if (self._destroyed)
91 throw new Error("Lifetime manager is destroyed");
98 throw new Error("Lifetime manager is destroyed");
92 if (cleanup) {
99 if (cleanup) {
93 self._cleanup.push(() => cleanup(item));
100 self._cleanup.push(() => cleanup(item));
94 } else if (isDestroyable(item)) {
101 } else if (isDestroyable(item)) {
95 self._cleanup.push(() => item.destroy());
102 self._cleanup.push(() => item.destroy());
96 } else if (isRemovable(item)) {
103 } else if (isRemovable(item)) {
97 self._cleanup.push(() => item.remove());
104 self._cleanup.push(() => item.remove());
98 }
105 }
99 }
106 }
100 };
107 };
101 }
108 }
102
109
103 destroy() {
110 destroy() {
104 if (!this._destroyed) {
111 if (!this._destroyed) {
105 this._destroyed = true;
112 this._destroyed = true;
106 this._cleanup.forEach(safeCall);
113 this._cleanup.forEach(safeCall);
107 this._cleanup.length = 0;
114 this._cleanup.length = 0;
108 }
115 }
109 }
116 }
110
117
111 static empty(): ILifetime {
118 static empty(): ILifetime {
112 return emptyLifetime;
119 return emptyLifetime;
113 }
120 }
114
121
115 static hierarchyLifetime(): ILifetime {
122 static hierarchyLifetime() {
116 let _lifetime = unknownLifetime;
123 let _lifetime = unknownLifetime;
117 return {
124 return {
118 initialize(context: ActivationContext<any>) {
125 initialize(context: ActivationContext<any>) {
119 if (_lifetime !== unknownLifetime)
126 if (_lifetime !== unknownLifetime)
120 throw new Error("Cyclic reference activation detected");
127 throw new Error("Cyclic reference activation detected");
121
128
122 _lifetime = context.getContainer().getLifetimeManager().create();
129 _lifetime = context.getContainer().getLifetimeManager().create();
123 },
130 },
124 get() {
131 get() {
125 return _lifetime.get();
132 return _lifetime.get();
126 },
133 },
127 has() {
134 has() {
128 return _lifetime.has();
135 return _lifetime.has();
129 },
136 },
130 store(item: any, cleanup?: (item: any) => void) {
137 store(item: any, cleanup?: (item: any) => void) {
131 return _lifetime.store(item, cleanup);
138 return _lifetime.store(item, cleanup);
139 },
140 toString() {
141 return `[object HierarchyLifetime, has=${this.has()}]`;
132 }
142 }
133 };
143 };
134 }
144 }
135
145
136 static contextLifetime(): ILifetime {
146 static contextLifetime() {
137 let _lifetime = unknownLifetime;
147 let _lifetime = unknownLifetime;
138 return {
148 return {
139 initialize(context: ActivationContext<any>) {
149 initialize(context: ActivationContext<any>) {
140 if (_lifetime !== unknownLifetime)
150 if (_lifetime !== unknownLifetime)
141 throw new Error("Cyclic reference detected");
151 throw new Error("Cyclic reference detected");
142 _lifetime = context.createLifetime();
152 _lifetime = context.createLifetime();
143 },
153 },
144 get() {
154 get() {
145 return _lifetime.get();
155 return _lifetime.get();
146 },
156 },
147 has() {
157 has() {
148 return _lifetime.has();
158 return _lifetime.has();
149 },
159 },
150 store(item: any) {
160 store(item: any) {
151 _lifetime.store(item);
161 _lifetime.store(item);
162 },
163 toString() {
164 return `[object ContextLifetime, has=${this.has()}]`;
152 }
165 }
153 };
166 };
154 }
167 }
155
168
156 static singletonLifetime(typeId: string): ILifetime {
169 static singletonLifetime(typeId: string) {
157 argumentNotEmptyString(typeId, "typeId");
170 argumentNotEmptyString(typeId, "typeId");
158 let pending = false;
171 let pending = false;
159 return {
172 return {
160 has() {
173 has() {
161 return typeId in singletons;
174 return typeId in singletons;
162 },
175 },
163 get() {
176 get() {
164 if (!this.has())
177 if (!this.has())
165 throw new Error(`The instance ${typeId} doesn't exists`);
178 throw new Error(`The instance ${typeId} doesn't exists`);
166 return singletons[typeId];
179 return singletons[typeId];
167 },
180 },
168 initialize() {
181 initialize() {
169 if (pending)
182 if (pending)
170 throw new Error("Cyclic reference detected");
183 throw new Error("Cyclic reference detected");
171 pending = true;
184 pending = true;
172 },
185 },
173 store(item: any) {
186 store(item: any) {
174 singletons[typeId] = item;
187 singletons[typeId] = item;
175 pending = false;
188 pending = false;
189 },
190 toString() {
191 return `[object SingletonLifetime, has=${this.has()}, typeId=${typeId}]`;
176 }
192 }
177 };
193 };
178 }
194 }
179
195
180 static containerLifetime(container: ServiceContainer<any>) {
196 static containerLifetime(container: ServiceContainer<any>) {
181 let _lifetime = unknownLifetime;
197 let _lifetime = unknownLifetime;
182 return {
198 return {
183 initialize(context: ActivationContext<any>) {
199 initialize(context: ActivationContext<any>) {
184 if (_lifetime !== unknownLifetime)
200 if (_lifetime !== unknownLifetime)
185 throw new Error("Cyclic reference detected");
201 throw new Error("Cyclic reference detected");
186 _lifetime = container.getLifetimeManager().create();
202 _lifetime = container.getLifetimeManager().create();
187 },
203 },
188 get() {
204 get() {
189 return _lifetime.get();
205 return _lifetime.get();
190 },
206 },
191 has() {
207 has() {
192 return _lifetime.has();
208 return _lifetime.has();
193 },
209 },
194 store(item: any) {
210 store(item: any) {
195 _lifetime.store(item);
211 _lifetime.store(item);
212 },
213 toString() {
214 return `[object ContainerLifetime, has=${_lifetime.has()}]`
196 }
215 }
197 };
216 };
198 }
217 }
199 }
218 }
@@ -1,63 +1,66
1 import { Descriptor, PartialServiceMap, ILifetime, ContainerKeys } from "../interfaces";
1 import { Descriptor, PartialServiceMap, ILifetime, ContainerKeys } from "../interfaces";
2 import { ActivationContext } from "../ActivationContext";
2 import { ActivationContext } from "../ActivationContext";
3 import { each } from "../../safe";
3 import { each } from "../../safe";
4 import { DependencyOptions, LazyDependencyOptions, Resolver } from "./interfaces";
4 import { DependencyOptions, LazyDependencyOptions, Resolver } from "./interfaces";
5
5
6 export interface DescriptorImplArgs<S extends object, T> {
6 export interface DescriptorImplArgs<S extends object, T> {
7 lifetime: ILifetime;
7 lifetime: ILifetime;
8
8
9 factory: (resolve: Resolver<S>) => T;
9 factory: (resolve: Resolver<S>) => T;
10
10
11 cleanup?: (item: T) => void;
11 cleanup?: (item: T) => void;
12
12
13 overrides?: PartialServiceMap<S>;
13 overrides?: PartialServiceMap<S>;
14 }
14 }
15
15
16 export class DescriptorImpl<S extends object, T> implements Descriptor<S, T> {
16 export class DescriptorImpl<S extends object, T> implements Descriptor<S, T> {
17
17
18 private readonly _overrides?: PartialServiceMap<S>;
18 private readonly _overrides?: PartialServiceMap<S>;
19
19
20 private readonly _lifetime: ILifetime;
20 private readonly _lifetime: ILifetime;
21
21
22 private readonly _factory: (resolve: Resolver<S>) => T;
22 private readonly _factory: (resolve: Resolver<S>) => T;
23
23
24 private readonly _cleanup?: (item: T) => void;
24 private readonly _cleanup?: (item: T) => void;
25
25
26 constructor(args: DescriptorImplArgs<S, T>) {
26 constructor(args: DescriptorImplArgs<S, T>) {
27 this._lifetime = args.lifetime;
27 this._lifetime = args.lifetime;
28 this._factory = args.factory;
28 this._factory = args.factory;
29 if (args.cleanup)
29 if (args.cleanup)
30 this._cleanup = args.cleanup;
30 this._cleanup = args.cleanup;
31 if (args.overrides)
31 if (args.overrides)
32 this._overrides = args.overrides;
32 this._overrides = args.overrides;
33 }
33 }
34
34
35 activate(context: ActivationContext<S>): T {
35 activate(context: ActivationContext<S>): T {
36
36
37 if (this._lifetime.has())
37 if (this._lifetime.has())
38 return this._lifetime.get();
38 return this._lifetime.get();
39
39
40 this._lifetime.initialize(context);
40 this._lifetime.initialize(context);
41
41
42 if (this._overrides)
42 if (this._overrides)
43 each(this._overrides, (v, k) => context.register(k, v));
43 each(this._overrides, (v, k) => context.register(k, v));
44
44
45 const resolve = (name: ContainerKeys<S>, opts?: DependencyOptions | LazyDependencyOptions) => {
45 const resolve = (name: ContainerKeys<S>, opts?: DependencyOptions | LazyDependencyOptions) => {
46 if (opts && "lazy" in opts && opts.lazy) {
46 if (opts && "lazy" in opts && opts.lazy) {
47 const c2 = context.clone();
47 const c2 = context.clone();
48 return () => {
48 return () => {
49 return opts.optional ? c2.resolve(name, opts.default) : c2.resolve(name);
49 return opts.optional ? c2.resolve(name, opts.default) : c2.resolve(name);
50 };
50 };
51 } else {
51 } else {
52 return opts && opts.optional ? context.resolve(name, opts.default) : context.resolve(name);
52 return opts && opts.optional ? context.resolve(name, opts.default) : context.resolve(name);
53 }
53 }
54 };
54 };
55
55
56 const instance = this._factory.call(undefined, resolve);
56 const instance = this._factory.call(undefined, resolve);
57
57
58 this._lifetime.store(instance, this._cleanup);
58 this._lifetime.store(instance, this._cleanup);
59
59
60 return instance;
60 return instance;
61 }
61 }
62
62
63 toString() {
64 return `[object DescriptorImpl, lifetime=${this._lifetime}]`;
63 }
65 }
66 }
@@ -1,106 +1,125
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 { Box } from "../mock/Box";
9
10
10 test("Container register/resolve tests", async t => {
11 test("Container register/resolve tests", async t => {
11 const container = new Container<{
12 const container = new Container<{
12 "bla-bla": string;
13 "bla-bla": string;
13 "connection": string;
14 "connection": string;
14 "dbParams": {
15 "dbParams": {
15 timeout: number;
16 timeout: number;
16 connection: string;
17 connection: string;
17 }
18 }
18 }>();
19 }>();
19
20
20 const connection1 = "db://localhost";
21 const connection1 = "db://localhost";
21
22
22 t.throws(
23 t.throws(
23 () => container.register("bla-bla", "bla-bla" as any),
24 () => container.register("bla-bla", "bla-bla" as any),
24 "Do not allow to register anything other than descriptors"
25 "Do not allow to register anything other than descriptors"
25 );
26 );
26
27
27 t.doesNotThrow(
28 t.doesNotThrow(
28 () => container.register("connection", new ValueDescriptor(connection1)),
29 () => container.register("connection", new ValueDescriptor(connection1)),
29 "register ValueDescriptor"
30 "register ValueDescriptor"
30 );
31 );
31
32
32 t.equals(container.resolve("connection"), connection1, "resolve string value");
33 t.equals(container.resolve("connection"), connection1, "resolve string value");
33
34
34 t.doesNotThrow(
35 t.doesNotThrow(
35 () => container.register(
36 () => container.register(
36 "dbParams",
37 "dbParams",
37 new AggregateDescriptor({
38 new AggregateDescriptor({
38 timeout: 10,
39 timeout: 10,
39 connection: new ReferenceDescriptor({ name: "connection" })
40 connection: new ReferenceDescriptor({ name: "connection" })
40 })
41 })
41 ),
42 ),
42 "register AggregateDescriptor"
43 "register AggregateDescriptor"
43 );
44 );
44
45
45 const dbParams = container.resolve("dbParams");
46 const dbParams = container.resolve("dbParams");
46 t.equals(dbParams.connection, connection1, "should get string value 'dbParams.connection'");
47 t.equals(dbParams.connection, connection1, "should get string value 'dbParams.connection'");
47 });
48 });
48
49
49 test("Container configure/resolve tests", async t => {
50 test("Container configure/resolve tests", async t => {
50
51
51 const container = new Container<{
52 const container = new Container<{
52 foo: Foo;
53 foo: Foo;
53 box: Bar;
54 box: Bar;
54 bar: Bar;
55 bar: Bar;
55 db: any;
56 db: any;
56 }>();
57 }>();
57
58
58 await container.configure({
59 await container.configure({
59 foo: {
60 foo: {
60 $type: Foo
61 $type: Foo
61 },
62 },
62
63
63 box: {
64 box: {
64 $type: Bar,
65 $type: Bar,
65 params: {
66 params: {
66 $dependency: "foo"
67 $dependency: "foo"
67 }
68 }
68 },
69 },
69
70
70 bar: {
71 bar: {
71 $type: Bar,
72 $type: Bar,
72 params: [{
73 params: [{
73 db: {
74 db: {
74 provider: {
75 provider: {
75 $dependency: "db"
76 $dependency: "db"
76 }
77 }
77 }
78 }
78 }]
79 }]
79 }
80 }
80 });
81 });
81 t.pass("should configure from js object");
82 t.pass("should configure from js object");
82
83
83 const f1 = container.resolve("foo");
84 const f1 = container.resolve("foo");
84
85
85 t.assert(!isNull(f1), "foo should be not null");
86 t.assert(!isNull(f1), "foo should be not null");
86
87
87 t.throws(() => container.resolve("bar"), "should not resolve dependency 'db'");
88 t.throws(() => container.resolve("bar"), "should not resolve dependency 'db'");
88
89
89 });
90 });
90
91
91 test("Load configuration from module", async t => {
92 test("Load configuration from module", async t => {
92 const container = new Container();
93 const container = new Container();
93
94
94 await container.configure("../mock/config1", { contextRequire: require });
95 await container.configure("../mock/config1", { contextRequire: require });
95 t.pass("The configuration should load");
96 t.pass("The configuration should load");
96
97
97 const f1 = container.resolve("foo");
98 const f1 = container.resolve("foo");
98
99
99 t.assert(!isNull(f1), "foo should be not null");
100 t.assert(!isNull(f1), "foo should be not null");
100
101
101 const b1 = container.resolve("bar") as Bar;
102 const b1 = container.resolve("bar") as Bar;
102
103
103 t.assert(!isNull(b1), "bar should not be null");
104 t.assert(!isNull(b1), "bar should not be null");
104 t.assert(!isNull(b1._v), "bar.foo should not be null");
105 t.assert(!isNull(b1._v), "bar.foo should not be null");
105
106
106 });
107 });
108
109 test("Optional dependency with child container", async t => {
110 const container = new Container<{
111 foo?: Foo;
112 box: Box<Foo>;
113 }>();
114 await container.fluent({
115 box: it => it.factory($ => new Box($("foo")))
116 });
117
118 const child = await container.createChildContainer()
119 .fluent({
120 foo: it => it.factory(() => new Foo())
121 })
122
123 const box = child.resolve("box");
124 t.assert(!isNull(box.getValue()), "'foo' dependency is declared in child container");
125 }); No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now