From 22c16d31416f310367b035e5204c584f5e7e4741 Mon Sep 17 00:00:00 2001 From: Andrew Kane Date: Wed, 13 Dec 2023 10:57:32 -0500 Subject: [PATCH] Added Objection.js example --- README.md | 58 ++++++++++++++++++++++++++++++++- package.json | 1 + tests/objection/index.test.mjs | 59 ++++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 tests/objection/index.test.mjs diff --git a/README.md b/README.md index 7686f8a..1473da7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [pgvector](https://github.com/pgvector/pgvector) support for Node.js and Bun (and TypeScript) -Supports [node-postgres](https://github.com/brianc/node-postgres), [Knex.js](https://github.com/knex/knex), [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), [TypeORM](https://github.com/typeorm/typeorm), [MikroORM](https://github.com/mikro-orm/mikro-orm), and [Drizzle ORM](https://github.com/drizzle-team/drizzle-orm) +Supports [node-postgres](https://github.com/brianc/node-postgres), [Knex.js](https://github.com/knex/knex), [Objection.js](https://github.com/vincit/objection.js), [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), [TypeORM](https://github.com/typeorm/typeorm), [MikroORM](https://github.com/mikro-orm/mikro-orm), and [Drizzle ORM](https://github.com/drizzle-team/drizzle-orm) [![Build Status](https://github.com/pgvector/pgvector-node/workflows/build/badge.svg?branch=master)](https://github.com/pgvector/pgvector-node/actions) @@ -18,6 +18,7 @@ And follow the instructions for your database library: - [node-postgres](#node-postgres) - [Knex.js](#knexjs) +- [Objection.js](#objectionjs) - [Sequelize](#sequelize) - [pg-promise](#pg-promise) - [Prisma](#prisma) @@ -142,6 +143,61 @@ Use `vector_ip_ops` for inner product and `vector_cosine_ops` for cosine distanc See a [full example](tests/knex/index.test.mjs) +## Objection.js + +Import the library + +```javascript +import pgvector from 'pgvector/knex'; +``` + +Enable the extension + +```javascript +await knex.schema.enableExtension('vector'); +``` + +Create a table + +```javascript +await knex.schema.createTable('items', (table) => { + table.increments('id'); + table.vector('embedding', {dimensions: 3}); +}); +``` + +Insert vectors + +```javascript +const newItems = [ + {embedding: pgvector.toSql([1, 2, 3])}, + {embedding: pgvector.toSql([4, 5, 6])} +]; +await Item.query().insert(newItems); +``` + +Get the nearest neighbors to a vector + +```javascript +const items = await Item.query() + .orderBy(knex.l2Distance('embedding', [1, 2, 3])) + .limit(5); +``` + +Also supports `maxInnerProduct` and `cosineDistance` + +Add an approximate index + +```javascript +await knex.schema.alterTable('items', function(table) { + table.index(knex.raw('embedding vector_l2_ops'), 'index_name', 'hnsw'); +}); +``` + +Use `vector_ip_ops` for inner product and `vector_cosine_ops` for cosine distance + +See a [full example](tests/objection/index.test.mjs) + ## Sequelize Enable the extension diff --git a/package.json b/package.json index cdf5790..ae9fed8 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "drizzle-orm": "^0.29.1", "jest": "^29.5.0", "knex": "^3.1.0", + "objection": "^3.1.3", "pg": "^8.6.0", "pg-promise": "^11.4.3", "postgres": "^3.3.4", diff --git a/tests/objection/index.test.mjs b/tests/objection/index.test.mjs new file mode 100644 index 0000000..8e4ad90 --- /dev/null +++ b/tests/objection/index.test.mjs @@ -0,0 +1,59 @@ +import Knex from 'knex'; +import { Model } from 'objection'; +import pgvector from 'pgvector/knex'; + +test('example', async () => { + const knex = Knex({ + client: 'pg', + connection: {database: 'pgvector_node_test'} + }); + + Model.knex(knex); + + class Item extends Model { + static get tableName() { + return 'objection_items'; + } + } + + await knex.schema.enableExtension('vector'); + await knex.schema.dropTableIfExists('objection_items'); + await knex.schema.createTable('objection_items', (table) => { + table.increments('id'); + table.vector('embedding', {dimensions: 3}); + }); + + const newItems = [ + {embedding: pgvector.toSql([1, 1, 1])}, + {embedding: pgvector.toSql([2, 2, 2])}, + {embedding: pgvector.toSql([1, 1, 2])} + ]; + await Item.query().insert(newItems); + + // L2 distance + let items = await Item.query() + .orderBy(knex.l2Distance('embedding', [1, 1, 1])) + .limit(5); + expect(items.map(v => v.id)).toStrictEqual([1, 3, 2]); + expect(pgvector.fromSql(items[0].embedding)).toStrictEqual([1, 1, 1]); + expect(pgvector.fromSql(items[1].embedding)).toStrictEqual([1, 1, 2]); + expect(pgvector.fromSql(items[2].embedding)).toStrictEqual([2, 2, 2]); + + // max inner product + items = await Item.query() + .orderBy(knex.maxInnerProduct('embedding', [1, 1, 1])) + .limit(5); + expect(items.map(v => v.id)).toStrictEqual([2, 3, 1]); + + // cosine distance + items = await Item.query() + .orderBy(knex.cosineDistance('embedding', [1, 1, 1])) + .limit(5); + expect(items[2].id).toEqual(3); + + await knex.schema.alterTable('objection_items', function(table) { + table.index(knex.raw('embedding vector_l2_ops'), 'objection_items_embedding_idx', 'hnsw'); + }); + + await knex.destroy(); +});