A codegen-free "tolerant store" mode — decouple store schema evolution from native rebuilds
Les mainteneurs répondent en général sous 4 jours
Personne n'a encore pris cette issue.
Évaluation
- Difficulté
- 5/5
- Temps estimé
- Plus d'une semaine
- Accessibilité débutants
- 38/100
- Type d'issue
- Fonctionnalité
- Clarté
- Plutôt claire
- Activité
- Calme
- Stack technique
- cpp, kotlin, react-native, swift, typescript
- Domaine
- developer-experience, mobile, mobile-dev
Piste de recherche
La proposition nomme le store JSI C++ utilisant folly::dynamic, StoreManager, nativeStoreDidChange(key), ainsi que les chemins Swift Codable et Kotlin @Serializable ; commencez par localiser ces points d’entrée. La tâche est terminée lorsqu’un mode tolerant-store documenté et pris en charge, avec le wiring proposé, existe et que le comportement a été validé sur TypeScript, iOS, Android et C++.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Description
Summary
Add an optional, codegen-free "tolerant store" mode alongside the existing generated-types path. Instead of generating exact native mirrors of the TS store type, the native side tolerantly (de)serializes and ignores unknown properties, and the shape is described only in TS.
This lets store-shape changes ship over-the-air (new RN bundle) without forcing a native rebuild + app-store release, while keeping the current strict/generated mode available for teams that want compile-time guarantees.
We run this variant in production today and would like to propose upstreaming it as a documented, supported mode. We're happy to contribute the docs + wiring + an optional validator as PRs.
Motivation
Codegen is great for strictness, but it couples every store-shape change to a native build + app-store release cycle:
add a field → regenerate Swift/Kotlin → recompile the native binary → ship through review
The JS bundle alone can't introduce a new store or field that the native side will accept.
In brownfield apps, the native shell and the RN bundle very often ship on different cadences (native = store review; RN = OTA / CodePush / remote bundle). Forcing a native rebuild for a pure data-shape change breaks that decoupling — which is the whole reason teams adopt OTA in the first place.
Proposed solution: the "tolerant store" model
C++ holds the full state as a dynamic value (folly::dynamic) — it's already the bridge representation. This is the lossless source of truth; it carries every field, including ones a given platform doesn't know about.
Each side decodes only the subset it declares, ignoring the rest:
- TypeScript — describe the shape with the usual
declare moduleaugmentation and use it. No generation step. - Swift — a plain
Codablestruct.JSONDecoder/Codablealready ignores unknown keys by default; extra fields in the dynamic state simply aren't decoded, no error. - Kotlin — a
@Serializabledata class decoded withJson { ignoreUnknownKeys = true }— same tolerance.
Writes are field-scoped merges: a partial setState({ a }) merges into the dynamic state in C++, so fields owned by other consumers are preserved across a write from any side. Unknown-to-this-platform fields survive a native write because they live in the dynamic layer, not in the platform struct.
Net effect: TS is the place you add a field; native keeps working without recompilation as long as it doesn't need to read the new field. The day native wants that field, you add it to the native struct — still no codegen, just one property.
Real-world architecture (our production setup)
This isn't hypothetical. Our brownfield native shell hosts a single RN runtime, and all UI widgets are Re.Pack Module-Federation remotes loaded into that one JS context. Everything — native and every federated widget — reads and writes the same hostconfig store through our store layer (Unistore). Each remote ships on its own cadence, so a shared-config field change cannot be allowed to force a native rebuild.
flowchart TB
subgraph NATIVE["Native host (brownfield shell)"]
iOS["iOS · Swift<br/>Codable struct"]
AND["Android · Kotlin<br/>@Serializable data class"]
end
subgraph CORE["Shared state backbone"]
CPP["C++ JSI store · folly::dynamic<br/>source of truth · dedup · change bus"]
HC["hostconfig store<br/>theme · locale · session · feature flags · env"]
CPP --- HC
end
subgraph RN["Single RN runtime — one JS context"]
SM["StoreManager<br/>one JS cache + listeners"]
subgraph MF["Re.Pack · Module Federation"]
W1["Widget A<br/>(MF remote)"]
W2["Widget B<br/>(MF remote)"]
W3["Widget C<br/>(MF remote)"]
end
SM --- W1
SM --- W2
SM --- W3
end
iOS <-->|hostconfig r/w| CPP
AND <-->|hostconfig r/w| CPP
CPP <-->|"JSI bridge · nativeStoreDidChange(key)"| SM
Data flow for hostconfig:
- The native shell writes
hostconfig(theme / locale / session / flags) into the C++folly::dynamicstore at startup and on any change. - C++ emits a per-key
nativeStoreDidChange(key);StoreManagerrefreshes only that store and notifies its listeners. - Every federated widget in the shared JS context reads
hostconfigfrom the sameStoreManagercache — one source of truth, no per-remote copy. - A widget can write back (e.g. toggling a preference); the partial merge lands in the C++ dynamic store and propagates to native and to the other widgets — bidirectionally.
Why this validates the proposal: hostconfig is consumed by N independently-deployed remotes. With codegen, adding one field means *regenerate native types → rebuild + re-release the native shell → only
- Langage dominant
- TypeScript
- Étoiles
- 552
- Forks
- 53
- Merge moyen
- 3 j 13 h
- PR mergées (30 j)
- 16
Préparer son environnement
- Aucun Dockerfile ni fichier Docker Compose
- Aucun modèle de pull request
- Lire le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Autres issues de callstack/react-native-brownfield
-
Android: generated ReactNativeHostManager loads React Native twice and crashes (Feature flags cannot be overridden more than once)Peut-être pris @hurali97 l’a pris il y a 1 jour. Ouverte
Difficulté 2/5 1-3 heures Accessibilité débutants 72/100
callstack/react-native-brownfield#492 · 1 commentaire ·
Les mainteneurs répondent en général sous 4 jours
-
Flaky Android Expo Detox E2E: native greeting never shows up / app drops Detox connectionPeut-être pris Une pull request liée à cette issue est ouverte ou déjà fusionnée. Ouverte
Difficulté 4/5 3-5 jours Accessibilité débutants 45/100
callstack/react-native-brownfield#499 ·
Les mainteneurs répondent en général sous 4 jours
-
Difficulté 4/5 3-5 jours Accessibilité débutants 48/100
callstack/react-native-brownfield#498 ·
Les mainteneurs répondent en général sous 4 jours
-
`package:ios --add-spm-package`: allow project-declared extra XCFrameworks as binary targetsOuverte
Difficulté 4/5 3-5 jours Accessibilité débutants 55/100
callstack/react-native-brownfield#468 ·
Les mainteneurs répondent en général sous 4 jours
-
Difficulté 4/5 3-5 jours Accessibilité débutants 48/100
callstack/react-native-brownfield#420 ·
Les mainteneurs répondent en général sous 4 jours
Toutes les issues de callstack/react-native-brownfield
Issues similaires
-
Difficulté 2/5 1-3 heures Accessibilité débutants 62/100
umami-software/umami#4637 ·
Les mainteneurs répondent en général sous 3 jours
-
Hotkeys fire when pressed with the Ctrl key, so browser shortcuts also trigger player actionsPeut-être pris @RaphaelFakhri l’a pris aujourd’hui. Ouverte
Difficulté 2/5 1-3 heures Accessibilité débutants 75/100
muxinc/media-chrome#1332 ·
Les mainteneurs répondent en général sous 5 jours
-
Difficulté 2/5 1-3 heures Accessibilité débutants 78/100
polarsource/polar#15580 ·
Les mainteneurs répondent en général sous 1 jour
-
accepted bug via-triage
Difficulté 2/5 1-3 heures Accessibilité débutants 69/100
pingdotgg/t3code#20931 · 1 commentaire ·
Les mainteneurs répondent en général sous 1 jour
-
bug/1-unconfirmed kind/bug
Difficulté 2/5 1-3 heures Accessibilité débutants 78/100
Les mainteneurs répondent en général sous 1 jour