| @@ -1,218 +1,216 | |||||
| 1 | import { IDestroyable, MapOf } from "../interfaces"; |
|
1 | import { IDestroyable, MapOf } from "../interfaces"; | |
| 2 | import { argumentNotNull, isDestroyable, argumentNotEmptyString, isRemovable } from "../safe"; |
|
2 | import { argumentNotNull, isDestroyable, argumentNotEmptyString, isRemovable } from "../safe"; | |
| 3 | import { ILifetime, ServiceContainer } from "./interfaces"; |
|
3 | import { ILifetime, ServiceContainer } from "./interfaces"; | |
| 4 | import { ActivationContext } from "./ActivationContext"; |
|
4 | import { ActivationContext } from "./ActivationContext"; | |
| 5 |
|
5 | |||
| 6 | function safeCall(item: () => void) { |
|
6 | 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 = Object.freeze({ |
|
14 | const emptyLifetime: ILifetime = Object.freeze({ | |
| 15 | has() { |
|
15 | has() { | |
| 16 | return false; |
|
16 | return false; | |
| 17 | }, |
|
17 | }, | |
| 18 |
|
18 | |||
| 19 | initialize() { |
|
19 | initialize() { | |
| 20 |
|
20 | |||
| 21 | }, |
|
21 | }, | |
| 22 |
|
22 | |||
| 23 | get() { |
|
23 | get() { | |
| 24 | throw new Error("The specified item isn't registered with this lifetime manager"); |
|
24 | throw new Error("The specified item isn't registered with this lifetime manager"); | |
| 25 | }, |
|
25 | }, | |
| 26 |
|
26 | |||
| 27 | store() { |
|
27 | store() { | |
| 28 | // does nothing |
|
28 | // does nothing | |
| 29 | }, |
|
29 | }, | |
| 30 |
|
30 | |||
| 31 | toString() { |
|
31 | toString() { | |
| 32 | return `[object EmptyLifetime]`; |
|
32 | return `[object EmptyLifetime]`; | |
| 33 | } |
|
33 | } | |
| 34 |
|
34 | |||
| 35 | }); |
|
35 | }); | |
| 36 |
|
36 | |||
| 37 | const unknownLifetime: ILifetime = Object.freeze({ |
|
37 | const unknownLifetime: ILifetime = Object.freeze({ | |
| 38 | has() { |
|
38 | has() { | |
| 39 | return false; |
|
39 | return false; | |
| 40 | }, |
|
40 | }, | |
| 41 | initialize() { |
|
41 | initialize() { | |
| 42 | throw new Error("Can't call initialize on the unknown lifetime object"); |
|
42 | throw new Error("Can't call initialize on the unknown lifetime object"); | |
| 43 | }, |
|
43 | }, | |
| 44 | get() { |
|
44 | get() { | |
| 45 | throw new Error("The lifetime object isn't initialized"); |
|
45 | throw new Error("The lifetime object isn't initialized"); | |
| 46 | }, |
|
46 | }, | |
| 47 | store() { |
|
47 | store() { | |
| 48 | throw new Error("Can't store a value in the unknown lifetime object"); |
|
48 | throw new Error("Can't store a value in the unknown lifetime object"); | |
| 49 | }, |
|
49 | }, | |
| 50 | toString() { |
|
50 | toString() { | |
| 51 | return `[object UnknownLifetime]`; |
|
51 | return `[object UnknownLifetime]`; | |
| 52 | } |
|
52 | } | |
| 53 | }); |
|
53 | }); | |
| 54 |
|
54 | |||
| 55 | let nextId = 0; |
|
55 | let nextId = 0; | |
| 56 |
|
56 | |||
| 57 | const singletons: any = {}; |
|
57 | const singletons: any = {}; | |
| 58 |
|
58 | |||
| 59 | export class LifetimeManager implements IDestroyable { |
|
59 | export class LifetimeManager implements IDestroyable { | |
| 60 | private _cleanup: (() => void)[] = []; |
|
60 | private _cleanup: (() => void)[] = []; | |
| 61 | private _cache: MapOf<any> = {}; |
|
61 | private _cache: MapOf<any> = {}; | |
| 62 | private _destroyed = false; |
|
62 | private _destroyed = false; | |
| 63 |
|
63 | |||
| 64 | private _pending: MapOf<boolean> = {}; |
|
64 | private _pending: MapOf<boolean> = {}; | |
| 65 |
|
65 | |||
| 66 | create(): ILifetime { |
|
66 | create(): ILifetime { | |
| 67 | const self = this; |
|
67 | const self = this; | |
| 68 | const id = ++nextId; |
|
68 | const id = ++nextId; | |
| 69 | return { |
|
69 | return { | |
| 70 | has() { |
|
70 | has() { | |
| 71 | return (id in self._cache); |
|
71 | return (id in self._cache); | |
| 72 | }, |
|
72 | }, | |
| 73 |
|
73 | |||
| 74 | get() { |
|
74 | get() { | |
| 75 | const t = self._cache[id]; |
|
75 | const t = self._cache[id]; | |
| 76 | if (t === undefined) |
|
76 | if (t === undefined) | |
| 77 | throw new Error(`The item with with the key ${id} isn't found`); |
|
77 | throw new Error(`The item with with the key ${id} isn't found`); | |
| 78 | return t; |
|
78 | return t; | |
| 79 | }, |
|
79 | }, | |
| 80 |
|
80 | |||
| 81 | initialize() { |
|
81 | initialize() { | |
| 82 | if (self._pending[id]) |
|
82 | if (self._pending[id]) | |
| 83 | throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`); |
|
83 | throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`); | |
| 84 | self._pending[id] = true; |
|
84 | self._pending[id] = true; | |
| 85 | }, |
|
85 | }, | |
| 86 |
|
86 | |||
| 87 | store(item: any, cleanup?: (item: any) => void) { |
|
87 | store(item: any, cleanup?: (item: any) => void) { | |
| 88 | argumentNotNull(id, "id"); |
|
88 | argumentNotNull(id, "id"); | |
| 89 | argumentNotNull(item, "item"); |
|
89 | argumentNotNull(item, "item"); | |
| 90 |
|
90 | |||
| 91 | if (this.has()) |
|
91 | if (this.has()) | |
| 92 | throw new Error(`The item with with the key ${id} already registered with this lifetime manager`); |
|
92 | throw new Error(`The item with with the key ${id} already registered with this lifetime manager`); | |
| 93 | delete self._pending[id]; |
|
93 | delete self._pending[id]; | |
| 94 |
|
94 | |||
| 95 | self._cache[id] = item; |
|
95 | self._cache[id] = item; | |
| 96 |
|
96 | |||
| 97 | if (self._destroyed) |
|
97 | if (self._destroyed) | |
| 98 | throw new Error("Lifetime manager is destroyed"); |
|
98 | throw new Error("Lifetime manager is destroyed"); | |
| 99 | if (cleanup) { |
|
99 | if (cleanup) { | |
| 100 | self._cleanup.push(() => cleanup(item)); |
|
100 | self._cleanup.push(() => cleanup(item)); | |
| 101 | } else if (isDestroyable(item)) { |
|
101 | } else if (isDestroyable(item)) { | |
| 102 | self._cleanup.push(() => item.destroy()); |
|
102 | self._cleanup.push(() => item.destroy()); | |
| 103 | } else if (isRemovable(item)) { |
|
|||
| 104 | self._cleanup.push(() => item.remove()); |
|
|||
| 105 | } |
|
103 | } | |
| 106 | } |
|
104 | } | |
| 107 | }; |
|
105 | }; | |
| 108 | } |
|
106 | } | |
| 109 |
|
107 | |||
| 110 | destroy() { |
|
108 | destroy() { | |
| 111 | if (!this._destroyed) { |
|
109 | if (!this._destroyed) { | |
| 112 | this._destroyed = true; |
|
110 | this._destroyed = true; | |
| 113 | this._cleanup.forEach(safeCall); |
|
111 | this._cleanup.forEach(safeCall); | |
| 114 | this._cleanup.length = 0; |
|
112 | this._cleanup.length = 0; | |
| 115 | } |
|
113 | } | |
| 116 | } |
|
114 | } | |
| 117 |
|
115 | |||
| 118 | static empty(): ILifetime { |
|
116 | static empty(): ILifetime { | |
| 119 | return emptyLifetime; |
|
117 | return emptyLifetime; | |
| 120 | } |
|
118 | } | |
| 121 |
|
119 | |||
| 122 | static hierarchyLifetime() { |
|
120 | static hierarchyLifetime() { | |
| 123 | let _lifetime = unknownLifetime; |
|
121 | let _lifetime = unknownLifetime; | |
| 124 | return { |
|
122 | return { | |
| 125 | initialize(context: ActivationContext<any>) { |
|
123 | initialize(context: ActivationContext<any>) { | |
| 126 | if (_lifetime !== unknownLifetime) |
|
124 | if (_lifetime !== unknownLifetime) | |
| 127 | throw new Error("Cyclic reference activation detected"); |
|
125 | throw new Error("Cyclic reference activation detected"); | |
| 128 |
|
126 | |||
| 129 | _lifetime = context.getContainer().getLifetimeManager().create(); |
|
127 | _lifetime = context.getContainer().getLifetimeManager().create(); | |
| 130 | }, |
|
128 | }, | |
| 131 | get() { |
|
129 | get() { | |
| 132 | return _lifetime.get(); |
|
130 | return _lifetime.get(); | |
| 133 | }, |
|
131 | }, | |
| 134 | has() { |
|
132 | has() { | |
| 135 | return _lifetime.has(); |
|
133 | return _lifetime.has(); | |
| 136 | }, |
|
134 | }, | |
| 137 | store(item: any, cleanup?: (item: any) => void) { |
|
135 | store(item: any, cleanup?: (item: any) => void) { | |
| 138 | return _lifetime.store(item, cleanup); |
|
136 | return _lifetime.store(item, cleanup); | |
| 139 | }, |
|
137 | }, | |
| 140 | toString() { |
|
138 | toString() { | |
| 141 | return `[object HierarchyLifetime, has=${this.has()}]`; |
|
139 | return `[object HierarchyLifetime, has=${this.has()}]`; | |
| 142 | } |
|
140 | } | |
| 143 | }; |
|
141 | }; | |
| 144 | } |
|
142 | } | |
| 145 |
|
143 | |||
| 146 | static contextLifetime() { |
|
144 | static contextLifetime() { | |
| 147 | let _lifetime = unknownLifetime; |
|
145 | let _lifetime = unknownLifetime; | |
| 148 | return { |
|
146 | return { | |
| 149 | initialize(context: ActivationContext<any>) { |
|
147 | initialize(context: ActivationContext<any>) { | |
| 150 | if (_lifetime !== unknownLifetime) |
|
148 | if (_lifetime !== unknownLifetime) | |
| 151 | throw new Error("Cyclic reference detected"); |
|
149 | throw new Error("Cyclic reference detected"); | |
| 152 | _lifetime = context.createLifetime(); |
|
150 | _lifetime = context.createLifetime(); | |
| 153 | }, |
|
151 | }, | |
| 154 | get() { |
|
152 | get() { | |
| 155 | return _lifetime.get(); |
|
153 | return _lifetime.get(); | |
| 156 | }, |
|
154 | }, | |
| 157 | has() { |
|
155 | has() { | |
| 158 | return _lifetime.has(); |
|
156 | return _lifetime.has(); | |
| 159 | }, |
|
157 | }, | |
| 160 | store(item: any) { |
|
158 | store(item: any) { | |
| 161 | _lifetime.store(item); |
|
159 | _lifetime.store(item); | |
| 162 | }, |
|
160 | }, | |
| 163 | toString() { |
|
161 | toString() { | |
| 164 | return `[object ContextLifetime, has=${this.has()}]`; |
|
162 | return `[object ContextLifetime, has=${this.has()}]`; | |
| 165 | } |
|
163 | } | |
| 166 | }; |
|
164 | }; | |
| 167 | } |
|
165 | } | |
| 168 |
|
166 | |||
| 169 | static singletonLifetime(typeId: string) { |
|
167 | static singletonLifetime(typeId: string) { | |
| 170 | argumentNotEmptyString(typeId, "typeId"); |
|
168 | argumentNotEmptyString(typeId, "typeId"); | |
| 171 | let pending = false; |
|
169 | let pending = false; | |
| 172 | return { |
|
170 | return { | |
| 173 | has() { |
|
171 | has() { | |
| 174 | return typeId in singletons; |
|
172 | return typeId in singletons; | |
| 175 | }, |
|
173 | }, | |
| 176 | get() { |
|
174 | get() { | |
| 177 | if (!this.has()) |
|
175 | if (!this.has()) | |
| 178 | throw new Error(`The instance ${typeId} doesn't exists`); |
|
176 | throw new Error(`The instance ${typeId} doesn't exists`); | |
| 179 | return singletons[typeId]; |
|
177 | return singletons[typeId]; | |
| 180 | }, |
|
178 | }, | |
| 181 | initialize() { |
|
179 | initialize() { | |
| 182 | if (pending) |
|
180 | if (pending) | |
| 183 | throw new Error("Cyclic reference detected"); |
|
181 | throw new Error("Cyclic reference detected"); | |
| 184 | pending = true; |
|
182 | pending = true; | |
| 185 | }, |
|
183 | }, | |
| 186 | store(item: any) { |
|
184 | store(item: any) { | |
| 187 | singletons[typeId] = item; |
|
185 | singletons[typeId] = item; | |
| 188 | pending = false; |
|
186 | pending = false; | |
| 189 | }, |
|
187 | }, | |
| 190 | toString() { |
|
188 | toString() { | |
| 191 | return `[object SingletonLifetime, has=${this.has()}, typeId=${typeId}]`; |
|
189 | return `[object SingletonLifetime, has=${this.has()}, typeId=${typeId}]`; | |
| 192 | } |
|
190 | } | |
| 193 | }; |
|
191 | }; | |
| 194 | } |
|
192 | } | |
| 195 |
|
193 | |||
| 196 | static containerLifetime(container: ServiceContainer<any>) { |
|
194 | static containerLifetime(container: ServiceContainer<any>) { | |
| 197 | let _lifetime = unknownLifetime; |
|
195 | let _lifetime = unknownLifetime; | |
| 198 | return { |
|
196 | return { | |
| 199 | initialize(context: ActivationContext<any>) { |
|
197 | initialize(context: ActivationContext<any>) { | |
| 200 | if (_lifetime !== unknownLifetime) |
|
198 | if (_lifetime !== unknownLifetime) | |
| 201 | throw new Error("Cyclic reference detected"); |
|
199 | throw new Error("Cyclic reference detected"); | |
| 202 | _lifetime = container.getLifetimeManager().create(); |
|
200 | _lifetime = container.getLifetimeManager().create(); | |
| 203 | }, |
|
201 | }, | |
| 204 | get() { |
|
202 | get() { | |
| 205 | return _lifetime.get(); |
|
203 | return _lifetime.get(); | |
| 206 | }, |
|
204 | }, | |
| 207 | has() { |
|
205 | has() { | |
| 208 | return _lifetime.has(); |
|
206 | return _lifetime.has(); | |
| 209 | }, |
|
207 | }, | |
| 210 | store(item: any) { |
|
208 | store(item: any) { | |
| 211 | _lifetime.store(item); |
|
209 | _lifetime.store(item); | |
| 212 | }, |
|
210 | }, | |
| 213 | toString() { |
|
211 | toString() { | |
| 214 | return `[object ContainerLifetime, has=${_lifetime.has()}]` |
|
212 | return `[object ContainerLifetime, has=${_lifetime.has()}]` | |
| 215 | } |
|
213 | } | |
| 216 | }; |
|
214 | }; | |
| 217 | } |
|
215 | } | |
| 218 | } |
|
216 | } | |
General Comments 0
You need to be logged in to leave comments.
Login now
