Valkey Search Kit
@betterdb/valkey-search-kit is a set of shared, low-level helpers for working with Valkey Search (FT.* commands): vector byte encoding, FT.SEARCH reply parsing, version-skew-tolerant FT.INFO parsing, TAG filter escaping, and error classification.
It has no runtime dependencies and exposes only pure functions, so it stays trivial to vendor and test. It is consumed by @betterdb/semantic-cache, @betterdb/retrieval, and @betterdb/agent-memory, and is the foundation you reach for when building directly against FT.*.
Prerequisites
- Valkey 8.0+ with the
valkey-searchmodule loaded (for theFT.*commands these helpers target) - Node.js >= 20
Installation
npm install @betterdb/valkey-search-kit
Exports
| Export | Signature | Description |
|---|---|---|
encodeFloat32 | (vec: number[]) => Buffer | Encode an embedding as a little-endian Float32 Buffer for binary HSET field values and KNN PARAMS. |
escapeTag | (value: string) => string | Escape a string for safe use as a Valkey Search TAG filter value, including spaces (which would otherwise split into OR terms). |
parseFtSearchResponse | (raw: unknown) => FtSearchHit[] | Parse a raw FT.SEARCH reply into hits; never throws, returns [] on empty or malformed input. |
FtSearchHit | { key: string; fields: Record<string, string> } | A single parsed search hit. |
parseDimensionFromInfo | (info: unknown) => number | Extract the vector field dimension from an FT.INFO reply, handling both flat DIM pairs and the Valkey Search 1.2 nested index/dimensions shape. |
parseFtInfoStats | (info: unknown) => FtIndexStats | Extract num_docs and indexing state from an FT.INFO reply. |
isIndexNotFoundError | (err: unknown) => boolean | Classify an error as a Valkey Search “index does not exist” error across Valkey Search / RediSearch message variants. |
Usage
Vector encoding
import { encodeFloat32 } from '@betterdb/valkey-search-kit';
const blob = encodeFloat32([0.1, 0.2, 0.3]); // little-endian Float32 Buffer
await client.hset('doc:1', 'embedding', blob);
Use encodeFloat32 to store embeddings as binary HSET field values and as the PARAMS vector for a KNN FT.SEARCH.
TAG escaping
import { escapeTag } from '@betterdb/valkey-search-kit';
const query = `@model:{${escapeTag('gpt-4o')}}`; // -> "@model:{gpt\\-4o}"
Escapes every character with special meaning in the TAG filter syntax, including spaces (unescaped spaces are treated as OR term separators).
FT.SEARCH reply parsing
import { parseFtSearchResponse } from '@betterdb/valkey-search-kit';
const raw = await client.call('FT.SEARCH', index, query /* ... */);
const hits = parseFtSearchResponse(raw);
// [{ key: 'cache:entry:abc', fields: { prompt: '...', __score: '0.05' } }]
Handles mixed Buffer/string replies, RETURN 0 mode (keys with no field list), and odd-length field lists. Binary field values that are not valid UTF-8 (e.g. raw embedding bytes) are skipped. Never throws.
FT.INFO parsing (version-skew tolerant)
import { parseDimensionFromInfo, parseFtInfoStats } from '@betterdb/valkey-search-kit';
const info = await client.call('FT.INFO', index);
const dims = parseDimensionFromInfo(info); // 1536, or 0 if no vector field
const stats = parseFtInfoStats(info); // { num_docs, indexing_state }
parseDimensionFromInfo understands both the flat DIM attribute pair and the nested index/dimensions shape introduced in Valkey Search 1.2.
Error classification
import { isIndexNotFoundError } from '@betterdb/valkey-search-kit';
try {
await client.call('FT.INFO', index);
} catch (err) {
if (isIndexNotFoundError(err)) {
// index does not exist yet
} else {
throw err;
}
}
Matches the “index does not exist” message variants emitted across Valkey Search / RediSearch versions, case-insensitively.
See also
- Valkey Search Kit (Python) - the Python port with the same surface.
- Retrieval and Agent Memory - higher-level SDKs built on this kit.