Crux
API Reference@use-crux/core

Storage Interfaces

RecordStore, VectorStore, AssetStore, storage(), and in-memory Storage Beta implementations.

import {
  inMemoryAssetStore,
  inMemoryRecordStore,
  inMemoryStorage,
  inMemoryVectorStore,
  storage,
  StorageError,
} from "@use-crux/core/storage";

import type {
  AssetStore,
  RecordStore,
  Storage,
  VectorStore,
} from "@use-crux/core/storage";

Overview

Crux storage is split into explicit capabilities:

InterfacePurpose
RecordStoreJSON records with get, put, create, delete, paged list, optional scan, TTL, filters, and watches
VectorStoreDense, sparse, and hybrid vector indexes with explicit capability claims
AssetStoreBinary and oversized payload storage with put, get, and delete
StorageA capability bundle: { records, vectors?, assets? }

Use this module for application code. @use-crux/core/storage is the canonical Storage Beta entry point.

storage(config)

Normalize and shallow-freeze a capability bundle:

const appStorage = storage({
  records,
  vectors,
  assets,
});

Primitives can consume the whole bundle or the specific capability they need:

workspace({ id: "files", namespace, storage: appStorage });
retriever({ id: "docs", records, vectors, dense });

Use storage.scope() for tenant, user, session, or workspace isolation:

const tenantStorage = storage.scope(appStorage, `tenant:${tenantId}`);

Scoped storage preserves capability claims while prefixing record and vector keys.

RecordStore

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?: RecordWriteOptions): Promise<void>;
  putMany?(entries: readonly RecordWrite<T>[]): Promise<void>;
  create(key: string, value: T, options?: RecordWriteOptions): 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;
  capabilities(): RecordStoreCapabilities;
}

put() accepts { ttlMs }, not { ttl }. create() is the atomic insert primitive and returns false when an active record already exists.

Capability levels are explicit:

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

Filters are exact top-level scalar equality only:

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

Adapters must reject unsupported filters with StorageError code invalid_filter; they must not silently drop filter clauses.

VectorStore

interface VectorStore {
  upsert(records: readonly VectorRecord[]): Promise<void>;
  delete(keys: readonly string[]): Promise<void>;
  search(query: VectorSearchQuery): Promise<readonly VectorHit[]>;
  capabilities(): VectorStoreCapabilities;
}

search() uses a discriminated query:

await vectors.search({
  mode: "hybrid",
  dense: [0.12, 0.4, 0.9],
  sparse: { indices: [12, 98], values: [0.8, 0.3] },
  fusion: "dbsf",
  limit: 10,
  filter: { namespace: "docs" },
});

Capability levels are part of correctness:

type VectorStoreCapabilities = {
  dense: boolean;
  sparse: boolean;
  hybrid: boolean;
  fusion: readonly ("rrf" | "dbsf")[];
  filter: "pre" | "post" | false;
  consistency: "strong" | "eventual";
};

Production filtered retrieval requires filter: 'pre'. A post filter can be useful for local tooling, but Crux primitives that rely on exact filtered top-k results reject it unless they explicitly opt into approximate behavior.

AssetStore

interface AssetStore {
  put(asset: Asset, options?: AssetPutOptions): Promise<StoredAsset>;
  get(ref: AssetRef): Promise<StoredAsset>;
  delete(ref: AssetRef): Promise<void>;
}

Asset URIs are internal application references, not automatic public URLs. Signed URLs are app-authorized and are not shown in devtools by default.

Errors

Storage contract failures throw StorageError:

try {
  await records.put("cache:key", { value: "x" }, { ttlMs: 60_000 });
} catch (error) {
  if (error instanceof StorageError && error.code === "ttl_unsupported") {
    // choose a non-TTL path
  }
}

Error codes include not_found, conflict, unsupported_capability, invalid_key, invalid_value, invalid_filter, ttl_unsupported, and backend_error.

In-Memory Implementations

Use these for tests, demos, and local examples:

const records = inMemoryRecordStore();
const vectors = inMemoryVectorStore();
const assets = inMemoryAssetStore();
const all = inMemoryStorage();

In-memory stores are process-local and not durable. They are contract-correct reference implementations: records use lazy TTL and scan filtering, vectors pre-filter metadata before scoring, and assets support explicit put(), get(), and delete() lifecycle behavior.

On this page