Skip to content

Remote Store

What It Is

Remote Store shows how to put shared DAG state behind a service boundary. The example GrpcStore extends BaseStore and implements RemoteStoreInterface; its network methods print to stdout, while its data operations behave like a real remote store.

This is a store-contract example rather than a graph-topology example. It is the template for replacing in-process memory with gRPC, REST, Redis, S3, or another process-owned state service.

How It Works

BaseStore owns the normal store API and snapshot/restore mechanics. RemoteStoreInterface adds lifecycle and coordination hooks: connect, disconnect, health, acquireLease, and releaseLease.

Diagrams, Examples, and Outputs

This page has no DAG diagram because it demonstrates a store implementation, not a routed graph. The CLI run shows the connection lifecycle, read/write/delete behavior, lease handling, and snapshot/restore round-trip.

Run

bash
npx tsx examples/store-remote.ts

What It Lets You Do

Remote stores let applications keep shared DAG state behind a service boundary while preserving the same Store and checkpoint contracts. Use this when memory, cache, checkpoint-adjacent data, or locks must survive one process and coordinate across workers.

GrpcStore extends BaseStore and implements RemoteStoreInterface. Its network methods (connect, disconnect, health, acquireLease, releaseLease) print to stdout instead of making real gRPC calls — this is a stub standing in for a real gRPC backend. The in-memory data map is identical in behaviour to a real remote store: put/get/delete round-trips work exactly as a production implementation would.

Use this as a template for implementing a real gRPC (or REST/Redis/S3) remote store: replace performGet/performSet/performDelete with real client calls and implement connect/disconnect against your actual service endpoint.

Code Samples

ts
/**
 * store-remote: exercises the GrpcStore stub from dags/store-remote.ts.
 *
 * GrpcStore extends BaseStore and implements RemoteStoreInterface. Its network methods
 * (connect, disconnect, health, acquireLease, releaseLease) print to stdout
 * instead of making real gRPC calls — this is a stub standing in for a real
 * gRPC backend. The in-memory data map is identical in behaviour to a real
 * remote store: put/get/delete round-trips work exactly as a production
 * implementation would.
 *
 * Note: GrpcStore is a stub. In production, replace performGet/performSet/
 * performDelete with real gRPC client calls and implement connect/disconnect
 * against your actual service endpoint.
 *
 * DAG definition (GrpcStore class): examples/dags/store-remote.ts
 *
 * Run: npx tsx examples/store-remote.ts
 */

import { GrpcStore } from './dags/store-remote.js';

process.stdout.write('\n=== RemoteStoreInterface stub (GrpcStore) round-trip ===\n\n');

const store = new GrpcStore('grpc://archivist.internal:50051', 'eu-west-1');

// ── connect/health ───────────────────────────────────────────────────────────
await store.connect();
const healthy = await store.health(1000);
process.stdout.write(`health ok=${String(healthy)}\n\n`);

// ── put/get round-trip ───────────────────────────────────────────────────────
await store.set('catalogue:entry:001', { title: 'The Archivist Compendium', volume: 1 });
const entry = await store.get('catalogue:entry:001');
process.stdout.write(`get after set: ${JSON.stringify(entry)}\n`);

// ── has ──────────────────────────────────────────────────────────────────────
const exists    = await store.has('catalogue:entry:001');
const notExists = await store.has('catalogue:entry:999');
process.stdout.write(`has '001'=${String(exists)}  has '999'=${String(notExists)}\n`);

// ── update (atomic read-modify-write) ────────────────────────────────────────
const updated = await store.update('catalogue:count', (n) => (typeof n === 'number' ? n : 0) + 1);
process.stdout.write(`update counter: ${String(updated)}\n`);

// ── delete ───────────────────────────────────────────────────────────────────
await store.delete('catalogue:entry:001');
const afterDelete = await store.get('catalogue:entry:001');
process.stdout.write(`after delete: ${JSON.stringify(afterDelete)}\n\n`);

// ── lease acquire/release ────────────────────────────────────────────────────
const lease = await store.acquireLease('catalogue:entry:001', 5000, 1000);
process.stdout.write(`lease.token=${lease.token}\n`);
await store.releaseLease(lease);

// ── snapshot/restore round-trip ──────────────────────────────────────────────
await store.set('persist:a', 'alpha');
await store.set('persist:b', 'beta');
const snap = await store.snapshot();
process.stdout.write(`\nsnapshot entries=${String(snap.entries.length)}\n`);

const fresh = new GrpcStore('grpc://archivist.internal:50051', 'eu-west-1');
await fresh.restore(snap);
const restored = await fresh.get('persist:a');
process.stdout.write(`restored persist:a=${String(restored)}\n`);

await store.disconnect();
process.stdout.write('\nLesson: BaseStore handles key qualification, snapshot, and restore;\n');
process.stdout.write('        subclasses implement the performGet/Set/Delete/... hooks.\n');

Details for Nerds

  • BaseStore extension. BaseStore provides the get/set/delete/has/update API and the snapshot/restore round-trip. Subclasses implement the three protected abstract methods: performGet, performSet, performDelete.
  • RemoteStoreInterface. Adds connect, disconnect, health, acquireLease, and releaseLease to the base contract. These hooks are called by connection-aware hosts (service containers, long-lived workers) to manage the store's network lifecycle.
  • acquireLease / releaseLease. Distributed-lock primitives. acquireLease(key, ttlMs, waitMs) returns a Lease token; releaseLease(lease) releases it. The stub implements optimistic single-process locking — replace with a real distributed lock in production.
  • snapshot / restore round-trip. BaseStore.snapshot() returns a serialisable StoreSnapshotType; BaseStore.restore(snapshot) rehydrates the in-memory map. Used by Checkpoint.capture to include store contents in the checkpoint.

Watched over by the Order of Dagon.