JavaScript · Inputs and Outputs

Inputs and Outputs

import * as usearch from 'usearch';import { DenseTraversalStats } from 'usearch';import { RowsView } from 'usearch';import { Arena } from 'usearch';import { ArenaView } from 'usearch';import { QueryMatches } from 'usearch';import { Matches } from 'usearch'; 3.0.0-rc.1 · 4c3e5023

§ Arenas

Every output whose length only the call knows, such as matches, rows, products, monoliths and cursor or stream batches, lands in an Arena you pass, and the verb answers a view over it without copying. new Arena(executor) places it where that executor computes, decided once, and new Arena() makes host pages for the calling thread. Each call rewinds its arena, so the previous call’s views die and a steady loop allocates once. To keep a result past the next call, or to read one while making the next, pass that call another arena. An arena serves one call at a time, so each deferred call needs its own. A deferred call borrows its arena as a blocking one does: another call naming it before the first is final finishes with Status.Contended, and free() waits for it. A rewind or a free() detaches every buffer over the previous outputs, so a stale view throws Status.Uninitialized and typed arrays taken from it read as empty. An arena from a VRAM executor holds device memory, which host accessors refuse with Status.Device. It grows, fills and frees on that executor’s first stream, so it serves only calls on that executor and keeps it alive until freed, and the executor’s free() refuses with Status.Contended while such an arena lives; a host arena may outlive its executor.

const { Arena, keyAt } = usearch;
using arena = new Arena(executor); // new Arena() is host pages for the calling thread
for (const batch of batches) {
  const matches = index.on(executor).search(batch, 10, arena); // rewinds: last batch's matches are gone
  total += matches.scores.reduce((sum, score) => sum + score, 0);
}

using found = new Arena(), fetched = new Arena(); // two live results, two arenas
const nearest = index.search(query, 10, found).get(0);
for (let position = 0; position < nearest.count; position++)
  rerank(keyAt(nearest.keys, position), index.get(keyAt(nearest.keys, position), fetched));

// One executor and one arena per submitted call
const calls = shards.map((shard, at) => shard.deferred(executors[at]).search(queries, 10, arenas[at]));
const results = [];
for (const call of calls) results.push(await call); // or: call.wait()

Only fixed-size reports, like describe and the telemetry readings, come back as plain objects.

§ Rows & Scalars

A bare TypedArray is one row, and a RowsView is a run of them borrowed in place. It names the array, the row count, the columns, the bytes between rows, and the scalar kind, so a memory-mapped dataset reaches the index with no reshape and no copy. RowsView.of reads a packed array as rows of the width it is given:

const keys = new BigUint64Array([15n, 16n]);
const vectors = usearch.RowsView.of(new Float32Array([10, 20, 10, 25]), 2); // two rows of two
index.insert(keys, vectors);

const padded = new usearch.RowsView(mapped, rows, 768, 1024 * 4, usearch.Scalar.F32); // a pitch of 1024 floats
const matches = index.search(vectors, 2, arena);

The engine converts rows on ingest, so what a caller holds is independent of what the index stores. A Float64Array, Float32Array, Float16Array, Int8Array or Uint8Array names its own scalar kind; every other kind travels as bytes in a RowsView naming what they mean:

const bits = usearch.DenseIndex.initRam({ metric: usearch.DenseMetric.Hamming, dimensions: 64, quantization: usearch.Scalar.U1x8 });
const packed = usearch.RowsView.of(new Uint8Array([0b10101010, 0, 0, 0, 0, 0, 0, 0]), 64, usearch.Scalar.U1x8);
bits.insert(1n, packed);
bits.search(packed, 2, arena);

const halves = usearch.RowsView.of(new Uint16Array([0x3f80, 0x3f80, 0x3f80, 0x3f80]), 4, usearch.Scalar.BF16);

A row run claiming more bytes than its array holds is refused before it crosses.

search lands its matches in the arena as Matches, three views: the keys, the scores, and how many matches every query actually found.

const matches = index.search(queries, 10, arena);

matches.queries;   // Queries in the run
matches.columns;   // Columns per query the call wrote, at most its 'k'
matches.counts;    // BigUint64Array, the matches every query found
matches.keys;      // Uint8Array, sixteen bytes per cell, queries laid end to end
matches.scores;    // Float64Array, one distance per cell

const found = matches.get(0); // A QueryMatches, borrowed out of the run rather than copied
found.count;                  // Matches this query found, never above `wanted`
found.keys;                   // Sixteen bytes per match found
found.scores;                 // One distance per match found

Every view reads the arena’s own memory rather than a copy, so reading a query costs nothing. Scores come back as a Float64Array, which is the width the ABI writes.

The breadth of the candidates rides every call rather than the construction, so a recall sweep runs against one immutable index with no mutation between points:

index.on(executor).insert(keys, vectors, { expansionFactor: 128 });
index.on(executor).search(queries, 10, arena, { expansionFactor: 64 });
index.on(executor).search(queries, 10, arena, { traversal: usearch.Traversal.Scan }); // exact, on RAM and NVMe handles

insert, refine, search, maxsim, multiply and attend each report how much graph they walked, into a DenseTraversalStats reading passed through the config:

const stats = new usearch.DenseTraversalStats();
index.on(executor).search(queries, 10, arena, { stats });

stats.visitedMembers;     // entries whose neighborhoods the walk expanded
stats.computedDistances;  // distance evaluations the walk paid for
stats.restarts;           // times the walk fell back to a fresh entry point

const total = new usearch.DenseTraversalStats();  // sweeping, and reading the whole sweep's cost
for (const expansionFactor of [16, 32, 64, 128]) {
  index.on(executor).search(queries, 10, arena, { expansionFactor, stats });
  total.accumulate(stats);
}

Telemetry travels through the reading rather than the return value, so refine still answers with its report and a ranked verb with its matches, and passing none costs nothing. A reading is a pure output that each call fully overwrites, so one DenseTraversalStats handed to several calls holds the last of them, and a deferred call writes it once it settles. Use accumulate to fold readings together: expansionFactor buys recall in distance computations, and only the counters say how many, so a sweep reusing one arena allocates once and reads its cost between the points.

API

Types

DenseInsertConfigOptional per-call settings of an insertion.
usearch.ts:91 ↗
interface DenseInsertConfig

Fields

expansionFactor?numberBreadth of the candidates the build walk explores; absent lets the engine derive it.
stats?DenseTraversalStatsThe reading this call overwrites with its own telemetry; absent skips reporting.
DenseSearchConfigOptional per-call settings of a search.
usearch.ts:131 ↗
interface DenseSearchConfig

Fields

expansionFactor?numberBreadth of the candidates the walk explores; absent lets the engine derive it, and a scan ignores it.
traversal?TraversalWalks the graph when absent; Traversal.Scan scores every entry exactly, on RAM and NVMe handles.
stats?DenseTraversalStatsThe reading this call overwrites with its own telemetry; absent skips reporting.
TraversalHow a search reaches the entries it scores.
usearch.ts:99 ↗
enum Traversal

Values

Walk&quot;walk&quot;Walks the graph, scoring the entries the expansion reaches - approximate, the default.
Scan&quot;scan&quot;Scores every entry published when the query's scan starts - exact, on RAM and NVMe handles.
ScalarEnumeration representing the scalar kinds vectors are stored and exchanged in.
usearch.ts:111 ↗
enum Scalar

Values

F64&quot;f64&quot;IEEE-754 binary64: ±1.80e308, epsilon 2.22e-16, with infinities and NaNs.
F32&quot;f32&quot;IEEE-754 binary32: ±3.40e38, epsilon 1.19e-7, with infinities and NaNs.
BF16&quot;bf16&quot;Truncated f32 with a 7-bit mantissa: f32's range at epsilon 7.81e-3.
F16&quot;f16&quot;IEEE-754 binary16: ±65504, epsilon 9.77e-4, with infinities and NaNs.
E5M2&quot;e5m2&quot;FP8 with a 5-bit exponent: ±57344, epsilon 0.25, with infinities and NaNs.
E4M3&quot;e4m3&quot;FP8 with a 4-bit exponent: ±448, epsilon 0.125, no infinities.
E3M2&quot;e3m2&quot;FP6 with a 3-bit exponent in one byte: ±28, every code a number.
E2M3&quot;e2m3&quot;FP6 with a 2-bit exponent in one byte: ±7.5, every code a number.
I8&quot;i8&quot;8-bit signed integer spanning -128 to 127.
I4x2&quot;i4x2&quot;Two signed nibbles per byte spanning -8 to 7, even dimensions in the low nibble.
I32&quot;i32&quot;The coordinates of geospatial points, which vectors refuse.
U8&quot;u8&quot;8-bit unsigned integer spanning 0 to 255.
U4x2&quot;u4x2&quot;Two unsigned nibbles per byte spanning 0 to 15, even dimensions in the low nibble.
U1x8&quot;u1x8&quot;Eight single-bit dimensions per byte, the form the Hamming and Jaccard metrics score.
VectorOne row of scalars, in whatever numeric TypedArray the caller already holds.
usearch.ts:150 ↗
type Vector = Float64Array | Float32Array | Float16Array | Int8Array | Uint8Array | Uint16Array

The engine converts on ingest, so this is independent of the index's own quantization: an Int8Array reaches an f16 index unchanged. The kinds no TypedArray spells on its own - bf16, the mini-floats, the packed nibbles and bits - travel as bytes in a Uint8Array or Uint16Array, inside a RowsView naming their Scalar.

ClassDenseTraversalStatsTraversal telemetry from one call, a pure output the call fully overwrites.4
dense_index.ts:74 ↗
class DenseTraversalStats

One reading handed to several calls holds the last of them, so a sweep that wants totals folds them with accumulate rather than expecting the calls to sum themselves.

accumulateFolds another reading into this one, which is how a sweep gathers totals.
dense_index.ts:83 ↗
accumulate(other: DenseTraversalStats): void

Binding-only.

visitedMembersEntries whose neighborhoods the walk expanded.
dense_index.ts:76 ↗
visitedMembers: number
computedDistancesDistance evaluations the walk paid for, diversity re-selections included.
dense_index.ts:78 ↗
computedDistances: number
restartsTimes the walk fell back to a fresh entry point after the frontier drained early.
dense_index.ts:80 ↗
restarts: number
ClassRowsViewA borrowed run of rows of one width - what every batch argument is, and what a single row is when it travels as a bare TypedArray.2
types.ts:321 ↗
class RowsView

Its row count travels beside it as the call's count.

RowsViewBorrows rows rows of columns values of scalar, stride bytes apart; a zero stride repeats the first row.
types.ts:331 ↗
new RowsView(
    data: Vector,
    rows: number,
    columns: number,
    stride: number,
    scalar: Scalar,
)

Parameters

dataVectorThe array holding every row; never copied.
rowsnumberRows the run holds, which the call reads as its batch.
columnsnumberValues per row, as the index counts its dimensions.
stridenumberBytes between the starts of consecutive rows.
scalarScalarWhat the bytes hold, converted by the engine where it differs from storage.
ofBorrows a packed run, every row columns values wide and laid end to end.
types.ts:348 ↗
static of(
    data: Vector,
    columns: number,
    scalar: Scalar = scalarOf(data),
): RowsView

Binding-only sugar.

Parameters

dataVectorThe rows, end to end.
columnsnumberValues per row.
scalarScalarWhat the bytes hold; defaults to the kind the array spells.

Returns

As many rows as data holds.

Throws

Will throw when the array does not split into whole rows.

InterfaceArenaMemory the library writes every output whose length scales with a call into, reused across calls.3
types.ts:496 ↗
interface Arena extends Disposable

Every verb with such an output takes one and answers a view into it rather than a copy, so a steady loop allocates once. Each call rewinds the arena it is handed and detaches every buffer over the previous call's outputs: a view refuses its reads with Status.Uninitialized from then on, and every array already taken out of one reads empty rather than another call's memory. To keep one result past the next call, give that call another arena. A deferred call borrows it as a blocking one does: another call naming it before the first is final finishes Status.Contended and leaves the first call's outputs alone, and free waits for it. The collector frees the arena once nothing views it; free releases it now.

A Vram arena grows, fills and frees on its executor's first stream, so it serves only calls on that executor and keeps it alive until freed; host pages may outlive their executor.

ArenaMakes an arena where executor computes, or host pages for calls on the calling thread when absent.
types.ts:512 ↗
new Arena(executor?: Executor): Arena
freeReleases the memory now, once every deferred call writing it is final, detaching every view into it; a second call does nothing.
types.ts:504 ↗
free(): void
residencyThe residency of the executor that made it; a Vram arena's views refuse host reads with Status.Device.
types.ts:498 ↗
readonly residency: Residency
ClassArenaViewOutputs a call wrote into an arena, readable until the arena's next call rewinds it.
types.ts:526 ↗
abstract class ArenaView

The rewind detaches the buffer under every array, so each accessor refuses once the arena moved on, with Status.Uninitialized, and refuses device memory with Status.Device, so a view never reads memory its call did not write.

ClassQueryMatchesThe matches of one query, borrowed out of a run rather than copied.2
types.ts:541 ↗
class QueryMatches

Both arrays are subarrays of the run's own storage, so reading a query costs nothing.

QueryMatches
types.ts:542 ↗
new QueryMatches(keys: Uint8Array, scores: Float64Array)
countMatches this query found, which may be fewer than the run asked for.
types.ts:550 ↗
readonly count: number
ClassMatchesThe ranked matches a search or a stream pull wrote into its arena: queries rows of columns cells, query i holding counts[i] matches.7
types.ts:559 ↗
class Matches extends ArenaView
MatchesMade by a ranked verb, never directly.
types.ts:567 ↗
new Matches(outputs: CompiledMatches, queries: number)
getBorrows the matches of one query, without copying.
types.ts:595 ↗
get(queryIndex: number): QueryMatches

Binding-only.

Parameters

queryIndexnumberWhich query of the run to read.

Returns

The keys and scores that query found.
queriesQueries the call resolved.
types.ts:561 ↗
readonly queries: number
columnsColumns per query the call wrote, at most its wanted - the "k" in "kANN".
types.ts:563 ↗
readonly columns: number
keysThe queries × columns identity matrix, rows contiguous, sixteen bytes per key.
types.ts:575 ↗
readonly keys: Uint8Array
scoresThe queries × columns distance matrix, rows contiguous.
types.ts:580 ↗
readonly scores: Float64Array
countsMatches written per query, one count per query.
types.ts:585 ↗
readonly counts: BigUint64Array