@buoy-gg/redux 7.0.35 → 7.0.38
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 +37 -9
- package/lib/commonjs/index.js +1 -256
- package/lib/commonjs/preset.js +1 -90
- package/lib/commonjs/redux/components/ReduxActionButton.js +1 -131
- package/lib/commonjs/redux/components/ReduxActionDetailContent.js +1 -437
- package/lib/commonjs/redux/components/ReduxActionDetailView.js +1 -401
- package/lib/commonjs/redux/components/ReduxActionInfoView.js +1 -836
- package/lib/commonjs/redux/components/ReduxActionItem.js +1 -334
- package/lib/commonjs/redux/components/ReduxAutoInstrument.js +1 -20
- package/lib/commonjs/redux/components/ReduxDetailViewToggle.js +1 -50
- package/lib/commonjs/redux/components/ReduxIcon.js +1 -18
- package/lib/commonjs/redux/components/ReduxModal.js +1 -703
- package/lib/commonjs/redux/hooks/useAutoInstrumentRedux.js +1 -187
- package/lib/commonjs/redux/hooks/useReduxActions.js +1 -58
- package/lib/commonjs/redux/sync/reduxSyncAdapter.js +1 -191
- package/lib/commonjs/redux/utils/activeStoreRegistry.js +1 -104
- package/lib/commonjs/redux/utils/autoInstrument.js +1 -231
- package/lib/commonjs/redux/utils/buoyReduxMiddleware.js +1 -202
- package/lib/commonjs/redux/utils/createReduxHistoryAdapter.js +1 -146
- package/lib/commonjs/redux/utils/devtoolsHook.js +1 -152
- package/lib/commonjs/redux/utils/reduxActionStore.js +1 -473
- package/lib/commonjs/redux/utils/snapshotProvider.js +1 -158
- package/lib/commonjs/redux/utils/testIDs.js +1 -75
- package/lib/module/index.js +1 -125
- package/lib/module/preset.js +1 -87
- package/lib/module/redux/components/ReduxActionButton.js +1 -128
- package/lib/module/redux/components/ReduxActionDetailContent.js +1 -433
- package/lib/module/redux/components/ReduxActionDetailView.js +1 -397
- package/lib/module/redux/components/ReduxActionInfoView.js +1 -831
- package/lib/module/redux/components/ReduxActionItem.js +1 -330
- package/lib/module/redux/components/ReduxAutoInstrument.js +1 -17
- package/lib/module/redux/components/ReduxDetailViewToggle.js +1 -45
- package/lib/module/redux/components/ReduxIcon.js +1 -8
- package/lib/module/redux/components/ReduxModal.js +1 -698
- package/lib/module/redux/hooks/useAutoInstrumentRedux.js +1 -183
- package/lib/module/redux/hooks/useReduxActions.js +1 -54
- package/lib/module/redux/sync/reduxSyncAdapter.js +1 -188
- package/lib/module/redux/utils/activeStoreRegistry.js +1 -92
- package/lib/module/redux/utils/autoInstrument.js +1 -201
- package/lib/module/redux/utils/buoyReduxMiddleware.js +1 -188
- package/lib/module/redux/utils/createReduxHistoryAdapter.js +1 -142
- package/lib/module/redux/utils/devtoolsHook.js +1 -145
- package/lib/module/redux/utils/reduxActionStore.js +1 -465
- package/lib/module/redux/utils/snapshotProvider.js +1 -157
- package/lib/module/redux/utils/testIDs.js +1 -68
- package/package.json +5 -5
- 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/ReduxAutoInstrument.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/hooks/useAutoInstrumentRedux.d.ts.map +0 -1
- package/lib/typescript/redux/hooks/useReduxActions.d.ts.map +0 -1
- package/lib/typescript/redux/sync/reduxSyncAdapter.d.ts.map +0 -1
- package/lib/typescript/redux/types/index.d.ts.map +0 -1
- package/lib/typescript/redux/utils/activeStoreRegistry.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/devtoolsHook.d.ts.map +0 -1
- package/lib/typescript/redux/utils/reduxActionStore.d.ts.map +0 -1
- package/lib/typescript/redux/utils/snapshotProvider.d.ts.map +0 -1
- package/lib/typescript/redux/utils/testIDs.d.ts.map +0 -1
|
@@ -1,183 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* React hook for auto-instrumenting Redux stores
|
|
5
|
-
*
|
|
6
|
-
* Mounted for the app's lifetime by FloatingDevTools (via ReduxAutoInstrument)
|
|
7
|
-
* and also called by the Redux modal. Binds Buoy to the store from react-redux
|
|
8
|
-
* context:
|
|
9
|
-
* - If the store was created through the devtools-hook enhancer, capture is
|
|
10
|
-
* already live — this just keeps the registry pointed at it.
|
|
11
|
-
* - Otherwise it applies the dispatch-patch fallback (top-level-only capture).
|
|
12
|
-
* - If react-redux is missing or there's no Provider above, it fails
|
|
13
|
-
* gracefully and records WHY (surfaced by the sync adapter / MCP).
|
|
14
|
-
*
|
|
15
|
-
* Re-instruments when the store INSTANCE changes (hosts that rebuild their
|
|
16
|
-
* store at runtime), so the registry never pins a dead store.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import { useEffect, useState, useRef, useCallback } from "react";
|
|
20
|
-
import { instrumentStore, uninstrumentStore, getInstrumentationStatus } from "../utils/autoInstrument";
|
|
21
|
-
import { setReduxDiagnostic } from "../utils/activeStoreRegistry";
|
|
22
|
-
|
|
23
|
-
// ============================================
|
|
24
|
-
// Safe Module Loading
|
|
25
|
-
// ============================================
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Safely attempt to load react-redux and get useStore hook
|
|
29
|
-
*/
|
|
30
|
-
function getReactReduxUseStore() {
|
|
31
|
-
try {
|
|
32
|
-
// Dynamic require to handle cases where react-redux isn't installed
|
|
33
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
34
|
-
const reactRedux = require("react-redux");
|
|
35
|
-
return reactRedux.useStore;
|
|
36
|
-
} catch {
|
|
37
|
-
// react-redux not available
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// ============================================
|
|
43
|
-
// Hook Types
|
|
44
|
-
// ============================================
|
|
45
|
-
|
|
46
|
-
// ============================================
|
|
47
|
-
// Main Hook
|
|
48
|
-
// ============================================
|
|
49
|
-
|
|
50
|
-
export function useAutoInstrumentRedux(options = {}) {
|
|
51
|
-
const {
|
|
52
|
-
autoStart = true
|
|
53
|
-
} = options;
|
|
54
|
-
|
|
55
|
-
// Options live in a ref: this hook is app-lifetime-mounted, and a fresh
|
|
56
|
-
// `{...rest}` object every render would churn startInstrumentation's
|
|
57
|
-
// identity and re-run the effect on every render.
|
|
58
|
-
const optionsRef = useRef(options);
|
|
59
|
-
optionsRef.current = options;
|
|
60
|
-
const [isInstrumented, setIsInstrumented] = useState(false);
|
|
61
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
62
|
-
const [error, setError] = useState(null);
|
|
63
|
-
const [store, setStore] = useState(null);
|
|
64
|
-
const cleanupRef = useRef(null);
|
|
65
|
-
|
|
66
|
-
// Check if react-redux is available
|
|
67
|
-
const useStoreHook = getReactReduxUseStore();
|
|
68
|
-
const isReactReduxAvailable = useStoreHook !== null;
|
|
69
|
-
|
|
70
|
-
// Try to get the store using react-redux's useStore
|
|
71
|
-
// This must be called unconditionally due to hooks rules
|
|
72
|
-
let reduxStore = null;
|
|
73
|
-
let storeError = null;
|
|
74
|
-
let storeDiagnostic = null;
|
|
75
|
-
if (useStoreHook) {
|
|
76
|
-
try {
|
|
77
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
78
|
-
reduxStore = useStoreHook();
|
|
79
|
-
} catch (e) {
|
|
80
|
-
// Not inside a Provider or other error
|
|
81
|
-
storeDiagnostic = "no-provider";
|
|
82
|
-
storeError = e instanceof Error ? e.message : "Failed to access Redux store. Make sure your app is wrapped in a Redux Provider.";
|
|
83
|
-
}
|
|
84
|
-
} else {
|
|
85
|
-
storeDiagnostic = "no-react-redux";
|
|
86
|
-
storeError = "react-redux is not installed or not available";
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Function to start instrumentation
|
|
90
|
-
const startInstrumentation = useCallback(() => {
|
|
91
|
-
if (!reduxStore) {
|
|
92
|
-
// Record the failure reason for non-React surfaces (sync adapter/MCP).
|
|
93
|
-
setReduxDiagnostic(storeDiagnostic);
|
|
94
|
-
setError(storeError || "No Redux store available");
|
|
95
|
-
setIsLoading(false);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
try {
|
|
99
|
-
// Instrument the store. Idempotent per store: enhancer-created and
|
|
100
|
-
// already-patched stores just get (re)registered, so a store SWAP at
|
|
101
|
-
// runtime re-binds the registry to the new instance.
|
|
102
|
-
const cleanup = instrumentStore(reduxStore, optionsRef.current);
|
|
103
|
-
cleanupRef.current = cleanup;
|
|
104
|
-
setIsInstrumented(true);
|
|
105
|
-
setStore(reduxStore);
|
|
106
|
-
setError(null);
|
|
107
|
-
} catch (e) {
|
|
108
|
-
setError(e instanceof Error ? e.message : "Failed to instrument Redux store");
|
|
109
|
-
setIsInstrumented(false);
|
|
110
|
-
} finally {
|
|
111
|
-
setIsLoading(false);
|
|
112
|
-
}
|
|
113
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps -- storeError/storeDiagnostic derive from reduxStore
|
|
114
|
-
}, [reduxStore]);
|
|
115
|
-
|
|
116
|
-
// Function to stop instrumentation
|
|
117
|
-
const stopInstrumentation = useCallback(() => {
|
|
118
|
-
if (cleanupRef.current) {
|
|
119
|
-
cleanupRef.current();
|
|
120
|
-
cleanupRef.current = null;
|
|
121
|
-
}
|
|
122
|
-
if (store) {
|
|
123
|
-
uninstrumentStore(store);
|
|
124
|
-
}
|
|
125
|
-
setIsInstrumented(false);
|
|
126
|
-
setStore(null);
|
|
127
|
-
}, [store]);
|
|
128
|
-
|
|
129
|
-
// Auto-start instrumentation on mount, and again whenever the store
|
|
130
|
-
// INSTANCE changes (hosts that rebuild their store re-point the registry).
|
|
131
|
-
useEffect(() => {
|
|
132
|
-
if (autoStart) {
|
|
133
|
-
startInstrumentation();
|
|
134
|
-
} else {
|
|
135
|
-
setIsLoading(false);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Cleanup on unmount
|
|
139
|
-
return () => {
|
|
140
|
-
// Note: We intentionally DON'T uninstrument on unmount
|
|
141
|
-
// This allows actions to continue being captured even when
|
|
142
|
-
// the DevTools modal is closed. The instrumentation persists
|
|
143
|
-
// for the lifetime of the app, similar to network/storage tools.
|
|
144
|
-
};
|
|
145
|
-
}, [autoStart, startInstrumentation]);
|
|
146
|
-
return {
|
|
147
|
-
isInstrumented,
|
|
148
|
-
isLoading,
|
|
149
|
-
error,
|
|
150
|
-
isReactReduxAvailable,
|
|
151
|
-
captureMechanism: getInstrumentationStatus().mechanism,
|
|
152
|
-
startInstrumentation,
|
|
153
|
-
stopInstrumentation,
|
|
154
|
-
store
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Lightweight hook to just check if Redux is available
|
|
160
|
-
* without auto-instrumenting
|
|
161
|
-
*/
|
|
162
|
-
export function useReduxAvailability() {
|
|
163
|
-
const useStoreHook = getReactReduxUseStore();
|
|
164
|
-
if (!useStoreHook) {
|
|
165
|
-
return {
|
|
166
|
-
isAvailable: false,
|
|
167
|
-
error: "react-redux is not installed"
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
try {
|
|
171
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
172
|
-
const store = useStoreHook();
|
|
173
|
-
return {
|
|
174
|
-
isAvailable: store !== null && store !== undefined,
|
|
175
|
-
error: null
|
|
176
|
-
};
|
|
177
|
-
} catch (e) {
|
|
178
|
-
return {
|
|
179
|
-
isAvailable: false,
|
|
180
|
-
error: e instanceof Error ? e.message : "Not inside a Redux Provider"
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
}
|
|
1
|
+
"use strict";import{useEffect,useState,useRef,useCallback}from"react";import{instrumentStore,uninstrumentStore,getInstrumentationStatus}from"../utils/autoInstrument";import{setReduxDiagnostic}from"../utils/activeStoreRegistry";function getReactReduxUseStore(){try{return require("react-redux").useStore}catch{return null}}export function useAutoInstrumentRedux(e={}){const{autoStart:t=!0}=e,r=useRef(e);r.current=e;const[n,u]=useState(!1),[s,a]=useState(!0),[o,i]=useState(null),[l,c]=useState(null),d=useRef(null),m=getReactReduxUseStore(),R=null!==m;let f=null,x=null,S=null;if(m)try{f=m()}catch(e){S="no-provider",x=e instanceof Error?e.message:"Failed to access Redux store. Make sure your app is wrapped in a Redux Provider."}else S="no-react-redux",x="react-redux is not installed or not available";const g=useCallback(()=>{if(!f)return setReduxDiagnostic(S),i(x||"No Redux store available"),void a(!1);try{const e=instrumentStore(f,r.current);d.current=e,u(!0),c(f),i(null)}catch(e){i(e instanceof Error?e.message:"Failed to instrument Redux store"),u(!1)}finally{a(!1)}},[f]),p=useCallback(()=>{d.current&&(d.current(),d.current=null),l&&uninstrumentStore(l),u(!1),c(null)},[l]);return useEffect(()=>(t?g():a(!1),()=>{}),[t,g]),{isInstrumented:n,isLoading:s,error:o,isReactReduxAvailable:R,captureMechanism:getInstrumentationStatus().mechanism,startInstrumentation:g,stopInstrumentation:p,store:l}}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,54 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Hook for consuming Redux actions from the store
|
|
5
|
-
*
|
|
6
|
-
* The subscribe / filter / pause / clear plumbing is shared `useChangeLog`;
|
|
7
|
-
* this file owns the redux-specific stats, action types and selection.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { useState, useMemo, useCallback } from "react";
|
|
11
|
-
import { useChangeLog } from "@buoy-gg/shared-ui";
|
|
12
|
-
import { reduxActionStore } from "../utils/reduxActionStore";
|
|
13
|
-
const filterActions = (_actions, filter) => reduxActionStore.filterActions(filter);
|
|
14
|
-
export function useReduxActions() {
|
|
15
|
-
const log = useChangeLog(reduxActionStore, {
|
|
16
|
-
filterItems: filterActions
|
|
17
|
-
});
|
|
18
|
-
const [selectedAction, setSelectedAction] = useState(null);
|
|
19
|
-
|
|
20
|
-
// Get stats
|
|
21
|
-
const stats = useMemo(() => {
|
|
22
|
-
return reduxActionStore.getStats();
|
|
23
|
-
}, [log.items]);
|
|
24
|
-
|
|
25
|
-
// Get unique action types
|
|
26
|
-
const actionTypes = useMemo(() => {
|
|
27
|
-
return reduxActionStore.getUniqueActionTypes();
|
|
28
|
-
}, [log.items]);
|
|
29
|
-
|
|
30
|
-
// Clear actions
|
|
31
|
-
const clearActions = useCallback(() => {
|
|
32
|
-
reduxActionStore.clearActions();
|
|
33
|
-
setSelectedAction(null);
|
|
34
|
-
}, []);
|
|
35
|
-
|
|
36
|
-
// Get action by ID
|
|
37
|
-
const getActionById = useCallback(id => {
|
|
38
|
-
return reduxActionStore.getActionById(id);
|
|
39
|
-
}, []);
|
|
40
|
-
return {
|
|
41
|
-
actions: log.items,
|
|
42
|
-
filteredActions: log.filteredItems,
|
|
43
|
-
filter: log.filter,
|
|
44
|
-
setFilter: log.setFilter,
|
|
45
|
-
stats,
|
|
46
|
-
clearActions,
|
|
47
|
-
isEnabled: log.isEnabled,
|
|
48
|
-
toggleCapture: log.toggleCapture,
|
|
49
|
-
actionTypes,
|
|
50
|
-
getActionById,
|
|
51
|
-
selectedAction,
|
|
52
|
-
setSelectedAction
|
|
53
|
-
};
|
|
54
|
-
}
|
|
1
|
+
"use strict";import{useState,useMemo,useCallback}from"react";import{useChangeLog}from"@buoy-gg/shared-ui";import{reduxActionStore}from"../utils/reduxActionStore";const filterActions=(e,t)=>reduxActionStore.filterActions(t);export function useReduxActions(){const e=useChangeLog(reduxActionStore,{filterItems:filterActions}),[t,o]=useState(null),i=useMemo(()=>reduxActionStore.getStats(),[e.items]),r=useMemo(()=>reduxActionStore.getUniqueActionTypes(),[e.items]),s=useCallback(()=>{reduxActionStore.clearActions(),o(null)},[]),c=useCallback(e=>reduxActionStore.getActionById(e),[]);return{actions:e.items,filteredActions:e.filteredItems,filter:e.filter,setFilter:e.setFilter,stats:i,clearActions:s,isEnabled:e.isEnabled,toggleCapture:e.toggleCapture,actionTypes:r,getActionById:c,selectedAction:t,setSelectedAction:o}}
|
|
@@ -1,188 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
import { reduxActionStore, STATE_ON_DEVICE, PAYLOAD_ON_DEVICE, isEvictedState } from "../utils/reduxActionStore";
|
|
4
|
-
import { WIRE_DETAIL_LIMIT_BYTES, capDetail, createSyncAdapter, memoByIdentity, readParams, toWire } from "@buoy-gg/shared-ui";
|
|
5
|
-
import { getActiveStore, getInstrumentationStatus } from "../utils/autoInstrument";
|
|
6
|
-
|
|
7
|
-
/** Per-action budget for `payload`/`action`/`meta.arg`/`error` on the snapshot wire. */
|
|
8
|
-
const toWireValue = value => toWire(value, PAYLOAD_ON_DEVICE);
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Per-action wire cache. `reduxActionStore` never mutates an action in place —
|
|
12
|
-
* it prepends, and `replaceActions` maps to fresh objects — so keying on
|
|
13
|
-
* identity is correct and self-invalidating. Without it, every payload,
|
|
14
|
-
* `action`, `meta.arg` and `error` in the list is re-measured on all ~5
|
|
15
|
-
* snapshots per second for as long as it stays in the list.
|
|
16
|
-
*/
|
|
17
|
-
const toWireAction = memoByIdentity(a => {
|
|
18
|
-
const wire = isEvictedState(a.prevState) ? {
|
|
19
|
-
...a
|
|
20
|
-
} : {
|
|
21
|
-
...a,
|
|
22
|
-
prevState: STATE_ON_DEVICE,
|
|
23
|
-
nextState: STATE_ON_DEVICE
|
|
24
|
-
};
|
|
25
|
-
// A dispatched payload can itself be huge (rehydration actions carry whole
|
|
26
|
-
// state trees). The list view renders payloadPreview anyway — oversized /
|
|
27
|
-
// unserializable values stay on-device behind markers. `meta.arg` is a
|
|
28
|
-
// leftover: RTK copies the thunk argument there, and capping `action` still
|
|
29
|
-
// left the extracted copy on the wire.
|
|
30
|
-
wire.payload = toWireValue(wire.payload);
|
|
31
|
-
wire.action = toWireValue(wire.action);
|
|
32
|
-
wire.error = toWireValue(wire.error);
|
|
33
|
-
if (wire.meta) {
|
|
34
|
-
const arg = toWireValue(wire.meta.arg);
|
|
35
|
-
if (arg !== wire.meta.arg) wire.meta = {
|
|
36
|
-
...wire.meta,
|
|
37
|
-
arg
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
return wire;
|
|
41
|
-
});
|
|
42
|
-
/** Top-level slice names of the state tree, for the compact (no-values) view. */
|
|
43
|
-
function topLevelKeys(state) {
|
|
44
|
-
return state && typeof state === "object" && !Array.isArray(state) ? Object.keys(state) : [];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Why there's no bound store, in a shape remote drivers (MCP/desktop) can turn
|
|
49
|
-
* into a SPECIFIC remedy instead of a generic "check your setup".
|
|
50
|
-
*/
|
|
51
|
-
function unavailable() {
|
|
52
|
-
const status = getInstrumentationStatus();
|
|
53
|
-
const reason = status.diagnostic ?? "not-instrumented";
|
|
54
|
-
return {
|
|
55
|
-
available: false,
|
|
56
|
-
slices: [],
|
|
57
|
-
/** 'no-react-redux' | 'no-provider' | 'not-instrumented' */
|
|
58
|
-
reason
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Sync adapter for the redux tool, consumed by @buoy-gg/external-sync's
|
|
64
|
-
* `useExternalSync` (structurally matches its ToolSyncAdapter interface so
|
|
65
|
-
* this package doesn't need a dependency on it).
|
|
66
|
-
*
|
|
67
|
-
* Actions are captured from app startup — by the store-creation enhancer or
|
|
68
|
-
* the explicit middleware (full capture), or by the dispatch-patch fallback
|
|
69
|
-
* mounted with FloatingDevTools (top-level-only) — independent of whether a
|
|
70
|
-
* dashboard is watching. Subscribing here only streams what's already
|
|
71
|
-
* recorded.
|
|
72
|
-
*
|
|
73
|
-
* `getState`/`dispatch` read & drive the live instrumented store (via
|
|
74
|
-
* getActiveStore()) so a remote driver (MCP/LLM) can inspect current state and
|
|
75
|
-
* dispatch actions — redux is action-based, so the *current state* isn't in the
|
|
76
|
-
* action-log snapshot.
|
|
77
|
-
*/
|
|
78
|
-
export const reduxSyncAdapter = createSyncAdapter({
|
|
79
|
-
version: 2,
|
|
80
|
-
/**
|
|
81
|
-
* WIRE-FORM snapshot: actions WITHOUT raw prevState/nextState trees.
|
|
82
|
-
* Shipping the trees serialized up to 25×2 full state trees through
|
|
83
|
-
* socket.emit per action — no guard on that path — which froze and
|
|
84
|
-
* native-OOM-crashed enterprise-scale apps whenever a dashboard was
|
|
85
|
-
* connected (root cause confirmed by patch-and-retest). Dashboards fetch
|
|
86
|
-
* trees on demand via `getActionDetail`; already-evicted entries keep the
|
|
87
|
-
* eviction sentinel so consumers can distinguish "fetchable" from "gone".
|
|
88
|
-
*/
|
|
89
|
-
getSnapshot: () => reduxActionStore.getActions().map(toWireAction),
|
|
90
|
-
subscribe: onChange => reduxActionStore.subscribe(onChange),
|
|
91
|
-
actions: {
|
|
92
|
-
clearEvents: () => {
|
|
93
|
-
reduxActionStore.clearActions();
|
|
94
|
-
},
|
|
95
|
-
/**
|
|
96
|
-
* On-demand detail: one action's real prevState/nextState trees.
|
|
97
|
-
* The per-snapshot stream stays tree-free; this is the explicit,
|
|
98
|
-
* size-guarded channel for the detail pane.
|
|
99
|
-
*/
|
|
100
|
-
getActionDetail: params => {
|
|
101
|
-
const {
|
|
102
|
-
id
|
|
103
|
-
} = readParams(params);
|
|
104
|
-
if (!id) return {
|
|
105
|
-
found: false,
|
|
106
|
-
reason: "missing id"
|
|
107
|
-
};
|
|
108
|
-
const action = reduxActionStore.getActionById(id);
|
|
109
|
-
if (!action) return {
|
|
110
|
-
found: false,
|
|
111
|
-
reason: "unknown id"
|
|
112
|
-
};
|
|
113
|
-
if (isEvictedState(action.prevState)) {
|
|
114
|
-
return {
|
|
115
|
-
found: true,
|
|
116
|
-
id,
|
|
117
|
-
evicted: true,
|
|
118
|
-
reason: `Raw state was evicted (retained for recent actions only).`
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
// Per-field cap: oversized fields degrade individually instead of
|
|
122
|
-
// failing the whole detail (a giant payload shouldn't hide the trees).
|
|
123
|
-
const cap = value => capDetail(value, WIRE_DETAIL_LIMIT_BYTES);
|
|
124
|
-
const capMeta = meta => {
|
|
125
|
-
if (!meta) return meta;
|
|
126
|
-
const arg = cap(meta.arg);
|
|
127
|
-
return arg === meta.arg ? meta : {
|
|
128
|
-
...meta,
|
|
129
|
-
arg
|
|
130
|
-
};
|
|
131
|
-
};
|
|
132
|
-
return {
|
|
133
|
-
found: true,
|
|
134
|
-
id,
|
|
135
|
-
prevState: cap(action.prevState),
|
|
136
|
-
nextState: cap(action.nextState),
|
|
137
|
-
payload: cap(action.payload),
|
|
138
|
-
action: cap(action.action),
|
|
139
|
-
meta: capMeta(action.meta),
|
|
140
|
-
error: cap(action.error)
|
|
141
|
-
};
|
|
142
|
-
},
|
|
143
|
-
/**
|
|
144
|
-
* Compact current-state reader: the instrumented store's slice names, and
|
|
145
|
-
* the full state tree only when `includeValues` is set (heavy).
|
|
146
|
-
*/
|
|
147
|
-
getState: params => {
|
|
148
|
-
const {
|
|
149
|
-
includeValues = false
|
|
150
|
-
} = readParams(params);
|
|
151
|
-
const store = getActiveStore();
|
|
152
|
-
if (!store) {
|
|
153
|
-
return unavailable();
|
|
154
|
-
}
|
|
155
|
-
const state = store.getState();
|
|
156
|
-
const status = getInstrumentationStatus();
|
|
157
|
-
return {
|
|
158
|
-
available: true,
|
|
159
|
-
slices: topLevelKeys(state),
|
|
160
|
-
/** 'full' | 'top-level-only' — what the live capture mechanism sees. */
|
|
161
|
-
capture: status.capture,
|
|
162
|
-
mechanism: status.mechanism,
|
|
163
|
-
...(includeValues ? {
|
|
164
|
-
state
|
|
165
|
-
} : {})
|
|
166
|
-
};
|
|
167
|
-
},
|
|
168
|
-
/** Dispatch a plain action object to the instrumented store. */
|
|
169
|
-
dispatch: params => {
|
|
170
|
-
const {
|
|
171
|
-
action
|
|
172
|
-
} = readParams(params);
|
|
173
|
-
const store = getActiveStore();
|
|
174
|
-
if (!store) {
|
|
175
|
-
return {
|
|
176
|
-
dispatched: false,
|
|
177
|
-
...unavailable()
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
if (!action) throw new Error("dispatch requires an `action`");
|
|
181
|
-
store.dispatch(action);
|
|
182
|
-
return {
|
|
183
|
-
dispatched: true,
|
|
184
|
-
type: action.type
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
});
|
|
1
|
+
"use strict";import{reduxActionStore,STATE_ON_DEVICE,PAYLOAD_ON_DEVICE,isEvictedState}from"../utils/reduxActionStore";import{WIRE_DETAIL_LIMIT_BYTES,capDetail,createSyncAdapter,memoByIdentity,readParams,toWire}from"@buoy-gg/shared-ui";import{getActiveStore,getInstrumentationStatus}from"../utils/autoInstrument";const toWireValue=t=>toWire(t,PAYLOAD_ON_DEVICE),toWireAction=memoByIdentity(t=>{const e=isEvictedState(t.prevState)?{...t}:{...t,prevState:STATE_ON_DEVICE,nextState:STATE_ON_DEVICE};if(e.payload=toWireValue(e.payload),e.action=toWireValue(e.action),e.error=toWireValue(e.error),e.meta){const t=toWireValue(e.meta.arg);t!==e.meta.arg&&(e.meta={...e.meta,arg:t})}return e});function topLevelKeys(t){return t&&"object"==typeof t&&!Array.isArray(t)?Object.keys(t):[]}function unavailable(){return{available:!1,slices:[],reason:getInstrumentationStatus().diagnostic??"not-instrumented"}}export const reduxSyncAdapter=createSyncAdapter({version:2,getSnapshot:()=>reduxActionStore.getActions().map(toWireAction),subscribe:t=>reduxActionStore.subscribe(t),actions:{clearEvents:()=>{reduxActionStore.clearActions()},getActionDetail:t=>{const{id:e}=readParams(t);if(!e)return{found:!1,reason:"missing id"};const r=reduxActionStore.getActionById(e);if(!r)return{found:!1,reason:"unknown id"};if(isEvictedState(r.prevState))return{found:!0,id:e,evicted:!0,reason:"Raw state was evicted (retained for recent actions only)."};const a=t=>capDetail(t,WIRE_DETAIL_LIMIT_BYTES);return{found:!0,id:e,prevState:a(r.prevState),nextState:a(r.nextState),payload:a(r.payload),action:a(r.action),meta:(t=>{if(!t)return t;const e=a(t.arg);return e===t.arg?t:{...t,arg:e}})(r.meta),error:a(r.error)}},getState:t=>{const{includeValues:e=!1}=readParams(t),r=getActiveStore();if(!r)return unavailable();const a=r.getState(),n=getInstrumentationStatus();return{available:!0,slices:topLevelKeys(a),capture:n.capture,mechanism:n.mechanism,...e?{state:a}:{}}},dispatch:t=>{const{action:e}=readParams(t),r=getActiveStore();if(!r)return{dispatched:!1,...unavailable()};if(!e)throw new Error("dispatch requires an `action`");return r.dispatch(e),{dispatched:!0,type:e.type}}}});
|
|
@@ -1,92 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Shared registry for the one live Redux store Buoy is bound to, plus how it
|
|
5
|
-
* got bound (the capture mechanism) and why binding failed when it did.
|
|
6
|
-
*
|
|
7
|
-
* Lives in its own module so the three capture paths (devtools-hook enhancer,
|
|
8
|
-
* explicit middleware, dispatch-patch fallback) and the sync adapter can all
|
|
9
|
-
* import it without cycles.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
/** The minimum store surface Buoy needs (middleware storeApi satisfies this). */
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* How the store got instrumented:
|
|
16
|
-
* - `enhancer` — store-creation-time enhancer via the Redux DevTools global.
|
|
17
|
-
* FULL capture (thunk-internal & RTK Query actions included).
|
|
18
|
-
* - `middleware` — user-added buoyReduxMiddleware. FULL capture.
|
|
19
|
-
* - `patch` — store.dispatch patched after creation (auto-instrumentation
|
|
20
|
-
* fallback). TOP-LEVEL-ONLY capture: actions dispatched from
|
|
21
|
-
* inside middleware/thunks bypass the patch.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
/** Why there is no bound store, when there isn't one. */
|
|
25
|
-
|
|
26
|
-
/** Marks stores created through the Buoy devtools-hook enhancer. */
|
|
27
|
-
export const ENHANCER_SYMBOL = Symbol.for("@@buoy/enhancerInstrumented");
|
|
28
|
-
let activeStore = null;
|
|
29
|
-
let mechanism = null;
|
|
30
|
-
let diagnostic = null;
|
|
31
|
-
let timeTravelReducer = false;
|
|
32
|
-
export function registerActiveStore(store, mech) {
|
|
33
|
-
activeStore = store;
|
|
34
|
-
mechanism = mech;
|
|
35
|
-
diagnostic = null;
|
|
36
|
-
}
|
|
37
|
-
export function unregisterActiveStore(store) {
|
|
38
|
-
if (activeStore === store) {
|
|
39
|
-
activeStore = null;
|
|
40
|
-
mechanism = null;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
export function getActiveStore() {
|
|
44
|
-
return activeStore;
|
|
45
|
-
}
|
|
46
|
-
export function getCaptureMechanism() {
|
|
47
|
-
return mechanism;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Record that a reducer able to serve `BUOY_JUMP_TO_STATE` is in the store.
|
|
52
|
-
*
|
|
53
|
-
* Called by the devtools-hook enhancer and by `withBuoyDevTools` — the only
|
|
54
|
-
* two things that wrap the root reducer. Capture and time travel are separate
|
|
55
|
-
* capabilities and they are wired separately: a store can be fully captured
|
|
56
|
-
* through `buoyReduxMiddleware` and still have no way to restore a state,
|
|
57
|
-
* because middleware sits ABOVE the reducer and cannot replace what it
|
|
58
|
-
* returns.
|
|
59
|
-
*/
|
|
60
|
-
export function markTimeTravelReducer() {
|
|
61
|
-
timeTravelReducer = true;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Whether pressing JUMP can actually do anything.
|
|
66
|
-
*
|
|
67
|
-
* Worth a first-class answer rather than a try-and-see, because the failure is
|
|
68
|
-
* invisible: `jumpToState` dispatches into a store that simply does not
|
|
69
|
-
* recognise the action, the reducer returns the state it already had, and the
|
|
70
|
-
* screen does not move. From the outside that is indistinguishable from a
|
|
71
|
-
* devtool that is broken.
|
|
72
|
-
*/
|
|
73
|
-
export function isTimeTravelAvailable() {
|
|
74
|
-
return timeTravelReducer && activeStore !== null;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Recorded by the auto-instrument hook so non-React surfaces (sync adapter,
|
|
79
|
-
* MCP) can report WHY there's no store instead of a generic failure.
|
|
80
|
-
*/
|
|
81
|
-
export function setReduxDiagnostic(d) {
|
|
82
|
-
diagnostic = d;
|
|
83
|
-
}
|
|
84
|
-
export function getInstrumentationStatus() {
|
|
85
|
-
return {
|
|
86
|
-
mechanism,
|
|
87
|
-
capture: mechanism === "enhancer" || mechanism === "middleware" ? "full" : mechanism === "patch" ? "top-level-only" : "none",
|
|
88
|
-
hasStore: activeStore !== null,
|
|
89
|
-
timeTravel: isTimeTravelAvailable(),
|
|
90
|
-
diagnostic
|
|
91
|
-
};
|
|
92
|
-
}
|
|
1
|
+
"use strict";export const ENHANCER_SYMBOL=Symbol.for("@@buoy/enhancerInstrumented");let activeStore=null,mechanism=null,diagnostic=null,timeTravelReducer=!1;export function registerActiveStore(e,t){activeStore=e,mechanism=t,diagnostic=null}export function unregisterActiveStore(e){activeStore===e&&(activeStore=null,mechanism=null)}export function getActiveStore(){return activeStore}export function getCaptureMechanism(){return mechanism}export function markTimeTravelReducer(){timeTravelReducer=!0}export function isTimeTravelAvailable(){return timeTravelReducer&&null!==activeStore}export function setReduxDiagnostic(e){diagnostic=e}export function getInstrumentationStatus(){return{mechanism:mechanism,capture:"enhancer"===mechanism||"middleware"===mechanism?"full":"patch"===mechanism?"top-level-only":"none",hasStore:null!==activeStore,timeTravel:isTimeTravelAvailable(),diagnostic:diagnostic}}
|