@buoy-gg/react-query 7.0.20 → 7.0.22

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.
@@ -55,6 +55,81 @@ function queryLabel(queryKey, queryHash) {
55
55
  }
56
56
  }
57
57
  const ITEM_RENDER_BYTE_LIMIT = 256 * 1024;
58
+
59
+ /**
60
+ * A query `error` (and `fetchFailureReason`) usually holds the live
61
+ * Axios/fetch error: `request` points at the XHR, which is cyclic and would
62
+ * fail the whole snapshot's serialization. Only name + message survive —
63
+ * that's all a restored error state needs.
64
+ */
65
+ function toStoredError(value) {
66
+ if (value == null) return value ?? null;
67
+ if (typeof value === "object") {
68
+ const err = value;
69
+ const name = typeof err.name === "string" ? err.name : undefined;
70
+ const message = typeof err.message === "string" ? err.message : undefined;
71
+ if (name !== undefined || message !== undefined) {
72
+ return {
73
+ ...(name !== undefined ? {
74
+ name
75
+ } : {}),
76
+ ...(message !== undefined ? {
77
+ message
78
+ } : {})
79
+ };
80
+ }
81
+ }
82
+ return value;
83
+ }
84
+
85
+ /** Would the Time Machine vault be able to store this value? */
86
+ function isStorable(value) {
87
+ if (value === undefined) return true;
88
+ try {
89
+ (0, _sharedUi.superJsonSerialize)(value);
90
+ return true;
91
+ } catch {
92
+ return false;
93
+ }
94
+ }
95
+
96
+ /**
97
+ * The queries as a SNAPSHOT sees them — raw.
98
+ *
99
+ * Deliberately NOT `dehydrateQueryClient()`: that is the WIRE form, which
100
+ * swaps any payload over 16KB for a `DATA_ON_DEVICE` marker so the dashboard
101
+ * stays inside its emit budget. Snapshots live on-device in persistentStorage
102
+ * with no such budget, and restoring a marker would write the placeholder
103
+ * object into the live cache — the app then renders `{__buoyDataOnDevice}` as
104
+ * if it were its data and crashes on the first field it reads.
105
+ */
106
+ function captureQueries(client) {
107
+ const warnings = [];
108
+ const queries = [];
109
+ for (const query of client.getQueryCache().getAll()) {
110
+ const label = queryLabel(query.queryKey, query.queryHash);
111
+ if (!isStorable(query.state.data)) {
112
+ warnings.push(`query ${label}: data can't be serialized (circular reference?) — not captured`);
113
+ continue;
114
+ }
115
+ queries.push({
116
+ queryKey: query.queryKey,
117
+ queryHash: query.queryHash,
118
+ ...(query.meta && isStorable(query.meta) ? {
119
+ meta: query.meta
120
+ } : {}),
121
+ state: normalizeState({
122
+ ...query.state,
123
+ error: toStoredError(query.state.error),
124
+ fetchFailureReason: toStoredError(query.state.fetchFailureReason)
125
+ })
126
+ });
127
+ }
128
+ return {
129
+ queries,
130
+ warnings
131
+ };
132
+ }
58
133
  function createQuerySnapshotProvider(queryClient) {
59
134
  return {
60
135
  id: "query",
@@ -66,21 +141,15 @@ function createQuerySnapshotProvider(queryClient) {
66
141
  async capture() {
67
142
  const {
68
143
  queries,
69
- mutations
70
- } = (0, _dehydrate.dehydrateQueryClient)(queryClient);
71
- const warnings = [];
72
- if (mutations.length > 0) {
73
- warnings.push(`${mutations.length} mutation(s) not captured (mutation functions cannot be restored)`);
144
+ warnings
145
+ } = captureQueries(queryClient);
146
+ const mutationCount = queryClient.getMutationCache().getAll().length;
147
+ if (mutationCount > 0) {
148
+ warnings.push(`${mutationCount} mutation(s) not captured (mutation functions cannot be restored)`);
74
149
  }
75
150
  return {
76
151
  data: {
77
- queries: queries.map(({
78
- observers: _observers,
79
- ...query
80
- }) => ({
81
- ...query,
82
- state: normalizeState(query.state)
83
- }))
152
+ queries
84
153
  },
85
154
  warnings
86
155
  };
@@ -112,6 +181,17 @@ function createQuerySnapshotProvider(queryClient) {
112
181
  excludedHits += 1;
113
182
  continue;
114
183
  }
184
+ // Snapshots taken before the raw-capture fix hold a wire placeholder
185
+ // instead of the data. Writing that into the cache hands the app an
186
+ // object with none of its fields — leave the live query alone and say
187
+ // why, rather than "restoring" a value that crashes the screen.
188
+ if ((0, _dehydrate.isWireDataMarker)(snap.state.data)) {
189
+ result.skipped.push({
190
+ id: snap.queryHash,
191
+ reason: "captured as an on-device placeholder by an older Buoy — re-capture this restore point"
192
+ });
193
+ continue;
194
+ }
115
195
  try {
116
196
  const state = normalizeState(snap.state);
117
197
  const existing = cache.get(snap.queryHash);
@@ -171,16 +251,24 @@ function createQuerySnapshotProvider(queryClient) {
171
251
  async previewItems(target, base) {
172
252
  const targetQueries = target?.queries ?? [];
173
253
  const liveMode = base === undefined;
174
- const baseQueries = liveMode ? (0, _dehydrate.dehydrateQueryClient)(queryClient).queries.map(({
175
- observers: _o,
176
- ...q
177
- }) => q) : base?.queries ?? [];
254
+ // Live side goes through the same raw capture as a snapshot would, so a
255
+ // big query diffs as its real value instead of a wire placeholder.
256
+ const baseQueries = liveMode ? captureQueries(queryClient).queries : base?.queries ?? [];
178
257
  const baseByHash = new Map(baseQueries.map(q => [q.queryHash, q]));
179
258
  const targetByHash = new Map(targetQueries.map(q => [q.queryHash, q]));
180
259
  const items = [];
181
260
  const notes = [];
182
261
  for (const q of targetQueries) {
183
262
  const label = queryLabel(q.queryKey, q.queryHash);
263
+ if ((0, _dehydrate.isWireDataMarker)(q.state.data)) {
264
+ items.push({
265
+ key: q.queryHash,
266
+ label,
267
+ verdict: "wont-apply",
268
+ reason: "data stayed on the device when this was captured (older Buoy) — re-capture this restore point"
269
+ });
270
+ continue;
271
+ }
184
272
  const baseQ = baseByHash.get(q.queryHash);
185
273
  if (!baseQ) {
186
274
  items.push({
@@ -17,9 +17,9 @@
17
17
  * the mutation cache is cleared instead.
18
18
  */
19
19
 
20
- import { registerStateSnapshotProvider } from "@buoy-gg/shared-ui";
20
+ import { registerStateSnapshotProvider, superJsonSerialize } from "@buoy-gg/shared-ui";
21
21
  import { boundedDeepEqual, jsonLikelyLargerThan } from "@buoy-gg/shared-ui/dataViewer";
22
- import { dehydrateQueryClient } from "./dehydrate";
22
+ import { isWireDataMarker } from "./dehydrate";
23
23
  /** A restored query must never claim an in-flight fetch it doesn't have. */
24
24
  function normalizeState(state) {
25
25
  return {
@@ -50,6 +50,81 @@ function queryLabel(queryKey, queryHash) {
50
50
  }
51
51
  }
52
52
  const ITEM_RENDER_BYTE_LIMIT = 256 * 1024;
53
+
54
+ /**
55
+ * A query `error` (and `fetchFailureReason`) usually holds the live
56
+ * Axios/fetch error: `request` points at the XHR, which is cyclic and would
57
+ * fail the whole snapshot's serialization. Only name + message survive —
58
+ * that's all a restored error state needs.
59
+ */
60
+ function toStoredError(value) {
61
+ if (value == null) return value ?? null;
62
+ if (typeof value === "object") {
63
+ const err = value;
64
+ const name = typeof err.name === "string" ? err.name : undefined;
65
+ const message = typeof err.message === "string" ? err.message : undefined;
66
+ if (name !== undefined || message !== undefined) {
67
+ return {
68
+ ...(name !== undefined ? {
69
+ name
70
+ } : {}),
71
+ ...(message !== undefined ? {
72
+ message
73
+ } : {})
74
+ };
75
+ }
76
+ }
77
+ return value;
78
+ }
79
+
80
+ /** Would the Time Machine vault be able to store this value? */
81
+ function isStorable(value) {
82
+ if (value === undefined) return true;
83
+ try {
84
+ superJsonSerialize(value);
85
+ return true;
86
+ } catch {
87
+ return false;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * The queries as a SNAPSHOT sees them — raw.
93
+ *
94
+ * Deliberately NOT `dehydrateQueryClient()`: that is the WIRE form, which
95
+ * swaps any payload over 16KB for a `DATA_ON_DEVICE` marker so the dashboard
96
+ * stays inside its emit budget. Snapshots live on-device in persistentStorage
97
+ * with no such budget, and restoring a marker would write the placeholder
98
+ * object into the live cache — the app then renders `{__buoyDataOnDevice}` as
99
+ * if it were its data and crashes on the first field it reads.
100
+ */
101
+ function captureQueries(client) {
102
+ const warnings = [];
103
+ const queries = [];
104
+ for (const query of client.getQueryCache().getAll()) {
105
+ const label = queryLabel(query.queryKey, query.queryHash);
106
+ if (!isStorable(query.state.data)) {
107
+ warnings.push(`query ${label}: data can't be serialized (circular reference?) — not captured`);
108
+ continue;
109
+ }
110
+ queries.push({
111
+ queryKey: query.queryKey,
112
+ queryHash: query.queryHash,
113
+ ...(query.meta && isStorable(query.meta) ? {
114
+ meta: query.meta
115
+ } : {}),
116
+ state: normalizeState({
117
+ ...query.state,
118
+ error: toStoredError(query.state.error),
119
+ fetchFailureReason: toStoredError(query.state.fetchFailureReason)
120
+ })
121
+ });
122
+ }
123
+ return {
124
+ queries,
125
+ warnings
126
+ };
127
+ }
53
128
  export function createQuerySnapshotProvider(queryClient) {
54
129
  return {
55
130
  id: "query",
@@ -61,21 +136,15 @@ export function createQuerySnapshotProvider(queryClient) {
61
136
  async capture() {
62
137
  const {
63
138
  queries,
64
- mutations
65
- } = dehydrateQueryClient(queryClient);
66
- const warnings = [];
67
- if (mutations.length > 0) {
68
- warnings.push(`${mutations.length} mutation(s) not captured (mutation functions cannot be restored)`);
139
+ warnings
140
+ } = captureQueries(queryClient);
141
+ const mutationCount = queryClient.getMutationCache().getAll().length;
142
+ if (mutationCount > 0) {
143
+ warnings.push(`${mutationCount} mutation(s) not captured (mutation functions cannot be restored)`);
69
144
  }
70
145
  return {
71
146
  data: {
72
- queries: queries.map(({
73
- observers: _observers,
74
- ...query
75
- }) => ({
76
- ...query,
77
- state: normalizeState(query.state)
78
- }))
147
+ queries
79
148
  },
80
149
  warnings
81
150
  };
@@ -107,6 +176,17 @@ export function createQuerySnapshotProvider(queryClient) {
107
176
  excludedHits += 1;
108
177
  continue;
109
178
  }
179
+ // Snapshots taken before the raw-capture fix hold a wire placeholder
180
+ // instead of the data. Writing that into the cache hands the app an
181
+ // object with none of its fields — leave the live query alone and say
182
+ // why, rather than "restoring" a value that crashes the screen.
183
+ if (isWireDataMarker(snap.state.data)) {
184
+ result.skipped.push({
185
+ id: snap.queryHash,
186
+ reason: "captured as an on-device placeholder by an older Buoy — re-capture this restore point"
187
+ });
188
+ continue;
189
+ }
110
190
  try {
111
191
  const state = normalizeState(snap.state);
112
192
  const existing = cache.get(snap.queryHash);
@@ -166,16 +246,24 @@ export function createQuerySnapshotProvider(queryClient) {
166
246
  async previewItems(target, base) {
167
247
  const targetQueries = target?.queries ?? [];
168
248
  const liveMode = base === undefined;
169
- const baseQueries = liveMode ? dehydrateQueryClient(queryClient).queries.map(({
170
- observers: _o,
171
- ...q
172
- }) => q) : base?.queries ?? [];
249
+ // Live side goes through the same raw capture as a snapshot would, so a
250
+ // big query diffs as its real value instead of a wire placeholder.
251
+ const baseQueries = liveMode ? captureQueries(queryClient).queries : base?.queries ?? [];
173
252
  const baseByHash = new Map(baseQueries.map(q => [q.queryHash, q]));
174
253
  const targetByHash = new Map(targetQueries.map(q => [q.queryHash, q]));
175
254
  const items = [];
176
255
  const notes = [];
177
256
  for (const q of targetQueries) {
178
257
  const label = queryLabel(q.queryKey, q.queryHash);
258
+ if (isWireDataMarker(q.state.data)) {
259
+ items.push({
260
+ key: q.queryHash,
261
+ label,
262
+ verdict: "wont-apply",
263
+ reason: "data stayed on the device when this was captured (older Buoy) — re-capture this restore point"
264
+ });
265
+ continue;
266
+ }
179
267
  const baseQ = baseByHash.get(q.queryHash);
180
268
  if (!baseQ) {
181
269
  items.push({
@@ -1 +1 @@
1
- {"version":3,"file":"snapshotProvider.d.ts","sourceRoot":"","sources":["../../../../src/react-query/sync/snapshotProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,WAAW,EAAc,MAAM,uBAAuB,CAAC;AACrE,OAAO,EAEL,KAAK,qBAAqB,EAK3B,MAAM,oBAAoB,CAAC;AAuC5B,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,WAAW,GACvB,qBAAqB,CAuLvB;AAED,4EAA4E;AAC5E,wBAAgB,6BAA6B,CAC3C,WAAW,EAAE,WAAW,GACvB,MAAM,IAAI,CAEZ"}
1
+ {"version":3,"file":"snapshotProvider.d.ts","sourceRoot":"","sources":["../../../../src/react-query/sync/snapshotProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,WAAW,EAAc,MAAM,uBAAuB,CAAC;AACrE,OAAO,EAGL,KAAK,qBAAqB,EAK3B,MAAM,oBAAoB,CAAC;AA8G5B,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,WAAW,GACvB,qBAAqB,CAwMvB;AAED,4EAA4E;AAC5E,wBAAgB,6BAA6B,CAC3C,WAAW,EAAE,WAAW,GACvB,MAAM,IAAI,CAEZ"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buoy-gg/react-query",
3
- "version": "7.0.20",
3
+ "version": "7.0.22",
4
4
  "description": "TanStack Query devtools inside your React Native app — cache inspection, refetch & invalidate, offline toggle. Part of Buoy devtools.",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -29,8 +29,8 @@
29
29
  "**/snapshotProvider.ts"
30
30
  ],
31
31
  "dependencies": {
32
- "@buoy-gg/shared-ui": "^7.0.20",
33
- "@buoy-gg/floating-tools-core": "^7.0.20"
32
+ "@buoy-gg/floating-tools-core": "^7.0.22",
33
+ "@buoy-gg/shared-ui": "^7.0.22"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "@tanstack/react-query": ">=5.0.0",