The app's synchronous key-value store, backed by a crash-safe log on iOS and Android
One.Storage is the app’s key-value store: string keys, string values, and
every call synchronous, with the same method names as the web’s
localStorage. Use it for settings, small caches, and app state you want
back on the next launch.
On iOS and Android it is one C++ engine that keeps every entry in memory and
records each write in a memory-mapped log, so reads cost a lookup and writes
survive a crash. On web it is localStorage. For secrets use
Secure Store; for structured or large data, or for
several separate stores, use Database.
import { useState } from 'react'import { Switch } from 'react-native'import { One } from 'one'
function DarkModeSetting() { const [dark, setDark] = useState(() => One.Storage.getItem('theme') === 'dark') return ( <Switch value={dark} onValueChange={(next) => { setDark(next) One.Storage.setItem('theme', next ? 'dark' : 'light') }} /> )}Because reads are synchronous, the saved value is there on the first render, with no loading state and no flash of the default.
One engine for both platforms. The store is a Nitro module written in C++, called straight through JSI. iOS and Android share the same code; only the folder differs: Application Support on iOS and the app’s files folder on Android, both private to the app.
Reads. The first call replays the log once and builds a map of every key to where its value sits in the mapped file. After that a read is a lookup and a copy out of memory; it never waits on the disk.
Writes. A write appends one record to the log through the memory map,
with no system call. The mapped pages belong to the operating system, so once
setItem returns, the value survives the app crashing. Each record’s type
byte is written after its body, so a crash mid-write leaves either the whole
record or nothing, and the next launch replays up to the last whole one.
Compaction. When the log fills, it is rewritten as a snapshot of the live entries if at least half of it is dead, or doubled otherwise. The snapshot is written to a temporary file, synced, and renamed over the log, so a crash during compaction leaves one complete log or the other.
Web. Each key is stored in localStorage with a One.Storage. prefix,
so getAllKeys lists only this store’s entries.
| Method | Returns |
|---|---|
| getItem(key) | The value, or null |
| setItem(key, value) | Nothing |
| removeItem(key) | Nothing |
| getAllKeys() | Every key in the store |
| iOS | Android | Web | |
|---|---|---|---|
| Engine | C++ log | C++ log | Local storage |
| Folder | Application Support | App files | The browser’s |
| Crash safe | Once the call returns | Once the call returns | The browser’s |
JSON.stringify.One.Database.openKeyValue for a store of your own.import { One } from 'one'
type Filters = { sort: 'new' | 'top'; tags: string[] }
const defaults: Filters = { sort: 'new', tags: [] }
function loadFilters(): Filters { const saved = One.Storage.getItem('feed.filters') return saved ? { ...defaults, ...JSON.parse(saved) } : defaults}
function saveFilters(filters: Filters) { One.Storage.setItem('feed.filters', JSON.stringify(filters))}Spreading over defaults keeps older saved data working when you add a field.
The feed. prefix keeps this feature’s keys apart from others.
import { useState } from 'react'import { Button, Text, View } from 'react-native'import { One } from 'one'
function Welcome({ children }: { children: React.ReactNode }) { const [seen, setSeen] = useState(() => One.Storage.getItem('onboarding.done') !== null) if (seen) return <>{children}</> return ( <View> <Text>Welcome</Text> <Button title="Get started" onPress={() => { One.Storage.setItem('onboarding.done', '1') setSeen(true) }} /> </View> )}The check runs during the first render, so people who finished onboarding never see it flash before the app.
Edit this page on GitHub.