@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/src/Blac.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { BlacPlugin } from "./BlacPlugin";
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<any>> {
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
- * Enum representing different lifecycle events that can occur in the Blac system.
30
- * These events are used to track the lifecycle of blocs and their consumers.
30
+ * Interface for Blac instance management
31
31
  */
32
- export enum BlacLifecycleEvent {
33
- BLOC_DISPOSED = "BLOC_DISPOSED",
34
- BLOC_CREATED = "BLOC_CREATED",
35
- LISTENER_REMOVED = "LISTENER_REMOVED",
36
- LISTENER_ADDED = "LISTENER_ADDED",
37
- STATE_CHANGED = "STATE_CHANGED",
38
- BLOC_CONSUMER_REMOVED = "BLOC_CONSUMER_REMOVED",
39
- BLOC_CONSUMER_ADDED = "BLOC_CONSUMER_ADDED",
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
- * Implements a singleton pattern to ensure only one instance exists.
45
- * Handles bloc lifecycle, plugin management, and instance tracking.
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
- /** The singleton instance of Blac */
55
- static instance: Blac = new 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 = Blac.instance.getAllBlocs;
59
- static addPlugin = Blac.instance.addPlugin;
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<any>> = new Map();
100
+ blocInstanceMap: Map<string, BlocBase<unknown>> = new Map();
62
101
  /** Map storing isolated bloc instances grouped by their constructor */
63
- isolatedBlocMap: Map<BlocConstructor<any>, BlocBase<any>[]> = new Map();
64
- pluginList: BlacPlugin[] = [];
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
- Blac.instance = this;
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 = Blac.instance.log;
134
+ static get log() { return Blac.instance.log; }
91
135
 
92
136
  /**
93
- * Gets the singleton instance of Blac
137
+ * Gets the current Blac instance
94
138
  * @returns The Blac instance
95
139
  */
96
140
  static getInstance(): Blac {
97
- return Blac.instance;
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 = Blac.instance.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 = Blac.instance.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._dispose();
180
+ if (!bloc._keepAlive) {
181
+ bloc._dispose();
182
+ }
137
183
  });
138
184
 
139
185
  oldIsolatedBlocMap.forEach((blocArray) => {
140
186
  blocArray.forEach((bloc) => {
141
- bloc._dispose();
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
- // Create and assign the new instance
149
- Blac.instance = new Blac({
150
- __unsafe_ignore_singleton: true,
151
- });
197
+ // Use instance manager to reset
198
+ instanceManager.resetInstance();
152
199
  }
153
- static resetInstance = Blac.instance.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<any>): void => {
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.dispatchEventToPlugins(BlacLifecycleEvent.BLOC_DISPOSED, bloc);
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<any>): void {
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<any>): void {
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
- if (found) {
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<any>): void {
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<any>): void {
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._id === bloc._id);
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) return undefined;
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<any>>>(
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<any>>>(
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
- this.log(`[${blocClass.name}:${String(blocId)}] No existing instance found. Creating new one.`);
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 = Blac.instance.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 = Blac.instance.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
  }
@@ -1,4 +1,4 @@
1
- import { Blac, BlacLifecycleEvent } from './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
- * Disposes of all observers and clears the observer set
127
+ * Clears the observer set
122
128
  */
123
- dispose() {
129
+ clear() {
124
130
  this._observers.forEach((observer) => {
125
131
  this.unsubscribe(observer);
126
132
  });