##// END OF EJS Templates
Refactoring, working on services lifetime
cin -
r131:674555892e14 ioc ts support
parent child
Show More
@@ -1,17 +1,16
1 1 import { TraceSource } from "../log/TraceSource";
2 2 import { argumentNotNull, argumentNotEmptyString } from "../safe";
3 import { Descriptor, ContainerProvided, ContainerServiceMap, ContainerKeys, ContainerResolve } from "./interfaces";
3 import { Descriptor, ContainerServiceMap, ContainerKeys, ContainerResolve } from "./interfaces";
4 4 import { Container } from "./Container";
5 5 import { MapOf } from "../interfaces";
6 6
7 7 const trace = TraceSource.get("@implab/core/di/ActivationContext");
8 8
9 export interface ActivationContextInfo<S extends object> {
9 export interface ActivationContextInfo {
10 10 name: string;
11 11
12 12 service: string;
13 13
14 scope: ContainerServiceMap<S>;
15 14 }
16 15
17 16 export class ActivationContext<S extends object> {
@@ -19,32 +18,33 export class ActivationContext<S extends
19 18
20 19 _services: ContainerServiceMap<S>;
21 20
22 _stack: ActivationContextInfo<S>[];
23
24 21 _visited: MapOf<any>;
25 22
26 23 _name: string;
27 24
28 _localized: boolean = false;
25 _service: Descriptor<S, any>;
29 26
30 container: Container<S>;
27 _container: Container<S>;
28
29 _parent: ActivationContext<S> | undefined;
31 30
32 constructor(container: Container<S>, services: ContainerServiceMap<S>, name?: string, cache?: object, visited?: MapOf<any>) {
33 argumentNotNull(container, "container");
34 argumentNotNull(services, "services");
35
36 this._name = name || "<unnamed>";
37 this._visited = visited || {};
38 this._stack = [];
39 this._cache = cache || {};
31 constructor(container: Container<S>, services: ContainerServiceMap<S>, name: string, service: Descriptor<S, any>) {
32 this._name = name;
33 this._service = service;
34 this._visited = {};
35 this._cache = {};
40 36 this._services = services;
41 this.container = container;
37 this._container = container;
42 38 }
43 39
44 40 getName() {
45 41 return this._name;
46 42 }
47 43
44 getContainer() {
45 return this._container;
46 }
47
48 48 resolve<K extends ContainerKeys<S>>(name: K, def?: ContainerResolve<S, K>) {
49 49 const d = this._services[name];
50 50
@@ -70,16 +70,6 export class ActivationContext<S extends
70 70 this._services[name] = service as any;
71 71 }
72 72
73 clone() {
74 return new ActivationContext<S>(
75 this.container,
76 this._services,
77 this._name,
78 this._cache,
79 this._visited
80 );
81 }
82
83 73 has(id: string) {
84 74 return id in this._cache;
85 75 }
@@ -96,9 +86,8 export class ActivationContext<S extends
96 86 if (trace.isLogEnabled())
97 87 trace.log(`enter ${name} ${d}`);
98 88
99 this.enter(name, d.toString());
100 const v = d.activate(this);
101 this.leave();
89 const ctx = this.enter(d, name);
90 const v = d.activate(ctx);
102 91
103 92 if (trace.isLogEnabled())
104 93 trace.log(`leave ${name}`);
@@ -112,27 +101,23 export class ActivationContext<S extends
112 101 return count;
113 102 }
114 103
115 getStack() {
116 return this._stack.slice().reverse();
104 getStack(): ActivationContextInfo[] {
105 const stack = [{
106 name: this._name,
107 service: this._service.toString()
108 }];
109
110 return this._parent ?
111 stack.concat(this._parent.getStack()) :
112 stack;
117 113 }
118 114
119 private enter(name: string, service: string) {
120 this._stack.push({
121 name,
122 service,
123 scope: this._services
124 });
125 this._name = name;
126 this._services = Object.create(this._services);
127 }
128
129 private leave() {
130 const ctx = this._stack.pop();
131 if (ctx) {
132 this._services = ctx.scope;
133 this._name = ctx.name;
134 } else {
135 trace.error("Trying to leave the last activation scope");
136 }
115 private enter(service: Descriptor<S, any>, name: string): this {
116 const clone = Object.create(this);
117 clone._name = name;
118 clone._services = Object.create(this._services);
119 clone._parent = this;
120 clone._service = service;
121 return clone;
137 122 }
138 123 }
@@ -1,12 +1,14
1 1 import { ActivationContext } from "./ActivationContext";
2 2 import { ValueDescriptor } from "./ValueDescriptor";
3 3 import { ActivationError } from "./ActivationError";
4 import { ServiceMap, Descriptor, PartialServiceMap, ContainerProvided, Resolver, ContainerServiceMap, ContainerKeys, ContainerResolve } from "./interfaces";
4 import { ServiceMap, Descriptor, PartialServiceMap, ContainerProvided, Resolver, ContainerServiceMap, ContainerKeys, ContainerResolve, ILifetimeManager } from "./interfaces";
5 5 import { TraceSource } from "../log/TraceSource";
6 6 import { Configuration, RegistrationMap } from "./Configuration";
7 7 import { Cancellation } from "../Cancellation";
8 8 import { MapOf, IDestroyable } from "../interfaces";
9 9 import { isDescriptor } from "./traits";
10 import { LifetimeManager } from "./LifetimeManager";
11 import { each } from "../safe";
10 12
11 13 const trace = TraceSource.get("@implab/core/di/ActivationContext");
12 14
@@ -15,6 +17,8 export class Container<S extends object
15 17
16 18 readonly _cache: MapOf<any>;
17 19
20 readonly _lifetimeManager: ILifetimeManager;
21
18 22 readonly _cleanup: (() => void)[];
19 23
20 24 readonly _root: Container<S>;
@@ -31,6 +35,7 export class Container<S extends object
31 35 this._root = parent ? parent.getRootContainer() : this;
32 36 this._services.container = new ValueDescriptor(this) as any;
33 37 this._disposed = false;
38 this._lifetimeManager = new LifetimeManager();
34 39 }
35 40
36 41 getRootContainer() {
@@ -41,6 +46,10 export class Container<S extends object
41 46 return this._parent;
42 47 }
43 48
49 getLifetimeManager() {
50 return this._lifetimeManager;
51 }
52
44 53 resolve<K extends ContainerKeys<S>>(name: K, def?: ContainerResolve<S, K>): ContainerResolve<S, K> {
45 54 trace.debug("resolve {0}", name);
46 55 const d = this._services[name];
@@ -51,9 +60,9 export class Container<S extends object
51 60 throw new Error("Service '" + name + "' isn't found");
52 61 } else {
53 62
54 const context = new ActivationContext<S>(this, this._services);
63 const context = new ActivationContext<S>(this, this._services, String(name), d);
55 64 try {
56 return context.activate(d, name.toString());
65 return d.activate(context);
57 66 } catch (error) {
58 67 throw new ActivationError(name.toString(), context.getStack(), error);
59 68 }
@@ -73,11 +82,7 export class Container<S extends object
73 82 if (arguments.length === 1) {
74 83 const data = nameOrCollection as ServiceMap<S>;
75 84
76 for (const name in data) {
77 if (Object.prototype.hasOwnProperty.call(data, name)) {
78 this.register(name, data[name] as Descriptor<S, S[keyof S]>);
79 }
80 }
85 each(data, (v, k) => this.register(k, v));
81 86 } else {
82 87 if (!isDescriptor(service))
83 88 throw new Error("The service parameter must be a descriptor");
@@ -127,17 +132,4 export class Container<S extends object
127 132 createChildContainer<S2 extends object = S>(): Container<S & S2> {
128 133 return new Container<S & S2>(this as any);
129 134 }
130
131 has(id: string | number) {
132 return id in this._cache;
133 }
134
135 get(id: string | number) {
136 return this._cache[id];
137 }
138
139 store(id: string | number, value: any) {
140 return (this._cache[id] = value);
141 }
142
143 135 }
@@ -7,16 +7,35 function safeCall(item: () => void) {
7 7 try {
8 8 item();
9 9 } catch {
10 // silence
10 // silence!
11 11 }
12 12 }
13 13
14 const emptyLifetime: ILifetime = {
15 has() {
16 return false;
17 },
18
19 enter() {
20
21 },
22
23 get() {
24 throw new Error("The specified item isn't registered with this lifetime manager");
25 },
26
27 store() {
28 // does nothing
29 }
30
31 };
32
14 33 export class LifetimeManager implements IDestroyable, ILifetimeManager {
15 34 private _cleanup: (() => void)[] = [];
16 35 private _cache: MapOf<any> = {};
17 36 private _destroyed = false;
18 37
19 initialize(id: string, context: ActivationContext<any>): ILifetime {
38 initialize(id: string): ILifetime {
20 39 const self = this;
21 40 let pending = false;
22 41 return {
@@ -58,12 +77,6 export class LifetimeManager implements
58 77 };
59 78 }
60 79
61
62
63
64
65
66
67 80 destroy() {
68 81 if (!this._destroyed) {
69 82 this._destroyed = true;
@@ -73,20 +86,56 export class LifetimeManager implements
73 86 }
74 87
75 88 static readonly empty: ILifetimeManager = {
76 has() {
77 return false;
89 initialize(): ILifetime {
90 return emptyLifetime;
78 91 },
92 destroy() {
93 throw new Error("Trying to destroy empty lifetime manager, this is a bug.");
94 }
95
96 };
97
98 static readonly hierarchyLifetime: ILifetimeManager = {
99 initialize(id: string, context: ActivationContext<any>): ILifetime {
100 return context.getContainer().getLifetimeManager().initialize(id, context);
101 },
102 destroy() {
103 throw new Error("Trying to destroy hierarchy lifetime manager, this is a bug.");
104 }
105 };
79 106
80 get() {
81 throw new Error("The specified item isn't registered with this lifetime manager");
107 static readonly singletonLifetime: ILifetimeManager = {
108 initialize(id: string): ILifetime {
109 return singletonLifetimeManager.initialize(id);
82 110 },
111 destroy() {
112 throw new Error("Trying to destroy singleton lifetime manager, this is a bug.");
113 }
114 };
83 115
84 register() {
85 // does nothing
116 static readonly contextLifetime: ILifetimeManager = {
117 initialize(id: string, context: ActivationContext<any>): ILifetime {
118 return {
119 enter() {
120 if (context.visit(id))
121 throw new Error("Cyclic reference detected");
122 },
123 get() {
124 return context.get(id);
125 },
126 has() {
127 return context.has(id);
128 },
129 store(item: any) {
130 context.store(id, item);
131 }
132
133 };
86 134 },
87
88 135 destroy() {
89 136 throw new Error("Trying to destroy empty lifetime manager, this is a bug.");
90 137 }
91 138 };
92 139 }
140
141 const singletonLifetimeManager = new LifetimeManager();
@@ -1,10 +1,11
1 1 import { ActivationContext } from "./ActivationContext";
2 import { Descriptor, ServiceMap, PartialServiceMap, ActivationType, ILifetimeManager } from "./interfaces";
2 import { Descriptor, ServiceMap, PartialServiceMap, ILifetimeManager } from "./interfaces";
3 3 import { argumentNotNull, isPrimitive, keys, isNull } from "../safe";
4 4 import { TraceSource } from "../log/TraceSource";
5 5 import { isDescriptor } from "./traits";
6 6 import { LifetimeManager } from "./LifetimeManager";
7 7 import { MatchingMemberKeys } from "../interfaces";
8 import { Container } from "./Container";
8 9
9 10 let cacheId = 0;
10 11
@@ -61,7 +62,9 export type InjectionSpec<T> = {
61 62 };
62 63
63 64 export interface ServiceDescriptorParams<S extends object, T, P extends any[]> {
64 lifetime: ILifetimeManager;
65 owner: Container<S>;
66
67 lifetime?: ILifetimeManager;
65 68
66 69 params?: P;
67 70
@@ -85,8 +88,12 export class ServiceDescriptor<S extends
85 88
86 89 _lifetime = LifetimeManager.empty;
87 90
91 _owner: Container<S>;
92
88 93 constructor(opts: ServiceDescriptorParams<S, T, P>) {
89 argumentNotNull(opts, "opts");
94 argumentNotNull(opts && opts.owner, "opts.owner");
95
96 this._owner = opts.owner;
90 97
91 98 if (opts.lifetime)
92 99 this._lifetime = opts.lifetime;
@@ -3,8 +3,6 import { IDestroyable } from "../interfa
3 3
4 4 export interface Descriptor<S extends object = any, T = any> {
5 5 activate(context: ActivationContext<S>): T;
6
7 clone(): this;
8 6 }
9 7
10 8 export type ServiceMap<S extends object> = {
General Comments 0
You need to be logged in to leave comments. Login now