@buoy-gg/zustand 2.1.12 → 2.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/lib/commonjs/index.js +1 -91
  2. package/lib/commonjs/preset.js +1 -102
  3. package/lib/commonjs/zustand/components/ZustandActionButton.js +1 -116
  4. package/lib/commonjs/zustand/components/ZustandDetailViewToggle.js +1 -134
  5. package/lib/commonjs/zustand/components/ZustandEventFilterView.js +1 -291
  6. package/lib/commonjs/zustand/components/ZustandIcon.js +1 -35
  7. package/lib/commonjs/zustand/components/ZustandModal.js +1 -603
  8. package/lib/commonjs/zustand/components/ZustandStateChangeItem.js +1 -165
  9. package/lib/commonjs/zustand/components/ZustandStateDetailContent.js +1 -352
  10. package/lib/commonjs/zustand/components/ZustandStateInfoView.js +1 -508
  11. package/lib/commonjs/zustand/components/ZustandStoreBrowser.js +1 -307
  12. package/lib/commonjs/zustand/components/index.js +1 -73
  13. package/lib/commonjs/zustand/hooks/index.js +1 -12
  14. package/lib/commonjs/zustand/hooks/useZustandStateChanges.js +1 -92
  15. package/lib/commonjs/zustand/index.js +1 -99
  16. package/lib/commonjs/zustand/utils/buoyZustandMiddleware.js +1 -220
  17. package/lib/commonjs/zustand/utils/index.js +1 -31
  18. package/lib/commonjs/zustand/utils/zustandStateStore.js +1 -361
  19. package/lib/module/index.js +1 -80
  20. package/lib/module/preset.js +1 -98
  21. package/lib/module/zustand/components/ZustandActionButton.js +1 -112
  22. package/lib/module/zustand/components/ZustandDetailViewToggle.js +1 -129
  23. package/lib/module/zustand/components/ZustandEventFilterView.js +1 -287
  24. package/lib/module/zustand/components/ZustandIcon.js +1 -32
  25. package/lib/module/zustand/components/ZustandModal.js +1 -599
  26. package/lib/module/zustand/components/ZustandStateChangeItem.js +1 -161
  27. package/lib/module/zustand/components/ZustandStateDetailContent.js +1 -348
  28. package/lib/module/zustand/components/ZustandStateInfoView.js +1 -503
  29. package/lib/module/zustand/components/ZustandStoreBrowser.js +1 -303
  30. package/lib/module/zustand/components/index.js +1 -10
  31. package/lib/module/zustand/hooks/index.js +1 -3
  32. package/lib/module/zustand/hooks/useZustandStateChanges.js +1 -88
  33. package/lib/module/zustand/index.js +1 -12
  34. package/lib/module/zustand/utils/buoyZustandMiddleware.js +1 -214
  35. package/lib/module/zustand/utils/index.js +1 -4
  36. package/lib/module/zustand/utils/zustandStateStore.js +1 -357
  37. package/package.json +3 -3
  38. package/lib/typescript/index.d.ts.map +0 -1
  39. package/lib/typescript/preset.d.ts.map +0 -1
  40. package/lib/typescript/zustand/components/ZustandActionButton.d.ts.map +0 -1
  41. package/lib/typescript/zustand/components/ZustandDetailViewToggle.d.ts.map +0 -1
  42. package/lib/typescript/zustand/components/ZustandEventFilterView.d.ts.map +0 -1
  43. package/lib/typescript/zustand/components/ZustandIcon.d.ts.map +0 -1
  44. package/lib/typescript/zustand/components/ZustandModal.d.ts.map +0 -1
  45. package/lib/typescript/zustand/components/ZustandStateChangeItem.d.ts.map +0 -1
  46. package/lib/typescript/zustand/components/ZustandStateDetailContent.d.ts.map +0 -1
  47. package/lib/typescript/zustand/components/ZustandStateInfoView.d.ts.map +0 -1
  48. package/lib/typescript/zustand/components/ZustandStoreBrowser.d.ts.map +0 -1
  49. package/lib/typescript/zustand/components/index.d.ts.map +0 -1
  50. package/lib/typescript/zustand/hooks/index.d.ts.map +0 -1
  51. package/lib/typescript/zustand/hooks/useZustandStateChanges.d.ts.map +0 -1
  52. package/lib/typescript/zustand/index.d.ts.map +0 -1
  53. package/lib/typescript/zustand/types/index.d.ts.map +0 -1
  54. package/lib/typescript/zustand/utils/buoyZustandMiddleware.d.ts.map +0 -1
  55. package/lib/typescript/zustand/utils/index.d.ts.map +0 -1
  56. package/lib/typescript/zustand/utils/zustandStateStore.d.ts.map +0 -1
@@ -1,303 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * ZustandStoreBrowser
5
- *
6
- * Browse tab — shows all registered Zustand stores and their current state.
7
- * Mirrors the Storage Browser pattern from @buoy-gg/storage.
8
- *
9
- * Each store is shown as an expandable row with its current state viewable
10
- * via the shared DataViewer component.
11
- */
12
-
13
- import { useState, useMemo, useCallback } from "react";
14
- import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from "react-native";
15
- import { CompactRow, macOSColors, buoyColors, Database, parseValue, Box, ExpandedInfoRow, PillBadge } from "@buoy-gg/shared-ui";
16
- import { DataViewer } from "@buoy-gg/shared-ui/dataViewer";
17
- import { zustandStateStore } from "../utils/zustandStateStore";
18
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
- /**
20
- * Get a preview of the store's top-level keys
21
- */
22
- function getKeysPreview(store) {
23
- try {
24
- const state = store.api.getState();
25
- if (state && typeof state === "object") {
26
- const keys = Object.keys(state);
27
- const dataKeys = keys.filter(k => {
28
- const val = state[k];
29
- return typeof val !== "function";
30
- });
31
- if (dataKeys.length === 0) return "no data keys";
32
- if (dataKeys.length <= 3) return dataKeys.join(", ");
33
- return `${dataKeys.slice(0, 2).join(", ")} +${dataKeys.length - 2}`;
34
- }
35
- return "";
36
- } catch {
37
- return "";
38
- }
39
- }
40
- function StoreExpandedContent({
41
- store,
42
- onViewHistory
43
- }) {
44
- const state = store.api.getState();
45
- const displayState = useMemo(() => {
46
- if (state && typeof state === "object") {
47
- const filtered = {};
48
- for (const [key, value] of Object.entries(state)) {
49
- if (typeof value !== "function") {
50
- filtered[key] = value;
51
- }
52
- }
53
- return parseValue(filtered);
54
- }
55
- return parseValue(state);
56
- }, [state]);
57
- return /*#__PURE__*/_jsxs(View, {
58
- style: expandedStyles.container,
59
- children: [/*#__PURE__*/_jsx(ExpandedInfoRow, {
60
- label: "Type",
61
- children: /*#__PURE__*/_jsx(PillBadge, {
62
- color: store.color,
63
- children: "ZUSTAND"
64
- })
65
- }), store.stateChangeCount > 0 && /*#__PURE__*/_jsxs(ExpandedInfoRow, {
66
- label: "Changes",
67
- children: [/*#__PURE__*/_jsx(PillBadge, {
68
- color: buoyColors.warning,
69
- children: String(store.stateChangeCount)
70
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
71
- onPress: () => onViewHistory(store.name),
72
- style: expandedStyles.viewHistoryButton,
73
- hitSlop: {
74
- top: 6,
75
- bottom: 6,
76
- left: 6,
77
- right: 6
78
- },
79
- children: /*#__PURE__*/_jsx(Text, {
80
- style: [expandedStyles.viewHistoryText, {
81
- color: store.color
82
- }],
83
- children: "view history \u2192"
84
- })
85
- })]
86
- }), store.isPersisted && /*#__PURE__*/_jsx(ExpandedInfoRow, {
87
- label: "Persist",
88
- children: /*#__PURE__*/_jsx(PillBadge, {
89
- color: buoyColors.info,
90
- icon: /*#__PURE__*/_jsx(Database, {
91
- size: 9,
92
- color: buoyColors.info
93
- }),
94
- children: store.persistName || "persisted"
95
- })
96
- }), /*#__PURE__*/_jsx(View, {
97
- style: expandedStyles.dataContainer,
98
- children: displayState && typeof displayState === "object" ? /*#__PURE__*/_jsx(DataViewer, {
99
- title: "",
100
- data: displayState,
101
- showTypeFilter: true,
102
- rawMode: true,
103
- initialExpanded: true
104
- }) : /*#__PURE__*/_jsx(Text, {
105
- style: expandedStyles.emptyText,
106
- children: "Empty state"
107
- })
108
- })]
109
- });
110
- }
111
- function EmptyBrowserState() {
112
- return /*#__PURE__*/_jsxs(View, {
113
- style: styles.emptyState,
114
- children: [/*#__PURE__*/_jsx(Box, {
115
- size: 32,
116
- color: macOSColors.text.muted
117
- }), /*#__PURE__*/_jsx(Text, {
118
- style: styles.emptyTitle,
119
- children: "No stores registered"
120
- }), /*#__PURE__*/_jsx(Text, {
121
- style: styles.emptyText,
122
- children: "Use watchStores() to register your Zustand stores.\nThey will appear here with their current state."
123
- })]
124
- });
125
- }
126
- export function ZustandStoreBrowser({
127
- stores,
128
- searchQuery,
129
- onViewHistory
130
- }) {
131
- const [expandedStore, setExpandedStore] = useState(null);
132
-
133
- // Filter stores by search query
134
- const filteredStores = useMemo(() => {
135
- if (!searchQuery) return stores;
136
- const search = searchQuery.toLowerCase();
137
- return stores.filter(s => s.name.toLowerCase().includes(search) || s.persistName && s.persistName.toLowerCase().includes(search));
138
- }, [stores, searchQuery]);
139
- const handleStorePress = useCallback(store => {
140
- setExpandedStore(prev => prev === store.name ? null : store.name);
141
- }, []);
142
- if (filteredStores.length === 0 && !searchQuery) {
143
- return /*#__PURE__*/_jsx(EmptyBrowserState, {});
144
- }
145
- if (filteredStores.length === 0 && searchQuery) {
146
- return /*#__PURE__*/_jsxs(View, {
147
- style: styles.emptyState,
148
- children: [/*#__PURE__*/_jsx(Text, {
149
- style: styles.emptyTitle,
150
- children: "No matching stores"
151
- }), /*#__PURE__*/_jsxs(Text, {
152
- style: styles.emptyText,
153
- children: ["No stores match \"", searchQuery, "\""]
154
- })]
155
- });
156
- }
157
- return /*#__PURE__*/_jsxs(ScrollView, {
158
- style: styles.container,
159
- contentContainerStyle: styles.scrollContent,
160
- showsVerticalScrollIndicator: true,
161
- children: [/*#__PURE__*/_jsxs(View, {
162
- style: styles.sectionHeader,
163
- children: [/*#__PURE__*/_jsx(Text, {
164
- style: styles.sectionTitle,
165
- children: "STORES"
166
- }), /*#__PURE__*/_jsx(View, {
167
- style: styles.sectionCountBadge,
168
- children: /*#__PURE__*/_jsx(Text, {
169
- style: styles.sectionCountText,
170
- children: filteredStores.length
171
- })
172
- })]
173
- }), filteredStores.map(store => {
174
- const isExpanded = expandedStore === store.name;
175
- const keysPreview = getKeysPreview(store);
176
- const storeColor = zustandStateStore.getStoreColor(store.name);
177
- return /*#__PURE__*/_jsxs(View, {
178
- style: styles.storeRowWrapper,
179
- children: [/*#__PURE__*/_jsx(CompactRow, {
180
- statusDotColor: storeColor,
181
- statusLabel: store.name,
182
- statusSublabel: store.isPersisted ? "persisted" : "in-memory",
183
- primaryText: keysPreview,
184
- showChevron: true,
185
- isExpanded: isExpanded,
186
- onPress: () => handleStorePress(store),
187
- expandedContent: isExpanded ? /*#__PURE__*/_jsx(StoreExpandedContent, {
188
- store: store,
189
- onViewHistory: onViewHistory
190
- }) : undefined
191
- }), store.stateChangeCount > 0 && /*#__PURE__*/_jsx(View, {
192
- style: [styles.absCountBadge, {
193
- backgroundColor: storeColor + "22",
194
- borderColor: storeColor + "55"
195
- }],
196
- pointerEvents: "none",
197
- children: /*#__PURE__*/_jsx(Text, {
198
- style: [styles.absCountText, {
199
- color: storeColor
200
- }],
201
- children: String(store.stateChangeCount)
202
- })
203
- })]
204
- }, store.name);
205
- })]
206
- });
207
- }
208
- const styles = StyleSheet.create({
209
- container: {
210
- flex: 1
211
- },
212
- storeRowWrapper: {
213
- position: "relative"
214
- },
215
- absCountBadge: {
216
- position: "absolute",
217
- top: 4,
218
- right: 10,
219
- paddingHorizontal: 5,
220
- paddingVertical: 1,
221
- borderRadius: 4,
222
- borderWidth: 1,
223
- zIndex: 1
224
- },
225
- absCountText: {
226
- fontSize: 9,
227
- fontWeight: "700",
228
- fontFamily: "monospace"
229
- },
230
- scrollContent: {
231
- paddingTop: 8,
232
- paddingBottom: 20
233
- },
234
- sectionHeader: {
235
- flexDirection: "row",
236
- alignItems: "center",
237
- paddingHorizontal: 16,
238
- paddingVertical: 8,
239
- gap: 8
240
- },
241
- sectionTitle: {
242
- fontSize: 11,
243
- fontWeight: "700",
244
- letterSpacing: 0.5,
245
- color: macOSColors.text.muted
246
- },
247
- sectionCountBadge: {
248
- backgroundColor: buoyColors.primary + "26",
249
- paddingHorizontal: 8,
250
- paddingVertical: 2,
251
- borderRadius: 4
252
- },
253
- sectionCountText: {
254
- fontSize: 10,
255
- fontWeight: "700",
256
- color: buoyColors.primary,
257
- fontFamily: "monospace"
258
- },
259
- emptyState: {
260
- alignItems: "center",
261
- paddingVertical: 40
262
- },
263
- emptyTitle: {
264
- color: macOSColors.text.primary,
265
- fontSize: 14,
266
- fontWeight: "600",
267
- marginTop: 12,
268
- marginBottom: 6
269
- },
270
- emptyText: {
271
- color: macOSColors.text.muted,
272
- fontSize: 12,
273
- textAlign: "center",
274
- lineHeight: 18
275
- }
276
- });
277
- const expandedStyles = StyleSheet.create({
278
- container: {
279
- gap: 10
280
- },
281
- viewHistoryButton: {
282
- marginLeft: 4
283
- },
284
- viewHistoryText: {
285
- fontSize: 10,
286
- fontWeight: "600",
287
- fontFamily: "monospace"
288
- },
289
- dataContainer: {
290
- backgroundColor: buoyColors.base,
291
- borderRadius: 6,
292
- borderWidth: 1,
293
- borderColor: buoyColors.border,
294
- overflow: "hidden",
295
- minHeight: 60
296
- },
297
- emptyText: {
298
- color: buoyColors.textMuted,
299
- fontSize: 12,
300
- padding: 14,
301
- fontStyle: "italic"
302
- }
303
- });
1
+ "use strict";import{useState,useMemo,useCallback}from"react";import{View,Text,StyleSheet,ScrollView,TouchableOpacity}from"react-native";import{CompactRow,macOSColors,buoyColors,Database,parseValue,Box,ExpandedInfoRow,PillBadge}from"@buoy-gg/shared-ui";import{DataViewer}from"@buoy-gg/shared-ui/dataViewer";import{zustandStateStore}from"../utils/zustandStateStore";import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";function getKeysPreview(e){try{const t=e.api.getState();if(t&&"object"==typeof t){const e=Object.keys(t).filter(e=>"function"!=typeof t[e]);return 0===e.length?"no data keys":e.length<=3?e.join(", "):`${e.slice(0,2).join(", ")} +${e.length-2}`}return""}catch{return""}}function StoreExpandedContent({store:e,onViewHistory:t}){const o=e.api.getState(),s=useMemo(()=>{if(o&&"object"==typeof o){const e={};for(const[t,s]of Object.entries(o))"function"!=typeof s&&(e[t]=s);return parseValue(e)}return parseValue(o)},[o]);return _jsxs(View,{style:expandedStyles.container,children:[_jsx(ExpandedInfoRow,{label:"Type",children:_jsx(PillBadge,{color:e.color,children:"ZUSTAND"})}),e.stateChangeCount>0&&_jsxs(ExpandedInfoRow,{label:"Changes",children:[_jsx(PillBadge,{color:buoyColors.warning,children:String(e.stateChangeCount)}),_jsx(TouchableOpacity,{onPress:()=>t(e.name),style:expandedStyles.viewHistoryButton,hitSlop:{top:6,bottom:6,left:6,right:6},children:_jsx(Text,{style:[expandedStyles.viewHistoryText,{color:e.color}],children:"view history →"})})]}),e.isPersisted&&_jsx(ExpandedInfoRow,{label:"Persist",children:_jsx(PillBadge,{color:buoyColors.info,icon:_jsx(Database,{size:9,color:buoyColors.info}),children:e.persistName||"persisted"})}),_jsx(View,{style:expandedStyles.dataContainer,children:s&&"object"==typeof s?_jsx(DataViewer,{title:"",data:s,showTypeFilter:!0,rawMode:!0,initialExpanded:!0}):_jsx(Text,{style:expandedStyles.emptyText,children:"Empty state"})})]})}function EmptyBrowserState(){return _jsxs(View,{style:styles.emptyState,children:[_jsx(Box,{size:32,color:macOSColors.text.muted}),_jsx(Text,{style:styles.emptyTitle,children:"No stores registered"}),_jsx(Text,{style:styles.emptyText,children:"Use watchStores() to register your Zustand stores.\nThey will appear here with their current state."})]})}export function ZustandStoreBrowser({stores:e,searchQuery:t,onViewHistory:o}){const[s,r]=useState(null),n=useMemo(()=>{if(!t)return e;const o=t.toLowerCase();return e.filter(e=>e.name.toLowerCase().includes(o)||e.persistName&&e.persistName.toLowerCase().includes(o))},[e,t]),i=useCallback(e=>{r(t=>t===e.name?null:e.name)},[]);return 0!==n.length||t?0===n.length&&t?_jsxs(View,{style:styles.emptyState,children:[_jsx(Text,{style:styles.emptyTitle,children:"No matching stores"}),_jsxs(Text,{style:styles.emptyText,children:['No stores match "',t,'"']})]}):_jsxs(ScrollView,{style:styles.container,contentContainerStyle:styles.scrollContent,showsVerticalScrollIndicator:!0,children:[_jsxs(View,{style:styles.sectionHeader,children:[_jsx(Text,{style:styles.sectionTitle,children:"STORES"}),_jsx(View,{style:styles.sectionCountBadge,children:_jsx(Text,{style:styles.sectionCountText,children:n.length})})]}),n.map(e=>{const t=s===e.name,r=getKeysPreview(e),n=zustandStateStore.getStoreColor(e.name);return _jsxs(View,{style:styles.storeRowWrapper,children:[_jsx(CompactRow,{statusDotColor:n,statusLabel:e.name,statusSublabel:e.isPersisted?"persisted":"in-memory",primaryText:r,showChevron:!0,isExpanded:t,onPress:()=>i(e),expandedContent:t?_jsx(StoreExpandedContent,{store:e,onViewHistory:o}):void 0}),e.stateChangeCount>0&&_jsx(View,{style:[styles.absCountBadge,{backgroundColor:n+"22",borderColor:n+"55"}],pointerEvents:"none",children:_jsx(Text,{style:[styles.absCountText,{color:n}],children:String(e.stateChangeCount)})})]},e.name)})]}):_jsx(EmptyBrowserState,{})}const styles=StyleSheet.create({container:{flex:1},storeRowWrapper:{position:"relative"},absCountBadge:{position:"absolute",top:4,right:10,paddingHorizontal:5,paddingVertical:1,borderRadius:4,borderWidth:1,zIndex:1},absCountText:{fontSize:9,fontWeight:"700",fontFamily:"monospace"},scrollContent:{paddingTop:8,paddingBottom:20},sectionHeader:{flexDirection:"row",alignItems:"center",paddingHorizontal:16,paddingVertical:8,gap:8},sectionTitle:{fontSize:11,fontWeight:"700",letterSpacing:.5,color:macOSColors.text.muted},sectionCountBadge:{backgroundColor:buoyColors.primary+"26",paddingHorizontal:8,paddingVertical:2,borderRadius:4},sectionCountText:{fontSize:10,fontWeight:"700",color:buoyColors.primary,fontFamily:"monospace"},emptyState:{alignItems:"center",paddingVertical:40},emptyTitle:{color:macOSColors.text.primary,fontSize:14,fontWeight:"600",marginTop:12,marginBottom:6},emptyText:{color:macOSColors.text.muted,fontSize:12,textAlign:"center",lineHeight:18}}),expandedStyles=StyleSheet.create({container:{gap:10},viewHistoryButton:{marginLeft:4},viewHistoryText:{fontSize:10,fontWeight:"600",fontFamily:"monospace"},dataContainer:{backgroundColor:buoyColors.base,borderRadius:6,borderWidth:1,borderColor:buoyColors.border,overflow:"hidden",minHeight:60},emptyText:{color:buoyColors.textMuted,fontSize:12,padding:14,fontStyle:"italic"}});
@@ -1,10 +1 @@
1
- "use strict";
2
-
3
- export { ZustandModal } from "./ZustandModal";
4
- export { ZustandIcon, ZUSTAND_ICON_COLOR } from "./ZustandIcon";
5
- export { ZustandStateChangeItem } from "./ZustandStateChangeItem";
6
- export { ZustandStateDetailContent, ZustandStateDetailFooter } from "./ZustandStateDetailContent";
7
- export { ZustandStateInfoView } from "./ZustandStateInfoView";
8
- export { ZustandDetailViewToggle } from "./ZustandDetailViewToggle";
9
- export { ZustandActionButton } from "./ZustandActionButton";
10
- export { ZustandStoreBrowser } from "./ZustandStoreBrowser";
1
+ "use strict";export{ZustandModal}from"./ZustandModal";export{ZustandIcon,ZUSTAND_ICON_COLOR}from"./ZustandIcon";export{ZustandStateChangeItem}from"./ZustandStateChangeItem";export{ZustandStateDetailContent,ZustandStateDetailFooter}from"./ZustandStateDetailContent";export{ZustandStateInfoView}from"./ZustandStateInfoView";export{ZustandDetailViewToggle}from"./ZustandDetailViewToggle";export{ZustandActionButton}from"./ZustandActionButton";export{ZustandStoreBrowser}from"./ZustandStoreBrowser";
@@ -1,3 +1 @@
1
- "use strict";
2
-
3
- export { useZustandStateChanges } from "./useZustandStateChanges";
1
+ "use strict";export{useZustandStateChanges}from"./useZustandStateChanges";
@@ -1,88 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * Hook for consuming Zustand state changes from the store
5
- *
6
- * Mirrors useReduxActions.ts from @buoy-gg/redux
7
- */
8
-
9
- import { useState, useEffect, useMemo, useCallback } from "react";
10
- import { zustandStateStore } from "../utils/zustandStateStore";
11
- export function useZustandStateChanges() {
12
- const [stateChanges, setStateChanges] = useState(() => zustandStateStore.getStateChanges());
13
- const [stores, setStores] = useState(() => zustandStateStore.getStores());
14
- const [filter, setFilter] = useState({});
15
- const [isEnabled, setIsEnabled] = useState(() => zustandStateStore.getEnabled());
16
-
17
- // Subscribe to state changes
18
- useEffect(() => {
19
- const unsubChanges = zustandStateStore.subscribe(newChanges => {
20
- setStateChanges(newChanges);
21
- });
22
- const unsubStores = zustandStateStore.subscribeToStores(newStores => {
23
- setStores(newStores);
24
- });
25
- return () => {
26
- unsubChanges();
27
- unsubStores();
28
- };
29
- }, []);
30
-
31
- // Filter state changes — derive directly from state to avoid stale reads
32
- const filteredChanges = useMemo(() => {
33
- let filtered = stateChanges;
34
- if (filter.searchText) {
35
- const search = filter.searchText.toLowerCase();
36
- filtered = filtered.filter(c => c.storeName.toLowerCase().includes(search) || c.partialPreview.toLowerCase().includes(search) || c.changedKeys.some(k => k.toLowerCase().includes(search)));
37
- }
38
- if (filter.storeNames && filter.storeNames.length > 0) {
39
- filtered = filtered.filter(c => filter.storeNames.includes(c.storeName));
40
- }
41
- if (filter.onlyWithChanges) {
42
- filtered = filtered.filter(c => c.hasStateChange);
43
- }
44
- return filtered;
45
- }, [stateChanges, filter]);
46
-
47
- // Get stats — derive directly from state
48
- const stats = useMemo(() => {
49
- const total = stateChanges.length;
50
- const withChanges = stateChanges.filter(c => c.hasStateChange).length;
51
- return {
52
- totalChanges: total,
53
- changesWithStateChange: withChanges,
54
- changesWithoutStateChange: total - withChanges,
55
- storeCount: stores.length,
56
- averageDuration: 0
57
- };
58
- }, [stateChanges, stores]);
59
-
60
- // Get unique store names
61
- const storeNames = useMemo(() => {
62
- return zustandStateStore.getUniqueStoreNames();
63
- }, [stores]);
64
- const clearChanges = useCallback(() => {
65
- zustandStateStore.clearStateChanges();
66
- }, []);
67
- const toggleCapture = useCallback(() => {
68
- const newEnabled = !isEnabled;
69
- zustandStateStore.setEnabled(newEnabled);
70
- setIsEnabled(newEnabled);
71
- }, [isEnabled]);
72
- const getChangeById = useCallback(id => {
73
- return zustandStateStore.getStateChangeById(id);
74
- }, []);
75
- return {
76
- stateChanges,
77
- filteredChanges,
78
- filter,
79
- setFilter,
80
- stats,
81
- stores,
82
- clearChanges,
83
- isEnabled,
84
- toggleCapture,
85
- storeNames,
86
- getChangeById
87
- };
88
- }
1
+ "use strict";import{useState,useEffect,useMemo,useCallback}from"react";import{zustandStateStore}from"../utils/zustandStateStore";export function useZustandStateChanges(){const[e,t]=useState(()=>zustandStateStore.getStateChanges()),[a,s]=useState(()=>zustandStateStore.getStores()),[r,n]=useState({}),[o,u]=useState(()=>zustandStateStore.getEnabled());useEffect(()=>{const e=zustandStateStore.subscribe(e=>{t(e)}),a=zustandStateStore.subscribeToStores(e=>{s(e)});return()=>{e(),a()}},[]);const S=useMemo(()=>{let t=e;if(r.searchText){const e=r.searchText.toLowerCase();t=t.filter(t=>t.storeName.toLowerCase().includes(e)||t.partialPreview.toLowerCase().includes(e)||t.changedKeys.some(t=>t.toLowerCase().includes(e)))}return r.storeNames&&r.storeNames.length>0&&(t=t.filter(e=>r.storeNames.includes(e.storeName))),r.onlyWithChanges&&(t=t.filter(e=>e.hasStateChange)),t},[e,r]),l=useMemo(()=>{const t=e.length,s=e.filter(e=>e.hasStateChange).length;return{totalChanges:t,changesWithStateChange:s,changesWithoutStateChange:t-s,storeCount:a.length,averageDuration:0}},[e,a]),g=useMemo(()=>zustandStateStore.getUniqueStoreNames(),[a]),c=useCallback(()=>{zustandStateStore.clearStateChanges()},[]),h=useCallback(()=>{const e=!o;zustandStateStore.setEnabled(e),u(e)},[o]),i=useCallback(e=>zustandStateStore.getStateChangeById(e),[]);return{stateChanges:e,filteredChanges:S,filter:r,setFilter:n,stats:l,stores:a,clearChanges:c,isEnabled:o,toggleCapture:h,storeNames:g,getChangeById:i}}
@@ -1,12 +1 @@
1
- "use strict";
2
-
3
- export { ZustandModal } from "./components/ZustandModal";
4
- export { ZustandIcon, ZUSTAND_ICON_COLOR } from "./components/ZustandIcon";
5
- export { ZustandStateChangeItem } from "./components/ZustandStateChangeItem";
6
- export { ZustandStateDetailContent, ZustandStateDetailFooter } from "./components/ZustandStateDetailContent";
7
- export { ZustandStateInfoView } from "./components/ZustandStateInfoView";
8
- export { ZustandDetailViewToggle } from "./components/ZustandDetailViewToggle";
9
- export { ZustandActionButton } from "./components/ZustandActionButton";
10
- export { zustandStateStore } from "./utils/zustandStateStore";
11
- export { watchStores, buoyDevTools, isStoreInstrumented } from "./utils/buoyZustandMiddleware";
12
- export { useZustandStateChanges } from "./hooks/useZustandStateChanges";
1
+ "use strict";export{ZustandModal}from"./components/ZustandModal";export{ZustandIcon,ZUSTAND_ICON_COLOR}from"./components/ZustandIcon";export{ZustandStateChangeItem}from"./components/ZustandStateChangeItem";export{ZustandStateDetailContent,ZustandStateDetailFooter}from"./components/ZustandStateDetailContent";export{ZustandStateInfoView}from"./components/ZustandStateInfoView";export{ZustandDetailViewToggle}from"./components/ZustandDetailViewToggle";export{ZustandActionButton}from"./components/ZustandActionButton";export{zustandStateStore}from"./utils/zustandStateStore";export{watchStores,buoyDevTools,isStoreInstrumented}from"./utils/buoyZustandMiddleware";export{useZustandStateChanges}from"./hooks/useZustandStateChanges";
@@ -1,214 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * Buoy Zustand DevTools — Store instrumentation
5
- *
6
- * Two approaches, from easiest to most detailed:
7
- *
8
- * 1. watchStores() — RECOMMENDED. One line, zero store modifications.
9
- * Uses store.subscribe() externally. Never touches setState.
10
- * Safe to add/remove — cannot break your stores even if our code has bugs.
11
- *
12
- * 2. buoyDevTools() — Opt-in middleware for advanced use.
13
- * Wraps setState to capture the partial argument and timing.
14
- * Requires modifying each store's create() call.
15
- */
16
-
17
- import { zustandStateStore } from "./zustandStateStore";
18
- /** Symbol to mark stores that have been watched/instrumented */
19
- const WATCHED_SYMBOL = Symbol.for("@@buoy-zustand/watched");
20
-
21
- /**
22
- * Detect if a store is using the persist middleware by checking for persist API
23
- */
24
- function detectPersist(store) {
25
- const persist = store.persist;
26
- if (persist && typeof persist.getOptions === "function") {
27
- const options = persist.getOptions();
28
- return {
29
- isPersisted: true,
30
- persistName: options?.name
31
- };
32
- }
33
- return {
34
- isPersisted: false
35
- };
36
- }
37
-
38
- // =============================================================================
39
- // watchStores — Primary, non-intrusive approach
40
- // =============================================================================
41
-
42
- /** Minimal store shape — what Zustand's create() returns */
43
-
44
- /**
45
- * Watch multiple Zustand stores for state changes.
46
- *
47
- * This is the recommended, non-intrusive approach. It uses each store's
48
- * `.subscribe()` method to observe changes from the outside — it never
49
- * modifies `setState` or any store internals.
50
- *
51
- * **Safe by design:** Even if our listener code has a bug, it cannot break
52
- * your stores. All listener code is wrapped in try/catch.
53
- *
54
- * @example
55
- * ```tsx
56
- * // In your _layout.tsx or App.tsx — ONE line:
57
- * import { watchStores } from '@buoy-gg/zustand';
58
- * import { useCounterStore } from './stores/counter';
59
- * import { useAuthStore } from './stores/auth';
60
- * import { useCartStore } from './stores/cart';
61
- *
62
- * watchStores({
63
- * counterStore: useCounterStore,
64
- * authStore: useAuthStore,
65
- * cartStore: useCartStore,
66
- * });
67
- * ```
68
- *
69
- * @param stores - Object mapping store names to Zustand store hooks
70
- * @returns Cleanup function that removes all subscriptions
71
- */
72
- export function watchStores(stores) {
73
- const cleanups = [];
74
- for (const [name, store] of Object.entries(stores)) {
75
- // Skip if already watched
76
- const storeAny = store;
77
- if (storeAny[WATCHED_SYMBOL]) continue;
78
- storeAny[WATCHED_SYMBOL] = true;
79
- const {
80
- isPersisted,
81
- persistName
82
- } = detectPersist(store);
83
-
84
- // Register the store for overview
85
- zustandStateStore.registerStore(name, {
86
- getState: store.getState,
87
- getInitialState: store.getInitialState,
88
- setState: store.setState,
89
- subscribe: store.subscribe
90
- }, {
91
- isPersisted,
92
- persistName
93
- });
94
-
95
- // Subscribe to state changes — completely external, never touches setState
96
- const unsubscribe = store.subscribe((state, prevState) => {
97
- try {
98
- zustandStateStore.addStateChange({
99
- storeName: name,
100
- partial: undefined,
101
- // Not available in subscribe-only mode
102
- replace: false,
103
- prevState,
104
- nextState: state,
105
- duration: undefined,
106
- // Not available in subscribe-only mode
107
- isPersisted
108
- });
109
- } catch {
110
- // Silently catch — our bug must never propagate into the store
111
- }
112
- });
113
- cleanups.push(() => {
114
- unsubscribe();
115
- delete store[WATCHED_SYMBOL];
116
- zustandStateStore.unregisterStore(name);
117
- });
118
- }
119
- return () => {
120
- cleanups.forEach(fn => fn());
121
- };
122
- }
123
-
124
- // =============================================================================
125
- // buoyDevTools — Opt-in middleware (advanced, more detail)
126
- // =============================================================================
127
-
128
- /** Auto-incrementing store name counter for unnamed stores */
129
- let unnamedCounter = 0;
130
-
131
- /**
132
- * Zustand middleware that instruments a store for Buoy DevTools.
133
- *
134
- * This is the advanced approach — it wraps setState to capture:
135
- * - The partial argument passed to setState
136
- * - Precise timing of each setState call
137
- *
138
- * Use this when you want maximum detail. For most cases, `watchStores()`
139
- * is simpler and safer.
140
- *
141
- * @example
142
- * ```tsx
143
- * import { create } from 'zustand';
144
- * import { buoyDevTools } from '@buoy-gg/zustand';
145
- *
146
- * const useCounterStore = create(
147
- * buoyDevTools(
148
- * (set) => ({
149
- * count: 0,
150
- * increment: () => set((s) => ({ count: s.count + 1 })),
151
- * }),
152
- * { name: 'counterStore' }
153
- * )
154
- * );
155
- * ```
156
- */
157
- export function buoyDevTools(config, options) {
158
- return (set, get, api) => {
159
- const storeName = options?.name ?? `store-${++unnamedCounter}`;
160
- const enabled = options?.enabled !== false;
161
- const instrumentedSet = (partial, replace) => {
162
- if (!enabled) {
163
- return set(partial, replace);
164
- }
165
- const startTime = performance.now();
166
- const prevState = get();
167
- set(partial, replace);
168
- const nextState = get();
169
- const duration = performance.now() - startTime;
170
- const resolvedPartial = typeof partial === "function" ? "(updater fn)" : partial;
171
- zustandStateStore.addStateChange({
172
- storeName,
173
- partial: resolvedPartial,
174
- replace: replace ?? false,
175
- prevState,
176
- nextState,
177
- duration,
178
- isPersisted: false
179
- });
180
- };
181
- const result = config(instrumentedSet, get, api);
182
-
183
- // Register the store after creation
184
- setTimeout(() => {
185
- if (api[WATCHED_SYMBOL]) return;
186
- api[WATCHED_SYMBOL] = true;
187
- const {
188
- isPersisted,
189
- persistName
190
- } = detectPersist(api);
191
- zustandStateStore.registerStore(storeName, {
192
- getState: get,
193
- getInitialState: api.getInitialState,
194
- setState: set,
195
- subscribe: api.subscribe
196
- }, {
197
- isPersisted,
198
- persistName
199
- });
200
- }, 0);
201
- return result;
202
- };
203
- }
204
-
205
- // =============================================================================
206
- // Utility
207
- // =============================================================================
208
-
209
- /**
210
- * Check if a store is being watched/instrumented
211
- */
212
- export function isStoreInstrumented(store) {
213
- return store[WATCHED_SYMBOL] === true;
214
- }
1
+ "use strict";import{zustandStateStore}from"./zustandStateStore";const WATCHED_SYMBOL=Symbol.for("@@buoy-zustand/watched");function detectPersist(t){const e=t.persist;if(e&&"function"==typeof e.getOptions){const t=e.getOptions();return{isPersisted:!0,persistName:t?.name}}return{isPersisted:!1}}export function watchStores(t){const e=[];for(const[s,r]of Object.entries(t)){const t=r;if(t[WATCHED_SYMBOL])continue;t[WATCHED_SYMBOL]=!0;const{isPersisted:n,persistName:a}=detectPersist(r);zustandStateStore.registerStore(s,{getState:r.getState,getInitialState:r.getInitialState,setState:r.setState,subscribe:r.subscribe},{isPersisted:n,persistName:a});const i=r.subscribe((t,e)=>{try{zustandStateStore.addStateChange({storeName:s,partial:void 0,replace:!1,prevState:e,nextState:t,duration:void 0,isPersisted:n})}catch{}});e.push(()=>{i(),delete r[WATCHED_SYMBOL],zustandStateStore.unregisterStore(s)})}return()=>{e.forEach(t=>t())}}let unnamedCounter=0;export function buoyDevTools(t,e){return(s,r,n)=>{const a=e?.name??"store-"+ ++unnamedCounter,i=!1!==e?.enabled,o=t((t,e)=>{if(!i)return s(t,e);const n=performance.now(),o=r();s(t,e);const S=r(),u=performance.now()-n,c="function"==typeof t?"(updater fn)":t;zustandStateStore.addStateChange({storeName:a,partial:c,replace:e??!1,prevState:o,nextState:S,duration:u,isPersisted:!1})},r,n);return setTimeout(()=>{if(n[WATCHED_SYMBOL])return;n[WATCHED_SYMBOL]=!0;const{isPersisted:t,persistName:e}=detectPersist(n);zustandStateStore.registerStore(a,{getState:r,getInitialState:n.getInitialState,setState:s,subscribe:n.subscribe},{isPersisted:t,persistName:e})},0),o}}export function isStoreInstrumented(t){return!0===t[WATCHED_SYMBOL]}
@@ -1,4 +1 @@
1
- "use strict";
2
-
3
- export { zustandStateStore } from "./zustandStateStore";
4
- export { watchStores, buoyDevTools, isStoreInstrumented } from "./buoyZustandMiddleware";
1
+ "use strict";export{zustandStateStore}from"./zustandStateStore";export{watchStores,buoyDevTools,isStoreInstrumented}from"./buoyZustandMiddleware";