@juspay/neurolink
Version:
The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002
96 lines (95 loc) • 4.6 kB
TypeScript
/**
* pgvector Vector Store Adapter
*
* Client-injection `VectorStore` implementation backed by Postgres +
* the `pgvector` extension. Callers construct and own the Postgres client
* (e.g. a `pg.Pool`, or an in-process `@electric-sql/pglite` instance) and
* pass it in — this module adds zero vendor SDKs as runtime dependencies.
*
* Storage model: one table per `indexName`, created lazily on first
* `upsert()` (`CREATE TABLE IF NOT EXISTS`, so it is safe to call
* repeatedly). Schema:
* id text PRIMARY KEY
* embedding vector(n) -- n fixed to the dimension of the first upsert
* metadata jsonb NOT NULL DEFAULT '{}'::jsonb
*
* Scoring: matches `InMemoryVectorStore`'s cosine-similarity convention
* (higher is better, 1.0 = identical direction) by converting pgvector's
* `<=>` cosine *distance* operator (0 = identical) via `score = 1 - distance`.
*
* `MetadataFilter` -> SQL: every value that flows into a query is a bound
* parameter (`$1`, `$2`, ...), including metadata field *names* — nothing
* from the filter is ever string-concatenated into SQL text. The one thing
* that IS textually embedded is the derived table name, and only after it
* passes a strict identifier allow-list (see `IDENTIFIER_RE`), because
* Postgres has no way to bind an identifier as a query parameter.
*
* Operators not expressible without ambiguity are rejected loudly instead of
* silently no-op'ing:
* - `$nor` (top-level logical operator) and `$size` (field-level operator)
* are declared on `MetadataFilter` but (per the reference
* `InMemoryVectorStore.matchesFilter`) are not actually implemented
* there either — rather than guess at semantics that would silently
* diverge from any caller's expectations, this adapter throws.
* - Any other unrecognized `$foo` key, top-level or field-level, throws
* for the same reason.
*
* Known, documented behavioral differences from `InMemoryVectorStore`:
* - `$eq` / bare-value equality compares via Postgres jsonb structural
* equality, whereas the in-memory reference uses JS `!==` (so it can
* never match on array/object metadata values — two distinct array
* instances are never `===`). This adapter is strictly more capable
* for those value types.
* - `$regex` runs Postgres POSIX regex (`~`) rather than a JS `RegExp`.
* For non-string or missing fields the in-memory store tests the
* pattern against `""` (so e.g. `^$` or `.*` can still match); this
* adapter instead requires the field to be a JSON string, so those
* edge-case patterns will not match a missing/non-string field here.
*/
import type { MetadataFilter, PgClientLike, PgVectorStoreOptions, VectorQueryResult, VectorStore } from "../../types/index.js";
export declare class PgVectorStore implements VectorStore {
private readonly client;
private readonly tablePrefix;
/** Tables we've already run `CREATE TABLE IF NOT EXISTS` for, this process. */
private readonly ensuredTables;
constructor(client: PgClientLike, options?: PgVectorStoreOptions);
/**
* Derive and validate the backing table name for an index. Throws on
* anything that isn't a safe bare SQL identifier — this is the injection
* guard for `indexName`.
*/
private tableName;
/**
* Ensure the `vector` extension and the index's table exist. Safe to call
* repeatedly (`IF NOT EXISTS` throughout). `dimension` is fixed by the
* first upsert into a fresh index; it is a validated positive integer
* embedded directly in DDL (Postgres cannot bind type modifiers either).
*/
private ensureTable;
/**
* Upsert vectors into an index, creating its table on first use.
* Mirrors `InMemoryVectorStore.upsert` (metadata defaults to `{}`).
*/
upsert(indexName: string, items: Array<{
id: string;
vector: number[];
metadata?: Record<string, unknown>;
}>): Promise<void>;
/**
* Query by cosine similarity. Returns `[]` if the index's table doesn't
* exist yet (mirrors `InMemoryVectorStore.query` on an unknown index)
* rather than throwing.
*/
query(params: {
indexName: string;
queryVector: number[];
topK?: number;
filter?: MetadataFilter;
includeVectors?: boolean;
}): Promise<VectorQueryResult[]>;
/**
* Delete vectors by id. No-op if the index's table doesn't exist yet
* (mirrors `InMemoryVectorStore.delete`).
*/
delete(indexName: string, ids: string[]): Promise<void>;
}