@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 CHANGED
@@ -1,329 +1,263 @@
1
1
  # @blac/core
2
2
 
3
- A lightweight, flexible state management library for JavaScript/TypeScript applications focusing on predictable state transitions.
4
-
5
- ## Features
6
-
7
- - ๐Ÿ”„ Predictable unidirectional data flow
8
- - ๐Ÿงฉ Modular architecture with Blocs and Cubits
9
- - ๐Ÿงช Unit test friendly
10
- - ๐Ÿ”’ Isolated state instances when needed
11
- - ๐Ÿ”Œ Plugin system for extensibility
3
+ Core state management library implementing the BloC pattern for TypeScript applications.
12
4
 
13
5
  ## Installation
14
6
 
15
- Install `@blac/core` using your favorite package manager:
16
-
17
7
  ```bash
18
- # pnpm
8
+ npm install @blac/core
9
+ # or
19
10
  pnpm add @blac/core
20
-
21
- # yarn
11
+ # or
22
12
  yarn add @blac/core
23
-
24
- # npm
25
- npm install @blac/core
26
- ```
27
-
28
- ## Configuration
29
-
30
- BlaC provides global configuration options to customize its behavior:
31
-
32
- ```typescript
33
- import { Blac } from '@blac/core';
34
-
35
- // Configure BlaC before using it
36
- Blac.setConfig({
37
- // Enable/disable automatic dependency tracking for optimized re-renders
38
- proxyDependencyTracking: true, // default: true
39
-
40
- // Expose Blac instance globally for debugging
41
- exposeBlacInstance: false // default: false
42
- });
43
-
44
- // Read current configuration
45
- const config = Blac.config;
46
- console.log(config.proxyDependencyTracking); // true
47
- ```
48
-
49
- ### Configuration Options
50
-
51
- - **`proxyDependencyTracking`**: When enabled (default), BlaC automatically tracks which state properties your components access and only triggers re-renders when those specific properties change. Disable this for simpler behavior where any state change triggers re-renders.
52
-
53
- - **`exposeBlacInstance`**: When enabled, exposes the BlaC instance globally (useful for debugging). Not recommended for production.
54
-
55
- ## Testing
56
-
57
- Blac provides comprehensive testing utilities to make testing your state management logic simple and powerful:
58
-
59
- ```typescript
60
- import { BlocTest, MockCubit, MemoryLeakDetector } from '@blac/core';
61
-
62
- describe('Counter Tests', () => {
63
- beforeEach(() => BlocTest.setUp());
64
- afterEach(() => BlocTest.tearDown());
65
-
66
- it('should increment counter', async () => {
67
- const counter = BlocTest.createBloc(CounterCubit);
68
-
69
- counter.increment();
70
-
71
- expect(counter.state.count).toBe(1);
72
- });
73
-
74
- it('should track state history', () => {
75
- const mockCubit = new MockCubit({ count: 0 });
76
-
77
- mockCubit.emit({ count: 1 });
78
- mockCubit.emit({ count: 2 });
79
-
80
- const history = mockCubit.getStateHistory();
81
- expect(history).toHaveLength(3); // Initial + 2 emissions
82
- });
83
-
84
- it('should detect memory leaks', () => {
85
- const detector = new MemoryLeakDetector();
86
-
87
- // Create and use blocs...
88
-
89
- const result = detector.checkForLeaks();
90
- expect(result.hasLeaks).toBe(false);
91
- });
92
- });
93
13
  ```
94
14
 
95
- **[๐Ÿ“š View Complete Testing Documentation](./docs/testing.md)**
96
-
97
15
  ## Core Concepts
98
16
 
99
- ### Blocs and Cubits
17
+ ### Cubit
100
18
 
101
- **Cubit**: A simple state container with methods to emit new states.
19
+ Simple state container with direct state emission. Use when you need straightforward state updates.
102
20
 
103
21
  ```typescript
104
- class CounterCubit extends Cubit<number> {
22
+ import { Cubit } from '@blac/core';
23
+
24
+ class CounterCubit extends Cubit<{ count: number }> {
105
25
  constructor() {
106
- super(0); // Initial state
26
+ super({ count: 0 });
27
+ }
28
+
29
+ increment() {
30
+ this.emit({ count: this.state.count + 1 });
107
31
  }
108
32
 
109
- increment = () => {
110
- this.emit(this.state + 1);
33
+ decrement() {
34
+ this.update((state) => ({ count: state.count - 1 }));
111
35
  }
112
36
 
113
- decrement = () => {
114
- this.emit(this.state - 1);
37
+ reset() {
38
+ this.patch({ count: 0 });
115
39
  }
116
40
  }
117
41
  ```
118
42
 
119
- **Bloc**: More powerful state container that uses an event-handler pattern for type-safe, event-driven state transitions. Events (instances of classes) are dispatched via `this.add()`, and handlers are registered using `this.on(EventClass, handler)`.
43
+ ### Vertex
44
+
45
+ Event-driven state container using discriminated union events. Use when you need structured event handling with type-safe exhaustive checking.
120
46
 
121
47
  ```typescript
122
- // Define event classes
123
- class IncrementEvent { constructor(public readonly amount: number = 1) {} }
124
- class DecrementEvent { constructor(public readonly amount: number = 1) {} }
48
+ import { Vertex } from '@blac/core';
125
49
 
126
- // Optional: Union type for all events
127
- type CounterEvent = IncrementEvent | DecrementEvent;
50
+ type CounterEvent =
51
+ | { type: 'increment'; amount: number }
52
+ | { type: 'decrement' }
53
+ | { type: 'reset' };
128
54
 
129
- class CounterBloc extends Bloc<number, CounterEvent> {
55
+ class CounterVertex extends Vertex<{ count: number }, CounterEvent> {
130
56
  constructor() {
131
- super(0); // Initial state
132
-
133
- // Register event handlers
134
- this.on(IncrementEvent, (event, emit) => {
135
- emit(this.state + event.amount);
136
- });
137
-
138
- this.on(DecrementEvent, (event, emit) => {
139
- emit(this.state - event.amount);
57
+ super({ count: 0 });
58
+ this.createHandlers({
59
+ increment: (event, emit) => {
60
+ emit({ count: this.state.count + event.amount });
61
+ },
62
+ decrement: (_, emit) => {
63
+ emit({ count: this.state.count - 1 });
64
+ },
65
+ reset: (_, emit) => {
66
+ emit({ count: 0 });
67
+ },
140
68
  });
141
69
  }
142
70
 
143
- // Helper methods to dispatch event instances (optional)
144
- increment = (amount = 1) => {
145
- this.add(new IncrementEvent(amount));
146
- }
147
-
148
- decrement = (amount = 1) => {
149
- this.add(new DecrementEvent(amount));
150
- }
71
+ increment = (amount = 1) => this.add({ type: 'increment', amount });
72
+ decrement = () => this.add({ type: 'decrement' });
73
+ reset = () => this.add({ type: 'reset' });
151
74
  }
152
75
  ```
153
76
 
154
- ### Important: Arrow Functions Required
77
+ ### StatelessCubit & StatelessVertex
155
78
 
156
- All methods in Bloc or Cubit classes must use arrow function syntax (`method = () => {}`) instead of the traditional method syntax (`method() {}`). This is because arrow functions automatically bind `this` to the class instance. Without this binding, methods called from React components would lose their context and could not access instance properties like `this.state` or `this.emit()`.
157
-
158
- ### State Management Patterns
159
-
160
- #### Shared State (Default)
161
-
162
- By default, bloc instances are shared across all consumers:
79
+ Stateless containers for services that don't need state tracking. Use for actions-only functionality like analytics, API calls, or navigation.
163
80
 
164
81
  ```typescript
165
- class GlobalCounterCubit extends Cubit<number> {
166
- constructor() {
167
- super(0);
168
- }
169
-
170
- increment = () => {
171
- this.emit(this.state + 1);
82
+ import { StatelessCubit } from '@blac/core';
83
+
84
+ class AnalyticsService extends StatelessCubit {
85
+ trackEvent(name: string, data?: Record<string, unknown>) {
86
+ // Send to analytics provider
172
87
  }
173
88
  }
174
89
  ```
175
90
 
176
- #### Isolated State
91
+ ## Registry API
177
92
 
178
- When each consumer needs its own state instance:
93
+ Manage state container instances with the registry functions:
179
94
 
180
95
  ```typescript
181
- class LocalCounterCubit extends Cubit<number> {
182
- static isolated = true; // Each consumer gets its own instance
183
-
184
- constructor() {
185
- super(0);
186
- }
187
-
188
- increment = () => {
189
- this.emit(this.state + 1);
190
- }
96
+ import { acquire, release, borrow, hasInstance, clear } from '@blac/core';
97
+
98
+ // Acquire an instance (creates if needed, increments ref count)
99
+ const counter = acquire(CounterCubit);
100
+
101
+ // Release when done (decrements ref count, disposes when 0)
102
+ release(CounterCubit);
103
+
104
+ // Borrow without affecting ref count
105
+ const instance = borrow(CounterCubit);
106
+
107
+ // Check if instance exists
108
+ if (hasInstance(CounterCubit)) {
109
+ // ...
191
110
  }
111
+
112
+ // Clear a specific class
113
+ clear(CounterCubit);
114
+
115
+ // Clear all instances
116
+ clearAll();
192
117
  ```
193
118
 
194
- #### Persistent State
119
+ ## Decorators
195
120
 
196
- Keep state alive even when no consumers are using it:
121
+ Use the `@blac` decorator to configure container behavior:
197
122
 
198
123
  ```typescript
199
- class PersistentCounterCubit extends Cubit<number> {
200
- static keepAlive = true; // State persists even when no consumers
201
-
202
- constructor() {
203
- super(0);
204
- }
205
-
206
- increment = () => {
207
- this.emit(this.state + 1);
208
- }
209
- }
124
+ import { Cubit, blac } from '@blac/core';
125
+
126
+ @blac({ isolated: true }) // Each consumer gets its own instance
127
+ class FormCubit extends Cubit<FormState> {}
128
+
129
+ @blac({ keepAlive: true }) // Never auto-dispose
130
+ class AuthCubit extends Cubit<AuthState> {}
131
+
132
+ @blac({ excludeFromDevTools: true }) // Hide from DevTools
133
+ class InternalCubit extends Cubit<State> {}
210
134
  ```
211
135
 
212
- ## Advanced Usage
136
+ ## Utilities
213
137
 
214
- ### Custom Plugins
138
+ ### waitUntil
215
139
 
216
- Create plugins to add functionality like logging, persistence, or analytics:
140
+ Wait for a specific state condition:
217
141
 
218
142
  ```typescript
219
- import { BlacPlugin, BlacLifecycleEvent, BlocBase } from '@blac/core';
220
-
221
- class LoggerPlugin implements BlacPlugin {
222
- name = 'LoggerPlugin';
223
-
224
- onEvent(event: BlacLifecycleEvent, bloc: BlocBase, params?: any) {
225
- if (event === BlacLifecycleEvent.STATE_CHANGED) {
226
- console.log(`[${bloc._name}] State changed:`, bloc.state);
227
- }
228
- }
229
- }
143
+ import { waitUntil } from '@blac/core';
144
+
145
+ const counter = acquire(CounterCubit);
230
146
 
231
- // Add the plugin to Blac
232
- import { Blac } from '@blac/core';
233
- Blac.addPlugin(new LoggerPlugin());
147
+ // Wait until count reaches 10
148
+ await waitUntil(counter, (state) => state.count >= 10);
149
+
150
+ // With timeout
151
+ await waitUntil(counter, (state) => state.count >= 10, {
152
+ timeout: 5000,
153
+ });
234
154
  ```
235
155
 
236
- ### Using Props with Blocs
156
+ ### watch
237
157
 
238
- Blocs can be designed to accept properties through their constructor, allowing for configurable instances. Here's an example of a `UserProfileBloc` that takes a `userId` prop:
158
+ Create computed values that react to state changes:
239
159
 
240
160
  ```typescript
241
- import { Bloc } from '@blac/core'; // Or your specific import path
161
+ import { watch, instance } from '@blac/core';
242
162
 
243
- // Define props interface (optional, but good practice)
244
- interface UserProfileProps {
245
- userId: string;
163
+ class DashboardCubit extends Cubit<DashboardState> {
164
+ constructor() {
165
+ super({ items: [] });
166
+
167
+ // Watch another bloc's state
168
+ watch(
169
+ instance(UserCubit),
170
+ (userState) => userState.preferences,
171
+ (preferences) => this.onPreferencesChanged(preferences),
172
+ );
173
+ }
246
174
  }
175
+ ```
247
176
 
248
- // Define state interface
249
- interface UserProfileState {
250
- loading: boolean;
251
- userData: { id: string; name: string; bio?: string } | null;
252
- error: string | null;
253
- }
177
+ ## Plugins
254
178
 
255
- // Define Event Classes for UserProfileBloc
256
- class UserProfileFetchEvent {}
257
- class UserProfileDataLoadedEvent { constructor(public readonly data: any) {} }
258
- class UserProfileErrorEvent { constructor(public readonly error: string) {} }
179
+ Extend functionality with plugins:
259
180
 
260
- type UserProfileEvents = UserProfileFetchEvent | UserProfileDataLoadedEvent | UserProfileErrorEvent;
181
+ ```typescript
182
+ import { getPluginManager, type BlacPlugin } from '@blac/core';
261
183
 
262
- class UserProfileBloc extends Bloc<UserProfileState, UserProfileEvents, UserProfileProps> {
263
- private userId: string;
184
+ const loggingPlugin: BlacPlugin = {
185
+ name: 'logging',
186
+ onStateChange: (container, prevState, newState) => {
187
+ console.log(`[${container.constructor.name}]`, prevState, '->', newState);
188
+ },
189
+ };
264
190
 
265
- constructor(props: UserProfileProps) {
266
- super({ loading: true, userData: null, error: null }); // Initial state
267
- this.userId = props.userId;
268
- this._name = `UserProfileBloc_${this.userId}`;
191
+ getPluginManager().register(loggingPlugin);
192
+ ```
269
193
 
270
- // Register event handlers
271
- this.on(UserProfileFetchEvent, this.handleFetchUserProfile);
272
- this.on(UserProfileDataLoadedEvent, (event, emit) => {
273
- emit({ ...this.state, loading: false, userData: event.data, error: null });
274
- });
275
- this.on(UserProfileErrorEvent, (event, emit) => {
276
- emit({ ...this.state, loading: false, error: event.error });
277
- });
194
+ ## Configuration
278
195
 
279
- // Initial fetch
280
- this.add(new UserProfileFetchEvent());
281
- }
196
+ Configure global behavior:
282
197
 
283
- private handleFetchUserProfile = async (_event: UserProfileFetchEvent, emit: (state: UserProfileState) => void) => {
284
- // Emit loading state directly if not already covered by initial state or another event
285
- // For this example, constructor sets loading: true, so an immediate emit here might be redundant
286
- // unless an event handler could set loading to false before this runs.
287
- // emit({ ...this.state, loading: true }); // Ensure loading is true
288
- try {
289
- await new Promise(resolve => setTimeout(resolve, 1000)); // Simulate API call
290
- const mockUserData = { id: this.userId, name: `User ${this.userId}`, bio: 'Loves Blac states!' };
291
- this.add(new UserProfileDataLoadedEvent(mockUserData));
292
- } catch (e:any) {
293
- this.add(new UserProfileErrorEvent(e.message || 'Failed to fetch user profile'));
294
- }
295
- }
296
-
297
- // Public method to re-trigger fetch if needed
298
- refetchUserProfile = () => {
299
- this.add(new UserProfileFetchEvent());
300
- }
301
- }
198
+ ```typescript
199
+ import { configureBlac } from '@blac/core';
200
+
201
+ configureBlac({
202
+ devMode: import.meta.env.DEV,
203
+ });
302
204
  ```
303
205
 
304
206
  ## API Reference
305
207
 
306
- ### Core Classes
208
+ ### State Containers
307
209
 
308
- - `BlocBase<S, P>`: Base class for state containers
309
- - `Cubit<S, P>`: Simple state container with `emit()` and `patch()`
310
- - `Bloc<S, E, P>`: Event-driven state container with `on(EventClass, handler)` and `add(eventInstance)` methods.
311
- - `Blac`: Singleton manager for all Bloc instances
210
+ | Class | Description |
211
+ | -------------------- | ----------------------------------------------------------- |
212
+ | `Cubit<S, P>` | Simple state container with `emit()`, `update()`, `patch()` |
213
+ | `Vertex<S, E, P>` | Event-driven container with `add()` and `createHandlers()` |
214
+ | `StatelessCubit` | Stateless service container |
215
+ | `StatelessVertex<E>` | Stateless event-driven container |
312
216
 
313
- ### React Hooks
217
+ ### Registry Functions
314
218
 
315
- - `useBloc<B>(BlocClass, options?)`: Connect a component to a Bloc
219
+ | Function | Description |
220
+ | -------------------------------- | ------------------------------------------- |
221
+ | `acquire(Class, key?, options?)` | Get or create instance, increment ref count |
222
+ | `release(Class, key?)` | Decrement ref count, dispose when 0 |
223
+ | `borrow(Class, key?)` | Get instance without affecting ref count |
224
+ | `borrowSafe(Class, key?)` | Borrow or return undefined |
225
+ | `ensure(Class, key?, options?)` | Acquire without incrementing ref count |
226
+ | `hasInstance(Class, key?)` | Check if instance exists |
227
+ | `getRefCount(Class, key?)` | Get current reference count |
228
+ | `clear(Class)` | Remove all instances of a class |
229
+ | `clearAll()` | Remove all instances |
316
230
 
317
- ### Lifecycle Events
231
+ ### Exports
318
232
 
319
- - `BLOC_CREATED`: When a new Bloc is instantiated
320
- - `BLOC_DISPOSED`: When a Bloc is disposed
321
- - `LISTENER_ADDED`: When a state listener is added
322
- - `LISTENER_REMOVED`: When a state listener is removed
323
- - `STATE_CHANGED`: When state is updated
324
- - `BLOC_CONSUMER_ADDED`: When a new consumer starts using a Bloc
325
- - `BLOC_CONSUMER_REMOVED`: When a consumer stops using a Bloc
233
+ ```typescript
234
+ // Core classes
235
+ export { Cubit, Vertex, StatelessCubit, StatelessVertex } from '@blac/core';
236
+
237
+ // Registry
238
+ export {
239
+ acquire,
240
+ release,
241
+ borrow,
242
+ ensure,
243
+ hasInstance,
244
+ clear,
245
+ clearAll,
246
+ } from '@blac/core';
247
+
248
+ // Utilities
249
+ export { waitUntil, watch, instance } from '@blac/core';
250
+
251
+ // Decorators
252
+ export { blac } from '@blac/core';
253
+
254
+ // Plugin system
255
+ export { getPluginManager, type BlacPlugin } from '@blac/core';
256
+
257
+ // Configuration
258
+ export { configureBlac, getBlacConfig, isDevMode } from '@blac/core';
259
+ ```
326
260
 
327
261
  ## License
328
262
 
329
- This project is licensed under the MIT License - see the [LICENSE](../../LICENSE) file for details.
263
+ MIT