@fukict/flux 0.1.0 → 0.1.2

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.
Files changed (2) hide show
  1. package/README.md +605 -0
  2. package/package.json +4 -1
package/README.md ADDED
@@ -0,0 +1,605 @@
1
+ # @fukict/flux
2
+
3
+ Minimal state management library for Fukict framework with Flux pattern and reactive subscriptions.
4
+
5
+ ## Features
6
+
7
+ - **Minimal API**: Simple `createFlux()` factory function
8
+ - **Reactive Subscriptions**: Subscribe to state changes with automatic updates
9
+ - **Selector Pattern**: Subscribe to derived/computed values
10
+ - **Type-Safe**: Full TypeScript support with type inference
11
+ - **Action Pattern**: Organized state mutations through actions
12
+ - **Dev Mode Protection**: Prevents direct state mutation in development
13
+ - **Zero Dependencies**: No external dependencies
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pnpm add @fukict/flux
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ### Basic Counter Example
24
+
25
+ ```tsx
26
+ import { Fukict } from '@fukict/basic';
27
+ import { createFlux } from '@fukict/flux';
28
+
29
+ // Create flux store
30
+ const counterFlux = createFlux({
31
+ state: {
32
+ count: 0,
33
+ },
34
+ actions: flux => ({
35
+ increment() {
36
+ const state = flux.getState();
37
+ flux.setState({ count: state.count + 1 });
38
+ },
39
+ decrement() {
40
+ const state = flux.getState();
41
+ flux.setState({ count: state.count - 1 });
42
+ },
43
+ setCount(value: number) {
44
+ flux.setState({ count: value });
45
+ },
46
+ }),
47
+ });
48
+
49
+ // Use in component
50
+ class Counter extends Fukict {
51
+ private unsubscribe?: () => void;
52
+
53
+ mounted() {
54
+ // Subscribe to state changes
55
+ this.unsubscribe = counterFlux.subscribe(() => {
56
+ this.update(this.props);
57
+ });
58
+ }
59
+
60
+ beforeUnmount() {
61
+ // Clean up subscription
62
+ this.unsubscribe?.();
63
+ }
64
+
65
+ render() {
66
+ const state = counterFlux.getState();
67
+ const actions = counterFlux.getActions();
68
+
69
+ return (
70
+ <div>
71
+ <p>Count: {state.count}</p>
72
+ <button on:click={actions.increment}>+</button>
73
+ <button on:click={actions.decrement}>-</button>
74
+ </div>
75
+ );
76
+ }
77
+ }
78
+ ```
79
+
80
+ ## Core Concepts
81
+
82
+ ### Creating Flux Store
83
+
84
+ ```typescript
85
+ import { createFlux } from '@fukict/flux';
86
+
87
+ interface TodoState {
88
+ todos: Todo[];
89
+ filter: 'all' | 'active' | 'completed';
90
+ }
91
+
92
+ const todoFlux = createFlux({
93
+ state: {
94
+ todos: [],
95
+ filter: 'all',
96
+ } as TodoState,
97
+ actions: flux => ({
98
+ addTodo(text: string) {
99
+ const state = flux.getState();
100
+ flux.setState({
101
+ todos: [...state.todos, { id: Date.now(), text, completed: false }],
102
+ });
103
+ },
104
+ toggleTodo(id: number) {
105
+ const state = flux.getState();
106
+ flux.setState({
107
+ todos: state.todos.map(todo =>
108
+ todo.id === id ? { ...todo, completed: !todo.completed } : todo,
109
+ ),
110
+ });
111
+ },
112
+ setFilter(filter: TodoState['filter']) {
113
+ flux.setState({ filter });
114
+ },
115
+ }),
116
+ });
117
+ ```
118
+
119
+ ### Subscribing to State
120
+
121
+ ```tsx
122
+ class TodoList extends Fukict {
123
+ private unsubscribe?: () => void;
124
+
125
+ mounted() {
126
+ // Subscribe to all state changes
127
+ this.unsubscribe = todoFlux.subscribe(() => {
128
+ this.update(this.props);
129
+ });
130
+ }
131
+
132
+ beforeUnmount() {
133
+ this.unsubscribe?.();
134
+ }
135
+
136
+ render() {
137
+ const { todos } = todoFlux.getState();
138
+ return (
139
+ <ul>
140
+ {todos.map(todo => (
141
+ <li key={todo.id}>{todo.text}</li>
142
+ ))}
143
+ </ul>
144
+ );
145
+ }
146
+ }
147
+ ```
148
+
149
+ ### Using Selectors
150
+
151
+ Selectors allow you to subscribe to derived/computed values:
152
+
153
+ ```tsx
154
+ // Define selector
155
+ const visibleTodosSelector = (state: TodoState) => {
156
+ switch (state.filter) {
157
+ case 'active':
158
+ return state.todos.filter(t => !t.completed);
159
+ case 'completed':
160
+ return state.todos.filter(t => t.completed);
161
+ default:
162
+ return state.todos;
163
+ }
164
+ };
165
+
166
+ class FilteredTodoList extends Fukict {
167
+ private unsubscribe?: () => void;
168
+
169
+ mounted() {
170
+ // Subscribe to selector (only updates when selector result changes)
171
+ this.unsubscribe = todoFlux.subscribe(
172
+ visibleTodosSelector,
173
+ visibleTodos => {
174
+ console.log('Visible todos changed:', visibleTodos);
175
+ this.update(this.props);
176
+ },
177
+ );
178
+ }
179
+
180
+ beforeUnmount() {
181
+ this.unsubscribe?.();
182
+ }
183
+
184
+ render() {
185
+ const visibleTodos = visibleTodosSelector(todoFlux.getState());
186
+ return (
187
+ <ul>
188
+ {visibleTodos.map(todo => (
189
+ <li key={todo.id}>{todo.text}</li>
190
+ ))}
191
+ </ul>
192
+ );
193
+ }
194
+ }
195
+ ```
196
+
197
+ ### Async Actions
198
+
199
+ ```typescript
200
+ const userFlux = createFlux({
201
+ state: {
202
+ user: null as User | null,
203
+ loading: false,
204
+ error: null as string | null,
205
+ },
206
+ actions: flux => ({
207
+ async login(email: string, password: string) {
208
+ flux.setState({ loading: true, error: null });
209
+
210
+ try {
211
+ const user = await api.login(email, password);
212
+ flux.setState({ user, loading: false });
213
+ } catch (error) {
214
+ flux.setState({
215
+ error: error.message,
216
+ loading: false,
217
+ });
218
+ }
219
+ },
220
+ logout() {
221
+ flux.setState({ user: null });
222
+ },
223
+ }),
224
+ });
225
+ ```
226
+
227
+ ## Subscription Patterns
228
+
229
+ ### Pattern 1: Top-Level Subscription
230
+
231
+ Subscribe at the root/layout component level:
232
+
233
+ ```tsx
234
+ class App extends Fukict {
235
+ private unsubscribe?: () => void;
236
+
237
+ mounted() {
238
+ // App subscribes, all children read state
239
+ this.unsubscribe = globalFlux.subscribe(() => {
240
+ this.update(this.props);
241
+ });
242
+ }
243
+
244
+ beforeUnmount() {
245
+ this.unsubscribe?.();
246
+ }
247
+
248
+ render() {
249
+ return (
250
+ <div>
251
+ <Header /> {/* Reads state, no subscription */}
252
+ <Content /> {/* Reads state, no subscription */}
253
+ </div>
254
+ );
255
+ }
256
+ }
257
+ ```
258
+
259
+ ### Pattern 2: Sibling Subscriptions
260
+
261
+ Different components subscribe to different stores:
262
+
263
+ ```tsx
264
+ class Dashboard extends Fukict {
265
+ render() {
266
+ return (
267
+ <div>
268
+ <CounterPanel /> {/* Subscribes to counterFlux */}
269
+ <TodoPanel /> {/* Subscribes to todoFlux */}
270
+ <UserPanel /> {/* Subscribes to userFlux */}
271
+ </div>
272
+ );
273
+ }
274
+ }
275
+ ```
276
+
277
+ ### Pattern 3: Detached Subscriptions
278
+
279
+ Use `fukict:detach` for independent updates:
280
+
281
+ ```tsx
282
+ class Parent extends Fukict {
283
+ render() {
284
+ return (
285
+ <div>
286
+ <ChildA /> {/* Parent updates trigger ChildA update */}
287
+ <ChildB fukict:detach={true} /> {/* Independent updates */}
288
+ </div>
289
+ );
290
+ }
291
+ }
292
+ ```
293
+
294
+ ## State Mutation Protection
295
+
296
+ In development mode, Flux prevents direct state mutation:
297
+
298
+ ```typescript
299
+ const flux = createFlux({
300
+ state: { count: 0 },
301
+ actions: flux => ({
302
+ // ✅ Good: Use setState
303
+ increment() {
304
+ const state = flux.getState();
305
+ flux.setState({ count: state.count + 1 });
306
+ },
307
+ }),
308
+ });
309
+
310
+ // ❌ Bad: Direct mutation (warning in dev mode)
311
+ const state = flux.getState();
312
+ state.count++; // Warning: [Flux] Direct state mutation is not allowed
313
+ ```
314
+
315
+ ## Advanced Usage
316
+
317
+ ### Multiple Stores
318
+
319
+ ```typescript
320
+ // User store
321
+ const userFlux = createFlux({
322
+ state: { user: null },
323
+ actions: (flux) => ({
324
+ setUser(user: User) {
325
+ flux.setState({ user });
326
+ },
327
+ }),
328
+ });
329
+
330
+ // Settings store
331
+ const settingsFlux = createFlux({
332
+ state: { theme: 'light', language: 'en' },
333
+ actions: (flux) => ({
334
+ setTheme(theme: string) {
335
+ flux.setState({ theme });
336
+ },
337
+ setLanguage(language: string) {
338
+ flux.setState({ language });
339
+ },
340
+ }),
341
+ });
342
+
343
+ // Use both in component
344
+ class Profile extends Fukict {
345
+ private unsubscribeUser?: () => void;
346
+ private unsubscribeSettings?: () => void;
347
+
348
+ mounted() {
349
+ this.unsubscribeUser = userFlux.subscribe(() => this.update(this.props));
350
+ this.unsubscribeSettings = settingsFlux.subscribe(() =>
351
+ this.update(this.props)
352
+ );
353
+ }
354
+
355
+ beforeUnmount() {
356
+ this.unsubscribeUser?.();
357
+ this.unsubscribeSettings?.();
358
+ }
359
+
360
+ render() {
361
+ const { user } = userFlux.getState();
362
+ const { theme } = settingsFlux.getState();
363
+ return <div class={theme}>{user?.name}</div>;
364
+ }
365
+ }
366
+ ```
367
+
368
+ ### Computed Values with Selectors
369
+
370
+ ```typescript
371
+ const statsSelector = (state: TodoState) => ({
372
+ total: state.todos.length,
373
+ completed: state.todos.filter((t) => t.completed).length,
374
+ active: state.todos.filter((t) => !t.completed).length,
375
+ });
376
+
377
+ class TodoStats extends Fukict {
378
+ private unsubscribe?: () => void;
379
+
380
+ mounted() {
381
+ this.unsubscribe = todoFlux.subscribe(statsSelector, (stats) => {
382
+ console.log('Stats changed:', stats);
383
+ this.update(this.props);
384
+ });
385
+ }
386
+
387
+ beforeUnmount() {
388
+ this.unsubscribe?.();
389
+ }
390
+
391
+ render() {
392
+ const stats = statsSelector(todoFlux.getState());
393
+ return (
394
+ <div>
395
+ <p>Total: {stats.total}</p>
396
+ <p>Completed: {stats.completed}</p>
397
+ <p>Active: {stats.active}</p>
398
+ </div>
399
+ );
400
+ }
401
+ }
402
+ ```
403
+
404
+ ### Store Composition
405
+
406
+ ```typescript
407
+ const createAppFlux = () => {
408
+ const userFlux = createFlux({
409
+ state: { user: null },
410
+ actions: (flux) => ({ /*...*/ }),
411
+ });
412
+
413
+ const todoFlux = createFlux({
414
+ state: { todos: [] },
415
+ actions: (flux) => ({ /*...*/ }),
416
+ });
417
+
418
+ return {
419
+ user: userFlux,
420
+ todo: todoFlux,
421
+ };
422
+ };
423
+
424
+ const appFlux = createAppFlux();
425
+
426
+ // Use in components
427
+ appFlux.user.getState();
428
+ appFlux.user.getActions().setUser(...);
429
+ appFlux.todo.getState();
430
+ appFlux.todo.getActions().addTodo(...);
431
+ ```
432
+
433
+ ## API Reference
434
+
435
+ ### createFlux(config)
436
+
437
+ Creates a new flux store.
438
+
439
+ ```typescript
440
+ const flux = createFlux({
441
+ state: initialState,
442
+ actions: flux => ({
443
+ actionName(...args) {
444
+ // Action implementation
445
+ flux.setState(newState);
446
+ },
447
+ }),
448
+ });
449
+ ```
450
+
451
+ ### flux.getState()
452
+
453
+ Returns current state (protected from mutation in dev mode).
454
+
455
+ ```typescript
456
+ const state = flux.getState();
457
+ console.log(state.count);
458
+ ```
459
+
460
+ ### flux.setState(partial)
461
+
462
+ Updates state with partial state object.
463
+
464
+ ```typescript
465
+ flux.setState({ count: 10 });
466
+ ```
467
+
468
+ ### flux.getActions()
469
+
470
+ Returns actions object.
471
+
472
+ ```typescript
473
+ const actions = flux.getActions();
474
+ actions.increment();
475
+ ```
476
+
477
+ ### flux.subscribe(listener)
478
+
479
+ Subscribes to all state changes.
480
+
481
+ ```typescript
482
+ const unsubscribe = flux.subscribe(() => {
483
+ console.log('State changed');
484
+ });
485
+
486
+ // Clean up
487
+ unsubscribe();
488
+ ```
489
+
490
+ ### flux.subscribe(selector, listener)
491
+
492
+ Subscribes to selector changes (only triggers when selector result changes).
493
+
494
+ ```typescript
495
+ const unsubscribe = flux.subscribe(
496
+ state => state.user.name,
497
+ name => {
498
+ console.log('Name changed:', name);
499
+ },
500
+ );
501
+ ```
502
+
503
+ ## Best Practices
504
+
505
+ ### 1. Organize Actions by Feature
506
+
507
+ ```typescript
508
+ // ✅ Good: Group related actions
509
+ const userFlux = createFlux({
510
+ state: { user: null, preferences: {} },
511
+ actions: flux => ({
512
+ // Auth actions
513
+ login(credentials) {
514
+ /*...*/
515
+ },
516
+ logout() {
517
+ /*...*/
518
+ },
519
+
520
+ // Preference actions
521
+ updatePreference(key, value) {
522
+ /*...*/
523
+ },
524
+ resetPreferences() {
525
+ /*...*/
526
+ },
527
+ }),
528
+ });
529
+ ```
530
+
531
+ ### 2. Use Selectors for Derived State
532
+
533
+ ```typescript
534
+ // ✅ Good: Selector for computed values
535
+ const expensiveSelector = (state) => {
536
+ return state.items.filter(/*...*/).map(/*...*/).reduce(/*...*/);
537
+ };
538
+
539
+ // ❌ Bad: Compute in render
540
+ render() {
541
+ const state = flux.getState();
542
+ const result = state.items.filter(/*...*/).map(/*...*/).reduce(/*...*/);
543
+ }
544
+ ```
545
+
546
+ ### 3. Clean Up Subscriptions
547
+
548
+ ```tsx
549
+ // ✅ Good: Always clean up
550
+ class MyComponent extends Fukict {
551
+ private unsubscribe?: () => void;
552
+
553
+ mounted() {
554
+ this.unsubscribe = flux.subscribe(() => this.update(this.props));
555
+ }
556
+
557
+ beforeUnmount() {
558
+ this.unsubscribe?.(); // Essential!
559
+ }
560
+ }
561
+ ```
562
+
563
+ ### 4. Subscribe at the Right Level
564
+
565
+ ```tsx
566
+ // ✅ Good: Subscribe at parent, read in children
567
+ class App extends Fukict {
568
+ mounted() {
569
+ this.unsubscribe = flux.subscribe(() => this.update(this.props));
570
+ }
571
+ render() {
572
+ return (
573
+ <div>
574
+ <Child />
575
+ </div>
576
+ );
577
+ }
578
+ }
579
+
580
+ class Child extends Fukict {
581
+ render() {
582
+ const state = flux.getState(); // Just read
583
+ return <div>{state.value}</div>;
584
+ }
585
+ }
586
+ ```
587
+
588
+ ## Examples
589
+
590
+ See [examples/infra-flux](../../examples/infra-flux) for complete examples:
591
+
592
+ - Counter with async actions
593
+ - Todo list with filters
594
+ - User profile with settings
595
+ - Selector patterns
596
+
597
+ ## Related Packages
598
+
599
+ - [@fukict/basic](../basic) - Core rendering engine
600
+ - [@fukict/router](../router) - SPA routing
601
+ - [@fukict/i18n](../i18n) - Internationalization
602
+
603
+ ## License
604
+
605
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fukict/flux",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Minimal state management library for Fukict framework",
5
5
  "keywords": [
6
6
  "fukict",
@@ -41,6 +41,9 @@
41
41
  "engines": {
42
42
  "node": ">=16.0.0"
43
43
  },
44
+ "publishConfig": {
45
+ "registry": "https://registry.npmjs.org/"
46
+ },
44
47
  "scripts": {
45
48
  "build": "tsx ../../scripts/build-package.ts --pkg-name flux --no-watch",
46
49
  "dev": "tsx ../../scripts/build-package.ts --pkg-name flux --watch",