##// 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 toString() {
33 toString() {
34 return "@walk";
34 return "@walk";
35 }
35 }
36
37 clone() {
38 return this;
39 }
36 }
40 }
@@ -5,12 +5,12 import { ServiceMap, Descriptor, Partial
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 } from "../interfaces";
8 import { MapOf, IDestroyable } from "../interfaces";
9 import { isDescriptor } from "./traits";
9 import { isDescriptor } from "./traits";
10
10
11 const trace = TraceSource.get("@implab/core/di/ActivationContext");
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 readonly _services: ContainerServiceMap<S>;
14 readonly _services: ContainerServiceMap<S>;
15
15
16 readonly _cache: MapOf<any>;
16 readonly _cache: MapOf<any>;
@@ -93,6 +93,9 export class Container<S extends object
93 this._cleanup.push(callback);
93 this._cleanup.push(callback);
94 }
94 }
95
95
96 destroy() {
97 return this.dispose();
98 }
96 dispose() {
99 dispose() {
97 if (this._disposed)
100 if (this._disposed)
98 return;
101 return;
@@ -14,8 +14,5 export class FactoryServiceDescriptor<S
14 // bind to null
14 // bind to null
15 this._factory = (...args) => opts.factory.apply(null, args as any);
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 }
20 }
17 }
21 }
18 }
@@ -1,9 +1,10
1 import { ActivationContext } from "./ActivationContext";
1 import { ActivationContext } from "./ActivationContext";
2 import { Descriptor, ServiceMap, PartialServiceMap, ActivationType } from "./interfaces";
2 import { Descriptor, ServiceMap, PartialServiceMap, ActivationType, ILifetimeManager } from "./interfaces";
3 import { Container } from "./Container";
4 import { argumentNotNull, isPrimitive, keys, isNull } from "../safe";
3 import { argumentNotNull, isPrimitive, keys, isNull } from "../safe";
5 import { TraceSource } from "../log/TraceSource";
4 import { TraceSource } from "../log/TraceSource";
6 import { isDescriptor } from "./traits";
5 import { isDescriptor } from "./traits";
6 import { LifetimeManager } from "./LifetimeManager";
7 import { MatchingMemberKeys } from "../interfaces";
7
8
8 let cacheId = 0;
9 let cacheId = 0;
9
10
@@ -21,15 +22,14 function injectMethod<T, M extends keyof
21 return m.call(target, _parse(args, context, "." + method));
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<T>(method: Cleaner<T>) {
25 function makeClenupCallback(target: any, method: any) {
26 if (typeof (method) === "function") {
26 if (typeof (method) === "string") {
27 return (target: T) => {
27 return () => {
28 method(target);
28 target[method]();
29 };
29 };
30 } else {
30 } else {
31 return () => {
31 return (target: T) => {
32 method(target);
32 (target[method] as any)();
33 };
33 };
34 }
34 }
35 }
35 }
@@ -53,16 +53,14 function _parse(value: any, context: Act
53 return t;
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 export type InjectionSpec<T> = {
58 export type InjectionSpec<T> = {
59 [m in keyof T]?: any;
59 [m in keyof T]?: any;
60 };
60 };
61
61
62 export interface ServiceDescriptorParams<S extends object, T, P extends any[]> {
62 export interface ServiceDescriptorParams<S extends object, T, P extends any[]> {
63 activation?: ActivationType;
63 lifetime: ILifetimeManager;
64
65 owner: Container<S>;
66
64
67 params?: P;
65 params?: P;
68
66
@@ -74,32 +72,23 export interface ServiceDescriptorParams
74 }
72 }
75
73
76 export class ServiceDescriptor<S extends object, T, P extends any[]> implements Descriptor<S, T> {
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 _services: ServiceMap<S>;
75 _services: ServiceMap<S>;
84
76
85 _params: P | undefined;
77 _params: P | undefined;
86
78
87 _inject: InjectionSpec<T>[];
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 constructor(opts: ServiceDescriptorParams<S, T, P>) {
87 constructor(opts: ServiceDescriptorParams<S, T, P>) {
96 argumentNotNull(opts, "opts");
88 argumentNotNull(opts, "opts");
97 argumentNotNull(opts.owner, "owner");
98
89
99 this._owner = opts.owner;
90 if (opts.lifetime)
100
91 this._lifetime = opts.lifetime;
101 if (!isNull(opts.activation))
102 this._activationType = opts.activation;
103
92
104 if (!isNull(opts.params))
93 if (!isNull(opts.params))
105 this._params = opts.params;
94 this._params = opts.params;
@@ -113,96 +102,22 export class ServiceDescriptor<S extends
113 throw new Error(
102 throw new Error(
114 "The cleanup parameter must be either a function or a function name");
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 activate(context: ActivationContext<S>) {
109 activate(context: ActivationContext<S>) {
121 // if we have a local service records, register them first
110 const lifetime = this._lifetime.initialize(this._cacheId, context);
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);
158
111
159 // the instance is bound to the container
112 if (lifetime.has()) {
160 if (this._cleanup)
113 return lifetime.get();
161 this._owner.onDispose(
114 } else {
162 makeClenupCallback(instance, this._cleanup));
115 const instance = this._create(context);
163
116 lifetime.store(this._cacheId, this._cleanup);
164 // cache and return the instance
117 return 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);
195 }
118 }
196 }
119 }
197
120
198 isInstanceCreated() {
199 return this._hasInstance;
200 }
201
202 getInstance() {
203 return this._instance;
204 }
205
206 _factory(...params: any[]): T {
121 _factory(...params: any[]): T {
207 throw Error("Not implemented");
122 throw Error("Not implemented");
208 }
123 }
@@ -210,10 +125,6 export class ServiceDescriptor<S extends
210 _create(context: ActivationContext<S>) {
125 _create(context: ActivationContext<S>) {
211 trace.debug(`constructing ${context._name}`);
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 if (this._services) {
128 if (this._services) {
218 keys(this._services).forEach(p => context.register(p, this._services[p]));
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 return instance;
148 return instance;
238 }
149 }
150
151 clone() {
152 return Object.create(this);
153 }
154
239 }
155 }
@@ -46,8 +46,6 export interface RegistrationVisitor<S e
46
46
47 visitFactoryRegistration(): void;
47 visitFactoryRegistration(): void;
48
48
49 visitBuilder<T>(builder: (t: ServiceRecordBuilder<T, S>) => void): void;
50
51 }
49 }
52
50
53 export interface RegistrationBuilder<T, S extends object> {
51 export interface RegistrationBuilder<T, S extends object> {
@@ -1,7 +1,10
1 import { ActivationContext } from "./ActivationContext";
1 import { ActivationContext } from "./ActivationContext";
2 import { IDestroyable } from "../interfaces";
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;
6
7 clone(): this;
5 }
8 }
6
9
7 export type ServiceMap<S extends object> = {
10 export type ServiceMap<S extends object> = {
@@ -36,3 +39,13 export type ContainerRegistered<S extend
36 Exclude<S, ContainerProvided<S>>;
39 Exclude<S, ContainerProvided<S>>;
37
40
38 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
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 export type Predicate<T = any> = (x: T) => boolean;
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 export interface MapOf<T> {
20 export interface MapOf<T> {
13 [key: string]: T;
21 [key: string]: T;
14 }
22 }
@@ -1,4 +1,4
1 import { ICancellable, Constructor } from "./interfaces";
1 import { ICancellable, Constructor, IDestroyable } from "./interfaces";
2 import { Cancellation } from "./Cancellation";
2 import { Cancellation } from "./Cancellation";
3
3
4 let _nextOid = 0;
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 export function destroy(d: any) {
486 export function destroy(d: any) {
481 if (d && "destroy" in d)
487 if (d && "destroy" in d)
482 d.destroy();
488 d.destroy();
General Comments 0
You need to be logged in to leave comments. Login now