@buoy-gg/jotai 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/jotai/components/JotaiAtomBrowser.js +20 -41
- package/lib/commonjs/jotai/components/JotaiAtomChangeItem.js +7 -52
- package/lib/commonjs/jotai/components/JotaiAtomDetailContent.js +8 -87
- package/lib/commonjs/jotai/components/JotaiEventFilterView.js +21 -299
- package/lib/commonjs/jotai/components/JotaiModal.js +82 -124
- package/lib/commonjs/jotai/hooks/useJotaiAtomChanges.js +33 -49
- package/lib/commonjs/jotai/sync/jotaiSyncAdapter.js +19 -66
- package/lib/commonjs/jotai/utils/jotaiStateStore.js +23 -188
- package/lib/commonjs/jotai/utils/snapshotProvider.js +7 -25
- package/lib/commonjs/preset.js +14 -22
- package/lib/module/jotai/components/JotaiAtomBrowser.js +21 -42
- package/lib/module/jotai/components/JotaiAtomChangeItem.js +9 -54
- package/lib/module/jotai/components/JotaiAtomDetailContent.js +9 -88
- package/lib/module/jotai/components/JotaiEventFilterView.js +24 -302
- package/lib/module/jotai/components/JotaiModal.js +84 -126
- package/lib/module/jotai/hooks/useJotaiAtomChanges.js +34 -50
- package/lib/module/jotai/sync/jotaiSyncAdapter.js +20 -67
- package/lib/module/jotai/utils/jotaiStateStore.js +23 -188
- package/lib/module/jotai/utils/snapshotProvider.js +8 -26
- package/lib/module/preset.js +13 -20
- package/lib/typescript/jotai/components/JotaiAtomBrowser.d.ts.map +1 -1
- package/lib/typescript/jotai/components/JotaiAtomChangeItem.d.ts.map +1 -1
- package/lib/typescript/jotai/components/JotaiAtomDetailContent.d.ts.map +1 -1
- package/lib/typescript/jotai/components/JotaiEventFilterView.d.ts +2 -2
- package/lib/typescript/jotai/components/JotaiEventFilterView.d.ts.map +1 -1
- package/lib/typescript/jotai/components/JotaiModal.d.ts.map +1 -1
- package/lib/typescript/jotai/hooks/useJotaiAtomChanges.d.ts +2 -1
- package/lib/typescript/jotai/hooks/useJotaiAtomChanges.d.ts.map +1 -1
- package/lib/typescript/jotai/sync/jotaiSyncAdapter.d.ts +54 -68
- package/lib/typescript/jotai/sync/jotaiSyncAdapter.d.ts.map +1 -1
- package/lib/typescript/jotai/utils/jotaiStateStore.d.ts +2 -25
- package/lib/typescript/jotai/utils/jotaiStateStore.d.ts.map +1 -1
- package/lib/typescript/preset.d.ts +2 -8
- package/lib/typescript/preset.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { useState, useMemo, useRef, useCallback, useEffect } from "react";
|
|
12
|
-
import { View, Text, StyleSheet, FlatList,
|
|
13
|
-
import { JsModal, ModalHeader, TabSelector, macOSColors, night, Search, Filter, Power,
|
|
12
|
+
import { View, Text, StyleSheet, FlatList, TouchableOpacity } from "react-native";
|
|
13
|
+
import { JsModal, ModalHeader, TabSelector, macOSColors, night, Search, Filter, Power, ToolEmptyState, resolveListEmptyState, devToolsStorageKeys, ProUpgradeModal, PowerToggleButton, ToolbarCopyButton, ToolbarClearButton, truncatePayload, TickProvider, useLimit, windowDragProps, EVENT_LIST_PRESET } from "@buoy-gg/shared-ui";
|
|
14
14
|
import { useIsPro } from "@buoy-gg/license";
|
|
15
15
|
import { useJotaiAtomChanges } from "../hooks/useJotaiAtomChanges";
|
|
16
16
|
import { JotaiAtomChangeItem } from "./JotaiAtomChangeItem";
|
|
@@ -21,33 +21,32 @@ import { JotaiEventFilterView } from "./JotaiEventFilterView";
|
|
|
21
21
|
import { JOTAI_TEST_ID, buildChangeRowTestIDs } from "../utils/testIDs";
|
|
22
22
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* `removeClippedSubviews` unmounts the views of rows scrolled out of the
|
|
27
|
-
* window, which is what makes a long change list cheap — and which makes
|
|
28
|
-
* Detox report a perfectly rendered row as un-hittable. The env var is
|
|
29
|
-
* inlined at bundle time, so a bundler that does NOT inline it leaves the
|
|
30
|
-
* production behaviour exactly as it was.
|
|
24
|
+
* The change log's "nothing here". Everything but the nouns is the shared
|
|
25
|
+
* precedence — see `ToolEmptyState` in shared-ui for why it lives there.
|
|
31
26
|
*/
|
|
32
|
-
const CLIP_OFFSCREEN_CELLS = !(typeof process !== "undefined" && process.env?.EXPO_PUBLIC_E2E);
|
|
33
27
|
function EventsEmptyState({
|
|
34
|
-
isEnabled
|
|
28
|
+
isEnabled,
|
|
29
|
+
searchText,
|
|
30
|
+
totalChanges
|
|
35
31
|
}) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
32
|
+
const content = resolveListEmptyState({
|
|
33
|
+
subject: "changes",
|
|
34
|
+
search: searchText,
|
|
35
|
+
total: totalChanges,
|
|
36
|
+
capturing: isEnabled,
|
|
37
|
+
waiting: {
|
|
38
|
+
otto: "waiting",
|
|
39
|
+
title: "No atom changes",
|
|
40
|
+
message: "Atom changes will appear here as values update."
|
|
41
|
+
},
|
|
42
|
+
paused: {
|
|
43
|
+
title: "No atom changes",
|
|
44
|
+
message: "Enable capture to start recording atom changes"
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
return /*#__PURE__*/_jsx(ToolEmptyState, {
|
|
48
|
+
...content,
|
|
49
|
+
testID: `${JOTAI_TEST_ID}-empty`
|
|
51
50
|
});
|
|
52
51
|
}
|
|
53
52
|
export function JotaiModal({
|
|
@@ -74,7 +73,9 @@ export function JotaiModal({
|
|
|
74
73
|
const [showFilters, setShowFilters] = useState(false);
|
|
75
74
|
const [ignoredPatterns, setIgnoredPatterns] = useState(new Set());
|
|
76
75
|
const {
|
|
76
|
+
atomChanges,
|
|
77
77
|
filteredChanges,
|
|
78
|
+
stats,
|
|
78
79
|
filter,
|
|
79
80
|
setFilter,
|
|
80
81
|
atoms,
|
|
@@ -97,6 +98,15 @@ export function JotaiModal({
|
|
|
97
98
|
|
|
98
99
|
// Pro is Infinity here, so slice() returns everything and the locked count
|
|
99
100
|
// floors at 0 — no tier branching needed at the call site.
|
|
101
|
+
/**
|
|
102
|
+
* How many changes this atom has captured, before the search — the
|
|
103
|
+
* denominator the scoped empty state needs so "clear the search" is only
|
|
104
|
+
* offered when clearing it would actually show something.
|
|
105
|
+
*/
|
|
106
|
+
const scopedTotalForAtom = useMemo(() => {
|
|
107
|
+
if (!selectedHistoryAtom) return 0;
|
|
108
|
+
return atomChanges.filter(c => c.atomLabel === selectedHistoryAtom).length;
|
|
109
|
+
}, [atomChanges, selectedHistoryAtom]);
|
|
100
110
|
const visibleChanges = useMemo(() => displayedChanges.slice(0, changeLimit), [displayedChanges, changeLimit]);
|
|
101
111
|
const lockedChangeCount = useMemo(() => Math.max(0, displayedChanges.length - changeLimit), [displayedChanges.length, changeLimit]);
|
|
102
112
|
const hasLockedChanges = lockedChangeCount > 0;
|
|
@@ -332,49 +342,24 @@ export function JotaiModal({
|
|
|
332
342
|
onBack: onBack
|
|
333
343
|
}), /*#__PURE__*/_jsx(ModalHeader.Content, {
|
|
334
344
|
title: "",
|
|
335
|
-
children: isSearchActive ? /*#__PURE__*/
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
// header went back to its TABS — whose counts are of the
|
|
354
|
-
// WHOLE capture — while the list stayed filtered, so the tool
|
|
355
|
-
// showed "Atoms (8)" above a single row and offered nothing to
|
|
356
|
-
// explain the gap. The only way back was to reopen a field the
|
|
357
|
-
// user had no reason to think was still armed. Same shape, and
|
|
358
|
-
// the same fix, as the Redux, Events and Zustand tools.
|
|
359
|
-
,
|
|
360
|
-
onBlur: () => {
|
|
361
|
-
if (searchText.length === 0) setIsSearchActive(false);
|
|
362
|
-
},
|
|
363
|
-
accessibilityLabel: "Search Jotai atoms and events",
|
|
364
|
-
autoCapitalize: "none",
|
|
365
|
-
autoCorrect: false,
|
|
366
|
-
returnKeyType: "search"
|
|
367
|
-
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
368
|
-
onPress: closeSearch,
|
|
369
|
-
style: styles.headerSearchClear,
|
|
370
|
-
testID: `${JOTAI_TEST_ID}-search-clear`,
|
|
371
|
-
accessibilityRole: "button",
|
|
372
|
-
accessibilityLabel: "Close search",
|
|
373
|
-
children: /*#__PURE__*/_jsx(X, {
|
|
374
|
-
size: 14,
|
|
375
|
-
color: night.color.textSecondary
|
|
376
|
-
})
|
|
377
|
-
})]
|
|
345
|
+
children: isSearchActive ? /*#__PURE__*/_jsx(ModalHeader.Search, {
|
|
346
|
+
value: searchText,
|
|
347
|
+
onChangeText: handleSearch,
|
|
348
|
+
onCancel: closeSearch,
|
|
349
|
+
placeholder: activeTab === "atoms" ? "Search atoms..." : "Search events...",
|
|
350
|
+
accessibilityLabel: "Search Jotai atoms and events",
|
|
351
|
+
iconSize: 14,
|
|
352
|
+
testID: `${JOTAI_TEST_ID}-search-field`,
|
|
353
|
+
containerTestID: `${JOTAI_TEST_ID}-search`,
|
|
354
|
+
clearTestID: `${JOTAI_TEST_ID}-search-clear`
|
|
355
|
+
// Stay open while the query is still narrowing the list: the
|
|
356
|
+
// header would otherwise go back to its TABS — whose counts are
|
|
357
|
+
// of the WHOLE capture — above a filtered list, with nothing to
|
|
358
|
+
// explain the gap.
|
|
359
|
+
,
|
|
360
|
+
onBlur: () => {
|
|
361
|
+
if (searchText.length === 0) setIsSearchActive(false);
|
|
362
|
+
}
|
|
378
363
|
}) : /*#__PURE__*/_jsx(TabSelector, {
|
|
379
364
|
tabs: [{
|
|
380
365
|
key: "atoms",
|
|
@@ -464,24 +449,25 @@ export function JotaiModal({
|
|
|
464
449
|
keyExtractor: keyExtractor,
|
|
465
450
|
contentContainerStyle: styles.listContent,
|
|
466
451
|
showsVerticalScrollIndicator: true,
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
maxToRenderPerBatch: 8,
|
|
470
|
-
windowSize: 5,
|
|
471
|
-
updateCellsBatchingPeriod: 50
|
|
472
|
-
}) : /*#__PURE__*/_jsxs(View, {
|
|
473
|
-
style: styles.emptyState,
|
|
452
|
+
...EVENT_LIST_PRESET
|
|
453
|
+
}) : /*#__PURE__*/_jsx(ToolEmptyState, {
|
|
474
454
|
testID: `${JOTAI_TEST_ID}-history-empty`,
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
455
|
+
...resolveListEmptyState({
|
|
456
|
+
subject: "changes",
|
|
457
|
+
search: searchText,
|
|
458
|
+
// Scoped to one atom, so the count is this atom's, not the log's.
|
|
459
|
+
total: searchText.trim() ? scopedTotalForAtom : 0,
|
|
460
|
+
capturing: isEnabled,
|
|
461
|
+
waiting: {
|
|
462
|
+
otto: "waiting",
|
|
463
|
+
title: "No history yet",
|
|
464
|
+
message: `Changes for ${selectedHistoryAtom} will appear here.`
|
|
465
|
+
},
|
|
466
|
+
paused: {
|
|
467
|
+
title: "No history yet",
|
|
468
|
+
message: `Capture is off, so nothing is being recorded for ${selectedHistoryAtom}.`
|
|
469
|
+
}
|
|
470
|
+
})
|
|
485
471
|
});
|
|
486
472
|
}
|
|
487
473
|
|
|
@@ -497,19 +483,14 @@ export function JotaiModal({
|
|
|
497
483
|
/*#__PURE__*/
|
|
498
484
|
// The atom fell out of the registry mid-look (a remote mirror
|
|
499
485
|
// rebuilding, a dev reload). Say so rather than rendering nothing.
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
children: "Atom no longer registered"
|
|
509
|
-
}), /*#__PURE__*/_jsxs(Text, {
|
|
510
|
-
style: styles.emptyText,
|
|
511
|
-
children: [selectedAtomLabel, " is not in the registry any more."]
|
|
512
|
-
})]
|
|
486
|
+
_jsx(ToolEmptyState, {
|
|
487
|
+
testID: `${JOTAI_TEST_ID}-atom-detail-gone`
|
|
488
|
+
// ghost = "stale or detached: an unmounted component, an old
|
|
489
|
+
// snapshot" — exactly what the atom you are looking at has become.
|
|
490
|
+
,
|
|
491
|
+
otto: "ghost",
|
|
492
|
+
title: "Atom no longer registered",
|
|
493
|
+
message: `${selectedAtomLabel} is not in the registry any more.`
|
|
513
494
|
});
|
|
514
495
|
}
|
|
515
496
|
if (activeTab === "atoms") {
|
|
@@ -557,13 +538,11 @@ export function JotaiModal({
|
|
|
557
538
|
keyExtractor: keyExtractor,
|
|
558
539
|
contentContainerStyle: styles.listContent,
|
|
559
540
|
showsVerticalScrollIndicator: true,
|
|
560
|
-
|
|
561
|
-
initialNumToRender: 12,
|
|
562
|
-
maxToRenderPerBatch: 8,
|
|
563
|
-
windowSize: 5,
|
|
564
|
-
updateCellsBatchingPeriod: 50
|
|
541
|
+
...EVENT_LIST_PRESET
|
|
565
542
|
}) : /*#__PURE__*/_jsx(EventsEmptyState, {
|
|
566
|
-
isEnabled: isEnabled
|
|
543
|
+
isEnabled: isEnabled,
|
|
544
|
+
searchText: searchText,
|
|
545
|
+
totalChanges: stats.totalChanges
|
|
567
546
|
})]
|
|
568
547
|
});
|
|
569
548
|
};
|
|
@@ -673,27 +652,6 @@ const styles = StyleSheet.create({
|
|
|
673
652
|
// Transparent so JsModal's ToolBackground shows through (the modal paints opaque base beneath it).
|
|
674
653
|
backgroundColor: "transparent"
|
|
675
654
|
},
|
|
676
|
-
headerSearchContainer: {
|
|
677
|
-
flexDirection: "row",
|
|
678
|
-
alignItems: "center",
|
|
679
|
-
backgroundColor: night.color.surfaceElevated,
|
|
680
|
-
borderRadius: night.radius.row,
|
|
681
|
-
borderWidth: 1,
|
|
682
|
-
borderColor: night.color.border,
|
|
683
|
-
paddingHorizontal: 12,
|
|
684
|
-
paddingVertical: 5
|
|
685
|
-
},
|
|
686
|
-
headerSearchInput: {
|
|
687
|
-
flex: 1,
|
|
688
|
-
color: night.color.text,
|
|
689
|
-
fontSize: 13,
|
|
690
|
-
marginLeft: 6,
|
|
691
|
-
paddingVertical: 2
|
|
692
|
-
},
|
|
693
|
-
headerSearchClear: {
|
|
694
|
-
marginLeft: 6,
|
|
695
|
-
padding: 4
|
|
696
|
-
},
|
|
697
655
|
headerActionButton: {
|
|
698
656
|
width: 32,
|
|
699
657
|
height: 32,
|
|
@@ -3,76 +3,60 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Hook for consuming Jotai atom changes from the store
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* The subscribe / filter / pause / clear plumbing is shared `useChangeLog`;
|
|
7
|
+
* this file owns the jotai-specific filter, stats and atom 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 { jotaiStateStore } from "../utils/jotaiStateStore";
|
|
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.atomLabel.toLowerCase().includes(search) || c.valuePreview.toLowerCase().includes(search) || c.changedKeys.some(k => k.toLowerCase().includes(search)));
|
|
18
|
+
}
|
|
19
|
+
if (filter.atomLabels && filter.atomLabels.length > 0) {
|
|
20
|
+
filtered = filtered.filter(c => filter.atomLabels.includes(c.atomLabel));
|
|
21
|
+
}
|
|
22
|
+
if (filter.onlyWithChanges) {
|
|
23
|
+
filtered = filtered.filter(c => c.hasValueChange);
|
|
24
|
+
}
|
|
25
|
+
return filtered;
|
|
26
|
+
}
|
|
27
|
+
const readAtoms = () => jotaiStateStore.getAtoms();
|
|
28
|
+
const subscribeToAtoms = listener => jotaiStateStore.subscribeToAtoms(listener);
|
|
11
29
|
export function useJotaiAtomChanges() {
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
useEffect(() => {
|
|
17
|
-
const unsubChanges = jotaiStateStore.subscribe(newChanges => {
|
|
18
|
-
setAtomChanges(newChanges);
|
|
19
|
-
});
|
|
20
|
-
const unsubAtoms = jotaiStateStore.subscribeToAtoms(newAtoms => {
|
|
21
|
-
setAtoms(newAtoms);
|
|
22
|
-
});
|
|
23
|
-
return () => {
|
|
24
|
-
unsubChanges();
|
|
25
|
-
unsubAtoms();
|
|
26
|
-
};
|
|
27
|
-
}, []);
|
|
28
|
-
const filteredChanges = useMemo(() => {
|
|
29
|
-
let filtered = atomChanges;
|
|
30
|
-
if (filter.searchText) {
|
|
31
|
-
const search = filter.searchText.toLowerCase();
|
|
32
|
-
filtered = filtered.filter(c => c.atomLabel.toLowerCase().includes(search) || c.valuePreview.toLowerCase().includes(search) || c.changedKeys.some(k => k.toLowerCase().includes(search)));
|
|
33
|
-
}
|
|
34
|
-
if (filter.atomLabels && filter.atomLabels.length > 0) {
|
|
35
|
-
filtered = filtered.filter(c => filter.atomLabels.includes(c.atomLabel));
|
|
36
|
-
}
|
|
37
|
-
if (filter.onlyWithChanges) {
|
|
38
|
-
filtered = filtered.filter(c => c.hasValueChange);
|
|
39
|
-
}
|
|
40
|
-
return filtered;
|
|
41
|
-
}, [atomChanges, filter]);
|
|
30
|
+
const log = useChangeLog(jotaiStateStore, {
|
|
31
|
+
filterItems: filterChanges
|
|
32
|
+
});
|
|
33
|
+
const atoms = useSubscribedValue(readAtoms, subscribeToAtoms);
|
|
42
34
|
const stats = useMemo(() => {
|
|
43
|
-
const total =
|
|
44
|
-
const withChanges =
|
|
35
|
+
const total = log.items.length;
|
|
36
|
+
const withChanges = log.items.filter(c => c.hasValueChange).length;
|
|
45
37
|
return {
|
|
46
38
|
totalChanges: total,
|
|
47
39
|
changesWithValueChange: withChanges,
|
|
48
40
|
changesWithoutValueChange: total - withChanges,
|
|
49
41
|
atomCount: atoms.length
|
|
50
42
|
};
|
|
51
|
-
}, [
|
|
43
|
+
}, [log.items, atoms]);
|
|
52
44
|
const atomLabels = useMemo(() => {
|
|
53
45
|
return jotaiStateStore.getUniqueAtomLabels();
|
|
54
46
|
}, [atoms]);
|
|
55
|
-
const clearChanges = useCallback(() => {
|
|
56
|
-
jotaiStateStore.clearAtomChanges();
|
|
57
|
-
}, []);
|
|
58
|
-
const toggleCapture = useCallback(() => {
|
|
59
|
-
const newEnabled = !isEnabled;
|
|
60
|
-
jotaiStateStore.setEnabled(newEnabled);
|
|
61
|
-
setIsEnabled(newEnabled);
|
|
62
|
-
}, [isEnabled]);
|
|
63
47
|
const getChangeById = useCallback(id => {
|
|
64
48
|
return jotaiStateStore.getAtomChangeById(id);
|
|
65
49
|
}, []);
|
|
66
50
|
return {
|
|
67
|
-
atomChanges,
|
|
68
|
-
filteredChanges,
|
|
69
|
-
filter,
|
|
70
|
-
setFilter,
|
|
51
|
+
atomChanges: log.items,
|
|
52
|
+
filteredChanges: log.filteredItems,
|
|
53
|
+
filter: log.filter,
|
|
54
|
+
setFilter: log.setFilter,
|
|
71
55
|
stats,
|
|
72
56
|
atoms,
|
|
73
|
-
clearChanges,
|
|
74
|
-
isEnabled,
|
|
75
|
-
toggleCapture,
|
|
57
|
+
clearChanges: log.clear,
|
|
58
|
+
isEnabled: log.isEnabled,
|
|
59
|
+
toggleCapture: log.toggleCapture,
|
|
76
60
|
atomLabels,
|
|
77
61
|
getChangeById
|
|
78
62
|
};
|
|
@@ -1,43 +1,28 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { jotaiStateStore } from "../utils/jotaiStateStore";
|
|
4
|
-
import {
|
|
4
|
+
import { WIRE_DETAIL_LIMIT_BYTES, createSyncAdapter, capDetail, detailByIdAction, memoByIdentity, onDeviceMarker, readParams, toWire } from "@buoy-gg/shared-ui";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Sentinel used in WIRE-FORM snapshots in place of raw prevValue/nextValue.
|
|
8
8
|
* Same hazard as redux/zustand: up to 200 changes × 2 values on every
|
|
9
9
|
* snapshot. Dashboards fetch values via `getChangeDetail`.
|
|
10
10
|
*/
|
|
11
|
-
export const VALUE_ON_DEVICE =
|
|
12
|
-
__buoyValueOnDevice: true,
|
|
13
|
-
note: "Atom values stay on the device — fetched on demand via getChangeDetail / getAtomValue."
|
|
14
|
-
});
|
|
15
|
-
const MAX_CHANGE_DETAIL_BYTES = 8 * 1024 * 1024;
|
|
16
|
-
const MAX_WIRE_PAYLOAD_BYTES = 16 * 1024;
|
|
11
|
+
export const VALUE_ON_DEVICE = onDeviceMarker("Value", "Atom values stay on the device — fetched on demand via getChangeDetail / getAtomValue.");
|
|
17
12
|
/**
|
|
18
13
|
* Per-change wire cache — the store prepends and never mutates a recorded
|
|
19
14
|
* change, so identity is a correct, self-invalidating key.
|
|
20
15
|
*/
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
prevValue: VALUE_ON_DEVICE,
|
|
28
|
-
nextValue: VALUE_ON_DEVICE
|
|
29
|
-
};
|
|
30
|
-
changeCache.set(change, wire);
|
|
31
|
-
return wire;
|
|
32
|
-
}
|
|
33
|
-
function toWireValue(value) {
|
|
34
|
-
return isOverWireBudget(value, MAX_WIRE_PAYLOAD_BYTES) ? VALUE_ON_DEVICE : value;
|
|
35
|
-
}
|
|
16
|
+
const toWireChange = memoByIdentity(change => ({
|
|
17
|
+
...change,
|
|
18
|
+
prevValue: VALUE_ON_DEVICE,
|
|
19
|
+
nextValue: VALUE_ON_DEVICE
|
|
20
|
+
}));
|
|
21
|
+
const toWireValue = value => toWire(value, VALUE_ON_DEVICE);
|
|
36
22
|
|
|
37
23
|
/**
|
|
38
24
|
* Sync adapter for the jotai tool, consumed by @buoy-gg/external-sync's
|
|
39
|
-
* `useExternalSync
|
|
40
|
-
* this package doesn't need a dependency on it).
|
|
25
|
+
* `useExternalSync`.
|
|
41
26
|
*
|
|
42
27
|
* Atom changes are captured by watchAtoms(), independent of whether a
|
|
43
28
|
* dashboard is watching — subscribing here only streams what the store
|
|
@@ -49,7 +34,7 @@ function toWireValue(value) {
|
|
|
49
34
|
* Dashboards fetch values via `getChangeDetail`. Oversized / unserializable
|
|
50
35
|
* `atoms[].currentValue` uses the same marker and is fetched via `getAtomValue`.
|
|
51
36
|
*/
|
|
52
|
-
export const jotaiSyncAdapter = {
|
|
37
|
+
export const jotaiSyncAdapter = createSyncAdapter({
|
|
53
38
|
version: 2,
|
|
54
39
|
getSnapshot: () => ({
|
|
55
40
|
changes: jotaiStateStore.getAtomChanges().map(toWireChange),
|
|
@@ -58,14 +43,7 @@ export const jotaiSyncAdapter = {
|
|
|
58
43
|
currentValue: toWireValue(atom.currentValue)
|
|
59
44
|
}))
|
|
60
45
|
}),
|
|
61
|
-
subscribe: onChange =>
|
|
62
|
-
const unsubscribeChanges = jotaiStateStore.subscribe(onChange);
|
|
63
|
-
const unsubscribeAtoms = jotaiStateStore.subscribeToAtoms(onChange);
|
|
64
|
-
return () => {
|
|
65
|
-
unsubscribeChanges();
|
|
66
|
-
unsubscribeAtoms();
|
|
67
|
-
};
|
|
68
|
-
},
|
|
46
|
+
subscribe: [onChange => jotaiStateStore.subscribe(onChange), onChange => jotaiStateStore.subscribeToAtoms(onChange)],
|
|
69
47
|
actions: {
|
|
70
48
|
clearEvents: () => {
|
|
71
49
|
jotaiStateStore.clearAtomChanges();
|
|
@@ -75,31 +53,10 @@ export const jotaiSyncAdapter = {
|
|
|
75
53
|
* The per-snapshot stream stays value-free; this is the explicit,
|
|
76
54
|
* size-guarded channel for the detail pane.
|
|
77
55
|
*/
|
|
78
|
-
getChangeDetail:
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (!id) return {
|
|
83
|
-
found: false,
|
|
84
|
-
reason: "missing id"
|
|
85
|
-
};
|
|
86
|
-
const change = jotaiStateStore.getAtomChangeById(id);
|
|
87
|
-
if (!change) return {
|
|
88
|
-
found: false,
|
|
89
|
-
reason: "unknown id"
|
|
90
|
-
};
|
|
91
|
-
const cappedNote = {
|
|
92
|
-
__buoyTruncated: true,
|
|
93
|
-
note: `Exceeds the ${MAX_CHANGE_DETAIL_BYTES / (1024 * 1024)}MB detail limit — inspect it on-device.`
|
|
94
|
-
};
|
|
95
|
-
const cap = value => isOverWireBudget(value, MAX_CHANGE_DETAIL_BYTES) ? cappedNote : value;
|
|
96
|
-
return {
|
|
97
|
-
found: true,
|
|
98
|
-
id,
|
|
99
|
-
prevValue: cap(change.prevValue),
|
|
100
|
-
nextValue: cap(change.nextValue)
|
|
101
|
-
};
|
|
102
|
-
},
|
|
56
|
+
getChangeDetail: detailByIdAction(id => jotaiStateStore.getAtomChangeById(id), (change, cap) => ({
|
|
57
|
+
prevValue: cap(change.prevValue),
|
|
58
|
+
nextValue: cap(change.nextValue)
|
|
59
|
+
})),
|
|
103
60
|
/**
|
|
104
61
|
* On-demand current value for one atom. Wire-form snapshots replace
|
|
105
62
|
* oversized / unserializable `currentValue` with VALUE_ON_DEVICE.
|
|
@@ -107,7 +64,7 @@ export const jotaiSyncAdapter = {
|
|
|
107
64
|
getAtomValue: params => {
|
|
108
65
|
const {
|
|
109
66
|
label
|
|
110
|
-
} = params
|
|
67
|
+
} = readParams(params);
|
|
111
68
|
if (!label) return {
|
|
112
69
|
found: false,
|
|
113
70
|
reason: "missing label"
|
|
@@ -123,10 +80,6 @@ export const jotaiSyncAdapter = {
|
|
|
123
80
|
} catch {
|
|
124
81
|
currentValue = undefined;
|
|
125
82
|
}
|
|
126
|
-
const cappedNote = {
|
|
127
|
-
__buoyTruncated: true,
|
|
128
|
-
note: `Exceeds the ${MAX_CHANGE_DETAIL_BYTES / (1024 * 1024)}MB detail limit — inspect it on-device.`
|
|
129
|
-
};
|
|
130
83
|
try {
|
|
131
84
|
JSON.stringify(currentValue);
|
|
132
85
|
} catch {
|
|
@@ -139,7 +92,7 @@ export const jotaiSyncAdapter = {
|
|
|
139
92
|
return {
|
|
140
93
|
found: true,
|
|
141
94
|
label,
|
|
142
|
-
currentValue:
|
|
95
|
+
currentValue: capDetail(currentValue, WIRE_DETAIL_LIMIT_BYTES)
|
|
143
96
|
};
|
|
144
97
|
},
|
|
145
98
|
/**
|
|
@@ -152,7 +105,7 @@ export const jotaiSyncAdapter = {
|
|
|
152
105
|
const {
|
|
153
106
|
includeValues = false,
|
|
154
107
|
limit
|
|
155
|
-
} = params
|
|
108
|
+
} = readParams(params);
|
|
156
109
|
const all = jotaiStateStore.getAtoms();
|
|
157
110
|
const capped = typeof limit === "number" && limit > 0 ? all.slice(0, limit) : all;
|
|
158
111
|
const atoms = capped.map(a => {
|
|
@@ -185,9 +138,9 @@ export const jotaiSyncAdapter = {
|
|
|
185
138
|
const {
|
|
186
139
|
label,
|
|
187
140
|
value
|
|
188
|
-
} = params
|
|
141
|
+
} = readParams(params);
|
|
189
142
|
if (!label) throw new Error("setAtom requires a `label`");
|
|
190
143
|
jotaiStateStore.setAtomValue(label, value);
|
|
191
144
|
}
|
|
192
145
|
}
|
|
193
|
-
};
|
|
146
|
+
});
|