##// END OF EJS Templates
intoruced initial work on ILifetimeSlot
cin -
r10:99a53d63fd6f default
parent child
Show More
@@ -1,4 +1,4
1 import { IActivationContext, ILifetime, ILifetimeManager } from "./interfaces";
1 import { IActivationContext, ILifetime, ILifetimeContext, ILifetimeManager, ILifetimeSlot } from "./interfaces";
2 import { ActivationContext } from "./ActivationContext";
2 import { ActivationContext } from "./ActivationContext";
3 import { argumentNotNull, isDestroyable } from "./traits";
3 import { argumentNotNull, isDestroyable } from "./traits";
4
4
@@ -10,44 +10,29 const safeCall = (item: () => void) => {
10 }
10 }
11 };
11 };
12
12
13 const _emptyLifetime = Object.freeze({
13 const _emptySlot = Object.freeze({
14 has() {
14 has: () => false,
15 return false;
16 },
17
15
18 initialize() {
16 initialize: () => void (0),
19 },
20
17
21 get() {
18 get: () => {
22 throw new Error("The specified item isn't registered with this lifetime manager");
19 throw new Error("The specified item isn't registered with this lifetime manager");
23 },
20 },
24
21
25 store() {
22 store: () => void (0)
26 // does nothing
27 },
28
29 toString() {
30 return `[object EmptyLifetime]`;
31 }
32
33 });
23 });
34
24
35 const _unknownLifetime = Object.freeze({
25 const _unknonwSlot = Object.freeze({
36 has() {
26 has: () => false,
37 return false;
27
38 },
28 initialize: () => {
39 initialize() {
40 throw new Error("Can't call initialize on the unknown lifetime object");
29 throw new Error("Can't call initialize on the unknown lifetime object");
41 },
30 },
42 get() {
31 get: () => {
43 throw new Error("The lifetime object isn't initialized");
32 throw new Error("The lifetime object isn't initialized");
44 },
33 },
45 store() {
34 store: () => {
46 throw new Error("Can't store a value in the unknown lifetime object");
35 throw new Error("Can't store a value in the unknown lifetime object");
47 },
48
49 toString() {
50 return `[object UnknownLifetime]`;
51 }
36 }
52 });
37 });
53
38
@@ -55,56 +40,73 let nextId = 0;
55
40
56 const singletons: { [K: string]: unknown } = {};
41 const singletons: { [K: string]: unknown } = {};
57
42
43
44 const pendingSlot = <T>(store: (item: T) => void) => ({
45 has: () => false,
46
47 get: () => {
48 throw new Error("The value in this slot doesn't exist");
49 },
50
51 initialize: () => {
52 throw new Error("Cyclic reference detected");
53 },
54
55 store
56 });
57
58 const noop = () => void(0);
59
60 const valueSlot = <T>(value: T, cleanup: (item: T) => void) => ({
61 has: () => true,
62
63 get: () => value,
64
65 initialize: () => {
66 throw new Error("The slot already has a value");
67 },
68
69 store: () => {
70 throw new Error("The slot already has a value");
71 }
72 });
73
58 export class LifetimeManager implements ILifetimeManager {
74 export class LifetimeManager implements ILifetimeManager {
59 private _cleanup: (() => void)[] = [];
60 private readonly _cache: { [K: string]: unknown } = {};
61 private _destroyed = false;
75 private _destroyed = false;
62
76
63 private readonly _pending: { [K: string]: unknown } = {};
77 private readonly _slots: Record<string, ILifetimeSlot<unknown>> = {};
64
65 create<T>(): ILifetime<T> & { remove(): void; } {
66 const id = ++nextId;
67 return {
68 has: () => id in this._cache,
69
78
70 get: () => {
79 slot<T>(cookie: string): ILifetimeSlot<T> {
71 const t = this._cache[id] as T;
80 if (cookie in this._slots)
72 if (t === undefined || t === null)
81 return this._slots[cookie] as ILifetimeSlot<T>;
73 throw new Error(`The item with with the key ${id} isn't found`);
74 return t;
75 },
76
77 initialize: () => {
78 if (this._pending[id])
79 throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`);
80 this._pending[id] = true;
81 },
82
82
83 store: (item: NonNullable<T>, cleanup?: (item: NonNullable<T>) => void) => {
83 const store = (item: T, cleanup?: (item: T) => void) => {
84 if (id in this._cache)
84 this._assertNotDestroyed();
85 throw new Error(`The item with with the key ${id} already registered with this lifetime manager`);
85 this._slots[cookie] = valueSlot(
86 if (this._destroyed)
86 item,
87 throw new Error("Lifetime manager is destroyed");
87 cleanup ?? isDestroyable(item) ? () => item.destroy() : noop
88
88 );
89 delete this._pending[id];
89 };
90
91 this._cache[id] = item;
92
90
93 if (cleanup) {
91 return {
94 this._cleanup.push(() => cleanup(item));
92 has: () => false,
95 } else if (isDestroyable(item)) {
93 get: () => {
96 this._cleanup.push(() => item.destroy());
94 throw new Error("The value isn't stored in this slot");
97 }
98 },
95 },
99
96 store,
100 remove: () => {
97 initialize: () => {
101 if (this._pending[id])
98 this._assertNotDestroyed();
102 throw new Error(`The item '${id}' can't be removed before it has been stored`);
99 this._slots[cookie] = pendingSlot(store);
103 delete this._cache[id];
104 }
100 }
105 };
101 };
106 }
102 }
107
103
104 private _assertNotDestroyed() {
105 if (this._destroyed)
106 throw new Error("The lifetime manager is destroyed");
107
108 }
109
108 destroy() {
110 destroy() {
109 if (!this._destroyed) {
111 if (!this._destroyed) {
110 this._destroyed = true;
112 this._destroyed = true;
@@ -122,7 +124,7 export const emptyLifetime = <T>(): ILif
122 export const hierarchyLifetime = <T>(): ILifetime<T> => {
124 export const hierarchyLifetime = <T>(): ILifetime<T> => {
123 let _lifetime: ILifetime<T> = _unknownLifetime;
125 let _lifetime: ILifetime<T> = _unknownLifetime;
124 return {
126 return {
125 initialize(context: IActivationContext<object>) {
127 initialize(context: ILifetimeContext) {
126 if (_lifetime !== _unknownLifetime)
128 if (_lifetime !== _unknownLifetime)
127 throw new Error("Cyclic reference activation detected");
129 throw new Error("Cyclic reference activation detected");
128
130
@@ -143,10 +145,19 export const hierarchyLifetime = <T>():
143 };
145 };
144 };
146 };
145
147
148 /**
149 * Creates a lifetime instance bound to the current activation context. This
150 * lifetime will store the service instance per activation context. Every
151 * top level service resolution will create a new activation context. This
152 * context is propagated to subsequent service resolution thus all services
153 * with context lifetime will be shared among their consumers.
154 *
155 * @returns The instance of the lifetime.
156 */
146 export const contextLifetime = <T>(): ILifetime<T> => {
157 export const contextLifetime = <T>(): ILifetime<T> => {
147 let _lifetime: ILifetime<T> = _unknownLifetime;
158 let _lifetime: ILifetime<T> = _unknownLifetime;
148 return {
159 return {
149 initialize(context: ActivationContext<object>) {
160 initialize(context: ILifetimeContext) {
150 if (_lifetime !== _unknownLifetime)
161 if (_lifetime !== _unknownLifetime)
151 throw new Error("Cyclic reference detected");
162 throw new Error("Cyclic reference detected");
152 _lifetime = context.createLifetime();
163 _lifetime = context.createLifetime();
@@ -166,6 +177,16 export const contextLifetime = <T>(): IL
166 };
177 };
167 };
178 };
168
179
180 /**
181 * Creates the lifetime for the service which will allow existence only one
182 * instance with the specified {@linkcode typeId}. If there will be created
183 * several lifetime instances with same `typeId` in the runtime, they will
184 * share the same service instance.
185 *
186 * @param typeId The identified for the global instance, usually this is a
187 * fully qualified class name
188 * @returns The lifetime instance
189 */
169 export const singletonLifetime = <T>(typeId: string): ILifetime<T> => {
190 export const singletonLifetime = <T>(typeId: string): ILifetime<T> => {
170 argumentNotNull(typeId, "typeId");
191 argumentNotNull(typeId, "typeId");
171 let pending = false;
192 let pending = false;
@@ -193,6 +214,11 export const singletonLifetime = <T>(typ
193 };
214 };
194 };
215 };
195
216
217 /** Creates a lifetime bound to the specified container. Using this lifetime
218 * will create a single service instance per the specified container.
219 *
220 * @param container The container which will manage the lifetime for the service
221 */
196 export const containerLifetime = <T>(container: { createLifetime<X>(): ILifetime<X> }) => {
222 export const containerLifetime = <T>(container: { createLifetime<X>(): ILifetime<X> }) => {
197 let _lifetime: ILifetime<T> = _unknownLifetime;
223 let _lifetime: ILifetime<T> = _unknownLifetime;
198 return {
224 return {
@@ -207,8 +233,8 export const containerLifetime = <T>(con
207 has() {
233 has() {
208 return _lifetime.has();
234 return _lifetime.has();
209 },
235 },
210 store(item: NonNullable<T>) {
236 store(item: NonNullable<T>, cleanup?: (item: NonNullable<T>) => void) {
211 _lifetime.store(item);
237 _lifetime.store(item, cleanup);
212 },
238 },
213 toString() {
239 toString() {
214 return `[object ContainerLifetime, has=${String(_lifetime.has())}]`;
240 return `[object ContainerLifetime, has=${String(_lifetime.has())}]`;
@@ -168,11 +168,16 export interface Descriptor<S, T> {
168 activate(context: IActivationContext<S>): NonNullable<T>;
168 activate(context: IActivationContext<S>): NonNullable<T>;
169 }
169 }
170
170
171 export interface IActivationContext<S> extends ServiceLocator<S> {
171 /** The context used to initialize lifetime instance {@linkcode ILifetime} */
172 export interface ILifetimeContext {
172 createLifetime<T>(): ILifetime<T>;
173 createLifetime<T>(): ILifetime<T>;
173
174
174 createContainerLifetime<T>(): ILifetime<T>;
175 createContainerLifetime<T>(): ILifetime<T>;
175
176
177 }
178
179 export interface IActivationContext<S> extends ILifetimeContext, ServiceLocator<S> {
180
176 register<K extends keyof S>(name: K, service: DescriptorMap<S>[K]): void;
181 register<K extends keyof S>(name: K, service: DescriptorMap<S>[K]): void;
177 }
182 }
178
183
@@ -242,13 +247,23 export interface ILifetime<T> {
242
247
243 get(): NonNullable<T>;
248 get(): NonNullable<T>;
244
249
245 initialize(context: IActivationContext<unknown>): void;
250 initialize(context: ILifetimeContext): void;
246
251
247 store(item: NonNullable<T>, cleanup?: (item: NonNullable<T>) => void): void;
252 store(item: NonNullable<T>, cleanup?: (item: NonNullable<T>) => void): void;
248
253
249 toString(): string;
254 toString(): string;
250 }
255 }
251
256
257 export interface ILifetimeSlot<T> {
258 has: () => boolean;
259
260 get: () => T;
261
262 initialize: () => void;
263
264 store(item: T, cleanup?: (item: T) => void): void;
265 }
266
252 export interface ILifetimeManager extends IDestroyable {
267 export interface ILifetimeManager extends IDestroyable {
253 create<T>(): ILifetime<T>;
268 create<T>(): ILifetime<T>;
254 }
269 }
General Comments 0
You need to be logged in to leave comments. Login now