@blac/core 2.0.0-rc-16 → 2.0.0-rc.1

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.
Files changed (157) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/Blac.d.ts +246 -0
  3. package/dist/Blac.d.ts.map +1 -0
  4. package/dist/Blac.js +624 -0
  5. package/dist/Blac.js.map +1 -0
  6. package/dist/Bloc.d.ts +41 -0
  7. package/dist/Bloc.d.ts.map +1 -0
  8. package/dist/Bloc.js +140 -0
  9. package/dist/Bloc.js.map +1 -0
  10. package/dist/BlocBase.d.ts +110 -0
  11. package/dist/BlocBase.d.ts.map +1 -0
  12. package/dist/BlocBase.js +287 -0
  13. package/dist/BlocBase.js.map +1 -0
  14. package/dist/Cubit.d.ts +25 -0
  15. package/dist/Cubit.d.ts.map +1 -0
  16. package/dist/Cubit.js +64 -0
  17. package/dist/Cubit.js.map +1 -0
  18. package/dist/adapter/BlacAdapter.d.ts +67 -0
  19. package/dist/adapter/BlacAdapter.d.ts.map +1 -0
  20. package/dist/adapter/BlacAdapter.js +263 -0
  21. package/dist/adapter/BlacAdapter.js.map +1 -0
  22. package/dist/adapter/ProxyFactory.d.ts +50 -0
  23. package/dist/adapter/ProxyFactory.d.ts.map +1 -0
  24. package/dist/adapter/ProxyFactory.js +150 -0
  25. package/dist/adapter/ProxyFactory.js.map +1 -0
  26. package/{src/adapter/index.ts → dist/adapter/index.d.ts} +1 -0
  27. package/dist/adapter/index.d.ts.map +1 -0
  28. package/dist/adapter/index.js +19 -0
  29. package/dist/adapter/index.js.map +1 -0
  30. package/dist/errors/BlacError.d.ts +38 -0
  31. package/dist/errors/BlacError.d.ts.map +1 -0
  32. package/dist/errors/BlacError.js +55 -0
  33. package/dist/errors/BlacError.js.map +1 -0
  34. package/dist/errors/ErrorManager.d.ts +39 -0
  35. package/dist/errors/ErrorManager.d.ts.map +1 -0
  36. package/dist/errors/ErrorManager.js +91 -0
  37. package/dist/errors/ErrorManager.js.map +1 -0
  38. package/dist/errors/handleError.d.ts +24 -0
  39. package/dist/errors/handleError.d.ts.map +1 -0
  40. package/dist/errors/handleError.js +62 -0
  41. package/dist/errors/handleError.js.map +1 -0
  42. package/dist/errors/index.d.ts +5 -0
  43. package/dist/errors/index.d.ts.map +1 -0
  44. package/dist/errors/index.js +13 -0
  45. package/dist/errors/index.js.map +1 -0
  46. package/{src/events.ts → dist/events.d.ts} +4 -2
  47. package/dist/events.d.ts.map +1 -0
  48. package/dist/events.js +17 -0
  49. package/dist/events.js.map +1 -0
  50. package/dist/index.cjs +38 -0
  51. package/{src/index.ts → dist/index.d.ts} +1 -10
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +38 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/lifecycle/BlocLifecycle.d.ts +51 -0
  56. package/dist/lifecycle/BlocLifecycle.d.ts.map +1 -0
  57. package/dist/lifecycle/BlocLifecycle.js +123 -0
  58. package/dist/lifecycle/BlocLifecycle.js.map +1 -0
  59. package/dist/lifecycle/index.d.ts +2 -0
  60. package/dist/lifecycle/index.d.ts.map +1 -0
  61. package/dist/lifecycle/index.js +18 -0
  62. package/dist/lifecycle/index.js.map +1 -0
  63. package/dist/plugins/BlocPluginRegistry.d.ts +58 -0
  64. package/dist/plugins/BlocPluginRegistry.d.ts.map +1 -0
  65. package/dist/plugins/BlocPluginRegistry.js +201 -0
  66. package/dist/plugins/BlocPluginRegistry.js.map +1 -0
  67. package/dist/plugins/SystemPluginRegistry.d.ts +79 -0
  68. package/dist/plugins/SystemPluginRegistry.d.ts.map +1 -0
  69. package/dist/plugins/SystemPluginRegistry.js +184 -0
  70. package/dist/plugins/SystemPluginRegistry.js.map +1 -0
  71. package/{src/plugins/index.ts → dist/plugins/index.d.ts} +1 -5
  72. package/dist/plugins/index.d.ts.map +1 -0
  73. package/dist/plugins/index.js +26 -0
  74. package/dist/plugins/index.js.map +1 -0
  75. package/dist/plugins/types.d.ts +103 -0
  76. package/dist/plugins/types.d.ts.map +1 -0
  77. package/dist/plugins/types.js +3 -0
  78. package/dist/plugins/types.js.map +1 -0
  79. package/dist/subscription/SubscriptionManager.d.ts +67 -0
  80. package/dist/subscription/SubscriptionManager.d.ts.map +1 -0
  81. package/dist/subscription/SubscriptionManager.js +317 -0
  82. package/dist/subscription/SubscriptionManager.js.map +1 -0
  83. package/{src/subscription/index.ts → dist/subscription/index.d.ts} +1 -0
  84. package/dist/subscription/index.d.ts.map +1 -0
  85. package/dist/subscription/index.js +19 -0
  86. package/dist/subscription/index.js.map +1 -0
  87. package/dist/subscription/types.d.ts +65 -0
  88. package/dist/subscription/types.d.ts.map +1 -0
  89. package/dist/subscription/types.js +3 -0
  90. package/dist/subscription/types.js.map +1 -0
  91. package/dist/testing.d.ts +33 -0
  92. package/dist/testing.d.ts.map +1 -0
  93. package/dist/testing.js +68 -0
  94. package/dist/testing.js.map +1 -0
  95. package/{src/types.ts → dist/types.d.ts} +11 -28
  96. package/dist/types.d.ts.map +1 -0
  97. package/dist/types.js +3 -0
  98. package/dist/types.js.map +1 -0
  99. package/dist/utils/BatchingManager.d.ts +28 -0
  100. package/dist/utils/BatchingManager.d.ts.map +1 -0
  101. package/dist/utils/BatchingManager.js +48 -0
  102. package/dist/utils/BatchingManager.js.map +1 -0
  103. package/dist/utils/RerenderLogger.d.ts +60 -0
  104. package/dist/utils/RerenderLogger.d.ts.map +1 -0
  105. package/dist/utils/RerenderLogger.js +205 -0
  106. package/dist/utils/RerenderLogger.js.map +1 -0
  107. package/dist/utils/generateInstanceId.d.ts +11 -0
  108. package/dist/utils/generateInstanceId.d.ts.map +1 -0
  109. package/dist/utils/generateInstanceId.js +50 -0
  110. package/dist/utils/generateInstanceId.js.map +1 -0
  111. package/dist/utils/shallowEqual.d.ts +9 -0
  112. package/dist/utils/shallowEqual.d.ts.map +1 -0
  113. package/dist/utils/shallowEqual.js +35 -0
  114. package/dist/utils/shallowEqual.js.map +1 -0
  115. package/dist/utils/uuid.d.ts +10 -0
  116. package/dist/utils/uuid.d.ts.map +1 -0
  117. package/dist/utils/uuid.js +24 -0
  118. package/dist/utils/uuid.js.map +1 -0
  119. package/package.json +19 -6
  120. package/src/Blac.ts +0 -802
  121. package/src/Bloc.ts +0 -180
  122. package/src/BlocBase.ts +0 -398
  123. package/src/Cubit.ts +0 -68
  124. package/src/__tests__/Blac.config.test.ts +0 -79
  125. package/src/__tests__/Bloc.event.test.ts +0 -394
  126. package/src/__tests__/BlocBase.subscription.test.ts +0 -349
  127. package/src/__tests__/Cubit.test.ts +0 -462
  128. package/src/__tests__/memory-leaks.test.ts +0 -383
  129. package/src/__tests__/plugins.test.ts +0 -464
  130. package/src/adapter/BlacAdapter.ts +0 -467
  131. package/src/adapter/ProxyFactory.ts +0 -249
  132. package/src/adapter/__tests__/BlacAdapter.test.ts +0 -555
  133. package/src/adapter/__tests__/ProxyFactory.test.ts +0 -655
  134. package/src/errors/BlacError.ts +0 -69
  135. package/src/errors/ErrorManager.ts +0 -101
  136. package/src/errors/handleError.ts +0 -114
  137. package/src/errors/index.ts +0 -13
  138. package/src/lifecycle/BlocLifecycle.ts +0 -156
  139. package/src/lifecycle/index.ts +0 -1
  140. package/src/plugins/BlocPluginRegistry.ts +0 -240
  141. package/src/plugins/SystemPluginRegistry.ts +0 -242
  142. package/src/plugins/__tests__/BlocPluginRegistry.test.ts +0 -450
  143. package/src/plugins/__tests__/SystemPluginRegistry.test.ts +0 -404
  144. package/src/plugins/types.ts +0 -129
  145. package/src/subscription/README.md +0 -132
  146. package/src/subscription/SubscriptionManager.ts +0 -395
  147. package/src/subscription/__tests__/SubscriptionManager.test.ts +0 -355
  148. package/src/subscription/types.ts +0 -90
  149. package/src/testing.ts +0 -246
  150. package/src/utils/BatchingManager.ts +0 -59
  151. package/src/utils/RerenderLogger.ts +0 -265
  152. package/src/utils/__tests__/generateInstanceId.test.ts +0 -104
  153. package/src/utils/__tests__/shallowEqual.test.ts +0 -171
  154. package/src/utils/__tests__/uuid.test.ts +0 -144
  155. package/src/utils/generateInstanceId.ts +0 -54
  156. package/src/utils/shallowEqual.ts +0 -40
  157. package/src/utils/uuid.ts +0 -22
package/src/Blac.ts DELETED
@@ -1,802 +0,0 @@
1
- import { BlocBase, BlocInstanceId } from './BlocBase';
2
- import { BlocLifecycleState } from './lifecycle/BlocLifecycle';
3
- import {
4
- BlocBaseAbstract,
5
- BlocConstructor,
6
- BlocHookDependencyArrayFn,
7
- BlocState,
8
- } from './types';
9
- import { SystemPluginRegistry } from './plugins/SystemPluginRegistry';
10
- import { BlacError, ErrorCategory, ErrorSeverity } from './errors/BlacError';
11
- import { ErrorManager } from './errors/ErrorManager';
12
-
13
- /**
14
- * Configuration options for the Blac instance
15
- */
16
- export interface BlacConfig {
17
- /**
18
- * Whether to enable proxy dependency tracking for automatic re-render optimization.
19
- * When false, state changes always cause re-renders unless dependencies are manually specified.
20
- * Default: true
21
- */
22
- proxyDependencyTracking?: boolean;
23
- }
24
-
25
- export interface GetBlocOptions<B extends BlocBase<unknown>> {
26
- id?: string;
27
- selector?: BlocHookDependencyArrayFn<BlocState<B>>;
28
- constructorParams?: ConstructorParameters<BlocConstructor<B>>[];
29
- onMount?: (bloc: B) => void;
30
- instanceRef?: string;
31
- throwIfNotFound?: boolean;
32
- /** Force creation of a new instance for isolated blocs, bypassing instance lookup */
33
- forceNewInstance?: boolean;
34
- }
35
-
36
- /**
37
- * Interface for Blac instance management
38
- */
39
- export interface BlacInstanceManager {
40
- getInstance(): Blac;
41
- setInstance(instance: Blac): void;
42
- resetInstance(): void;
43
- }
44
-
45
- /**
46
- * Default singleton implementation of BlacInstanceManager
47
- */
48
- class SingletonBlacManager implements BlacInstanceManager {
49
- private static _instance: Blac;
50
-
51
- getInstance(): Blac {
52
- if (!SingletonBlacManager._instance) {
53
- SingletonBlacManager._instance = new Blac({
54
- __unsafe_ignore_singleton: true,
55
- });
56
- }
57
- return SingletonBlacManager._instance;
58
- }
59
-
60
- setInstance(instance: Blac): void {
61
- SingletonBlacManager._instance = instance;
62
- }
63
-
64
- resetInstance(): void {
65
- const oldInstance = SingletonBlacManager._instance;
66
- SingletonBlacManager._instance = new Blac({
67
- __unsafe_ignore_singleton: true,
68
- });
69
-
70
- // Transfer any keep-alive blocs to the new instance
71
- if (oldInstance) {
72
- for (const bloc of oldInstance.keepAliveBlocs) {
73
- SingletonBlacManager._instance.keepAliveBlocs.add(bloc);
74
- SingletonBlacManager._instance.uidRegistry.set(bloc.uid, bloc);
75
- }
76
- }
77
- }
78
- }
79
-
80
- /**
81
- * Global instance manager (can be replaced for testing or different patterns)
82
- */
83
- let instanceManager: BlacInstanceManager = new SingletonBlacManager();
84
-
85
- /**
86
- * Sets a custom instance manager (useful for testing or advanced patterns)
87
- */
88
- export function setBlacInstanceManager(manager: BlacInstanceManager): void {
89
- instanceManager = manager;
90
- }
91
-
92
- /**
93
- * Main Blac class that manages the state management system.
94
- * Can work with singleton pattern or dependency injection.
95
- * Handles bloc lifecycle, and instance tracking.
96
- *
97
- * Key responsibilities:
98
- * - Managing bloc instances (creation, disposal, lookup)
99
- * - Handling isolated and non-isolated blocs
100
- * - Providing logging and debugging capabilities
101
- */
102
- export class Blac {
103
- static get instance(): Blac {
104
- return instanceManager.getInstance();
105
- }
106
- /** Timestamp when the instance was created */
107
- createdAt = Date.now();
108
- private errorManager = ErrorManager.getInstance();
109
- static get getAllBlocs() {
110
- return Blac.instance.getAllBlocs;
111
- }
112
-
113
- /** Private static configuration */
114
- private static _config: BlacConfig = {
115
- proxyDependencyTracking: true,
116
- };
117
-
118
- /** Get current configuration */
119
- static get config(): Readonly<BlacConfig> {
120
- return { ...this._config };
121
- }
122
-
123
- /**
124
- * Set or update Blac configuration
125
- * @param config - Partial configuration to merge with existing config
126
- * @throws Error if configuration is invalid
127
- */
128
- static setConfig(config: Partial<BlacConfig>): void {
129
- // Validate config
130
- if (
131
- config.proxyDependencyTracking !== undefined &&
132
- typeof config.proxyDependencyTracking !== 'boolean'
133
- ) {
134
- const error = new BlacError(
135
- 'BlacConfig.proxyDependencyTracking must be a boolean',
136
- ErrorCategory.VALIDATION,
137
- ErrorSeverity.FATAL,
138
- );
139
- this.instance.errorManager.handle(error);
140
- }
141
-
142
- // Merge with existing config
143
- this._config = {
144
- ...this._config,
145
- ...config,
146
- };
147
-
148
- // Log config update if logging is enabled
149
- if (this.enableLog) {
150
- this.instance.log('Blac config updated:', this._config);
151
- }
152
- }
153
- /** Map storing all registered bloc instances by their class name and ID */
154
- blocInstanceMap: Map<string, BlocBase<unknown>> = new Map();
155
- /** Map storing isolated bloc instances grouped by their constructor */
156
- // Using BlocConstructor<BlocBase<unknown>> would break type inference when storing
157
- // different bloc types in the same map. The 'any' allows proper polymorphic storage
158
- // while maintaining type safety at usage sites through the BlocConstructor constraint.
159
- isolatedBlocMap: Map<BlocConstructor<any>, BlocBase<unknown>[]> = new Map();
160
- /** Map for O(1) lookup of isolated blocs by UID */
161
- isolatedBlocIndex: Map<string, BlocBase<unknown>> = new Map();
162
- /** Map tracking UIDs to prevent memory leaks */
163
- uidRegistry: Map<string, BlocBase<unknown>> = new Map();
164
- /** Set of keep-alive blocs for controlled cleanup */
165
- keepAliveBlocs: Set<BlocBase<unknown>> = new Set();
166
- /** Flag to control whether changes should be posted to document */
167
- postChangesToDocument = false;
168
- /** System plugin registry */
169
- readonly plugins = new SystemPluginRegistry();
170
-
171
- /**
172
- * Creates a new Blac instance.
173
- * @param options - Configuration options including singleton control
174
- */
175
- constructor(options: { __unsafe_ignore_singleton?: boolean } = {}) {
176
- const { __unsafe_ignore_singleton = false } = options;
177
- if (!__unsafe_ignore_singleton) {
178
- return Blac.instance;
179
- }
180
- instanceManager.setInstance(this);
181
-
182
- // Bootstrap plugins on creation
183
- this.plugins.bootstrap();
184
- }
185
-
186
- /** Flag to enable/disable logging */
187
- static enableLog = false;
188
- static logLevel: 'warn' | 'log' = 'warn';
189
- static logSpy: ((...args: unknown[]) => void) | null = null;
190
-
191
- /**
192
- * Logs messages to console when logging is enabled
193
- * @param args - Arguments to log
194
- */
195
- log = (...args: unknown[]) => {
196
- if (Blac.logSpy) {
197
- Blac.logSpy(args);
198
- }
199
- if (Blac.enableLog && Blac.logLevel === 'warn') {
200
- // Logging disabled - console.warn removed
201
- }
202
- if (Blac.enableLog && Blac.logLevel === 'log') {
203
- // Logging disabled - console.log removed
204
- }
205
- };
206
- static get log() {
207
- return Blac.instance.log;
208
- }
209
-
210
- /**
211
- * Gets the current Blac instance
212
- * @returns The Blac instance
213
- */
214
- static getInstance(): Blac {
215
- return instanceManager.getInstance();
216
- }
217
-
218
- /**
219
- * Logs a warning message
220
- * @param message - Warning message
221
- * @param args - Additional arguments
222
- */
223
- warn = (message: string, ...args: unknown[]) => {
224
- console.warn(message, ...args);
225
- };
226
- static get warn() {
227
- return Blac.instance.warn;
228
- }
229
- /**
230
- * Logs an error message
231
- * @param message - Error message
232
- * @param args - Additional arguments
233
- */
234
- error = (message: string, ...args: unknown[]) => {
235
- if (Blac.enableLog) {
236
- console.error(message, ...args);
237
- }
238
- };
239
- static get error() {
240
- return Blac.instance.error;
241
- }
242
-
243
- /**
244
- * Resets the Blac instance to a new one, disposing non-keepAlive blocs
245
- * from the old instance.
246
- */
247
- resetInstance = (): void => {
248
- this.log('Reset Blac instance');
249
-
250
- // Create snapshots to avoid concurrent modification issues
251
- const oldBlocInstanceMap = new Map(this.blocInstanceMap);
252
- const oldIsolatedBlocMap = new Map(this.isolatedBlocMap);
253
-
254
- // Dispose non-keepAlive blocs from the current instance
255
- // Use disposeBloc method to ensure proper cleanup
256
- oldBlocInstanceMap.forEach((bloc) => {
257
- // TODO: Type assertion for private property access (see explanation above)
258
- if (
259
- !bloc._keepAlive &&
260
- (bloc as any)._disposalState === BlocLifecycleState.ACTIVE
261
- ) {
262
- this.disposeBloc(bloc);
263
- }
264
- });
265
-
266
- oldIsolatedBlocMap.forEach((blocArray) => {
267
- blocArray.forEach((bloc) => {
268
- // TODO: Type assertion for private property access (see explanation above)
269
- if (
270
- !bloc._keepAlive &&
271
- (bloc as any)._disposalState === BlocLifecycleState.ACTIVE
272
- ) {
273
- this.disposeBloc(bloc);
274
- }
275
- });
276
- });
277
-
278
- // Clear registries (keep-alive blocs will be re-added by instance manager)
279
- this.blocInstanceMap.clear();
280
- this.isolatedBlocMap.clear();
281
- this.isolatedBlocIndex.clear();
282
-
283
- // Use instance manager to reset
284
- instanceManager.resetInstance();
285
- };
286
- static resetInstance = (): void => {
287
- instanceManager.resetInstance();
288
- };
289
-
290
- /**
291
- * Disposes of a bloc instance by removing it from the appropriate registry
292
- * @param bloc - The bloc instance to dispose
293
- */
294
- disposeBloc = (bloc: BlocBase<unknown>): void => {
295
- // Check if bloc is already disposed to prevent double disposal
296
- // TODO: Type assertion needed to access private _disposalState property from external class.
297
- // This is safe because we know BlocBase has this property, but TypeScript can't verify
298
- // private property access across class boundaries. Alternative would be to make
299
- // _disposalState protected, but that would expose internal implementation details.
300
- const currentState = (bloc as any)._disposalState;
301
- const validStatesForDisposal = [
302
- BlocLifecycleState.ACTIVE,
303
- BlocLifecycleState.DISPOSAL_REQUESTED,
304
- ];
305
-
306
- if (!validStatesForDisposal.includes(currentState)) {
307
- this.log(
308
- `[${bloc._name}:${String(bloc._id)}] disposeBloc called on bloc in invalid state: ${currentState}`,
309
- );
310
- return;
311
- }
312
-
313
- const base = bloc.constructor as unknown as BlocBaseAbstract;
314
- const key = this.createBlocInstanceMapKey(bloc._name, bloc._id);
315
- this.log(
316
- `[${bloc._name}:${String(bloc._id)}] disposeBloc called. Isolated: ${String(base.isolated)}`,
317
- );
318
-
319
- // First dispose the bloc to prevent further operations
320
- bloc.dispose();
321
-
322
- // Notify plugins of bloc disposal
323
- this.plugins.notifyBlocDisposed(bloc);
324
-
325
- // Then clean up from registries
326
- if (base.isolated) {
327
- this.unregisterIsolatedBlocInstance(bloc);
328
- this.blocInstanceMap.delete(key);
329
- } else {
330
- this.unregisterBlocInstance(bloc);
331
- }
332
-
333
- this.log('dispatched bloc', bloc);
334
- };
335
- static get disposeBloc() {
336
- return Blac.instance.disposeBloc;
337
- }
338
-
339
- /**
340
- * Creates a unique key for a bloc instance in the map based on the bloc class name and instance ID
341
- * @param blocClassName - The name of the bloc class
342
- * @param id - The instance ID
343
- * @returns A unique key string in the format "className:id"
344
- */
345
- createBlocInstanceMapKey(blocClassName: string, id: BlocInstanceId): string {
346
- return `${blocClassName}:${String(id)}`;
347
- }
348
-
349
- /**
350
- * Unregister a bloc instance from the main registry
351
- * @param bloc - The bloc instance to unregister
352
- */
353
- unregisterBlocInstance(bloc: BlocBase<unknown>): void {
354
- const key = this.createBlocInstanceMapKey(bloc._name, bloc._id);
355
- this.blocInstanceMap.delete(key);
356
-
357
- // Clean up UID tracking
358
- this.uidRegistry.delete(bloc.uid);
359
-
360
- // Remove from keep-alive set
361
- this.keepAliveBlocs.delete(bloc);
362
- }
363
-
364
- /**
365
- * Registers a bloc instance in the main registry
366
- * @param bloc - The bloc instance to register
367
- */
368
- registerBlocInstance(bloc: BlocBase<unknown>): void {
369
- const key = this.createBlocInstanceMapKey(bloc._name, bloc._id);
370
- this.blocInstanceMap.set(key, bloc);
371
-
372
- // Track UID for cleanup
373
- this.uidRegistry.set(bloc.uid, bloc);
374
-
375
- // Track keep-alive blocs
376
- if (bloc._keepAlive) {
377
- this.keepAliveBlocs.add(bloc);
378
- }
379
- }
380
-
381
- /**
382
- * Finds a registered bloc instance by its class and ID
383
- * @param blocClass - The bloc class to search for
384
- * @param id - The instance ID
385
- * @returns The found bloc instance or undefined if not found
386
- */
387
- findRegisteredBlocInstance<B extends BlocConstructor<any>>(
388
- blocClass: B,
389
- id: BlocInstanceId,
390
- ): InstanceType<B> | undefined {
391
- const base = blocClass as unknown as BlocBaseAbstract;
392
- if (base.isolated) return undefined;
393
-
394
- const key = this.createBlocInstanceMapKey(blocClass.name, id);
395
- const found = this.blocInstanceMap.get(key) as InstanceType<B> | undefined;
396
-
397
- // Don't return disposed blocs
398
- if (found && (found as any).isDisposed) {
399
- return undefined;
400
- }
401
-
402
- return found;
403
- }
404
-
405
- /**
406
- * Registers an isolated bloc instance in the isolated registry
407
- * @param bloc - The isolated bloc instance to register
408
- */
409
- registerIsolatedBlocInstance(bloc: BlocBase<unknown>): void {
410
- const blocClass = bloc.constructor as BlocConstructor<any>;
411
- const blocs = this.isolatedBlocMap.get(blocClass);
412
- if (blocs) {
413
- blocs.push(bloc);
414
- } else {
415
- this.isolatedBlocMap.set(blocClass, [bloc]);
416
- }
417
-
418
- // Add to isolated index for O(1) lookups
419
- this.isolatedBlocIndex.set(bloc.uid, bloc);
420
-
421
- // Track UID for cleanup
422
- this.uidRegistry.set(bloc.uid, bloc);
423
-
424
- // Track keep-alive blocs
425
- if (bloc._keepAlive) {
426
- this.keepAliveBlocs.add(bloc);
427
- }
428
- }
429
-
430
- /**
431
- * Unregister an isolated bloc instance from the isolated registry
432
- * @param bloc - The isolated bloc instance to unregister
433
- */
434
- unregisterIsolatedBlocInstance(bloc: BlocBase<unknown>): void {
435
- const blocClass = bloc.constructor;
436
- const blocs = this.isolatedBlocMap.get(blocClass as BlocConstructor<any>);
437
-
438
- // Ensure both data structures are synchronized
439
- let wasRemoved = false;
440
-
441
- if (blocs) {
442
- const index = blocs.findIndex((b) => b.uid === bloc.uid);
443
- if (index !== -1) {
444
- blocs.splice(index, 1);
445
- wasRemoved = true;
446
- }
447
-
448
- if (blocs.length === 0) {
449
- this.isolatedBlocMap.delete(blocClass as BlocConstructor<any>);
450
- }
451
- }
452
-
453
- // Always try to remove from isolated index, even if not found in map
454
- const wasInIndex = this.isolatedBlocIndex.delete(bloc.uid);
455
-
456
- // Clean up UID tracking
457
- this.uidRegistry.delete(bloc.uid);
458
-
459
- // Remove from keep-alive set
460
- this.keepAliveBlocs.delete(bloc);
461
-
462
- // Log inconsistency for debugging
463
- if (wasRemoved !== wasInIndex) {
464
- this.warn(
465
- `[Blac] Inconsistent state detected during isolated bloc cleanup for ${bloc._name}:${bloc.uid}. ` +
466
- `Map removal: ${wasRemoved}, Index removal: ${wasInIndex}`,
467
- );
468
- }
469
- }
470
-
471
- /**
472
- * Finds an isolated bloc instance by its class and ID (O(n) lookup)
473
- */
474
- findIsolatedBlocInstance<B extends BlocConstructor<any>>(
475
- blocClass: B,
476
- id: BlocInstanceId,
477
- ): InstanceType<B> | undefined {
478
- const base = blocClass as unknown as BlocBaseAbstract;
479
- if (!base.isolated) return undefined;
480
-
481
- const blocs = this.isolatedBlocMap.get(blocClass);
482
- if (!blocs) {
483
- return undefined;
484
- }
485
- // Find the specific bloc by ID within the isolated array
486
- const found = blocs.find((b) => b._id === id && !(b as any).isDisposed) as
487
- | InstanceType<B>
488
- | undefined;
489
- return found;
490
- }
491
-
492
- /**
493
- * Finds an isolated bloc instance by UID (O(1) lookup)
494
- */
495
- findIsolatedBlocInstanceByUid<B extends BlocBase<unknown>>(
496
- uid: string,
497
- ): B | undefined {
498
- return this.isolatedBlocIndex.get(uid) as B | undefined;
499
- }
500
-
501
- /**
502
- * Creates a new bloc instance and registers it in the appropriate registry
503
- * @param blocClass - The bloc class to instantiate
504
- * @param id - The instance ID
505
- * @param props - Properties to pass to the bloc constructor
506
- * @param instanceRef - Optional reference string for the instance
507
- * @returns The newly created bloc instance
508
- */
509
- createNewBlocInstance<B extends BlocConstructor<BlocBase<any>>>(
510
- blocClass: B,
511
- id: BlocInstanceId,
512
- options: GetBlocOptions<InstanceType<B>> = {},
513
- ): InstanceType<B> {
514
- const { constructorParams, instanceRef } = options;
515
- const newBloc = new blocClass(constructorParams) as InstanceType<B>;
516
- newBloc.blacInstance = this;
517
- newBloc._instanceRef = instanceRef;
518
- newBloc._id = id;
519
-
520
- // Set up disposal handler to break circular dependency
521
- newBloc.setDisposalHandler((bloc) => this.disposeBloc(bloc));
522
-
523
- if (newBloc.isIsolated) {
524
- this.registerIsolatedBlocInstance(newBloc);
525
- } else {
526
- this.registerBlocInstance(newBloc);
527
- }
528
-
529
- // Plugins are activated in constructor
530
-
531
- // Notify system plugins of bloc creation
532
- this.plugins.notifyBlocCreated(newBloc);
533
-
534
- return newBloc;
535
- }
536
-
537
- activateBloc = (bloc: BlocBase<unknown>): void => {
538
- // Don't activate disposed blocs
539
- if ((bloc as any)._disposalState !== BlocLifecycleState.ACTIVE) {
540
- this.log(
541
- `[${bloc._name}:${String(bloc._id)}] activateBloc called on disposed bloc. Ignoring.`,
542
- );
543
- return;
544
- }
545
-
546
- const base = bloc.constructor as unknown as BlocConstructor<BlocBase<any>>;
547
- const isIsolated = bloc.isIsolated;
548
-
549
- let found = isIsolated
550
- ? this.findIsolatedBlocInstance(base, bloc._id)
551
- : this.findRegisteredBlocInstance(base, bloc._id);
552
- if (found) {
553
- return;
554
- }
555
-
556
- this.log(
557
- `[${bloc._name}:${String(bloc._id)}] activateBloc called. Isolated: ${String(bloc.isIsolated)}`,
558
- );
559
- if (bloc.isIsolated) {
560
- this.registerIsolatedBlocInstance(bloc);
561
- } else {
562
- this.registerBlocInstance(bloc);
563
- }
564
- };
565
- static get activateBloc() {
566
- return Blac.instance.activateBloc;
567
- }
568
-
569
- /**
570
- * Gets or creates a bloc instance based on the provided class and options.
571
- * If a bloc with the given ID exists, it will be returned. Otherwise, a new instance will be created.
572
- *
573
- * @param blocClass - The bloc class to get or create
574
- * @param options - Options including:
575
- * - id: The instance ID (defaults to class name if not provided)
576
- * - props: Properties to pass to the bloc constructor
577
- * - instanceRef: Optional reference string for the instance
578
- * @returns The bloc instance
579
- */
580
- getBloc = <B extends BlocConstructor<BlocBase<any>>>(
581
- blocClass: B,
582
- options: GetBlocOptions<InstanceType<B>> = {},
583
- ): InstanceType<B> => {
584
- const { id, forceNewInstance } = options;
585
- const base = blocClass as unknown as BlocBaseAbstract;
586
- const blocId = id ?? blocClass.name;
587
- this.log(`[${blocClass.name}:${String(blocId)}] (getBloc) Called:`, {
588
- options,
589
- base,
590
- });
591
-
592
- // Skip instance lookup if forceNewInstance is true for isolated blocs
593
- if (!forceNewInstance) {
594
- if (base.isolated) {
595
- const isolatedBloc = this.findIsolatedBlocInstance<B>(
596
- blocClass,
597
- options.instanceRef ?? blocId,
598
- );
599
- if (isolatedBloc) {
600
- return isolatedBloc;
601
- } else {
602
- if (options.throwIfNotFound) {
603
- throw new Error(`Isolated bloc ${blocClass.name} not found`);
604
- }
605
- }
606
- } else {
607
- const registeredBloc = this.findRegisteredBlocInstance(
608
- blocClass,
609
- blocId,
610
- );
611
- if (registeredBloc) {
612
- return registeredBloc;
613
- } else {
614
- if (options.throwIfNotFound) {
615
- throw new Error(`Registered bloc ${blocClass.name} not found`);
616
- }
617
- }
618
- }
619
- }
620
-
621
- const bloc = this.createNewBlocInstance(blocClass, blocId, options);
622
- this.log(
623
- `[${blocClass.name}:${String(blocId)}] (getBloc) No existing instance found. Creating new one.`,
624
- options,
625
- bloc,
626
- );
627
-
628
- if (!bloc) {
629
- throw new Error(
630
- `[getBloc] Failed to create bloc instance for ${blocClass.name}. This should never happen.`,
631
- );
632
- }
633
-
634
- return bloc;
635
- };
636
- static get getBloc() {
637
- return Blac.instance.getBloc;
638
- }
639
-
640
- /**
641
- * Gets all instances of a specific bloc class
642
- * @param blocClass - The bloc class to search for
643
- * @param options - Options including:
644
- * - searchIsolated: Whether to search in isolated blocs (defaults to bloc's isolated property)
645
- * @returns Array of matching bloc instances
646
- */
647
- getAllBlocs = <B extends BlocConstructor<any>>(
648
- blocClass: B,
649
- options: {
650
- searchIsolated?: boolean;
651
- } = {},
652
- ): InstanceType<B>[] => {
653
- const results: InstanceType<B>[] = [];
654
-
655
- // Search non-isolated blocs
656
- this.blocInstanceMap.forEach((blocInstance) => {
657
- if (blocInstance.constructor === blocClass) {
658
- // Strict constructor check
659
- results.push(blocInstance as InstanceType<B>);
660
- }
661
- });
662
-
663
- // Optionally search isolated blocs
664
- if (options.searchIsolated !== false) {
665
- const isolatedBlocs = this.isolatedBlocMap.get(blocClass);
666
- if (isolatedBlocs) {
667
- results.push(...isolatedBlocs.map((bloc) => bloc as InstanceType<B>));
668
- }
669
- }
670
-
671
- return results;
672
- };
673
-
674
- /**
675
- * Disposes all keep-alive blocs of a specific type
676
- * @param blocClass - The bloc class to dispose
677
- */
678
- disposeKeepAliveBlocs = <B extends BlocConstructor<any>>(
679
- blocClass?: B,
680
- ): void => {
681
- const toDispose: BlocBase<unknown>[] = [];
682
-
683
- for (const bloc of this.keepAliveBlocs) {
684
- if (!blocClass || bloc.constructor === blocClass) {
685
- toDispose.push(bloc);
686
- }
687
- }
688
-
689
- toDispose.forEach((bloc) => bloc.dispose());
690
- };
691
- static get disposeKeepAliveBlocs() {
692
- return Blac.instance.disposeKeepAliveBlocs;
693
- }
694
-
695
- /**
696
- * Disposes all blocs matching a pattern
697
- * @param predicate - Function to test each bloc for disposal
698
- */
699
- disposeBlocs = (predicate: (bloc: BlocBase<unknown>) => boolean): void => {
700
- const toDispose: BlocBase<unknown>[] = [];
701
-
702
- // Check registered blocs
703
- for (const bloc of this.blocInstanceMap.values()) {
704
- if (predicate(bloc)) {
705
- toDispose.push(bloc);
706
- }
707
- }
708
-
709
- // Check isolated blocs
710
- for (const blocs of this.isolatedBlocMap.values()) {
711
- for (const bloc of blocs) {
712
- if (predicate(bloc)) {
713
- toDispose.push(bloc);
714
- }
715
- }
716
- }
717
-
718
- toDispose.forEach((bloc) => bloc.dispose());
719
- };
720
- static get disposeBlocs() {
721
- return Blac.instance.disposeBlocs;
722
- }
723
-
724
- /**
725
- * Gets memory usage statistics for debugging
726
- */
727
- getMemoryStats = () => {
728
- return {
729
- totalBlocs: this.uidRegistry.size,
730
- registeredBlocs: this.blocInstanceMap.size,
731
- isolatedBlocs: Array.from(this.isolatedBlocMap.values()).reduce(
732
- (sum, arr) => sum + arr.length,
733
- 0,
734
- ),
735
- keepAliveBlocs: this.keepAliveBlocs.size,
736
- isolatedBlocTypes: this.isolatedBlocMap.size,
737
- };
738
- };
739
- static get getMemoryStats() {
740
- return Blac.instance.getMemoryStats;
741
- }
742
-
743
- /**
744
- * Validates subscription references and cleans up orphaned blocs
745
- */
746
- validateConsumers = (): void => {
747
- for (const bloc of this.uidRegistry.values()) {
748
- // Check if bloc should be disposed after validation
749
- // TODO: Type assertion for private property access (see explanation above)
750
- if (
751
- bloc.subscriptionCount === 0 &&
752
- !bloc._keepAlive &&
753
- (bloc as any)._disposalState === BlocLifecycleState.ACTIVE
754
- ) {
755
- // Schedule disposal for blocs with no subscriptions
756
- setTimeout(() => {
757
- // Double-check conditions before disposal
758
- // TODO: Type assertion for private property access (see explanation above)
759
- if (
760
- bloc.subscriptionCount === 0 &&
761
- !bloc._keepAlive &&
762
- (bloc as any)._disposalState === BlocLifecycleState.ACTIVE
763
- ) {
764
- this.disposeBloc(bloc);
765
- }
766
- }, 1000); // Give a grace period
767
- }
768
- }
769
- };
770
- static get validateConsumers() {
771
- return Blac.instance.validateConsumers;
772
- }
773
-
774
- /**
775
- * Bootstrap the Blac instance and all plugins
776
- */
777
- bootstrap(): void {
778
- this.plugins.bootstrap();
779
- }
780
-
781
- /**
782
- * Shutdown the Blac instance and all plugins
783
- */
784
- shutdown(): void {
785
- this.plugins.shutdown();
786
-
787
- // Dispose all non-keepAlive blocs
788
- for (const bloc of this.blocInstanceMap.values()) {
789
- if (!bloc._keepAlive) {
790
- this.disposeBloc(bloc);
791
- }
792
- }
793
-
794
- for (const blocs of this.isolatedBlocMap.values()) {
795
- for (const bloc of blocs) {
796
- if (!bloc._keepAlive) {
797
- this.disposeBloc(bloc);
798
- }
799
- }
800
- }
801
- }
802
- }