@buoy-gg/react-query 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.
@@ -34,10 +34,12 @@ function dehydrateMutation(mutation) {
34
34
  function dehydrateQuery(query) {
35
35
  const observerStates = query.observers.map(observer => ({
36
36
  queryHash: query.queryHash,
37
- options: observer.options,
38
37
  // Remove queryFn from observer options so the dashboard can't accidentally
39
38
  // run device fetch functions (they aren't serializable anyway)
40
- queryFn: undefined
39
+ options: {
40
+ ...observer.options,
41
+ queryFn: undefined
42
+ }
41
43
  }));
42
44
  return {
43
45
  state: {
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.createQuerySnapshotProvider = createQuerySnapshotProvider;
7
+ exports.registerQuerySnapshotProvider = registerQuerySnapshotProvider;
8
+ var _sharedUi = require("@buoy-gg/shared-ui");
9
+ var _dehydrate = require("./dehydrate");
10
+ /**
11
+ * Time Machine snapshot provider for React Query.
12
+ *
13
+ * Unlike the other providers this one can't register at module init — the
14
+ * QueryClient only exists in React context — so useReactQuerySyncAdapter
15
+ * (mounted app-lifetime by FloatingDevTools' auto external sync) registers
16
+ * it per-client via an effect.
17
+ *
18
+ * Restore is a device-safe DIFF against the live cache, NOT the desktop
19
+ * Hydrate: that one detaches live observers and mocks queryFns, which is
20
+ * fine on a mirror client and destructive in a running app. Here existing
21
+ * queries get `setState` (observers stay attached and are notified, keeping
22
+ * their real queryFns), missing ones are built into the cache, and extras
23
+ * are removed. Mutations aren't restorable (mutationFns can't be captured);
24
+ * the mutation cache is cleared instead.
25
+ */
26
+
27
+ /** A restored query must never claim an in-flight fetch it doesn't have. */
28
+ function normalizeState(state) {
29
+ return {
30
+ ...state,
31
+ fetchStatus: "idle",
32
+ fetchMeta: null
33
+ };
34
+ }
35
+ function createQuerySnapshotProvider(queryClient) {
36
+ return {
37
+ id: "query",
38
+ label: "React Query",
39
+ status: () => ({
40
+ canCapture: true,
41
+ canRestore: true
42
+ }),
43
+ async capture() {
44
+ const {
45
+ queries,
46
+ mutations
47
+ } = (0, _dehydrate.dehydrateQueryClient)(queryClient);
48
+ const warnings = [];
49
+ if (mutations.length > 0) {
50
+ warnings.push(`${mutations.length} mutation(s) not captured (mutation functions cannot be restored)`);
51
+ }
52
+ return {
53
+ data: {
54
+ queries: queries.map(({
55
+ observers: _observers,
56
+ ...query
57
+ }) => ({
58
+ ...query,
59
+ state: normalizeState(query.state)
60
+ }))
61
+ },
62
+ warnings
63
+ };
64
+ },
65
+ async restore(data) {
66
+ const snapshot = data;
67
+ const result = {
68
+ ok: true,
69
+ applied: 0,
70
+ skipped: [],
71
+ warnings: []
72
+ };
73
+ if (!snapshot?.queries) {
74
+ return {
75
+ ok: false,
76
+ applied: 0,
77
+ skipped: [],
78
+ warnings: ["empty query snapshot"]
79
+ };
80
+ }
81
+ const cache = queryClient.getQueryCache();
82
+ const snapHashes = new Set(snapshot.queries.map(q => q.queryHash));
83
+ for (const snap of snapshot.queries) {
84
+ try {
85
+ const state = normalizeState(snap.state);
86
+ const existing = cache.get(snap.queryHash);
87
+ if (existing) {
88
+ // Observers stay attached (with their real queryFns) and get
89
+ // notified — the safe on-device path, same as triggerError's.
90
+ existing.setState(state);
91
+ } else {
92
+ cache.build(queryClient, {
93
+ queryKey: snap.queryKey,
94
+ queryHash: snap.queryHash,
95
+ ...(snap.meta && {
96
+ meta: snap.meta
97
+ })
98
+ }, state);
99
+ }
100
+ result.applied += 1;
101
+ } catch (e) {
102
+ result.skipped.push({
103
+ id: snap.queryHash,
104
+ reason: String(e)
105
+ });
106
+ result.ok = false;
107
+ }
108
+ }
109
+
110
+ // Cache entries the snapshot doesn't know about get removed; a mounted
111
+ // observer will rebuild + refetch its query on next interaction.
112
+ for (const query of cache.getAll().slice()) {
113
+ if (!snapHashes.has(query.queryHash)) {
114
+ try {
115
+ cache.remove(query);
116
+ result.applied += 1;
117
+ } catch (e) {
118
+ result.skipped.push({
119
+ id: query.queryHash,
120
+ reason: String(e)
121
+ });
122
+ }
123
+ }
124
+ }
125
+ const mutationCount = queryClient.getMutationCache().getAll().length;
126
+ if (mutationCount > 0) {
127
+ queryClient.getMutationCache().clear();
128
+ result.warnings.push(`mutation cache cleared (${mutationCount} mutation(s))`);
129
+ }
130
+ return result;
131
+ }
132
+ };
133
+ }
134
+
135
+ /** Register for one client; returns the unregister fn. Last client wins. */
136
+ function registerQuerySnapshotProvider(queryClient) {
137
+ return (0, _sharedUi.registerStateSnapshotProvider)(createQuerySnapshotProvider(queryClient));
138
+ }
@@ -7,6 +7,7 @@ exports.useReactQuerySyncAdapter = useReactQuerySyncAdapter;
7
7
  var _react = require("react");
8
8
  var _reactQuery = require("@tanstack/react-query");
9
9
  var _reactQuerySyncAdapter = require("./reactQuerySyncAdapter");
10
+ var _snapshotProvider = require("./snapshotProvider");
10
11
  /**
11
12
  * Context-based variant of createReactQuerySyncAdapter for zero-config
12
13
  * wiring (FloatingDevTools' auto external sync): reads the QueryClient from
@@ -22,5 +23,12 @@ function useReactQuerySyncAdapter() {
22
23
  } catch {
23
24
  queryClient = null;
24
25
  }
26
+
27
+ // Register the Time Machine snapshot provider for this client — this hook
28
+ // is the one app-lifetime spot that actually holds the QueryClient.
29
+ (0, _react.useEffect)(() => {
30
+ if (!queryClient) return;
31
+ return (0, _snapshotProvider.registerQuerySnapshotProvider)(queryClient);
32
+ }, [queryClient]);
25
33
  return (0, _react.useMemo)(() => queryClient ? (0, _reactQuerySyncAdapter.createReactQuerySyncAdapter)(queryClient) : null, [queryClient]);
26
34
  }
@@ -30,10 +30,12 @@ function dehydrateMutation(mutation) {
30
30
  function dehydrateQuery(query) {
31
31
  const observerStates = query.observers.map(observer => ({
32
32
  queryHash: query.queryHash,
33
- options: observer.options,
34
33
  // Remove queryFn from observer options so the dashboard can't accidentally
35
34
  // run device fetch functions (they aren't serializable anyway)
36
- queryFn: undefined
35
+ options: {
36
+ ...observer.options,
37
+ queryFn: undefined
38
+ }
37
39
  }));
38
40
  return {
39
41
  state: {
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Time Machine snapshot provider for React Query.
5
+ *
6
+ * Unlike the other providers this one can't register at module init — the
7
+ * QueryClient only exists in React context — so useReactQuerySyncAdapter
8
+ * (mounted app-lifetime by FloatingDevTools' auto external sync) registers
9
+ * it per-client via an effect.
10
+ *
11
+ * Restore is a device-safe DIFF against the live cache, NOT the desktop
12
+ * Hydrate: that one detaches live observers and mocks queryFns, which is
13
+ * fine on a mirror client and destructive in a running app. Here existing
14
+ * queries get `setState` (observers stay attached and are notified, keeping
15
+ * their real queryFns), missing ones are built into the cache, and extras
16
+ * are removed. Mutations aren't restorable (mutationFns can't be captured);
17
+ * the mutation cache is cleared instead.
18
+ */
19
+
20
+ import { registerStateSnapshotProvider } from "@buoy-gg/shared-ui";
21
+ import { dehydrateQueryClient } from "./dehydrate";
22
+ /** A restored query must never claim an in-flight fetch it doesn't have. */
23
+ function normalizeState(state) {
24
+ return {
25
+ ...state,
26
+ fetchStatus: "idle",
27
+ fetchMeta: null
28
+ };
29
+ }
30
+ export function createQuerySnapshotProvider(queryClient) {
31
+ return {
32
+ id: "query",
33
+ label: "React Query",
34
+ status: () => ({
35
+ canCapture: true,
36
+ canRestore: true
37
+ }),
38
+ async capture() {
39
+ const {
40
+ queries,
41
+ mutations
42
+ } = dehydrateQueryClient(queryClient);
43
+ const warnings = [];
44
+ if (mutations.length > 0) {
45
+ warnings.push(`${mutations.length} mutation(s) not captured (mutation functions cannot be restored)`);
46
+ }
47
+ return {
48
+ data: {
49
+ queries: queries.map(({
50
+ observers: _observers,
51
+ ...query
52
+ }) => ({
53
+ ...query,
54
+ state: normalizeState(query.state)
55
+ }))
56
+ },
57
+ warnings
58
+ };
59
+ },
60
+ async restore(data) {
61
+ const snapshot = data;
62
+ const result = {
63
+ ok: true,
64
+ applied: 0,
65
+ skipped: [],
66
+ warnings: []
67
+ };
68
+ if (!snapshot?.queries) {
69
+ return {
70
+ ok: false,
71
+ applied: 0,
72
+ skipped: [],
73
+ warnings: ["empty query snapshot"]
74
+ };
75
+ }
76
+ const cache = queryClient.getQueryCache();
77
+ const snapHashes = new Set(snapshot.queries.map(q => q.queryHash));
78
+ for (const snap of snapshot.queries) {
79
+ try {
80
+ const state = normalizeState(snap.state);
81
+ const existing = cache.get(snap.queryHash);
82
+ if (existing) {
83
+ // Observers stay attached (with their real queryFns) and get
84
+ // notified — the safe on-device path, same as triggerError's.
85
+ existing.setState(state);
86
+ } else {
87
+ cache.build(queryClient, {
88
+ queryKey: snap.queryKey,
89
+ queryHash: snap.queryHash,
90
+ ...(snap.meta && {
91
+ meta: snap.meta
92
+ })
93
+ }, state);
94
+ }
95
+ result.applied += 1;
96
+ } catch (e) {
97
+ result.skipped.push({
98
+ id: snap.queryHash,
99
+ reason: String(e)
100
+ });
101
+ result.ok = false;
102
+ }
103
+ }
104
+
105
+ // Cache entries the snapshot doesn't know about get removed; a mounted
106
+ // observer will rebuild + refetch its query on next interaction.
107
+ for (const query of cache.getAll().slice()) {
108
+ if (!snapHashes.has(query.queryHash)) {
109
+ try {
110
+ cache.remove(query);
111
+ result.applied += 1;
112
+ } catch (e) {
113
+ result.skipped.push({
114
+ id: query.queryHash,
115
+ reason: String(e)
116
+ });
117
+ }
118
+ }
119
+ }
120
+ const mutationCount = queryClient.getMutationCache().getAll().length;
121
+ if (mutationCount > 0) {
122
+ queryClient.getMutationCache().clear();
123
+ result.warnings.push(`mutation cache cleared (${mutationCount} mutation(s))`);
124
+ }
125
+ return result;
126
+ }
127
+ };
128
+ }
129
+
130
+ /** Register for one client; returns the unregister fn. Last client wins. */
131
+ export function registerQuerySnapshotProvider(queryClient) {
132
+ return registerStateSnapshotProvider(createQuerySnapshotProvider(queryClient));
133
+ }
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
 
3
- import { useMemo } from "react";
3
+ import { useEffect, useMemo } from "react";
4
4
  import { useQueryClient } from "@tanstack/react-query";
5
5
  import { createReactQuerySyncAdapter } from "./reactQuerySyncAdapter";
6
+ import { registerQuerySnapshotProvider } from "./snapshotProvider";
6
7
 
7
8
  /**
8
9
  * Context-based variant of createReactQuerySyncAdapter for zero-config
@@ -19,5 +20,12 @@ export function useReactQuerySyncAdapter() {
19
20
  } catch {
20
21
  queryClient = null;
21
22
  }
23
+
24
+ // Register the Time Machine snapshot provider for this client — this hook
25
+ // is the one app-lifetime spot that actually holds the QueryClient.
26
+ useEffect(() => {
27
+ if (!queryClient) return;
28
+ return registerQuerySnapshotProvider(queryClient);
29
+ }, [queryClient]);
22
30
  return useMemo(() => queryClient ? createReactQuerySyncAdapter(queryClient) : null, [queryClient]);
23
31
  }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Time Machine snapshot provider for React Query.
3
+ *
4
+ * Unlike the other providers this one can't register at module init — the
5
+ * QueryClient only exists in React context — so useReactQuerySyncAdapter
6
+ * (mounted app-lifetime by FloatingDevTools' auto external sync) registers
7
+ * it per-client via an effect.
8
+ *
9
+ * Restore is a device-safe DIFF against the live cache, NOT the desktop
10
+ * Hydrate: that one detaches live observers and mocks queryFns, which is
11
+ * fine on a mirror client and destructive in a running app. Here existing
12
+ * queries get `setState` (observers stay attached and are notified, keeping
13
+ * their real queryFns), missing ones are built into the cache, and extras
14
+ * are removed. Mutations aren't restorable (mutationFns can't be captured);
15
+ * the mutation cache is cleared instead.
16
+ */
17
+ import type { QueryClient } from "@tanstack/react-query";
18
+ import { type StateSnapshotProvider } from "@buoy-gg/shared-ui";
19
+ export declare function createQuerySnapshotProvider(queryClient: QueryClient): StateSnapshotProvider;
20
+ /** Register for one client; returns the unregister fn. Last client wins. */
21
+ export declare function registerQuerySnapshotProvider(queryClient: QueryClient): () => void;
22
+ //# sourceMappingURL=snapshotProvider.d.ts.map
@@ -0,0 +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,EAG3B,MAAM,oBAAoB,CAAC;AAc5B,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,WAAW,GACvB,qBAAqB,CAsFvB;AAED,4EAA4E;AAC5E,wBAAgB,6BAA6B,CAC3C,WAAW,EAAE,WAAW,GACvB,MAAM,IAAI,CAEZ"}
@@ -1 +1 @@
1
- {"version":3,"file":"useReactQuerySyncAdapter.d.ts","sourceRoot":"","sources":["../../../../src/react-query/sync/useReactQuerySyncAdapter.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AAEtE;;;;;GAKG;AACH,wBAAgB,wBAAwB,IAAI,UAAU,CACpD,OAAO,2BAA2B,CACnC,GAAG,IAAI,CAcP"}
1
+ {"version":3,"file":"useReactQuerySyncAdapter.d.ts","sourceRoot":"","sources":["../../../../src/react-query/sync/useReactQuerySyncAdapter.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AAGtE;;;;;GAKG;AACH,wBAAgB,wBAAwB,IAAI,UAAU,CACpD,OAAO,2BAA2B,CACnC,GAAG,IAAI,CAqBP"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buoy-gg/react-query",
3
- "version": "7.0.15",
3
+ "version": "7.0.17",
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",
@@ -24,10 +24,13 @@
24
24
  "files": [
25
25
  "lib"
26
26
  ],
27
- "sideEffects": false,
27
+ "sideEffects": [
28
+ "**/snapshotProvider.js",
29
+ "**/snapshotProvider.ts"
30
+ ],
28
31
  "dependencies": {
29
- "@buoy-gg/floating-tools-core": "^7.0.15",
30
- "@buoy-gg/shared-ui": "^7.0.15"
32
+ "@buoy-gg/shared-ui": "^7.0.17",
33
+ "@buoy-gg/floating-tools-core": "^7.0.17"
31
34
  },
32
35
  "peerDependencies": {
33
36
  "@tanstack/react-query": ">=5.0.0",