@buoy-gg/shared-ui 7.0.16 → 7.0.18
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/dataViewer/ThemedSplitView.js +622 -0
- package/lib/commonjs/dataViewer/index.js +32 -0
- package/lib/commonjs/dataViewer/objectDiff.js +304 -0
- package/lib/commonjs/hooks/index.js +7 -0
- package/lib/commonjs/hooks/safe-area-impl.js +1 -1
- package/lib/commonjs/hooks/usePersistedState.js +71 -0
- package/lib/commonjs/icons/index.js +14 -0
- package/lib/commonjs/index.js +61 -0
- package/lib/commonjs/storage/devToolsStorageKeys.js +31 -0
- package/lib/commonjs/sync/wireBudget.js +97 -0
- package/lib/commonjs/ui/components/HeaderSearchButton.js +6 -1
- package/lib/commonjs/ui/components/ModalHeader.js +69 -0
- package/lib/commonjs/ui/components/ToolButton.js +90 -0
- package/lib/commonjs/ui/components/index.js +7 -0
- package/lib/commonjs/ui/index.js +8 -0
- package/lib/commonjs/ui/toolTheme.js +96 -0
- package/lib/commonjs/utils/index.js +38 -0
- package/lib/commonjs/utils/persistentStorage.js +6 -3
- package/lib/commonjs/utils/stateSnapshotRegistry.js +71 -0
- package/lib/module/dataViewer/ThemedSplitView.js +617 -0
- package/lib/module/dataViewer/index.js +7 -1
- package/lib/module/dataViewer/objectDiff.js +297 -0
- package/lib/module/hooks/index.js +1 -0
- package/lib/module/hooks/safe-area-impl.js +1 -1
- package/lib/module/hooks/usePersistedState.js +68 -0
- package/lib/module/icons/index.js +1 -0
- package/lib/module/index.js +6 -1
- package/lib/module/storage/devToolsStorageKeys.js +31 -0
- package/lib/module/sync/wireBudget.js +92 -0
- package/lib/module/ui/components/HeaderSearchButton.js +6 -1
- package/lib/module/ui/components/ModalHeader.js +70 -2
- package/lib/module/ui/components/ToolButton.js +86 -0
- package/lib/module/ui/components/index.js +1 -0
- package/lib/module/ui/index.js +3 -0
- package/lib/module/ui/toolTheme.js +93 -0
- package/lib/module/utils/index.js +2 -0
- package/lib/module/utils/persistentStorage.js +6 -3
- package/lib/module/utils/stateSnapshotRegistry.js +64 -0
- package/lib/typescript/commonjs/dataViewer/ThemedSplitView.d.ts +28 -0
- package/lib/typescript/commonjs/dataViewer/ThemedSplitView.d.ts.map +1 -0
- package/lib/typescript/commonjs/dataViewer/index.d.ts +2 -0
- package/lib/typescript/commonjs/dataViewer/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/dataViewer/objectDiff.d.ts +67 -0
- package/lib/typescript/commonjs/dataViewer/objectDiff.d.ts.map +1 -0
- package/lib/typescript/commonjs/hooks/index.d.ts +1 -0
- package/lib/typescript/commonjs/hooks/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/hooks/safe-area-impl.d.ts +1 -1
- package/lib/typescript/commonjs/hooks/usePersistedState.d.ts +16 -0
- package/lib/typescript/commonjs/hooks/usePersistedState.d.ts.map +1 -0
- package/lib/typescript/commonjs/icons/index.d.ts +1 -0
- package/lib/typescript/commonjs/icons/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +3 -2
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/storage/devToolsStorageKeys.d.ts +28 -0
- package/lib/typescript/commonjs/storage/devToolsStorageKeys.d.ts.map +1 -1
- package/lib/typescript/commonjs/sync/wireBudget.d.ts +50 -0
- package/lib/typescript/commonjs/sync/wireBudget.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/HeaderSearchButton.d.ts +3 -1
- package/lib/typescript/commonjs/ui/components/HeaderSearchButton.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/ModalHeader.d.ts +13 -1
- package/lib/typescript/commonjs/ui/components/ModalHeader.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/ToolButton.d.ts +24 -0
- package/lib/typescript/commonjs/ui/components/ToolButton.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/index.d.ts +4 -0
- package/lib/typescript/commonjs/ui/components/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/index.d.ts +1 -0
- package/lib/typescript/commonjs/ui/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/toolTheme.d.ts +68 -0
- package/lib/typescript/commonjs/ui/toolTheme.d.ts.map +1 -0
- package/lib/typescript/commonjs/utils/index.d.ts +2 -0
- package/lib/typescript/commonjs/utils/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/utils/persistentStorage.d.ts.map +1 -1
- package/lib/typescript/commonjs/utils/stateSnapshotRegistry.d.ts +111 -0
- package/lib/typescript/commonjs/utils/stateSnapshotRegistry.d.ts.map +1 -0
- package/lib/typescript/module/dataViewer/ThemedSplitView.d.ts +28 -0
- package/lib/typescript/module/dataViewer/ThemedSplitView.d.ts.map +1 -0
- package/lib/typescript/module/dataViewer/index.d.ts +2 -0
- package/lib/typescript/module/dataViewer/index.d.ts.map +1 -1
- package/lib/typescript/module/dataViewer/objectDiff.d.ts +67 -0
- package/lib/typescript/module/dataViewer/objectDiff.d.ts.map +1 -0
- package/lib/typescript/module/hooks/index.d.ts +1 -0
- package/lib/typescript/module/hooks/index.d.ts.map +1 -1
- package/lib/typescript/module/hooks/safe-area-impl.d.ts +1 -1
- package/lib/typescript/module/hooks/usePersistedState.d.ts +16 -0
- package/lib/typescript/module/hooks/usePersistedState.d.ts.map +1 -0
- package/lib/typescript/module/icons/index.d.ts +1 -0
- package/lib/typescript/module/icons/index.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +3 -2
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/storage/devToolsStorageKeys.d.ts +28 -0
- package/lib/typescript/module/storage/devToolsStorageKeys.d.ts.map +1 -1
- package/lib/typescript/module/sync/wireBudget.d.ts +50 -0
- package/lib/typescript/module/sync/wireBudget.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/HeaderSearchButton.d.ts +3 -1
- package/lib/typescript/module/ui/components/HeaderSearchButton.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/ModalHeader.d.ts +13 -1
- package/lib/typescript/module/ui/components/ModalHeader.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/ToolButton.d.ts +24 -0
- package/lib/typescript/module/ui/components/ToolButton.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/index.d.ts +4 -0
- package/lib/typescript/module/ui/components/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/index.d.ts +1 -0
- package/lib/typescript/module/ui/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/toolTheme.d.ts +68 -0
- package/lib/typescript/module/ui/toolTheme.d.ts.map +1 -0
- package/lib/typescript/module/utils/index.d.ts +2 -0
- package/lib/typescript/module/utils/index.d.ts.map +1 -1
- package/lib/typescript/module/utils/persistentStorage.d.ts.map +1 -1
- package/lib/typescript/module/utils/stateSnapshotRegistry.d.ts +111 -0
- package/lib/typescript/module/utils/stateSnapshotRegistry.d.ts.map +1 -0
- package/package.json +4 -4
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.boundedDeepEqual = boundedDeepEqual;
|
|
7
|
+
exports.jsonLikelyLargerThan = jsonLikelyLargerThan;
|
|
8
|
+
exports.objectDiff = objectDiff;
|
|
9
|
+
exports.toDiffable = toDiffable;
|
|
10
|
+
/**
|
|
11
|
+
* objectDiff — the bounded, cycle-safe diff KERNEL the dataViewer never had.
|
|
12
|
+
*
|
|
13
|
+
* Every diff consumer so far either hand-rolled a private summary helper or
|
|
14
|
+
* fed whole values into the render-layer viewers (which compute eagerly, have
|
|
15
|
+
* no cycle guards, and mis-compare rich types — TreeDiffViewer treats two
|
|
16
|
+
* different Dates/Maps/Sets as EQUAL because they have zero own keys).
|
|
17
|
+
*
|
|
18
|
+
* This kernel is for VERDICTS and BADGES, not rendering:
|
|
19
|
+
* - correct equality for Date / RegExp / Map / Set / BigInt / NaN /
|
|
20
|
+
* TypedArray / URL / Error, and `undefined`-value vs absent-key
|
|
21
|
+
* - WeakSet cycle guard (a revisited pair reports unequal-safe truncation,
|
|
22
|
+
* never a stack overflow)
|
|
23
|
+
* - hard depth + node budgets with an explicit `truncated` flag; when the
|
|
24
|
+
* budget runs out a subtree is reported as CHANGED (a false "changed" is
|
|
25
|
+
* recoverable — a false "unchanged" hides a real difference, the worst
|
|
26
|
+
* failure mode for a restore preview)
|
|
27
|
+
*
|
|
28
|
+
* `toDiffable` is the companion RENDER normalizer: it converts rich types to
|
|
29
|
+
* plain-JSON stand-ins so the existing viewers (which only handle plain JSON
|
|
30
|
+
* faithfully) never see a Date/Map/Set/BigInt/cycle at all.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
function isPlainObjectLike(v) {
|
|
34
|
+
if (typeof v !== "object" || v === null) return false;
|
|
35
|
+
const proto = Object.getPrototypeOf(v);
|
|
36
|
+
return proto === Object.prototype || proto === null;
|
|
37
|
+
}
|
|
38
|
+
function isTypedArray(v) {
|
|
39
|
+
return ArrayBuffer.isView(v) && !(v instanceof DataView);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Bounded deep equality. Returns false on budget exhaustion (conservative:
|
|
44
|
+
* report changed rather than hide a change) and marks the budget truncated.
|
|
45
|
+
*/
|
|
46
|
+
function boundedDeepEqual(a, b, options) {
|
|
47
|
+
const budget = {
|
|
48
|
+
nodes: options?.maxNodes ?? 2000,
|
|
49
|
+
truncated: false
|
|
50
|
+
};
|
|
51
|
+
return deepEqual(a, b, options?.maxDepth ?? 8, budget, new WeakSet());
|
|
52
|
+
}
|
|
53
|
+
function deepEqual(a, b, depth, budget, seen) {
|
|
54
|
+
if (budget.nodes-- <= 0) {
|
|
55
|
+
budget.truncated = true;
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
// NaN-equal, -0-distinct — Object.is has the right scalar semantics.
|
|
59
|
+
if (Object.is(a, b)) return true;
|
|
60
|
+
if (typeof a !== typeof b) return false;
|
|
61
|
+
if (typeof a !== "object" || a === null || b === null) {
|
|
62
|
+
// Distinct functions: treated as EQUAL — providers strip/preserve them
|
|
63
|
+
// and reference inequality across a serialization boundary is pure noise.
|
|
64
|
+
return typeof a === "function" && typeof b === "function";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Rich types the render-layer viewers get wrong:
|
|
68
|
+
if (a instanceof Date || b instanceof Date) {
|
|
69
|
+
return a instanceof Date && b instanceof Date && a.getTime() === b.getTime();
|
|
70
|
+
}
|
|
71
|
+
if (a instanceof RegExp || b instanceof RegExp) {
|
|
72
|
+
return a instanceof RegExp && b instanceof RegExp && a.source === b.source && a.flags === b.flags;
|
|
73
|
+
}
|
|
74
|
+
if (a instanceof Error || b instanceof Error) {
|
|
75
|
+
return a instanceof Error && b instanceof Error && a.name === b.name && a.message === b.message;
|
|
76
|
+
}
|
|
77
|
+
if (typeof URL !== "undefined" && (a instanceof URL || b instanceof URL)) {
|
|
78
|
+
return a instanceof URL && b instanceof URL && a.href === b.href;
|
|
79
|
+
}
|
|
80
|
+
if (isTypedArray(a) || isTypedArray(b)) {
|
|
81
|
+
if (!isTypedArray(a) || !isTypedArray(b)) return false;
|
|
82
|
+
if (a.constructor !== b.constructor || a.byteLength !== b.byteLength) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
const ua = new Uint8Array(a.buffer, a.byteOffset, a.byteLength);
|
|
86
|
+
const ub = new Uint8Array(b.buffer, b.byteOffset, b.byteLength);
|
|
87
|
+
for (let i = 0; i < ua.length; i++) if (ua[i] !== ub[i]) return false;
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
if (depth <= 0) {
|
|
91
|
+
budget.truncated = true;
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
// Cycle guard: a revisited left-side object ends the walk for this branch.
|
|
95
|
+
// Reporting unequal here is the conservative direction.
|
|
96
|
+
if (seen.has(a)) {
|
|
97
|
+
budget.truncated = true;
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
seen.add(a);
|
|
101
|
+
if (a instanceof Map || b instanceof Map) {
|
|
102
|
+
if (!(a instanceof Map) || !(b instanceof Map) || a.size !== b.size) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
// Insertion-order comparison — superjson round-trips preserve it, and
|
|
106
|
+
// order-insensitive matching with object keys is quadratic.
|
|
107
|
+
const ea = Array.from(a.entries());
|
|
108
|
+
const eb = Array.from(b.entries());
|
|
109
|
+
for (let i = 0; i < ea.length; i++) {
|
|
110
|
+
if (!deepEqual(ea[i][0], eb[i][0], depth - 1, budget, seen)) return false;
|
|
111
|
+
if (!deepEqual(ea[i][1], eb[i][1], depth - 1, budget, seen)) return false;
|
|
112
|
+
}
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
if (a instanceof Set || b instanceof Set) {
|
|
116
|
+
if (!(a instanceof Set) || !(b instanceof Set) || a.size !== b.size) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
const va = Array.from(a.values());
|
|
120
|
+
const vb = Array.from(b.values());
|
|
121
|
+
for (let i = 0; i < va.length; i++) {
|
|
122
|
+
if (!deepEqual(va[i], vb[i], depth - 1, budget, seen)) return false;
|
|
123
|
+
}
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
127
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
for (let i = 0; i < a.length; i++) {
|
|
131
|
+
if (!deepEqual(a[i], b[i], depth - 1, budget, seen)) return false;
|
|
132
|
+
}
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
const ka = Object.keys(a);
|
|
136
|
+
const kb = Object.keys(b);
|
|
137
|
+
if (ka.length !== kb.length) return false;
|
|
138
|
+
for (const k of ka) {
|
|
139
|
+
if (!Object.prototype.hasOwnProperty.call(b, k)) return false;
|
|
140
|
+
if (!deepEqual(a[k], b[k], depth - 1, budget, seen)) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Top-level key diff of two values with bounded deep equality per key.
|
|
149
|
+
* Non-object inputs degrade to a single implicit "value" comparison.
|
|
150
|
+
*/
|
|
151
|
+
function objectDiff(oldValue, newValue, options) {
|
|
152
|
+
const maxDepth = options?.maxDepth ?? 8;
|
|
153
|
+
const budget = {
|
|
154
|
+
nodes: options?.maxNodes ?? 2000,
|
|
155
|
+
truncated: false
|
|
156
|
+
};
|
|
157
|
+
const oldIsObj = isPlainObjectLike(oldValue);
|
|
158
|
+
const newIsObj = isPlainObjectLike(newValue);
|
|
159
|
+
if (!oldIsObj || !newIsObj) {
|
|
160
|
+
const equal = deepEqual(oldValue, newValue, maxDepth, budget, new WeakSet());
|
|
161
|
+
return {
|
|
162
|
+
equal,
|
|
163
|
+
addedKeys: [],
|
|
164
|
+
removedKeys: [],
|
|
165
|
+
changedKeys: equal ? [] : ["value"],
|
|
166
|
+
truncated: budget.truncated
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
const addedKeys = [];
|
|
170
|
+
const removedKeys = [];
|
|
171
|
+
const changedKeys = [];
|
|
172
|
+
const oldObj = oldValue;
|
|
173
|
+
const newObj = newValue;
|
|
174
|
+
for (const k of Object.keys(oldObj)) {
|
|
175
|
+
if (!Object.prototype.hasOwnProperty.call(newObj, k)) {
|
|
176
|
+
removedKeys.push(k);
|
|
177
|
+
} else if (!deepEqual(oldObj[k], newObj[k], maxDepth - 1, budget, new WeakSet())) {
|
|
178
|
+
changedKeys.push(k);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
for (const k of Object.keys(newObj)) {
|
|
182
|
+
if (!Object.prototype.hasOwnProperty.call(oldObj, k)) addedKeys.push(k);
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
equal: addedKeys.length === 0 && removedKeys.length === 0 && changedKeys.length === 0,
|
|
186
|
+
addedKeys,
|
|
187
|
+
removedKeys,
|
|
188
|
+
changedKeys,
|
|
189
|
+
truncated: budget.truncated
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ============================================================================
|
|
194
|
+
// toDiffable — render normalizer
|
|
195
|
+
// ============================================================================
|
|
196
|
+
|
|
197
|
+
const TO_DIFFABLE_MAX_DEPTH = 12;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Convert a value tree into plain JSON the render-layer diff viewers handle
|
|
201
|
+
* faithfully. Dates → ISO strings, Maps → objects, Sets → arrays,
|
|
202
|
+
* BigInt → "123n", TypedArrays/cycles/depth-overflow → placeholder strings.
|
|
203
|
+
* Functions become "[Function]" (providers have already recorded real ones).
|
|
204
|
+
*/
|
|
205
|
+
function toDiffable(value) {
|
|
206
|
+
return normalizeForDiff(value, TO_DIFFABLE_MAX_DEPTH, new WeakSet());
|
|
207
|
+
}
|
|
208
|
+
function normalizeForDiff(value, depth, seen) {
|
|
209
|
+
if (value === null || value === undefined) return value;
|
|
210
|
+
const t = typeof value;
|
|
211
|
+
if (t === "string" || t === "boolean") return value;
|
|
212
|
+
if (t === "number") {
|
|
213
|
+
return Number.isFinite(value) ? value : String(value);
|
|
214
|
+
}
|
|
215
|
+
if (t === "bigint") return `${String(value)}n`;
|
|
216
|
+
if (t === "function") return "[Function]";
|
|
217
|
+
if (t === "symbol") return String(value);
|
|
218
|
+
if (t !== "object") return String(value);
|
|
219
|
+
if (value instanceof Date) return value.toISOString();
|
|
220
|
+
if (value instanceof RegExp) return String(value);
|
|
221
|
+
if (value instanceof Error) return `[${value.name}: ${value.message}]`;
|
|
222
|
+
if (typeof URL !== "undefined" && value instanceof URL) return value.href;
|
|
223
|
+
if (isTypedArray(value)) {
|
|
224
|
+
return `<${value.constructor.name} ${value.byteLength} bytes>`;
|
|
225
|
+
}
|
|
226
|
+
if (depth <= 0) return "[Max depth]";
|
|
227
|
+
if (seen.has(value)) return "[Circular]";
|
|
228
|
+
seen.add(value);
|
|
229
|
+
if (value instanceof Map) {
|
|
230
|
+
const out = {};
|
|
231
|
+
for (const [k, v] of value.entries()) {
|
|
232
|
+
const key = typeof k === "string" ? k : JSON.stringify(toDiffable(k));
|
|
233
|
+
out[key] = normalizeForDiff(v, depth - 1, seen);
|
|
234
|
+
}
|
|
235
|
+
return out;
|
|
236
|
+
}
|
|
237
|
+
if (value instanceof Set) {
|
|
238
|
+
return Array.from(value.values()).map(v => normalizeForDiff(v, depth - 1, seen));
|
|
239
|
+
}
|
|
240
|
+
if (Array.isArray(value)) {
|
|
241
|
+
return value.map(v => normalizeForDiff(v, depth - 1, seen));
|
|
242
|
+
}
|
|
243
|
+
const out = {};
|
|
244
|
+
for (const [k, v] of Object.entries(value)) {
|
|
245
|
+
out[k] = normalizeForDiff(v, depth - 1, seen);
|
|
246
|
+
}
|
|
247
|
+
return out;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ============================================================================
|
|
251
|
+
// Size probe — O(limit) early-bail, cycle-safe
|
|
252
|
+
// ============================================================================
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* True when `value`'s JSON form is likely larger than `limitBytes`. Iterative
|
|
256
|
+
* walk that bails the moment the running estimate crosses the limit, so cost
|
|
257
|
+
* is O(limit) regardless of payload size (the redux store's `jsonLongerThan`
|
|
258
|
+
* lesson: a full stringify per check froze enterprise-scale apps).
|
|
259
|
+
*/
|
|
260
|
+
function jsonLikelyLargerThan(value, limitBytes) {
|
|
261
|
+
let size = 0;
|
|
262
|
+
const stack = [value];
|
|
263
|
+
const seenObjects = new Set();
|
|
264
|
+
while (stack.length > 0) {
|
|
265
|
+
const v = stack.pop();
|
|
266
|
+
if (v === null || v === undefined) {
|
|
267
|
+
size += 4;
|
|
268
|
+
} else if (typeof v === "string") {
|
|
269
|
+
size += v.length + 2;
|
|
270
|
+
} else if (typeof v === "number" || typeof v === "boolean") {
|
|
271
|
+
size += 8;
|
|
272
|
+
} else if (typeof v === "bigint") {
|
|
273
|
+
size += String(v).length + 1;
|
|
274
|
+
} else if (typeof v === "object") {
|
|
275
|
+
if (seenObjects.has(v)) continue;
|
|
276
|
+
seenObjects.add(v);
|
|
277
|
+
if (v instanceof Date || v instanceof RegExp) {
|
|
278
|
+
size += 32;
|
|
279
|
+
} else if (isTypedArray(v)) {
|
|
280
|
+
size += v.byteLength;
|
|
281
|
+
} else if (v instanceof Map) {
|
|
282
|
+
for (const [k, val] of v.entries()) {
|
|
283
|
+
stack.push(k, val);
|
|
284
|
+
size += 4;
|
|
285
|
+
}
|
|
286
|
+
} else if (v instanceof Set) {
|
|
287
|
+
for (const val of v.values()) {
|
|
288
|
+
stack.push(val);
|
|
289
|
+
size += 2;
|
|
290
|
+
}
|
|
291
|
+
} else if (Array.isArray(v)) {
|
|
292
|
+
for (const item of v) stack.push(item);
|
|
293
|
+
size += 2 * v.length + 2;
|
|
294
|
+
} else {
|
|
295
|
+
for (const [k, val] of Object.entries(v)) {
|
|
296
|
+
size += k.length + 4;
|
|
297
|
+
stack.push(val);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (size > limitBytes) return true;
|
|
302
|
+
}
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
@@ -33,6 +33,12 @@ Object.defineProperty(exports, "useFilterManager", {
|
|
|
33
33
|
return _useFilterManager.useFilterManager;
|
|
34
34
|
}
|
|
35
35
|
});
|
|
36
|
+
Object.defineProperty(exports, "usePersistedState", {
|
|
37
|
+
enumerable: true,
|
|
38
|
+
get: function () {
|
|
39
|
+
return _usePersistedState.usePersistedState;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
36
42
|
Object.defineProperty(exports, "useSafeAreaInsets", {
|
|
37
43
|
enumerable: true,
|
|
38
44
|
get: function () {
|
|
@@ -40,5 +46,6 @@ Object.defineProperty(exports, "useSafeAreaInsets", {
|
|
|
40
46
|
}
|
|
41
47
|
});
|
|
42
48
|
var _useFilterManager = require("./useFilterManager.js");
|
|
49
|
+
var _usePersistedState = require("./usePersistedState.js");
|
|
43
50
|
var _useSafeAreaInsets = require("./useSafeAreaInsets.js");
|
|
44
51
|
var _useDeviceInfo = require("./useDeviceInfo.js");
|
|
@@ -7,7 +7,7 @@ exports.useNativeSafeAreaInsets = exports.safeAreaType = exports.hasSafeAreaPack
|
|
|
7
7
|
/**
|
|
8
8
|
* Auto-generated safe area implementation
|
|
9
9
|
* Detected: none
|
|
10
|
-
* Generated at: 2026-08-
|
|
10
|
+
* Generated at: 2026-08-14T00:03:32.153Z
|
|
11
11
|
*
|
|
12
12
|
* DO NOT EDIT - This file is generated by scripts/detect-safe-area.js
|
|
13
13
|
*
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.usePersistedState = usePersistedState;
|
|
7
|
+
var _react = require("react");
|
|
8
|
+
var _persistentStorage = require("../utils/persistentStorage.js");
|
|
9
|
+
/**
|
|
10
|
+
* Per-key cache so remounts (and multiple consumers of the same key) render
|
|
11
|
+
* the last-known value immediately instead of flashing the default while the
|
|
12
|
+
* async backend loads again.
|
|
13
|
+
*/
|
|
14
|
+
const hydratedCache = new Map();
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* `useState` persisted through `persistentStorage` (FileSystem → AsyncStorage
|
|
18
|
+
* → memory). Replaces the load-then-`hasLoaded`-guard `useEffect` pattern
|
|
19
|
+
* every tool used to hand-roll.
|
|
20
|
+
*
|
|
21
|
+
* Values are JSON-serialized; the default is returned until hydration
|
|
22
|
+
* completes (subsequent mounts of the same key hydrate synchronously from an
|
|
23
|
+
* in-memory cache). The third tuple element reports hydration so callers can
|
|
24
|
+
* defer writes that must not clobber a not-yet-loaded value.
|
|
25
|
+
*
|
|
26
|
+
* ```tsx
|
|
27
|
+
* const [tab, setTab, tabHydrated] = usePersistedState("@react_buoy_custom_flags_tab", "flags");
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
function usePersistedState(key, defaultValue) {
|
|
31
|
+
const [value, setValue] = (0, _react.useState)(() => hydratedCache.has(key) ? hydratedCache.get(key) : defaultValue);
|
|
32
|
+
const [hydrated, setHydrated] = (0, _react.useState)(() => hydratedCache.has(key));
|
|
33
|
+
const keyRef = (0, _react.useRef)(key);
|
|
34
|
+
keyRef.current = key;
|
|
35
|
+
(0, _react.useEffect)(() => {
|
|
36
|
+
if (hydratedCache.has(key)) {
|
|
37
|
+
setValue(hydratedCache.get(key));
|
|
38
|
+
setHydrated(true);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
let cancelled = false;
|
|
42
|
+
_persistentStorage.persistentStorage.getItem(key).then(raw => {
|
|
43
|
+
if (cancelled) return;
|
|
44
|
+
if (raw != null) {
|
|
45
|
+
try {
|
|
46
|
+
const parsed = JSON.parse(raw);
|
|
47
|
+
hydratedCache.set(key, parsed);
|
|
48
|
+
setValue(parsed);
|
|
49
|
+
} catch {
|
|
50
|
+
// Corrupt payload — fall back to the default and let the next
|
|
51
|
+
// write repair it.
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
setHydrated(true);
|
|
55
|
+
}).catch(() => {
|
|
56
|
+
if (!cancelled) setHydrated(true);
|
|
57
|
+
});
|
|
58
|
+
return () => {
|
|
59
|
+
cancelled = true;
|
|
60
|
+
};
|
|
61
|
+
}, [key]);
|
|
62
|
+
const set = (0, _react.useCallback)(next => {
|
|
63
|
+
setValue(prev => {
|
|
64
|
+
const resolved = typeof next === "function" ? next(prev) : next;
|
|
65
|
+
hydratedCache.set(keyRef.current, resolved);
|
|
66
|
+
_persistentStorage.persistentStorage.setItem(keyRef.current, JSON.stringify(resolved)).catch(() => {});
|
|
67
|
+
return resolved;
|
|
68
|
+
});
|
|
69
|
+
}, []);
|
|
70
|
+
return [value, set, hydrated];
|
|
71
|
+
}
|
|
@@ -10,6 +10,8 @@ var _exportNames = {
|
|
|
10
10
|
ENV_ICON_COLOR: true,
|
|
11
11
|
ConsoleIcon: true,
|
|
12
12
|
CONSOLE_ICON_COLOR: true,
|
|
13
|
+
TimeMachineIcon: true,
|
|
14
|
+
TIME_MACHINE_ICON_COLOR: true,
|
|
13
15
|
ImagesIcon: true,
|
|
14
16
|
IMAGES_ICON_COLOR: true,
|
|
15
17
|
AssetsIcon: true,
|
|
@@ -218,6 +220,18 @@ Object.defineProperty(exports, "StorageStackIcon", {
|
|
|
218
220
|
return _StorageStackIcon.StorageStackIcon;
|
|
219
221
|
}
|
|
220
222
|
});
|
|
223
|
+
Object.defineProperty(exports, "TIME_MACHINE_ICON_COLOR", {
|
|
224
|
+
enumerable: true,
|
|
225
|
+
get: function () {
|
|
226
|
+
return _floatingToolsCore.TIME_MACHINE_ICON_COLOR;
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
Object.defineProperty(exports, "TimeMachineIcon", {
|
|
230
|
+
enumerable: true,
|
|
231
|
+
get: function () {
|
|
232
|
+
return _floatingToolsCore.TimeMachineIcon;
|
|
233
|
+
}
|
|
234
|
+
});
|
|
221
235
|
Object.defineProperty(exports, "WifiCircuitIcon", {
|
|
222
236
|
enumerable: true,
|
|
223
237
|
get: function () {
|
package/lib/commonjs/index.js
CHANGED
|
@@ -29,6 +29,12 @@ var _exportNames = {
|
|
|
29
29
|
getCachedOptionalModule: true,
|
|
30
30
|
setRenderCaptureProvider: true,
|
|
31
31
|
getRenderCaptureProvider: true,
|
|
32
|
+
registerStateSnapshotProvider: true,
|
|
33
|
+
getStateSnapshotProviders: true,
|
|
34
|
+
getStateSnapshotProvider: true,
|
|
35
|
+
subscribeToStateSnapshotProviders: true,
|
|
36
|
+
superJsonSerialize: true,
|
|
37
|
+
superJsonDeserialize: true,
|
|
32
38
|
reloadApp: true,
|
|
33
39
|
scheduleReloadApp: true,
|
|
34
40
|
resolveReloadStrategy: true,
|
|
@@ -47,6 +53,7 @@ var _exportNames = {
|
|
|
47
53
|
isPlainObjectUtil: true,
|
|
48
54
|
useSafeAreaInsets: true,
|
|
49
55
|
useFilterManager: true,
|
|
56
|
+
usePersistedState: true,
|
|
50
57
|
useDeviceInfo: true,
|
|
51
58
|
getDeviceInfo: true,
|
|
52
59
|
logDeviceInfo: true,
|
|
@@ -248,6 +255,18 @@ Object.defineProperty(exports, "getSafeRouter", {
|
|
|
248
255
|
return _index3.getSafeRouter;
|
|
249
256
|
}
|
|
250
257
|
});
|
|
258
|
+
Object.defineProperty(exports, "getStateSnapshotProvider", {
|
|
259
|
+
enumerable: true,
|
|
260
|
+
get: function () {
|
|
261
|
+
return _index3.getStateSnapshotProvider;
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
Object.defineProperty(exports, "getStateSnapshotProviders", {
|
|
265
|
+
enumerable: true,
|
|
266
|
+
get: function () {
|
|
267
|
+
return _index3.getStateSnapshotProviders;
|
|
268
|
+
}
|
|
269
|
+
});
|
|
251
270
|
Object.defineProperty(exports, "getStorageBackendType", {
|
|
252
271
|
enumerable: true,
|
|
253
272
|
get: function () {
|
|
@@ -380,6 +399,12 @@ Object.defineProperty(exports, "refreshGameUIColors", {
|
|
|
380
399
|
return _gameUIColors.refreshGameUIColors;
|
|
381
400
|
}
|
|
382
401
|
});
|
|
402
|
+
Object.defineProperty(exports, "registerStateSnapshotProvider", {
|
|
403
|
+
enumerable: true,
|
|
404
|
+
get: function () {
|
|
405
|
+
return _index3.registerStateSnapshotProvider;
|
|
406
|
+
}
|
|
407
|
+
});
|
|
383
408
|
Object.defineProperty(exports, "reloadApp", {
|
|
384
409
|
enumerable: true,
|
|
385
410
|
get: function () {
|
|
@@ -410,6 +435,12 @@ Object.defineProperty(exports, "setRenderCaptureProvider", {
|
|
|
410
435
|
return _index3.setRenderCaptureProvider;
|
|
411
436
|
}
|
|
412
437
|
});
|
|
438
|
+
Object.defineProperty(exports, "subscribeToStateSnapshotProviders", {
|
|
439
|
+
enumerable: true,
|
|
440
|
+
get: function () {
|
|
441
|
+
return _index3.subscribeToStateSnapshotProviders;
|
|
442
|
+
}
|
|
443
|
+
});
|
|
413
444
|
Object.defineProperty(exports, "subscribeToSubscriberCountChanges", {
|
|
414
445
|
enumerable: true,
|
|
415
446
|
get: function () {
|
|
@@ -422,6 +453,18 @@ Object.defineProperty(exports, "subscriberCountNotifier", {
|
|
|
422
453
|
return _index3.subscriberCountNotifier;
|
|
423
454
|
}
|
|
424
455
|
});
|
|
456
|
+
Object.defineProperty(exports, "superJsonDeserialize", {
|
|
457
|
+
enumerable: true,
|
|
458
|
+
get: function () {
|
|
459
|
+
return _index3.superJsonDeserialize;
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
Object.defineProperty(exports, "superJsonSerialize", {
|
|
463
|
+
enumerable: true,
|
|
464
|
+
get: function () {
|
|
465
|
+
return _index3.superJsonSerialize;
|
|
466
|
+
}
|
|
467
|
+
});
|
|
425
468
|
Object.defineProperty(exports, "truncateText", {
|
|
426
469
|
enumerable: true,
|
|
427
470
|
get: function () {
|
|
@@ -458,6 +501,12 @@ Object.defineProperty(exports, "useHintsDisabled", {
|
|
|
458
501
|
return _index10.useHintsDisabled;
|
|
459
502
|
}
|
|
460
503
|
});
|
|
504
|
+
Object.defineProperty(exports, "usePersistedState", {
|
|
505
|
+
enumerable: true,
|
|
506
|
+
get: function () {
|
|
507
|
+
return _index1.usePersistedState;
|
|
508
|
+
}
|
|
509
|
+
});
|
|
461
510
|
Object.defineProperty(exports, "useSafeAreaInsets", {
|
|
462
511
|
enumerable: true,
|
|
463
512
|
get: function () {
|
|
@@ -512,6 +561,18 @@ Object.keys(_index2).forEach(function (key) {
|
|
|
512
561
|
}
|
|
513
562
|
});
|
|
514
563
|
});
|
|
564
|
+
var _wireBudget = require("./sync/wireBudget.js");
|
|
565
|
+
Object.keys(_wireBudget).forEach(function (key) {
|
|
566
|
+
if (key === "default" || key === "__esModule") return;
|
|
567
|
+
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
|
568
|
+
if (key in exports && exports[key] === _wireBudget[key]) return;
|
|
569
|
+
Object.defineProperty(exports, key, {
|
|
570
|
+
enumerable: true,
|
|
571
|
+
get: function () {
|
|
572
|
+
return _wireBudget[key];
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
});
|
|
515
576
|
var _index3 = require("./utils/index.js");
|
|
516
577
|
var _index4 = require("./utils/formatting/index.js");
|
|
517
578
|
Object.keys(_index4).forEach(function (key) {
|
|
@@ -38,6 +38,28 @@ const devToolsStorageKeys = exports.devToolsStorageKeys = {
|
|
|
38
38
|
dimensions: () => `${devToolsStorageKeys.modal.root()}_dimensions`,
|
|
39
39
|
hintAcknowledged: () => `${devToolsStorageKeys.modal.root()}_hint_acknowledged`
|
|
40
40
|
},
|
|
41
|
+
/**
|
|
42
|
+
* Namespace factory for third-party custom tools. Keys share the
|
|
43
|
+
* `@react_buoy` prefix, so `isDevToolsStorageKey` filtering (and the Storage
|
|
44
|
+
* tool's internal-key handling) covers custom-tool persistence for free.
|
|
45
|
+
*
|
|
46
|
+
* ```ts
|
|
47
|
+
* const keys = devToolsStorageKeys.custom("feature-flags");
|
|
48
|
+
* keys.modal(); // "@react_buoy_custom_feature-flags_modal"
|
|
49
|
+
* keys.key("overrides"); // "@react_buoy_custom_feature-flags_overrides"
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
custom: toolId => {
|
|
53
|
+
const root = `${devToolsStorageKeys.base}_custom_${toolId}`;
|
|
54
|
+
return {
|
|
55
|
+
root: () => root,
|
|
56
|
+
/** JsModal `persistenceKey` for this tool's window dimensions. */
|
|
57
|
+
modal: () => `${root}_modal`,
|
|
58
|
+
settings: () => `${root}_settings`,
|
|
59
|
+
/** Arbitrary tool-scoped key. */
|
|
60
|
+
key: suffix => `${root}_${suffix}`
|
|
61
|
+
};
|
|
62
|
+
},
|
|
41
63
|
/**
|
|
42
64
|
* Settings-related storage keys
|
|
43
65
|
*/
|
|
@@ -104,6 +126,15 @@ const devToolsStorageKeys = exports.devToolsStorageKeys = {
|
|
|
104
126
|
isMonitoring: () => `${devToolsStorageKeys.storage.root()}_is_monitoring`,
|
|
105
127
|
diffViewerMode: () => `${devToolsStorageKeys.storage.root()}_diff_viewer_mode` // 'split' | 'tree'
|
|
106
128
|
},
|
|
129
|
+
/**
|
|
130
|
+
* Time Machine storage keys. NOTE: the snapshot VAULT keys
|
|
131
|
+
* (`@react_buoy_time_machine:index` / `:snap:*`) predate this namespace and
|
|
132
|
+
* stay as-is — only preferences live here.
|
|
133
|
+
*/
|
|
134
|
+
timeMachine: {
|
|
135
|
+
root: () => `${devToolsStorageKeys.base}_time_machine`,
|
|
136
|
+
diffViewerMode: () => `${devToolsStorageKeys.timeMachine.root()}_diff_viewer_mode` // 'split' | 'tree'
|
|
137
|
+
},
|
|
107
138
|
/**
|
|
108
139
|
* React Query-related storage keys
|
|
109
140
|
*/
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.approxJsonSize = approxJsonSize;
|
|
7
|
+
exports.isOverWireBudget = isOverWireBudget;
|
|
8
|
+
var _typeHelpers = require("../utils/typeHelpers.js");
|
|
9
|
+
/**
|
|
10
|
+
* Size guards for anything a tool puts on the sync wire.
|
|
11
|
+
*
|
|
12
|
+
* Every adapter faces the same problem: a snapshot is rebuilt and serialized
|
|
13
|
+
* on the device's JS thread ~5x/sec while a dashboard watches, and one fat or
|
|
14
|
+
* unserializable field can either freeze that thread or blow the emit layer's
|
|
15
|
+
* budget — which drops the WHOLE snapshot and leaves the panel stale.
|
|
16
|
+
*
|
|
17
|
+
* Two rules make that safe, and both are easy to get subtly wrong, which is
|
|
18
|
+
* why they live here instead of being copied per adapter:
|
|
19
|
+
*
|
|
20
|
+
* 1. NEVER measure by serializing. `JSON.stringify(value).length` is
|
|
21
|
+
* O(payload) and allocates the whole string — an unbounded cost to
|
|
22
|
+
* enforce a 16KB cap, i.e. the protection becomes the freeze. Walk with
|
|
23
|
+
* an early abort instead: the cost is O(limit) no matter how big the
|
|
24
|
+
* value, and a string costs a length read rather than a scan.
|
|
25
|
+
*
|
|
26
|
+
* 2. Only reach for `JSON.stringify` when the walk saw something that could
|
|
27
|
+
* actually make it throw. A value straight out of `JSON.parse` — which is
|
|
28
|
+
* what a network body or a stored string is — is a finite tree of plain
|
|
29
|
+
* values and can never throw, so the check is pure waste on the common
|
|
30
|
+
* path.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Approximate JSON size, aborting as soon as `limit` is passed — O(limit),
|
|
35
|
+
* never O(payload). Returns the bytes counted so far, so one walk can both
|
|
36
|
+
* threshold ("over the per-field cap?") and accumulate ("how much of the
|
|
37
|
+
* snapshot budget is left?").
|
|
38
|
+
*/
|
|
39
|
+
function approxJsonSize(value, limit) {
|
|
40
|
+
let bytes = 0;
|
|
41
|
+
let sawRepeat = false;
|
|
42
|
+
let sawBigInt = false;
|
|
43
|
+
const stack = [value];
|
|
44
|
+
const seen = new Set();
|
|
45
|
+
while (stack.length > 0) {
|
|
46
|
+
if (bytes > limit) return {
|
|
47
|
+
bytes,
|
|
48
|
+
sawRepeat,
|
|
49
|
+
sawBigInt
|
|
50
|
+
};
|
|
51
|
+
const v = stack.pop();
|
|
52
|
+
if (v === null || v === undefined) bytes += 4;else if (typeof v === "string") bytes += v.length + 2; // O(1), no scan
|
|
53
|
+
else if (typeof v === "number") bytes += 8;else if (typeof v === "boolean") bytes += 5;else if (typeof v === "object") {
|
|
54
|
+
if (seen.has(v)) {
|
|
55
|
+
sawRepeat = true;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
seen.add(v);
|
|
59
|
+
if (Array.isArray(v)) {
|
|
60
|
+
bytes += 2 + v.length;
|
|
61
|
+
for (let i = 0; i < v.length; i++) stack.push(v[i]);
|
|
62
|
+
} else {
|
|
63
|
+
bytes += 2;
|
|
64
|
+
for (const k in v) {
|
|
65
|
+
bytes += k.length + 4;
|
|
66
|
+
stack.push(v[k]);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
if (typeof v === "bigint") sawBigInt = true;
|
|
71
|
+
bytes += 4;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
bytes,
|
|
76
|
+
sawRepeat,
|
|
77
|
+
sawBigInt
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Should this value be withheld from the wire — too big, or unserializable?
|
|
83
|
+
*
|
|
84
|
+
* The ordering is the point: measure with the early-abort walk, and only pay
|
|
85
|
+
* for `JSON.stringify` when the walk found a reason it might throw.
|
|
86
|
+
*/
|
|
87
|
+
function isOverWireBudget(value, limit) {
|
|
88
|
+
const walk = approxJsonSize(value, limit);
|
|
89
|
+
if (walk.bytes > limit) return true;
|
|
90
|
+
// A finished walk with no repeated reference and no bigint cannot throw.
|
|
91
|
+
//
|
|
92
|
+
// Not exhaustive even then: a `toJSON`/getter that throws is invisible to
|
|
93
|
+
// the walk, so callers handling hostile payloads should still guard their
|
|
94
|
+
// own conversion. `safeEmit` is the last backstop.
|
|
95
|
+
if (!walk.sawRepeat && !walk.sawBigInt) return false;
|
|
96
|
+
return !(0, _typeHelpers.isJsonSerializable)(value);
|
|
97
|
+
}
|