{"version":3,"file":"index.cjs","sources":["../src/utils/similarity.js","../src/utils/neighbors.js"],"sourcesContent":["/**\n * Calculates cosine similarity between two vectors.\n * Measures how similar their directions are, ignoring magnitude.\n * Use for comparing semantic or normalized vectors (e.g., text embeddings).\n * \n * @public\n * @param {number[]} vecA - First vector.\n * @param {number[]} vecB - Second vector.\n * @returns {number} - Cosine similarity score between `vecA` and `vecB`.\n * @example\n * computeCosineSimilarity([1, 2, 3], [1, 2, 3]);\n * // => 1 (identical vectors)\n * computeCosineSimilarity([1, 0], [0, 1]);\n * // => 0 (orthogonal vectors)\n * computeCosineSimilarity([1, 2], [2, 3]);\n * // => 0.992...\n * computeCosineSimilarity([1, 0], [-1, 0]);\n * // => -1 (vectors diametrically opposed)\n * computeCosineSimilarity([0, 0], [1, 2]);\n * // => 0 (one vector has zero magnitude)\n */\nfunction computeCosineSimilarity(vecA, vecB) {\n    let dot = 0;\n    let magA = 0;\n    let magB = 0;\n    for (let i = 0; i < vecA.length; i++) {\n        const a = vecA[i];\n        const b = vecB[i];\n        dot += a * b;\n        magA += a * a;\n        magB += b * b;\n    }\n    const denom = Math.sqrt(magA) * Math.sqrt(magB);\n    return denom === 0 ? 0 : dot / denom;\n}\n\n/**\n * Calculates Euclidean distance between two vectors.\n * Measures straight-line distance considering both magnitude and direction.\n * Use for raw numeric data or spatial coordinates.\n * \n * @public\n * @param {number[]} vecA - First vector.\n * @param {number[]} vecB - Second vector.\n * @returns {number} - Euclidean distance between `vecA` and `vecB`.\n * @example\n * computeEuclideanDistance([1, 2], [4, 6]);\n * // => 5 (distance between (1,2) and (4,6))\n * computeEuclideanDistance([0, 0], [0, 0]);\n * // => 0 (identical vectors)\n * computeEuclideanDistance([1, 0], [0, 1]);\n * // => 1.414...\n * computeEuclideanDistance([1, 2, 3], [4, 5, 6]);\n * // => 5.196...\n */\nfunction computeEuclideanDistance(vecA, vecB) {\n    let sum = 0;\n    for (let i = 0; i < vecA.length; i++) {\n        const diff = vecA[i] - vecB[i];\n        sum += diff * diff;\n    }\n    return Math.sqrt(sum);\n}\n\n/**\n * Calculates Manhattan distance between two vectors.\n * Measures sum of absolute differences.\n * Use for grid-like data or when less sensitive to large differences.\n * \n * @public\n * @param {number[]} vecA - First vector.\n * @param {number[]} vecB - Second vector.\n * @returns {number} - Manhattan distance between `vecA` and `vecB`.\n * @example\n * computeManhattanDistance([1, 2, 3], [4, 5, 6]);\n * // => 9\n * computeManhattanDistance([1, 0], [0, 1]);\n * // => 2\n * computeManhattanDistance([1, 2], [1, 2]);\n * // => 0 (identical vectors)\n * computeManhattanDistance([1, -1], [-1, 1]);\n * // => 4\n */\nfunction computeManhattanDistance(vecA, vecB) {\n    let sum = 0;\n    for (let i = 0; i < vecA.length; i++) {\n        sum += Math.abs(vecA[i] - vecB[i]);\n    }\n    return sum;\n}\n\n/**\n * Normalizes a vector to unit length. If the vector has zero magnitude, returns the original vector.\n * @public\n * @param {number[]} vec - Input vector.\n * @returns {number[]} - A new vector scaled to unit length.\n * @example\n * normalizeVector([3, 4]);\n * // => [0.6, 0.8] (vector normalized to length 1)\n * normalizeVector([0, 0]);\n * // => [0, 0] (zero vector remains unchanged)\n * normalizeVector([1, 1, 1]);\n * // => [0.5773502691896258, 0.5773502691896258, 0.5773502691896258]\n */\nfunction normalizeVector(vec) {\n    let sumSquares = 0;\n    for (let i = 0; i < vec.length; i++) {\n        sumSquares += vec[i] * vec[i];\n    }\n    const magnitude = Math.sqrt(sumSquares);\n    if (magnitude === 0) return vec.slice();\n    const result = new Array(vec.length);\n    for (let i = 0; i < vec.length; i++) {\n        result[i] = vec[i] / magnitude;\n    }\n    return result;\n}\n\n/**\n * Efficiently checks if a vector is L2-normalized (unit length).\n * @public\n * @param {number[]} vec - Input vector.\n * @param {number} [epsilon=1e-6] - Tolerance for floating-point comparison.\n * @returns {boolean} - True if the L2 norm is within epsilon of 1.\n * @example\n * isNormalized([1, 0]);\n * // => true (vector length is exactly 1)\n * isNormalized([0.6, 0.8]);\n * // => true (approximately unit length)\n * isNormalized([3, 4]);\n * // => false (length is 5)\n * isNormalized([0, 0]);\n * // => false (length is 0)\n */\nfunction isNormalized(vec, epsilon = 1e-6) {\n    let sum = 0;\n    for (let i = 0; i < vec.length; i++) {\n        const x = vec[i];\n        sum += x * x;\n    }\n    return Math.abs(sum - 1) <= epsilon;\n}\n\n/**\n * Computes the mean (centroid) vector from an array of vectors.\n * Assumes all vectors are of equal length.\n * @public\n * @param {number[][]} vectors - Array of input vectors.\n * @returns {number[]} - The mean vector.\n * @example\n * meanVector([[1, 2], [3, 4], [5, 6]]);\n * // => [3, 4]\n * meanVector([]);\n * // => []\n */\nfunction meanVector(vectors) {\n    const numVectors = vectors.length;\n    if (numVectors === 0) return [];\n    const dim = vectors[0].length;\n    const mean = new Array(dim).fill(0);\n    for (let i = 0; i < numVectors; i++) {\n        const vec = vectors[i];\n        for (let j = 0; j < dim; j++) {\n            mean[j] += vec[j];\n        }\n    }\n    for (let j = 0; j < dim; j++) {\n        mean[j] /= numVectors;\n    }\n    return mean;\n}\n\n\nexport {\n    computeCosineSimilarity,\n    computeEuclideanDistance,\n    computeManhattanDistance,\n    normalizeVector,\n    isNormalized,\n    meanVector,\n};","import { computeCosineSimilarity, computeEuclideanDistance, computeManhattanDistance } from './similarity.js';\n\n/**\n * Finds the nearest neighbors to a given query embedding from a list of samples\n * based on the specified distance/similarity method.\n *  \n * `'cosine'`: Cosine similarity (higher = more similar, range: [-1, 1]).\n * \n * `'euclidean'`: Euclidean distance (lower = closer, ≥ 0).\n * \n * `'manhattan'`: Manhattan distance (lower = closer, ≥ 0).\n * \n * @public\n * @param {number[]} queryEmbedding - The embedding vector to compare against.\n * @param {{ embedding: number[], label: string }[]} samples - An array of samples, each with an `embedding` and a `label`.\n * @param {object} [options={}] - Optional settings.\n * @param {number} [options.topK=1] - Number of top results to return. Default is 1.\n * @param {number} [options.threshold] - Minimum similarity score threshold for results (cosine) or maximum distance threshold (euclidean/manhattan).\n * @param {'cosine' | 'euclidean' | 'manhattan'} [options.method='cosine'] - The metric to compute:\n\n * @returns {{ embedding: number[], label: string, similarityScore?: number, distance?: number }[]} - An array of nearest neighbors with scores/distances.\n * @example\n * const samples = [\n *   { embedding: [1, 0], label: 'A' },\n *   { embedding: [0, 1], label: 'B' },\n *   { embedding: [1, 1], label: 'C' },\n * ];\n *\n * // Default cosine similarity\n * findNearestNeighbors([1, 0], samples);\n * // => [{ embedding: [1, 0], label: 'A', similarityScore: 1 }]\n *\n * // Euclidean distance\n * findNearestNeighbors([1, 0], samples, { method: 'euclidean', topK: 2 });\n * // => [\n * //   { embedding: [1, 0], label: 'A', distance: 0 },\n * //   { embedding: [1, 1], label: 'C', distance: 1 }\n * // ]\n *\n * // Manhattan distance with threshold\n * findNearestNeighbors([1, 0], samples, { method: 'manhattan', threshold: 1.5 });\n * // => [{ embedding: [1, 0], label: 'A', distance: 0 }, { embedding: [1, 1], label: 'C', distance: 1 }]\n *\n * // Cosine with threshold\n * findNearestNeighbors([1, 0], samples, { threshold: 0.9 });\n * // => [{ embedding: [1, 0], label: 'A', similarityScore: 1 }]\n */\nfunction findNearestNeighbors(queryEmbedding, samples, options = {}) {\n    const { topK, threshold, method = 'cosine' } = options;\n\n    // If threshold is provided but topK is not, return all matches within threshold\n    // If topK is provided but threshold is not, return top K matches\n    // If both are provided, return top K matches within threshold\n    // If neither is provided, return top 1 match\n    const effectiveTopK = topK !== undefined ? topK : (threshold !== undefined ? samples.length : 1);\n\n    /** @type {(typeof samples[number] & { similarityScore?: number, distance?: number })[]} */\n    const scoredSamples = [];\n\n    let computeFn, isDistance\n    /** @type {'similarityScore' | 'distance'} */\n    let scoreKey;\n    switch (method) {\n        case 'euclidean':\n            computeFn = computeEuclideanDistance;\n            isDistance = true;\n            scoreKey = 'distance';\n            break;\n        case 'manhattan':\n            computeFn = computeManhattanDistance;\n            isDistance = true;\n            scoreKey = 'distance';\n            break;\n        case 'cosine':\n        default:\n            computeFn = computeCosineSimilarity;\n            isDistance = false;\n            scoreKey = 'similarityScore';\n            break;\n    }\n    for (const sample of samples) {\n        const score = computeFn(queryEmbedding, sample.embedding);\n\n        if (threshold !== undefined) {\n            const passesThreshold = isDistance ? score <= threshold : score >= threshold;\n            if (passesThreshold) {\n                scoredSamples.push({\n                    ...sample,\n                    [scoreKey]: score,\n                });\n            }\n        } else {\n            scoredSamples.push({\n                ...sample,\n                [scoreKey]: score,\n            });\n        }\n    }\n\n    // Sort by score/distance (descending for similarity, ascending for distance)\n    scoredSamples.sort((a, b) => {\n        if (scoreKey === 'similarityScore' || scoreKey === 'distance') {\n            const aScore = a[scoreKey] ?? 0;\n            const bScore = b[scoreKey] ?? 0;\n            return isDistance ? aScore - bScore : bScore - aScore;\n        }\n        return 0;\n    });\n    return scoredSamples.slice(0, effectiveTopK);\n}\n\n/**\n * Ranks all samples by similarity/distance to the query embedding.\n * Does NOT apply threshold or topK filtering.\n * @public\n * @param {number[]} queryEmbedding - The embedding vector to compare against.\n * @param {{ embedding: number[], label: string }[]} samples - Samples with embeddings and labels.\n * @param {object} [options={}] - Optional settings.\n * @param  {'cosine' | 'euclidean' | 'manhattan'} [options.method='cosine'] - Distance/similarity method to use. Default is 'cosine'.\n * @returns {{ embedding: number[], label: string, similarityScore?: number, distance?: number }[]} Sorted by best match first.\n * @example\n * const samples = [\n *   { embedding: [1, 0], label: 'A' },\n *   { embedding: [0, 1], label: 'B' },\n *   { embedding: [1, 1], label: 'C' },\n * ];\n * \n * // Default cosine similarity\n * rankBySimilarity([1, 0], samples);\n * // => [\n * //   { embedding: [1, 0], label: 'A', similarityScore: 1 },\n * //   { embedding: [1, 1], label: 'C', similarityScore: 0.707... },\n * //   { embedding: [0, 1], label: 'B', similarityScore: 0 }\n * // ]\n *\n * // Euclidean distance\n * rankBySimilarity([1, 0], samples, { method: 'euclidean' });\n * // => [\n * //   { embedding: [1, 0], label: 'A', distance: 0 },\n * //   { embedding: [1, 1], label: 'C', distance: 1 },\n * //   { embedding: [0, 1], label: 'B', distance: 1.414... }\n * // ]\n *\n * // Manhattan distance\n * rankBySimilarity([0, 1], samples, { method: 'manhattan' });\n * // => [\n * //   { embedding: [0, 1], label: 'B', distance: 0 },\n * //   { embedding: [1, 1], label: 'C', distance: 1 },\n * //   { embedding: [1, 0], label: 'A', distance: 2 }\n * // ]\n */\nfunction rankBySimilarity(queryEmbedding, samples, options = {}) {\n    const { method = 'cosine' } = options;\n    let computeFn, isDistance, scoreKey;\n    switch (method) {\n        case 'euclidean':\n            computeFn = computeEuclideanDistance;\n            isDistance = true;\n            scoreKey = 'distance';\n            break;\n        case 'manhattan':\n            computeFn = computeManhattanDistance;\n            isDistance = true;\n            scoreKey = 'distance';\n            break;\n        case 'cosine':\n        default:\n            computeFn = computeCosineSimilarity;\n            isDistance = false;\n            scoreKey = 'similarityScore';\n            break;\n    }\n\n    const results = new Array(samples.length);\n    for (let i = 0; i < samples.length; i++) {\n        results[i] = {\n            ...samples[i],\n            [scoreKey]: computeFn(queryEmbedding, samples[i].embedding),\n        };\n    }\n\n    // Sort by score/distance (descending for similarity, ascending for distance)\n    results.sort((a, b) =>\n        isDistance ? a[scoreKey] - b[scoreKey] : b[scoreKey] - a[scoreKey]\n    );\n\n    return results;\n}\n\nexport {\n    findNearestNeighbors,\n    rankBySimilarity,\n};"],"names":["computeCosineSimilarity","vecA","vecB","dot","magA","magB","i","length","a","b","denom","Math","sqrt","computeEuclideanDistance","sum","diff","computeManhattanDistance","abs","queryEmbedding","samples","options","topK","threshold","method","effectiveTopK","undefined","scoredSamples","computeFn","isDistance","scoreKey","sample","score","embedding","push","sort","aScore","bScore","slice","vec","epsilon","x","vectors","numVectors","dim","mean","Array","fill","j","sumSquares","magnitude","result","results"],"mappings":"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"}