@buoy-gg/zustand 7.0.15 → 7.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/index.js +1 -0
- package/lib/commonjs/zustand/utils/snapshotProvider.js +171 -0
- package/lib/module/index.js +3 -0
- package/lib/module/zustand/utils/snapshotProvider.js +170 -0
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/zustand/utils/snapshotProvider.d.ts +2 -0
- package/lib/typescript/zustand/utils/snapshotProvider.d.ts.map +1 -0
- package/package.json +7 -4
package/lib/commonjs/index.js
CHANGED
|
@@ -93,6 +93,7 @@ Object.defineProperty(exports, "zustandToolPreset", {
|
|
|
93
93
|
return _preset.zustandToolPreset;
|
|
94
94
|
}
|
|
95
95
|
});
|
|
96
|
+
require("./zustand/utils/snapshotProvider");
|
|
96
97
|
var _preset = require("./preset");
|
|
97
98
|
var _buoyZustandMiddleware = require("./zustand/utils/buoyZustandMiddleware");
|
|
98
99
|
var _useZustandStateChanges = require("./zustand/hooks/useZustandStateChanges");
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
4
|
+
var _zustandStateStore = require("./zustandStateStore");
|
|
5
|
+
/**
|
|
6
|
+
* Time Machine snapshot provider for Zustand.
|
|
7
|
+
*
|
|
8
|
+
* Registered into @buoy-gg/shared-ui's stateSnapshotRegistry at module init;
|
|
9
|
+
* consumed by @buoy-gg/time-machine. Neither package imports the other.
|
|
10
|
+
*
|
|
11
|
+
* The Zustand-specific hazard: stores conventionally keep their action
|
|
12
|
+
* functions IN state (`increment: () => ...`). Functions don't survive
|
|
13
|
+
* serialization, and a full `setState(snapshot, true)` replace would wipe
|
|
14
|
+
* them, breaking every `useStore(s => s.increment)` call site. So capture
|
|
15
|
+
* strips functions (recording where they were), and restore re-grafts the
|
|
16
|
+
* LIVE store's functions onto the snapshot before replacing.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const MAX_DEPTH = 8;
|
|
20
|
+
function isPlainObjectLike(v) {
|
|
21
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Clone `value` without functions, recording their paths. */
|
|
25
|
+
function stripFunctions(value, path, fnPaths, seen, depth) {
|
|
26
|
+
if (typeof value === "function") {
|
|
27
|
+
fnPaths.push(path);
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
if (typeof value !== "object" || value === null || depth >= MAX_DEPTH) {
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
if (seen.has(value)) return undefined; // cycle — drop the back-reference
|
|
34
|
+
seen.add(value);
|
|
35
|
+
if (Array.isArray(value)) {
|
|
36
|
+
return value.map((item, i) => typeof item === "function" ? (fnPaths.push(`${path}[${i}]`), null) : stripFunctions(item, `${path}[${i}]`, fnPaths, seen, depth + 1));
|
|
37
|
+
}
|
|
38
|
+
if (!isPlainObjectLike(value)) return value; // Date/Map/Set/etc. — superjson handles
|
|
39
|
+
const out = {};
|
|
40
|
+
for (const [k, v] of Object.entries(value)) {
|
|
41
|
+
if (typeof v === "function") {
|
|
42
|
+
fnPaths.push(path ? `${path}.${k}` : k);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
out[k] = stripFunctions(v, path ? `${path}.${k}` : k, fnPaths, seen, depth + 1);
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Copy every function-valued property from the LIVE state onto the restored
|
|
52
|
+
* snapshot (creating containers as needed), so store actions keep working
|
|
53
|
+
* after a full replace.
|
|
54
|
+
*/
|
|
55
|
+
function graftFunctions(target, live, seen, depth) {
|
|
56
|
+
if (!isPlainObjectLike(live) || !isPlainObjectLike(target)) return;
|
|
57
|
+
if (depth >= MAX_DEPTH || seen.has(live)) return;
|
|
58
|
+
seen.add(live);
|
|
59
|
+
for (const [k, v] of Object.entries(live)) {
|
|
60
|
+
if (typeof v === "function") {
|
|
61
|
+
target[k] = v;
|
|
62
|
+
} else if (isPlainObjectLike(v)) {
|
|
63
|
+
if (!isPlainObjectLike(target[k])) {
|
|
64
|
+
// Branch missing from the snapshot — only recreate it if it holds
|
|
65
|
+
// functions somewhere; otherwise the snapshot's absence wins.
|
|
66
|
+
if (!containsFunction(v, new WeakSet(), depth + 1)) continue;
|
|
67
|
+
target[k] = {};
|
|
68
|
+
}
|
|
69
|
+
graftFunctions(target[k], v, seen, depth + 1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function containsFunction(value, seen, depth) {
|
|
74
|
+
if (depth >= MAX_DEPTH || seen.has(value)) return false;
|
|
75
|
+
seen.add(value);
|
|
76
|
+
for (const v of Object.values(value)) {
|
|
77
|
+
if (typeof v === "function") return true;
|
|
78
|
+
if (isPlainObjectLike(v) && containsFunction(v, seen, depth + 1)) return true;
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
function status() {
|
|
83
|
+
const count = _zustandStateStore.zustandStateStore.getStores().length;
|
|
84
|
+
if (count === 0) {
|
|
85
|
+
return {
|
|
86
|
+
canCapture: false,
|
|
87
|
+
canRestore: false,
|
|
88
|
+
detail: "No Zustand stores registered — pass `zustandStores` to FloatingDevTools or add the buoyDevTools middleware"
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
canCapture: true,
|
|
93
|
+
canRestore: true
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
async function capture() {
|
|
97
|
+
const warnings = [];
|
|
98
|
+
const stores = {};
|
|
99
|
+
for (const info of _zustandStateStore.zustandStateStore.getStores()) {
|
|
100
|
+
const fnPaths = [];
|
|
101
|
+
let state;
|
|
102
|
+
try {
|
|
103
|
+
state = stripFunctions(info.api.getState(), "", fnPaths, new WeakSet(), 0);
|
|
104
|
+
} catch (e) {
|
|
105
|
+
warnings.push(`store "${info.name}": capture failed (${String(e)})`);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
stores[info.name] = {
|
|
109
|
+
state,
|
|
110
|
+
fnPaths
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
data: {
|
|
115
|
+
stores
|
|
116
|
+
},
|
|
117
|
+
warnings
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
async function restore(data) {
|
|
121
|
+
const snapshot = data;
|
|
122
|
+
const result = {
|
|
123
|
+
ok: true,
|
|
124
|
+
applied: 0,
|
|
125
|
+
skipped: [],
|
|
126
|
+
warnings: []
|
|
127
|
+
};
|
|
128
|
+
if (!snapshot?.stores) {
|
|
129
|
+
return {
|
|
130
|
+
ok: false,
|
|
131
|
+
applied: 0,
|
|
132
|
+
skipped: [],
|
|
133
|
+
warnings: ["empty zustand snapshot"]
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
for (const [name, envelope] of Object.entries(snapshot.stores)) {
|
|
137
|
+
const info = _zustandStateStore.zustandStateStore.getStore(name);
|
|
138
|
+
if (!info) {
|
|
139
|
+
result.skipped.push({
|
|
140
|
+
id: name,
|
|
141
|
+
reason: "store not registered (renamed, or created after snapshot?)"
|
|
142
|
+
});
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
const next = envelope.state;
|
|
147
|
+
graftFunctions(next, info.api.getState(), new WeakSet(), 0);
|
|
148
|
+
info.api.setState(next, true);
|
|
149
|
+
if (info.api.getState() === next) {
|
|
150
|
+
result.applied += 1;
|
|
151
|
+
} else {
|
|
152
|
+
result.warnings.push(`store "${name}": setState did not take effect`);
|
|
153
|
+
result.ok = false;
|
|
154
|
+
}
|
|
155
|
+
} catch (e) {
|
|
156
|
+
result.skipped.push({
|
|
157
|
+
id: name,
|
|
158
|
+
reason: String(e)
|
|
159
|
+
});
|
|
160
|
+
result.ok = false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
(0, _sharedUi.registerStateSnapshotProvider)({
|
|
166
|
+
id: "zustand",
|
|
167
|
+
label: "Zustand",
|
|
168
|
+
status,
|
|
169
|
+
capture,
|
|
170
|
+
restore
|
|
171
|
+
});
|
package/lib/module/index.js
CHANGED
|
@@ -37,6 +37,9 @@
|
|
|
37
37
|
* ```
|
|
38
38
|
*/
|
|
39
39
|
|
|
40
|
+
// Side-effect: registers the Time Machine snapshot provider
|
|
41
|
+
import "./zustand/utils/snapshotProvider";
|
|
42
|
+
|
|
40
43
|
// =============================================================================
|
|
41
44
|
// PRESET (Primary entry point for FloatingDevTools)
|
|
42
45
|
// =============================================================================
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Time Machine snapshot provider for Zustand.
|
|
5
|
+
*
|
|
6
|
+
* Registered into @buoy-gg/shared-ui's stateSnapshotRegistry at module init;
|
|
7
|
+
* consumed by @buoy-gg/time-machine. Neither package imports the other.
|
|
8
|
+
*
|
|
9
|
+
* The Zustand-specific hazard: stores conventionally keep their action
|
|
10
|
+
* functions IN state (`increment: () => ...`). Functions don't survive
|
|
11
|
+
* serialization, and a full `setState(snapshot, true)` replace would wipe
|
|
12
|
+
* them, breaking every `useStore(s => s.increment)` call site. So capture
|
|
13
|
+
* strips functions (recording where they were), and restore re-grafts the
|
|
14
|
+
* LIVE store's functions onto the snapshot before replacing.
|
|
15
|
+
*/
|
|
16
|
+
import { registerStateSnapshotProvider } from "@buoy-gg/shared-ui";
|
|
17
|
+
import { zustandStateStore } from "./zustandStateStore";
|
|
18
|
+
const MAX_DEPTH = 8;
|
|
19
|
+
function isPlainObjectLike(v) {
|
|
20
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Clone `value` without functions, recording their paths. */
|
|
24
|
+
function stripFunctions(value, path, fnPaths, seen, depth) {
|
|
25
|
+
if (typeof value === "function") {
|
|
26
|
+
fnPaths.push(path);
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
if (typeof value !== "object" || value === null || depth >= MAX_DEPTH) {
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
if (seen.has(value)) return undefined; // cycle — drop the back-reference
|
|
33
|
+
seen.add(value);
|
|
34
|
+
if (Array.isArray(value)) {
|
|
35
|
+
return value.map((item, i) => typeof item === "function" ? (fnPaths.push(`${path}[${i}]`), null) : stripFunctions(item, `${path}[${i}]`, fnPaths, seen, depth + 1));
|
|
36
|
+
}
|
|
37
|
+
if (!isPlainObjectLike(value)) return value; // Date/Map/Set/etc. — superjson handles
|
|
38
|
+
const out = {};
|
|
39
|
+
for (const [k, v] of Object.entries(value)) {
|
|
40
|
+
if (typeof v === "function") {
|
|
41
|
+
fnPaths.push(path ? `${path}.${k}` : k);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
out[k] = stripFunctions(v, path ? `${path}.${k}` : k, fnPaths, seen, depth + 1);
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Copy every function-valued property from the LIVE state onto the restored
|
|
51
|
+
* snapshot (creating containers as needed), so store actions keep working
|
|
52
|
+
* after a full replace.
|
|
53
|
+
*/
|
|
54
|
+
function graftFunctions(target, live, seen, depth) {
|
|
55
|
+
if (!isPlainObjectLike(live) || !isPlainObjectLike(target)) return;
|
|
56
|
+
if (depth >= MAX_DEPTH || seen.has(live)) return;
|
|
57
|
+
seen.add(live);
|
|
58
|
+
for (const [k, v] of Object.entries(live)) {
|
|
59
|
+
if (typeof v === "function") {
|
|
60
|
+
target[k] = v;
|
|
61
|
+
} else if (isPlainObjectLike(v)) {
|
|
62
|
+
if (!isPlainObjectLike(target[k])) {
|
|
63
|
+
// Branch missing from the snapshot — only recreate it if it holds
|
|
64
|
+
// functions somewhere; otherwise the snapshot's absence wins.
|
|
65
|
+
if (!containsFunction(v, new WeakSet(), depth + 1)) continue;
|
|
66
|
+
target[k] = {};
|
|
67
|
+
}
|
|
68
|
+
graftFunctions(target[k], v, seen, depth + 1);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function containsFunction(value, seen, depth) {
|
|
73
|
+
if (depth >= MAX_DEPTH || seen.has(value)) return false;
|
|
74
|
+
seen.add(value);
|
|
75
|
+
for (const v of Object.values(value)) {
|
|
76
|
+
if (typeof v === "function") return true;
|
|
77
|
+
if (isPlainObjectLike(v) && containsFunction(v, seen, depth + 1)) return true;
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
function status() {
|
|
82
|
+
const count = zustandStateStore.getStores().length;
|
|
83
|
+
if (count === 0) {
|
|
84
|
+
return {
|
|
85
|
+
canCapture: false,
|
|
86
|
+
canRestore: false,
|
|
87
|
+
detail: "No Zustand stores registered — pass `zustandStores` to FloatingDevTools or add the buoyDevTools middleware"
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
canCapture: true,
|
|
92
|
+
canRestore: true
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
async function capture() {
|
|
96
|
+
const warnings = [];
|
|
97
|
+
const stores = {};
|
|
98
|
+
for (const info of zustandStateStore.getStores()) {
|
|
99
|
+
const fnPaths = [];
|
|
100
|
+
let state;
|
|
101
|
+
try {
|
|
102
|
+
state = stripFunctions(info.api.getState(), "", fnPaths, new WeakSet(), 0);
|
|
103
|
+
} catch (e) {
|
|
104
|
+
warnings.push(`store "${info.name}": capture failed (${String(e)})`);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
stores[info.name] = {
|
|
108
|
+
state,
|
|
109
|
+
fnPaths
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
data: {
|
|
114
|
+
stores
|
|
115
|
+
},
|
|
116
|
+
warnings
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
async function restore(data) {
|
|
120
|
+
const snapshot = data;
|
|
121
|
+
const result = {
|
|
122
|
+
ok: true,
|
|
123
|
+
applied: 0,
|
|
124
|
+
skipped: [],
|
|
125
|
+
warnings: []
|
|
126
|
+
};
|
|
127
|
+
if (!snapshot?.stores) {
|
|
128
|
+
return {
|
|
129
|
+
ok: false,
|
|
130
|
+
applied: 0,
|
|
131
|
+
skipped: [],
|
|
132
|
+
warnings: ["empty zustand snapshot"]
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
for (const [name, envelope] of Object.entries(snapshot.stores)) {
|
|
136
|
+
const info = zustandStateStore.getStore(name);
|
|
137
|
+
if (!info) {
|
|
138
|
+
result.skipped.push({
|
|
139
|
+
id: name,
|
|
140
|
+
reason: "store not registered (renamed, or created after snapshot?)"
|
|
141
|
+
});
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
const next = envelope.state;
|
|
146
|
+
graftFunctions(next, info.api.getState(), new WeakSet(), 0);
|
|
147
|
+
info.api.setState(next, true);
|
|
148
|
+
if (info.api.getState() === next) {
|
|
149
|
+
result.applied += 1;
|
|
150
|
+
} else {
|
|
151
|
+
result.warnings.push(`store "${name}": setState did not take effect`);
|
|
152
|
+
result.ok = false;
|
|
153
|
+
}
|
|
154
|
+
} catch (e) {
|
|
155
|
+
result.skipped.push({
|
|
156
|
+
id: name,
|
|
157
|
+
reason: String(e)
|
|
158
|
+
});
|
|
159
|
+
result.ok = false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
registerStateSnapshotProvider({
|
|
165
|
+
id: "zustand",
|
|
166
|
+
label: "Zustand",
|
|
167
|
+
status,
|
|
168
|
+
capture,
|
|
169
|
+
restore
|
|
170
|
+
});
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
* );
|
|
35
35
|
* ```
|
|
36
36
|
*/
|
|
37
|
+
import "./zustand/utils/snapshotProvider";
|
|
37
38
|
export { zustandToolPreset, createZustandTool } from "./preset";
|
|
38
39
|
export { watchStores } from "./zustand/utils/buoyZustandMiddleware";
|
|
39
40
|
export { buoyDevTools } from "./zustand/utils/buoyZustandMiddleware";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAGH,OAAO,kCAAkC,CAAC;AAK1C,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAKhE,OAAO,EAAE,WAAW,EAAE,MAAM,uCAAuC,CAAC;AAKpE,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAKrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AAK5E,OAAO,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AAChF,YAAY,EAAE,4BAA4B,EAAE,MAAM,wCAAwC,CAAC;AAO3F,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAClF,OAAO,EAAE,sBAAsB,EAAE,MAAM,6CAA6C,CAAC;AACrF,OAAO,EACL,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,gDAAgD,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAKnF,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAKvE,YAAY,EACV,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,oBAAoB,EACpB,aAAa,EACb,mBAAmB,EACnB,4BAA4B,GAC7B,MAAM,iBAAiB,CAAC;AAKzB,gBAAgB;AAChB,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshotProvider.d.ts","sourceRoot":"","sources":["../../../../src/zustand/utils/snapshotProvider.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buoy-gg/zustand",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.17",
|
|
4
4
|
"description": "Zustand devtools inside your React Native app — state diffs, jump-to-state & one-tap store reset, no middleware. Part of Buoy devtools.",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -24,10 +24,13 @@
|
|
|
24
24
|
"files": [
|
|
25
25
|
"lib"
|
|
26
26
|
],
|
|
27
|
-
"sideEffects":
|
|
27
|
+
"sideEffects": [
|
|
28
|
+
"**/snapshotProvider.js",
|
|
29
|
+
"**/snapshotProvider.ts"
|
|
30
|
+
],
|
|
28
31
|
"dependencies": {
|
|
29
|
-
"@buoy-gg/shared-ui": "^7.0.
|
|
30
|
-
"@buoy-gg/license": "^7.0.
|
|
32
|
+
"@buoy-gg/shared-ui": "^7.0.17",
|
|
33
|
+
"@buoy-gg/license": "^7.0.17"
|
|
31
34
|
},
|
|
32
35
|
"peerDependencies": {
|
|
33
36
|
"react": "*",
|