@skein-js/storage-memory
v0.18.0
Published
In-memory SkeinStore + queue driver for development and tests.
Maintainers
Readme
@skein-js/storage-memory
In-memory
SkeinStore+ run queue + event bus for development and tests.
Part of skein-js — the open-source alternative to LangGraph Platform for TypeScript: a self-hosted Agent Protocol server for LangGraph.js, and a drop-in replacement for the LangGraph CLI.
Status: 🚧 Pre-alpha — implemented; passes the shared SkeinStore conformance suite.
What it does
Zero-dependency, in-process drivers that power skein dev and back the test fixtures. They
implement the @skein-js/core contracts, so the engine uses them unchanged:
MemorySkeinStore— aSkeinStoreover plainMaps (assistants / threads / runs / store items), with the run-concurrency guard, thread→runs cascade delete, and naive prefix/substring store search. Every read and write deep-clones at the boundary — like a real serializing driver — so callers can't mutate stored rows or corrupt the store through a retained reference.MemoryRunQueue— a single-process queue of background runs. FIFO for ready work, plus a delayed set forafter_seconds; idempotent onrun_id, so a re-enqueue of a run already waiting, delayed, or in flight is a no-op.MemoryRunEventBus— buffered run-frame pub/sub with replay (afterSeq) and live-tail, so a client can join a run's stream late or reconnect.
Validated against the shared SkeinStore conformance suite, so it behaves identically to the
Postgres driver — the same tests run against both.
Install
pnpm add @skein-js/storage-memoryNo peer dependencies. Pair it with MemorySaver (from @langchain/langgraph) as the graph
checkpointer — this package stores only Agent Protocol resources, never graph state.
Usage
Construct with new — there is no connect/migrate step:
import { MemorySkeinStore, MemoryRunQueue, MemoryRunEventBus } from "@skein-js/storage-memory";
const store = new MemorySkeinStore();
const thread = await store.threads.create({ metadata: { user: "a" } });
await store.threads.get(thread.thread_id);Wired into an engine as a full set of in-memory drivers (this is what skein dev does):
import { MemorySaver } from "@langchain/langgraph";
import { createProtocolRuntime } from "@skein-js/agent-protocol";
import { MemoryRunEventBus, MemoryRunQueue, MemorySkeinStore } from "@skein-js/storage-memory";
const runtime = createProtocolRuntime({
store: new MemorySkeinStore(),
queue: new MemoryRunQueue(),
bus: new MemoryRunEventBus(),
checkpointer: new MemorySaver(),
graphs, // a GraphResolver, e.g. from @skein-js/config
});API
class MemorySkeinStore implements SkeinStore—new MemorySkeinStore({ ttl?, threadTtl?, maxPageSize? }).ttlis store-item expiry andthreadTtlis thread expiry ({ defaultTtl?, sweepIntervalMinutes? }, minutes) — see docs/storage.md.maxPageSize(default 1000) bounds every list/search, including one with nolimit— see docs/storage.md. Exposes the four repos (assistants,threads,runs,store) defined bySkeinStore. Plus two methods used byskein dev's persistence:snapshot(): MemoryStoreSnapshot— serialize all rows.hydrate(snapshot: MemoryStoreSnapshot): void— restore a snapshot (used to survive restarts).
interface MemoryStoreSnapshot— the serialized form (assistants/assistantVersions/threads/runs/runKwargs/items/cronsentry arrays). An alias of core'sSkeinStoreSnapshot.class MemoryRunQueue implements RunQueue—new MemoryRunQueue().enqueue(run, options?)(options.delayMsholds the run back — the run-createafter_seconds; kept in an unref'd timer, so a restart loses it, like every other run in this queue) ·consume(process, options?)(options.concurrencydefault1) → aRunConsumerwithclose(force?).class MemoryRunEventBus implements RunEventBus—new MemoryRunEventBus(options?).publish(runId, frame)·close(runId)·subscribe(runId, afterSeq = 0), plustrackedChannelCount/bufferedFrameCountfor diagnostics.Options:
maxFramesPerRun(default10000) is a hard maximum on one run's buffer — past it the oldest frames go, which ends the stream of any subscriber that had not read them.maxRetainedRuns(default50) is how many finished runs stay replayable for a late join; beyond it a run's channel is deleted, not blanked, and its id is remembered forfinishedIdTtlMs(default 24h) so the join still completes rather than waiting forever.nowinjects a clock.Prefer
resolveMemoryBusLimits()from@skein-js/server-kitover hand-passing the bounds — it appliesSKEIN_MEMORY_BUS_*from the environment, which is what every skein-assembled runtime does.
See @skein-js/core for the full SkeinStore / RunQueue / RunEventBus method
signatures these implement.
Reuse
Pairs with MemorySaver (re-exported from @langchain/langgraph) for graph checkpoints — it stores
only Agent Protocol resources, never graph state.
