dev-react-microstore 4.0.1 → 6.0.0
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 +208 -142
- package/dist/index.d.mts +129 -2
- package/dist/index.d.ts +129 -2
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/example/README.md +54 -0
- package/example/eslint.config.js +28 -0
- package/example/index.html +13 -0
- package/example/package-lock.json +3382 -0
- package/example/package.json +29 -0
- package/example/public/index.html +98 -0
- package/example/public/vite.svg +1 -0
- package/example/src/App.css +613 -0
- package/example/src/App.tsx +34 -0
- package/example/src/assets/react.svg +1 -0
- package/example/src/components/Counter.tsx +112 -0
- package/example/src/components/CustomCompare.tsx +466 -0
- package/example/src/components/Logs.tsx +28 -0
- package/example/src/components/Search.tsx +38 -0
- package/example/src/components/ThemeToggle.tsx +25 -0
- package/example/src/components/TodoList.tsx +63 -0
- package/example/src/components/UserManager.tsx +68 -0
- package/example/src/index.css +68 -0
- package/example/src/main.tsx +10 -0
- package/example/src/store.ts +223 -0
- package/example/src/vite-env.d.ts +1 -0
- package/example/tsconfig.app.json +26 -0
- package/example/tsconfig.json +7 -0
- package/example/tsconfig.node.json +25 -0
- package/example/vite.config.ts +7 -0
- package/package.json +22 -5
- package/src/hooks.test.tsx +271 -0
- package/src/index.ts +514 -87
- package/src/store.test.ts +997 -0
- package/src/types.test.ts +161 -0
- package/vitest.config.ts +10 -0
package/README.md
CHANGED
|
@@ -1,188 +1,254 @@
|
|
|
1
|
-
# react-microstore
|
|
1
|
+
# dev-react-microstore
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Probably the fastest store library ever created for React.
|
|
4
|
+
|
|
5
|
+
A minimal, zero-dependency global state manager with fine-grained subscriptions, full TypeScript inference, and a tiny footprint (< 2KB minified).
|
|
4
6
|
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
|
-
npm install react-microstore
|
|
10
|
+
npm install dev-react-microstore
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
##
|
|
13
|
+
## Quick Start
|
|
12
14
|
|
|
13
15
|
```tsx
|
|
14
|
-
import { createStoreState,
|
|
16
|
+
import { createStoreState, createSelectorHook } from 'dev-react-microstore';
|
|
17
|
+
|
|
18
|
+
const store = createStoreState({
|
|
19
|
+
count: 0,
|
|
20
|
+
user: { name: 'Alice', age: 30 },
|
|
21
|
+
});
|
|
15
22
|
|
|
16
|
-
const
|
|
23
|
+
export const useStore = createSelectorHook(store);
|
|
17
24
|
|
|
18
25
|
function Counter() {
|
|
19
|
-
const { count } =
|
|
20
|
-
|
|
21
|
-
return (
|
|
22
|
-
<div>
|
|
23
|
-
<p>{count}</p>
|
|
24
|
-
<button onClick={() => counterStore.set({ count: count + 1 })}>
|
|
25
|
-
Increment
|
|
26
|
-
</button>
|
|
27
|
-
</div>
|
|
28
|
-
);
|
|
26
|
+
const { count } = useStore(['count']);
|
|
27
|
+
return <button onClick={() => store.setKey('count', count + 1)}>{count}</button>;
|
|
29
28
|
}
|
|
30
29
|
```
|
|
31
30
|
|
|
32
|
-
|
|
31
|
+
One line to create the hook, all types inferred from the store instance — no manual generics.
|
|
32
|
+
|
|
33
|
+
## API
|
|
34
|
+
|
|
35
|
+
### `createStoreState(initialState)`
|
|
36
|
+
|
|
37
|
+
Creates a reactive store. Returns:
|
|
38
|
+
|
|
39
|
+
| Method | Description |
|
|
40
|
+
|--------|-------------|
|
|
41
|
+
| `get()` | Returns the full state object |
|
|
42
|
+
| `getKey(key)` | Returns the value of a single key |
|
|
43
|
+
| `set(partial)` | Partially updates state. Middleware runs, listeners fire for changed keys only |
|
|
44
|
+
| `setKey(key, value)` | Sets a single key |
|
|
45
|
+
| `merge(key, partial)` | **Pure read** — returns `{ ...state[key], ...partial }` without writing. Type-safe: only works on object-valued keys |
|
|
46
|
+
| `mergeSet(key, partial)` | `merge` + `set` — shallow-merges and writes. Middleware & listeners fire as normal |
|
|
47
|
+
| `reset(keys?)` | Resets keys to their initial values. No args = full reset |
|
|
48
|
+
| `batch(fn)` | Groups multiple `set`/`setKey`/`mergeSet` calls — listeners fire once at the end |
|
|
49
|
+
| `subscribe(keys, listener)` | Fine-grained subscription. Returns unsubscribe function |
|
|
50
|
+
| `select(keys)` | Returns a `Pick<T, K>` snapshot of specific keys |
|
|
51
|
+
| `onChange(keys, callback)` | Non-React listener with `(newValues, prevValues)` — batched per microtask |
|
|
52
|
+
| `addMiddleware(fn, keys?)` | Express-style middleware to intercept, block, or transform updates |
|
|
53
|
+
| `skipSetWhen(key, fn)` | Skip updates for a key when `fn(prev, next)` returns `true`. Runs after `Object.is` |
|
|
54
|
+
| `removeSkipSetWhen(key)` | Remove the skip condition, restoring default `Object.is` behavior |
|
|
55
|
+
|
|
56
|
+
### `createSelectorHook(store)`
|
|
57
|
+
|
|
58
|
+
Returns a pre-bound React hook with full type inference:
|
|
33
59
|
|
|
34
60
|
```tsx
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const taskStore = createStoreState({
|
|
38
|
-
tasks: [
|
|
39
|
-
{ id: 1, title: 'Learn React', completed: false, priority: 'high' },
|
|
40
|
-
{ id: 2, title: 'Build app', completed: false, priority: 'medium' }
|
|
41
|
-
],
|
|
42
|
-
filters: {
|
|
43
|
-
showCompleted: true,
|
|
44
|
-
priorityFilter: null
|
|
45
|
-
}
|
|
46
|
-
});
|
|
61
|
+
const useStore = createSelectorHook(store);
|
|
47
62
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<ul>
|
|
68
|
-
{tasks.map(task => (
|
|
69
|
-
<li key={task.id}>
|
|
70
|
-
{task.title} - {task.completed ? 'Done' : 'Pending'}
|
|
71
|
-
<button onClick={() => toggleTask(task.id)}>
|
|
72
|
-
Toggle
|
|
73
|
-
</button>
|
|
74
|
-
</li>
|
|
75
|
-
))}
|
|
76
|
-
</ul>
|
|
77
|
-
);
|
|
78
|
-
}
|
|
63
|
+
// In a component — types are inferred, no generics needed
|
|
64
|
+
const { user } = useStore(['user']);
|
|
65
|
+
// user is { name: string; age: number }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### `useStoreSelector(store, selector)`
|
|
69
|
+
|
|
70
|
+
Low-level React hook. Prefer `createSelectorHook` for cleaner usage.
|
|
71
|
+
|
|
72
|
+
## merge vs mergeSet
|
|
73
|
+
|
|
74
|
+
`merge` is a pure read helper — it returns the merged object without touching the store:
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
const updated = store.merge('user', { age: 31 });
|
|
78
|
+
// updated = { name: 'Alice', age: 31 }
|
|
79
|
+
// store is unchanged
|
|
79
80
|
```
|
|
80
81
|
|
|
81
|
-
|
|
82
|
+
`mergeSet` writes it:
|
|
82
83
|
|
|
83
84
|
```tsx
|
|
84
|
-
|
|
85
|
+
store.mergeSet('user', { age: 31 });
|
|
86
|
+
// store.user is now { name: 'Alice', age: 31 }
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Both are type-safe — calling on a primitive key (e.g. `merge('count', ...)`) is a compile error.
|
|
90
|
+
|
|
91
|
+
## Batching
|
|
85
92
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
Group multiple updates so listeners fire once:
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
store.batch(() => {
|
|
97
|
+
store.setKey('count', 10);
|
|
98
|
+
store.mergeSet('user', { age: 25 });
|
|
99
|
+
store.setKey('name', 'Bob');
|
|
91
100
|
});
|
|
101
|
+
// Listeners fire once with all changes
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## reset
|
|
92
105
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
|
|
106
|
+
```tsx
|
|
107
|
+
store.reset(); // Full reset to initial state
|
|
108
|
+
store.reset(['count']); // Reset specific keys
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Middleware
|
|
112
|
+
|
|
113
|
+
Express-style middleware to intercept, block, or transform updates:
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
// Validation — block negative counts
|
|
117
|
+
store.addMiddleware(
|
|
118
|
+
(state, update, next) => {
|
|
119
|
+
if (update.count !== undefined && update.count < 0) return;
|
|
120
|
+
next();
|
|
121
|
+
},
|
|
122
|
+
['count']
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// Transform — normalize user input
|
|
126
|
+
store.addMiddleware((state, update, next) => {
|
|
127
|
+
if (update.user?.name) {
|
|
128
|
+
next({ ...update, user: { ...update.user, name: update.user.name.trim() } });
|
|
129
|
+
} else {
|
|
130
|
+
next();
|
|
118
131
|
}
|
|
119
|
-
}
|
|
132
|
+
});
|
|
120
133
|
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
134
|
+
// Logging
|
|
135
|
+
store.addMiddleware((state, update, next) => {
|
|
136
|
+
console.log('Update:', update);
|
|
137
|
+
next();
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
No-middleware fast path: when no middleware is registered, `set()` skips the pipeline entirely.
|
|
142
|
+
|
|
143
|
+
## Persistence
|
|
144
|
+
|
|
145
|
+
Built-in middleware for automatic state persistence. Supports both sync (`localStorage`) and async (`AsyncStorage`) backends:
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
import { createStoreState, createPersistenceMiddleware, loadPersistedState } from 'dev-react-microstore';
|
|
149
|
+
|
|
150
|
+
// Sync (localStorage / sessionStorage)
|
|
151
|
+
const persisted = loadPersistedState<AppState>(localStorage, 'app', ['theme', 'user']);
|
|
136
152
|
|
|
137
|
-
|
|
138
|
-
|
|
153
|
+
const store = createStoreState<AppState>({ theme: 'light', user: null, ...persisted });
|
|
154
|
+
store.addMiddleware(createPersistenceMiddleware(localStorage, 'app', ['theme', 'user']));
|
|
155
|
+
|
|
156
|
+
// Async (React Native AsyncStorage)
|
|
157
|
+
const persisted = await loadPersistedState<AppState>(AsyncStorage, 'app', ['theme', 'user']);
|
|
158
|
+
|
|
159
|
+
const store = createStoreState<AppState>({ theme: 'light', user: null, ...persisted });
|
|
160
|
+
store.addMiddleware(createPersistenceMiddleware(AsyncStorage, 'app', ['theme', 'user']));
|
|
139
161
|
```
|
|
140
162
|
|
|
141
|
-
|
|
163
|
+
Each key is stored individually (`app:theme`, `app:user`) — no serializing the entire state blob.
|
|
164
|
+
|
|
165
|
+
## Change Listeners (outside React)
|
|
142
166
|
|
|
143
|
-
|
|
144
|
-
- Fine-grained subscriptions to minimize re-renders
|
|
145
|
-
- Custom comparison functions for complex state updates
|
|
146
|
-
- Fully Type-safe
|
|
147
|
-
- No dependencies other than React
|
|
148
|
-
- Update store from anywhere in your application
|
|
167
|
+
Listen for value changes anywhere — module scope, event handlers, async functions:
|
|
149
168
|
|
|
150
|
-
|
|
169
|
+
```tsx
|
|
170
|
+
const unsub = store.onChange(['theme', 'locale'], (values, prev) => {
|
|
171
|
+
document.body.className = values.theme;
|
|
172
|
+
console.log(`Theme: ${prev.theme} → ${values.theme}`);
|
|
173
|
+
});
|
|
151
174
|
|
|
152
|
-
|
|
175
|
+
// Multiple synchronous set() calls are batched into one notification
|
|
176
|
+
unsub();
|
|
177
|
+
```
|
|
153
178
|
|
|
154
|
-
|
|
179
|
+
## Custom Comparison
|
|
155
180
|
|
|
156
|
-
|
|
181
|
+
Control when re-renders happen with custom equality functions:
|
|
157
182
|
|
|
158
|
-
```
|
|
159
|
-
|
|
183
|
+
```tsx
|
|
184
|
+
const { tasks } = useStore([
|
|
185
|
+
{
|
|
186
|
+
tasks: (prev, next) =>
|
|
187
|
+
!prev.some((t, i) => t.completed !== next?.[i]?.completed)
|
|
188
|
+
}
|
|
189
|
+
]);
|
|
160
190
|
```
|
|
161
191
|
|
|
162
|
-
|
|
192
|
+
## skipSetWhen
|
|
163
193
|
|
|
164
|
-
|
|
194
|
+
Skip updates for a key when a condition is met. `Object.is` always runs first as a fast path; `skipSetWhen` is an additional check when references differ. Return `true` = skip the update:
|
|
165
195
|
|
|
166
196
|
```tsx
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
197
|
+
const store = createStoreState({ user: { id: 1, name: 'Alice' }, tags: ['a', 'b'] });
|
|
198
|
+
|
|
199
|
+
// Skip update if id and name haven't changed
|
|
200
|
+
store.skipSetWhen('user', (prev, next) => prev.id === next.id && prev.name === next.name);
|
|
201
|
+
|
|
202
|
+
// Skip if array contents are the same
|
|
203
|
+
store.skipSetWhen('tags', (prev, next) => prev.length === next.length && prev.every((t, i) => t === next[i]));
|
|
204
|
+
|
|
205
|
+
// mergeSet with identical content is now a no-op — no listeners, no middleware
|
|
206
|
+
store.mergeSet('user', { name: 'Alice' }); // skipped
|
|
207
|
+
|
|
208
|
+
// Remove when no longer needed
|
|
209
|
+
store.removeSkipSetWhen('user');
|
|
177
210
|
```
|
|
178
211
|
|
|
179
|
-
`
|
|
180
|
-
|
|
212
|
+
Type-safe: `prev` and `next` types are inferred from the key.
|
|
213
|
+
|
|
214
|
+
## Features
|
|
215
|
+
|
|
216
|
+
- Probably the fastest React store library ever created
|
|
217
|
+
- Extremely lightweight (< 2KB minified)
|
|
218
|
+
- Fine-grained subscriptions — components only re-render when their keys change
|
|
219
|
+
- `createSelectorHook` for one-line, fully-typed per-store hooks
|
|
220
|
+
- `merge` / `mergeSet` for ergonomic object updates
|
|
221
|
+
- `batch` to group updates and fire listeners once
|
|
222
|
+
- `reset` to restore initial state (full or per-key)
|
|
223
|
+
- `onChange` for non-React listeners with value tracking
|
|
224
|
+
- `skipSetWhen` to prevent unnecessary updates on object-valued keys
|
|
225
|
+
- Custom comparison functions for complex equality
|
|
226
|
+
- Express-style middleware (validation, transforms, logging)
|
|
227
|
+
- Automatic persistence to localStorage, sessionStorage, or AsyncStorage
|
|
228
|
+
- Fully type-safe — all types inferred from store instance
|
|
229
|
+
- Zero dependencies (peer: React >= 17)
|
|
230
|
+
|
|
231
|
+
## ESLint Plugin
|
|
232
|
+
|
|
233
|
+
[eslint-plugin-dev-react-microstore](https://www.npmjs.com/package/eslint-plugin-dev-react-microstore) warns on unused selector keys:
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
npm install --save-dev eslint-plugin-dev-react-microstore
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
```tsx
|
|
240
|
+
import reactMicrostore from 'eslint-plugin-dev-react-microstore';
|
|
241
|
+
|
|
242
|
+
export default [{
|
|
243
|
+
plugins: { 'dev-react-microstore': reactMicrostore },
|
|
244
|
+
rules: { 'dev-react-microstore/no-unused-selector-keys': 'warn' }
|
|
245
|
+
}];
|
|
246
|
+
```
|
|
181
247
|
|
|
182
248
|
```tsx
|
|
183
|
-
// ❌
|
|
184
|
-
const { a } =
|
|
249
|
+
// ❌ Warns — 'b' is selected but unused
|
|
250
|
+
const { a } = useStore(['a', 'b']);
|
|
185
251
|
|
|
186
|
-
// ✅
|
|
187
|
-
const { a, b } =
|
|
188
|
-
```
|
|
252
|
+
// ✅ Fine
|
|
253
|
+
const { a, b } = useStore(['a', 'b']);
|
|
254
|
+
```
|
package/dist/index.d.mts
CHANGED
|
@@ -1,9 +1,34 @@
|
|
|
1
1
|
type StoreListener = () => void;
|
|
2
|
+
type MiddlewareFunction<T extends object> = (currentState: T, update: Partial<T>, next: (modifiedUpdate?: Partial<T>) => void) => void;
|
|
3
|
+
/**
|
|
4
|
+
* Creates a new reactive store with fine-grained subscriptions and middleware support.
|
|
5
|
+
*
|
|
6
|
+
* @param initialState - The initial state object for the store
|
|
7
|
+
* @returns Store object with methods: get, set, subscribe, select, addMiddleware, onChange
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* const store = createStoreState({ count: 0, name: 'John' });
|
|
12
|
+
* store.set({ count: 1 }); // Update state
|
|
13
|
+
* const { count } = useStoreSelector(store, ['count']); // Subscribe in React
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
2
16
|
declare function createStoreState<T extends object>(initialState: T): {
|
|
3
17
|
get: () => T;
|
|
4
|
-
|
|
18
|
+
getKey: <K extends keyof T>(key: K) => T[K];
|
|
19
|
+
set: (update: Partial<T>) => void;
|
|
20
|
+
setKey: <K extends keyof T>(key: K, value: T[K]) => void;
|
|
21
|
+
merge: <K extends keyof T>(key: K, value: T[K] extends object ? Partial<T[K]> : never) => T[K];
|
|
22
|
+
mergeSet: <K extends keyof T>(key: K, value: T[K] extends object ? Partial<T[K]> : never) => void;
|
|
23
|
+
reset: (keys?: (keyof T)[]) => void;
|
|
24
|
+
batch: (fn: () => void) => void;
|
|
5
25
|
subscribe: (keys: (keyof T)[], listener: StoreListener) => (() => void);
|
|
6
26
|
select: <K extends keyof T>(keys: K[]) => Pick<T, K>;
|
|
27
|
+
addMiddleware: (callbackOrTuple: MiddlewareFunction<T> | [MiddlewareFunction<T>, (keyof T)[]], affectedKeys?: (keyof T)[] | null) => () => void;
|
|
28
|
+
onChange: <K extends keyof T>(keys: K[], callback: (values: Pick<T, K>, prev: Pick<T, K>) => void) => (() => void);
|
|
29
|
+
skipSetWhen: <K extends keyof T>(key: K, fn: (prev: T[K], next: T[K]) => boolean) => void;
|
|
30
|
+
removeSkipSetWhen: (key: keyof T) => void;
|
|
31
|
+
_eqReg: Record<string, ((prev: any, next: any) => boolean) | undefined>;
|
|
7
32
|
};
|
|
8
33
|
type StoreType<T extends object> = ReturnType<typeof createStoreState<T>>;
|
|
9
34
|
type PrimitiveKey<T extends object> = keyof T;
|
|
@@ -17,6 +42,108 @@ type ExtractSelectorKeys<T extends object, S extends SelectorInput<T>> = {
|
|
|
17
42
|
[K in S[number] extends infer Item ? Item extends keyof T ? Item : keyof Item : never]: T[K];
|
|
18
43
|
};
|
|
19
44
|
type Picked<T extends object, S extends SelectorInput<T>> = ExtractSelectorKeys<T, S>;
|
|
45
|
+
/**
|
|
46
|
+
* React hook that subscribes to specific keys in a store with fine-grained re-renders.
|
|
47
|
+
* Only re-renders when the selected keys actually change (using Object.is comparison).
|
|
48
|
+
*
|
|
49
|
+
* @param store - The store created with createStoreState
|
|
50
|
+
* @param selector - Array of keys to subscribe to, or objects with custom compare functions
|
|
51
|
+
* @returns Selected state values from the store
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* // Subscribe to specific keys
|
|
56
|
+
* const { count, name } = useStoreSelector(store, ['count', 'name']);
|
|
57
|
+
*
|
|
58
|
+
* // Custom comparison for complex objects
|
|
59
|
+
* const { tasks } = useStoreSelector(store, [
|
|
60
|
+
* { tasks: (prev, next) => prev.length === next.length }
|
|
61
|
+
* ]);
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
20
64
|
declare function useStoreSelector<T extends object, S extends SelectorInput<T>>(store: StoreType<T>, selector: S): Picked<T, S>;
|
|
65
|
+
/**
|
|
66
|
+
* Creates a pre-bound selector hook for a specific store instance.
|
|
67
|
+
* Infers the state type from the store — no manual generics needed.
|
|
68
|
+
*
|
|
69
|
+
* @param store - The store created with createStoreState
|
|
70
|
+
* @returns A React hook with the same API as useStoreSelector, but with the store already bound
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```ts
|
|
74
|
+
* const useMyStore = createSelectorHook(myStore);
|
|
75
|
+
*
|
|
76
|
+
* // In a component:
|
|
77
|
+
* const { count, name } = useMyStore(['count', 'name']);
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
declare function createSelectorHook<T extends object>(store: StoreType<T>): <S extends SelectorInput<T>>(selector: S) => Picked<T, S>;
|
|
81
|
+
/**
|
|
82
|
+
* Interface for synchronous storage (localStorage, sessionStorage, etc.).
|
|
83
|
+
*/
|
|
84
|
+
interface StorageSupportingInterface {
|
|
85
|
+
getItem(key: string): string | null;
|
|
86
|
+
setItem(key: string, value: string): void;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Interface for asynchronous storage (React Native AsyncStorage, etc.).
|
|
90
|
+
*/
|
|
91
|
+
interface AsyncStorageSupportingInterface {
|
|
92
|
+
getItem(key: string): Promise<string | null>;
|
|
93
|
+
setItem(key: string, value: string): Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
type AnyStorage = Storage | StorageSupportingInterface | AsyncStorageSupportingInterface;
|
|
96
|
+
/**
|
|
97
|
+
* Creates a persistence middleware that saves individual keys to storage.
|
|
98
|
+
* Only writes when the specified keys actually change, using per-key storage.
|
|
99
|
+
* Storage format: `${persistKey}:${keyName}` for each persisted key.
|
|
100
|
+
*
|
|
101
|
+
* Works with both synchronous storage (localStorage) and asynchronous storage
|
|
102
|
+
* (React Native AsyncStorage). Async writes are fire-and-forget — the state
|
|
103
|
+
* update is never blocked by a slow write.
|
|
104
|
+
*
|
|
105
|
+
* @param storage - Storage interface (localStorage, sessionStorage, AsyncStorage, etc.)
|
|
106
|
+
* @param persistKey - Base key prefix for storage (e.g., 'myapp' creates 'myapp:theme')
|
|
107
|
+
* @param keys - Array of state keys to persist
|
|
108
|
+
* @returns Tuple of [middleware function, affected keys] for use with addMiddleware
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* // Sync — localStorage
|
|
113
|
+
* store.addMiddleware(
|
|
114
|
+
* createPersistenceMiddleware(localStorage, 'myapp', ['theme', 'isLoggedIn'])
|
|
115
|
+
* );
|
|
116
|
+
*
|
|
117
|
+
* // Async — React Native AsyncStorage
|
|
118
|
+
* store.addMiddleware(
|
|
119
|
+
* createPersistenceMiddleware(AsyncStorage, 'myapp', ['theme', 'isLoggedIn'])
|
|
120
|
+
* );
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
declare function createPersistenceMiddleware<T extends object>(storage: AnyStorage, persistKey: string, keys: (keyof T)[]): [MiddlewareFunction<T>, (keyof T)[]];
|
|
124
|
+
/**
|
|
125
|
+
* Loads persisted state from individual key storage during store initialization.
|
|
126
|
+
* Reads keys saved by createPersistenceMiddleware and returns them as partial state.
|
|
127
|
+
*
|
|
128
|
+
* Returns synchronously for sync storage and a Promise for async storage.
|
|
129
|
+
*
|
|
130
|
+
* @param storage - Storage interface to read from (same as used in middleware)
|
|
131
|
+
* @param persistKey - Base key prefix used for storage (same as used in middleware)
|
|
132
|
+
* @param keys - Array of keys to restore (should match middleware keys)
|
|
133
|
+
* @returns Partial state object (sync) or Promise of partial state (async)
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```ts
|
|
137
|
+
* // Sync — localStorage
|
|
138
|
+
* const persisted = loadPersistedState(localStorage, 'myapp', ['theme']);
|
|
139
|
+
* const store = createStoreState({ theme: 'light', ...persisted });
|
|
140
|
+
*
|
|
141
|
+
* // Async — React Native AsyncStorage
|
|
142
|
+
* const persisted = await loadPersistedState(AsyncStorage, 'myapp', ['theme']);
|
|
143
|
+
* const store = createStoreState({ theme: 'light', ...persisted });
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
declare function loadPersistedState<T extends object>(storage: Storage | StorageSupportingInterface, persistKey: string, keys: (keyof T)[]): Partial<T>;
|
|
147
|
+
declare function loadPersistedState<T extends object>(storage: AsyncStorageSupportingInterface, persistKey: string, keys: (keyof T)[]): Promise<Partial<T>>;
|
|
21
148
|
|
|
22
|
-
export { createStoreState, useStoreSelector };
|
|
149
|
+
export { type AsyncStorageSupportingInterface, type StorageSupportingInterface, createPersistenceMiddleware, createSelectorHook, createStoreState, loadPersistedState, useStoreSelector };
|