@bkincz/clutch 1.0.0 → 1.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.
@@ -1,262 +1,262 @@
1
- [![Release](https://github.com/bkincz/clutch/actions/workflows/release.yml/badge.svg?branch=master)](https://github.com/bkincz/clutch/actions/workflows/release.yml)
2
- [![codecov](https://codecov.io/gh/bkincz/clutch/branch/master/graph/badge.svg)](https://codecov.io/gh/bkincz/clutch)
3
- [![npm version](https://badge.fury.io/js/@bkincz%2Fclutch.svg)](https://badge.fury.io/js/@bkincz%2Fclutch)
4
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
- [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
6
-
7
- # Clutch - State Machine
8
-
9
- A production-ready, TypeScript-first state management library built on Immer with advanced features.
10
-
11
- This was primarily created for a personal project of mine called Kintsugi, hence the very specific features like `Auto-Save` or `Undo/Redo`, but I've found it a useful replacement for most state managers in my other projects.
12
- Thought I'd put it here for anyone that might want to use it as well.
13
-
14
- ## Features
15
-
16
- - 🔄 **Immutable Updates** - Powered by Immer for clean, mutable-style code that produces immutable state
17
- - ⏪ **Undo/Redo** - Built-in history management using efficient patch-based storage
18
- - 💾 **Persistence** - Automatic localStorage backup with optional server synchronization
19
- - 🚀 **Performance** - Debounced notifications, memory tracking, and efficient batch operations
20
- - 🛡️ **Type Safety** - Full TypeScript support with runtime validation
21
- - 🔍 **Debugging** - Comprehensive logging system with structured output
22
- - 🧹 **Memory Management** - Automatic cleanup and configurable history limits
23
- - ⚡ **Auto-Save** - Configurable automatic persistence to prevent data loss
24
-
25
- ## Installation
26
-
27
- ```bash
28
- npm install @bkincz/clutch
29
- # or
30
- yarn add @bkincz/clutch
31
- # or
32
- pnpm install @bkincz/clutch
33
- ```
34
-
35
- ## Quick Start
36
-
37
- ### 1. Define Your State
38
-
39
- ```typescript
40
- interface AppState {
41
- user: { id: string; name: string } | null;
42
- todos: Array<{ id: string; text: string; completed: boolean }>;
43
- ui: { loading: boolean; error: string | null };
44
- }
45
-
46
- const initialState: AppState = {
47
- user: null,
48
- todos: [],
49
- ui: { loading: false, error: null },
50
- };
51
- ```
52
-
53
- ### 2. Create Your State Machine
54
-
55
- ```typescript
56
- import { StateMachine } from "@bkincz/clutch";
57
-
58
- class TodoState extends StateMachine<AppState> {
59
- constructor() {
60
- super({
61
- initialState,
62
- persistenceKey: "todo-app",
63
- autoSaveInterval: 5, // minutes
64
- enableLogging: process.env.NODE_ENV === "development",
65
- });
66
- }
67
-
68
- // Optional: implement server persistence
69
- protected async saveToServer(state: AppState): Promise<void> {
70
- await fetch("/api/state", {
71
- method: "POST",
72
- body: JSON.stringify(state),
73
- });
74
- }
75
-
76
- protected async loadFromServer(): Promise<AppState | null> {
77
- const response = await fetch("/api/state");
78
- return response.ok ? await response.json() : null;
79
- }
80
- }
81
-
82
- export const todoState = new TodoState();
83
- ```
84
-
85
- ### 3. Use in React
86
-
87
- ```typescript
88
- import { useStateMachine } from "@bkincz/clutch";
89
- import { todoState } from "./todoState";
90
-
91
- function TodoApp() {
92
- const { state, mutate } = useStateMachine(todoState);
93
-
94
- const addTodo = (text: string) => {
95
- mutate((draft) => {
96
- draft.todos.push({
97
- id: Date.now().toString(),
98
- text,
99
- completed: false,
100
- });
101
- }, "Add todo");
102
- };
103
-
104
- const toggleTodo = (id: string) => {
105
- mutate((draft) => {
106
- const todo = draft.todos.find((t) => t.id === id);
107
- if (todo) {
108
- todo.completed = !todo.completed;
109
- }
110
- }, "Toggle todo");
111
- };
112
-
113
- return (
114
- <div>
115
- <input
116
- type="text"
117
- onKeyDown={(e) => {
118
- if (e.key === "Enter") {
119
- addTodo(e.currentTarget.value);
120
- e.currentTarget.value = "";
121
- }
122
- }}
123
- placeholder="Add a todo..."
124
- />
125
-
126
- {state.todos.map((todo) => (
127
- <div key={todo.id}>
128
- <input
129
- type="checkbox"
130
- checked={todo.completed}
131
- onChange={() => toggleTodo(todo.id)}
132
- />
133
- <span>{todo.text}</span>
134
- </div>
135
- ))}
136
- </div>
137
- );
138
- }
139
- ```
140
-
141
- ## Core Features
142
-
143
- ### Immutable Updates with Immer
144
-
145
- Write simple, mutable-looking code that produces immutable state:
146
-
147
- ```typescript
148
- // Instead of complex spread operations
149
- const newState = {
150
- ...state,
151
- todos: state.todos.map(todo =>
152
- todo.id === id ? { ...todo, completed: !todo.completed } : todo
153
- )
154
- };
155
-
156
- // Write simple mutations
157
- state.mutate(draft => {
158
- const todo = draft.todos.find(t => t.id === id);
159
- if (todo) {
160
- todo.completed = !todo.completed;
161
- }
162
- });
163
- ```
164
-
165
- ### Undo/Redo
166
-
167
- ```typescript
168
- import { useStateActions } from "@bkincz/clutch";
169
-
170
- function UndoRedoButtons() {
171
- const { undo, redo } = useStateActions(todoState);
172
-
173
- return (
174
- <>
175
- <button onClick={() => undo()}>Undo</button>
176
- <button onClick={() => redo()}>Redo</button>
177
- </>
178
- );
179
- }
180
- ```
181
-
182
- ### Batch Operations
183
-
184
- Group multiple changes into a single undo operation:
185
-
186
- ```typescript
187
- state.batch([
188
- draft => draft.todos.push(newTodo1),
189
- draft => draft.todos.push(newTodo2),
190
- draft => { draft.ui.loading = false; }
191
- ], "Add multiple todos");
192
- ```
193
-
194
- ## React Hooks
195
-
196
- ### `useStateMachine(engine)`
197
- Subscribe to entire state and get mutation methods.
198
-
199
- ### `useStateSlice(engine, selector, equalityFn?)`
200
- Subscribe to a specific slice of state for better performance.
201
-
202
- ```typescript
203
- const todoCount = useStateSlice(todoState, state => state.todos.length);
204
- const completedTodos = useStateSlice(
205
- todoState,
206
- state => state.todos.filter(t => t.completed)
207
- );
208
- ```
209
-
210
- ### `useStateActions(engine)`
211
- Get mutation methods without subscribing to state changes.
212
-
213
- ### `useStateHistory(engine)`
214
- Access undo/redo state and controls.
215
-
216
- ```typescript
217
- const { canUndo, canRedo, undo, redo } = useStateHistory(todoState);
218
- ```
219
-
220
- ### `useStatePersist(engine)`
221
- Handle save/load operations and persistence state.
222
-
223
- ```typescript
224
- const { save, load, isSaving, hasUnsavedChanges } = useStatePersist(todoState);
225
- ```
226
-
227
- ## Configuration
228
-
229
- ```typescript
230
- interface StateConfig<T> {
231
- initialState: T;
232
- persistenceKey?: string; // localStorage key
233
- autoSaveInterval?: number; // minutes (default: 5)
234
- maxHistorySize?: number; // undo history limit (default: 50)
235
- enablePersistence?: boolean; // localStorage backup (default: true)
236
- enableAutoSave?: boolean; // auto-save timer (default: true)
237
- enableLogging?: boolean; // debug logs (default: false)
238
- validateState?: (state: T) => boolean; // state validation
239
- }
240
- ```
241
-
242
- ## API Reference
243
-
244
- ### Core Methods
245
-
246
- - `getState(): T` - Get current state
247
- - `mutate(recipe, description?)` - Update state with Immer draft
248
- - `batch(mutations, description?)` - Batch multiple mutations
249
- - `subscribe(listener)` - Subscribe to state changes
250
- - `undo(): boolean` - Undo last operation
251
- - `redo(): boolean` - Redo next operation
252
- - `destroy()` - Clean up resources
253
-
254
- ### Persistence Methods
255
-
256
- - `forceSave(): Promise<void>` - Immediately save state
257
- - `hasUnsavedChanges(): boolean` - Check for unsaved changes
258
- - `loadFromServerManually(): Promise<boolean>` - Manual server load
259
-
260
- ## License
261
-
1
+ [![Release](https://github.com/bkincz/clutch/actions/workflows/release.yml/badge.svg?branch=master)](https://github.com/bkincz/clutch/actions/workflows/release.yml)
2
+ [![codecov](https://codecov.io/gh/bkincz/clutch/branch/main/graph/badge.svg)](https://codecov.io/gh/bkincz/clutch)
3
+ [![npm version](https://badge.fury.io/js/@bkincz%2Fclutch.svg)](https://badge.fury.io/js/@bkincz%2Fclutch)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
6
+
7
+ # Clutch - State Machine
8
+
9
+ A production-ready, TypeScript-first state management library built on Immer with advanced features.
10
+
11
+ This was primarily created for a personal project of mine called Kintsugi, hence the very specific features like `Auto-Save` or `Undo/Redo`, but I've found it a useful replacement for most state managers in my other projects.
12
+ Thought I'd put it here for anyone that might want to use it as well.
13
+
14
+ ## Features
15
+
16
+ - 🔄 **Immutable Updates** - Powered by Immer for clean, mutable-style code that produces immutable state
17
+ - ⏪ **Undo/Redo** - Built-in history management using efficient patch-based storage
18
+ - 💾 **Persistence** - Automatic localStorage backup with optional server synchronization
19
+ - 🚀 **Performance** - Debounced notifications, memory tracking, and efficient batch operations
20
+ - 🛡️ **Type Safety** - Full TypeScript support with runtime validation
21
+ - 🔍 **Debugging** - Comprehensive logging system with structured output
22
+ - 🧹 **Memory Management** - Automatic cleanup and configurable history limits
23
+ - ⚡ **Auto-Save** - Configurable automatic persistence to prevent data loss
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ npm install @bkincz/clutch
29
+ # or
30
+ yarn add @bkincz/clutch
31
+ # or
32
+ pnpm install @bkincz/clutch
33
+ ```
34
+
35
+ ## Quick Start
36
+
37
+ ### 1. Define Your State
38
+
39
+ ```typescript
40
+ interface AppState {
41
+ user: { id: string; name: string } | null;
42
+ todos: Array<{ id: string; text: string; completed: boolean }>;
43
+ ui: { loading: boolean; error: string | null };
44
+ }
45
+
46
+ const initialState: AppState = {
47
+ user: null,
48
+ todos: [],
49
+ ui: { loading: false, error: null },
50
+ };
51
+ ```
52
+
53
+ ### 2. Create Your State Machine
54
+
55
+ ```typescript
56
+ import { StateMachine } from "@bkincz/clutch";
57
+
58
+ class TodoState extends StateMachine<AppState> {
59
+ constructor() {
60
+ super({
61
+ initialState,
62
+ persistenceKey: "todo-app",
63
+ autoSaveInterval: 5, // minutes
64
+ enableLogging: process.env.NODE_ENV === "development",
65
+ });
66
+ }
67
+
68
+ // Optional: implement server persistence
69
+ protected async saveToServer(state: AppState): Promise<void> {
70
+ await fetch("/api/state", {
71
+ method: "POST",
72
+ body: JSON.stringify(state),
73
+ });
74
+ }
75
+
76
+ protected async loadFromServer(): Promise<AppState | null> {
77
+ const response = await fetch("/api/state");
78
+ return response.ok ? await response.json() : null;
79
+ }
80
+ }
81
+
82
+ export const todoState = new TodoState();
83
+ ```
84
+
85
+ ### 3. Use in React
86
+
87
+ ```typescript
88
+ import { useStateMachine } from "@bkincz/clutch";
89
+ import { todoState } from "./todoState";
90
+
91
+ function TodoApp() {
92
+ const { state, mutate } = useStateMachine(todoState);
93
+
94
+ const addTodo = (text: string) => {
95
+ mutate((draft) => {
96
+ draft.todos.push({
97
+ id: Date.now().toString(),
98
+ text,
99
+ completed: false,
100
+ });
101
+ }, "Add todo");
102
+ };
103
+
104
+ const toggleTodo = (id: string) => {
105
+ mutate((draft) => {
106
+ const todo = draft.todos.find((t) => t.id === id);
107
+ if (todo) {
108
+ todo.completed = !todo.completed;
109
+ }
110
+ }, "Toggle todo");
111
+ };
112
+
113
+ return (
114
+ <div>
115
+ <input
116
+ type="text"
117
+ onKeyDown={(e) => {
118
+ if (e.key === "Enter") {
119
+ addTodo(e.currentTarget.value);
120
+ e.currentTarget.value = "";
121
+ }
122
+ }}
123
+ placeholder="Add a todo..."
124
+ />
125
+
126
+ {state.todos.map((todo) => (
127
+ <div key={todo.id}>
128
+ <input
129
+ type="checkbox"
130
+ checked={todo.completed}
131
+ onChange={() => toggleTodo(todo.id)}
132
+ />
133
+ <span>{todo.text}</span>
134
+ </div>
135
+ ))}
136
+ </div>
137
+ );
138
+ }
139
+ ```
140
+
141
+ ## Core Features
142
+
143
+ ### Immutable Updates with Immer
144
+
145
+ Write simple, mutable-looking code that produces immutable state:
146
+
147
+ ```typescript
148
+ // Instead of complex spread operations
149
+ const newState = {
150
+ ...state,
151
+ todos: state.todos.map(todo =>
152
+ todo.id === id ? { ...todo, completed: !todo.completed } : todo
153
+ )
154
+ };
155
+
156
+ // Write simple mutations
157
+ state.mutate(draft => {
158
+ const todo = draft.todos.find(t => t.id === id);
159
+ if (todo) {
160
+ todo.completed = !todo.completed;
161
+ }
162
+ });
163
+ ```
164
+
165
+ ### Undo/Redo
166
+
167
+ ```typescript
168
+ import { useStateActions } from "@bkincz/clutch";
169
+
170
+ function UndoRedoButtons() {
171
+ const { undo, redo } = useStateActions(todoState);
172
+
173
+ return (
174
+ <>
175
+ <button onClick={() => undo()}>Undo</button>
176
+ <button onClick={() => redo()}>Redo</button>
177
+ </>
178
+ );
179
+ }
180
+ ```
181
+
182
+ ### Batch Operations
183
+
184
+ Group multiple changes into a single undo operation:
185
+
186
+ ```typescript
187
+ state.batch([
188
+ draft => draft.todos.push(newTodo1),
189
+ draft => draft.todos.push(newTodo2),
190
+ draft => { draft.ui.loading = false; }
191
+ ], "Add multiple todos");
192
+ ```
193
+
194
+ ## React Hooks
195
+
196
+ ### `useStateMachine(engine)`
197
+ Subscribe to entire state and get mutation methods.
198
+
199
+ ### `useStateSlice(engine, selector, equalityFn?)`
200
+ Subscribe to a specific slice of state for better performance.
201
+
202
+ ```typescript
203
+ const todoCount = useStateSlice(todoState, state => state.todos.length);
204
+ const completedTodos = useStateSlice(
205
+ todoState,
206
+ state => state.todos.filter(t => t.completed)
207
+ );
208
+ ```
209
+
210
+ ### `useStateActions(engine)`
211
+ Get mutation methods without subscribing to state changes.
212
+
213
+ ### `useStateHistory(engine)`
214
+ Access undo/redo state and controls.
215
+
216
+ ```typescript
217
+ const { canUndo, canRedo, undo, redo } = useStateHistory(todoState);
218
+ ```
219
+
220
+ ### `useStatePersist(engine)`
221
+ Handle save/load operations and persistence state.
222
+
223
+ ```typescript
224
+ const { save, load, isSaving, hasUnsavedChanges } = useStatePersist(todoState);
225
+ ```
226
+
227
+ ## Configuration
228
+
229
+ ```typescript
230
+ interface StateConfig<T> {
231
+ initialState: T;
232
+ persistenceKey?: string; // localStorage key
233
+ autoSaveInterval?: number; // minutes (default: 5)
234
+ maxHistorySize?: number; // undo history limit (default: 50)
235
+ enablePersistence?: boolean; // localStorage backup (default: true)
236
+ enableAutoSave?: boolean; // auto-save timer (default: true)
237
+ enableLogging?: boolean; // debug logs (default: false)
238
+ validateState?: (state: T) => boolean; // state validation
239
+ }
240
+ ```
241
+
242
+ ## API Reference
243
+
244
+ ### Core Methods
245
+
246
+ - `getState(): T` - Get current state
247
+ - `mutate(recipe, description?)` - Update state with Immer draft
248
+ - `batch(mutations, description?)` - Batch multiple mutations
249
+ - `subscribe(listener)` - Subscribe to state changes
250
+ - `undo(): boolean` - Undo last operation
251
+ - `redo(): boolean` - Redo next operation
252
+ - `destroy()` - Clean up resources
253
+
254
+ ### Persistence Methods
255
+
256
+ - `forceSave(): Promise<void>` - Immediately save state
257
+ - `hasUnsavedChanges(): boolean` - Check for unsaved changes
258
+ - `loadFromServerManually(): Promise<boolean>` - Manual server load
259
+
260
+ ## License
261
+
262
262
  MIT