Skip to content

fix(query-broadcast-client): handle postMessage errors#10770

Open
raashish1601 wants to merge 2 commits into
TanStack:mainfrom
raashish1601:codex/10542-broadcast-post-errors
Open

fix(query-broadcast-client): handle postMessage errors#10770
raashish1601 wants to merge 2 commits into
TanStack:mainfrom
raashish1601:codex/10542-broadcast-post-errors

Conversation

@raashish1601
Copy link
Copy Markdown
Contributor

@raashish1601 raashish1601 commented May 24, 2026

Summary

Fixes #10542 and #10543.

broadcastQueryClient now sends cache events through a guarded postMessage helper. If the underlying broadcast-channel implementation rejects or throws for a non-cloneable payload, the rejection is handled instead of surfacing as an unhandled rejection. Consumers can also pass onBroadcastError to log the offending broadcast message in production, while development still gets a queryHash-aware warning.

Testing

  • corepack pnpm --filter @tanstack/query-broadcast-client-experimental exec vitest run src/__tests__/index.test.ts
  • corepack pnpm exec prettier --check .changeset/broadcast-client-post-errors.md packages/query-broadcast-client-experimental/src/index.ts packages/query-broadcast-client-experimental/src/__tests__/index.test.ts
  • corepack pnpm exec eslint --concurrency=auto -c eslint.config.js packages/query-broadcast-client-experimental/src/index.ts packages/query-broadcast-client-experimental/src/__tests__/index.test.ts
  • corepack pnpm exec tsc -p packages/query-broadcast-client-experimental/tsconfig.prod.json --noEmit --pretty false
  • corepack pnpm exec tsup src/index.ts --no-config --format cjs,esm --target es2020,node16 --dts --sourcemap --clean --out-dir build/legacy --tsconfig tsconfig.prod.json
  • git diff --check

Note: the package test:types:tscurrent and build scripts still hit the existing Windows symlink-placeholder checkout issue for root.eslint.config.js / root.tsup.config.js, so I validated the changed source with the direct TypeScript and tsup --no-config commands above.

Summary by CodeRabbit

  • New Features

    • Added an onBroadcastError callback option to surface broadcast transmission failures.
    • Added development-time warnings for failed broadcasts including message type and query identifier.
  • Bug Fixes

    • Prevented unhandled rejections from broadcast message posting failures.
  • Tests

    • Added regression tests validating error reporting and warnings when broadcasts fail.
  • Documentation

    • Added a changeset documenting the patch release handling broadcast postMessage failures.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 24, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f9855aef-a549-4acf-9c06-a975d1d325c6

📥 Commits

Reviewing files that changed from the base of the PR and between c543e8c and e11f994.

📒 Files selected for processing (1)
  • packages/query-broadcast-client-experimental/src/__tests__/index.test.ts

📝 Walkthrough

Walkthrough

Wraps BroadcastChannel.postMessage calls in a helper that catches sync throws and promise rejections, invokes an optional onBroadcastError callback with the error and message, logs a dev warning with message type and queryHash, adds tests simulating rejection/sync-throw, and adds a changeset for a patch release.

Changes

Broadcast postMessage Error Handling

Layer / File(s) Summary
Type Contract and Options Signature
packages/query-broadcast-client-experimental/src/index.ts
BroadcastQueryClientOptions now includes onBroadcastError?: (error: unknown, message: BroadcastMessage) => void; introduces BroadcastMessage union for updated/removed/added payloads.
Error Handling Mechanism
packages/query-broadcast-client-experimental/src/index.ts
New handleBroadcastError and postMessage helper catch sync throws and promise rejections from channel.postMessage, call onBroadcastError when provided, and emit a development-time console.warn including message type and queryHash.
Broadcast Event Integration
packages/query-broadcast-client-experimental/src/index.ts
Replaces direct channel.postMessage(...) calls for updated, removed, and added broadcasts with the postMessage(...) helper to ensure uniform error handling.
Tests and Release Documentation
packages/query-broadcast-client-experimental/src/__tests__/index.test.ts, .changeset/broadcast-client-post-errors.md
Adds a hoisted BroadcastChannel mock, two regression tests that simulate postMessage rejection and synchronous throws verifying onBroadcastError and dev warnings, and a changeset documenting the patch release.

Sequence Diagram

sequenceDiagram
  participant App as Application Code
  participant BQC as broadcastQueryClient
  participant Helper as postMessage Helper
  participant Chan as BroadcastChannel
  participant CB as onBroadcastError Callback
  
  App->>BQC: setQueryData(...ReadableStream...)
  BQC->>Helper: postMessage(updated message)
  Helper->>Chan: channel.postMessage(message)
  Chan-->>Helper: rejects / throws (DataCloneError)
  Helper->>CB: onBroadcastError(error, message)
  Helper->>Helper: console.warn (dev) with type + queryHash
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A broadcast that once crashed in silence,
now whispers its pain with clear guidance.
Streams no longer break the sync,
errors caught — no more brink.
a tiny hop toward safer tabs.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: handling postMessage errors in the broadcast client, which is the core objective of this PR.
Description check ✅ Passed The description references fixed issues (#10542, #10543), explains the implementation (guarded postMessage helper, onBroadcastError callback), and details comprehensive testing steps. However, the checklist items are not marked as completed.
Linked Issues check ✅ Passed The PR successfully addresses all objectives from issue #10542: prevents unhandled rejections via try/catch in a postMessage helper, adds onBroadcastError callback for consumer logging, includes regression tests for both sync and async broadcast failures, and provides queryHash-aware warnings in development.
Out of Scope Changes check ✅ Passed All changes are directly scoped to handling postMessage errors in broadcastQueryClient: a changeset, updated index.ts with error handling logic, and comprehensive test coverage. No extraneous modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/query-broadcast-client-experimental/src/__tests__/index.test.ts (1)

64-99: ⚡ Quick win

Add a regression test for synchronous postMessage throws.

This test only exercises promise rejection. Adding a sibling case where channel.postMessage throws synchronously would lock in the try/catch path in the helper.

Suggested test addition
+  it('should report synchronous postMessage throws', async () => {
+    const error = new DOMException('cannot clone', 'DataCloneError')
+    const onBroadcastError = vi.fn()
+    const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
+
+    broadcastQueryClient({
+      queryClient,
+      broadcastChannel: 'test_channel',
+      onBroadcastError,
+    })
+
+    const channel = broadcastChannelMock.channels[0]!
+    channel.postMessage.mockImplementation(() => {
+      throw error
+    })
+
+    queryClient.setQueryData(['stream'], new ReadableStream())
+
+    await vi.waitFor(() => {
+      expect(onBroadcastError).toHaveBeenCalledWith(
+        error,
+        expect.objectContaining({
+          type: 'updated',
+          queryHash: '["stream"]',
+          queryKey: ['stream'],
+        }),
+      )
+    })
+
+    expect(consoleWarnSpy).toHaveBeenCalledWith(
+      '[broadcastQueryClient] failed to broadcast "updated" for queryHash "["stream"]"',
+      error,
+    )
+  })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/query-broadcast-client-experimental/src/__tests__/index.test.ts`
around lines 64 - 99, Add a sibling test in the same file that mirrors the
existing "should report postMessage rejections..." spec but simulates a
synchronous throw from channel.postMessage instead of returning a rejected
Promise: create a DOMException, spy on console.warn, call
broadcastQueryClient(...) (same args), find broadcastChannelMock.channels[0],
set channel.postMessage to a function that throws the error immediately, invoke
queryClient.setQueryData(['stream'], new ReadableStream()), then await
vi.waitFor to assert onBroadcastError was called with the error and message
shape (type: 'updated', queryHash: '["stream"]', queryKey: ['stream']), and
assert console.warn was called with the same failure message and error—this
locks in the helper's try/catch path for synchronous throws (reference symbols:
broadcastQueryClient, broadcastChannelMock, channel.postMessage,
onBroadcastError, queryClient.setQueryData).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/query-broadcast-client-experimental/src/__tests__/index.test.ts`:
- Around line 64-99: Add a sibling test in the same file that mirrors the
existing "should report postMessage rejections..." spec but simulates a
synchronous throw from channel.postMessage instead of returning a rejected
Promise: create a DOMException, spy on console.warn, call
broadcastQueryClient(...) (same args), find broadcastChannelMock.channels[0],
set channel.postMessage to a function that throws the error immediately, invoke
queryClient.setQueryData(['stream'], new ReadableStream()), then await
vi.waitFor to assert onBroadcastError was called with the error and message
shape (type: 'updated', queryHash: '["stream"]', queryKey: ['stream']), and
assert console.warn was called with the same failure message and error—this
locks in the helper's try/catch path for synchronous throws (reference symbols:
broadcastQueryClient, broadcastChannelMock, channel.postMessage,
onBroadcastError, queryClient.setQueryData).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e0417935-b2d1-4c3a-b58f-940a70816dc6

📥 Commits

Reviewing files that changed from the base of the PR and between ba6e7be and c543e8c.

📒 Files selected for processing (3)
  • .changeset/broadcast-client-post-errors.md
  • packages/query-broadcast-client-experimental/src/__tests__/index.test.ts
  • packages/query-broadcast-client-experimental/src/index.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[query-broadcast-client-experimental] postMessage failures surface as unhandled DataCloneError rejections

1 participant