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
npx tsx examples/store-remote.tsWhat 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
/**
* 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
BaseStoreextension.BaseStoreprovides theget/set/delete/has/updateAPI and thesnapshot/restoreround-trip. Subclasses implement the three protected abstract methods:performGet,performSet,performDelete.RemoteStoreInterface. Addsconnect,disconnect,health,acquireLease, andreleaseLeaseto 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 aLeasetoken;releaseLease(lease)releases it. The stub implements optimistic single-process locking — replace with a real distributed lock in production.snapshot/restoreround-trip.BaseStore.snapshot()returns a serialisableStoreSnapshotType;BaseStore.restore(snapshot)rehydrates the in-memory map. Used byCheckpoint.captureto include store contents in the checkpoint.
Related Concepts
- Example 10: Shared State - Store injected via node constructors with checkpoint round-trip
- Shared state guide - Store, MemoryStore, TypedStore, and RemoteStoreInterface
- Reference: Store - BaseStore, RemoteStoreInterface, MemoryStore API reference