Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
731
vendor/ruvector/npm/packages/rvf/src/backend.js
vendored
Normal file
731
vendor/ruvector/npm/packages/rvf/src/backend.js
vendored
Normal file
@@ -0,0 +1,731 @@
|
||||
"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
|
||||
Reference in New Issue
Block a user