@blac/core 2.0.0-rc-2 → 2.0.0-rc-4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -45
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +90 -113
- package/dist/index.es.js +359 -284
- package/dist/index.es.js.map +1 -1
- package/package.json +3 -2
- package/src/Blac.ts +258 -134
- package/src/BlacObserver.ts +12 -6
- package/src/BlocBase.ts +129 -40
- package/src/index.ts +2 -3
- package/src/BlacObservable.ts +0 -85
- package/src/BlacPlugin.ts +0 -13
- package/src/addons/BlacAddon.ts +0 -17
- package/src/addons/Persist.ts +0 -105
- package/src/addons/index.ts +0 -1
package/src/Blac.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
|
|
2
|
+
// TODO: Remove this eslint disable once any types are properly replaced
|
|
3
3
|
import { BlocBase, BlocInstanceId } from "./BlocBase";
|
|
4
4
|
import {
|
|
5
5
|
BlocBaseAbstract,
|
|
@@ -17,51 +17,95 @@ export interface BlacConfig {
|
|
|
17
17
|
exposeBlacInstance?: boolean;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export interface GetBlocOptions<B extends BlocBase<
|
|
20
|
+
export interface GetBlocOptions<B extends BlocBase<unknown>> {
|
|
21
21
|
id?: string;
|
|
22
22
|
selector?: BlocHookDependencyArrayFn<BlocState<B>>;
|
|
23
23
|
props?: InferPropsFromGeneric<B>;
|
|
24
24
|
onMount?: (bloc: B) => void;
|
|
25
25
|
instanceRef?: string;
|
|
26
|
+
throwIfNotFound?: boolean;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
|
-
*
|
|
30
|
-
* These events are used to track the lifecycle of blocs and their consumers.
|
|
30
|
+
* Interface for Blac instance management
|
|
31
31
|
*/
|
|
32
|
-
export
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
export interface BlacInstanceManager {
|
|
33
|
+
getInstance(): Blac;
|
|
34
|
+
setInstance(instance: Blac): void;
|
|
35
|
+
resetInstance(): void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Default singleton implementation of BlacInstanceManager
|
|
40
|
+
*/
|
|
41
|
+
class SingletonBlacManager implements BlacInstanceManager {
|
|
42
|
+
private static _instance: Blac;
|
|
43
|
+
|
|
44
|
+
getInstance(): Blac {
|
|
45
|
+
if (!SingletonBlacManager._instance) {
|
|
46
|
+
SingletonBlacManager._instance = new Blac({ __unsafe_ignore_singleton: true });
|
|
47
|
+
}
|
|
48
|
+
return SingletonBlacManager._instance;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
setInstance(instance: Blac): void {
|
|
52
|
+
SingletonBlacManager._instance = instance;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
resetInstance(): void {
|
|
56
|
+
const oldInstance = SingletonBlacManager._instance;
|
|
57
|
+
SingletonBlacManager._instance = new Blac({ __unsafe_ignore_singleton: true });
|
|
58
|
+
|
|
59
|
+
// Transfer any keep-alive blocs to the new instance
|
|
60
|
+
for (const bloc of oldInstance.keepAliveBlocs) {
|
|
61
|
+
SingletonBlacManager._instance.keepAliveBlocs.add(bloc);
|
|
62
|
+
SingletonBlacManager._instance.uidRegistry.set(bloc.uid, bloc);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Global instance manager (can be replaced for testing or different patterns)
|
|
69
|
+
*/
|
|
70
|
+
let instanceManager: BlacInstanceManager = new SingletonBlacManager();
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Sets a custom instance manager (useful for testing or advanced patterns)
|
|
74
|
+
*/
|
|
75
|
+
export function setBlacInstanceManager(manager: BlacInstanceManager): void {
|
|
76
|
+
instanceManager = manager;
|
|
40
77
|
}
|
|
41
78
|
|
|
42
79
|
/**
|
|
43
80
|
* Main Blac class that manages the state management system.
|
|
44
|
-
*
|
|
45
|
-
* Handles bloc lifecycle,
|
|
81
|
+
* Can work with singleton pattern or dependency injection.
|
|
82
|
+
* Handles bloc lifecycle, and instance tracking.
|
|
46
83
|
*
|
|
47
84
|
* Key responsibilities:
|
|
48
85
|
* - Managing bloc instances (creation, disposal, lookup)
|
|
49
86
|
* - Handling isolated and non-isolated blocs
|
|
50
|
-
* - Managing plugins and lifecycle events
|
|
51
87
|
* - Providing logging and debugging capabilities
|
|
52
88
|
*/
|
|
53
89
|
export class Blac {
|
|
54
|
-
/**
|
|
55
|
-
static instance: Blac
|
|
90
|
+
/** @deprecated Use getInstance() instead */
|
|
91
|
+
static get instance(): Blac {
|
|
92
|
+
return instanceManager.getInstance();
|
|
93
|
+
}
|
|
56
94
|
/** Timestamp when the instance was created */
|
|
57
95
|
createdAt = Date.now();
|
|
58
|
-
static getAllBlocs
|
|
59
|
-
|
|
96
|
+
static get getAllBlocs() {
|
|
97
|
+
return Blac.instance.getAllBlocs;
|
|
98
|
+
}
|
|
60
99
|
/** Map storing all registered bloc instances by their class name and ID */
|
|
61
|
-
blocInstanceMap: Map<string, BlocBase<
|
|
100
|
+
blocInstanceMap: Map<string, BlocBase<unknown>> = new Map();
|
|
62
101
|
/** Map storing isolated bloc instances grouped by their constructor */
|
|
63
|
-
isolatedBlocMap: Map<BlocConstructor<
|
|
64
|
-
|
|
102
|
+
isolatedBlocMap: Map<BlocConstructor<unknown>, BlocBase<unknown>[]> = new Map();
|
|
103
|
+
/** Map for O(1) lookup of isolated blocs by UID */
|
|
104
|
+
private isolatedBlocIndex: Map<string, BlocBase<unknown>> = new Map();
|
|
105
|
+
/** Map tracking UIDs to prevent memory leaks */
|
|
106
|
+
uidRegistry: Map<string, BlocBase<unknown>> = new Map();
|
|
107
|
+
/** Set of keep-alive blocs for controlled cleanup */
|
|
108
|
+
keepAliveBlocs: Set<BlocBase<unknown>> = new Set();
|
|
65
109
|
/** Flag to control whether changes should be posted to document */
|
|
66
110
|
postChangesToDocument = false;
|
|
67
111
|
|
|
@@ -74,7 +118,7 @@ export class Blac {
|
|
|
74
118
|
if (!__unsafe_ignore_singleton) {
|
|
75
119
|
return Blac.instance;
|
|
76
120
|
}
|
|
77
|
-
|
|
121
|
+
instanceManager.setInstance(this);
|
|
78
122
|
}
|
|
79
123
|
|
|
80
124
|
/** Flag to enable/disable logging */
|
|
@@ -87,14 +131,14 @@ export class Blac {
|
|
|
87
131
|
log = (...args: unknown[]) => {
|
|
88
132
|
if (Blac.enableLog) console.warn(`☢️ [Blac ${this.createdAt.toString()}]`, ...args);
|
|
89
133
|
};
|
|
90
|
-
static log
|
|
134
|
+
static get log() { return Blac.instance.log; }
|
|
91
135
|
|
|
92
136
|
/**
|
|
93
|
-
* Gets the
|
|
137
|
+
* Gets the current Blac instance
|
|
94
138
|
* @returns The Blac instance
|
|
95
139
|
*/
|
|
96
140
|
static getInstance(): Blac {
|
|
97
|
-
return
|
|
141
|
+
return instanceManager.getInstance();
|
|
98
142
|
}
|
|
99
143
|
|
|
100
144
|
|
|
@@ -108,7 +152,7 @@ export class Blac {
|
|
|
108
152
|
console.warn(`🚨 [Blac ${String(Blac.instance.createdAt)}]`, message, ...args);
|
|
109
153
|
}
|
|
110
154
|
};
|
|
111
|
-
static warn
|
|
155
|
+
static get warn() { return Blac.instance.warn; }
|
|
112
156
|
/**
|
|
113
157
|
* Logs an error message
|
|
114
158
|
* @param message - Error message
|
|
@@ -119,7 +163,7 @@ export class Blac {
|
|
|
119
163
|
console.error(`🚨 [Blac ${String(Blac.instance.createdAt)}]`, message, ...args);
|
|
120
164
|
}
|
|
121
165
|
};
|
|
122
|
-
static error
|
|
166
|
+
static get error() { return Blac.instance.error; }
|
|
123
167
|
|
|
124
168
|
/**
|
|
125
169
|
* Resets the Blac instance to a new one, disposing non-keepAlive blocs
|
|
@@ -133,98 +177,35 @@ export class Blac {
|
|
|
133
177
|
const oldIsolatedBlocMap = new Map(this.isolatedBlocMap);
|
|
134
178
|
|
|
135
179
|
oldBlocInstanceMap.forEach((bloc) => {
|
|
136
|
-
bloc.
|
|
180
|
+
if (!bloc._keepAlive) {
|
|
181
|
+
bloc._dispose();
|
|
182
|
+
}
|
|
137
183
|
});
|
|
138
184
|
|
|
139
185
|
oldIsolatedBlocMap.forEach((blocArray) => {
|
|
140
186
|
blocArray.forEach((bloc) => {
|
|
141
|
-
bloc.
|
|
187
|
+
if (!bloc._keepAlive) {
|
|
188
|
+
bloc._dispose();
|
|
189
|
+
}
|
|
142
190
|
});
|
|
143
191
|
});
|
|
144
192
|
|
|
145
193
|
this.blocInstanceMap.clear();
|
|
146
194
|
this.isolatedBlocMap.clear();
|
|
195
|
+
this.isolatedBlocIndex.clear();
|
|
147
196
|
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
__unsafe_ignore_singleton: true,
|
|
151
|
-
});
|
|
197
|
+
// Use instance manager to reset
|
|
198
|
+
instanceManager.resetInstance();
|
|
152
199
|
}
|
|
153
|
-
static resetInstance =
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Adds a plugin to the Blac instance
|
|
157
|
-
* @param plugin - The plugin to add
|
|
158
|
-
*/
|
|
159
|
-
addPlugin = (plugin: BlacPlugin): void => {
|
|
160
|
-
// check if already added
|
|
161
|
-
const index = this.pluginList.findIndex((p) => p.name === plugin.name);
|
|
162
|
-
if (index !== -1) return;
|
|
163
|
-
this.log("Add plugin", plugin.name);
|
|
164
|
-
this.pluginList.push(plugin);
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Dispatches a lifecycle event to all registered plugins
|
|
169
|
-
* @param event - The lifecycle event to dispatch
|
|
170
|
-
* @param bloc - The bloc instance involved in the event
|
|
171
|
-
* @param params - Additional parameters for the event
|
|
172
|
-
*/
|
|
173
|
-
dispatchEventToPlugins = (
|
|
174
|
-
event: BlacLifecycleEvent,
|
|
175
|
-
bloc: BlocBase<any>,
|
|
176
|
-
params?: unknown,
|
|
177
|
-
) => {
|
|
178
|
-
this.pluginList.forEach((plugin) => {
|
|
179
|
-
plugin.onEvent(event, bloc, params);
|
|
180
|
-
});
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Dispatches a lifecycle event and handles related cleanup actions.
|
|
185
|
-
* This method is responsible for:
|
|
186
|
-
* - Logging the event
|
|
187
|
-
* - Handling bloc disposal when needed
|
|
188
|
-
* - Managing bloc consumer cleanup
|
|
189
|
-
* - Forwarding the event to plugins
|
|
190
|
-
*
|
|
191
|
-
* @param event - The lifecycle event to dispatch
|
|
192
|
-
* @param bloc - The bloc instance involved in the event
|
|
193
|
-
* @param params - Additional parameters for the event
|
|
194
|
-
*/
|
|
195
|
-
dispatchEvent = (
|
|
196
|
-
event: BlacLifecycleEvent,
|
|
197
|
-
bloc: BlocBase<any>,
|
|
198
|
-
params?: unknown,
|
|
199
|
-
) => {
|
|
200
|
-
this.log(event, bloc, params);
|
|
201
|
-
|
|
202
|
-
switch (event) {
|
|
203
|
-
case BlacLifecycleEvent.BLOC_DISPOSED:
|
|
204
|
-
this.disposeBloc(bloc);
|
|
205
|
-
break;
|
|
206
|
-
case BlacLifecycleEvent.BLOC_CONSUMER_REMOVED:
|
|
207
|
-
case BlacLifecycleEvent.LISTENER_REMOVED:
|
|
208
|
-
this.log(`[${bloc._name}:${String(bloc._id)}] Listener/Consumer removed. Listeners: ${String(bloc._observer.size)}, Consumers: ${String(bloc._consumers.size)}, KeepAlive: ${String(bloc._keepAlive)}`);
|
|
209
|
-
if (
|
|
210
|
-
bloc._observer.size === 0 &&
|
|
211
|
-
bloc._consumers.size === 0 &&
|
|
212
|
-
!bloc._keepAlive
|
|
213
|
-
) {
|
|
214
|
-
this.log(`[${bloc._name}:${String(bloc._id)}] No listeners or consumers left and not keepAlive. Disposing.`);
|
|
215
|
-
bloc._dispose();
|
|
216
|
-
}
|
|
217
|
-
break;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
this.dispatchEventToPlugins(event, bloc, params);
|
|
200
|
+
static resetInstance = (): void => {
|
|
201
|
+
instanceManager.resetInstance();
|
|
221
202
|
};
|
|
222
203
|
|
|
223
204
|
/**
|
|
224
205
|
* Disposes of a bloc instance by removing it from the appropriate registry
|
|
225
206
|
* @param bloc - The bloc instance to dispose
|
|
226
207
|
*/
|
|
227
|
-
disposeBloc = (bloc: BlocBase<
|
|
208
|
+
disposeBloc = (bloc: BlocBase<unknown>): void => {
|
|
228
209
|
const base = bloc.constructor as unknown as BlocBaseAbstract;
|
|
229
210
|
const key = this.createBlocInstanceMapKey(bloc._name, bloc._id);
|
|
230
211
|
this.log(`[${bloc._name}:${String(bloc._id)}] disposeBloc called. Isolated: ${String(base.isolated)}`);
|
|
@@ -235,7 +216,7 @@ export class Blac {
|
|
|
235
216
|
} else {
|
|
236
217
|
this.unregisterBlocInstance(bloc);
|
|
237
218
|
}
|
|
238
|
-
this.
|
|
219
|
+
this.log('dispatched bloc', bloc)
|
|
239
220
|
};
|
|
240
221
|
|
|
241
222
|
/**
|
|
@@ -252,18 +233,32 @@ export class Blac {
|
|
|
252
233
|
* Unregister a bloc instance from the main registry
|
|
253
234
|
* @param bloc - The bloc instance to unregister
|
|
254
235
|
*/
|
|
255
|
-
unregisterBlocInstance(bloc: BlocBase<
|
|
236
|
+
unregisterBlocInstance(bloc: BlocBase<unknown>): void {
|
|
256
237
|
const key = this.createBlocInstanceMapKey(bloc._name, bloc._id);
|
|
257
238
|
this.blocInstanceMap.delete(key);
|
|
239
|
+
|
|
240
|
+
// Clean up UID tracking
|
|
241
|
+
this.uidRegistry.delete(bloc.uid);
|
|
242
|
+
|
|
243
|
+
// Remove from keep-alive set
|
|
244
|
+
this.keepAliveBlocs.delete(bloc);
|
|
258
245
|
}
|
|
259
246
|
|
|
260
247
|
/**
|
|
261
248
|
* Registers a bloc instance in the main registry
|
|
262
249
|
* @param bloc - The bloc instance to register
|
|
263
250
|
*/
|
|
264
|
-
registerBlocInstance(bloc: BlocBase<
|
|
251
|
+
registerBlocInstance(bloc: BlocBase<unknown>): void {
|
|
265
252
|
const key = this.createBlocInstanceMapKey(bloc._name, bloc._id);
|
|
266
253
|
this.blocInstanceMap.set(key, bloc);
|
|
254
|
+
|
|
255
|
+
// Track UID for cleanup
|
|
256
|
+
this.uidRegistry.set(bloc.uid, bloc);
|
|
257
|
+
|
|
258
|
+
// Track keep-alive blocs
|
|
259
|
+
if (bloc._keepAlive) {
|
|
260
|
+
this.keepAliveBlocs.add(bloc);
|
|
261
|
+
}
|
|
267
262
|
}
|
|
268
263
|
|
|
269
264
|
/**
|
|
@@ -281,17 +276,14 @@ export class Blac {
|
|
|
281
276
|
|
|
282
277
|
const key = this.createBlocInstanceMapKey(blocClass.name, id);
|
|
283
278
|
const found = this.blocInstanceMap.get(key) as InstanceType<B> | undefined;
|
|
284
|
-
|
|
285
|
-
this.log(`[${blocClass.name}:${String(id)}] Found registered instance. Returning.`);
|
|
286
|
-
}
|
|
287
|
-
return found
|
|
279
|
+
return found
|
|
288
280
|
}
|
|
289
281
|
|
|
290
282
|
/**
|
|
291
283
|
* Registers an isolated bloc instance in the isolated registry
|
|
292
284
|
* @param bloc - The isolated bloc instance to register
|
|
293
285
|
*/
|
|
294
|
-
registerIsolatedBlocInstance(bloc: BlocBase<
|
|
286
|
+
registerIsolatedBlocInstance(bloc: BlocBase<unknown>): void {
|
|
295
287
|
const blocClass = bloc.constructor as BlocConstructor<unknown>;
|
|
296
288
|
const blocs = this.isolatedBlocMap.get(blocClass);
|
|
297
289
|
if (blocs) {
|
|
@@ -299,17 +291,28 @@ export class Blac {
|
|
|
299
291
|
} else {
|
|
300
292
|
this.isolatedBlocMap.set(blocClass, [bloc]);
|
|
301
293
|
}
|
|
294
|
+
|
|
295
|
+
// Add to isolated index for O(1) lookups
|
|
296
|
+
this.isolatedBlocIndex.set(bloc.uid, bloc);
|
|
297
|
+
|
|
298
|
+
// Track UID for cleanup
|
|
299
|
+
this.uidRegistry.set(bloc.uid, bloc);
|
|
300
|
+
|
|
301
|
+
// Track keep-alive blocs
|
|
302
|
+
if (bloc._keepAlive) {
|
|
303
|
+
this.keepAliveBlocs.add(bloc);
|
|
304
|
+
}
|
|
302
305
|
}
|
|
303
306
|
|
|
304
307
|
/**
|
|
305
308
|
* Unregister an isolated bloc instance from the isolated registry
|
|
306
309
|
* @param bloc - The isolated bloc instance to unregister
|
|
307
310
|
*/
|
|
308
|
-
unregisterIsolatedBlocInstance(bloc: BlocBase<
|
|
311
|
+
unregisterIsolatedBlocInstance(bloc: BlocBase<unknown>): void {
|
|
309
312
|
const blocClass = bloc.constructor;
|
|
310
313
|
const blocs = this.isolatedBlocMap.get(blocClass as BlocConstructor<unknown>);
|
|
311
314
|
if (blocs) {
|
|
312
|
-
const index = blocs.findIndex((b) => b.
|
|
315
|
+
const index = blocs.findIndex((b) => b.uid === bloc.uid);
|
|
313
316
|
if (index !== -1) {
|
|
314
317
|
blocs.splice(index, 1);
|
|
315
318
|
}
|
|
@@ -318,10 +321,19 @@ export class Blac {
|
|
|
318
321
|
this.isolatedBlocMap.delete(blocClass as BlocConstructor<unknown>);
|
|
319
322
|
}
|
|
320
323
|
}
|
|
324
|
+
|
|
325
|
+
// Remove from isolated index
|
|
326
|
+
this.isolatedBlocIndex.delete(bloc.uid);
|
|
327
|
+
|
|
328
|
+
// Clean up UID tracking
|
|
329
|
+
this.uidRegistry.delete(bloc.uid);
|
|
330
|
+
|
|
331
|
+
// Remove from keep-alive set
|
|
332
|
+
this.keepAliveBlocs.delete(bloc);
|
|
321
333
|
}
|
|
322
334
|
|
|
323
335
|
/**
|
|
324
|
-
* Finds an isolated bloc instance by its class and ID
|
|
336
|
+
* Finds an isolated bloc instance by its class and ID (O(n) lookup)
|
|
325
337
|
*/
|
|
326
338
|
findIsolatedBlocInstance<B extends BlocConstructor<unknown>>(
|
|
327
339
|
blocClass: B,
|
|
@@ -331,18 +343,23 @@ export class Blac {
|
|
|
331
343
|
if (!base.isolated) return undefined;
|
|
332
344
|
|
|
333
345
|
const blocs = this.isolatedBlocMap.get(blocClass);
|
|
334
|
-
if (!blocs)
|
|
335
|
-
|
|
336
|
-
// Fix: Find the specific bloc by ID within the isolated array
|
|
337
|
-
const found = blocs.find((b) => b._id === id) as InstanceType<B> | undefined;
|
|
338
|
-
|
|
339
|
-
if (found) {
|
|
340
|
-
this.log(`[${blocClass.name}:${String(id)}] Found isolated instance. Returning.`);
|
|
346
|
+
if (!blocs) {
|
|
347
|
+
return undefined;
|
|
341
348
|
}
|
|
342
|
-
|
|
349
|
+
// Find the specific bloc by ID within the isolated array
|
|
350
|
+
const found = blocs.find((b) => b._id === id) as InstanceType<B> | undefined;
|
|
343
351
|
return found;
|
|
344
352
|
}
|
|
345
353
|
|
|
354
|
+
/**
|
|
355
|
+
* Finds an isolated bloc instance by UID (O(1) lookup)
|
|
356
|
+
*/
|
|
357
|
+
findIsolatedBlocInstanceByUid<B extends BlocBase<unknown>>(
|
|
358
|
+
uid: string
|
|
359
|
+
): B | undefined {
|
|
360
|
+
return this.isolatedBlocIndex.get(uid) as B | undefined;
|
|
361
|
+
}
|
|
362
|
+
|
|
346
363
|
/**
|
|
347
364
|
* Creates a new bloc instance and registers it in the appropriate registry
|
|
348
365
|
* @param blocClass - The bloc class to instantiate
|
|
@@ -351,7 +368,7 @@ export class Blac {
|
|
|
351
368
|
* @param instanceRef - Optional reference string for the instance
|
|
352
369
|
* @returns The newly created bloc instance
|
|
353
370
|
*/
|
|
354
|
-
createNewBlocInstance<B extends BlocConstructor<BlocBase<
|
|
371
|
+
createNewBlocInstance<B extends BlocConstructor<BlocBase<unknown>>>(
|
|
355
372
|
blocClass: B,
|
|
356
373
|
id: BlocInstanceId,
|
|
357
374
|
options: GetBlocOptions<InstanceType<B>> = {},
|
|
@@ -362,6 +379,9 @@ export class Blac {
|
|
|
362
379
|
newBloc.props = props || null;
|
|
363
380
|
newBloc._updateId(id);
|
|
364
381
|
|
|
382
|
+
// Set up disposal handler to break circular dependency
|
|
383
|
+
newBloc._setDisposalHandler((bloc) => this.disposeBloc(bloc));
|
|
384
|
+
|
|
365
385
|
if (newBloc.isIsolated) {
|
|
366
386
|
this.registerIsolatedBlocInstance(newBloc);
|
|
367
387
|
return newBloc as InstanceType<B>;
|
|
@@ -371,6 +391,25 @@ export class Blac {
|
|
|
371
391
|
return newBloc as InstanceType<B>;
|
|
372
392
|
}
|
|
373
393
|
|
|
394
|
+
|
|
395
|
+
activateBloc = (bloc: BlocBase<unknown>): void => {
|
|
396
|
+
const base = bloc.constructor as unknown as BlocConstructor<BlocBase<unknown>>;
|
|
397
|
+
const isIsolated = bloc.isIsolated;
|
|
398
|
+
|
|
399
|
+
let found = isIsolated ? this.findIsolatedBlocInstance(base, bloc._id) : this.findRegisteredBlocInstance(base, bloc._id);
|
|
400
|
+
if (found) {
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
this.log(`[${bloc._name}:${String(bloc._id)}] activateBloc called. Isolated: ${String(bloc.isIsolated)}`);
|
|
405
|
+
if (bloc.isIsolated) {
|
|
406
|
+
this.registerIsolatedBlocInstance(bloc);
|
|
407
|
+
} else {
|
|
408
|
+
this.registerBlocInstance(bloc);
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
static get activateBloc() { return Blac.instance.activateBloc; }
|
|
412
|
+
|
|
374
413
|
/**
|
|
375
414
|
* Gets or creates a bloc instance based on the provided class and options.
|
|
376
415
|
* If a bloc with the given ID exists, it will be returned. Otherwise, a new instance will be created.
|
|
@@ -382,7 +421,7 @@ export class Blac {
|
|
|
382
421
|
* - instanceRef: Optional reference string for the instance
|
|
383
422
|
* @returns The bloc instance
|
|
384
423
|
*/
|
|
385
|
-
getBloc = <B extends BlocConstructor<BlocBase<
|
|
424
|
+
getBloc = <B extends BlocConstructor<BlocBase<unknown>>>(
|
|
386
425
|
blocClass: B,
|
|
387
426
|
options: GetBlocOptions<InstanceType<B>> = {},
|
|
388
427
|
): InstanceType<B> => {
|
|
@@ -390,32 +429,38 @@ export class Blac {
|
|
|
390
429
|
const base = blocClass as unknown as BlocBaseAbstract;
|
|
391
430
|
const blocId = id ?? blocClass.name;
|
|
392
431
|
|
|
393
|
-
this.log(`[${blocClass.name}:${String(blocId)}] getBloc called. Options:`, options);
|
|
394
432
|
|
|
395
433
|
if (base.isolated) {
|
|
396
434
|
const isolatedBloc = this.findIsolatedBlocInstance<B>(blocClass, blocId)
|
|
397
435
|
if (isolatedBloc) {
|
|
398
|
-
this.log(`[${blocClass.name}:${String(blocId)}] Found existing isolated instance
|
|
436
|
+
this.log(`[${blocClass.name}:${String(blocId)}] (getBloc) Found existing isolated instance.`, options);
|
|
399
437
|
return isolatedBloc;
|
|
438
|
+
} else {
|
|
439
|
+
if (options.throwIfNotFound) {
|
|
440
|
+
throw new Error(`Isolated bloc ${blocClass.name} not found`);
|
|
441
|
+
}
|
|
400
442
|
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
if (!base.isolated) {
|
|
443
|
+
} else {
|
|
404
444
|
const registeredBloc = this.findRegisteredBlocInstance(blocClass, blocId)
|
|
405
445
|
if (registeredBloc) {
|
|
406
|
-
this.log(`[${blocClass.name}:${String(blocId)}] Found existing registered instance
|
|
446
|
+
this.log(`[${blocClass.name}:${String(blocId)}] (getBloc) Found existing registered instance.`, options);
|
|
407
447
|
return registeredBloc
|
|
448
|
+
} else {
|
|
449
|
+
if (options.throwIfNotFound) {
|
|
450
|
+
throw new Error(`Registered bloc ${blocClass.name} not found`);
|
|
451
|
+
}
|
|
408
452
|
}
|
|
409
453
|
}
|
|
410
454
|
|
|
411
|
-
|
|
412
|
-
return this.createNewBlocInstance(
|
|
455
|
+
const bloc = this.createNewBlocInstance(
|
|
413
456
|
blocClass,
|
|
414
457
|
blocId,
|
|
415
458
|
options,
|
|
416
459
|
);
|
|
460
|
+
this.log(`[${blocClass.name}:${String(blocId)}] (getBloc) No existing instance found. Creating new one.`, options, bloc);
|
|
461
|
+
return bloc;
|
|
417
462
|
};
|
|
418
|
-
static getBloc
|
|
463
|
+
static get getBloc() { return Blac.instance.getBloc; }
|
|
419
464
|
|
|
420
465
|
/**
|
|
421
466
|
* Gets a bloc instance or throws an error if it doesn't exist
|
|
@@ -445,7 +490,7 @@ export class Blac {
|
|
|
445
490
|
}
|
|
446
491
|
throw new Error(`Bloc ${blocClass.name} not found`);
|
|
447
492
|
};
|
|
448
|
-
static getBlocOrThrow
|
|
493
|
+
static get getBlocOrThrow() { return Blac.instance.getBlocOrThrow; }
|
|
449
494
|
|
|
450
495
|
/**
|
|
451
496
|
* Gets all instances of a specific bloc class
|
|
@@ -480,4 +525,83 @@ export class Blac {
|
|
|
480
525
|
|
|
481
526
|
return results;
|
|
482
527
|
};
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Disposes all keep-alive blocs of a specific type
|
|
531
|
+
* @param blocClass - The bloc class to dispose
|
|
532
|
+
*/
|
|
533
|
+
disposeKeepAliveBlocs = <B extends BlocConstructor<unknown>>(
|
|
534
|
+
blocClass?: B
|
|
535
|
+
): void => {
|
|
536
|
+
const toDispose: BlocBase<unknown>[] = [];
|
|
537
|
+
|
|
538
|
+
for (const bloc of this.keepAliveBlocs) {
|
|
539
|
+
if (!blocClass || bloc.constructor === blocClass) {
|
|
540
|
+
toDispose.push(bloc);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
toDispose.forEach(bloc => bloc._dispose());
|
|
545
|
+
};
|
|
546
|
+
static get disposeKeepAliveBlocs() { return Blac.instance.disposeKeepAliveBlocs; }
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Disposes all blocs matching a pattern
|
|
550
|
+
* @param predicate - Function to test each bloc for disposal
|
|
551
|
+
*/
|
|
552
|
+
disposeBlocs = (predicate: (bloc: BlocBase<unknown>) => boolean): void => {
|
|
553
|
+
const toDispose: BlocBase<unknown>[] = [];
|
|
554
|
+
|
|
555
|
+
// Check registered blocs
|
|
556
|
+
for (const bloc of this.blocInstanceMap.values()) {
|
|
557
|
+
if (predicate(bloc)) {
|
|
558
|
+
toDispose.push(bloc);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Check isolated blocs
|
|
563
|
+
for (const blocs of this.isolatedBlocMap.values()) {
|
|
564
|
+
for (const bloc of blocs) {
|
|
565
|
+
if (predicate(bloc)) {
|
|
566
|
+
toDispose.push(bloc);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
toDispose.forEach(bloc => bloc._dispose());
|
|
572
|
+
};
|
|
573
|
+
static get disposeBlocs() { return Blac.instance.disposeBlocs; }
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Gets memory usage statistics for debugging
|
|
577
|
+
*/
|
|
578
|
+
getMemoryStats = () => {
|
|
579
|
+
return {
|
|
580
|
+
totalBlocs: this.uidRegistry.size,
|
|
581
|
+
registeredBlocs: this.blocInstanceMap.size,
|
|
582
|
+
isolatedBlocs: Array.from(this.isolatedBlocMap.values()).reduce((sum, arr) => sum + arr.length, 0),
|
|
583
|
+
keepAliveBlocs: this.keepAliveBlocs.size,
|
|
584
|
+
isolatedBlocTypes: this.isolatedBlocMap.size,
|
|
585
|
+
};
|
|
586
|
+
};
|
|
587
|
+
static get getMemoryStats() { return Blac.instance.getMemoryStats; }
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Validates consumer references and cleans up orphaned consumers
|
|
591
|
+
*/
|
|
592
|
+
validateConsumers = (): void => {
|
|
593
|
+
for (const bloc of this.uidRegistry.values()) {
|
|
594
|
+
// This would need to be called periodically to clean up orphaned consumers
|
|
595
|
+
// Implementation depends on how we want to handle consumer validation
|
|
596
|
+
if (bloc._consumers.size === 0 && !bloc._keepAlive) {
|
|
597
|
+
// Schedule disposal for blocs with no consumers
|
|
598
|
+
setTimeout(() => {
|
|
599
|
+
if (bloc._consumers.size === 0 && !bloc._keepAlive) {
|
|
600
|
+
bloc._dispose();
|
|
601
|
+
}
|
|
602
|
+
}, 1000); // Give a grace period
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
static get validateConsumers() { return Blac.instance.validateConsumers; }
|
|
483
607
|
}
|
package/src/BlacObserver.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Blac
|
|
1
|
+
import { Blac } from './Blac';
|
|
2
2
|
import { BlocBase } from './BlocBase';
|
|
3
3
|
import { BlocHookDependencyArrayFn } from './types';
|
|
4
4
|
|
|
@@ -59,14 +59,16 @@ export class BlacObservable<S = unknown> {
|
|
|
59
59
|
* @returns A function that can be called to unsubscribe the observer
|
|
60
60
|
*/
|
|
61
61
|
subscribe(observer: BlacObserver<S>): () => void {
|
|
62
|
+
Blac.log('BlacObservable.subscribe: Subscribing observer.', this.bloc, observer);
|
|
62
63
|
this._observers.add(observer);
|
|
63
|
-
Blac.instance.dispatchEvent(BlacLifecycleEvent.LISTENER_ADDED, this.bloc, { listenerId: observer.id });
|
|
64
|
+
// Blac.instance.dispatchEvent(BlacLifecycleEvent.LISTENER_ADDED, this.bloc, { listenerId: observer.id });
|
|
64
65
|
if (!observer.lastState) {
|
|
65
66
|
observer.lastState = observer.dependencyArray
|
|
66
67
|
? observer.dependencyArray(this.bloc.state)
|
|
67
68
|
: [];
|
|
68
69
|
}
|
|
69
70
|
return () => {
|
|
71
|
+
Blac.log('BlacObservable.subscribe: Unsubscribing observer.', this.bloc, observer);
|
|
70
72
|
this.unsubscribe(observer);
|
|
71
73
|
}
|
|
72
74
|
}
|
|
@@ -76,9 +78,13 @@ export class BlacObservable<S = unknown> {
|
|
|
76
78
|
* @param observer - The observer to unsubscribe
|
|
77
79
|
*/
|
|
78
80
|
unsubscribe(observer: BlacObserver<S>) {
|
|
79
|
-
observer.dispose?.();
|
|
80
81
|
this._observers.delete(observer);
|
|
81
|
-
Blac.instance.dispatchEvent(BlacLifecycleEvent.LISTENER_REMOVED, this.bloc, { listenerId: observer.id });
|
|
82
|
+
// Blac.instance.dispatchEvent(BlacLifecycleEvent.LISTENER_REMOVED, this.bloc, { listenerId: observer.id });
|
|
83
|
+
|
|
84
|
+
if (this.size === 0) {
|
|
85
|
+
Blac.log('BlacObservable.unsubscribe: No observers left. Disposing bloc.', this.bloc);
|
|
86
|
+
this.bloc._dispose();
|
|
87
|
+
}
|
|
82
88
|
}
|
|
83
89
|
|
|
84
90
|
/**
|
|
@@ -118,9 +124,9 @@ export class BlacObservable<S = unknown> {
|
|
|
118
124
|
}
|
|
119
125
|
|
|
120
126
|
/**
|
|
121
|
-
*
|
|
127
|
+
* Clears the observer set
|
|
122
128
|
*/
|
|
123
|
-
|
|
129
|
+
clear() {
|
|
124
130
|
this._observers.forEach((observer) => {
|
|
125
131
|
this.unsubscribe(observer);
|
|
126
132
|
});
|