Fix React Native runtime visibility packaging#4683
Conversation
commit: |
✅ Deploy Preview for electric-next ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Codecov Report✅ All modified and coverable lines are covered by tests. 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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
kevin-dp
left a comment
There was a problem hiding this comment.
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:
-
Legacy
react-nativefield points at ESM (./dist/index.react-native.mjs). Consumers running Jest with the RN preset will resolve this field and choke on untranspiled ESM innode_modules(previously they got CJS viamain). Older Metro (< 0.73) also can't resolve.mjsat all. Suggest pointing the legacy field at a transpiled.jsbuild (like the existingindex.legacy-esm.js), or verifying a packed tarball against a default RN Jest setup. -
Type shim leak.
src/react-native-shim.d.tsdeclaresmodule 'react-native'andsrc/is published. If that declaration ends up in consumers' TS programs (via the shippedsrc/or thedts: { resolve: true }bundle), it conflicts with real RN types. Please checkdist/index.react-native.d.tsafter build and consider excluding the shim from the published files. -
Expo web takes the RN path. Metro's web conditions include
react-native, so Expo web builds will useAppStatefromreact-native-webinstead ofdocument.visibilitychange. Should be equivalent behavior, but worth a manual check and a note in the docs. -
Types nit: under the
react-nativecondition, the flattypespoints at the ESM.d.tsfor bothimportandrequire; nestingtypesper 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-nativesubpath export so non-Metro RN setups can opt in withimport '@electric-sql/client/react-native'instead of hand-wiring the adapter. - Run
publint/@arethetypeswrong/cliagainstnpm packoutput (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.
Fixes React Native/Expo app lifecycle handling for ShapeStream by replacing brittle runtime
require('react-native')probing with a Metro/Exporeact-nativepackage export. React Native builds now wireAppStateautomatically, whileruntimeVisibilityremains 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')viaglobalThis.requireorFunction('return require'). In Metro/Hermes builds,requireis available at module scope but not necessarily onglobalThis, and the function fallback can returnundefined. When that happened,runtimeVisibilitystayed unset and the AppState listener was never installed.Approach
ShapeStream.src/react-native.ts) that statically importsAppStatefromreact-nativeand registers the default runtime visibility adapter.browserandreact-nativeconditional package exports, plus an explicit./react-nativesubpath and legacy top-levelreact-nativefield, so Metro/Expo resolve the correct entrypoint for web, native, and explicit React Native imports:runtimeVisibilityas the explicit override/fallback for custom runtimes or Metro configs that do not resolve thereact-nativecondition.package.jsonso the AppState registration is not tree-shaken while preserving tree-shaking for the rest of the package..d.ts/.d.ctsdeclaration files under the RN conditional export to match the package's existing dual-format publishing.Key invariants
react-native.runtimeVisibilitystill wins over the default adapter.Non-goals
react-nativeas a runtime dependency of@electric-sql/client.requireprobing or Metro internals.runtimeVisibilityAPI; it remains the escape hatch.ShapeStreamwrapper in this PR.Trade-offs
The RN entrypoint uses top-level registration through a small shared factory. This keeps the main
ShapeStreamimplementation simple and avoids duplicating the class for React Native, but it means the RN entrypoint must be marked as side-effectful. ThesideEffectsallowlist is limited to the RN entry files so other package entrypoints remain tree-shakable.Verification
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.tspassed. 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-nativesubpath, 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 externalizesreact-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 explicitruntimeVisibilityfallback.