##// END OF EJS Templates
working on lifetime management
cin -
r129:c13384c6c1ac ioc ts support
parent child
Show More
@@ -0,0 +1,70
1 import { IDestroyable, MapOf } from "../interfaces";
2 import { argumentNotNull, isDestroyable } from "../safe";
3 import { ILifetimeManager } from "./interfaces";
4
5 function safeCall(item: () => void) {
6 try {
7 item();
8 } catch {
9 // silence
10 }
11 }
12
13 export class LifetimeManager implements IDestroyable, ILifetimeManager {
14 private _cleanup: (() => void)[] = [];
15 private _cache: MapOf<any> = {};
16 private _destroyed = false;
17
18 has(id: string) {
19 return id in this._cache;
20 }
21
22 get(id: string) {
23 const t = this._cache[id];
24 if (t === undefined)
25 throw new Error(`The item with with the key ${id} isn't found`);
26 return t;
27 }
28
29 register(id: string, item: any, cleanup?: (item: any) => void) {
30 argumentNotNull(id, "id");
31 argumentNotNull(item, "item");
32 if (this.has(id))
33 throw new Error(`The item with with the key ${id} already registered with this lifetime manager`);
34 this._cache[id] = item;
35
36 if (this._destroyed)
37 throw new Error("Lifetime manager is destroyed");
38 if (cleanup) {
39 this._cleanup.push(() => cleanup(item));
40 } else if (isDestroyable(item)) {
41 this._cleanup.push(() => item.destroy());
42 }
43 }
44
45 destroy() {
46 if (!this._destroyed) {
47 this._destroyed = true;
48 this._cleanup.forEach(safeCall);
49 this._cleanup.length = 0;
50 }
51 }
52
53 static readonly empty: ILifetimeManager = {
54 has() {
55 return false;
56 },
57
58 get() {
59 throw new Error("The specified item isn't registered with this lifetime manager");
60 },
61
62 register() {
63 // does nothing
64 },
65
66 destroy() {
67 throw new Error("Trying to destroy empty lifetime manager, this is a bug.");
68 }
69 };
70 }
@@ -33,4 +33,8 export class AggregateDescriptor<S exten
33 33 toString() {
34 34 return "@walk";
35 35 }
36
37 clone() {
38 return this;
36 39 }
40 }
@@ -5,12 +5,12 import { ServiceMap, Descriptor, Partial
5 5 import { TraceSource } from "../log/TraceSource";
6 6 import { Configuration, RegistrationMap } from "./Configuration";
7 7 import { Cancellation } from "../Cancellation";
8 import { MapOf } from "../interfaces";
8 import { MapOf, IDestroyable } from "../interfaces";
9 9 import { isDescriptor } from "./traits";
10 10
11 11 const trace = TraceSource.get("@implab/core/di/ActivationContext");
12 12
13 export class Container<S extends object = any> implements Resolver<S> {
13 export class Container<S extends object = any> implements Resolver<S>, IDestroyable {
14 14 readonly _services: ContainerServiceMap<S>;
15 15
16 16 readonly _cache: MapOf<any>;
@@ -93,6 +93,9 export class Container<S extends object
93 93 this._cleanup.push(callback);
94 94 }
95 95
96 destroy() {
97 return this.dispose();
98 }
96 99 dispose() {
97 100 if (this._disposed)
98 101 return;
@@ -14,8 +14,5 export class FactoryServiceDescriptor<S
14 14 // bind to null
15 15 this._factory = (...args) => opts.factory.apply(null, args as any);
16 16
17 if (opts.activation === "singleton") {
18 this._cacheId = oid(opts.factory);
19 17 }
20 18 }
21 }
@@ -1,9 +1,10
1 1 import { ActivationContext } from "./ActivationContext";
2 import { Descriptor, ServiceMap, PartialServiceMap, ActivationType } from "./interfaces";
3 import { Container } from "./Container";
2 import { Descriptor, ServiceMap, PartialServiceMap, ActivationType, ILifetimeManager } from "./interfaces";
4 3 import { argumentNotNull, isPrimitive, keys, isNull } from "../safe";
5 4 import { TraceSource } from "../log/TraceSource";
6 5 import { isDescriptor } from "./traits";
6 import { LifetimeManager } from "./LifetimeManager";
7 import { MatchingMemberKeys } from "../interfaces";
7 8
8 9 let cacheId = 0;
9 10
@@ -21,15 +22,14 function injectMethod<T, M extends keyof
21 22 return m.call(target, _parse(args, context, "." + method));
22 23 }
23 24
24 function makeClenupCallback<T>(target: T, method: Cleaner<T>): () => void;
25 function makeClenupCallback(target: any, method: any) {
26 if (typeof (method) === "string") {
27 return () => {
28 target[method]();
25 function makeClenupCallback<T>(method: Cleaner<T>) {
26 if (typeof (method) === "function") {
27 return (target: T) => {
28 method(target);
29 29 };
30 30 } else {
31 return () => {
32 method(target);
31 return (target: T) => {
32 (target[method] as any)();
33 33 };
34 34 }
35 35 }
@@ -53,16 +53,14 function _parse(value: any, context: Act
53 53 return t;
54 54 }
55 55
56 export type Cleaner<T> = ((x: T) => void) | keyof Extract<T, { [M in keyof T]: () => void }>;
56 export type Cleaner<T> = ((x: T) => void) | MatchingMemberKeys<() => void, T>;
57 57
58 58 export type InjectionSpec<T> = {
59 59 [m in keyof T]?: any;
60 60 };
61 61
62 62 export interface ServiceDescriptorParams<S extends object, T, P extends any[]> {
63 activation?: ActivationType;
64
65 owner: Container<S>;
63 lifetime: ILifetimeManager;
66 64
67 65 params?: P;
68 66
@@ -74,32 +72,23 export interface ServiceDescriptorParams
74 72 }
75 73
76 74 export class ServiceDescriptor<S extends object, T, P extends any[]> implements Descriptor<S, T> {
77 _instance: T | undefined;
78
79 _hasInstance = false;
80
81 _activationType: ActivationType = "call";
82
83 75 _services: ServiceMap<S>;
84 76
85 77 _params: P | undefined;
86 78
87 79 _inject: InjectionSpec<T>[];
88 80
89 _cleanup: Cleaner<T> | undefined;
81 _cleanup: ((item: T) => void) | undefined;
90 82
91 _cacheId: any;
83 _cacheId = String(++cacheId);
92 84
93 _owner: Container<S>;
85 _lifetime = LifetimeManager.empty;
94 86
95 87 constructor(opts: ServiceDescriptorParams<S, T, P>) {
96 88 argumentNotNull(opts, "opts");
97 argumentNotNull(opts.owner, "owner");
98 89
99 this._owner = opts.owner;
100
101 if (!isNull(opts.activation))
102 this._activationType = opts.activation;
90 if (opts.lifetime)
91 this._lifetime = opts.lifetime;
103 92
104 93 if (!isNull(opts.params))
105 94 this._params = opts.params;
@@ -113,96 +102,22 export class ServiceDescriptor<S extends
113 102 throw new Error(
114 103 "The cleanup parameter must be either a function or a function name");
115 104
116 this._cleanup = opts.cleanup;
105 this._cleanup = makeClenupCallback(opts.cleanup);
117 106 }
118 107 }
119 108
120 109 activate(context: ActivationContext<S>) {
121 // if we have a local service records, register them first
122 let instance: T;
123
124 // ensure we have a cache id
125 if (!this._cacheId)
126 this._cacheId = ++cacheId;
127
128 switch (this._activationType) {
129 case "singleton": // SINGLETON
130 // if the value is cached return it
131 if (this._hasInstance)
132 return this._instance;
133
134 // singletons are bound to the root container
135 const container = context.container.getRootContainer();
136
137 if (container.has(this._cacheId)) {
138 instance = container.get(this._cacheId);
139 } else {
140 instance = this._create(context);
141 container.store(this._cacheId, instance);
142 if (this._cleanup)
143 container.onDispose(
144 makeClenupCallback(instance, this._cleanup));
145 }
146
147 this._hasInstance = true;
148 return (this._instance = instance);
149
150 case "container": // CONTAINER
151 // return a cached value
152
153 if (this._hasInstance)
154 return this._instance;
155
156 // create an instance
157 instance = this._create(context);
110 const lifetime = this._lifetime.initialize(this._cacheId, context);
158 111
159 // the instance is bound to the container
160 if (this._cleanup)
161 this._owner.onDispose(
162 makeClenupCallback(instance, this._cleanup));
163
164 // cache and return the instance
165 this._hasInstance = true;
166 return (this._instance = instance);
167 case "context": // CONTEXT
168 // return a cached value if one exists
169
170 if (context.has(this._cacheId))
171 return context.get(this._cacheId);
172 // context context activated instances are controlled by callers
173 return context.store(this._cacheId, this._create(context));
174 case "call": // CALL
175 // per-call created instances are controlled by callers
176 return this._create(context);
177 case "hierarchy": // HIERARCHY
178 // hierarchy activated instances are behave much like container activated
179 // except they are created and bound to the child container
180
181 // return a cached value
182 if (context.container.has(this._cacheId))
183 return context.container.get(this._cacheId);
184
185 instance = this._create(context);
186
187 if (this._cleanup)
188 context.container.onDispose(makeClenupCallback(
189 instance,
190 this._cleanup));
191
192 return context.container.store(this._cacheId, instance);
193 default:
194 throw new Error("Invalid activation type: " + this._activationType);
112 if (lifetime.has()) {
113 return lifetime.get();
114 } else {
115 const instance = this._create(context);
116 lifetime.store(this._cacheId, this._cleanup);
117 return instance;
195 118 }
196 119 }
197 120
198 isInstanceCreated() {
199 return this._hasInstance;
200 }
201
202 getInstance() {
203 return this._instance;
204 }
205
206 121 _factory(...params: any[]): T {
207 122 throw Error("Not implemented");
208 123 }
@@ -210,10 +125,6 export class ServiceDescriptor<S extends
210 125 _create(context: ActivationContext<S>) {
211 126 trace.debug(`constructing ${context._name}`);
212 127
213 if (this._activationType !== "call" &&
214 context.visit(this._cacheId) > 0)
215 throw new Error("Recursion detected");
216
217 128 if (this._services) {
218 129 keys(this._services).forEach(p => context.register(p, this._services[p]));
219 130 }
@@ -236,4 +147,9 export class ServiceDescriptor<S extends
236 147 }
237 148 return instance;
238 149 }
150
151 clone() {
152 return Object.create(this);
239 153 }
154
155 }
@@ -46,8 +46,6 export interface RegistrationVisitor<S e
46 46
47 47 visitFactoryRegistration(): void;
48 48
49 visitBuilder<T>(builder: (t: ServiceRecordBuilder<T, S>) => void): void;
50
51 49 }
52 50
53 51 export interface RegistrationBuilder<T, S extends object> {
@@ -1,7 +1,10
1 1 import { ActivationContext } from "./ActivationContext";
2 import { IDestroyable } from "../interfaces";
2 3
3 4 export interface Descriptor<S extends object = any, T = any> {
4 5 activate(context: ActivationContext<S>): T;
6
7 clone(): this;
5 8 }
6 9
7 10 export type ServiceMap<S extends object> = {
@@ -36,3 +39,13 export type ContainerRegistered<S extend
36 39 Exclude<S, ContainerProvided<S>>;
37 40
38 41 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
42
43 export interface ILifetimeManager extends IDestroyable {
44 initialize(id: string, context: ActivationContext<any>): ILifetime;
45 }
46
47 export interface ILifetime {
48 has(): boolean;
49 get(): any;
50 store(item: any, cleanup?: (item: any) => void): void;
51 } No newline at end of file
@@ -9,6 +9,14 export type Factory<T = {}> = (...args:
9 9
10 10 export type Predicate<T = any> = (x: T) => boolean;
11 11
12 export type MatchingMemberKeys<T, From> = { [K in keyof From]: From[K] extends T ? K : never}[keyof From];
13
14 export type NotMatchingMemberKeys<T, From> = { [K in keyof From]: From[K] extends T ? never : K}[keyof From];
15
16 export type ExtractMembers<T, From> = Pick<From, MatchingMemberKeys<T, From>>;
17
18 export type ExcludeMembers<T, From> = Pick<From, NotMatchingMemberKeys<T, From>>;
19
12 20 export interface MapOf<T> {
13 21 [key: string]: T;
14 22 }
@@ -1,4 +1,4
1 import { ICancellable, Constructor } from "./interfaces";
1 import { ICancellable, Constructor, IDestroyable } from "./interfaces";
2 2 import { Cancellation } from "./Cancellation";
3 3
4 4 let _nextOid = 0;
@@ -477,6 +477,12 export function firstWhere<T>(
477 477 }
478 478 }
479 479
480 export function isDestroyable(d: any): d is IDestroyable {
481 if (d && "destroy" in d && typeof(destroy) === "function")
482 return true;
483 return false;
484 }
485
480 486 export function destroy(d: any) {
481 487 if (d && "destroy" in d)
482 488 d.destroy();
General Comments 0
You need to be logged in to leave comments. Login now