Skip to content
6 min read · 1,283 words

The Adapter Matrix

29 adapters, one contract. Every one of them passes the same 7-test conformance suite, so the builder behaves identically across all of them — the entries below differ only in what their backend physically can and can't do (an edition requirement, a dimension floor, a metric it lacks, how lazily it becomes consistent). Each adapter lives at @nhtio/adk/batteries/vector/<name> and is reached by deep import, which is what loads its optional-peer driver. Two — in_memory and orama — are also re-exported from the core barrel because they run in the browser.

How to read the caveats

A caveat is a fact about the backend, surfaced once so it doesn't ambush you in production. None of them change the query you write — vs('docs').nearText(…).select('id').limit(10) is the same line against all 29. They change what the backend requires to run, or what it can't promise. Where a backend is eventually consistent, see Consistency & Capabilities; where it has a metric or dimension limit, it's noted inline.

In-process & browser

No server. Embedded libraries, in-memory indexes, single-file stores — the cheapest path to a working store, and the only adapters that run in a browser tab.

AdapterDriverNotes
in_memorynoneThe reference adapter. Hand-rolled cosine, cross-env, zero deps. The semantics every other adapter is tested against. Use for tests, prototypes, and the browser.
orama@orama/oramaCross-env, browser-capable. Real vector + hybrid store; backs in-browser retrieval (see Ask ADK). Re-exported from the core barrel.
hnswlibhnswlib-nodeEmbedded ANN (HNSW), in-process, native addon. Fast approximate search with no server.
lancedb@lancedb/lancedb + apache-arrowEmbedded columnar vector store; persists to disk, no server.
sqlite_vecbetter-sqlite3 + sqlite-vecSingle-file or :memory:. ACID — transactions: true. The cheapest real end-to-end backend.
duckdb@duckdb/node-apiIn-process via the vss extension; :memory: or a file.

SQL-extension

A real database you may already run, with a vector type or extension bolted on. Metadata is real columns; some offer true transactions.

AdapterDriverNotes
pgvectorpgPostgres + the vector extension. ACID — transactions: true. Parameterized SQL filters, real columns, the works.
mariadbmariadbNative VECTOR(N) columns (MariaDB 11.7+); vectors via VEC_FromText/VEC_ToText.
oracle23aioracledb (thin mode, no Instant Client)Native VECTOR(dims, FLOAT32); KNN via VECTOR_DISTANCE … FETCH APPROX FIRST k. Caveat: VECTOR columns are rejected in the SYSTEM tablespace — the connecting user must default to a normal tablespace (e.g. USERS) with CREATE TABLE.
clickhouse@clickhouse/clientVector search over a MergeTree table; upsert is delete-then-insert (ClickHouse allows duplicate keys).

Self-hosted server

A dedicated vector engine (or a search/graph/document engine with vector search) you run yourself.

AdapterDriverNotes
qdrant@qdrant/js-client-restNamed vectors, native filter translation.
weaviateweaviate-clientNamed vectors; can embed server-side (builtInEncoding).
milvus@zilliz/milvus2-sdk-nodeBoolean-expression filters.
chromachromadbNative where filtering.
redisredisRediSearch vector index. Covers Valkey — point it at a Valkey instance and it works unchanged.
elasticsearch@elastic/elasticsearchES 8 dialectdense_vector + a top-level knn clause. Use the v8 client against an 8.x server (a v9 client sends a compat header 8.x rejects). Distinct adapter from opensearch.
opensearch@opensearch-project/opensearchOpenSearch's knn_vector + query.knn dialect. A separate adapter because an ES client can't drive it and vice-versa.
solrnone (HTTP/JSON)Dense-vector / kNN query parser (Solr 9+). The collection is a Solr core that must be precreated.
typesensetypesenseNative vector search.
meilisearchmeilisearchNeeds the vector-store experimental feature + a userProvided embedder.
mongodbmongodbAtlas Vector Search ($vectorSearch). The vector index is async; the document store is strongly consistent.
surrealdbsurrealdbMulti-model; vector::similarity::cosine / vector::distance::euclidean.
neo4jneo4j-driverNative vector index (5.13+); db.index.vector.queryNodes.
arangodbarangojsBrute-force AQL COSINE_SIMILARITY / L2_DISTANCE.
couchbasecouchbaseEnterprise Edition only for vector search — Community rejects vector fields. Scope.collection = logical collection; scoped FTS vector index for KNN.
vespanone (HTTP/JSON)A collection is a document type in a deployed application package — the adapter rebuilds + redeploys the package (dependency-free store-zip) to the config server on create/drop.

Couchbase is Enterprise-only — and that's fine

Vector search in Couchbase requires the Enterprise edition; Community throws on vector fields. The adapter ships anyway. The kit provides batteries and does not enforce a backend's licensing — whether Couchbase EE fits your deployment is your call, not something the battery gates on.

Managed / serverless

Someone else runs the infrastructure. No local container; you supply credentials. These are eventually consistent (see Consistency & Capabilities) and are omitted from the CI matrix — their suites run only when their credentials are set.

AdapterDriverNotes
pinecone@pinecone-database/pineconeNeeds an existing index + API key. Eventually consistent (configurable consistency); a logical collection maps to a namespace. Can embed server-side.
s3vectors@aws-sdk/client-s3vectorsAWS S3 Vectors. Bucket provisioned out-of-band; collection = an index in it. cosine/euclidean only (no dot), topK capped at 100, index names 3–63 chars.
cloudflarenone (Vectorize V2 REST, pure fetch)Cloudflare Vectorize. Dimensions 32–1536, topK capped at 50 when returning values/metadata, aggressively eventually consistent. No driver dependency.

Running an adapter's integration suite

Every adapter's functional suite is gated on an environment variable — set it to run that suite against a live backend, leave it blank and the suite skips green, not red. The zero-infra ones run out of the box; the rest you point at a container or a service.

bash
# zero infra — run as-is
TEST_VECTOR_ORAMA_URL=1            # orama, in-process
TEST_VECTOR_SQLITE_VEC_URL=:memory: # sqlite-vec, no server
TEST_VECTOR_DUCKDB_URL=:memory:     # duckdb, no server
TEST_VECTOR_HNSWLIB_URL=1           # hnswlib, embedded
TEST_VECTOR_LANCEDB_URL=1           # lancedb, embedded (temp dir per test)

# a local container (see docker-compose.vector.yml profiles)
TEST_VECTOR_PGVECTOR_URL=postgres://vector:vector@localhost:5432/vector
TEST_VECTOR_QDRANT_URL=http://localhost:6333
TEST_VECTOR_ELASTICSEARCH_URL=http://localhost:9200
# … one TEST_VECTOR_<NAME>_URL per backend

# a managed service — credentials, no container
TEST_VECTOR_PINECONE_API_KEY=  TEST_VECTOR_PINECONE_INDEX=
TEST_VECTOR_S3VECTORS_BUCKET=  TEST_VECTOR_S3VECTORS_REGION=
TEST_VECTOR_CLOUDFLARE_ACCOUNT_ID=  TEST_VECTOR_CLOUDFLARE_API_KEY=

The full list of variables (with the exact connection-string shapes and per-backend extras like _USER/_PASS/_TOKEN) lives in .env.test.example, and the container definitions are in docker-compose.vector.yml — one Compose profile per backend. The shipped adapters are each verified at 7/7 conformance, run deterministically, against a live instance.

What didn't ship

Three backends were attempted and backed out. They are not adapters you can import; they're recorded here so the gaps are intentional and documented, not mysterious:

BackendWhy it's not here
KùzuThe kuzu npm package is flagged "no longer supported"; the only alternative is stale and WASM-only. No viable non-deprecated Node distribution.
MemgraphBackend defect: vector_search.search throws after a DETACH DELETE of an indexed node (a dangling index tombstone that drop+recreate doesn't prune), so the conformance suite's delete-then-read can't pass. Revisit when the index-deletion pruning is fixed.
VearchThe published arm64 image is non-functional — its JSON serialization falls back to a broken path and every API call returns a 500. An image/architecture defect, not an adapter problem.

Where to go next