From 7875d24d48f17eb8d4f9bb386b236730ff407708 Mon Sep 17 00:00:00 2001 From: Julien Calixte Date: Sun, 7 Jun 2026 12:04:43 +0200 Subject: [PATCH] refactor(search): use typed OpenSearch SDK request body Pull in @opensearch-project/opensearch only for its Search_RequestBody type so the inline search body is type-checked. Also drops the unsupported highlight.content.boundary_scanner_locale option. --- deno.json | 1 + deno.lock | 36 ++++++++++++++++++++++++++++++++++++ src/search/opensearch.ts | 12 +++++------- 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/deno.json b/deno.json index 56a2a04..e77c1e0 100644 --- a/deno.json +++ b/deno.json @@ -18,6 +18,7 @@ "imports": { "@db/sqlite": "jsr:@db/sqlite@^0.13.0", "@oak/oak": "jsr:@oak/oak@^17.2.0", + "@opensearch-project/opensearch": "npm:@opensearch-project/opensearch@^3.6.0", "@skyware/jetstream": "npm:@skyware/jetstream@^0.2.5", "@std/assert": "jsr:@std/assert@1" } diff --git a/deno.lock b/deno.lock index 5be5f47..fb60f9b 100644 --- a/deno.lock +++ b/deno.lock @@ -20,6 +20,8 @@ "jsr:@std/path@1": "1.1.4", "jsr:@std/path@1.0": "1.0.9", "jsr:@std/path@^1.1.1": "1.1.4", + "npm:@opensearch-project/opensearch@3": "3.6.0", + "npm:@opensearch-project/opensearch@^3.6.0": "3.6.0", "npm:@skyware/jetstream@~0.2.5": "0.2.5", "npm:path-to-regexp@^6.3.0": "6.3.0" }, @@ -142,6 +144,17 @@ "unicode-segmenter" ] }, + "@opensearch-project/opensearch@3.6.0": { + "integrity": "sha512-ow1A/Z7MBlNL/JZzTY4+M+8psiVh66g0Z7EQSl/qbT0U6zLBsVDYh6nvm8D4e4tIzQtn6ODbA2OsqYGvqZj76g==", + "dependencies": [ + "aws4", + "debug", + "hpagent", + "json11", + "ms", + "secure-json-parse" + ] + }, "@skyware/jetstream@0.2.5": { "integrity": "sha512-fM/zs03DLwqRyzZZJFWN20e76KrdqIp97Tlm8Cek+vxn96+tu5d/fx79V6H85L0QN6HvGiX2l9A8hWFqHvYlOA==", "dependencies": [ @@ -155,12 +168,31 @@ "@standard-schema/spec@1.1.0": { "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==" }, + "aws4@1.13.2": { + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==" + }, + "debug@4.4.3": { + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dependencies": [ + "ms" + ] + }, "esm-env@1.2.2": { "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==" }, "event-target-polyfill@0.0.4": { "integrity": "sha512-Gs6RLjzlLRdT8X9ZipJdIZI/Y6/HhRLyq9RdDlCsnpxr/+Nn6bU2EFGuC94GjxqhM+Nmij2Vcq98yoHrU8uNFQ==" }, + "hpagent@1.2.0": { + "integrity": "sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==" + }, + "json11@2.0.2": { + "integrity": "sha512-HIrd50UPYmP6sqLuLbFVm75g16o0oZrVfxrsY0EEys22klz8mRoWlX9KAEDOSOR9Q34rcxsyC8oDveGrCz5uLQ==", + "bin": true + }, + "ms@2.1.3": { + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "partysocket@1.1.12": { "integrity": "sha512-079YDW1QZsFwJ8syLmr9xFMbE+VwbK4SpcKSOPVApX8rpIMennkxx0MeWl4oWutP/Zjgy8TMnQZ3FOXTQvu3DA==", "dependencies": [ @@ -170,6 +202,9 @@ "path-to-regexp@6.3.0": { "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==" }, + "secure-json-parse@2.7.0": { + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" + }, "tiny-emitter@2.1.0": { "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" }, @@ -196,6 +231,7 @@ "jsr:@db/sqlite@0.13", "jsr:@oak/oak@^17.2.0", "jsr:@std/assert@1", + "npm:@opensearch-project/opensearch@^3.6.0", "npm:@skyware/jetstream@~0.2.5" ] } diff --git a/src/search/opensearch.ts b/src/search/opensearch.ts index 5885fb7..cb0ffe2 100644 --- a/src/search/opensearch.ts +++ b/src/search/opensearch.ts @@ -1,3 +1,4 @@ +import type { API } from "@opensearch-project/opensearch"; import { log } from "../log.ts"; const OPENSEARCH_URL = Deno.env.get("OPENSEARCH_URL"); @@ -185,7 +186,9 @@ export const searchNotes = async ( filters.push({ term: { did: opts.did } }); } - const body: Record = { + const searchAfter = opts.cursor ? decodeCursor(opts.cursor) : undefined; + + const body: API.Search_RequestBody = { size: limit, query: { bool: { @@ -207,20 +210,15 @@ export const searchNotes = async ( fragment_size: 200, number_of_fragments: 1, boundary_scanner: "sentence", - boundary_scanner_locale: "en-US", }, title: { number_of_fragments: 0 }, }, }, sort: [{ _score: "desc" }, { rkey: "desc" }], _source: ["did", "rkey", "title", "content", "publishedAt"], + ...(searchAfter ? { search_after: searchAfter } : {}), }; - if (opts.cursor) { - const decoded = decodeCursor(opts.cursor); - if (decoded) body.search_after = decoded; - } - const res = await request("POST", `/${OPENSEARCH_INDEX}/_search`, body); if (!res.ok) { const text = await res.text();