@buoy-gg/redux 7.0.5 → 7.0.7
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 +38 -0
- package/lib/commonjs/redux/components/ReduxAutoInstrument.js +20 -0
- package/lib/commonjs/redux/components/ReduxModal.js +11 -1
- 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 +26 -3
- package/lib/module/redux/components/ReduxAutoInstrument.js +17 -0
- package/lib/module/redux/components/ReduxModal.js +11 -1
- 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 +18 -4
- 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.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
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ npm install @buoy-gg/core @buoy-gg/redux
|
|
|
14
14
|
|
|
15
15
|
## Quick start
|
|
16
16
|
|
|
17
|
-
Zero config. Your existing store works as-is — no middleware, no wrapper.
|
|
17
|
+
Zero config. Your existing store works as-is — no middleware, no wrapper. Buoy hooks the store at creation via the official Redux DevTools integration point (which Redux Toolkit enables by default), so actions are captured from your app's very first dispatch — including everything dispatched inside thunks and RTK Query.
|
|
18
18
|
|
|
19
19
|
```tsx
|
|
20
20
|
import { configureStore } from '@reduxjs/toolkit';
|
|
@@ -33,9 +33,28 @@ export default function App() {
|
|
|
33
33
|
}
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
### How capture works (and how to guarantee full capture)
|
|
37
|
+
|
|
38
|
+
Buoy binds to your store the earliest way available, in this order:
|
|
39
|
+
|
|
40
|
+
1. **Store-creation hook (default, full capture)** — importing `@buoy-gg/redux` claims the Redux DevTools global that RTK checks by default. If the package loads before your store module, Buoy is inside the store from creation: every action (thunk-internal, RTK Query) is captured and time travel is real. To **guarantee** this ordering, make it the first import of your app entry:
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
// index.js — first line
|
|
44
|
+
import '@buoy-gg/redux';
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
2. **Middleware (explicit full capture)** — one line if you prefer being explicit:
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
middleware: (getDefault) => getDefault().concat(buoyReduxMiddleware),
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
3. **Startup fallback** — if your store was created before Buoy loaded, `FloatingDevTools` binds it automatically at app mount. Top-level dispatches are captured from startup; actions dispatched *inside* thunks/middleware aren't visible to this mode (the tool tells you when it's in it).
|
|
54
|
+
|
|
36
55
|
### Optional: full time travel
|
|
37
56
|
|
|
38
|
-
|
|
57
|
+
Time travel works out of the box on path 1. On the middleware path, wrap your reducer:
|
|
39
58
|
|
|
40
59
|
```tsx
|
|
41
60
|
import { withBuoyDevTools } from '@buoy-gg/redux';
|
package/lib/commonjs/index.js
CHANGED
|
@@ -45,6 +45,12 @@ Object.defineProperty(exports, "ReduxActionItem", {
|
|
|
45
45
|
return _ReduxActionItem.ReduxActionItem;
|
|
46
46
|
}
|
|
47
47
|
});
|
|
48
|
+
Object.defineProperty(exports, "ReduxAutoInstrument", {
|
|
49
|
+
enumerable: true,
|
|
50
|
+
get: function () {
|
|
51
|
+
return _ReduxAutoInstrument.ReduxAutoInstrument;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
48
54
|
Object.defineProperty(exports, "ReduxIcon", {
|
|
49
55
|
enumerable: true,
|
|
50
56
|
get: function () {
|
|
@@ -63,12 +69,24 @@ Object.defineProperty(exports, "ReduxToolView", {
|
|
|
63
69
|
return _ReduxModal.ReduxToolView;
|
|
64
70
|
}
|
|
65
71
|
});
|
|
72
|
+
Object.defineProperty(exports, "buoyDevtoolsEnhancer", {
|
|
73
|
+
enumerable: true,
|
|
74
|
+
get: function () {
|
|
75
|
+
return _devtoolsHook.buoyDevtoolsEnhancer;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
66
78
|
Object.defineProperty(exports, "buoyReduxMiddleware", {
|
|
67
79
|
enumerable: true,
|
|
68
80
|
get: function () {
|
|
69
81
|
return _buoyReduxMiddleware.buoyReduxMiddleware;
|
|
70
82
|
}
|
|
71
83
|
});
|
|
84
|
+
Object.defineProperty(exports, "claimDevtoolsHook", {
|
|
85
|
+
enumerable: true,
|
|
86
|
+
get: function () {
|
|
87
|
+
return _devtoolsHook.claimDevtoolsHook;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
72
90
|
Object.defineProperty(exports, "createBuoyReduxMiddleware", {
|
|
73
91
|
enumerable: true,
|
|
74
92
|
get: function () {
|
|
@@ -93,6 +111,12 @@ Object.defineProperty(exports, "getActiveStore", {
|
|
|
93
111
|
return _autoInstrument.getActiveStore;
|
|
94
112
|
}
|
|
95
113
|
});
|
|
114
|
+
Object.defineProperty(exports, "getInstrumentationStatus", {
|
|
115
|
+
enumerable: true,
|
|
116
|
+
get: function () {
|
|
117
|
+
return _autoInstrument.getInstrumentationStatus;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
96
120
|
Object.defineProperty(exports, "instrumentStore", {
|
|
97
121
|
enumerable: true,
|
|
98
122
|
get: function () {
|
|
@@ -105,6 +129,12 @@ Object.defineProperty(exports, "isAutoInstrumentActive", {
|
|
|
105
129
|
return _autoInstrument.isAutoInstrumentActive;
|
|
106
130
|
}
|
|
107
131
|
});
|
|
132
|
+
Object.defineProperty(exports, "isDevtoolsHookClaimed", {
|
|
133
|
+
enumerable: true,
|
|
134
|
+
get: function () {
|
|
135
|
+
return _devtoolsHook.isDevtoolsHookClaimed;
|
|
136
|
+
}
|
|
137
|
+
});
|
|
108
138
|
Object.defineProperty(exports, "isStoreInstrumented", {
|
|
109
139
|
enumerable: true,
|
|
110
140
|
get: function () {
|
|
@@ -141,6 +171,12 @@ Object.defineProperty(exports, "reduxToolPreset", {
|
|
|
141
171
|
return _preset.reduxToolPreset;
|
|
142
172
|
}
|
|
143
173
|
});
|
|
174
|
+
Object.defineProperty(exports, "registerReduxStore", {
|
|
175
|
+
enumerable: true,
|
|
176
|
+
get: function () {
|
|
177
|
+
return _autoInstrument.registerReduxStore;
|
|
178
|
+
}
|
|
179
|
+
});
|
|
144
180
|
Object.defineProperty(exports, "replayAction", {
|
|
145
181
|
enumerable: true,
|
|
146
182
|
get: function () {
|
|
@@ -177,6 +213,7 @@ Object.defineProperty(exports, "withBuoyDevTools", {
|
|
|
177
213
|
return _buoyReduxMiddleware.withBuoyDevTools;
|
|
178
214
|
}
|
|
179
215
|
});
|
|
216
|
+
var _devtoolsHook = require("./redux/utils/devtoolsHook");
|
|
180
217
|
var _preset = require("./preset");
|
|
181
218
|
var _buoyReduxMiddleware = require("./redux/utils/buoyReduxMiddleware");
|
|
182
219
|
var _autoInstrument = require("./redux/utils/autoInstrument");
|
|
@@ -189,4 +226,5 @@ var _ReduxActionItem = require("./redux/components/ReduxActionItem");
|
|
|
189
226
|
var _ReduxActionDetailView = require("./redux/components/ReduxActionDetailView");
|
|
190
227
|
var _ReduxActionDetailContent = require("./redux/components/ReduxActionDetailContent");
|
|
191
228
|
var _ReduxIcon = require("./redux/components/ReduxIcon");
|
|
229
|
+
var _ReduxAutoInstrument = require("./redux/components/ReduxAutoInstrument");
|
|
192
230
|
var _reduxActionStore = require("./redux/utils/reduxActionStore");
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.ReduxAutoInstrument = ReduxAutoInstrument;
|
|
7
|
+
var _useAutoInstrumentRedux = require("../hooks/useAutoInstrumentRedux");
|
|
8
|
+
/**
|
|
9
|
+
* Headless startup instrumentation. Auto-rendered by FloatingDevTools (same
|
|
10
|
+
* pattern as RouteTracker & the overlay tools) so the Redux store is bound
|
|
11
|
+
* from app mount — action log, MCP getState/dispatch, and desktop sync all
|
|
12
|
+
* work WITHOUT the user ever opening the Redux tool. Renders nothing.
|
|
13
|
+
*
|
|
14
|
+
* Safe anywhere: if react-redux is missing or there's no Provider above, the
|
|
15
|
+
* hook records the diagnostic and fails gracefully.
|
|
16
|
+
*/
|
|
17
|
+
function ReduxAutoInstrument() {
|
|
18
|
+
(0, _useAutoInstrumentRedux.useAutoInstrumentRedux)();
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
@@ -58,7 +58,8 @@ function ReduxModal({
|
|
|
58
58
|
isInstrumented,
|
|
59
59
|
isLoading: isInstrumentLoading,
|
|
60
60
|
error: instrumentError,
|
|
61
|
-
isReactReduxAvailable
|
|
61
|
+
isReactReduxAvailable,
|
|
62
|
+
captureMechanism
|
|
62
63
|
} = (0, _useAutoInstrumentRedux.useAutoInstrumentRedux)();
|
|
63
64
|
const {
|
|
64
65
|
filteredActions,
|
|
@@ -314,6 +315,15 @@ function ReduxModal({
|
|
|
314
315
|
style: styles.disabledText,
|
|
315
316
|
children: "Action capture is disabled"
|
|
316
317
|
})]
|
|
318
|
+
}), captureMechanism === "patch" && !_reduxActionStore.reduxActionStore.isCaptureSuppressed() && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
319
|
+
style: styles.disabledBanner,
|
|
320
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.AlertTriangle, {
|
|
321
|
+
size: 14,
|
|
322
|
+
color: _sharedUi.macOSColors.semantic.warning
|
|
323
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
324
|
+
style: styles.disabledText,
|
|
325
|
+
children: "Top-level capture only \u2014 thunk/RTK Query internals aren't visible. Put import '@buoy-gg/redux' first in your app entry (or add buoyReduxMiddleware) for full capture."
|
|
326
|
+
})]
|
|
317
327
|
}), hasLockedActions && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
318
328
|
style: styles.lockedBanner,
|
|
319
329
|
onPress: handleUpgradePress,
|
|
@@ -33,6 +33,12 @@ Object.defineProperty(exports, "ReduxActionItem", {
|
|
|
33
33
|
return _ReduxActionItem.ReduxActionItem;
|
|
34
34
|
}
|
|
35
35
|
});
|
|
36
|
+
Object.defineProperty(exports, "ReduxAutoInstrument", {
|
|
37
|
+
enumerable: true,
|
|
38
|
+
get: function () {
|
|
39
|
+
return _ReduxAutoInstrument.ReduxAutoInstrument;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
36
42
|
Object.defineProperty(exports, "ReduxIcon", {
|
|
37
43
|
enumerable: true,
|
|
38
44
|
get: function () {
|
|
@@ -49,4 +55,5 @@ var _ReduxModal = require("./ReduxModal");
|
|
|
49
55
|
var _ReduxActionItem = require("./ReduxActionItem");
|
|
50
56
|
var _ReduxActionDetailView = require("./ReduxActionDetailView");
|
|
51
57
|
var _ReduxActionDetailContent = require("./ReduxActionDetailContent");
|
|
52
|
-
var _ReduxIcon = require("./ReduxIcon");
|
|
58
|
+
var _ReduxIcon = require("./ReduxIcon");
|
|
59
|
+
var _ReduxAutoInstrument = require("./ReduxAutoInstrument");
|
|
@@ -7,29 +7,21 @@ exports.useAutoInstrumentRedux = useAutoInstrumentRedux;
|
|
|
7
7
|
exports.useReduxAvailability = useReduxAvailability;
|
|
8
8
|
var _react = require("react");
|
|
9
9
|
var _autoInstrument = require("../utils/autoInstrument");
|
|
10
|
+
var _activeStoreRegistry = require("../utils/activeStoreRegistry");
|
|
10
11
|
/**
|
|
11
12
|
* React hook for auto-instrumenting Redux stores
|
|
12
13
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
14
|
+
* Mounted for the app's lifetime by FloatingDevTools (via ReduxAutoInstrument)
|
|
15
|
+
* and also called by the Redux modal. Binds Buoy to the store from react-redux
|
|
16
|
+
* context:
|
|
17
|
+
* - If the store was created through the devtools-hook enhancer, capture is
|
|
18
|
+
* already live — this just keeps the registry pointed at it.
|
|
19
|
+
* - Otherwise it applies the dispatch-patch fallback (top-level-only capture).
|
|
20
|
+
* - If react-redux is missing or there's no Provider above, it fails
|
|
21
|
+
* gracefully and records WHY (surfaced by the sync adapter / MCP).
|
|
15
22
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* - If successful, it instruments the store automatically
|
|
19
|
-
* - If react-redux is not available or not in a Provider, it fails gracefully
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* ```tsx
|
|
23
|
-
* function ReduxDevTools() {
|
|
24
|
-
* const { isInstrumented, error } = useAutoInstrumentRedux();
|
|
25
|
-
*
|
|
26
|
-
* if (error) {
|
|
27
|
-
* return <Text>Redux not available: {error}</Text>;
|
|
28
|
-
* }
|
|
29
|
-
*
|
|
30
|
-
* return <ReduxActionList />;
|
|
31
|
-
* }
|
|
32
|
-
* ```
|
|
23
|
+
* Re-instruments when the store INSTANCE changes (hosts that rebuild their
|
|
24
|
+
* store at runtime), so the registry never pins a dead store.
|
|
33
25
|
*/
|
|
34
26
|
|
|
35
27
|
// ============================================
|
|
@@ -59,19 +51,16 @@ function getReactReduxUseStore() {
|
|
|
59
51
|
// Main Hook
|
|
60
52
|
// ============================================
|
|
61
53
|
|
|
62
|
-
/**
|
|
63
|
-
* Hook to automatically instrument Redux store for DevTools
|
|
64
|
-
*
|
|
65
|
-
* This provides zero-config Redux action capture by:
|
|
66
|
-
* 1. Using react-redux's useStore() to get the Redux store
|
|
67
|
-
* 2. Automatically patching store.dispatch to capture actions
|
|
68
|
-
* 3. Cleaning up when the component unmounts
|
|
69
|
-
*/
|
|
70
54
|
function useAutoInstrumentRedux(options = {}) {
|
|
71
55
|
const {
|
|
72
|
-
autoStart = true
|
|
73
|
-
...instrumentOptions
|
|
56
|
+
autoStart = true
|
|
74
57
|
} = options;
|
|
58
|
+
|
|
59
|
+
// Options live in a ref: this hook is app-lifetime-mounted, and a fresh
|
|
60
|
+
// `{...rest}` object every render would churn startInstrumentation's
|
|
61
|
+
// identity and re-run the effect on every render.
|
|
62
|
+
const optionsRef = (0, _react.useRef)(options);
|
|
63
|
+
optionsRef.current = options;
|
|
75
64
|
const [isInstrumented, setIsInstrumented] = (0, _react.useState)(false);
|
|
76
65
|
const [isLoading, setIsLoading] = (0, _react.useState)(true);
|
|
77
66
|
const [error, setError] = (0, _react.useState)(null);
|
|
@@ -86,37 +75,35 @@ function useAutoInstrumentRedux(options = {}) {
|
|
|
86
75
|
// This must be called unconditionally due to hooks rules
|
|
87
76
|
let reduxStore = null;
|
|
88
77
|
let storeError = null;
|
|
78
|
+
let storeDiagnostic = null;
|
|
89
79
|
if (useStoreHook) {
|
|
90
80
|
try {
|
|
91
81
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
92
82
|
reduxStore = useStoreHook();
|
|
93
83
|
} catch (e) {
|
|
94
84
|
// Not inside a Provider or other error
|
|
85
|
+
storeDiagnostic = "no-provider";
|
|
95
86
|
storeError = e instanceof Error ? e.message : "Failed to access Redux store. Make sure your app is wrapped in a Redux Provider.";
|
|
96
87
|
}
|
|
97
88
|
} else {
|
|
89
|
+
storeDiagnostic = "no-react-redux";
|
|
98
90
|
storeError = "react-redux is not installed or not available";
|
|
99
91
|
}
|
|
100
92
|
|
|
101
93
|
// Function to start instrumentation
|
|
102
94
|
const startInstrumentation = (0, _react.useCallback)(() => {
|
|
103
95
|
if (!reduxStore) {
|
|
96
|
+
// Record the failure reason for non-React surfaces (sync adapter/MCP).
|
|
97
|
+
(0, _activeStoreRegistry.setReduxDiagnostic)(storeDiagnostic);
|
|
104
98
|
setError(storeError || "No Redux store available");
|
|
105
99
|
setIsLoading(false);
|
|
106
100
|
return;
|
|
107
101
|
}
|
|
108
|
-
|
|
109
|
-
// Check if already instrumented
|
|
110
|
-
if ((0, _autoInstrument.isStoreInstrumented)(reduxStore)) {
|
|
111
|
-
setIsInstrumented(true);
|
|
112
|
-
setStore(reduxStore);
|
|
113
|
-
setIsLoading(false);
|
|
114
|
-
setError(null);
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
102
|
try {
|
|
118
|
-
// Instrument the store
|
|
119
|
-
|
|
103
|
+
// Instrument the store. Idempotent per store: enhancer-created and
|
|
104
|
+
// already-patched stores just get (re)registered, so a store SWAP at
|
|
105
|
+
// runtime re-binds the registry to the new instance.
|
|
106
|
+
const cleanup = (0, _autoInstrument.instrumentStore)(reduxStore, optionsRef.current);
|
|
120
107
|
cleanupRef.current = cleanup;
|
|
121
108
|
setIsInstrumented(true);
|
|
122
109
|
setStore(reduxStore);
|
|
@@ -127,7 +114,8 @@ function useAutoInstrumentRedux(options = {}) {
|
|
|
127
114
|
} finally {
|
|
128
115
|
setIsLoading(false);
|
|
129
116
|
}
|
|
130
|
-
|
|
117
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- storeError/storeDiagnostic derive from reduxStore
|
|
118
|
+
}, [reduxStore]);
|
|
131
119
|
|
|
132
120
|
// Function to stop instrumentation
|
|
133
121
|
const stopInstrumentation = (0, _react.useCallback)(() => {
|
|
@@ -142,7 +130,8 @@ function useAutoInstrumentRedux(options = {}) {
|
|
|
142
130
|
setStore(null);
|
|
143
131
|
}, [store]);
|
|
144
132
|
|
|
145
|
-
// Auto-start instrumentation on mount
|
|
133
|
+
// Auto-start instrumentation on mount, and again whenever the store
|
|
134
|
+
// INSTANCE changes (hosts that rebuild their store re-point the registry).
|
|
146
135
|
(0, _react.useEffect)(() => {
|
|
147
136
|
if (autoStart) {
|
|
148
137
|
startInstrumentation();
|
|
@@ -163,6 +152,7 @@ function useAutoInstrumentRedux(options = {}) {
|
|
|
163
152
|
isLoading,
|
|
164
153
|
error,
|
|
165
154
|
isReactReduxAvailable,
|
|
155
|
+
captureMechanism: (0, _autoInstrument.getInstrumentationStatus)().mechanism,
|
|
166
156
|
startInstrumentation,
|
|
167
157
|
stopInstrumentation,
|
|
168
158
|
store
|
|
@@ -11,14 +11,31 @@ function topLevelKeys(state) {
|
|
|
11
11
|
return state && typeof state === "object" && !Array.isArray(state) ? Object.keys(state) : [];
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Why there's no bound store, in a shape remote drivers (MCP/desktop) can turn
|
|
16
|
+
* into a SPECIFIC remedy instead of a generic "check your setup".
|
|
17
|
+
*/
|
|
18
|
+
function unavailable() {
|
|
19
|
+
const status = (0, _autoInstrument.getInstrumentationStatus)();
|
|
20
|
+
const reason = status.diagnostic ?? "not-instrumented";
|
|
21
|
+
return {
|
|
22
|
+
available: false,
|
|
23
|
+
slices: [],
|
|
24
|
+
/** 'no-react-redux' | 'no-provider' | 'not-instrumented' */
|
|
25
|
+
reason
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
14
29
|
/**
|
|
15
30
|
* Sync adapter for the redux tool, consumed by @buoy-gg/external-sync's
|
|
16
31
|
* `useExternalSync` (structurally matches its ToolSyncAdapter interface so
|
|
17
32
|
* this package doesn't need a dependency on it).
|
|
18
33
|
*
|
|
19
|
-
* Actions are captured by the
|
|
20
|
-
*
|
|
21
|
-
*
|
|
34
|
+
* Actions are captured from app startup — by the store-creation enhancer or
|
|
35
|
+
* the explicit middleware (full capture), or by the dispatch-patch fallback
|
|
36
|
+
* mounted with FloatingDevTools (top-level-only) — independent of whether a
|
|
37
|
+
* dashboard is watching. Subscribing here only streams what's already
|
|
38
|
+
* recorded.
|
|
22
39
|
*
|
|
23
40
|
* `getState`/`dispatch` read & drive the live instrumented store (via
|
|
24
41
|
* getActiveStore()) so a remote driver (MCP/LLM) can inspect current state and
|
|
@@ -43,15 +60,16 @@ const reduxSyncAdapter = exports.reduxSyncAdapter = {
|
|
|
43
60
|
} = params ?? {};
|
|
44
61
|
const store = (0, _autoInstrument.getActiveStore)();
|
|
45
62
|
if (!store) {
|
|
46
|
-
return
|
|
47
|
-
available: false,
|
|
48
|
-
slices: []
|
|
49
|
-
};
|
|
63
|
+
return unavailable();
|
|
50
64
|
}
|
|
51
65
|
const state = store.getState();
|
|
66
|
+
const status = (0, _autoInstrument.getInstrumentationStatus)();
|
|
52
67
|
return {
|
|
53
68
|
available: true,
|
|
54
69
|
slices: topLevelKeys(state),
|
|
70
|
+
/** 'full' | 'top-level-only' — what the live capture mechanism sees. */
|
|
71
|
+
capture: status.capture,
|
|
72
|
+
mechanism: status.mechanism,
|
|
55
73
|
...(includeValues ? {
|
|
56
74
|
state
|
|
57
75
|
} : {})
|
|
@@ -65,7 +83,8 @@ const reduxSyncAdapter = exports.reduxSyncAdapter = {
|
|
|
65
83
|
const store = (0, _autoInstrument.getActiveStore)();
|
|
66
84
|
if (!store) {
|
|
67
85
|
return {
|
|
68
|
-
dispatched: false
|
|
86
|
+
dispatched: false,
|
|
87
|
+
...unavailable()
|
|
69
88
|
};
|
|
70
89
|
}
|
|
71
90
|
store.dispatch(action);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.ENHANCER_SYMBOL = void 0;
|
|
7
|
+
exports.getActiveStore = getActiveStore;
|
|
8
|
+
exports.getCaptureMechanism = getCaptureMechanism;
|
|
9
|
+
exports.getInstrumentationStatus = getInstrumentationStatus;
|
|
10
|
+
exports.registerActiveStore = registerActiveStore;
|
|
11
|
+
exports.setReduxDiagnostic = setReduxDiagnostic;
|
|
12
|
+
exports.unregisterActiveStore = unregisterActiveStore;
|
|
13
|
+
/**
|
|
14
|
+
* Shared registry for the one live Redux store Buoy is bound to, plus how it
|
|
15
|
+
* got bound (the capture mechanism) and why binding failed when it did.
|
|
16
|
+
*
|
|
17
|
+
* Lives in its own module so the three capture paths (devtools-hook enhancer,
|
|
18
|
+
* explicit middleware, dispatch-patch fallback) and the sync adapter can all
|
|
19
|
+
* import it without cycles.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** The minimum store surface Buoy needs (middleware storeApi satisfies this). */
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* How the store got instrumented:
|
|
26
|
+
* - `enhancer` — store-creation-time enhancer via the Redux DevTools global.
|
|
27
|
+
* FULL capture (thunk-internal & RTK Query actions included).
|
|
28
|
+
* - `middleware` — user-added buoyReduxMiddleware. FULL capture.
|
|
29
|
+
* - `patch` — store.dispatch patched after creation (auto-instrumentation
|
|
30
|
+
* fallback). TOP-LEVEL-ONLY capture: actions dispatched from
|
|
31
|
+
* inside middleware/thunks bypass the patch.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/** Why there is no bound store, when there isn't one. */
|
|
35
|
+
|
|
36
|
+
/** Marks stores created through the Buoy devtools-hook enhancer. */
|
|
37
|
+
const ENHANCER_SYMBOL = exports.ENHANCER_SYMBOL = Symbol.for("@@buoy/enhancerInstrumented");
|
|
38
|
+
let activeStore = null;
|
|
39
|
+
let mechanism = null;
|
|
40
|
+
let diagnostic = null;
|
|
41
|
+
function registerActiveStore(store, mech) {
|
|
42
|
+
activeStore = store;
|
|
43
|
+
mechanism = mech;
|
|
44
|
+
diagnostic = null;
|
|
45
|
+
}
|
|
46
|
+
function unregisterActiveStore(store) {
|
|
47
|
+
if (activeStore === store) {
|
|
48
|
+
activeStore = null;
|
|
49
|
+
mechanism = null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function getActiveStore() {
|
|
53
|
+
return activeStore;
|
|
54
|
+
}
|
|
55
|
+
function getCaptureMechanism() {
|
|
56
|
+
return mechanism;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Recorded by the auto-instrument hook so non-React surfaces (sync adapter,
|
|
61
|
+
* MCP) can report WHY there's no store instead of a generic failure.
|
|
62
|
+
*/
|
|
63
|
+
function setReduxDiagnostic(d) {
|
|
64
|
+
diagnostic = d;
|
|
65
|
+
}
|
|
66
|
+
function getInstrumentationStatus() {
|
|
67
|
+
return {
|
|
68
|
+
mechanism,
|
|
69
|
+
capture: mechanism === "enhancer" || mechanism === "middleware" ? "full" : mechanism === "patch" ? "top-level-only" : "none",
|
|
70
|
+
hasStore: activeStore !== null,
|
|
71
|
+
diagnostic
|
|
72
|
+
};
|
|
73
|
+
}
|