hermes-client-typescript 1.8.144 → 1.8.146

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +59 -225
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -22,6 +22,7 @@ try {
22
22
  "articles",
23
23
  `
24
24
  index articles {
25
+ field id: text<raw> [primary, stored]
25
26
  field title: text<simple> [indexed, stored]
26
27
  field body: text<simple> [indexed, stored]
27
28
  }
@@ -31,8 +32,8 @@ try {
31
32
  const [indexedCount, errorCount, errors] = await client.indexDocuments(
32
33
  "articles",
33
34
  [
34
- { title: "Hello", body: "First article" },
35
- { title: "Hermes", body: "Fast search" },
35
+ { id: "1", title: "Hello", body: "First article" },
36
+ { id: "2", title: "Hermes", body: "Fast search" },
36
37
  ],
37
38
  );
38
39
  if (errorCount) throw new Error(JSON.stringify(errors));
@@ -61,148 +62,94 @@ try {
61
62
  }
62
63
  ```
63
64
 
64
- Call `connect()` before the first RPC and `close()` when the client is no
65
- longer needed.
65
+ Call `connect()` before the first RPC and `close()` when finished.
66
66
 
67
67
  ## Index management
68
68
 
69
69
  ```typescript
70
- await client.createIndex("articles", schema);
71
- const names = await client.listIndexes();
70
+ await client.listIndexes();
72
71
  const info = await client.getIndexInfo("articles");
73
-
74
- await client.forceMerge("articles");
75
72
  await client.reorder("articles");
76
73
  await client.retrainVectorIndex("articles");
77
- await client.deleteIndex("articles");
78
74
  ```
79
75
 
80
- Newly indexed documents become searchable after `commit()`.
76
+ `indexDocuments` returns `[indexedCount, errorCount, errors]`;
77
+ `indexDocumentsStream` takes an async iterable. Inspect errors, then `commit()`
78
+ to publish accepted work. Arrays hold repeated values; flat numeric arrays are
79
+ dense vectors. One sparse vector uses `[[[1, 0.5], [8, 0.25]]]`: the outer array
80
+ is required because `[[1, 0.5], [8, 0.25]]` means two dense vectors.
81
+
82
+ ### Delete and upsert documents
81
83
 
82
- ### Batch and streaming indexing
84
+ With a text primary key, delete by exact key or supply a complete replacement:
83
85
 
84
86
  ```typescript
85
- const [indexed, errorCount, errors] = await client.indexDocuments("articles", [
86
- { title: "One", tags: ["search", "typescript"] },
87
- { title: "Two", tags: ["grpc"] },
88
- ]);
89
-
90
- async function* documents() {
91
- for (let number = 0; number < 10_000; number += 1) {
92
- yield { title: `Document ${number}` };
93
- }
94
- }
87
+ await client.deleteDocument("articles", "2");
88
+ await client.upsertDocument("articles", { id: "1", title: "Updated title" });
89
+ await client.commit("articles");
90
+ ```
91
+
92
+ `deleteDocuments` / `upsertDocuments` return `DocumentMutationResult` with
93
+ `acceptedCount` and `errors: [{index, error}]`. Single-item helpers throw on
94
+ rejection. Missing deletes succeed; upserts insert missing keys. Staged rows can
95
+ be replaced/deleted again before commit; the latest accepted version wins.
96
+ Optional [content hashes](../docs/content-deduplication.md) skip unchanged writes.
97
+
98
+ Limits: 100,000 deletion keys / 8 MiB key bytes; 1,000 replacements / 32 MiB
99
+ encoded protobuf, or one replacement / 200 MiB including the request envelope.
100
+ Broker commits are atomic per partition. See [mutation semantics](../docs/row-deletion.md).
101
+
102
+ ### Compact deleted rows
95
103
 
96
- const streamed = await client.indexDocumentsStream("articles", documents());
104
+ ```typescript
105
+ await client.forceMerge("articles"); // Retain tombstones.
106
+ await client.forceMerge("articles", undefined, true); // Physically remove deleted rows.
97
107
  ```
98
108
 
99
- Repeated arrays become repeated field entries. Flat numeric arrays are dense
100
- vectors. Sparse vectors use arrays of `[dimension, weight]` pairs inside an
101
- outer repeated-value array—for example, `[[[1, 0.5], [8, 0.25]]]` for one
102
- sparse vector. The outer array is required because `[[1, 0.5], [8, 0.25]]` is
103
- the legacy shape for two dense vectors.
109
+ The second argument remains the timeout in milliseconds. Compaction handles
110
+ singletons and may change addresses and BM25 scores. Index info exposes
111
+ `numDocs`, `physicalNumDocs`, `numDeletedDocs`, and `deletedRatio`.
104
112
 
105
113
  ## Searching
106
114
 
107
- `search()` accepts a `SearchRequest`. Its `query` is a discriminated union, so
108
- exactly one query variant is selected:
115
+ `search(indexName, request)` accepts a typed `SearchRequest`. Its `query` selects
116
+ one variant: `term`, `match`, `phrase`, `boolean`, `sparseVector`, `denseVector`,
117
+ `binaryDenseVector`, `boost`, `range`, `prefix`, `all`, or `fusion`.
118
+ See [client types](src/types.ts) and the [protocol](../hermes-proto/hermes.proto).
109
119
 
110
120
  ```typescript
111
- // Exact term
112
- await client.search("articles", {
113
- query: { term: { field: "title", term: "hermes" } },
114
- });
115
-
116
- // Recursive Boolean query
117
- await client.search("articles", {
121
+ const results = await client.search("articles", {
118
122
  query: {
119
123
  boolean: {
120
- must: [{ match: { field: "body", text: "fast search" } }],
124
+ must: [{ match: { field: "title", text: "search" } }],
121
125
  mustNot: [{ term: { field: "title", term: "draft" } }],
122
126
  },
123
127
  },
124
- });
125
-
126
- // Dense retrieval with reranking
127
- await client.search("articles", {
128
- query: {
129
- denseVector: {
130
- field: "embedding",
131
- vector: [0.1, 0.2, 0.3],
132
- nprobe: 16,
133
- },
134
- },
135
- reranker: {
136
- field: "embedding",
137
- vector: [0.1, 0.2, 0.3],
138
- },
139
- candidateLimit: 20,
140
- limit: 10,
141
128
  fieldsToLoad: ["title"],
142
129
  });
143
-
144
- // Hybrid union fusion
145
- await client.search("articles", {
146
- query: {
147
- fusion: {
148
- method: "rrf",
149
- rrfK: 60,
150
- queries: [
151
- {
152
- query: {
153
- sparseVector: {
154
- field: "sparseEmbedding",
155
- indices: [1, 5],
156
- values: [0.8, 0.2],
157
- },
158
- },
159
- },
160
- {
161
- query: {
162
- denseVector: {
163
- field: "embedding",
164
- vector: [0.1, 0.2, 0.3],
165
- },
166
- },
167
- },
168
- ],
169
- },
170
- },
171
- });
172
130
  ```
173
131
 
174
- Supported variants are `term`, `match`, `phrase`, `boolean`, `sparseVector`,
175
- `denseVector`, `binaryDenseVector`, `boost`, `range`, `prefix`, `all`, and
176
- `fusion`.
132
+ `getDocument(indexName, hit.address)` uses the full segment/document address
133
+ and returns `null` on `NOT_FOUND`. Use primary keys for durable identity.
177
134
 
178
- `getDocument()` takes the full address returned by a search hit:
135
+ ## Ranking diagnostics and recall traces
179
136
 
180
- ```typescript
181
- const document = await client.getDocument("articles", hit.address);
182
- ```
137
+ Search options `includeRrfScores: true` and `tracing: true` default to false.
138
+ RRF diagnostics describe organic branch nominations; traces retain bounded
139
+ candidates and query trees, including hits outside the final page. Neither
140
+ changes retrieval depth or ranking. Oversized exports fail explicitly.
183
141
 
184
- It returns `null` when the server responds with gRPC `NOT_FOUND`.
142
+ For named branches, use `l1: { formula: "0.2 * title + 0.8 * body + 3 * rrf" }`.
143
+ The formula is the only L1 scoring interface; old coefficient fields are removed.
144
+ See [candidate scoring](../docs/candidate-rescoring.md) for backfill, passage
145
+ selection, expression limits, capability versions, and distributed behavior.
185
146
 
186
147
  ## Deadlines
187
148
 
188
- Every RPC accepts an optional trailing deadline in milliseconds. A per-call
189
- value overrides the client default:
190
-
191
- ```typescript
192
- const client = new HermesClient("localhost:50051", {
193
- defaultTimeoutMs: 5_000,
194
- });
195
-
196
- client.connect();
197
- try {
198
- await client.search("articles", { query: { all: {} } }, 500);
199
- await client.forceMerge("articles", 3_600_000);
200
- } finally {
201
- client.close();
202
- }
203
- ```
204
-
205
- Expired calls reject with a gRPC `DEADLINE_EXCEEDED` error.
149
+ RPCs accept a trailing timeout in milliseconds, overriding the constructor's
150
+ `defaultTimeoutMs`. Expired calls reject with gRPC `DEADLINE_EXCEEDED`.
151
+ An expired mutation may already be staged, and an accepted commit continues
152
+ after disconnection. Resolve the outcome before retrying replacements.
206
153
 
207
154
  ## Development
208
155
 
@@ -211,119 +158,6 @@ pnpm install --frozen-lockfile
211
158
  pnpm check
212
159
  ```
213
160
 
214
- `pnpm check` compiles the strict TypeScript sources and runs the pure converter
215
- unit tests. After changing `hermes-proto/hermes.proto`, regenerate and check in
216
- the generated source:
217
-
218
- ```bash
219
- pnpm generate
220
- pnpm check
221
- ```
222
-
223
- ## License
224
-
225
- MIT
226
-
227
- ## Ranking diagnostics and recall traces
228
-
229
- Both options default to false and preserve the requested ranking:
230
-
231
- ```typescript
232
- const response = await client.search("articles", {
233
- query: {
234
- fusion: {
235
- queries: [
236
- { name: "title", query: { match: { field: "title", text: "rust" } } },
237
- { name: "body", query: { match: { field: "body", text: "rust" } } },
238
- ],
239
- },
240
- },
241
- includeRrfScores: true,
242
- tracing: true,
243
- });
244
- for (const hit of response.hits) {
245
- console.log(hit.score, hit.rrfScore, hit.rrfContributions);
246
- }
247
- for (const shard of response.trace?.shards ?? []) {
248
- for (const branch of shard.queries) {
249
- console.log(shard.shardId, branch.queryName, branch.candidates);
250
- }
251
- }
252
- ```
253
-
254
- RRF diagnostics use organic nomination ranks merged across all shards, separate
255
- from L1/reranker scores. Each vote carries the branch index/name, one-based rank,
256
- weighted contribution and optional ordinal. An absent ordinal denotes document
257
- context; ordinal 0 denotes a real passage. Backfilled and score-only features do
258
- not vote.
259
-
260
- The trace retains all bounded branch nominations, query trees, common filters
261
- and shard selections, including candidates discarded before the final page.
262
- Trace candidates contain addresses, raw scores and ordinals, without stored
263
- fields. Tracing does not increase retrieval depth or rerun Boolean clauses.
264
- Oversized diagnostics and unsupported backends fail explicitly. See the
265
- [scoring and tracing contract](../docs/candidate-rescoring.md).
266
-
267
- For a named, scoped L1 query, specify the complete scoring formula:
268
-
269
- ```typescript
270
- l1: {
271
- formula: "0.2 * title + 0.8 * log1p(body) + 3 * rrf";
272
- }
273
- ```
274
-
275
- `formula` is the only L1 scoring interface. Coefficients, offsets and RRF
276
- multipliers go in the expression; the former coefficient fields are removed.
277
- Arithmetic, powers, logarithms, `sqrt`, `abs`, `exp`, `min`/`max` and trigonometry
278
- are supported. Use `{body.bm25}` for punctuated branch names. `log` and `ln` are
279
- natural logarithms; `log2` and `log10` select those bases. Missing branch values
280
- use configured missing defaults, otherwise zero. Backfill remains optional.
281
-
282
- The formula runs before passage selection and the document combiner. A formula
283
- using `rrf` makes the broker obtain the complete bounded candidate and passage
284
- union before global inference. Exports that exceed budgets fail explicitly.
285
- Expressions are bounded to 4 KiB, 256 tokens and 32 parenthesis levels. Invalid
286
- variables, invalid syntax and non-finite predictions fail explicitly. Servers
287
- and brokers must support `formula_v1` (`candidate_scoring_version=3`).
288
-
289
- ### Compact deleted rows
290
-
291
- ```typescript
292
- await client.forceMerge("articles"); // Retain deletion masks.
293
- await client.forceMerge("articles", undefined, true); // Physically compact final outputs.
294
- const info = await client.getIndexInfo("articles");
295
- console.log(info.numDeletedDocs, info.physicalNumDocs, info.deletedRatio);
296
- ```
297
-
298
- The optional second argument remains the timeout in milliseconds. Compaction
299
- also handles a singleton and can change document addresses and BM25 statistics.
300
-
301
- ### Delete and upsert documents
302
-
303
- With a text field declared `[primary]`, delete by exact key and replace by passing
304
- the complete document. Every chunk belongs to its document and is deleted with it.
305
-
306
- ```typescript
307
- await client.deleteDocument("articles", "obsolete-key");
308
- await client.upsertDocument("articles", {
309
- id: "article-42",
310
- body: ["replacement chunk one", "replacement chunk two"],
311
- });
312
- await client.commit("articles");
313
-
314
- const result = await client.deleteDocuments("articles", ["old-a", "old-b"]);
315
- console.log(result.acceptedCount, result.errors); // errors: [{ index, error }]
316
- await client.commit("articles"); // publishes accepted operations
317
- ```
318
-
319
- `upsertDocuments` accepts a list of full replacements and returns the same
320
- `DocumentMutationResult`. Single-item helpers throw on rejection. Missing deletes
321
- are accepted; upserts insert missing keys. Replacing/deleting a pending insertion
322
- is supported; the latest accepted version is published at commit. With a schema
323
- content hash, an identical retry of the latest staged version is a no-op.
324
- Limits are 100,000 deletion keys / 8 MiB key bytes and 1,000
325
- replacement documents / 32 MiB encoded bytes, or one replacement / 200 MiB
326
- including the request envelope. Normal deadlines apply; an expired
327
- RPC can have staged work, so do not blindly retry replacements. Broker commits
328
- are atomic within each partition. Physical cleanup remains
329
- `await client.forceMerge("articles", undefined, true)`.
161
+ `check` compiles strict TypeScript and runs converter tests. After
162
+ [protocol changes](../hermes-proto/README.md#regeneration-and-validation), run
163
+ `pnpm generate && pnpm check` and include the generated source.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hermes-client-typescript",
3
- "version": "1.8.144",
3
+ "version": "1.8.146",
4
4
  "description": "TypeScript client for Hermes search server",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",