@blac/core 2.0.0-rc-3 → 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/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +92 -24
- package/dist/index.es.js +312 -139
- package/dist/index.es.js.map +1 -1
- package/package.json +3 -2
- package/src/Blac.ts +226 -35
- package/src/BlocBase.ts +127 -19
package/src/Blac.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
// TODO: Remove this eslint disable once any types are properly replaced
|
|
2
3
|
import { BlocBase, BlocInstanceId } from "./BlocBase";
|
|
3
4
|
import {
|
|
4
5
|
BlocBaseAbstract,
|
|
@@ -16,7 +17,7 @@ export interface BlacConfig {
|
|
|
16
17
|
exposeBlacInstance?: boolean;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
export interface GetBlocOptions<B extends BlocBase<
|
|
20
|
+
export interface GetBlocOptions<B extends BlocBase<unknown>> {
|
|
20
21
|
id?: string;
|
|
21
22
|
selector?: BlocHookDependencyArrayFn<BlocState<B>>;
|
|
22
23
|
props?: InferPropsFromGeneric<B>;
|
|
@@ -25,9 +26,59 @@ export interface GetBlocOptions<B extends BlocBase<any>> {
|
|
|
25
26
|
throwIfNotFound?: boolean;
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Interface for Blac instance management
|
|
31
|
+
*/
|
|
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;
|
|
77
|
+
}
|
|
78
|
+
|
|
28
79
|
/**
|
|
29
80
|
* Main Blac class that manages the state management system.
|
|
30
|
-
*
|
|
81
|
+
* Can work with singleton pattern or dependency injection.
|
|
31
82
|
* Handles bloc lifecycle, and instance tracking.
|
|
32
83
|
*
|
|
33
84
|
* Key responsibilities:
|
|
@@ -36,15 +87,25 @@ export interface GetBlocOptions<B extends BlocBase<any>> {
|
|
|
36
87
|
* - Providing logging and debugging capabilities
|
|
37
88
|
*/
|
|
38
89
|
export class Blac {
|
|
39
|
-
/**
|
|
40
|
-
static instance: Blac
|
|
90
|
+
/** @deprecated Use getInstance() instead */
|
|
91
|
+
static get instance(): Blac {
|
|
92
|
+
return instanceManager.getInstance();
|
|
93
|
+
}
|
|
41
94
|
/** Timestamp when the instance was created */
|
|
42
95
|
createdAt = Date.now();
|
|
43
|
-
static getAllBlocs
|
|
96
|
+
static get getAllBlocs() {
|
|
97
|
+
return Blac.instance.getAllBlocs;
|
|
98
|
+
}
|
|
44
99
|
/** Map storing all registered bloc instances by their class name and ID */
|
|
45
|
-
blocInstanceMap: Map<string, BlocBase<
|
|
100
|
+
blocInstanceMap: Map<string, BlocBase<unknown>> = new Map();
|
|
46
101
|
/** Map storing isolated bloc instances grouped by their constructor */
|
|
47
|
-
isolatedBlocMap: Map<BlocConstructor<
|
|
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();
|
|
48
109
|
/** Flag to control whether changes should be posted to document */
|
|
49
110
|
postChangesToDocument = false;
|
|
50
111
|
|
|
@@ -57,7 +118,7 @@ export class Blac {
|
|
|
57
118
|
if (!__unsafe_ignore_singleton) {
|
|
58
119
|
return Blac.instance;
|
|
59
120
|
}
|
|
60
|
-
|
|
121
|
+
instanceManager.setInstance(this);
|
|
61
122
|
}
|
|
62
123
|
|
|
63
124
|
/** Flag to enable/disable logging */
|
|
@@ -70,14 +131,14 @@ export class Blac {
|
|
|
70
131
|
log = (...args: unknown[]) => {
|
|
71
132
|
if (Blac.enableLog) console.warn(`☢️ [Blac ${this.createdAt.toString()}]`, ...args);
|
|
72
133
|
};
|
|
73
|
-
static log
|
|
134
|
+
static get log() { return Blac.instance.log; }
|
|
74
135
|
|
|
75
136
|
/**
|
|
76
|
-
* Gets the
|
|
137
|
+
* Gets the current Blac instance
|
|
77
138
|
* @returns The Blac instance
|
|
78
139
|
*/
|
|
79
140
|
static getInstance(): Blac {
|
|
80
|
-
return
|
|
141
|
+
return instanceManager.getInstance();
|
|
81
142
|
}
|
|
82
143
|
|
|
83
144
|
|
|
@@ -91,7 +152,7 @@ export class Blac {
|
|
|
91
152
|
console.warn(`🚨 [Blac ${String(Blac.instance.createdAt)}]`, message, ...args);
|
|
92
153
|
}
|
|
93
154
|
};
|
|
94
|
-
static warn
|
|
155
|
+
static get warn() { return Blac.instance.warn; }
|
|
95
156
|
/**
|
|
96
157
|
* Logs an error message
|
|
97
158
|
* @param message - Error message
|
|
@@ -102,7 +163,7 @@ export class Blac {
|
|
|
102
163
|
console.error(`🚨 [Blac ${String(Blac.instance.createdAt)}]`, message, ...args);
|
|
103
164
|
}
|
|
104
165
|
};
|
|
105
|
-
static error
|
|
166
|
+
static get error() { return Blac.instance.error; }
|
|
106
167
|
|
|
107
168
|
/**
|
|
108
169
|
* Resets the Blac instance to a new one, disposing non-keepAlive blocs
|
|
@@ -116,30 +177,35 @@ export class Blac {
|
|
|
116
177
|
const oldIsolatedBlocMap = new Map(this.isolatedBlocMap);
|
|
117
178
|
|
|
118
179
|
oldBlocInstanceMap.forEach((bloc) => {
|
|
119
|
-
bloc.
|
|
180
|
+
if (!bloc._keepAlive) {
|
|
181
|
+
bloc._dispose();
|
|
182
|
+
}
|
|
120
183
|
});
|
|
121
184
|
|
|
122
185
|
oldIsolatedBlocMap.forEach((blocArray) => {
|
|
123
186
|
blocArray.forEach((bloc) => {
|
|
124
|
-
bloc.
|
|
187
|
+
if (!bloc._keepAlive) {
|
|
188
|
+
bloc._dispose();
|
|
189
|
+
}
|
|
125
190
|
});
|
|
126
191
|
});
|
|
127
192
|
|
|
128
193
|
this.blocInstanceMap.clear();
|
|
129
194
|
this.isolatedBlocMap.clear();
|
|
195
|
+
this.isolatedBlocIndex.clear();
|
|
130
196
|
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
__unsafe_ignore_singleton: true,
|
|
134
|
-
});
|
|
197
|
+
// Use instance manager to reset
|
|
198
|
+
instanceManager.resetInstance();
|
|
135
199
|
}
|
|
136
|
-
static resetInstance =
|
|
200
|
+
static resetInstance = (): void => {
|
|
201
|
+
instanceManager.resetInstance();
|
|
202
|
+
};
|
|
137
203
|
|
|
138
204
|
/**
|
|
139
205
|
* Disposes of a bloc instance by removing it from the appropriate registry
|
|
140
206
|
* @param bloc - The bloc instance to dispose
|
|
141
207
|
*/
|
|
142
|
-
disposeBloc = (bloc: BlocBase<
|
|
208
|
+
disposeBloc = (bloc: BlocBase<unknown>): void => {
|
|
143
209
|
const base = bloc.constructor as unknown as BlocBaseAbstract;
|
|
144
210
|
const key = this.createBlocInstanceMapKey(bloc._name, bloc._id);
|
|
145
211
|
this.log(`[${bloc._name}:${String(bloc._id)}] disposeBloc called. Isolated: ${String(base.isolated)}`);
|
|
@@ -167,18 +233,32 @@ export class Blac {
|
|
|
167
233
|
* Unregister a bloc instance from the main registry
|
|
168
234
|
* @param bloc - The bloc instance to unregister
|
|
169
235
|
*/
|
|
170
|
-
unregisterBlocInstance(bloc: BlocBase<
|
|
236
|
+
unregisterBlocInstance(bloc: BlocBase<unknown>): void {
|
|
171
237
|
const key = this.createBlocInstanceMapKey(bloc._name, bloc._id);
|
|
172
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);
|
|
173
245
|
}
|
|
174
246
|
|
|
175
247
|
/**
|
|
176
248
|
* Registers a bloc instance in the main registry
|
|
177
249
|
* @param bloc - The bloc instance to register
|
|
178
250
|
*/
|
|
179
|
-
registerBlocInstance(bloc: BlocBase<
|
|
251
|
+
registerBlocInstance(bloc: BlocBase<unknown>): void {
|
|
180
252
|
const key = this.createBlocInstanceMapKey(bloc._name, bloc._id);
|
|
181
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
|
+
}
|
|
182
262
|
}
|
|
183
263
|
|
|
184
264
|
/**
|
|
@@ -203,7 +283,7 @@ export class Blac {
|
|
|
203
283
|
* Registers an isolated bloc instance in the isolated registry
|
|
204
284
|
* @param bloc - The isolated bloc instance to register
|
|
205
285
|
*/
|
|
206
|
-
registerIsolatedBlocInstance(bloc: BlocBase<
|
|
286
|
+
registerIsolatedBlocInstance(bloc: BlocBase<unknown>): void {
|
|
207
287
|
const blocClass = bloc.constructor as BlocConstructor<unknown>;
|
|
208
288
|
const blocs = this.isolatedBlocMap.get(blocClass);
|
|
209
289
|
if (blocs) {
|
|
@@ -211,17 +291,28 @@ export class Blac {
|
|
|
211
291
|
} else {
|
|
212
292
|
this.isolatedBlocMap.set(blocClass, [bloc]);
|
|
213
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
|
+
}
|
|
214
305
|
}
|
|
215
306
|
|
|
216
307
|
/**
|
|
217
308
|
* Unregister an isolated bloc instance from the isolated registry
|
|
218
309
|
* @param bloc - The isolated bloc instance to unregister
|
|
219
310
|
*/
|
|
220
|
-
unregisterIsolatedBlocInstance(bloc: BlocBase<
|
|
311
|
+
unregisterIsolatedBlocInstance(bloc: BlocBase<unknown>): void {
|
|
221
312
|
const blocClass = bloc.constructor;
|
|
222
313
|
const blocs = this.isolatedBlocMap.get(blocClass as BlocConstructor<unknown>);
|
|
223
314
|
if (blocs) {
|
|
224
|
-
const index = blocs.findIndex((b) => b.
|
|
315
|
+
const index = blocs.findIndex((b) => b.uid === bloc.uid);
|
|
225
316
|
if (index !== -1) {
|
|
226
317
|
blocs.splice(index, 1);
|
|
227
318
|
}
|
|
@@ -230,10 +321,19 @@ export class Blac {
|
|
|
230
321
|
this.isolatedBlocMap.delete(blocClass as BlocConstructor<unknown>);
|
|
231
322
|
}
|
|
232
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);
|
|
233
333
|
}
|
|
234
334
|
|
|
235
335
|
/**
|
|
236
|
-
* Finds an isolated bloc instance by its class and ID
|
|
336
|
+
* Finds an isolated bloc instance by its class and ID (O(n) lookup)
|
|
237
337
|
*/
|
|
238
338
|
findIsolatedBlocInstance<B extends BlocConstructor<unknown>>(
|
|
239
339
|
blocClass: B,
|
|
@@ -246,11 +346,20 @@ export class Blac {
|
|
|
246
346
|
if (!blocs) {
|
|
247
347
|
return undefined;
|
|
248
348
|
}
|
|
249
|
-
//
|
|
349
|
+
// Find the specific bloc by ID within the isolated array
|
|
250
350
|
const found = blocs.find((b) => b._id === id) as InstanceType<B> | undefined;
|
|
251
351
|
return found;
|
|
252
352
|
}
|
|
253
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
|
+
|
|
254
363
|
/**
|
|
255
364
|
* Creates a new bloc instance and registers it in the appropriate registry
|
|
256
365
|
* @param blocClass - The bloc class to instantiate
|
|
@@ -259,7 +368,7 @@ export class Blac {
|
|
|
259
368
|
* @param instanceRef - Optional reference string for the instance
|
|
260
369
|
* @returns The newly created bloc instance
|
|
261
370
|
*/
|
|
262
|
-
createNewBlocInstance<B extends BlocConstructor<BlocBase<
|
|
371
|
+
createNewBlocInstance<B extends BlocConstructor<BlocBase<unknown>>>(
|
|
263
372
|
blocClass: B,
|
|
264
373
|
id: BlocInstanceId,
|
|
265
374
|
options: GetBlocOptions<InstanceType<B>> = {},
|
|
@@ -270,6 +379,9 @@ export class Blac {
|
|
|
270
379
|
newBloc.props = props || null;
|
|
271
380
|
newBloc._updateId(id);
|
|
272
381
|
|
|
382
|
+
// Set up disposal handler to break circular dependency
|
|
383
|
+
newBloc._setDisposalHandler((bloc) => this.disposeBloc(bloc));
|
|
384
|
+
|
|
273
385
|
if (newBloc.isIsolated) {
|
|
274
386
|
this.registerIsolatedBlocInstance(newBloc);
|
|
275
387
|
return newBloc as InstanceType<B>;
|
|
@@ -280,8 +392,8 @@ export class Blac {
|
|
|
280
392
|
}
|
|
281
393
|
|
|
282
394
|
|
|
283
|
-
activateBloc = (bloc: BlocBase<
|
|
284
|
-
const base = bloc.constructor as unknown as BlocConstructor<BlocBase<
|
|
395
|
+
activateBloc = (bloc: BlocBase<unknown>): void => {
|
|
396
|
+
const base = bloc.constructor as unknown as BlocConstructor<BlocBase<unknown>>;
|
|
285
397
|
const isIsolated = bloc.isIsolated;
|
|
286
398
|
|
|
287
399
|
let found = isIsolated ? this.findIsolatedBlocInstance(base, bloc._id) : this.findRegisteredBlocInstance(base, bloc._id);
|
|
@@ -296,7 +408,7 @@ export class Blac {
|
|
|
296
408
|
this.registerBlocInstance(bloc);
|
|
297
409
|
}
|
|
298
410
|
};
|
|
299
|
-
static activateBloc
|
|
411
|
+
static get activateBloc() { return Blac.instance.activateBloc; }
|
|
300
412
|
|
|
301
413
|
/**
|
|
302
414
|
* Gets or creates a bloc instance based on the provided class and options.
|
|
@@ -309,7 +421,7 @@ export class Blac {
|
|
|
309
421
|
* - instanceRef: Optional reference string for the instance
|
|
310
422
|
* @returns The bloc instance
|
|
311
423
|
*/
|
|
312
|
-
getBloc = <B extends BlocConstructor<BlocBase<
|
|
424
|
+
getBloc = <B extends BlocConstructor<BlocBase<unknown>>>(
|
|
313
425
|
blocClass: B,
|
|
314
426
|
options: GetBlocOptions<InstanceType<B>> = {},
|
|
315
427
|
): InstanceType<B> => {
|
|
@@ -348,7 +460,7 @@ export class Blac {
|
|
|
348
460
|
this.log(`[${blocClass.name}:${String(blocId)}] (getBloc) No existing instance found. Creating new one.`, options, bloc);
|
|
349
461
|
return bloc;
|
|
350
462
|
};
|
|
351
|
-
static getBloc
|
|
463
|
+
static get getBloc() { return Blac.instance.getBloc; }
|
|
352
464
|
|
|
353
465
|
/**
|
|
354
466
|
* Gets a bloc instance or throws an error if it doesn't exist
|
|
@@ -378,7 +490,7 @@ export class Blac {
|
|
|
378
490
|
}
|
|
379
491
|
throw new Error(`Bloc ${blocClass.name} not found`);
|
|
380
492
|
};
|
|
381
|
-
static getBlocOrThrow
|
|
493
|
+
static get getBlocOrThrow() { return Blac.instance.getBlocOrThrow; }
|
|
382
494
|
|
|
383
495
|
/**
|
|
384
496
|
* Gets all instances of a specific bloc class
|
|
@@ -413,4 +525,83 @@ export class Blac {
|
|
|
413
525
|
|
|
414
526
|
return results;
|
|
415
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; }
|
|
416
607
|
}
|
package/src/BlocBase.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Blac } from './Blac';
|
|
2
1
|
import { BlacObservable } from './BlacObserver';
|
|
3
2
|
import { BlocConstructor } from './types';
|
|
4
3
|
|
|
@@ -62,12 +61,6 @@ export abstract class BlocBase<
|
|
|
62
61
|
*/
|
|
63
62
|
public _observer: BlacObservable<S>;
|
|
64
63
|
|
|
65
|
-
/**
|
|
66
|
-
* @internal
|
|
67
|
-
* Reference to the global Blac manager instance.
|
|
68
|
-
*/
|
|
69
|
-
public _blac = Blac.getInstance();
|
|
70
|
-
|
|
71
64
|
/**
|
|
72
65
|
* The unique identifier for this Bloc instance.
|
|
73
66
|
* Defaults to the class name, but can be customized.
|
|
@@ -119,14 +112,14 @@ export abstract class BlocBase<
|
|
|
119
112
|
constructor(initialState: S) {
|
|
120
113
|
this._state = initialState;
|
|
121
114
|
this._observer = new BlacObservable(this);
|
|
122
|
-
Blac.log('Bloc Created', this)
|
|
123
115
|
this._id = this.constructor.name;
|
|
124
116
|
|
|
125
|
-
//
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
this.
|
|
117
|
+
// Access static properties safely with proper type checking
|
|
118
|
+
const Constructor = this.constructor as typeof BlocBase & BlocStaticProperties;
|
|
119
|
+
|
|
120
|
+
// Validate that the static properties exist and are boolean
|
|
121
|
+
this._keepAlive = typeof Constructor.keepAlive === 'boolean' ? Constructor.keepAlive : false;
|
|
122
|
+
this._isolated = typeof Constructor.isolated === 'boolean' ? Constructor.isolated : false;
|
|
130
123
|
}
|
|
131
124
|
|
|
132
125
|
/**
|
|
@@ -164,11 +157,17 @@ export abstract class BlocBase<
|
|
|
164
157
|
* Notifies the Blac manager and clears all observers.
|
|
165
158
|
*/
|
|
166
159
|
_dispose() {
|
|
160
|
+
// Clear all consumers
|
|
161
|
+
this._consumers.clear();
|
|
162
|
+
|
|
163
|
+
// Clear observer subscriptions
|
|
167
164
|
this._observer.clear();
|
|
165
|
+
|
|
166
|
+
// Call user-defined disposal hook
|
|
168
167
|
this.onDispose?.();
|
|
169
|
-
|
|
170
|
-
Blac
|
|
171
|
-
Blac.
|
|
168
|
+
|
|
169
|
+
// The Blac manager will handle removal from registry
|
|
170
|
+
// This method is called by Blac.disposeBloc, so we don't need to call it again
|
|
172
171
|
}
|
|
173
172
|
|
|
174
173
|
/**
|
|
@@ -183,18 +182,29 @@ export abstract class BlocBase<
|
|
|
183
182
|
*/
|
|
184
183
|
_consumers = new Set<string>();
|
|
185
184
|
|
|
185
|
+
/**
|
|
186
|
+
* @internal
|
|
187
|
+
* WeakSet to track consumer references for cleanup validation
|
|
188
|
+
*/
|
|
189
|
+
private _consumerRefs = new WeakSet<object>();
|
|
190
|
+
|
|
186
191
|
/**
|
|
187
192
|
* @internal
|
|
188
193
|
* Registers a new consumer to this Bloc instance.
|
|
189
194
|
* Notifies the Blac manager that a consumer has been added.
|
|
190
195
|
*
|
|
191
196
|
* @param consumerId The unique ID of the consumer being added
|
|
197
|
+
* @param consumerRef Optional reference to the consumer object for cleanup validation
|
|
192
198
|
*/
|
|
193
|
-
_addConsumer = (consumerId: string) => {
|
|
199
|
+
_addConsumer = (consumerId: string, consumerRef?: object) => {
|
|
194
200
|
if (this._consumers.has(consumerId)) return;
|
|
195
201
|
this._consumers.add(consumerId);
|
|
202
|
+
|
|
203
|
+
if (consumerRef) {
|
|
204
|
+
this._consumerRefs.add(consumerRef);
|
|
205
|
+
}
|
|
206
|
+
|
|
196
207
|
// this._blac.dispatchEvent(BlacLifecycleEvent.BLOC_CONSUMER_ADDED, this, { consumerId });
|
|
197
|
-
Blac.log('BlocBase._addConsumer', this, consumerId);
|
|
198
208
|
};
|
|
199
209
|
|
|
200
210
|
/**
|
|
@@ -208,11 +218,58 @@ export abstract class BlocBase<
|
|
|
208
218
|
if (!this._consumers.has(consumerId)) return;
|
|
209
219
|
this._consumers.delete(consumerId);
|
|
210
220
|
// this._blac.dispatchEvent(BlacLifecycleEvent.BLOC_CONSUMER_REMOVED, this, { consumerId });
|
|
211
|
-
|
|
221
|
+
|
|
222
|
+
// If no consumers remain and not keep-alive, schedule disposal
|
|
223
|
+
if (this._consumers.size === 0 && !this._keepAlive) {
|
|
224
|
+
this._scheduleDisposal();
|
|
225
|
+
}
|
|
212
226
|
};
|
|
213
227
|
|
|
228
|
+
/**
|
|
229
|
+
* @internal
|
|
230
|
+
* Handler function for disposal (can be set by Blac manager)
|
|
231
|
+
*/
|
|
232
|
+
private _disposalHandler?: (bloc: BlocBase<unknown>) => void;
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* @internal
|
|
236
|
+
* Sets the disposal handler for this bloc
|
|
237
|
+
*/
|
|
238
|
+
_setDisposalHandler(handler: (bloc: BlocBase<unknown>) => void) {
|
|
239
|
+
this._disposalHandler = handler;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* @internal
|
|
244
|
+
* Schedules disposal of this bloc instance if it has no consumers
|
|
245
|
+
*/
|
|
246
|
+
private _scheduleDisposal() {
|
|
247
|
+
// Use setTimeout to avoid disposal during render cycles
|
|
248
|
+
setTimeout(() => {
|
|
249
|
+
if (this._consumers.size === 0 && !this._keepAlive) {
|
|
250
|
+
if (this._disposalHandler) {
|
|
251
|
+
this._disposalHandler(this as any);
|
|
252
|
+
} else {
|
|
253
|
+
this._dispose();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}, 0);
|
|
257
|
+
}
|
|
258
|
+
|
|
214
259
|
lastUpdate = Date.now();
|
|
215
260
|
|
|
261
|
+
/**
|
|
262
|
+
* @internal
|
|
263
|
+
* Flag to indicate if batching is enabled for this bloc
|
|
264
|
+
*/
|
|
265
|
+
private _batchingEnabled = false;
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* @internal
|
|
269
|
+
* Pending state updates when batching is enabled
|
|
270
|
+
*/
|
|
271
|
+
private _pendingUpdates: Array<{newState: S, oldState: S, action?: unknown}> = [];
|
|
272
|
+
|
|
216
273
|
/**
|
|
217
274
|
* @internal
|
|
218
275
|
* Updates the state and notifies all observers of the change.
|
|
@@ -222,8 +279,59 @@ export abstract class BlocBase<
|
|
|
222
279
|
* @param action Optional metadata about what caused the state change
|
|
223
280
|
*/
|
|
224
281
|
_pushState = (newState: S, oldState: S, action?: unknown): void => {
|
|
282
|
+
// Runtime validation for state changes
|
|
283
|
+
if (newState === undefined) {
|
|
284
|
+
console.warn('BlocBase._pushState: newState is undefined', this);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Validate action type if provided
|
|
289
|
+
if (action !== undefined && action !== null) {
|
|
290
|
+
const actionType = typeof action;
|
|
291
|
+
if (!(['string', 'object', 'number'].includes(actionType))) {
|
|
292
|
+
console.warn('BlocBase._pushState: Invalid action type', this, action);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// If batching is enabled, queue the update
|
|
297
|
+
if (this._batchingEnabled) {
|
|
298
|
+
this._pendingUpdates.push({ newState, oldState, action });
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
this._oldState = oldState;
|
|
225
303
|
this._state = newState;
|
|
226
304
|
this._observer.notify(newState, oldState, action);
|
|
227
305
|
this.lastUpdate = Date.now();
|
|
228
306
|
};
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Enables batching for multiple state updates
|
|
310
|
+
* @param batchFn Function to execute with batching enabled
|
|
311
|
+
*/
|
|
312
|
+
batch = <T>(batchFn: () => T): T => {
|
|
313
|
+
const wasBatching = this._batchingEnabled;
|
|
314
|
+
this._batchingEnabled = true;
|
|
315
|
+
|
|
316
|
+
try {
|
|
317
|
+
const result = batchFn();
|
|
318
|
+
|
|
319
|
+
// Process all pending updates
|
|
320
|
+
if (this._pendingUpdates.length > 0) {
|
|
321
|
+
const lastUpdate = this._pendingUpdates[this._pendingUpdates.length - 1];
|
|
322
|
+
this._oldState = this._pendingUpdates[0].oldState;
|
|
323
|
+
this._state = lastUpdate.newState;
|
|
324
|
+
|
|
325
|
+
// Notify with the final state
|
|
326
|
+
this._observer.notify(lastUpdate.newState, this._oldState, lastUpdate.action);
|
|
327
|
+
this.lastUpdate = Date.now();
|
|
328
|
+
|
|
329
|
+
this._pendingUpdates = [];
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return result;
|
|
333
|
+
} finally {
|
|
334
|
+
this._batchingEnabled = wasBatching;
|
|
335
|
+
}
|
|
336
|
+
};
|
|
229
337
|
}
|