@blac/core 2.0.0-rc-11 → 2.0.0-rc-14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs.js +2 -2
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +739 -260
- package/dist/index.es.js +1924 -1237
- package/dist/index.es.js.map +1 -1
- package/package.json +6 -7
- package/src/Blac.ts +82 -69
- package/src/Bloc.ts +45 -3
- package/src/BlocBase.ts +263 -454
- package/src/Cubit.ts +1 -16
- package/src/__tests__/Blac.config.test.ts +10 -17
- package/src/__tests__/Bloc.event.test.ts +27 -52
- package/src/__tests__/BlocBase.subscription.test.ts +349 -0
- package/src/__tests__/Cubit.test.ts +39 -50
- package/src/__tests__/memory-leaks.test.ts +383 -0
- package/src/__tests__/plugins.test.ts +464 -0
- package/src/adapter/BlacAdapter.ts +347 -207
- package/src/adapter/ProxyFactory.ts +196 -255
- package/src/adapter/__tests__/BlacAdapter.test.ts +71 -77
- package/src/adapter/__tests__/ProxyFactory.test.ts +1 -1
- package/src/adapter/index.ts +0 -1
- package/src/errors/BlacError.ts +69 -0
- package/src/errors/ErrorManager.ts +101 -0
- package/src/errors/handleError.ts +114 -0
- package/src/errors/index.ts +13 -0
- package/src/events.ts +7 -1
- package/src/index.ts +10 -1
- package/src/lifecycle/BlocLifecycle.ts +156 -0
- package/src/lifecycle/index.ts +1 -0
- package/src/plugins/BlocPluginRegistry.ts +240 -0
- package/src/plugins/SystemPluginRegistry.ts +242 -0
- package/src/plugins/__tests__/BlocPluginRegistry.test.ts +450 -0
- package/src/plugins/__tests__/SystemPluginRegistry.test.ts +404 -0
- package/src/plugins/index.ts +8 -0
- package/src/plugins/types.ts +129 -0
- package/src/subscription/README.md +132 -0
- package/src/subscription/SubscriptionManager.ts +395 -0
- package/src/subscription/__tests__/SubscriptionManager.test.ts +355 -0
- package/src/subscription/index.ts +2 -0
- package/src/subscription/types.ts +90 -0
- package/src/testing.ts +31 -37
- package/src/types.ts +4 -21
- package/src/utils/BatchingManager.ts +59 -0
- package/src/utils/RerenderLogger.ts +265 -0
- package/src/utils/__tests__/generateInstanceId.test.ts +104 -0
- package/src/utils/__tests__/shallowEqual.test.ts +171 -0
- package/src/utils/__tests__/uuid.test.ts +144 -0
- package/src/utils/generateInstanceId.ts +54 -0
- package/src/utils/shallowEqual.ts +1 -1
- package/README.md +0 -329
- package/src/BlacObserver.ts +0 -186
- package/src/__tests__/BlocBase.lifecycle.test.ts +0 -441
- package/src/__tests__/props.test.ts +0 -256
- package/src/adapter/ConsumerTracker.ts +0 -241
- package/src/adapter/__tests__/ConsumerTracker.test.ts +0 -562
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blac/core",
|
|
3
|
-
"version": "2.0.0-rc-
|
|
3
|
+
"version": "2.0.0-rc-14",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Brendan Mullins <jsnanigans@gmail.com>",
|
|
6
6
|
"main": "dist/index.cjs.js",
|
|
@@ -27,16 +27,15 @@
|
|
|
27
27
|
"@testing-library/jest-dom": "^6.6.4",
|
|
28
28
|
"@testing-library/user-event": "^14.6.1",
|
|
29
29
|
"@vitest/browser": "^3.2.4",
|
|
30
|
-
"jsdom": "^
|
|
30
|
+
"jsdom": "^26.1.0",
|
|
31
31
|
"prettier": "^3.6.2",
|
|
32
|
-
"typescript": "^5.
|
|
33
|
-
"vite": "^
|
|
32
|
+
"typescript": "^5.9.2",
|
|
33
|
+
"vite": "^7.0.6",
|
|
34
34
|
"vite-plugin-dts": "^4.5.4",
|
|
35
|
-
"vitest": "^
|
|
35
|
+
"vitest": "^3.2.4"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
|
-
"
|
|
39
|
-
"format": "prettier --write \"src/**/*.{ts,tsx,js,jsx}\"",
|
|
38
|
+
"format": "prettier --write \".\"",
|
|
40
39
|
"test": "vitest run",
|
|
41
40
|
"test:watch": "vitest --watch",
|
|
42
41
|
"coverage": "vitest run --coverage",
|
package/src/Blac.ts
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
import { BlocBase, BlocInstanceId
|
|
1
|
+
import { BlocBase, BlocInstanceId } from './BlocBase';
|
|
2
|
+
import { BlocLifecycleState } from './lifecycle/BlocLifecycle';
|
|
2
3
|
import {
|
|
3
4
|
BlocBaseAbstract,
|
|
4
5
|
BlocConstructor,
|
|
5
6
|
BlocHookDependencyArrayFn,
|
|
6
7
|
BlocState,
|
|
7
|
-
InferPropsFromGeneric,
|
|
8
8
|
} from './types';
|
|
9
|
+
import { SystemPluginRegistry } from './plugins/SystemPluginRegistry';
|
|
10
|
+
import { BlacError, ErrorCategory, ErrorSeverity } from './errors/BlacError';
|
|
11
|
+
import { ErrorManager } from './errors/ErrorManager';
|
|
9
12
|
|
|
10
13
|
/**
|
|
11
14
|
* Configuration options for the Blac instance
|
|
12
15
|
*/
|
|
13
16
|
export interface BlacConfig {
|
|
14
|
-
/** Whether to expose the Blac instance globally */
|
|
15
|
-
exposeBlacInstance?: boolean;
|
|
16
17
|
/**
|
|
17
18
|
* Whether to enable proxy dependency tracking for automatic re-render optimization.
|
|
18
19
|
* When false, state changes always cause re-renders unless dependencies are manually specified.
|
|
@@ -24,7 +25,7 @@ export interface BlacConfig {
|
|
|
24
25
|
export interface GetBlocOptions<B extends BlocBase<unknown>> {
|
|
25
26
|
id?: string;
|
|
26
27
|
selector?: BlocHookDependencyArrayFn<BlocState<B>>;
|
|
27
|
-
|
|
28
|
+
constructorParams?: ConstructorParameters<BlocConstructor<B>>[];
|
|
28
29
|
onMount?: (bloc: B) => void;
|
|
29
30
|
instanceRef?: string;
|
|
30
31
|
throwIfNotFound?: boolean;
|
|
@@ -104,13 +105,13 @@ export class Blac {
|
|
|
104
105
|
}
|
|
105
106
|
/** Timestamp when the instance was created */
|
|
106
107
|
createdAt = Date.now();
|
|
108
|
+
private errorManager = ErrorManager.getInstance();
|
|
107
109
|
static get getAllBlocs() {
|
|
108
110
|
return Blac.instance.getAllBlocs;
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
/** Private static configuration */
|
|
112
114
|
private static _config: BlacConfig = {
|
|
113
|
-
exposeBlacInstance: false,
|
|
114
115
|
proxyDependencyTracking: true,
|
|
115
116
|
};
|
|
116
117
|
|
|
@@ -126,14 +127,16 @@ export class Blac {
|
|
|
126
127
|
*/
|
|
127
128
|
static setConfig(config: Partial<BlacConfig>): void {
|
|
128
129
|
// Validate config
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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);
|
|
137
140
|
}
|
|
138
141
|
|
|
139
142
|
// Merge with existing config
|
|
@@ -162,6 +165,8 @@ export class Blac {
|
|
|
162
165
|
keepAliveBlocs: Set<BlocBase<unknown>> = new Set();
|
|
163
166
|
/** Flag to control whether changes should be posted to document */
|
|
164
167
|
postChangesToDocument = false;
|
|
168
|
+
/** System plugin registry */
|
|
169
|
+
readonly plugins = new SystemPluginRegistry();
|
|
165
170
|
|
|
166
171
|
/**
|
|
167
172
|
* Creates a new Blac instance.
|
|
@@ -173,6 +178,9 @@ export class Blac {
|
|
|
173
178
|
return Blac.instance;
|
|
174
179
|
}
|
|
175
180
|
instanceManager.setInstance(this);
|
|
181
|
+
|
|
182
|
+
// Bootstrap plugins on creation
|
|
183
|
+
this.plugins.bootstrap();
|
|
176
184
|
}
|
|
177
185
|
|
|
178
186
|
/** Flag to enable/disable logging */
|
|
@@ -213,9 +221,7 @@ export class Blac {
|
|
|
213
221
|
* @param args - Additional arguments
|
|
214
222
|
*/
|
|
215
223
|
warn = (message: string, ...args: unknown[]) => {
|
|
216
|
-
|
|
217
|
-
// Logging disabled - console.warn removed
|
|
218
|
-
}
|
|
224
|
+
console.warn(message, ...args);
|
|
219
225
|
};
|
|
220
226
|
static get warn() {
|
|
221
227
|
return Blac.instance.warn;
|
|
@@ -227,7 +233,7 @@ export class Blac {
|
|
|
227
233
|
*/
|
|
228
234
|
error = (message: string, ...args: unknown[]) => {
|
|
229
235
|
if (Blac.enableLog) {
|
|
230
|
-
|
|
236
|
+
console.error(message, ...args);
|
|
231
237
|
}
|
|
232
238
|
};
|
|
233
239
|
static get error() {
|
|
@@ -311,7 +317,10 @@ export class Blac {
|
|
|
311
317
|
);
|
|
312
318
|
|
|
313
319
|
// First dispose the bloc to prevent further operations
|
|
314
|
-
bloc.
|
|
320
|
+
bloc.dispose();
|
|
321
|
+
|
|
322
|
+
// Notify plugins of bloc disposal
|
|
323
|
+
this.plugins.notifyBlocDisposed(bloc);
|
|
315
324
|
|
|
316
325
|
// Then clean up from registries
|
|
317
326
|
if (base.isolated) {
|
|
@@ -384,6 +393,12 @@ export class Blac {
|
|
|
384
393
|
|
|
385
394
|
const key = this.createBlocInstanceMapKey(blocClass.name, id);
|
|
386
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
|
+
|
|
387
402
|
return found;
|
|
388
403
|
}
|
|
389
404
|
|
|
@@ -468,7 +483,7 @@ export class Blac {
|
|
|
468
483
|
return undefined;
|
|
469
484
|
}
|
|
470
485
|
// Find the specific bloc by ID within the isolated array
|
|
471
|
-
const found = blocs.find((b) => b._id === id) as
|
|
486
|
+
const found = blocs.find((b) => b._id === id && !(b as any).isDisposed) as
|
|
472
487
|
| InstanceType<B>
|
|
473
488
|
| undefined;
|
|
474
489
|
return found;
|
|
@@ -496,21 +511,26 @@ export class Blac {
|
|
|
496
511
|
id: BlocInstanceId,
|
|
497
512
|
options: GetBlocOptions<InstanceType<B>> = {},
|
|
498
513
|
): InstanceType<B> {
|
|
499
|
-
const {
|
|
500
|
-
const newBloc = new blocClass(
|
|
514
|
+
const { constructorParams, instanceRef } = options;
|
|
515
|
+
const newBloc = new blocClass(constructorParams) as InstanceType<B>;
|
|
516
|
+
newBloc.blacInstance = this;
|
|
501
517
|
newBloc._instanceRef = instanceRef;
|
|
502
|
-
newBloc.
|
|
503
|
-
newBloc._updateId(id);
|
|
518
|
+
newBloc._id = id;
|
|
504
519
|
|
|
505
520
|
// Set up disposal handler to break circular dependency
|
|
506
|
-
newBloc.
|
|
521
|
+
newBloc.setDisposalHandler((bloc) => this.disposeBloc(bloc));
|
|
507
522
|
|
|
508
523
|
if (newBloc.isIsolated) {
|
|
509
524
|
this.registerIsolatedBlocInstance(newBloc);
|
|
510
|
-
|
|
525
|
+
} else {
|
|
526
|
+
this.registerBlocInstance(newBloc);
|
|
511
527
|
}
|
|
512
528
|
|
|
513
|
-
|
|
529
|
+
// Plugins are activated in constructor
|
|
530
|
+
|
|
531
|
+
// Notify system plugins of bloc creation
|
|
532
|
+
this.plugins.notifyBlocCreated(newBloc);
|
|
533
|
+
|
|
514
534
|
return newBloc;
|
|
515
535
|
}
|
|
516
536
|
|
|
@@ -617,38 +637,6 @@ export class Blac {
|
|
|
617
637
|
return Blac.instance.getBloc;
|
|
618
638
|
}
|
|
619
639
|
|
|
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
640
|
/**
|
|
653
641
|
* Gets all instances of a specific bloc class
|
|
654
642
|
* @param blocClass - The bloc class to search for
|
|
@@ -663,7 +651,6 @@ export class Blac {
|
|
|
663
651
|
} = {},
|
|
664
652
|
): InstanceType<B>[] => {
|
|
665
653
|
const results: InstanceType<B>[] = [];
|
|
666
|
-
// const blocClassName = (blocClass as any).name; // Temporarily removed for debugging
|
|
667
654
|
|
|
668
655
|
// Search non-isolated blocs
|
|
669
656
|
this.blocInstanceMap.forEach((blocInstance) => {
|
|
@@ -699,7 +686,7 @@ export class Blac {
|
|
|
699
686
|
}
|
|
700
687
|
}
|
|
701
688
|
|
|
702
|
-
toDispose.forEach((bloc) => bloc.
|
|
689
|
+
toDispose.forEach((bloc) => bloc.dispose());
|
|
703
690
|
};
|
|
704
691
|
static get disposeKeepAliveBlocs() {
|
|
705
692
|
return Blac.instance.disposeKeepAliveBlocs;
|
|
@@ -728,7 +715,7 @@ export class Blac {
|
|
|
728
715
|
}
|
|
729
716
|
}
|
|
730
717
|
|
|
731
|
-
toDispose.forEach((bloc) => bloc.
|
|
718
|
+
toDispose.forEach((bloc) => bloc.dispose());
|
|
732
719
|
};
|
|
733
720
|
static get disposeBlocs() {
|
|
734
721
|
return Blac.instance.disposeBlocs;
|
|
@@ -754,26 +741,23 @@ export class Blac {
|
|
|
754
741
|
}
|
|
755
742
|
|
|
756
743
|
/**
|
|
757
|
-
* Validates
|
|
744
|
+
* Validates subscription references and cleans up orphaned blocs
|
|
758
745
|
*/
|
|
759
746
|
validateConsumers = (): void => {
|
|
760
747
|
for (const bloc of this.uidRegistry.values()) {
|
|
761
|
-
// Validate consumers using the bloc's own validation method
|
|
762
|
-
bloc._validateConsumers();
|
|
763
|
-
|
|
764
748
|
// Check if bloc should be disposed after validation
|
|
765
749
|
// TODO: Type assertion for private property access (see explanation above)
|
|
766
750
|
if (
|
|
767
|
-
bloc.
|
|
751
|
+
bloc.subscriptionCount === 0 &&
|
|
768
752
|
!bloc._keepAlive &&
|
|
769
753
|
(bloc as any)._disposalState === BlocLifecycleState.ACTIVE
|
|
770
754
|
) {
|
|
771
|
-
// Schedule disposal for blocs with no
|
|
755
|
+
// Schedule disposal for blocs with no subscriptions
|
|
772
756
|
setTimeout(() => {
|
|
773
757
|
// Double-check conditions before disposal
|
|
774
758
|
// TODO: Type assertion for private property access (see explanation above)
|
|
775
759
|
if (
|
|
776
|
-
bloc.
|
|
760
|
+
bloc.subscriptionCount === 0 &&
|
|
777
761
|
!bloc._keepAlive &&
|
|
778
762
|
(bloc as any)._disposalState === BlocLifecycleState.ACTIVE
|
|
779
763
|
) {
|
|
@@ -786,4 +770,33 @@ export class Blac {
|
|
|
786
770
|
static get validateConsumers() {
|
|
787
771
|
return Blac.instance.validateConsumers;
|
|
788
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
|
+
}
|
|
789
802
|
}
|
package/src/Bloc.ts
CHANGED
|
@@ -5,8 +5,7 @@ import { BlocEventConstraint } from './types';
|
|
|
5
5
|
export abstract class Bloc<
|
|
6
6
|
S, // State type
|
|
7
7
|
A extends BlocEventConstraint = BlocEventConstraint, // Base Action/Event type with proper constraints
|
|
8
|
-
|
|
9
|
-
> extends BlocBase<S, P> {
|
|
8
|
+
> extends BlocBase<S> {
|
|
10
9
|
readonly eventHandlers: Map<
|
|
11
10
|
new (...args: any[]) => A,
|
|
12
11
|
(event: A, emit: (newState: S) => void) => void | Promise<void>
|
|
@@ -55,7 +54,31 @@ export abstract class Bloc<
|
|
|
55
54
|
* @param action The action/event instance to be processed.
|
|
56
55
|
*/
|
|
57
56
|
public add = async (action: A): Promise<void> => {
|
|
58
|
-
|
|
57
|
+
// Transform event through plugins
|
|
58
|
+
let transformedAction: A | null = action;
|
|
59
|
+
try {
|
|
60
|
+
transformedAction = (this._plugins as any).transformEvent(action);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('Error transforming event:', error);
|
|
63
|
+
// Continue with original event if transformation fails
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// If event was cancelled by plugin, don't process it
|
|
67
|
+
if (transformedAction === null) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Notify bloc plugins of event
|
|
72
|
+
try {
|
|
73
|
+
(this._plugins as any).notifyEvent(transformedAction);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error('Error notifying plugins of event:', error);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Notify system plugins of event
|
|
79
|
+
Blac.instance.plugins.notifyEventAdded(this as any, transformedAction);
|
|
80
|
+
|
|
81
|
+
this._eventQueue.push(transformedAction);
|
|
59
82
|
|
|
60
83
|
if (!this._isProcessingEvent) {
|
|
61
84
|
await this._processEventQueue();
|
|
@@ -119,6 +142,25 @@ export abstract class Bloc<
|
|
|
119
142
|
errorContext,
|
|
120
143
|
);
|
|
121
144
|
|
|
145
|
+
// Notify plugins of the error
|
|
146
|
+
if (error instanceof Error) {
|
|
147
|
+
try {
|
|
148
|
+
this._plugins.notifyError(error, {
|
|
149
|
+
phase: 'event-processing',
|
|
150
|
+
operation: 'handler',
|
|
151
|
+
metadata: { event: action },
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
Blac.instance.plugins.notifyError(error, this as any, {
|
|
155
|
+
phase: 'event-processing',
|
|
156
|
+
operation: 'handler',
|
|
157
|
+
metadata: { event: action },
|
|
158
|
+
});
|
|
159
|
+
} catch (pluginError) {
|
|
160
|
+
console.error('Error notifying plugins:', pluginError);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
122
164
|
if (error instanceof Error && error.name === 'CriticalError') {
|
|
123
165
|
throw error;
|
|
124
166
|
}
|