scope-state 0.1.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 +372 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.esm.js +2023 -0
- package/dist/index.js +2073 -0
- package/dist/src/config/index.d.ts +84 -0
- package/dist/src/config/index.d.ts.map +1 -0
- package/dist/src/core/listeners.d.ts +24 -0
- package/dist/src/core/listeners.d.ts.map +1 -0
- package/dist/src/core/monitoring.d.ts +167 -0
- package/dist/src/core/monitoring.d.ts.map +1 -0
- package/dist/src/core/proxy.d.ts +50 -0
- package/dist/src/core/proxy.d.ts.map +1 -0
- package/dist/src/core/store.d.ts +39 -0
- package/dist/src/core/store.d.ts.map +1 -0
- package/dist/src/core/tracking.d.ts +36 -0
- package/dist/src/core/tracking.d.ts.map +1 -0
- package/dist/src/hooks/useLocal.d.ts +32 -0
- package/dist/src/hooks/useLocal.d.ts.map +1 -0
- package/dist/src/hooks/useScope.d.ts +29 -0
- package/dist/src/hooks/useScope.d.ts.map +1 -0
- package/dist/src/index.d.ts +33 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/persistence/advanced.d.ts +47 -0
- package/dist/src/persistence/advanced.d.ts.map +1 -0
- package/dist/src/persistence/storage.d.ts +14 -0
- package/dist/src/persistence/storage.d.ts.map +1 -0
- package/dist/src/types/index.d.ts +175 -0
- package/dist/src/types/index.d.ts.map +1 -0
- package/package.json +69 -0
package/README.md
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
# Scope State
|
|
2
|
+
_The simplest global state system for React._
|
|
3
|
+
|
|
4
|
+
A tiny reactive state manager with global reach and local clarity. Built for modern React. No stale bugs. No mental gymnastics. Full support for storage persistence, auto-optimization, and more.
|
|
5
|
+
|
|
6
|
+
Built for developers who hate Redux and love clarity.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## Why Scope State?
|
|
11
|
+
|
|
12
|
+
Scope State gives you **global reactive state** with:
|
|
13
|
+
- **Zero reducers, zero contexts**
|
|
14
|
+
- **Zero boilerplate**
|
|
15
|
+
- **Zero spreads, zero selectors**
|
|
16
|
+
- **No need for `setState`**
|
|
17
|
+
- And most importantly — **no stale value bugs**
|
|
18
|
+
|
|
19
|
+
Just write:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { $, useScope } from 'scope-state';
|
|
23
|
+
|
|
24
|
+
export const ProfilePage = () => {
|
|
25
|
+
const name = useScope(() => $.user.name);
|
|
26
|
+
return <h1>{name}</h1>
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
That's it. It tracks dependencies automatically and re-renders only what changed.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
## What Makes It Different?
|
|
35
|
+
|
|
36
|
+
- **Fully reactive** — inspired by proxies, not reducers
|
|
37
|
+
- **Intuitive reads and writes** — no `.get()` or `.set()` syntax hell
|
|
38
|
+
- **Mutate like it's a regular object or ref** — works with objects, arrays, numbers, everything
|
|
39
|
+
- **Fine-grained tracking** — no wasted renders
|
|
40
|
+
- **Built-in debug tools**
|
|
41
|
+
- **Feels like magic**
|
|
42
|
+
- **Read and set states *independently* —** outside of functional components or custom hooks
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
## Getting Started
|
|
47
|
+
|
|
48
|
+
### Installation
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install scope-state
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Quick Start
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
// store.ts
|
|
58
|
+
import { configure } from 'scope-state';
|
|
59
|
+
export const $ = configure({
|
|
60
|
+
initialState: {
|
|
61
|
+
user: { name: 'John', age: 30 }
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
// UserProfile.tsx
|
|
68
|
+
import { useScope } from 'scope-state';
|
|
69
|
+
import { $ } from './store';
|
|
70
|
+
|
|
71
|
+
export const UserProfile = () => {
|
|
72
|
+
const name = useScope(() => $.user.name);
|
|
73
|
+
return <h1>{name}</h1>;
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Basic Usage
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
import { useScope, configure } from 'scope-state';
|
|
81
|
+
|
|
82
|
+
// RECOMMENDED: Configure with your initial state
|
|
83
|
+
// It's best to configure your initial store in a separate file.
|
|
84
|
+
// The usage of the dollar sign ($) is optional; just a way to keep it brief
|
|
85
|
+
// and easy to identify.
|
|
86
|
+
|
|
87
|
+
export const $ = configure({
|
|
88
|
+
initialState: {
|
|
89
|
+
user: { name: 'John', age: 30 },
|
|
90
|
+
todos: [],
|
|
91
|
+
theme: 'dark'
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Use in components
|
|
96
|
+
import { useScope } from 'scope-state';
|
|
97
|
+
|
|
98
|
+
export const UserProfileComponent = () => {
|
|
99
|
+
|
|
100
|
+
const user = useScope(() => $.user);
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div>
|
|
104
|
+
<h1>{user.name}</h1>
|
|
105
|
+
<button
|
|
106
|
+
onClick={() => {
|
|
107
|
+
user.age += 1
|
|
108
|
+
}}
|
|
109
|
+
>
|
|
110
|
+
Age: {user.age}
|
|
111
|
+
</button>
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const TodoList = () => {
|
|
117
|
+
const todos = useScope(() => $.todos);
|
|
118
|
+
|
|
119
|
+
const addTodo = () => {
|
|
120
|
+
$.todos.push({ id: Date.now(), text: 'New todo', done: false });
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<div>
|
|
125
|
+
{todos.map(todo => (
|
|
126
|
+
<div key={todo.id}>{todo.text}</div>
|
|
127
|
+
))}
|
|
128
|
+
<button onClick={addTodo}>Add Todo</button>
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## 📚 API Reference
|
|
135
|
+
|
|
136
|
+
### Core Functions
|
|
137
|
+
|
|
138
|
+
#### `useScope(selector)`
|
|
139
|
+
Subscribe to reactive state changes.
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
// Subscribe to entire object
|
|
143
|
+
const user = useScope(() => $.user);
|
|
144
|
+
|
|
145
|
+
// Subscribe to specific property
|
|
146
|
+
const userName = useScope(() => $.user.name);
|
|
147
|
+
|
|
148
|
+
// Subscribe to computed value
|
|
149
|
+
const isAdmin = useScope(() => $.user.role === 'admin');
|
|
150
|
+
|
|
151
|
+
// Subscribe to array
|
|
152
|
+
const todos = useScope(() => $.todos);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### `configure(options)`
|
|
156
|
+
Configure Scope State with custom settings.
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
import { configure, presets } from 'scope-state';
|
|
160
|
+
|
|
161
|
+
// Use a preset
|
|
162
|
+
configure(presets.production());
|
|
163
|
+
|
|
164
|
+
// Custom configuration
|
|
165
|
+
const $ = configure({
|
|
166
|
+
initialState: { /* your state */ },
|
|
167
|
+
monitoring: { enabled: true },
|
|
168
|
+
proxy: { maxDepth: 3 }
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Then access any item in your state by scoping it using the main hook:
|
|
172
|
+
const restaurants = useScope(() => $.restaurants || []) // optional fallback
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
### Object Methods
|
|
178
|
+
|
|
179
|
+
All objects in the store have these reactive methods:
|
|
180
|
+
|
|
181
|
+
#### `$merge(newProps)`
|
|
182
|
+
Merge new properties without removing existing ones.
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
$.user.$merge({ name: 'John' }); // Updates only name
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
#### `$set(newProps)`
|
|
189
|
+
Replace object with new properties.
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
$.user.$set({ name: 'John', age: 25 }); // Replaces entire user object
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
#### `raw()`
|
|
196
|
+
Get plain, serializable JavaScript object without any function references (the reactivity methods removed).
|
|
197
|
+
|
|
198
|
+
```tsx
|
|
199
|
+
const plainUser = $.user.raw();
|
|
200
|
+
```
|
|
201
|
+
_This is helpful when you need to serialize the state for storage, API calls, or debugging. Otherwise, it's not necessary._
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
### Array Methods
|
|
206
|
+
|
|
207
|
+
Arrays have enhanced methods that trigger reactivity:
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
todos.push({ id: 1, text: 'Buy milk' }); // This will trigger a re-render
|
|
211
|
+
|
|
212
|
+
todos.splice(0, 1); // This will trigger a re-render
|
|
213
|
+
|
|
214
|
+
$.todos = [/* new array */] // You can also directly assign a new array to the property in the global state itself ($).
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Utility Functions
|
|
218
|
+
|
|
219
|
+
Create reactive local state (not global).
|
|
220
|
+
|
|
221
|
+
```tsx
|
|
222
|
+
import { useLocal } from 'scope-state';
|
|
223
|
+
|
|
224
|
+
function MyComponent() {
|
|
225
|
+
const localState = useLocal({ count: 0 });
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<button onClick={() => localState.count + 1}>
|
|
229
|
+
Count: {localState.count}
|
|
230
|
+
</button>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
## Configuration
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
### Presets
|
|
242
|
+
|
|
243
|
+
```tsx
|
|
244
|
+
import { configure, presets } from 'scope-state';
|
|
245
|
+
|
|
246
|
+
// Development: Enhanced debugging
|
|
247
|
+
configure(presets.development());
|
|
248
|
+
|
|
249
|
+
// Production: Optimized performance
|
|
250
|
+
configure(presets.production());
|
|
251
|
+
|
|
252
|
+
// Minimal: Memory-constrained environments
|
|
253
|
+
configure(presets.minimal());
|
|
254
|
+
|
|
255
|
+
// Full-featured: All features enabled
|
|
256
|
+
configure(presets.full());
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
### Custom Configuration
|
|
262
|
+
|
|
263
|
+
```tsx
|
|
264
|
+
configure({
|
|
265
|
+
initialState: {
|
|
266
|
+
// Your app's initial state
|
|
267
|
+
},
|
|
268
|
+
proxy: {
|
|
269
|
+
maxDepth: 5, // How deep to proxy objects
|
|
270
|
+
smartArrayTracking: true, // Optimize array operations
|
|
271
|
+
},
|
|
272
|
+
monitoring: {
|
|
273
|
+
enabled: true, // Enable debug logging
|
|
274
|
+
verboseLogging: false, // Detailed logs
|
|
275
|
+
autoLeakDetection: true, // Detect memory leaks
|
|
276
|
+
},
|
|
277
|
+
persistence: {
|
|
278
|
+
enabled: true, // Enable state persistence
|
|
279
|
+
paths: ['user', 'settings.theme'], // Which paths to persist (leave as undefined to persist all paths)
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
## Philosophy
|
|
287
|
+
|
|
288
|
+
React's core primitives like `useState`, `useReducer`, and `useContext` work well for many use cases.
|
|
289
|
+
|
|
290
|
+
But when your app grows in complexity…
|
|
291
|
+
- deeply nested objects,
|
|
292
|
+
- shared state across pages,
|
|
293
|
+
- state persistence,
|
|
294
|
+
- or fine-grained reactivity,
|
|
295
|
+
|
|
296
|
+
suddenly you're spending time wiring reducers, spreading props, memoizing selectors, and debugging re-renders.
|
|
297
|
+
|
|
298
|
+
Scope State simplifies that.
|
|
299
|
+
|
|
300
|
+
You write and read state _directly_, just like a `ref` or a signal, but with full reactivity, automatic tracking, and global accessibility.
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
This library was built out of frustration with every other state system:
|
|
304
|
+
- **Redux** is too bloated
|
|
305
|
+
- **Recoil** is too verbose
|
|
306
|
+
- **Zustand** still forces manual updates
|
|
307
|
+
- **Legend State** is performant (and deserves significant respect) but has a higher learning curve and confusing API. It's simply ahead of its time.
|
|
308
|
+
|
|
309
|
+
The mental model is simple: write and read directly. Like `useRef`, but global, reactive, and tracked.
|
|
310
|
+
|
|
311
|
+
## Best Practices
|
|
312
|
+
|
|
313
|
+
### 1. Keep Selectors Simple
|
|
314
|
+
|
|
315
|
+
```tsx
|
|
316
|
+
// ✅ Good
|
|
317
|
+
const name = useScope(() => $.user.name);
|
|
318
|
+
|
|
319
|
+
// ❌ Avoid complex computations in selectors
|
|
320
|
+
const expensiveData = useScope(() => $.data.map(/* heavy computation */));
|
|
321
|
+
|
|
322
|
+
// Instead...
|
|
323
|
+
const data = useScope(() => $.data);
|
|
324
|
+
const expensiveCalculation = useMemo(() => data.map(/* heavy computation */), [data])
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
### 2. Use Direct Assignment & Flexible Methods for Updates
|
|
330
|
+
|
|
331
|
+
```tsx
|
|
332
|
+
// Option 1: Direct assignment
|
|
333
|
+
$.user.name = "John";
|
|
334
|
+
|
|
335
|
+
$.todos.push({ title: "Do Laundry", date: new Date().toISOString() })
|
|
336
|
+
|
|
337
|
+
// Option 2: Shallow merge a new value without changing existing properties
|
|
338
|
+
$.user.$merge({ name: 'John' });
|
|
339
|
+
|
|
340
|
+
// Option 3: Use the updater function [NEW!]
|
|
341
|
+
$.user.$update("age", (age) => age + 1);
|
|
342
|
+
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
### 3. Configure Persistence (Optional)
|
|
348
|
+
|
|
349
|
+
```tsx
|
|
350
|
+
// ✅ Configure before your app starts
|
|
351
|
+
configure(presets.production());
|
|
352
|
+
|
|
353
|
+
function App() {
|
|
354
|
+
// Your app components
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
## Created by Dalton Letorney
|
|
360
|
+
|
|
361
|
+
If you like this, feel free to star the repo! If you love it, use it in production. If it breaks, open a PR so we can make this even more epic.
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
**Scope State is minimal by design** — the goal is not to reinvent React, but to make it finally feel clean again.
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
## License
|
|
371
|
+
|
|
372
|
+
MIT © Dalton Letorney
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export { useScope } from './hooks/useScope';
|
|
2
|
+
export { useLocal } from './hooks/useLocal';
|
|
3
|
+
export { getConfig, resetConfig, presets } from './config';
|
|
4
|
+
export { initializeStore, getStore, resetStore } from './core/store';
|
|
5
|
+
export { monitorAPI } from './core/monitoring';
|
|
6
|
+
export { persistenceAPI } from './persistence/advanced';
|
|
7
|
+
export { setInitialStoreState, createAdvancedProxy, pathUsageStats, selectorPaths, clearProxyCache, getProxyCacheStats, optimizeMemoryUsage, proxyPathMap } from './core/proxy';
|
|
8
|
+
export type { ScopeConfig, ProxyConfig, MonitoringConfig, PersistenceConfig, CustomMethods, CustomArrayMethods, StoreType, MonitoringStats, ProxyCacheStats, PathUsageStats } from './types';
|
|
9
|
+
import type { StoreType, CustomMethods, CustomArrayMethods, ScopeConfig } from './types';
|
|
10
|
+
/**
|
|
11
|
+
* Configure Scope with custom settings and return a properly typed store
|
|
12
|
+
* This is the main way to set up Scope with TypeScript support
|
|
13
|
+
*/
|
|
14
|
+
export declare function configure<T extends Record<string, any>>(config: ScopeConfig<T>): StoreType<T>;
|
|
15
|
+
export declare function trackDependenciesAdvanced<T>(selector: () => T): {
|
|
16
|
+
value: T;
|
|
17
|
+
paths: string[];
|
|
18
|
+
};
|
|
19
|
+
export declare const $: StoreType<any>;
|
|
20
|
+
export declare function createReactive<T extends object>(obj: T): T & (T extends any[] ? CustomArrayMethods<T[0]> : CustomMethods<T>);
|
|
21
|
+
export declare const $local: typeof createReactive;
|
|
22
|
+
export declare function isReactive(obj: any): boolean;
|
|
23
|
+
export declare function activate<T>(obj: T): T;
|
|
24
|
+
export declare function $activate<T>(obj: T): T;
|
|
25
|
+
export declare function getProxy<T = any>(path: string | string[] | any): T;
|
|
26
|
+
export declare function $get<T = any>(path: string | string[] | T): T;
|
|
27
|
+
export declare const debugInfo: {
|
|
28
|
+
getListenerCount: () => any;
|
|
29
|
+
getPathCount: () => any;
|
|
30
|
+
getActivePaths: () => any;
|
|
31
|
+
};
|
|
32
|
+
export declare const rawStore: any;
|
|
33
|
+
//# sourceMappingURL=index.d.ts.map
|