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