@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/BlocBase.ts
DELETED
|
@@ -1,589 +0,0 @@
|
|
|
1
|
-
import { BlacObservable } from './BlacObserver';
|
|
2
|
-
import { generateUUID } from './utils/uuid';
|
|
3
|
-
|
|
4
|
-
export type BlocInstanceId = string | number | undefined;
|
|
5
|
-
type DependencySelector<S> = (
|
|
6
|
-
currentState: S,
|
|
7
|
-
previousState: S | undefined,
|
|
8
|
-
instance: any,
|
|
9
|
-
) => unknown[];
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Enum representing the lifecycle states of a Bloc instance
|
|
13
|
-
* Used for atomic state transitions to prevent race conditions
|
|
14
|
-
*/
|
|
15
|
-
export enum BlocLifecycleState {
|
|
16
|
-
ACTIVE = 'active',
|
|
17
|
-
DISPOSAL_REQUESTED = 'disposal_requested',
|
|
18
|
-
DISPOSING = 'disposing',
|
|
19
|
-
DISPOSED = 'disposed',
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Result of an atomic state transition operation
|
|
24
|
-
*/
|
|
25
|
-
export interface StateTransitionResult {
|
|
26
|
-
success: boolean;
|
|
27
|
-
currentState: BlocLifecycleState;
|
|
28
|
-
previousState: BlocLifecycleState;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Define an interface for the static properties expected on a Bloc/Cubit constructor
|
|
32
|
-
interface BlocStaticProperties {
|
|
33
|
-
isolated: boolean;
|
|
34
|
-
keepAlive: boolean;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Base class for both Blocs and Cubits that provides core state management functionality.
|
|
39
|
-
* Handles state transitions, observer notifications, lifecycle management, and addon integration.
|
|
40
|
-
*
|
|
41
|
-
* @abstract This class should be extended, not instantiated directly
|
|
42
|
-
* @template S The type of state managed by this Bloc
|
|
43
|
-
* @template P The type of props that can be passed during instance creation (optional)
|
|
44
|
-
*/
|
|
45
|
-
export abstract class BlocBase<S, P = unknown> {
|
|
46
|
-
public uid = generateUUID();
|
|
47
|
-
/**
|
|
48
|
-
* When true, every consumer will receive its own unique instance of this Bloc.
|
|
49
|
-
* Use this when state should not be shared between components.
|
|
50
|
-
* @default false
|
|
51
|
-
*/
|
|
52
|
-
static isolated = false;
|
|
53
|
-
get isIsolated() {
|
|
54
|
-
return this._isolated;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* When true, the Bloc instance persists even when there are no active consumers.
|
|
59
|
-
* Useful for maintaining state between component unmount/remount cycles.
|
|
60
|
-
* @default false
|
|
61
|
-
*/
|
|
62
|
-
static keepAlive = false;
|
|
63
|
-
get isKeepAlive() {
|
|
64
|
-
return this._keepAlive;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Defines how dependencies are selected from the state for efficient updates.
|
|
69
|
-
* When provided, observers will only be notified when selected dependencies change.
|
|
70
|
-
*/
|
|
71
|
-
defaultDependencySelector: DependencySelector<S> | undefined;
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* @internal
|
|
75
|
-
* Indicates if this specific Bloc instance is isolated from others of the same type.
|
|
76
|
-
*/
|
|
77
|
-
public _isolated = false;
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* @internal
|
|
81
|
-
* Observable responsible for managing state listeners and notifying consumers.
|
|
82
|
-
*/
|
|
83
|
-
public _observer: BlacObservable<S>;
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* The unique identifier for this Bloc instance.
|
|
87
|
-
* Defaults to the class name, but can be customized.
|
|
88
|
-
*/
|
|
89
|
-
public _id: BlocInstanceId;
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* @internal
|
|
93
|
-
* Reference string used internally for tracking and debugging.
|
|
94
|
-
*/
|
|
95
|
-
public _instanceRef?: string;
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* @internal
|
|
99
|
-
* Indicates if this specific Bloc instance should be kept alive when no consumers are present.
|
|
100
|
-
*/
|
|
101
|
-
public _keepAlive = false;
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* @readonly
|
|
105
|
-
* Timestamp when this Bloc instance was created, useful for debugging and performance tracking.
|
|
106
|
-
*/
|
|
107
|
-
public readonly _createdAt = Date.now();
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* @internal
|
|
111
|
-
* Atomic disposal state to prevent race conditions
|
|
112
|
-
*/
|
|
113
|
-
private _disposalState: BlocLifecycleState = BlocLifecycleState.ACTIVE;
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* @internal
|
|
117
|
-
* Timestamp when disposal was requested (for React Strict Mode grace period)
|
|
118
|
-
*/
|
|
119
|
-
private _disposalRequestTime: number = 0;
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* @internal
|
|
123
|
-
* The current state of the Bloc.
|
|
124
|
-
*/
|
|
125
|
-
public _state: S;
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* @internal
|
|
129
|
-
* The previous state of the Bloc, maintained for comparison and history.
|
|
130
|
-
*/
|
|
131
|
-
public _oldState: S | undefined;
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Props passed during Bloc instance creation.
|
|
135
|
-
* Can be used to configure or parameterize the Bloc's behavior.
|
|
136
|
-
*/
|
|
137
|
-
public props: P | null = null;
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* @internal
|
|
141
|
-
* Flag to prevent batching race conditions
|
|
142
|
-
*/
|
|
143
|
-
private _batchingLock = false;
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* @internal
|
|
147
|
-
* Pending batched updates
|
|
148
|
-
*/
|
|
149
|
-
private _pendingUpdates: Array<{
|
|
150
|
-
newState: S;
|
|
151
|
-
oldState: S;
|
|
152
|
-
action?: unknown;
|
|
153
|
-
}> = [];
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* @internal
|
|
157
|
-
* Map of consumer IDs to their WeakRef objects for proper cleanup
|
|
158
|
-
*/
|
|
159
|
-
private _consumerRefs = new Map<string, WeakRef<object>>();
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* @internal
|
|
163
|
-
* Validates that all consumer references are still alive
|
|
164
|
-
* Removes dead consumers automatically
|
|
165
|
-
*/
|
|
166
|
-
_validateConsumers = (): void => {
|
|
167
|
-
const deadConsumers: string[] = [];
|
|
168
|
-
|
|
169
|
-
for (const [consumerId, weakRef] of this._consumerRefs) {
|
|
170
|
-
if (weakRef.deref() === undefined) {
|
|
171
|
-
deadConsumers.push(consumerId);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Clean up dead consumers
|
|
176
|
-
for (const consumerId of deadConsumers) {
|
|
177
|
-
this._consumers.delete(consumerId);
|
|
178
|
-
this._consumerRefs.delete(consumerId);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Schedule disposal if no live consumers remain
|
|
182
|
-
if (
|
|
183
|
-
this._consumers.size === 0 &&
|
|
184
|
-
!this._keepAlive &&
|
|
185
|
-
this._disposalState === BlocLifecycleState.ACTIVE
|
|
186
|
-
) {
|
|
187
|
-
this._scheduleDisposal();
|
|
188
|
-
}
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Creates a new BlocBase instance with the given initial state.
|
|
193
|
-
* Sets up the observer, registers with the Blac manager, and initializes addons.
|
|
194
|
-
*
|
|
195
|
-
* @param initialState The initial state value for this Bloc
|
|
196
|
-
*/
|
|
197
|
-
constructor(initialState: S) {
|
|
198
|
-
this._state = initialState;
|
|
199
|
-
this._observer = new BlacObservable(this);
|
|
200
|
-
this._id = this.constructor.name;
|
|
201
|
-
|
|
202
|
-
// Access static properties safely with proper type checking
|
|
203
|
-
const Constructor = this.constructor as typeof BlocBase &
|
|
204
|
-
BlocStaticProperties;
|
|
205
|
-
|
|
206
|
-
// Validate that the static properties exist and are boolean
|
|
207
|
-
this._keepAlive =
|
|
208
|
-
typeof Constructor.keepAlive === 'boolean'
|
|
209
|
-
? Constructor.keepAlive
|
|
210
|
-
: false;
|
|
211
|
-
this._isolated =
|
|
212
|
-
typeof Constructor.isolated === 'boolean' ? Constructor.isolated : false;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Returns the current state of the Bloc.
|
|
217
|
-
* Use this getter to access the state in a read-only manner.
|
|
218
|
-
* Returns the state even during transitional lifecycle states for React compatibility.
|
|
219
|
-
*/
|
|
220
|
-
get state(): S {
|
|
221
|
-
// Allow state access during all states except DISPOSED for React compatibility
|
|
222
|
-
if (this._disposalState === BlocLifecycleState.DISPOSED) {
|
|
223
|
-
// Return the last known state for disposed blocs to prevent crashes
|
|
224
|
-
return this._state;
|
|
225
|
-
}
|
|
226
|
-
return this._state;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Returns whether this Bloc instance has been disposed.
|
|
231
|
-
* @returns true if the bloc is in DISPOSED state
|
|
232
|
-
*/
|
|
233
|
-
get isDisposed(): boolean {
|
|
234
|
-
return this._disposalState === BlocLifecycleState.DISPOSED;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* @internal
|
|
239
|
-
* Returns the name of the Bloc class for identification and debugging.
|
|
240
|
-
*/
|
|
241
|
-
get _name() {
|
|
242
|
-
return this.constructor.name;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* @internal
|
|
247
|
-
* Updates the Bloc instance's ID to a new value.
|
|
248
|
-
* Only updates if the new ID is defined and different from the current one.
|
|
249
|
-
*
|
|
250
|
-
* @param id The new ID to assign to this Bloc instance
|
|
251
|
-
*/
|
|
252
|
-
_updateId = (id?: BlocInstanceId) => {
|
|
253
|
-
const originalId = this._id;
|
|
254
|
-
if (!id || id === originalId) return;
|
|
255
|
-
this._id = id;
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* @internal
|
|
260
|
-
* Performs atomic state transition using compare-and-swap semantics
|
|
261
|
-
* @param expectedState The expected current state
|
|
262
|
-
* @param newState The desired new state
|
|
263
|
-
* @returns Result indicating success/failure and state information
|
|
264
|
-
*/
|
|
265
|
-
_atomicStateTransition(
|
|
266
|
-
expectedState: BlocLifecycleState,
|
|
267
|
-
newState: BlocLifecycleState,
|
|
268
|
-
): StateTransitionResult {
|
|
269
|
-
if (this._disposalState === expectedState) {
|
|
270
|
-
const previousState = this._disposalState;
|
|
271
|
-
this._disposalState = newState;
|
|
272
|
-
|
|
273
|
-
// Log state transition for debugging
|
|
274
|
-
if ((globalThis as any).Blac?.enableLog) {
|
|
275
|
-
(globalThis as any).Blac?.log(
|
|
276
|
-
`[${this._name}:${this._id}] State transition: ${previousState} -> ${newState} (SUCCESS)`,
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
return {
|
|
281
|
-
success: true,
|
|
282
|
-
currentState: newState,
|
|
283
|
-
previousState,
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Log failed transition attempt
|
|
288
|
-
if ((globalThis as any).Blac?.enableLog) {
|
|
289
|
-
(globalThis as any).Blac?.log(
|
|
290
|
-
`[${this._name}:${this._id}] State transition failed: expected ${expectedState}, current ${this._disposalState}`,
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
return {
|
|
295
|
-
success: false,
|
|
296
|
-
currentState: this._disposalState,
|
|
297
|
-
previousState: expectedState,
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* @internal
|
|
303
|
-
* Cleans up resources and removes this Bloc from the system.
|
|
304
|
-
* Notifies the Blac manager and clears all observers.
|
|
305
|
-
*/
|
|
306
|
-
_dispose(): boolean {
|
|
307
|
-
// Step 1: Attempt atomic transition to DISPOSING state from either ACTIVE or DISPOSAL_REQUESTED
|
|
308
|
-
let transitionResult = this._atomicStateTransition(
|
|
309
|
-
BlocLifecycleState.ACTIVE,
|
|
310
|
-
BlocLifecycleState.DISPOSING,
|
|
311
|
-
);
|
|
312
|
-
|
|
313
|
-
// If that failed, try from DISPOSAL_REQUESTED state
|
|
314
|
-
if (!transitionResult.success) {
|
|
315
|
-
transitionResult = this._atomicStateTransition(
|
|
316
|
-
BlocLifecycleState.DISPOSAL_REQUESTED,
|
|
317
|
-
BlocLifecycleState.DISPOSING,
|
|
318
|
-
);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if (!transitionResult.success) {
|
|
322
|
-
// Already disposing or disposed - idempotent operation
|
|
323
|
-
return false;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
try {
|
|
327
|
-
// Step 2: Perform cleanup operations
|
|
328
|
-
this._consumers.clear();
|
|
329
|
-
this._consumerRefs.clear();
|
|
330
|
-
this._observer.clear();
|
|
331
|
-
|
|
332
|
-
// Call user-defined disposal hook
|
|
333
|
-
this.onDispose?.();
|
|
334
|
-
|
|
335
|
-
// Step 3: Final state transition to DISPOSED
|
|
336
|
-
const finalResult = this._atomicStateTransition(
|
|
337
|
-
BlocLifecycleState.DISPOSING,
|
|
338
|
-
BlocLifecycleState.DISPOSED,
|
|
339
|
-
);
|
|
340
|
-
|
|
341
|
-
return finalResult.success;
|
|
342
|
-
} catch (error) {
|
|
343
|
-
// Recovery: Reset state on cleanup failure
|
|
344
|
-
this._disposalState = BlocLifecycleState.ACTIVE;
|
|
345
|
-
throw error;
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
* @internal
|
|
351
|
-
* Optional function to be called when the Bloc is disposed.
|
|
352
|
-
*/
|
|
353
|
-
onDispose?: () => void;
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* @internal
|
|
357
|
-
* Set of consumer IDs currently listening to this Bloc's state changes.
|
|
358
|
-
*/
|
|
359
|
-
_consumers = new Set<string>();
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* @internal
|
|
363
|
-
* Registers a new consumer to this Bloc instance.
|
|
364
|
-
* Notifies the Blac manager that a consumer has been added.
|
|
365
|
-
*
|
|
366
|
-
* @param consumerId The unique ID of the consumer being added
|
|
367
|
-
* @param consumerRef Optional reference to the consumer object for cleanup validation
|
|
368
|
-
*/
|
|
369
|
-
_addConsumer = (consumerId: string, consumerRef?: object): boolean => {
|
|
370
|
-
// Atomic state validation - only allow consumer addition in ACTIVE state
|
|
371
|
-
if (this._disposalState !== BlocLifecycleState.ACTIVE) {
|
|
372
|
-
return false; // Clear failure indication
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Prevent duplicate consumers
|
|
376
|
-
if (this._consumers.has(consumerId)) return true;
|
|
377
|
-
|
|
378
|
-
// Safe consumer addition
|
|
379
|
-
this._consumers.add(consumerId);
|
|
380
|
-
|
|
381
|
-
// Store WeakRef for proper memory management
|
|
382
|
-
if (consumerRef) {
|
|
383
|
-
this._consumerRefs.set(consumerId, new WeakRef(consumerRef));
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// @ts-ignore - Blac is available globally
|
|
387
|
-
(globalThis as any).Blac?.log(
|
|
388
|
-
`[${this._name}:${this._id}] Consumer added. Total consumers: ${this._consumers.size}`,
|
|
389
|
-
);
|
|
390
|
-
|
|
391
|
-
// this._blac.dispatchEvent(BlacLifecycleEvent.BLOC_CONSUMER_ADDED, this, { consumerId });
|
|
392
|
-
return true;
|
|
393
|
-
};
|
|
394
|
-
|
|
395
|
-
/**
|
|
396
|
-
* @internal
|
|
397
|
-
* Unregisters a consumer from this Bloc instance.
|
|
398
|
-
* Notifies the Blac manager that a consumer has been removed.
|
|
399
|
-
*
|
|
400
|
-
* @param consumerId The unique ID of the consumer being removed
|
|
401
|
-
*/
|
|
402
|
-
_removeConsumer = (consumerId: string) => {
|
|
403
|
-
if (!this._consumers.has(consumerId)) return;
|
|
404
|
-
|
|
405
|
-
this._consumers.delete(consumerId);
|
|
406
|
-
this._consumerRefs.delete(consumerId);
|
|
407
|
-
// this._blac.dispatchEvent(BlacLifecycleEvent.BLOC_CONSUMER_REMOVED, this, { consumerId });
|
|
408
|
-
|
|
409
|
-
// @ts-ignore - Blac is available globally
|
|
410
|
-
(globalThis as any).Blac?.log(
|
|
411
|
-
`[${this._name}:${this._id}] Consumer removed. Remaining consumers: ${this._consumers.size}, keepAlive: ${this._keepAlive}`,
|
|
412
|
-
);
|
|
413
|
-
|
|
414
|
-
// If no consumers remain and not keep-alive, schedule disposal
|
|
415
|
-
if (
|
|
416
|
-
this._consumers.size === 0 &&
|
|
417
|
-
!this._keepAlive &&
|
|
418
|
-
this._disposalState === BlocLifecycleState.ACTIVE
|
|
419
|
-
) {
|
|
420
|
-
// @ts-ignore - Blac is available globally
|
|
421
|
-
(globalThis as any).Blac?.log(
|
|
422
|
-
`[${this._name}:${this._id}] No consumers left and not keep-alive. Scheduling disposal.`,
|
|
423
|
-
);
|
|
424
|
-
this._scheduleDisposal();
|
|
425
|
-
}
|
|
426
|
-
};
|
|
427
|
-
|
|
428
|
-
/**
|
|
429
|
-
* @internal
|
|
430
|
-
* Handler function for disposal (can be set by Blac manager)
|
|
431
|
-
*/
|
|
432
|
-
private _disposalHandler?: (bloc: BlocBase<unknown>) => void;
|
|
433
|
-
|
|
434
|
-
/**
|
|
435
|
-
* @internal
|
|
436
|
-
* Sets the disposal handler for this bloc
|
|
437
|
-
*/
|
|
438
|
-
_setDisposalHandler(handler: (bloc: BlocBase<unknown>) => void) {
|
|
439
|
-
this._disposalHandler = handler;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
/**
|
|
443
|
-
* @internal
|
|
444
|
-
* Schedules disposal of this bloc instance if it has no consumers
|
|
445
|
-
* Uses atomic state transitions to prevent race conditions
|
|
446
|
-
*/
|
|
447
|
-
private _scheduleDisposal(): void {
|
|
448
|
-
// Step 1: Atomic transition to DISPOSAL_REQUESTED
|
|
449
|
-
const requestResult = this._atomicStateTransition(
|
|
450
|
-
BlocLifecycleState.ACTIVE,
|
|
451
|
-
BlocLifecycleState.DISPOSAL_REQUESTED,
|
|
452
|
-
);
|
|
453
|
-
|
|
454
|
-
if (!requestResult.success) {
|
|
455
|
-
// Already requested, disposing, or disposed
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// Step 2: Verify disposal conditions under atomic protection
|
|
460
|
-
const shouldDispose = this._consumers.size === 0 && !this._keepAlive;
|
|
461
|
-
|
|
462
|
-
if (!shouldDispose) {
|
|
463
|
-
// Conditions no longer met, revert to active
|
|
464
|
-
this._atomicStateTransition(
|
|
465
|
-
BlocLifecycleState.DISPOSAL_REQUESTED,
|
|
466
|
-
BlocLifecycleState.ACTIVE,
|
|
467
|
-
);
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
// Record disposal request time for tracking
|
|
472
|
-
this._disposalRequestTime = Date.now();
|
|
473
|
-
|
|
474
|
-
// Step 3: Defer disposal until after current execution completes
|
|
475
|
-
// This allows React Strict Mode's immediate remount to cancel disposal
|
|
476
|
-
queueMicrotask(() => {
|
|
477
|
-
// Re-verify disposal conditions - React Strict Mode remount may have cancelled this
|
|
478
|
-
const stillShouldDispose =
|
|
479
|
-
this._consumers.size === 0 &&
|
|
480
|
-
!this._keepAlive &&
|
|
481
|
-
this._observer.size === 0 &&
|
|
482
|
-
(this as any)._disposalState === BlocLifecycleState.DISPOSAL_REQUESTED;
|
|
483
|
-
|
|
484
|
-
if (stillShouldDispose) {
|
|
485
|
-
// No cancellation occurred, proceed with disposal
|
|
486
|
-
if (this._disposalHandler) {
|
|
487
|
-
this._disposalHandler(this as any);
|
|
488
|
-
} else {
|
|
489
|
-
this._dispose();
|
|
490
|
-
}
|
|
491
|
-
} else {
|
|
492
|
-
// Disposal was cancelled (React Strict Mode remount), revert to active
|
|
493
|
-
this._atomicStateTransition(
|
|
494
|
-
BlocLifecycleState.DISPOSAL_REQUESTED,
|
|
495
|
-
BlocLifecycleState.ACTIVE,
|
|
496
|
-
);
|
|
497
|
-
}
|
|
498
|
-
});
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
lastUpdate = Date.now();
|
|
502
|
-
|
|
503
|
-
/**
|
|
504
|
-
* @internal
|
|
505
|
-
* Flag to indicate if batching is enabled for this bloc
|
|
506
|
-
*/
|
|
507
|
-
private _batchingEnabled = false;
|
|
508
|
-
|
|
509
|
-
/**
|
|
510
|
-
* @internal
|
|
511
|
-
* Updates the state and notifies all observers of the change.
|
|
512
|
-
*
|
|
513
|
-
* @param newState The new state to be set
|
|
514
|
-
* @param oldState The previous state for comparison
|
|
515
|
-
* @param action Optional metadata about what caused the state change
|
|
516
|
-
*/
|
|
517
|
-
_pushState = (newState: S, oldState: S, action?: unknown): void => {
|
|
518
|
-
// Validate newState
|
|
519
|
-
if (newState === undefined) {
|
|
520
|
-
return;
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
// Validate action type if provided
|
|
524
|
-
if (
|
|
525
|
-
action !== undefined &&
|
|
526
|
-
typeof action !== 'object' &&
|
|
527
|
-
typeof action !== 'function'
|
|
528
|
-
) {
|
|
529
|
-
return;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
if (this._batchingEnabled) {
|
|
533
|
-
// When batching, just accumulate the updates
|
|
534
|
-
this._pendingUpdates.push({ newState, oldState, action });
|
|
535
|
-
|
|
536
|
-
// Update internal state for consistency
|
|
537
|
-
this._oldState = oldState;
|
|
538
|
-
this._state = newState;
|
|
539
|
-
return;
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
// Normal state update flow
|
|
543
|
-
this._oldState = oldState;
|
|
544
|
-
this._state = newState;
|
|
545
|
-
|
|
546
|
-
// Notify observers of the state change
|
|
547
|
-
this._observer.notify(newState, oldState, action);
|
|
548
|
-
this.lastUpdate = Date.now();
|
|
549
|
-
};
|
|
550
|
-
|
|
551
|
-
/**
|
|
552
|
-
* Enables batching for multiple state updates
|
|
553
|
-
* @param batchFn Function to execute with batching enabled
|
|
554
|
-
*/
|
|
555
|
-
batch = <T>(batchFn: () => T): T => {
|
|
556
|
-
// Prevent batching race conditions
|
|
557
|
-
if (this._batchingLock) {
|
|
558
|
-
// If already batching, just execute the function without nesting batches
|
|
559
|
-
return batchFn();
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
this._batchingLock = true;
|
|
563
|
-
this._batchingEnabled = true;
|
|
564
|
-
this._pendingUpdates = [];
|
|
565
|
-
|
|
566
|
-
try {
|
|
567
|
-
const result = batchFn();
|
|
568
|
-
|
|
569
|
-
// Process all batched updates
|
|
570
|
-
if (this._pendingUpdates.length > 0) {
|
|
571
|
-
// Only notify once with the final state
|
|
572
|
-
const finalUpdate =
|
|
573
|
-
this._pendingUpdates[this._pendingUpdates.length - 1];
|
|
574
|
-
this._observer.notify(
|
|
575
|
-
finalUpdate.newState,
|
|
576
|
-
finalUpdate.oldState,
|
|
577
|
-
finalUpdate.action,
|
|
578
|
-
);
|
|
579
|
-
this.lastUpdate = Date.now();
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
return result;
|
|
583
|
-
} finally {
|
|
584
|
-
this._batchingEnabled = false;
|
|
585
|
-
this._batchingLock = false;
|
|
586
|
-
this._pendingUpdates = [];
|
|
587
|
-
}
|
|
588
|
-
};
|
|
589
|
-
}
|
package/src/Cubit.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { Blac } from './Blac';
|
|
2
|
-
import { BlocBase } from './BlocBase';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* A Cubit is a simpler version of a Bloc that doesn't handle events.
|
|
6
|
-
* It manages state and provides methods to update it.
|
|
7
|
-
* @template S - The type of state this Cubit manages
|
|
8
|
-
* @template P - The type of parameters (optional, defaults to null)
|
|
9
|
-
*/
|
|
10
|
-
export abstract class Cubit<S, P = null> extends BlocBase<S, P> {
|
|
11
|
-
/**
|
|
12
|
-
* @internal
|
|
13
|
-
* Protected method for useBloc to call
|
|
14
|
-
*/
|
|
15
|
-
protected _updateProps(props: P): void {
|
|
16
|
-
const oldProps = this.props;
|
|
17
|
-
this.props = props;
|
|
18
|
-
this.onPropsChanged?.(oldProps as P | undefined, props);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Optional override for props handling
|
|
23
|
-
*/
|
|
24
|
-
protected onPropsChanged?(oldProps: P | undefined, newProps: P): void;
|
|
25
|
-
/**
|
|
26
|
-
* Updates the current state and notifies all observers of the change.
|
|
27
|
-
* If the new state is identical to the current state (using Object.is),
|
|
28
|
-
* no update will occur.
|
|
29
|
-
* @param state - The new state to set
|
|
30
|
-
*/
|
|
31
|
-
emit(state: S): void {
|
|
32
|
-
if (Object.is(state, this.state)) {
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const oldState = this.state;
|
|
37
|
-
const newState = state;
|
|
38
|
-
this._pushState(newState, oldState);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Partially updates the current state by merging it with the provided state patch.
|
|
43
|
-
* This method is only applicable when the state is an object type.
|
|
44
|
-
*
|
|
45
|
-
* @param statePatch - A partial state object containing only the properties to update
|
|
46
|
-
* @param ignoreChangeCheck - If true, skips checking if the state has actually changed
|
|
47
|
-
* @throws {TypeError} If the state is not an object type
|
|
48
|
-
*/
|
|
49
|
-
patch(
|
|
50
|
-
statePatch: S extends object ? Partial<S> : S,
|
|
51
|
-
ignoreChangeCheck = false,
|
|
52
|
-
): void {
|
|
53
|
-
if (typeof this.state !== 'object' || this.state === null) {
|
|
54
|
-
Blac.warn(
|
|
55
|
-
'Cubit.patch: was called on a cubit where the state is not an object. This is a no-op.',
|
|
56
|
-
);
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
let changes = false;
|
|
61
|
-
if (!ignoreChangeCheck) {
|
|
62
|
-
for (const key in statePatch) {
|
|
63
|
-
if (Object.prototype.hasOwnProperty.call(statePatch, key)) {
|
|
64
|
-
const s = this.state;
|
|
65
|
-
const current = s[key as keyof S];
|
|
66
|
-
if (!Object.is(statePatch[key as keyof S], current)) {
|
|
67
|
-
changes = true;
|
|
68
|
-
break;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
} else {
|
|
73
|
-
changes = true;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (changes) {
|
|
77
|
-
this.emit({
|
|
78
|
-
...this.state,
|
|
79
|
-
...(statePatch as Partial<S>),
|
|
80
|
-
} as S);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|