Skip to content

Fix React Native runtime visibility packaging#4683

Open
KyleAMathews wants to merge 1 commit into
mainfrom
fix-rn-runtime-visibility-detection
Open

Fix React Native runtime visibility packaging#4683
KyleAMathews wants to merge 1 commit into
mainfrom
fix-rn-runtime-visibility-detection

Conversation

@KyleAMathews

Copy link
Copy Markdown
Contributor

Fixes React Native/Expo app lifecycle handling for ShapeStream by replacing brittle runtime require('react-native') probing with a Metro/Expo react-native package export. React Native builds now wire AppState automatically, while runtimeVisibility remains available as an explicit escape hatch for custom bundlers/runtimes.

Root cause

The previous React Native visibility detection depended on runtime access to require('react-native') via globalThis.require or Function('return require'). In Metro/Hermes builds, require is available at module scope but not necessarily on globalThis, and the function fallback can return undefined. When that happened, runtimeVisibility stayed unset and the AppState listener was never installed.

Approach

  • Remove the brittle runtime React Native detection from ShapeStream.
  • Add a React Native-specific entrypoint (src/react-native.ts) that statically imports AppState from react-native and registers the default runtime visibility adapter.
  • Add a react-native conditional package export, plus a legacy top-level react-native field, so Metro/Expo native builds resolve the RN entrypoint from standard imports:
import { ShapeStream } from '@electric-sql/client'
  • Keep runtimeVisibility as the explicit override/fallback for custom runtimes or Metro configs that do not resolve the react-native condition.
  • Mark only the React Native entrypoints as side-effectful in package.json so the AppState registration is not tree-shaken while preserving tree-shaking for the rest of the package.
  • Expose both ESM and CJS targets under the RN conditional export to match the package's existing dual-format publishing.

Key invariants

  • Browser/Node/default imports do not statically import react-native.
  • React Native/Metro imports run the RN entrypoint and install the AppState-backed adapter.
  • User-provided runtimeVisibility still wins over the default adapter.
  • Hidden/background state pauses streams and aborts in-flight requests; visible/active state resumes with non-live catch-up.
  • Non-RN package entrypoints remain side-effect-free for tree-shaking.

Non-goals

  • Do not add react-native as a runtime dependency of @electric-sql/client.
  • Do not rely on additional global require probing or Metro internals.
  • Do not replace the existing runtimeVisibility API; it remains the escape hatch.
  • Do not introduce an RN-specific ShapeStream wrapper in this PR.

Trade-offs

The RN entrypoint uses top-level registration through a small shared factory. This keeps the main ShapeStream implementation simple and avoids duplicating the class for React Native, but it means the RN entrypoint must be marked as side-effectful. The sideEffects allowlist is limited to the RN entry files so other package entrypoints remain tree-shakable.

Verification

pnpm install
pnpm --dir packages/typescript-client run typecheck
pnpm --dir packages/typescript-client run stylecheck
cd packages/typescript-client && pnpm build

I also ran the targeted wake-detection unit test with a temporary Vitest config that skips the integration global setup; all 15 tests in test/wake-detection.test.ts passed. The normal package test command attempted to run global integration setup and timed out waiting for Electric to be active in this environment.

Files changed

  • .changeset/fresh-ravens-foreground.md — patch changeset for @electric-sql/client.
  • packages/typescript-client/package.json — adds RN conditional export, top-level RN field, CJS RN target, and sideEffects allowlist for RN entrypoints.
  • packages/typescript-client/tsup.config.ts — builds the RN entrypoint and externalizes react-native.
  • packages/typescript-client/src/client.ts — removes runtime RN probing and reads the optional default runtime visibility factory.
  • packages/typescript-client/src/react-native.ts — RN entrypoint that wires AppState lifecycle handling and re-exports the normal client API.
  • packages/typescript-client/src/runtime-visibility.ts — shared default runtime visibility factory registration.
  • packages/typescript-client/src/react-native-shim.d.ts — minimal local typing for the RN entrypoint without adding an RN dependency.
  • packages/typescript-client/test/wake-detection.test.ts — updates the RN test to exercise the RN entrypoint instead of global require detection.
  • packages/typescript-client/SPEC.md — updates the pause-lock notes to describe RN handling through package exports.
  • packages/typescript-client/README.md, website/docs/sync/api/clients/typescript.md, website/docs/sync/integrations/expo.md — document automatic Metro/Expo handling and explicit runtimeVisibility fallback.

@pkg-pr-new

pkg-pr-new Bot commented Jul 2, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@electric-sql/react@4683
npm i https://pkg.pr.new/@electric-sql/client@4683
npm i https://pkg.pr.new/@electric-sql/y-electric@4683

commit: b3b3222

@netlify

netlify Bot commented Jul 2, 2026

Copy link
Copy Markdown

Deploy Preview for electric-next ready!

Name Link
🔨 Latest commit b3b3222
🔍 Latest deploy log https://app.netlify.com/projects/electric-next/deploys/6a46bbc4672a850008fe6836
😎 Deploy Preview https://deploy-preview-4683--electric-next.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 73.63%. Comparing base (9e3af10) to head (b3b3222).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@             Coverage Diff             @@
##             main    #4683       +/-   ##
===========================================
+ Coverage   60.03%   73.63%   +13.60%     
===========================================
  Files         395       88      -307     
  Lines       43763    10088    -33675     
  Branches    12587     3102     -9485     
===========================================
- Hits        26271     7428    -18843     
+ Misses      17411     2603    -14808     
+ Partials       81       57       -24     
Flag Coverage Δ
packages/agents ?
packages/agents-mcp ?
packages/agents-mobile ?
packages/agents-runtime ?
packages/agents-server 75.54% <ø> (ø)
packages/agents-server-ui ?
packages/electric-ax 51.06% <ø> (ø)
packages/experimental 87.73% <ø> (ø)
packages/react-hooks 86.48% <ø> (ø)
packages/start ?
packages/typescript-client 91.89% <100.00%> (+0.02%) ⬆️
packages/y-electric 56.05% <ø> (ø)
typescript 73.63% <100.00%> (+13.60%) ⬆️
unit-tests 73.63% <100.00%> (+13.60%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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.

1 participant