@buoy-gg/redux 2.1.11 → 2.1.13
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 +58 -0
- package/lib/commonjs/index.js +1 -179
- package/lib/commonjs/preset.js +1 -98
- package/lib/commonjs/redux/components/ReduxActionButton.js +1 -129
- package/lib/commonjs/redux/components/ReduxActionDetailContent.js +1 -380
- package/lib/commonjs/redux/components/ReduxActionDetailView.js +1 -401
- package/lib/commonjs/redux/components/ReduxActionInfoView.js +1 -838
- package/lib/commonjs/redux/components/ReduxActionItem.js +1 -366
- package/lib/commonjs/redux/components/ReduxDetailViewToggle.js +1 -134
- package/lib/commonjs/redux/components/ReduxIcon.js +1 -18
- package/lib/commonjs/redux/components/ReduxModal.js +1 -530
- package/lib/commonjs/redux/components/index.js +1 -52
- package/lib/commonjs/redux/hooks/index.js +1 -25
- package/lib/commonjs/redux/hooks/useAutoInstrumentRedux.js +1 -197
- package/lib/commonjs/redux/hooks/useReduxActions.js +1 -75
- package/lib/commonjs/redux/index.js +1 -49
- package/lib/commonjs/redux/utils/autoInstrument.js +1 -270
- package/lib/commonjs/redux/utils/buoyReduxMiddleware.js +1 -166
- package/lib/commonjs/redux/utils/createReduxHistoryAdapter.js +1 -146
- package/lib/commonjs/redux/utils/index.js +1 -111
- package/lib/commonjs/redux/utils/reduxActionStore.js +1 -358
- package/lib/module/index.js +1 -87
- package/lib/module/preset.js +1 -94
- package/lib/module/redux/components/ReduxActionButton.js +1 -126
- package/lib/module/redux/components/ReduxActionDetailContent.js +1 -376
- package/lib/module/redux/components/ReduxActionDetailView.js +1 -397
- package/lib/module/redux/components/ReduxActionInfoView.js +1 -833
- package/lib/module/redux/components/ReduxActionItem.js +1 -362
- package/lib/module/redux/components/ReduxDetailViewToggle.js +1 -129
- package/lib/module/redux/components/ReduxIcon.js +1 -8
- package/lib/module/redux/components/ReduxModal.js +1 -525
- package/lib/module/redux/components/index.js +1 -7
- package/lib/module/redux/hooks/index.js +1 -4
- package/lib/module/redux/hooks/useAutoInstrumentRedux.js +1 -193
- package/lib/module/redux/hooks/useReduxActions.js +1 -71
- package/lib/module/redux/index.js +1 -13
- package/lib/module/redux/utils/autoInstrument.js +1 -260
- package/lib/module/redux/utils/buoyReduxMiddleware.js +1 -157
- package/lib/module/redux/utils/createReduxHistoryAdapter.js +1 -142
- package/lib/module/redux/utils/index.js +1 -8
- package/lib/module/redux/utils/reduxActionStore.js +1 -354
- package/package.json +12 -12
- package/lib/typescript/index.d.ts.map +0 -1
- package/lib/typescript/preset.d.ts.map +0 -1
- package/lib/typescript/redux/components/ReduxActionButton.d.ts.map +0 -1
- package/lib/typescript/redux/components/ReduxActionDetailContent.d.ts.map +0 -1
- package/lib/typescript/redux/components/ReduxActionDetailView.d.ts.map +0 -1
- package/lib/typescript/redux/components/ReduxActionInfoView.d.ts.map +0 -1
- package/lib/typescript/redux/components/ReduxActionItem.d.ts.map +0 -1
- package/lib/typescript/redux/components/ReduxDetailViewToggle.d.ts.map +0 -1
- package/lib/typescript/redux/components/ReduxIcon.d.ts.map +0 -1
- package/lib/typescript/redux/components/ReduxModal.d.ts.map +0 -1
- package/lib/typescript/redux/components/index.d.ts.map +0 -1
- package/lib/typescript/redux/hooks/index.d.ts.map +0 -1
- package/lib/typescript/redux/hooks/useAutoInstrumentRedux.d.ts.map +0 -1
- package/lib/typescript/redux/hooks/useReduxActions.d.ts.map +0 -1
- package/lib/typescript/redux/index.d.ts.map +0 -1
- package/lib/typescript/redux/types/index.d.ts.map +0 -1
- package/lib/typescript/redux/utils/autoInstrument.d.ts.map +0 -1
- package/lib/typescript/redux/utils/buoyReduxMiddleware.d.ts.map +0 -1
- package/lib/typescript/redux/utils/createReduxHistoryAdapter.d.ts.map +0 -1
- package/lib/typescript/redux/utils/index.d.ts.map +0 -1
- package/lib/typescript/redux/utils/reduxActionStore.d.ts.map +0 -1
|
@@ -1,193 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* React hook for auto-instrumenting Redux stores
|
|
5
|
-
*
|
|
6
|
-
* This hook automatically patches the Redux store when the component mounts,
|
|
7
|
-
* providing zero-config action capture for the Redux DevTools.
|
|
8
|
-
*
|
|
9
|
-
* Usage:
|
|
10
|
-
* - The hook attempts to use react-redux's useStore() to get the store
|
|
11
|
-
* - If successful, it instruments the store automatically
|
|
12
|
-
* - If react-redux is not available or not in a Provider, it fails gracefully
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* ```tsx
|
|
16
|
-
* function ReduxDevTools() {
|
|
17
|
-
* const { isInstrumented, error } = useAutoInstrumentRedux();
|
|
18
|
-
*
|
|
19
|
-
* if (error) {
|
|
20
|
-
* return <Text>Redux not available: {error}</Text>;
|
|
21
|
-
* }
|
|
22
|
-
*
|
|
23
|
-
* return <ReduxActionList />;
|
|
24
|
-
* }
|
|
25
|
-
* ```
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
import { useEffect, useState, useRef, useCallback } from "react";
|
|
29
|
-
import { instrumentStore, uninstrumentStore, isStoreInstrumented } from "../utils/autoInstrument";
|
|
30
|
-
|
|
31
|
-
// ============================================
|
|
32
|
-
// Safe Module Loading
|
|
33
|
-
// ============================================
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Safely attempt to load react-redux and get useStore hook
|
|
37
|
-
*/
|
|
38
|
-
function getReactReduxUseStore() {
|
|
39
|
-
try {
|
|
40
|
-
// Dynamic require to handle cases where react-redux isn't installed
|
|
41
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
42
|
-
const reactRedux = require("react-redux");
|
|
43
|
-
return reactRedux.useStore;
|
|
44
|
-
} catch {
|
|
45
|
-
// react-redux not available
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// ============================================
|
|
51
|
-
// Hook Types
|
|
52
|
-
// ============================================
|
|
53
|
-
|
|
54
|
-
// ============================================
|
|
55
|
-
// Main Hook
|
|
56
|
-
// ============================================
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Hook to automatically instrument Redux store for DevTools
|
|
60
|
-
*
|
|
61
|
-
* This provides zero-config Redux action capture by:
|
|
62
|
-
* 1. Using react-redux's useStore() to get the Redux store
|
|
63
|
-
* 2. Automatically patching store.dispatch to capture actions
|
|
64
|
-
* 3. Cleaning up when the component unmounts
|
|
65
|
-
*/
|
|
66
|
-
export function useAutoInstrumentRedux(options = {}) {
|
|
67
|
-
const {
|
|
68
|
-
autoStart = true,
|
|
69
|
-
...instrumentOptions
|
|
70
|
-
} = options;
|
|
71
|
-
const [isInstrumented, setIsInstrumented] = useState(false);
|
|
72
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
73
|
-
const [error, setError] = useState(null);
|
|
74
|
-
const [store, setStore] = useState(null);
|
|
75
|
-
const cleanupRef = useRef(null);
|
|
76
|
-
|
|
77
|
-
// Check if react-redux is available
|
|
78
|
-
const useStoreHook = getReactReduxUseStore();
|
|
79
|
-
const isReactReduxAvailable = useStoreHook !== null;
|
|
80
|
-
|
|
81
|
-
// Try to get the store using react-redux's useStore
|
|
82
|
-
// This must be called unconditionally due to hooks rules
|
|
83
|
-
let reduxStore = null;
|
|
84
|
-
let storeError = null;
|
|
85
|
-
if (useStoreHook) {
|
|
86
|
-
try {
|
|
87
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
88
|
-
reduxStore = useStoreHook();
|
|
89
|
-
} catch (e) {
|
|
90
|
-
// Not inside a Provider or other error
|
|
91
|
-
storeError = e instanceof Error ? e.message : "Failed to access Redux store. Make sure your app is wrapped in a Redux Provider.";
|
|
92
|
-
}
|
|
93
|
-
} else {
|
|
94
|
-
storeError = "react-redux is not installed or not available";
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Function to start instrumentation
|
|
98
|
-
const startInstrumentation = useCallback(() => {
|
|
99
|
-
if (!reduxStore) {
|
|
100
|
-
setError(storeError || "No Redux store available");
|
|
101
|
-
setIsLoading(false);
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Check if already instrumented
|
|
106
|
-
if (isStoreInstrumented(reduxStore)) {
|
|
107
|
-
setIsInstrumented(true);
|
|
108
|
-
setStore(reduxStore);
|
|
109
|
-
setIsLoading(false);
|
|
110
|
-
setError(null);
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
try {
|
|
114
|
-
// Instrument the store
|
|
115
|
-
const cleanup = instrumentStore(reduxStore, instrumentOptions);
|
|
116
|
-
cleanupRef.current = cleanup;
|
|
117
|
-
setIsInstrumented(true);
|
|
118
|
-
setStore(reduxStore);
|
|
119
|
-
setError(null);
|
|
120
|
-
} catch (e) {
|
|
121
|
-
setError(e instanceof Error ? e.message : "Failed to instrument Redux store");
|
|
122
|
-
setIsInstrumented(false);
|
|
123
|
-
} finally {
|
|
124
|
-
setIsLoading(false);
|
|
125
|
-
}
|
|
126
|
-
}, [reduxStore, storeError, instrumentOptions]);
|
|
127
|
-
|
|
128
|
-
// Function to stop instrumentation
|
|
129
|
-
const stopInstrumentation = useCallback(() => {
|
|
130
|
-
if (cleanupRef.current) {
|
|
131
|
-
cleanupRef.current();
|
|
132
|
-
cleanupRef.current = null;
|
|
133
|
-
}
|
|
134
|
-
if (store) {
|
|
135
|
-
uninstrumentStore(store);
|
|
136
|
-
}
|
|
137
|
-
setIsInstrumented(false);
|
|
138
|
-
setStore(null);
|
|
139
|
-
}, [store]);
|
|
140
|
-
|
|
141
|
-
// Auto-start instrumentation on mount
|
|
142
|
-
useEffect(() => {
|
|
143
|
-
if (autoStart) {
|
|
144
|
-
startInstrumentation();
|
|
145
|
-
} else {
|
|
146
|
-
setIsLoading(false);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Cleanup on unmount
|
|
150
|
-
return () => {
|
|
151
|
-
// Note: We intentionally DON'T uninstrument on unmount
|
|
152
|
-
// This allows actions to continue being captured even when
|
|
153
|
-
// the DevTools modal is closed. The instrumentation persists
|
|
154
|
-
// for the lifetime of the app, similar to network/storage tools.
|
|
155
|
-
};
|
|
156
|
-
}, [autoStart, startInstrumentation]);
|
|
157
|
-
return {
|
|
158
|
-
isInstrumented,
|
|
159
|
-
isLoading,
|
|
160
|
-
error,
|
|
161
|
-
isReactReduxAvailable,
|
|
162
|
-
startInstrumentation,
|
|
163
|
-
stopInstrumentation,
|
|
164
|
-
store
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Lightweight hook to just check if Redux is available
|
|
170
|
-
* without auto-instrumenting
|
|
171
|
-
*/
|
|
172
|
-
export function useReduxAvailability() {
|
|
173
|
-
const useStoreHook = getReactReduxUseStore();
|
|
174
|
-
if (!useStoreHook) {
|
|
175
|
-
return {
|
|
176
|
-
isAvailable: false,
|
|
177
|
-
error: "react-redux is not installed"
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
try {
|
|
181
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
182
|
-
const store = useStoreHook();
|
|
183
|
-
return {
|
|
184
|
-
isAvailable: store !== null && store !== undefined,
|
|
185
|
-
error: null
|
|
186
|
-
};
|
|
187
|
-
} catch (e) {
|
|
188
|
-
return {
|
|
189
|
-
isAvailable: false,
|
|
190
|
-
error: e instanceof Error ? e.message : "Not inside a Redux Provider"
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
}
|
|
1
|
+
"use strict";import{useEffect,useState,useRef,useCallback}from"react";import{instrumentStore,uninstrumentStore,isStoreInstrumented}from"../utils/autoInstrument";function getReactReduxUseStore(){try{return require("react-redux").useStore}catch{return null}}export function useAutoInstrumentRedux(e={}){const{autoStart:t=!0,...r}=e,[u,n]=useState(!1),[s,a]=useState(!0),[l,o]=useState(null),[i,c]=useState(null),d=useRef(null),f=getReactReduxUseStore(),m=null!==f;let R=null,x=null;if(f)try{R=f()}catch(e){x=e instanceof Error?e.message:"Failed to access Redux store. Make sure your app is wrapped in a Redux Provider."}else x="react-redux is not installed or not available";const S=useCallback(()=>{if(!R)return o(x||"No Redux store available"),void a(!1);if(isStoreInstrumented(R))return n(!0),c(R),a(!1),void o(null);try{const e=instrumentStore(R,r);d.current=e,n(!0),c(R),o(null)}catch(e){o(e instanceof Error?e.message:"Failed to instrument Redux store"),n(!1)}finally{a(!1)}},[R,x,r]),v=useCallback(()=>{d.current&&(d.current(),d.current=null),i&&uninstrumentStore(i),n(!1),c(null)},[i]);return useEffect(()=>(t?S():a(!1),()=>{}),[t,S]),{isInstrumented:u,isLoading:s,error:l,isReactReduxAvailable:m,startInstrumentation:S,stopInstrumentation:v,store:i}}export function useReduxAvailability(){const e=getReactReduxUseStore();if(!e)return{isAvailable:!1,error:"react-redux is not installed"};try{return{isAvailable:null!=e(),error:null}}catch(e){return{isAvailable:!1,error:e instanceof Error?e.message:"Not inside a Redux Provider"}}}
|
|
@@ -1,71 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Hook for consuming Redux actions from the store
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { useState, useEffect, useMemo, useCallback } from "react";
|
|
8
|
-
import { reduxActionStore } from "../utils/reduxActionStore";
|
|
9
|
-
export function useReduxActions() {
|
|
10
|
-
const [actions, setActions] = useState(() => reduxActionStore.getActions());
|
|
11
|
-
const [filter, setFilter] = useState({});
|
|
12
|
-
const [isEnabled, setIsEnabled] = useState(() => reduxActionStore.getEnabled());
|
|
13
|
-
const [selectedAction, setSelectedAction] = useState(null);
|
|
14
|
-
|
|
15
|
-
// Subscribe to store changes
|
|
16
|
-
useEffect(() => {
|
|
17
|
-
const unsubscribe = reduxActionStore.subscribe(newActions => {
|
|
18
|
-
setActions(newActions);
|
|
19
|
-
});
|
|
20
|
-
return () => {
|
|
21
|
-
unsubscribe();
|
|
22
|
-
};
|
|
23
|
-
}, []);
|
|
24
|
-
|
|
25
|
-
// Filter actions based on current filter
|
|
26
|
-
const filteredActions = useMemo(() => {
|
|
27
|
-
return reduxActionStore.filterActions(filter);
|
|
28
|
-
}, [actions, filter]);
|
|
29
|
-
|
|
30
|
-
// Get stats
|
|
31
|
-
const stats = useMemo(() => {
|
|
32
|
-
return reduxActionStore.getStats();
|
|
33
|
-
}, [actions]);
|
|
34
|
-
|
|
35
|
-
// Get unique action types
|
|
36
|
-
const actionTypes = useMemo(() => {
|
|
37
|
-
return reduxActionStore.getUniqueActionTypes();
|
|
38
|
-
}, [actions]);
|
|
39
|
-
|
|
40
|
-
// Clear actions
|
|
41
|
-
const clearActions = useCallback(() => {
|
|
42
|
-
reduxActionStore.clearActions();
|
|
43
|
-
setSelectedAction(null);
|
|
44
|
-
}, []);
|
|
45
|
-
|
|
46
|
-
// Toggle capture
|
|
47
|
-
const toggleCapture = useCallback(() => {
|
|
48
|
-
const newEnabled = !isEnabled;
|
|
49
|
-
reduxActionStore.setEnabled(newEnabled);
|
|
50
|
-
setIsEnabled(newEnabled);
|
|
51
|
-
}, [isEnabled]);
|
|
52
|
-
|
|
53
|
-
// Get action by ID
|
|
54
|
-
const getActionById = useCallback(id => {
|
|
55
|
-
return reduxActionStore.getActionById(id);
|
|
56
|
-
}, []);
|
|
57
|
-
return {
|
|
58
|
-
actions,
|
|
59
|
-
filteredActions,
|
|
60
|
-
filter,
|
|
61
|
-
setFilter,
|
|
62
|
-
stats,
|
|
63
|
-
clearActions,
|
|
64
|
-
isEnabled,
|
|
65
|
-
toggleCapture,
|
|
66
|
-
actionTypes,
|
|
67
|
-
getActionById,
|
|
68
|
-
selectedAction,
|
|
69
|
-
setSelectedAction
|
|
70
|
-
};
|
|
71
|
-
}
|
|
1
|
+
"use strict";import{useState,useEffect,useMemo,useCallback}from"react";import{reduxActionStore}from"../utils/reduxActionStore";export function useReduxActions(){const[e,t]=useState(()=>reduxActionStore.getActions()),[o,c]=useState({}),[s,r]=useState(()=>reduxActionStore.getEnabled()),[n,u]=useState(null);useEffect(()=>{const e=reduxActionStore.subscribe(e=>{t(e)});return()=>{e()}},[]);const i=useMemo(()=>reduxActionStore.filterActions(o),[e,o]),l=useMemo(()=>reduxActionStore.getStats(),[e]),a=useMemo(()=>reduxActionStore.getUniqueActionTypes(),[e]),A=useCallback(()=>{reduxActionStore.clearActions(),u(null)},[]),d=useCallback(()=>{const e=!s;reduxActionStore.setEnabled(e),r(e)},[s]),S=useCallback(e=>reduxActionStore.getActionById(e),[]);return{actions:e,filteredActions:i,filter:o,setFilter:c,stats:l,clearActions:A,isEnabled:s,toggleCapture:d,actionTypes:a,getActionById:S,selectedAction:n,setSelectedAction:u}}
|
|
@@ -1,13 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
// Types
|
|
4
|
-
export * from "./types";
|
|
5
|
-
|
|
6
|
-
// Utils
|
|
7
|
-
export * from "./utils";
|
|
8
|
-
|
|
9
|
-
// Hooks
|
|
10
|
-
export * from "./hooks";
|
|
11
|
-
|
|
12
|
-
// Components
|
|
13
|
-
export * from "./components";
|
|
1
|
+
"use strict";export*from"./types";export*from"./utils";export*from"./hooks";export*from"./components";
|
|
@@ -1,260 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Auto-instrumentation for Redux stores
|
|
5
|
-
*
|
|
6
|
-
* This module provides zero-config Redux DevTools integration by automatically
|
|
7
|
-
* patching the store's dispatch method when the DevTools component mounts.
|
|
8
|
-
*
|
|
9
|
-
* Similar to how @buoy-gg/network patches globalThis.fetch, this patches
|
|
10
|
-
* store.dispatch to capture all Redux actions without requiring manual
|
|
11
|
-
* middleware configuration.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { reduxActionStore } from "./reduxActionStore";
|
|
15
|
-
import { BUOY_JUMP_TO_STATE, isMiddlewareActive } from "./buoyReduxMiddleware";
|
|
16
|
-
|
|
17
|
-
// ============================================
|
|
18
|
-
// Store Tracking
|
|
19
|
-
// ============================================
|
|
20
|
-
|
|
21
|
-
/** Symbol to mark stores that have been instrumented */
|
|
22
|
-
const INSTRUMENTED_SYMBOL = Symbol.for("@@buoy/instrumented");
|
|
23
|
-
|
|
24
|
-
/** Symbol to store original dispatch reference */
|
|
25
|
-
const ORIGINAL_DISPATCH_SYMBOL = Symbol.for("@@buoy/originalDispatch");
|
|
26
|
-
|
|
27
|
-
/** Symbol to store original reducer reference */
|
|
28
|
-
const ORIGINAL_REDUCER_SYMBOL = Symbol.for("@@buoy/originalReducer");
|
|
29
|
-
|
|
30
|
-
/** Reference to the active store for time-travel operations */
|
|
31
|
-
let activeStore = null;
|
|
32
|
-
|
|
33
|
-
/** Track if store has been enhanced for time-travel */
|
|
34
|
-
let timeTravelEnabled = false;
|
|
35
|
-
|
|
36
|
-
// ============================================
|
|
37
|
-
// Type Definitions
|
|
38
|
-
// ============================================
|
|
39
|
-
|
|
40
|
-
// ============================================
|
|
41
|
-
// Core Instrumentation
|
|
42
|
-
// ============================================
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Check if a store has already been instrumented
|
|
46
|
-
*/
|
|
47
|
-
export function isStoreInstrumented(store) {
|
|
48
|
-
return store[INSTRUMENTED_SYMBOL] === true;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Instrument a Redux store for action capture
|
|
53
|
-
*
|
|
54
|
-
* This function patches store.dispatch to capture all actions without
|
|
55
|
-
* requiring the user to configure middleware manually.
|
|
56
|
-
*
|
|
57
|
-
* @param store - The Redux store to instrument
|
|
58
|
-
* @param options - Configuration options
|
|
59
|
-
* @returns Cleanup function to restore original dispatch
|
|
60
|
-
*/
|
|
61
|
-
export function instrumentStore(store, options = {}) {
|
|
62
|
-
const instrumentedStore = store;
|
|
63
|
-
|
|
64
|
-
// Skip if middleware is already handling action capture
|
|
65
|
-
// This prevents duplicate recording when both mechanisms are active
|
|
66
|
-
if (isMiddlewareActive()) {
|
|
67
|
-
activeStore = store;
|
|
68
|
-
return () => {};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Skip if already instrumented
|
|
72
|
-
if (instrumentedStore[INSTRUMENTED_SYMBOL]) {
|
|
73
|
-
// Update active store reference even if already instrumented
|
|
74
|
-
activeStore = store;
|
|
75
|
-
return () => {};
|
|
76
|
-
}
|
|
77
|
-
const {
|
|
78
|
-
enableTimeTravel = false,
|
|
79
|
-
maxActions = 200,
|
|
80
|
-
ignoreActions = []
|
|
81
|
-
} = options;
|
|
82
|
-
|
|
83
|
-
// Configure store
|
|
84
|
-
reduxActionStore.setMaxActions(maxActions);
|
|
85
|
-
|
|
86
|
-
// Store original dispatch
|
|
87
|
-
const originalDispatch = store.dispatch.bind(store);
|
|
88
|
-
instrumentedStore[ORIGINAL_DISPATCH_SYMBOL] = originalDispatch;
|
|
89
|
-
|
|
90
|
-
// Create instrumented dispatch
|
|
91
|
-
const instrumentedDispatch = action => {
|
|
92
|
-
// Skip if action is not a plain object with a type property
|
|
93
|
-
// This filters out thunk functions, promises, and other non-action dispatches
|
|
94
|
-
if (typeof action !== "object" || action === null || typeof action.type !== "string") {
|
|
95
|
-
return originalDispatch(action);
|
|
96
|
-
}
|
|
97
|
-
const actionType = action.type;
|
|
98
|
-
|
|
99
|
-
// Don't record devtools internal actions
|
|
100
|
-
if (actionType.startsWith("@@buoy/")) {
|
|
101
|
-
return originalDispatch(action);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Don't record Redux internal actions
|
|
105
|
-
if (actionType.startsWith("@@redux/")) {
|
|
106
|
-
return originalDispatch(action);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Check ignore list
|
|
110
|
-
if (ignoreActions.includes(actionType)) {
|
|
111
|
-
return originalDispatch(action);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Capture timing and state
|
|
115
|
-
const startTime = performance.now();
|
|
116
|
-
const prevState = store.getState();
|
|
117
|
-
const result = originalDispatch(action);
|
|
118
|
-
const nextState = store.getState();
|
|
119
|
-
const duration = performance.now() - startTime;
|
|
120
|
-
|
|
121
|
-
// Record the action
|
|
122
|
-
reduxActionStore.addAction(action, prevState, nextState, duration);
|
|
123
|
-
return result;
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
// Replace dispatch
|
|
127
|
-
store.dispatch = instrumentedDispatch;
|
|
128
|
-
instrumentedStore[INSTRUMENTED_SYMBOL] = true;
|
|
129
|
-
activeStore = store;
|
|
130
|
-
|
|
131
|
-
// Enable time-travel if requested and store supports replaceReducer
|
|
132
|
-
if (enableTimeTravel && typeof store.replaceReducer === "function") {
|
|
133
|
-
enableTimeTravelSupport(store);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Return cleanup function
|
|
137
|
-
return () => {
|
|
138
|
-
uninstrumentStore(store);
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Remove instrumentation from a store
|
|
144
|
-
*/
|
|
145
|
-
export function uninstrumentStore(store) {
|
|
146
|
-
const instrumentedStore = store;
|
|
147
|
-
if (!instrumentedStore[INSTRUMENTED_SYMBOL]) {
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Restore original dispatch
|
|
152
|
-
const originalDispatch = instrumentedStore[ORIGINAL_DISPATCH_SYMBOL];
|
|
153
|
-
if (originalDispatch) {
|
|
154
|
-
store.dispatch = originalDispatch;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Restore original reducer if time-travel was enabled
|
|
158
|
-
const originalReducer = instrumentedStore[ORIGINAL_REDUCER_SYMBOL];
|
|
159
|
-
if (originalReducer && typeof store.replaceReducer === "function") {
|
|
160
|
-
store.replaceReducer(originalReducer);
|
|
161
|
-
timeTravelEnabled = false;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Clean up symbols
|
|
165
|
-
delete instrumentedStore[INSTRUMENTED_SYMBOL];
|
|
166
|
-
delete instrumentedStore[ORIGINAL_DISPATCH_SYMBOL];
|
|
167
|
-
delete instrumentedStore[ORIGINAL_REDUCER_SYMBOL];
|
|
168
|
-
|
|
169
|
-
// Clear active store reference
|
|
170
|
-
if (activeStore === store) {
|
|
171
|
-
activeStore = null;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// ============================================
|
|
176
|
-
// Time-Travel Support
|
|
177
|
-
// ============================================
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Enable time-travel support by wrapping the store's reducer
|
|
181
|
-
*
|
|
182
|
-
* This uses store.replaceReducer to inject our time-travel wrapper
|
|
183
|
-
* dynamically, without requiring users to manually wrap their reducer.
|
|
184
|
-
*/
|
|
185
|
-
function enableTimeTravelSupport(store) {
|
|
186
|
-
const instrumentedStore = store;
|
|
187
|
-
|
|
188
|
-
// Get current reducer by dispatching a probe action
|
|
189
|
-
// Note: This is a workaround since there's no store.getReducer()
|
|
190
|
-
// We'll wrap whatever reducer is currently active
|
|
191
|
-
const currentState = store.getState();
|
|
192
|
-
|
|
193
|
-
// Create a time-travel enabled reducer wrapper
|
|
194
|
-
const timeTravelReducer = (state = currentState, action) => {
|
|
195
|
-
// Handle jump to state action
|
|
196
|
-
if (action.type === BUOY_JUMP_TO_STATE) {
|
|
197
|
-
return action.payload;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Get the result from the original dispatch flow
|
|
201
|
-
// Since we've already wrapped dispatch, we need to be careful here
|
|
202
|
-
// The state will be updated by the store's internal reducer
|
|
203
|
-
return state;
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
// Store reference (we can't actually get the original reducer directly)
|
|
207
|
-
// This approach has limitations - full time-travel works better with
|
|
208
|
-
// the explicit withBuoyDevTools wrapper
|
|
209
|
-
instrumentedStore[ORIGINAL_REDUCER_SYMBOL] = timeTravelReducer;
|
|
210
|
-
timeTravelEnabled = true;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Jump to a specific state (time-travel)
|
|
215
|
-
*
|
|
216
|
-
* For full time-travel support, users should use withBuoyDevTools.
|
|
217
|
-
* This limited version attempts to work with auto-instrumentation.
|
|
218
|
-
*/
|
|
219
|
-
export function jumpToState(state) {
|
|
220
|
-
if (activeStore) {
|
|
221
|
-
activeStore.dispatch({
|
|
222
|
-
type: BUOY_JUMP_TO_STATE,
|
|
223
|
-
payload: state
|
|
224
|
-
});
|
|
225
|
-
} else {
|
|
226
|
-
console.warn("[BuoyRedux] Cannot jump to state - no store instrumented");
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Replay an action (dispatch it again)
|
|
232
|
-
*/
|
|
233
|
-
export function replayAction(action) {
|
|
234
|
-
if (activeStore) {
|
|
235
|
-
activeStore.dispatch(action);
|
|
236
|
-
} else {
|
|
237
|
-
console.warn("[BuoyRedux] Cannot replay action - no store instrumented");
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Get the currently active (instrumented) store
|
|
243
|
-
*/
|
|
244
|
-
export function getActiveStore() {
|
|
245
|
-
return activeStore;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Check if auto-instrumentation is active
|
|
250
|
-
*/
|
|
251
|
-
export function isAutoInstrumentActive() {
|
|
252
|
-
return activeStore !== null && isStoreInstrumented(activeStore);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Check if time-travel support is enabled
|
|
257
|
-
*/
|
|
258
|
-
export function isTimeTravelEnabled() {
|
|
259
|
-
return timeTravelEnabled;
|
|
260
|
-
}
|
|
1
|
+
"use strict";import{reduxActionStore}from"./reduxActionStore";import{BUOY_JUMP_TO_STATE,isMiddlewareActive}from"./buoyReduxMiddleware";const INSTRUMENTED_SYMBOL=Symbol.for("@@buoy/instrumented"),ORIGINAL_DISPATCH_SYMBOL=Symbol.for("@@buoy/originalDispatch"),ORIGINAL_REDUCER_SYMBOL=Symbol.for("@@buoy/originalReducer");let activeStore=null,timeTravelEnabled=!1;export function isStoreInstrumented(e){return!0===e[INSTRUMENTED_SYMBOL]}export function instrumentStore(e,t={}){const r=e;if(isMiddlewareActive())return activeStore=e,()=>{};if(r[INSTRUMENTED_SYMBOL])return activeStore=e,()=>{};const{enableTimeTravel:o=!1,maxActions:n=200,ignoreActions:i=[]}=t;reduxActionStore.setMaxActions(n);const c=e.dispatch.bind(e);return r[ORIGINAL_DISPATCH_SYMBOL]=c,e.dispatch=t=>{if("object"!=typeof t||null===t||"string"!=typeof t.type)return c(t);const r=t.type;if(r.startsWith("@@buoy/"))return c(t);if(r.startsWith("@@redux/"))return c(t);if(i.includes(r))return c(t);const o=performance.now(),n=e.getState(),a=c(t),u=e.getState(),S=performance.now()-o;return reduxActionStore.addAction(t,n,u,S),a},r[INSTRUMENTED_SYMBOL]=!0,activeStore=e,o&&"function"==typeof e.replaceReducer&&enableTimeTravelSupport(e),()=>{uninstrumentStore(e)}}export function uninstrumentStore(e){const t=e;if(!t[INSTRUMENTED_SYMBOL])return;const r=t[ORIGINAL_DISPATCH_SYMBOL];r&&(e.dispatch=r);const o=t[ORIGINAL_REDUCER_SYMBOL];o&&"function"==typeof e.replaceReducer&&(e.replaceReducer(o),timeTravelEnabled=!1),delete t[INSTRUMENTED_SYMBOL],delete t[ORIGINAL_DISPATCH_SYMBOL],delete t[ORIGINAL_REDUCER_SYMBOL],activeStore===e&&(activeStore=null)}function enableTimeTravelSupport(e){const t=e,r=e.getState();t[ORIGINAL_REDUCER_SYMBOL]=(e=r,t)=>t.type===BUOY_JUMP_TO_STATE?t.payload:e,timeTravelEnabled=!0}export function jumpToState(e){activeStore?activeStore.dispatch({type:BUOY_JUMP_TO_STATE,payload:e}):console.warn("[BuoyRedux] Cannot jump to state - no store instrumented")}export function replayAction(e){activeStore?activeStore.dispatch(e):console.warn("[BuoyRedux] Cannot replay action - no store instrumented")}export function getActiveStore(){return activeStore}export function isAutoInstrumentActive(){return null!==activeStore&&isStoreInstrumented(activeStore)}export function isTimeTravelEnabled(){return timeTravelEnabled}
|