State API
Reactive, shared, and persisted state for your project. Connect a state once and use it like a plain object — changes propagate to all contexts (tabs, popup, background, etc.) and are automatically persisted in IndexedDB so they survive refreshes.
epos.state.connect
Connects to a state. If it doesn’t exist, it’s created with the provided initial value. Returns a reactive object you can mutate directly.
Syntax:
// Connect to the default state
epos.state.connect(initial?: Initial, versioner?: Versioner)
// Connect to a specific named state
epos.state.connect(name?, initial?: Initial, versioner?: Versioner)Types:
type Initial<T extends Record<string, any>> = T | (() => T)
type Versioner = Record<number, () => void>Examples:
// Connect to the default state
const state = await epos.state.connect({ count: 0 })
console.log(state.count) // 0 initially, then +1 on each reload
state.count += 1 // Reactive update, synced across contexts and saved to IDB
// Using lazy initial state
const state = await epos.state.connect(() => ({ count: 0 }))
// Connect to a named state
const chat = await epos.state.connect('chat', { messages: [] })
// Using versioning (migrations)
const state = await epos.state.connect(
{ count: 0 },
{
// Migration from version 0 (no version) to version 1
1() {
this.count = 1
},
// Migration from version 1 to version 2
2() {
delete this.count
this.newProp = true
},
},
)Versioning & migrations
Use a versioner to evolve state shape safely over time. Keys are incremental integers (1, 2, 3, …). Epos stores the current version in :version.
- On first initialization, no migrations run — the latest shape is used and
:versionis set accordingly. - When connecting to existing state, migrations from the stored
:versionup to the latest are applied.
Example
type S = { initial: number; newValue?: number }
const s = await epos.state.connect<S>(
'counter',
{ initial: 1, newValue: 2 },
{
1() {
// `this` is the mutable state snapshot
this.newValue = 2
},
2() {
// future migrations...
},
},
)- If stored state is
{ initial: 1 }with no:version, migration1runs and addsnewValue. - If state is created now for the first time, migrations are not applied and
newValuealready exists.
epos.state.disconnect
Disconnects from a state (stops syncing/reactivity in this context). Data remains persisted.
Usage
epos.state.disconnect(name?)Example
epos.state.disconnect('prefs')epos.state.transaction
Batches multiple mutations into a single reactive update.
Usage
epos.state.transaction(fn)Example
epos.state.transaction(() => {
prefs.theme = 'dark'
prefs.volume = 1
})epos.state.local
Creates a local-only reactive state (no sync, no persistence).
Usage
epos.state.local<T extends object = {}>(state?): TExample
const ui = epos.state.local({ modalOpen: false })
ui.modalOpen = true // local to this contextepos.state.list
Lists known states. Pass { connected: true } to see only states currently connected in this context.
Usage
epos.state.list(filter?: { connected?: boolean }): Promise<{ name: string | null }[]>Example
const names = await epos.state.list()
console.table(names)epos.state.destroy
Removes a state and all its persisted data.
Usage
epos.state.destroy(name?): Promise<void>Example
await epos.state.destroy('prefs')Advanced
epos.state.registerModels(...)— placeholder (advanced feature, docs TBD).epos.state.symbols— placeholder (advanced feature, docs TBD).
Notes
- State objects are reactive (MobX under the hood), so reading properties inside components will re-render them on change.
- Persistence uses IDB; schema changes should be handled via the
versioner.