@asaidimu/react-store 1.5.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 +20 -715
- package/index.d.ts +20 -715
- package/index.js +1 -1
- package/package.json +3 -5
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/events"),r=require("uuid"),s=require("@asaidimu/indexed"),a=require("@asaidimu/query");function n(e,t){const r=new Set,s=Symbol.for("delete"),a=e=>r.has(e)||r.add(e);function n(e){if(!e)return;const t=e.split(".");a(e);for(let e=t.length-1;e>0;e--)a(t.slice(0,e).join("."))}const i=[{path:"",orig:e||{},part:t||{}}];for(;i.length>0;){const{path:e,orig:t,part:r}=i.pop();if(null!=r)if(Array.isArray(r))Array.isArray(t)&&JSON.stringify(t)===JSON.stringify(r)||n(e);else if("object"==typeof r)for(const a of Object.keys(r)){const o=e?`${e}.${a}`:a,c=r[a];if(c===s||null==t){n(o);continue}const d=t[a];"object"==typeof c&&null!==c?i.push({path:o,orig:d,part:c}):d!==c&&n(o)}}return Array.from(r)}var i=class{store;eventHistory=[];maxEvents;enableConsoleLogging;logEvents;performanceThresholds;unsubscribers=[];stateHistory=[];maxStateHistory=20;activeTransactionCount=0;activeBatches=new Set;constructor(e,t={}){this.store=e,this.maxEvents=t.maxEvents??500,this.maxStateHistory=t.maxStateHistory??20,this.enableConsoleLogging=t.enableConsoleLogging??!1,this.logEvents={updates:t.logEvents?.updates??!0,middleware:t.logEvents?.middleware??!0,transactions:t.logEvents?.transactions??!0},this.performanceThresholds={updateTime:t.performanceThresholds?.updateTime??50,middlewareTime:t.performanceThresholds?.middlewareTime??20},this.recordStateSnapshot(),this.setupEventListeners()}setupEventListeners(){const e=["update:start","update:complete","middleware:start","middleware:complete","middleware:error","middleware:blocked","transaction:start","transaction:complete","transaction:error"];this.unsubscribers.push(this.store.subscribe("",(()=>{this.recordStateSnapshot()})));for(const t of e){const e=t.startsWith("update")&&this.logEvents.updates||t.startsWith("middleware")&&this.logEvents.middleware||t.startsWith("transaction")&&this.logEvents.transactions;this.unsubscribers.push(this.store.onStoreEvent(t,(r=>{"transaction:start"===t?this.activeTransactionCount++:"transaction:complete"!==t&&"transaction:error"!==t||(this.activeTransactionCount=Math.max(0,this.activeTransactionCount-1)),r.batchId&&(t.endsWith("start")?this.activeBatches.add(r.batchId):(t.endsWith("complete")||t.endsWith("error"))&&this.activeBatches.delete(r.batchId)),this.recordEvent(t,r),this.enableConsoleLogging&&e&&this.logEventToConsole(t,r),this.checkPerformance(t,r)})))}}recordStateSnapshot(){const e=structuredClone(this.store.get());this.stateHistory.length>0&&0===n(e,this.stateHistory[0]).length||(this.stateHistory.unshift(e),this.stateHistory.length>this.maxStateHistory&&this.stateHistory.pop())}recordEvent(e,t){const r={type:e,timestamp:Date.now(),data:structuredClone(t)};this.eventHistory.unshift(r),this.eventHistory.length>this.maxEvents&&this.eventHistory.pop()}logEventToConsole(e,t){const r=new Date(t.timestamp||Date.now()).toISOString().split("T")[1].replace("Z","");if("update:start"===e)console.group(`%cā” Store Update Started [${r}]`,"color: #4a6da7");else if("update:complete"===e){if(t.blocked)console.warn(`%cā Update Blocked [${r}]`,"color: #bf8c0a",t.error);else{const e=t.changedPaths||[];e.length>0&&console.log(`%cā
Update Complete [${r}] - ${e.length} paths changed in ${t.duration?.toFixed(2)}ms`,"color: #2a9d8f",e)}console.groupEnd()}else"middleware:start"===e?console.debug(`%cā Middleware "${t.name}" started [${r}] (${t.type})`,"color: #8c8c8c"):"middleware:complete"===e?console.debug(`%cā¶ Middleware "${t.name}" completed [${r}] in ${t.duration?.toFixed(2)}ms`,"color: #7c9c7c"):"middleware:error"===e?console.error(`%cā Middleware "${t.name}" error [${r}]:`,"color: #e63946",t.error):"middleware:blocked"===e?console.warn(`%cš Middleware "${t.name}" blocked update [${r}]`,"color: #e76f51"):"transaction:start"===e?console.group(`%cš¦ Transaction Started [${r}]`,"color: #6d597a"):"transaction:complete"===e?(console.log(`%cš¦ Transaction Complete [${r}]`,"color: #355070"),console.groupEnd()):"transaction:error"===e&&(console.error(`%cš¦ Transaction Error [${r}]:`,"color: #e56b6f",t.error),console.groupEnd())}checkPerformance(e,t){this.enableConsoleLogging&&("update:complete"===e&&!t.blocked&&t.duration>this.performanceThresholds.updateTime&&console.warn(`%cā ļø Slow update detected [${t.duration.toFixed(2)}ms]`,"color: #ff9f1c",{changedPaths:t.changedPaths,threshold:this.performanceThresholds.updateTime}),"middleware:complete"===e&&t.duration>this.performanceThresholds.middlewareTime&&console.warn(`%cā ļø Slow middleware "${t.name}" [${t.duration.toFixed(2)}ms]`,"color: #ff9f1c",{threshold:this.performanceThresholds.middlewareTime}))}getEventHistory(){return structuredClone(this.eventHistory)}getStateHistory(){return structuredClone(this.stateHistory)}getMiddlewareExecutions(){return this.store.getMiddlewareExecutions()}getPerformanceMetrics(){return this.store.getPerformanceMetrics()}getTransactionStatus(){return{activeTransactions:this.activeTransactionCount,activeBatches:Array.from(this.activeBatches)}}createLoggingMiddleware(e={}){const{logLevel:t="debug",logUpdates:r=!0}=e;return(e,s)=>{if(r){(console[t]||console.log)("State Update:",s)}return s}}createValidationMiddleware(e){return(t,r)=>{const s=e(t,r);return"boolean"==typeof s?s:(!s.valid&&s.reason&&console.warn("Validation failed:",s.reason),s.valid)}}clearHistory(){if(this.eventHistory=[],this.stateHistory.length>0){const e=this.stateHistory[0];this.stateHistory=[e]}}getRecentChanges(e=5){const t=[],r=Math.min(e,this.stateHistory.length-1);for(let e=0;e<r;e++){const r=this.stateHistory[e],s=this.stateHistory[e+1],a=n(s,r),i={},o={};for(const e of a){const t=e.split("."),a=(e,t)=>t.reduce(((e,t)=>e&&void 0!==e[t]?e[t]:void 0),e),n=(e,t,r)=>{const s=t.length-1,a=t[s];t.slice(0,s).reduce(((e,t)=>(e[t]=e[t]??{},e[t])),e)[a]=r},c=a(s,t),d=a(r,t);n(i,t,c),n(o,t,d)}let c=Date.now();for(const e of this.eventHistory)if("update:complete"===e.type&&e.data.changedPaths?.length>0){c=e.timestamp;break}0!==a.length&&t.push({timestamp:c,changedPaths:a,from:i,to:o})}return t}createTimeTravel(){let e=0,t=[];return{canUndo:()=>e<this.stateHistory.length-1,canRedo:()=>t.length>0,undo:async()=>{if(e<this.stateHistory.length-1){const r=e+1,s=this.stateHistory[r];t.unshift(this.stateHistory[e]),e=r,await this.store.set(s)}},redo:async()=>{if(t.length>0){const r=t.shift();e=Math.max(0,e-1),await this.store.set(r)}},getHistoryLength:()=>this.stateHistory.length,clear:()=>{t=[],e=0}}}disconnect(){this.unsubscribers.forEach((e=>e())),this.unsubscribers=[],this.clearHistory()}},o=e=>Array.isArray(e)?[...e]:{...e};function c(e,t){const r=Symbol.for("delete");if("object"!=typeof e||null===e)return"object"==typeof t&&null!==t?n(t):t===r?{}:t;if("object"!=typeof t||null===t)return e;const s=o(e),a=[{target:s,source:t,path:[]}];for(;a.length>0;){const{target:e,source:t,path:s}=a.pop();for(const i of Object.keys(t)){const c=t[i],d=[...s,i];c!==r?Array.isArray(c)?e[i]=c.map((e=>"object"==typeof e&&null!==e?n(e):e===r?void 0:e)):"object"==typeof c&&null!==c?(e[i]=o(i in e&&"object"==typeof e[i]&&null!==e[i]?e[i]:{}),a.push({target:e[i],source:c,path:d})):e[i]=c:delete e[i]}}return s;function n(e){if(null==e)return e;if(Array.isArray(e))return e.filter((e=>e!==r)).map((e=>"object"==typeof e&&null!==e?n(e):e===r?void 0:e));if("object"==typeof e){const t={};for(const[s,a]of Object.entries(e))t[s]="object"==typeof a&&null!==a?n(a):a===r?void 0:a;return t}return e===r?void 0:e}}var d=class{metrics;middleware;blockingMiddleware;middlewareExecutions=[];maxExecutionHistory=100;pendingUpdates;isUpdating;updateTimes;cache=null;updateBus=t.createEventBus();eventBus=t.createEventBus();executionState;persistence;instanceID=r.v4();persistenceReady=!1;constructor(e,t){this.middleware=[],this.blockingMiddleware=[],this.pendingUpdates=[],this.isUpdating=!1,this.updateTimes=[],this.metrics={updateCount:0,listenerExecutions:0,averageUpdateTime:0,largestUpdateSize:0,mostActiveListenerPaths:[]},this.executionState={executing:!1,changes:null,pendingChanges:[],middlewares:[],runningMiddleware:null,transactionActive:!1},this.cache=e,t?this.setPersistence(t):this.setPersistenceReady()}setPersistenceReady(){this.persistenceReady=!0,this.eventBus.emit({name:"persistence:ready",payload:{timestamp:Date.now()}})}async setPersistence(e){this.persistence=e;try{const e=await this.persistence.get();e&&await this.set(e)}catch(e){console.error("Failed to initialize persistence:",e)}finally{this.setPersistenceReady()}this.persistence.subscribe(this.instanceID,(async e=>{this.isUpdating=!0;try{const t=n(this.get(),e);t.length>0&&(await this.set(e),this.eventBus.emit({name:"update:complete",payload:{changedPaths:t,source:"external",timestamp:Date.now()}}))}finally{this.isUpdating=!1}}))}isReady(){return this.persistenceReady}getExecutionState(){return this.executionState}get(e){return e?structuredClone(this.cache):this.cache}async set(e){if(this.isUpdating)return this.pendingUpdates.push(e),void(this.executionState.pendingChanges=[...this.pendingUpdates]);this.isUpdating=!0,this.executionState.executing=!0;const t=performance.now();this.eventBus.emit({name:"update:start",payload:{timestamp:t}});let r=e;if("function"==typeof e){const t=e(this.get(!0));r=t instanceof Promise?await t:t}try{const e=await this.applyBlockingMiddleware(r);if(e.blocked)return void this.eventBus.emit({name:"update:complete",payload:{blocked:!0,error:e.error,timestamp:Date.now()}})}catch(e){throw this.eventBus.emit({name:"update:complete",payload:{blocked:!0,error:e,timestamp:Date.now()}}),e}const s=c(this.get(!0),r),a=await this.applyMiddleware(s,r);try{const e=n(this.get(!0),a);if(this.metrics.updateCount++,this.metrics.largestUpdateSize=Math.max(this.metrics.largestUpdateSize,e.length),e.length>0){this.cache=c(this.get(!0),a);try{await(this.persistence?.set(this.instanceID,this.get()))||this.eventBus.emit({name:"update:complete",payload:{persistence:!1,timestamp:Date.now()}})}catch(e){this.eventBus.emit({name:"update:complete",payload:{persistence:!1,error:e,timestamp:Date.now()}})}this.notifyListeners(e)}const r=performance.now();this.updateTimes.push(r-t),this.updateTimes.length>100&&this.updateTimes.shift(),this.metrics.averageUpdateTime=this.updateTimes.reduce(((e,t)=>e+t),0)/this.updateTimes.length,this.eventBus.emit({name:"update:complete",payload:{changedPaths:e,duration:r-t,timestamp:Date.now()}})}finally{if(this.isUpdating=!1,this.executionState.executing=!1,this.executionState.changes=null,this.executionState.runningMiddleware=null,this.pendingUpdates.length>0){const e=this.pendingUpdates.shift();if(this.executionState.pendingChanges=[...this.pendingUpdates],e)return this.set(e)}}}async applyBlockingMiddleware(e){for(const{fn:t,name:r,id:s}of this.blockingMiddleware){const a={id:s,name:r,startTime:performance.now()};this.executionState.runningMiddleware={id:s,name:r,startTime:performance.now()},this.eventBus.emit({name:"middleware:start",payload:{id:s,name:r,type:"blocking",timestamp:Date.now()}});try{const n=await Promise.resolve(t(this.get(),e));if(a.endTime=performance.now(),a.duration=void 0!==a.startTime?a.endTime-a.startTime:0,!1===n)return a.blocked=!0,this.trackMiddlewareExecution(a),this.eventBus.emit({name:"middleware:blocked",payload:{id:s,name:r,duration:a.duration,timestamp:Date.now()}}),{blocked:!0};this.eventBus.emit({name:"middleware:complete",payload:{id:s,name:r,type:"blocking",duration:a.duration,timestamp:Date.now()}}),this.trackMiddlewareExecution({...a,blocked:!1})}catch(e){return a.endTime=performance.now(),a.duration=void 0!==a.startTime?a.endTime-a.startTime:0,a.error=e instanceof Error?e:new Error(String(e)),a.blocked=!0,this.trackMiddlewareExecution(a),this.eventBus.emit({name:"middleware:error",payload:{id:s,name:r,error:a.error,duration:a.duration,timestamp:Date.now()}}),{blocked:!0,error:a.error}}finally{this.executionState.runningMiddleware=null}}return{blocked:!1}}async applyMiddleware(e,t){let r=e,s=t;for(const{fn:e,name:a,id:n}of this.middleware){const i={id:n,name:a,startTime:performance.now()};this.executionState.runningMiddleware={id:n,name:a,startTime:performance.now()},this.eventBus.emit({name:"middleware:start",payload:{id:n,name:a,type:"transform",timestamp:Date.now()}});try{const o=await Promise.resolve(e(r,t));i.endTime=performance.now(),i.duration=void 0!==i.startTime?i.endTime-i.startTime:0,i.blocked=!1,o&&"object"==typeof o&&(r=c(r,o),s=c(s,o)),this.trackMiddlewareExecution(i),this.eventBus.emit({name:"middleware:complete",payload:{id:n,name:a,type:"transform",duration:i.duration,timestamp:Date.now()}})}catch(e){i.endTime=performance.now(),i.duration=void 0!==i.startTime?i.endTime-i.startTime:0,i.error=e instanceof Error?e:new Error(String(e)),i.blocked=!1,this.trackMiddlewareExecution(i),this.eventBus.emit({name:"middleware:error",payload:{id:n,name:a,error:i.error,duration:i.duration,timestamp:Date.now()}}),console.error(`Middleware ${a} error:`,e)}finally{this.executionState.runningMiddleware=null}}return s}subscribe(e,t){const r=Array.isArray(e)?e:[e];return this.updateBus.subscribe("update",(s=>{(r.includes(s)||""===e)&&(t(this.get()),this.metrics.listenerExecutions++)}))}id(){return this.instanceID}async transaction(e){const t=this.get(!0);this.executionState.transactionActive=!0,this.eventBus.emit({name:"transaction:start",payload:{timestamp:Date.now()}});try{const t=await Promise.resolve(e());return this.eventBus.emit({name:"transaction:complete",payload:{timestamp:Date.now()}}),this.executionState.transactionActive=!1,t}catch(e){throw this.cache=t,this.eventBus.emit({name:"transaction:error",payload:{error:e instanceof Error?e:new Error(String(e)),timestamp:Date.now()}}),this.executionState.transactionActive=!1,e}}use(e,t="unnamed-middleware"){const r=crypto.randomUUID?crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).substring(2,15)}`;return this.middleware.push({fn:e,name:t,id:r}),this.executionState.middlewares=[...this.middleware.map((e=>e.name)),...this.blockingMiddleware.map((e=>e.name))],r}useBlockingMiddleware(e,t="unnamed-blocking-middleware"){const r=crypto.randomUUID?crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).substring(2,15)}`;return this.blockingMiddleware.push({fn:e,name:t,id:r}),this.executionState.middlewares=[...this.middleware.map((e=>e.name)),...this.blockingMiddleware.map((e=>e.name))],r}removeMiddleware(e){const t=this.middleware.length+this.blockingMiddleware.length;return this.middleware=this.middleware.filter((t=>t.id!==e)),this.blockingMiddleware=this.blockingMiddleware.filter((t=>t.id!==e)),this.executionState.middlewares=[...this.middleware.map((e=>e.name)),...this.blockingMiddleware.map((e=>e.name))],this.middleware.length+this.blockingMiddleware.length<t}getPerformanceMetrics(){return structuredClone(this.metrics)}getMiddlewareExecutions(){return structuredClone(this.middlewareExecutions)}onStoreEvent(e,t){return this.eventBus.subscribe(e,t)}notifyListeners(e){e.forEach((e=>this.updateBus.emit({name:"update",payload:e})))}trackMiddlewareExecution(e){this.middlewareExecutions.unshift(e),this.middlewareExecutions.length>this.maxExecutionHistory&&this.middlewareExecutions.pop()}};var l=class{selectorCache=new WeakMap;create(e){return t=>{let r=this.selectorCache.get(e);r||(r=new WeakMap,this.selectorCache.set(e,r));const s=r.get(t);if(s)return s.result;const a=e(t);return r.set(t,{result:a,deps:[]}),a}}},u=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 h(e){return{id:"opentelemetry",name:"OpenTelemetry Collector",config:e,testConnection:async()=>{try{return(await fetch(`${e.endpoint}/v1/metrics`,{method:"OPTIONS",headers:{...e.apiKey?{"api-key":e.apiKey}:{}}})).ok}catch(e){return!1}},send:async t=>{const r={resourceMetrics:[{resource:{attributes:{"service.name":t.source,...e.resource}},scopeMetrics:[{metrics:t.metrics.map((e=>"counter"===e.type?{name:e.name,unit:e.unit||"1",sum:{dataPoints:[{timeUnixNano:BigInt(1e6*t.timestamp),asInt:Number(e.value),attributes:Object.entries(e.labels||{}).map((([e,t])=>({key:e,value:{stringValue:t}})))}]}}:"histogram"===e.type?{name:e.name,unit:e.unit||"ms",histogram:{dataPoints:[{timeUnixNano:BigInt(1e6*t.timestamp),count:1,sum:Number(e.value),attributes:Object.entries(e.labels||{}).map((([e,t])=>({key:e,value:{stringValue:t}})))}]}}:{name:e.name,unit:e.unit||"1",gauge:{dataPoints:[{timeUnixNano:BigInt(1e6*t.timestamp),asDouble:"number"==typeof e.value?e.value:1,attributes:Object.entries(e.labels||{}).map((([e,t])=>({key:e,value:{stringValue:t}})))}]}}))}]}]};await fetch(`${e.endpoint}/v1/metrics`,{method:"POST",headers:{"Content-Type":"application/json",...e.apiKey?{"api-key":e.apiKey}:{}},body:JSON.stringify(r)})}}}function m(e){return{id:"prometheus",name:"Prometheus Pushgateway",config:e,testConnection:async()=>{try{const t=e.username&&e.password?`Basic ${btoa(`${e.username}:${e.password}`)}`:void 0;return(await fetch(e.pushgatewayUrl,{method:"HEAD",headers:{...t?{Authorization:t}:{}}})).ok}catch(e){return!1}},send:async t=>{let r="";for(const e of t.metrics){if("log"===e.type||"trace"===e.type)continue;const s=Object.entries({...e.labels,source:t.source,instance:t.metrics.find((e=>e.labels?.instanceId))?.labels?.instanceId||"unknown"}).map((([e,t])=>`${e}="${t.replace(/"/g,'\\"')}"`)).join(","),a=e.name.replace(/\./g,"_");"counter"===e.type?r+=`# TYPE ${a} counter\n`:r+=`# TYPE ${a} gauge\n`,r+=`${a}{${s}} ${e.value}\n`}const s=e.username&&e.password?`Basic ${btoa(`${e.username}:${e.password}`)}`:void 0;await fetch(`${e.pushgatewayUrl}/metrics/job/${e.jobName}`,{method:"POST",headers:{"Content-Type":"text/plain",...s?{Authorization:s}:{}},body:r})}}}function p(e){return{id:"grafana-cloud",name:"Grafana Cloud",config:e,testConnection:async()=>{try{return(await fetch(`${e.url}/api/v1/query?query=up`,{method:"GET",headers:{Authorization:`Bearer ${e.apiKey}`}})).ok}catch(e){return!1}},send:async t=>{const r={streams:[{stream:{service:t.source,job:"store-metrics"},values:t.metrics.map((e=>{const r=1e6*t.timestamp;let s="";if("object"==typeof e.value)s=JSON.stringify(e.value);else if(s=`level=info metric=${e.name} value=${e.value} type=${e.type}`,e.labels)for(const[t,r]of Object.entries(e.labels))s+=` ${t}=${r}`;return[String(r),s]}))}]};await fetch(`${e.url}/loki/api/v1/push`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e.apiKey}`},body:JSON.stringify(r)})}}}function g(e){return{id:"http-endpoint",name:"HTTP Metrics Endpoint",config:e,testConnection:async()=>{try{return(await fetch(e.url,{method:"OPTIONS",headers:e.headers})).ok}catch(e){return!1}},send:async t=>{const r=e.transformPayload?e.transformPayload(t):t;await fetch(e.url,{method:e.method||"POST",headers:{"Content-Type":"application/json",...e.headers},body:JSON.stringify(r)})}}}var y=class extends i{destinations=new Map;metricsBatch=[];reportingInterval;batchSize;serviceName;environment;instanceId;immediateReporting;collectCategories;compressPayloads;reportingTimer=null;traceIdCounter=0;activeTraces=new Map;constructor(e,t){super(e,t),this.serviceName=t.serviceName,this.environment=t.environment,this.instanceId=t.instanceId||this.generateInstanceId(),this.reportingInterval=t.reportingInterval||3e4,this.batchSize=t.batchSize||100,this.immediateReporting=t.immediateReporting||!1,this.collectCategories=t.collectCategories||{performance:!0,errors:!0,stateChanges:!0,middleware:!0},this.compressPayloads=t.compressPayloads||!1,this.immediateReporting||this.startReportingCycle(),this.setupRemoteEventListeners()}generateInstanceId(){return`${Date.now()}-${Math.random().toString(36).substring(2,9)}`}setupRemoteEventListeners(){this.store.onStoreEvent("update:complete",(e=>{this.collectCategories?.stateChanges&&(this.trackMetric({name:"store.update.duration",type:"histogram",value:e.duration||0,unit:"ms",labels:{pathCount:String(e.changedPaths?.length||0)}}),this.trackMetric({name:"store.update.paths_changed",type:"counter",value:e.changedPaths?.length||0,labels:{blocked:String(!!e.blocked)}}))})),this.store.onStoreEvent("middleware:error",(e=>{this.collectCategories?.errors&&this.trackMetric({name:"store.middleware.error",type:"log",value:{middleware:e.name,error:e.error?.message||"Unknown error",stack:e.error?.stack},labels:{middlewareName:e.name}})})),this.store.onStoreEvent("transaction:start",(e=>{if(!this.collectCategories?.performance)return;const t=this.beginTrace("transaction");e.traceId=t,this.beginSpan(t,"transaction.execution",{transactionId:e.id||String(Date.now())})})),this.store.onStoreEvent("transaction:complete",(e=>{this.collectCategories?.performance&&e.traceId&&(this.endSpan(e.traceId,"transaction.execution"),this.endTrace(e.traceId))}))}beginTrace(e){const t=`trace-${++this.traceIdCounter}-${Date.now()}`;return this.activeTraces.set(t,{startTime:performance.now(),name:e,spans:{}}),t}beginSpan(e,t,r={}){const s=this.activeTraces.get(e);if(!s)return"";const a=`span-${Date.now()}-${Math.random().toString(36).substring(2,9)}`;return s.spans[a]={startTime:performance.now(),name:t,labels:r},a}endSpan(e,t){const r=this.activeTraces.get(e);if(!r)return;const s=Object.entries(r.spans).filter((([e,r])=>r.name===t&&!r.endTime)).map((([e])=>e));if(s.length>0){const e=s[s.length-1];r.spans[e].endTime=performance.now()}}endTrace(e){const t=this.activeTraces.get(e);if(!t)return;const r=performance.now(),s=r-t.startTime;this.trackMetric({name:"store.trace.duration",type:"histogram",value:s,unit:"ms",labels:{traceName:t.name,traceId:e}}),Object.entries(t.spans).forEach((([t,s])=>{s.endTime||(s.endTime=r);const a=s.endTime-s.startTime;this.trackMetric({name:"store.trace.span.duration",type:"trace",value:a,unit:"ms",labels:{...s.labels,spanName:s.name},traceId:e,parentId:t})})),this.activeTraces.delete(e)}addDestination(e){return this.destinations.has(e.id)?(console.warn(`Destination with ID ${e.id} already exists`),!1):(this.destinations.set(e.id,e),!0)}removeDestination(e){return this.destinations.delete(e)}getDestinations(){return Array.from(this.destinations.values()).map((e=>({id:e.id,name:e.name})))}async testAllConnections(){const e={};for(const[t,r]of this.destinations.entries())if(r.testConnection)try{e[t]=await r.testConnection()}catch(r){e[t]=!1}else e[t]=!0;return e}trackMetric(e){e.labels={...e.labels,environment:this.environment,service:this.serviceName,instanceId:this.instanceId},this.metricsBatch.push(e),(this.immediateReporting||this.metricsBatch.length>=this.batchSize)&&this.flushMetrics()}async flushMetrics(){if(0===this.metricsBatch.length)return;const e={timestamp:Date.now(),source:this.serviceName,metrics:[...this.metricsBatch]};this.metricsBatch=[];const t=Array.from(this.destinations.values()).map((async t=>{try{let r=e;if(this.compressPayloads&&"undefined"!=typeof window&&window.CompressionStream){const t=JSON.stringify(e),s=(new TextEncoder).encode(t),a=new CompressionStream("gzip"),n=new Blob([s]).stream().pipeThrough(a);await new Response(n).blob();r._compressed=!0,r._originalSize=t.length}await t.send(r)}catch(r){if(console.error(`Failed to send metrics to destination ${t.name}:`,r),e.metrics.some((e=>"log"===e.type&&e.name.includes("error")))){const t=e.metrics.filter((e=>"log"===e.type&&e.name.includes("error")));this.metricsBatch.push(...t)}}}));await Promise.all(t)}startReportingCycle(){this.reportingTimer&&clearInterval(this.reportingTimer),this.reportingTimer=setInterval((()=>{this.flushMetrics()}),this.reportingInterval)}setReportingInterval(e){this.reportingInterval=e,this.startReportingCycle()}reportCurrentMetrics(){const e=this.store.getPerformanceMetrics();this.trackMetric({name:"store.performance.update_count",type:"counter",value:e.updateCount,labels:{}}),this.trackMetric({name:"store.performance.listener_executions",type:"counter",value:e.listenerExecutions,labels:{}}),this.trackMetric({name:"store.performance.average_update_time",type:"gauge",value:e.averageUpdateTime,unit:"ms",labels:{}}),this.trackMetric({name:"store.performance.largest_update_size",type:"gauge",value:e.largestUpdateSize,unit:"paths",labels:{}})}disconnect(){this.reportingTimer&&(clearInterval(this.reportingTimer),this.reportingTimer=null),this.flushMetrics().catch((e=>console.error("Error flushing metrics during disconnect:",e))),super.disconnect()}};var f=class{storageKey;eventBus;storage;constructor(e,t=!1){this.storageKey=e,this.storage=t?sessionStorage:localStorage,this.eventBus=this.initializeEventBus(),t||this.setupStorageEventListener()}initializeEventBus(){const e={async:!0,batchSize:5,batchDelay:16,errorHandler:e=>console.error(`Event bus error for ${this.storageKey}:`,e),crossTab:!0,channelName:`storage_${this.storageKey}`};return t.createEventBus(e)}setupStorageEventListener(){window.addEventListener("storage",(e=>{if(e.key===this.storageKey&&e.newValue)try{const t=JSON.parse(e.newValue);this.eventBus.emit({name:"storage:updated",payload:{storageKey:this.storageKey,instanceId:"external",state:t}})}catch(e){console.error("Failed to parse storage event data:",e)}}))}set(e,t){try{const r=JSON.stringify(t);return this.storage.setItem(this.storageKey,r),this.eventBus.emit({name:"storage:updated",payload:{storageKey:this.storageKey,instanceId:e,state:t}}),!0}catch(e){return console.error(`Failed to persist state to web storage for ${this.storageKey}:`,e),!1}}get(){try{const e=this.storage.getItem(this.storageKey);return e?JSON.parse(e):null}catch(e){return console.error(`Failed to retrieve state from web storage for ${this.storageKey}:`,e),null}}subscribe(e,t){return this.eventBus.subscribe("storage:updated",(({storageKey:r,instanceId:s,state:a})=>{r===this.storageKey&&s!==e&&t(a)}))}clear(){try{return this.storage.removeItem(this.storageKey),!0}catch(e){return console.error(`Failed to clear persisted state for ${this.storageKey}:`,e),!1}}},w=f,b=class e{static dbInstance=null;static eventBusMap=new Map;static dbName="ReactiveDataStore";static modelName="stores";static getDatabase(){return e.dbInstance||(e.dbInstance=s.DatabaseConnection({name:e.dbName,enableTelemetry:!1}).then((async t=>{const r={name:e.modelName,version:"1.0.0",fields:{storeId:{type:"string",required:!0},data:{type:"object",required:!0}}};try{await t.createModel(r)}catch(e){if(e instanceof s.DatabaseError&&"SCHEMA_ALREADY_EXISTS"!==e.type)throw e}return t}))),e.dbInstance}static getEventBus(r){const s=`store_${r}`;return e.eventBusMap.has(s)||e.eventBusMap.set(s,t.createEventBus({batchSize:5,async:!0,batchDelay:16,errorHandler:e=>console.error(`Event bus error for ${r}:`,e),crossTab:!0,channelName:s})),e.eventBusMap.get(s)}static async getCursor(){return(await e.getDatabase()).cursor(e.modelName)}static async close(){const t=await e.dbInstance;t&&t.close(),e.dbInstance=null,e.eventBusMap.forEach((e=>e.clear())),e.eventBusMap.clear()}};exports.IndexedDBPersistence=class{cursorPromise;storeId;eventBus;constructor(e){this.storeId=e,this.cursorPromise=b.getCursor(),this.eventBus=b.getEventBus(e)}async set(e,t){try{const r=await this.cursorPromise,s=(new a.QueryBuilder).where({field:"storeId",operator:"eq",value:this.storeId}).build(),n=await r.find(s.filters),i={storeId:this.storeId,data:t};let o;return n?o=await n.update(i):(await r.create(i),o=!0),o&&this.eventBus.emit({name:"store:updated",payload:{storeId:this.storeId,instanceId:e,state:t}}),o}catch(t){return console.error(`Failed to set state for store ${this.storeId} by instance ${e}:`,t),!1}}async get(){try{const e=await this.cursorPromise,t=(new a.QueryBuilder).where({field:"storeId",operator:"eq",value:this.storeId}).build(),r=await e.find(t.filters);return r?r.read().then((()=>r.data)):null}catch(e){return console.error(`Failed to get state for store ${this.storeId}:`,e),null}}subscribe(e,t){return this.eventBus.subscribe("store:updated",(({storeId:r,instanceId:s,state:a})=>{r===this.storeId&&s!==e&&t(a)}))}async clear(){try{const e=await this.cursorPromise,t=(new a.QueryBuilder).where({field:"storeId",operator:"eq",value:this.storeId}).build(),r=await e.find(t.filters);return!r||await r.delete()}catch(e){return console.error(`Failed to clear state for store ${this.storeId}:`,e),!1}}static async closeAll(){await b.close()}},exports.LocalStoragePersistence=w,exports.ReactiveDataStore=d,exports.RemoteObservability=y,exports.StoreObservability=i,exports.WebStoragePersistence=f,exports.createGrafanaCloudDestination=p,exports.createHttpDestination=g,exports.createOpenTelemetryDestination=h,exports.createPrometheusDestination=m,exports.createStore=function(t,{enableMetrics:r,debounceTime:s=250,...a}={}){const n=new d(t.state,a.persistence),o=r?new i(n,a):void 0,c=new l;t.middleware&&Object.entries(t.middleware).forEach((([e,t])=>n.use(t,e))),t.blockingMiddleware&&Object.entries(t.blockingMiddleware).forEach((([e,t])=>n.useBlockingMiddleware(t,e)));const h=new u,m=new Map,p=Object.entries(t.actions).reduce(((e,[t,r])=>(e[t]=function(e,t){let r=null,s=null;return function(...a){return r&&(clearTimeout(r),s&&s(new Error("Debounced by a newer call"))),new Promise(((n,i)=>{const o=this;s=i,r=setTimeout((()=>{r=null,s=null,e.apply(o,a).then(n).catch(i)}),t)}))}}((async(...e)=>{const s=`${t}-${JSON.stringify(e)}`;if(m.get(s))return;m.set(s,!0);const a=crypto.randomUUID(),i=performance.now(),o={id:a,name:t,params:e,startTime:i};try{return await n.set((async t=>{const s=await r(t,...e);return o.result=s,s})),o.status="success",n.get()}catch(e){throw o.status="error",o.error=e instanceof Error?e:new Error(String(e)),console.error(`Error in action ${t}:`,e),e}finally{o.endTime=performance.now(),o.duration=o.endTime-i,h.track(o),m.delete(s)}}),s),e)),{});function g(e,t){const r=function(e,t="."){const r=[],s={get:(e,t)=>{const a=[];return r.length>0&&a.push(...r[r.length-1]),a.push(t),r.push(a),new Proxy({},s)}};return e(new Proxy({},s)),r.map((e=>e.join(t)))}(e);return n.subscribe(r,t)}const y=()=>n.get(),f=e=>(n.isReady()&&e(),n.onStoreEvent("persistence:ready",e)),w=()=>n.isReady();return function(){const t=e.useCallback((t=>{const r=c.create(t);return e.useSyncExternalStore((e=>g(r,e)),(()=>r(n.get())),(()=>r(n.get())))}),[]),r=e.useCallback((()=>e.useSyncExternalStore((e=>n.subscribe("",e)),y,y)),[]),s=e.useSyncExternalStore(f,w,w);return{store:n,observer:o,select:t,actions:p,isReady:s,actionTracker:h,get state(){return r}}}},exports.useRemoteObservability=function(e,t){const r=new y(e,t);return{remote:r,addOpenTelemetryDestination:e=>r.addDestination(h(e)),addPrometheusDestination:e=>r.addDestination(m(e)),addGrafanaCloudDestination:e=>r.addDestination(p(e)),addHttpDestination:e=>r.addDestination(g(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}}}};
|