@blac/core 2.0.0-rc-13 → 2.0.0-rc-16
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/package.json +10 -15
- package/src/Blac.ts +38 -71
- package/src/Bloc.ts +8 -9
- package/src/BlocBase.ts +246 -540
- 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 +107 -97
- 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 +7 -1
- package/src/lifecycle/BlocLifecycle.ts +156 -0
- package/src/lifecycle/index.ts +1 -0
- package/src/plugins/BlocPluginRegistry.ts +71 -51
- package/src/plugins/SystemPluginRegistry.ts +63 -13
- package/src/plugins/__tests__/BlocPluginRegistry.test.ts +450 -0
- package/src/plugins/__tests__/SystemPluginRegistry.test.ts +404 -0
- package/src/plugins/types.ts +32 -3
- 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/dist/index.cjs.js +0 -11
- package/dist/index.cjs.js.map +0 -1
- package/dist/index.d.ts +0 -1078
- package/dist/index.es.js +0 -1992
- package/dist/index.es.js.map +0 -1
- package/src/BlacObserver.ts +0 -189
- 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,14 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blac/core",
|
|
3
|
-
"version": "2.0.0-rc-
|
|
3
|
+
"version": "2.0.0-rc-16",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Brendan Mullins <jsnanigans@gmail.com>",
|
|
6
|
-
"main": "
|
|
7
|
-
"module": "
|
|
8
|
-
"typings": "
|
|
9
|
-
"types": "
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"module": "src/index.ts",
|
|
8
|
+
"typings": "src/index.ts",
|
|
9
|
+
"types": "src/index.ts",
|
|
10
10
|
"files": [
|
|
11
|
-
"dist",
|
|
12
11
|
"src",
|
|
13
12
|
"README.md",
|
|
14
13
|
"LICENSE"
|
|
@@ -27,21 +26,17 @@
|
|
|
27
26
|
"@testing-library/jest-dom": "^6.6.4",
|
|
28
27
|
"@testing-library/user-event": "^14.6.1",
|
|
29
28
|
"@vitest/browser": "^3.2.4",
|
|
30
|
-
"jsdom": "^
|
|
29
|
+
"jsdom": "^26.1.0",
|
|
31
30
|
"prettier": "^3.6.2",
|
|
32
|
-
"typescript": "^5.
|
|
33
|
-
"
|
|
34
|
-
"vite-plugin-dts": "^4.5.4",
|
|
35
|
-
"vitest": "^1.6.0"
|
|
31
|
+
"typescript": "^5.9.2",
|
|
32
|
+
"vitest": "^3.2.4"
|
|
36
33
|
},
|
|
37
34
|
"scripts": {
|
|
38
|
-
"
|
|
39
|
-
"format": "prettier --write \"src/**/*.{ts,tsx,js,jsx}\"",
|
|
35
|
+
"format": "prettier --write \".\"",
|
|
40
36
|
"test": "vitest run",
|
|
41
37
|
"test:watch": "vitest --watch",
|
|
42
38
|
"coverage": "vitest run --coverage",
|
|
43
39
|
"typecheck": "tsc --noEmit",
|
|
44
|
-
"
|
|
45
|
-
"deploy": "pnpm run build && pnpm publish --access public"
|
|
40
|
+
"deploy": "pnpm publish --access public"
|
|
46
41
|
}
|
|
47
42
|
}
|
package/src/Blac.ts
CHANGED
|
@@ -1,19 +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
9
|
import { SystemPluginRegistry } from './plugins/SystemPluginRegistry';
|
|
10
|
+
import { BlacError, ErrorCategory, ErrorSeverity } from './errors/BlacError';
|
|
11
|
+
import { ErrorManager } from './errors/ErrorManager';
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Configuration options for the Blac instance
|
|
13
15
|
*/
|
|
14
16
|
export interface BlacConfig {
|
|
15
|
-
/** Whether to expose the Blac instance globally */
|
|
16
|
-
exposeBlacInstance?: boolean;
|
|
17
17
|
/**
|
|
18
18
|
* Whether to enable proxy dependency tracking for automatic re-render optimization.
|
|
19
19
|
* When false, state changes always cause re-renders unless dependencies are manually specified.
|
|
@@ -25,7 +25,7 @@ export interface BlacConfig {
|
|
|
25
25
|
export interface GetBlocOptions<B extends BlocBase<unknown>> {
|
|
26
26
|
id?: string;
|
|
27
27
|
selector?: BlocHookDependencyArrayFn<BlocState<B>>;
|
|
28
|
-
|
|
28
|
+
constructorParams?: ConstructorParameters<BlocConstructor<B>>[];
|
|
29
29
|
onMount?: (bloc: B) => void;
|
|
30
30
|
instanceRef?: string;
|
|
31
31
|
throwIfNotFound?: boolean;
|
|
@@ -105,13 +105,13 @@ export class Blac {
|
|
|
105
105
|
}
|
|
106
106
|
/** Timestamp when the instance was created */
|
|
107
107
|
createdAt = Date.now();
|
|
108
|
+
private errorManager = ErrorManager.getInstance();
|
|
108
109
|
static get getAllBlocs() {
|
|
109
110
|
return Blac.instance.getAllBlocs;
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
/** Private static configuration */
|
|
113
114
|
private static _config: BlacConfig = {
|
|
114
|
-
exposeBlacInstance: false,
|
|
115
115
|
proxyDependencyTracking: true,
|
|
116
116
|
};
|
|
117
117
|
|
|
@@ -127,14 +127,16 @@ export class Blac {
|
|
|
127
127
|
*/
|
|
128
128
|
static setConfig(config: Partial<BlacConfig>): void {
|
|
129
129
|
// Validate config
|
|
130
|
-
if (
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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);
|
|
138
140
|
}
|
|
139
141
|
|
|
140
142
|
// Merge with existing config
|
|
@@ -219,9 +221,7 @@ export class Blac {
|
|
|
219
221
|
* @param args - Additional arguments
|
|
220
222
|
*/
|
|
221
223
|
warn = (message: string, ...args: unknown[]) => {
|
|
222
|
-
|
|
223
|
-
// Logging disabled - console.warn removed
|
|
224
|
-
}
|
|
224
|
+
console.warn(message, ...args);
|
|
225
225
|
};
|
|
226
226
|
static get warn() {
|
|
227
227
|
return Blac.instance.warn;
|
|
@@ -233,7 +233,7 @@ export class Blac {
|
|
|
233
233
|
*/
|
|
234
234
|
error = (message: string, ...args: unknown[]) => {
|
|
235
235
|
if (Blac.enableLog) {
|
|
236
|
-
|
|
236
|
+
console.error(message, ...args);
|
|
237
237
|
}
|
|
238
238
|
};
|
|
239
239
|
static get error() {
|
|
@@ -317,7 +317,7 @@ export class Blac {
|
|
|
317
317
|
);
|
|
318
318
|
|
|
319
319
|
// First dispose the bloc to prevent further operations
|
|
320
|
-
bloc.
|
|
320
|
+
bloc.dispose();
|
|
321
321
|
|
|
322
322
|
// Notify plugins of bloc disposal
|
|
323
323
|
this.plugins.notifyBlocDisposed(bloc);
|
|
@@ -393,6 +393,12 @@ export class Blac {
|
|
|
393
393
|
|
|
394
394
|
const key = this.createBlocInstanceMapKey(blocClass.name, id);
|
|
395
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
|
+
|
|
396
402
|
return found;
|
|
397
403
|
}
|
|
398
404
|
|
|
@@ -477,7 +483,7 @@ export class Blac {
|
|
|
477
483
|
return undefined;
|
|
478
484
|
}
|
|
479
485
|
// Find the specific bloc by ID within the isolated array
|
|
480
|
-
const found = blocs.find((b) => b._id === id) as
|
|
486
|
+
const found = blocs.find((b) => b._id === id && !(b as any).isDisposed) as
|
|
481
487
|
| InstanceType<B>
|
|
482
488
|
| undefined;
|
|
483
489
|
return found;
|
|
@@ -505,14 +511,14 @@ export class Blac {
|
|
|
505
511
|
id: BlocInstanceId,
|
|
506
512
|
options: GetBlocOptions<InstanceType<B>> = {},
|
|
507
513
|
): InstanceType<B> {
|
|
508
|
-
const {
|
|
509
|
-
const newBloc = new blocClass(
|
|
514
|
+
const { constructorParams, instanceRef } = options;
|
|
515
|
+
const newBloc = new blocClass(constructorParams) as InstanceType<B>;
|
|
516
|
+
newBloc.blacInstance = this;
|
|
510
517
|
newBloc._instanceRef = instanceRef;
|
|
511
|
-
newBloc.
|
|
512
|
-
newBloc._updateId(id);
|
|
518
|
+
newBloc._id = id;
|
|
513
519
|
|
|
514
520
|
// Set up disposal handler to break circular dependency
|
|
515
|
-
newBloc.
|
|
521
|
+
newBloc.setDisposalHandler((bloc) => this.disposeBloc(bloc));
|
|
516
522
|
|
|
517
523
|
if (newBloc.isIsolated) {
|
|
518
524
|
this.registerIsolatedBlocInstance(newBloc);
|
|
@@ -520,8 +526,7 @@ export class Blac {
|
|
|
520
526
|
this.registerBlocInstance(newBloc);
|
|
521
527
|
}
|
|
522
528
|
|
|
523
|
-
//
|
|
524
|
-
newBloc._activatePlugins();
|
|
529
|
+
// Plugins are activated in constructor
|
|
525
530
|
|
|
526
531
|
// Notify system plugins of bloc creation
|
|
527
532
|
this.plugins.notifyBlocCreated(newBloc);
|
|
@@ -632,38 +637,6 @@ export class Blac {
|
|
|
632
637
|
return Blac.instance.getBloc;
|
|
633
638
|
}
|
|
634
639
|
|
|
635
|
-
/**
|
|
636
|
-
* Gets a bloc instance or throws an error if it doesn't exist
|
|
637
|
-
* @param blocClass - The bloc class to get
|
|
638
|
-
* @param options - Options including:
|
|
639
|
-
* - id: The instance ID (defaults to class name if not provided)
|
|
640
|
-
* - props: Properties to pass to the bloc constructor
|
|
641
|
-
* - instanceRef: Optional reference string for the instance
|
|
642
|
-
*/
|
|
643
|
-
getBlocOrThrow = <B extends BlocConstructor<any>>(
|
|
644
|
-
blocClass: B,
|
|
645
|
-
options: {
|
|
646
|
-
id?: BlocInstanceId;
|
|
647
|
-
props?: InferPropsFromGeneric<B>;
|
|
648
|
-
instanceRef?: string;
|
|
649
|
-
} = {},
|
|
650
|
-
): InstanceType<B> => {
|
|
651
|
-
const isIsolated = (blocClass as unknown as BlocBaseAbstract).isolated;
|
|
652
|
-
const id = options.id || blocClass.name;
|
|
653
|
-
|
|
654
|
-
const registered = isIsolated
|
|
655
|
-
? this.findIsolatedBlocInstance(blocClass, id)
|
|
656
|
-
: this.findRegisteredBlocInstance(blocClass, id);
|
|
657
|
-
|
|
658
|
-
if (registered) {
|
|
659
|
-
return registered;
|
|
660
|
-
}
|
|
661
|
-
throw new Error(`Bloc ${blocClass.name} not found`);
|
|
662
|
-
};
|
|
663
|
-
static get getBlocOrThrow() {
|
|
664
|
-
return Blac.instance.getBlocOrThrow;
|
|
665
|
-
}
|
|
666
|
-
|
|
667
640
|
/**
|
|
668
641
|
* Gets all instances of a specific bloc class
|
|
669
642
|
* @param blocClass - The bloc class to search for
|
|
@@ -678,7 +651,6 @@ export class Blac {
|
|
|
678
651
|
} = {},
|
|
679
652
|
): InstanceType<B>[] => {
|
|
680
653
|
const results: InstanceType<B>[] = [];
|
|
681
|
-
// const blocClassName = (blocClass as any).name; // Temporarily removed for debugging
|
|
682
654
|
|
|
683
655
|
// Search non-isolated blocs
|
|
684
656
|
this.blocInstanceMap.forEach((blocInstance) => {
|
|
@@ -714,7 +686,7 @@ export class Blac {
|
|
|
714
686
|
}
|
|
715
687
|
}
|
|
716
688
|
|
|
717
|
-
toDispose.forEach((bloc) => bloc.
|
|
689
|
+
toDispose.forEach((bloc) => bloc.dispose());
|
|
718
690
|
};
|
|
719
691
|
static get disposeKeepAliveBlocs() {
|
|
720
692
|
return Blac.instance.disposeKeepAliveBlocs;
|
|
@@ -743,7 +715,7 @@ export class Blac {
|
|
|
743
715
|
}
|
|
744
716
|
}
|
|
745
717
|
|
|
746
|
-
toDispose.forEach((bloc) => bloc.
|
|
718
|
+
toDispose.forEach((bloc) => bloc.dispose());
|
|
747
719
|
};
|
|
748
720
|
static get disposeBlocs() {
|
|
749
721
|
return Blac.instance.disposeBlocs;
|
|
@@ -769,26 +741,23 @@ export class Blac {
|
|
|
769
741
|
}
|
|
770
742
|
|
|
771
743
|
/**
|
|
772
|
-
* Validates
|
|
744
|
+
* Validates subscription references and cleans up orphaned blocs
|
|
773
745
|
*/
|
|
774
746
|
validateConsumers = (): void => {
|
|
775
747
|
for (const bloc of this.uidRegistry.values()) {
|
|
776
|
-
// Validate consumers using the bloc's own validation method
|
|
777
|
-
bloc._validateConsumers();
|
|
778
|
-
|
|
779
748
|
// Check if bloc should be disposed after validation
|
|
780
749
|
// TODO: Type assertion for private property access (see explanation above)
|
|
781
750
|
if (
|
|
782
|
-
bloc.
|
|
751
|
+
bloc.subscriptionCount === 0 &&
|
|
783
752
|
!bloc._keepAlive &&
|
|
784
753
|
(bloc as any)._disposalState === BlocLifecycleState.ACTIVE
|
|
785
754
|
) {
|
|
786
|
-
// Schedule disposal for blocs with no
|
|
755
|
+
// Schedule disposal for blocs with no subscriptions
|
|
787
756
|
setTimeout(() => {
|
|
788
757
|
// Double-check conditions before disposal
|
|
789
758
|
// TODO: Type assertion for private property access (see explanation above)
|
|
790
759
|
if (
|
|
791
|
-
bloc.
|
|
760
|
+
bloc.subscriptionCount === 0 &&
|
|
792
761
|
!bloc._keepAlive &&
|
|
793
762
|
(bloc as any)._disposalState === BlocLifecycleState.ACTIVE
|
|
794
763
|
) {
|
|
@@ -830,6 +799,4 @@ export class Blac {
|
|
|
830
799
|
}
|
|
831
800
|
}
|
|
832
801
|
}
|
|
833
|
-
|
|
834
|
-
|
|
835
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>
|
|
@@ -63,22 +62,22 @@ export abstract class Bloc<
|
|
|
63
62
|
console.error('Error transforming event:', error);
|
|
64
63
|
// Continue with original event if transformation fails
|
|
65
64
|
}
|
|
66
|
-
|
|
65
|
+
|
|
67
66
|
// If event was cancelled by plugin, don't process it
|
|
68
67
|
if (transformedAction === null) {
|
|
69
68
|
return;
|
|
70
69
|
}
|
|
71
|
-
|
|
70
|
+
|
|
72
71
|
// Notify bloc plugins of event
|
|
73
72
|
try {
|
|
74
73
|
(this._plugins as any).notifyEvent(transformedAction);
|
|
75
74
|
} catch (error) {
|
|
76
75
|
console.error('Error notifying plugins of event:', error);
|
|
77
76
|
}
|
|
78
|
-
|
|
77
|
+
|
|
79
78
|
// Notify system plugins of event
|
|
80
79
|
Blac.instance.plugins.notifyEventAdded(this as any, transformedAction);
|
|
81
|
-
|
|
80
|
+
|
|
82
81
|
this._eventQueue.push(transformedAction);
|
|
83
82
|
|
|
84
83
|
if (!this._isProcessingEvent) {
|
|
@@ -149,13 +148,13 @@ export abstract class Bloc<
|
|
|
149
148
|
this._plugins.notifyError(error, {
|
|
150
149
|
phase: 'event-processing',
|
|
151
150
|
operation: 'handler',
|
|
152
|
-
metadata: { event: action }
|
|
151
|
+
metadata: { event: action },
|
|
153
152
|
});
|
|
154
|
-
|
|
153
|
+
|
|
155
154
|
Blac.instance.plugins.notifyError(error, this as any, {
|
|
156
155
|
phase: 'event-processing',
|
|
157
156
|
operation: 'handler',
|
|
158
|
-
metadata: { event: action }
|
|
157
|
+
metadata: { event: action },
|
|
159
158
|
});
|
|
160
159
|
} catch (pluginError) {
|
|
161
160
|
console.error('Error notifying plugins:', pluginError);
|