@buoy-gg/zustand 7.0.35 → 7.0.38
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/README.md +27 -5
- package/lib/commonjs/index.js +1 -105
- package/lib/commonjs/preset.js +1 -102
- package/lib/commonjs/zustand/components/ZustandActionButton.js +1 -118
- package/lib/commonjs/zustand/components/ZustandDetailViewToggle.js +1 -48
- package/lib/commonjs/zustand/components/ZustandEventFilterView.js +1 -43
- package/lib/commonjs/zustand/components/ZustandIcon.js +1 -18
- package/lib/commonjs/zustand/components/ZustandModal.js +1 -772
- package/lib/commonjs/zustand/components/ZustandStateChangeItem.js +1 -117
- package/lib/commonjs/zustand/components/ZustandStateDetailContent.js +1 -372
- package/lib/commonjs/zustand/components/ZustandStateInfoView.js +1 -488
- package/lib/commonjs/zustand/components/ZustandStoreBrowser.js +1 -204
- package/lib/commonjs/zustand/components/ZustandStoreDetailContent.js +1 -471
- package/lib/commonjs/zustand/components/ZustandStoreEditorScreen.js +1 -393
- package/lib/commonjs/zustand/hooks/useZustandStateChanges.js +1 -72
- package/lib/commonjs/zustand/sync/zustandSyncAdapter.js +1 -219
- package/lib/commonjs/zustand/utils/buoyZustandMiddleware.js +1 -284
- package/lib/commonjs/zustand/utils/editableState.js +1 -139
- package/lib/commonjs/zustand/utils/snapshotProvider.js +1 -209
- package/lib/commonjs/zustand/utils/testIDs.js +1 -82
- package/lib/commonjs/zustand/utils/zustandStateStore.js +1 -375
- package/lib/module/index.js +1 -90
- package/lib/module/preset.js +1 -98
- package/lib/module/zustand/components/ZustandActionButton.js +1 -114
- package/lib/module/zustand/components/ZustandDetailViewToggle.js +1 -43
- package/lib/module/zustand/components/ZustandEventFilterView.js +1 -39
- package/lib/module/zustand/components/ZustandIcon.js +1 -11
- package/lib/module/zustand/components/ZustandModal.js +1 -767
- package/lib/module/zustand/components/ZustandStateChangeItem.js +1 -113
- package/lib/module/zustand/components/ZustandStateDetailContent.js +1 -368
- package/lib/module/zustand/components/ZustandStateInfoView.js +1 -483
- package/lib/module/zustand/components/ZustandStoreBrowser.js +1 -200
- package/lib/module/zustand/components/ZustandStoreDetailContent.js +1 -469
- package/lib/module/zustand/components/ZustandStoreEditorScreen.js +1 -389
- package/lib/module/zustand/hooks/useZustandStateChanges.js +1 -68
- package/lib/module/zustand/sync/zustandSyncAdapter.js +1 -216
- package/lib/module/zustand/utils/buoyZustandMiddleware.js +1 -278
- package/lib/module/zustand/utils/editableState.js +1 -131
- package/lib/module/zustand/utils/snapshotProvider.js +1 -209
- package/lib/module/zustand/utils/testIDs.js +1 -74
- package/lib/module/zustand/utils/zustandStateStore.js +1 -371
- package/lib/typescript/zustand/sync/zustandSyncAdapter.d.ts +33 -0
- package/package.json +4 -4
- package/lib/typescript/index.d.ts.map +0 -1
- package/lib/typescript/preset.d.ts.map +0 -1
- package/lib/typescript/zustand/components/ZustandActionButton.d.ts.map +0 -1
- package/lib/typescript/zustand/components/ZustandDetailViewToggle.d.ts.map +0 -1
- package/lib/typescript/zustand/components/ZustandEventFilterView.d.ts.map +0 -1
- package/lib/typescript/zustand/components/ZustandIcon.d.ts.map +0 -1
- package/lib/typescript/zustand/components/ZustandModal.d.ts.map +0 -1
- package/lib/typescript/zustand/components/ZustandStateChangeItem.d.ts.map +0 -1
- package/lib/typescript/zustand/components/ZustandStateDetailContent.d.ts.map +0 -1
- package/lib/typescript/zustand/components/ZustandStateInfoView.d.ts.map +0 -1
- package/lib/typescript/zustand/components/ZustandStoreBrowser.d.ts.map +0 -1
- package/lib/typescript/zustand/components/ZustandStoreDetailContent.d.ts.map +0 -1
- package/lib/typescript/zustand/components/ZustandStoreEditorScreen.d.ts.map +0 -1
- package/lib/typescript/zustand/hooks/useZustandStateChanges.d.ts.map +0 -1
- package/lib/typescript/zustand/sync/zustandSyncAdapter.d.ts.map +0 -1
- package/lib/typescript/zustand/types/index.d.ts.map +0 -1
- package/lib/typescript/zustand/utils/buoyZustandMiddleware.d.ts.map +0 -1
- package/lib/typescript/zustand/utils/editableState.d.ts.map +0 -1
- package/lib/typescript/zustand/utils/snapshotProvider.d.ts.map +0 -1
- package/lib/typescript/zustand/utils/testIDs.d.ts.map +0 -1
- package/lib/typescript/zustand/utils/zustandStateStore.d.ts.map +0 -1
|
@@ -1,219 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.zustandSyncAdapter = exports.STATE_ON_DEVICE = exports.PAYLOAD_ON_DEVICE = void 0;
|
|
7
|
-
var _zustandStateStore = require("../utils/zustandStateStore");
|
|
8
|
-
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
9
|
-
/**
|
|
10
|
-
* Sentinel used in WIRE-FORM snapshots in place of raw prevState/nextState.
|
|
11
|
-
* Same hazard as redux: up to 200 changes × 2 full trees on every snapshot
|
|
12
|
-
* freeze/OOM-killed large apps. Dashboards fetch trees via `getChangeDetail`.
|
|
13
|
-
*/
|
|
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.");
|
|
15
|
-
|
|
16
|
-
/** Wire-form sentinel for an oversized `partial` (replace can carry a whole tree). */
|
|
17
|
-
const PAYLOAD_ON_DEVICE = exports.PAYLOAD_ON_DEVICE = (0, _sharedUi.onDeviceMarker)("Payload", "Partial stays on the device — fetched on demand via getChangeDetail.");
|
|
18
|
-
/** Top-level keys of an object state, for the compact (no-values) view. */
|
|
19
|
-
function topLevelKeys(state) {
|
|
20
|
-
return state && typeof state === "object" && !Array.isArray(state) ? Object.keys(state) : undefined;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Per-change wire cache. The store prepends and never mutates a recorded
|
|
25
|
-
* change, so keying on identity is correct and self-invalidating — without it
|
|
26
|
-
* every `partial` in the list is re-measured on all ~5 snapshots per second.
|
|
27
|
-
*/
|
|
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);
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Sync adapter for the zustand tool, consumed by @buoy-gg/external-sync's
|
|
38
|
-
* `useExternalSync`.
|
|
39
|
-
*
|
|
40
|
-
* State changes are captured by the middleware (watchStores/buoyDevTools),
|
|
41
|
-
* independent of whether a dashboard is watching — subscribing here only
|
|
42
|
-
* streams what the store already records. The snapshot carries both the
|
|
43
|
-
* change timeline and the store registry (with each store's current state)
|
|
44
|
-
* so the dashboard can render the store browser.
|
|
45
|
-
*
|
|
46
|
-
* v2: change-log prevState/nextState are replaced with on-device markers
|
|
47
|
-
* (see STATE_ON_DEVICE). Dashboards fetch trees via `getChangeDetail`.
|
|
48
|
-
* Oversized / unserializable `stores[].currentState` uses the same marker
|
|
49
|
-
* and is fetched via `getStoreState`.
|
|
50
|
-
*
|
|
51
|
-
* The `setState` action powers remote time-travel/reset from the dashboard.
|
|
52
|
-
*/
|
|
53
|
-
const zustandSyncAdapter = exports.zustandSyncAdapter = (0, _sharedUi.createSyncAdapter)({
|
|
54
|
-
version: 2,
|
|
55
|
-
getSnapshot: () => ({
|
|
56
|
-
changes: _zustandStateStore.zustandStateStore.getStateChanges().map(toWireChange),
|
|
57
|
-
stores: _zustandStateStore.zustandStateStore.getStoreSnapshots().map(store => ({
|
|
58
|
-
...store,
|
|
59
|
-
currentState: toWireState(store.currentState)
|
|
60
|
-
}))
|
|
61
|
-
}),
|
|
62
|
-
subscribe: [onChange => _zustandStateStore.zustandStateStore.subscribe(onChange), onChange => _zustandStateStore.zustandStateStore.subscribeToStores(onChange)],
|
|
63
|
-
actions: {
|
|
64
|
-
clearEvents: () => {
|
|
65
|
-
_zustandStateStore.zustandStateStore.clearStateChanges();
|
|
66
|
-
},
|
|
67
|
-
/**
|
|
68
|
-
* On-demand detail: one change's real prevState/nextState/partial.
|
|
69
|
-
* The per-snapshot stream stays tree-free; this is the explicit,
|
|
70
|
-
* size-guarded channel for the detail pane.
|
|
71
|
-
*/
|
|
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
|
-
})),
|
|
77
|
-
/**
|
|
78
|
-
* On-demand current state for one store. Wire-form snapshots replace
|
|
79
|
-
* oversized / unserializable `currentState` with STATE_ON_DEVICE.
|
|
80
|
-
*/
|
|
81
|
-
getStoreState: params => {
|
|
82
|
-
const {
|
|
83
|
-
storeName
|
|
84
|
-
} = (0, _sharedUi.readParams)(params);
|
|
85
|
-
if (!storeName) return {
|
|
86
|
-
found: false,
|
|
87
|
-
reason: "missing storeName"
|
|
88
|
-
};
|
|
89
|
-
const store = _zustandStateStore.zustandStateStore.getStore(storeName);
|
|
90
|
-
if (!store) return {
|
|
91
|
-
found: false,
|
|
92
|
-
reason: "unknown storeName"
|
|
93
|
-
};
|
|
94
|
-
let currentState;
|
|
95
|
-
try {
|
|
96
|
-
currentState = store.api.getState();
|
|
97
|
-
} catch {
|
|
98
|
-
currentState = undefined;
|
|
99
|
-
}
|
|
100
|
-
if (!(0, _sharedUi.isOverWireBudget)(currentState, _sharedUi.WIRE_DETAIL_LIMIT_BYTES)) {
|
|
101
|
-
return {
|
|
102
|
-
found: true,
|
|
103
|
-
storeName,
|
|
104
|
-
currentState
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
return {
|
|
108
|
-
found: true,
|
|
109
|
-
storeName,
|
|
110
|
-
currentState: (0, _sharedUi.isJsonSerializable)(currentState) ? (0, _sharedUi.truncatedNote)(_sharedUi.WIRE_DETAIL_LIMIT_BYTES) : STATE_ON_DEVICE
|
|
111
|
-
};
|
|
112
|
-
},
|
|
113
|
-
/**
|
|
114
|
-
* Compact current-state reader for a remote driver (MCP/LLM): the registered
|
|
115
|
-
* stores with light metadata; each store's full `currentState` object stays
|
|
116
|
-
* on the device unless `includeValues` is set (compact view returns just the
|
|
117
|
-
* top-level `keys` so the shape is visible cheaply).
|
|
118
|
-
*/
|
|
119
|
-
listStores: params => {
|
|
120
|
-
const {
|
|
121
|
-
includeValues = false,
|
|
122
|
-
limit
|
|
123
|
-
} = (0, _sharedUi.readParams)(params);
|
|
124
|
-
const all = _zustandStateStore.zustandStateStore.getStoreSnapshots();
|
|
125
|
-
const capped = typeof limit === "number" && limit > 0 ? all.slice(0, limit) : all;
|
|
126
|
-
const stores = capped.map(s => ({
|
|
127
|
-
name: s.name,
|
|
128
|
-
changes: s.stateChangeCount,
|
|
129
|
-
isPersisted: s.isPersisted,
|
|
130
|
-
persistName: s.persistName,
|
|
131
|
-
canRehydrate: s.canRehydrate,
|
|
132
|
-
...(includeValues ? {
|
|
133
|
-
currentState: s.currentState
|
|
134
|
-
} : {
|
|
135
|
-
keys: topLevelKeys(s.currentState)
|
|
136
|
-
})
|
|
137
|
-
}));
|
|
138
|
-
return {
|
|
139
|
-
stores,
|
|
140
|
-
total: all.length,
|
|
141
|
-
returned: stores.length,
|
|
142
|
-
includedValues: includeValues
|
|
143
|
-
};
|
|
144
|
-
},
|
|
145
|
-
setState: params => {
|
|
146
|
-
const {
|
|
147
|
-
storeName,
|
|
148
|
-
state,
|
|
149
|
-
replace
|
|
150
|
-
} = (0, _sharedUi.readParams)(params);
|
|
151
|
-
if (!storeName) return {
|
|
152
|
-
ok: false,
|
|
153
|
-
error: "Missing storeName."
|
|
154
|
-
};
|
|
155
|
-
const store = _zustandStateStore.zustandStateStore.getStore(storeName);
|
|
156
|
-
if (!store) return {
|
|
157
|
-
ok: false,
|
|
158
|
-
error: `No store named "${storeName}".`
|
|
159
|
-
};
|
|
160
|
-
store.api.setState(state, replace ?? true);
|
|
161
|
-
return {
|
|
162
|
-
ok: true
|
|
163
|
-
};
|
|
164
|
-
},
|
|
165
|
-
/**
|
|
166
|
-
* Re-read a persisted store's saved value and merge it into the live store.
|
|
167
|
-
*
|
|
168
|
-
* This is what makes editing a store's storage key mean anything. The app
|
|
169
|
-
* reads that key once at startup and then holds the state in memory, so an
|
|
170
|
-
* outside write — the Storage tool, a scenario, an agent — changes the disk
|
|
171
|
-
* and nothing else, and the next time the store saves it writes its own
|
|
172
|
-
* copy straight back over the edit.
|
|
173
|
-
*
|
|
174
|
-
* `persist.rehydrate()` is plain zustand, not a devtools hook, so unlike
|
|
175
|
-
* reloading the app this also works in a release build. It merges the saved
|
|
176
|
-
* value OVER current state (zustand's default merge is
|
|
177
|
-
* `{...current, ...persisted}`), so the store's action functions survive,
|
|
178
|
-
* and it does not write back to storage unless a version migration ran —
|
|
179
|
-
* the edit stays exactly as it was typed.
|
|
180
|
-
*
|
|
181
|
-
* Two honest limits, both inherent to persist rather than to this action:
|
|
182
|
-
* a key the store's `partialize` excludes is not in the saved value and so
|
|
183
|
-
* cannot be applied, and because the merge is shallow, DELETING a top-level
|
|
184
|
-
* key from the saved JSON does not remove it from the live store.
|
|
185
|
-
*/
|
|
186
|
-
rehydrate: async params => {
|
|
187
|
-
const {
|
|
188
|
-
storeName
|
|
189
|
-
} = (0, _sharedUi.readParams)(params);
|
|
190
|
-
if (!storeName) return {
|
|
191
|
-
ok: false,
|
|
192
|
-
error: "Missing storeName."
|
|
193
|
-
};
|
|
194
|
-
const store = _zustandStateStore.zustandStateStore.getStore(storeName);
|
|
195
|
-
if (!store) return {
|
|
196
|
-
ok: false,
|
|
197
|
-
error: `No store named "${storeName}".`
|
|
198
|
-
};
|
|
199
|
-
if (!store.isPersisted) {
|
|
200
|
-
return {
|
|
201
|
-
ok: false,
|
|
202
|
-
error: `"${storeName}" is not a persisted store — it has no saved copy to re-read.`
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
if (!store.rehydrate) {
|
|
206
|
-
return {
|
|
207
|
-
ok: false,
|
|
208
|
-
error: `"${storeName}" is persisted but Buoy has no rehydrate handle for it. It was probably registered before the app's persist middleware attached.`
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
await store.rehydrate();
|
|
212
|
-
return {
|
|
213
|
-
ok: true,
|
|
214
|
-
storeName,
|
|
215
|
-
persistName: store.persistName
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.zustandSyncAdapter=exports.STATE_ON_DEVICE=exports.PAYLOAD_ON_DEVICE=void 0;var _sharedUi=require("@buoy-gg/shared-ui"),_zustandStateStore=require("../utils/zustandStateStore");const STATE_ON_DEVICE=exports.STATE_ON_DEVICE=(0,_sharedUi.onDeviceMarker)("State","State trees stay on the device — fetched on demand via getChangeDetail / getStoreState."),PAYLOAD_ON_DEVICE=exports.PAYLOAD_ON_DEVICE=(0,_sharedUi.onDeviceMarker)("Payload","Partial stays on the device — fetched on demand via getChangeDetail."),isPlainObject=e=>!!e&&"object"==typeof e&&!Array.isArray(e);function touchesList(e,t){let r=e;for(const e of t.split(".")){if(!r||"object"!=typeof r||Array.isArray(r))return!1;if(!(e in r))return!1;r=r[e]}return Array.isArray(r)?r.length>0:!!r&&"object"==typeof r}function topLevelKeys(e){return e&&"object"==typeof e&&!Array.isArray(e)?Object.keys(e):void 0}const toWireChange=(0,_sharedUi.memoByIdentity)(e=>({...e,prevState:STATE_ON_DEVICE,nextState:STATE_ON_DEVICE,partial:(0,_sharedUi.toWire)(e.partial,PAYLOAD_ON_DEVICE)})),toWireState=e=>(0,_sharedUi.toWire)(e,STATE_ON_DEVICE),zustandSyncAdapter=exports.zustandSyncAdapter=(0,_sharedUi.createSyncAdapter)({version:2,getSnapshot:()=>({changes:_zustandStateStore.zustandStateStore.getStateChanges().map(toWireChange),stores:_zustandStateStore.zustandStateStore.getStoreSnapshots().map(e=>({...e,currentState:toWireState(e.currentState)}))}),subscribe:[e=>_zustandStateStore.zustandStateStore.subscribe(e),e=>_zustandStateStore.zustandStateStore.subscribeToStores(e)],actions:{clearEvents:()=>{_zustandStateStore.zustandStateStore.clearStateChanges()},getChangeDetail:(0,_sharedUi.detailByIdAction)(e=>_zustandStateStore.zustandStateStore.getStateChangeById(e),(e,t)=>({prevState:t(e.prevState),nextState:t(e.nextState),partial:t(e.partial)})),getStoreState:e=>{const{storeName:t,path:r}=(0,_sharedUi.readParams)(e);if(!t)return{found:!1,reason:"missing storeName"};const a=_zustandStateStore.zustandStateStore.getStore(t);if(!a)return{found:!1,reason:"unknown storeName"};let s;try{s=a.api.getState()}catch{s=void 0}if(r){const e=(0,_sharedUi.readPath)(s,r);return e.ok?{found:!0,storeName:t,path:r,shape:(0,_sharedUi.shapeSummary)(e.value),currentState:e.value}:{found:!1,reason:e.error}}const o=(0,_sharedUi.shapeSummary)(s);return(0,_sharedUi.isOverWireBudget)(s,_sharedUi.WIRE_DETAIL_LIMIT_BYTES)?{found:!0,storeName:t,...o?{shape:o}:{},currentState:(0,_sharedUi.isJsonSerializable)(s)?(0,_sharedUi.truncatedNote)(_sharedUi.WIRE_DETAIL_LIMIT_BYTES):STATE_ON_DEVICE}:{found:!0,storeName:t,...o?{shape:o}:{},currentState:s}},listStores:e=>{const{includeValues:t=!1,limit:r}=(0,_sharedUi.readParams)(e),a=_zustandStateStore.zustandStateStore.getStoreSnapshots(),s=("number"==typeof r&&r>0?a.slice(0,r):a).map(e=>({name:e.name,changes:e.stateChangeCount,isPersisted:e.isPersisted,persistName:e.persistName,canRehydrate:e.canRehydrate,...t?{currentState:e.currentState}:{keys:topLevelKeys(e.currentState)},...(0,_sharedUi.shapeSummary)(e.currentState)?{shape:(0,_sharedUi.shapeSummary)(e.currentState)}:{}}));return{stores:s,total:a.length,returned:s.length,includedValues:t}},setState:e=>{const{storeName:t,state:r,replace:a,force:s,path:o,value:n}=(0,_sharedUi.readParams)(e);let i=r,d=[],u=[];if(!t)return{ok:!1,error:"Missing storeName."};const h=_zustandStateStore.zustandStateStore.getStore(t);if(!h)return{ok:!1,error:`No store named "${t}".`};if(void 0===r&&void 0===o)return{ok:!1,error:"Refused: no change was described. Send `state` with the fields to merge, or `path` and `value` to set one value."};let S=!1;if(void 0!==o){if(void 0===n)return{ok:!1,error:`Refused: sent \`path\` without \`value\`. Say what to set "${o}" to; if you mean to clear it, send value:null and the field must already be nullable.`};let e;try{e=h.api.getState()}catch{e=void 0}const t=(0,_sharedUi.patchFromPath)(e,o,n);if(!t.ok)return{ok:!1,error:`Refused: ${t.error}`};i=t.patch,S=!0}if(!s&&(!1===a||S)){let e;try{e=h.api.getState()}catch{e=void 0}if(void 0!==e){const t=(0,_sharedUi.expandKeyedArrays)(e,i);if(t.error)return{ok:!1,error:`Refused: ${t.error}`};i=t.patch,d=t.notes}if(void 0!==e&&(u=(0,_sharedUi.unshapedLists)(e).filter(e=>touchesList(i,e))),void 0!==e){const t=(0,_sharedUi.typedEditViolations)(e,i,{noun:"store"});if(t.length>0)return{ok:!1,error:(0,_sharedUi.refusalText)(t,"store",(0,_sharedUi.suggestedPatch)(i,t)),...(0,_sharedUi.suggestedPatch)(i,t)?{suggestedState:(0,_sharedUi.suggestedPatch)(i,t)}:{},violations:t}}}return h.api.setState(i,!S&&(a??!0)),{ok:!0,...void 0!==o?{path:o}:{},...d.length?{keyed:d}:{},...u.length?{unchecked:u.map(e=>`\`${e}\` 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.`)}:{}}},rehydrate:async e=>{const{storeName:t}=(0,_sharedUi.readParams)(e);if(!t)return{ok:!1,error:"Missing storeName."};const r=_zustandStateStore.zustandStateStore.getStore(t);return r?r.isPersisted?r.rehydrate?(await r.rehydrate(),{ok:!0,storeName:t,persistName:r.persistName}):{ok:!1,error:`"${t}" is persisted but Buoy has no rehydrate handle for it. It was probably registered before the app's persist middleware attached.`}:{ok:!1,error:`"${t}" is not a persisted store — it has no saved copy to re-read.`}:{ok:!1,error:`No store named "${t}".`}}}});
|
|
@@ -1,284 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.buoyDevTools = buoyDevTools;
|
|
7
|
-
exports.isStoreInstrumented = isStoreInstrumented;
|
|
8
|
-
exports.watchStores = watchStores;
|
|
9
|
-
var _zustandStateStore = require("./zustandStateStore");
|
|
10
|
-
/**
|
|
11
|
-
* Buoy Zustand DevTools — Store instrumentation
|
|
12
|
-
*
|
|
13
|
-
* Two approaches, from easiest to most detailed:
|
|
14
|
-
*
|
|
15
|
-
* 1. watchStores() — RECOMMENDED. One line, zero store modifications.
|
|
16
|
-
* Uses store.subscribe() externally. Never touches setState.
|
|
17
|
-
* Safe to add/remove — cannot break your stores even if our code has bugs.
|
|
18
|
-
*
|
|
19
|
-
* 2. buoyDevTools() — Opt-in middleware for advanced use.
|
|
20
|
-
* Wraps setState to capture the partial argument and timing.
|
|
21
|
-
* Requires modifying each store's create() call.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
/** Symbol to mark stores that have been watched/instrumented */
|
|
25
|
-
const WATCHED_SYMBOL = Symbol.for("@@buoy-zustand/watched");
|
|
26
|
-
|
|
27
|
-
/** The slice of zustand's persist API this module uses. */
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Detect if a store is using the persist middleware by checking for persist API
|
|
31
|
-
*/
|
|
32
|
-
function detectPersist(store) {
|
|
33
|
-
const persist = store.persist;
|
|
34
|
-
if (persist && typeof persist.getOptions === "function") {
|
|
35
|
-
const options = persist.getOptions();
|
|
36
|
-
return {
|
|
37
|
-
isPersisted: true,
|
|
38
|
-
persistName: options?.name,
|
|
39
|
-
persistApi: persist
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
return {
|
|
43
|
-
isPersisted: false
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// =============================================================================
|
|
48
|
-
// watchStores — Primary, non-intrusive approach
|
|
49
|
-
// =============================================================================
|
|
50
|
-
|
|
51
|
-
/** Minimal store shape — what Zustand's create() returns */
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Watch multiple Zustand stores for state changes.
|
|
55
|
-
*
|
|
56
|
-
* This is the recommended, non-intrusive approach. It uses each store's
|
|
57
|
-
* `.subscribe()` method to observe changes from the outside — it never
|
|
58
|
-
* modifies `setState` or any store internals.
|
|
59
|
-
*
|
|
60
|
-
* **Safe by design:** Even if our listener code has a bug, it cannot break
|
|
61
|
-
* your stores. All listener code is wrapped in try/catch.
|
|
62
|
-
*
|
|
63
|
-
* @example
|
|
64
|
-
* ```tsx
|
|
65
|
-
* // In your _layout.tsx or App.tsx — ONE line:
|
|
66
|
-
* import { watchStores } from '@buoy-gg/zustand';
|
|
67
|
-
* import { useCounterStore } from './stores/counter';
|
|
68
|
-
* import { useAuthStore } from './stores/auth';
|
|
69
|
-
* import { useCartStore } from './stores/cart';
|
|
70
|
-
*
|
|
71
|
-
* watchStores({
|
|
72
|
-
* counterStore: useCounterStore,
|
|
73
|
-
* authStore: useAuthStore,
|
|
74
|
-
* cartStore: useCartStore,
|
|
75
|
-
* });
|
|
76
|
-
* ```
|
|
77
|
-
*
|
|
78
|
-
* @param stores - Object mapping store names to Zustand store hooks
|
|
79
|
-
* @returns Cleanup function that removes all subscriptions
|
|
80
|
-
*/
|
|
81
|
-
function watchStores(stores) {
|
|
82
|
-
const cleanups = [];
|
|
83
|
-
for (const [name, store] of Object.entries(stores)) {
|
|
84
|
-
// Skip if already watched
|
|
85
|
-
const storeAny = store;
|
|
86
|
-
if (storeAny[WATCHED_SYMBOL]) continue;
|
|
87
|
-
storeAny[WATCHED_SYMBOL] = true;
|
|
88
|
-
const {
|
|
89
|
-
isPersisted,
|
|
90
|
-
persistName,
|
|
91
|
-
persistApi
|
|
92
|
-
} = detectPersist(store);
|
|
93
|
-
|
|
94
|
-
// Register the store for overview
|
|
95
|
-
_zustandStateStore.zustandStateStore.registerStore(name, {
|
|
96
|
-
getState: store.getState,
|
|
97
|
-
getInitialState: store.getInitialState,
|
|
98
|
-
setState: store.setState,
|
|
99
|
-
subscribe: store.subscribe
|
|
100
|
-
}, {
|
|
101
|
-
isPersisted,
|
|
102
|
-
persistName,
|
|
103
|
-
// Was detected and thrown away. Without it nothing can re-read the
|
|
104
|
-
// saved copy after an outside edit — see ZustandStoreInfo.rehydrate.
|
|
105
|
-
rehydrate: typeof persistApi?.rehydrate === "function" ? () => persistApi.rehydrate() : undefined
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
// The rehydration is a state change nobody's code asked for — the store
|
|
109
|
-
// simply becomes what the disk said, seconds into the process. Drawn as an
|
|
110
|
-
// ordinary setState it is the most surprising row in the list and the one
|
|
111
|
-
// that looks most like a bug, so it gets its own category. Recognised
|
|
112
|
-
// rather than announced: `hasHydrated()` is still false while the
|
|
113
|
-
// rehydrating `set` is running, and this listener runs inside it.
|
|
114
|
-
let sawHydration = false;
|
|
115
|
-
|
|
116
|
-
// Subscribe to state changes — completely external, never touches setState
|
|
117
|
-
const unsubscribe = store.subscribe((state, prevState) => {
|
|
118
|
-
try {
|
|
119
|
-
let category;
|
|
120
|
-
if (!sawHydration && isPersisted) {
|
|
121
|
-
let hydrating = false;
|
|
122
|
-
try {
|
|
123
|
-
hydrating = persistApi?.hasHydrated?.() === false;
|
|
124
|
-
} catch {
|
|
125
|
-
// An older persist API without hasHydrated — no worse than before.
|
|
126
|
-
}
|
|
127
|
-
if (hydrating) {
|
|
128
|
-
sawHydration = true;
|
|
129
|
-
category = "persist";
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
_zustandStateStore.zustandStateStore.addStateChange({
|
|
133
|
-
storeName: name,
|
|
134
|
-
partial: undefined,
|
|
135
|
-
// Not available in subscribe-only mode
|
|
136
|
-
replace: false,
|
|
137
|
-
prevState,
|
|
138
|
-
nextState: state,
|
|
139
|
-
duration: undefined,
|
|
140
|
-
// Not available in subscribe-only mode
|
|
141
|
-
category,
|
|
142
|
-
isPersisted
|
|
143
|
-
});
|
|
144
|
-
} catch {
|
|
145
|
-
// Silently catch — our bug must never propagate into the store
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
cleanups.push(() => {
|
|
149
|
-
unsubscribe();
|
|
150
|
-
delete store[WATCHED_SYMBOL];
|
|
151
|
-
_zustandStateStore.zustandStateStore.unregisterStore(name);
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
return () => {
|
|
155
|
-
cleanups.forEach(fn => fn());
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// =============================================================================
|
|
160
|
-
// buoyDevTools — Opt-in middleware (advanced, more detail)
|
|
161
|
-
// =============================================================================
|
|
162
|
-
|
|
163
|
-
/** Auto-incrementing store name counter for unnamed stores */
|
|
164
|
-
let unnamedCounter = 0;
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Zustand middleware that instruments a store for Buoy DevTools.
|
|
168
|
-
*
|
|
169
|
-
* This is the advanced approach — it wraps setState to capture:
|
|
170
|
-
* - The partial argument passed to setState
|
|
171
|
-
* - Precise timing of each setState call
|
|
172
|
-
*
|
|
173
|
-
* Use this when you want maximum detail. For most cases, `watchStores()`
|
|
174
|
-
* is simpler and safer.
|
|
175
|
-
*
|
|
176
|
-
* @example
|
|
177
|
-
* ```tsx
|
|
178
|
-
* import { create } from 'zustand';
|
|
179
|
-
* import { buoyDevTools } from '@buoy-gg/zustand';
|
|
180
|
-
*
|
|
181
|
-
* const useCounterStore = create(
|
|
182
|
-
* buoyDevTools(
|
|
183
|
-
* (set) => ({
|
|
184
|
-
* count: 0,
|
|
185
|
-
* increment: () => set((s) => ({ count: s.count + 1 })),
|
|
186
|
-
* }),
|
|
187
|
-
* { name: 'counterStore' }
|
|
188
|
-
* )
|
|
189
|
-
* );
|
|
190
|
-
* ```
|
|
191
|
-
*/
|
|
192
|
-
/**
|
|
193
|
-
* Zustand's own `set` shape (v5 types): with `replace: true` the argument must
|
|
194
|
-
* be a FULL state, so a single `(partial, replace?: boolean)` signature is
|
|
195
|
-
* incompatible in both directions — zustand's set is not assignable to it,
|
|
196
|
-
* which made `create(buoyDevTools(...))` fail to typecheck for every strict-TS
|
|
197
|
-
* user. Mirroring the overloads keeps buoyDevTools a drop-in for `create()`.
|
|
198
|
-
*/
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* The store api object zustand hands a middleware, described structurally so
|
|
202
|
-
* this package never has to import zustand's own types (which differ between
|
|
203
|
-
* v4 and v5). `Record<string, unknown>` was wrong here: `StoreApi<T>` has no
|
|
204
|
-
* index signature, so `create(buoyDevTools(...))` failed to typecheck.
|
|
205
|
-
*/
|
|
206
|
-
|
|
207
|
-
function buoyDevTools(config, options) {
|
|
208
|
-
return (set, get, api) => {
|
|
209
|
-
const storeName = options?.name ?? `store-${++unnamedCounter}`;
|
|
210
|
-
const enabled = options?.enabled !== false;
|
|
211
|
-
|
|
212
|
-
// Forwarding a plain `replace?: boolean` through the overloads needs one
|
|
213
|
-
// widening cast; runtime behavior is unchanged.
|
|
214
|
-
const rawSet = set;
|
|
215
|
-
const instrumentedSet = (partial, replace) => {
|
|
216
|
-
if (!enabled) {
|
|
217
|
-
return rawSet(partial, replace);
|
|
218
|
-
}
|
|
219
|
-
const startTime = performance.now();
|
|
220
|
-
const prevState = get();
|
|
221
|
-
rawSet(partial, replace);
|
|
222
|
-
const nextState = get();
|
|
223
|
-
const duration = performance.now() - startTime;
|
|
224
|
-
const resolvedPartial = typeof partial === "function" ? "(updater fn)" : partial;
|
|
225
|
-
_zustandStateStore.zustandStateStore.addStateChange({
|
|
226
|
-
storeName,
|
|
227
|
-
partial: resolvedPartial,
|
|
228
|
-
replace: replace ?? false,
|
|
229
|
-
prevState,
|
|
230
|
-
nextState,
|
|
231
|
-
duration,
|
|
232
|
-
// Read back off the registry rather than hardcoded false. Persistence
|
|
233
|
-
// is a property of the STORE, and this middleware can be wrapped
|
|
234
|
-
// around a persisted one — every change on such a store used to be
|
|
235
|
-
// drawn as un-persisted, which is the opposite of what the badge is
|
|
236
|
-
// for. Registration happens a tick after creation, so a set fired in
|
|
237
|
-
// that first tick still reports false; the alternative is re-sniffing
|
|
238
|
-
// `api.persist` on every set.
|
|
239
|
-
isPersisted: _zustandStateStore.zustandStateStore.getStore(storeName)?.isPersisted ?? false
|
|
240
|
-
});
|
|
241
|
-
};
|
|
242
|
-
const result = config(instrumentedSet, get, api);
|
|
243
|
-
|
|
244
|
-
// Register the store after creation
|
|
245
|
-
setTimeout(() => {
|
|
246
|
-
if (api[WATCHED_SYMBOL]) return;
|
|
247
|
-
api[WATCHED_SYMBOL] = true;
|
|
248
|
-
const {
|
|
249
|
-
isPersisted,
|
|
250
|
-
persistName
|
|
251
|
-
} = detectPersist(api);
|
|
252
|
-
_zustandStateStore.zustandStateStore.registerStore(storeName, {
|
|
253
|
-
getState: get,
|
|
254
|
-
getInitialState: api.getInitialState,
|
|
255
|
-
// The INSTRUMENTED set, not the raw one.
|
|
256
|
-
//
|
|
257
|
-
// This handle is what the tool's own JUMP and RESET STORE call, and
|
|
258
|
-
// registering the raw `set` here made both of them invisible: the
|
|
259
|
-
// store changed, the app re-rendered, and nothing appeared in the
|
|
260
|
-
// log — so the newest row's "state after change" no longer described
|
|
261
|
-
// the store, and the next diff compared against a state the device
|
|
262
|
-
// had never been in. The watch path never had this problem, because
|
|
263
|
-
// it observes the store from outside and cannot be gone around.
|
|
264
|
-
setState: instrumentedSet,
|
|
265
|
-
subscribe: api.subscribe
|
|
266
|
-
}, {
|
|
267
|
-
isPersisted,
|
|
268
|
-
persistName
|
|
269
|
-
});
|
|
270
|
-
}, 0);
|
|
271
|
-
return result;
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// =============================================================================
|
|
276
|
-
// Utility
|
|
277
|
-
// =============================================================================
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Check if a store is being watched/instrumented
|
|
281
|
-
*/
|
|
282
|
-
function isStoreInstrumented(store) {
|
|
283
|
-
return store[WATCHED_SYMBOL] === true;
|
|
284
|
-
}
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.buoyDevTools=buoyDevTools,exports.isStoreInstrumented=isStoreInstrumented,exports.watchStores=watchStores;var _zustandStateStore=require("./zustandStateStore");const WATCHED_SYMBOL=Symbol.for("@@buoy-zustand/watched");function detectPersist(t){const e=t.persist;if(e&&"function"==typeof e.getOptions){const t=e.getOptions();return{isPersisted:!0,persistName:t?.name,persistApi:e}}return{isPersisted:!1}}function watchStores(t){const e=[];for(const[s,r]of Object.entries(t)){const t=r;if(t[WATCHED_SYMBOL])continue;t[WATCHED_SYMBOL]=!0;const{isPersisted:a,persistName:n,persistApi:o}=detectPersist(r);_zustandStateStore.zustandStateStore.registerStore(s,{getState:r.getState,getInitialState:r.getInitialState,setState:r.setState,subscribe:r.subscribe},{isPersisted:a,persistName:n,rehydrate:"function"==typeof o?.rehydrate?()=>o.rehydrate():void 0});let i=!1;const S=r.subscribe((t,e)=>{try{let r;if(!i&&a){let t=!1;try{t=!1===o?.hasHydrated?.()}catch{}t&&(i=!0,r="persist")}_zustandStateStore.zustandStateStore.addStateChange({storeName:s,partial:void 0,replace:!1,prevState:e,nextState:t,duration:void 0,category:r,isPersisted:a})}catch{}});e.push(()=>{S(),delete r[WATCHED_SYMBOL],_zustandStateStore.zustandStateStore.unregisterStore(s)})}return()=>{e.forEach(t=>t())}}let unnamedCounter=0;function buoyDevTools(t,e){return(s,r,a)=>{const n=e?.name??"store-"+ ++unnamedCounter,o=!1!==e?.enabled,i=s,S=(t,e)=>{if(!o)return i(t,e);const s=performance.now(),a=r();i(t,e);const S=r(),u=performance.now()-s,d="function"==typeof t?"(updater fn)":t;_zustandStateStore.zustandStateStore.addStateChange({storeName:n,partial:d,replace:e??!1,prevState:a,nextState:S,duration:u,isPersisted:_zustandStateStore.zustandStateStore.getStore(n)?.isPersisted??!1})},u=t(S,r,a);return setTimeout(()=>{if(a[WATCHED_SYMBOL])return;a[WATCHED_SYMBOL]=!0;const{isPersisted:t,persistName:e}=detectPersist(a);_zustandStateStore.zustandStateStore.registerStore(n,{getState:r,getInitialState:a.getInitialState,setState:S,subscribe:a.subscribe},{isPersisted:t,persistName:e})},0),u}}function isStoreInstrumented(t){return!0===t[WATCHED_SYMBOL]}
|
|
@@ -1,139 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.STRIP_MAX_DEPTH = void 0;
|
|
7
|
-
exports.computeMergePartial = computeMergePartial;
|
|
8
|
-
exports.editableStateOf = editableStateOf;
|
|
9
|
-
exports.isPlainObjectLike = isPlainObjectLike;
|
|
10
|
-
exports.stripFunctions = stripFunctions;
|
|
11
|
-
/**
|
|
12
|
-
* What of a zustand store's state can be EDITED, and how an edit is written.
|
|
13
|
-
*
|
|
14
|
-
* The two zustand facts every rule here follows from:
|
|
15
|
-
*
|
|
16
|
-
* 1. Stores conventionally keep their action functions IN state
|
|
17
|
-
* (`increment: () => …`). Functions can't be typed into a tree editor and
|
|
18
|
-
* don't survive serialization, so the editor works on a function-free view
|
|
19
|
-
* of state.
|
|
20
|
-
* 2. `setState` is either a TOP-LEVEL SHALLOW MERGE (`Object.assign({}, state,
|
|
21
|
-
* partial)`) or a full replace that deletes every key not present — actions
|
|
22
|
-
* included. So the editor writes merge-only, always: every edit, however
|
|
23
|
-
* deep, is expressed as "replace the top-level key(s) that contain it"
|
|
24
|
-
* ({@link computeMergePartial}), and the two edits a merge cannot express —
|
|
25
|
-
* removing or renaming a top-level key — are refused rather than silently
|
|
26
|
-
* not happening.
|
|
27
|
-
*
|
|
28
|
-
* `stripFunctions` lives here (the Time Machine snapshot provider imports it)
|
|
29
|
-
* because capture and editing need the identical answer to "what is this
|
|
30
|
-
* store's data".
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
/** Bounds every recursive walk over store state. */
|
|
34
|
-
const STRIP_MAX_DEPTH = exports.STRIP_MAX_DEPTH = 8;
|
|
35
|
-
function isPlainObjectLike(v) {
|
|
36
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** Clone `value` without functions, recording their paths. */
|
|
40
|
-
function stripFunctions(value, path, fnPaths, seen, depth) {
|
|
41
|
-
if (typeof value === "function") {
|
|
42
|
-
fnPaths.push(path);
|
|
43
|
-
return undefined;
|
|
44
|
-
}
|
|
45
|
-
if (typeof value !== "object" || value === null || depth >= STRIP_MAX_DEPTH) {
|
|
46
|
-
return value;
|
|
47
|
-
}
|
|
48
|
-
if (seen.has(value)) return undefined; // cycle — drop the back-reference
|
|
49
|
-
seen.add(value);
|
|
50
|
-
if (Array.isArray(value)) {
|
|
51
|
-
return value.map((item, i) => typeof item === "function" ? (fnPaths.push(`${path}[${i}]`), null) : stripFunctions(item, `${path}[${i}]`, fnPaths, seen, depth + 1));
|
|
52
|
-
}
|
|
53
|
-
if (!isPlainObjectLike(value)) return value; // Date/Map/Set/etc. — superjson handles
|
|
54
|
-
const out = {};
|
|
55
|
-
for (const [k, v] of Object.entries(value)) {
|
|
56
|
-
if (typeof v === "function") {
|
|
57
|
-
fnPaths.push(path ? `${path}.${k}` : k);
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
out[k] = stripFunctions(v, path ? `${path}.${k}` : k, fnPaths, seen, depth + 1);
|
|
61
|
-
}
|
|
62
|
-
return out;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* The editor's view of one store's state, with the reasons it may refuse.
|
|
66
|
-
*
|
|
67
|
-
* - A remote mirror that hasn't hydrated yet holds a marker, not state — the
|
|
68
|
-
* real value is one `getStoreState` round trip away, so "wait" rather than
|
|
69
|
-
* "can't".
|
|
70
|
-
* - Non-object state (`create(() => 5)`) has no top-level keys, so a merge
|
|
71
|
-
* write cannot address any part of it. Editing it would require a replace,
|
|
72
|
-
* which is the destructive path this editor exists to avoid.
|
|
73
|
-
* - A function nested BELOW the top level (rare — the convention keeps actions
|
|
74
|
-
* at the top) sits inside the very key an edit would rewrite, and a merge
|
|
75
|
-
* write of that key would silently drop it. Refusing the whole store is
|
|
76
|
-
* blunt but honest; per-key locks can come later if this ever bites.
|
|
77
|
-
*/
|
|
78
|
-
function editableStateOf(state) {
|
|
79
|
-
if (isPlainObjectLike(state) && state.__buoyStateOnDevice === true) {
|
|
80
|
-
return {
|
|
81
|
-
data: null,
|
|
82
|
-
fnPaths: [],
|
|
83
|
-
blockedReason: "Store state hasn't synced from the device yet."
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
if (!isPlainObjectLike(state)) {
|
|
87
|
-
return {
|
|
88
|
-
data: null,
|
|
89
|
-
fnPaths: [],
|
|
90
|
-
blockedReason: "Only stores whose state is a plain object can be edited — " + "a merge write has nothing to address in " + (Array.isArray(state) ? "an array" : `a ${typeof state}`) + "."
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
const fnPaths = [];
|
|
94
|
-
const data = stripFunctions(state, "", fnPaths, new WeakSet(), 0);
|
|
95
|
-
const nested = fnPaths.filter(p => p.includes(".") || p.includes("["));
|
|
96
|
-
if (nested.length > 0) {
|
|
97
|
-
return {
|
|
98
|
-
data: null,
|
|
99
|
-
fnPaths,
|
|
100
|
-
blockedReason: `State keeps a function inside "${nested[0]}" — a merge write of ` + "that key would drop it, so this store can't be edited here."
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
return {
|
|
104
|
-
data,
|
|
105
|
-
fnPaths,
|
|
106
|
-
blockedReason: null
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Diff an edited draft against the state it was seeded from, as a `setState`
|
|
112
|
-
* MERGE partial: only the top-level keys whose contents changed.
|
|
113
|
-
*
|
|
114
|
-
* Diffed against the SEED, not the live state at save time, on purpose — the
|
|
115
|
-
* partial then carries exactly what the user changed, so a key the app wrote
|
|
116
|
-
* while the editor was open and the user never touched keeps the app's value
|
|
117
|
-
* (per-key last-write-wins, the same contract the draft's own dirtiness uses).
|
|
118
|
-
*
|
|
119
|
-
* Throws on the two edits a merge cannot express, naming the key: a draft
|
|
120
|
-
* that dropped a top-level key (which is also what a raw-JSON rename produces
|
|
121
|
-
* — the old key dropped, a new one added), because `setState(partial, false)`
|
|
122
|
-
* would leave it exactly as it was while the editor showed it gone.
|
|
123
|
-
*/
|
|
124
|
-
function computeMergePartial(seed, draft) {
|
|
125
|
-
if (!isPlainObjectLike(draft)) {
|
|
126
|
-
throw new Error("The store's state must stay an object.");
|
|
127
|
-
}
|
|
128
|
-
const dropped = Object.keys(seed).filter(k => !(k in draft));
|
|
129
|
-
if (dropped.length > 0) {
|
|
130
|
-
throw new Error(`Can't remove top-level ${dropped.length === 1 ? "key" : "keys"} ` + `${dropped.map(k => `"${k}"`).join(", ")} — zustand merge writes ` + "can't delete. Edit the value instead.");
|
|
131
|
-
}
|
|
132
|
-
const partial = {};
|
|
133
|
-
for (const key of Object.keys(draft)) {
|
|
134
|
-
if (!(key in seed) || JSON.stringify(draft[key]) !== JSON.stringify(seed[key])) {
|
|
135
|
-
partial[key] = draft[key];
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
return partial;
|
|
139
|
-
}
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.STRIP_MAX_DEPTH=void 0,exports.computeMergePartial=computeMergePartial,exports.editableStateOf=editableStateOf,exports.isPlainObjectLike=isPlainObjectLike,exports.stripFunctions=stripFunctions;const STRIP_MAX_DEPTH=exports.STRIP_MAX_DEPTH=8;function isPlainObjectLike(e){if("object"!=typeof e||null===e||Array.isArray(e))return!1;const t=Object.getPrototypeOf(e);return t===Object.prototype||null===t}function stripFunctions(e,t,n,i,r){if("function"==typeof e)return void n.push(t);if("object"!=typeof e||null===e||r>=STRIP_MAX_DEPTH)return e;if(i.has(e))return;if(i.add(e),Array.isArray(e))return e.map((e,s)=>"function"==typeof e?(n.push(`${t}[${s}]`),null):stripFunctions(e,`${t}[${s}]`,n,i,r+1));if(!isPlainObjectLike(e))return e;const s={};for(const[o,a]of Object.entries(e))"function"!=typeof a?s[o]=stripFunctions(a,t?`${t}.${o}`:o,n,i,r+1):n.push(t?`${t}.${o}`:o);return s}function editableStateOf(e){if(isPlainObjectLike(e)&&!0===e.__buoyStateOnDevice)return{data:null,fnPaths:[],blockedReason:"Store state hasn't synced from the device yet."};if(!isPlainObjectLike(e))return{data:null,fnPaths:[],blockedReason:"Only stores whose state is a plain object can be edited — a merge write has nothing to address in "+(Array.isArray(e)?"an array":"a "+typeof e)+"."};const t=[],n=stripFunctions(e,"",t,new WeakSet,0),i=t.filter(e=>e.includes(".")||e.includes("["));return i.length>0?{data:null,fnPaths:t,blockedReason:`State keeps a function inside "${i[0]}" — a merge write of that key would drop it, so this store can't be edited here.`}:{data:n,fnPaths:t,blockedReason:null}}function computeMergePartial(e,t){if(!isPlainObjectLike(t))throw new Error("The store's state must stay an object.");const n=Object.keys(e).filter(e=>!(e in t));if(n.length>0)throw new Error(`Can't remove top-level ${1===n.length?"key":"keys"} ${n.map(e=>`"${e}"`).join(", ")} — zustand merge writes can't delete. Edit the value instead.`);const i={};for(const n of Object.keys(t))n in e&&JSON.stringify(t[n])===JSON.stringify(e[n])||(i[n]=t[n]);return i}
|