// Runs both LibSQLVector builds against the same in-browser libSQL (Wasm) database. import { createClient } from '@libsql/client'; import { LibSQLVector as PublishedVector } from '@mastra/libsql'; // npm @mastra/libsql@1.25.1 import { LibSQLVector as FixedVector } from '../vendor/libsql-fix/index.js'; // fork build, see vendor/libsql-fix/SOURCE.md const INDEX = 'chunks'; function mulberry32(seed) { return () => { seed |= 0; seed = (seed + 0x6d2b79f5) | 0; let t = Math.imul(seed ^ (seed >>> 15), 1 | seed); t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; } const post = (type, payload = {}) => self.postMessage({ type, ...payload }); const median = xs => [...xs].sort((a, b) => a - b)[Math.floor(xs.length / 2)]; // Record the SQL each store sends, so the page can show which branch ran. function spy(store) { const log = []; const original = store.turso.execute.bind(store.turso); store.turso.execute = stmt => { const s = typeof stmt === 'string' ? { sql: stmt, args: [] } : stmt; if (s.sql.includes('vector_distance_cos')) log.push(s); return original(stmt); }; return log; } function describeStatement(stmt) { if (!stmt) return null; const lines = stmt.sql.split('\n').filter(l => l.trim()); const indent = Math.min(...lines.map(l => l.match(/^ */)[0].length)); let sql = lines.map(l => l.slice(indent)).join('\n'); const literal = sql.match(/'(\[[^']{40,}\])'/); if (literal) { const head = literal[1].slice(0, 28); sql = sql.replace(literal[0], `'${head}…' /* ${literal[1].length.toLocaleString('en-US')} chars inlined */`); } const args = (stmt.args ?? []).map(a => typeof a === 'string' && a.length > 60 ? `'${a.slice(0, 24)}…' (${a.length.toLocaleString('en-US')} chars)` : JSON.stringify(a), ); return { sql, args, usesTopK: stmt.sql.includes('vector_top_k') }; } async function run({ n, dim, topK, runs, filterMode }) { const t0 = performance.now(); const url = `file:demo-${Date.now()}.db`; const rnd = mulberry32(42); const randomVector = () => Array.from({ length: dim }, () => rnd() * 2 - 1); const filter = filterMode === 'bucket' ? { bucket: 3 } : undefined; post('status', { text: 'Creating index…' }); const setup = new PublishedVector({ url, id: 'setup' }); await setup.createIndex({ indexName: INDEX, dimension: dim }); // Same as an app that drops the DiskANN index because inserts into it are expensive at high dimensions. const raw = createClient({ url }); await raw.execute(`DROP INDEX ${INDEX}_vector_idx`); const BATCH = 250; for (let off = 0; off < n; off += BATCH) { const size = Math.min(BATCH, n - off); const vectors = Array.from({ length: size }, randomVector); await setup.upsert({ indexName: INDEX, vectors, metadata: vectors.map((_, j) => ({ i: off + j, bucket: (off + j) % 10 })), ids: vectors.map((_, j) => `v${off + j}`), }); post('seed', { done: off + size, total: n }); } await setup.close(); // Fresh instances: on init they discover that chunks_vector_idx does not exist. const published = new PublishedVector({ url, id: 'published' }); const fixed = new FixedVector({ url, id: 'fixed' }); const publishedLog = spy(published); const fixedLog = spy(fixed); const queryVector = randomVector(); const params = { indexName: INDEX, queryVector, topK, ...(filter ? { filter } : {}) }; const times = { published: [], fixed: [] }; let out = {}; // One warm-up round, then interleave the two builds so drift affects both equally. for (let r = -1; r < runs; r++) { for (const [name, store] of [ ['published', published], ['fixed', fixed], ]) { post('status', { text: r < 0 ? `Warm-up: ${name}…` : `Run ${r + 1}/${runs}: ${name}…` }); const s = performance.now(); out[name] = await store.query(params); const ms = performance.now() - s; if (r >= 0) times[name].push(ms); post('tick', { name, run: r, ms }); } } const identical = JSON.stringify(out.published) === JSON.stringify(out.fixed); await published.close(); await fixed.close(); raw.close(); return { n, dim, topK, runs, filter: filter ?? null, published: { ms: median(times.published), all: times.published, statement: describeStatement(publishedLog.at(-1)) }, fixed: { ms: median(times.fixed), all: times.fixed, statement: describeStatement(fixedLog.at(-1)) }, identical, rows: out.published.length, top: out.published.slice(0, 3).map(r => ({ id: r.id, score: r.score })), totalMs: performance.now() - t0, }; } self.onmessage = async ({ data }) => { try { post('done', { result: await run(data) }); } catch (error) { console.error(error); post('error', { message: String(error?.stack ?? error) }); } }; post('ready');