@buoy-gg/zustand 7.0.40 → 7.0.42

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 (67) hide show
  1. package/README.md +11 -72
  2. package/lib/commonjs/index.js +1 -105
  3. package/lib/commonjs/preset.js +1 -102
  4. package/lib/commonjs/zustand/components/ZustandActionButton.js +1 -118
  5. package/lib/commonjs/zustand/components/ZustandDetailViewToggle.js +1 -48
  6. package/lib/commonjs/zustand/components/ZustandEventFilterView.js +1 -43
  7. package/lib/commonjs/zustand/components/ZustandIcon.js +1 -18
  8. package/lib/commonjs/zustand/components/ZustandModal.js +1 -772
  9. package/lib/commonjs/zustand/components/ZustandStateChangeItem.js +1 -117
  10. package/lib/commonjs/zustand/components/ZustandStateDetailContent.js +1 -372
  11. package/lib/commonjs/zustand/components/ZustandStateInfoView.js +1 -488
  12. package/lib/commonjs/zustand/components/ZustandStoreBrowser.js +1 -204
  13. package/lib/commonjs/zustand/components/ZustandStoreDetailContent.js +1 -471
  14. package/lib/commonjs/zustand/hooks/useZustandStateChanges.js +1 -74
  15. package/lib/commonjs/zustand/sync/zustandSyncAdapter.js +1 -382
  16. package/lib/commonjs/zustand/types/index.js +1 -1
  17. package/lib/commonjs/zustand/utils/buoyZustandMiddleware.js +1 -284
  18. package/lib/commonjs/zustand/utils/editableState.js +1 -146
  19. package/lib/commonjs/zustand/utils/snapshotProvider.js +1 -209
  20. package/lib/commonjs/zustand/utils/testIDs.js +1 -82
  21. package/lib/commonjs/zustand/utils/zustandStateStore.js +1 -379
  22. package/lib/module/index.js +1 -90
  23. package/lib/module/preset.js +1 -98
  24. package/lib/module/zustand/components/ZustandActionButton.js +1 -114
  25. package/lib/module/zustand/components/ZustandDetailViewToggle.js +1 -43
  26. package/lib/module/zustand/components/ZustandEventFilterView.js +1 -39
  27. package/lib/module/zustand/components/ZustandIcon.js +1 -11
  28. package/lib/module/zustand/components/ZustandModal.js +1 -767
  29. package/lib/module/zustand/components/ZustandStateChangeItem.js +1 -113
  30. package/lib/module/zustand/components/ZustandStateDetailContent.js +1 -368
  31. package/lib/module/zustand/components/ZustandStateInfoView.js +1 -483
  32. package/lib/module/zustand/components/ZustandStoreBrowser.js +1 -200
  33. package/lib/module/zustand/components/ZustandStoreDetailContent.js +1 -469
  34. package/lib/module/zustand/hooks/useZustandStateChanges.js +1 -70
  35. package/lib/module/zustand/sync/zustandSyncAdapter.js +1 -380
  36. package/lib/module/zustand/types/index.js +1 -1
  37. package/lib/module/zustand/utils/buoyZustandMiddleware.js +1 -278
  38. package/lib/module/zustand/utils/editableState.js +1 -138
  39. package/lib/module/zustand/utils/snapshotProvider.js +1 -209
  40. package/lib/module/zustand/utils/testIDs.js +1 -74
  41. package/lib/module/zustand/utils/zustandStateStore.js +1 -375
  42. package/lib/web/index.mjs +2 -0
  43. package/package.json +35 -12
  44. package/lib/commonjs/zustand/components/ZustandStoreEditorScreen.js +0 -393
  45. package/lib/module/zustand/components/ZustandStoreEditorScreen.js +0 -389
  46. package/lib/typescript/index.d.ts.map +0 -1
  47. package/lib/typescript/preset.d.ts.map +0 -1
  48. package/lib/typescript/zustand/components/ZustandActionButton.d.ts.map +0 -1
  49. package/lib/typescript/zustand/components/ZustandDetailViewToggle.d.ts.map +0 -1
  50. package/lib/typescript/zustand/components/ZustandEventFilterView.d.ts.map +0 -1
  51. package/lib/typescript/zustand/components/ZustandIcon.d.ts.map +0 -1
  52. package/lib/typescript/zustand/components/ZustandModal.d.ts.map +0 -1
  53. package/lib/typescript/zustand/components/ZustandStateChangeItem.d.ts.map +0 -1
  54. package/lib/typescript/zustand/components/ZustandStateDetailContent.d.ts.map +0 -1
  55. package/lib/typescript/zustand/components/ZustandStateInfoView.d.ts.map +0 -1
  56. package/lib/typescript/zustand/components/ZustandStoreBrowser.d.ts.map +0 -1
  57. package/lib/typescript/zustand/components/ZustandStoreDetailContent.d.ts.map +0 -1
  58. package/lib/typescript/zustand/components/ZustandStoreEditorScreen.d.ts +0 -42
  59. package/lib/typescript/zustand/components/ZustandStoreEditorScreen.d.ts.map +0 -1
  60. package/lib/typescript/zustand/hooks/useZustandStateChanges.d.ts.map +0 -1
  61. package/lib/typescript/zustand/sync/zustandSyncAdapter.d.ts.map +0 -1
  62. package/lib/typescript/zustand/types/index.d.ts.map +0 -1
  63. package/lib/typescript/zustand/utils/buoyZustandMiddleware.d.ts.map +0 -1
  64. package/lib/typescript/zustand/utils/editableState.d.ts.map +0 -1
  65. package/lib/typescript/zustand/utils/snapshotProvider.d.ts.map +0 -1
  66. package/lib/typescript/zustand/utils/testIDs.d.ts.map +0 -1
  67. package/lib/typescript/zustand/utils/zustandStateStore.d.ts.map +0 -1
@@ -1,70 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * Hook for consuming Zustand state changes from the store
5
- *
6
- * The subscribe / filter / pause / clear plumbing is shared `useChangeLog`;
7
- * this file owns the zustand-specific filter, stats and store registry.
8
- */
9
-
10
- import { useCallback, useMemo } from "react";
11
- import { useChangeLog, useSubscribedValue } from "@buoy-gg/shared-ui";
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);
29
- export function useZustandStateChanges() {
30
- const log = useChangeLog(zustandStateStore, {
31
- filterItems: filterChanges
32
- });
33
- const stores = useSubscribedValue(readStores, subscribeToStores);
34
-
35
- // Get stats — derive directly from state
36
- const stats = useMemo(() => {
37
- const total = log.items.length;
38
- const withChanges = log.items.filter(c => c.hasStateChange).length;
39
- return {
40
- totalChanges: total,
41
- changesWithStateChange: withChanges,
42
- changesWithoutStateChange: total - withChanges,
43
- storeCount: stores.length,
44
- averageDuration: 0
45
- };
46
- }, [log.items, stores]);
47
-
48
- // Get unique store names
49
- const storeNames = useMemo(() => {
50
- return zustandStateStore.getUniqueStoreNames();
51
- }, [stores]);
52
- const getChangeById = useCallback(id => {
53
- return zustandStateStore.getStateChangeById(id);
54
- }, []);
55
- return {
56
- stateChanges: log.items,
57
- filteredChanges: log.filteredItems,
58
- filter: log.filter,
59
- setFilter: log.setFilter,
60
- stats,
61
- stores,
62
- // Not the shared log.clear: the store also owns each store's change
63
- // count, and only clearStateChanges resets those with the list.
64
- clearChanges: zustandStateStore.clearStateChanges.bind(zustandStateStore),
65
- isEnabled: log.isEnabled,
66
- toggleCapture: log.toggleCapture,
67
- storeNames,
68
- getChangeById
69
- };
70
- }
1
+ "use strict";import{useCallback as l,useMemo as g}from"react";import{useChangeLog as u,useSubscribedValue as c}from"@buoy-gg/shared-ui";import{zustandStateStore as r}from"../utils/zustandStateStore";function C(e,t){let s=e;if(t.searchText){const a=t.searchText.toLowerCase();s=s.filter(o=>o.storeName.toLowerCase().includes(a)||o.partialPreview.toLowerCase().includes(a)||o.changedKeys.some(n=>n.toLowerCase().includes(a)))}if(t.storeNames&&t.storeNames.length>0){s=s.filter(a=>t.storeNames.includes(a.storeName))}if(t.onlyWithChanges){s=s.filter(a=>a.hasStateChange)}return s}const m=()=>r.getStores();const d=e=>r.subscribeToStores(e);export function useZustandStateChanges(){const e=u(r,{filterItems:C});const t=c(m,d);const s=g(()=>{const n=e.items.length;const i=e.items.filter(h=>h.hasStateChange).length;return{totalChanges:n,changesWithStateChange:i,changesWithoutStateChange:n-i,storeCount:t.length,averageDuration:0}},[e.items,t]);const a=g(()=>{return r.getUniqueStoreNames()},[t]);const o=l(n=>{return r.getStateChangeById(n)},[]);return{stateChanges:e.items,filteredChanges:e.filteredItems,filter:e.filter,setFilter:e.setFilter,stats:s,stores:t,clearChanges:r.clearStateChanges.bind(r),isEnabled:e.isEnabled,toggleCapture:e.toggleCapture,storeNames:a,getChangeById:o}}
@@ -1,380 +1 @@
1
- "use strict";
2
-
3
- import { expandKeyedArrays, patchFromPath, readPath, refusalText, shapeSummary, suggestedPatch, typedEditViolations, unshapedLists } from "@buoy-gg/shared-ui";
4
- import { zustandStateStore } from "../utils/zustandStateStore";
5
- import { WIRE_DETAIL_LIMIT_BYTES, createSyncAdapter, detailByIdAction, isJsonSerializable, isOverWireBudget, memoByIdentity, onDeviceMarker, readParams, toWire, truncatedNote } from "@buoy-gg/shared-ui";
6
-
7
- /**
8
- * Sentinel used in WIRE-FORM snapshots in place of raw prevState/nextState.
9
- * Same hazard as redux: up to 200 changes × 2 full trees on every snapshot
10
- * freeze/OOM-killed large apps. Dashboards fetch trees via `getChangeDetail`.
11
- */
12
- export const STATE_ON_DEVICE = onDeviceMarker("State", "State trees stay on the device — fetched on demand via getChangeDetail / getStoreState.");
13
-
14
- /** Wire-form sentinel for an oversized `partial` (replace can carry a whole tree). */
15
- export const PAYLOAD_ON_DEVICE = onDeviceMarker("Payload", "Partial stays on the device — fetched on demand via getChangeDetail.");
16
- const isPlainObject = v => !!v && typeof v === "object" && !Array.isArray(v);
17
- /**
18
- * Does this patch write into the list at `path`? Only then is an unchecked-shape
19
- * warning about that list worth saying — a patch that changes `promoCode` should
20
- * not be told about an empty `lines`.
21
- */
22
- function touchesList(patch, path) {
23
- let cur = patch;
24
- for (const seg of path.split(".")) {
25
- if (!cur || typeof cur !== "object" || Array.isArray(cur)) return false;
26
- if (!(seg in cur)) return false;
27
- cur = cur[seg];
28
- }
29
- // An empty list stays empty; there is no new item to be unsure about.
30
- return Array.isArray(cur) ? cur.length > 0 : !!cur && typeof cur === "object";
31
- }
32
-
33
- /** Top-level keys of an object state, for the compact (no-values) view. */
34
- function topLevelKeys(state) {
35
- return state && typeof state === "object" && !Array.isArray(state) ? Object.keys(state) : undefined;
36
- }
37
-
38
- /**
39
- * Per-change wire cache. The store prepends and never mutates a recorded
40
- * change, so keying on identity is correct and self-invalidating — without it
41
- * every `partial` in the list is re-measured on all ~5 snapshots per second.
42
- */
43
- const toWireChange = memoByIdentity(change => ({
44
- ...change,
45
- prevState: STATE_ON_DEVICE,
46
- nextState: STATE_ON_DEVICE,
47
- partial: toWire(change.partial, PAYLOAD_ON_DEVICE)
48
- }));
49
- const toWireState = value => toWire(value, STATE_ON_DEVICE);
50
-
51
- /**
52
- * Sync adapter for the zustand tool, consumed by @buoy-gg/external-sync's
53
- * `useExternalSync`.
54
- *
55
- * State changes are captured by the middleware (watchStores/buoyDevTools),
56
- * independent of whether a dashboard is watching — subscribing here only
57
- * streams what the store already records. The snapshot carries both the
58
- * change timeline and the store registry (with each store's current state)
59
- * so the dashboard can render the store browser.
60
- *
61
- * v2: change-log prevState/nextState are replaced with on-device markers
62
- * (see STATE_ON_DEVICE). Dashboards fetch trees via `getChangeDetail`.
63
- * Oversized / unserializable `stores[].currentState` uses the same marker
64
- * and is fetched via `getStoreState`.
65
- *
66
- * The `setState` action powers remote time-travel/reset from the dashboard.
67
- */
68
- export const zustandSyncAdapter = createSyncAdapter({
69
- version: 2,
70
- getSnapshot: () => ({
71
- changes: zustandStateStore.getStateChanges().map(toWireChange),
72
- stores: zustandStateStore.getStoreSnapshots().map(store => ({
73
- ...store,
74
- currentState: toWireState(store.currentState)
75
- }))
76
- }),
77
- subscribe: [onChange => zustandStateStore.subscribe(onChange), onChange => zustandStateStore.subscribeToStores(onChange)],
78
- actions: {
79
- clearEvents: () => {
80
- zustandStateStore.clearStateChanges();
81
- },
82
- /**
83
- * On-demand detail: one change's real prevState/nextState/partial.
84
- * The per-snapshot stream stays tree-free; this is the explicit,
85
- * size-guarded channel for the detail pane.
86
- */
87
- getChangeDetail: detailByIdAction(id => zustandStateStore.getStateChangeById(id), (change, cap) => ({
88
- prevState: cap(change.prevState),
89
- nextState: cap(change.nextState),
90
- partial: cap(change.partial)
91
- })),
92
- /**
93
- * On-demand current state for one store. Wire-form snapshots replace
94
- * oversized / unserializable `currentState` with STATE_ON_DEVICE.
95
- */
96
- getStoreState: params => {
97
- const {
98
- storeName,
99
- path
100
- } = readParams(params);
101
- if (!storeName) return {
102
- found: false,
103
- reason: "missing storeName"
104
- };
105
- const store = zustandStateStore.getStore(storeName);
106
- if (!store) return {
107
- found: false,
108
- reason: "unknown storeName"
109
- };
110
- let currentState;
111
- try {
112
- currentState = store.api.getState();
113
- } catch {
114
- currentState = undefined;
115
- }
116
- // One slice instead of the whole store. The reason to want it is the
117
- // engine's 24,000-character result cap: a caller that never saw the field
118
- // it means to edit is left inventing a shape, which is the failure this
119
- // whole surface keeps producing.
120
- if (path) {
121
- const at = readPath(currentState, path);
122
- if (!at.ok) return {
123
- found: false,
124
- reason: at.error
125
- };
126
- return {
127
- found: true,
128
- storeName,
129
- path,
130
- shape: shapeSummary(at.value),
131
- currentState: at.value
132
- };
133
- }
134
- // The sketch goes BEFORE currentState, deliberately. The engine truncates
135
- // a result by slicing the encoded string at 24,000 characters, so whatever
136
- // is serialised first is what survives — and the reads most in need of a
137
- // shape are exactly the long ones that get cut. It costs ~150 characters
138
- // and does not grow with the data.
139
- const shape = shapeSummary(currentState);
140
- if (!isOverWireBudget(currentState, WIRE_DETAIL_LIMIT_BYTES)) {
141
- return {
142
- found: true,
143
- storeName,
144
- ...(shape ? {
145
- shape
146
- } : {}),
147
- currentState
148
- };
149
- }
150
- return {
151
- found: true,
152
- storeName,
153
- ...(shape ? {
154
- shape
155
- } : {}),
156
- currentState: isJsonSerializable(currentState) ? truncatedNote(WIRE_DETAIL_LIMIT_BYTES) : STATE_ON_DEVICE
157
- };
158
- },
159
- /**
160
- * Compact current-state reader for a remote driver (MCP/LLM): the registered
161
- * stores with light metadata; each store's full `currentState` object stays
162
- * on the device unless `includeValues` is set (compact view returns just the
163
- * top-level `keys` so the shape is visible cheaply).
164
- */
165
- listStores: params => {
166
- const {
167
- includeValues = false,
168
- limit
169
- } = readParams(params);
170
- const all = zustandStateStore.getStoreSnapshots();
171
- const capped = typeof limit === "number" && limit > 0 ? all.slice(0, limit) : all;
172
- const stores = capped.map(s => ({
173
- name: s.name,
174
- changes: s.stateChangeCount,
175
- isPersisted: s.isPersisted,
176
- persistName: s.persistName,
177
- canRehydrate: s.canRehydrate,
178
- ...(includeValues ? {
179
- currentState: s.currentState
180
- } : {
181
- keys: topLevelKeys(s.currentState)
182
- }),
183
- // Cheap enough to always include: this is the compact view a caller
184
- // reaches for precisely because it does not want to pull the data, and
185
- // the item shape of a list is the one thing it cannot infer without it.
186
- ...(shapeSummary(s.currentState) ? {
187
- shape: shapeSummary(s.currentState)
188
- } : {})
189
- }));
190
- return {
191
- stores,
192
- total: all.length,
193
- returned: stores.length,
194
- includedValues: includeValues
195
- };
196
- },
197
- setState: params => {
198
- const {
199
- storeName,
200
- state,
201
- replace,
202
- force,
203
- path,
204
- value
205
- } = readParams(params);
206
- let effective = state;
207
- let keyedNotes = [];
208
- let unchecked = [];
209
- if (!storeName) return {
210
- ok: false,
211
- error: "Missing storeName."
212
- };
213
- const store = zustandStateStore.getStore(storeName);
214
- if (!store) return {
215
- ok: false,
216
- error: `No store named "${storeName}".`
217
- };
218
- // `path` is sugar over a merge: it can only ever produce a patch, which
219
- // then goes through the guard below like any other. Its value is that
220
- // there is no nesting depth for the caller to get wrong.
221
- // `state` is no longer schema-required, because `path`+`value` is a valid
222
- // alternative — so the handler has to be the thing that rejects NEITHER.
223
- // Without this, `state` arrives undefined and falls through to
224
- // `setState(undefined, true)`, which is zustand's whole-state replace: the
225
- // store's data AND its action functions, gone, from a call that looks like
226
- // a typo. This is the most destructive thing in this file.
227
- if (state === undefined && path === undefined) {
228
- return {
229
- ok: false,
230
- error: `Refused: no change was described. Send \`state\` with the fields to merge, or \`path\` and \`value\` to set one value.`
231
- };
232
- }
233
- let pathed = false;
234
- if (path !== undefined) {
235
- if (value === undefined) {
236
- return {
237
- ok: false,
238
- error: `Refused: sent \`path\` without \`value\`. Say what to set "${path}" to; if you mean to clear it, send value:null and the field must already be nullable.`
239
- };
240
- }
241
- let currentForPath;
242
- try {
243
- currentForPath = store.api.getState();
244
- } catch {
245
- currentForPath = undefined;
246
- }
247
- const built = patchFromPath(currentForPath, path, value);
248
- if (!built.ok) return {
249
- ok: false,
250
- error: `Refused: ${built.error}`
251
- };
252
- effective = built.patch;
253
- pathed = true;
254
- }
255
- // On a MERGE (replace:false), constrain it to a typed edit so a remote
256
- // caller can't hand the store a field of the wrong type or a malformed
257
- // new list item — the same protection the query cache has. A replace
258
- // (whole-store reset) and an explicit `force` skip this.
259
- if (!force && (replace === false || pathed)) {
260
- let current;
261
- try {
262
- current = store.api.getState();
263
- } catch {
264
- current = undefined;
265
- }
266
- // Expand id-addressed list edits BEFORE validating, so the typed-edit
267
- // guard below sees a real array and every existing protection still
268
- // applies to the result.
269
- if (current !== undefined) {
270
- const expanded = expandKeyedArrays(current, effective);
271
- // See keyedArrays.ts: an added item whose id disagrees with the key
272
- // it was sent under is ambiguous, and only this layer can say so.
273
- if (expanded.error) {
274
- return {
275
- ok: false,
276
- error: `Refused: ${expanded.error}`
277
- };
278
- }
279
- effective = expanded.patch;
280
- keyedNotes = expanded.notes;
281
- }
282
- // The guard builds its template from `list[0]`, so a list that is EMPTY
283
- // has no template and an added item is written with nothing to check it
284
- // against. That is not a rare corner: it is every "add the first thing"
285
- // flow, and it is where models invent shapes, because on an empty list
286
- // the item shape genuinely exists nowhere in the running app. We cannot
287
- // refuse it — refusing would break adding the first item to anything —
288
- // so we say so instead of letting the guess through unremarked.
289
- if (current !== undefined) {
290
- unchecked = unshapedLists(current).filter(path => touchesList(effective, path));
291
- }
292
- if (current !== undefined) {
293
- const violations = typedEditViolations(current, effective, {
294
- noun: "store"
295
- });
296
- if (violations.length > 0) {
297
- return {
298
- ok: false,
299
- error: refusalText(violations, "store", suggestedPatch(effective, violations)),
300
- ...(suggestedPatch(effective, violations) ? {
301
- suggestedState: suggestedPatch(effective, violations)
302
- } : {}),
303
- violations
304
- };
305
- }
306
- }
307
- }
308
- // A pathed write is a merge by construction — it names one value, so it
309
- // must never fall through to zustand's default whole-state replace.
310
- store.api.setState(effective, pathed ? false : replace ?? true);
311
- // Say what an addressed edit resolved to. A caller that meant to change one
312
- // line and sees "17 item(s) remain" has caught its own mistake.
313
- return {
314
- ok: true,
315
- ...(path !== undefined ? {
316
- path
317
- } : {}),
318
- ...(keyedNotes.length ? {
319
- keyed: keyedNotes
320
- } : {}),
321
- ...(unchecked.length ? {
322
- unchecked: unchecked.map(path => `\`${path}\` was empty, so nothing in the app knows what its items look like and the shape of what you added was NOT checked. If the app renders a field you left out, it will render undefined — read the store back and compare against how the screen uses it.`)
323
- } : {})
324
- };
325
- },
326
- /**
327
- * Re-read a persisted store's saved value and merge it into the live store.
328
- *
329
- * This is what makes editing a store's storage key mean anything. The app
330
- * reads that key once at startup and then holds the state in memory, so an
331
- * outside write — the Storage tool, a scenario, an agent — changes the disk
332
- * and nothing else, and the next time the store saves it writes its own
333
- * copy straight back over the edit.
334
- *
335
- * `persist.rehydrate()` is plain zustand, not a devtools hook, so unlike
336
- * reloading the app this also works in a release build. It merges the saved
337
- * value OVER current state (zustand's default merge is
338
- * `{...current, ...persisted}`), so the store's action functions survive,
339
- * and it does not write back to storage unless a version migration ran —
340
- * the edit stays exactly as it was typed.
341
- *
342
- * Two honest limits, both inherent to persist rather than to this action:
343
- * a key the store's `partialize` excludes is not in the saved value and so
344
- * cannot be applied, and because the merge is shallow, DELETING a top-level
345
- * key from the saved JSON does not remove it from the live store.
346
- */
347
- rehydrate: async params => {
348
- const {
349
- storeName
350
- } = readParams(params);
351
- if (!storeName) return {
352
- ok: false,
353
- error: "Missing storeName."
354
- };
355
- const store = zustandStateStore.getStore(storeName);
356
- if (!store) return {
357
- ok: false,
358
- error: `No store named "${storeName}".`
359
- };
360
- if (!store.isPersisted) {
361
- return {
362
- ok: false,
363
- error: `"${storeName}" is not a persisted store — it has no saved copy to re-read.`
364
- };
365
- }
366
- if (!store.rehydrate) {
367
- return {
368
- ok: false,
369
- error: `"${storeName}" is persisted but Buoy has no rehydrate handle for it. It was probably registered before the app's persist middleware attached.`
370
- };
371
- }
372
- await store.rehydrate();
373
- return {
374
- ok: true,
375
- storeName,
376
- persistName: store.persistName
377
- };
378
- }
379
- }
380
- });
1
+ "use strict";import{expandKeyedArrays as I,patchFromPath as O,readPath as j,refusalText as C,shapeSummary as l,suggestedPatch as k,typedEditViolations as E,unshapedLists as x}from"@buoy-gg/shared-ui";import{zustandStateStore as f}from"../utils/zustandStateStore";import{WIRE_DETAIL_LIMIT_BYTES as A,createSyncAdapter as D,detailByIdAction as R,isJsonSerializable as T,isOverWireBudget as $,memoByIdentity as _,onDeviceMarker as N,readParams as p,toWire as w,truncatedNote as B}from"@buoy-gg/shared-ui";const h=e=>typeof e==="object"&&e!==null&&!Array.isArray(e)&&Object.getPrototypeOf(e)===Object.prototype;function P(e,r){if(!h(e)||!h(r))return r;const t={...e};for(const[o,i]of Object.entries(r))t[o]=o in e?P(e[o],i):i;return t}export const STATE_ON_DEVICE=N("State","State trees stay on the device \u2014 fetched on demand via getChangeDetail / getStoreState.");export const PAYLOAD_ON_DEVICE=N("Payload","Partial stays on the device \u2014 fetched on demand via getChangeDetail.");const Y=e=>!!e&&typeof e==="object"&&!Array.isArray(e);function L(e,r){let t=e;for(const o of r.split(".")){if(!t||typeof t!=="object"||Array.isArray(t))return false;if(!(o in t))return false;t=t[o]}return Array.isArray(t)?t.length>0:!!t&&typeof t==="object"}function M(e){return e&&typeof e==="object"&&!Array.isArray(e)?Object.keys(e):void 0}const V=_(e=>({...e,prevState:STATE_ON_DEVICE,nextState:STATE_ON_DEVICE,partial:w(e.partial,PAYLOAD_ON_DEVICE)}));const W=e=>w(e,STATE_ON_DEVICE);export const zustandSyncAdapter=D({version:2,getSnapshot:()=>({changes:f.getStateChanges().map(V),stores:f.getStoreSnapshots().map(e=>({...e,currentState:W(e.currentState)}))}),subscribe:[e=>f.subscribe(e),e=>f.subscribeToStores(e)],actions:{clearEvents:()=>{f.clearStateChanges()},getChangeDetail:R(e=>f.getStateChangeById(e),(e,r)=>({prevState:r(e.prevState),nextState:r(e.nextState),partial:r(e.partial)})),getStoreState:e=>{const{storeName:r,path:t}=p(e);if(!r)return{found:false,reason:"missing storeName"};const o=f.getStore(r);if(!o)return{found:false,reason:"unknown storeName"};let i;try{i=o.api.getState()}catch{i=void 0}if(t){const s=j(i,t);if(!s.ok)return{found:false,reason:s.error};return{found:true,storeName:r,path:t,shape:l(s.value),currentState:s.value}}const d=l(i);if(!$(i,A)){return{found:true,storeName:r,...d?{shape:d}:{},currentState:i}}return{found:true,storeName:r,...d?{shape:d}:{},currentState:T(i)?B(A):STATE_ON_DEVICE}},listStores:e=>{const{includeValues:r=false,limit:t}=p(e);const o=f.getStoreSnapshots();const i=typeof t==="number"&&t>0?o.slice(0,t):o;const d=i.map(s=>({name:s.name,changes:s.stateChangeCount,isPersisted:s.isPersisted,persistName:s.persistName,canRehydrate:s.canRehydrate,...r?{currentState:s.currentState}:{keys:M(s.currentState)},...l(s.currentState)?{shape:l(s.currentState)}:{}}));return{stores:d,total:o.length,returned:d.length,includedValues:r}},setState:e=>{const{storeName:r,state:t,replace:o,force:i,path:d,value:s}=p(e);let u=t;let y=[];let S=[];if(!r)return{ok:false,error:"Missing storeName."};const c=f.getStore(r);if(!c)return{ok:false,error:`No store named "${r}".`};if(t===void 0&&d===void 0){return{ok:false,error:`Refused: no change was described. Send \`state\` with the fields to merge, or \`path\` and \`value\` to set one value.`}}let g=false;if(d!==void 0){if(s===void 0){return{ok:false,error:`Refused: sent \`path\` without \`value\`. Say what to set "${d}" to; if you mean to clear it, send value:null and the field must already be nullable.`}}let a;try{a=c.api.getState()}catch{a=void 0}const n=O(a,d,s);if(!n.ok)return{ok:false,error:`Refused: ${n.error}`};u=n.patch;g=true}if(!i&&(o===false||g)){let a;try{a=c.api.getState()}catch{a=void 0}if(a!==void 0){const n=I(a,u);if(n.error){return{ok:false,error:`Refused: ${n.error}`}}u=n.patch;y=n.notes}if(a!==void 0){S=x(a).filter(n=>L(u,n))}if(a!==void 0){const n=E(a,u,{noun:"store"});if(n.length>0){return{ok:false,error:C(n,"store",k(u,n)),...k(u,n)?{suggestedState:k(u,n)}:{},violations:n}}}}const b=g||o===false;if(b){let a;try{a=c.api.getState()}catch{a=void 0}if(h(a)&&h(u)){const n={};for(const[m,v]of Object.entries(u))n[m]=m in a?P(a[m],v):v;u=n}}c.api.setState(u,b?false:o??true);return{ok:true,...d!==void 0?{path:d}:{},...y.length?{keyed:y}:{},...S.length?{unchecked:S.map(a=>`\`${a}\` was empty, so nothing in the app knows what its items look like and the shape of what you added was NOT checked. If the app renders a field you left out, it will render undefined \u2014 read the store back and compare against how the screen uses it.`)}:{}}},rehydrate:async e=>{const{storeName:r}=p(e);if(!r)return{ok:false,error:"Missing storeName."};const t=f.getStore(r);if(!t)return{ok:false,error:`No store named "${r}".`};if(!t.isPersisted){return{ok:false,error:`"${r}" is not a persisted store \u2014 it has no saved copy to re-read.`}}if(!t.rehydrate){return{ok:false,error:`"${r}" is persisted but Buoy has no rehydrate handle for it. It was probably registered before the app's persist middleware attached.`}}await t.rehydrate();return{ok:true,storeName:r,persistName:t.persistName}}}});
@@ -1 +1 @@
1
- "use strict";
1
+ "use strict";