OpenSearchStore

Read this page in the documentation

OpenSearchStore Reference for the OpenSearchStore, the ORM's NoSQL store for OpenSearch. Overview OpenSearch is a distributed search and analytics engine forked from Elasticsearch 7.10. Like Elasticsearch, it is built around an inverted index and a JSON-based Query DSL (match/term/bool/range queries, aggregations, sorting) rather than SQL: there are no JOINs, no transaction log, and documents are schema-flexible. Because it does not fit the SQL-shaped Dialect interface used by the SQL dialects in this repo, OpenSearchStore instead implements the minimal NoSqlStore marker interface (src/nosql/store.ts), which requires only connection lifecycle (connect/disconnect/isConnected), a getClient() escape hatch, and name/library identifiers. The store then exposes OpenSearch's real capabilities grouped by concern: connection lifecycle, index management, document CRUD (single and bulk), and search. Property | Value | --- | --- | name | 'opensearch' | library | '@opensearch-project/opensearch' | Interface implemented | NoSqlStore | Source | src/nosql/opensearch/index.ts | The store uses the official @opensearch-project/opensearch driver. Although the package is present as a project dependency, the driver is still loaded lazily via require() — importing this module does not force the dependency to be resolved unless an OpenSearchStore is actually constructed and connected. For unit testing without a live cluster, the constructor also accepts an already-built client, which connect() uses as-is instead of instantiating a new one. Response envelope. The OpenSearch client wraps every response in an envelope of the shape { body, statusCode, headers, meta }, placing the actual payload under .body. Every wrapper method in this store reads through response.body — e.g. get() returns body.source, search() returns body.hits, and indexExists() returns body. Connection Options node (or nodes), auth, and ssl are passed straight through to the driver's new Client(...). When client is present, node/auth/ssl are ignored and the injected client is used directly. Real connection On connect(), if no client was injected, the store lazily require()s the driver's Client and constructs it from { node, nodes, auth, ssl }. The OpenSearch driver connects lazily on first request, so the store calls client.ping() to surface connection failures (bad host, refused connection, TLS errors) immediately rather than on the caller's first real operation. A failed ping closes the client and throws a ConnectionError (an ECONNREFUSED error is normalized to a 'Connection refused' message). Injected client (testing / full driver control) Methods Connection lifecycle connect(): Promise<void> Builds (or reuses the injected) client and pings it. Idempotent: if already connected with a live client, it returns immediately without re-pinging. On ping failure it clears state and throws ConnectionError. disconnect(): Promise<void> Closes the underlying client (client.close()) if present and marks the store disconnected. isConnected(): boolean Returns true only when connected and a client is present. getClient(): any Returns the underlying @opensearch-project/opensearch client for anything not wrapped here. Throws ConnectionError('Not connected to OpenSearch') if called before a successful connect(). Index management createIndex(index: string, body?: Record<string, unknown>): Promise<void> Creates an index, optionally with a body carrying mappings/settings/aliases. Calls client.indices.create({ index, body }). When no body is supplied, body is passed as undefined. deleteIndex(index: string): Promise<void> Deletes an index via client.indices.delete({ index }). indexExists(index: string): Promise<boolean> Calls client.indices.exists({ index }) and returns the boolean response.body, defaulting to false when the body is absent. Document CRUD indexDocument(index: string, id: string, body: Record<string, unknown>): Promise<Record<string, unknown>> Indexes (creates or overwrites) a document under an explicit id via client.index({ index, id, body }). Returns the response body (e.g. { index, id, result, version }). get<T = Record<string, unknown>>(index: string, id: string): Promise<T | undefined> Fetches a document via client.get({ index, id }) and returns response.body.source. Returns undefined when there is no source (e.g. a not-found response). deleteDocument(index: string, id: string): Promise<Record<string, unknown>> Deletes a document by ID via client.delete({ index, id }) and returns the response body. Search search<T = Record<string, unknown>>(index: string, query: Record<string, unknown>): Promise<T> Runs a Query-DSL search via client.search({ index, body: query }) and returns the hits object from the response (response.body.hits) — which itself contains total, maxscore, and the hits array. The query argument is passed straight through as the request body, so it should be a full request body such as { query: { match: { ... } } }. Bulk operations bulk(operations: BulkOperation[]): Promise<BulkResult> Runs a batch of operations in a single bulk request. operations must already be in the driver's NDJSON action/source array form (action line followed by source line, flattened). Sends them via client.bulk({ body: operations }) and normalizes the response into: errors reflects per-item failures reported by the server (e.g. a versionconflict); a BulkResult with errors: true is a normal return value, not a thrown error. items and took default to [] and 0 when absent from the body. Empty-bulk short-circuit. The real bulk API rejects an empty request body outright, so a zero-length operations array is short-circuited to a trivial no-op result — { errors: false, items: [], took: 0 } — without calling the client. Errors All wrapped operations funnel through an internal executor that rethrows driver failures as DatabaseError (with the HTTP status code appended when available). Connection-time failures throw ConnectionError. Example Bulk indexing follows the driver's NDJSON action/source shape: Verification status Unit / mock-verified. The test suite (tests/nosql/opensearch.test.ts) is pure unit tests: a hand-built mock client with Jest-spy methods is injected via the constructor's client option, and mock responses are shaped exactly like the real driver's (every payload wrapped in an envelope under .body). No live OpenSearch cluster is contacted in the tests. Covered by these tests: Connection lifecycle — name/library identifiers, connect() calls ping(), idempotent re-connect, getClient() exposes the raw client, clean disconnect()/close(), isConnected() before/after connect, getClient() throwing ConnectionError before connect, ping failures wrapped in ConnectionError, and ECONNREFUSED normalized to 'Connection refused'. Index management — createIndex with and without a body, deleteIndex, indexExists returning the boolean body (true and false), and errors wrapped as DatabaseError. Document CRUD — indexDocument returning the body, get returning source (and undefined when absent), deleteDocument returning the body, and errors wrapped as DatabaseError. Search — query passed through as the request body and hits returned; errors wrapped as DatabaseError. Bulk — operations sent as the request body with normalized results, the empty-array short-circuit (client not called), per-item server errors surfaced via errors: true, and transport errors wrapped as DatabaseError. Behavior against a real OpenSearch cluster (actual driver wire format, TLS handshakes, real bulk semantics) has not been exercised in this repo's automated tests. Related reading All data stores — the full catalogue, grouped by purpose Database types — where this sits among the 22 categories