731 lines
26 KiB
JavaScript
731 lines
26 KiB
JavaScript
"use strict";
|
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
if (k2 === undefined) k2 = k;
|
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
}
|
|
Object.defineProperty(o, k2, desc);
|
|
}) : (function(o, m, k, k2) {
|
|
if (k2 === undefined) k2 = k;
|
|
o[k2] = m[k];
|
|
}));
|
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
}) : function(o, v) {
|
|
o["default"] = v;
|
|
});
|
|
var __importStar = (this && this.__importStar) || (function () {
|
|
var ownKeys = function(o) {
|
|
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
var ar = [];
|
|
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
return ar;
|
|
};
|
|
return ownKeys(o);
|
|
};
|
|
return function (mod) {
|
|
if (mod && mod.__esModule) return mod;
|
|
var result = {};
|
|
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
__setModuleDefault(result, mod);
|
|
return result;
|
|
};
|
|
})();
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.WasmBackend = exports.NodeBackend = void 0;
|
|
exports.resolveBackend = resolveBackend;
|
|
const errors_1 = require("./errors");
|
|
// ---------------------------------------------------------------------------
|
|
// NodeBackend — wraps @ruvector/rvf-node (N-API)
|
|
// ---------------------------------------------------------------------------
|
|
/**
|
|
* Backend that delegates to the `@ruvector/rvf-node` native N-API addon.
|
|
*
|
|
* The native addon is loaded lazily on first use so that the SDK package can
|
|
* be imported in environments where the native build is unavailable (e.g.
|
|
* browsers) without throwing at import time.
|
|
*/
|
|
class NodeBackend {
|
|
constructor() {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
this.native = null;
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
this.handle = null;
|
|
// String ID <-> Numeric Label mappings (N-API layer requires i64 labels)
|
|
this.idToLabel = new Map();
|
|
this.labelToId = new Map();
|
|
this.nextLabel = 1; // RVF uses 1-based labels
|
|
this.storePath = '';
|
|
}
|
|
async loadNative() {
|
|
if (this.native)
|
|
return;
|
|
try {
|
|
// Dynamic import so the SDK can be bundled for browsers without
|
|
// pulling in the native addon at compile time.
|
|
// The NAPI addon exports a `RvfDatabase` class with factory methods.
|
|
const mod = await Promise.resolve().then(() => __importStar(require('@ruvector/rvf-node')));
|
|
this.native = mod.RvfDatabase ?? mod.default?.RvfDatabase ?? mod;
|
|
}
|
|
catch {
|
|
throw new errors_1.RvfError(errors_1.RvfErrorCode.BackendNotFound, 'Could not load @ruvector/rvf-node — is it installed?');
|
|
}
|
|
}
|
|
ensureHandle() {
|
|
if (!this.handle) {
|
|
throw new errors_1.RvfError(errors_1.RvfErrorCode.StoreClosed);
|
|
}
|
|
}
|
|
async create(path, options) {
|
|
await this.loadNative();
|
|
try {
|
|
this.handle = await this.native.create(path, mapOptionsToNative(options));
|
|
this.storePath = path;
|
|
this.idToLabel.clear();
|
|
this.labelToId.clear();
|
|
this.nextLabel = 1;
|
|
}
|
|
catch (err) {
|
|
throw errors_1.RvfError.fromNative(err);
|
|
}
|
|
}
|
|
async open(path) {
|
|
await this.loadNative();
|
|
try {
|
|
this.handle = await this.native.open(path);
|
|
this.storePath = path;
|
|
await this.loadMappings();
|
|
}
|
|
catch (err) {
|
|
throw errors_1.RvfError.fromNative(err);
|
|
}
|
|
}
|
|
async openReadonly(path) {
|
|
await this.loadNative();
|
|
try {
|
|
this.handle = await this.native.openReadonly(path);
|
|
this.storePath = path;
|
|
await this.loadMappings();
|
|
}
|
|
catch (err) {
|
|
throw errors_1.RvfError.fromNative(err);
|
|
}
|
|
}
|
|
async ingestBatch(entries) {
|
|
this.ensureHandle();
|
|
try {
|
|
// NAPI signature: ingestBatch(vectors: Float32Array, ids: i64[], metadata?)
|
|
// Flatten individual vectors into a single contiguous Float32Array.
|
|
const n = entries.length;
|
|
if (n === 0)
|
|
return { accepted: 0, rejected: 0, epoch: 0 };
|
|
const first = entries[0].vector;
|
|
const dim = first instanceof Float32Array ? first.length : first.length;
|
|
const flat = new Float32Array(n * dim);
|
|
for (let i = 0; i < n; i++) {
|
|
const v = entries[i].vector;
|
|
const f32 = v instanceof Float32Array ? v : new Float32Array(v);
|
|
flat.set(f32, i * dim);
|
|
}
|
|
// Map string IDs to numeric labels for the N-API layer.
|
|
// The native Rust HNSW expects i64 labels — non-numeric strings cause
|
|
// silent data loss (NaN → dropped). We maintain a bidirectional
|
|
// string↔label mapping and persist it as a sidecar JSON file.
|
|
const ids = entries.map((e) => this.resolveLabel(e.id));
|
|
const result = this.handle.ingestBatch(flat, ids);
|
|
// Persist mappings after every ingest so they survive crashes.
|
|
await this.saveMappings();
|
|
return {
|
|
accepted: Number(result.accepted),
|
|
rejected: Number(result.rejected),
|
|
epoch: result.epoch,
|
|
};
|
|
}
|
|
catch (err) {
|
|
throw errors_1.RvfError.fromNative(err);
|
|
}
|
|
}
|
|
async query(vector, k, options) {
|
|
this.ensureHandle();
|
|
try {
|
|
const nativeOpts = options ? mapQueryOptionsToNative(options) : undefined;
|
|
const results = this.handle.query(vector, k, nativeOpts);
|
|
// Map numeric labels back to original string IDs.
|
|
return results.map((r) => ({
|
|
id: this.labelToId.get(Number(r.id)) ?? String(r.id),
|
|
distance: r.distance,
|
|
}));
|
|
}
|
|
catch (err) {
|
|
throw errors_1.RvfError.fromNative(err);
|
|
}
|
|
}
|
|
async delete(ids) {
|
|
this.ensureHandle();
|
|
try {
|
|
// Resolve string IDs to numeric labels for the N-API layer.
|
|
const numIds = ids
|
|
.map((id) => this.idToLabel.get(id))
|
|
.filter((label) => label !== undefined);
|
|
if (numIds.length === 0) {
|
|
return { deleted: 0, epoch: 0 };
|
|
}
|
|
const result = this.handle.delete(numIds);
|
|
// Remove deleted entries from the mapping.
|
|
for (const id of ids) {
|
|
const label = this.idToLabel.get(id);
|
|
if (label !== undefined) {
|
|
this.idToLabel.delete(id);
|
|
this.labelToId.delete(label);
|
|
}
|
|
}
|
|
await this.saveMappings();
|
|
return { deleted: Number(result.deleted), epoch: result.epoch };
|
|
}
|
|
catch (err) {
|
|
throw errors_1.RvfError.fromNative(err);
|
|
}
|
|
}
|
|
async deleteByFilter(filter) {
|
|
this.ensureHandle();
|
|
try {
|
|
// NAPI takes a JSON string for the filter expression.
|
|
const result = this.handle.deleteByFilter(JSON.stringify(filter));
|
|
return { deleted: Number(result.deleted), epoch: result.epoch };
|
|
}
|
|
catch (err) {
|
|
throw errors_1.RvfError.fromNative(err);
|
|
}
|
|
}
|
|
async compact() {
|
|
this.ensureHandle();
|
|
try {
|
|
const result = this.handle.compact();
|
|
return {
|
|
segmentsCompacted: result.segmentsCompacted ?? result.segments_compacted,
|
|
bytesReclaimed: Number(result.bytesReclaimed ?? result.bytes_reclaimed),
|
|
epoch: result.epoch,
|
|
};
|
|
}
|
|
catch (err) {
|
|
throw errors_1.RvfError.fromNative(err);
|
|
}
|
|
}
|
|
async status() {
|
|
this.ensureHandle();
|
|
try {
|
|
const s = this.handle.status();
|
|
return mapNativeStatus(s);
|
|
}
|
|
catch (err) {
|
|
throw errors_1.RvfError.fromNative(err);
|
|
}
|
|
}
|
|
async close() {
|
|
if (!this.handle)
|
|
return;
|
|
try {
|
|
await this.saveMappings();
|
|
this.handle.close();
|
|
}
|
|
catch (err) {
|
|
throw errors_1.RvfError.fromNative(err);
|
|
}
|
|
finally {
|
|
this.handle = null;
|
|
this.idToLabel.clear();
|
|
this.labelToId.clear();
|
|
this.nextLabel = 1;
|
|
this.storePath = '';
|
|
}
|
|
}
|
|
async fileId() {
|
|
this.ensureHandle();
|
|
try {
|
|
return this.handle.fileId();
|
|
}
|
|
catch (err) {
|
|
throw errors_1.RvfError.fromNative(err);
|
|
}
|
|
}
|
|
async parentId() {
|
|
this.ensureHandle();
|
|
try {
|
|
return this.handle.parentId();
|
|
}
|
|
catch (err) {
|
|
throw errors_1.RvfError.fromNative(err);
|
|
}
|
|
}
|
|
async lineageDepth() {
|
|
this.ensureHandle();
|
|
try {
|
|
return this.handle.lineageDepth();
|
|
}
|
|
catch (err) {
|
|
throw errors_1.RvfError.fromNative(err);
|
|
}
|
|
}
|
|
async derive(childPath, options) {
|
|
this.ensureHandle();
|
|
try {
|
|
const nativeOpts = options ? mapOptionsToNative(options) : undefined;
|
|
const childHandle = this.handle.derive(childPath, nativeOpts);
|
|
const child = new NodeBackend();
|
|
child.native = this.native;
|
|
child.handle = childHandle;
|
|
child.storePath = childPath;
|
|
// Copy parent mappings to child (COW semantics)
|
|
child.idToLabel = new Map(this.idToLabel);
|
|
child.labelToId = new Map(this.labelToId);
|
|
child.nextLabel = this.nextLabel;
|
|
await child.saveMappings();
|
|
return child;
|
|
}
|
|
catch (err) {
|
|
throw errors_1.RvfError.fromNative(err);
|
|
}
|
|
}
|
|
async embedKernel(arch, kernelType, flags, image, apiPort, cmdline) {
|
|
this.ensureHandle();
|
|
try {
|
|
return this.handle.embedKernel(arch, kernelType, flags, Buffer.from(image), apiPort, cmdline);
|
|
}
|
|
catch (err) {
|
|
throw errors_1.RvfError.fromNative(err);
|
|
}
|
|
}
|
|
async extractKernel() {
|
|
this.ensureHandle();
|
|
try {
|
|
const result = this.handle.extractKernel();
|
|
if (!result)
|
|
return null;
|
|
return {
|
|
header: new Uint8Array(result.header),
|
|
image: new Uint8Array(result.image),
|
|
};
|
|
}
|
|
catch (err) {
|
|
throw errors_1.RvfError.fromNative(err);
|
|
}
|
|
}
|
|
async embedEbpf(programType, attachType, maxDimension, bytecode, btf) {
|
|
this.ensureHandle();
|
|
try {
|
|
return this.handle.embedEbpf(programType, attachType, maxDimension, Buffer.from(bytecode), btf ? Buffer.from(btf) : undefined);
|
|
}
|
|
catch (err) {
|
|
throw errors_1.RvfError.fromNative(err);
|
|
}
|
|
}
|
|
async extractEbpf() {
|
|
this.ensureHandle();
|
|
try {
|
|
const result = this.handle.extractEbpf();
|
|
if (!result)
|
|
return null;
|
|
return {
|
|
header: new Uint8Array(result.header),
|
|
payload: new Uint8Array(result.payload),
|
|
};
|
|
}
|
|
catch (err) {
|
|
throw errors_1.RvfError.fromNative(err);
|
|
}
|
|
}
|
|
async segments() {
|
|
this.ensureHandle();
|
|
try {
|
|
const segs = this.handle.segments();
|
|
return segs.map((s) => ({
|
|
id: s.id,
|
|
offset: s.offset,
|
|
payloadLength: s.payloadLength ?? s.payload_length,
|
|
segType: s.segType ?? s.seg_type,
|
|
}));
|
|
}
|
|
catch (err) {
|
|
throw errors_1.RvfError.fromNative(err);
|
|
}
|
|
}
|
|
async dimension() {
|
|
this.ensureHandle();
|
|
try {
|
|
return this.handle.dimension();
|
|
}
|
|
catch (err) {
|
|
throw errors_1.RvfError.fromNative(err);
|
|
}
|
|
}
|
|
// ─── String ID ↔ Numeric Label mapping helpers ───
|
|
/**
|
|
* Get or allocate a numeric label for a string ID.
|
|
* If the ID was already seen, returns the existing label.
|
|
*/
|
|
resolveLabel(id) {
|
|
let label = this.idToLabel.get(id);
|
|
if (label !== undefined)
|
|
return label;
|
|
label = this.nextLabel++;
|
|
this.idToLabel.set(id, label);
|
|
this.labelToId.set(label, id);
|
|
return label;
|
|
}
|
|
/** Path to the sidecar mappings file. */
|
|
mappingsPath() {
|
|
return this.storePath ? this.storePath + '.idmap.json' : '';
|
|
}
|
|
/** Persist the string↔label mapping to a sidecar JSON file. */
|
|
async saveMappings() {
|
|
const mp = this.mappingsPath();
|
|
if (!mp)
|
|
return;
|
|
try {
|
|
const fs = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
const data = JSON.stringify({
|
|
idToLabel: Object.fromEntries(this.idToLabel),
|
|
labelToId: Object.fromEntries(Array.from(this.labelToId.entries()).map(([k, v]) => [String(k), v])),
|
|
nextLabel: this.nextLabel,
|
|
});
|
|
fs.writeFileSync(mp, data, 'utf-8');
|
|
}
|
|
catch {
|
|
// Non-fatal: mapping persistence is best-effort (e.g. read-only FS).
|
|
}
|
|
}
|
|
/** Load the string↔label mapping from the sidecar JSON file if it exists. */
|
|
async loadMappings() {
|
|
const mp = this.mappingsPath();
|
|
if (!mp)
|
|
return;
|
|
try {
|
|
const fs = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
if (!fs.existsSync(mp))
|
|
return;
|
|
const raw = JSON.parse(fs.readFileSync(mp, 'utf-8'));
|
|
this.idToLabel = new Map(Object.entries(raw.idToLabel ?? {}).map(([k, v]) => [k, Number(v)]));
|
|
this.labelToId = new Map(Object.entries(raw.labelToId ?? {}).map(([k, v]) => [Number(k), v]));
|
|
this.nextLabel = raw.nextLabel ?? this.idToLabel.size + 1;
|
|
}
|
|
catch {
|
|
// Non-fatal: start with empty mappings.
|
|
}
|
|
}
|
|
}
|
|
exports.NodeBackend = NodeBackend;
|
|
// ---------------------------------------------------------------------------
|
|
// WasmBackend — wraps @ruvector/rvf-wasm
|
|
// ---------------------------------------------------------------------------
|
|
/**
|
|
* Backend that delegates to the `@ruvector/rvf-wasm` WASM build.
|
|
*
|
|
* The WASM microkernel exposes C-ABI store functions (`rvf_store_create`,
|
|
* `rvf_store_query`, etc.) operating on integer handles. This backend wraps
|
|
* them behind the same `RvfBackend` interface.
|
|
*
|
|
* Suitable for browser environments. The WASM module is loaded lazily.
|
|
*/
|
|
class WasmBackend {
|
|
constructor() {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
this.wasm = null;
|
|
/** Integer store handle returned by `rvf_store_create` / `rvf_store_open`. */
|
|
this.handle = 0;
|
|
this.dim = 0;
|
|
}
|
|
async loadWasm() {
|
|
if (this.wasm)
|
|
return;
|
|
try {
|
|
const mod = await Promise.resolve().then(() => __importStar(require('@ruvector/rvf-wasm')));
|
|
// wasm-pack default export is the init function
|
|
if (typeof mod.default === 'function') {
|
|
this.wasm = await mod.default();
|
|
}
|
|
else {
|
|
this.wasm = mod;
|
|
}
|
|
}
|
|
catch {
|
|
throw new errors_1.RvfError(errors_1.RvfErrorCode.BackendNotFound, 'Could not load @ruvector/rvf-wasm — is it installed?');
|
|
}
|
|
}
|
|
ensureHandle() {
|
|
if (!this.handle) {
|
|
throw new errors_1.RvfError(errors_1.RvfErrorCode.StoreClosed);
|
|
}
|
|
}
|
|
metricCode(metric) {
|
|
switch (metric) {
|
|
case 'Cosine': return 2;
|
|
case 'InnerProduct': return 1;
|
|
default: return 0; // L2
|
|
}
|
|
}
|
|
async create(_path, options) {
|
|
await this.loadWasm();
|
|
try {
|
|
const nativeOpts = mapOptionsToNative(options);
|
|
const dim = nativeOpts.dimension;
|
|
const metric = this.metricCode(nativeOpts.metric);
|
|
const h = this.wasm.rvf_store_create(dim, metric);
|
|
if (h <= 0)
|
|
throw new Error('rvf_store_create returned ' + h);
|
|
this.handle = h;
|
|
this.dim = dim;
|
|
}
|
|
catch (err) {
|
|
throw errors_1.RvfError.fromNative(err);
|
|
}
|
|
}
|
|
async open(_path) {
|
|
throw new errors_1.RvfError(errors_1.RvfErrorCode.BackendNotFound, 'WASM backend does not support file-based open (in-memory only)');
|
|
}
|
|
async openReadonly(_path) {
|
|
throw new errors_1.RvfError(errors_1.RvfErrorCode.BackendNotFound, 'WASM backend does not support file-based openReadonly (in-memory only)');
|
|
}
|
|
async ingestBatch(entries) {
|
|
this.ensureHandle();
|
|
try {
|
|
const n = entries.length;
|
|
if (n === 0)
|
|
return { accepted: 0, rejected: 0, epoch: 0 };
|
|
const dim = this.dim || (entries[0].vector instanceof Float32Array
|
|
? entries[0].vector.length : entries[0].vector.length);
|
|
const flat = new Float32Array(n * dim);
|
|
const ids = new BigUint64Array(n);
|
|
for (let i = 0; i < n; i++) {
|
|
const v = entries[i].vector;
|
|
const f32 = v instanceof Float32Array ? v : new Float32Array(v);
|
|
flat.set(f32, i * dim);
|
|
ids[i] = BigInt(entries[i].id);
|
|
}
|
|
// Allocate in WASM memory and call
|
|
const vecsPtr = this.wasm.rvf_alloc(flat.byteLength);
|
|
const idsPtr = this.wasm.rvf_alloc(ids.byteLength);
|
|
new Float32Array(this.wasm.memory.buffer, vecsPtr, flat.length).set(flat);
|
|
new BigUint64Array(this.wasm.memory.buffer, idsPtr, ids.length).set(ids);
|
|
const accepted = this.wasm.rvf_store_ingest(this.handle, vecsPtr, idsPtr, n);
|
|
this.wasm.rvf_free(vecsPtr, flat.byteLength);
|
|
this.wasm.rvf_free(idsPtr, ids.byteLength);
|
|
return { accepted: accepted > 0 ? accepted : 0, rejected: accepted < 0 ? n : 0, epoch: 0 };
|
|
}
|
|
catch (err) {
|
|
throw errors_1.RvfError.fromNative(err);
|
|
}
|
|
}
|
|
async query(vector, k, _options) {
|
|
this.ensureHandle();
|
|
try {
|
|
const queryPtr = this.wasm.rvf_alloc(vector.byteLength);
|
|
new Float32Array(this.wasm.memory.buffer, queryPtr, vector.length).set(vector);
|
|
// Each result = 8 bytes id + 4 bytes dist = 12 bytes
|
|
const outSize = k * 12;
|
|
const outPtr = this.wasm.rvf_alloc(outSize);
|
|
const count = this.wasm.rvf_store_query(this.handle, queryPtr, k, 0, outPtr);
|
|
const results = [];
|
|
const view = new DataView(this.wasm.memory.buffer);
|
|
for (let i = 0; i < count; i++) {
|
|
const off = outPtr + i * 12;
|
|
const id = view.getBigUint64(off, true);
|
|
const dist = view.getFloat32(off + 8, true);
|
|
results.push({ id: String(id), distance: dist });
|
|
}
|
|
this.wasm.rvf_free(queryPtr, vector.byteLength);
|
|
this.wasm.rvf_free(outPtr, outSize);
|
|
return results;
|
|
}
|
|
catch (err) {
|
|
throw errors_1.RvfError.fromNative(err);
|
|
}
|
|
}
|
|
async delete(ids) {
|
|
this.ensureHandle();
|
|
try {
|
|
const arr = new BigUint64Array(ids.map((id) => BigInt(id)));
|
|
const ptr = this.wasm.rvf_alloc(arr.byteLength);
|
|
new BigUint64Array(this.wasm.memory.buffer, ptr, arr.length).set(arr);
|
|
const deleted = this.wasm.rvf_store_delete(this.handle, ptr, ids.length);
|
|
this.wasm.rvf_free(ptr, arr.byteLength);
|
|
return { deleted: deleted > 0 ? deleted : 0, epoch: 0 };
|
|
}
|
|
catch (err) {
|
|
throw errors_1.RvfError.fromNative(err);
|
|
}
|
|
}
|
|
async deleteByFilter(_filter) {
|
|
throw new errors_1.RvfError(errors_1.RvfErrorCode.BackendNotFound, 'deleteByFilter not supported in WASM backend');
|
|
}
|
|
async compact() {
|
|
return { segmentsCompacted: 0, bytesReclaimed: 0, epoch: 0 };
|
|
}
|
|
async status() {
|
|
this.ensureHandle();
|
|
try {
|
|
const outPtr = this.wasm.rvf_alloc(20);
|
|
this.wasm.rvf_store_status(this.handle, outPtr);
|
|
const view = new DataView(this.wasm.memory.buffer);
|
|
const totalVectors = view.getUint32(outPtr, true);
|
|
const dim = view.getUint32(outPtr + 4, true);
|
|
this.wasm.rvf_free(outPtr, 20);
|
|
return {
|
|
totalVectors,
|
|
totalSegments: 1,
|
|
fileSizeBytes: 0,
|
|
epoch: 0,
|
|
profileId: 0,
|
|
compactionState: 'idle',
|
|
deadSpaceRatio: 0,
|
|
readOnly: false,
|
|
};
|
|
}
|
|
catch (err) {
|
|
throw errors_1.RvfError.fromNative(err);
|
|
}
|
|
}
|
|
async close() {
|
|
if (!this.handle)
|
|
return;
|
|
try {
|
|
this.wasm.rvf_store_close(this.handle);
|
|
}
|
|
catch (err) {
|
|
throw errors_1.RvfError.fromNative(err);
|
|
}
|
|
finally {
|
|
this.handle = 0;
|
|
}
|
|
}
|
|
async fileId() {
|
|
throw new errors_1.RvfError(errors_1.RvfErrorCode.BackendNotFound, 'fileId not supported in WASM backend');
|
|
}
|
|
async parentId() {
|
|
throw new errors_1.RvfError(errors_1.RvfErrorCode.BackendNotFound, 'parentId not supported in WASM backend');
|
|
}
|
|
async lineageDepth() {
|
|
throw new errors_1.RvfError(errors_1.RvfErrorCode.BackendNotFound, 'lineageDepth not supported in WASM backend');
|
|
}
|
|
async derive(_childPath, _options) {
|
|
throw new errors_1.RvfError(errors_1.RvfErrorCode.BackendNotFound, 'derive not supported in WASM backend');
|
|
}
|
|
async embedKernel() {
|
|
throw new errors_1.RvfError(errors_1.RvfErrorCode.BackendNotFound, 'embedKernel not supported in WASM backend');
|
|
}
|
|
async extractKernel() {
|
|
throw new errors_1.RvfError(errors_1.RvfErrorCode.BackendNotFound, 'extractKernel not supported in WASM backend');
|
|
}
|
|
async embedEbpf() {
|
|
throw new errors_1.RvfError(errors_1.RvfErrorCode.BackendNotFound, 'embedEbpf not supported in WASM backend');
|
|
}
|
|
async extractEbpf() {
|
|
throw new errors_1.RvfError(errors_1.RvfErrorCode.BackendNotFound, 'extractEbpf not supported in WASM backend');
|
|
}
|
|
async segments() {
|
|
throw new errors_1.RvfError(errors_1.RvfErrorCode.BackendNotFound, 'segments not supported in WASM backend');
|
|
}
|
|
async dimension() {
|
|
this.ensureHandle();
|
|
const d = this.wasm.rvf_store_dimension(this.handle);
|
|
if (d < 0)
|
|
throw new errors_1.RvfError(errors_1.RvfErrorCode.StoreClosed);
|
|
return d;
|
|
}
|
|
}
|
|
exports.WasmBackend = WasmBackend;
|
|
// ---------------------------------------------------------------------------
|
|
// Backend resolution
|
|
// ---------------------------------------------------------------------------
|
|
/**
|
|
* Resolve a `BackendType` to a concrete `RvfBackend` instance.
|
|
*
|
|
* - `'node'` Always returns a `NodeBackend`.
|
|
* - `'wasm'` Always returns a `WasmBackend`.
|
|
* - `'auto'` Tries `node` first, falls back to `wasm`.
|
|
*/
|
|
function resolveBackend(type) {
|
|
switch (type) {
|
|
case 'node':
|
|
return new NodeBackend();
|
|
case 'wasm':
|
|
return new WasmBackend();
|
|
case 'auto': {
|
|
// In Node.js environments, prefer native; in browsers, prefer WASM.
|
|
const isNode = typeof process !== 'undefined' &&
|
|
typeof process.versions !== 'undefined' &&
|
|
typeof process.versions.node === 'string';
|
|
return isNode ? new NodeBackend() : new WasmBackend();
|
|
}
|
|
}
|
|
}
|
|
// ---------------------------------------------------------------------------
|
|
// Mapping helpers (TS options -> native/wasm shapes)
|
|
// ---------------------------------------------------------------------------
|
|
function mapMetricToNative(metric) {
|
|
switch (metric) {
|
|
case 'cosine':
|
|
return 'Cosine';
|
|
case 'dotproduct':
|
|
return 'InnerProduct';
|
|
case 'l2':
|
|
default:
|
|
return 'L2';
|
|
}
|
|
}
|
|
function mapCompressionToNative(compression) {
|
|
switch (compression) {
|
|
case 'scalar':
|
|
return 'Scalar';
|
|
case 'product':
|
|
return 'Product';
|
|
case 'none':
|
|
default:
|
|
return 'None';
|
|
}
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
function mapOptionsToNative(options) {
|
|
return {
|
|
dimension: options.dimensions,
|
|
metric: mapMetricToNative(options.metric),
|
|
profile: options.profile ?? 0,
|
|
compression: mapCompressionToNative(options.compression),
|
|
signing: options.signing ?? false,
|
|
m: options.m ?? 16,
|
|
ef_construction: options.efConstruction ?? 200,
|
|
};
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
function mapQueryOptionsToNative(options) {
|
|
return {
|
|
ef_search: options.efSearch ?? 100,
|
|
// NAPI accepts the filter as a JSON string, not an object.
|
|
filter: options.filter ? JSON.stringify(options.filter) : undefined,
|
|
timeout_ms: options.timeoutMs ?? 0,
|
|
};
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
function mapNativeStatus(s) {
|
|
return {
|
|
totalVectors: s.total_vectors ?? s.totalVectors ?? 0,
|
|
totalSegments: s.total_segments ?? s.totalSegments ?? 0,
|
|
fileSizeBytes: s.file_size ?? s.fileSizeBytes ?? 0,
|
|
epoch: s.current_epoch ?? s.epoch ?? 0,
|
|
profileId: s.profile_id ?? s.profileId ?? 0,
|
|
compactionState: mapCompactionState(s.compaction_state ?? s.compactionState),
|
|
deadSpaceRatio: s.dead_space_ratio ?? s.deadSpaceRatio ?? 0,
|
|
readOnly: s.read_only ?? s.readOnly ?? false,
|
|
};
|
|
}
|
|
function mapCompactionState(state) {
|
|
if (typeof state === 'string') {
|
|
const lower = state.toLowerCase();
|
|
if (lower === 'running')
|
|
return 'running';
|
|
if (lower === 'emergency')
|
|
return 'emergency';
|
|
}
|
|
return 'idle';
|
|
}
|
|
//# sourceMappingURL=backend.js.map
|