##// END OF EJS Templates
WIP lifetime services
cin -
r12:94f233c23aa4 default
parent child
Show More
@@ -35,12 +35,11 export class ContainerBuilder<S, U exten
35 if (!this._complete())
35 if (!this._complete())
36 throw new Error("The configuration didn't complete.");
36 throw new Error("The configuration didn't complete.");
37
37
38 const lifetime = this._lifetime;
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);
42 store(container);
43 lifetime.store(container);
44
43
45 return container;
44 return container;
46 }
45 }
@@ -1,4 +1,3
1 import { ActivationError } from "./ActivationError";
2 import { Descriptor, ILifetime, DepsMap, IActivationContext, DescriptorMap } from "./interfaces";
1 import { Descriptor, ILifetime, DepsMap, IActivationContext, DescriptorMap } from "./interfaces";
3 import { each, key } from "./traits";
2 import { each, key } from "./traits";
4
3
@@ -44,10 +43,12 export class DescriptorImpl<S, T> implem
44
43
45 activate(context: IActivationContext<S>): NonNullable<T> {
44 activate(context: IActivationContext<S>): NonNullable<T> {
46
45
47 if (this._lifetime.has())
46 const { has, get, initialize, store } = this._lifetime(context);
48 return this._lifetime.get();
49
47
50 this._lifetime.initialize(context);
48 if (has())
49 return get();
50
51 initialize();
51
52
52 if (this._overrides)
53 if (this._overrides)
53 each(this._overrides, (v, k) => context.register(k, v));
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 try {
81 try {
82 // call the factory method
81 const instance = (0,this._factory)(refs);
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 return instance;
87 return instance;
84 } catch(err) {
88 } catch(err) {
85 context.fail(err);
89 context.fail(err);
@@ -1,5 +1,4
1 import { IActivationContext, ILifetime, ILifetimeContext, ILifetimeManager, ILifetimeSlot } from "./interfaces";
1 import { IDestroyable, ILifetimeContext, ILifetimeManager, ILifetimeSlot } from "./interfaces";
2 import { ActivationContext } from "./ActivationContext";
3 import { argumentNotNull, isDestroyable } from "./traits";
2 import { argumentNotNull, isDestroyable } from "./traits";
4
3
5 const safeCall = (item: () => void) => {
4 const safeCall = (item: () => void) => {
@@ -10,9 +9,9 const safeCall = (item: () => void) => {
10 }
9 }
11 };
10 };
12
11
13 const noop = () => {};
12 const noop = () => { };
14
13
15 const fail = (message: string) => {
14 const fail = (message: string) => (): never => {
16 throw new Error(message);
15 throw new Error(message);
17 };
16 };
18
17
@@ -25,87 +24,81 const _emptySlot = Object.freeze({
25
24
26 store: noop,
25 store: noop,
27
26
28 remove: noop
27 remove: noop,
29 });
30
31 const _unknonwSlot = Object.freeze({
32 has: () => false,
33
28
34 initialize: () => {
29 cleanup: noop,
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 }
43 });
30 });
44
31
32 const _destroy = (item: IDestroyable) => item.destroy();
45
33
46 const pendingSlot = <T>(store: (item: T) => void) => ({
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 has: () => false,
40 has: () => false,
48
41
49 get: () => {
42 initialize: () => put(pendingSlot(put, remove)),
50 throw new Error("The value in this slot doesn't exist");
43
51 },
44 get: fail("The slot doesn't hold a value"),
52
45
53 initialize: () => {
46 store: (value, cleanup) => put(valueSlot(value, cleanup, remove)),
54 throw new Error("Cyclic reference detected");
55 },
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 has: () => true,
68 has: () => true,
62
69
63 get: () => value,
70 get: () => value,
64
71
65 initialize: () => {
72 initialize: fail("The slot already has a value"),
66 throw new Error("The slot already has a value");
73
67 },
74 store: fail("The slot already has a value"),
68
75
69 store: () => {
76 cleanup: _makeCleanup(value, cleanup),
70 throw new Error("The slot already has a value");
77
71 }
78 remove: remove
72 });
79 });
73
80
74 const singletons: { [K: string]: unknown } = {};
75
76 export class LifetimeManager implements ILifetimeManager {
81 export class LifetimeManager implements ILifetimeManager {
77 private _destroyed = false;
82 private _destroyed = false;
78
83
79 private readonly _slots: Record<string, ILifetimeSlot<unknown>> = {};
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 if (cookie in this._slots)
87 if (cookie in this._slots)
83 return this._slots[cookie] as ILifetimeSlot<T>;
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));
86 this._assertNotDestroyed();
87 this._slots[cookie] = valueSlot(
88 item,
89 cleanup ?? isDestroyable(item) ? () => item.destroy() : noop
90 );
91 };
92
93 return {
94 has: () => false,
95 get: () => {
96 throw new Error("The value isn't stored in this slot");
97 },
98 store,
99 initialize: () => {
100 this._assertNotDestroyed();
101 this._slots[cookie] = pendingSlot(store);
102 }
103 };
104 }
91 }
105
92
106 remove(cookie: string) {
93 private readonly _put = (id: string | number) => <T>(slot: ILifetimeSlot<T>) => {
107 delete this._slots[cookie];
94 this._assertNotDestroyed();
108 }
95 this._slots[id] = slot as ILifetimeSlot<unknown>;
96 };
97
98 private readonly _remove = (id: string | number) => () => {
99 this._assertNotDestroyed();
100 delete this._slots[id];
101 };
109
102
110 private _assertNotDestroyed() {
103 private _assertNotDestroyed() {
111 if (this._destroyed)
104 if (this._destroyed)
@@ -116,42 +109,20 export class LifetimeManager implements
116 destroy() {
109 destroy() {
117 if (!this._destroyed) {
110 if (!this._destroyed) {
118 this._destroyed = true;
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>(): ILifetime<T> => {
118 export const emptyLifetime = <T>() => () => _emptySlot as ILifetimeSlot<T>;
126 return _emptyLifetime;
127 };
128
119
129 export const hierarchyLifetime = <T>(): ILifetime<T> => {
120 let nextId = 1;
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");
139
121
140 _lifetime = context.createContainerLifetime<T>();
122 export const hierarchyLifetime = <T>() => {
141 },
123 const slotId = nextId++;
142 get() {
124 return (context: ILifetimeContext) =>
143 return _lifetime.get();
125 context.containerSlot<T>(slotId);
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 };
155 };
126 };
156
127
157 /**
128 /**
@@ -163,29 +134,15 export const hierarchyLifetime = <T>():
163 *
134 *
164 * @returns The instance of the lifetime.
135 * @returns The instance of the lifetime.
165 */
136 */
166 export const contextLifetime = <T>(): ILifetime<T> => {
137 export const contextLifetime = <T>() => {
167 let _lifetime: ILifetime<T> = _unknownLifetime;
138 const slotId = nextId++;
168 return {
139
169 initialize(context: ILifetimeContext) {
140 return (context: ILifetimeContext) =>
170 if (_lifetime !== _unknownLifetime)
141 context.contextSlot<T>(slotId);
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 }
186 };
187 };
142 };
188
143
144 const singletons = new LifetimeManager();
145
189 /**
146 /**
190 * Creates the lifetime for the service which will allow existence only one
147 * Creates the lifetime for the service which will allow existence only one
191 * instance with the specified {@linkcode typeId}. If there will be created
148 * instance with the specified {@linkcode typeId}. If there will be created
@@ -196,31 +153,10 export const contextLifetime = <T>(): IL
196 * fully qualified class name
153 * fully qualified class name
197 * @returns The lifetime instance
154 * @returns The lifetime instance
198 */
155 */
199 export const singletonLifetime = <T>(typeId: string): ILifetime<T> => {
156 export const singletonLifetime = <T>(typeId: string) => {
200 argumentNotNull(typeId, "typeId");
157 argumentNotNull(typeId, "typeId");
201 let pending = false;
158
202 return {
159 return () => singletons.slot<T>(typeId);
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 };
224 };
160 };
225
161
226 /** Creates a lifetime bound to the specified container. Using this lifetime
162 /** Creates a lifetime bound to the specified container. Using this lifetime
@@ -228,25 +164,7 export const singletonLifetime = <T>(typ
228 *
164 *
229 * @param container The container which will manage the lifetime for the service
165 * @param container The container which will manage the lifetime for the service
230 */
166 */
231 export const containerLifetime = <T>(container: { createLifetime<X>(): ILifetime<X> }) => {
167 export const containerLifetime = <T>(manager: ILifetimeManager) => {
232 let _lifetime: ILifetime<T> = _unknownLifetime;
168 const slotId = nextId++;
233 return {
169 return () => manager.slot<T>(slotId);
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 }
251 };
252 };
170 };
@@ -170,9 +170,9 export interface Descriptor<S, T> {
170
170
171 /** The context used to initialize lifetime instance {@linkcode ILifetime} */
171 /** The context used to initialize lifetime instance {@linkcode ILifetime} */
172 export interface ILifetimeContext {
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
@@ -210,8 +210,8 export type ContainerProvided<S extends
210 */
210 */
211 export type ContainerServices<S extends ContainerServicesConstraint<S>> = {
211 export type ContainerServices<S extends ContainerServicesConstraint<S>> = {
212 [k in keyof S | ContainerKeys]:
212 [k in keyof S | ContainerKeys]:
213 k extends ContainerKeys ? ContainerProvided<S>[k] :
213 k extends ContainerKeys ? ContainerProvided<S>[k] :
214 k extends keyof S ? S[k] : never
214 k extends keyof S ? S[k] : never
215 };
215 };
216
216
217
217
@@ -240,31 +240,24 export type ActivationType = "singleton"
240 * Π˜Π½Ρ‚Π΅Ρ€Ρ„Π΅ΠΉΡ для управлСния Тизнью экзСмпляра ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π°. КаТдая рСгистрация ΠΈΠΌΠ΅Π΅Ρ‚
240 * Π˜Π½Ρ‚Π΅Ρ€Ρ„Π΅ΠΉΡ для управлСния Тизнью экзСмпляра ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π°. КаТдая рСгистрация ΠΈΠΌΠ΅Π΅Ρ‚
241 * свой собствСнный ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ `ILifetime`, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ создаСтся ΠΏΡ€ΠΈ ΠΏΠ΅Ρ€Π²ΠΎΠΉ Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ
241 * свой собствСнный ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ `ILifetime`, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ создаСтся ΠΏΡ€ΠΈ ΠΏΠ΅Ρ€Π²ΠΎΠΉ Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ
242 */
242 */
243 export interface ILifetime<T> {
243 export type ILifetime<T> = (context: ILifetimeContext) => ILifetimeSlot<NonNullable<T>>;
244 /** ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅Ρ‚, Ρ‡Ρ‚ΠΎ ΡƒΠΆΠ΅ создан экзСмпляр ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π° */
245 has(): boolean;
246
244
247 get(): NonNullable<T>;
245 export interface ILifetimeSlot<T> {
246 readonly has: () => boolean;
247
248 readonly get: () => T;
248
249
249 initialize(context: ILifetimeContext): void;
250 readonly initialize: () => void;
251
252 readonly store: (item: T, cleanup?: (item: T) => void) => void;
250
253
251 store(item: NonNullable<T>, cleanup?: (item: NonNullable<T>) => void): void;
254 readonly remove: () => void;
252
255
253 toString(): string;
256 readonly cleanup: () => void;
254 }
257 }
255
258
256 export interface ILifetimeSlot<T> {
257 has: () => boolean;
258
259 get: () => T;
260
261 initialize: () => void;
262
263 store(item: T, cleanup?: (item: T) => void): void;
264 }
265
266 export interface ILifetimeManager extends IDestroyable {
259 export interface ILifetimeManager extends IDestroyable {
267 create<T>(): ILifetime<T>;
260 slot<T>(id: string | number): ILifetimeSlot<T>;
268 }
261 }
269
262
270 export type ExtractRequired<T, K extends keyof T = keyof T> = { [p in K as (undefined extends T[p] ? never : p)]-?: T[p] };
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