@blac/core 2.0.0 โ†’ 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,252 +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
13
  ```
27
14
 
28
15
  ## Core Concepts
29
16
 
30
- ### Blocs and Cubits
17
+ ### Cubit
31
18
 
32
- **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.
33
20
 
34
21
  ```typescript
35
- class CounterCubit extends Cubit<number> {
22
+ import { Cubit } from '@blac/core';
23
+
24
+ class CounterCubit extends Cubit<{ count: number }> {
36
25
  constructor() {
37
- super(0); // Initial state
26
+ super({ count: 0 });
38
27
  }
39
28
 
40
- increment = () => {
41
- this.emit(this.state + 1);
29
+ increment() {
30
+ this.emit({ count: this.state.count + 1 });
42
31
  }
43
32
 
44
- decrement = () => {
45
- this.emit(this.state - 1);
33
+ decrement() {
34
+ this.update((state) => ({ count: state.count - 1 }));
35
+ }
36
+
37
+ reset() {
38
+ this.patch({ count: 0 });
46
39
  }
47
40
  }
48
41
  ```
49
42
 
50
- **Bloc**: More powerful state container with a reducer pattern for action-based state transitions.
43
+ ### Vertex
44
+
45
+ Event-driven state container using discriminated union events. Use when you need structured event handling with type-safe exhaustive checking.
51
46
 
52
47
  ```typescript
53
- // Define actions
54
- type CounterAction =
55
- | { type: 'increment', amount: number }
56
- | { type: 'decrement', amount: number };
48
+ import { Vertex } from '@blac/core';
49
+
50
+ type CounterEvent =
51
+ | { type: 'increment'; amount: number }
52
+ | { type: 'decrement' }
53
+ | { type: 'reset' };
57
54
 
58
- class CounterBloc extends Bloc<number, CounterAction> {
55
+ class CounterVertex extends Vertex<{ count: number }, CounterEvent> {
59
56
  constructor() {
60
- super(0); // Initial state
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
+ },
68
+ });
61
69
  }
62
70
 
63
- reducer = (action: CounterAction, state: number): number => {
64
- switch (action.type) {
65
- case 'increment':
66
- return state + action.amount;
67
- case 'decrement':
68
- return state - action.amount;
69
- }
70
- }
71
+ increment = (amount = 1) => this.add({ type: 'increment', amount });
72
+ decrement = () => this.add({ type: 'decrement' });
73
+ reset = () => this.add({ type: 'reset' });
74
+ }
75
+ ```
71
76
 
72
- increment = (amount = 1) => {
73
- this.add({ type: 'increment', amount });
74
- }
77
+ ### StatelessCubit & StatelessVertex
78
+
79
+ Stateless containers for services that don't need state tracking. Use for actions-only functionality like analytics, API calls, or navigation.
80
+
81
+ ```typescript
82
+ import { StatelessCubit } from '@blac/core';
75
83
 
76
- decrement = (amount = 1) => {
77
- this.add({ type: 'decrement', amount });
84
+ class AnalyticsService extends StatelessCubit {
85
+ trackEvent(name: string, data?: Record<string, unknown>) {
86
+ // Send to analytics provider
78
87
  }
79
88
  }
80
89
  ```
81
90
 
82
- ### Important: Arrow Functions Required
91
+ ## Registry API
83
92
 
84
- 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()`.
93
+ Manage state container instances with the registry functions:
85
94
 
86
- ### State Management Patterns
95
+ ```typescript
96
+ import { acquire, release, borrow, hasInstance, clear } from '@blac/core';
87
97
 
88
- #### Shared State (Default)
98
+ // Acquire an instance (creates if needed, increments ref count)
99
+ const counter = acquire(CounterCubit);
89
100
 
90
- By default, bloc instances are shared across all consumers:
101
+ // Release when done (decrements ref count, disposes when 0)
102
+ release(CounterCubit);
91
103
 
92
- ```typescript
93
- class GlobalCounterCubit extends Cubit<number> {
94
- constructor() {
95
- super(0);
96
- }
97
-
98
- increment = () => {
99
- this.emit(this.state + 1);
100
- }
104
+ // Borrow without affecting ref count
105
+ const instance = borrow(CounterCubit);
106
+
107
+ // Check if instance exists
108
+ if (hasInstance(CounterCubit)) {
109
+ // ...
101
110
  }
111
+
112
+ // Clear a specific class
113
+ clear(CounterCubit);
114
+
115
+ // Clear all instances
116
+ clearAll();
102
117
  ```
103
118
 
104
- #### Isolated State
119
+ ## Decorators
105
120
 
106
- When each consumer needs its own state instance:
121
+ Use the `@blac` decorator to configure container behavior:
107
122
 
108
123
  ```typescript
109
- class LocalCounterCubit extends Cubit<number> {
110
- static isolated = true; // Each consumer gets its own instance
111
-
112
- constructor() {
113
- super(0);
114
- }
115
-
116
- increment = () => {
117
- this.emit(this.state + 1);
118
- }
119
- }
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> {}
120
134
  ```
121
135
 
122
- #### Persistent State
136
+ ## Utilities
137
+
138
+ ### waitUntil
123
139
 
124
- Keep state alive even when no consumers are using it:
140
+ Wait for a specific state condition:
125
141
 
126
142
  ```typescript
127
- class PersistentCounterCubit extends Cubit<number> {
128
- static keepAlive = true; // State persists even when no consumers
129
-
130
- constructor() {
131
- super(0);
132
- }
133
-
134
- increment = () => {
135
- this.emit(this.state + 1);
136
- }
137
- }
138
- ```
143
+ import { waitUntil } from '@blac/core';
139
144
 
140
- ## Advanced Usage
145
+ const counter = acquire(CounterCubit);
146
+
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
+ });
154
+ ```
141
155
 
142
- ### Custom Plugins
156
+ ### watch
143
157
 
144
- Create plugins to add functionality like logging, persistence, or analytics:
158
+ Create computed values that react to state changes:
145
159
 
146
160
  ```typescript
147
- import { BlacPlugin, BlacLifecycleEvent, BlocBase } from '@blac/core';
148
-
149
- class LoggerPlugin implements BlacPlugin {
150
- name = 'LoggerPlugin';
151
-
152
- onEvent(event: BlacLifecycleEvent, bloc: BlocBase, params?: any) {
153
- if (event === BlacLifecycleEvent.STATE_CHANGED) {
154
- console.log(`[${bloc._name}] State changed:`, bloc.state);
155
- }
161
+ import { watch, instance } from '@blac/core';
162
+
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
+ );
156
173
  }
157
174
  }
158
-
159
- // Add the plugin to Blac
160
- import { Blac } from '@blac/core';
161
- Blac.addPlugin(new LoggerPlugin());
162
175
  ```
163
176
 
164
- ### Using Props with Blocs
177
+ ## Plugins
165
178
 
166
- 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:
179
+ Extend functionality with plugins:
167
180
 
168
181
  ```typescript
169
- import { Bloc } from '@blac/core'; // Or your specific import path
182
+ import { getPluginManager, type BlacPlugin } from '@blac/core';
170
183
 
171
- // Define props interface (optional, but good practice)
172
- interface UserProfileProps {
173
- userId: string;
174
- }
184
+ const loggingPlugin: BlacPlugin = {
185
+ name: 'logging',
186
+ onStateChange: (container, prevState, newState) => {
187
+ console.log(`[${container.constructor.name}]`, prevState, '->', newState);
188
+ },
189
+ };
175
190
 
176
- // Define state interface
177
- interface UserProfileState {
178
- loading: boolean;
179
- userData: { id: string; name: string; bio?: string } | null;
180
- error: string | null;
181
- }
191
+ getPluginManager().register(loggingPlugin);
192
+ ```
182
193
 
183
- // Define actions (if any, for this example we'll focus on constructor and an async method)
184
- type UserProfileAction = { type: 'dataLoaded', data: any } | { type: 'error', error: string };
185
-
186
- class UserProfileBloc extends Bloc<UserProfileState, UserProfileAction> {
187
- private userId: string;
188
-
189
- // The Blac library or its React bindings (like @blac/react)
190
- // might provide a way to pass these props during instantiation.
191
- // For example, `useBloc(UserProfileBloc, { props: { userId: '123' } })`
192
- constructor(props: UserProfileProps) {
193
- super({ loading: true, userData: null, error: null }); // Initial state
194
- this.userId = props.userId;
195
- // Optional: Set a dynamic name for easier debugging with multiple instances
196
- this._name = `UserProfileBloc_${this.userId}`;
197
- this.fetchUserProfile();
198
- }
194
+ ## Configuration
199
195
 
200
- // Example reducer
201
- reducer = (action: UserProfileAction, state: UserProfileState): UserProfileState => {
202
- switch (action.type) {
203
- case 'dataLoaded':
204
- return { ...state, loading: false, userData: action.data, error: null };
205
- case 'error':
206
- return { ...state, loading: false, error: action.error };
207
- default:
208
- return state;
209
- }
210
- }
196
+ Configure global behavior:
211
197
 
212
- fetchUserProfile = async ()_ => {
213
- this.emit({ ...this.state, loading: true }); // Set loading true before fetch
214
- try {
215
- // Simulate an API call
216
- await new Promise(resolve => setTimeout(resolve, 1000));
217
- const mockUserData = { id: this.userId, name: `User ${this.userId}`, bio: 'Loves Blac states!' };
218
- // Dispatch an action or directly emit a new state
219
- this.add({ type: 'dataLoaded', data: mockUserData });
220
- } catch (e:any) {
221
- this.add({ type: 'error', error: e.message || 'Failed to fetch user profile' });
222
- }
223
- }
224
- }
198
+ ```typescript
199
+ import { configureBlac } from '@blac/core';
200
+
201
+ configureBlac({
202
+ devMode: import.meta.env.DEV,
203
+ });
225
204
  ```
226
205
 
227
206
  ## API Reference
228
207
 
229
- ### Core Classes
208
+ ### State Containers
230
209
 
231
- - `BlocBase<S, P>`: Base class for state containers
232
- - `Cubit<S, P>`: Simple state container with `emit()` and `patch()`
233
- - `Bloc<S, A, P>`: Action-based state container with a reducer pattern
234
- - `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 |
235
216
 
236
- ### React Hooks
217
+ ### Registry Functions
237
218
 
238
- - `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 |
239
230
 
240
- ### Lifecycle Events
231
+ ### Exports
241
232
 
242
- - `BLOC_CREATED`: When a new Bloc is instantiated
243
- - `BLOC_DISPOSED`: When a Bloc is disposed
244
- - `LISTENER_ADDED`: When a state listener is added
245
- - `LISTENER_REMOVED`: When a state listener is removed
246
- - `STATE_CHANGED`: When state is updated
247
- - `BLOC_CONSUMER_ADDED`: When a new consumer starts using a Bloc
248
- - `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
+ ```
249
260
 
250
261
  ## License
251
262
 
252
- This project is licensed under the MIT License - see the [LICENSE](../../LICENSE) file for details.
263
+ MIT