@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.
Files changed (35) hide show
  1. package/lib/commonjs/zustand/components/ZustandDetailViewToggle.js +13 -107
  2. package/lib/commonjs/zustand/components/ZustandEventFilterView.js +24 -288
  3. package/lib/commonjs/zustand/components/ZustandModal.js +83 -141
  4. package/lib/commonjs/zustand/components/ZustandStateChangeItem.js +8 -61
  5. package/lib/commonjs/zustand/components/ZustandStateInfoView.js +2 -46
  6. package/lib/commonjs/zustand/components/ZustandStoreBrowser.js +29 -54
  7. package/lib/commonjs/zustand/hooks/useZustandStateChanges.js +33 -53
  8. package/lib/commonjs/zustand/sync/zustandSyncAdapter.js +24 -78
  9. package/lib/commonjs/zustand/utils/snapshotProvider.js +17 -60
  10. package/lib/commonjs/zustand/utils/zustandStateStore.js +37 -223
  11. package/lib/module/zustand/components/ZustandDetailViewToggle.js +15 -109
  12. package/lib/module/zustand/components/ZustandEventFilterView.js +28 -292
  13. package/lib/module/zustand/components/ZustandModal.js +85 -143
  14. package/lib/module/zustand/components/ZustandStateChangeItem.js +10 -63
  15. package/lib/module/zustand/components/ZustandStateInfoView.js +1 -45
  16. package/lib/module/zustand/components/ZustandStoreBrowser.js +31 -56
  17. package/lib/module/zustand/hooks/useZustandStateChanges.js +34 -54
  18. package/lib/module/zustand/sync/zustandSyncAdapter.js +25 -79
  19. package/lib/module/zustand/utils/snapshotProvider.js +18 -61
  20. package/lib/module/zustand/utils/zustandStateStore.js +37 -223
  21. package/lib/typescript/zustand/components/ZustandDetailViewToggle.d.ts +2 -1
  22. package/lib/typescript/zustand/components/ZustandDetailViewToggle.d.ts.map +1 -1
  23. package/lib/typescript/zustand/components/ZustandEventFilterView.d.ts +6 -0
  24. package/lib/typescript/zustand/components/ZustandEventFilterView.d.ts.map +1 -1
  25. package/lib/typescript/zustand/components/ZustandModal.d.ts.map +1 -1
  26. package/lib/typescript/zustand/components/ZustandStateChangeItem.d.ts.map +1 -1
  27. package/lib/typescript/zustand/components/ZustandStateInfoView.d.ts.map +1 -1
  28. package/lib/typescript/zustand/components/ZustandStoreBrowser.d.ts.map +1 -1
  29. package/lib/typescript/zustand/hooks/useZustandStateChanges.d.ts +2 -1
  30. package/lib/typescript/zustand/hooks/useZustandStateChanges.d.ts.map +1 -1
  31. package/lib/typescript/zustand/sync/zustandSyncAdapter.d.ts +103 -117
  32. package/lib/typescript/zustand/sync/zustandSyncAdapter.d.ts.map +1 -1
  33. package/lib/typescript/zustand/utils/zustandStateStore.d.ts +6 -26
  34. package/lib/typescript/zustand/utils/zustandStateStore.d.ts.map +1 -1
  35. package/package.json +3 -3
@@ -5,53 +5,41 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.useZustandStateChanges = useZustandStateChanges;
7
7
  var _react = require("react");
8
+ var _sharedUi = require("@buoy-gg/shared-ui");
8
9
  var _zustandStateStore = require("../utils/zustandStateStore");
9
10
  /**
10
11
  * Hook for consuming Zustand state changes from the store
11
12
  *
12
- * Mirrors useReduxActions.ts from @buoy-gg/redux
13
+ * The subscribe / filter / pause / clear plumbing is shared `useChangeLog`;
14
+ * this file owns the zustand-specific filter, stats and store registry.
13
15
  */
14
16
 
17
+ function filterChanges(changes, filter) {
18
+ let filtered = changes;
19
+ if (filter.searchText) {
20
+ const search = filter.searchText.toLowerCase();
21
+ filtered = filtered.filter(c => c.storeName.toLowerCase().includes(search) || c.partialPreview.toLowerCase().includes(search) || c.changedKeys.some(k => k.toLowerCase().includes(search)));
22
+ }
23
+ if (filter.storeNames && filter.storeNames.length > 0) {
24
+ filtered = filtered.filter(c => filter.storeNames.includes(c.storeName));
25
+ }
26
+ if (filter.onlyWithChanges) {
27
+ filtered = filtered.filter(c => c.hasStateChange);
28
+ }
29
+ return filtered;
30
+ }
31
+ const readStores = () => _zustandStateStore.zustandStateStore.getStores();
32
+ const subscribeToStores = listener => _zustandStateStore.zustandStateStore.subscribeToStores(listener);
15
33
  function useZustandStateChanges() {
16
- const [stateChanges, setStateChanges] = (0, _react.useState)(() => _zustandStateStore.zustandStateStore.getStateChanges());
17
- const [stores, setStores] = (0, _react.useState)(() => _zustandStateStore.zustandStateStore.getStores());
18
- const [filter, setFilter] = (0, _react.useState)({});
19
- const [isEnabled, setIsEnabled] = (0, _react.useState)(() => _zustandStateStore.zustandStateStore.getEnabled());
20
-
21
- // Subscribe to state changes
22
- (0, _react.useEffect)(() => {
23
- const unsubChanges = _zustandStateStore.zustandStateStore.subscribe(newChanges => {
24
- setStateChanges(newChanges);
25
- });
26
- const unsubStores = _zustandStateStore.zustandStateStore.subscribeToStores(newStores => {
27
- setStores(newStores);
28
- });
29
- return () => {
30
- unsubChanges();
31
- unsubStores();
32
- };
33
- }, []);
34
-
35
- // Filter state changes — derive directly from state to avoid stale reads
36
- const filteredChanges = (0, _react.useMemo)(() => {
37
- let filtered = stateChanges;
38
- if (filter.searchText) {
39
- const search = filter.searchText.toLowerCase();
40
- filtered = filtered.filter(c => c.storeName.toLowerCase().includes(search) || c.partialPreview.toLowerCase().includes(search) || c.changedKeys.some(k => k.toLowerCase().includes(search)));
41
- }
42
- if (filter.storeNames && filter.storeNames.length > 0) {
43
- filtered = filtered.filter(c => filter.storeNames.includes(c.storeName));
44
- }
45
- if (filter.onlyWithChanges) {
46
- filtered = filtered.filter(c => c.hasStateChange);
47
- }
48
- return filtered;
49
- }, [stateChanges, filter]);
34
+ const log = (0, _sharedUi.useChangeLog)(_zustandStateStore.zustandStateStore, {
35
+ filterItems: filterChanges
36
+ });
37
+ const stores = (0, _sharedUi.useSubscribedValue)(readStores, subscribeToStores);
50
38
 
51
39
  // Get stats — derive directly from state
52
40
  const stats = (0, _react.useMemo)(() => {
53
- const total = stateChanges.length;
54
- const withChanges = stateChanges.filter(c => c.hasStateChange).length;
41
+ const total = log.items.length;
42
+ const withChanges = log.items.filter(c => c.hasStateChange).length;
55
43
  return {
56
44
  totalChanges: total,
57
45
  changesWithStateChange: withChanges,
@@ -59,33 +47,25 @@ function useZustandStateChanges() {
59
47
  storeCount: stores.length,
60
48
  averageDuration: 0
61
49
  };
62
- }, [stateChanges, stores]);
50
+ }, [log.items, stores]);
63
51
 
64
52
  // Get unique store names
65
53
  const storeNames = (0, _react.useMemo)(() => {
66
54
  return _zustandStateStore.zustandStateStore.getUniqueStoreNames();
67
55
  }, [stores]);
68
- const clearChanges = (0, _react.useCallback)(() => {
69
- _zustandStateStore.zustandStateStore.clearStateChanges();
70
- }, []);
71
- const toggleCapture = (0, _react.useCallback)(() => {
72
- const newEnabled = !isEnabled;
73
- _zustandStateStore.zustandStateStore.setEnabled(newEnabled);
74
- setIsEnabled(newEnabled);
75
- }, [isEnabled]);
76
56
  const getChangeById = (0, _react.useCallback)(id => {
77
57
  return _zustandStateStore.zustandStateStore.getStateChangeById(id);
78
58
  }, []);
79
59
  return {
80
- stateChanges,
81
- filteredChanges,
82
- filter,
83
- setFilter,
60
+ stateChanges: log.items,
61
+ filteredChanges: log.filteredItems,
62
+ filter: log.filter,
63
+ setFilter: log.setFilter,
84
64
  stats,
85
65
  stores,
86
- clearChanges,
87
- isEnabled,
88
- toggleCapture,
66
+ clearChanges: log.clear,
67
+ isEnabled: log.isEnabled,
68
+ toggleCapture: log.toggleCapture,
89
69
  storeNames,
90
70
  getChangeById
91
71
  };
@@ -11,18 +11,10 @@ var _sharedUi = require("@buoy-gg/shared-ui");
11
11
  * Same hazard as redux: up to 200 changes × 2 full trees on every snapshot
12
12
  * freeze/OOM-killed large apps. Dashboards fetch trees via `getChangeDetail`.
13
13
  */
14
- const STATE_ON_DEVICE = exports.STATE_ON_DEVICE = Object.freeze({
15
- __buoyStateOnDevice: true,
16
- note: "State trees stay on the device — fetched on demand via getChangeDetail / getStoreState."
17
- });
14
+ const STATE_ON_DEVICE = exports.STATE_ON_DEVICE = (0, _sharedUi.onDeviceMarker)("State", "State trees stay on the device — fetched on demand via getChangeDetail / getStoreState.");
18
15
 
19
16
  /** Wire-form sentinel for an oversized `partial` (replace can carry a whole tree). */
20
- const PAYLOAD_ON_DEVICE = exports.PAYLOAD_ON_DEVICE = Object.freeze({
21
- __buoyPayloadOnDevice: true,
22
- note: "Partial stays on the device — fetched on demand via getChangeDetail."
23
- });
24
- const MAX_CHANGE_DETAIL_BYTES = 8 * 1024 * 1024;
25
- const MAX_WIRE_PAYLOAD_BYTES = 16 * 1024;
17
+ const PAYLOAD_ON_DEVICE = exports.PAYLOAD_ON_DEVICE = (0, _sharedUi.onDeviceMarker)("Payload", "Partial stays on the device — fetched on demand via getChangeDetail.");
26
18
  /** Top-level keys of an object state, for the compact (no-values) view. */
27
19
  function topLevelKeys(state) {
28
20
  return state && typeof state === "object" && !Array.isArray(state) ? Object.keys(state) : undefined;
@@ -33,31 +25,17 @@ function topLevelKeys(state) {
33
25
  * change, so keying on identity is correct and self-invalidating — without it
34
26
  * every `partial` in the list is re-measured on all ~5 snapshots per second.
35
27
  */
36
- const changeCache = new WeakMap();
37
- function toWireChange(change) {
38
- const cached = changeCache.get(change);
39
- if (cached) return cached;
40
- const wire = {
41
- ...change,
42
- prevState: STATE_ON_DEVICE,
43
- nextState: STATE_ON_DEVICE,
44
- partial: toWirePartial(change.partial)
45
- };
46
- changeCache.set(change, wire);
47
- return wire;
48
- }
49
- function toWirePartial(value) {
50
- if (value === undefined) return value;
51
- return (0, _sharedUi.isOverWireBudget)(value, MAX_WIRE_PAYLOAD_BYTES) ? PAYLOAD_ON_DEVICE : value;
52
- }
53
- function toWireState(value) {
54
- return (0, _sharedUi.isOverWireBudget)(value, MAX_WIRE_PAYLOAD_BYTES) ? STATE_ON_DEVICE : value;
55
- }
28
+ const toWireChange = (0, _sharedUi.memoByIdentity)(change => ({
29
+ ...change,
30
+ prevState: STATE_ON_DEVICE,
31
+ nextState: STATE_ON_DEVICE,
32
+ partial: (0, _sharedUi.toWire)(change.partial, PAYLOAD_ON_DEVICE)
33
+ }));
34
+ const toWireState = value => (0, _sharedUi.toWire)(value, STATE_ON_DEVICE);
56
35
 
57
36
  /**
58
37
  * Sync adapter for the zustand tool, consumed by @buoy-gg/external-sync's
59
- * `useExternalSync` (structurally matches its ToolSyncAdapter interface so
60
- * this package doesn't need a dependency on it).
38
+ * `useExternalSync`.
61
39
  *
62
40
  * State changes are captured by the middleware (watchStores/buoyDevTools),
63
41
  * independent of whether a dashboard is watching — subscribing here only
@@ -72,7 +50,7 @@ function toWireState(value) {
72
50
  *
73
51
  * The `setState` action powers remote time-travel/reset from the dashboard.
74
52
  */
75
- const zustandSyncAdapter = exports.zustandSyncAdapter = {
53
+ const zustandSyncAdapter = exports.zustandSyncAdapter = (0, _sharedUi.createSyncAdapter)({
76
54
  version: 2,
77
55
  getSnapshot: () => ({
78
56
  changes: _zustandStateStore.zustandStateStore.getStateChanges().map(toWireChange),
@@ -81,14 +59,7 @@ const zustandSyncAdapter = exports.zustandSyncAdapter = {
81
59
  currentState: toWireState(store.currentState)
82
60
  }))
83
61
  }),
84
- subscribe: onChange => {
85
- const unsubscribeChanges = _zustandStateStore.zustandStateStore.subscribe(onChange);
86
- const unsubscribeStores = _zustandStateStore.zustandStateStore.subscribeToStores(onChange);
87
- return () => {
88
- unsubscribeChanges();
89
- unsubscribeStores();
90
- };
91
- },
62
+ subscribe: [onChange => _zustandStateStore.zustandStateStore.subscribe(onChange), onChange => _zustandStateStore.zustandStateStore.subscribeToStores(onChange)],
92
63
  actions: {
93
64
  clearEvents: () => {
94
65
  _zustandStateStore.zustandStateStore.clearStateChanges();
@@ -98,32 +69,11 @@ const zustandSyncAdapter = exports.zustandSyncAdapter = {
98
69
  * The per-snapshot stream stays tree-free; this is the explicit,
99
70
  * size-guarded channel for the detail pane.
100
71
  */
101
- getChangeDetail: params => {
102
- const {
103
- id
104
- } = params ?? {};
105
- if (!id) return {
106
- found: false,
107
- reason: "missing id"
108
- };
109
- const change = _zustandStateStore.zustandStateStore.getStateChangeById(id);
110
- if (!change) return {
111
- found: false,
112
- reason: "unknown id"
113
- };
114
- const cappedNote = {
115
- __buoyTruncated: true,
116
- note: `Exceeds the ${MAX_CHANGE_DETAIL_BYTES / (1024 * 1024)}MB detail limit — inspect it on-device.`
117
- };
118
- const cap = value => (0, _sharedUi.isOverWireBudget)(value, MAX_CHANGE_DETAIL_BYTES) ? cappedNote : value;
119
- return {
120
- found: true,
121
- id,
122
- prevState: cap(change.prevState),
123
- nextState: cap(change.nextState),
124
- partial: cap(change.partial)
125
- };
126
- },
72
+ getChangeDetail: (0, _sharedUi.detailByIdAction)(id => _zustandStateStore.zustandStateStore.getStateChangeById(id), (change, cap) => ({
73
+ prevState: cap(change.prevState),
74
+ nextState: cap(change.nextState),
75
+ partial: cap(change.partial)
76
+ })),
127
77
  /**
128
78
  * On-demand current state for one store. Wire-form snapshots replace
129
79
  * oversized / unserializable `currentState` with STATE_ON_DEVICE.
@@ -131,7 +81,7 @@ const zustandSyncAdapter = exports.zustandSyncAdapter = {
131
81
  getStoreState: params => {
132
82
  const {
133
83
  storeName
134
- } = params ?? {};
84
+ } = (0, _sharedUi.readParams)(params);
135
85
  if (!storeName) return {
136
86
  found: false,
137
87
  reason: "missing storeName"
@@ -147,11 +97,7 @@ const zustandSyncAdapter = exports.zustandSyncAdapter = {
147
97
  } catch {
148
98
  currentState = undefined;
149
99
  }
150
- const cappedNote = {
151
- __buoyTruncated: true,
152
- note: `Exceeds the ${MAX_CHANGE_DETAIL_BYTES / (1024 * 1024)}MB detail limit — inspect it on-device.`
153
- };
154
- if (!(0, _sharedUi.isOverWireBudget)(currentState, MAX_CHANGE_DETAIL_BYTES)) {
100
+ if (!(0, _sharedUi.isOverWireBudget)(currentState, _sharedUi.WIRE_DETAIL_LIMIT_BYTES)) {
155
101
  return {
156
102
  found: true,
157
103
  storeName,
@@ -161,7 +107,7 @@ const zustandSyncAdapter = exports.zustandSyncAdapter = {
161
107
  return {
162
108
  found: true,
163
109
  storeName,
164
- currentState: (0, _sharedUi.isJsonSerializable)(currentState) ? cappedNote : STATE_ON_DEVICE
110
+ currentState: (0, _sharedUi.isJsonSerializable)(currentState) ? (0, _sharedUi.truncatedNote)(_sharedUi.WIRE_DETAIL_LIMIT_BYTES) : STATE_ON_DEVICE
165
111
  };
166
112
  },
167
113
  /**
@@ -174,7 +120,7 @@ const zustandSyncAdapter = exports.zustandSyncAdapter = {
174
120
  const {
175
121
  includeValues = false,
176
122
  limit
177
- } = params ?? {};
123
+ } = (0, _sharedUi.readParams)(params);
178
124
  const all = _zustandStateStore.zustandStateStore.getStoreSnapshots();
179
125
  const capped = typeof limit === "number" && limit > 0 ? all.slice(0, limit) : all;
180
126
  const stores = capped.map(s => ({
@@ -201,7 +147,7 @@ const zustandSyncAdapter = exports.zustandSyncAdapter = {
201
147
  storeName,
202
148
  state,
203
149
  replace
204
- } = params ?? {};
150
+ } = (0, _sharedUi.readParams)(params);
205
151
  if (!storeName) return {
206
152
  ok: false,
207
153
  error: "Missing storeName."
@@ -240,7 +186,7 @@ const zustandSyncAdapter = exports.zustandSyncAdapter = {
240
186
  rehydrate: async params => {
241
187
  const {
242
188
  storeName
243
- } = params ?? {};
189
+ } = (0, _sharedUi.readParams)(params);
244
190
  if (!storeName) return {
245
191
  ok: false,
246
192
  error: "Missing storeName."
@@ -270,4 +216,4 @@ const zustandSyncAdapter = exports.zustandSyncAdapter = {
270
216
  };
271
217
  }
272
218
  }
273
- };
219
+ });
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
 
3
3
  var _sharedUi = require("@buoy-gg/shared-ui");
4
- var _dataViewer = require("@buoy-gg/shared-ui/dataViewer");
5
4
  var _zustandStateStore = require("./zustandStateStore");
6
5
  var _editableState = require("./editableState");
7
6
  /**
@@ -93,29 +92,13 @@ async function capture() {
93
92
  }
94
93
  async function restore(data, options) {
95
94
  const snapshot = data;
96
- const excludedSet = new Set(options?.excludeKeys ?? []);
97
- const includedSet = options?.includeKeys ? new Set(options.includeKeys) : null;
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 = (0, _sharedUi.createRestoreScope)(options);
96
+ const result = (0, _sharedUi.emptyRestoreResult)();
106
97
  if (!snapshot?.stores) {
107
- return {
108
- ok: false,
109
- applied: 0,
110
- skipped: [],
111
- warnings: ["empty zustand snapshot"]
112
- };
98
+ return (0, _sharedUi.failedRestore)("empty zustand snapshot");
113
99
  }
114
100
  for (const [name, envelope] of Object.entries(snapshot.stores)) {
115
- if (!touch(name)) {
116
- excludedHits += 1;
117
- continue;
118
- }
101
+ if (!scope.allows(name)) continue;
119
102
  const info = _zustandStateStore.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
- if (excludedHits > 0) {
146
- result.warnings.push(`${excludedHits} store(s) excluded by you`);
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 names = new Set([...Object.keys(baseStores), ...Object.keys(targetStores)]);
180
- for (const name of names) {
160
+ for (const name of (0, _sharedUi.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
- if (!inBase) {
204
- items.push({
205
- key: name,
206
- label: name,
207
- verdict: "added",
208
- newValue: targetStores[name].state
209
- });
210
- continue;
211
- }
212
- const oldState = baseStores[name].state;
213
- const newState = targetStores[name].state;
214
- if ((0, _dataViewer.boundedDeepEqual)(oldState, newState)) {
215
- items.push({
216
- key: name,
217
- label: name,
218
- verdict: "unchanged"
219
- });
220
- } else if ((0, _dataViewer.jsonLikelyLargerThan)(oldState, ITEM_RENDER_BYTE_LIMIT) || (0, _dataViewer.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((0, _sharedUi.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)`);