@tanstack/query-broadcast-client-experimental 5.101.4 → 5.102.1

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/src/index.ts CHANGED
@@ -1,23 +1,70 @@
1
1
  import { BroadcastChannel } from 'broadcast-channel'
2
2
  import type { BroadcastChannelOptions } from 'broadcast-channel'
3
- import type { QueryClient } from '@tanstack/query-core'
3
+ import type { QueryClient, QueryKey } from '@tanstack/query-core'
4
+
5
+ /**
6
+ * Metadata describing a broadcast that failed to be delivered to other tabs.
7
+ * Passed to {@link BroadcastQueryClientOptions.onBroadcastError} so callers
8
+ * can correlate failures with the originating query.
9
+ */
10
+ export interface BroadcastErrorEvent {
11
+ type: 'updated' | 'removed' | 'added'
12
+ queryHash: string
13
+ queryKey: QueryKey
14
+ }
15
+
16
+ type BroadcastMessage =
17
+ | { type: 'updated'; queryHash: string; queryKey: QueryKey; state: unknown }
18
+ | { type: 'removed'; queryHash: string; queryKey: QueryKey }
19
+ | { type: 'added'; queryHash: string; queryKey: QueryKey; state: unknown }
4
20
 
5
21
  interface BroadcastQueryClientOptions {
22
+ /** The QueryClient to sync. */
6
23
  queryClient: QueryClient
24
+ /**
25
+ * Unique channel name used to communicate between tabs and windows.
26
+ * @default 'tanstack-query'
27
+ */
7
28
  broadcastChannel?: string
29
+ /** Options forwarded to the underlying `BroadcastChannel`. */
8
30
  options?: BroadcastChannelOptions
31
+ /**
32
+ * Called when a query event fails to broadcast to other tabs — most
33
+ * commonly because the query's `state.data`, `state.error`, or `queryKey`
34
+ * contains a value the structured-clone algorithm cannot serialize
35
+ * (e.g. `ReadableStream`, `File`, functions, Vue `reactive` proxies).
36
+ *
37
+ * Provide this to route failures to an error tracker. If omitted, a
38
+ * `console.warn` is emitted in development so failures are never silent.
39
+ *
40
+ * May return a `Promise`; any rejection is caught internally so it cannot
41
+ * cause a secondary unhandled rejection.
42
+ */
43
+ onBroadcastError?: (
44
+ error: unknown,
45
+ event: BroadcastErrorEvent,
46
+ ) => void | Promise<void>
9
47
  }
10
48
 
11
49
  export function broadcastQueryClient({
12
50
  queryClient,
13
51
  broadcastChannel = 'tanstack-query',
14
52
  options,
53
+ onBroadcastError,
15
54
  }: BroadcastQueryClientOptions): () => void {
16
55
  let transaction = false
17
56
  const tx = (cb: () => void) => {
18
57
  transaction = true
19
- cb()
20
- transaction = false
58
+ try {
59
+ cb()
60
+ } finally {
61
+ // Guard against `cb` throwing (e.g. `query.setState`/`queryCache.build`
62
+ // triggering a listener that throws while applying an incoming
63
+ // cross-tab message). Without this, `transaction` would stay `true`
64
+ // forever, silently disabling this tab's own broadcasts to other tabs
65
+ // for the rest of the session.
66
+ transaction = false
67
+ }
21
68
  }
22
69
 
23
70
  const channel = new BroadcastChannel(broadcastChannel, {
@@ -27,7 +74,42 @@ export function broadcastQueryClient({
27
74
 
28
75
  const queryCache = queryClient.getQueryCache()
29
76
 
30
- const unsubscribe = queryClient.getQueryCache().subscribe((queryEvent) => {
77
+ const safePost = (message: BroadcastMessage): void => {
78
+ channel.postMessage(message).catch((error: unknown) => {
79
+ const event: BroadcastErrorEvent = {
80
+ type: message.type,
81
+ queryHash: message.queryHash,
82
+ queryKey: message.queryKey,
83
+ }
84
+
85
+ if (onBroadcastError) {
86
+ const warnCallbackError = (callbackError: unknown) => {
87
+ if (process.env.NODE_ENV !== 'production') {
88
+ console.warn(
89
+ `[broadcastQueryClient] onBroadcastError threw while handling "${event.type}" for query ${event.queryHash}.`,
90
+ callbackError,
91
+ )
92
+ }
93
+ }
94
+ let result: void | Promise<void>
95
+ try {
96
+ result = onBroadcastError(error, event)
97
+ } catch (callbackError) {
98
+ warnCallbackError(callbackError)
99
+ return
100
+ }
101
+ result?.catch(warnCallbackError)
102
+ } else if (process.env.NODE_ENV !== 'production') {
103
+ console.warn(
104
+ `[broadcastQueryClient] Failed to broadcast "${event.type}" event for query ${event.queryHash}. ` +
105
+ 'The query value could not be structured-cloned; cross-tab sync for this query was skipped.',
106
+ error,
107
+ )
108
+ }
109
+ })
110
+ }
111
+
112
+ const unsubscribe = queryCache.subscribe((queryEvent) => {
31
113
  if (transaction) {
32
114
  return
33
115
  }
@@ -37,7 +119,7 @@ export function broadcastQueryClient({
37
119
  } = queryEvent
38
120
 
39
121
  if (queryEvent.type === 'updated' && queryEvent.action.type === 'success') {
40
- channel.postMessage({
122
+ safePost({
41
123
  type: 'updated',
42
124
  queryHash,
43
125
  queryKey,
@@ -46,7 +128,7 @@ export function broadcastQueryClient({
46
128
  }
47
129
 
48
130
  if (queryEvent.type === 'removed' && observers.length > 0) {
49
- channel.postMessage({
131
+ safePost({
50
132
  type: 'removed',
51
133
  queryHash,
52
134
  queryKey,
@@ -54,10 +136,11 @@ export function broadcastQueryClient({
54
136
  }
55
137
 
56
138
  if (queryEvent.type === 'added') {
57
- channel.postMessage({
139
+ safePost({
58
140
  type: 'added',
59
141
  queryHash,
60
142
  queryKey,
143
+ state,
61
144
  })
62
145
  }
63
146
  })
@@ -1,12 +0,0 @@
1
- import type { BroadcastChannelOptions } from 'broadcast-channel';
2
- import type { QueryClient } from '@tanstack/query-core';
3
-
4
- export declare function broadcastQueryClient({ queryClient, broadcastChannel, options, }: BroadcastQueryClientOptions): () => void;
5
-
6
- declare interface BroadcastQueryClientOptions {
7
- queryClient: QueryClient;
8
- broadcastChannel?: string;
9
- options?: BroadcastChannelOptions;
10
- }
11
-
12
- export { }
@@ -1,12 +0,0 @@
1
- import type { BroadcastChannelOptions } from 'broadcast-channel';
2
- import type { QueryClient } from '@tanstack/query-core';
3
-
4
- export declare function broadcastQueryClient({ queryClient, broadcastChannel, options, }: BroadcastQueryClientOptions): () => void;
5
-
6
- declare interface BroadcastQueryClientOptions {
7
- queryClient: QueryClient;
8
- broadcastChannel?: string;
9
- options?: BroadcastChannelOptions;
10
- }
11
-
12
- export { }
@@ -1,12 +0,0 @@
1
- import type { BroadcastChannelOptions } from 'broadcast-channel';
2
- import type { QueryClient } from '@tanstack/query-core';
3
-
4
- export declare function broadcastQueryClient({ queryClient, broadcastChannel, options, }: BroadcastQueryClientOptions): () => void;
5
-
6
- declare interface BroadcastQueryClientOptions {
7
- queryClient: QueryClient;
8
- broadcastChannel?: string;
9
- options?: BroadcastChannelOptions;
10
- }
11
-
12
- export { }
@@ -1,12 +0,0 @@
1
- import type { BroadcastChannelOptions } from 'broadcast-channel';
2
- import type { QueryClient } from '@tanstack/query-core';
3
-
4
- export declare function broadcastQueryClient({ queryClient, broadcastChannel, options, }: BroadcastQueryClientOptions): () => void;
5
-
6
- declare interface BroadcastQueryClientOptions {
7
- queryClient: QueryClient;
8
- broadcastChannel?: string;
9
- options?: BroadcastChannelOptions;
10
- }
11
-
12
- export { }