| @@ -35,12 +35,11 export class ContainerBuilder<S, U exten | |||
|
|
35 | 35 | if (!this._complete()) |
|
|
36 | 36 | throw new Error("The configuration didn't complete."); |
|
|
37 | 37 | |
|
|
38 |
const |
|
|
|
38 | const {remove, store} = this._lifetime(null); | |
|
|
39 | 39 | |
|
|
40 | const detach = isDestroyable(lifetime) ? () => lifetime.destroy() : () => void (0); | |
|
|
40 | const container = new Container(this._services, this._lifetimeManager, remove); | |
|
|
41 | 41 | |
|
|
42 | const container = new Container(this._services, this._lifetimeManager, detach); | |
|
|
43 | lifetime.store(container); | |
|
|
42 | store(container); | |
|
|
44 | 43 | |
|
|
45 | 44 | return container; |
|
|
46 | 45 | } |
| @@ -1,4 +1,3 | |||
|
|
1 | import { ActivationError } from "./ActivationError"; | |
|
|
2 | 1 |
|
|
|
3 | 2 | import { each, key } from "./traits"; |
|
|
4 | 3 | |
| @@ -44,10 +43,12 export class DescriptorImpl<S, T> implem | |||
|
|
44 | 43 | |
|
|
45 | 44 | activate(context: IActivationContext<S>): NonNullable<T> { |
|
|
46 | 45 | |
|
|
47 | if (this._lifetime.has()) | |
|
|
48 | return this._lifetime.get(); | |
|
|
46 | const { has, get, initialize, store } = this._lifetime(context); | |
|
|
49 | 47 | |
|
|
50 | this._lifetime.initialize(context); | |
|
|
48 | if (has()) | |
|
|
49 | return get(); | |
|
|
50 | ||
|
|
51 | initialize(); | |
|
|
51 | 52 | |
|
|
52 | 53 | if (this._overrides) |
|
|
53 | 54 | each(this._overrides, (v, k) => context.register(k, v)); |
| @@ -78,8 +79,11 export class DescriptorImpl<S, T> implem | |||
|
|
78 | 79 | {}; |
|
|
79 | 80 | |
|
|
80 | 81 | try { |
|
|
82 | // call the factory method | |
|
|
81 | 83 | const instance = (0,this._factory)(refs); |
|
|
82 | this._lifetime.store(instance, this._cleanup); | |
|
|
84 | ||
|
|
85 | // store the instance | |
|
|
86 | store(instance, this._cleanup); | |
|
|
83 | 87 | return instance; |
|
|
84 | 88 | } catch(err) { |
|
|
85 | 89 | context.fail(err); |
| @@ -1,5 +1,4 | |||
|
|
1 |
import { I |
|
|
|
2 | import { ActivationContext } from "./ActivationContext"; | |
|
|
1 | import { IDestroyable, ILifetimeContext, ILifetimeManager, ILifetimeSlot } from "./interfaces"; | |
|
|
3 | 2 | import { argumentNotNull, isDestroyable } from "./traits"; |
|
|
4 | 3 | |
|
|
5 | 4 | const safeCall = (item: () => void) => { |
| @@ -12,7 +11,7 const safeCall = (item: () => void) => { | |||
|
|
12 | 11 | |
|
|
13 | 12 | const noop = () => {}; |
|
|
14 | 13 | |
|
|
15 | const fail = (message: string) => { | |
|
|
14 | const fail = (message: string) => (): never => { | |
|
|
16 | 15 | throw new Error(message); |
|
|
17 | 16 | }; |
|
|
18 | 17 | |
| @@ -25,87 +24,81 const _emptySlot = Object.freeze({ | |||
|
|
25 | 24 | |
|
|
26 | 25 | store: noop, |
|
|
27 | 26 | |
|
|
28 | remove: noop | |
|
|
29 | }); | |
|
|
30 | ||
|
|
31 | const _unknonwSlot = Object.freeze({ | |
|
|
32 | has: () => false, | |
|
|
27 | remove: noop, | |
|
|
33 | 28 | |
|
|
34 | initialize: () => { | |
|
|
35 | throw new Error("Can't call initialize on the unknown lifetime object"); | |
|
|
36 | }, | |
|
|
37 | get: () => { | |
|
|
38 | throw new Error("The lifetime object isn't initialized"); | |
|
|
39 | }, | |
|
|
40 | store: () => { | |
|
|
41 | throw new Error("Can't store a value in the unknown lifetime object"); | |
|
|
42 | } | |
|
|
29 | cleanup: noop, | |
|
|
43 | 30 | }); |
|
|
44 | 31 | |
|
|
32 | const _destroy = (item: IDestroyable) => item.destroy(); | |
|
|
45 | 33 | |
|
|
46 |
const |
|
|
|
34 | const _makeCleanup = <T>(value: T, cleanup?: (item: T) => void) => | |
|
|
35 | cleanup ? () => cleanup(value) : | |
|
|
36 | isDestroyable(value) ? () => _destroy(value) : | |
|
|
37 | noop; | |
|
|
38 | ||
|
|
39 | const newSlot = <T>(put: (item: ILifetimeSlot<T>) => void, remove: () => void): ILifetimeSlot<T> => ({ | |
|
|
47 | 40 | has: () => false, |
|
|
48 | 41 | |
|
|
49 | get: () => { | |
|
|
50 | throw new Error("The value in this slot doesn't exist"); | |
|
|
51 | }, | |
|
|
42 | initialize: () => put(pendingSlot(put, remove)), | |
|
|
43 | ||
|
|
44 | get: fail("The slot doesn't hold a value"), | |
|
|
52 | 45 | |
|
|
53 | initialize: () => { | |
|
|
54 | throw new Error("Cyclic reference detected"); | |
|
|
55 | }, | |
|
|
46 | store: (value, cleanup) => put(valueSlot(value, cleanup, remove)), | |
|
|
56 | 47 | |
|
|
57 | store | |
|
|
48 | remove: noop, | |
|
|
49 | ||
|
|
50 | cleanup: noop, | |
|
|
58 | 51 | }); |
|
|
59 | 52 | |
|
|
60 | const valueSlot = <T>(value: T, cleanup: (item: T) => void) => ({ | |
|
|
53 | const pendingSlot = <T>(put: (item: ILifetimeSlot<T>) => void, remove: () => void): ILifetimeSlot<T> => ({ | |
|
|
54 | has: () => false, | |
|
|
55 | ||
|
|
56 | get: fail("The value in this slot doesn't exist"), | |
|
|
57 | ||
|
|
58 | initialize: fail("Cyclic reference detected"), | |
|
|
59 | ||
|
|
60 | store: (value, cleanup) => put(valueSlot(value, cleanup, remove)), | |
|
|
61 | ||
|
|
62 | remove, | |
|
|
63 | ||
|
|
64 | cleanup: noop | |
|
|
65 | }); | |
|
|
66 | ||
|
|
67 | const valueSlot = <T>(value: T, cleanup: ((item: T) => void) | undefined, remove: () => void) => ({ | |
|
|
61 | 68 | has: () => true, |
|
|
62 | 69 | |
|
|
63 | 70 | get: () => value, |
|
|
64 | 71 | |
|
|
65 | initialize: () => { | |
|
|
66 | throw new Error("The slot already has a value"); | |
|
|
67 | }, | |
|
|
72 | initialize: fail("The slot already has a value"), | |
|
|
73 | ||
|
|
74 | store: fail("The slot already has a value"), | |
|
|
68 | 75 | |
|
|
69 | store: () => { | |
|
|
70 | throw new Error("The slot already has a value"); | |
|
|
71 | } | |
|
|
76 | cleanup: _makeCleanup(value, cleanup), | |
|
|
77 | ||
|
|
78 | remove: remove | |
|
|
72 | 79 | }); |
|
|
73 | 80 | |
|
|
74 | const singletons: { [K: string]: unknown } = {}; | |
|
|
75 | ||
|
|
76 | 81 | export class LifetimeManager implements ILifetimeManager { |
|
|
77 | 82 | private _destroyed = false; |
|
|
78 | 83 | |
|
|
79 | 84 | private readonly _slots: Record<string, ILifetimeSlot<unknown>> = {}; |
|
|
80 | 85 | |
|
|
81 | slot<T>(cookie: string): ILifetimeSlot<T> { | |
|
|
86 | slot<T>(cookie: string | number): ILifetimeSlot<T> { | |
|
|
82 | 87 | if (cookie in this._slots) |
|
|
83 | 88 | return this._slots[cookie] as ILifetimeSlot<T>; |
|
|
84 | 89 | |
|
|
85 | const store = (item: T, cleanup?: (item: T) => void) => { | |
|
|
90 | return newSlot<T>(this._put(cookie), this._remove(cookie)); | |
|
|
91 | } | |
|
|
92 | ||
|
|
93 | private readonly _put = (id: string | number) => <T>(slot: ILifetimeSlot<T>) => { | |
|
|
86 | 94 |
|
|
|
87 |
|
|
|
|
88 | item, | |
|
|
89 | cleanup ?? isDestroyable(item) ? () => item.destroy() : noop | |
|
|
90 | ); | |
|
|
95 | this._slots[id] = slot as ILifetimeSlot<unknown>; | |
|
|
91 | 96 |
|
|
|
92 | 97 | |
|
|
93 | return { | |
|
|
94 | has: () => false, | |
|
|
95 | get: () => { | |
|
|
96 | throw new Error("The value isn't stored in this slot"); | |
|
|
97 | }, | |
|
|
98 | store, | |
|
|
99 | initialize: () => { | |
|
|
98 | private readonly _remove = (id: string | number) => () => { | |
|
|
100 | 99 |
|
|
|
101 | this._slots[cookie] = pendingSlot(store); | |
|
|
102 | } | |
|
|
100 | delete this._slots[id]; | |
|
|
103 | 101 |
|
|
|
104 | } | |
|
|
105 | ||
|
|
106 | remove(cookie: string) { | |
|
|
107 | delete this._slots[cookie]; | |
|
|
108 | } | |
|
|
109 | 102 | |
|
|
110 | 103 | private _assertNotDestroyed() { |
|
|
111 | 104 | if (this._destroyed) |
| @@ -116,42 +109,20 export class LifetimeManager implements | |||
|
|
116 | 109 | destroy() { |
|
|
117 | 110 | if (!this._destroyed) { |
|
|
118 | 111 | this._destroyed = true; |
|
|
119 | Object.values(this._slots).forEach(({clean}) => ) | |
|
|
112 | Object.values(this._slots).forEach(({ cleanup }) => safeCall(cleanup)); | |
|
|
120 | 113 | } |
|
|
121 | 114 | } |
|
|
122 | 115 | |
|
|
123 | 116 | } |
|
|
124 | 117 | |
|
|
125 |
export const emptyLifetime = <T>() |
|
|
|
126 | return _emptyLifetime; | |
|
|
127 | }; | |
|
|
118 | export const emptyLifetime = <T>() => () => _emptySlot as ILifetimeSlot<T>; | |
|
|
128 | 119 | |
|
|
129 | export const hierarchyLifetime = <T>(): ILifetime<T> => { | |
|
|
130 | // TODO: вот здесь ошибка, при первой активации сервиса будет получен и | |
|
|
131 | // привязан lifetime из дочернего контейнера, при активации через второй | |
|
|
132 | // дочерний контейнера это приведет к ошибке, точнее будет взят экземпляр | |
|
|
133 | // из первого контейнера. | |
|
|
134 | let _lifetime: ILifetime<T> = _unknownLifetime; | |
|
|
135 | return { | |
|
|
136 | initialize(context: ILifetimeContext) { | |
|
|
137 | if (_lifetime !== _unknownLifetime) | |
|
|
138 | throw new Error("Cyclic reference activation detected"); | |
|
|
120 | let nextId = 1; | |
|
|
139 | 121 | |
|
|
140 | _lifetime = context.createContainerLifetime<T>(); | |
|
|
141 | }, | |
|
|
142 | get() { | |
|
|
143 | return _lifetime.get(); | |
|
|
144 | }, | |
|
|
145 | has() { | |
|
|
146 | return _lifetime.has(); | |
|
|
147 | }, | |
|
|
148 | store(item: NonNullable<T>, cleanup?: (item: NonNullable<T>) => void) { | |
|
|
149 | return _lifetime.store(item, cleanup); | |
|
|
150 | }, | |
|
|
151 | toString() { | |
|
|
152 | return `[object HierarchyLifetime, has=${String(this.has())}]`; | |
|
|
153 | } | |
|
|
154 | }; | |
|
|
122 | export const hierarchyLifetime = <T>() => { | |
|
|
123 | const slotId = nextId++; | |
|
|
124 | return (context: ILifetimeContext) => | |
|
|
125 | context.containerSlot<T>(slotId); | |
|
|
155 | 126 | }; |
|
|
156 | 127 | |
|
|
157 | 128 | /** |
| @@ -163,28 +134,14 export const hierarchyLifetime = <T>(): | |||
|
|
163 | 134 | * |
|
|
164 | 135 | * @returns The instance of the lifetime. |
|
|
165 | 136 | */ |
|
|
166 |
export const contextLifetime = <T>() |
|
|
|
167 | let _lifetime: ILifetime<T> = _unknownLifetime; | |
|
|
168 | return { | |
|
|
169 |
|
|
|
|
170 | if (_lifetime !== _unknownLifetime) | |
|
|
171 | throw new Error("Cyclic reference detected"); | |
|
|
172 | _lifetime = context.createLifetime(); | |
|
|
173 | }, | |
|
|
174 | get() { | |
|
|
175 | return _lifetime.get(); | |
|
|
176 | }, | |
|
|
177 | has() { | |
|
|
178 | return _lifetime.has(); | |
|
|
179 | }, | |
|
|
180 | store(item: NonNullable<T>) { | |
|
|
181 | _lifetime.store(item); | |
|
|
182 | }, | |
|
|
183 | toString() { | |
|
|
184 | return `[object ContextLifetime, has=${String(this.has())}]`; | |
|
|
185 | } | |
|
|
137 | export const contextLifetime = <T>() => { | |
|
|
138 | const slotId = nextId++; | |
|
|
139 | ||
|
|
140 | return (context: ILifetimeContext) => | |
|
|
141 | context.contextSlot<T>(slotId); | |
|
|
186 | 142 | }; |
|
|
187 | }; | |
|
|
143 | ||
|
|
144 | const singletons = new LifetimeManager(); | |
|
|
188 | 145 | |
|
|
189 | 146 | /** |
|
|
190 | 147 | * Creates the lifetime for the service which will allow existence only one |
| @@ -196,31 +153,10 export const contextLifetime = <T>(): IL | |||
|
|
196 | 153 | * fully qualified class name |
|
|
197 | 154 | * @returns The lifetime instance |
|
|
198 | 155 | */ |
|
|
199 |
export const singletonLifetime = <T>(typeId: string) |
|
|
|
156 | export const singletonLifetime = <T>(typeId: string) => { | |
|
|
200 | 157 | argumentNotNull(typeId, "typeId"); |
|
|
201 | let pending = false; | |
|
|
202 | return { | |
|
|
203 | has() { | |
|
|
204 | return typeId in singletons; | |
|
|
205 | }, | |
|
|
206 | get() { | |
|
|
207 | if (!this.has()) | |
|
|
208 | throw new Error(`The instance ${typeId} doesn't exists`); | |
|
|
209 | return singletons[typeId] as NonNullable<T>; | |
|
|
210 | }, | |
|
|
211 | initialize() { | |
|
|
212 | if (pending) | |
|
|
213 | throw new Error("Cyclic reference detected"); | |
|
|
214 | pending = true; | |
|
|
215 | }, | |
|
|
216 | store(item: NonNullable<T>) { | |
|
|
217 | singletons[typeId] = item; | |
|
|
218 | pending = false; | |
|
|
219 | }, | |
|
|
220 | toString() { | |
|
|
221 | return `[object SingletonLifetime, has=${String(this.has())}, typeId=${typeId}]`; | |
|
|
222 | } | |
|
|
223 | }; | |
|
|
158 | ||
|
|
159 | return () => singletons.slot<T>(typeId); | |
|
|
224 | 160 | }; |
|
|
225 | 161 | |
|
|
226 | 162 | /** Creates a lifetime bound to the specified container. Using this lifetime |
| @@ -228,25 +164,7 export const singletonLifetime = <T>(typ | |||
|
|
228 | 164 | * |
|
|
229 | 165 | * @param container The container which will manage the lifetime for the service |
|
|
230 | 166 | */ |
|
|
231 |
export const containerLifetime = <T>( |
|
|
|
232 | let _lifetime: ILifetime<T> = _unknownLifetime; | |
|
|
233 | return { | |
|
|
234 | initialize() { | |
|
|
235 | if (_lifetime !== _unknownLifetime) | |
|
|
236 | throw new Error("Cyclic reference detected"); | |
|
|
237 | _lifetime = container.createLifetime(); | |
|
|
238 | }, | |
|
|
239 | get() { | |
|
|
240 | return _lifetime.get(); | |
|
|
241 | }, | |
|
|
242 | has() { | |
|
|
243 | return _lifetime.has(); | |
|
|
244 | }, | |
|
|
245 | store(item: NonNullable<T>, cleanup?: (item: NonNullable<T>) => void) { | |
|
|
246 | _lifetime.store(item, cleanup); | |
|
|
247 | }, | |
|
|
248 | toString() { | |
|
|
249 | return `[object ContainerLifetime, has=${String(_lifetime.has())}]`; | |
|
|
250 | } | |
|
|
167 | export const containerLifetime = <T>(manager: ILifetimeManager) => { | |
|
|
168 | const slotId = nextId++; | |
|
|
169 | return () => manager.slot<T>(slotId); | |
|
|
251 | 170 | }; |
|
|
252 | }; | |
| @@ -170,9 +170,9 export interface Descriptor<S, T> { | |||
|
|
170 | 170 | |
|
|
171 | 171 | /** The context used to initialize lifetime instance {@linkcode ILifetime} */ |
|
|
172 | 172 | export interface ILifetimeContext { |
|
|
173 | contextSlot<T>(slotId: string): ILifetimeSlot<T>; | |
|
|
173 | contextSlot<T>(slotId: string | number): ILifetimeSlot<T>; | |
|
|
174 | 174 | |
|
|
175 | containerSlot<T>(slotId: string): ILifetimeSlot<T>; | |
|
|
175 | containerSlot<T>(slotId: string | number): ILifetimeSlot<T>; | |
|
|
176 | 176 | |
|
|
177 | 177 | } |
|
|
178 | 178 | |
| @@ -240,31 +240,24 export type ActivationType = "singleton" | |||
|
|
240 | 240 | * Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет |
|
|
241 | 241 | * свой собственный объект `ILifetime`, который создается при первой активации |
|
|
242 | 242 | */ |
|
|
243 | export interface ILifetime<T> { | |
|
|
244 | /** Проверяет, что уже создан экземпляр объекта */ | |
|
|
245 | has(): boolean; | |
|
|
246 | ||
|
|
247 | get(): NonNullable<T>; | |
|
|
248 | ||
|
|
249 | initialize(context: ILifetimeContext): void; | |
|
|
250 | ||
|
|
251 | store(item: NonNullable<T>, cleanup?: (item: NonNullable<T>) => void): void; | |
|
|
252 | ||
|
|
253 | toString(): string; | |
|
|
254 | } | |
|
|
243 | export type ILifetime<T> = (context: ILifetimeContext) => ILifetimeSlot<NonNullable<T>>; | |
|
|
255 | 244 | |
|
|
256 | 245 | export interface ILifetimeSlot<T> { |
|
|
257 | has: () => boolean; | |
|
|
246 | readonly has: () => boolean; | |
|
|
258 | 247 | |
|
|
259 | get: () => T; | |
|
|
248 | readonly get: () => T; | |
|
|
249 | ||
|
|
250 | readonly initialize: () => void; | |
|
|
260 | 251 | |
|
|
261 | initialize: () => void; | |
|
|
252 | readonly store: (item: T, cleanup?: (item: T) => void) => void; | |
|
|
262 | 253 | |
|
|
263 | store(item: T, cleanup?: (item: T) => void): void; | |
|
|
254 | readonly remove: () => void; | |
|
|
255 | ||
|
|
256 | readonly cleanup: () => void; | |
|
|
264 | 257 |
} |
|
|
265 | 258 | |
|
|
266 | 259 | export interface ILifetimeManager extends IDestroyable { |
|
|
267 |
|
|
|
|
260 | slot<T>(id: string | number): ILifetimeSlot<T>; | |
|
|
268 | 261 | } |
|
|
269 | 262 | |
|
|
270 | 263 | export type ExtractRequired<T, K extends keyof T = keyof T> = { [p in K as (undefined extends T[p] ? never : p)]-?: T[p] }; |
General Comments 0
You need to be logged in to leave comments.
Login now
