Vix.cpp docs moved to vixcpp.com
Skip to content

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.

txt
local write

persistent operation record

local store apply

sync tracking
txt
Persistence -> durable history
Store       -> current value

Basic persistent example

js
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:

txt
key          : settings/theme
value        : dark
wal path     : data/sdk-persistent-store.wal
store size   : 1
outbox size  : 1

Create the data directory

sh
mkdir -p data

If the directory does not exist, opening the client or writing persistent data can fail.

Recommended WAL path pattern: data/<node-id>.wal.

Persistent configuration

js
options.enableWal = true;
options.walPath = "data/sdk-persistent-store.wal";
options.autoFlush = true;

Persistent helper

js
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:

txt
process starts

client.open()

persistent log is read

valid operations are replayed

local store is restored

Test persistence manually

Create persistent-test.js:

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:

sh
mkdir -p data

Run:

sh
node persistent-test.js

Expected output:

txt
first run value written
recovered value: Softadastra

Persistent store and sync state

js
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();
txt
Persistence -> makes local operation recoverable
Sync        -> tracks operation for propagation

WAL path per node

js
// 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

sh
cd ~/softadastra/sdk-js
npm install
mkdir -p data
npm run examples:persistent-store

Verify the WAL file was created:

sh
ls -la data

Error handling

Always check results:

js
const result = await client.put("settings/theme", "dark");

if (result.isErr()) {
  console.error(result.error().message);
  await client.close();
  process.exit(1);
}

Missing directory

sh
mkdir -p data

Invalid WAL path

js
// wrong
options.enableWal = true;
options.walPath = "";

// correct
options.enableWal = true;
options.walPath = "data/app.wal";

Persistent store API reference

MethodPurpose
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:

js
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:

Go to Sync

Released under the Apache License 2.0.