##// 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 2 import { ActivationContext } from "./ActivationContext";
3 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({
14 has() {
15 return false;
16 },
13 const _emptySlot = Object.freeze({
14 has: () => false,
17 15
18 initialize() {
19 },
16 initialize: () => void (0),
20 17
21 get() {
18 get: () => {
22 19 throw new Error("The specified item isn't registered with this lifetime manager");
23 20 },
24 21
25 store() {
26 // does nothing
27 },
28
29 toString() {
30 return `[object EmptyLifetime]`;
31 }
32
22 store: () => void (0)
33 23 });
34 24
35 const _unknownLifetime = Object.freeze({
36 has() {
37 return false;
38 },
39 initialize() {
25 const _unknonwSlot = Object.freeze({
26 has: () => false,
27
28 initialize: () => {
40 29 throw new Error("Can't call initialize on the unknown lifetime object");
41 30 },
42 get() {
31 get: () => {
43 32 throw new Error("The lifetime object isn't initialized");
44 33 },
45 store() {
34 store: () => {
46 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 41 const singletons: { [K: string]: unknown } = {};
57 42
58 export class LifetimeManager implements ILifetimeManager {
59 private _cleanup: (() => void)[] = [];
60 private readonly _cache: { [K: string]: unknown } = {};
61 private _destroyed = false;
62 43
63 private readonly _pending: { [K: string]: unknown } = {};
64
65 create<T>(): ILifetime<T> & { remove(): void; } {
66 const id = ++nextId;
67 return {
68 has: () => id in this._cache,
44 const pendingSlot = <T>(store: (item: T) => void) => ({
45 has: () => false,
69 46
70 47 get: () => {
71 const t = this._cache[id] as T;
72 if (t === undefined || t === null)
73 throw new Error(`The item with with the key ${id} isn't found`);
74 return t;
48 throw new Error("The value in this slot doesn't exist");
75 49 },
76 50
77 51 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;
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");
81 67 },
82 68
83 store: (item: NonNullable<T>, cleanup?: (item: NonNullable<T>) => void) => {
84 if (id in this._cache)
85 throw new Error(`The item with with the key ${id} already registered with this lifetime manager`);
86 if (this._destroyed)
87 throw new Error("Lifetime manager is destroyed");
69 store: () => {
70 throw new Error("The slot already has a value");
71 }
72 });
88 73
89 delete this._pending[id];
74 export class LifetimeManager implements ILifetimeManager {
75 private _destroyed = false;
90 76
91 this._cache[id] = item;
77 private readonly _slots: Record<string, ILifetimeSlot<unknown>> = {};
78
79 slot<T>(cookie: string): ILifetimeSlot<T> {
80 if (cookie in this._slots)
81 return this._slots[cookie] as ILifetimeSlot<T>;
92 82
93 if (cleanup) {
94 this._cleanup.push(() => cleanup(item));
95 } else if (isDestroyable(item)) {
96 this._cleanup.push(() => item.destroy());
97 }
83 const store = (item: T, cleanup?: (item: T) => void) => {
84 this._assertNotDestroyed();
85 this._slots[cookie] = valueSlot(
86 item,
87 cleanup ?? isDestroyable(item) ? () => item.destroy() : noop
88 );
89 };
90
91 return {
92 has: () => false,
93 get: () => {
94 throw new Error("The value isn't stored in this slot");
98 95 },
99
100 remove: () => {
101 if (this._pending[id])
102 throw new Error(`The item '${id}' can't be removed before it has been stored`);
103 delete this._cache[id];
96 store,
97 initialize: () => {
98 this._assertNotDestroyed();
99 this._slots[cookie] = pendingSlot(store);
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 110 destroy() {
109 111 if (!this._destroyed) {
110 112 this._destroyed = true;
@@ -122,7 +124,7 export const emptyLifetime = <T>(): ILif
122 124 export const hierarchyLifetime = <T>(): ILifetime<T> => {
123 125 let _lifetime: ILifetime<T> = _unknownLifetime;
124 126 return {
125 initialize(context: IActivationContext<object>) {
127 initialize(context: ILifetimeContext) {
126 128 if (_lifetime !== _unknownLifetime)
127 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 157 export const contextLifetime = <T>(): ILifetime<T> => {
147 158 let _lifetime: ILifetime<T> = _unknownLifetime;
148 159 return {
149 initialize(context: ActivationContext<object>) {
160 initialize(context: ILifetimeContext) {
150 161 if (_lifetime !== _unknownLifetime)
151 162 throw new Error("Cyclic reference detected");
152 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 190 export const singletonLifetime = <T>(typeId: string): ILifetime<T> => {
170 191 argumentNotNull(typeId, "typeId");
171 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 222 export const containerLifetime = <T>(container: { createLifetime<X>(): ILifetime<X> }) => {
197 223 let _lifetime: ILifetime<T> = _unknownLifetime;
198 224 return {
@@ -207,8 +233,8 export const containerLifetime = <T>(con
207 233 has() {
208 234 return _lifetime.has();
209 235 },
210 store(item: NonNullable<T>) {
211 _lifetime.store(item);
236 store(item: NonNullable<T>, cleanup?: (item: NonNullable<T>) => void) {
237 _lifetime.store(item, cleanup);
212 238 },
213 239 toString() {
214 240 return `[object ContainerLifetime, has=${String(_lifetime.has())}]`;
@@ -168,11 +168,16 export interface Descriptor<S, T> {
168 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 173 createLifetime<T>(): ILifetime<T>;
173 174
174 175 createContainerLifetime<T>(): ILifetime<T>;
175 176
177 }
178
179 export interface IActivationContext<S> extends ILifetimeContext, ServiceLocator<S> {
180
176 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 248 get(): NonNullable<T>;
244 249
245 initialize(context: IActivationContext<unknown>): void;
250 initialize(context: ILifetimeContext): void;
246 251
247 252 store(item: NonNullable<T>, cleanup?: (item: NonNullable<T>) => void): void;
248 253
249 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 267 export interface ILifetimeManager extends IDestroyable {
253 268 create<T>(): ILifetime<T>;
254 269 }
General Comments 0
You need to be logged in to leave comments. Login now