├── .github └── workflows │ └── build.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── examples ├── bun │ ├── example.js │ └── package.json ├── citus │ ├── example.js │ └── package.json ├── cohere │ ├── example.js │ └── package.json ├── disco │ ├── example.js │ └── package.json ├── hybrid-search │ ├── example.js │ └── package.json ├── loading │ ├── example.js │ └── package.json ├── openai │ ├── example.js │ └── package.json ├── pglite │ ├── example.js │ └── package.json ├── rdkit │ ├── example.js │ └── package.json ├── sparse-search │ ├── example.js │ └── package.json └── transformers │ ├── example.js │ └── package.json ├── package.json ├── prisma ├── migrations │ └── 0_init │ │ └── migration.sql └── schema.prisma ├── src ├── index.js ├── knex │ └── index.js ├── kysely │ └── index.js ├── mikro-orm │ ├── bit.js │ ├── halfvec.js │ ├── index.js │ ├── sparsevec.js │ └── vector.js ├── objection │ └── index.js ├── pg-promise │ └── index.js ├── pg │ └── index.js ├── sequelize │ ├── halfvec.js │ ├── index.js │ ├── sparsevec.js │ └── vector.js └── utils │ ├── index.js │ └── sparse-vector.js ├── tests ├── drizzle-orm.test.mjs ├── knex.test.mjs ├── kysely.test.mjs ├── mikro-orm.test.mjs ├── objection.test.mjs ├── pg-promise.test.mjs ├── pg.test.mjs ├── postgres.test.mjs ├── prisma.test.mjs ├── sequelize.test.mjs ├── slonik.test.mjs ├── sparse-vector.test.mjs ├── typeorm.test.mjs └── utils.test.mjs ├── tsconfig.json └── types ├── index.d.ts ├── knex └── index.d.ts ├── kysely └── index.d.ts ├── mikro-orm ├── bit.d.ts ├── halfvec.d.ts ├── index.d.ts ├── sparsevec.d.ts └── vector.d.ts ├── objection └── index.d.ts ├── pg-promise └── index.d.ts ├── pg └── index.d.ts ├── sequelize ├── halfvec.d.ts ├── index.d.ts ├── sparsevec.d.ts └── vector.d.ts └── utils ├── index.d.ts └── sparse-vector.d.ts /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [push, pull_request] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v4 8 | - uses: ankane/setup-postgres@v1 9 | with: 10 | database: pgvector_node_test 11 | dev-files: true 12 | - run: | 13 | cd /tmp 14 | git clone --branch v0.8.0 https://github.com/pgvector/pgvector.git 15 | cd pgvector 16 | make 17 | sudo make install 18 | 19 | # Node.js 20 | - uses: actions/setup-node@v4 21 | - run: npm install 22 | - run: npx prisma migrate dev 23 | - run: npm test 24 | 25 | # Bun 26 | - uses: oven-sh/setup-bun@v1 27 | - run: bun install 28 | - run: bunx prisma migrate reset --force 29 | - run: bun run test 30 | - run: | 31 | cd examples/bun 32 | bun install 33 | createdb pgvector_example 34 | bun run example.js 35 | 36 | # Deno 37 | - uses: denoland/setup-deno@v1 38 | with: 39 | deno-version: v2.x 40 | - run: deno install 41 | - run: deno task test 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | package-lock.json 3 | bun.lock 4 | bun.lockb 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.2.1 (2025-05-20) 2 | 3 | - Improved support for migrations with Sequelize 4 | 5 | ## 0.2.0 (2024-06-27) 6 | 7 | - Added support for `halfvec` and `sparsevec` types to node-postgres 8 | - Added support for `halfvec` and `sparsevec` types to Knex.js 9 | - Added support for `halfvec` and `sparsevec` types to Sequelize 10 | - Added support for `halfvec` and `sparsevec` types to pg-promise 11 | - Added support for `halfvec`, `bit`, and `sparsevec` types to MikroORM 12 | - Added `l1Distance`, `hammingDistance`, and `jaccardDistance` functions for Knex.js 13 | - Added `l1Distance`, `hammingDistance`, and `jaccardDistance` functions for Objection.js 14 | - Added `l1Distance`, `hammingDistance`, and `jaccardDistance` functions for Kysely 15 | - Added `l1Distance`, `hammingDistance`, and `jaccardDistance` functions for Sequelize 16 | - Added `l1Distance`, `hammingDistance`, and `jaccardDistance` functions for MikroORM 17 | - Added support for passing literals to distance functions with Sequelize 18 | - Removed experimental `drizzle-orm` module (no longer needed) 19 | - Dropped support for Node < 18 20 | 21 | ## 0.1.8 (2024-02-11) 22 | 23 | - Added support for MikroORM 6 24 | 25 | ## 0.1.7 (2023-12-18) 26 | 27 | - Added main module 28 | - Added `kysely` module 29 | - Added `mikro-orm` module 30 | - Added `objection` module 31 | - Added `pg-promise` module 32 | - Added distance functions for Sequelize 33 | 34 | ## 0.1.6 (2023-12-12) 35 | 36 | - Added `knex` module 37 | 38 | ## 0.1.5 (2023-08-19) 39 | 40 | - Added experimental `drizzle-orm` module 41 | - Improved TypeScript support 42 | 43 | ## 0.1.4 (2023-05-02) 44 | 45 | - Fixed error with `import` 46 | 47 | ## 0.1.3 (2023-04-25) 48 | 49 | - Added `utils` module 50 | 51 | ## 0.1.2 (2023-04-09) 52 | 53 | - Added types to published package 54 | 55 | ## 0.1.1 (2023-03-30) 56 | 57 | - Added types 58 | 59 | ## 0.1.0 (2021-06-18) 60 | 61 | - First release 62 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021-2025 Andrew Kane 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pgvector-node 2 | 3 | [pgvector](https://github.com/pgvector/pgvector) support for Node.js, Deno, and Bun (and TypeScript) 4 | 5 | Supports [node-postgres](https://github.com/brianc/node-postgres), [Knex.js](https://github.com/knex/knex), [Objection.js](https://github.com/vincit/objection.js), [Kysely](https://github.com/kysely-org/kysely), [Sequelize](https://github.com/sequelize/sequelize), [pg-promise](https://github.com/vitaly-t/pg-promise), [Prisma](https://github.com/prisma/prisma), [Postgres.js](https://github.com/porsager/postgres), [Slonik](https://github.com/gajus/slonik), [TypeORM](https://github.com/typeorm/typeorm), [MikroORM](https://github.com/mikro-orm/mikro-orm), [Drizzle ORM](https://github.com/drizzle-team/drizzle-orm), and [Bun SQL](https://bun.sh/docs/api/sql) 6 | 7 | [![Build Status](https://github.com/pgvector/pgvector-node/actions/workflows/build.yml/badge.svg)](https://github.com/pgvector/pgvector-node/actions) 8 | 9 | ## Installation 10 | 11 | Run: 12 | 13 | ```sh 14 | npm install pgvector 15 | ``` 16 | 17 | And follow the instructions for your database library: 18 | 19 | - [node-postgres](#node-postgres) 20 | - [Knex.js](#knexjs) 21 | - [Objection.js](#objectionjs) 22 | - [Kysely](#kysely) 23 | - [Sequelize](#sequelize) 24 | - [pg-promise](#pg-promise) 25 | - [Prisma](#prisma) 26 | - [Postgres.js](#postgresjs) 27 | - [Slonik](#slonik) 28 | - [TypeORM](#typeorm) 29 | - [MikroORM](#mikroorm) 30 | - [Drizzle ORM](#drizzle-orm) 31 | - [Bun SQL](#bun-sql) 32 | 33 | Or check out some examples: 34 | 35 | - [Embeddings](examples/openai/example.js) with OpenAI 36 | - [Binary embeddings](examples/cohere/example.js) with Cohere 37 | - [Sentence embeddings](examples/transformers/example.js) with Transformers.js 38 | - [Hybrid search](examples/hybrid-search/example.js) with Transformers.js 39 | - [Sparse search](examples/sparse-search/example.js) with Text Embeddings Inference 40 | - [Morgan fingerprints](examples/rdkit/example.js) with RDKit.js 41 | - [Recommendations](examples/disco/example.js) with Disco 42 | - [Horizontal scaling](examples/citus/example.js) with Citus 43 | - [WebAssembly](examples/pglite/example.js) with PGLite 44 | - [Bulk loading](examples/loading/example.js) with `COPY` 45 | 46 | ## node-postgres 47 | 48 | Enable the extension 49 | 50 | ```javascript 51 | await client.query('CREATE EXTENSION IF NOT EXISTS vector'); 52 | ``` 53 | 54 | Register the types for a client 55 | 56 | ```javascript 57 | import pgvector from 'pgvector/pg'; 58 | 59 | await pgvector.registerTypes(client); 60 | ``` 61 | 62 | or a pool 63 | 64 | ```javascript 65 | pool.on('connect', async function (client) { 66 | await pgvector.registerTypes(client); 67 | }); 68 | ``` 69 | 70 | Create a table 71 | 72 | ```javascript 73 | await client.query('CREATE TABLE items (id bigserial PRIMARY KEY, embedding vector(3))'); 74 | ``` 75 | 76 | Insert a vector 77 | 78 | ```javascript 79 | await client.query('INSERT INTO items (embedding) VALUES ($1)', [pgvector.toSql([1, 2, 3])]); 80 | ``` 81 | 82 | Get the nearest neighbors to a vector 83 | 84 | ```javascript 85 | const result = await client.query('SELECT * FROM items ORDER BY embedding <-> $1 LIMIT 5', [pgvector.toSql([1, 2, 3])]); 86 | ``` 87 | 88 | Add an approximate index 89 | 90 | ```javascript 91 | await client.query('CREATE INDEX ON items USING hnsw (embedding vector_l2_ops)'); 92 | // or 93 | await client.query('CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 100)'); 94 | ``` 95 | 96 | Use `vector_ip_ops` for inner product and `vector_cosine_ops` for cosine distance 97 | 98 | See a [full example](tests/pg.test.mjs) 99 | 100 | ## Knex.js 101 | 102 | Import the library 103 | 104 | ```javascript 105 | import pgvector from 'pgvector/knex'; 106 | ``` 107 | 108 | Enable the extension 109 | 110 | ```javascript 111 | await knex.schema.createExtensionIfNotExists('vector'); 112 | ``` 113 | 114 | Create a table 115 | 116 | ```javascript 117 | await knex.schema.createTable('items', (table) => { 118 | table.increments('id'); 119 | table.vector('embedding', 3); 120 | }); 121 | ``` 122 | 123 | Insert vectors 124 | 125 | ```javascript 126 | const newItems = [ 127 | {embedding: pgvector.toSql([1, 2, 3])}, 128 | {embedding: pgvector.toSql([4, 5, 6])} 129 | ]; 130 | await knex('items').insert(newItems); 131 | ``` 132 | 133 | Get the nearest neighbors to a vector 134 | 135 | ```javascript 136 | const items = await knex('items') 137 | .orderBy(knex.l2Distance('embedding', [1, 2, 3])) 138 | .limit(5); 139 | ``` 140 | 141 | Also supports `maxInnerProduct`, `cosineDistance`, `l1Distance`, `hammingDistance`, and `jaccardDistance` 142 | 143 | Add an approximate index 144 | 145 | ```javascript 146 | await knex.schema.alterTable('items', function (table) { 147 | table.index(knex.raw('embedding vector_l2_ops'), 'index_name', 'hnsw'); 148 | }); 149 | ``` 150 | 151 | Use `vector_ip_ops` for inner product and `vector_cosine_ops` for cosine distance 152 | 153 | See a [full example](tests/knex.test.mjs) 154 | 155 | ## Objection.js 156 | 157 | Import the library 158 | 159 | ```javascript 160 | import pgvector from 'pgvector/objection'; 161 | ``` 162 | 163 | Enable the extension 164 | 165 | ```javascript 166 | await knex.schema.createExtensionIfNotExists('vector'); 167 | ``` 168 | 169 | Create a table 170 | 171 | ```javascript 172 | await knex.schema.createTable('items', (table) => { 173 | table.increments('id'); 174 | table.vector('embedding', 3); 175 | }); 176 | ``` 177 | 178 | Insert vectors 179 | 180 | ```javascript 181 | const newItems = [ 182 | {embedding: pgvector.toSql([1, 2, 3])}, 183 | {embedding: pgvector.toSql([4, 5, 6])} 184 | ]; 185 | await Item.query().insert(newItems); 186 | ``` 187 | 188 | Get the nearest neighbors to a vector 189 | 190 | ```javascript 191 | import { l2Distance } from 'pgvector/objection'; 192 | 193 | const items = await Item.query() 194 | .orderBy(l2Distance('embedding', [1, 2, 3])) 195 | .limit(5); 196 | ``` 197 | 198 | Also supports `maxInnerProduct`, `cosineDistance`, `l1Distance`, `hammingDistance`, and `jaccardDistance` 199 | 200 | Add an approximate index 201 | 202 | ```javascript 203 | await knex.schema.alterTable('items', function (table) { 204 | table.index(knex.raw('embedding vector_l2_ops'), 'index_name', 'hnsw'); 205 | }); 206 | ``` 207 | 208 | Use `vector_ip_ops` for inner product and `vector_cosine_ops` for cosine distance 209 | 210 | See a [full example](tests/objection.test.mjs) 211 | 212 | ## Kysely 213 | 214 | Enable the extension 215 | 216 | ```javascript 217 | await sql`CREATE EXTENSION IF NOT EXISTS vector`.execute(db); 218 | ``` 219 | 220 | Create a table 221 | 222 | ```javascript 223 | await db.schema.createTable('items') 224 | .addColumn('id', 'serial', (cb) => cb.primaryKey()) 225 | .addColumn('embedding', sql`vector(3)`) 226 | .execute(); 227 | ``` 228 | 229 | Insert vectors 230 | 231 | ```javascript 232 | import pgvector from 'pgvector/kysely'; 233 | 234 | const newItems = [ 235 | {embedding: pgvector.toSql([1, 2, 3])}, 236 | {embedding: pgvector.toSql([4, 5, 6])} 237 | ]; 238 | await db.insertInto('items').values(newItems).execute(); 239 | ``` 240 | 241 | Get the nearest neighbors to a vector 242 | 243 | ```javascript 244 | import { l2Distance } from 'pgvector/kysely'; 245 | 246 | const items = await db.selectFrom('items') 247 | .selectAll() 248 | .orderBy(l2Distance('embedding', [1, 2, 3])) 249 | .limit(5) 250 | .execute(); 251 | ``` 252 | 253 | Also supports `maxInnerProduct`, `cosineDistance`, `l1Distance`, `hammingDistance`, and `jaccardDistance` 254 | 255 | Get items within a certain distance 256 | 257 | ```javascript 258 | const items = await db.selectFrom('items') 259 | .selectAll() 260 | .where(l2Distance('embedding', [1, 2, 3]), '<', 5) 261 | .execute(); 262 | ``` 263 | 264 | Add an approximate index 265 | 266 | ```javascript 267 | await db.schema.createIndex('index_name') 268 | .on('items') 269 | .using('hnsw') 270 | .expression(sql`embedding vector_l2_ops`) 271 | .execute(); 272 | ``` 273 | 274 | Use `vector_ip_ops` for inner product and `vector_cosine_ops` for cosine distance 275 | 276 | See a [full example](tests/kysely.test.mjs) 277 | 278 | ## Sequelize 279 | 280 | Enable the extension 281 | 282 | ```javascript 283 | await sequelize.query('CREATE EXTENSION IF NOT EXISTS vector'); 284 | ``` 285 | 286 | Register the types 287 | 288 | ```javascript 289 | import { Sequelize } from 'sequelize'; 290 | import pgvector from 'pgvector/sequelize'; 291 | 292 | pgvector.registerTypes(Sequelize); 293 | ``` 294 | 295 | Add a vector field 296 | 297 | ```javascript 298 | const Item = sequelize.define('Item', { 299 | embedding: { 300 | type: DataTypes.VECTOR(3) 301 | } 302 | }, ...); 303 | ``` 304 | 305 | Insert a vector 306 | 307 | ```javascript 308 | await Item.create({embedding: [1, 2, 3]}); 309 | ``` 310 | 311 | Get the nearest neighbors to a vector 312 | 313 | ```javascript 314 | import { l2Distance } from 'pgvector/sequelize'; 315 | 316 | const items = await Item.findAll({ 317 | order: l2Distance('embedding', [1, 1, 1], sequelize), 318 | limit: 5 319 | }); 320 | ``` 321 | 322 | Also supports `maxInnerProduct`, `cosineDistance`, `l1Distance`, `hammingDistance`, and `jaccardDistance` 323 | 324 | Add an approximate index 325 | 326 | ```javascript 327 | const Item = sequelize.define('Item', ..., { 328 | indexes: [ 329 | { 330 | fields: ['embedding'], 331 | using: 'hnsw', 332 | operator: 'vector_l2_ops' 333 | } 334 | ] 335 | }); 336 | ``` 337 | 338 | Use `vector_ip_ops` for inner product and `vector_cosine_ops` for cosine distance 339 | 340 | See a [full example](tests/sequelize.test.mjs) 341 | 342 | ## pg-promise 343 | 344 | Enable the extension 345 | 346 | ```javascript 347 | await db.none('CREATE EXTENSION IF NOT EXISTS vector'); 348 | ``` 349 | 350 | Register the types 351 | 352 | ```javascript 353 | import pgpromise from 'pg-promise'; 354 | import pgvector from 'pgvector/pg-promise'; 355 | 356 | const initOptions = { 357 | async connect(e) { 358 | await pgvector.registerTypes(e.client); 359 | } 360 | }; 361 | const pgp = pgpromise(initOptions); 362 | ``` 363 | 364 | Create a table 365 | 366 | ```javascript 367 | await db.none('CREATE TABLE items (id bigserial PRIMARY KEY, embedding vector(3))'); 368 | ``` 369 | 370 | Insert a vector 371 | 372 | ```javascript 373 | await db.none('INSERT INTO items (embedding) VALUES ($1)', [pgvector.toSql([1, 2, 3])]); 374 | ``` 375 | 376 | Get the nearest neighbors to a vector 377 | 378 | ```javascript 379 | const result = await db.any('SELECT * FROM items ORDER BY embedding <-> $1 LIMIT 5', [pgvector.toSql([1, 2, 3])]); 380 | ``` 381 | 382 | Add an approximate index 383 | 384 | ```javascript 385 | await db.none('CREATE INDEX ON items USING hnsw (embedding vector_l2_ops)'); 386 | // or 387 | await db.none('CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 100)'); 388 | ``` 389 | 390 | Use `vector_ip_ops` for inner product and `vector_cosine_ops` for cosine distance 391 | 392 | See a [full example](tests/pg-promise.test.mjs) 393 | 394 | ## Prisma 395 | 396 | Note: `prisma migrate dev` does not support pgvector indexes 397 | 398 | Import the library 399 | 400 | ```javascript 401 | import pgvector from 'pgvector'; 402 | ``` 403 | 404 | Add the extension to the schema 405 | 406 | ```prisma 407 | generator client { 408 | provider = "prisma-client-js" 409 | previewFeatures = ["postgresqlExtensions"] 410 | } 411 | 412 | datasource db { 413 | provider = "postgresql" 414 | url = env("DATABASE_URL") 415 | extensions = [vector] 416 | } 417 | ``` 418 | 419 | Add a vector column to the schema 420 | 421 | ```prisma 422 | model Item { 423 | id Int @id @default(autoincrement()) 424 | embedding Unsupported("vector(3)")? 425 | } 426 | ``` 427 | 428 | Insert a vector 429 | 430 | ```javascript 431 | const embedding = pgvector.toSql([1, 2, 3]) 432 | await prisma.$executeRaw`INSERT INTO items (embedding) VALUES (${embedding}::vector)` 433 | ``` 434 | 435 | Get the nearest neighbors to a vector 436 | 437 | ```javascript 438 | const embedding = pgvector.toSql([1, 2, 3]) 439 | const items = await prisma.$queryRaw`SELECT id, embedding::text FROM items ORDER BY embedding <-> ${embedding}::vector LIMIT 5` 440 | ``` 441 | 442 | See a [full example](tests/prisma.test.mjs) (and the [schema](prisma/schema.prisma)) 443 | 444 | ## Postgres.js 445 | 446 | Import the library 447 | 448 | ```javascript 449 | import pgvector from 'pgvector'; 450 | ``` 451 | 452 | Enable the extension 453 | 454 | ```javascript 455 | await sql`CREATE EXTENSION IF NOT EXISTS vector`; 456 | ``` 457 | 458 | Create a table 459 | 460 | ```javascript 461 | await sql`CREATE TABLE items (id bigserial PRIMARY KEY, embedding vector(3))`; 462 | ``` 463 | 464 | Insert vectors 465 | 466 | ```javascript 467 | const newItems = [ 468 | {embedding: pgvector.toSql([1, 2, 3])}, 469 | {embedding: pgvector.toSql([4, 5, 6])} 470 | ]; 471 | await sql`INSERT INTO items ${ sql(newItems, 'embedding') }`; 472 | ``` 473 | 474 | Get the nearest neighbors to a vector 475 | 476 | ```javascript 477 | const embedding = pgvector.toSql([1, 2, 3]); 478 | const items = await sql`SELECT * FROM items ORDER BY embedding <-> ${ embedding } LIMIT 5`; 479 | ``` 480 | 481 | Add an approximate index 482 | 483 | ```javascript 484 | await sql`CREATE INDEX ON items USING hnsw (embedding vector_l2_ops)`; 485 | // or 486 | await sql`CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 100)`; 487 | ``` 488 | 489 | Use `vector_ip_ops` for inner product and `vector_cosine_ops` for cosine distance 490 | 491 | See a [full example](tests/postgres.test.mjs) 492 | 493 | ## Slonik 494 | 495 | Import the library 496 | 497 | ```javascript 498 | import pgvector from 'pgvector'; 499 | ``` 500 | 501 | Enable the extension 502 | 503 | ```javascript 504 | await pool.query(sql.unsafe`CREATE EXTENSION IF NOT EXISTS vector`); 505 | ``` 506 | 507 | Create a table 508 | 509 | ```javascript 510 | await pool.query(sql.unsafe`CREATE TABLE items (id serial PRIMARY KEY, embedding vector(3))`); 511 | ``` 512 | 513 | Insert a vector 514 | 515 | ```javascript 516 | const embedding = pgvector.toSql([1, 2, 3]); 517 | await pool.query(sql.unsafe`INSERT INTO items (embedding) VALUES (${embedding})`); 518 | ``` 519 | 520 | Get the nearest neighbors to a vector 521 | 522 | ```javascript 523 | const embedding = pgvector.toSql([1, 2, 3]); 524 | const items = await pool.query(sql.unsafe`SELECT * FROM items ORDER BY embedding <-> ${embedding} LIMIT 5`); 525 | ``` 526 | 527 | Add an approximate index 528 | 529 | ```javascript 530 | await pool.query(sql.unsafe`CREATE INDEX ON items USING hnsw (embedding vector_l2_ops)`); 531 | // or 532 | await pool.query(sql.unsafe`CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 100)`); 533 | ``` 534 | 535 | Use `vector_ip_ops` for inner product and `vector_cosine_ops` for cosine distance 536 | 537 | See a [full example](tests/slonik.test.mjs) 538 | 539 | ## TypeORM 540 | 541 | Import the library 542 | 543 | ```javascript 544 | import pgvector from 'pgvector'; 545 | ``` 546 | 547 | Enable the extension 548 | 549 | ```javascript 550 | await AppDataSource.query('CREATE EXTENSION IF NOT EXISTS vector'); 551 | ``` 552 | 553 | Create a table 554 | 555 | ```javascript 556 | await AppDataSource.query('CREATE TABLE item (id bigserial PRIMARY KEY, embedding vector(3))'); 557 | ``` 558 | 559 | Define an entity 560 | 561 | ```typescript 562 | @Entity() 563 | class Item { 564 | @PrimaryGeneratedColumn() 565 | id: number 566 | 567 | @Column() 568 | embedding: string 569 | } 570 | ``` 571 | 572 | Insert a vector 573 | 574 | ```javascript 575 | const itemRepository = AppDataSource.getRepository(Item); 576 | await itemRepository.save({embedding: pgvector.toSql([1, 2, 3])}); 577 | ``` 578 | 579 | Get the nearest neighbors to a vector 580 | 581 | ```javascript 582 | const items = await itemRepository 583 | .createQueryBuilder('item') 584 | .orderBy('embedding <-> :embedding') 585 | .setParameters({embedding: pgvector.toSql([1, 2, 3])}) 586 | .limit(5) 587 | .getMany(); 588 | ``` 589 | 590 | See a [full example](tests/typeorm.test.mjs) 591 | 592 | ## MikroORM 593 | 594 | Enable the extension 595 | 596 | ```javascript 597 | await em.execute('CREATE EXTENSION IF NOT EXISTS vector'); 598 | ``` 599 | 600 | Define an entity 601 | 602 | ```typescript 603 | import { VectorType } from 'pgvector/mikro-orm'; 604 | 605 | @Entity() 606 | class Item { 607 | @PrimaryKey() 608 | id: number; 609 | 610 | @Property({type: VectorType}) 611 | embedding: number[]; 612 | } 613 | ``` 614 | 615 | Insert a vector 616 | 617 | ```javascript 618 | em.create(Item, {embedding: [1, 2, 3]}); 619 | ``` 620 | 621 | Get the nearest neighbors to a vector 622 | 623 | ```javascript 624 | import { l2Distance } from 'pgvector/mikro-orm'; 625 | 626 | const items = await em.createQueryBuilder(Item) 627 | .orderBy({[l2Distance('embedding', [1, 2, 3])]: 'ASC'}) 628 | .limit(5) 629 | .getResult(); 630 | ``` 631 | 632 | Also supports `maxInnerProduct`, `cosineDistance`, `l1Distance`, `hammingDistance`, and `jaccardDistance` 633 | 634 | See a [full example](tests/mikro-orm.test.mjs) 635 | 636 | ## Drizzle ORM 637 | 638 | Drizzle ORM 0.31.0+ has [built-in support](https://orm.drizzle.team/docs/extensions/pg#pg_vector) for pgvector :tada: 639 | 640 | Enable the extension 641 | 642 | ```javascript 643 | await client`CREATE EXTENSION IF NOT EXISTS vector`; 644 | ``` 645 | 646 | Add a vector field 647 | 648 | ```javascript 649 | import { vector } from 'drizzle-orm/pg-core'; 650 | 651 | const items = pgTable('items', { 652 | id: serial('id').primaryKey(), 653 | embedding: vector('embedding', {dimensions: 3}) 654 | }); 655 | ``` 656 | 657 | Also supports `halfvec`, `bit`, and `sparsevec` 658 | 659 | Insert vectors 660 | 661 | ```javascript 662 | const newItems = [ 663 | {embedding: [1, 2, 3]}, 664 | {embedding: [4, 5, 6]} 665 | ]; 666 | await db.insert(items).values(newItems); 667 | ``` 668 | 669 | Get the nearest neighbors to a vector 670 | 671 | ```javascript 672 | import { l2Distance } from 'drizzle-orm'; 673 | 674 | const allItems = await db.select() 675 | .from(items) 676 | .orderBy(l2Distance(items.embedding, [1, 2, 3])) 677 | .limit(5); 678 | ``` 679 | 680 | Also supports `innerProduct`, `cosineDistance`, `l1Distance`, `hammingDistance`, and `jaccardDistance` 681 | 682 | See a [full example](tests/drizzle-orm.test.mjs) 683 | 684 | ## Bun SQL 685 | 686 | Import the library 687 | 688 | ```javascript 689 | import pgvector from 'pgvector'; 690 | ``` 691 | 692 | Enable the extension 693 | 694 | ```javascript 695 | await sql`CREATE EXTENSION IF NOT EXISTS vector`; 696 | ``` 697 | 698 | Create a table 699 | 700 | ```javascript 701 | await sql`CREATE TABLE items (id bigserial PRIMARY KEY, embedding vector(3))`; 702 | ``` 703 | 704 | Insert vectors 705 | 706 | ```javascript 707 | const newItems = [ 708 | {embedding: pgvector.toSql([1, 2, 3])}, 709 | {embedding: pgvector.toSql([4, 5, 6])} 710 | ]; 711 | await sql`INSERT INTO items ${sql(newItems)}`; 712 | ``` 713 | 714 | Get the nearest neighbors to a vector 715 | 716 | ```javascript 717 | const embedding = pgvector.toSql([1, 2, 3]); 718 | const items = await sql`SELECT * FROM items ORDER BY embedding <-> ${embedding} LIMIT 5`.values(); 719 | ``` 720 | 721 | Add an approximate index 722 | 723 | ```javascript 724 | await sql`CREATE INDEX ON items USING hnsw (embedding vector_l2_ops)`; 725 | // or 726 | await sql`CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 100)`; 727 | ``` 728 | 729 | Use `vector_ip_ops` for inner product and `vector_cosine_ops` for cosine distance 730 | 731 | See a [full example](examples/bun/example.js) 732 | 733 | ## Reference 734 | 735 | ### Sparse Vectors 736 | 737 | Create a sparse vector from an array 738 | 739 | ```javascript 740 | const vec = new SparseVector([1, 0, 2, 0, 3, 0]); 741 | ``` 742 | 743 | Or a map of non-zero elements 744 | 745 | ```javascript 746 | const vec = new SparseVector({0: 1, 2: 2, 4: 3}, 6); 747 | // or 748 | const map = new Map(); 749 | map.set(0, 1); 750 | map.set(2, 2); 751 | map.set(4, 3); 752 | const vec = new SparseVector(map, 6); 753 | ``` 754 | 755 | Note: Indices start at 0 756 | 757 | Get the number of dimensions 758 | 759 | ```javascript 760 | const dim = vec.dimensions; 761 | ``` 762 | 763 | Get the indices of non-zero elements 764 | 765 | ```javascript 766 | const indices = vec.indices; 767 | ``` 768 | 769 | Get the values of non-zero elements 770 | 771 | ```javascript 772 | const values = vec.values; 773 | ``` 774 | 775 | Get an array 776 | 777 | ```javascript 778 | const arr = vec.toArray(); 779 | ``` 780 | 781 | ## History 782 | 783 | View the [changelog](https://github.com/pgvector/pgvector-node/blob/master/CHANGELOG.md) 784 | 785 | ## Contributing 786 | 787 | Everyone is encouraged to help improve this project. Here are a few ways you can help: 788 | 789 | - [Report bugs](https://github.com/pgvector/pgvector-node/issues) 790 | - Fix bugs and [submit pull requests](https://github.com/pgvector/pgvector-node/pulls) 791 | - Write, clarify, or fix documentation 792 | - Suggest or add new features 793 | 794 | To get started with development: 795 | 796 | ```sh 797 | git clone https://github.com/pgvector/pgvector-node.git 798 | cd pgvector-node 799 | npm install 800 | createdb pgvector_node_test 801 | npx prisma migrate dev 802 | npm test 803 | ``` 804 | 805 | To run an example: 806 | 807 | ```sh 808 | cd examples/loading 809 | npm install 810 | createdb pgvector_example 811 | node example.js 812 | ``` 813 | -------------------------------------------------------------------------------- /examples/bun/example.js: -------------------------------------------------------------------------------- 1 | import { SQL } from 'bun'; 2 | import pgvector from 'pgvector'; 3 | 4 | const sql = new SQL({database: 'pgvector_example'}); 5 | 6 | await sql`CREATE EXTENSION IF NOT EXISTS vector`; 7 | await sql`DROP TABLE IF EXISTS items`; 8 | await sql`CREATE TABLE items (id bigserial PRIMARY KEY, embedding vector(3))`; 9 | 10 | const item = { 11 | embedding: pgvector.toSql([1, 2, 3]) 12 | }; 13 | await sql`INSERT INTO items ${sql(item)}`; 14 | 15 | const items = [ 16 | {embedding: pgvector.toSql([4, 5, 6])}, 17 | {embedding: pgvector.toSql([7, 8, 9])} 18 | ]; 19 | await sql`INSERT INTO items ${sql(items)}`; 20 | 21 | await sql`CREATE INDEX ON items USING hnsw (embedding vector_l2_ops)`; 22 | 23 | const rows = await sql`SELECT * FROM items ORDER BY embedding <-> ${item.embedding} LIMIT 5`.values(); 24 | console.log(rows); 25 | -------------------------------------------------------------------------------- /examples/bun/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "dependencies": { 5 | "pgvector": "file:../.." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/citus/example.js: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | import pgvector from 'pgvector/pg'; 3 | import { from as copyFrom } from 'pg-copy-streams'; 4 | import { stdout } from 'process'; 5 | 6 | // generate random data 7 | const rows = 100000; 8 | const dimensions = 128; 9 | const embeddings = Array.from({length: rows}, () => Array.from({length: dimensions}, () => Math.random())); 10 | const categories = Array.from({length: rows}, () => Math.floor(Math.random() * 100)); 11 | const queries = Array.from({length: 10}, () => Array.from({length: dimensions}, () => Math.random())); 12 | 13 | // enable extensions 14 | let client = new pg.Client({database: 'pgvector_citus'}); 15 | await client.connect(); 16 | await client.query('CREATE EXTENSION IF NOT EXISTS citus'); 17 | await client.query('CREATE EXTENSION IF NOT EXISTS vector'); 18 | 19 | // GUC variables set on the session do not propagate to Citus workers 20 | // https://github.com/citusdata/citus/issues/462 21 | // you can either: 22 | // 1. set them on the system, user, or database and reconnect 23 | // 2. set them for a transaction with SET LOCAL 24 | await client.query("ALTER DATABASE pgvector_citus SET maintenance_work_mem = '512MB'"); 25 | await client.query('ALTER DATABASE pgvector_citus SET hnsw.ef_search = 20'); 26 | await client.end(); 27 | 28 | // reconnect for updated GUC variables to take effect 29 | client = new pg.Client({database: 'pgvector_citus'}); 30 | await client.connect(); 31 | await pgvector.registerTypes(client); 32 | 33 | console.log('Creating distributed table'); 34 | await client.query('DROP TABLE IF EXISTS items'); 35 | await client.query(`CREATE TABLE items (id bigserial, embedding vector(${dimensions}), category_id bigint, PRIMARY KEY (id, category_id))`); 36 | await client.query('SET citus.shard_count = 4'); 37 | await client.query("SELECT create_distributed_table('items', 'category_id')"); 38 | 39 | console.log('Loading data in parallel'); 40 | const stream = client.query(copyFrom('COPY items (embedding, category_id) FROM STDIN')); 41 | for (const [i, embedding] of embeddings.entries()) { 42 | const line = `${pgvector.toSql(embedding)}\t${categories[i]}\n`; 43 | stream.flushChunk(line); 44 | } 45 | 46 | stream.on('finish', async function () { 47 | console.log('Creating index in parallel'); 48 | await client.query('CREATE INDEX ON items USING hnsw (embedding vector_l2_ops)'); 49 | 50 | console.log('Running distributed queries'); 51 | for (const query of queries) { 52 | const { rows } = await client.query('SELECT id FROM items ORDER BY embedding <-> $1 LIMIT 5', [pgvector.toSql(query)]); 53 | console.log(rows.map((r) => r.id)); 54 | } 55 | 56 | client.end(); 57 | }); 58 | stream.end(); 59 | -------------------------------------------------------------------------------- /examples/citus/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "dependencies": { 5 | "pg": "^8.11.3", 6 | "pg-copy-streams": "^6.0.6", 7 | "pgvector": "file:../.." 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/cohere/example.js: -------------------------------------------------------------------------------- 1 | import { CohereClient } from 'cohere-ai'; 2 | import pg from 'pg'; 3 | import pgvector from 'pgvector/pg'; 4 | 5 | const client = new pg.Client({database: 'pgvector_example'}); 6 | await client.connect(); 7 | 8 | await client.query('CREATE EXTENSION IF NOT EXISTS vector'); 9 | await pgvector.registerTypes(client); 10 | 11 | await client.query('DROP TABLE IF EXISTS documents'); 12 | await client.query('CREATE TABLE documents (id bigserial PRIMARY KEY, content text, embedding bit(1536))'); 13 | 14 | async function embed(texts, inputType) { 15 | const cohere = new CohereClient(); 16 | const response = await cohere.embed({ 17 | texts: texts, 18 | model: 'embed-v4.0', 19 | inputType: inputType, 20 | embeddingTypes: ['ubinary'] 21 | }); 22 | return response.embeddings.ubinary.map((e) => { 23 | return e.map((v) => v.toString(2).padStart(8, '0')).join('') 24 | }); 25 | } 26 | 27 | const input = [ 28 | 'The dog is barking', 29 | 'The cat is purring', 30 | 'The bear is growling' 31 | ]; 32 | const embeddings = await embed(input, 'search_document'); 33 | for (let [i, content] of input.entries()) { 34 | await client.query('INSERT INTO documents (content, embedding) VALUES ($1, $2)', [content, embeddings[i]]); 35 | } 36 | 37 | const query = 'forest'; 38 | const queryEmbedding = (await embed([query], 'search_query'))[0]; 39 | const { rows } = await client.query('SELECT content FROM documents ORDER BY embedding <~> $1 LIMIT 5', [queryEmbedding]); 40 | for (let row of rows) { 41 | console.log(row.content); 42 | } 43 | 44 | await client.end(); 45 | -------------------------------------------------------------------------------- /examples/cohere/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "dependencies": { 5 | "cohere-ai": "^7.10.6", 6 | "pg": "^8.11.3", 7 | "pgvector": "file:../.." 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/disco/example.js: -------------------------------------------------------------------------------- 1 | import { Recommender, loadMovieLens } from 'disco-rec'; 2 | import pg from 'pg'; 3 | import pgvector from 'pgvector/pg'; 4 | 5 | const client = new pg.Client({database: 'pgvector_example'}); 6 | await client.connect(); 7 | 8 | await client.query('CREATE EXTENSION IF NOT EXISTS vector'); 9 | await pgvector.registerTypes(client); 10 | 11 | await client.query('DROP TABLE IF EXISTS users'); 12 | await client.query('DROP TABLE IF EXISTS movies'); 13 | await client.query('CREATE TABLE users (id integer PRIMARY KEY, factors vector(20))'); 14 | await client.query('CREATE TABLE movies (name text PRIMARY KEY, factors vector(20))'); 15 | 16 | const data = await loadMovieLens(); 17 | const recommender = new Recommender({factors: 20}); 18 | recommender.fit(data); 19 | 20 | for (let userId of recommender.userIds()) { 21 | const factors = Array.from(recommender.userFactors(userId)); 22 | await client.query('INSERT INTO users (id, factors) VALUES ($1, $2)', [userId, pgvector.toSql(factors)]); 23 | } 24 | 25 | for (let itemId of recommender.itemIds()) { 26 | const factors = Array.from(recommender.itemFactors(itemId)); 27 | await client.query('INSERT INTO movies (name, factors) VALUES ($1, $2)', [itemId, pgvector.toSql(factors)]); 28 | } 29 | 30 | const movie = 'Star Wars (1977)'; 31 | console.log(`Item-based recommendations for ${movie}`); 32 | let result = await client.query('SELECT name FROM movies WHERE name != $1 ORDER BY factors <=> (SELECT factors FROM movies WHERE name = $1) LIMIT 5', [movie]); 33 | for (let row of result.rows) { 34 | console.log(`- ${row.name}`); 35 | } 36 | 37 | const userId = 123; 38 | console.log(`\nUser-based recommendations for user ${userId}`); 39 | result = await client.query('SELECT name FROM movies ORDER BY factors <#> (SELECT factors FROM users WHERE id = $1) LIMIT 5', [userId]); 40 | for (let row of result.rows) { 41 | console.log(`- ${row.name}`); 42 | } 43 | 44 | await client.end(); 45 | -------------------------------------------------------------------------------- /examples/disco/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "dependencies": { 5 | "disco-rec": "^0.2.0", 6 | "pg": "^8.11.3", 7 | "pgvector": "file:../.." 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/hybrid-search/example.js: -------------------------------------------------------------------------------- 1 | import { pipeline } from '@huggingface/transformers'; 2 | import pg from 'pg'; 3 | import pgvector from 'pgvector/pg'; 4 | 5 | const client = new pg.Client({database: 'pgvector_example'}); 6 | await client.connect(); 7 | 8 | await client.query('CREATE EXTENSION IF NOT EXISTS vector'); 9 | await pgvector.registerTypes(client); 10 | 11 | await client.query('DROP TABLE IF EXISTS documents'); 12 | await client.query('CREATE TABLE documents (id bigserial PRIMARY KEY, content text, embedding vector(384))'); 13 | await client.query("CREATE INDEX ON documents USING GIN (to_tsvector('english', content))"); 14 | 15 | const extractor = await pipeline('feature-extraction', 'Xenova/multi-qa-MiniLM-L6-cos-v1', {dtype: 'fp32'}); 16 | 17 | async function embed(content) { 18 | const output = await extractor(content, {pooling: 'mean', normalize: true}); 19 | return output.tolist(); 20 | } 21 | 22 | const input = [ 23 | 'The dog is barking', 24 | 'The cat is purring', 25 | 'The bear is growling' 26 | ]; 27 | const embeddings = await embed(input); 28 | for (let [i, content] of input.entries()) { 29 | await client.query('INSERT INTO documents (content, embedding) VALUES ($1, $2)', [content, pgvector.toSql(embeddings[i])]); 30 | } 31 | 32 | const sql = ` 33 | WITH semantic_search AS ( 34 | SELECT id, RANK () OVER (ORDER BY embedding <=> $2) AS rank 35 | FROM documents 36 | ORDER BY embedding <=> $2 37 | LIMIT 20 38 | ), 39 | keyword_search AS ( 40 | SELECT id, RANK () OVER (ORDER BY ts_rank_cd(to_tsvector('english', content), query) DESC) 41 | FROM documents, plainto_tsquery('english', $1) query 42 | WHERE to_tsvector('english', content) @@ query 43 | ORDER BY ts_rank_cd(to_tsvector('english', content), query) DESC 44 | LIMIT 20 45 | ) 46 | SELECT 47 | COALESCE(semantic_search.id, keyword_search.id) AS id, 48 | COALESCE(1.0 / ($3 + semantic_search.rank), 0.0) + 49 | COALESCE(1.0 / ($3 + keyword_search.rank), 0.0) AS score 50 | FROM semantic_search 51 | FULL OUTER JOIN keyword_search ON semantic_search.id = keyword_search.id 52 | ORDER BY score DESC 53 | LIMIT 5 54 | `; 55 | const query = 'growling bear' 56 | const queryEmbedding = (await embed([query]))[0]; 57 | const k = 60 58 | const { rows } = await client.query(sql, [query, pgvector.toSql(queryEmbedding), k]); 59 | for (let row of rows) { 60 | console.log(row); 61 | } 62 | 63 | await client.end(); 64 | -------------------------------------------------------------------------------- /examples/hybrid-search/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "dependencies": { 5 | "@huggingface/transformers": "^3.3.3", 6 | "pg": "^8.11.3", 7 | "pgvector": "file:../.." 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/loading/example.js: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | import pgvector from 'pgvector/pg'; 3 | import { from as copyFrom } from 'pg-copy-streams'; 4 | import { stdout } from 'process'; 5 | 6 | // generate random data 7 | const rows = 100000; 8 | const dimensions = 128; 9 | const embeddings = Array.from({length: rows}, () => Array.from({length: dimensions}, () => Math.random())); 10 | 11 | // connect 12 | const client = new pg.Client({database: 'pgvector_example'}); 13 | await client.connect(); 14 | 15 | // enable extension 16 | await client.query('CREATE EXTENSION IF NOT EXISTS vector'); 17 | await pgvector.registerTypes(client); 18 | 19 | // create table 20 | await client.query('DROP TABLE IF EXISTS items'); 21 | await client.query(`CREATE TABLE items (id bigserial, embedding vector(${dimensions}))`); 22 | 23 | function copyRow(stream, line) { 24 | return new Promise((resolve) => { 25 | let ok = stream.write(line); 26 | if (!ok) { 27 | stream.once('drain', () => resolve()); 28 | } else { 29 | resolve(); 30 | } 31 | }); 32 | } 33 | 34 | // load data 35 | console.log(`Loading ${embeddings.length} rows`); 36 | const stream = client.query(copyFrom('COPY items (embedding) FROM STDIN')); 37 | for (const [i, embedding] of embeddings.entries()) { 38 | const line = `${pgvector.toSql(embedding)}\n`; 39 | await copyRow(stream, line); 40 | 41 | // show progress 42 | if (i % 10000 == 0) { 43 | stdout.write('.'); 44 | } 45 | } 46 | 47 | stream.on('finish', async function () { 48 | console.log('\nSuccess!'); 49 | 50 | // create any indexes *after* loading initial data (skipping for this example) 51 | const createIndex = false; 52 | if (createIndex) { 53 | console.log('Creating index'); 54 | await client.query("SET maintenance_work_mem = '8GB'"); 55 | await client.query('SET max_parallel_maintenance_workers = 7'); 56 | await client.query('CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops)'); 57 | } 58 | 59 | // update planner statistics for good measure 60 | await client.query('ANALYZE items'); 61 | 62 | client.end(); 63 | }); 64 | stream.end(); 65 | -------------------------------------------------------------------------------- /examples/loading/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "dependencies": { 5 | "pg": "^8.11.3", 6 | "pg-copy-streams": "^6.0.6", 7 | "pgvector": "file:../.." 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/openai/example.js: -------------------------------------------------------------------------------- 1 | import OpenAI from 'openai'; 2 | import pg from 'pg'; 3 | import pgvector from 'pgvector/pg'; 4 | 5 | const client = new pg.Client({database: 'pgvector_example'}); 6 | await client.connect(); 7 | 8 | await client.query('CREATE EXTENSION IF NOT EXISTS vector'); 9 | await pgvector.registerTypes(client); 10 | 11 | await client.query('DROP TABLE IF EXISTS documents'); 12 | await client.query('CREATE TABLE documents (id bigserial PRIMARY KEY, content text, embedding vector(1536))'); 13 | 14 | async function embed(input) { 15 | const openai = new OpenAI(); 16 | const response = await openai.embeddings.create({input: input, model: 'text-embedding-3-small'}); 17 | return response.data.map((v) => v.embedding); 18 | } 19 | 20 | const input = [ 21 | 'The dog is barking', 22 | 'The cat is purring', 23 | 'The bear is growling' 24 | ]; 25 | const embeddings = await embed(input); 26 | for (let [i, content] of input.entries()) { 27 | await client.query('INSERT INTO documents (content, embedding) VALUES ($1, $2)', [content, pgvector.toSql(embeddings[i])]); 28 | } 29 | 30 | const query = 'forest'; 31 | const queryEmbedding = (await embed([query]))[0]; 32 | const { rows } = await client.query('SELECT content FROM documents ORDER BY embedding <=> $1 LIMIT 5', [pgvector.toSql(queryEmbedding)]); 33 | for (let row of rows) { 34 | console.log(row.content); 35 | } 36 | 37 | await client.end(); 38 | -------------------------------------------------------------------------------- /examples/openai/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "dependencies": { 5 | "openai": "^4.5.0", 6 | "pg": "^8.11.3", 7 | "pgvector": "file:../.." 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/pglite/example.js: -------------------------------------------------------------------------------- 1 | import { PGlite } from '@electric-sql/pglite'; 2 | import { vector } from '@electric-sql/pglite/vector'; 3 | 4 | const db = new PGlite({extensions: {vector}}); 5 | 6 | await db.query('CREATE EXTENSION IF NOT EXISTS vector'); 7 | await db.query('CREATE TABLE items (id bigserial PRIMARY KEY, embedding vector(3))'); 8 | await db.query("INSERT INTO items (embedding) VALUES ('[1,2,3]'), ('[4,5,6]')"); 9 | await db.query('CREATE INDEX ON items USING hnsw (embedding vector_l2_ops)'); 10 | 11 | const { rows } = await db.query("SELECT * FROM items ORDER BY embedding <-> '[3,1,2]' LIMIT 5"); 12 | console.log(rows); 13 | -------------------------------------------------------------------------------- /examples/pglite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "dependencies": { 5 | "@electric-sql/pglite": "^0.3.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/rdkit/example.js: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | import pgvector from 'pgvector/pg'; 3 | import initRDKitModule from '@rdkit/rdkit'; 4 | 5 | const RDKit = await initRDKitModule(); 6 | 7 | function generateFingerprint(molecule) { 8 | const options = {radius: 3}; 9 | return RDKit.get_mol(molecule).get_morgan_fp(JSON.stringify(options)); 10 | } 11 | 12 | const client = new pg.Client({database: 'pgvector_example'}); 13 | await client.connect(); 14 | 15 | await client.query('CREATE EXTENSION IF NOT EXISTS vector'); 16 | await pgvector.registerTypes(client); 17 | 18 | await client.query('DROP TABLE IF EXISTS molecules'); 19 | await client.query('CREATE TABLE molecules (id text PRIMARY KEY, fingerprint bit(2048))'); 20 | 21 | const molecules = ['Cc1ccccc1', 'Cc1ncccc1', 'c1ccccn1']; 22 | for (const molecule of molecules) { 23 | const fingerprint = generateFingerprint(molecule); 24 | await client.query('INSERT INTO molecules (id, fingerprint) VALUES ($1, $2)', [molecule, fingerprint]); 25 | } 26 | 27 | const queryMolecule = 'c1ccco1'; 28 | const queryFingerprint = generateFingerprint(queryMolecule); 29 | const { rows } = await client.query('SELECT id, fingerprint <%> $1 AS distance FROM molecules ORDER BY distance LIMIT 5', [queryFingerprint]); 30 | for (let row of rows) { 31 | console.log(row); 32 | } 33 | 34 | await client.end(); 35 | -------------------------------------------------------------------------------- /examples/rdkit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "dependencies": { 5 | "@rdkit/rdkit": "^2025.3.2-1.0.0", 6 | "pg": "^8.11.3", 7 | "pgvector": "file:../.." 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/sparse-search/example.js: -------------------------------------------------------------------------------- 1 | // good resources 2 | // https://opensearch.org/blog/improving-document-retrieval-with-sparse-semantic-encoders/ 3 | // https://huggingface.co/opensearch-project/opensearch-neural-sparse-encoding-v1 4 | // 5 | // run with 6 | // text-embeddings-router --model-id opensearch-project/opensearch-neural-sparse-encoding-v1 --pooling splade 7 | 8 | import pg from 'pg'; 9 | import { SparseVector } from 'pgvector'; 10 | import pgvector from 'pgvector/pg'; 11 | 12 | const client = new pg.Client({database: 'pgvector_example'}); 13 | await client.connect(); 14 | 15 | await client.query('CREATE EXTENSION IF NOT EXISTS vector'); 16 | await pgvector.registerTypes(client); 17 | 18 | await client.query('DROP TABLE IF EXISTS documents'); 19 | await client.query('CREATE TABLE documents (id bigserial PRIMARY KEY, content text, embedding sparsevec(30522))'); 20 | 21 | async function embed(inputs) { 22 | const url = 'http://localhost:3000/embed_sparse'; 23 | const data = {inputs: inputs}; 24 | const options = { 25 | method: 'POST', 26 | headers: {'Content-Type': 'application/json'}, 27 | body: JSON.stringify(data) 28 | }; 29 | const response = await fetch(url, options); 30 | if (!response.ok) { 31 | throw new Error(`Bad status: ${response.status}`); 32 | } 33 | const json = await response.json(); 34 | const embeddings = []; 35 | for (let item of json) { 36 | const embedding = {}; 37 | for (let e of item) { 38 | embedding[e['index']] = e['value']; 39 | } 40 | embeddings.push(embedding); 41 | } 42 | return embeddings; 43 | } 44 | 45 | const input = [ 46 | 'The dog is barking', 47 | 'The cat is purring', 48 | 'The bear is growling' 49 | ]; 50 | const embeddings = await embed(input); 51 | for (let [i, content] of input.entries()) { 52 | await client.query('INSERT INTO documents (content, embedding) VALUES ($1, $2)', [content, new SparseVector(embeddings[i], 30522)]); 53 | } 54 | 55 | const query = 'forest'; 56 | const queryEmbedding = (await embed([query]))[0]; 57 | const { rows } = await client.query('SELECT content FROM documents ORDER BY embedding <#> $1 LIMIT 5', [new SparseVector(queryEmbedding, 30522)]); 58 | for (let row of rows) { 59 | console.log(row.content); 60 | } 61 | 62 | await client.end(); 63 | -------------------------------------------------------------------------------- /examples/sparse-search/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "dependencies": { 5 | "pg": "^8.11.3", 6 | "pgvector": "file:../.." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/transformers/example.js: -------------------------------------------------------------------------------- 1 | import { pipeline } from '@huggingface/transformers'; 2 | import pg from 'pg'; 3 | import pgvector from 'pgvector/pg'; 4 | 5 | const client = new pg.Client({database: 'pgvector_example'}); 6 | await client.connect(); 7 | 8 | await client.query('CREATE EXTENSION IF NOT EXISTS vector'); 9 | await pgvector.registerTypes(client); 10 | 11 | await client.query('DROP TABLE IF EXISTS documents'); 12 | await client.query('CREATE TABLE documents (id bigserial PRIMARY KEY, content text, embedding vector(384))'); 13 | 14 | const extractor = await pipeline('feature-extraction', 'Xenova/multi-qa-MiniLM-L6-cos-v1', {dtype: 'fp32'}); 15 | 16 | async function embed(input) { 17 | const output = await extractor(input, {pooling: 'mean', normalize: true}); 18 | return output.tolist(); 19 | } 20 | 21 | const input = [ 22 | 'The dog is barking', 23 | 'The cat is purring', 24 | 'The bear is growling' 25 | ]; 26 | const embeddings = await embed(input); 27 | for (let [i, content] of input.entries()) { 28 | await client.query('INSERT INTO documents (content, embedding) VALUES ($1, $2)', [content, pgvector.toSql(embeddings[i])]); 29 | } 30 | 31 | const query = 'forest'; 32 | const queryEmbedding = (await embed([query]))[0]; 33 | const { rows } = await client.query('SELECT content FROM documents ORDER BY embedding <=> $1 LIMIT 5', [pgvector.toSql(queryEmbedding)]); 34 | for (let row of rows) { 35 | console.log(row.content); 36 | } 37 | 38 | await client.end(); 39 | -------------------------------------------------------------------------------- /examples/transformers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "dependencies": { 5 | "@huggingface/transformers": "^3.3.3", 6 | "pg": "^8.11.3", 7 | "pgvector": "file:../.." 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pgvector", 3 | "version": "0.2.1", 4 | "description": "pgvector support for Node.js, Deno, and Bun (and TypeScript)", 5 | "homepage": "https://github.com/pgvector/pgvector-node", 6 | "license": "MIT", 7 | "authors": [ 8 | "ankane" 9 | ], 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/pgvector/pgvector-node" 13 | }, 14 | "exports": { 15 | ".": { 16 | "types": "./types/index.d.ts", 17 | "default": "./src/index.js" 18 | }, 19 | "./knex": { 20 | "types": "./types/knex/index.d.ts", 21 | "default": "./src/knex/index.js" 22 | }, 23 | "./kysely": { 24 | "types": "./types/kysely/index.d.ts", 25 | "default": "./src/kysely/index.js" 26 | }, 27 | "./mikro-orm": { 28 | "types": "./types/mikro-orm/index.d.ts", 29 | "default": "./src/mikro-orm/index.js" 30 | }, 31 | "./objection": { 32 | "types": "./types/objection/index.d.ts", 33 | "default": "./src/objection/index.js" 34 | }, 35 | "./pg": { 36 | "types": "./types/pg/index.d.ts", 37 | "default": "./src/pg/index.js" 38 | }, 39 | "./pg-promise": { 40 | "types": "./types/pg-promise/index.d.ts", 41 | "default": "./src/pg-promise/index.js" 42 | }, 43 | "./sequelize": { 44 | "types": "./types/sequelize/index.d.ts", 45 | "default": "./src/sequelize/index.js" 46 | }, 47 | "./utils": { 48 | "types": "./types/utils/index.d.ts", 49 | "default": "./src/utils/index.js" 50 | }, 51 | "./package.json": "./package.json" 52 | }, 53 | "typesVersions": { 54 | "*": { 55 | "*": [ 56 | "types/index.d.ts" 57 | ], 58 | "knex": [ 59 | "types/knex/index.d.ts" 60 | ], 61 | "kysely": [ 62 | "types/kysely/index.d.ts" 63 | ], 64 | "mikro-orm": [ 65 | "types/mikro-orm/index.d.ts" 66 | ], 67 | "objection": [ 68 | "types/objection/index.d.ts" 69 | ], 70 | "pg": [ 71 | "types/pg/index.d.ts" 72 | ], 73 | "pg-promise": [ 74 | "types/pg-promise/index.d.ts" 75 | ], 76 | "sequelize": [ 77 | "types/sequelize/index.d.ts" 78 | ], 79 | "utils": [ 80 | "types/utils/index.d.ts" 81 | ] 82 | } 83 | }, 84 | "files": [ 85 | "src", 86 | "types" 87 | ], 88 | "engines": { 89 | "node": ">= 18" 90 | }, 91 | "scripts": { 92 | "build": "tsc", 93 | "test": "node --test" 94 | }, 95 | "devDependencies": { 96 | "@mikro-orm/core": "^6.1.1", 97 | "@mikro-orm/postgresql": "^6.1.1", 98 | "@prisma/client": "^6.2.1", 99 | "drizzle-orm": "^0.43.1", 100 | "knex": "^3.1.0", 101 | "kysely": "^0.28.2", 102 | "objection": "^3.1.3", 103 | "pg": "^8.6.0", 104 | "pg-promise": "^11.4.3", 105 | "postgres": "^3.3.4", 106 | "sequelize": "^6.6.2", 107 | "slonik": "^47.1.0", 108 | "typeorm": "^0.3.17", 109 | "typescript": "^5.0.3" 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /prisma/migrations/0_init/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateExtension 2 | CREATE EXTENSION IF NOT EXISTS "vector"; 3 | 4 | -- CreateTable 5 | CREATE TABLE "prisma_items" ( 6 | "id" SERIAL NOT NULL, 7 | "embedding" vector(3), 8 | "half_embedding" halfvec(3), 9 | "binary_embedding" BIT(3), 10 | "sparse_embedding" sparsevec(3), 11 | 12 | CONSTRAINT "prisma_items_pkey" PRIMARY KEY ("id") 13 | ); 14 | -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | generator client { 5 | provider = "prisma-client-js" 6 | previewFeatures = ["postgresqlExtensions"] 7 | } 8 | 9 | datasource db { 10 | provider = "postgresql" 11 | url = "postgresql://runner@localhost/pgvector_node_test" 12 | extensions = [vector] 13 | } 14 | 15 | model Item { 16 | id Int @id @default(autoincrement()) 17 | embedding Unsupported("vector(3)")? 18 | half_embedding Unsupported("halfvec(3)")? 19 | binary_embedding String? @db.Bit(3) 20 | sparse_embedding Unsupported("sparsevec(3)")? 21 | 22 | @@map("prisma_items") 23 | } 24 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const { fromSql, toSql, SparseVector } = require('./utils'); 2 | 3 | module.exports = {fromSql, toSql, SparseVector}; 4 | -------------------------------------------------------------------------------- /src/knex/index.js: -------------------------------------------------------------------------------- 1 | const knex = require('knex'); 2 | const { fromSql, toSql, vectorType, halfvecType, sparsevecType } = require('../utils'); 3 | 4 | knex.SchemaBuilder.extend('enableExtension', function (name) { 5 | return this.raw('CREATE EXTENSION IF NOT EXISTS ??', [name]); 6 | }); 7 | 8 | knex.TableBuilder.extend('vector', function (name, options) { 9 | const dimensions = options && (Number.isInteger(options) ? options : options.dimensions); 10 | return this.specificType(name, vectorType(dimensions)); 11 | }); 12 | 13 | knex.TableBuilder.extend('halfvec', function (name, options) { 14 | const dimensions = options && (Number.isInteger(options) ? options : options.dimensions); 15 | return this.specificType(name, halfvecType(dimensions)); 16 | }); 17 | 18 | knex.TableBuilder.extend('sparsevec', function (name, options) { 19 | const dimensions = options && (Number.isInteger(options) ? options : options.dimensions); 20 | return this.specificType(name, sparsevecType(dimensions)); 21 | }); 22 | 23 | knex.QueryBuilder.extend('l2Distance', function (column, value) { 24 | return this.client.raw('?? <-> ?', [column, toSql(value)]); 25 | }); 26 | 27 | knex.QueryBuilder.extend('maxInnerProduct', function (column, value) { 28 | return this.client.raw('?? <#> ?', [column, toSql(value)]); 29 | }); 30 | 31 | knex.QueryBuilder.extend('cosineDistance', function (column, value) { 32 | return this.client.raw('?? <=> ?', [column, toSql(value)]); 33 | }); 34 | 35 | knex.QueryBuilder.extend('l1Distance', function (column, value) { 36 | return this.client.raw('?? <+> ?', [column, toSql(value)]); 37 | }); 38 | 39 | knex.QueryBuilder.extend('hammingDistance', function (column, value) { 40 | return this.client.raw('?? <~> ?', [column, value]); 41 | }); 42 | 43 | knex.QueryBuilder.extend('jaccardDistance', function (column, value) { 44 | return this.client.raw('?? <%> ?', [column, value]); 45 | }); 46 | 47 | module.exports = {fromSql, toSql}; 48 | -------------------------------------------------------------------------------- /src/kysely/index.js: -------------------------------------------------------------------------------- 1 | const { sql } = require('kysely'); 2 | const { fromSql, toSql } = require('..'); 3 | 4 | function l2Distance(column, value) { 5 | return sql`${sql.ref(column)} <-> ${toSql(value)}`; 6 | } 7 | 8 | function maxInnerProduct(column, value) { 9 | return sql`${sql.ref(column)} <#> ${toSql(value)}`; 10 | } 11 | 12 | function cosineDistance(column, value) { 13 | return sql`${sql.ref(column)} <=> ${toSql(value)}`; 14 | } 15 | 16 | function l1Distance(column, value) { 17 | return sql`${sql.ref(column)} <+> ${toSql(value)}`; 18 | } 19 | 20 | function hammingDistance(column, value) { 21 | return sql`${sql.ref(column)} <~> ${value}`; 22 | } 23 | 24 | function jaccardDistance(column, value) { 25 | return sql`${sql.ref(column)} <%> ${value}`; 26 | } 27 | 28 | module.exports = { 29 | fromSql, 30 | toSql, 31 | l2Distance, 32 | maxInnerProduct, 33 | cosineDistance, 34 | l1Distance, 35 | hammingDistance, 36 | jaccardDistance 37 | }; 38 | -------------------------------------------------------------------------------- /src/mikro-orm/bit.js: -------------------------------------------------------------------------------- 1 | const { Type } = require('@mikro-orm/core'); 2 | const utils = require('../utils'); 3 | 4 | class BitType extends Type { 5 | getColumnType(prop, platform) { 6 | return utils.bitType(prop.length); 7 | } 8 | } 9 | 10 | module.exports = {BitType}; 11 | -------------------------------------------------------------------------------- /src/mikro-orm/halfvec.js: -------------------------------------------------------------------------------- 1 | const { Type } = require('@mikro-orm/core'); 2 | const utils = require('../utils'); 3 | 4 | class HalfvecType extends Type { 5 | convertToDatabaseValue(value, platform) { 6 | if (value === null) { 7 | return null; 8 | } 9 | return utils.halfvecToSql(value); 10 | } 11 | 12 | convertToJSValue(value, platform) { 13 | if (value === null) { 14 | return null; 15 | } 16 | return utils.halfvecFromSql(value); 17 | } 18 | 19 | getColumnType(prop, platform) { 20 | return utils.halfvecType(prop.dimensions); 21 | } 22 | } 23 | 24 | module.exports = {HalfvecType}; 25 | -------------------------------------------------------------------------------- /src/mikro-orm/index.js: -------------------------------------------------------------------------------- 1 | const { raw } = require('@mikro-orm/core'); 2 | const { BitType } = require('./bit'); 3 | const { HalfvecType } = require('./halfvec'); 4 | const { SparsevecType } = require('./sparsevec'); 5 | const { VectorType } = require('./vector'); 6 | const { toSql } = require('../utils'); 7 | 8 | function distance(op, column, value, em, binary) { 9 | if (raw) { 10 | return raw(`?? ${op} ?`, [column, binary ? value : toSql(value)]); 11 | } else { 12 | return em.raw(`?? ${op} ?`, [column, binary ? value : toSql(value)]); 13 | } 14 | } 15 | 16 | function l2Distance(column, value, em) { 17 | return distance('<->', column, value, em); 18 | } 19 | 20 | function maxInnerProduct(column, value, em) { 21 | return distance('<#>', column, value, em); 22 | } 23 | 24 | function cosineDistance(column, value, em) { 25 | return distance('<=>', column, value, em); 26 | } 27 | 28 | function l1Distance(column, value, em) { 29 | return distance('<+>', column, value, em); 30 | } 31 | 32 | function hammingDistance(column, value, em) { 33 | return distance('<~>', column, value, em, true); 34 | } 35 | 36 | function jaccardDistance(column, value, em) { 37 | return distance('<%>', column, value, em, true); 38 | } 39 | 40 | module.exports = { 41 | VectorType, 42 | HalfvecType, 43 | BitType, 44 | SparsevecType, 45 | l2Distance, 46 | maxInnerProduct, 47 | cosineDistance, 48 | l1Distance, 49 | hammingDistance, 50 | jaccardDistance 51 | }; 52 | -------------------------------------------------------------------------------- /src/mikro-orm/sparsevec.js: -------------------------------------------------------------------------------- 1 | const { Type } = require('@mikro-orm/core'); 2 | const utils = require('../utils'); 3 | 4 | class SparsevecType extends Type { 5 | convertToDatabaseValue(value, platform) { 6 | if (value === null) { 7 | return null; 8 | } 9 | return utils.sparsevecToSql(value); 10 | } 11 | 12 | convertToJSValue(value, platform) { 13 | if (value === null) { 14 | return null; 15 | } 16 | return utils.sparsevecFromSql(value); 17 | } 18 | 19 | getColumnType(prop, platform) { 20 | return utils.sparsevecType(prop.dimensions); 21 | } 22 | } 23 | 24 | module.exports = {SparsevecType}; 25 | -------------------------------------------------------------------------------- /src/mikro-orm/vector.js: -------------------------------------------------------------------------------- 1 | const { Type } = require('@mikro-orm/core'); 2 | const utils = require('../utils'); 3 | 4 | class VectorType extends Type { 5 | convertToDatabaseValue(value, platform) { 6 | if (value === null) { 7 | return null; 8 | } 9 | return utils.vectorToSql(value); 10 | } 11 | 12 | convertToJSValue(value, platform) { 13 | if (value === null) { 14 | return null; 15 | } 16 | return utils.vectorFromSql(value); 17 | } 18 | 19 | getColumnType(prop, platform) { 20 | return utils.vectorType(prop.dimensions); 21 | } 22 | } 23 | 24 | module.exports = {VectorType}; 25 | -------------------------------------------------------------------------------- /src/objection/index.js: -------------------------------------------------------------------------------- 1 | const { fromSql, toSql } = require('../knex'); 2 | const { raw } = require('objection'); 3 | 4 | function l2Distance(column, value) { 5 | return raw('?? <-> ?', [column, toSql(value)]); 6 | } 7 | 8 | function maxInnerProduct(column, value) { 9 | return raw('?? <#> ?', [column, toSql(value)]); 10 | } 11 | 12 | function cosineDistance(column, value) { 13 | return raw('?? <=> ?', [column, toSql(value)]); 14 | } 15 | 16 | function l1Distance(column, value) { 17 | return raw('?? <+> ?', [column, toSql(value)]); 18 | } 19 | 20 | function hammingDistance(column, value) { 21 | return raw('?? <~> ?', [column, value]); 22 | } 23 | 24 | function jaccardDistance(column, value) { 25 | return raw('?? <%> ?', [column, value]); 26 | } 27 | 28 | module.exports = { 29 | fromSql, 30 | toSql, 31 | l2Distance, 32 | maxInnerProduct, 33 | cosineDistance, 34 | l1Distance, 35 | hammingDistance, 36 | jaccardDistance 37 | }; 38 | -------------------------------------------------------------------------------- /src/pg-promise/index.js: -------------------------------------------------------------------------------- 1 | const { registerType, registerTypes, toSql } = require('../pg'); 2 | 3 | module.exports = {registerType, registerTypes, toSql}; 4 | -------------------------------------------------------------------------------- /src/pg/index.js: -------------------------------------------------------------------------------- 1 | const utils = require('../utils'); 2 | const { toSql } = require('../utils'); 3 | 4 | async function registerTypes(client) { 5 | const result = await client.query('SELECT typname, oid FROM pg_type WHERE typname IN ($1, $2, $3)', ['vector', 'halfvec', 'sparsevec']); 6 | const rows = result.rows; 7 | 8 | const vector = rows.find((v) => v.typname == 'vector'); 9 | const halfvec = rows.find((v) => v.typname == 'halfvec'); 10 | const sparsevec = rows.find((v) => v.typname == 'sparsevec'); 11 | 12 | if (!vector) { 13 | throw new Error('vector type not found in the database'); 14 | } 15 | 16 | client.setTypeParser(vector.oid, 'text', function (value) { 17 | return utils.vectorFromSql(value); 18 | }); 19 | 20 | if (halfvec) { 21 | client.setTypeParser(halfvec.oid, 'text', function (value) { 22 | return utils.halfvecFromSql(value); 23 | }); 24 | } 25 | 26 | if (sparsevec) { 27 | client.setTypeParser(sparsevec.oid, 'text', function (value) { 28 | return utils.sparsevecFromSql(value); 29 | }); 30 | } 31 | } 32 | 33 | const registerType = registerTypes; 34 | 35 | module.exports = {registerType, registerTypes, toSql}; 36 | -------------------------------------------------------------------------------- /src/sequelize/halfvec.js: -------------------------------------------------------------------------------- 1 | const util = require('node:util'); 2 | const utils = require('../utils'); 3 | 4 | function registerHalfvec(Sequelize) { 5 | const DataTypes = Sequelize.DataTypes; 6 | const PgTypes = DataTypes.postgres; 7 | const ABSTRACT = DataTypes.ABSTRACT.prototype.constructor; 8 | 9 | class HALFVEC extends ABSTRACT { 10 | constructor(dimensions) { 11 | super(); 12 | this._dimensions = dimensions; 13 | } 14 | 15 | toSql() { 16 | return utils.halfvecType(this._dimensions).toUpperCase(); 17 | } 18 | 19 | _stringify(value) { 20 | return utils.halfvecToSql(value); 21 | } 22 | 23 | static parse(value) { 24 | return utils.halfvecFromSql(value); 25 | } 26 | } 27 | 28 | HALFVEC.prototype.key = HALFVEC.key = 'halfvec'; 29 | 30 | DataTypes.HALFVEC = Sequelize.Utils.classToInvokable(HALFVEC); 31 | DataTypes.HALFVEC.types.postgres = ['halfvec']; 32 | 33 | PgTypes.HALFVEC = function HALFVEC() { 34 | if (!(this instanceof PgTypes.HALFVEC)) { 35 | return new PgTypes.HALFVEC(); 36 | } 37 | DataTypes.HALFVEC.apply(this, arguments); 38 | }; 39 | util.inherits(PgTypes.HALFVEC, DataTypes.HALFVEC); 40 | PgTypes.HALFVEC.parse = DataTypes.HALFVEC.parse; 41 | PgTypes.HALFVEC.types = {postgres: ['halfvec']}; 42 | DataTypes.postgres.HALFVEC.key = 'halfvec'; 43 | 44 | // for migrations 45 | Sequelize.HALFVEC ??= DataTypes.HALFVEC; 46 | } 47 | 48 | module.exports = {registerHalfvec}; 49 | -------------------------------------------------------------------------------- /src/sequelize/index.js: -------------------------------------------------------------------------------- 1 | const { toSql } = require('../utils'); 2 | const { Utils } = require('sequelize'); 3 | const { registerHalfvec } = require('./halfvec'); 4 | const { registerSparsevec } = require('./sparsevec'); 5 | const { registerVector } = require('./vector'); 6 | 7 | function registerTypes(Sequelize) { 8 | registerVector(Sequelize); 9 | registerHalfvec(Sequelize); 10 | registerSparsevec(Sequelize); 11 | } 12 | 13 | function distance(op, column, value, sequelize, binary) { 14 | const quotedColumn = column instanceof Utils.Literal ? column.val : sequelize.dialect.queryGenerator.quoteIdentifier(column); 15 | const escapedValue = sequelize.escape(binary ? value : toSql(value)); 16 | return sequelize.literal(`${quotedColumn} ${op} ${escapedValue}`); 17 | } 18 | 19 | function l2Distance(column, value, sequelize) { 20 | return distance('<->', column, value, sequelize); 21 | } 22 | 23 | function maxInnerProduct(column, value, sequelize) { 24 | return distance('<#>', column, value, sequelize); 25 | } 26 | 27 | function cosineDistance(column, value, sequelize) { 28 | return distance('<=>', column, value, sequelize); 29 | } 30 | 31 | function l1Distance(column, value, sequelize) { 32 | return distance('<+>', column, value, sequelize); 33 | } 34 | 35 | function hammingDistance(column, value, sequelize) { 36 | return distance('<~>', column, value, sequelize, true); 37 | } 38 | 39 | function jaccardDistance(column, value, sequelize) { 40 | return distance('<%>', column, value, sequelize, true); 41 | } 42 | 43 | const registerType = registerTypes; 44 | 45 | module.exports = { 46 | registerType, 47 | registerTypes, 48 | l2Distance, 49 | maxInnerProduct, 50 | cosineDistance, 51 | l1Distance, 52 | hammingDistance, 53 | jaccardDistance 54 | }; 55 | -------------------------------------------------------------------------------- /src/sequelize/sparsevec.js: -------------------------------------------------------------------------------- 1 | const util = require('node:util'); 2 | const utils = require('../utils'); 3 | 4 | function registerSparsevec(Sequelize) { 5 | const DataTypes = Sequelize.DataTypes; 6 | const PgTypes = DataTypes.postgres; 7 | const ABSTRACT = DataTypes.ABSTRACT.prototype.constructor; 8 | 9 | class SPARSEVEC extends ABSTRACT { 10 | constructor(dimensions) { 11 | super(); 12 | this._dimensions = dimensions; 13 | } 14 | 15 | toSql() { 16 | return utils.sparsevecType(this._dimensions).toUpperCase(); 17 | } 18 | 19 | _stringify(value) { 20 | return utils.sparsevecToSql(value); 21 | } 22 | 23 | static parse(value) { 24 | return utils.sparsevecFromSql(value); 25 | } 26 | } 27 | 28 | SPARSEVEC.prototype.key = SPARSEVEC.key = 'sparsevec'; 29 | 30 | DataTypes.SPARSEVEC = Sequelize.Utils.classToInvokable(SPARSEVEC); 31 | DataTypes.SPARSEVEC.types.postgres = ['sparsevec']; 32 | 33 | PgTypes.SPARSEVEC = function SPARSEVEC() { 34 | if (!(this instanceof PgTypes.SPARSEVEC)) { 35 | return new PgTypes.SPARSEVEC(); 36 | } 37 | DataTypes.SPARSEVEC.apply(this, arguments); 38 | }; 39 | util.inherits(PgTypes.SPARSEVEC, DataTypes.SPARSEVEC); 40 | PgTypes.SPARSEVEC.parse = DataTypes.SPARSEVEC.parse; 41 | PgTypes.SPARSEVEC.types = {postgres: ['sparsevec']}; 42 | DataTypes.postgres.SPARSEVEC.key = 'sparsevec'; 43 | 44 | // for migrations 45 | Sequelize.SPARSEVEC ??= DataTypes.SPARSEVEC; 46 | } 47 | 48 | module.exports = {registerSparsevec}; 49 | -------------------------------------------------------------------------------- /src/sequelize/vector.js: -------------------------------------------------------------------------------- 1 | const util = require('node:util'); 2 | const utils = require('../utils'); 3 | 4 | function registerVector(Sequelize) { 5 | const DataTypes = Sequelize.DataTypes; 6 | const PgTypes = DataTypes.postgres; 7 | const ABSTRACT = DataTypes.ABSTRACT.prototype.constructor; 8 | 9 | class VECTOR extends ABSTRACT { 10 | constructor(dimensions) { 11 | super(); 12 | this._dimensions = dimensions; 13 | } 14 | 15 | toSql() { 16 | return utils.vectorType(this._dimensions).toUpperCase(); 17 | } 18 | 19 | _stringify(value) { 20 | return utils.vectorToSql(value); 21 | } 22 | 23 | static parse(value) { 24 | return utils.vectorFromSql(value); 25 | } 26 | } 27 | 28 | VECTOR.prototype.key = VECTOR.key = 'vector'; 29 | 30 | DataTypes.VECTOR = Sequelize.Utils.classToInvokable(VECTOR); 31 | DataTypes.VECTOR.types.postgres = ['vector']; 32 | 33 | PgTypes.VECTOR = function VECTOR() { 34 | if (!(this instanceof PgTypes.VECTOR)) { 35 | return new PgTypes.VECTOR(); 36 | } 37 | DataTypes.VECTOR.apply(this, arguments); 38 | }; 39 | util.inherits(PgTypes.VECTOR, DataTypes.VECTOR); 40 | PgTypes.VECTOR.parse = DataTypes.VECTOR.parse; 41 | PgTypes.VECTOR.types = {postgres: ['vector']}; 42 | DataTypes.postgres.VECTOR.key = 'vector'; 43 | 44 | // for migrations 45 | Sequelize.VECTOR ??= DataTypes.VECTOR; 46 | } 47 | 48 | module.exports = {registerVector}; 49 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | const util = require('node:util'); 2 | const { SparseVector } = require('./sparse-vector'); 3 | 4 | function vectorFromSql(value) { 5 | if (value === null) { 6 | return null; 7 | } 8 | return value.substring(1, value.length - 1).split(',').map((v) => parseFloat(v)); 9 | } 10 | 11 | function vectorToSql(value) { 12 | if (Array.isArray(value)) { 13 | return JSON.stringify(value.map((v) => Number(v))); 14 | } 15 | return value; 16 | } 17 | 18 | const halfvecFromSql = vectorFromSql; 19 | const halfvecToSql = vectorToSql; 20 | 21 | function sparsevecFromSql(value) { 22 | if (value === null) { 23 | return null; 24 | } 25 | return new SparseVector(value); 26 | } 27 | 28 | function sparsevecToSql(value) { 29 | if (value instanceof SparseVector) { 30 | return value.toPostgres(); 31 | } 32 | return value; 33 | } 34 | 35 | function fromSql(value) { 36 | if (value === null) { 37 | return null; 38 | } else if (value[0] == '[') { 39 | return vectorFromSql(value); 40 | } else if (value[0] == '{') { 41 | return sparsevecFromSql(value); 42 | } else { 43 | throw new Error('invalid text representation'); 44 | } 45 | } 46 | 47 | function toSql(value) { 48 | if (value === null) { 49 | return null; 50 | } else if (Array.isArray(value)) { 51 | return vectorToSql(value); 52 | } else if (value instanceof SparseVector) { 53 | return sparsevecToSql(value); 54 | } else { 55 | throw new Error('expected array or sparse vector'); 56 | } 57 | } 58 | 59 | function typeWithDimensions(name, dimensions) { 60 | if (dimensions === undefined || dimensions === null) { 61 | return name; 62 | } 63 | 64 | if (!Number.isInteger(dimensions)) { 65 | throw new Error('expected integer'); 66 | } 67 | 68 | return util.format('%s(%d)', name, dimensions); 69 | } 70 | 71 | function vectorType(dimensions) { 72 | return typeWithDimensions('vector', dimensions); 73 | } 74 | 75 | function halfvecType(dimensions) { 76 | return typeWithDimensions('halfvec', dimensions); 77 | } 78 | 79 | function bitType(dimensions) { 80 | return typeWithDimensions('bit', dimensions); 81 | } 82 | 83 | function sparsevecType(dimensions) { 84 | return typeWithDimensions('sparsevec', dimensions); 85 | } 86 | 87 | // for backwards compatibility 88 | const sqlType = vectorType; 89 | 90 | module.exports = { 91 | fromSql, 92 | toSql, 93 | vectorFromSql, 94 | vectorToSql, 95 | halfvecFromSql, 96 | halfvecToSql, 97 | sparsevecFromSql, 98 | sparsevecToSql, 99 | sqlType, 100 | vectorType, 101 | halfvecType, 102 | bitType, 103 | sparsevecType, 104 | SparseVector 105 | }; 106 | -------------------------------------------------------------------------------- /src/utils/sparse-vector.js: -------------------------------------------------------------------------------- 1 | const util = require('node:util'); 2 | 3 | class SparseVector { 4 | constructor(value, dimensions) { 5 | if (typeof value === 'string') { 6 | this.#fromSql(value); 7 | } else if (dimensions !== undefined) { 8 | this.#fromMap(value, dimensions); 9 | } else { 10 | this.#fromDense(value); 11 | } 12 | } 13 | 14 | toPostgres() { 15 | const values = this.values; 16 | const elements = this.indices.map((index, i) => util.format('%i:%f', index + 1, values[i])).join(','); 17 | return util.format('{%s}/%d', elements, this.dimensions); 18 | } 19 | 20 | toString() { 21 | return this.toPostgres(); 22 | } 23 | 24 | toArray() { 25 | const arr = Array(this.dimensions).fill(0.0); 26 | for (const [i, index] of this.indices.entries()) { 27 | arr[index] = this.values[i]; 28 | } 29 | return arr; 30 | } 31 | 32 | #fromSql(value) { 33 | const parts = value.split('/', 2); 34 | 35 | this.dimensions = parseInt(parts[1]); 36 | this.indices = []; 37 | this.values = []; 38 | 39 | const elements = parts[0].slice(1, -1).split(','); 40 | for (const element of elements) { 41 | const ep = element.split(':', 2); 42 | this.indices.push(parseInt(ep[0]) - 1); 43 | this.values.push(parseFloat(ep[1])); 44 | } 45 | } 46 | 47 | #fromDense(value) { 48 | this.dimensions = value.length; 49 | this.indices = []; 50 | this.values = []; 51 | 52 | for (const [i, v] of value.entries()) { 53 | const f = Number(v); 54 | if (f != 0) { 55 | this.indices.push(Number(i)); 56 | this.values.push(f); 57 | } 58 | } 59 | } 60 | 61 | #fromMap(map, dimensions) { 62 | this.dimensions = Number(dimensions); 63 | this.indices = []; 64 | this.values = []; 65 | 66 | const entries = map instanceof Map ? map.entries() : Object.entries(map); 67 | for (const [i, v] of entries) { 68 | const f = Number(v); 69 | if (f != 0) { 70 | this.indices.push(Number(i)); 71 | this.values.push(f); 72 | } 73 | } 74 | } 75 | } 76 | 77 | module.exports = {SparseVector}; 78 | -------------------------------------------------------------------------------- /tests/drizzle-orm.test.mjs: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import test from 'node:test'; 3 | import { l2Distance, innerProduct, cosineDistance, l1Distance, hammingDistance, jaccardDistance } from 'drizzle-orm'; 4 | import { drizzle } from 'drizzle-orm/postgres-js'; 5 | import { pgTable, serial, vector, halfvec, bit, sparsevec } from 'drizzle-orm/pg-core'; 6 | import { SparseVector } from 'pgvector'; 7 | import postgres from 'postgres'; 8 | 9 | test('drizzle-orm example', async () => { 10 | const client = postgres({database: 'pgvector_node_test', onnotice: function () { }}); 11 | const db = drizzle(client); 12 | 13 | await client`CREATE EXTENSION IF NOT EXISTS vector`; 14 | await client`DROP TABLE IF EXISTS drizzle_items`; 15 | await client`CREATE TABLE drizzle_items (id serial PRIMARY KEY, embedding vector(3), half_embedding halfvec(3), binary_embedding bit(3), sparse_embedding sparsevec(3))`; 16 | 17 | const items = pgTable('drizzle_items', { 18 | id: serial('id').primaryKey(), 19 | embedding: vector('embedding', {dimensions: 3}), 20 | halfEmbedding: halfvec('half_embedding', {dimensions: 3}), 21 | binaryEmbedding: bit('binary_embedding', {dimensions: 3}), 22 | sparseEmbedding: sparsevec('sparse_embedding', {dimensions: 3}) 23 | }); 24 | 25 | const newItems = [ 26 | {embedding: [1, 1, 1], halfEmbedding: [1, 1, 1], binaryEmbedding: '000', sparseEmbedding: new SparseVector([1, 1, 1])}, 27 | {embedding: [2, 2, 2], halfEmbedding: [2, 2, 2], binaryEmbedding: '101', sparseEmbedding: new SparseVector([2, 2, 2])}, 28 | {embedding: [1, 1, 2], halfEmbedding: [1, 1, 2], binaryEmbedding: '111', sparseEmbedding: new SparseVector([1, 1, 2])}, 29 | {embedding: null} 30 | ]; 31 | await db.insert(items).values(newItems); 32 | 33 | // L2 distance 34 | let allItems = await db.select() 35 | .from(items) 36 | .orderBy(l2Distance(items.embedding, [1, 1, 1])) 37 | .limit(5); 38 | assert.deepEqual(allItems.map(v => v.id), [1, 3, 2, 4]); 39 | assert.deepEqual(allItems[0].embedding, [1, 1, 1]); 40 | assert.deepEqual(allItems[1].embedding, [1, 1, 2]); 41 | assert.deepEqual(allItems[2].embedding, [2, 2, 2]); 42 | 43 | // L2 distance - halfvec 44 | allItems = await db.select() 45 | .from(items) 46 | .orderBy(l2Distance(items.halfEmbedding, [1, 1, 1])) 47 | .limit(5); 48 | assert.deepEqual(allItems.map(v => v.id), [1, 3, 2, 4]); 49 | 50 | // L2 distance - sparsevec 51 | allItems = await db.select() 52 | .from(items) 53 | .orderBy(l2Distance(items.sparseEmbedding, new SparseVector([1, 1, 1]))) 54 | .limit(5); 55 | assert.deepEqual(allItems.map(v => v.id), [1, 3, 2, 4]); 56 | 57 | // max inner product 58 | allItems = await db.select() 59 | .from(items) 60 | .orderBy(innerProduct(items.embedding, [1, 1, 1])) 61 | .limit(5); 62 | assert.deepEqual(allItems.map(v => v.id), [2, 3, 1, 4]); 63 | 64 | // cosine distance 65 | allItems = await db.select() 66 | .from(items) 67 | .orderBy(cosineDistance(items.embedding, [1, 1, 1])) 68 | .limit(5); 69 | assert.deepEqual(allItems.map(v => v.id).slice(2), [3, 4]); 70 | 71 | // L1 distance 72 | allItems = await db.select() 73 | .from(items) 74 | .orderBy(l1Distance(items.embedding, [1, 1, 1])) 75 | .limit(5); 76 | assert.deepEqual(allItems.map(v => v.id), [1, 3, 2, 4]); 77 | 78 | // Hamming distance 79 | allItems = await db.select() 80 | .from(items) 81 | .orderBy(hammingDistance(items.binaryEmbedding, '101')) 82 | .limit(5); 83 | assert.deepEqual(allItems.map(v => v.id), [2, 3, 1, 4]); 84 | 85 | // Jaccard distance 86 | allItems = await db.select() 87 | .from(items) 88 | .orderBy(jaccardDistance(items.binaryEmbedding, '101')) 89 | .limit(5); 90 | assert.deepEqual(allItems.map(v => v.id), [2, 3, 1, 4]); 91 | 92 | await client.end(); 93 | }); 94 | -------------------------------------------------------------------------------- /tests/knex.test.mjs: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import test from 'node:test'; 3 | import Knex from 'knex'; 4 | import pgvector from 'pgvector/knex'; 5 | import { SparseVector } from 'pgvector'; 6 | 7 | test('knex example', async () => { 8 | const knex = Knex({ 9 | client: 'pg', 10 | connection: {database: 'pgvector_node_test'} 11 | }); 12 | 13 | await knex.schema.createExtensionIfNotExists('vector'); 14 | await knex.schema.dropTableIfExists('knex_items'); 15 | await knex.schema.createTable('knex_items', (table) => { 16 | table.increments('id'); 17 | table.vector('embedding', {dimensions: 3}); 18 | table.halfvec('half_embedding', {dimensions: 3}); 19 | table.bit('binary_embedding', {length: 3}); 20 | table.sparsevec('sparse_embedding', {dimensions: 3}); 21 | }); 22 | 23 | const newItems = [ 24 | {embedding: pgvector.toSql([1, 1, 1]), half_embedding: pgvector.toSql([1, 1, 1]), binary_embedding: '000', sparse_embedding: new SparseVector([1, 1, 1])}, 25 | {embedding: pgvector.toSql([2, 2, 2]), half_embedding: pgvector.toSql([2, 2, 2]), binary_embedding: '101', sparse_embedding: new SparseVector([2, 2, 2])}, 26 | {embedding: pgvector.toSql([1, 1, 2]), half_embedding: pgvector.toSql([1, 1, 2]), binary_embedding: '111', sparse_embedding: new SparseVector([1, 1, 2])}, 27 | {embedding: null} 28 | ]; 29 | await knex('knex_items').insert(newItems); 30 | 31 | // L2 distance 32 | let items = await knex('knex_items') 33 | .orderBy(knex.l2Distance('embedding', [1, 1, 1])) 34 | .limit(5); 35 | assert.deepEqual(items.map(v => v.id), [1, 3, 2, 4]); 36 | assert.deepEqual(pgvector.fromSql(items[0].embedding), [1, 1, 1]); 37 | assert.deepEqual(pgvector.fromSql(items[1].embedding), [1, 1, 2]); 38 | assert.deepEqual(pgvector.fromSql(items[2].embedding), [2, 2, 2]); 39 | 40 | // L2 distance - halfvec 41 | items = await knex('knex_items') 42 | .orderBy(knex.l2Distance('half_embedding', [1, 1, 1])) 43 | .limit(5); 44 | assert.deepEqual(items.map(v => v.id), [1, 3, 2, 4]); 45 | 46 | // L2 distance - sparsevec 47 | items = await knex('knex_items') 48 | .orderBy(knex.l2Distance('sparse_embedding', new SparseVector([1, 1, 1]))) 49 | .limit(5); 50 | assert.deepEqual(items.map(v => v.id), [1, 3, 2, 4]); 51 | 52 | // max inner product 53 | items = await knex('knex_items') 54 | .orderBy(knex.maxInnerProduct('embedding', [1, 1, 1])) 55 | .limit(5); 56 | assert.deepEqual(items.map(v => v.id), [2, 3, 1, 4]); 57 | 58 | // cosine distance 59 | items = await knex('knex_items') 60 | .orderBy(knex.cosineDistance('embedding', [1, 1, 1])) 61 | .limit(5); 62 | assert.deepEqual(items.map(v => v.id).slice(2), [3, 4]); 63 | 64 | // L1 distance 65 | items = await knex('knex_items') 66 | .orderBy(knex.l1Distance('embedding', [1, 1, 1])) 67 | .limit(5); 68 | assert.deepEqual(items.map(v => v.id), [1, 3, 2, 4]); 69 | 70 | // Hamming distance 71 | items = await knex('knex_items') 72 | .orderBy(knex.hammingDistance('binary_embedding', '101')) 73 | .limit(5); 74 | assert.deepEqual(items.map(v => v.id), [2, 3, 1, 4]); 75 | 76 | // Jaccard distance 77 | items = await knex('knex_items') 78 | .orderBy(knex.jaccardDistance('binary_embedding', '101')) 79 | .limit(5); 80 | assert.deepEqual(items.map(v => v.id), [2, 3, 1, 4]); 81 | 82 | await knex.schema.alterTable('knex_items', function (table) { 83 | table.index(knex.raw('embedding vector_l2_ops'), 'knex_items_embedding_idx', 'hnsw'); 84 | }); 85 | 86 | await knex.destroy(); 87 | }); 88 | -------------------------------------------------------------------------------- /tests/kysely.test.mjs: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import test from 'node:test'; 3 | import pg from 'pg'; 4 | import { Kysely, PostgresDialect, sql } from 'kysely'; 5 | import pgvector from 'pgvector/kysely'; 6 | import { l2Distance, maxInnerProduct, cosineDistance, l1Distance, hammingDistance, jaccardDistance } from 'pgvector/kysely'; 7 | import { SparseVector } from 'pgvector'; 8 | 9 | test('kysely example', async () => { 10 | const dialect = new PostgresDialect({ 11 | pool: new pg.Pool({ 12 | database: 'pgvector_node_test' 13 | }) 14 | }); 15 | 16 | const db = new Kysely({ 17 | dialect 18 | }); 19 | 20 | await sql`CREATE EXTENSION IF NOT EXISTS vector`.execute(db); 21 | 22 | await db.schema.dropTable('kysely_items') 23 | .ifExists() 24 | .execute(); 25 | 26 | await db.schema.createTable('kysely_items') 27 | .addColumn('id', 'serial', (cb) => cb.primaryKey()) 28 | .addColumn('embedding', sql`vector(3)`) 29 | .addColumn('half_embedding', sql`halfvec(3)`) 30 | .addColumn('binary_embedding', sql`bit(3)`) 31 | .addColumn('sparse_embedding', sql`sparsevec(3)`) 32 | .execute(); 33 | 34 | const newItems = [ 35 | {embedding: pgvector.toSql([1, 1, 1]), half_embedding: pgvector.toSql([1, 1, 1]), binary_embedding: '000', sparse_embedding: new SparseVector([1, 1, 1])}, 36 | {embedding: pgvector.toSql([2, 2, 2]), half_embedding: pgvector.toSql([2, 2, 2]), binary_embedding: '101', sparse_embedding: new SparseVector([2, 2, 2])}, 37 | {embedding: pgvector.toSql([1, 1, 2]), half_embedding: pgvector.toSql([1, 1, 2]), binary_embedding: '111', sparse_embedding: new SparseVector([1, 1, 2])}, 38 | {embedding: null} 39 | ]; 40 | await db.insertInto('kysely_items') 41 | .values(newItems) 42 | .execute(); 43 | 44 | // L2 distance 45 | let items = await db.selectFrom('kysely_items') 46 | .selectAll() 47 | .orderBy(l2Distance('embedding', [1, 1, 1])) 48 | .limit(5) 49 | .execute(); 50 | assert.deepEqual(items.map(v => v.id), [1, 3, 2, 4]); 51 | assert.deepEqual(pgvector.fromSql(items[0].embedding), [1, 1, 1]); 52 | assert.deepEqual(pgvector.fromSql(items[1].embedding), [1, 1, 2]); 53 | assert.deepEqual(pgvector.fromSql(items[2].embedding), [2, 2, 2]); 54 | 55 | // L2 distance - halfvec 56 | items = await db.selectFrom('kysely_items') 57 | .selectAll() 58 | .orderBy(l2Distance('half_embedding', [1, 1, 1])) 59 | .limit(5) 60 | .execute(); 61 | assert.deepEqual(items.map(v => v.id), [1, 3, 2, 4]); 62 | 63 | // L2 distance - sparsevec 64 | items = await db.selectFrom('kysely_items') 65 | .selectAll() 66 | .orderBy(l2Distance('sparse_embedding', new SparseVector([1, 1, 1]))) 67 | .limit(5) 68 | .execute(); 69 | assert.deepEqual(items.map(v => v.id), [1, 3, 2, 4]); 70 | 71 | // max inner product 72 | items = await db.selectFrom('kysely_items') 73 | .selectAll() 74 | .orderBy(maxInnerProduct('embedding', [1, 1, 1])) 75 | .limit(5) 76 | .execute(); 77 | assert.deepEqual(items.map(v => v.id), [2, 3, 1, 4]); 78 | 79 | // cosine distance 80 | items = await db.selectFrom('kysely_items') 81 | .selectAll() 82 | .orderBy(cosineDistance('embedding', [1, 1, 1])) 83 | .limit(5) 84 | .execute(); 85 | assert.deepEqual(items.map(v => v.id).slice(2), [3, 4]); 86 | 87 | // L1 distance 88 | items = await db.selectFrom('kysely_items') 89 | .selectAll() 90 | .orderBy(l1Distance('embedding', [1, 1, 1])) 91 | .limit(5) 92 | .execute(); 93 | assert.deepEqual(items.map(v => v.id), [1, 3, 2, 4]); 94 | 95 | // Hamming distance 96 | items = await db.selectFrom('kysely_items') 97 | .selectAll() 98 | .orderBy(hammingDistance('binary_embedding', '101')) 99 | .limit(5) 100 | .execute(); 101 | assert.deepEqual(items.map(v => v.id), [2, 3, 1, 4]); 102 | 103 | // Jaccard distance 104 | items = await db.selectFrom('kysely_items') 105 | .selectAll() 106 | .orderBy(jaccardDistance('binary_embedding', '101')) 107 | .limit(5) 108 | .execute(); 109 | assert.deepEqual(items.map(v => v.id), [2, 3, 1, 4]); 110 | 111 | // within distance 112 | items = await db.selectFrom('kysely_items') 113 | .selectAll() 114 | .where(l2Distance('embedding', [1, 1, 1]), '<', 0.5) 115 | .execute(); 116 | assert.deepEqual(items.map(v => v.id), [1]); 117 | 118 | await db.schema.createIndex('kysely_items_embedding_idx') 119 | .on('kysely_items') 120 | .using('hnsw') 121 | .expression(sql`embedding vector_l2_ops`) 122 | .execute(); 123 | 124 | db.destroy(); 125 | }); 126 | -------------------------------------------------------------------------------- /tests/mikro-orm.test.mjs: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import test from 'node:test'; 3 | import { MikroORM, EntitySchema } from '@mikro-orm/postgresql'; 4 | import { VectorType, HalfvecType, BitType, SparsevecType, l2Distance, maxInnerProduct, cosineDistance, l1Distance, hammingDistance, jaccardDistance } from 'pgvector/mikro-orm'; 5 | import { SparseVector } from 'pgvector'; 6 | 7 | test('mikro-orm example', async () => { 8 | const Item = new EntitySchema({ 9 | name: 'Item', 10 | tableName: 'mikro_items', 11 | properties: { 12 | id: {type: Number, primary: true}, 13 | embedding: {type: VectorType, dimensions: 3, nullable: true}, 14 | half_embedding: {type: HalfvecType, dimensions: 3, nullable: true}, 15 | binary_embedding: {type: BitType, length: 3, nullable: true}, 16 | sparse_embedding: {type: SparsevecType, dimensions: 3, nullable: true} 17 | }, 18 | }); 19 | 20 | const orm = await MikroORM.init({ 21 | entities: [Item], 22 | dbName: 'pgvector_node_test', 23 | user: process.env['USER'] 24 | }); 25 | const em = orm.em.fork(); 26 | 27 | await em.execute('CREATE EXTENSION IF NOT EXISTS vector'); 28 | await em.execute('DROP TABLE IF EXISTS mikro_items'); 29 | 30 | const generator = orm.getSchemaGenerator(); 31 | await generator.createSchema(); 32 | 33 | em.create(Item, {embedding: [1, 1, 1], half_embedding: [1, 1, 1], binary_embedding: '000', sparse_embedding: new SparseVector([1, 1, 1])}); 34 | em.create(Item, {embedding: [2, 2, 2], half_embedding: [2, 2, 2], binary_embedding: '101', sparse_embedding: new SparseVector([2, 2, 2])}); 35 | em.create(Item, {embedding: [1, 1, 2], half_embedding: [1, 1, 2], binary_embedding: '111', sparse_embedding: new SparseVector([1, 1, 2])}); 36 | em.create(Item, {embedding: null}); 37 | 38 | // L2 distance 39 | let items = await em.createQueryBuilder(Item) 40 | .orderBy({[l2Distance('embedding', [1, 1, 1])]: 'ASC'}) 41 | .limit(5) 42 | .getResult(); 43 | assert.deepEqual(items.map(v => v.id), [1, 3, 2, 4]); 44 | assert.deepEqual(items[0].embedding, [1, 1, 1]); 45 | assert.deepEqual(items[1].embedding, [1, 1, 2]); 46 | assert.deepEqual(items[2].embedding, [2, 2, 2]); 47 | 48 | // L2 distance - halfvec 49 | items = await em.createQueryBuilder(Item) 50 | .orderBy({[l2Distance('half_embedding', [1, 1, 1])]: 'ASC'}) 51 | .limit(5) 52 | .getResult(); 53 | assert.deepEqual(items.map(v => v.id), [1, 3, 2, 4]); 54 | 55 | // L2 distance - sparsevec 56 | items = await em.createQueryBuilder(Item) 57 | .orderBy({[l2Distance('sparse_embedding', new SparseVector([1, 1, 1]))]: 'ASC'}) 58 | .limit(5) 59 | .getResult(); 60 | assert.deepEqual(items.map(v => v.id), [1, 3, 2, 4]); 61 | 62 | // max inner product 63 | items = await em.createQueryBuilder(Item) 64 | .orderBy({[maxInnerProduct('embedding', [1, 1, 1])]: 'ASC'}) 65 | .limit(5) 66 | .getResult(); 67 | assert.deepEqual(items.map(v => v.id), [2, 3, 1, 4]); 68 | 69 | // cosine distance 70 | items = await em.createQueryBuilder(Item) 71 | .orderBy({[cosineDistance('embedding', [1, 1, 1])]: 'ASC'}) 72 | .limit(5) 73 | .getResult(); 74 | assert.deepEqual(items.map(v => v.id).slice(2), [3, 4]); 75 | 76 | // L1 distance 77 | items = await em.createQueryBuilder(Item) 78 | .orderBy({[l1Distance('embedding', [1, 1, 1])]: 'ASC'}) 79 | .limit(5) 80 | .getResult(); 81 | assert.deepEqual(items.map(v => v.id), [1, 3, 2, 4]); 82 | 83 | // Hamming distance 84 | items = await em.createQueryBuilder(Item) 85 | .orderBy({[hammingDistance('binary_embedding', '101')]: 'ASC'}) 86 | .limit(5) 87 | .getResult(); 88 | assert.deepEqual(items.map(v => v.id), [2, 3, 1, 4]); 89 | 90 | // Jaccard distance 91 | items = await em.createQueryBuilder(Item) 92 | .orderBy({[jaccardDistance('binary_embedding', '101')]: 'ASC'}) 93 | .limit(5) 94 | .getResult(); 95 | assert.deepEqual(items.map(v => v.id), [2, 3, 1, 4]); 96 | 97 | orm.close(); 98 | }); 99 | -------------------------------------------------------------------------------- /tests/objection.test.mjs: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import test from 'node:test'; 3 | import Knex from 'knex'; 4 | import { Model } from 'objection'; 5 | import pgvector from 'pgvector/objection'; 6 | import { l2Distance, maxInnerProduct, cosineDistance, l1Distance, hammingDistance, jaccardDistance } from 'pgvector/objection'; 7 | import { SparseVector } from 'pgvector'; 8 | 9 | test('objection example', async () => { 10 | const knex = Knex({ 11 | client: 'pg', 12 | connection: {database: 'pgvector_node_test'} 13 | }); 14 | 15 | Model.knex(knex); 16 | 17 | class Item extends Model { 18 | static get tableName() { 19 | return 'objection_items'; 20 | } 21 | } 22 | 23 | await knex.schema.createExtensionIfNotExists('vector'); 24 | await knex.schema.dropTableIfExists('objection_items'); 25 | await knex.schema.createTable('objection_items', (table) => { 26 | table.increments('id'); 27 | table.vector('embedding', 3); 28 | table.halfvec('half_embedding', 3); 29 | table.bit('binary_embedding', {length: 3}); 30 | table.sparsevec('sparse_embedding', 3); 31 | }); 32 | 33 | const newItems = [ 34 | {embedding: pgvector.toSql([1, 1, 1]), half_embedding: pgvector.toSql([1, 1, 1]), binary_embedding: '000', sparse_embedding: new SparseVector([1, 1, 1])}, 35 | {embedding: pgvector.toSql([2, 2, 2]), half_embedding: pgvector.toSql([2, 2, 2]), binary_embedding: '101', sparse_embedding: new SparseVector([2, 2, 2])}, 36 | {embedding: pgvector.toSql([1, 1, 2]), half_embedding: pgvector.toSql([1, 1, 2]), binary_embedding: '111', sparse_embedding: new SparseVector([1, 1, 2])}, 37 | {embedding: null} 38 | ]; 39 | await Item.query().insert(newItems); 40 | 41 | // L2 distance 42 | let items = await Item.query() 43 | .orderBy(l2Distance('embedding', [1, 1, 1])) 44 | .limit(5); 45 | assert.deepEqual(items.map(v => v.id), [1, 3, 2, 4]); 46 | assert.deepEqual(pgvector.fromSql(items[0].embedding), [1, 1, 1]); 47 | assert.deepEqual(pgvector.fromSql(items[1].embedding), [1, 1, 2]); 48 | assert.deepEqual(pgvector.fromSql(items[2].embedding), [2, 2, 2]); 49 | 50 | // L2 distance - halfvec 51 | items = await Item.query() 52 | .orderBy(l2Distance('half_embedding', [1, 1, 1])) 53 | .limit(5); 54 | assert.deepEqual(items.map(v => v.id), [1, 3, 2, 4]); 55 | 56 | // L2 distance - sparsevec 57 | items = await Item.query() 58 | .orderBy(l2Distance('sparse_embedding', new SparseVector([1, 1, 1]))) 59 | .limit(5); 60 | assert.deepEqual(items.map(v => v.id), [1, 3, 2, 4]); 61 | 62 | // max inner product 63 | items = await Item.query() 64 | .orderBy(maxInnerProduct('embedding', [1, 1, 1])) 65 | .limit(5); 66 | assert.deepEqual(items.map(v => v.id), [2, 3, 1, 4]); 67 | 68 | // cosine distance 69 | items = await Item.query() 70 | .orderBy(cosineDistance('embedding', [1, 1, 1])) 71 | .limit(5); 72 | assert.deepEqual(items.map(v => v.id).slice(2), [3, 4]); 73 | 74 | // L1 distance 75 | items = await Item.query() 76 | .orderBy(l1Distance('embedding', [1, 1, 1])) 77 | .limit(5); 78 | assert.deepEqual(items.map(v => v.id), [1, 3, 2, 4]); 79 | 80 | // Hamming distance 81 | items = await Item.query() 82 | .orderBy(hammingDistance('binary_embedding', '101')) 83 | .limit(5); 84 | assert.deepEqual(items.map(v => v.id), [2, 3, 1, 4]); 85 | 86 | // Jaccard distance 87 | items = await Item.query() 88 | .orderBy(jaccardDistance('binary_embedding', '101')) 89 | .limit(5); 90 | assert.deepEqual(items.map(v => v.id), [2, 3, 1, 4]); 91 | 92 | await knex.schema.alterTable('objection_items', function (table) { 93 | table.index(knex.raw('embedding vector_l2_ops'), 'objection_items_embedding_idx', 'hnsw'); 94 | }); 95 | 96 | await knex.destroy(); 97 | }); 98 | -------------------------------------------------------------------------------- /tests/pg-promise.test.mjs: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import test from 'node:test'; 3 | import pgpromise from 'pg-promise'; 4 | import pgvector from 'pgvector/pg-promise'; 5 | import { SparseVector } from 'pgvector'; 6 | 7 | test('pg-promise example', async () => { 8 | const initOptions = { 9 | async connect(e) { 10 | await pgvector.registerTypes(e.client); 11 | } 12 | }; 13 | const pgp = pgpromise(initOptions); 14 | const db = pgp({database: 'pgvector_node_test'}); 15 | 16 | await db.none('CREATE EXTENSION IF NOT EXISTS vector'); 17 | await db.none('DROP TABLE IF EXISTS pg_promise_items'); 18 | await db.none('CREATE TABLE pg_promise_items (id serial PRIMARY KEY, embedding vector(3), half_embedding halfvec(3), binary_embedding bit(3), sparse_embedding sparsevec(3))'); 19 | 20 | const params = [ 21 | pgvector.toSql([1, 1, 1]), pgvector.toSql([1, 1, 1]), '000', new SparseVector([1, 1, 1]), 22 | pgvector.toSql([2, 2, 2]), pgvector.toSql([2, 2, 2]), '101', new SparseVector([2, 2, 2]), 23 | pgvector.toSql([1, 1, 2]), pgvector.toSql([1, 1, 2]), '111', new SparseVector([1, 1, 2]), 24 | null, null, null, null 25 | ]; 26 | await db.none('INSERT INTO pg_promise_items (embedding, half_embedding, binary_embedding, sparse_embedding) VALUES ($1, $2, $3, $4), ($5, $6, $7, $8), ($9, $10, $11, $12), ($13, $14, $15, $16)', params); 27 | 28 | const items = await db.any('SELECT * FROM pg_promise_items ORDER BY embedding <-> $1 LIMIT 5', [pgvector.toSql([1, 1, 1])]); 29 | assert.deepEqual(items.map(v => v.id), [1, 3, 2, 4]); 30 | assert.deepEqual(items[0].embedding, [1, 1, 1]); 31 | assert.deepEqual(items[0].half_embedding, [1, 1, 1]); 32 | assert.equal(items[0].binary_embedding, '000'); 33 | assert.deepEqual(items[0].sparse_embedding.toArray(), [1, 1, 1]); 34 | 35 | await db.none('CREATE INDEX ON pg_promise_items USING hnsw (embedding vector_l2_ops)'); 36 | 37 | await pgp.end(); 38 | }); 39 | -------------------------------------------------------------------------------- /tests/pg.test.mjs: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import test from 'node:test'; 3 | import pg from 'pg'; 4 | import pgvector from 'pgvector/pg'; 5 | import { SparseVector } from 'pgvector'; 6 | 7 | test('pg example', async () => { 8 | const client = new pg.Client({database: 'pgvector_node_test'}); 9 | await client.connect(); 10 | 11 | await client.query('CREATE EXTENSION IF NOT EXISTS vector'); 12 | await pgvector.registerTypes(client); 13 | 14 | await client.query('DROP TABLE IF EXISTS pg_items'); 15 | await client.query('CREATE TABLE pg_items (id serial PRIMARY KEY, embedding vector(3), half_embedding halfvec(3), binary_embedding bit(3), sparse_embedding sparsevec(3))'); 16 | 17 | const params = [ 18 | pgvector.toSql([1, 1, 1]), pgvector.toSql([1, 1, 1]), '000', new SparseVector([1, 1, 1]), 19 | pgvector.toSql([2, 2, 2]), pgvector.toSql([2, 2, 2]), '101', new SparseVector([2, 2, 2]), 20 | pgvector.toSql([1, 1, 2]), pgvector.toSql([1, 1, 2]), '111', new SparseVector([1, 1, 2]), 21 | null, null, null, null 22 | ]; 23 | await client.query('INSERT INTO pg_items (embedding, half_embedding, binary_embedding, sparse_embedding) VALUES ($1, $2, $3, $4), ($5, $6, $7, $8), ($9, $10, $11, $12), ($13, $14, $15, $16)', params); 24 | 25 | const { rows } = await client.query('SELECT * FROM pg_items ORDER BY embedding <-> $1 LIMIT 5', [pgvector.toSql([1, 1, 1])]); 26 | assert.deepEqual(rows.map(v => v.id), [1, 3, 2, 4]); 27 | assert.deepEqual(rows[0].embedding, [1, 1, 1]); 28 | assert.deepEqual(rows[0].half_embedding, [1, 1, 1]); 29 | assert.deepEqual(rows[0].binary_embedding, '000'); 30 | assert.deepEqual(rows[0].sparse_embedding.toArray(), [1, 1, 1]); 31 | 32 | await client.query('CREATE INDEX ON pg_items USING hnsw (embedding vector_l2_ops)'); 33 | 34 | await client.end(); 35 | }); 36 | 37 | test('pool', async () => { 38 | const pool = new pg.Pool({database: 'pgvector_node_test'}); 39 | pool.on('connect', async function (client) { 40 | await client.query('CREATE EXTENSION IF NOT EXISTS vector'); 41 | await pgvector.registerType(client); 42 | }); 43 | 44 | await pool.query('DROP TABLE IF EXISTS pg_items'); 45 | await pool.query('CREATE TABLE pg_items (id serial PRIMARY KEY, embedding vector(3))'); 46 | 47 | const params = [ 48 | pgvector.toSql([1, 1, 1]), 49 | pgvector.toSql([2, 2, 2]), 50 | pgvector.toSql([1, 1, 2]), 51 | null 52 | ]; 53 | await pool.query('INSERT INTO pg_items (embedding) VALUES ($1), ($2), ($3), ($4)', params); 54 | 55 | const { rows } = await pool.query('SELECT * FROM pg_items ORDER BY embedding <-> $1 LIMIT 5', [pgvector.toSql([1, 1, 1])]); 56 | assert.deepEqual(rows.map(v => v.id), [1, 3, 2, 4]); 57 | assert.deepEqual(rows[0].embedding, [1, 1, 1]); 58 | assert.deepEqual(rows[1].embedding, [1, 1, 2]); 59 | assert.deepEqual(rows[2].embedding, [2, 2, 2]); 60 | 61 | await pool.query('CREATE INDEX ON pg_items USING hnsw (embedding vector_l2_ops)'); 62 | 63 | await pool.end(); 64 | }); 65 | -------------------------------------------------------------------------------- /tests/postgres.test.mjs: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import test from 'node:test'; 3 | import postgres from 'postgres'; 4 | import pgvector from 'pgvector'; 5 | import { SparseVector } from 'pgvector'; 6 | 7 | test('postgres example', async () => { 8 | const sql = postgres({database: 'pgvector_node_test', onnotice: function () { }}); 9 | 10 | await sql`CREATE EXTENSION IF NOT EXISTS vector`; 11 | await sql`DROP TABLE IF EXISTS postgres_items`; 12 | await sql`CREATE TABLE postgres_items (id serial PRIMARY KEY, embedding vector(3), half_embedding halfvec(3), binary_embedding bit(3), sparse_embedding sparsevec(3))`; 13 | 14 | const newItems = [ 15 | {embedding: pgvector.toSql([1, 1, 1]), half_embedding: pgvector.toSql([1, 1, 1]), binary_embedding: '000', sparse_embedding: new SparseVector([1, 1, 1])}, 16 | {embedding: pgvector.toSql([2, 2, 2]), half_embedding: pgvector.toSql([2, 2, 2]), binary_embedding: '101', sparse_embedding: new SparseVector([2, 2, 2])}, 17 | {embedding: pgvector.toSql([1, 1, 2]), half_embedding: pgvector.toSql([1, 1, 2]), binary_embedding: '111', sparse_embedding: new SparseVector([1, 1, 2])} 18 | ]; 19 | await sql`INSERT INTO postgres_items ${ sql(newItems, 'embedding', 'half_embedding', 'binary_embedding', 'sparse_embedding') }`; 20 | 21 | const embedding = pgvector.toSql([1, 1, 1]); 22 | const items = await sql`SELECT * FROM postgres_items ORDER BY embedding <-> ${ embedding } LIMIT 5`; 23 | assert.deepEqual(items.map(v => v.id), [1, 3, 2]); 24 | assert.deepEqual(pgvector.fromSql(items[0].embedding), [1, 1, 1]); 25 | assert.deepEqual(pgvector.fromSql(items[0].half_embedding), [1, 1, 1]); 26 | assert.equal(items[0].binary_embedding, '000'); 27 | assert.deepEqual((new SparseVector(items[0].sparse_embedding)).toArray(), [1, 1, 1]); 28 | 29 | await sql`CREATE INDEX ON postgres_items USING hnsw (embedding vector_l2_ops)`; 30 | 31 | await sql.end(); 32 | }); 33 | -------------------------------------------------------------------------------- /tests/prisma.test.mjs: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import test, { beforeEach } from 'node:test'; 3 | import pgvector from 'pgvector'; 4 | import { SparseVector } from 'pgvector'; 5 | import { PrismaClient } from '@prisma/client'; 6 | 7 | test('vector', async () => { 8 | const prisma = new PrismaClient(); 9 | 10 | // TODO use create when possible (field is not available in the generated client) 11 | // https://www.prisma.io/docs/concepts/components/prisma-schema/features-without-psl-equivalent#unsupported-field-types 12 | const embedding1 = pgvector.toSql([1, 1, 1]); 13 | const embedding2 = pgvector.toSql([2, 2, 2]); 14 | const embedding3 = pgvector.toSql([1, 1, 2]); 15 | await prisma.$executeRaw`INSERT INTO prisma_items (embedding) VALUES (${embedding1}::vector), (${embedding2}::vector), (${embedding3}::vector)`; 16 | 17 | // TODO use raw orderBy when available 18 | // https://github.com/prisma/prisma/issues/5848 19 | const embedding = pgvector.toSql([1, 1, 1]); 20 | const items = await prisma.$queryRaw`SELECT id, embedding::text FROM prisma_items ORDER BY embedding <-> ${embedding}::vector LIMIT 5`; 21 | assert.deepEqual(pgvector.fromSql(items[0].embedding), [1, 1, 1]); 22 | assert.deepEqual(pgvector.fromSql(items[1].embedding), [1, 1, 2]); 23 | assert.deepEqual(pgvector.fromSql(items[2].embedding), [2, 2, 2]); 24 | }); 25 | 26 | test('halfvec', async () => { 27 | const prisma = new PrismaClient(); 28 | 29 | // TODO use create when possible (field is not available in the generated client) 30 | // https://www.prisma.io/docs/concepts/components/prisma-schema/features-without-psl-equivalent#unsupported-field-types 31 | const embedding1 = pgvector.toSql([1, 1, 1]); 32 | const embedding2 = pgvector.toSql([2, 2, 2]); 33 | const embedding3 = pgvector.toSql([1, 1, 2]); 34 | await prisma.$executeRaw`INSERT INTO prisma_items (half_embedding) VALUES (${embedding1}::halfvec), (${embedding2}::halfvec), (${embedding3}::halfvec)`; 35 | 36 | // TODO use raw orderBy when available 37 | // https://github.com/prisma/prisma/issues/5848 38 | const embedding = pgvector.toSql([1, 1, 1]); 39 | const items = await prisma.$queryRaw`SELECT id, half_embedding::text FROM prisma_items ORDER BY half_embedding <-> ${embedding}::halfvec LIMIT 5`; 40 | assert.deepEqual(pgvector.fromSql(items[0].half_embedding), [1, 1, 1]); 41 | assert.deepEqual(pgvector.fromSql(items[1].half_embedding), [1, 1, 2]); 42 | assert.deepEqual(pgvector.fromSql(items[2].half_embedding), [2, 2, 2]); 43 | }); 44 | 45 | test('bit', async () => { 46 | const prisma = new PrismaClient(); 47 | 48 | await prisma.item.createMany({ 49 | data: [ 50 | {binary_embedding: '000'}, 51 | {binary_embedding: '101'}, 52 | {binary_embedding: '111'} 53 | ] 54 | }); 55 | 56 | // TODO use raw orderBy when available 57 | // https://github.com/prisma/prisma/issues/5848 58 | const embedding = '101'; 59 | const items = await prisma.$queryRaw`SELECT id, binary_embedding::text FROM prisma_items ORDER BY binary_embedding <~> ${embedding}::varbit LIMIT 5`; 60 | assert.equal(items[0].binary_embedding, '101'); 61 | assert.equal(items[1].binary_embedding, '111'); 62 | assert.equal(items[2].binary_embedding, '000'); 63 | }); 64 | 65 | test('sparsevec', async () => { 66 | const prisma = new PrismaClient(); 67 | 68 | // TODO use create when possible (field is not available in the generated client) 69 | // https://www.prisma.io/docs/concepts/components/prisma-schema/features-without-psl-equivalent#unsupported-field-types 70 | const embedding1 = pgvector.toSql(new SparseVector([1, 1, 1])); 71 | const embedding2 = pgvector.toSql(new SparseVector([2, 2, 2])); 72 | const embedding3 = pgvector.toSql(new SparseVector([1, 1, 2])); 73 | await prisma.$executeRaw`INSERT INTO prisma_items (sparse_embedding) VALUES (${embedding1}::sparsevec), (${embedding2}::sparsevec), (${embedding3}::sparsevec)`; 74 | 75 | // TODO use raw orderBy when available 76 | // https://github.com/prisma/prisma/issues/5848 77 | const embedding = pgvector.toSql(new SparseVector([1, 1, 1])); 78 | const items = await prisma.$queryRaw`SELECT id, sparse_embedding::text FROM prisma_items ORDER BY sparse_embedding <-> ${embedding}::sparsevec LIMIT 5`; 79 | assert.deepEqual(pgvector.fromSql(items[0].sparse_embedding).toArray(), [1, 1, 1]); 80 | assert.deepEqual(pgvector.fromSql(items[1].sparse_embedding).toArray(), [1, 1, 2]); 81 | assert.deepEqual(pgvector.fromSql(items[2].sparse_embedding).toArray(), [2, 2, 2]); 82 | }); 83 | 84 | beforeEach(async () => { 85 | const prisma = new PrismaClient(); 86 | await prisma.item.deleteMany({}); 87 | }); 88 | -------------------------------------------------------------------------------- /tests/sequelize.test.mjs: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import test from 'node:test'; 3 | import { Sequelize, DataTypes } from 'sequelize'; 4 | import pgvector from 'pgvector/sequelize'; 5 | import { l2Distance, maxInnerProduct, cosineDistance, l1Distance, hammingDistance, jaccardDistance } from 'pgvector/sequelize'; 6 | import { SparseVector } from 'pgvector'; 7 | 8 | test('sequelize example', async () => { 9 | pgvector.registerTypes(Sequelize); 10 | 11 | let sequelize = new Sequelize('postgres://localhost/pgvector_node_test', { 12 | logging: false 13 | }); 14 | await sequelize.query('CREATE EXTENSION IF NOT EXISTS vector'); 15 | 16 | // need to reconnect after the vector extension has been created 17 | sequelize.close(); 18 | sequelize = new Sequelize('postgres://localhost/pgvector_node_test', { 19 | logging: false 20 | }); 21 | 22 | const Item = sequelize.define('Item', { 23 | embedding: { 24 | type: DataTypes.VECTOR(3) 25 | }, 26 | half_embedding: { 27 | type: DataTypes.HALFVEC(3) 28 | }, 29 | binary_embedding: { 30 | type: 'BIT(3)' 31 | }, 32 | sparse_embedding: { 33 | type: DataTypes.SPARSEVEC(3) 34 | } 35 | }, { 36 | modelName: 'Item', 37 | tableName: 'sequelize_items', 38 | indexes: [ 39 | { 40 | fields: ['embedding'], 41 | using: 'hnsw', 42 | operator: 'vector_l2_ops' 43 | } 44 | ] 45 | }); 46 | 47 | await Item.sync({force: true}); 48 | 49 | await Item.create({embedding: [1, 1, 1], half_embedding: [1, 1, 1], binary_embedding: '000', sparse_embedding: new SparseVector([1, 1, 1])}); 50 | await Item.create({embedding: [2, 2, 2], half_embedding: [2, 2, 2], binary_embedding: '101', sparse_embedding: new SparseVector([2, 2, 2])}); 51 | await Item.create({embedding: [1, 1, 2], half_embedding: [1, 1, 2], binary_embedding: '111', sparse_embedding: new SparseVector([1, 1, 2])}); 52 | 53 | // L2 distance 54 | let items = await Item.findAll({ 55 | order: l2Distance('embedding', [1, 1, 1], sequelize), 56 | limit: 5 57 | }); 58 | assert.deepEqual(items.map(v => v.id), [1, 3, 2]); 59 | assert.deepEqual(items[0].embedding, [1, 1, 1]); 60 | assert.deepEqual(items[1].embedding, [1, 1, 2]); 61 | assert.deepEqual(items[2].embedding, [2, 2, 2]); 62 | 63 | // L2 distance - halfvec 64 | items = await Item.findAll({ 65 | order: l2Distance('half_embedding', [1, 1, 1], sequelize), 66 | limit: 5 67 | }); 68 | assert.deepEqual(items.map(v => v.id), [1, 3, 2]); 69 | 70 | // L2 distance - sparsevec 71 | items = await Item.findAll({ 72 | order: l2Distance('sparse_embedding', new SparseVector([1, 1, 1]), sequelize), 73 | limit: 5 74 | }); 75 | assert.deepEqual(items.map(v => v.id), [1, 3, 2]); 76 | 77 | await Item.create({}); 78 | 79 | // max inner product 80 | items = await Item.findAll({ 81 | order: maxInnerProduct(sequelize.literal('"Item".embedding'), [1, 1, 1], sequelize), 82 | limit: 5 83 | }); 84 | assert.deepEqual(items.map(v => v.id), [2, 3, 1, 4]); 85 | 86 | // cosine distance 87 | items = await Item.findAll({ 88 | order: cosineDistance('embedding', [1, 1, 1], sequelize), 89 | limit: 5 90 | }); 91 | assert.deepEqual(items.map(v => v.id).slice(2), [3, 4]); 92 | 93 | // L1 distance 94 | items = await Item.findAll({ 95 | order: l1Distance('embedding', [1, 1, 1], sequelize), 96 | limit: 5 97 | }); 98 | assert.deepEqual(items.map(v => v.id), [1, 3, 2, 4]); 99 | 100 | // Hamming distance 101 | items = await Item.findAll({ 102 | order: hammingDistance('binary_embedding', '101', sequelize), 103 | limit: 5 104 | }); 105 | assert.deepEqual(items.map(v => v.id), [2, 3, 1, 4]); 106 | 107 | // Jaccard distance 108 | items = await Item.findAll({ 109 | order: jaccardDistance('binary_embedding', '101', sequelize), 110 | limit: 5 111 | }); 112 | assert.deepEqual(items.map(v => v.id), [2, 3, 1, 4]); 113 | 114 | // bad value 115 | await assert.rejects(Item.create({embedding: 'bad'}), {message: /invalid input syntax for type vector/}) 116 | 117 | sequelize.close(); 118 | }); 119 | 120 | test('dimensions', () => { 121 | assert.equal(DataTypes.VECTOR(3).toSql(), 'VECTOR(3)'); 122 | }); 123 | 124 | test('no dimensions', () => { 125 | assert.equal(DataTypes.VECTOR().toSql(), 'VECTOR'); 126 | }); 127 | 128 | test('bad dimensions', () => { 129 | assert.throws(() => { 130 | DataTypes.VECTOR('bad').toSql(); 131 | }, {message: 'expected integer'}); 132 | }); 133 | -------------------------------------------------------------------------------- /tests/slonik.test.mjs: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import test from 'node:test'; 3 | import pgvector from 'pgvector'; 4 | import { SparseVector } from 'pgvector'; 5 | import { createPool, sql } from 'slonik'; 6 | 7 | test('slonik example', async () => { 8 | const pool = await createPool('postgres://localhost/pgvector_node_test'); 9 | 10 | await pool.query(sql.unsafe`CREATE EXTENSION IF NOT EXISTS vector`); 11 | await pool.query(sql.unsafe`DROP TABLE IF EXISTS slonik_items`); 12 | await pool.query(sql.unsafe`CREATE TABLE slonik_items (id serial PRIMARY KEY, embedding vector(3), half_embedding halfvec(3), binary_embedding bit(3), sparse_embedding sparsevec(3))`); 13 | 14 | const embedding1 = pgvector.toSql([1, 1, 1]); 15 | const embedding2 = pgvector.toSql([2, 2, 2]); 16 | const embedding3 = pgvector.toSql([1, 1, 2]); 17 | const halfEmbedding1 = pgvector.toSql([1, 1, 1]); 18 | const halfEmbedding2 = pgvector.toSql([2, 2, 2]); 19 | const halfEmbedding3 = pgvector.toSql([1, 1, 2]); 20 | const binaryEmbedding1 = '000'; 21 | const binaryEmbedding2 = '101'; 22 | const binaryEmbedding3 = '111'; 23 | const sparseEmbedding1 = pgvector.toSql(new SparseVector([1, 1, 1])); 24 | const sparseEmbedding2 = pgvector.toSql(new SparseVector([2, 2, 2])); 25 | const sparseEmbedding3 = pgvector.toSql(new SparseVector([1, 1, 2])); 26 | await pool.query(sql.unsafe`INSERT INTO slonik_items (embedding, half_embedding, binary_embedding, sparse_embedding) VALUES (${embedding1}, ${halfEmbedding1}, ${binaryEmbedding1}, ${sparseEmbedding1}), (${embedding2}, ${halfEmbedding2}, ${binaryEmbedding2}, ${sparseEmbedding2}), (${embedding3}, ${halfEmbedding3}, ${binaryEmbedding3}, ${sparseEmbedding3})`); 27 | 28 | const embedding = pgvector.toSql([1, 1, 1]); 29 | const items = await pool.query(sql.unsafe`SELECT * FROM slonik_items ORDER BY embedding <-> ${embedding} LIMIT 5`); 30 | assert.deepEqual(items.rows.map(v => v.id), [1, 3, 2]); 31 | assert.deepEqual(pgvector.fromSql(items.rows[0].embedding), [1, 1, 1]); 32 | assert.deepEqual(pgvector.fromSql(items.rows[0].half_embedding), [1, 1, 1]); 33 | assert.equal(items.rows[0].binary_embedding, '000'); 34 | assert.deepEqual(pgvector.fromSql(items.rows[0].sparse_embedding).toArray(), [1, 1, 1]); 35 | 36 | await pool.query(sql.unsafe`CREATE INDEX ON slonik_items USING hnsw (embedding vector_l2_ops)`); 37 | 38 | await pool.end(); 39 | }); 40 | -------------------------------------------------------------------------------- /tests/sparse-vector.test.mjs: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import test from 'node:test'; 3 | import { SparseVector } from 'pgvector/utils'; 4 | 5 | test('fromSql', () => { 6 | const vec = new SparseVector('{1:1,3:2,5:3}/6'); 7 | assert.deepEqual(vec.toArray(), [1, 0, 2, 0, 3, 0]); 8 | assert.equal(vec.dimensions, 6); 9 | assert.deepEqual(vec.indices, [0, 2, 4]); 10 | assert.deepEqual(vec.values, [1, 2, 3]); 11 | }); 12 | 13 | test('fromDense', () => { 14 | const vec = new SparseVector([1, 0, 2, 0, 3, 0]); 15 | assert.equal(vec.toPostgres(), '{1:1,3:2,5:3}/6'); 16 | assert.equal(vec.dimensions, 6); 17 | assert.deepEqual(vec.indices, [0, 2, 4]); 18 | assert.deepEqual(vec.values, [1, 2, 3]); 19 | }); 20 | 21 | test('fromMap', () => { 22 | const map = new Map(); 23 | map.set(2, 2); 24 | map.set(4, 3); 25 | map.set(0, 1); 26 | map.set(3, 0); 27 | const vec = new SparseVector(map, 6); 28 | assert.equal(vec.dimensions, 6); 29 | assert.deepEqual(vec.indices, [2, 4, 0]); 30 | assert.deepEqual(vec.values, [2, 3, 1]); 31 | }); 32 | 33 | test('fromMapObject', () => { 34 | const map = {2: 2, 4: 3, 0: 1, 3: 0}; 35 | const vec = new SparseVector(map, 6); 36 | assert.equal(vec.dimensions, 6); 37 | assert.deepEqual(vec.indices, [0, 2, 4]); 38 | assert.deepEqual(vec.values, [1, 2, 3]); 39 | }); 40 | 41 | test('toPostgres', () => { 42 | const vec = new SparseVector([1.23456789]); 43 | assert.equal(vec.toPostgres(), '{1:1.23456789}/1'); 44 | }); 45 | -------------------------------------------------------------------------------- /tests/typeorm.test.mjs: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import test from 'node:test'; 3 | import pgvector from 'pgvector'; 4 | import { SparseVector } from 'pgvector'; 5 | import { DataSource, EntitySchema } from 'typeorm'; 6 | 7 | test('typeorm example', async () => { 8 | // entity definition without decorators 9 | // https://typeorm.io/separating-entity-definition 10 | const Item = new EntitySchema({ 11 | name: 'Item', 12 | tableName: 'typeorm_items', 13 | columns: { 14 | id: { 15 | type: Number, 16 | primary: true, 17 | generated: true 18 | }, 19 | // custom types not supported 20 | // https://github.com/typeorm/typeorm/issues/10056 21 | embedding: { 22 | type: String 23 | }, 24 | half_embedding: { 25 | type: String 26 | }, 27 | binary_embedding: { 28 | type: String 29 | }, 30 | sparse_embedding: { 31 | type: String 32 | } 33 | } 34 | }); 35 | 36 | const AppDataSource = new DataSource({ 37 | type: 'postgres', 38 | database: 'pgvector_node_test', 39 | logging: false, 40 | entities: [Item] 41 | }); 42 | await AppDataSource.initialize(); 43 | 44 | await AppDataSource.query('CREATE EXTENSION IF NOT EXISTS vector'); 45 | await AppDataSource.query('DROP TABLE IF EXISTS typeorm_items'); 46 | await AppDataSource.query('CREATE TABLE typeorm_items (id bigserial PRIMARY KEY, embedding vector(3), half_embedding halfvec(3), binary_embedding bit(3), sparse_embedding sparsevec(3))'); 47 | 48 | const itemRepository = AppDataSource.getRepository(Item); 49 | await itemRepository.save({embedding: pgvector.toSql([1, 1, 1]), half_embedding: pgvector.toSql([1, 1, 1]), binary_embedding: '000', sparse_embedding: new SparseVector([1, 1, 1])}); 50 | await itemRepository.save({embedding: pgvector.toSql([2, 2, 2]), half_embedding: pgvector.toSql([2, 2, 2]), binary_embedding: '101', sparse_embedding: new SparseVector([2, 2, 2])}); 51 | await itemRepository.save({embedding: pgvector.toSql([1, 1, 2]), half_embedding: pgvector.toSql([1, 1, 2]), binary_embedding: '111', sparse_embedding: new SparseVector([1, 1, 2])}); 52 | 53 | const items = await itemRepository 54 | .createQueryBuilder('item') 55 | .orderBy('embedding <-> :embedding') 56 | .setParameters({embedding: pgvector.toSql([1, 1, 1])}) 57 | .limit(5) 58 | .getMany(); 59 | assert.deepEqual(items.map(v => v.id), [1, 3, 2]); 60 | assert.deepEqual(pgvector.fromSql(items[0].embedding), [1, 1, 1]); 61 | assert.deepEqual(pgvector.fromSql(items[0].half_embedding), [1, 1, 1]); 62 | assert.equal(items[0].binary_embedding, '000'); 63 | assert.deepEqual((new SparseVector(items[0].sparse_embedding).toArray()), [1, 1, 1]); 64 | 65 | await AppDataSource.destroy(); 66 | }); 67 | -------------------------------------------------------------------------------- /tests/utils.test.mjs: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import test from 'node:test'; 3 | import pgvector from 'pgvector/utils'; 4 | import { SparseVector } from 'pgvector/utils'; 5 | 6 | test('fromSql', () => { 7 | assert.deepEqual(pgvector.fromSql('[1,2,3]'), [1, 2, 3]); 8 | assert.deepEqual(pgvector.fromSql('{1:1,2:2,3:3}/3').toArray(), [1, 2, 3]); 9 | assert.equal(pgvector.fromSql(null), null); 10 | assert.throws(() => pgvector.fromSql(''), {message: 'invalid text representation'}); 11 | }); 12 | 13 | test('toSql', () => { 14 | assert.equal(pgvector.toSql([1, 2, 3]), '[1,2,3]'); 15 | assert.equal(pgvector.toSql(new SparseVector([1, 2, 3])), '{1:1,2:2,3:3}/3'); 16 | assert.equal(pgvector.toSql(null), null); 17 | assert.throws(() => pgvector.toSql({}), {message: 'expected array or sparse vector'}); 18 | }); 19 | 20 | test('sqlType', () => { 21 | assert.equal(pgvector.sqlType(), 'vector'); 22 | assert.equal(pgvector.sqlType(3), 'vector(3)'); 23 | assert.throws(() => pgvector.sqlType('3'), {message: 'expected integer'}); 24 | }); 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*"], 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "declaration": true, 6 | "emitDeclarationOnly": true, 7 | "outDir": "types", 8 | "declarationMap": false, 9 | "esModuleInterop": true, 10 | "target": "es6", 11 | "module": "nodenext", 12 | "moduleResolution": "nodenext" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | import { fromSql } from "./utils"; 2 | import { toSql } from "./utils"; 3 | import { SparseVector } from "./utils"; 4 | export { fromSql, toSql, SparseVector }; 5 | -------------------------------------------------------------------------------- /types/knex/index.d.ts: -------------------------------------------------------------------------------- 1 | import { fromSql } from "../utils"; 2 | import { toSql } from "../utils"; 3 | export { fromSql, toSql }; 4 | -------------------------------------------------------------------------------- /types/kysely/index.d.ts: -------------------------------------------------------------------------------- 1 | import { fromSql } from ".."; 2 | import { toSql } from ".."; 3 | export function l2Distance(column: any, value: any): import("kysely").RawBuilder; 4 | export function maxInnerProduct(column: any, value: any): import("kysely").RawBuilder; 5 | export function cosineDistance(column: any, value: any): import("kysely").RawBuilder; 6 | export function l1Distance(column: any, value: any): import("kysely").RawBuilder; 7 | export function hammingDistance(column: any, value: any): import("kysely").RawBuilder; 8 | export function jaccardDistance(column: any, value: any): import("kysely").RawBuilder; 9 | export { fromSql, toSql }; 10 | -------------------------------------------------------------------------------- /types/mikro-orm/bit.d.ts: -------------------------------------------------------------------------------- 1 | export class BitType extends Type { 2 | constructor(); 3 | getColumnType(prop: any, platform: any): any; 4 | } 5 | import { Type } from "@mikro-orm/core/types"; 6 | -------------------------------------------------------------------------------- /types/mikro-orm/halfvec.d.ts: -------------------------------------------------------------------------------- 1 | export class HalfvecType extends Type { 2 | constructor(); 3 | convertToDatabaseValue(value: any, platform: any): any; 4 | convertToJSValue(value: any, platform: any): any; 5 | getColumnType(prop: any, platform: any): any; 6 | } 7 | import { Type } from "@mikro-orm/core/types"; 8 | -------------------------------------------------------------------------------- /types/mikro-orm/index.d.ts: -------------------------------------------------------------------------------- 1 | import { VectorType } from "./vector"; 2 | import { HalfvecType } from "./halfvec"; 3 | import { BitType } from "./bit"; 4 | import { SparsevecType } from "./sparsevec"; 5 | export function l2Distance(column: any, value: any, em: any): any; 6 | export function maxInnerProduct(column: any, value: any, em: any): any; 7 | export function cosineDistance(column: any, value: any, em: any): any; 8 | export function l1Distance(column: any, value: any, em: any): any; 9 | export function hammingDistance(column: any, value: any, em: any): any; 10 | export function jaccardDistance(column: any, value: any, em: any): any; 11 | export { VectorType, HalfvecType, BitType, SparsevecType }; 12 | -------------------------------------------------------------------------------- /types/mikro-orm/sparsevec.d.ts: -------------------------------------------------------------------------------- 1 | export class SparsevecType extends Type { 2 | constructor(); 3 | convertToDatabaseValue(value: any, platform: any): any; 4 | convertToJSValue(value: any, platform: any): utils.SparseVector; 5 | getColumnType(prop: any, platform: any): any; 6 | } 7 | import { Type } from "@mikro-orm/core/types"; 8 | import utils = require("../utils"); 9 | -------------------------------------------------------------------------------- /types/mikro-orm/vector.d.ts: -------------------------------------------------------------------------------- 1 | export class VectorType extends Type { 2 | constructor(); 3 | convertToDatabaseValue(value: any, platform: any): any; 4 | convertToJSValue(value: any, platform: any): any; 5 | getColumnType(prop: any, platform: any): any; 6 | } 7 | import { Type } from "@mikro-orm/core/types"; 8 | -------------------------------------------------------------------------------- /types/objection/index.d.ts: -------------------------------------------------------------------------------- 1 | import { fromSql } from "../knex"; 2 | import { toSql } from "../knex"; 3 | export function l2Distance(column: any, value: any): import("objection").RawBuilder; 4 | export function maxInnerProduct(column: any, value: any): import("objection").RawBuilder; 5 | export function cosineDistance(column: any, value: any): import("objection").RawBuilder; 6 | export function l1Distance(column: any, value: any): import("objection").RawBuilder; 7 | export function hammingDistance(column: any, value: any): import("objection").RawBuilder; 8 | export function jaccardDistance(column: any, value: any): import("objection").RawBuilder; 9 | export { fromSql, toSql }; 10 | -------------------------------------------------------------------------------- /types/pg-promise/index.d.ts: -------------------------------------------------------------------------------- 1 | import { registerType } from "../pg"; 2 | import { registerTypes } from "../pg"; 3 | import { toSql } from "../pg"; 4 | export { registerType, registerTypes, toSql }; 5 | -------------------------------------------------------------------------------- /types/pg/index.d.ts: -------------------------------------------------------------------------------- 1 | export function registerType(client: any): Promise; 2 | export function registerTypes(client: any): Promise; 3 | import { toSql } from "../utils"; 4 | export { toSql }; 5 | -------------------------------------------------------------------------------- /types/sequelize/halfvec.d.ts: -------------------------------------------------------------------------------- 1 | export function registerHalfvec(Sequelize: any): void; 2 | -------------------------------------------------------------------------------- /types/sequelize/index.d.ts: -------------------------------------------------------------------------------- 1 | export function registerType(Sequelize: any): void; 2 | export function registerTypes(Sequelize: any): void; 3 | export function l2Distance(column: any, value: any, sequelize: any): any; 4 | export function maxInnerProduct(column: any, value: any, sequelize: any): any; 5 | export function cosineDistance(column: any, value: any, sequelize: any): any; 6 | export function l1Distance(column: any, value: any, sequelize: any): any; 7 | export function hammingDistance(column: any, value: any, sequelize: any): any; 8 | export function jaccardDistance(column: any, value: any, sequelize: any): any; 9 | -------------------------------------------------------------------------------- /types/sequelize/sparsevec.d.ts: -------------------------------------------------------------------------------- 1 | export function registerSparsevec(Sequelize: any): void; 2 | -------------------------------------------------------------------------------- /types/sequelize/vector.d.ts: -------------------------------------------------------------------------------- 1 | export function registerVector(Sequelize: any): void; 2 | -------------------------------------------------------------------------------- /types/utils/index.d.ts: -------------------------------------------------------------------------------- 1 | export function fromSql(value: any): any; 2 | export function toSql(value: any): any; 3 | export function vectorFromSql(value: any): any; 4 | export function vectorToSql(value: any): any; 5 | export function halfvecFromSql(value: any): any; 6 | export function halfvecToSql(value: any): any; 7 | export function sparsevecFromSql(value: any): SparseVector; 8 | export function sparsevecToSql(value: any): any; 9 | export function sqlType(dimensions: any): any; 10 | export function vectorType(dimensions: any): any; 11 | export function halfvecType(dimensions: any): any; 12 | export function bitType(dimensions: any): any; 13 | export function sparsevecType(dimensions: any): any; 14 | import { SparseVector } from "./sparse-vector"; 15 | export { SparseVector }; 16 | -------------------------------------------------------------------------------- /types/utils/sparse-vector.d.ts: -------------------------------------------------------------------------------- 1 | export class SparseVector { 2 | constructor(value: any, dimensions: any); 3 | toPostgres(): string; 4 | toString(): string; 5 | toArray(): any[]; 6 | dimensions: any; 7 | indices: any[]; 8 | values: any[]; 9 | #private; 10 | } 11 | --------------------------------------------------------------------------------