Store
Lightweight reactive data store with deep proxy observation and path-based subscriptions.
Import #
import Store from '/rom/std/store.js';Overview #
Store is a reactive state container. You read and write properties on the store as if it were a plain object, and subscribers are notified automatically — even for deeply nested changes.
Under the hood, Store wraps its data in ES Proxy objects that intercept every get and set. Writes trigger callbacks registered via subscribe(), using dot-delimited paths to identify which part of the state changed.
Quick example #
import Store from '/rom/std/store.js';
const store = new Store({
page: 'home',
user: { name: { first: 'Alice', last: 'Smith' } }
});
// Subscribe to a top-level key
const off = store.subscribe('page', (val) => {
console.log('page changed to:', val);
});
// Fires immediately with current value: 'home'
// Assign like a normal object
store.page = 'settings'; // subscriber fires: 'settings'
// Deep changes are tracked
store.user.name.first = 'Bob'; // fires 'user.name.first' subscribers
// Unsubscribe when done
off();Constructor #
initial data. The returned object is a Proxy — you can read and write data keys directly on it.new Store(initial?: object): StoreMethods #
callback to be called whenever the value at path changes. Returns an unsubscribe function.path:string— Dot-delimited path, e.g.'user.name.first'.callback:(value) => void— Invoked with the new value on change.immediate:boolean— Iftrue(the default), the callback fires immediately with the current value.
store.subscribe(path: string, callback: (value): void, immediate?: boolean): () : voidnull or undefined.store.get(path: string): anyReactive writes #
Every property write — at any depth — fires subscribers whose path matches the write path exactly. Writes are detected through ES Proxy traps, so no special setter functions are needed.
const store = new Store({ config: { theme: 'dark', fontSize: 14 } });
store.subscribe('config.theme', (theme) => {
console.log('theme:', theme);
});
store.config.theme = 'light'; // subscriber fires with 'light'Subscribers are matched by exact path. A subscriber on 'config' will not fire when 'config.theme' changes; subscribe to the specific path you care about.
Common patterns #
Binding UI to state #
Subscribe to store paths and update Koya elements when values change. This keeps rendering logic reactive without manual polling.
import { Window } from '/rom/std/koya-ui.js';
import Store from '/rom/std/store.js';
const win = await Window.create({ role: 'window', size: { x: 300, y: 100 } });
const label = await win.createElement({
renderable: { type: 'text', size: 16, colour: [1, 1, 1, 1] }
});
await win.attach(label);
const store = new Store({ greeting: 'Hello, world!' });
store.subscribe('greeting', async (val) => {
await label.setTextString(val);
await win.render();
});
// Later…
store.greeting = 'Welcome back!';Page navigation #
Use a store key to represent the active page and subscribe to it for view switching.
const store = new Store({ page: 'home' });
store.subscribe('page', (page) => {
switch (page) {
case 'home': showHome(); break;
case 'settings': showSettings(); break;
}
});
// Navigate
store.page = 'settings';