tile-live-layer
tile-live-layer
Remote config SDK for Tile (Expo / React Native) apps.
Fetches a per-(app, environment) nested JSON config from the CDN, sliced by
a generic category, caches it, and reads values on-device with a 4-layer
fallback so a read never fails and the app never blocks on the network:
live (network) → cached (AsyncStorage) → bootstrap (baked at build) → code default
Config is arbitrary nested JSON — read any path with
useLiveLayer(['home', 'cta', 'label']). It is fully decoupled from OTA
bundles.
Install
npm install tile-live-layer
# AsyncStorage enables cross-launch persistence (optional but recommended):
npx expo install @react-native-async-storage/async-storageUsage
import { LiveLayerProvider, useLiveLayer } from 'tile-live-layer';
import bootstrap from './assets/tile-live-layer.json'; // baked at build time
export default function App() {
return (
<LiveLayerProvider
options={{
appId: 'YOUR_APP_ID',
env: 'prod', // 'dev' | 'staging' | 'prod' | 'preview'
baseUrl: 'https://storage.googleapis.com/tile-livelayer-configs',
// The category this user/session belongs to, computed from on-device
// signals at boot. The SDK fetches the longest existing prefix of this
// key (falling back to the base layer []).
context: ['in', 'mumbai'],
bootstrap,
refreshOnForeground: true,
}}
>
<Screen />
</LiveLayerProvider>
);
}
function Screen() {
const label = useLiveLayer<string>(['home', 'cta', 'label'], 'Shop now');
const timeout = useLiveLayer<number>(['checkout', 'timeoutMs'], 8000);
// ...
}Categories
A category is an ordered array of arbitrary strings (['in', 'mumbai']) — region,
tier, A/B bucket, anything. The app declares which category it belongs to via the
provider's context; the SDK fetches the longest existing prefix of it from
the manifest, falling back through the chain to the base layer:
['in','mumbai','pro'] → in/mumbai/pro → in/mumbai → in → (base)
Each category is one already-resolved flat document — the SDK fetches exactly one file and does no merging on-device.
Hooks
useLiveLayer(path, fallback?)— read one nested value, e.g.useLiveLayer(['home','cta','label'], 'Shop now').useLiveLayerAll()— the whole active config document.useLiveLayerStatus()—{ config, category, version, status, source, error, refresh }.
Debug panel
Render anywhere inside the provider to see live status + every resolved value:
import { LiveLayerDebugPanel } from 'tile-live-layer';
<LiveLayerDebugPanel />Build-time snapshot & management — the tile CLI
Management and build-time snapshots are handled by the unified
tile CLI (npm i -g @tile/cli).
# bake the current published base config into your assets so first launch
# (no network, no cache) still shows real values:
tile live-layer pull --app YOUR_APP_ID --env prod --out assets/tile-live-layer.json
# manage config headless (CI / agents) — auth via `tile login` or TILE_TOKEN:
tile live-layer get # base config
tile live-layer get home.cta.label --category in/mumbai # one nested path
tile live-layer set home.cta.label="₹500 off" --category in/mumbai
tile live-layer publish --file config.json --category in/mumbai # replace whole category
tile live-layer layers # list categories
tile live-layer versions --category in/mumbai
tile live-layer rollback --to 3 --category in/mumbai
tile live-layer rm-layer in/mumbai # delete a categoryWrites are atomic per category (optimistic-locked, auto-retry once on a 409).
How serving works
The backend publishes a no-cache manifest plus one immutable blob per category:
live-layer/{appId}/{env}/manifest.json no-cache (which categories exist + current blob)
live-layer/{appId}/{env}/v/{N}.json immutable (base layer)
live-layer/{appId}/{env}/{category}/v/{N}.json immutable (e.g. in/mumbai/v/3.json)
The SDK reads the manifest each launch (cheap, non-blocking via
stale-while-revalidate), picks the longest matching category, and fetches that
one immutable blob at most once per version per device. Point baseUrl at a CDN
domain later with no code change.