A codegen-free "tolerant store" mode — decouple store schema evolution from native rebuilds
I maintainer di solito rispondono entro 4 giorni
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 5/5
- Tempo stimato
- Più di una settimana
- Idoneità per principianti
- 38/100
- Tipo di issue
- Funzionalità
- Chiarezza
- Abbastanza chiara
- Stato di attività
- Tranquilla
- Stack tecnologico
- cpp, kotlin, react-native, swift, typescript
- Ambito
- developer-experience, mobile, mobile-dev
Direzione di ricerca
La proposta indica lo store JSI C++ che usa folly::dynamic, StoreManager, nativeStoreDidChange(key) e i percorsi Swift Codable e Kotlin @Serializable; inizia individuando questi punti di ingresso. Il lavoro è completato quando esiste una modalità tolerant-store documentata e supportata, con il wiring proposto, e il comportamento è stato convalidato su TypeScript, iOS, Android e C++.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
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
- Lingua principale
- TypeScript
- Stelle
- 552
- Fork
- 53
- Merge medio
- 5g 9h
- PR unite (30g)
- 17
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di callstack/react-native-brownfield
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 55/100
callstack/react-native-brownfield#468 ·
I maintainer di solito rispondono entro 4 giorni
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 48/100
callstack/react-native-brownfield#420 ·
I maintainer di solito rispondono entro 4 giorni
-
Common network layerForse di nuovo libera @dratwas l’ha presa 2537 giorni fa e non c’è nessuna pull request aperta. Aperta
callstack/react-native-brownfield#23 · 1 assegnatario ·
I maintainer di solito rispondono entro 4 giorni
-
Add support for bottom tabsForse di nuovo libera @krizzu l’ha presa 2537 giorni fa e non c’è nessuna pull request aperta. Aperta
callstack/react-native-brownfield#22 · 1 assegnatario ·
I maintainer di solito rispondono entro 4 giorni
-
Tests for libraryAperta
Difficoltà 4/5 3-5 giorni Idoneità per principianti 42/100
callstack/react-native-brownfield#11 · 2 commenti ·
I maintainer di solito rispondono entro 4 giorni
Tutte le issue di callstack/react-native-brownfield
Issue simili
-
Dependencies view: `getParent` loops forever on untitled documents, extension host runs out of memoryForse già presa Una pull request collegata a questa issue è aperta o già unita. Apertabug
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 2/5 Meno di un'ora Idoneità per principianti 82/100
awslabs/visual-asset-management-system#413 ·
I maintainer di solito rispondono entro 1 giorno
-
bug confirmed perf
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
videojs/video.js#9400 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
bug pending triage scope/agent
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 82/100
I maintainer di solito rispondono entro 1 giorno
-
good first issue hacktoberfest
Difficoltà 2/5 Mezza giornata Idoneità per principianti 70/100
HelpCode-ai/anythingmcp#996 ·
I maintainer di solito rispondono entro 1 giorno