A codegen-free "tolerant store" mode — decouple store schema evolution from native rebuilds
Maintainer antworten meist innerhalb von 4 Tagen
Dieses Issue hat noch niemand übernommen.
Bewertung
- Schwierigkeit
- 5/5
- Geschätzter Aufwand
- Über eine Woche
- Anfängerfreundlichkeit
- 38/100
- Issue-Typ
- Feature
- Klarheit
- Größtenteils klar
- Aktivitätsstatus
- Ruhig
- Tech-Stack
- cpp, kotlin, react-native, swift, typescript
- Bereich
- developer-experience, mobile, mobile-dev
Rechercherichtung
Der Vorschlag nennt den C++-JSI-Store mit folly::dynamic, StoreManager, nativeStoreDidChange(key) sowie die Swift-Codable- und Kotlin-@Serializable-Pfade; beginne damit, diese Einstiegspunkte zu lokalisieren. Die Aufgabe ist abgeschlossen, wenn ein dokumentierter, unterstützter tolerant-store-Modus mit der vorgeschlagenen Verdrahtung vorhanden ist und das Verhalten über TypeScript, iOS, Android und C++ hinweg validiert wurde.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Beschreibung
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
- Vorherrschende Sprache
- TypeScript
- Sterne
- 552
- Forks
- 53
- Ø Merge
- 3 T. 13 Std.
- Gemergte PRs (30 T.)
- 16
Entwicklungsumgebung
- Kein Dockerfile und keine Docker-Compose-Datei
- Keine Pull-Request-Vorlage
- Beitragsleitfaden lesen
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus callstack/react-native-brownfield
-
Android: generated ReactNativeHostManager loads React Native twice and crashes (Feature flags cannot be overridden more than once)Evtl. vergeben @hurali97 hat das vor 1 Tag übernommen. Offen
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 72/100
callstack/react-native-brownfield#492 · 1 Kommentar ·
Maintainer antworten meist innerhalb von 4 Tagen
-
Flaky Android Expo Detox E2E: native greeting never shows up / app drops Detox connectionEvtl. vergeben Ein verknüpfter Pull Request ist offen oder bereits gemergt. Offen
Schwierigkeit 4/5 3-5 Tage Anfängerfreundlichkeit 45/100
callstack/react-native-brownfield#499 ·
Maintainer antworten meist innerhalb von 4 Tagen
-
Schwierigkeit 4/5 3-5 Tage Anfängerfreundlichkeit 48/100
callstack/react-native-brownfield#498 ·
Maintainer antworten meist innerhalb von 4 Tagen
-
Schwierigkeit 4/5 3-5 Tage Anfängerfreundlichkeit 55/100
callstack/react-native-brownfield#468 ·
Maintainer antworten meist innerhalb von 4 Tagen
-
Schwierigkeit 4/5 3-5 Tage Anfängerfreundlichkeit 48/100
callstack/react-native-brownfield#420 ·
Maintainer antworten meist innerhalb von 4 Tagen
Alle Issues in callstack/react-native-brownfield
Ähnliche Issues
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 77/100
TomerAberbach/profiler-md#733 ·
Maintainer antworten meist innerhalb von 1 Tag
-
bug priority: medium
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 68/100
brentspine/brents-toasts#143 ·
-
🐛 bug 🗺️ DB Models & Ops
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 83/100
LibreChat-AI/LibreChat#17214 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 67/100
Wavvon/Wavvon-clients#136 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 62/100
umami-software/umami#4637 ·
Maintainer antworten meist innerhalb von 3 Tagen