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

Sync

Sync is the part of the Softadastra JavaScript SDK that tracks local operations and moves them toward synchronization.

A local write can happen first:

js
await client.put("profile/name", "Ada");

Then sync can inspect and move pending work:

js
await client.syncStateInfo();
await client.tick();

The core rule is:

Write locally first. Sync later.

Why sync exists

Softadastra is local-first. That means an application can accept local writes before a peer, server, or transport connection is available.

But local-first applications still need a way to propagate local work later. Sync exists for that.

txt
local write

local store

sync operation

outbox

queue

tick

transport batch, if transport is enabled

Sync is not transport

txt
Sync       -> tracks and prepares operations
Transport  -> sends messages to peers

You can use sync without transport:

js
options.enableTransport = false;
options.enableDiscovery = false;

Basic sync example

js
import { Client, ClientOptions } from "@softadastra/sdk";

const options = ClientOptions.local("node-basic-sync");

options.enableTransport = false;
options.enableDiscovery = false;

options.enableWal = true;
options.walPath = "data/sdk-basic-sync.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("profile/name", "Softadastra");

if (putResult.isErr()) {
  console.error(`failed to submit local value: ${putResult.error().message}`);
  await client.close();
  process.exit(1);
}

const before = await client.syncStateInfo();

if (before.isOk()) {
  console.log("before tick");
  console.log(`  outbox : ${before.value().outboxSize}`);
  console.log(`  queued : ${before.value().queuedCount}`);
  console.log(`  failed : ${before.value().failedCount}`);
}

const tickResult = await client.tick();

if (tickResult.isErr()) {
  console.error(`failed to tick sync pipeline: ${tickResult.error().message}`);
  await client.close();
  process.exit(1);
}

console.log("\ntick result");
console.log(`  retried : ${tickResult.value().retriedCount}`);
console.log(`  pruned  : ${tickResult.value().prunedCount}`);
console.log(`  batch   : ${tickResult.value().batchSize}`);

await client.close();

Expected output style:

txt
before tick
  outbox : 1
  queued : 1
  failed : 0

tick result
  retried : 0
  pruned  : 0
  batch   : 1

syncStateInfo

js
const state = await client.syncStateInfo();

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

console.log(`outbox : ${state.value().outboxSize}`);
console.log(`queued : ${state.value().queuedCount}`);
console.log(`failed : ${state.value().failedCount}`);

Snake_case alias, if exposed: await client.sync_state_info().

Sync state fields

outboxSize — Local operations waiting for synchronization. Does not mean work has been delivered to a peer.

queuedCount — Operations ready to be selected for sending.

inFlightCount — Operations sent or prepared for delivery, possibly waiting for ACK.

acknowledgedCount — Operations confirmed by the remote side.

failedCount — Operations that exceeded the retry policy. Failed sync does not mean the local value disappeared.

totalRetries — Total retry attempts. Helps debug unstable transport or missing ACKs.

tick

js
const tick = await client.tick();

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

console.log(`retried : ${tick.value().retriedCount}`);
console.log(`pruned  : ${tick.value().prunedCount}`);
console.log(`batch   : ${tick.value().batchSize}`);

Tick result fields

retriedCount — Operations retried during this tick.

prunedCount — Completed entries removed during this tick.

batchSize — Operations produced in the current sync batch.

Tick with pruning

If supported:

js
const tick = await client.tick({ prune: true });

Sync without transport

js
options.enableTransport = false;
options.enableDiscovery = false;

You can still call syncStateInfo() and tick(). Transport is only needed for delivery to peers.

Sync with transport

js
options.enableTransport = true;
options.transportHost = "127.0.0.1";
options.transportPort = 4041;

Then:

js
await client.open();
await client.startTransport();

const peer = new Peer("node-b", "127.0.0.1", 4042);
await client.connect(peer);

await client.put("message/1", "hello");
await client.tick();

Sync and failed work

If sync work fails, local data can still exist:

js
await client.put("draft/1", "hello");
const tick = await client.tick();

// Even if sync fails:
const value = await client.get("draft/1"); // still readable locally

Sync API reference

MethodPurpose
syncStateInfo()Read current sync state
sync_state_info()Snake_case alias, if exposed
tick()Run one sync tick
tick(true)Run one tick and prune, if supported
tick({ prune: true })Run one tick and prune, if supported

SyncResult fields

FieldPurpose
outboxSizeNumber of tracked sync entries
queuedCountNumber of queued entries
inFlightCountNumber of in-flight entries
acknowledgedCountNumber of acknowledged entries
failedCountNumber of failed entries
totalRetriesTotal retry attempts

TickResult fields

FieldPurpose
retriedCountOperations retried during the tick
prunedCountCompleted entries pruned
batchSizeOperations produced in the tick batch

Run the SDK example

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

Common mistakes

Expecting sync to mean immediate network delivery

Sync tracks and prepares operations. Transport delivers them.

Treating failed sync as lost local data

Failed sync means propagation failed. Local store data can still exist.

Forgetting to check results

Always check isErr() before reading value().

Summary

Sync in the JavaScript SDK gives you syncStateInfo, tick, outbox visibility, queued work, failed work, retry visibility, and batch visibility.

The key idea is: local writes happen first. Sync makes them observable and movable later.

Next step

Continue with transport:

Go to Transport

Released under the Apache License 2.0.