Crux
API Reference@use-crux/core

RecordStore

JSON record storage methods, capabilities, TTL, filters, and bundled adapters.

RecordStore is the JSON record capability from @use-crux/core/storage.

import {
  inMemoryRecordStore,
  keySpace,
  mutateRecord,
} from "@use-crux/core/storage";
import type { JsonObject, RecordStore } from "@use-crux/core/storage";

const records = inMemoryRecordStore();
await records.put("plan:abc", { title: "My Plan", version: 1 });
const plan = await records.get("plan:abc");

Use RecordStore for memory records, cache entries, flow snapshots, plans, tasks, blackboards, workspace metadata, and other structured Crux state. Use SearchStore for retrieval search and AssetStore for binary or oversized payloads.

Interface

interface RecordStore<T extends JsonObject = JsonObject> {
  get(key: string): Promise<T | null>;
  getMany?(keys: readonly string[]): Promise<readonly (T | null)[]>;
  put(key: string, value: T, options?: { ttlMs?: number }): Promise<void>;
  putMany?(
    entries: readonly { key: string; value: T; options?: { ttlMs?: number } }[],
  ): Promise<void>;
  create(key: string, value: T, options?: { ttlMs?: number }): Promise<boolean>;
  delete(key: string): Promise<void>;
  deleteMany?(keys: readonly string[]): Promise<void>;
  list(prefix: string, options?: RecordListOptions): Promise<RecordPage<T>>;
  scan?(
    prefix: string,
    options?: Omit<RecordListOptions, "cursor">,
  ): AsyncIterable<RecordEntry<T>>;
  watch?(prefix: string, callback: (event: RecordEvent<T>) => void): () => void;
  mutate?(
    key: string,
    fn: (current: T | null) =>
      | RecordMutation<T>
      | Promise<RecordMutation<T>>,
  ): Promise<T | null>;
  getVersioned?(
    key: string,
  ): Promise<{ value: T | null; version: string | null }>;
  putVersioned?(
    key: string,
    value: T | null,
    expectedVersion: string | null,
  ): Promise<boolean>;
  capabilities(): RecordStoreCapabilities;
}

put() creates or replaces a record. create() is the atomic insert primitive and returns false when an active record already exists. Use mutateRecord() when a read-modify-write must be linearizable across concurrent callers:

await mutateRecord(records, "counter", (current) => ({
  type: "put",
  value: { count: (current?.count ?? 0) + 1 },
}));

Capabilities

type RecordStoreCapabilities = {
  ttl: "native" | "lazy" | false;
  filter: "native" | "scan" | false;
  watch: boolean;
  batch: boolean;
  mutate: "native" | "cas" | false;
};

Adapters must report the behavior they actually provide. Passing { ttlMs } to a store with ttl: false throws StorageError code ttl_unsupported.

Filters

Filters are exact top-level scalar equality:

await records.list("docs:", {
  filter: { namespace: "public", active: true, deletedAt: null },
});

Unsupported filter values must throw StorageError code invalid_filter; adapters should not silently drop unsupported clauses.

Watches

watch(prefix, callback) emits put and delete events when a backend supports live notifications:

records.watch?.("plan:", (event) => {
  if (event.type === "put") {
    console.log("Updated:", event.key, event.value);
  } else {
    console.log("Deleted:", event.key);
  }
});

Keyspace

keySpace centralizes Crux record-key prefixes used by core primitives:

const key = keySpace.plan.key("abc");
const page = await records.list(keySpace.plan.prefix);

Implementations

StorePackageUse case
inMemoryRecordStore()@use-crux/core/storageTests and local examples
convexRecordStore()@use-crux/convexConvex-backed records
upstashRedisRecordStore()@use-crux/upstashUpstash Redis-backed records

Bundle records with search and assets through storage({ records, search, assets }) when a primitive needs multiple capabilities.

On this page