@buoy-gg/zustand 7.0.33 → 7.0.35
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/ZustandActionButton.js +1 -1
- 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 +20 -85
- package/lib/commonjs/zustand/components/ZustandStateChangeItem.js +8 -61
- package/lib/commonjs/zustand/components/ZustandStateDetailContent.js +5 -5
- package/lib/commonjs/zustand/components/ZustandStateInfoView.js +8 -52
- package/lib/commonjs/zustand/components/ZustandStoreBrowser.js +2 -2
- package/lib/commonjs/zustand/components/ZustandStoreDetailContent.js +4 -4
- package/lib/commonjs/zustand/components/ZustandStoreEditorScreen.js +3 -3
- 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/ZustandActionButton.js +2 -2
- 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 +22 -87
- package/lib/module/zustand/components/ZustandStateChangeItem.js +10 -63
- package/lib/module/zustand/components/ZustandStateDetailContent.js +6 -6
- package/lib/module/zustand/components/ZustandStateInfoView.js +7 -51
- package/lib/module/zustand/components/ZustandStoreBrowser.js +3 -3
- package/lib/module/zustand/components/ZustandStoreDetailContent.js +5 -5
- package/lib/module/zustand/components/ZustandStoreEditorScreen.js +4 -4
- 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/ZustandStateDetailContent.d.ts.map +1 -1
- package/lib/typescript/zustand/components/ZustandStateInfoView.d.ts.map +1 -1
- package/lib/typescript/zustand/components/ZustandStoreDetailContent.d.ts.map +1 -1
- package/lib/typescript/zustand/components/ZustandStoreEditorScreen.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
|
@@ -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, monoFont } 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");
|
|
@@ -355,33 +332,12 @@ const styles = StyleSheet.create({
|
|
|
355
332
|
fontWeight: "600",
|
|
356
333
|
letterSpacing: 0.5,
|
|
357
334
|
color: night.color.accent,
|
|
358
|
-
fontFamily:
|
|
335
|
+
fontFamily: monoFont
|
|
359
336
|
},
|
|
360
337
|
cardContent: {
|
|
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",
|
|
@@ -414,7 +370,7 @@ const styles = StyleSheet.create({
|
|
|
414
370
|
fontSize: 9,
|
|
415
371
|
fontWeight: "600",
|
|
416
372
|
color: night.color.textSecondary,
|
|
417
|
-
fontFamily:
|
|
373
|
+
fontFamily: monoFont
|
|
418
374
|
},
|
|
419
375
|
durationTextSlow: {
|
|
420
376
|
color: buoyColors.error
|
|
@@ -476,7 +432,7 @@ const styles = StyleSheet.create({
|
|
|
476
432
|
fontSize: 11,
|
|
477
433
|
fontWeight: "600",
|
|
478
434
|
color: night.color.accent,
|
|
479
|
-
fontFamily:
|
|
435
|
+
fontFamily: monoFont
|
|
480
436
|
},
|
|
481
437
|
countBadge: {
|
|
482
438
|
paddingHorizontal: 8,
|
|
@@ -488,7 +444,7 @@ const styles = StyleSheet.create({
|
|
|
488
444
|
fontSize: 9,
|
|
489
445
|
fontWeight: "700",
|
|
490
446
|
color: night.color.accent,
|
|
491
|
-
fontFamily:
|
|
447
|
+
fontFamily: monoFont
|
|
492
448
|
},
|
|
493
449
|
typeBadge: {
|
|
494
450
|
paddingHorizontal: 6,
|
|
@@ -500,7 +456,7 @@ const styles = StyleSheet.create({
|
|
|
500
456
|
fontSize: 8,
|
|
501
457
|
fontWeight: "600",
|
|
502
458
|
color: night.color.textTertiary,
|
|
503
|
-
fontFamily:
|
|
459
|
+
fontFamily: monoFont
|
|
504
460
|
},
|
|
505
461
|
dataViewerContainer: {
|
|
506
462
|
// Inset display panel — darker than the card, the night track color.
|
|
@@ -510,7 +466,7 @@ const styles = StyleSheet.create({
|
|
|
510
466
|
updaterText: {
|
|
511
467
|
fontSize: 12,
|
|
512
468
|
color: night.color.text,
|
|
513
|
-
fontFamily:
|
|
469
|
+
fontFamily: monoFont
|
|
514
470
|
},
|
|
515
471
|
updaterSubtext: {
|
|
516
472
|
fontSize: 11,
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import { useMemo } from "react";
|
|
13
13
|
import { View, Text, StyleSheet, ScrollView } from "react-native";
|
|
14
|
-
import { CompactRow, night, ToolEmptyState, resolveListEmptyState } from "@buoy-gg/shared-ui";
|
|
14
|
+
import { CompactRow, night, ToolEmptyState, resolveListEmptyState, monoFont } 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";
|
|
@@ -166,7 +166,7 @@ const styles = StyleSheet.create({
|
|
|
166
166
|
absCountText: {
|
|
167
167
|
fontSize: 9,
|
|
168
168
|
fontWeight: "700",
|
|
169
|
-
fontFamily:
|
|
169
|
+
fontFamily: monoFont
|
|
170
170
|
},
|
|
171
171
|
scrollContent: {
|
|
172
172
|
paddingTop: 8,
|
|
@@ -195,6 +195,6 @@ const styles = StyleSheet.create({
|
|
|
195
195
|
fontSize: 10,
|
|
196
196
|
fontWeight: "700",
|
|
197
197
|
color: night.color.accent,
|
|
198
|
-
fontFamily:
|
|
198
|
+
fontFamily: monoFont
|
|
199
199
|
}
|
|
200
200
|
});
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
|
|
35
35
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
36
36
|
import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
|
37
|
-
import { buoyColors, Database, ExpandedInfoRow, FileJson, night, PillBadge, parseValue } from "@buoy-gg/shared-ui";
|
|
37
|
+
import { buoyColors, Database, ExpandedInfoRow, FileJson, night, PillBadge, parseValue, monoFont } from "@buoy-gg/shared-ui";
|
|
38
38
|
import { DataViewer, LiveExplorer, ValueEditModal, updateNestedDataByPath, deleteNestedDataByPath } from "@buoy-gg/shared-ui/dataViewer";
|
|
39
39
|
import { computeMergePartial, editableStateOf, isPlainObjectLike } from "../utils/editableState";
|
|
40
40
|
import { ZUSTAND_TEST_ID, storeRowTestID } from "../utils/testIDs";
|
|
@@ -394,7 +394,7 @@ const styles = StyleSheet.create({
|
|
|
394
394
|
color: night.color.accent,
|
|
395
395
|
letterSpacing: 0.5,
|
|
396
396
|
textTransform: "uppercase",
|
|
397
|
-
fontFamily:
|
|
397
|
+
fontFamily: monoFont
|
|
398
398
|
},
|
|
399
399
|
rawButton: {
|
|
400
400
|
flexDirection: "row",
|
|
@@ -441,7 +441,7 @@ const styles = StyleSheet.create({
|
|
|
441
441
|
borderBottomColor: night.color.border,
|
|
442
442
|
letterSpacing: 0.5,
|
|
443
443
|
textTransform: "uppercase",
|
|
444
|
-
fontFamily:
|
|
444
|
+
fontFamily: monoFont
|
|
445
445
|
},
|
|
446
446
|
infoBody: {
|
|
447
447
|
padding: 12,
|
|
@@ -457,13 +457,13 @@ const styles = StyleSheet.create({
|
|
|
457
457
|
inlineLinkText: {
|
|
458
458
|
fontSize: 10,
|
|
459
459
|
fontWeight: "600",
|
|
460
|
-
fontFamily:
|
|
460
|
+
fontFamily: monoFont
|
|
461
461
|
},
|
|
462
462
|
actionsText: {
|
|
463
463
|
flex: 1,
|
|
464
464
|
color: night.color.textSecondary,
|
|
465
465
|
fontSize: 10,
|
|
466
|
-
fontFamily:
|
|
466
|
+
fontFamily: monoFont,
|
|
467
467
|
lineHeight: 15
|
|
468
468
|
}
|
|
469
469
|
});
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
|
|
32
32
|
import { useCallback, useMemo, useState } from "react";
|
|
33
33
|
import { Alert, ScrollView, StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
|
34
|
-
import { buoyColors, Database, FileJson, night, PillBadge, ToolBackground, useSafeAreaInsets, X } from "@buoy-gg/shared-ui";
|
|
34
|
+
import { buoyColors, Database, FileJson, night, PillBadge, ToolBackground, useSafeAreaInsets, X, monoFont } from "@buoy-gg/shared-ui";
|
|
35
35
|
import { DataTreeActionDock, DataViewer, getAtPath, useTreeEditor, ValueEditModal } from "@buoy-gg/shared-ui/dataViewer";
|
|
36
36
|
import { computeMergePartial, editableStateOf } from "../utils/editableState";
|
|
37
37
|
import { ZUSTAND_TEST_ID } from "../utils/testIDs";
|
|
@@ -297,7 +297,7 @@ const styles = StyleSheet.create({
|
|
|
297
297
|
color: night.color.text,
|
|
298
298
|
fontSize: 13,
|
|
299
299
|
fontWeight: "600",
|
|
300
|
-
fontFamily:
|
|
300
|
+
fontFamily: monoFont
|
|
301
301
|
},
|
|
302
302
|
headerState: {
|
|
303
303
|
color: night.color.textTertiary,
|
|
@@ -316,7 +316,7 @@ const styles = StyleSheet.create({
|
|
|
316
316
|
metaText: {
|
|
317
317
|
color: night.color.textTertiary,
|
|
318
318
|
fontSize: 11,
|
|
319
|
-
fontFamily:
|
|
319
|
+
fontFamily: monoFont
|
|
320
320
|
},
|
|
321
321
|
treeScroll: {
|
|
322
322
|
flex: 1
|
|
@@ -341,7 +341,7 @@ const styles = StyleSheet.create({
|
|
|
341
341
|
error: {
|
|
342
342
|
color: night.color.danger,
|
|
343
343
|
fontSize: 11,
|
|
344
|
-
fontFamily:
|
|
344
|
+
fontFamily: monoFont,
|
|
345
345
|
paddingHorizontal: 12,
|
|
346
346
|
paddingBottom: 6
|
|
347
347
|
},
|
|
@@ -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
|
+
});
|
|
@@ -13,8 +13,7 @@
|
|
|
13
13
|
* strips functions (recording where they were), and restore re-grafts the
|
|
14
14
|
* LIVE store's functions onto the snapshot before replacing.
|
|
15
15
|
*/
|
|
16
|
-
import { registerStateSnapshotProvider } from "@buoy-gg/shared-ui";
|
|
17
|
-
import { boundedDeepEqual, jsonLikelyLargerThan } from "@buoy-gg/shared-ui/dataViewer";
|
|
16
|
+
import { classifyPreviewItem, createRestoreScope, emptyRestoreResult, failedRestore, registerStateSnapshotProvider, unionKeys } from "@buoy-gg/shared-ui";
|
|
18
17
|
import { zustandStateStore } from "./zustandStateStore";
|
|
19
18
|
import { isPlainObjectLike, stripFunctions, STRIP_MAX_DEPTH } from "./editableState";
|
|
20
19
|
|
|
@@ -93,29 +92,13 @@ async function capture() {
|
|
|
93
92
|
}
|
|
94
93
|
async function restore(data, options) {
|
|
95
94
|
const snapshot = data;
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
const touch = key => !excludedSet.has(key) && (!includedSet || includedSet.has(key));
|
|
99
|
-
let excludedHits = 0;
|
|
100
|
-
const result = {
|
|
101
|
-
ok: true,
|
|
102
|
-
applied: 0,
|
|
103
|
-
skipped: [],
|
|
104
|
-
warnings: []
|
|
105
|
-
};
|
|
95
|
+
const scope = createRestoreScope(options);
|
|
96
|
+
const result = emptyRestoreResult();
|
|
106
97
|
if (!snapshot?.stores) {
|
|
107
|
-
return
|
|
108
|
-
ok: false,
|
|
109
|
-
applied: 0,
|
|
110
|
-
skipped: [],
|
|
111
|
-
warnings: ["empty zustand snapshot"]
|
|
112
|
-
};
|
|
98
|
+
return failedRestore("empty zustand snapshot");
|
|
113
99
|
}
|
|
114
100
|
for (const [name, envelope] of Object.entries(snapshot.stores)) {
|
|
115
|
-
if (!
|
|
116
|
-
excludedHits += 1;
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
101
|
+
if (!scope.allows(name)) continue;
|
|
119
102
|
const info = zustandStateStore.getStore(name);
|
|
120
103
|
if (!info) {
|
|
121
104
|
result.skipped.push({
|
|
@@ -142,12 +125,10 @@ async function restore(data, options) {
|
|
|
142
125
|
result.ok = false;
|
|
143
126
|
}
|
|
144
127
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
128
|
+
const excludedWarning = scope.excludedWarning("store(s)");
|
|
129
|
+
if (excludedWarning) result.warnings.push(excludedWarning);
|
|
148
130
|
return result;
|
|
149
131
|
}
|
|
150
|
-
const ITEM_RENDER_BYTE_LIMIT = 256 * 1024;
|
|
151
132
|
|
|
152
133
|
/**
|
|
153
134
|
* Per-store restore preview. Both sides are function-stripped through the
|
|
@@ -176,8 +157,7 @@ async function previewItems(target, base) {
|
|
|
176
157
|
const items = [];
|
|
177
158
|
const notes = [];
|
|
178
159
|
let preservedFns = 0;
|
|
179
|
-
const
|
|
180
|
-
for (const name of names) {
|
|
160
|
+
for (const name of unionKeys(baseStores, targetStores)) {
|
|
181
161
|
const inTarget = name in targetStores;
|
|
182
162
|
const inBase = name in baseStores;
|
|
183
163
|
if (!inTarget) {
|
|
@@ -200,39 +180,16 @@ async function previewItems(target, base) {
|
|
|
200
180
|
});
|
|
201
181
|
continue;
|
|
202
182
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const newState = targetStores[name].state;
|
|
214
|
-
if (boundedDeepEqual(oldState, newState)) {
|
|
215
|
-
items.push({
|
|
216
|
-
key: name,
|
|
217
|
-
label: name,
|
|
218
|
-
verdict: "unchanged"
|
|
219
|
-
});
|
|
220
|
-
} else if (jsonLikelyLargerThan(oldState, ITEM_RENDER_BYTE_LIMIT) || jsonLikelyLargerThan(newState, ITEM_RENDER_BYTE_LIMIT)) {
|
|
221
|
-
items.push({
|
|
222
|
-
key: name,
|
|
223
|
-
label: name,
|
|
224
|
-
verdict: "too-large",
|
|
225
|
-
reason: "store too large to render a diff — restore still applies it"
|
|
226
|
-
});
|
|
227
|
-
} else {
|
|
228
|
-
items.push({
|
|
229
|
-
key: name,
|
|
230
|
-
label: name,
|
|
231
|
-
verdict: "changed",
|
|
232
|
-
oldValue: oldState,
|
|
233
|
-
newValue: newState
|
|
234
|
-
});
|
|
235
|
-
}
|
|
183
|
+
items.push(classifyPreviewItem({
|
|
184
|
+
key: name,
|
|
185
|
+
label: name,
|
|
186
|
+
oldValue: baseStores[name]?.state,
|
|
187
|
+
newValue: targetStores[name].state,
|
|
188
|
+
inOld: inBase,
|
|
189
|
+
inNew: true
|
|
190
|
+
}, {
|
|
191
|
+
tooLargeReason: "store too large to render a diff — restore still applies it"
|
|
192
|
+
}));
|
|
236
193
|
}
|
|
237
194
|
if (preservedFns > 0) {
|
|
238
195
|
notes.push(`${preservedFns} store action function${preservedFns === 1 ? "" : "s"} preserved on restore (not diffed)`);
|