@ragpipe/plugin-supabase
Advanced tools
| -- ragpipe/plugin-supabase initial schema | ||
| -- 1. Enable pgvector | ||
| CREATE EXTENSION IF NOT EXISTS vector; | ||
| -- 2. Create the documents table | ||
| CREATE TABLE documents ( | ||
| id BIGSERIAL PRIMARY KEY, | ||
| source TEXT NOT NULL, | ||
| content TEXT NOT NULL, | ||
| vector VECTOR(3072), | ||
| UNIQUE(source, content) | ||
| ); | ||
| -- 3. Create the similarity search function | ||
| CREATE OR REPLACE FUNCTION match_documents( | ||
| query_embedding VECTOR(3072), | ||
| match_count INT DEFAULT 5 | ||
| ) | ||
| RETURNS TABLE ( | ||
| source TEXT, | ||
| content TEXT, | ||
| similarity FLOAT | ||
| ) | ||
| LANGUAGE plpgsql | ||
| AS $$ | ||
| BEGIN | ||
| RETURN QUERY | ||
| SELECT | ||
| d.source, | ||
| d.content, | ||
| 1 - (d.vector <=> query_embedding) AS similarity | ||
| FROM documents d | ||
| ORDER BY d.vector <=> query_embedding | ||
| LIMIT match_count; | ||
| END; | ||
| $$; | ||
| -- 4. Create an index for faster search | ||
| CREATE INDEX ON documents | ||
| USING ivfflat (vector vector_cosine_ops) | ||
| WITH (lists = 100); |
+28
-32
| "use strict"; | ||
| var __create = Object.create; | ||
| var __defProp = Object.defineProperty; | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __getProtoOf = Object.getPrototypeOf; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
@@ -20,10 +18,2 @@ var __export = (target, all) => { | ||
| }; | ||
| var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( | ||
| // If the importer is in node compatibility mode or this is not an ESM | ||
| // file that has been converted to a CommonJS file using a Babel- | ||
| // compatible transform (i.e. "__esModule" has not been set), then set | ||
| // "default" to the CommonJS "module.exports" for node compatibility. | ||
| isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, | ||
| mod | ||
| )); | ||
| var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | ||
@@ -39,35 +29,41 @@ | ||
| // src/vector-store.ts | ||
| var import_postgres = __toESM(require("postgres"), 1); | ||
| var import_supabase_js = require("@supabase/supabase-js"); | ||
| function supabaseVectorStore(options) { | ||
| const table = options.tableName ?? "documents"; | ||
| const sql = (0, import_postgres.default)(options.databaseUrl); | ||
| const queryName = options.queryName ?? "match_documents"; | ||
| const supabase = (0, import_supabase_js.createClient)( | ||
| options.supabaseUrl, | ||
| options.supabaseKey | ||
| ); | ||
| return { | ||
| name: "supabase", | ||
| async search(vector, topK) { | ||
| const vectorStr = `[${vector.join(",")}]`; | ||
| const results = await sql` | ||
| SELECT source, content, | ||
| 1 - (vector <=> ${vectorStr}::vector) AS score | ||
| FROM ${sql(table)} | ||
| ORDER BY vector <=> ${vectorStr}::vector | ||
| LIMIT ${topK} | ||
| `; | ||
| return results; | ||
| const { data, error } = await supabase.rpc(queryName, { | ||
| query_embedding: vector, | ||
| match_count: topK | ||
| }); | ||
| if (error) { | ||
| throw new Error(`Supabase search error: ${error.message}`); | ||
| } | ||
| return (data ?? []).map( | ||
| (row) => ({ | ||
| source: row.source, | ||
| content: row.content, | ||
| score: row.similarity | ||
| }) | ||
| ); | ||
| }, | ||
| async upsert(source, content, vector) { | ||
| const vectorStr = `[${vector.join(",")}]`; | ||
| await sql` | ||
| INSERT INTO ${sql(table)} (source, content, vector) | ||
| SELECT ${source}, ${content}, ${vectorStr}::vector | ||
| WHERE NOT EXISTS ( | ||
| SELECT 1 FROM ${sql(table)} | ||
| WHERE source = ${source} AND content = ${content} | ||
| ) | ||
| `; | ||
| const { error } = await supabase.from(table).upsert({ source, content, vector }, { onConflict: "source,content" }); | ||
| if (error) { | ||
| throw new Error(`Supabase upsert error: ${error.message}`); | ||
| } | ||
| }, | ||
| async clear() { | ||
| await sql`TRUNCATE TABLE ${sql(table)}`; | ||
| const { error } = await supabase.from(table).delete().gte("id", 0); | ||
| if (error) { | ||
| throw new Error(`Supabase clear error: ${error.message}`); | ||
| } | ||
| }, | ||
| async disconnect() { | ||
| await sql.end(); | ||
| } | ||
@@ -74,0 +70,0 @@ }; |
+3
-2
| import { VectorStorePlugin } from 'ragpipe'; | ||
| interface SupabaseVectorStoreOptions { | ||
| databaseUrl: string; | ||
| supabaseUrl: string; | ||
| supabaseKey: string; | ||
| tableName?: string; | ||
| dimensions?: number; | ||
| queryName?: string; | ||
| } | ||
@@ -8,0 +9,0 @@ declare function supabaseVectorStore(options: SupabaseVectorStoreOptions): VectorStorePlugin; |
+3
-2
| import { VectorStorePlugin } from 'ragpipe'; | ||
| interface SupabaseVectorStoreOptions { | ||
| databaseUrl: string; | ||
| supabaseUrl: string; | ||
| supabaseKey: string; | ||
| tableName?: string; | ||
| dimensions?: number; | ||
| queryName?: string; | ||
| } | ||
@@ -8,0 +9,0 @@ declare function supabaseVectorStore(options: SupabaseVectorStoreOptions): VectorStorePlugin; |
+28
-22
| // src/vector-store.ts | ||
| import postgres from "postgres"; | ||
| import { createClient } from "@supabase/supabase-js"; | ||
| function supabaseVectorStore(options) { | ||
| const table = options.tableName ?? "documents"; | ||
| const sql = postgres(options.databaseUrl); | ||
| const queryName = options.queryName ?? "match_documents"; | ||
| const supabase = createClient( | ||
| options.supabaseUrl, | ||
| options.supabaseKey | ||
| ); | ||
| return { | ||
| name: "supabase", | ||
| async search(vector, topK) { | ||
| const vectorStr = `[${vector.join(",")}]`; | ||
| const results = await sql` | ||
| SELECT source, content, | ||
| 1 - (vector <=> ${vectorStr}::vector) AS score | ||
| FROM ${sql(table)} | ||
| ORDER BY vector <=> ${vectorStr}::vector | ||
| LIMIT ${topK} | ||
| `; | ||
| return results; | ||
| const { data, error } = await supabase.rpc(queryName, { | ||
| query_embedding: vector, | ||
| match_count: topK | ||
| }); | ||
| if (error) { | ||
| throw new Error(`Supabase search error: ${error.message}`); | ||
| } | ||
| return (data ?? []).map( | ||
| (row) => ({ | ||
| source: row.source, | ||
| content: row.content, | ||
| score: row.similarity | ||
| }) | ||
| ); | ||
| }, | ||
| async upsert(source, content, vector) { | ||
| const vectorStr = `[${vector.join(",")}]`; | ||
| await sql` | ||
| INSERT INTO ${sql(table)} (source, content, vector) | ||
| SELECT ${source}, ${content}, ${vectorStr}::vector | ||
| WHERE NOT EXISTS ( | ||
| SELECT 1 FROM ${sql(table)} | ||
| WHERE source = ${source} AND content = ${content} | ||
| ) | ||
| `; | ||
| const { error } = await supabase.from(table).upsert({ source, content, vector }, { onConflict: "source,content" }); | ||
| if (error) { | ||
| throw new Error(`Supabase upsert error: ${error.message}`); | ||
| } | ||
| }, | ||
| async clear() { | ||
| await sql`TRUNCATE TABLE ${sql(table)}`; | ||
| const { error } = await supabase.from(table).delete().gte("id", 0); | ||
| if (error) { | ||
| throw new Error(`Supabase clear error: ${error.message}`); | ||
| } | ||
| }, | ||
| async disconnect() { | ||
| await sql.end(); | ||
| } | ||
@@ -36,0 +42,0 @@ }; |
+3
-3
| { | ||
| "name": "@ragpipe/plugin-supabase", | ||
| "version": "0.0.1", | ||
| "version": "0.1.0-alpha.1", | ||
| "description": "Supabase pgvector vector store plugin for ragpipe", | ||
@@ -33,3 +33,3 @@ "type": "module", | ||
| "types": "./dist/index.d.ts", | ||
| "files": ["dist", "README.md"], | ||
| "files": ["dist", "sql", "README.md"], | ||
| "scripts": { | ||
@@ -55,3 +55,3 @@ "build": "tsup", | ||
| "dependencies": { | ||
| "postgres": "^3.4.5" | ||
| "@supabase/supabase-js": "^2.49.4" | ||
| }, | ||
@@ -58,0 +58,0 @@ "devDependencies": { |
+44
-14
| # @ragpipe/plugin-supabase | ||
| Supabase pgvector vector store plugin for [ragpipe](https://github.com/yungblud/ragpipe). | ||
| Supabase vector store plugin for [ragpipe](https://github.com/yungblud/ragpipe), powered by [`@supabase/supabase-js`](https://github.com/supabase/supabase-js). | ||
@@ -20,5 +20,6 @@ ## Install | ||
| vectorStore: supabaseVectorStore({ | ||
| databaseUrl: process.env.DATABASE_URL ?? "", | ||
| tableName: "documents", // default | ||
| dimensions: 3072, | ||
| supabaseUrl: process.env.SUPABASE_URL ?? "", | ||
| supabaseKey: process.env.SUPABASE_SERVICE_ROLE_KEY ?? "", | ||
| tableName: "documents", // default | ||
| queryName: "match_documents", // default RPC function name | ||
| }), | ||
@@ -32,9 +33,10 @@ }); | ||
| Returns a `VectorStorePlugin` backed by Supabase PostgreSQL with pgvector. | ||
| Returns a `VectorStorePlugin` backed by Supabase using the official JS SDK. | ||
| | Option | Type | Default | Description | | ||
| |---|---|---|---| | ||
| | `databaseUrl` | `string` | — | PostgreSQL connection string (required) | | ||
| | `tableName` | `string` | `"documents"` | Table to store/query vectors | | ||
| | `dimensions` | `number` | — | Vector dimensions (for documentation; table must match) | | ||
| | `supabaseUrl` | `string` | — | Supabase project URL (required) | | ||
| | `supabaseKey` | `string` | — | Service role key (required) | | ||
| | `tableName` | `string` | `"documents"` | Table to store documents | | ||
| | `queryName` | `string` | `"match_documents"` | PostgreSQL function for vector search | | ||
@@ -45,14 +47,16 @@ ### Methods | ||
| |---|---| | ||
| | `search(vector, topK)` | Cosine similarity search, returns top-K results with scores | | ||
| | `upsert(source, content, vector)` | Insert a document if it doesn't already exist | | ||
| | `clear()` | Truncate the documents table | | ||
| | `disconnect()` | Close the PostgreSQL connection | | ||
| | `search(vector, topK)` | Calls `supabase.rpc()` for cosine similarity search | | ||
| | `upsert(source, content, vector)` | Inserts via `supabase.from().upsert()` with dedup on `source,content` | | ||
| | `clear()` | Deletes all rows from the documents table | | ||
| | `disconnect()` | No-op (Supabase JS client manages connections automatically) | | ||
| ## Database Setup | ||
| Enable pgvector and create the documents table in your Supabase SQL editor: | ||
| Run the following in your Supabase SQL editor: | ||
| ```sql | ||
| -- 1. Enable pgvector | ||
| CREATE EXTENSION IF NOT EXISTS vector; | ||
| -- 2. Create the documents table | ||
| CREATE TABLE documents ( | ||
@@ -62,5 +66,31 @@ id BIGSERIAL PRIMARY KEY, | ||
| content TEXT NOT NULL, | ||
| vector VECTOR(3072) | ||
| vector VECTOR(3072), | ||
| UNIQUE(source, content) | ||
| ); | ||
| -- 3. Create the similarity search function | ||
| CREATE OR REPLACE FUNCTION match_documents( | ||
| query_embedding VECTOR(3072), | ||
| match_count INT DEFAULT 5 | ||
| ) | ||
| RETURNS TABLE ( | ||
| source TEXT, | ||
| content TEXT, | ||
| similarity FLOAT | ||
| ) | ||
| LANGUAGE plpgsql | ||
| AS $$ | ||
| BEGIN | ||
| RETURN QUERY | ||
| SELECT | ||
| d.source, | ||
| d.content, | ||
| 1 - (d.vector <=> query_embedding) AS similarity | ||
| FROM documents d | ||
| ORDER BY d.vector <=> query_embedding | ||
| LIMIT match_count; | ||
| END; | ||
| $$; | ||
| -- 4. Create an index for faster search | ||
| CREATE INDEX ON documents | ||
@@ -67,0 +97,0 @@ USING ivfflat (vector vector_cosine_ops) |
9132
22.22%7
16.67%126
2.44%102
41.67%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed