@buoy-gg/redux 7.0.38 → 7.0.39

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.
Files changed (68) hide show
  1. package/lib/commonjs/index.js +256 -1
  2. package/lib/commonjs/preset.js +90 -1
  3. package/lib/commonjs/redux/components/ReduxActionButton.js +131 -1
  4. package/lib/commonjs/redux/components/ReduxActionDetailContent.js +437 -1
  5. package/lib/commonjs/redux/components/ReduxActionDetailView.js +401 -1
  6. package/lib/commonjs/redux/components/ReduxActionInfoView.js +836 -1
  7. package/lib/commonjs/redux/components/ReduxActionItem.js +334 -1
  8. package/lib/commonjs/redux/components/ReduxAutoInstrument.js +20 -1
  9. package/lib/commonjs/redux/components/ReduxDetailViewToggle.js +50 -1
  10. package/lib/commonjs/redux/components/ReduxIcon.js +18 -1
  11. package/lib/commonjs/redux/components/ReduxModal.js +703 -1
  12. package/lib/commonjs/redux/hooks/useAutoInstrumentRedux.js +187 -1
  13. package/lib/commonjs/redux/hooks/useReduxActions.js +58 -1
  14. package/lib/commonjs/redux/sync/reduxSyncAdapter.js +191 -1
  15. package/lib/commonjs/redux/utils/activeStoreRegistry.js +104 -1
  16. package/lib/commonjs/redux/utils/autoInstrument.js +231 -1
  17. package/lib/commonjs/redux/utils/buoyReduxMiddleware.js +202 -1
  18. package/lib/commonjs/redux/utils/createReduxHistoryAdapter.js +146 -1
  19. package/lib/commonjs/redux/utils/devtoolsHook.js +152 -1
  20. package/lib/commonjs/redux/utils/reduxActionStore.js +473 -1
  21. package/lib/commonjs/redux/utils/snapshotProvider.js +158 -1
  22. package/lib/commonjs/redux/utils/testIDs.js +75 -1
  23. package/lib/module/index.js +125 -1
  24. package/lib/module/preset.js +87 -1
  25. package/lib/module/redux/components/ReduxActionButton.js +128 -1
  26. package/lib/module/redux/components/ReduxActionDetailContent.js +433 -1
  27. package/lib/module/redux/components/ReduxActionDetailView.js +397 -1
  28. package/lib/module/redux/components/ReduxActionInfoView.js +831 -1
  29. package/lib/module/redux/components/ReduxActionItem.js +330 -1
  30. package/lib/module/redux/components/ReduxAutoInstrument.js +17 -1
  31. package/lib/module/redux/components/ReduxDetailViewToggle.js +45 -1
  32. package/lib/module/redux/components/ReduxIcon.js +8 -1
  33. package/lib/module/redux/components/ReduxModal.js +698 -1
  34. package/lib/module/redux/hooks/useAutoInstrumentRedux.js +183 -1
  35. package/lib/module/redux/hooks/useReduxActions.js +54 -1
  36. package/lib/module/redux/sync/reduxSyncAdapter.js +188 -1
  37. package/lib/module/redux/utils/activeStoreRegistry.js +92 -1
  38. package/lib/module/redux/utils/autoInstrument.js +201 -1
  39. package/lib/module/redux/utils/buoyReduxMiddleware.js +188 -1
  40. package/lib/module/redux/utils/createReduxHistoryAdapter.js +142 -1
  41. package/lib/module/redux/utils/devtoolsHook.js +145 -1
  42. package/lib/module/redux/utils/reduxActionStore.js +465 -1
  43. package/lib/module/redux/utils/snapshotProvider.js +157 -1
  44. package/lib/module/redux/utils/testIDs.js +68 -1
  45. package/lib/typescript/index.d.ts.map +1 -0
  46. package/lib/typescript/preset.d.ts.map +1 -0
  47. package/lib/typescript/redux/components/ReduxActionButton.d.ts.map +1 -0
  48. package/lib/typescript/redux/components/ReduxActionDetailContent.d.ts.map +1 -0
  49. package/lib/typescript/redux/components/ReduxActionDetailView.d.ts.map +1 -0
  50. package/lib/typescript/redux/components/ReduxActionInfoView.d.ts.map +1 -0
  51. package/lib/typescript/redux/components/ReduxActionItem.d.ts.map +1 -0
  52. package/lib/typescript/redux/components/ReduxAutoInstrument.d.ts.map +1 -0
  53. package/lib/typescript/redux/components/ReduxDetailViewToggle.d.ts.map +1 -0
  54. package/lib/typescript/redux/components/ReduxIcon.d.ts.map +1 -0
  55. package/lib/typescript/redux/components/ReduxModal.d.ts.map +1 -0
  56. package/lib/typescript/redux/hooks/useAutoInstrumentRedux.d.ts.map +1 -0
  57. package/lib/typescript/redux/hooks/useReduxActions.d.ts.map +1 -0
  58. package/lib/typescript/redux/sync/reduxSyncAdapter.d.ts.map +1 -0
  59. package/lib/typescript/redux/types/index.d.ts.map +1 -0
  60. package/lib/typescript/redux/utils/activeStoreRegistry.d.ts.map +1 -0
  61. package/lib/typescript/redux/utils/autoInstrument.d.ts.map +1 -0
  62. package/lib/typescript/redux/utils/buoyReduxMiddleware.d.ts.map +1 -0
  63. package/lib/typescript/redux/utils/createReduxHistoryAdapter.d.ts.map +1 -0
  64. package/lib/typescript/redux/utils/devtoolsHook.d.ts.map +1 -0
  65. package/lib/typescript/redux/utils/reduxActionStore.d.ts.map +1 -0
  66. package/lib/typescript/redux/utils/snapshotProvider.d.ts.map +1 -0
  67. package/lib/typescript/redux/utils/testIDs.d.ts.map +1 -0
  68. package/package.json +4 -4
@@ -1 +1,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
+ "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 +1,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
+ "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 +1,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
+ "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 +1,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}}
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
+ }