@asaidimu/react-store 2.0.0 → 2.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 +106 -62
- package/index.cjs +1 -1
- package/index.d.cts +16 -2
- package/index.d.ts +16 -2
- package/index.js +1 -1
- package/package.json +3 -7
package/README.md
CHANGED
|
@@ -21,9 +21,9 @@ This package is currently in **beta**. The API is subject to rapid changes and s
|
|
|
21
21
|
* [Persistence](#persistence)
|
|
22
22
|
* [Middleware](#middleware)
|
|
23
23
|
* [Observability](#observability)
|
|
24
|
-
* [Remote Observability](#remote-observability)
|
|
25
24
|
* [Transaction Support](#transaction-support)
|
|
26
25
|
* [Event System](#event-system)
|
|
26
|
+
* [Watching Action Loading States](#watching-action-loading-states)
|
|
27
27
|
* [Advanced Hook Properties](#advanced-hook-properties)
|
|
28
28
|
* [Project Architecture](#project-architecture)
|
|
29
29
|
* [Development & Contributing](#development--contributing)
|
|
@@ -51,8 +51,8 @@ Designed with modern React in mind, it leverages `useSyncExternalStore` for opti
|
|
|
51
51
|
* 📦 **Transaction Support**: Group multiple state updates into a single atomic operation, with automatic rollback if any part of the transaction fails.
|
|
52
52
|
* 💾 **Built-in Persistence**: Seamlessly integrate with web storage mechanisms like `IndexedDB` and `WebStorage` (localStorage/sessionStorage), including cross-tab synchronization.
|
|
53
53
|
* 🔍 **Deep Observability**: Gain profound insights into your application's state with built-in metrics, detailed event logging, state history, and time-travel debugging capabilities.
|
|
54
|
-
*
|
|
55
|
-
*
|
|
54
|
+
* ⚡ **Performance Optimized**: Features intelligent selector caching (now using `WeakMap` for enhanced efficiency) and debounced actions with configurable immediate execution to prevent rapid successive calls and ensure smooth application performance.
|
|
55
|
+
* 🚀 **Action Loading States**: Track the real-time loading status of individual actions, providing immediate feedback on asynchronous operations.
|
|
56
56
|
* ⚛️ **React 18+ Ready**: Fully compatible with the latest React versions, leveraging modern APIs for enhanced performance and development ergonomics.
|
|
57
57
|
* 🗑️ **Explicit Deletions**: Use `Symbol.for("delete")` to explicitly remove properties from nested state objects.
|
|
58
58
|
|
|
@@ -94,7 +94,6 @@ const myStore = createStore({
|
|
|
94
94
|
},
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
-
console.log(myStore().select(s => s.value)); // Should output 'hello'
|
|
98
97
|
```
|
|
99
98
|
|
|
100
99
|
If no errors are thrown, the package is correctly installed.
|
|
@@ -135,7 +134,7 @@ const myStore = createStore({
|
|
|
135
134
|
}),
|
|
136
135
|
},
|
|
137
136
|
}, {
|
|
138
|
-
debounceTime:
|
|
137
|
+
debounceTime: 0, // Actions execute immediately by default
|
|
139
138
|
enableConsoleLogging: true, // Enable console logs for store events
|
|
140
139
|
logEvents: { updates: true, transactions: true, middleware: true },
|
|
141
140
|
performanceThresholds: { updateTime: 20, middlewareTime: 5 }, // Warn for slow operations
|
|
@@ -431,7 +430,7 @@ const useObservedStore = createStore(
|
|
|
431
430
|
},
|
|
432
431
|
maxEvents: 500, // Max number of events to keep in history
|
|
433
432
|
maxStateHistory: 50, // Max number of state snapshots for time travel
|
|
434
|
-
debounceTime:
|
|
433
|
+
debounceTime: 0, // Actions execute immediately by default
|
|
435
434
|
},
|
|
436
435
|
);
|
|
437
436
|
|
|
@@ -737,6 +736,63 @@ function EventMonitor() {
|
|
|
737
736
|
}
|
|
738
737
|
```
|
|
739
738
|
|
|
739
|
+
### Watching Action Loading States
|
|
740
|
+
|
|
741
|
+
The `watch` function returned by the `useStore` hook allows you to subscribe to the loading status of individual actions. This is particularly useful for displaying loading indicators for asynchronous operations.
|
|
742
|
+
|
|
743
|
+
```tsx
|
|
744
|
+
import React from 'react';
|
|
745
|
+
import { createStore } from '@asaidimu/react-store';
|
|
746
|
+
|
|
747
|
+
interface DataState {
|
|
748
|
+
items: string[];
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
const useDataStore = createStore({
|
|
752
|
+
state: { items: [] },
|
|
753
|
+
actions: {
|
|
754
|
+
fetchItems: async (state) => {
|
|
755
|
+
// Simulate an API call
|
|
756
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
757
|
+
return { items: ['Item A', 'Item B', 'Item C'] };
|
|
758
|
+
},
|
|
759
|
+
addItem: async (state, item: string) => {
|
|
760
|
+
// Simulate a quick add operation
|
|
761
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
762
|
+
return { items: [...state.items, item] };
|
|
763
|
+
},
|
|
764
|
+
},
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
function DataLoader() {
|
|
768
|
+
const { actions, select, watch } = useDataStore();
|
|
769
|
+
const items = select(s => s.items);
|
|
770
|
+
|
|
771
|
+
// Watch the loading state of specific actions
|
|
772
|
+
const isFetchingItems = watch('fetchItems');
|
|
773
|
+
const isAddingItem = watch('addItem');
|
|
774
|
+
|
|
775
|
+
return (
|
|
776
|
+
<div>
|
|
777
|
+
<h2>Data Loader</h2>
|
|
778
|
+
<button onClick={() => actions.fetchItems()} disabled={isFetchingItems}>
|
|
779
|
+
{isFetchingItems ? 'Fetching...' : 'Fetch Items'}
|
|
780
|
+
</button>
|
|
781
|
+
<button onClick={() => actions.addItem(`New Item ${items.length + 1}`)} disabled={isAddingItem}>
|
|
782
|
+
{isAddingItem ? 'Adding...' : 'Add Item'}
|
|
783
|
+
</button>
|
|
784
|
+
|
|
785
|
+
<h3>Items:</h3>
|
|
786
|
+
<ul>
|
|
787
|
+
{items.map((item, index) => (
|
|
788
|
+
<li key={index}>{item}</li>
|
|
789
|
+
))}
|
|
790
|
+
</ul>
|
|
791
|
+
</div>
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
```
|
|
795
|
+
|
|
740
796
|
### Advanced Hook Properties
|
|
741
797
|
|
|
742
798
|
The hook returned by `createStore` provides several properties for advanced usage and debugging, beyond the commonly used `select`, `actions`, and `isReady`:
|
|
@@ -751,6 +807,7 @@ function MyAdvancedComponent() {
|
|
|
751
807
|
observer, // StoreObservability instance (if `enableMetrics` was true)
|
|
752
808
|
actionTracker, // Instance of ActionTracker for monitoring action executions
|
|
753
809
|
state, // A hook `() => T` to get the entire reactive state object
|
|
810
|
+
watch, // Function to watch the loading status of actions
|
|
754
811
|
} = useMyStore(); // Assuming useMyStore is defined from createStore
|
|
755
812
|
|
|
756
813
|
// Example: Accessing the full state (use with caution for performance, `select` is preferred)
|
|
@@ -766,6 +823,10 @@ function MyAdvancedComponent() {
|
|
|
766
823
|
// Example: Accessing action history
|
|
767
824
|
console.log("Action executions:", actionTracker.getExecutions());
|
|
768
825
|
|
|
826
|
+
// Example: Watching a specific action's loading state
|
|
827
|
+
const isLoadingSomeAction = watch('someActionName');
|
|
828
|
+
console.log("Is 'someActionName' loading?", isLoadingSomeAction);
|
|
829
|
+
|
|
769
830
|
return (
|
|
770
831
|
<div>
|
|
771
832
|
{/* ... your component content ... */}
|
|
@@ -778,57 +839,53 @@ function MyAdvancedComponent() {
|
|
|
778
839
|
|
|
779
840
|
`@asaidimu/react-store` is structured to provide a modular yet integrated state management solution.
|
|
780
841
|
|
|
842
|
+
### Internal Structure
|
|
843
|
+
|
|
844
|
+
The core logic of this package resides directly within the `src/` directory:
|
|
845
|
+
|
|
781
846
|
```
|
|
782
847
|
.
|
|
783
848
|
├── src/
|
|
784
|
-
│ ├──
|
|
785
|
-
│
|
|
786
|
-
│ ├──
|
|
787
|
-
│
|
|
788
|
-
│
|
|
789
|
-
│
|
|
790
|
-
|
|
791
|
-
│ │ ├── diff.ts # Utility for deep diffing state objects
|
|
792
|
-
│ │ ├── merge.ts # Utility for immutable deep merging state objects
|
|
793
|
-
│ │ ├── observability.ts # Core observability logic for ReactiveDataStore
|
|
794
|
-
│ │ └── store.ts # Core ReactiveDataStore implementation (the state machine)
|
|
795
|
-
│ ├── store/
|
|
796
|
-
│ │ ├── compare.ts # Utilities for fast comparison (e.g., array hashing)
|
|
797
|
-
│ │ ├── execution.ts # Action tracking interface and class
|
|
798
|
-
│ │ ├── hash.ts # Utilities for hashing objects (e.g., for selectors)
|
|
799
|
-
│ │ ├── index.ts # Main `createStore` React hook
|
|
800
|
-
│ │ ├── paths.ts # Utility for building selector paths
|
|
801
|
-
│ │ └── selector.ts # Selector memoization manager
|
|
802
|
-
│ ├── types.ts # Core TypeScript types for the library
|
|
803
|
-
│ └── utils/
|
|
804
|
-
│ ├── destinations.ts # Concrete remote observability destinations (OTel, Prometheus, Grafana)
|
|
805
|
-
│ └── remote-observability.ts # Remote observability extension for StoreObservability
|
|
806
|
-
├── index.ts # Main entry point for the library
|
|
849
|
+
│ ├── execution.ts # Action tracking interface and class
|
|
850
|
+
│ ├── hash.ts # Utilities for hashing objects (e.g., for selectors)
|
|
851
|
+
│ ├── paths.ts # Utility for building selector paths
|
|
852
|
+
│ ├── selector.ts # Selector memoization manager
|
|
853
|
+
│ ├── store.ts # Main `createStore` React hook implementation
|
|
854
|
+
│ └── types.ts # Core TypeScript types for the library
|
|
855
|
+
├── index.ts # Main entry point for the library (re-exports `createStore` and other public APIs)
|
|
807
856
|
├── package.json
|
|
808
857
|
└── tsconfig.json
|
|
809
858
|
```
|
|
810
859
|
|
|
860
|
+
### Key External Dependencies
|
|
861
|
+
|
|
862
|
+
This library leverages the following `@asaidimu` packages for its core functionalities:
|
|
863
|
+
|
|
864
|
+
* **`@asaidimu/utils-store`**: Provides the foundational `ReactiveDataStore` for state management, including immutable updates, transactions, and core event emission. It also includes `StoreObservability` for deep insights into state changes.
|
|
865
|
+
* **`@asaidimu/utils-persistence`**: Offers persistence adapters like `WebStoragePersistence` and `IndexedDBPersistence` for saving and loading state.
|
|
866
|
+
|
|
811
867
|
### Core Components
|
|
812
868
|
|
|
813
|
-
* **`ReactiveDataStore` (
|
|
814
|
-
* **`StoreObservability` (
|
|
815
|
-
* **`createStore` Hook (`src/store
|
|
816
|
-
* **Persistence Adapters (
|
|
817
|
-
* **`RemoteObservability` (`src/utils/remote-observability.ts`)**: Extends `StoreObservability` to enable sending metrics, logs, and traces to external monitoring systems. It defines a pluggable `RemoteDestination` interface and provides out-of-the-box implementations.
|
|
869
|
+
* **`ReactiveDataStore` (from `@asaidimu/utils-store`)**: The foundational state management layer. This external library manages the immutable state, processes updates, handles middleware, transactions, and interacts with persistence adapters. It also emits detailed internal events for observability.
|
|
870
|
+
* **`StoreObservability` (from `@asaidimu/utils-store`)**: An extension built on top of the external `ReactiveDataStore`'s event system. It provides debugging features like event history, state snapshots for time-travel, performance metrics, and utilities to create logging/validation middleware.
|
|
871
|
+
* **`createStore` Hook (`src/store.ts`)**: The primary React-facing API. It instantiates the external `ReactiveDataStore` and `StoreObservability`, wraps actions with debouncing and tracking, and provides the `select` hook powered by `useSyncExternalStore` for efficient component updates, and the `watch` function for action loading states.
|
|
872
|
+
* **Persistence Adapters (from `@asaidimu/utils-persistence`)**: Implement the `DataStorePersistence` interface. `WebStoragePersistence` and `IndexedDBPersistence` provide concrete storage solutions with cross-tab synchronization.
|
|
818
873
|
|
|
819
874
|
### Data Flow
|
|
820
875
|
|
|
821
876
|
1. **Action Dispatch**: A React component calls an action (e.g., `actions.increment(1)`). Actions are debounced by default.
|
|
822
|
-
2. **Action
|
|
823
|
-
3. **
|
|
824
|
-
4. **
|
|
825
|
-
5. **
|
|
826
|
-
6. **
|
|
827
|
-
7. **
|
|
828
|
-
8. **
|
|
829
|
-
9. **
|
|
830
|
-
10. **
|
|
831
|
-
11. **
|
|
877
|
+
2. **Action Loading State Update**: The store immediately updates the loading state for the dispatched action to `true`.
|
|
878
|
+
3. **Action Execution Tracking**: The `ActionTracker` records the action's details (name, params, start time).
|
|
879
|
+
4. **State Update Request**: The action, after potential debouncing, initiates a `store.set()` call with a partial state update or a function returning a partial object.
|
|
880
|
+
5. **Transaction Context**: If within a `store.transaction()`, the state is snapshotted for potential rollback.
|
|
881
|
+
6. **Blocking Middleware**: Updates first pass through `blockingMiddleware`. If any middleware returns `false` or throws, the update is halted, and the state is not modified.
|
|
882
|
+
7. **Transform Middleware**: If not blocked, updates then pass through transforming `middleware`. These functions can modify the partial update.
|
|
883
|
+
8. **State Merging**: The final transformed update is immutably merged into the current state using `ReactiveDataStore`'s internal merge utility.
|
|
884
|
+
9. **Change Detection**: `ReactiveDataStore`'s internal diffing mechanism identifies which paths in the state have truly changed.
|
|
885
|
+
10. **Persistence**: If changes occurred, the new state is saved via the configured `DataStorePersistence` adapter (e.g., `localStorage`, `IndexedDB`). External changes from persistence are also subscribed to and applied.
|
|
886
|
+
11. **Listener Notification**: Only `React.useSyncExternalStore` subscribers whose selected paths have changed are notified, triggering re-renders of relevant components.
|
|
887
|
+
12. **Action Loading State Reset**: Once the action completes (success or error), the loading state for that action is reset to `false`.
|
|
888
|
+
13. **Observability Events**: Throughout this flow, the `ReactiveDataStore` emits fine-grained events (`update:start`, `middleware:complete`, `transaction:error`, etc.) that `StoreObservability` captures for debugging, metrics, and remote reporting.
|
|
832
889
|
|
|
833
890
|
### Extension Points
|
|
834
891
|
|
|
@@ -861,7 +918,7 @@ We welcome contributions! Please follow the guidelines below.
|
|
|
861
918
|
* `bun clean`: Removes the `dist` directory.
|
|
862
919
|
* `bun prebuild`: Cleans `dist` and runs a sync script (internal).
|
|
863
920
|
* `bun build`: Compiles the TypeScript source into `dist/` for CJS and ESM formats, generates type definitions, and minifies.
|
|
864
|
-
* `bun dev`: Starts
|
|
921
|
+
* `bun dev`: Starts the e-commerce dashboard demonstration.
|
|
865
922
|
* `bun postbuild`: Copies `README.md`, `LICENSE.md`, and `dist.package.json` into the `dist` folder.
|
|
866
923
|
|
|
867
924
|
### Testing
|
|
@@ -929,7 +986,7 @@ interface StoreOptions<T> {
|
|
|
929
986
|
middlewareTime?: number; // default: 20ms
|
|
930
987
|
};
|
|
931
988
|
persistence?: DataStorePersistence<T>; // Optional persistence adapter instance
|
|
932
|
-
debounceTime?: number; // Time in milliseconds to debounce actions (default:
|
|
989
|
+
debounceTime?: number; // Time in milliseconds to debounce actions (default: 0ms)
|
|
933
990
|
}
|
|
934
991
|
|
|
935
992
|
const useStore = createStore(definition, options);
|
|
@@ -943,13 +1000,14 @@ const useStore = createStore(definition, options);
|
|
|
943
1000
|
* `actionTracker`: An instance of `ActionTracker` for monitoring the execution history of your actions.
|
|
944
1001
|
* `state`: A hook `() => T` that returns the entire current state object. Use sparingly as it will cause re-renders on *any* state change.
|
|
945
1002
|
* `isReady`: A boolean indicating whether the store's persistence layer (if configured) has finished loading its initial state.
|
|
1003
|
+
* `watch`: A function to watch the loading status of actions.
|
|
946
1004
|
|
|
947
1005
|
#### `ReactiveDataStore` (accessed via `useStore().store`)
|
|
948
1006
|
|
|
949
1007
|
* `get(clone?: boolean): T`: Retrieves the current state. Pass `true` to get a deep clone (recommended for mutations outside of actions).
|
|
950
1008
|
* `set(update: StateUpdater<T>): Promise<void>`: Updates the state with a partial object or a function returning a partial object.
|
|
951
1009
|
* `subscribe(path: string | string[], listener: (state: T) => void): () => void`: Subscribes a listener to changes at a specific path or array of paths. Returns an unsubscribe function.
|
|
952
|
-
* `transaction<R>(operation: () => R | Promise<R>): Promise<R
|
|
1010
|
+
* `transaction<R>(operation: () => R | Promise<R>): Promise<R}`: Executes a function as an atomic transaction. Rolls back all changes if an error occurs.
|
|
953
1011
|
* `use(middleware: Middleware<T>, name?: string): string`: Adds a transforming middleware. Returns its ID.
|
|
954
1012
|
* `useBlockingMiddleware(middleware: BlockingMiddleware<T>, name?: string): string`: Adds a blocking middleware. Returns its ID.
|
|
955
1013
|
* `removeMiddleware(id: string): boolean`: Removes a middleware by its ID.
|
|
@@ -968,20 +1026,6 @@ const useStore = createStore(definition, options);
|
|
|
968
1026
|
* `clearHistory(): void`: Clears the event and state history.
|
|
969
1027
|
* `disconnect(): void`: Cleans up all listeners and resources.
|
|
970
1028
|
|
|
971
|
-
#### `RemoteObservability` (accessed via `useRemoteObservability` hook)
|
|
972
|
-
|
|
973
|
-
Extends `StoreObservability` with methods for sending metrics and traces externally.
|
|
974
|
-
|
|
975
|
-
* `addDestination(destination: RemoteDestination): boolean`: Adds a remote destination for metrics.
|
|
976
|
-
* `removeDestination(id: string): boolean`: Removes a remote destination by ID.
|
|
977
|
-
* `getDestinations(): Array<{ id: string; name: string }> `: Gets a list of configured destinations.
|
|
978
|
-
* `testAllConnections(): Promise<Record<string, boolean>>`: Tests connectivity to all destinations.
|
|
979
|
-
* `beginTrace(name: string): string`: Starts a new performance trace, returning its ID.
|
|
980
|
-
* `beginSpan(traceId: string, name: string, labels?: Record<string, string>): string`: Starts a new span within a trace, returning its ID.
|
|
981
|
-
* `endSpan(traceId: string, spanName: string): void`: Ends a specific span within a trace.
|
|
982
|
-
* `endTrace(traceId: string): void`: Ends a performance trace and sends it to remote destinations.
|
|
983
|
-
* `trackMetric(metric: RemoteMetricsPayload['metrics'][0]): void`: Manually add a metric to the batch for reporting.
|
|
984
|
-
|
|
985
1029
|
#### Persistence Adapters
|
|
986
1030
|
|
|
987
1031
|
All adapters implement `DataStorePersistence<T>`:
|
|
@@ -1006,7 +1050,7 @@ All adapters implement `DataStorePersistence<T>`:
|
|
|
1006
1050
|
|
|
1007
1051
|
### Comparison with Other State Management Solutions
|
|
1008
1052
|
|
|
1009
|
-
`@asaidimu/react-store` aims to
|
|
1053
|
+
`@asaidimu/react-store` aims to be a comprehensive, all-in-one solution for React state management. Here's a comparison to popular alternatives:
|
|
1010
1054
|
|
|
1011
1055
|
| **Feature** | **@asaidimu/react-store** | **Redux** | **Zustand** | **MobX** | **Recoil** |
|
|
1012
1056
|
| :--------------------- | :------------------------ | :----------------- | :----------------- | :----------------- | :----------------- |
|
package/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e=require("react"),t=require("@asaidimu/utils-store");var r=class{selectorCache=new WeakMap;create(e){return t=>{let r=this.selectorCache.get(e);
|
|
1
|
+
"use strict";var e=require("react"),t=require("@asaidimu/utils-store");var r=class{selectorCache=new WeakMap;create(e){return t=>{let r=this.selectorCache.get(e);if(r&&r.lastState===t)return r.lastResult;const n=e(t);return this.selectorCache.set(e,{lastState:t,lastResult:n}),n}}},n=class{executions=[];maxHistory=100;listeners=new Set;track(e){this.executions.unshift(e),this.executions.length>this.maxHistory&&this.executions.pop(),this.notify()}getExecutions(){return[...this.executions]}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}notify(){this.listeners.forEach((e=>e()))}};function s(e,t=new WeakMap){return void 0===e?"undefined":"function"==typeof e||"symbol"==typeof e?"":"string"==typeof e?`"${e}"`:"number"==typeof e||"boolean"==typeof e||null===e?String(e):Array.isArray(e)?`[${e.map((e=>s(e,t))).join(",")}]`:"object"==typeof e?t.has(e)?'"__circular__"':(t.set(e,!0),`{${Object.keys(e).sort().map((r=>`"${r}":${s(e[r],t)}`)).join(",")}}`):""}function o(e){let t=3421674724,r=2216829733;const n=s(e);for(let e=0;e<n.length;e++){r^=n.charCodeAt(e);const s=Math.imul(r,435),o=Math.imul(r,435)+Math.imul(t,435);r=s>>>0,t=o>>>0}return(t>>>0).toString(16)+(r>>>0).toString(16)}exports.createStore=function(s,{enableMetrics:i,debounceTime:a=0,...c}={}){const u=new t.ReactiveDataStore(s.state,c.persistence),l=new t.ReactiveDataStore(Object.fromEntries(Object.keys(s.actions).map((e=>[e,!1])))),h=i?new t.StoreObserver(u,c):void 0,d=new r,f=i?new n:void 0,y=i?new Map:void 0;s.middleware&&Object.entries(s.middleware).forEach((([e,t])=>u.use({name:e,action:t}))),s.blockingMiddleware&&Object.entries(s.blockingMiddleware).forEach((([e,t])=>u.use({block:!0,name:e,action:t})));const m=Object.entries(s.actions).reduce(((e,[t,r])=>(e[t]=function(e,t){if(0===t)return e;let r=null;return function(...n){return new Promise(((s,o)=>{r&&clearTimeout(r);const i=this;r=setTimeout((()=>{r=null,e.apply(i,n).then(s).catch(o)}),t)}))}}((async(...e)=>{const n=`${t}-${o(e)}`;if(y?.get(n))return;y?.set(n,!0);const s=f?crypto.randomUUID():void 0,i=f?performance.now():void 0,a=f?{id:s,name:t,params:e,startTime:i}:{};try{return l.set((e=>({...e,[t]:!0}))),await u.set((async t=>{const n=await r(t,...e);return f&&(a.result=n),n})),f&&(a.status="success"),u.get()}catch(e){throw f&&(a.status="error",a.error=e instanceof Error?e:new Error(String(e))),console.error(`Error in action ${t}:`,e),e}finally{l.set((e=>({...e,[t]:!1}))),f&&(a.endTime=performance.now(),a.duration=a.endTime-i,f.track(a)),y?.delete(n)}}),a),e)),{});function p(e,t){const r=function(e,t="."){const r=[],n={get:(e,t)=>{const s=[];return r.length>0&&s.push(...r[r.length-1]),s.push(t),r.push(s),new Proxy({},n)}};return e(new Proxy({},n)),r.map((e=>e.join(t)))}(e);return u.subscribe(r,t)}const b=()=>u.get(),w=e=>(u.isReady()&&e(),u.onStoreEvent("persistence:ready",e)),g=()=>u.isReady(),S=new Map;return function(){const t=e.useCallback((t=>{S.has(t)||S.set(t,d.create(t));const r=S.get(t);return e.useSyncExternalStore((e=>p(r,e)),(()=>r(u.get())),(()=>r(u.get())))}),[]),r=e.useCallback((()=>e.useSyncExternalStore((e=>u.subscribe("",e)),b,b)),[]),n=e.useSyncExternalStore(w,g,g);return{store:u,observer:h,select:t,actions:m,isReady:n,actionTracker:f,watch:t=>e.useSyncExternalStore((e=>l.subscribe(t,e)),(()=>l.get()[t]),(()=>l.get()[t])),get state(){return r}}}};
|
package/index.d.cts
CHANGED
|
@@ -6,6 +6,7 @@ type StoreOptions<T> = ObservabilityOptions & {
|
|
|
6
6
|
persistence?: SimplePersistence<T>;
|
|
7
7
|
};
|
|
8
8
|
type Action<T> = (state: T, ...args: any[]) => DeepPartial<T>;
|
|
9
|
+
type LoadingState<T extends string> = Record<T, boolean>;
|
|
9
10
|
type StoreSelector<T, S> = (state: T) => S;
|
|
10
11
|
type Actions<T> = {
|
|
11
12
|
[K: string]: (state: T, ...args: any[]) => DeepPartial<T> | Promise<DeepPartial<T>>;
|
|
@@ -13,10 +14,21 @@ type Actions<T> = {
|
|
|
13
14
|
interface StoreDefinition<T, R extends Actions<T>> {
|
|
14
15
|
state: T;
|
|
15
16
|
actions: R;
|
|
17
|
+
loading?: LoadingState<keyof R & string>;
|
|
16
18
|
sync?: (args: T) => void;
|
|
17
19
|
blockingMiddleware?: Record<string, BlockingMiddleware<T>>;
|
|
18
20
|
middleware?: Record<string, Middleware<T>>;
|
|
19
21
|
}
|
|
22
|
+
type StoreHook<T, R extends Actions<T>> = () => {
|
|
23
|
+
store: any;
|
|
24
|
+
observer: any;
|
|
25
|
+
select: <S>(selector: (state: T) => S) => S;
|
|
26
|
+
actions: R;
|
|
27
|
+
isReady: boolean;
|
|
28
|
+
actionTracker: any;
|
|
29
|
+
watch: (action: keyof R & string) => boolean;
|
|
30
|
+
state: () => T;
|
|
31
|
+
};
|
|
20
32
|
|
|
21
33
|
interface ActionExecution {
|
|
22
34
|
id: string;
|
|
@@ -60,9 +72,11 @@ declare function createStore<T extends Record<string, unknown>, R extends Action
|
|
|
60
72
|
readonly actions: { [K in keyof R]: (...args: Parameters<R[K]> extends [T, ...infer P] ? P : never) => Promise<T>; };
|
|
61
73
|
readonly isReady: boolean;
|
|
62
74
|
/** track actions */
|
|
63
|
-
readonly actionTracker: ActionTracker;
|
|
75
|
+
readonly actionTracker: ActionTracker | undefined;
|
|
76
|
+
/** watch loading state */
|
|
77
|
+
readonly watch: (action: keyof R & string) => LoadingState<keyof R & string>[keyof R & string];
|
|
64
78
|
/** Complete store state */
|
|
65
79
|
readonly state: () => T;
|
|
66
80
|
};
|
|
67
81
|
|
|
68
|
-
export { type Action, type Actions, type StoreDefinition, type StoreOptions, type StoreSelector, createStore };
|
|
82
|
+
export { type Action, type Actions, type LoadingState, type StoreDefinition, type StoreHook, type StoreOptions, type StoreSelector, createStore };
|
package/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ type StoreOptions<T> = ObservabilityOptions & {
|
|
|
6
6
|
persistence?: SimplePersistence<T>;
|
|
7
7
|
};
|
|
8
8
|
type Action<T> = (state: T, ...args: any[]) => DeepPartial<T>;
|
|
9
|
+
type LoadingState<T extends string> = Record<T, boolean>;
|
|
9
10
|
type StoreSelector<T, S> = (state: T) => S;
|
|
10
11
|
type Actions<T> = {
|
|
11
12
|
[K: string]: (state: T, ...args: any[]) => DeepPartial<T> | Promise<DeepPartial<T>>;
|
|
@@ -13,10 +14,21 @@ type Actions<T> = {
|
|
|
13
14
|
interface StoreDefinition<T, R extends Actions<T>> {
|
|
14
15
|
state: T;
|
|
15
16
|
actions: R;
|
|
17
|
+
loading?: LoadingState<keyof R & string>;
|
|
16
18
|
sync?: (args: T) => void;
|
|
17
19
|
blockingMiddleware?: Record<string, BlockingMiddleware<T>>;
|
|
18
20
|
middleware?: Record<string, Middleware<T>>;
|
|
19
21
|
}
|
|
22
|
+
type StoreHook<T, R extends Actions<T>> = () => {
|
|
23
|
+
store: any;
|
|
24
|
+
observer: any;
|
|
25
|
+
select: <S>(selector: (state: T) => S) => S;
|
|
26
|
+
actions: R;
|
|
27
|
+
isReady: boolean;
|
|
28
|
+
actionTracker: any;
|
|
29
|
+
watch: (action: keyof R & string) => boolean;
|
|
30
|
+
state: () => T;
|
|
31
|
+
};
|
|
20
32
|
|
|
21
33
|
interface ActionExecution {
|
|
22
34
|
id: string;
|
|
@@ -60,9 +72,11 @@ declare function createStore<T extends Record<string, unknown>, R extends Action
|
|
|
60
72
|
readonly actions: { [K in keyof R]: (...args: Parameters<R[K]> extends [T, ...infer P] ? P : never) => Promise<T>; };
|
|
61
73
|
readonly isReady: boolean;
|
|
62
74
|
/** track actions */
|
|
63
|
-
readonly actionTracker: ActionTracker;
|
|
75
|
+
readonly actionTracker: ActionTracker | undefined;
|
|
76
|
+
/** watch loading state */
|
|
77
|
+
readonly watch: (action: keyof R & string) => LoadingState<keyof R & string>[keyof R & string];
|
|
64
78
|
/** Complete store state */
|
|
65
79
|
readonly state: () => T;
|
|
66
80
|
};
|
|
67
81
|
|
|
68
|
-
export { type Action, type Actions, type StoreDefinition, type StoreOptions, type StoreSelector, createStore };
|
|
82
|
+
export { type Action, type Actions, type LoadingState, type StoreDefinition, type StoreHook, type StoreOptions, type StoreSelector, createStore };
|
package/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useCallback as e,useSyncExternalStore as t}from"react";import{ReactiveDataStore as
|
|
1
|
+
import{useCallback as e,useSyncExternalStore as t}from"react";import{ReactiveDataStore as n,StoreObserver as r}from"@asaidimu/utils-store";var s=class{selectorCache=new WeakMap;create(e){return t=>{let n=this.selectorCache.get(e);if(n&&n.lastState===t)return n.lastResult;const r=e(t);return this.selectorCache.set(e,{lastState:t,lastResult:r}),r}}},o=class{executions=[];maxHistory=100;listeners=new Set;track(e){this.executions.unshift(e),this.executions.length>this.maxHistory&&this.executions.pop(),this.notify()}getExecutions(){return[...this.executions]}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}notify(){this.listeners.forEach((e=>e()))}};function i(e,t=new WeakMap){return void 0===e?"undefined":"function"==typeof e||"symbol"==typeof e?"":"string"==typeof e?`"${e}"`:"number"==typeof e||"boolean"==typeof e||null===e?String(e):Array.isArray(e)?`[${e.map((e=>i(e,t))).join(",")}]`:"object"==typeof e?t.has(e)?'"__circular__"':(t.set(e,!0),`{${Object.keys(e).sort().map((n=>`"${n}":${i(e[n],t)}`)).join(",")}}`):""}function c(e){let t=3421674724,n=2216829733;const r=i(e);for(let e=0;e<r.length;e++){n^=r.charCodeAt(e);const s=Math.imul(n,435),o=Math.imul(n,435)+Math.imul(t,435);n=s>>>0,t=o>>>0}return(t>>>0).toString(16)+(n>>>0).toString(16)}function a(i,{enableMetrics:a,debounceTime:u=0,...l}={}){const h=new n(i.state,l.persistence),d=new n(Object.fromEntries(Object.keys(i.actions).map((e=>[e,!1])))),f=a?new r(h,l):void 0,m=new s,p=a?new o:void 0,y=a?new Map:void 0;i.middleware&&Object.entries(i.middleware).forEach((([e,t])=>h.use({name:e,action:t}))),i.blockingMiddleware&&Object.entries(i.blockingMiddleware).forEach((([e,t])=>h.use({block:!0,name:e,action:t})));const b=Object.entries(i.actions).reduce(((e,[t,n])=>(e[t]=function(e,t){if(0===t)return e;let n=null;return function(...r){return new Promise(((s,o)=>{n&&clearTimeout(n);const i=this;n=setTimeout((()=>{n=null,e.apply(i,r).then(s).catch(o)}),t)}))}}((async(...e)=>{const r=`${t}-${c(e)}`;if(y?.get(r))return;y?.set(r,!0);const s=p?crypto.randomUUID():void 0,o=p?performance.now():void 0,i=p?{id:s,name:t,params:e,startTime:o}:{};try{return d.set((e=>({...e,[t]:!0}))),await h.set((async t=>{const r=await n(t,...e);return p&&(i.result=r),r})),p&&(i.status="success"),h.get()}catch(e){throw p&&(i.status="error",i.error=e instanceof Error?e:new Error(String(e))),console.error(`Error in action ${t}:`,e),e}finally{d.set((e=>({...e,[t]:!1}))),p&&(i.endTime=performance.now(),i.duration=i.endTime-o,p.track(i)),y?.delete(r)}}),u),e)),{});function w(e,t){const n=function(e,t="."){const n=[],r={get:(e,t)=>{const s=[];return n.length>0&&s.push(...n[n.length-1]),s.push(t),n.push(s),new Proxy({},r)}};return e(new Proxy({},r)),n.map((e=>e.join(t)))}(e);return h.subscribe(n,t)}const g=()=>h.get(),x=e=>(h.isReady()&&e(),h.onStoreEvent("persistence:ready",e)),j=()=>h.isReady(),k=new Map;return function(){const n=e((e=>{k.has(e)||k.set(e,m.create(e));const n=k.get(e);return t((e=>w(n,e)),(()=>n(h.get())),(()=>n(h.get())))}),[]),r=e((()=>t((e=>h.subscribe("",e)),g,g)),[]),s=t(x,j,j);return{store:h,observer:f,select:n,actions:b,isReady:s,actionTracker:p,watch:e=>t((t=>d.subscribe(e,t)),(()=>d.get()[e]),(()=>d.get()[e])),get state(){return r}}}}export{a as createStore};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@asaidimu/react-store",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Efficient react state manager.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -26,12 +26,8 @@
|
|
|
26
26
|
"access": "public"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@asaidimu/
|
|
30
|
-
"@asaidimu/
|
|
31
|
-
"@asaidimu/node": "^1.0.6",
|
|
32
|
-
"@asaidimu/query": "^1.1.2",
|
|
33
|
-
"@asaidimu/utils-persistence": "^2.0.1",
|
|
34
|
-
"@asaidimu/utils-store": "^2.1.0",
|
|
29
|
+
"@asaidimu/utils-persistence": "^2.1.0",
|
|
30
|
+
"@asaidimu/utils-store": "^2.2.0",
|
|
35
31
|
"hash-wasm": "^4.12.0",
|
|
36
32
|
"react": "^19.0.0",
|
|
37
33
|
"uuid": "^11.1.0"
|