@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/README.md
CHANGED
|
@@ -1,329 +1,263 @@
|
|
|
1
1
|
# @blac/core
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
17
|
+
### Cubit
|
|
100
18
|
|
|
101
|
-
|
|
19
|
+
Simple state container with direct state emission. Use when you need straightforward state updates.
|
|
102
20
|
|
|
103
21
|
```typescript
|
|
104
|
-
|
|
22
|
+
import { Cubit } from '@blac/core';
|
|
23
|
+
|
|
24
|
+
class CounterCubit extends Cubit<{ count: number }> {
|
|
105
25
|
constructor() {
|
|
106
|
-
super(0);
|
|
26
|
+
super({ count: 0 });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
increment() {
|
|
30
|
+
this.emit({ count: this.state.count + 1 });
|
|
107
31
|
}
|
|
108
32
|
|
|
109
|
-
|
|
110
|
-
this.
|
|
33
|
+
decrement() {
|
|
34
|
+
this.update((state) => ({ count: state.count - 1 }));
|
|
111
35
|
}
|
|
112
36
|
|
|
113
|
-
|
|
114
|
-
this.
|
|
37
|
+
reset() {
|
|
38
|
+
this.patch({ count: 0 });
|
|
115
39
|
}
|
|
116
40
|
}
|
|
117
41
|
```
|
|
118
42
|
|
|
119
|
-
|
|
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
|
-
|
|
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
|
-
|
|
127
|
-
type
|
|
50
|
+
type CounterEvent =
|
|
51
|
+
| { type: 'increment'; amount: number }
|
|
52
|
+
| { type: 'decrement' }
|
|
53
|
+
| { type: 'reset' };
|
|
128
54
|
|
|
129
|
-
class
|
|
55
|
+
class CounterVertex extends Vertex<{ count: number }, CounterEvent> {
|
|
130
56
|
constructor() {
|
|
131
|
-
super(0);
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
###
|
|
77
|
+
### StatelessCubit & StatelessVertex
|
|
155
78
|
|
|
156
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
91
|
+
## Registry API
|
|
177
92
|
|
|
178
|
-
|
|
93
|
+
Manage state container instances with the registry functions:
|
|
179
94
|
|
|
180
95
|
```typescript
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
119
|
+
## Decorators
|
|
195
120
|
|
|
196
|
-
|
|
121
|
+
Use the `@blac` decorator to configure container behavior:
|
|
197
122
|
|
|
198
123
|
```typescript
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
##
|
|
136
|
+
## Utilities
|
|
213
137
|
|
|
214
|
-
###
|
|
138
|
+
### waitUntil
|
|
215
139
|
|
|
216
|
-
|
|
140
|
+
Wait for a specific state condition:
|
|
217
141
|
|
|
218
142
|
```typescript
|
|
219
|
-
import {
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
//
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
###
|
|
156
|
+
### watch
|
|
237
157
|
|
|
238
|
-
|
|
158
|
+
Create computed values that react to state changes:
|
|
239
159
|
|
|
240
160
|
```typescript
|
|
241
|
-
import {
|
|
161
|
+
import { watch, instance } from '@blac/core';
|
|
242
162
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
181
|
+
```typescript
|
|
182
|
+
import { getPluginManager, type BlacPlugin } from '@blac/core';
|
|
261
183
|
|
|
262
|
-
|
|
263
|
-
|
|
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
|
-
|
|
266
|
-
|
|
267
|
-
this.userId = props.userId;
|
|
268
|
-
this._name = `UserProfileBloc_${this.userId}`;
|
|
191
|
+
getPluginManager().register(loggingPlugin);
|
|
192
|
+
```
|
|
269
193
|
|
|
270
|
-
|
|
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
|
-
|
|
280
|
-
this.add(new UserProfileFetchEvent());
|
|
281
|
-
}
|
|
196
|
+
Configure global behavior:
|
|
282
197
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
###
|
|
208
|
+
### State Containers
|
|
307
209
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
###
|
|
217
|
+
### Registry Functions
|
|
314
218
|
|
|
315
|
-
|
|
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
|
-
###
|
|
231
|
+
### Exports
|
|
318
232
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
263
|
+
MIT
|