@asaidimu/react-store 1.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/LICENSE.md +21 -0
- package/README.md +170 -0
- package/index.cjs +1 -0
- package/index.d.cts +91 -0
- package/index.d.ts +91 -0
- package/index.js +1 -0
- package/package.json +33 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Saidimu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# @asaidimu/react-store
|
|
2
|
+
|
|
3
|
+
A robust, production-ready state management solution for React applications, designed for performance, scalability, and ease of use.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install the package using npm or yarn:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @asaidimu/react-store
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Key Features
|
|
14
|
+
|
|
15
|
+
- **Performance Metrics**: Built-in metrics collection for debugging and performance monitoring.
|
|
16
|
+
- **Selector Caching**: Efficient selector caching system to optimize state access.
|
|
17
|
+
- **Middleware Support**: Execute middleware functions during state updates.
|
|
18
|
+
- **Persistence**: Persist state across page reloads with customizable storage options.
|
|
19
|
+
- **Error Handling**: Comprehensive error handling with recovery strategies.
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
### Basic Example
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
// store.ts
|
|
27
|
+
import { createNodeStore } from "@asaidimu/react-store";
|
|
28
|
+
|
|
29
|
+
interface State {
|
|
30
|
+
count: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const definition = {
|
|
34
|
+
state: { count: 0 },
|
|
35
|
+
actions: {
|
|
36
|
+
increment: (state) => ({ count: state.count + 1 }),
|
|
37
|
+
decrement: (state) => ({ count: state.count - 1 }),
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const useStore = createNodeStore(definition);
|
|
42
|
+
|
|
43
|
+
export default useStore;
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
// component.tsx
|
|
48
|
+
import useStore from "./store";
|
|
49
|
+
|
|
50
|
+
function Counter() {
|
|
51
|
+
const { select, actions } = useStore();
|
|
52
|
+
|
|
53
|
+
const count = select((state) => state.count);
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div>
|
|
57
|
+
<p>Count: {count}</p>
|
|
58
|
+
<button onClick={() => actions.increment()}>Increment</button>
|
|
59
|
+
<button onClick={() => actions.decrement()}>Decrement</button>
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default Counter;
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Advanced Example with Middleware and Persistence
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// store.ts
|
|
71
|
+
import { createNodeStore } from "@asaidimu/react-store";
|
|
72
|
+
|
|
73
|
+
interface State {
|
|
74
|
+
count: number;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const definition = {
|
|
78
|
+
state: { count: 0 },
|
|
79
|
+
actions: {
|
|
80
|
+
increment: (state) => ({ count: state.count + 1 }),
|
|
81
|
+
decrement: (state) => ({ count: state.count - 1 }),
|
|
82
|
+
},
|
|
83
|
+
middleware: [(state) => {
|
|
84
|
+
console.log("State updated:", state);
|
|
85
|
+
return state;
|
|
86
|
+
}],
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const useStore = createNodeStore(definition);
|
|
90
|
+
|
|
91
|
+
export default useStore;
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Using Selectors
|
|
95
|
+
|
|
96
|
+
Selectors can be used to memoize frequently accessed state properties:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
const { select } = useStore();
|
|
100
|
+
|
|
101
|
+
// Memoized selector for total items
|
|
102
|
+
const totalItems = select((state) => state.items.length);
|
|
103
|
+
|
|
104
|
+
// Usage in component
|
|
105
|
+
function Component() {
|
|
106
|
+
const itemsCount = totalItems();
|
|
107
|
+
|
|
108
|
+
return <div>Items: {itemsCount}</div>;
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Configuration Options
|
|
113
|
+
|
|
114
|
+
The `createNodeStore` function accepts a configuration object with the following options:
|
|
115
|
+
|
|
116
|
+
| Option | Description | Default Value |
|
|
117
|
+
|----------------------|-----------------------------------------------------------------------------|--------------|
|
|
118
|
+
| `enableMetrics` | Enable performance metrics collection | `false` |
|
|
119
|
+
| `middleware` | Array of middleware functions to execute during state updates | `[]` |
|
|
120
|
+
| `error` | Error handling and recovery configuration | `null` |
|
|
121
|
+
|
|
122
|
+
### Error Handling Configuration
|
|
123
|
+
|
|
124
|
+
The `error` option accepts an object with the following properties:
|
|
125
|
+
|
|
126
|
+
| Property | Description |
|
|
127
|
+
|--------------------|-----------------------------------------------------------------------------|
|
|
128
|
+
| `onError` | Function to call when an error occurs |
|
|
129
|
+
| `recovery` | Recovery configuration |
|
|
130
|
+
|
|
131
|
+
The `recovery` object can have the following properties:
|
|
132
|
+
|
|
133
|
+
| Property | Description |
|
|
134
|
+
|--------------------|-----------------------------------------------------------------------------|
|
|
135
|
+
| `strategy` | Recovery strategy (`"rollback"`, `"retry"`, `"reset"`) |
|
|
136
|
+
| `maxAttempts` | Maximum number of retry attempts |
|
|
137
|
+
| `backoffMs` | Backoff time in milliseconds between retry attempts |
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
## Middleware
|
|
141
|
+
|
|
142
|
+
Middleware functions can be used to execute logic before or after state updates. They receive the current state and must return a new state object.
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
const middleware = (state) => {
|
|
146
|
+
console.log("State before update:", state);
|
|
147
|
+
return state;
|
|
148
|
+
};
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Performance Metrics
|
|
152
|
+
|
|
153
|
+
The library provides built-in performance metrics collection. You can access these metrics using the `getMetrics` function.
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
const metrics = useStore.getMetrics();
|
|
157
|
+
console.log("Updates:", metrics.updates);
|
|
158
|
+
console.log("Last update duration:", metrics.lastUpdateDuration);
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Debugging
|
|
162
|
+
|
|
163
|
+
The library provides several debugging features:
|
|
164
|
+
|
|
165
|
+
- **Metrics Collection**: Track state updates and subscription triggers.
|
|
166
|
+
- **Error Logging**: Detailed error logging with stack traces.
|
|
167
|
+
|
|
168
|
+
## License
|
|
169
|
+
|
|
170
|
+
This library is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
package/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var p=Object.defineProperty;var S=Object.getOwnPropertyDescriptor;var b=Object.getOwnPropertyNames;var T=Object.prototype.hasOwnProperty;var v=(a,e)=>{for(var t in e)p(a,t,{get:e[t],enumerable:!0})},w=(a,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of b(e))!T.call(a,r)&&r!==t&&p(a,r,{get:()=>e[r],enumerable:!(i=S(e,r))||i.enumerable});return a};var M=a=>w(p({},"__esModule",{value:!0}),a);var $={};v($,{StateManager:()=>l,createStore:()=>x});module.exports=M($);var h=require("react");var y=require("@asaidimu/node"),k="root",g=class{cache=new Map;maxSize;maxAge;constructor(e=100,t=5e3){this.maxSize=e,this.maxAge=t}get(e){let t=this.cache.get(e);if(t){if(Date.now()-t.timestamp>this.maxAge){this.cache.delete(e);return}return t.value}}set(e,t){if(this.cache.size>=this.maxSize){let i=this.cache.keys().next().value;this.cache.delete(i)}this.cache.set(e,{value:t,timestamp:Date.now()})}},l=class{manager;subscribers=new Set;middleware;options;definition;middlewareCache;constructor(e,t={}){this.definition=e,this.options=t,this.middleware=t.middleware||[],this.middlewareCache=new g,this.manager=this.initializeManager()}initializeManager(){let e=(0,y.createNodeManager)();e.add("root",null,{key:"root"});let t=(i="root",r=this.definition.state)=>{for(let[s,n]of Object.entries(r))typeof n=="object"&&!Array.isArray(n)?(e.add(`${i}/${s}`,null,{key:s}),t(`${i}/${s}`,n)):e.add(`${i}/${s}`,n,{key:s})};if(t(),this.definition.sync){let i=this.getState.bind(this),r=this.definition.sync;e.subscribe("node",()=>{r(i())})}return e}getState(){return this.manager.store().map("key").root}getTree(){return this.manager.store().serialize()}setState(e,t){let i=this.getState();if(!i)return;let r=(s,n)=>{if(typeof n=="object"&&!Array.isArray(n))for(let[o,u]of Object.entries(n)){let d=`${s}/${o}`;r(d,u)}else this.manager.update(s,n)};try{for(let[s,n]of Object.entries(e)){let o=t?`${t}/${s}`:`root/${s}`;if(i[s]===n)continue;if(!this.validateState({[s]:n},o))throw new Error(`Validation failed for ${o}`);let u=this.applyMiddleware({[s]:n},o);r(o,u[s])}this.notifySubscribers()}catch(s){this.handleError(s,{action:"setState",state:e})}}validateState(e,t){if(!this.options.validation)return!0;let i=t.split("/").pop(),r=this.options.validation[i];return r?r.every(s=>{try{return s.validate(e[i])}catch(n){return console.error(`Validation error at ${t}:`,n),!1}}):!0}applyMiddleware(e,t){let i=`${t}-${JSON.stringify(e)}`,r=this.middlewareCache.get(i);if(r)return r;let s=this.middleware.reduce((n,o)=>o(n),e);return this.middlewareCache.set(i,s),s}handleError(e,t){this.options.error?.onError&&this.options.error.onError(e,t),this.options.error?.recovery}subscribe(e){return this.subscribers.add(e),()=>{this.subscribers.delete(e)}}notifySubscribers(){this.subscribers.forEach(e=>e())}select(e,t){let i=[],r=this.manager;function s(n){let o={get:(u,d)=>{let c=`${n}/${d}`;return r.exists(c)&&i.push(c),s(c)}};return new Proxy({},o)}return e(s(k)),r.subscribe({event:"node",path:i},t)}};var f=class{metrics={updates:0,subscriptionTriggers:0,middlewareExecutions:0,batchSize:0,lastUpdateDuration:0};enableMetrics;constructor(e=!1){this.enableMetrics=e}track(e,t){if(!this.enableMetrics)return t();let i=performance.now();try{let r=t();return this.recordMetric(e,performance.now()-i),r}catch(r){throw this.recordError(e,r),r}}recordMetric(e,t){switch(e){case"update":this.metrics.updates++,this.metrics.lastUpdateDuration=t;break;case"subscription":this.metrics.subscriptionTriggers++;break;case"middleware":this.metrics.middlewareExecutions++;break}}recordError(e,t){console.error(`Error in ${e}:`,t)}getMetrics(){return{...this.metrics}}reset(){this.metrics={updates:0,subscriptionTriggers:0,middlewareExecutions:0,batchSize:0,lastUpdateDuration:0}}},m=class{selectorCache=new WeakMap;create(e){return t=>{let i=this.selectorCache.get(e);i||(i=new WeakMap,this.selectorCache.set(e,i));let r=i.get(t);if(r)return r.result;let s=e(t);return i.set(t,{result:s,deps:[]}),s}}};function x(a,e={}){let t=new l(a,e),i=new m,r=new f(e.enableMetrics),s=Object.entries(a.actions).reduce((n,[o,u])=>(n[o]=async(...d)=>{try{return await r.track("update",async()=>{let c=await u(t.getState(),...d);return t.setState(c),c})}catch(c){throw console.error(`Error in action ${o}:`,c),c}},n),{});return function(){return{select:(0,h.useCallback)(u=>{let d=i.create(u);return(0,h.useSyncExternalStore)(c=>r.track("subscription",()=>t.select(d,c)),()=>d(t.getState()),()=>d(t.getState()))},[]),actions:s,getState:()=>t.getState(),getTree:()=>t.getTree(),getMetrics:()=>r.getMetrics(),resetMetrics:()=>r.reset()}}}0&&(module.exports={StateManager,createStore});
|
package/index.d.cts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import * as _asaidimu_node from '@asaidimu/node';
|
|
2
|
+
|
|
3
|
+
type Middleware<T> = (state: T) => T;
|
|
4
|
+
type Actions<T> = {
|
|
5
|
+
[K: string]: (state: T, ...args: any[]) => Partial<T> | Promise<Partial<T>>;
|
|
6
|
+
};
|
|
7
|
+
interface StoreDefinition<T, R extends Actions<T>> {
|
|
8
|
+
state: T;
|
|
9
|
+
actions: R;
|
|
10
|
+
sync?: (args: T) => void;
|
|
11
|
+
}
|
|
12
|
+
type Schema<T> = {
|
|
13
|
+
[K in keyof T]?: {
|
|
14
|
+
validate: (value: T[K]) => boolean;
|
|
15
|
+
message: string;
|
|
16
|
+
}[];
|
|
17
|
+
};
|
|
18
|
+
interface Migration<T> {
|
|
19
|
+
version: number;
|
|
20
|
+
migrate: (state: T) => T;
|
|
21
|
+
}
|
|
22
|
+
interface ErrorConfig {
|
|
23
|
+
onError?: (error: Error, errorInfo: {
|
|
24
|
+
action?: string;
|
|
25
|
+
state?: any;
|
|
26
|
+
[key: string]: unknown;
|
|
27
|
+
}) => void;
|
|
28
|
+
recovery?: {
|
|
29
|
+
strategy: "rollback" | "retry" | "reset";
|
|
30
|
+
maxAttempts: number;
|
|
31
|
+
backoffMs: number;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
interface StoreOptions<T> {
|
|
35
|
+
name?: string;
|
|
36
|
+
trace?: boolean;
|
|
37
|
+
maxTraceHistory?: number;
|
|
38
|
+
traceRetentionPeriod?: number;
|
|
39
|
+
middleware?: Middleware<T>[];
|
|
40
|
+
migrations?: Migration<T>[];
|
|
41
|
+
validation?: Schema<T>;
|
|
42
|
+
error?: ErrorConfig;
|
|
43
|
+
}
|
|
44
|
+
interface StateManagerMetrics {
|
|
45
|
+
updates: number;
|
|
46
|
+
subscriptionTriggers: number;
|
|
47
|
+
middlewareExecutions: number;
|
|
48
|
+
batchSize: number;
|
|
49
|
+
lastUpdateDuration: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Production-ready store creator without unnecessary memoization
|
|
54
|
+
*/
|
|
55
|
+
declare function createStore<T extends Record<string, unknown>, R extends Actions<T>>(definition: StoreDefinition<T, R>, options?: StoreOptions<T> & {
|
|
56
|
+
enableMetrics?: boolean;
|
|
57
|
+
devTools?: boolean;
|
|
58
|
+
}): () => {
|
|
59
|
+
select: <S>(selector: (state: T) => S) => S;
|
|
60
|
+
actions: { [K in keyof R]: (...args: Parameters<R[K]> extends [T, ...infer P] ? P : never) => void; };
|
|
61
|
+
getState: () => T;
|
|
62
|
+
getTree: () => _asaidimu_node.Node<any, {
|
|
63
|
+
key: string;
|
|
64
|
+
}>[];
|
|
65
|
+
getMetrics: () => StateManagerMetrics;
|
|
66
|
+
resetMetrics: () => void;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
declare class StateManager<T extends Record<string, unknown>, R extends Actions<T>> {
|
|
70
|
+
private manager;
|
|
71
|
+
private subscribers;
|
|
72
|
+
private middleware;
|
|
73
|
+
private options;
|
|
74
|
+
private definition;
|
|
75
|
+
private middlewareCache;
|
|
76
|
+
constructor(definition: StoreDefinition<T, R>, options?: StoreOptions<T>);
|
|
77
|
+
private initializeManager;
|
|
78
|
+
getState(): T;
|
|
79
|
+
getTree(): _asaidimu_node.Node<any, {
|
|
80
|
+
key: string;
|
|
81
|
+
}>[];
|
|
82
|
+
setState(nextState: Partial<T>, path?: string): void;
|
|
83
|
+
private validateState;
|
|
84
|
+
private applyMiddleware;
|
|
85
|
+
private handleError;
|
|
86
|
+
subscribe(callback: () => void): () => void;
|
|
87
|
+
private notifySubscribers;
|
|
88
|
+
select<S>(selector: (state: T) => S, callback: (state: any) => void): () => void;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export { StateManager, createStore };
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import * as _asaidimu_node from '@asaidimu/node';
|
|
2
|
+
|
|
3
|
+
type Middleware<T> = (state: T) => T;
|
|
4
|
+
type Actions<T> = {
|
|
5
|
+
[K: string]: (state: T, ...args: any[]) => Partial<T> | Promise<Partial<T>>;
|
|
6
|
+
};
|
|
7
|
+
interface StoreDefinition<T, R extends Actions<T>> {
|
|
8
|
+
state: T;
|
|
9
|
+
actions: R;
|
|
10
|
+
sync?: (args: T) => void;
|
|
11
|
+
}
|
|
12
|
+
type Schema<T> = {
|
|
13
|
+
[K in keyof T]?: {
|
|
14
|
+
validate: (value: T[K]) => boolean;
|
|
15
|
+
message: string;
|
|
16
|
+
}[];
|
|
17
|
+
};
|
|
18
|
+
interface Migration<T> {
|
|
19
|
+
version: number;
|
|
20
|
+
migrate: (state: T) => T;
|
|
21
|
+
}
|
|
22
|
+
interface ErrorConfig {
|
|
23
|
+
onError?: (error: Error, errorInfo: {
|
|
24
|
+
action?: string;
|
|
25
|
+
state?: any;
|
|
26
|
+
[key: string]: unknown;
|
|
27
|
+
}) => void;
|
|
28
|
+
recovery?: {
|
|
29
|
+
strategy: "rollback" | "retry" | "reset";
|
|
30
|
+
maxAttempts: number;
|
|
31
|
+
backoffMs: number;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
interface StoreOptions<T> {
|
|
35
|
+
name?: string;
|
|
36
|
+
trace?: boolean;
|
|
37
|
+
maxTraceHistory?: number;
|
|
38
|
+
traceRetentionPeriod?: number;
|
|
39
|
+
middleware?: Middleware<T>[];
|
|
40
|
+
migrations?: Migration<T>[];
|
|
41
|
+
validation?: Schema<T>;
|
|
42
|
+
error?: ErrorConfig;
|
|
43
|
+
}
|
|
44
|
+
interface StateManagerMetrics {
|
|
45
|
+
updates: number;
|
|
46
|
+
subscriptionTriggers: number;
|
|
47
|
+
middlewareExecutions: number;
|
|
48
|
+
batchSize: number;
|
|
49
|
+
lastUpdateDuration: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Production-ready store creator without unnecessary memoization
|
|
54
|
+
*/
|
|
55
|
+
declare function createStore<T extends Record<string, unknown>, R extends Actions<T>>(definition: StoreDefinition<T, R>, options?: StoreOptions<T> & {
|
|
56
|
+
enableMetrics?: boolean;
|
|
57
|
+
devTools?: boolean;
|
|
58
|
+
}): () => {
|
|
59
|
+
select: <S>(selector: (state: T) => S) => S;
|
|
60
|
+
actions: { [K in keyof R]: (...args: Parameters<R[K]> extends [T, ...infer P] ? P : never) => void; };
|
|
61
|
+
getState: () => T;
|
|
62
|
+
getTree: () => _asaidimu_node.Node<any, {
|
|
63
|
+
key: string;
|
|
64
|
+
}>[];
|
|
65
|
+
getMetrics: () => StateManagerMetrics;
|
|
66
|
+
resetMetrics: () => void;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
declare class StateManager<T extends Record<string, unknown>, R extends Actions<T>> {
|
|
70
|
+
private manager;
|
|
71
|
+
private subscribers;
|
|
72
|
+
private middleware;
|
|
73
|
+
private options;
|
|
74
|
+
private definition;
|
|
75
|
+
private middlewareCache;
|
|
76
|
+
constructor(definition: StoreDefinition<T, R>, options?: StoreOptions<T>);
|
|
77
|
+
private initializeManager;
|
|
78
|
+
getState(): T;
|
|
79
|
+
getTree(): _asaidimu_node.Node<any, {
|
|
80
|
+
key: string;
|
|
81
|
+
}>[];
|
|
82
|
+
setState(nextState: Partial<T>, path?: string): void;
|
|
83
|
+
private validateState;
|
|
84
|
+
private applyMiddleware;
|
|
85
|
+
private handleError;
|
|
86
|
+
subscribe(callback: () => void): () => void;
|
|
87
|
+
private notifySubscribers;
|
|
88
|
+
select<S>(selector: (state: T) => S, callback: (state: any) => void): () => void;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export { StateManager, createStore };
|
package/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{useCallback as y,useSyncExternalStore as S}from"react";import{createNodeManager as f}from"@asaidimu/node";var m="root",h=class{cache=new Map;maxSize;maxAge;constructor(e=100,t=5e3){this.maxSize=e,this.maxAge=t}get(e){let t=this.cache.get(e);if(t){if(Date.now()-t.timestamp>this.maxAge){this.cache.delete(e);return}return t.value}}set(e,t){if(this.cache.size>=this.maxSize){let i=this.cache.keys().next().value;this.cache.delete(i)}this.cache.set(e,{value:t,timestamp:Date.now()})}},l=class{manager;subscribers=new Set;middleware;options;definition;middlewareCache;constructor(e,t={}){this.definition=e,this.options=t,this.middleware=t.middleware||[],this.middlewareCache=new h,this.manager=this.initializeManager()}initializeManager(){let e=f();e.add("root",null,{key:"root"});let t=(i="root",s=this.definition.state)=>{for(let[r,n]of Object.entries(s))typeof n=="object"&&!Array.isArray(n)?(e.add(`${i}/${r}`,null,{key:r}),t(`${i}/${r}`,n)):e.add(`${i}/${r}`,n,{key:r})};if(t(),this.definition.sync){let i=this.getState.bind(this),s=this.definition.sync;e.subscribe("node",()=>{s(i())})}return e}getState(){return this.manager.store().map("key").root}getTree(){return this.manager.store().serialize()}setState(e,t){let i=this.getState();if(!i)return;let s=(r,n)=>{if(typeof n=="object"&&!Array.isArray(n))for(let[a,d]of Object.entries(n)){let c=`${r}/${a}`;s(c,d)}else this.manager.update(r,n)};try{for(let[r,n]of Object.entries(e)){let a=t?`${t}/${r}`:`root/${r}`;if(i[r]===n)continue;if(!this.validateState({[r]:n},a))throw new Error(`Validation failed for ${a}`);let d=this.applyMiddleware({[r]:n},a);s(a,d[r])}this.notifySubscribers()}catch(r){this.handleError(r,{action:"setState",state:e})}}validateState(e,t){if(!this.options.validation)return!0;let i=t.split("/").pop(),s=this.options.validation[i];return s?s.every(r=>{try{return r.validate(e[i])}catch(n){return console.error(`Validation error at ${t}:`,n),!1}}):!0}applyMiddleware(e,t){let i=`${t}-${JSON.stringify(e)}`,s=this.middlewareCache.get(i);if(s)return s;let r=this.middleware.reduce((n,a)=>a(n),e);return this.middlewareCache.set(i,r),r}handleError(e,t){this.options.error?.onError&&this.options.error.onError(e,t),this.options.error?.recovery}subscribe(e){return this.subscribers.add(e),()=>{this.subscribers.delete(e)}}notifySubscribers(){this.subscribers.forEach(e=>e())}select(e,t){let i=[],s=this.manager;function r(n){let a={get:(d,c)=>{let o=`${n}/${c}`;return s.exists(o)&&i.push(o),r(o)}};return new Proxy({},a)}return e(r(m)),s.subscribe({event:"node",path:i},t)}};var p=class{metrics={updates:0,subscriptionTriggers:0,middlewareExecutions:0,batchSize:0,lastUpdateDuration:0};enableMetrics;constructor(e=!1){this.enableMetrics=e}track(e,t){if(!this.enableMetrics)return t();let i=performance.now();try{let s=t();return this.recordMetric(e,performance.now()-i),s}catch(s){throw this.recordError(e,s),s}}recordMetric(e,t){switch(e){case"update":this.metrics.updates++,this.metrics.lastUpdateDuration=t;break;case"subscription":this.metrics.subscriptionTriggers++;break;case"middleware":this.metrics.middlewareExecutions++;break}}recordError(e,t){console.error(`Error in ${e}:`,t)}getMetrics(){return{...this.metrics}}reset(){this.metrics={updates:0,subscriptionTriggers:0,middlewareExecutions:0,batchSize:0,lastUpdateDuration:0}}},g=class{selectorCache=new WeakMap;create(e){return t=>{let i=this.selectorCache.get(e);i||(i=new WeakMap,this.selectorCache.set(e,i));let s=i.get(t);if(s)return s.result;let r=e(t);return i.set(t,{result:r,deps:[]}),r}}};function M(u,e={}){let t=new l(u,e),i=new g,s=new p(e.enableMetrics),r=Object.entries(u.actions).reduce((n,[a,d])=>(n[a]=async(...c)=>{try{return await s.track("update",async()=>{let o=await d(t.getState(),...c);return t.setState(o),o})}catch(o){throw console.error(`Error in action ${a}:`,o),o}},n),{});return function(){return{select:y(d=>{let c=i.create(d);return S(o=>s.track("subscription",()=>t.select(c,o)),()=>c(t.getState()),()=>c(t.getState()))},[]),actions:r,getState:()=>t.getState(),getTree:()=>t.getTree(),getMetrics:()=>s.getMetrics(),resetMetrics:()=>s.reset()}}}export{l as StateManager,M as createStore};
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@asaidimu/react-store",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Efficient react state manager.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"./*"
|
|
9
|
+
],
|
|
10
|
+
"keywords": [
|
|
11
|
+
"typescript"
|
|
12
|
+
],
|
|
13
|
+
"author": "Saidimu <47994458+asaidimu@users.noreply.github.com>",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/asaidimu/node-react.git"
|
|
18
|
+
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/asaidimu/node-react/issues"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/asaidimu/node-react#readme",
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"registry": "https://registry.npmjs.org/",
|
|
25
|
+
"tag": "latest",
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@asaidimu/events": "^1.0.0",
|
|
30
|
+
"@asaidimu/node": "^1.0.4",
|
|
31
|
+
"react": "^19.0.0"
|
|
32
|
+
}
|
|
33
|
+
}
|