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