Persistent Store
Persistent store is the WAL-backed local storage mode of the Softadastra JavaScript SDK.
It lets your application write locally while keeping accepted operations recoverable after restart.
The core rule is:
Write locally. Persist locally. Recover later.
A persistent store is still local-first. It does not require a server, peer, transport, or discovery to accept local work.
Why persistent store exists
Memory-only local store is useful for tests, demos, and temporary state. But real applications often need local data to survive process restart, machine restart, application crash, network interruption, or sync interruption.
Persistent store solves this by using a WAL-style persistence path.
local write
↓
persistent operation record
↓
local store apply
↓
sync trackingPersistence -> durable history
Store -> current valueBasic persistent example
import { Client, ClientOptions } from "@softadastra/sdk";
const options = ClientOptions.local("node-persistent");
options.enableTransport = false;
options.enableDiscovery = false;
options.enableWal = true;
options.walPath = "data/sdk-persistent-store.wal";
options.autoFlush = true;
const client = new Client(options);
const openResult = await client.open();
if (openResult.isErr()) {
console.error(`failed to open client: ${openResult.error().message}`);
process.exit(1);
}
const putResult = await client.put("settings/theme", "dark");
if (putResult.isErr()) {
console.error(`failed to store value: ${putResult.error().message}`);
await client.close();
process.exit(1);
}
const valueResult = await client.get("settings/theme");
if (valueResult.isErr()) {
console.error(`failed to read value: ${valueResult.error().message}`);
await client.close();
process.exit(1);
}
const syncResult = await client.syncStateInfo();
console.log("key : settings/theme");
console.log(`value : ${valueResult.value().toString()}`);
console.log(`wal path : ${options.walPath}`);
console.log(`store size : ${client.size()}`);
if (syncResult.isOk()) {
console.log(`outbox size : ${syncResult.value().outboxSize}`);
}
await client.close();Expected output:
key : settings/theme
value : dark
wal path : data/sdk-persistent-store.wal
store size : 1
outbox size : 1Create the data directory
mkdir -p dataIf the directory does not exist, opening the client or writing persistent data can fail.
Recommended WAL path pattern: data/<node-id>.wal.
Persistent configuration
options.enableWal = true;
options.walPath = "data/sdk-persistent-store.wal";
options.autoFlush = true;Persistent helper
const options = ClientOptions.persistent(
"node-persistent",
"data/sdk-persistent-store.wal",
);
options.autoFlush = true;
options.enableTransport = false;
options.enableDiscovery = false;WAL and recovery
The recovery model is:
process starts
↓
client.open()
↓
persistent log is read
↓
valid operations are replayed
↓
local store is restoredTest persistence manually
Create persistent-test.js:
import { Client, ClientOptions } from "@softadastra/sdk";
const options = ClientOptions.local("node-persistent-test");
options.enableTransport = false;
options.enableDiscovery = false;
options.enableWal = true;
options.walPath = "data/persistent-test.wal";
options.autoFlush = true;
{
const client = new Client(options);
const opened = await client.open();
if (opened.isErr()) {
console.error(opened.error().message);
process.exit(1);
}
await client.put("app/name", "Softadastra");
console.log("first run value written");
await client.close();
}
{
const client = new Client(options);
const opened = await client.open();
if (opened.isErr()) {
console.error(opened.error().message);
process.exit(1);
}
const value = await client.get("app/name");
if (value.isErr()) {
console.error(`recovery failed: ${value.error().message}`);
await client.close();
process.exit(1);
}
console.log(`recovered value: ${value.value().toString()}`);
await client.close();
}Before running:
mkdir -p dataRun:
node persistent-test.jsExpected output:
first run value written
recovered value: SoftadastraPersistent store and sync state
await client.put("profile/name", "Ada");
const state = await client.syncStateInfo();
if (state.isOk()) {
console.log(`outbox : ${state.value().outboxSize}`);
}
const tick = await client.tick();Persistence -> makes local operation recoverable
Sync -> tracks operation for propagationWAL path per node
// Node A
const nodeA = ClientOptions.persistent("node-a", "data/node-a.wal");
// Node B
const nodeB = ClientOptions.persistent("node-b", "data/node-b.wal");One node → one WAL path.
Run the SDK example
cd ~/softadastra/sdk-js
npm install
mkdir -p data
npm run examples:persistent-storeVerify the WAL file was created:
ls -la dataError handling
Always check results:
const result = await client.put("settings/theme", "dark");
if (result.isErr()) {
console.error(result.error().message);
await client.close();
process.exit(1);
}Missing directory
mkdir -p dataInvalid WAL path
// wrong
options.enableWal = true;
options.walPath = "";
// correct
options.enableWal = true;
options.walPath = "data/app.wal";Persistent store API reference
| Method | Purpose |
|---|---|
put(key, value) | Write or update a local value |
get(key) | Read a local value |
remove(key) | Remove a local value |
contains(key) | Check whether a key exists |
size() | Return the number of local entries |
empty() | Check whether the store is empty |
syncStateInfo() | Inspect sync work created by writes |
tick() | Move sync forward once |
The difference from local store is configuration:
options.enableWal = true;
options.walPath = "data/app.wal";
options.autoFlush = true;Common mistakes
Forgetting mkdir -p data
If your WAL path is under data/, create the directory first.
Expecting WAL to mean synced
WAL means locally persisted. Sync state tells you whether work is pending, queued, acknowledged, or failed.
Disabling auto flush for important data
Keep options.autoFlush = true for normal durable examples.
Editing the WAL manually
The WAL is an internal runtime file. Do not edit it by hand.
Summary
Persistent store adds WAL-backed durability to the local store.
The key idea is: persistent means locally recoverable. It does not automatically mean remotely synced.
Next step
Continue with sync: