##// END OF EJS Templates
working on services lifetime
cin -
r130:1af015b1d265 ioc ts support
parent child
Show More
@@ -1,70 +1,92
1 1 import { IDestroyable, MapOf } from "../interfaces";
2 2 import { argumentNotNull, isDestroyable } from "../safe";
3 import { ILifetimeManager } from "./interfaces";
3 import { ILifetimeManager, ILifetime } from "./interfaces";
4 import { ActivationContext } from "./ActivationContext";
4 5
5 6 function safeCall(item: () => void) {
6 7 try {
7 8 item();
8 9 } catch {
9 10 // silence
10 11 }
11 12 }
12 13
13 14 export class LifetimeManager implements IDestroyable, ILifetimeManager {
14 15 private _cleanup: (() => void)[] = [];
15 16 private _cache: MapOf<any> = {};
16 17 private _destroyed = false;
17 18
18 has(id: string) {
19 return id in this._cache;
20 }
19 initialize(id: string, context: ActivationContext<any>): ILifetime {
20 const self = this;
21 let pending = false;
22 return {
23 has() {
24 return (id in self._cache);
25 },
21 26
22 get(id: string) {
23 const t = this._cache[id];
27 get() {
28 const t = self._cache[id];
24 29 if (t === undefined)
25 30 throw new Error(`The item with with the key ${id} isn't found`);
26 31 return t;
27 }
32 },
28 33
29 register(id: string, item: any, cleanup?: (item: any) => void) {
34 enter() {
35 if (pending)
36 throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`);
37 pending = true;
38 },
39
40 store(item: any, cleanup?: (item: any) => void) {
30 41 argumentNotNull(id, "id");
31 42 argumentNotNull(item, "item");
32 if (this.has(id))
43
44 if (this.has())
33 45 throw new Error(`The item with with the key ${id} already registered with this lifetime manager`);
34 this._cache[id] = item;
46 pending = false;
35 47
36 if (this._destroyed)
48 self._cache[id] = item;
49
50 if (self._destroyed)
37 51 throw new Error("Lifetime manager is destroyed");
38 52 if (cleanup) {
39 this._cleanup.push(() => cleanup(item));
53 self._cleanup.push(() => cleanup(item));
40 54 } else if (isDestroyable(item)) {
41 this._cleanup.push(() => item.destroy());
55 self._cleanup.push(() => item.destroy());
42 56 }
43 57 }
58 };
59 }
60
61
62
63
64
65
44 66
45 67 destroy() {
46 68 if (!this._destroyed) {
47 69 this._destroyed = true;
48 70 this._cleanup.forEach(safeCall);
49 71 this._cleanup.length = 0;
50 72 }
51 73 }
52 74
53 75 static readonly empty: ILifetimeManager = {
54 76 has() {
55 77 return false;
56 78 },
57 79
58 80 get() {
59 81 throw new Error("The specified item isn't registered with this lifetime manager");
60 82 },
61 83
62 84 register() {
63 85 // does nothing
64 86 },
65 87
66 88 destroy() {
67 89 throw new Error("Trying to destroy empty lifetime manager, this is a bug.");
68 90 }
69 91 };
70 92 }
@@ -1,155 +1,157
1 1 import { ActivationContext } from "./ActivationContext";
2 2 import { Descriptor, ServiceMap, PartialServiceMap, ActivationType, 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 8
9 9 let cacheId = 0;
10 10
11 11 const trace = TraceSource.get("@implab/core/di/ActivationContext");
12 12
13 13 function injectMethod<T, M extends keyof T, S extends object, A>(target: T, method: M, context: ActivationContext<S>, args: A) {
14 14
15 15 const m = target[method];
16 16 if (!m || typeof m !== "function")
17 17 throw new Error("Method '" + method + "' not found");
18 18
19 19 if (args instanceof Array)
20 20 return m.apply(target, _parse(args, context, "." + method));
21 21 else
22 22 return m.call(target, _parse(args, context, "." + method));
23 23 }
24 24
25 function makeClenupCallback<T>(method: Cleaner<T>) {
25 function makeCleanupCallback<T>(method: Cleaner<T>) {
26 26 if (typeof (method) === "function") {
27 27 return (target: T) => {
28 28 method(target);
29 29 };
30 30 } else {
31 31 return (target: T) => {
32 (target[method] as any)();
32 const m = target[method] as any;
33 m.apply(target);
33 34 };
34 35 }
35 36 }
36 37
37 38 function _parse(value: any, context: ActivationContext<any>, path: string): any {
38 39 if (isPrimitive(value))
39 40 return value as any;
40 41
41 42 trace.debug("parse {0}", path);
42 43
43 44 if (isDescriptor(value))
44 45 return context.activate(value, path);
45 46
46 47 if (value instanceof Array)
47 48 return value.map((x, i) => _parse(x, context, `${path}[${i}]`)) as any;
48 49
49 50 const t: any = {};
50 51
51 52 keys(value).forEach(p => t[p] = _parse(value[p], context, `${path}.${p}`));
52 53
53 54 return t;
54 55 }
55 56
56 57 export type Cleaner<T> = ((x: T) => void) | MatchingMemberKeys<() => void, T>;
57 58
58 59 export type InjectionSpec<T> = {
59 60 [m in keyof T]?: any;
60 61 };
61 62
62 63 export interface ServiceDescriptorParams<S extends object, T, P extends any[]> {
63 64 lifetime: ILifetimeManager;
64 65
65 66 params?: P;
66 67
67 68 inject?: InjectionSpec<T>[];
68 69
69 70 services?: PartialServiceMap<S>;
70 71
71 72 cleanup?: Cleaner<T>;
72 73 }
73 74
74 75 export class ServiceDescriptor<S extends object, T, P extends any[]> implements Descriptor<S, T> {
75 76 _services: ServiceMap<S>;
76 77
77 78 _params: P | undefined;
78 79
79 80 _inject: InjectionSpec<T>[];
80 81
81 82 _cleanup: ((item: T) => void) | undefined;
82 83
83 84 _cacheId = String(++cacheId);
84 85
85 86 _lifetime = LifetimeManager.empty;
86 87
87 88 constructor(opts: ServiceDescriptorParams<S, T, P>) {
88 89 argumentNotNull(opts, "opts");
89 90
90 91 if (opts.lifetime)
91 92 this._lifetime = opts.lifetime;
92 93
93 94 if (!isNull(opts.params))
94 95 this._params = opts.params;
95 96
96 97 this._inject = opts.inject || [];
97 98
98 99 this._services = (opts.services || {}) as ServiceMap<S>;
99 100
100 101 if (opts.cleanup) {
101 102 if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function))
102 103 throw new Error(
103 104 "The cleanup parameter must be either a function or a function name");
104 105
105 this._cleanup = makeClenupCallback(opts.cleanup);
106 this._cleanup = makeCleanupCallback(opts.cleanup);
106 107 }
107 108 }
108 109
109 110 activate(context: ActivationContext<S>) {
110 111 const lifetime = this._lifetime.initialize(this._cacheId, context);
111 112
112 113 if (lifetime.has()) {
113 114 return lifetime.get();
114 115 } else {
116 lifetime.enter();
115 117 const instance = this._create(context);
116 118 lifetime.store(this._cacheId, this._cleanup);
117 119 return instance;
118 120 }
119 121 }
120 122
121 123 _factory(...params: any[]): T {
122 124 throw Error("Not implemented");
123 125 }
124 126
125 127 _create(context: ActivationContext<S>) {
126 128 trace.debug(`constructing ${context._name}`);
127 129
128 130 if (this._services) {
129 131 keys(this._services).forEach(p => context.register(p, this._services[p]));
130 132 }
131 133
132 134 let instance: T;
133 135
134 136 if (this._params === undefined) {
135 137 instance = this._factory();
136 138 } else if (this._params instanceof Array) {
137 139 instance = this._factory.apply(this, _parse(this._params, context, "args"));
138 140 } else {
139 141 instance = this._factory(_parse(this._params, context, "args"));
140 142 }
141 143
142 144 if (this._inject) {
143 145 this._inject.forEach(spec => {
144 146 for (const m in spec)
145 147 injectMethod(instance, m, context, spec[m]);
146 148 });
147 149 }
148 150 return instance;
149 151 }
150 152
151 153 clone() {
152 154 return Object.create(this);
153 155 }
154 156
155 157 }
@@ -1,51 +1,55
1 1 import { ActivationContext } from "./ActivationContext";
2 2 import { IDestroyable } from "../interfaces";
3 3
4 4 export interface Descriptor<S extends object = any, T = any> {
5 5 activate(context: ActivationContext<S>): T;
6 6
7 7 clone(): this;
8 8 }
9 9
10 10 export type ServiceMap<S extends object> = {
11 11 [k in keyof S]: Descriptor<S, S[k]>;
12 12 };
13 13
14 14 export type ContainerKeys<S extends object> = keyof S | keyof ContainerProvided<S>;
15 15
16 16 export type ContainerResolve<S extends object, K> =
17 17 K extends keyof ContainerProvided<S> ? ContainerProvided<S>[K] :
18 18 K extends keyof S ? S[K] : never;
19 19
20 20 export type ContainerServiceMap<S extends object> = {
21 21 [K in ContainerKeys<S>]: Descriptor<S, ContainerResolve<S, K>>;
22 22 };
23 23
24 24 export type PartialServiceMap<S extends object> = {
25 25 [k in keyof S]?: Descriptor<S, S[k]>;
26 26 };
27 27
28 28 export interface Resolver<S extends object> {
29 29 resolve<K extends ContainerKeys<S>>(name: K, def?: ContainerResolve<S, K>): ContainerResolve<S, K>;
30 30 }
31 31
32 32 export interface ContainerProvided<S extends object> {
33 33 container: Resolver<S>;
34 34 }
35 35
36 36 export type ContainerRegistered<S extends object> = /*{
37 37 [K in Exclude<keyof S, keyof ContainerProvided<S>>]: S[K];
38 38 };*/
39 39 Exclude<S, ContainerProvided<S>>;
40 40
41 41 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
42 42
43 43 export interface ILifetimeManager extends IDestroyable {
44 44 initialize(id: string, context: ActivationContext<any>): ILifetime;
45 45 }
46 46
47 47 export interface ILifetime {
48 48 has(): boolean;
49
49 50 get(): any;
51
52 enter(): void;
53
50 54 store(item: any, cleanup?: (item: any) => void): void;
51 55 } No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now