@buoy-gg/redux 7.0.4 → 7.0.6
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 +21 -2
- package/lib/commonjs/index.js +44 -0
- package/lib/commonjs/redux/components/ReduxAutoInstrument.js +20 -0
- package/lib/commonjs/redux/components/ReduxModal.js +123 -68
- package/lib/commonjs/redux/components/index.js +8 -1
- package/lib/commonjs/redux/hooks/useAutoInstrumentRedux.js +32 -42
- package/lib/commonjs/redux/sync/reduxSyncAdapter.js +27 -8
- package/lib/commonjs/redux/utils/activeStoreRegistry.js +73 -0
- package/lib/commonjs/redux/utils/autoInstrument.js +88 -114
- package/lib/commonjs/redux/utils/buoyReduxMiddleware.js +20 -15
- package/lib/commonjs/redux/utils/devtoolsHook.js +137 -0
- package/lib/commonjs/redux/utils/index.js +32 -1
- package/lib/module/index.js +29 -4
- package/lib/module/redux/components/ReduxAutoInstrument.js +17 -0
- package/lib/module/redux/components/ReduxModal.js +122 -68
- package/lib/module/redux/components/index.js +2 -1
- package/lib/module/redux/hooks/useAutoInstrumentRedux.js +33 -43
- package/lib/module/redux/sync/reduxSyncAdapter.js +28 -9
- package/lib/module/redux/utils/activeStoreRegistry.js +63 -0
- package/lib/module/redux/utils/autoInstrument.js +82 -113
- package/lib/module/redux/utils/buoyReduxMiddleware.js +20 -15
- package/lib/module/redux/utils/devtoolsHook.js +130 -0
- package/lib/module/redux/utils/index.js +3 -1
- package/lib/typescript/index.d.ts +19 -5
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/redux/components/ReduxAutoInstrument.d.ts +11 -0
- package/lib/typescript/redux/components/ReduxAutoInstrument.d.ts.map +1 -0
- package/lib/typescript/redux/components/ReduxModal.d.ts +15 -1
- package/lib/typescript/redux/components/ReduxModal.d.ts.map +1 -1
- package/lib/typescript/redux/components/index.d.ts +1 -0
- package/lib/typescript/redux/components/index.d.ts.map +1 -1
- package/lib/typescript/redux/hooks/useAutoInstrumentRedux.d.ts +16 -28
- package/lib/typescript/redux/hooks/useAutoInstrumentRedux.d.ts.map +1 -1
- package/lib/typescript/redux/sync/reduxSyncAdapter.d.ts +18 -4
- package/lib/typescript/redux/sync/reduxSyncAdapter.d.ts.map +1 -1
- package/lib/typescript/redux/utils/activeStoreRegistry.d.ts +47 -0
- package/lib/typescript/redux/utils/activeStoreRegistry.d.ts.map +1 -0
- package/lib/typescript/redux/utils/autoInstrument.d.ts +46 -23
- package/lib/typescript/redux/utils/autoInstrument.d.ts.map +1 -1
- package/lib/typescript/redux/utils/buoyReduxMiddleware.d.ts +5 -1
- package/lib/typescript/redux/utils/buoyReduxMiddleware.d.ts.map +1 -1
- package/lib/typescript/redux/utils/devtoolsHook.d.ts +45 -0
- package/lib/typescript/redux/utils/devtoolsHook.d.ts.map +1 -0
- package/lib/typescript/redux/utils/index.d.ts +3 -2
- package/lib/typescript/redux/utils/index.d.ts.map +1 -1
- package/package.json +8 -5
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Fallback auto-instrumentation for Redux stores.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* The PRIMARY capture path is the store-creation-time enhancer in
|
|
7
|
+
* devtoolsHook.ts (full capture, real time travel). This module is the
|
|
8
|
+
* fallback for stores created before our module loaded: it patches
|
|
9
|
+
* store.dispatch after the fact.
|
|
8
10
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* middleware
|
|
11
|
+
* Fallback limitation (inherent to patching, verified against redux 5.x):
|
|
12
|
+
* `middlewareAPI.dispatch` is a separate closure from `store.dispatch`, so
|
|
13
|
+
* actions dispatched from INSIDE middleware/thunks (RTK pending/fulfilled/
|
|
14
|
+
* rejected, RTK Query) bypass the patch. That's why this mechanism reports
|
|
15
|
+
* capture as "top-level-only" — see activeStoreRegistry.getInstrumentationStatus.
|
|
12
16
|
*/
|
|
13
17
|
|
|
14
18
|
import { reduxActionStore } from "./reduxActionStore";
|
|
15
19
|
import { BUOY_JUMP_TO_STATE, isMiddlewareActive } from "./buoyReduxMiddleware";
|
|
20
|
+
import { registerActiveStore, unregisterActiveStore, getActiveStore as getRegisteredStore, ENHANCER_SYMBOL } from "./activeStoreRegistry";
|
|
21
|
+
|
|
22
|
+
// Re-exported for non-React callers (sync adapter, app code).
|
|
23
|
+
export { getInstrumentationStatus } from "./activeStoreRegistry";
|
|
16
24
|
|
|
17
25
|
// ============================================
|
|
18
26
|
// Store Tracking
|
|
@@ -24,15 +32,6 @@ const INSTRUMENTED_SYMBOL = Symbol.for("@@buoy/instrumented");
|
|
|
24
32
|
/** Symbol to store original dispatch reference */
|
|
25
33
|
const ORIGINAL_DISPATCH_SYMBOL = Symbol.for("@@buoy/originalDispatch");
|
|
26
34
|
|
|
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
35
|
// ============================================
|
|
37
36
|
// Type Definitions
|
|
38
37
|
// ============================================
|
|
@@ -42,40 +41,44 @@ let timeTravelEnabled = false;
|
|
|
42
41
|
// ============================================
|
|
43
42
|
|
|
44
43
|
/**
|
|
45
|
-
* Check if a store has already been instrumented
|
|
44
|
+
* Check if a store has already been instrumented (either mechanism)
|
|
46
45
|
*/
|
|
47
46
|
export function isStoreInstrumented(store) {
|
|
48
|
-
|
|
47
|
+
const s = store;
|
|
48
|
+
return s[INSTRUMENTED_SYMBOL] === true || s[ENHANCER_SYMBOL] === true;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
|
-
* Instrument a Redux store for action capture
|
|
52
|
+
* Instrument a Redux store for action capture (fallback path).
|
|
53
53
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
54
|
+
* Patches store.dispatch to capture top-level dispatches. No-ops (but still
|
|
55
|
+
* registers the store for getState/dispatch) when the enhancer or the
|
|
56
|
+
* explicit middleware is already capturing.
|
|
56
57
|
*
|
|
57
|
-
* @param store - The Redux store to instrument
|
|
58
|
-
* @param options - Configuration options
|
|
59
58
|
* @returns Cleanup function to restore original dispatch
|
|
60
59
|
*/
|
|
61
60
|
export function instrumentStore(store, options = {}) {
|
|
62
61
|
const instrumentedStore = store;
|
|
63
62
|
|
|
63
|
+
// Store was created through the devtools-hook enhancer — full capture is
|
|
64
|
+
// already live; just (re)point the registry at it.
|
|
65
|
+
if (instrumentedStore[ENHANCER_SYMBOL]) {
|
|
66
|
+
registerActiveStore(store, "enhancer");
|
|
67
|
+
return () => {};
|
|
68
|
+
}
|
|
69
|
+
|
|
64
70
|
// Skip if middleware is already handling action capture
|
|
65
|
-
// This prevents duplicate recording when both mechanisms are active
|
|
66
71
|
if (isMiddlewareActive()) {
|
|
67
|
-
|
|
72
|
+
registerActiveStore(store, "middleware");
|
|
68
73
|
return () => {};
|
|
69
74
|
}
|
|
70
75
|
|
|
71
|
-
// Skip if already
|
|
76
|
+
// Skip if already patched — update the registry even so (store swap case)
|
|
72
77
|
if (instrumentedStore[INSTRUMENTED_SYMBOL]) {
|
|
73
|
-
|
|
74
|
-
activeStore = store;
|
|
78
|
+
registerActiveStore(store, "patch");
|
|
75
79
|
return () => {};
|
|
76
80
|
}
|
|
77
81
|
const {
|
|
78
|
-
enableTimeTravel = false,
|
|
79
82
|
maxActions = 200,
|
|
80
83
|
ignoreActions = []
|
|
81
84
|
} = options;
|
|
@@ -126,12 +129,7 @@ export function instrumentStore(store, options = {}) {
|
|
|
126
129
|
// Replace dispatch
|
|
127
130
|
store.dispatch = instrumentedDispatch;
|
|
128
131
|
instrumentedStore[INSTRUMENTED_SYMBOL] = true;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
// Enable time-travel if requested and store supports replaceReducer
|
|
132
|
-
if (enableTimeTravel && typeof store.replaceReducer === "function") {
|
|
133
|
-
enableTimeTravelSupport(store);
|
|
134
|
-
}
|
|
132
|
+
registerActiveStore(store, "patch");
|
|
135
133
|
|
|
136
134
|
// Return cleanup function
|
|
137
135
|
return () => {
|
|
@@ -140,90 +138,63 @@ export function instrumentStore(store, options = {}) {
|
|
|
140
138
|
}
|
|
141
139
|
|
|
142
140
|
/**
|
|
143
|
-
* Remove instrumentation from a store
|
|
141
|
+
* Remove dispatch-patch instrumentation from a store.
|
|
142
|
+
*
|
|
143
|
+
* Only restores the patched dispatch. (This used to also replaceReducer with a
|
|
144
|
+
* synthetic reducer that froze the host store permanently — that path is gone.)
|
|
145
|
+
* Enhancer-created stores are left untouched apart from the registry.
|
|
144
146
|
*/
|
|
145
147
|
export function uninstrumentStore(store) {
|
|
146
148
|
const instrumentedStore = store;
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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;
|
|
149
|
+
if (instrumentedStore[INSTRUMENTED_SYMBOL]) {
|
|
150
|
+
const originalDispatch = instrumentedStore[ORIGINAL_DISPATCH_SYMBOL];
|
|
151
|
+
if (originalDispatch) {
|
|
152
|
+
store.dispatch = originalDispatch;
|
|
153
|
+
}
|
|
154
|
+
delete instrumentedStore[INSTRUMENTED_SYMBOL];
|
|
155
|
+
delete instrumentedStore[ORIGINAL_DISPATCH_SYMBOL];
|
|
172
156
|
}
|
|
157
|
+
unregisterActiveStore(store);
|
|
173
158
|
}
|
|
174
159
|
|
|
175
|
-
// ============================================
|
|
176
|
-
// Time-Travel Support
|
|
177
|
-
// ============================================
|
|
178
|
-
|
|
179
160
|
/**
|
|
180
|
-
*
|
|
161
|
+
* Manually register a store with Buoy — the escape hatch for apps that can't
|
|
162
|
+
* render FloatingDevTools inside a react-redux Provider (or don't use
|
|
163
|
+
* react-redux at all). Instruments via the fallback dispatch patch.
|
|
181
164
|
*
|
|
182
|
-
*
|
|
183
|
-
* dynamically, without requiring users to manually wrap their reducer.
|
|
165
|
+
* Prefer the zero-config paths; this exists so no app is ever stuck.
|
|
184
166
|
*/
|
|
185
|
-
function
|
|
186
|
-
|
|
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
|
-
}
|
|
167
|
+
export function registerReduxStore(store, options = {}) {
|
|
168
|
+
return instrumentStore(store, options);
|
|
169
|
+
}
|
|
199
170
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
return state;
|
|
204
|
-
};
|
|
171
|
+
// ============================================
|
|
172
|
+
// Time-Travel / Introspection
|
|
173
|
+
// ============================================
|
|
205
174
|
|
|
206
|
-
|
|
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
|
-
}
|
|
175
|
+
let warnedJumpUnsupported = false;
|
|
212
176
|
|
|
213
177
|
/**
|
|
214
|
-
* Jump to a specific state (time-travel)
|
|
178
|
+
* Jump to a specific state (time-travel).
|
|
215
179
|
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
180
|
+
* Real on the enhancer path (reducer is wrapped from creation) and on the
|
|
181
|
+
* middleware path when the app used `withBuoyDevTools`. On the bare patch
|
|
182
|
+
* path there's no reducer wrapper, so the jump action is ignored — we detect
|
|
183
|
+
* that and warn once instead of pretending it worked.
|
|
218
184
|
*/
|
|
219
185
|
export function jumpToState(state) {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
type: BUOY_JUMP_TO_STATE,
|
|
223
|
-
payload: state
|
|
224
|
-
});
|
|
225
|
-
} else {
|
|
186
|
+
const store = getRegisteredStore();
|
|
187
|
+
if (!store) {
|
|
226
188
|
console.warn("[BuoyRedux] Cannot jump to state - no store instrumented");
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
store.dispatch({
|
|
192
|
+
type: BUOY_JUMP_TO_STATE,
|
|
193
|
+
payload: state
|
|
194
|
+
});
|
|
195
|
+
if (typeof __DEV__ !== "undefined" && __DEV__ && !warnedJumpUnsupported && store.getState() !== state) {
|
|
196
|
+
warnedJumpUnsupported = true;
|
|
197
|
+
console.warn("[BuoyRedux] Time-travel jump was ignored by the store. Wrap your root " + "reducer with withBuoyDevTools(), or ensure @buoy-gg/redux is imported " + "before your store is created (enhancer path) for real time travel.");
|
|
227
198
|
}
|
|
228
199
|
}
|
|
229
200
|
|
|
@@ -231,30 +202,28 @@ export function jumpToState(state) {
|
|
|
231
202
|
* Replay an action (dispatch it again)
|
|
232
203
|
*/
|
|
233
204
|
export function replayAction(action) {
|
|
234
|
-
|
|
235
|
-
|
|
205
|
+
const store = getRegisteredStore();
|
|
206
|
+
if (store) {
|
|
207
|
+
store.dispatch(action);
|
|
236
208
|
} else {
|
|
237
209
|
console.warn("[BuoyRedux] Cannot replay action - no store instrumented");
|
|
238
210
|
}
|
|
239
211
|
}
|
|
240
212
|
|
|
241
213
|
/**
|
|
242
|
-
* Get the currently active (instrumented) store
|
|
214
|
+
* Get the currently active (instrumented) store.
|
|
215
|
+
*
|
|
216
|
+
* NOTE: on the middleware path this is the middleware `storeApi`
|
|
217
|
+
* ({getState, dispatch}) rather than the full Store object — every consumer
|
|
218
|
+
* in this package only needs those two.
|
|
243
219
|
*/
|
|
244
220
|
export function getActiveStore() {
|
|
245
|
-
return
|
|
221
|
+
return getRegisteredStore();
|
|
246
222
|
}
|
|
247
223
|
|
|
248
224
|
/**
|
|
249
225
|
* Check if auto-instrumentation is active
|
|
250
226
|
*/
|
|
251
227
|
export function isAutoInstrumentActive() {
|
|
252
|
-
return
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Check if time-travel support is enabled
|
|
257
|
-
*/
|
|
258
|
-
export function isTimeTravelEnabled() {
|
|
259
|
-
return timeTravelEnabled;
|
|
228
|
+
return getRegisteredStore() !== null;
|
|
260
229
|
}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { reduxActionStore } from "./reduxActionStore";
|
|
20
|
+
import { registerActiveStore, getActiveStore } from "./activeStoreRegistry";
|
|
20
21
|
|
|
21
22
|
// ============================================
|
|
22
23
|
// Middleware Active Tracking
|
|
@@ -39,20 +40,22 @@ export function isMiddlewareActive() {
|
|
|
39
40
|
|
|
40
41
|
export const BUOY_JUMP_TO_STATE = "@@buoy/JUMP_TO_STATE";
|
|
41
42
|
export const BUOY_REPLAY_ACTION = "@@buoy/REPLAY_ACTION";
|
|
42
|
-
// Store reference to dispatch for time-travel
|
|
43
|
-
let storeDispatch = null;
|
|
44
|
-
|
|
45
43
|
/**
|
|
46
|
-
* Jump to a specific state (time-travel)
|
|
44
|
+
* Jump to a specific state (time-travel).
|
|
45
|
+
*
|
|
46
|
+
* Registry-backed, so it works on EVERY capture path (enhancer, middleware,
|
|
47
|
+
* patch) — previously this only worked when the middleware was installed,
|
|
48
|
+
* which silently broke the modal's Jump button on the zero-config path.
|
|
47
49
|
*/
|
|
48
50
|
export function jumpToState(state) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
const store = getActiveStore();
|
|
52
|
+
if (store) {
|
|
53
|
+
store.dispatch({
|
|
51
54
|
type: BUOY_JUMP_TO_STATE,
|
|
52
55
|
payload: state
|
|
53
56
|
});
|
|
54
57
|
} else {
|
|
55
|
-
console.warn("[BuoyRedux] Cannot jump to state -
|
|
58
|
+
console.warn("[BuoyRedux] Cannot jump to state - no store instrumented");
|
|
56
59
|
}
|
|
57
60
|
}
|
|
58
61
|
|
|
@@ -60,11 +63,11 @@ export function jumpToState(state) {
|
|
|
60
63
|
* Replay an action (dispatch it again)
|
|
61
64
|
*/
|
|
62
65
|
export function replayAction(action) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
const store = getActiveStore();
|
|
67
|
+
if (store) {
|
|
68
|
+
store.dispatch(action);
|
|
66
69
|
} else {
|
|
67
|
-
console.warn("[BuoyRedux] Cannot replay action -
|
|
70
|
+
console.warn("[BuoyRedux] Cannot replay action - no store instrumented");
|
|
68
71
|
}
|
|
69
72
|
}
|
|
70
73
|
|
|
@@ -99,8 +102,10 @@ export const buoyReduxMiddleware = storeApi => {
|
|
|
99
102
|
// Mark middleware as active so auto-instrument knows to skip
|
|
100
103
|
middlewareActive = true;
|
|
101
104
|
|
|
102
|
-
//
|
|
103
|
-
|
|
105
|
+
// Register as the active store so getState/dispatch (MCP, desktop, time
|
|
106
|
+
// travel) work on the middleware path too — storeApi has both getState and
|
|
107
|
+
// dispatch, which is all the registry needs.
|
|
108
|
+
registerActiveStore(storeApi, "middleware");
|
|
104
109
|
return next => action => {
|
|
105
110
|
const actionObj = action;
|
|
106
111
|
const actionType = actionObj.type;
|
|
@@ -132,8 +137,8 @@ export function createBuoyReduxMiddleware(options = {}) {
|
|
|
132
137
|
// Mark middleware as active so auto-instrument knows to skip
|
|
133
138
|
middlewareActive = true;
|
|
134
139
|
|
|
135
|
-
//
|
|
136
|
-
|
|
140
|
+
// Register as the active store (see buoyReduxMiddleware above).
|
|
141
|
+
registerActiveStore(storeApi, "middleware");
|
|
137
142
|
return next => action => {
|
|
138
143
|
const actionObj = action;
|
|
139
144
|
const actionType = actionObj.type || "";
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Store-creation-time capture via the OFFICIAL Redux DevTools hook.
|
|
5
|
+
*
|
|
6
|
+
* `configureStore` defaults `devTools: true` and, at RTK module-evaluation
|
|
7
|
+
* time, snapshots `window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__` if defined.
|
|
8
|
+
* That global doesn't exist in React Native — so Buoy claims it (dev builds
|
|
9
|
+
* only, and only when nothing else has). This makes Buoy a real store
|
|
10
|
+
* ENHANCER, present from the store's birth:
|
|
11
|
+
*
|
|
12
|
+
* - FULL capture: actions dispatched from inside middleware/thunks (RTK
|
|
13
|
+
* `pending`/`fulfilled`/`rejected`, RTK Query) hit our dispatch because the
|
|
14
|
+
* enhancer sits BELOW applyMiddleware in the compose chain.
|
|
15
|
+
* - Real time travel: we wrap the reducer, so BUOY_JUMP_TO_STATE actually
|
|
16
|
+
* jumps — and replaceReducer is re-wrapped, so hosts that inject reducers
|
|
17
|
+
* dynamically keep working.
|
|
18
|
+
* - Nothing is monkey-patched: this is the integration point Redux itself
|
|
19
|
+
* ships for devtools (same one react-native-debugger used).
|
|
20
|
+
*
|
|
21
|
+
* Race note: RTK snapshots the global when ITS module first evaluates, so this
|
|
22
|
+
* module must load before `@reduxjs/toolkit` does. `@buoy-gg/core` requires us
|
|
23
|
+
* at module scope, which usually wins; when it doesn't, the dispatch-patch
|
|
24
|
+
* fallback in autoInstrument.ts still binds at first mount. Apps that want a
|
|
25
|
+
* guarantee put `import '@buoy-gg/redux';` as the FIRST import of their entry.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { reduxActionStore } from "./reduxActionStore";
|
|
29
|
+
import { BUOY_JUMP_TO_STATE, isMiddlewareActive } from "./buoyReduxMiddleware";
|
|
30
|
+
import { registerActiveStore, ENHANCER_SYMBOL } from "./activeStoreRegistry";
|
|
31
|
+
/** Standard redux compose. */
|
|
32
|
+
function composeFns(...funcs) {
|
|
33
|
+
if (funcs.length === 0) return arg => arg;
|
|
34
|
+
if (funcs.length === 1) return funcs[0];
|
|
35
|
+
return funcs.reduce((a, b) => (...args) => a(b(...args)));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Wrap a reducer so BUOY_JUMP_TO_STATE performs a real state jump. */
|
|
39
|
+
function wrapReducer(reducer) {
|
|
40
|
+
return (state, action) => {
|
|
41
|
+
if (action && action.type === BUOY_JUMP_TO_STATE) {
|
|
42
|
+
return action.payload;
|
|
43
|
+
}
|
|
44
|
+
return reducer(state, action);
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Should this action be recorded? (Mirrors autoInstrument's filters.) */
|
|
49
|
+
function isRecordable(action) {
|
|
50
|
+
if (typeof action !== "object" || action === null) return false;
|
|
51
|
+
const type = action.type;
|
|
52
|
+
if (typeof type !== "string") return false;
|
|
53
|
+
return !type.startsWith("@@buoy/") && !type.startsWith("@@redux/");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The Buoy store enhancer. Innermost in the compose chain (below
|
|
58
|
+
* applyMiddleware), so every action that reaches the reducer — including ones
|
|
59
|
+
* dispatched from inside middleware and thunks — passes through here.
|
|
60
|
+
*/
|
|
61
|
+
export const buoyDevtoolsEnhancer = createStore => (reducer, preloadedState) => {
|
|
62
|
+
const store = createStore(wrapReducer(reducer), preloadedState);
|
|
63
|
+
const dispatch = action => {
|
|
64
|
+
// If the explicit middleware is installed it already records — the
|
|
65
|
+
// enhancer stays passive to avoid double capture.
|
|
66
|
+
if (!isRecordable(action) || isMiddlewareActive()) {
|
|
67
|
+
return store.dispatch(action);
|
|
68
|
+
}
|
|
69
|
+
const startTime = performance.now();
|
|
70
|
+
const prevState = store.getState();
|
|
71
|
+
const result = store.dispatch(action);
|
|
72
|
+
reduxActionStore.addAction(action, prevState, store.getState(), performance.now() - startTime);
|
|
73
|
+
return result;
|
|
74
|
+
};
|
|
75
|
+
const enhanced = {
|
|
76
|
+
...store,
|
|
77
|
+
dispatch,
|
|
78
|
+
// Keep time travel alive across dynamic reducer injection.
|
|
79
|
+
replaceReducer: next => store.replaceReducer(wrapReducer(next)),
|
|
80
|
+
[ENHANCER_SYMBOL]: true
|
|
81
|
+
};
|
|
82
|
+
registerActiveStore(enhanced, "enhancer");
|
|
83
|
+
return enhanced;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Implements the `__REDUX_DEVTOOLS_EXTENSION_COMPOSE__` calling conventions:
|
|
88
|
+
* - `compose(options)(...enhancers)` — what RTK's configureStore does
|
|
89
|
+
* - `compose(...enhancers)` — classic createStore usage from the Redux docs
|
|
90
|
+
*/
|
|
91
|
+
function buoyComposeWithDevTools(...args) {
|
|
92
|
+
if (args.length === 0) {
|
|
93
|
+
return composeFns(buoyDevtoolsEnhancer);
|
|
94
|
+
}
|
|
95
|
+
if (args.length === 1 && typeof args[0] === "object" && args[0] !== null) {
|
|
96
|
+
// Options form → return a compose function.
|
|
97
|
+
return (...enhancers) => composeFns(...enhancers, buoyDevtoolsEnhancer);
|
|
98
|
+
}
|
|
99
|
+
return composeFns(...args, buoyDevtoolsEnhancer);
|
|
100
|
+
}
|
|
101
|
+
let claimed = false;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Claim the DevTools globals — dev builds only, and never over an existing
|
|
105
|
+
* claimant (e.g. the real browser extension on web).
|
|
106
|
+
*
|
|
107
|
+
* Idempotent; runs at module scope so simply importing `@buoy-gg/redux` (which
|
|
108
|
+
* `@buoy-gg/core` does at module scope) performs the claim as early as
|
|
109
|
+
* possible.
|
|
110
|
+
*/
|
|
111
|
+
export function claimDevtoolsHook() {
|
|
112
|
+
const isDev = typeof __DEV__ !== "undefined" && __DEV__;
|
|
113
|
+
if (!isDev) return false;
|
|
114
|
+
const g = globalThis;
|
|
115
|
+
if (!g.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) {
|
|
116
|
+
g.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ = buoyComposeWithDevTools;
|
|
117
|
+
claimed = true;
|
|
118
|
+
}
|
|
119
|
+
if (!g.__REDUX_DEVTOOLS_EXTENSION__) {
|
|
120
|
+
// Legacy single-enhancer form: `createStore(reducer, __REDUX_DEVTOOLS_EXTENSION__())`
|
|
121
|
+
g.__REDUX_DEVTOOLS_EXTENSION__ = () => buoyDevtoolsEnhancer;
|
|
122
|
+
}
|
|
123
|
+
return claimed;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Whether Buoy owns the DevTools compose global (i.e. won the load race). */
|
|
127
|
+
export function isDevtoolsHookClaimed() {
|
|
128
|
+
return claimed;
|
|
129
|
+
}
|
|
130
|
+
claimDevtoolsHook();
|
|
@@ -5,4 +5,6 @@ export { buoyReduxMiddleware, createBuoyReduxMiddleware, withBuoyDevTools, jumpT
|
|
|
5
5
|
export { createReduxHistoryAdapter, reduxHistoryAdapter } from "./createReduxHistoryAdapter";
|
|
6
6
|
|
|
7
7
|
// Auto-instrumentation (zero-config)
|
|
8
|
-
export { instrumentStore, uninstrumentStore, isStoreInstrumented, isAutoInstrumentActive, getActiveStore, jumpToState as autoJumpToState, replayAction as autoReplayAction } from "./autoInstrument";
|
|
8
|
+
export { instrumentStore, uninstrumentStore, isStoreInstrumented, isAutoInstrumentActive, getActiveStore, getInstrumentationStatus, registerReduxStore, jumpToState as autoJumpToState, replayAction as autoReplayAction } from "./autoInstrument";
|
|
9
|
+
// Store-creation-time capture (the primary, full-capture path)
|
|
10
|
+
export { buoyDevtoolsEnhancer, claimDevtoolsHook, isDevtoolsHookClaimed } from "./devtoolsHook";
|
|
@@ -27,9 +27,20 @@
|
|
|
27
27
|
*
|
|
28
28
|
* That's it! The Redux tool will auto-detect your store and capture actions.
|
|
29
29
|
*
|
|
30
|
-
*
|
|
30
|
+
* HOW CAPTURE WORKS (in priority order):
|
|
31
|
+
* 1. ENHANCER — importing this package claims the official Redux DevTools
|
|
32
|
+
* global (`__REDUX_DEVTOOLS_EXTENSION_COMPOSE__`), which RTK checks by
|
|
33
|
+
* default. If this package loads before your store module, Buoy is inside
|
|
34
|
+
* the store from creation: FULL capture (thunk/RTK Query actions included)
|
|
35
|
+
* and real time travel. To guarantee this ordering, make
|
|
36
|
+
* `import '@buoy-gg/redux';` the FIRST import of your app entry.
|
|
37
|
+
* 2. MIDDLEWARE — add `buoyReduxMiddleware` for explicit full capture.
|
|
38
|
+
* 3. PATCH — otherwise, `FloatingDevTools` binds the store at first mount by
|
|
39
|
+
* patching store.dispatch. Top-level dispatches only (thunk-internal
|
|
40
|
+
* actions bypass a dispatch patch — that's a Redux architecture fact).
|
|
41
|
+
*
|
|
42
|
+
* @example Advanced: Manual middleware (explicit full capture)
|
|
31
43
|
* ```tsx
|
|
32
|
-
* // For time-travel or custom options, you can optionally use middleware:
|
|
33
44
|
* import { buoyReduxMiddleware, withBuoyDevTools } from '@buoy-gg/redux';
|
|
34
45
|
*
|
|
35
46
|
* const store = configureStore({
|
|
@@ -39,21 +50,24 @@
|
|
|
39
50
|
* });
|
|
40
51
|
* ```
|
|
41
52
|
*/
|
|
53
|
+
import "./redux/utils/devtoolsHook";
|
|
42
54
|
export { reduxToolPreset, createReduxTool } from "./preset";
|
|
43
55
|
export { buoyReduxMiddleware, createBuoyReduxMiddleware, withBuoyDevTools, jumpToState, replayAction, BUOY_JUMP_TO_STATE, BUOY_REPLAY_ACTION, } from "./redux/utils/buoyReduxMiddleware";
|
|
44
|
-
export { instrumentStore, uninstrumentStore, isStoreInstrumented, isAutoInstrumentActive, getActiveStore, } from "./redux/utils/autoInstrument";
|
|
45
|
-
export type { AutoInstrumentOptions } from "./redux/utils/autoInstrument";
|
|
56
|
+
export { instrumentStore, uninstrumentStore, isStoreInstrumented, isAutoInstrumentActive, getActiveStore, getInstrumentationStatus, registerReduxStore, } from "./redux/utils/autoInstrument";
|
|
57
|
+
export type { AutoInstrumentOptions, InstrumentationStatus, CaptureMechanism, } from "./redux/utils/autoInstrument";
|
|
58
|
+
export { buoyDevtoolsEnhancer, claimDevtoolsHook, isDevtoolsHookClaimed, } from "./redux/utils/devtoolsHook";
|
|
46
59
|
export { createReduxHistoryAdapter, reduxHistoryAdapter, type ReduxHistoryAdapterOptions, } from "./redux/utils/createReduxHistoryAdapter";
|
|
47
60
|
export { reduxSyncAdapter } from "./redux/sync/reduxSyncAdapter";
|
|
48
61
|
export { useReduxActions } from "./redux/hooks/useReduxActions";
|
|
49
62
|
export type { UseReduxActionsResult } from "./redux/hooks/useReduxActions";
|
|
50
63
|
export { useAutoInstrumentRedux, useReduxAvailability, } from "./redux/hooks/useAutoInstrumentRedux";
|
|
51
64
|
export type { UseAutoInstrumentReduxOptions, UseAutoInstrumentReduxResult, } from "./redux/hooks/useAutoInstrumentRedux";
|
|
52
|
-
export { ReduxModal, FREE_TIER_ACTION_LIMIT } from "./redux/components/ReduxModal";
|
|
65
|
+
export { ReduxModal, ReduxToolView, FREE_TIER_ACTION_LIMIT, } from "./redux/components/ReduxModal";
|
|
53
66
|
export { ReduxActionItem } from "./redux/components/ReduxActionItem";
|
|
54
67
|
export { ReduxActionDetailView } from "./redux/components/ReduxActionDetailView";
|
|
55
68
|
export { ReduxActionDetailContent, ReduxActionDetailFooter, } from "./redux/components/ReduxActionDetailContent";
|
|
56
69
|
export { ReduxIcon } from "./redux/components/ReduxIcon";
|
|
70
|
+
export { ReduxAutoInstrument } from "./redux/components/ReduxAutoInstrument";
|
|
57
71
|
export type { ActionCategory, ActionMeta, ReduxAction, ReduxFilter, ReduxConnectorOptions, ReduxStoreMinimal, BuoyReduxMiddlewareOptions, } from "./redux/types";
|
|
58
72
|
/** @internal */
|
|
59
73
|
export { reduxActionStore } from "./redux/utils/reduxActionStore";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAKH,OAAO,4BAA4B,CAAC;AAKpC,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAK5D,OAAO,EACL,mBAAmB,EACnB,yBAAyB,EACzB,gBAAgB,EAChB,WAAW,EACX,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,mCAAmC,CAAC;AAK3C,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,sBAAsB,EACtB,cAAc,EACd,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,8BAA8B,CAAC;AACtC,YAAY,EACV,qBAAqB,EACrB,qBAAqB,EACrB,gBAAgB,GACjB,MAAM,8BAA8B,CAAC;AAItC,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,4BAA4B,CAAC;AAKpC,OAAO,EACL,yBAAyB,EACzB,mBAAmB,EACnB,KAAK,0BAA0B,GAChC,MAAM,yCAAyC,CAAC;AAKjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAKjE,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,YAAY,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAG3E,OAAO,EACL,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,sCAAsC,CAAC;AAC9C,YAAY,EACV,6BAA6B,EAC7B,4BAA4B,GAC7B,MAAM,sCAAsC,CAAC;AAO9C,OAAO,EACL,UAAU,EACV,aAAa,EACb,sBAAsB,GACvB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AACrE,OAAO,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,EACL,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,6CAA6C,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAGzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAK7E,YAAY,EACV,cAAc,EACd,UAAU,EACV,WAAW,EACX,WAAW,EACX,qBAAqB,EACrB,iBAAiB,EACjB,0BAA0B,GAC3B,MAAM,eAAe,CAAC;AAKvB,gBAAgB;AAChB,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Headless startup instrumentation. Auto-rendered by FloatingDevTools (same
|
|
3
|
+
* pattern as RouteTracker & the overlay tools) so the Redux store is bound
|
|
4
|
+
* from app mount — action log, MCP getState/dispatch, and desktop sync all
|
|
5
|
+
* work WITHOUT the user ever opening the Redux tool. Renders nothing.
|
|
6
|
+
*
|
|
7
|
+
* Safe anywhere: if react-redux is missing or there's no Provider above, the
|
|
8
|
+
* hook records the diagnostic and fails gracefully.
|
|
9
|
+
*/
|
|
10
|
+
export declare function ReduxAutoInstrument(): null;
|
|
11
|
+
//# sourceMappingURL=ReduxAutoInstrument.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReduxAutoInstrument.d.ts","sourceRoot":"","sources":["../../../../src/redux/components/ReduxAutoInstrument.tsx"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAG1C"}
|
|
@@ -7,12 +7,26 @@
|
|
|
7
7
|
/** Free tier limit for action history */
|
|
8
8
|
export declare const FREE_TIER_ACTION_LIMIT = 25;
|
|
9
9
|
interface ReduxModalProps {
|
|
10
|
+
/**
|
|
11
|
+
* Render as a header-bar + body (+ footer) stack instead of inside `JsModal`,
|
|
12
|
+
* for hosts that place tools in a panel rather than a modal (Buoy Desktop).
|
|
13
|
+
* Only the shell differs — header, screens and state are shared. See
|
|
14
|
+
* `ReduxToolView` at the bottom of this file.
|
|
15
|
+
*/
|
|
16
|
+
inline?: boolean;
|
|
10
17
|
visible: boolean;
|
|
11
18
|
onClose: () => void;
|
|
12
19
|
onBack?: () => void;
|
|
13
20
|
onMinimize?: (modalState: unknown) => void;
|
|
14
21
|
enableSharedModalDimensions?: boolean;
|
|
15
22
|
}
|
|
16
|
-
export declare function ReduxModal({ visible, onClose, onBack, onMinimize, enableSharedModalDimensions, }: ReduxModalProps): import("react").JSX.Element | null;
|
|
23
|
+
export declare function ReduxModal({ visible, onClose, onBack, onMinimize, enableSharedModalDimensions, inline, }: ReduxModalProps): import("react").JSX.Element | null;
|
|
24
|
+
/**
|
|
25
|
+
* The same Redux tool as `ReduxModal`, rendered INLINE for hosts that place
|
|
26
|
+
* tools in a panel rather than a modal. Exists so there is exactly one
|
|
27
|
+
* implementation — the desktop dashboard used to ship its own copy of this
|
|
28
|
+
* component's header, action list and detail router.
|
|
29
|
+
*/
|
|
30
|
+
export declare function ReduxToolView(props?: Omit<ReduxModalProps, "visible" | "onClose" | "inline">): import("react").JSX.Element;
|
|
17
31
|
export {};
|
|
18
32
|
//# sourceMappingURL=ReduxModal.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReduxModal.d.ts","sourceRoot":"","sources":["../../../../src/redux/components/ReduxModal.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AA2CH,yCAAyC;AACzC,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC,UAAU,eAAe;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3C,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC;AAgBD,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,OAAO,EACP,MAAM,EACN,UAAU,EACV,2BAAmC,
|
|
1
|
+
{"version":3,"file":"ReduxModal.d.ts","sourceRoot":"","sources":["../../../../src/redux/components/ReduxModal.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AA2CH,yCAAyC;AACzC,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC,UAAU,eAAe;IACvB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3C,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC;AAgBD,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,OAAO,EACP,MAAM,EACN,UAAU,EACV,2BAAmC,EACnC,MAAc,GACf,EAAE,eAAe,sCAwajB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,KAAK,GAAE,IAAI,CAAC,eAAe,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAM,+BAGpE"}
|
|
@@ -3,4 +3,5 @@ export { ReduxActionItem } from "./ReduxActionItem";
|
|
|
3
3
|
export { ReduxActionDetailView } from "./ReduxActionDetailView";
|
|
4
4
|
export { ReduxActionDetailContent, ReduxActionDetailFooter, } from "./ReduxActionDetailContent";
|
|
5
5
|
export { ReduxIcon } from "./ReduxIcon";
|
|
6
|
+
export { ReduxAutoInstrument } from "./ReduxAutoInstrument";
|
|
6
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/redux/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EACL,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/redux/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EACL,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC"}
|