@samveys/opensearch

Version
1.0.2Nov 5, 2026
Install
pnpm add @samveys/opensearch

From packages/opensearch/README.md in the framework repository.

An OpenSearch driver for @samveys/search. It talks to OpenSearch over its REST API with the global fetch, so it has no runtime dependencies. It passes the @samveys/search conformance suite against a real OpenSearch container.

Use it

ts
import { opensearch } from '@samveys/opensearch'
import type { SearchIndexDefinition } from '@samveys/search/contracts'

const driver = opensearch({ node: 'http://127.0.0.1:9200' })

const reports: SearchIndexDefinition = {
  name: 'reports',
  fields: { title: { weight: 3 }, body: { weight: 1 } },
  filters: ['region'],
}

export async function demo(): Promise<readonly string[]> {
  await driver.ensureIndex(reports)
  await driver.upsert(reports, [{ id: '1', fields: { title: 'October revenue report', body: 'numbers' }, filters: { region: 'bali' } }])
  const { ids } = await driver.search(reports, { text: 'october revenue', filters: { region: 'bali' }, limit: 10, offset: 0 })
  return ids
}

With basic auth:

ts
opensearch({ node: 'https://search.internal:9200', auth: { username: 'svc', password: process.env['OPENSEARCH_PASSWORD'] ?? '' } })
OptionMeaning
nodeURL of an OpenSearch node
authHTTP basic credentials. Never put in an error message
requestTimeoutMsPer-request timeout. Default 30 000

Engine failures throw SamveysError SVS_E107.

How it behaves

  • Ranking. multi_match over fields.<name>^<weight> with operator: and, so every word must match and the title outranks the body. Scores are BM25. The index has one primary shard so scores are not skewed by per-shard term statistics; with several shards, ranking of near-equal documents can vary.
  • Plain words. User text goes in the query of a multi_match, which does not parse operators, so quotes, -, * and : are plain text.
  • Consistency. OpenSearch is near-real-time. Writes use refresh=wait_for and flush refreshes, so a document is searchable as soon as the call returns. This makes writes slower than with a background refresh.
  • Totals. track_total_hits is on, so the total is exact.
  • Filters. Exact term matches on keyword fields. The mapping is strict, so only declared fields and filters can be indexed.
  • Index settings. Created with number_of_shards: 1 and number_of_replicas: 0. Change replicas on the cluster if you need them.
  • flush. Runs _delete_by_query on every document and keeps the index and its mapping.

Run the conformance test

sh
SVS_REQUIRE_DOCKER=1 node --no-warnings --test packages/opensearch/test/conformance.test.ts

The test starts opensearchproject/opensearch:2.17.1 (security plugin off, 512 MB heap) as samveys-conformance-opensearch-<pid> and removes it afterwards. Allow a few minutes for the first run. Without Docker it skips; SVS_DOCKER=0 forces a skip, and SVS_REQUIRE_DOCKER=1 makes a missing Docker a failure.