Skip to content
7 min read · 1,356 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=  TEST_VECTOR_CLOUDFLARE_ENABLED=1

The Cloudflare Vectorize suite needs the extra TEST_VECTOR_CLOUDFLARE_ENABLED=1 opt-in on top of its credentials. Vectorize's public endpoint is aggressively eventually-consistent — its query index flaps for seconds after a write or delete — so even with the conformance harness's retries the read-after-write race loses often enough to red-flag an otherwise-green run. It is therefore excluded from CI and skips unless you deliberately switch it on to exercise the adapter against live Vectorize.

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