Skip to content

Recipes

A per-user preference that the relay cannot correlate

Section titled “A per-user preference that the relay cannot correlate”
// WRONG — the relay can join u:<sam>/pin_doc7 to g:acme/doc7
await engine.put('u', { id: `pin_${docId}`, kind: 'pin', data: { docId } });
// RIGHT
const id = await engine.personalId('pin', docId);
await engine.put('u', { id, kind: 'pin', data: { docId } });

Deterministic (upsert-by-id still merges across your devices), stable through rotation, linking and recovery, and unlinkable to any shared id without a key only you hold. This is a rule apps must follow, not an optimisation.

const s = engine.status.graphs['g:acme'];
if (!s || s.key === 'none') return <NotSetUp />;
if (s.key === 'waiting') return <Sealed hint="Open the app on another device to finish linking." />;
return <TheActualUi />;
engine.on('status', () => {
const { pending, syncing, lastError, rejected } = engine.status;
setBadge(rejected > 0 ? 'error' : lastError ? 'offline' : syncing ? 'syncing' : pending ? `${pending} pending` : 'synced');
});
for (let i = 0; i < rows.length; i += 200) {
await engine.putMany('g:acme', rows.slice(i, i + 200).map(toRecord));
await engine.flush(); // keep the queue bounded
}

A push is bounded by bytes as well as op count, so oversized records split automatically — but flushing in batches keeps memory flat and gives you a progress point.

import { SkmEngine, HttpTransport } from '@skm/client';
import { NodeSqliteGraphStore, FileKeyStore } from '@skm/client/node';
const agent = await SkmEngine.open({
memberId: 'bot',
deviceId: 'bot-worker-1',
transport: new HttpTransport({ baseUrl, headers: () => ({ authorization: `Bearer ${token}` }) }),
store: new NodeSqliteGraphStore({ path: '/var/lib/bot/graph.db' }),
keyStore: new FileKeyStore('/var/lib/bot/keys'),
kinds,
});

The agent is a device like any other: grant it, revoke it, rotate it out. It holds real keys, so its disk is a real trust boundary.

const engine = await SkmEngine.open({
memberId: 'sam',
deviceId: 'sam-test',
transport: new InjectTransport(app), // or HttpTransport against a test server
store: new MemoryGraphStore(),
keyStore: new MemoryKeyStore(),
kinds,
pushDebounceMs: 0,
});
await engine.openGraph('u', { create: true });

MemoryGraphStore + MemoryKeyStore + a relay on an ephemeral port gives you a full deployment per test in a few milliseconds. Always await engine.flush() before asserting on what the relay holds.