@buoy-gg/zustand 7.0.32 → 7.0.34
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/lib/commonjs/zustand/components/ZustandDetailViewToggle.js +13 -107
- package/lib/commonjs/zustand/components/ZustandEventFilterView.js +24 -288
- package/lib/commonjs/zustand/components/ZustandModal.js +83 -141
- package/lib/commonjs/zustand/components/ZustandStateChangeItem.js +8 -61
- package/lib/commonjs/zustand/components/ZustandStateInfoView.js +2 -46
- package/lib/commonjs/zustand/components/ZustandStoreBrowser.js +29 -54
- package/lib/commonjs/zustand/hooks/useZustandStateChanges.js +33 -53
- package/lib/commonjs/zustand/sync/zustandSyncAdapter.js +24 -78
- package/lib/commonjs/zustand/utils/snapshotProvider.js +17 -60
- package/lib/commonjs/zustand/utils/zustandStateStore.js +37 -223
- package/lib/module/zustand/components/ZustandDetailViewToggle.js +15 -109
- package/lib/module/zustand/components/ZustandEventFilterView.js +28 -292
- package/lib/module/zustand/components/ZustandModal.js +85 -143
- package/lib/module/zustand/components/ZustandStateChangeItem.js +10 -63
- package/lib/module/zustand/components/ZustandStateInfoView.js +1 -45
- package/lib/module/zustand/components/ZustandStoreBrowser.js +31 -56
- package/lib/module/zustand/hooks/useZustandStateChanges.js +34 -54
- package/lib/module/zustand/sync/zustandSyncAdapter.js +25 -79
- package/lib/module/zustand/utils/snapshotProvider.js +18 -61
- package/lib/module/zustand/utils/zustandStateStore.js +37 -223
- package/lib/typescript/zustand/components/ZustandDetailViewToggle.d.ts +2 -1
- package/lib/typescript/zustand/components/ZustandDetailViewToggle.d.ts.map +1 -1
- package/lib/typescript/zustand/components/ZustandEventFilterView.d.ts +6 -0
- package/lib/typescript/zustand/components/ZustandEventFilterView.d.ts.map +1 -1
- package/lib/typescript/zustand/components/ZustandModal.d.ts.map +1 -1
- package/lib/typescript/zustand/components/ZustandStateChangeItem.d.ts.map +1 -1
- package/lib/typescript/zustand/components/ZustandStateInfoView.d.ts.map +1 -1
- package/lib/typescript/zustand/components/ZustandStoreBrowser.d.ts.map +1 -1
- package/lib/typescript/zustand/hooks/useZustandStateChanges.d.ts +2 -1
- package/lib/typescript/zustand/hooks/useZustandStateChanges.d.ts.map +1 -1
- package/lib/typescript/zustand/sync/zustandSyncAdapter.d.ts +103 -117
- package/lib/typescript/zustand/sync/zustandSyncAdapter.d.ts.map +1 -1
- package/lib/typescript/zustand/utils/zustandStateStore.d.ts +6 -26
- package/lib/typescript/zustand/utils/zustandStateStore.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -7,10 +7,9 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { memo } from "react";
|
|
10
|
-
import {
|
|
11
|
-
import { CompactRow, buoyColors, night, Zap, Clock, RelativeTime } from "@buoy-gg/shared-ui";
|
|
10
|
+
import { ChangeRow, buoyColors, night } from "@buoy-gg/shared-ui";
|
|
12
11
|
import { zustandStateStore } from "../utils/zustandStateStore";
|
|
13
|
-
import { jsx as _jsx
|
|
12
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
14
13
|
/**
|
|
15
14
|
* Get status label based on category
|
|
16
15
|
*/
|
|
@@ -99,68 +98,16 @@ export const ZustandStateChangeItem = /*#__PURE__*/memo(function ZustandStateCha
|
|
|
99
98
|
const sublabel = getSublabel(change);
|
|
100
99
|
const primaryText = getPrimaryText(change);
|
|
101
100
|
const badgeText = getBadgeText(change);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
// alternative — letting CompactRow draw `badgeText` itself — leaves the one
|
|
105
|
-
// row whose badge matters most (a set that changed NOTHING) as the only row
|
|
106
|
-
// in the list with no handle on its badge.
|
|
107
|
-
const customBadge = /*#__PURE__*/_jsxs(View, {
|
|
108
|
-
style: styles.badgeContainer,
|
|
109
|
-
children: [/*#__PURE__*/_jsx(Text, {
|
|
110
|
-
style: [styles.badgeText, {
|
|
111
|
-
color: statusColor
|
|
112
|
-
}],
|
|
113
|
-
testID: testID ? `${testID}-badge` : undefined,
|
|
114
|
-
children: badgeText
|
|
115
|
-
}), change.isSlowUpdate ? /*#__PURE__*/_jsx(View, {
|
|
116
|
-
style: styles.slowBadge,
|
|
117
|
-
testID: testID ? `${testID}-slow` : undefined,
|
|
118
|
-
children: /*#__PURE__*/_jsx(Clock, {
|
|
119
|
-
size: 12,
|
|
120
|
-
color: buoyColors.error
|
|
121
|
-
})
|
|
122
|
-
}) : change.hasStateChange ? /*#__PURE__*/_jsx(View, {
|
|
123
|
-
style: styles.changeBadge,
|
|
124
|
-
testID: testID ? `${testID}-change` : undefined,
|
|
125
|
-
children: /*#__PURE__*/_jsx(Zap, {
|
|
126
|
-
size: 12,
|
|
127
|
-
color: buoyColors.warning
|
|
128
|
-
})
|
|
129
|
-
}) : null]
|
|
130
|
-
});
|
|
131
|
-
return /*#__PURE__*/_jsx(CompactRow, {
|
|
132
|
-
statusDotColor: statusColor,
|
|
101
|
+
return /*#__PURE__*/_jsx(ChangeRow, {
|
|
102
|
+
statusColor: statusColor,
|
|
133
103
|
statusLabel: statusLabel,
|
|
134
|
-
|
|
104
|
+
sublabel: sublabel,
|
|
135
105
|
primaryText: primaryText,
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
badgeColor: statusColor,
|
|
141
|
-
showChevron: true,
|
|
106
|
+
timestamp: change.timestamp,
|
|
107
|
+
badgeText: badgeText,
|
|
108
|
+
changed: change.hasStateChange,
|
|
109
|
+
slow: change.isSlowUpdate,
|
|
142
110
|
onPress: () => onPress(change),
|
|
143
|
-
testID: testID
|
|
144
|
-
statusTestID: testID ? `${testID}-status` : undefined,
|
|
145
|
-
statusSublabelTestID: testID ? `${testID}-sublabel` : undefined,
|
|
146
|
-
primaryTestID: testID ? `${testID}-keys` : undefined
|
|
111
|
+
testID: testID
|
|
147
112
|
});
|
|
148
|
-
});
|
|
149
|
-
const styles = StyleSheet.create({
|
|
150
|
-
badgeContainer: {
|
|
151
|
-
flexDirection: "row",
|
|
152
|
-
alignItems: "center",
|
|
153
|
-
gap: 4
|
|
154
|
-
},
|
|
155
|
-
badgeText: {
|
|
156
|
-
fontSize: 11,
|
|
157
|
-
fontWeight: "600",
|
|
158
|
-
fontFamily: "monospace"
|
|
159
|
-
},
|
|
160
|
-
changeBadge: {
|
|
161
|
-
paddingHorizontal: 4
|
|
162
|
-
},
|
|
163
|
-
slowBadge: {
|
|
164
|
-
paddingHorizontal: 4
|
|
165
|
-
}
|
|
166
113
|
});
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import React, { memo, useCallback, useState, useEffect } from "react";
|
|
15
15
|
import { View, Text, StyleSheet, ScrollView } from "react-native";
|
|
16
|
-
import { buoyColors, night, Clock, AlertTriangle, Zap, Info, Box, Database, parseValue, ProUpgradeModal, copyToClipboard } from "@buoy-gg/shared-ui";
|
|
16
|
+
import { buoyColors, night, Clock, AlertTriangle, Zap, Info, Box, Database, parseValue, ProUpgradeModal, copyToClipboard, InfoRow } from "@buoy-gg/shared-ui";
|
|
17
17
|
import { useIsPro } from "@buoy-gg/license";
|
|
18
18
|
import { DataViewer } from "@buoy-gg/shared-ui/dataViewer";
|
|
19
19
|
import { ZustandActionButton } from "./ZustandActionButton";
|
|
@@ -74,29 +74,6 @@ function SectionHeader({
|
|
|
74
74
|
}), badge]
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
|
-
function InfoRow({
|
|
78
|
-
label,
|
|
79
|
-
value,
|
|
80
|
-
valueColor,
|
|
81
|
-
mono = false,
|
|
82
|
-
testID
|
|
83
|
-
}) {
|
|
84
|
-
return /*#__PURE__*/_jsxs(View, {
|
|
85
|
-
style: styles.infoRow,
|
|
86
|
-
children: [/*#__PURE__*/_jsx(Text, {
|
|
87
|
-
style: styles.infoLabel,
|
|
88
|
-
children: label
|
|
89
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
90
|
-
testID: testID,
|
|
91
|
-
style: [styles.infoValue, mono && styles.infoValueMono, valueColor ? {
|
|
92
|
-
color: valueColor
|
|
93
|
-
} : undefined],
|
|
94
|
-
numberOfLines: 1,
|
|
95
|
-
ellipsizeMode: "middle",
|
|
96
|
-
children: value
|
|
97
|
-
})]
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
77
|
function formatTime(timestamp) {
|
|
101
78
|
const date = new Date(timestamp);
|
|
102
79
|
const hours = date.getHours().toString().padStart(2, "0");
|
|
@@ -361,27 +338,6 @@ const styles = StyleSheet.create({
|
|
|
361
338
|
padding: 14,
|
|
362
339
|
gap: 10
|
|
363
340
|
},
|
|
364
|
-
infoRow: {
|
|
365
|
-
flexDirection: "row",
|
|
366
|
-
alignItems: "center",
|
|
367
|
-
justifyContent: "space-between"
|
|
368
|
-
},
|
|
369
|
-
infoLabel: {
|
|
370
|
-
fontSize: 11,
|
|
371
|
-
color: night.color.textTertiary,
|
|
372
|
-
fontWeight: "500"
|
|
373
|
-
},
|
|
374
|
-
infoValue: {
|
|
375
|
-
fontSize: 11,
|
|
376
|
-
color: night.color.text,
|
|
377
|
-
fontWeight: "500",
|
|
378
|
-
flex: 1,
|
|
379
|
-
textAlign: "right",
|
|
380
|
-
marginLeft: 12
|
|
381
|
-
},
|
|
382
|
-
infoValueMono: {
|
|
383
|
-
fontFamily: "monospace"
|
|
384
|
-
},
|
|
385
341
|
badgeRow: {
|
|
386
342
|
flexDirection: "row",
|
|
387
343
|
flexWrap: "wrap",
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { useMemo } from "react";
|
|
13
|
-
import { View, Text, StyleSheet, ScrollView
|
|
14
|
-
import { CompactRow, night,
|
|
13
|
+
import { View, Text, StyleSheet, ScrollView } from "react-native";
|
|
14
|
+
import { CompactRow, night, ToolEmptyState, resolveListEmptyState } from "@buoy-gg/shared-ui";
|
|
15
15
|
import { zustandStateStore } from "../utils/zustandStateStore";
|
|
16
16
|
import { ZUSTAND_TEST_ID, storeRowTestID } from "../utils/testIDs";
|
|
17
17
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
@@ -40,36 +40,26 @@ function getKeysPreview(store) {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
function LoadingBrowserState() {
|
|
43
|
-
return /*#__PURE__*/
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
53
|
-
style: styles.emptyText,
|
|
54
|
-
children: "Waiting for the device to send its registered stores."
|
|
55
|
-
})]
|
|
43
|
+
return /*#__PURE__*/_jsx(ToolEmptyState, {
|
|
44
|
+
testID: `${ZUSTAND_TEST_ID}-stores-loading`
|
|
45
|
+
// Replaces the spinner rather than joining it: two animated things
|
|
46
|
+
// stacked is clutter, and `loading` IS the indeterminate spinner in
|
|
47
|
+
// mascot form.
|
|
48
|
+
,
|
|
49
|
+
otto: "loading",
|
|
50
|
+
title: "Loading stores\u2026",
|
|
51
|
+
message: "Waiting for the device to send its registered stores."
|
|
56
52
|
});
|
|
57
53
|
}
|
|
58
54
|
function EmptyBrowserState() {
|
|
59
|
-
return /*#__PURE__*/
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
testID: `${ZUSTAND_TEST_ID}-stores-empty-title`,
|
|
68
|
-
children: "No stores registered"
|
|
69
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
70
|
-
style: styles.emptyText,
|
|
71
|
-
children: "Use watchStores() to register your Zustand stores.\nThey will appear here with their current state."
|
|
72
|
-
})]
|
|
55
|
+
return /*#__PURE__*/_jsx(ToolEmptyState, {
|
|
56
|
+
testID: `${ZUSTAND_TEST_ID}-stores-empty`
|
|
57
|
+
// scanning = "scanning for targets… and finding none". The registry is
|
|
58
|
+
// discovered, not configured, so an empty one is a scan result.
|
|
59
|
+
,
|
|
60
|
+
otto: "scanning",
|
|
61
|
+
title: "No stores registered",
|
|
62
|
+
message: "Use watchStores() to register your Zustand stores.\nThey will appear here with their current state."
|
|
73
63
|
});
|
|
74
64
|
}
|
|
75
65
|
export function ZustandStoreBrowser({
|
|
@@ -88,17 +78,19 @@ export function ZustandStoreBrowser({
|
|
|
88
78
|
return isLoading ? /*#__PURE__*/_jsx(LoadingBrowserState, {}) : /*#__PURE__*/_jsx(EmptyBrowserState, {});
|
|
89
79
|
}
|
|
90
80
|
if (filteredStores.length === 0 && searchQuery) {
|
|
91
|
-
return /*#__PURE__*/
|
|
92
|
-
style: styles.emptyState,
|
|
81
|
+
return /*#__PURE__*/_jsx(ToolEmptyState, {
|
|
93
82
|
testID: `${ZUSTAND_TEST_ID}-stores-no-match`,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
83
|
+
...resolveListEmptyState({
|
|
84
|
+
subject: "stores",
|
|
85
|
+
verb: "registered",
|
|
86
|
+
search: searchQuery,
|
|
87
|
+
total: stores.length,
|
|
88
|
+
waiting: {
|
|
89
|
+
otto: "scanning",
|
|
90
|
+
title: "No stores registered",
|
|
91
|
+
message: "Use watchStores() to register your Zustand stores."
|
|
92
|
+
}
|
|
93
|
+
})
|
|
102
94
|
});
|
|
103
95
|
}
|
|
104
96
|
return /*#__PURE__*/_jsxs(ScrollView, {
|
|
@@ -204,22 +196,5 @@ const styles = StyleSheet.create({
|
|
|
204
196
|
fontWeight: "700",
|
|
205
197
|
color: night.color.accent,
|
|
206
198
|
fontFamily: "monospace"
|
|
207
|
-
},
|
|
208
|
-
emptyState: {
|
|
209
|
-
alignItems: "center",
|
|
210
|
-
paddingVertical: 40
|
|
211
|
-
},
|
|
212
|
-
emptyTitle: {
|
|
213
|
-
color: night.color.text,
|
|
214
|
-
fontSize: 14,
|
|
215
|
-
fontWeight: "600",
|
|
216
|
-
marginTop: 12,
|
|
217
|
-
marginBottom: 6
|
|
218
|
-
},
|
|
219
|
-
emptyText: {
|
|
220
|
-
color: night.color.textTertiary,
|
|
221
|
-
fontSize: 12,
|
|
222
|
-
textAlign: "center",
|
|
223
|
-
lineHeight: 18
|
|
224
199
|
}
|
|
225
200
|
});
|
|
@@ -3,51 +3,39 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Hook for consuming Zustand state changes from the store
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* The subscribe / filter / pause / clear plumbing is shared `useChangeLog`;
|
|
7
|
+
* this file owns the zustand-specific filter, stats and store registry.
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
|
-
import {
|
|
10
|
+
import { useCallback, useMemo } from "react";
|
|
11
|
+
import { useChangeLog, useSubscribedValue } from "@buoy-gg/shared-ui";
|
|
10
12
|
import { zustandStateStore } from "../utils/zustandStateStore";
|
|
13
|
+
function filterChanges(changes, filter) {
|
|
14
|
+
let filtered = changes;
|
|
15
|
+
if (filter.searchText) {
|
|
16
|
+
const search = filter.searchText.toLowerCase();
|
|
17
|
+
filtered = filtered.filter(c => c.storeName.toLowerCase().includes(search) || c.partialPreview.toLowerCase().includes(search) || c.changedKeys.some(k => k.toLowerCase().includes(search)));
|
|
18
|
+
}
|
|
19
|
+
if (filter.storeNames && filter.storeNames.length > 0) {
|
|
20
|
+
filtered = filtered.filter(c => filter.storeNames.includes(c.storeName));
|
|
21
|
+
}
|
|
22
|
+
if (filter.onlyWithChanges) {
|
|
23
|
+
filtered = filtered.filter(c => c.hasStateChange);
|
|
24
|
+
}
|
|
25
|
+
return filtered;
|
|
26
|
+
}
|
|
27
|
+
const readStores = () => zustandStateStore.getStores();
|
|
28
|
+
const subscribeToStores = listener => zustandStateStore.subscribeToStores(listener);
|
|
11
29
|
export function useZustandStateChanges() {
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
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]);
|
|
30
|
+
const log = useChangeLog(zustandStateStore, {
|
|
31
|
+
filterItems: filterChanges
|
|
32
|
+
});
|
|
33
|
+
const stores = useSubscribedValue(readStores, subscribeToStores);
|
|
46
34
|
|
|
47
35
|
// Get stats — derive directly from state
|
|
48
36
|
const stats = useMemo(() => {
|
|
49
|
-
const total =
|
|
50
|
-
const withChanges =
|
|
37
|
+
const total = log.items.length;
|
|
38
|
+
const withChanges = log.items.filter(c => c.hasStateChange).length;
|
|
51
39
|
return {
|
|
52
40
|
totalChanges: total,
|
|
53
41
|
changesWithStateChange: withChanges,
|
|
@@ -55,33 +43,25 @@ export function useZustandStateChanges() {
|
|
|
55
43
|
storeCount: stores.length,
|
|
56
44
|
averageDuration: 0
|
|
57
45
|
};
|
|
58
|
-
}, [
|
|
46
|
+
}, [log.items, stores]);
|
|
59
47
|
|
|
60
48
|
// Get unique store names
|
|
61
49
|
const storeNames = useMemo(() => {
|
|
62
50
|
return zustandStateStore.getUniqueStoreNames();
|
|
63
51
|
}, [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
52
|
const getChangeById = useCallback(id => {
|
|
73
53
|
return zustandStateStore.getStateChangeById(id);
|
|
74
54
|
}, []);
|
|
75
55
|
return {
|
|
76
|
-
stateChanges,
|
|
77
|
-
filteredChanges,
|
|
78
|
-
filter,
|
|
79
|
-
setFilter,
|
|
56
|
+
stateChanges: log.items,
|
|
57
|
+
filteredChanges: log.filteredItems,
|
|
58
|
+
filter: log.filter,
|
|
59
|
+
setFilter: log.setFilter,
|
|
80
60
|
stats,
|
|
81
61
|
stores,
|
|
82
|
-
clearChanges,
|
|
83
|
-
isEnabled,
|
|
84
|
-
toggleCapture,
|
|
62
|
+
clearChanges: log.clear,
|
|
63
|
+
isEnabled: log.isEnabled,
|
|
64
|
+
toggleCapture: log.toggleCapture,
|
|
85
65
|
storeNames,
|
|
86
66
|
getChangeById
|
|
87
67
|
};
|
|
@@ -1,25 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { zustandStateStore } from "../utils/zustandStateStore";
|
|
4
|
-
import { isJsonSerializable, isOverWireBudget } from "@buoy-gg/shared-ui";
|
|
4
|
+
import { WIRE_DETAIL_LIMIT_BYTES, createSyncAdapter, detailByIdAction, isJsonSerializable, isOverWireBudget, memoByIdentity, onDeviceMarker, readParams, toWire, truncatedNote } from "@buoy-gg/shared-ui";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Sentinel used in WIRE-FORM snapshots in place of raw prevState/nextState.
|
|
8
8
|
* Same hazard as redux: up to 200 changes × 2 full trees on every snapshot
|
|
9
9
|
* freeze/OOM-killed large apps. Dashboards fetch trees via `getChangeDetail`.
|
|
10
10
|
*/
|
|
11
|
-
export const STATE_ON_DEVICE =
|
|
12
|
-
__buoyStateOnDevice: true,
|
|
13
|
-
note: "State trees stay on the device — fetched on demand via getChangeDetail / getStoreState."
|
|
14
|
-
});
|
|
11
|
+
export const STATE_ON_DEVICE = onDeviceMarker("State", "State trees stay on the device — fetched on demand via getChangeDetail / getStoreState.");
|
|
15
12
|
|
|
16
13
|
/** Wire-form sentinel for an oversized `partial` (replace can carry a whole tree). */
|
|
17
|
-
export const PAYLOAD_ON_DEVICE =
|
|
18
|
-
__buoyPayloadOnDevice: true,
|
|
19
|
-
note: "Partial stays on the device — fetched on demand via getChangeDetail."
|
|
20
|
-
});
|
|
21
|
-
const MAX_CHANGE_DETAIL_BYTES = 8 * 1024 * 1024;
|
|
22
|
-
const MAX_WIRE_PAYLOAD_BYTES = 16 * 1024;
|
|
14
|
+
export const PAYLOAD_ON_DEVICE = onDeviceMarker("Payload", "Partial stays on the device — fetched on demand via getChangeDetail.");
|
|
23
15
|
/** Top-level keys of an object state, for the compact (no-values) view. */
|
|
24
16
|
function topLevelKeys(state) {
|
|
25
17
|
return state && typeof state === "object" && !Array.isArray(state) ? Object.keys(state) : undefined;
|
|
@@ -30,31 +22,17 @@ function topLevelKeys(state) {
|
|
|
30
22
|
* change, so keying on identity is correct and self-invalidating — without it
|
|
31
23
|
* every `partial` in the list is re-measured on all ~5 snapshots per second.
|
|
32
24
|
*/
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
nextState: STATE_ON_DEVICE,
|
|
41
|
-
partial: toWirePartial(change.partial)
|
|
42
|
-
};
|
|
43
|
-
changeCache.set(change, wire);
|
|
44
|
-
return wire;
|
|
45
|
-
}
|
|
46
|
-
function toWirePartial(value) {
|
|
47
|
-
if (value === undefined) return value;
|
|
48
|
-
return isOverWireBudget(value, MAX_WIRE_PAYLOAD_BYTES) ? PAYLOAD_ON_DEVICE : value;
|
|
49
|
-
}
|
|
50
|
-
function toWireState(value) {
|
|
51
|
-
return isOverWireBudget(value, MAX_WIRE_PAYLOAD_BYTES) ? STATE_ON_DEVICE : value;
|
|
52
|
-
}
|
|
25
|
+
const toWireChange = memoByIdentity(change => ({
|
|
26
|
+
...change,
|
|
27
|
+
prevState: STATE_ON_DEVICE,
|
|
28
|
+
nextState: STATE_ON_DEVICE,
|
|
29
|
+
partial: toWire(change.partial, PAYLOAD_ON_DEVICE)
|
|
30
|
+
}));
|
|
31
|
+
const toWireState = value => toWire(value, STATE_ON_DEVICE);
|
|
53
32
|
|
|
54
33
|
/**
|
|
55
34
|
* Sync adapter for the zustand tool, consumed by @buoy-gg/external-sync's
|
|
56
|
-
* `useExternalSync
|
|
57
|
-
* this package doesn't need a dependency on it).
|
|
35
|
+
* `useExternalSync`.
|
|
58
36
|
*
|
|
59
37
|
* State changes are captured by the middleware (watchStores/buoyDevTools),
|
|
60
38
|
* independent of whether a dashboard is watching — subscribing here only
|
|
@@ -69,7 +47,7 @@ function toWireState(value) {
|
|
|
69
47
|
*
|
|
70
48
|
* The `setState` action powers remote time-travel/reset from the dashboard.
|
|
71
49
|
*/
|
|
72
|
-
export const zustandSyncAdapter = {
|
|
50
|
+
export const zustandSyncAdapter = createSyncAdapter({
|
|
73
51
|
version: 2,
|
|
74
52
|
getSnapshot: () => ({
|
|
75
53
|
changes: zustandStateStore.getStateChanges().map(toWireChange),
|
|
@@ -78,14 +56,7 @@ export const zustandSyncAdapter = {
|
|
|
78
56
|
currentState: toWireState(store.currentState)
|
|
79
57
|
}))
|
|
80
58
|
}),
|
|
81
|
-
subscribe: onChange =>
|
|
82
|
-
const unsubscribeChanges = zustandStateStore.subscribe(onChange);
|
|
83
|
-
const unsubscribeStores = zustandStateStore.subscribeToStores(onChange);
|
|
84
|
-
return () => {
|
|
85
|
-
unsubscribeChanges();
|
|
86
|
-
unsubscribeStores();
|
|
87
|
-
};
|
|
88
|
-
},
|
|
59
|
+
subscribe: [onChange => zustandStateStore.subscribe(onChange), onChange => zustandStateStore.subscribeToStores(onChange)],
|
|
89
60
|
actions: {
|
|
90
61
|
clearEvents: () => {
|
|
91
62
|
zustandStateStore.clearStateChanges();
|
|
@@ -95,32 +66,11 @@ export const zustandSyncAdapter = {
|
|
|
95
66
|
* The per-snapshot stream stays tree-free; this is the explicit,
|
|
96
67
|
* size-guarded channel for the detail pane.
|
|
97
68
|
*/
|
|
98
|
-
getChangeDetail:
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
found: false,
|
|
104
|
-
reason: "missing id"
|
|
105
|
-
};
|
|
106
|
-
const change = zustandStateStore.getStateChangeById(id);
|
|
107
|
-
if (!change) return {
|
|
108
|
-
found: false,
|
|
109
|
-
reason: "unknown id"
|
|
110
|
-
};
|
|
111
|
-
const cappedNote = {
|
|
112
|
-
__buoyTruncated: true,
|
|
113
|
-
note: `Exceeds the ${MAX_CHANGE_DETAIL_BYTES / (1024 * 1024)}MB detail limit — inspect it on-device.`
|
|
114
|
-
};
|
|
115
|
-
const cap = value => isOverWireBudget(value, MAX_CHANGE_DETAIL_BYTES) ? cappedNote : value;
|
|
116
|
-
return {
|
|
117
|
-
found: true,
|
|
118
|
-
id,
|
|
119
|
-
prevState: cap(change.prevState),
|
|
120
|
-
nextState: cap(change.nextState),
|
|
121
|
-
partial: cap(change.partial)
|
|
122
|
-
};
|
|
123
|
-
},
|
|
69
|
+
getChangeDetail: detailByIdAction(id => zustandStateStore.getStateChangeById(id), (change, cap) => ({
|
|
70
|
+
prevState: cap(change.prevState),
|
|
71
|
+
nextState: cap(change.nextState),
|
|
72
|
+
partial: cap(change.partial)
|
|
73
|
+
})),
|
|
124
74
|
/**
|
|
125
75
|
* On-demand current state for one store. Wire-form snapshots replace
|
|
126
76
|
* oversized / unserializable `currentState` with STATE_ON_DEVICE.
|
|
@@ -128,7 +78,7 @@ export const zustandSyncAdapter = {
|
|
|
128
78
|
getStoreState: params => {
|
|
129
79
|
const {
|
|
130
80
|
storeName
|
|
131
|
-
} = params
|
|
81
|
+
} = readParams(params);
|
|
132
82
|
if (!storeName) return {
|
|
133
83
|
found: false,
|
|
134
84
|
reason: "missing storeName"
|
|
@@ -144,11 +94,7 @@ export const zustandSyncAdapter = {
|
|
|
144
94
|
} catch {
|
|
145
95
|
currentState = undefined;
|
|
146
96
|
}
|
|
147
|
-
|
|
148
|
-
__buoyTruncated: true,
|
|
149
|
-
note: `Exceeds the ${MAX_CHANGE_DETAIL_BYTES / (1024 * 1024)}MB detail limit — inspect it on-device.`
|
|
150
|
-
};
|
|
151
|
-
if (!isOverWireBudget(currentState, MAX_CHANGE_DETAIL_BYTES)) {
|
|
97
|
+
if (!isOverWireBudget(currentState, WIRE_DETAIL_LIMIT_BYTES)) {
|
|
152
98
|
return {
|
|
153
99
|
found: true,
|
|
154
100
|
storeName,
|
|
@@ -158,7 +104,7 @@ export const zustandSyncAdapter = {
|
|
|
158
104
|
return {
|
|
159
105
|
found: true,
|
|
160
106
|
storeName,
|
|
161
|
-
currentState: isJsonSerializable(currentState) ?
|
|
107
|
+
currentState: isJsonSerializable(currentState) ? truncatedNote(WIRE_DETAIL_LIMIT_BYTES) : STATE_ON_DEVICE
|
|
162
108
|
};
|
|
163
109
|
},
|
|
164
110
|
/**
|
|
@@ -171,7 +117,7 @@ export const zustandSyncAdapter = {
|
|
|
171
117
|
const {
|
|
172
118
|
includeValues = false,
|
|
173
119
|
limit
|
|
174
|
-
} = params
|
|
120
|
+
} = readParams(params);
|
|
175
121
|
const all = zustandStateStore.getStoreSnapshots();
|
|
176
122
|
const capped = typeof limit === "number" && limit > 0 ? all.slice(0, limit) : all;
|
|
177
123
|
const stores = capped.map(s => ({
|
|
@@ -198,7 +144,7 @@ export const zustandSyncAdapter = {
|
|
|
198
144
|
storeName,
|
|
199
145
|
state,
|
|
200
146
|
replace
|
|
201
|
-
} = params
|
|
147
|
+
} = readParams(params);
|
|
202
148
|
if (!storeName) return {
|
|
203
149
|
ok: false,
|
|
204
150
|
error: "Missing storeName."
|
|
@@ -237,7 +183,7 @@ export const zustandSyncAdapter = {
|
|
|
237
183
|
rehydrate: async params => {
|
|
238
184
|
const {
|
|
239
185
|
storeName
|
|
240
|
-
} = params
|
|
186
|
+
} = readParams(params);
|
|
241
187
|
if (!storeName) return {
|
|
242
188
|
ok: false,
|
|
243
189
|
error: "Missing storeName."
|
|
@@ -267,4 +213,4 @@ export const zustandSyncAdapter = {
|
|
|
267
213
|
};
|
|
268
214
|
}
|
|
269
215
|
}
|
|
270
|
-
};
|
|
216
|
+
});
|