From d57f82b4d07413da49f13e4634bd3cceef785425 Mon Sep 17 00:00:00 2001 From: Steven Roussey Date: Thu, 18 Jan 2024 12:29:55 -0800 Subject: [PATCH] Make: ESM and CommonJS release with fallbacks (#63) * Rework releases so they have pre-built binaries * Fallback JS implementations for unsupported hardware --------- Co-authored-by: Victor Guyard <19635051+FlipFloop@users.noreply.github.com> --- .github/workflows/prerelease.yml | 1 + .github/workflows/release.yml | 23 ++++-- .gitignore | 1 + .npmignore | 1 + bench.js | 51 ++++++++++++ golang/simsimd.go | 4 +- javascript/{bench.mjs => bench.js} | 12 ++- javascript/dist-package-cjs.json | 3 + javascript/dist-package-esm.json | 3 + javascript/fallback.ts | 73 ++++++++++++++++++ javascript/node-gyp-build.d.ts | 1 + javascript/simsimd.d.ts | 35 --------- javascript/simsimd.js | 47 ----------- javascript/simsimd.ts | 120 +++++++++++++++++++++++++++++ javascript/test.mjs | 119 +++++++++++++++++++++------- javascript/tsconfig-base.json | 25 ++++++ javascript/tsconfig-cjs.json | 8 ++ javascript/tsconfig-esm.json | 8 ++ package-lock.json | 64 ++++++++++----- package.json | 19 +++-- 20 files changed, 468 insertions(+), 150 deletions(-) create mode 100644 bench.js rename javascript/{bench.mjs => bench.js} (94%) create mode 100644 javascript/dist-package-cjs.json create mode 100644 javascript/dist-package-esm.json create mode 100644 javascript/fallback.ts create mode 100644 javascript/node-gyp-build.d.ts delete mode 100644 javascript/simsimd.d.ts delete mode 100644 javascript/simsimd.js create mode 100644 javascript/simsimd.ts create mode 100644 javascript/tsconfig-base.json create mode 100644 javascript/tsconfig-cjs.json create mode 100644 javascript/tsconfig-esm.json diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 47f008a8..884781b0 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -84,4 +84,5 @@ jobs: run: | npm ci --ignore-scripts npm run install + npm run build-js npm test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 49350e76..ee27ee05 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,12 +23,15 @@ jobs: - uses: actions/checkout@v3 with: persist-credentials: false - - uses: actions/setup-node@v3 + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 - run: npm install --ignore-scripts --save-dev --prefix ./package-ci @semantic-release/exec @semantic-release/git conventional-changelog-eslint semantic-release && npx --prefix ./package-ci semantic-release rebase: name: Rebase Dev. Branch - if: github.ref_name == 'main' + if: github.ref == 'refs/heads/main' needs: versioning runs-on: ubuntu-22.04 steps: @@ -83,6 +86,7 @@ jobs: publish_python: name: Publish Python + if: github.ref == 'refs/heads/main' needs: build_wheels runs-on: ubuntu-22.04 environment: @@ -105,7 +109,7 @@ jobs: print-hash: true build_javascript: - name: Build JavaScript + name: Build JavaScript Native Modules needs: versioning strategy: fail-fast: false @@ -146,7 +150,7 @@ jobs: sudo apt install gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu - run: npm ci --ignore-scripts - - run: npm run prebuild + - run: npm run prebuild-single if: matrix.os != 'macos-latest' - run: npm run prebuild-darwin-x64+arm64 env: @@ -183,7 +187,16 @@ jobs: - name: Look for links run: find . -type f -links +1 - - name: Test publish + - name: Install dependencies + run: npm ci --ignore-scripts + + - name: Build the JS from TS + run: npm run build-js + + - name: Last minute test with prebuild artifact + run: npm run test + + - name: Publish Dry Run run: npm publish --dry-run if: github.ref != 'refs/heads/main' diff --git a/.gitignore b/.gitignore index 25e03073..e57831c2 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ node_modules/ package-ci/ prebuilds/ build/ +javascript/dist # for Google Benchmark compare.py diff --git a/.npmignore b/.npmignore index 604208b2..a424b290 100644 --- a/.npmignore +++ b/.npmignore @@ -10,3 +10,4 @@ VERSION package-ci.json setup.py CMakeLists.txt +build diff --git a/bench.js b/bench.js new file mode 100644 index 00000000..5df4b4fe --- /dev/null +++ b/bench.js @@ -0,0 +1,51 @@ +const benchmark = require('benchmark'); + +// Assuming the vectors are of the same length +function cosineDistance(a, b) { + let dotProduct = 0; + let magA = 0; + let magB = 0; + for (let i = 0; i < a.length; i++) { + dotProduct += a[i] * b[i]; + magA += a[i] * a[i]; + magB += b[i] * b[i]; + } + return 1 - (dotProduct / (Math.sqrt(magA) * Math.sqrt(magB))); +} + + +// Generate random data for testing +const dimensions = 1536; // Adjust dimensions as needed +const array1 = Array.from({ length: dimensions }, () => Math.random() * 100); +const array2 = Array.from({ length: dimensions }, () => Math.random() * 100); +const floatArray1 = new Float32Array(array1); +const floatArray2 = new Float32Array(array2); + + +// Create benchmark suite +const singleSuite = new benchmark.Suite('Single Vector Processing'); + +// Single-vector processing benchmarks +singleSuite + + // Pure JavaScript + .add('Array of Numbers', () => { + cosineDistance(array1, array2); + }) + .add('TypedArray of Float32', () => { + cosineDistance(floatArray1, floatArray2); + }) + .on('cycle', (event) => { + if (event.target.error) { + console.error(String(event.target.error)); + } else { + console.log(String(event.target)); + } + }) + .on('complete', () => { + console.log('Fastest Single-Vector Processing is ' + singleSuite.filter('fastest').map('name')); + }) + .run({ + noCache: true, + async: false, + }); diff --git a/golang/simsimd.go b/golang/simsimd.go index cd1c574c..49bd476b 100644 --- a/golang/simsimd.go +++ b/golang/simsimd.go @@ -16,12 +16,12 @@ inline static simsimd_f32_t sqeuclidean_f32(simsimd_f32_t const* a, simsimd_f32_ import "C" -// CosineI8 computes the cosine similarity between two i8 vectors using the most suitable SIMD instruction set available. +// CosineI8 computes the cosine distance between two i8 vectors using the most suitable SIMD instruction set available. func CosineI8(a, b []int8) float32 { return float32(C.cosine_i8((*C.simsimd_i8_t)(&a[0]), (*C.simsimd_i8_t)(&b[0]), C.simsimd_size_t(len(a)))) } -// CosineF32 computes the cosine similarity between two i8 vectors using the most suitable SIMD instruction set available. +// CosineF32 computes the cosine distance between two i8 vectors using the most suitable SIMD instruction set available. func CosineF32(a, b []float32) float32 { return float32(C.cosine_f32((*C.simsimd_f32_t)(&a[0]), (*C.simsimd_f32_t)(&b[0]), C.simsimd_size_t(len(a)))) } diff --git a/javascript/bench.mjs b/javascript/bench.js similarity index 94% rename from javascript/bench.mjs rename to javascript/bench.js index aa433480..df3dec09 100644 --- a/javascript/bench.mjs +++ b/javascript/bench.js @@ -1,10 +1,8 @@ -import benchmark from 'benchmark'; -import * as math from 'mathjs'; -import usearch, { MetricKind } from 'usearch'; -import build from 'node-gyp-build'; -import process from 'node:process'; - -const simsimd = build(process.cwd()); +const benchmark = require('benchmark'); +const math = require('mathjs'); +const usearch = require('usearch'); +const MetricKind = usearch.MetricKind; +const simsimd = require("./dist/cjs/simsimd.js"); // Assuming the vectors are of the same length function cosineDistance(a, b) { diff --git a/javascript/dist-package-cjs.json b/javascript/dist-package-cjs.json new file mode 100644 index 00000000..6a0d2ef2 --- /dev/null +++ b/javascript/dist-package-cjs.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} \ No newline at end of file diff --git a/javascript/dist-package-esm.json b/javascript/dist-package-esm.json new file mode 100644 index 00000000..96ae6e57 --- /dev/null +++ b/javascript/dist-package-esm.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} \ No newline at end of file diff --git a/javascript/fallback.ts b/javascript/fallback.ts new file mode 100644 index 00000000..40df9526 --- /dev/null +++ b/javascript/fallback.ts @@ -0,0 +1,73 @@ +console.warn( + "It seems like your environment does't support the native simsimd module, so we are providing a JS fallback." +); + +/** + * Computes the inner distance between two arrays. + * @param {number[]} arr1 - The first array. + * @param {number[]} arr2 - The second array. + * @returns {number} The inner distance between arr1 and arr2. + */ +export function inner_distance(arr1: number[], arr2: number[]): number { + if (arr1.length !== arr2.length) { + throw new Error("Vectors must have the same length"); + } + + let result = 0; + for (let i = 0; i < arr1.length; i++) { + result += arr1[i] * arr2[i]; + } + return 1 - result; +} + +/** + * Computes the squared Euclidean distance between two arrays. + * @param {number[]} arr1 - The first array. + * @param {number[]} arr2 - The second array. + * @returns {number} The squared Euclidean distance between arr1 and arr2. + */ +export function sqeuclidean(arr1: number[], arr2: number[]): number { + if (arr1.length !== arr2.length) { + throw new Error("Vectors must have the same length"); + } + + let result = 0; + for (let i = 0; i < arr1.length; i++) { + result += (arr1[i] - arr2[i]) * (arr1[i] - arr2[i]); + } + return result; +} + +/** + * Computes the cosine distance between two arrays. + * @param {number[]} arr1 - The first array. + * @param {number[]} arr2 - The second array. + * @returns {number} The cosine distance between arr1 and arr2. + */ +export function cosine(arr1: number[], arr2: number[]): number { + if (arr1.length !== arr2.length) { + throw new Error("Vectors must have the same length"); + } + + let dotProduct = 0; + let magnitudeA = 0; + let magnitudeB = 0; + + for (let i = 0; i < arr1.length; i++) { + dotProduct += arr1[i] * arr2[i]; + magnitudeA += arr1[i] * arr1[i]; + magnitudeB += arr2[i] * arr2[i]; + } + + magnitudeA = Math.sqrt(magnitudeA); + magnitudeB = Math.sqrt(magnitudeB); + + if (magnitudeA === 0 || magnitudeB === 0) { + console.warn( + "Warning: One of the magnitudes is zero. Cosine similarity is undefined." + ); + return 0; + } + + return 1 - dotProduct / (magnitudeA * magnitudeB); +} diff --git a/javascript/node-gyp-build.d.ts b/javascript/node-gyp-build.d.ts new file mode 100644 index 00000000..da3d75a5 --- /dev/null +++ b/javascript/node-gyp-build.d.ts @@ -0,0 +1 @@ +declare module "node-gyp-build"; diff --git a/javascript/simsimd.d.ts b/javascript/simsimd.d.ts deleted file mode 100644 index d1e3a236..00000000 --- a/javascript/simsimd.d.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * @brief Computes the squared Euclidean distance between two vectors. - * @param {Float32Array|Int8Array} a - The first vector. - * @param {Float32Array|Int8Array} b - The second vector. - * @returns {number} The squared Euclidean distance between vectors a and b. - */ -export declare const sqeuclidean: (a: Float32Array | Int8Array, b: Float32Array | Int8Array) => number; -/** - * @brief Computes the cosine similarity between two vectors. - * @param {Float32Array|Int8Array} a - The first vector. - * @param {Float32Array|Int8Array} b - The second vector. - * @returns {number} The cosine similarity between vectors a and b. - */ -export declare const cosine: (a: Float32Array | Int8Array, b: Float32Array | Int8Array) => number; -/** - * @brief Computes the inner product of two vectors. - * @param {Float32Array} a - The first vector. - * @param {Float32Array} b - The second vector. - * @returns {number} The inner product of vectors a and b. - */ -export declare const inner: (a: Float32Array, b: Float32Array) => number; -/** - * @brief Computes the bitwise Hamming distance between two vectors. - * @param {Uint8Array} a - The first vector. - * @param {Uint8Array} b - The second vector. - * @returns {number} The Hamming distance between vectors a and b. - */ -export declare const hamming: (a: Uint8Array, b: Uint8Array) => number; -/** - * @brief Computes the bitwise Jaccard similarity coefficient between two vectors. - * @param {Uint8Array} a - The first vector. - * @param {Uint8Array} b - The second vector. - * @returns {number} The Jaccard similarity coefficient between vectors a and b. - */ -export declare const jaccard: (a: Uint8Array, b: Uint8Array) => number; diff --git a/javascript/simsimd.js b/javascript/simsimd.js deleted file mode 100644 index 7a90e76b..00000000 --- a/javascript/simsimd.js +++ /dev/null @@ -1,47 +0,0 @@ -const build = require('node-gyp-build'); -const path = require('path'); -const compiled = build(path.resolve(__dirname, '..')); - -module.exports = { - - /** - * @brief Computes the squared Euclidean distance between two vectors. - * @param {Float32Array|Int8Array} a - The first vector. - * @param {Float32Array|Int8Array} b - The second vector. - * @returns {number} The squared Euclidean distance between vectors a and b. - */ - sqeuclidean: compiled.sqeuclidean, - - /** - * @brief Computes the cosine similarity between two vectors. - * @param {Float32Array|Int8Array} a - The first vector. - * @param {Float32Array|Int8Array} b - The second vector. - * @returns {number} The cosine similarity between vectors a and b. - */ - cosine: compiled.cosine, - - /** - * @brief Computes the inner product of two vectors. - * @param {Float32Array} a - The first vector. - * @param {Float32Array} b - The second vector. - * @returns {number} The inner product of vectors a and b. - */ - inner: compiled.inner, - - /** - * @brief Computes the bitwise Hamming distance between two vectors. - * @param {Uint8Array} a - The first vector. - * @param {Uint8Array} b - The second vector. - * @returns {number} The Hamming distance between vectors a and b. - */ - hamming: compiled.hamming, - - /** - * @brief Computes the bitwise Jaccard similarity coefficient between two vectors. - * @param {Uint8Array} a - The first vector. - * @param {Uint8Array} b - The second vector. - * @returns {number} The Jaccard similarity coefficient between vectors a and b. - */ - jaccard: compiled.jaccard, - -}; diff --git a/javascript/simsimd.ts b/javascript/simsimd.ts new file mode 100644 index 00000000..8a728da3 --- /dev/null +++ b/javascript/simsimd.ts @@ -0,0 +1,120 @@ +import build from "node-gyp-build"; +import * as path from "node:path"; +import { existsSync } from "node:fs"; +import { getFileName, getRoot } from "bindings"; + +let compiled: any; + +try { + let builddir = getBuildDir(getDirName()); + compiled = build(builddir); +} catch (e) { + compiled = import("./fallback"); +} + +/** + * @brief Computes the squared Euclidean distance between two vectors. + * @param {Float32Array|Int8Array} a - The first vector. + * @param {Float32Array|Int8Array} b - The second vector. + * @returns {number} The squared Euclidean distance between vectors a and b. + */ +export const sqeuclidean = ( + a: Float32Array | Int8Array, + b: Float32Array | Int8Array +): number => { + return compiled.sqeuclidean(a, b); +}; + +/** + * @brief Computes the cosine distance between two vectors. + * @param {Float32Array|Int8Array} a - The first vector. + * @param {Float32Array|Int8Array} b - The second vector. + * @returns {number} The cosine distance between vectors a and b. + */ +export const cosine = ( + a: Float32Array | Int8Array, + b: Float32Array | Int8Array +): number => { + return compiled.cosine(a, b); +}; + +/** + * @brief Computes the inner product of two vectors. + * @param {Float32Array} a - The first vector. + * @param {Float32Array} b - The second vector. + * @returns {number} The inner product of vectors a and b. + */ +export const inner = (a: Float32Array, b: Float32Array): number => { + return compiled.inner(a, b); +}; + +/** + * @brief Computes the bitwise Hamming distance between two vectors. + * @param {Uint8Array} a - The first vector. + * @param {Uint8Array} b - The second vector. + * @returns {number} The Hamming distance between vectors a and b. + */ +export const hamming = (a: Uint8Array, b: Uint8Array): number => { + return compiled.hamming(a, b); +}; + +/** + * @brief Computes the bitwise Jaccard similarity coefficient between two vectors. + * @param {Uint8Array} a - The first vector. + * @param {Uint8Array} b - The second vector. + * @returns {number} The Jaccard similarity coefficient between vectors a and b. + */ +export const jaccard = (a: Uint8Array, b: Uint8Array): number => { + return compiled.jaccard(a, b); +}; + +/** + * @brief Computes the kullbackleibler similarity coefficient between two vectors. + * @param {Float32Array} a - The first vector. + * @param {Float32Array} b - The second vector. + * @returns {number} The Jaccard similarity coefficient between vectors a and b. + */ +export const kullbackleibler = (a: Float32Array, b: Float32Array): number => { + return compiled.kullbackleibler(a, b); +}; + +/** + * @brief Computes the jensenshannon similarity coefficient between two vectors. + * @param {Float32Array} a - The first vector. + * @param {Float32Array} b - The second vector. + * @returns {number} The Jaccard similarity coefficient between vectors a and b. + */ +export const jensenshannon = (a: Float32Array, b: Float32Array): number => { + return compiled.jensenshannon(a, b); +}; + +export default { + sqeuclidean, + cosine, + inner, + hamming, + jaccard, + kullbackleibler, + jensenshannon, +}; + +// utility functions to help find native builds + +function getBuildDir(dir: string) { + if (existsSync(path.join(dir, "build"))) return dir; + if (existsSync(path.join(dir, "prebuilds"))) return dir; + if (path.basename(dir) === ".next") { + // special case for next.js on custom node (not vercel) + const sideways = path.join(dir, "..", "node_modules", "simsimd"); + if (existsSync(sideways)) return getBuildDir(sideways); + } + if (dir === "/") throw new Error("Could not find native build for simsimd"); + return getBuildDir(path.join(dir, "..")); +} + +function getDirName() { + try { + if (__dirname) return __dirname; + } catch (e) {} + return getRoot(getFileName()); +} diff --git a/javascript/test.mjs b/javascript/test.mjs index 4ff8d255..90d7dbd2 100644 --- a/javascript/test.mjs +++ b/javascript/test.mjs @@ -1,48 +1,109 @@ -import test from 'node:test'; -import assert from 'node:assert'; -import build from 'node-gyp-build'; -import process from 'node:process'; +import test from "node:test"; +import assert from "node:assert"; -const simsimd = build(process.cwd()); +import * as simsimd from "./dist/esm/simsimd.js"; + +import { sqeuclidean, cosine, inner_distance } from "./dist/esm/fallback.js"; function assertAlmostEqual(actual, expected, tolerance = 1e-6) { - const lowerBound = expected - tolerance; - const upperBound = expected + tolerance; - assert(actual >= lowerBound && actual <= upperBound, `Expected ${actual} to be almost equal to ${expected}`); + const lowerBound = expected - tolerance; + const upperBound = expected + tolerance; + assert( + actual >= lowerBound && actual <= upperBound, + `Expected ${actual} to be almost equal to ${expected}` + ); } -test('Distance from itself', () => { +test("Distance from itself", () => { + const f32s = new Float32Array([1.0, 2.0, 3.0]); + assertAlmostEqual(simsimd.sqeuclidean(f32s, f32s), 0.0, 0.01); + assertAlmostEqual(simsimd.cosine(f32s, f32s), 0.0, 0.01); + + const f32sNormalized = new Float32Array([ + 1.0 / Math.sqrt(14), + 2.0 / Math.sqrt(14), + 3.0 / Math.sqrt(14), + ]); + assertAlmostEqual(simsimd.inner(f32sNormalized, f32sNormalized), 0.0, 0.01); - const f32s = new Float32Array([1.0, 2.0, 3.0]); - assertAlmostEqual(simsimd.sqeuclidean(f32s, f32s), 0.0, 0.01); - assertAlmostEqual(simsimd.cosine(f32s, f32s), 0.0, 0.01); + const f32sDistribution = new Float32Array([1.0 / 6, 2.0 / 6, 3.0 / 6]); + assertAlmostEqual( + simsimd.kullbackleibler(f32sDistribution, f32sDistribution), + 0.0, + 0.01 + ); + assertAlmostEqual( + simsimd.jensenshannon(f32sDistribution, f32sDistribution), + 0.0, + 0.01 + ); + + const u8s = new Uint8Array([1, 2, 3]); + assertAlmostEqual(simsimd.hamming(u8s, u8s), 0.0, 0.01); + assertAlmostEqual(simsimd.jaccard(u8s, u8s), 0.0, 0.01); +}); - const f32sNormalized = new Float32Array([1.0 / Math.sqrt(14), 2.0 / Math.sqrt(14), 3.0 / Math.sqrt(14)]); - assertAlmostEqual(simsimd.inner(f32sNormalized, f32sNormalized), 0.0, 0.01); +test("Distance from itself JS", () => { + const arr = [1.0, 2.0, 3.0]; - const f32sDistribution = new Float32Array([1.0 / 6, 2.0 / 6, 3.0 / 6]); - assertAlmostEqual(simsimd.kullbackleibler(f32sDistribution, f32sDistribution), 0.0, 0.01); - assertAlmostEqual(simsimd.jensenshannon(f32sDistribution, f32sDistribution), 0.0, 0.01); + assertAlmostEqual(sqeuclidean(arr, arr), 0.0, 0.01); + assertAlmostEqual(cosine(arr, arr), 0.0, 0.01); - const u8s = new Uint8Array([1, 2, 3]); - assertAlmostEqual(simsimd.hamming(u8s, u8s), 0.0, 0.01); - assertAlmostEqual(simsimd.jaccard(u8s, u8s), 0.0, 0.01); + const arrNormalized = new Float32Array([ + 1.0 / Math.sqrt(14), + 2.0 / Math.sqrt(14), + 3.0 / Math.sqrt(14), + ]); + assertAlmostEqual(inner_distance(arrNormalized, arrNormalized), 0.0, 0.01); }); const f32Array1 = new Float32Array([1.0, 2.0, 3.0]); const f32Array2 = new Float32Array([4.0, 5.0, 6.0]); -test('Squared Euclidean Distance', () => { - const result = simsimd.sqeuclidean(f32Array1, f32Array2); - assertAlmostEqual(result, 27.0, 0.01); +test("Squared Euclidean Distance", () => { + const result = simsimd.sqeuclidean(f32Array1, f32Array2); + assertAlmostEqual(result, 27.0, 0.01); +}); + +test("Inner Product", () => { + const result = simsimd.inner(f32Array1, f32Array2); + assertAlmostEqual(result, -31.0, 0.01); +}); + +test("Cosine Similarity", () => { + const result = simsimd.cosine(f32Array1, f32Array2); + assertAlmostEqual(result, 0.029, 0.01); +}); + +test("Squared Euclidean Distance JS", () => { + const result = sqeuclidean(f32Array1, f32Array2); + assertAlmostEqual(result, 27.0, 0.01); +}); + +test("Inner Product JS", () => { + const result = inner_distance(f32Array1, f32Array2); + assertAlmostEqual(result, -31.0, 0.01); +}); + +test("Cosine Similarity JS", () => { + const result = cosine(f32Array1, f32Array2); + assertAlmostEqual(result, 0.029, 0.01); +}); + +test("Squared Euclidean Distance C vs JS", () => { + const result = simsimd.sqeuclidean(f32Array1, f32Array2); + const resultjs = sqeuclidean(f32Array1, f32Array2); + assertAlmostEqual(resultjs, result, 0.01); }); -test('Inner Product', () => { - const result = simsimd.inner(f32Array1, f32Array2); - assertAlmostEqual(result, -31.0, 0.01); +test("Inner Distance C vs JS", () => { + const result = simsimd.inner(f32Array1, f32Array2); + const resultjs = inner_distance(f32Array1, f32Array2); + assertAlmostEqual(resultjs, result, 0.01); }); -test('Cosine Similarity', () => { - const result = simsimd.cosine(f32Array1, f32Array2); - assertAlmostEqual(result, 0.029, 0.01); +test("Cosine Similarity C vs JS", () => { + const result = simsimd.cosine(f32Array1, f32Array2); + const resultjs = cosine(f32Array1, f32Array2); + assertAlmostEqual(resultjs, result, 0.01); }); diff --git a/javascript/tsconfig-base.json b/javascript/tsconfig-base.json new file mode 100644 index 00000000..f916d5d7 --- /dev/null +++ b/javascript/tsconfig-base.json @@ -0,0 +1,25 @@ +{ + "compileOnSave": false, + "exclude": ["node_modules", "dist"], + "include": ["node-gyp-build.d.ts", "simsimd.ts"], + "compilerOptions": { + "allowJs": true, + "allowSyntheticDefaultImports": true, + "baseUrl": ".", + "declaration": true, + "esModuleInterop": true, + "inlineSourceMap": false, + "lib": ["esnext"], + "listEmittedFiles": false, + "listFiles": false, + "moduleResolution": "node", + "noFallthroughCasesInSwitch": true, + "pretty": true, + "resolveJsonModule": true, + "rootDir": ".", + "skipLibCheck": true, + "strict": true, + "traceResolution": false, + "types": ["node","./node-gyp-build.d.ts"] + } +} \ No newline at end of file diff --git a/javascript/tsconfig-cjs.json b/javascript/tsconfig-cjs.json new file mode 100644 index 00000000..13a7594b --- /dev/null +++ b/javascript/tsconfig-cjs.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig-base.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "dist/cjs", + "target": "es2015" + } +} \ No newline at end of file diff --git a/javascript/tsconfig-esm.json b/javascript/tsconfig-esm.json new file mode 100644 index 00000000..d03b8cb8 --- /dev/null +++ b/javascript/tsconfig-esm.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig-base.json", + "compilerOptions": { + "module": "esnext", + "outDir": "dist/esm", + "target": "esnext" + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a94b8520..07559fc3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,22 @@ { "name": "simsimd", - "version": "3.6.8", + "version": "3.6.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "simsimd", - "version": "3.6.8", + "version": "3.6.4", "hasInstallScript": true, "license": "Apache 2.0", "dependencies": { "@types/node": "^20.10.7", + "bindings": "^1.5.0", "node-addon-api": "^3.2.1", "node-gyp-build": "^4.8.0" }, "devDependencies": { + "@types/bindings": "^1.5.5", "node-gyp": "^10.0.1", "prebuildify": "^6.0.0", "typescript": "^5.3.3" @@ -25,13 +27,13 @@ "optionalDependencies": { "benchmark": "^2.1.4", "mathjs": "^11.11.2", - "usearch": "^2.7.2" + "usearch": "^2.8.15" } }, "node_modules/@babel/runtime": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.7.tgz", - "integrity": "sha512-w06OXVOFso7LcbzMiDGt+3X7Rh7Ho8MmgPoWU3rarH+8upf+wSU/grlGbWzQyr3DkdN6ZeuMFjpdwW0Q+HxobA==", + "version": "7.23.8", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.8.tgz", + "integrity": "sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==", "optional": true, "dependencies": { "regenerator-runtime": "^0.14.0" @@ -95,10 +97,19 @@ "node": ">=14" } }, + "node_modules/@types/bindings": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@types/bindings/-/bindings-1.5.5.tgz", + "integrity": "sha512-y59PRZBTo2/HuN94qRjyJD+465vGoXMsqz9MMJDbtJL9oT5/B+tAL6c3k10epIinC2/BBkLqKzKC6keukl8wdQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { - "version": "20.10.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.7.tgz", - "integrity": "sha512-fRbIKb8C/Y2lXxB5eVMj4IU7xpdox0Lh8bUPEdtLysaylsml1hOOx1+STloRs/B9nf7C6kPRmmg/V7aQW7usNg==", + "version": "20.11.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.0.tgz", + "integrity": "sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==", "dependencies": { "undici-types": "~5.26.4" } @@ -198,10 +209,12 @@ } }, "node_modules/bindings": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz", - "integrity": "sha512-u4cBQNepWxYA55FunZSM7wMi55yQaN0otnhhilNoWHq0MfOfJeQx0v0mRRpolGOExPjZcl6FtB0BB8Xkb88F0g==", - "optional": true + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dependencies": { + "file-uri-to-path": "1.0.0" + } }, "node_modules/bl": { "version": "4.1.0", @@ -444,6 +457,11 @@ "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", "dev": true }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, "node_modules/foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", @@ -700,12 +718,12 @@ } }, "node_modules/mathjs": { - "version": "11.11.2", - "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-11.11.2.tgz", - "integrity": "sha512-SL4/0Fxm9X4sgovUpJTeyVeZ2Ifnk4tzLPTYWDyR3AIx9SabnXYqtCkyJtmoF3vZrDPKGkLvrhbIL4YN2YbXLQ==", + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-11.12.0.tgz", + "integrity": "sha512-UGhVw8rS1AyedyI55DGz9q1qZ0p98kyKPyc9vherBkoueLntPfKtPBh14x+V4cdUWK0NZV2TBwqRFlvadscSuw==", "optional": true, "dependencies": { - "@babel/runtime": "^7.23.1", + "@babel/runtime": "^7.23.2", "complex.js": "^2.1.1", "decimal.js": "^10.4.3", "escape-latex": "^1.2.0", @@ -1516,9 +1534,9 @@ } }, "node_modules/usearch": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/usearch/-/usearch-2.7.2.tgz", - "integrity": "sha512-Y/DwE819I5AfCP0KwX+tiatGG80ptWyBoVCXSVyUcQT54+fpvtVjus8rNeEWmgoLymcGKdkDJ6F9U6ke+1HLMw==", + "version": "2.8.15", + "resolved": "https://registry.npmjs.org/usearch/-/usearch-2.8.15.tgz", + "integrity": "sha512-cAAy+NdHHoA54N0KA5b39SBaAy2XAg0GqTVYEZk/gvXx46WjBRNV22u5ge20UYz9zAYEDUMBj2igtuAfAMxZ+Q==", "hasInstallScript": true, "optional": true, "dependencies": { @@ -1530,6 +1548,12 @@ "node": "~10 >=10.20 || >=12.17" } }, + "node_modules/usearch/node_modules/bindings": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz", + "integrity": "sha512-u4cBQNepWxYA55FunZSM7wMi55yQaN0otnhhilNoWHq0MfOfJeQx0v0mRRpolGOExPjZcl6FtB0BB8Xkb88F0g==", + "optional": true + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 17c719e1..7e6822bf 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,6 @@ "homepage": "https://github.com/ashvardanian/simsimd", "author": "Ash Vardanian", "license": "Apache 2.0", - "main": "javascript/simsimd.js", "keywords": [ "vector", "distance", @@ -24,22 +23,32 @@ ], "scripts": { "install": "node-gyp-build", - "prebuild": "prebuildify --napi --strip --target=10.4.0", + "prebuild-single": "prebuildify --napi --strip --target=10.4.0", "prebuild-arm64": "prebuildify --arch arm64 --napi --strip --target=10.4.0", "prebuild-darwin-x64+arm64": "prebuildify --arch arm64+x64 --napi --strip --target=10.4.0", + "build-js": "rm -fr javascript/dist/* && tsc -p javascript/tsconfig-esm.json && tsc -p javascript/tsconfig-cjs.json && cp javascript/dist-package-esm.json javascript/dist/esm/package.json && cp javascript/dist-package-cjs.json javascript/dist/cjs/package.json", "test": "node --test ./javascript/test.mjs", - "bench": "node ./javascript/bench.mjs" + "bench": "node ./javascript/bench.js" + }, + "main": "javascript/dist/cjs/simsimd.js", + "module": "javascript/dist/esm/simsimd.js", + "exports": { + ".": { + "import": "./javascript/dist/esm/simsimd.js", + "require": "./javascript/dist/cjs/simsimd.js" + } }, - "gypfile": true, "engines": { "node": "~10 >=10.20 || >=12.17" }, "dependencies": { "@types/node": "^20.10.7", + "bindings": "^1.5.0", "node-addon-api": "^3.2.1", "node-gyp-build": "^4.8.0" }, "devDependencies": { + "@types/bindings": "^1.5.5", "node-gyp": "^10.0.1", "prebuildify": "^6.0.0", "typescript": "^5.3.3" @@ -47,6 +56,6 @@ "optionalDependencies": { "benchmark": "^2.1.4", "mathjs": "^11.11.2", - "usearch": "^2.7.2" + "usearch": "^2.8.15" } }