Writing a Store
A store is any object that implements IStore from @chamberu/core. Pass it to new RunEngine({ store }); the engine needs nothing else.
Prove it with the contract suite
@chamberu/test-kit ships describeStoreContract, the Vitest suite every store must pass. It is the same suite MemoryStore runs.
bash
npm install --save-dev @chamberu/test-kit vitestts
// test/unit/store.spec.ts
import { MemoryStore } from '@chamberu/core';
import { describeStoreContract } from '@chamberu/test-kit';
describeStoreContract((clock) => new MemoryStore({ clock }));The factory gets a fresh ControllableClock per test and must return a fresh, empty store that uses that clock for every expiry decision. The factory may be async, for example to create a database schema.
The suite covers:
- atomicity under concurrency: parallel
claimKey,claimRun,acquireandcreateRuncalls have exactly one winner, - expiry: keys, claims and locks expire after their TTL, and exactly at
expiresAtthey count as expired, updateRunguards:expectStatus(one or several allowed statuses) andexpectOwner,RunNotFoundErrorfor a missing run, untouched fields preserved,claimRunandrenewClaimon a missing run resolvefalse,- checkpoints: the highest
stepIndexwins, an equal index replaces, - locks: exclusive, released by their owner only, taken over after expiry,
listRunsfilters (agent,status,concurrencyKey),total, and thecreatedAt/idorder,- effect and state round trips, including
wherefilters, - no aliasing of values passed in or returned.
Implementation notes
- Do atomic work in one statement. In SQL, a conditional
UPDATE ... WHERE ... RETURNINGorINSERT ... ON CONFLICTinstead of a read followed by a write in JavaScript. - Pass the clock's time in. Compare against
clock.now()passed as a parameter, not the database'snow(). - Revive dates.
Run,Step,CheckpointandWaitPointcarryDatevalues (createdAt,trigger.receivedAt,waiting.expiresAt,claim.expiresAt, ...). Return them asDateobjects. - Store messages as they are. Checkpoint messages are AI SDK
ModelMessagearrays; store them as JSON without reshaping. - Only the latest checkpoint is read. Keeping older ones is optional.
Time in tests
ControllableClock implements IClock and moves only when you call advance(ms), firing due timers in order. Pass the same clock to the store and to the engine to test leases, timeouts and approval expiry without real timers. See the test-kit reference.