@blac/core 2.0.0-rc-8 → 2.0.0-rc-11
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 +27 -0
- package/dist/index.cjs.js +2 -2
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +224 -14
- package/dist/index.es.js +1122 -556
- package/dist/index.es.js.map +1 -1
- package/package.json +5 -4
- package/src/Blac.ts +109 -47
- package/src/BlacObserver.ts +58 -24
- package/src/Bloc.ts +120 -160
- package/src/BlocBase.ts +266 -90
- package/src/Cubit.ts +15 -1
- package/src/__tests__/Blac.config.test.ts +86 -0
- package/src/__tests__/Bloc.event.test.ts +419 -0
- package/src/__tests__/BlocBase.lifecycle.test.ts +441 -0
- package/src/__tests__/Cubit.test.ts +473 -0
- package/src/__tests__/props.test.ts +256 -0
- package/src/adapter/BlacAdapter.ts +327 -0
- package/src/adapter/ConsumerTracker.ts +241 -0
- package/src/adapter/ProxyFactory.ts +308 -0
- package/src/adapter/__tests__/BlacAdapter.test.ts +561 -0
- package/src/adapter/__tests__/ConsumerTracker.test.ts +562 -0
- package/src/adapter/__tests__/ProxyFactory.test.ts +655 -0
- package/src/adapter/index.ts +3 -0
- package/src/events.ts +3 -0
- package/src/index.ts +4 -1
- package/src/testing.ts +60 -47
- package/src/types.ts +12 -13
- package/src/utils/shallowEqual.ts +40 -0
- package/src/utils/uuid.ts +4 -4
- package/src/BlacEvent.ts +0 -5
package/src/Bloc.ts
CHANGED
|
@@ -2,177 +2,137 @@ import { Blac } from './Blac';
|
|
|
2
2
|
import { BlocBase } from './BlocBase';
|
|
3
3
|
import { BlocEventConstraint } from './types';
|
|
4
4
|
|
|
5
|
-
// A should be the base type for all events this Bloc handles and must extend BlocEventConstraint
|
|
6
|
-
// to access action.constructor and ensure proper event structure.
|
|
7
|
-
// P is for props, changed from any to unknown.
|
|
8
5
|
export abstract class Bloc<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
S, // State type
|
|
7
|
+
A extends BlocEventConstraint = BlocEventConstraint, // Base Action/Event type with proper constraints
|
|
8
|
+
P = unknown, // Props type
|
|
12
9
|
> extends BlocBase<S, P> {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
// Key: Constructor of a specific event E (where E extends A)
|
|
18
|
-
// TODO: 'any[]' is required for constructor arguments to allow flexible event instantiation.
|
|
19
|
-
// Using specific parameter types would break type inference for events with different
|
|
20
|
-
// constructor signatures. The 'any[]' enables polymorphic event handling while
|
|
21
|
-
// maintaining type safety through the generic constraint 'E extends A'.
|
|
22
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
23
|
-
new (...args: any[]) => A,
|
|
24
|
-
// Value: Handler function. 'event: A' is used here for the stored function type.
|
|
25
|
-
// The 'on' method ensures the specific handler (event: E) is correctly typed.
|
|
26
|
-
(event: A, emit: (newState: S) => void) => void | Promise<void>
|
|
27
|
-
> = new Map();
|
|
10
|
+
readonly eventHandlers: Map<
|
|
11
|
+
new (...args: any[]) => A,
|
|
12
|
+
(event: A, emit: (newState: S) => void) => void | Promise<void>
|
|
13
|
+
> = new Map();
|
|
28
14
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* @internal
|
|
37
|
-
* Flag indicating if an event is currently being processed
|
|
38
|
-
*/
|
|
39
|
-
private _isProcessingEvent = false;
|
|
15
|
+
/**
|
|
16
|
+
* @internal
|
|
17
|
+
* Event queue to ensure sequential processing of async events
|
|
18
|
+
*/
|
|
19
|
+
private _eventQueue: A[] = [];
|
|
40
20
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
// Cast the specific handler (event: E) to a more general (event: A) for storage.
|
|
63
|
-
// This is safe because E extends A. When the handler is called with an 'action' of type A,
|
|
64
|
-
// if it was originally registered for type E, 'action' must be an instance of E.
|
|
65
|
-
this.eventHandlers.set(
|
|
66
|
-
eventConstructor,
|
|
67
|
-
handler as (event: A, emit: (newState: S) => void) => void | Promise<void>
|
|
68
|
-
);
|
|
21
|
+
/**
|
|
22
|
+
* @internal
|
|
23
|
+
* Flag indicating if an event is currently being processed
|
|
24
|
+
*/
|
|
25
|
+
private _isProcessingEvent = false;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Registers an event handler for a specific event type.
|
|
29
|
+
* This method is typically called in the constructor of a derived Bloc class.
|
|
30
|
+
* @param eventConstructor The constructor of the event to handle (e.g., LoadDataEvent).
|
|
31
|
+
* @param handler A function that processes the event and can emit new states.
|
|
32
|
+
* The 'event' parameter in the handler will be typed to the specific eventConstructor.
|
|
33
|
+
*/
|
|
34
|
+
protected on<E extends A>(
|
|
35
|
+
eventConstructor: new (...args: any[]) => E,
|
|
36
|
+
handler: (event: E, emit: (newState: S) => void) => void | Promise<void>,
|
|
37
|
+
): void {
|
|
38
|
+
if (this.eventHandlers.has(eventConstructor)) {
|
|
39
|
+
Blac.warn(
|
|
40
|
+
`[Bloc ${this._name}:${String(this._id)}] Handler for event '${eventConstructor.name}' already registered. It will be overwritten.`,
|
|
41
|
+
);
|
|
69
42
|
}
|
|
43
|
+
this.eventHandlers.set(
|
|
44
|
+
eventConstructor,
|
|
45
|
+
handler as (
|
|
46
|
+
event: A,
|
|
47
|
+
emit: (newState: S) => void,
|
|
48
|
+
) => void | Promise<void>,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
70
51
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
this._eventQueue.push(action);
|
|
79
|
-
|
|
80
|
-
// If not already processing, start processing the queue
|
|
81
|
-
if (!this._isProcessingEvent) {
|
|
82
|
-
await this._processEventQueue();
|
|
83
|
-
}
|
|
84
|
-
};
|
|
52
|
+
/**
|
|
53
|
+
* Dispatches an action/event to the Bloc.
|
|
54
|
+
* Events are queued and processed sequentially to prevent race conditions.
|
|
55
|
+
* @param action The action/event instance to be processed.
|
|
56
|
+
*/
|
|
57
|
+
public add = async (action: A): Promise<void> => {
|
|
58
|
+
this._eventQueue.push(action);
|
|
85
59
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
60
|
+
if (!this._isProcessingEvent) {
|
|
61
|
+
await this._processEventQueue();
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @internal
|
|
67
|
+
* Processes events from the queue sequentially
|
|
68
|
+
*/
|
|
69
|
+
private async _processEventQueue(): Promise<void> {
|
|
70
|
+
if (this._isProcessingEvent) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this._isProcessingEvent = true;
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
while (this._eventQueue.length > 0) {
|
|
78
|
+
const action = this._eventQueue.shift()!;
|
|
79
|
+
await this._processEvent(action);
|
|
80
|
+
}
|
|
81
|
+
} finally {
|
|
82
|
+
this._isProcessingEvent = false;
|
|
106
83
|
}
|
|
84
|
+
}
|
|
107
85
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
// JavaScript's constructor property returns 'Function', but we need the specific event
|
|
116
|
-
// constructor type to look up handlers. This is safe because we validate the action
|
|
117
|
-
// extends the BlocEventConstraint interface.
|
|
118
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
119
|
-
const eventConstructor = action.constructor as new (...args: any[]) => A;
|
|
120
|
-
const handler = this.eventHandlers.get(eventConstructor);
|
|
86
|
+
/**
|
|
87
|
+
* @internal
|
|
88
|
+
* Processes a single event
|
|
89
|
+
*/
|
|
90
|
+
private async _processEvent(action: A): Promise<void> {
|
|
91
|
+
const eventConstructor = action.constructor as new (...args: any[]) => A;
|
|
92
|
+
const handler = this.eventHandlers.get(eventConstructor);
|
|
121
93
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
94
|
+
if (handler) {
|
|
95
|
+
const emit = (newState: S): void => {
|
|
96
|
+
const previousState = this.state; // State just before this specific emission
|
|
97
|
+
this._pushState(newState, previousState, action);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
await handler(action, emit);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
const constructorName =
|
|
104
|
+
(action.constructor as { name?: string }).name ||
|
|
105
|
+
'UnnamedConstructor';
|
|
106
|
+
const errorContext = {
|
|
107
|
+
blocName: this._name,
|
|
108
|
+
blocId: String(this._id),
|
|
109
|
+
eventType: constructorName,
|
|
110
|
+
currentState: this.state,
|
|
111
|
+
action: action,
|
|
112
|
+
timestamp: new Date().toISOString(),
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
Blac.error(
|
|
116
|
+
`[Bloc ${this._name}:${String(this._id)}] Error in event handler for '${constructorName}':`,
|
|
117
|
+
error,
|
|
118
|
+
'Context:',
|
|
119
|
+
errorContext,
|
|
120
|
+
);
|
|
132
121
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
// The 'action' is passed to the handler, and due to the way 'on' is typed,
|
|
136
|
-
// the 'event' parameter within the handler function will be correctly
|
|
137
|
-
// typed to its specific class (e.g., LoadMyFeatureData).
|
|
138
|
-
await handler(action, emit);
|
|
139
|
-
} catch (error) {
|
|
140
|
-
// Enhanced error handling with better context
|
|
141
|
-
const constructorName = (action.constructor as { name?: string }).name || 'UnnamedConstructor';
|
|
142
|
-
const errorContext = {
|
|
143
|
-
blocName: this._name,
|
|
144
|
-
blocId: String(this._id),
|
|
145
|
-
eventType: constructorName,
|
|
146
|
-
currentState: this.state,
|
|
147
|
-
action: action,
|
|
148
|
-
timestamp: new Date().toISOString()
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
Blac.error(
|
|
152
|
-
`[Bloc ${this._name}:${String(this._id)}] Error in event handler for '${constructorName}':`,
|
|
153
|
-
error,
|
|
154
|
-
"Context:", errorContext
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
// TODO: Consider implementing error boundary pattern
|
|
158
|
-
// For now, we log and continue, but applications may want to:
|
|
159
|
-
// 1. Emit an error state
|
|
160
|
-
// 2. Re-throw the error
|
|
161
|
-
// 3. Call an error handler callback
|
|
162
|
-
|
|
163
|
-
// Optional: Re-throw for critical errors (can be configured)
|
|
164
|
-
if (error instanceof Error && error.name === 'CriticalError') {
|
|
165
|
-
throw error;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
} else {
|
|
169
|
-
// Enhanced warning with more context
|
|
170
|
-
const constructorName = (action.constructor as { name?: string }).name || 'UnnamedConstructor';
|
|
171
|
-
Blac.warn(
|
|
172
|
-
`[Bloc ${this._name}:${String(this._id)}] No handler registered for action type: '${constructorName}'.`,
|
|
173
|
-
"Registered handlers:", Array.from(this.eventHandlers.keys()).map(k => k.name),
|
|
174
|
-
"Action was:", action
|
|
175
|
-
);
|
|
122
|
+
if (error instanceof Error && error.name === 'CriticalError') {
|
|
123
|
+
throw error;
|
|
176
124
|
}
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
const constructorName =
|
|
128
|
+
(action.constructor as { name?: string }).name || 'UnnamedConstructor';
|
|
129
|
+
Blac.warn(
|
|
130
|
+
`[Bloc ${this._name}:${String(this._id)}] No handler registered for action type: '${constructorName}'.`,
|
|
131
|
+
'Registered handlers:',
|
|
132
|
+
Array.from(this.eventHandlers.keys()).map((k) => k.name),
|
|
133
|
+
'Action was:',
|
|
134
|
+
action,
|
|
135
|
+
);
|
|
177
136
|
}
|
|
137
|
+
}
|
|
178
138
|
}
|