Skip to content

Fix React Native runtime visibility packaging#4683

Merged
KyleAMathews merged 2 commits into
mainfrom
fix-rn-runtime-visibility-detection
Jul 7, 2026
Merged

Fix React Native runtime visibility packaging#4683
KyleAMathews merged 2 commits into
mainfrom
fix-rn-runtime-visibility-detection

Conversation

@KyleAMathews

@KyleAMathews KyleAMathews commented Jul 2, 2026

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 browser and react-native conditional package exports, plus an explicit ./react-native subpath and legacy top-level react-native field, so Metro/Expo resolve the correct entrypoint for web, native, and explicit React Native 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 and matching .d.ts/.d.cts declaration files under the RN conditional export to match the package's existing dual-format publishing.

Key invariants

  • Browser/Expo web/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 browser/RN conditional exports, explicit ./react-native subpath, top-level RN field, CJS RN target, matching declaration targets, pack-file exclusion for the local shim, 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: 72a5e54

@netlify

netlify Bot commented Jul 2, 2026

Copy link
Copy Markdown

Deploy Preview for electric-next ready!

Name Link
🔨 Latest commit 72a5e54
🔍 Latest deploy log https://app.netlify.com/projects/electric-next/deploys/6a4d4c9d4066a70008bc7d28
😎 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 60.01%. Comparing base (9e3af10) to head (72a5e54).
⚠️ Report is 3 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4683      +/-   ##
==========================================
- Coverage   60.03%   60.01%   -0.02%     
==========================================
  Files         395      397       +2     
  Lines       43763    43766       +3     
  Branches    12587    12585       -2     
==========================================
- Hits        26271    26267       -4     
- Misses      17411    17418       +7     
  Partials       81       81              
Flag Coverage Δ
packages/agents 72.64% <ø> (ø)
packages/agents-mcp 77.70% <ø> (ø)
packages/agents-mobile 80.67% <ø> (ø)
packages/agents-runtime 83.72% <ø> (-0.02%) ⬇️
packages/agents-server 75.47% <ø> (-0.07%) ⬇️
packages/agents-server-ui 8.32% <ø> (ø)
packages/electric-ax 51.06% <ø> (ø)
packages/experimental 87.73% <ø> (ø)
packages/react-hooks 86.48% <ø> (ø)
packages/start 82.83% <ø> (ø)
packages/typescript-client 91.89% <100.00%> (+0.02%) ⬆️
packages/y-electric 56.05% <ø> (ø)
typescript 60.01% <100.00%> (-0.02%) ⬇️
unit-tests 60.01% <100.00%> (-0.02%) ⬇️

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.

@KyleAMathews KyleAMathews requested a review from kevin-dp July 7, 2026 15:02

@kevin-dp kevin-dp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice fix, replacing the runtime require probing with a react-native conditional export is the right approach, and the wiring looks correct (factory is read lazily at construction, both import/require nested under the RN condition, sideEffects allowlist scoped to the RN entries).

A few things to address/verify before merge:

  1. Legacy react-native field points at ESM (./dist/index.react-native.mjs). Consumers running Jest with the RN preset will resolve this field and choke on untranspiled ESM in node_modules (previously they got CJS via main). Older Metro (< 0.73) also can't resolve .mjs at all. Suggest pointing the legacy field at a transpiled .js build (like the existing index.legacy-esm.js), or verifying a packed tarball against a default RN Jest setup.

  2. Type shim leak. src/react-native-shim.d.ts declares module 'react-native' and src/ is published. If that declaration ends up in consumers' TS programs (via the shipped src/ or the dts: { resolve: true } bundle), it conflicts with real RN types. Please check dist/index.react-native.d.ts after build and consider excluding the shim from the published files.

  3. Expo web takes the RN path. Metro's web conditions include react-native, so Expo web builds will use AppState from react-native-web instead of document.visibilitychange. Should be equivalent behavior, but worth a manual check and a note in the docs.

  4. Types nit: under the react-native condition, the flat types points at the ESM .d.ts for both import and require; nesting types per sub-condition (as the existing . conditions do) would be more correct since the CJS build emits a .d.cts.

Suggestions (non-blocking):

  • Add an explicit ./react-native subpath export so non-Metro RN setups can opt in with import '@electric-sql/client/react-native' instead of hand-wiring the adapter.
  • Run publint / @arethetypeswrong/cli against npm pack output (ideally in CI) — this kind of exports-map change is exactly what they catch.
  • Confirm CI actually runs wake-detection.test.ts, since the full suite didn't run locally.

@KyleAMathews KyleAMathews merged commit 2ab02bb into main Jul 7, 2026
63 checks passed
@KyleAMathews KyleAMathews deleted the fix-rn-runtime-visibility-detection branch July 7, 2026 19:07
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.

2 participants