@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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/{README.md → readme.md} +261 -261
package/{README.md → readme.md}
RENAMED
|
@@ -1,262 +1,262 @@
|
|
|
1
|
-
[](https://github.com/bkincz/clutch/actions/workflows/release.yml)
|
|
2
|
-
[](https://badge.fury.io/js/@bkincz%2Fclutch)
|
|
4
|
-
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[](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
|
+
[](https://github.com/bkincz/clutch/actions/workflows/release.yml)
|
|
2
|
+
[](https://codecov.io/gh/bkincz/clutch)
|
|
3
|
+
[](https://badge.fury.io/js/@bkincz%2Fclutch)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](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
|