@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,209 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
4
|
-
var _zustandStateStore = require("./zustandStateStore");
|
|
5
|
-
var _editableState = require("./editableState");
|
|
6
|
-
/**
|
|
7
|
-
* Time Machine snapshot provider for Zustand.
|
|
8
|
-
*
|
|
9
|
-
* Registered into @buoy-gg/shared-ui's stateSnapshotRegistry at module init;
|
|
10
|
-
* consumed by @buoy-gg/time-machine. Neither package imports the other.
|
|
11
|
-
*
|
|
12
|
-
* The Zustand-specific hazard: stores conventionally keep their action
|
|
13
|
-
* functions IN state (`increment: () => ...`). Functions don't survive
|
|
14
|
-
* serialization, and a full `setState(snapshot, true)` replace would wipe
|
|
15
|
-
* them, breaking every `useStore(s => s.increment)` call site. So capture
|
|
16
|
-
* strips functions (recording where they were), and restore re-grafts the
|
|
17
|
-
* LIVE store's functions onto the snapshot before replacing.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
// The function-stripping walk is shared with the store editor (editableState
|
|
21
|
-
// .ts) — capture and editing must agree on what a store's "data" is.
|
|
22
|
-
const MAX_DEPTH = _editableState.STRIP_MAX_DEPTH;
|
|
23
|
-
/**
|
|
24
|
-
* Copy every function-valued property from the LIVE state onto the restored
|
|
25
|
-
* snapshot (creating containers as needed), so store actions keep working
|
|
26
|
-
* after a full replace.
|
|
27
|
-
*/
|
|
28
|
-
function graftFunctions(target, live, seen, depth) {
|
|
29
|
-
if (!(0, _editableState.isPlainObjectLike)(live) || !(0, _editableState.isPlainObjectLike)(target)) return;
|
|
30
|
-
if (depth >= MAX_DEPTH || seen.has(live)) return;
|
|
31
|
-
seen.add(live);
|
|
32
|
-
for (const [k, v] of Object.entries(live)) {
|
|
33
|
-
if (typeof v === "function") {
|
|
34
|
-
target[k] = v;
|
|
35
|
-
} else if ((0, _editableState.isPlainObjectLike)(v)) {
|
|
36
|
-
if (!(0, _editableState.isPlainObjectLike)(target[k])) {
|
|
37
|
-
// Branch missing from the snapshot — only recreate it if it holds
|
|
38
|
-
// functions somewhere; otherwise the snapshot's absence wins.
|
|
39
|
-
if (!containsFunction(v, new WeakSet(), depth + 1)) continue;
|
|
40
|
-
target[k] = {};
|
|
41
|
-
}
|
|
42
|
-
graftFunctions(target[k], v, seen, depth + 1);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
function containsFunction(value, seen, depth) {
|
|
47
|
-
if (depth >= MAX_DEPTH || seen.has(value)) return false;
|
|
48
|
-
seen.add(value);
|
|
49
|
-
for (const v of Object.values(value)) {
|
|
50
|
-
if (typeof v === "function") return true;
|
|
51
|
-
if ((0, _editableState.isPlainObjectLike)(v) && containsFunction(v, seen, depth + 1)) return true;
|
|
52
|
-
}
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
function status() {
|
|
56
|
-
const count = _zustandStateStore.zustandStateStore.getStores().length;
|
|
57
|
-
if (count === 0) {
|
|
58
|
-
return {
|
|
59
|
-
canCapture: false,
|
|
60
|
-
canRestore: false,
|
|
61
|
-
detail: "No Zustand stores registered — pass `zustandStores` to FloatingDevTools or add the buoyDevTools middleware"
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
return {
|
|
65
|
-
canCapture: true,
|
|
66
|
-
canRestore: true
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
async function capture() {
|
|
70
|
-
const warnings = [];
|
|
71
|
-
const stores = {};
|
|
72
|
-
for (const info of _zustandStateStore.zustandStateStore.getStores()) {
|
|
73
|
-
const fnPaths = [];
|
|
74
|
-
let state;
|
|
75
|
-
try {
|
|
76
|
-
state = (0, _editableState.stripFunctions)(info.api.getState(), "", fnPaths, new WeakSet(), 0);
|
|
77
|
-
} catch (e) {
|
|
78
|
-
warnings.push(`store "${info.name}": capture failed (${String(e)})`);
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
stores[info.name] = {
|
|
82
|
-
state,
|
|
83
|
-
fnPaths
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
return {
|
|
87
|
-
data: {
|
|
88
|
-
stores
|
|
89
|
-
},
|
|
90
|
-
warnings
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
async function restore(data, options) {
|
|
94
|
-
const snapshot = data;
|
|
95
|
-
const scope = (0, _sharedUi.createRestoreScope)(options);
|
|
96
|
-
const result = (0, _sharedUi.emptyRestoreResult)();
|
|
97
|
-
if (!snapshot?.stores) {
|
|
98
|
-
return (0, _sharedUi.failedRestore)("empty zustand snapshot");
|
|
99
|
-
}
|
|
100
|
-
for (const [name, envelope] of Object.entries(snapshot.stores)) {
|
|
101
|
-
if (!scope.allows(name)) continue;
|
|
102
|
-
const info = _zustandStateStore.zustandStateStore.getStore(name);
|
|
103
|
-
if (!info) {
|
|
104
|
-
result.skipped.push({
|
|
105
|
-
id: name,
|
|
106
|
-
reason: "store not registered (renamed, or created after snapshot?)"
|
|
107
|
-
});
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
try {
|
|
111
|
-
const next = envelope.state;
|
|
112
|
-
graftFunctions(next, info.api.getState(), new WeakSet(), 0);
|
|
113
|
-
info.api.setState(next, true);
|
|
114
|
-
if (info.api.getState() === next) {
|
|
115
|
-
result.applied += 1;
|
|
116
|
-
} else {
|
|
117
|
-
result.warnings.push(`store "${name}": setState did not take effect`);
|
|
118
|
-
result.ok = false;
|
|
119
|
-
}
|
|
120
|
-
} catch (e) {
|
|
121
|
-
result.skipped.push({
|
|
122
|
-
id: name,
|
|
123
|
-
reason: String(e)
|
|
124
|
-
});
|
|
125
|
-
result.ok = false;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
const excludedWarning = scope.excludedWarning("store(s)");
|
|
129
|
-
if (excludedWarning) result.warnings.push(excludedWarning);
|
|
130
|
-
return result;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Per-store restore preview. Both sides are function-stripped through the
|
|
135
|
-
* SAME depth-8 normalizer as capture — diffing snapshot-vs-RAW-live would
|
|
136
|
-
* report every stripped function and every beyond-depth subtree as removed.
|
|
137
|
-
*/
|
|
138
|
-
async function previewItems(target, base) {
|
|
139
|
-
const targetStores = target?.stores ?? {};
|
|
140
|
-
const liveMode = base === undefined;
|
|
141
|
-
const baseStores = {};
|
|
142
|
-
if (liveMode) {
|
|
143
|
-
for (const info of _zustandStateStore.zustandStateStore.getStores()) {
|
|
144
|
-
const fnPaths = [];
|
|
145
|
-
try {
|
|
146
|
-
baseStores[info.name] = {
|
|
147
|
-
state: (0, _editableState.stripFunctions)(info.api.getState(), "", fnPaths, new WeakSet(), 0),
|
|
148
|
-
fnPaths
|
|
149
|
-
};
|
|
150
|
-
} catch {
|
|
151
|
-
// unreadable live store — treated as absent
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
} else {
|
|
155
|
-
Object.assign(baseStores, base?.stores ?? {});
|
|
156
|
-
}
|
|
157
|
-
const items = [];
|
|
158
|
-
const notes = [];
|
|
159
|
-
let preservedFns = 0;
|
|
160
|
-
for (const name of (0, _sharedUi.unionKeys)(baseStores, targetStores)) {
|
|
161
|
-
const inTarget = name in targetStores;
|
|
162
|
-
const inBase = name in baseStores;
|
|
163
|
-
if (!inTarget) {
|
|
164
|
-
// Restore never touches stores the snapshot doesn't know about.
|
|
165
|
-
if (liveMode) notes.push(`store "${name}" not in snapshot — untouched`);else items.push({
|
|
166
|
-
key: name,
|
|
167
|
-
label: name,
|
|
168
|
-
verdict: "removed",
|
|
169
|
-
oldValue: baseStores[name].state
|
|
170
|
-
});
|
|
171
|
-
continue;
|
|
172
|
-
}
|
|
173
|
-
preservedFns += targetStores[name].fnPaths?.length ?? 0;
|
|
174
|
-
if (liveMode && !_zustandStateStore.zustandStateStore.getStore(name)) {
|
|
175
|
-
items.push({
|
|
176
|
-
key: name,
|
|
177
|
-
label: name,
|
|
178
|
-
verdict: "wont-apply",
|
|
179
|
-
reason: "store not registered (renamed, or created after snapshot?)"
|
|
180
|
-
});
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
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
|
-
}));
|
|
193
|
-
}
|
|
194
|
-
if (preservedFns > 0) {
|
|
195
|
-
notes.push(`${preservedFns} store action function${preservedFns === 1 ? "" : "s"} preserved on restore (not diffed)`);
|
|
196
|
-
}
|
|
197
|
-
return {
|
|
198
|
-
items,
|
|
199
|
-
notes
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
(0, _sharedUi.registerStateSnapshotProvider)({
|
|
203
|
-
id: "zustand",
|
|
204
|
-
label: "Zustand",
|
|
205
|
-
status,
|
|
206
|
-
capture,
|
|
207
|
-
restore,
|
|
208
|
-
previewItems
|
|
209
|
-
});
|
|
1
|
+
"use strict";var _sharedUi=require("@buoy-gg/shared-ui"),_zustandStateStore=require("./zustandStateStore"),_editableState=require("./editableState");const MAX_DEPTH=_editableState.STRIP_MAX_DEPTH;function graftFunctions(t,e,s,a){if((0,_editableState.isPlainObjectLike)(e)&&(0,_editableState.isPlainObjectLike)(t)&&!(a>=MAX_DEPTH||s.has(e))){s.add(e);for(const[n,r]of Object.entries(e))if("function"==typeof r)t[n]=r;else if((0,_editableState.isPlainObjectLike)(r)){if(!(0,_editableState.isPlainObjectLike)(t[n])){if(!containsFunction(r,new WeakSet,a+1))continue;t[n]={}}graftFunctions(t[n],r,s,a+1)}}}function containsFunction(t,e,s){if(s>=MAX_DEPTH||e.has(t))return!1;e.add(t);for(const a of Object.values(t)){if("function"==typeof a)return!0;if((0,_editableState.isPlainObjectLike)(a)&&containsFunction(a,e,s+1))return!0}return!1}function status(){return 0===_zustandStateStore.zustandStateStore.getStores().length?{canCapture:!1,canRestore:!1,detail:"No Zustand stores registered — pass `zustandStores` to FloatingDevTools or add the buoyDevTools middleware"}:{canCapture:!0,canRestore:!0}}async function capture(){const t=[],e={};for(const s of _zustandStateStore.zustandStateStore.getStores()){const a=[];let n;try{n=(0,_editableState.stripFunctions)(s.api.getState(),"",a,new WeakSet,0)}catch(e){t.push(`store "${s.name}": capture failed (${String(e)})`);continue}e[s.name]={state:n,fnPaths:a}}return{data:{stores:e},warnings:t}}async function restore(t,e){const s=t,a=(0,_sharedUi.createRestoreScope)(e),n=(0,_sharedUi.emptyRestoreResult)();if(!s?.stores)return(0,_sharedUi.failedRestore)("empty zustand snapshot");for(const[t,e]of Object.entries(s.stores)){if(!a.allows(t))continue;const s=_zustandStateStore.zustandStateStore.getStore(t);if(s)try{const a=e.state;graftFunctions(a,s.api.getState(),new WeakSet,0),s.api.setState(a,!0),s.api.getState()===a?n.applied+=1:(n.warnings.push(`store "${t}": setState did not take effect`),n.ok=!1)}catch(e){n.skipped.push({id:t,reason:String(e)}),n.ok=!1}else n.skipped.push({id:t,reason:"store not registered (renamed, or created after snapshot?)"})}const r=a.excludedWarning("store(s)");return r&&n.warnings.push(r),n}async function previewItems(t,e){const s=t?.stores??{},a=void 0===e,n={};if(a)for(const t of _zustandStateStore.zustandStateStore.getStores()){const e=[];try{n[t.name]={state:(0,_editableState.stripFunctions)(t.api.getState(),"",e,new WeakSet,0),fnPaths:e}}catch{}}else Object.assign(n,e?.stores??{});const r=[],o=[];let i=0;for(const t of(0,_sharedUi.unionKeys)(n,s)){const e=t in n;t in s?(i+=s[t].fnPaths?.length??0,!a||_zustandStateStore.zustandStateStore.getStore(t)?r.push((0,_sharedUi.classifyPreviewItem)({key:t,label:t,oldValue:n[t]?.state,newValue:s[t].state,inOld:e,inNew:!0},{tooLargeReason:"store too large to render a diff — restore still applies it"})):r.push({key:t,label:t,verdict:"wont-apply",reason:"store not registered (renamed, or created after snapshot?)"})):a?o.push(`store "${t}" not in snapshot — untouched`):r.push({key:t,label:t,verdict:"removed",oldValue:n[t].state})}return i>0&&o.push(`${i} store action function${1===i?"":"s"} preserved on restore (not diffed)`),{items:r,notes:o}}(0,_sharedUi.registerStateSnapshotProvider)({id:"zustand",label:"Zustand",status:status,capture:capture,restore:restore,previewItems:previewItems});
|
|
@@ -1,82 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.ZUSTAND_TEST_ID = void 0;
|
|
7
|
-
exports.buildChangeRowTestIDs = buildChangeRowTestIDs;
|
|
8
|
-
exports.changeRowTestID = changeRowTestID;
|
|
9
|
-
exports.slugForTestID = slugForTestID;
|
|
10
|
-
exports.storeRowTestID = storeRowTestID;
|
|
11
|
-
/**
|
|
12
|
-
* Zustand tool test handles.
|
|
13
|
-
*
|
|
14
|
-
* Every tappable and every value the e2e suite reads back is addressed
|
|
15
|
-
* `buoy-zustand-<thing>`. Two rules in here need explaining.
|
|
16
|
-
*
|
|
17
|
-
* **A change row's handle carries an ORDINAL, and it counts per STORE.** The
|
|
18
|
-
* Events tool can address a row by what it is about, because an event is a
|
|
19
|
-
* thing that happened to a named subject — one key, one URL, one atom. A
|
|
20
|
-
* zustand state change is not: a store that a screen writes to fifty times
|
|
21
|
-
* produces fifty separate, equally real rows. Addressing them by store name
|
|
22
|
-
* alone would give fifty views one handle, and Detox fails a *matcher* that
|
|
23
|
-
* hits more than one element — so a suite would break on `toExist()`, before
|
|
24
|
-
* any assertion, with an error that says nothing about duplicate changes.
|
|
25
|
-
*
|
|
26
|
-
* So the handle is `buoy-zustand-row-<slug(storeName)>-<n>`, where n counts
|
|
27
|
-
* back from the newest row of that store (1 = newest). Deliberately relative:
|
|
28
|
-
* a state change has no identity a host could know in advance — `id` is a
|
|
29
|
-
* timestamp plus a per-process counter whose value depends on how many
|
|
30
|
-
* changes the app happened to make before the test looked. What a test CAN
|
|
31
|
-
* know is "the set I just fired is the newest change to that store". Read and
|
|
32
|
-
* tap in one beat: a row's ordinal moves the instant another change lands.
|
|
33
|
-
*
|
|
34
|
-
* **A store row's handle does NOT.** A store is registered once under a name
|
|
35
|
-
* that is unique by construction (`zustandStateStore.registerStore` returns
|
|
36
|
-
* early on a name it already holds), so `buoy-zustand-store-<slug(name)>` is
|
|
37
|
-
* as stable as the store itself — and unlike a change, it is a thing the host
|
|
38
|
-
* knows the name of before the app has booted.
|
|
39
|
-
*
|
|
40
|
-
* The suite mirrors these rules in CommonJS (`example-dev-build/utils/
|
|
41
|
-
* zustandBench.js`) and asserts the two agree — Detox runs in Node on the host
|
|
42
|
-
* and cannot import TypeScript.
|
|
43
|
-
*/
|
|
44
|
-
|
|
45
|
-
/** Prefix every handle in this tool shares. */
|
|
46
|
-
const ZUSTAND_TEST_ID = exports.ZUSTAND_TEST_ID = "buoy-zustand";
|
|
47
|
-
|
|
48
|
-
/** `zbenchWatch` → `zbenchwatch`. Shared with every Buoy tool. */
|
|
49
|
-
function slugForTestID(value) {
|
|
50
|
-
return String(value ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "unknown";
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Handle for one change row: `buoy-zustand-row-zbenchwatch-1`.
|
|
55
|
-
*
|
|
56
|
-
* `ordinal` is 1-based and counts back from the newest row of this store.
|
|
57
|
-
*/
|
|
58
|
-
function changeRowTestID(storeName, ordinal) {
|
|
59
|
-
return `${ZUSTAND_TEST_ID}-row-${slugForTestID(storeName)}-${ordinal}`;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** Handle for one store row in the browser: `buoy-zustand-store-zbenchwatch`. */
|
|
63
|
-
function storeRowTestID(storeName) {
|
|
64
|
-
return `${ZUSTAND_TEST_ID}-store-${slugForTestID(storeName)}`;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Row handle per change id for one rendered list, newest-first.
|
|
69
|
-
*
|
|
70
|
-
* Built once for the whole list rather than per row, because an ordinal is a
|
|
71
|
-
* fact about the list and a row cannot compute its own.
|
|
72
|
-
*/
|
|
73
|
-
function buildChangeRowTestIDs(changes) {
|
|
74
|
-
const seen = new Map();
|
|
75
|
-
const handles = new Map();
|
|
76
|
-
for (const change of changes) {
|
|
77
|
-
const ordinal = (seen.get(change.storeName) ?? 0) + 1;
|
|
78
|
-
seen.set(change.storeName, ordinal);
|
|
79
|
-
handles.set(change.id, changeRowTestID(change.storeName, ordinal));
|
|
80
|
-
}
|
|
81
|
-
return handles;
|
|
82
|
-
}
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.ZUSTAND_TEST_ID=void 0,exports.buildChangeRowTestIDs=buildChangeRowTestIDs,exports.changeRowTestID=changeRowTestID,exports.slugForTestID=slugForTestID,exports.storeRowTestID=storeRowTestID;const ZUSTAND_TEST_ID=exports.ZUSTAND_TEST_ID="buoy-zustand";function slugForTestID(e){return String(e??"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"unknown"}function changeRowTestID(e,t){return`${ZUSTAND_TEST_ID}-row-${slugForTestID(e)}-${t}`}function storeRowTestID(e){return`${ZUSTAND_TEST_ID}-store-${slugForTestID(e)}`}function buildChangeRowTestIDs(e){const t=new Map,s=new Map;for(const o of e){const e=(t.get(o.storeName)??0)+1;t.set(o.storeName,e),s.set(o.id,changeRowTestID(o.storeName,e))}return s}
|
|
@@ -1,375 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.zustandStateStore = void 0;
|
|
7
|
-
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
8
|
-
/**
|
|
9
|
-
* Zustand state store - captures and stores Zustand state changes
|
|
10
|
-
*
|
|
11
|
-
* The generic event-log half (ring buffer, listeners, clear-forwarding for
|
|
12
|
-
* the desktop mirror, pause/suppress flags, hydrate-by-id) is the shared
|
|
13
|
-
* `ChangeLog`; this file owns the zustand-specific half — the store registry,
|
|
14
|
-
* snapshots, mirror rebuild, filters and stats.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
// ============================================
|
|
18
|
-
// Store Color Palette
|
|
19
|
-
// ============================================
|
|
20
|
-
|
|
21
|
-
const STORE_COLORS = {
|
|
22
|
-
counter: "#10B981",
|
|
23
|
-
// emerald
|
|
24
|
-
auth: "#8B5CF6",
|
|
25
|
-
// purple
|
|
26
|
-
user: "#3B82F6",
|
|
27
|
-
// blue
|
|
28
|
-
cart: "#EC4899",
|
|
29
|
-
// pink
|
|
30
|
-
app: "#6366F1",
|
|
31
|
-
// indigo
|
|
32
|
-
ui: "#F59E0B",
|
|
33
|
-
// amber
|
|
34
|
-
settings: "#14B8A6",
|
|
35
|
-
// teal
|
|
36
|
-
theme: "#06B6D4",
|
|
37
|
-
// cyan
|
|
38
|
-
navigation: "#F97316",
|
|
39
|
-
// orange
|
|
40
|
-
form: "#EF4444" // red
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Get consistent color for a store based on its name
|
|
45
|
-
*/
|
|
46
|
-
function getStoreColor(storeName) {
|
|
47
|
-
return (0, _sharedUi.entityColor)(storeName, STORE_COLORS);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Format payload preview for display
|
|
52
|
-
*/
|
|
53
|
-
function formatPartialPreview(partial, maxLength = 40) {
|
|
54
|
-
return (0, _sharedUi.formatValuePreview)(partial, {
|
|
55
|
-
maxLength,
|
|
56
|
-
undefinedLabel: "",
|
|
57
|
-
functionLabel: "(updater fn)"
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ============================================
|
|
62
|
-
// Zustand State Store
|
|
63
|
-
// ============================================
|
|
64
|
-
|
|
65
|
-
class ZustandStateStore extends _sharedUi.ChangeLog {
|
|
66
|
-
stores = new Map();
|
|
67
|
-
storeListeners = new Set();
|
|
68
|
-
remoteSetStateHandler = null;
|
|
69
|
-
|
|
70
|
-
// ---- State Change Tracking ----
|
|
71
|
-
|
|
72
|
-
addStateChange(params) {
|
|
73
|
-
if (!this.canRecord()) return;
|
|
74
|
-
const {
|
|
75
|
-
storeName,
|
|
76
|
-
partial,
|
|
77
|
-
replace,
|
|
78
|
-
prevState,
|
|
79
|
-
nextState,
|
|
80
|
-
duration,
|
|
81
|
-
category,
|
|
82
|
-
isPersisted = false
|
|
83
|
-
} = params;
|
|
84
|
-
const hasStateChange = prevState !== nextState;
|
|
85
|
-
// `changedCount` counts the keys in `changedKeys` and nothing else, so the
|
|
86
|
-
// row's "N keys changed" and the detail page's CHANGED KEYS card agree —
|
|
87
|
-
// including the persist-rehydrate case where the object is replaced
|
|
88
|
-
// without any top-level value moving.
|
|
89
|
-
const {
|
|
90
|
-
summary,
|
|
91
|
-
changedKeys,
|
|
92
|
-
changedCount
|
|
93
|
-
} = (0, _sharedUi.shallowDiffSummary)(prevState, nextState);
|
|
94
|
-
const partialPreview = formatPartialPreview(partial);
|
|
95
|
-
const isSlowUpdate = (duration ?? 0) > 16;
|
|
96
|
-
const resolvedCategory = category ?? (replace ? "replace" : "setState");
|
|
97
|
-
const stateChange = {
|
|
98
|
-
id: this.nextId(),
|
|
99
|
-
storeName,
|
|
100
|
-
timestamp: Date.now(),
|
|
101
|
-
prevState,
|
|
102
|
-
nextState,
|
|
103
|
-
partial,
|
|
104
|
-
duration,
|
|
105
|
-
hasStateChange,
|
|
106
|
-
replace,
|
|
107
|
-
category: resolvedCategory,
|
|
108
|
-
changedKeys,
|
|
109
|
-
changedKeysCount: changedCount,
|
|
110
|
-
diffSummary: summary,
|
|
111
|
-
partialPreview,
|
|
112
|
-
isSlowUpdate,
|
|
113
|
-
isPersisted
|
|
114
|
-
};
|
|
115
|
-
this.push(stateChange);
|
|
116
|
-
|
|
117
|
-
// Update store change count
|
|
118
|
-
const storeInfo = this.stores.get(storeName);
|
|
119
|
-
if (storeInfo) {
|
|
120
|
-
storeInfo.stateChangeCount++;
|
|
121
|
-
}
|
|
122
|
-
this.notifyListeners();
|
|
123
|
-
}
|
|
124
|
-
getStateChanges() {
|
|
125
|
-
return this.getAll();
|
|
126
|
-
}
|
|
127
|
-
getStateChangeById(id) {
|
|
128
|
-
return this.getById(id);
|
|
129
|
-
}
|
|
130
|
-
clearStateChanges() {
|
|
131
|
-
// Reset counts
|
|
132
|
-
for (const store of this.stores.values()) {
|
|
133
|
-
store.stateChangeCount = 0;
|
|
134
|
-
}
|
|
135
|
-
this.clear();
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// ---- Remote Mirror Mode ----
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Replace the change log without touching the store registry. Used when a
|
|
142
|
-
* host keeps real registered stores (JUMP / RESET stay wired) but needs to
|
|
143
|
-
* reseed the event list — the docs live tour, or any mirror that already
|
|
144
|
-
* called `registerStore`. Does not fire `onClear`. Respects the enabled
|
|
145
|
-
* flag so the UI's pause button freezes the reseed.
|
|
146
|
-
*/
|
|
147
|
-
replaceStateChanges(changes) {
|
|
148
|
-
if (!this.getEnabled()) return;
|
|
149
|
-
for (const store of this.stores.values()) {
|
|
150
|
-
store.stateChangeCount = 0;
|
|
151
|
-
}
|
|
152
|
-
const kept = changes.slice(0, this.max);
|
|
153
|
-
for (const change of kept) {
|
|
154
|
-
const store = this.stores.get(change.storeName);
|
|
155
|
-
if (store) store.stateChangeCount++;
|
|
156
|
-
}
|
|
157
|
-
this.setItems(kept);
|
|
158
|
-
this.notifyStoreListeners();
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Serializable snapshot of the tracked stores (registry metadata plus each
|
|
163
|
-
* store's current state). Used by the sync adapter on the device side —
|
|
164
|
-
* the live `api` handles can't go over the wire.
|
|
165
|
-
*/
|
|
166
|
-
getStoreSnapshots() {
|
|
167
|
-
return Array.from(this.stores.values()).map(store => {
|
|
168
|
-
let currentState;
|
|
169
|
-
try {
|
|
170
|
-
currentState = store.api.getState();
|
|
171
|
-
} catch {
|
|
172
|
-
currentState = undefined;
|
|
173
|
-
}
|
|
174
|
-
return {
|
|
175
|
-
name: store.name,
|
|
176
|
-
stateChangeCount: store.stateChangeCount,
|
|
177
|
-
isPersisted: store.isPersisted,
|
|
178
|
-
persistName: store.persistName,
|
|
179
|
-
// Persisted is not the same as re-readable: a store registered before
|
|
180
|
-
// its persist middleware attached has a name but no handle. The
|
|
181
|
-
// Storage tool needs the difference to decide between offering "apply"
|
|
182
|
-
// and telling the user to restart.
|
|
183
|
-
canRehydrate: typeof store.rehydrate === "function",
|
|
184
|
-
color: store.color,
|
|
185
|
-
currentState
|
|
186
|
-
};
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Handler invoked when the UI calls setState on a mirrored store (e.g.
|
|
192
|
-
* time-travel from the dashboard). Forward it to the synced device.
|
|
193
|
-
*/
|
|
194
|
-
setRemoteSetStateHandler(handler) {
|
|
195
|
-
this.remoteSetStateHandler = handler;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Replace the entire mirror contents from a synced device snapshot. The
|
|
200
|
-
* rebuilt registry entries get stub `api` handles: getState() serves the
|
|
201
|
-
* snapshotted state, setState() forwards to the remote handler. Respects
|
|
202
|
-
* the enabled flag so the UI's pause button freezes the mirror.
|
|
203
|
-
*/
|
|
204
|
-
replaceFromSnapshot(changes, storeSnapshots) {
|
|
205
|
-
if (!this.getEnabled()) return;
|
|
206
|
-
|
|
207
|
-
// Preserve trees a remote mirror already hydrated — wire-form snapshots
|
|
208
|
-
// always carry markers, and a later snapshot must not blank the detail pane.
|
|
209
|
-
const hydrated = new Map();
|
|
210
|
-
for (const existing of this.items) {
|
|
211
|
-
const prev = existing.prevState;
|
|
212
|
-
if (!prev || prev.__buoyStateOnDevice !== true) {
|
|
213
|
-
hydrated.set(existing.id, existing);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
const merged = changes.slice(0, this.max).map(change => {
|
|
217
|
-
const kept = hydrated.get(change.id);
|
|
218
|
-
const incoming = change.prevState;
|
|
219
|
-
return kept && incoming?.__buoyStateOnDevice === true ? {
|
|
220
|
-
...change,
|
|
221
|
-
prevState: kept.prevState,
|
|
222
|
-
nextState: kept.nextState,
|
|
223
|
-
partial: kept.partial
|
|
224
|
-
} : change;
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
// Preserve currentState a remote mirror already hydrated — oversized
|
|
228
|
-
// stores arrive as STATE_ON_DEVICE markers on every snapshot.
|
|
229
|
-
const hydratedStores = new Map();
|
|
230
|
-
for (const [name, store] of this.stores) {
|
|
231
|
-
try {
|
|
232
|
-
const state = store.api.getState();
|
|
233
|
-
const marker = state;
|
|
234
|
-
if (!marker || marker.__buoyStateOnDevice !== true) {
|
|
235
|
-
hydratedStores.set(name, state);
|
|
236
|
-
}
|
|
237
|
-
} catch {
|
|
238
|
-
// ignore
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
this.stores = new Map(storeSnapshots.map(snapshot => {
|
|
242
|
-
const incoming = snapshot.currentState;
|
|
243
|
-
const kept = hydratedStores.get(snapshot.name);
|
|
244
|
-
const currentState = kept !== undefined && incoming?.__buoyStateOnDevice === true ? kept : snapshot.currentState;
|
|
245
|
-
return [snapshot.name, {
|
|
246
|
-
name: snapshot.name,
|
|
247
|
-
api: {
|
|
248
|
-
getState: () => currentState,
|
|
249
|
-
setState: (partial, replace) => {
|
|
250
|
-
this.remoteSetStateHandler?.(snapshot.name, partial, replace);
|
|
251
|
-
},
|
|
252
|
-
subscribe: () => () => {}
|
|
253
|
-
},
|
|
254
|
-
stateChangeCount: snapshot.stateChangeCount,
|
|
255
|
-
isPersisted: snapshot.isPersisted,
|
|
256
|
-
persistName: snapshot.persistName,
|
|
257
|
-
color: snapshot.color
|
|
258
|
-
}];
|
|
259
|
-
}));
|
|
260
|
-
this.setItems(merged);
|
|
261
|
-
this.notifyStoreListeners();
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Hydrate one mirrored change with real trees fetched on demand from the
|
|
266
|
-
* device via the adapter's `getChangeDetail`. Remote-mirror (desktop) only.
|
|
267
|
-
*/
|
|
268
|
-
hydrateChange(id, patch) {
|
|
269
|
-
this.hydrateById(id, patch);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Hydrate one mirrored store's currentState fetched on demand from the
|
|
274
|
-
* device via the adapter's `getStoreState`. Remote-mirror (desktop) only.
|
|
275
|
-
*/
|
|
276
|
-
hydrateStoreState(storeName, currentState) {
|
|
277
|
-
const store = this.stores.get(storeName);
|
|
278
|
-
if (!store) return;
|
|
279
|
-
this.stores.set(storeName, {
|
|
280
|
-
...store,
|
|
281
|
-
api: {
|
|
282
|
-
...store.api,
|
|
283
|
-
getState: () => currentState
|
|
284
|
-
}
|
|
285
|
-
});
|
|
286
|
-
this.notifyStoreListeners();
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// ---- Store Registry ----
|
|
290
|
-
|
|
291
|
-
registerStore(name, api, options) {
|
|
292
|
-
if (this.stores.has(name)) return;
|
|
293
|
-
const storeInfo = {
|
|
294
|
-
name,
|
|
295
|
-
api,
|
|
296
|
-
stateChangeCount: 0,
|
|
297
|
-
isPersisted: options?.isPersisted ?? false,
|
|
298
|
-
persistName: options?.persistName,
|
|
299
|
-
rehydrate: options?.rehydrate,
|
|
300
|
-
color: getStoreColor(name)
|
|
301
|
-
};
|
|
302
|
-
this.stores.set(name, storeInfo);
|
|
303
|
-
this.notifyStoreListeners();
|
|
304
|
-
}
|
|
305
|
-
unregisterStore(name) {
|
|
306
|
-
this.stores.delete(name);
|
|
307
|
-
this.notifyStoreListeners();
|
|
308
|
-
}
|
|
309
|
-
getStores() {
|
|
310
|
-
return Array.from(this.stores.values());
|
|
311
|
-
}
|
|
312
|
-
getStore(name) {
|
|
313
|
-
return this.stores.get(name);
|
|
314
|
-
}
|
|
315
|
-
getStoreColor(name) {
|
|
316
|
-
return this.stores.get(name)?.color ?? getStoreColor(name);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// ---- Filtering ----
|
|
320
|
-
|
|
321
|
-
filterStateChanges(filter) {
|
|
322
|
-
let filtered = this.getAll();
|
|
323
|
-
if (filter.searchText) {
|
|
324
|
-
const search = filter.searchText.toLowerCase();
|
|
325
|
-
filtered = filtered.filter(c => c.storeName.toLowerCase().includes(search) || c.partialPreview.toLowerCase().includes(search) || c.changedKeys.some(k => k.toLowerCase().includes(search)));
|
|
326
|
-
}
|
|
327
|
-
if (filter.storeNames && filter.storeNames.length > 0) {
|
|
328
|
-
filtered = filtered.filter(c => filter.storeNames.includes(c.storeName));
|
|
329
|
-
}
|
|
330
|
-
if (filter.onlyWithChanges) {
|
|
331
|
-
filtered = filtered.filter(c => c.hasStateChange);
|
|
332
|
-
}
|
|
333
|
-
return filtered;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// ---- Stats ----
|
|
337
|
-
|
|
338
|
-
getStats() {
|
|
339
|
-
const total = this.items.length;
|
|
340
|
-
const withChanges = this.items.filter(c => c.hasStateChange).length;
|
|
341
|
-
const storeCount = this.stores.size;
|
|
342
|
-
const durations = this.items.filter(c => c.duration !== undefined).map(c => c.duration);
|
|
343
|
-
const avgDuration = durations.length > 0 ? durations.reduce((sum, d) => sum + d, 0) / durations.length : 0;
|
|
344
|
-
return {
|
|
345
|
-
totalChanges: total,
|
|
346
|
-
changesWithStateChange: withChanges,
|
|
347
|
-
changesWithoutStateChange: total - withChanges,
|
|
348
|
-
storeCount,
|
|
349
|
-
averageDuration: Math.round(avgDuration * 100) / 100
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
getUniqueStoreNames() {
|
|
353
|
-
return Array.from(this.stores.keys()).sort();
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// ---- Enable / Disable ----
|
|
357
|
-
|
|
358
|
-
setMaxChanges(max) {
|
|
359
|
-
this.setMax(max);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// ---- Subscriptions ----
|
|
363
|
-
|
|
364
|
-
subscribeToStores(listener) {
|
|
365
|
-
this.storeListeners.add(listener);
|
|
366
|
-
return () => {
|
|
367
|
-
this.storeListeners.delete(listener);
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
notifyStoreListeners() {
|
|
371
|
-
const stores = this.getStores();
|
|
372
|
-
this.storeListeners.forEach(listener => listener(stores));
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
const zustandStateStore = exports.zustandStateStore = new ZustandStateStore();
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.zustandStateStore=void 0;var _sharedUi=require("@buoy-gg/shared-ui");const STORE_COLORS={counter:"#10B981",auth:"#8B5CF6",user:"#3B82F6",cart:"#EC4899",app:"#6366F1",ui:"#F59E0B",settings:"#14B8A6",theme:"#06B6D4",navigation:"#F97316",form:"#EF4444"};function getStoreColor(t){return(0,_sharedUi.entityColor)(t,STORE_COLORS)}function formatPartialPreview(t,e=40){return(0,_sharedUi.formatValuePreview)(t,{maxLength:e,undefinedLabel:"",functionLabel:"(updater fn)"})}class ZustandStateStore extends _sharedUi.ChangeLog{stores=new Map;storeListeners=new Set;remoteSetStateHandler=null;addStateChange(t){if(!this.canRecord())return;const{storeName:e,partial:s,replace:r,prevState:a,nextState:o,duration:n,category:i,isPersisted:h=!1}=t,u=a!==o,{summary:S,changedKeys:c,changedCount:g}=(0,_sharedUi.shallowDiffSummary)(a,o),l=formatPartialPreview(s),d=(n??0)>16,m=i??(r?"replace":"setState"),p={id:this.nextId(),storeName:e,timestamp:Date.now(),prevState:a,nextState:o,partial:s,duration:n,hasStateChange:u,replace:r,category:m,changedKeys:c,changedKeysCount:g,diffSummary:S,partialPreview:l,isSlowUpdate:d,isPersisted:h};this.push(p);const C=this.stores.get(e);C&&C.stateChangeCount++,this.notifyListeners()}getStateChanges(){return this.getAll()}getStateChangeById(t){return this.getById(t)}clearStateChanges(){for(const t of this.stores.values())t.stateChangeCount=0;this.clear(),this.notifyStoreListeners()}replaceStateChanges(t){if(!this.getEnabled())return;for(const t of this.stores.values())t.stateChangeCount=0;const e=t.slice(0,this.max);for(const t of e){const e=this.stores.get(t.storeName);e&&e.stateChangeCount++}this.setItems(e),this.notifyStoreListeners()}getStoreSnapshots(){return Array.from(this.stores.values()).map(t=>{let e;try{e=t.api.getState()}catch{e=void 0}return{name:t.name,stateChangeCount:t.stateChangeCount,isPersisted:t.isPersisted,persistName:t.persistName,canRehydrate:"function"==typeof t.rehydrate,color:t.color,currentState:e}})}setRemoteSetStateHandler(t){this.remoteSetStateHandler=t}replaceFromSnapshot(t,e){if(!this.getEnabled())return;const s=new Map;for(const t of this.items){const e=t.prevState;e&&!0===e.__buoyStateOnDevice||s.set(t.id,t)}const r=t.slice(0,this.max).map(t=>{const e=s.get(t.id),r=t.prevState;return e&&!0===r?.__buoyStateOnDevice?{...t,prevState:e.prevState,nextState:e.nextState,partial:e.partial}:t}),a=new Map;for(const[t,e]of this.stores)try{const s=e.api.getState();s&&!0===s.__buoyStateOnDevice||a.set(t,s)}catch{}this.stores=new Map(e.map(t=>{const e=t.currentState,s=a.get(t.name),r=void 0!==s&&!0===e?.__buoyStateOnDevice?s:t.currentState;return[t.name,{name:t.name,api:{getState:()=>r,setState:(e,s)=>{this.remoteSetStateHandler?.(t.name,e,s)},subscribe:()=>()=>{}},stateChangeCount:t.stateChangeCount,isPersisted:t.isPersisted,persistName:t.persistName,color:t.color}]})),this.setItems(r),this.notifyStoreListeners()}hydrateChange(t,e){this.hydrateById(t,e)}hydrateStoreState(t,e){const s=this.stores.get(t);s&&(this.stores.set(t,{...s,api:{...s.api,getState:()=>e}}),this.notifyStoreListeners())}registerStore(t,e,s){if(this.stores.has(t))return;const r={name:t,api:e,stateChangeCount:0,isPersisted:s?.isPersisted??!1,persistName:s?.persistName,rehydrate:s?.rehydrate,color:getStoreColor(t)};this.stores.set(t,r),this.notifyStoreListeners()}unregisterStore(t){this.stores.delete(t),this.notifyStoreListeners()}getStores(){return Array.from(this.stores.values())}getStore(t){return this.stores.get(t)}getStoreColor(t){return this.stores.get(t)?.color??getStoreColor(t)}filterStateChanges(t){let e=this.getAll();if(t.searchText){const s=t.searchText.toLowerCase();e=e.filter(t=>t.storeName.toLowerCase().includes(s)||t.partialPreview.toLowerCase().includes(s)||t.changedKeys.some(t=>t.toLowerCase().includes(s)))}return t.storeNames&&t.storeNames.length>0&&(e=e.filter(e=>t.storeNames.includes(e.storeName))),t.onlyWithChanges&&(e=e.filter(t=>t.hasStateChange)),e}getStats(){const t=this.items.length,e=this.items.filter(t=>t.hasStateChange).length,s=this.stores.size,r=this.items.filter(t=>void 0!==t.duration).map(t=>t.duration),a=r.length>0?r.reduce((t,e)=>t+e,0)/r.length:0;return{totalChanges:t,changesWithStateChange:e,changesWithoutStateChange:t-e,storeCount:s,averageDuration:Math.round(100*a)/100}}getUniqueStoreNames(){return Array.from(this.stores.keys()).sort()}setMaxChanges(t){this.setMax(t)}subscribeToStores(t){return this.storeListeners.add(t),()=>{this.storeListeners.delete(t)}}notifyStoreListeners(){const t=this.getStores();this.storeListeners.forEach(e=>e(t))}}const zustandStateStore=exports.zustandStateStore=new ZustandStateStore;
|