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/doc7await engine.put('u', { id: `pin_${docId}`, kind: 'pin', data: { docId } });
// RIGHTconst 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.
A “sealed, not empty” screen
Section titled “A “sealed, not empty” screen”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 />;Showing sync state honestly
Section titled “Showing sync state honestly”engine.on('status', () => { const { pending, syncing, lastError, rejected } = engine.status; setBadge(rejected > 0 ? 'error' : lastError ? 'offline' : syncing ? 'syncing' : pending ? `${pending} pending` : 'synced');});A bulk import that does not stall
Section titled “A bulk import that does not stall”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.
A server-side agent
Section titled “A server-side agent”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.
Testing an app built on skm
Section titled “Testing an app built on skm”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.