hermes-client-typescript 1.8.96 → 1.8.98

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.
package/README.md CHANGED
@@ -1,6 +1,7 @@
1
- # hermes-client-typescript
1
+ # Hermes TypeScript client
2
2
 
3
- TypeScript client for [Hermes](https://github.com/SpaceFrontiers/hermes) search server.
3
+ Typed Node.js client for the
4
+ [Hermes](https://github.com/SpaceFrontiers/hermes) gRPC search server.
4
5
 
5
6
  ## Installation
6
7
 
@@ -8,7 +9,7 @@ TypeScript client for [Hermes](https://github.com/SpaceFrontiers/hermes) search
8
9
  pnpm add hermes-client-typescript
9
10
  ```
10
11
 
11
- ## Usage
12
+ ## Quick start
12
13
 
13
14
  ```typescript
14
15
  import { HermesClient } from "hermes-client-typescript";
@@ -16,109 +17,204 @@ import { HermesClient } from "hermes-client-typescript";
16
17
  const client = new HermesClient("localhost:50051");
17
18
  client.connect();
18
19
 
19
- // Create index
20
- await client.createIndex(
21
- "articles",
22
- `
23
- index articles {
24
- field title: text [indexed, stored]
25
- field body: text [indexed, stored]
20
+ try {
21
+ await client.createIndex(
22
+ "articles",
23
+ `
24
+ index articles {
25
+ field title: text<simple> [indexed, stored]
26
+ field body: text<simple> [indexed, stored]
27
+ }
28
+ `,
29
+ );
30
+
31
+ const [indexedCount, errorCount, errors] = await client.indexDocuments(
32
+ "articles",
33
+ [
34
+ { title: "Hello", body: "First article" },
35
+ { title: "Hermes", body: "Fast search" },
36
+ ],
37
+ );
38
+ if (errorCount) throw new Error(JSON.stringify(errors));
39
+ console.log(`Indexed ${indexedCount} documents`);
40
+
41
+ await client.commit("articles");
42
+
43
+ const results = await client.search("articles", {
44
+ query: { match: { field: "title", text: "hello" } },
45
+ fieldsToLoad: ["title", "body"],
46
+ });
47
+
48
+ for (const hit of results.hits) {
49
+ console.log(hit.address, hit.score, hit.fields);
26
50
  }
27
- `,
28
- );
29
51
 
30
- // Index documents
31
- await client.indexDocuments("articles", [
32
- { title: "Hello", body: "World" },
33
- { title: "Foo", body: "Bar" },
34
- ]);
35
- await client.commit("articles");
36
-
37
- // Search
38
- const results = await client.search("articles", {
39
- term: ["title", "hello"],
40
- });
41
- for (const hit of results.hits) {
42
- console.log(hit.docId, hit.score, hit.fields);
52
+ if (results.hits.length > 0) {
53
+ const document = await client.getDocument(
54
+ "articles",
55
+ results.hits[0].address,
56
+ );
57
+ console.log(document?.fields);
58
+ }
59
+ } finally {
60
+ client.close();
43
61
  }
62
+ ```
44
63
 
45
- // Clean up
46
- client.close();
64
+ Call `connect()` before the first RPC and `close()` when the client is no
65
+ longer needed.
66
+
67
+ ## Index management
68
+
69
+ ```typescript
70
+ await client.createIndex("articles", schema);
71
+ const names = await client.listIndexes();
72
+ const info = await client.getIndexInfo("articles");
73
+
74
+ await client.forceMerge("articles");
75
+ await client.reorder("articles");
76
+ await client.retrainVectorIndex("articles");
77
+ await client.deleteIndex("articles");
47
78
  ```
48
79
 
49
- ## API
80
+ Newly indexed documents become searchable after `commit()`.
50
81
 
51
- ### Index Management
82
+ ### Batch and streaming indexing
52
83
 
53
- - `createIndex(indexName, schema)` — Create a new index with SDL or JSON schema
54
- - `deleteIndex(indexName)` Delete an index
55
- - `listIndexes()` List all index names
56
- - `getIndexInfo(indexName)` Get index metadata (doc count, segments, schema)
84
+ ```typescript
85
+ const [indexed, errorCount, errors] = await client.indexDocuments("articles", [
86
+ { title: "One", tags: ["search", "typescript"] },
87
+ { title: "Two", tags: ["grpc"] },
88
+ ]);
57
89
 
58
- ### Document Indexing
90
+ async function* documents() {
91
+ for (let number = 0; number < 10_000; number += 1) {
92
+ yield { title: `Document ${number}` };
93
+ }
94
+ }
59
95
 
60
- - `indexDocuments(indexName, docs)` Batch index documents, returns `[indexedCount, errorCount, errors]`
61
- - `indexDocument(indexName, doc)` — Index a single document
62
- - `indexDocumentsStream(indexName, asyncIterable)` — Stream documents for indexing
63
- - `commit(indexName)` — Commit pending changes, returns total doc count
64
- - `forceMerge(indexName)` — Force merge segments
96
+ const streamed = await client.indexDocumentsStream("articles", documents());
97
+ ```
65
98
 
66
- ### Search
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.
67
104
 
68
- - `search(indexName, options)` — Search with term, boolean, sparse/dense vector queries
69
- - `getDocument(indexName, docId)` — Get document by ID
105
+ ## Searching
70
106
 
71
- ### Search Options
107
+ `search()` accepts a `SearchRequest`. Its `query` is a discriminated union, so
108
+ exactly one query variant is selected:
72
109
 
73
110
  ```typescript
74
- await client.search("docs", {
75
- // Term query
76
- term: ["field", "value"],
77
-
78
- // Boolean query
79
- boolean: {
80
- must: [["title", "hello"]],
81
- should: [["body", "world"]],
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", {
118
+ query: {
119
+ boolean: {
120
+ must: [{ match: { field: "body", text: "fast search" } }],
121
+ mustNot: [{ term: { field: "title", term: "draft" } }],
122
+ },
82
123
  },
124
+ });
83
125
 
84
- // Sparse vector (pre-tokenized)
85
- sparseVector: ["embedding", [1, 5, 10], [0.5, 0.3, 0.2]],
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
+ fieldsToLoad: ["title"],
142
+ });
86
143
 
87
- // Sparse text (server-side tokenization)
88
- sparseText: ["embedding", "what is machine learning?"],
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
+ ```
89
173
 
90
- // Dense vector
91
- denseVector: ["embedding", [0.1, 0.2, 0.3]],
174
+ Supported variants are `term`, `match`, `boolean`, `sparseVector`,
175
+ `denseVector`, `binaryDenseVector`, `boost`, `range`, `prefix`, `all`, and
176
+ `fusion`.
92
177
 
93
- // Options
94
- limit: 10,
95
- offset: 0,
96
- fieldsToLoad: ["title", "body"],
97
- combiner: "sum", // "sum" | "max" | "avg"
178
+ `getDocument()` takes the full address returned by a search hit:
98
179
 
99
- // L2 reranker; candidateLimit is the shared first-stage pool.
100
- reranker: ["embedding", [0.1, 0.2]],
101
- candidateLimit: 20,
180
+ ```typescript
181
+ const document = await client.getDocument("articles", hit.address);
182
+ ```
183
+
184
+ It returns `null` when the server responds with gRPC `NOT_FOUND`.
185
+
186
+ ## Deadlines
187
+
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,
102
194
  });
195
+
196
+ await client.search("articles", { query: { all: {} } }, 500);
197
+ await client.forceMerge("articles", 3_600_000);
103
198
  ```
104
199
 
200
+ Expired calls reject with a gRPC `DEADLINE_EXCEEDED` error.
201
+
105
202
  ## Development
106
203
 
107
204
  ```bash
108
- pnpm install
109
- pnpm run generate # regenerate proto stubs
110
- pnpm run build # compile TypeScript
205
+ pnpm install --frozen-lockfile
206
+ pnpm check
111
207
  ```
112
208
 
113
- ## Timeouts / deadlines
209
+ `pnpm check` compiles the strict TypeScript sources and runs the pure converter
210
+ unit tests. After changing `hermes-proto/hermes.proto`, regenerate and check in
211
+ the generated source:
114
212
 
115
- Every RPC accepts an optional trailing `timeoutMs` which sets a real gRPC
116
- deadline (propagated to the server via `grpc-timeout`); a client-wide default
117
- can be set in the constructor. On expiry the call rejects with
118
- `DEADLINE_EXCEEDED`.
119
-
120
- ```ts
121
- const client = new HermesClient("localhost:50051", { defaultTimeoutMs: 5000 });
122
- await client.search("articles", { query: { ... } }, 500); // per-call override
123
- await client.forceMerge("articles", 3_600_000); // long op, long deadline
213
+ ```bash
214
+ pnpm generate
215
+ pnpm check
124
216
  ```
217
+
218
+ ## License
219
+
220
+ MIT
package/dist/client.d.ts CHANGED
@@ -1,31 +1,31 @@
1
1
  /**
2
2
  * Async Hermes client implementation.
3
3
  *
4
- * All search types mirror the proto API structure exactly.
5
- * See types.ts for Query, Reranker, SearchRequest definitions.
4
+ * Search types mirror the protobuf API. Serialization details live in
5
+ * converters.ts so this class remains focused on connection and RPC lifecycle.
6
6
  */
7
- import type { DocAddress, Document, SearchResponse, IndexInfo, SearchRequest } from "./types";
7
+ import type { DocAddress, Document, IndexInfo, SearchRequest, SearchResponse } from "./types";
8
8
  export interface HermesClientOptions {
9
9
  /**
10
10
  * Default per-RPC deadline in milliseconds, applied to every call unless
11
- * overridden by the call's `timeoutMs` argument. Undefined = no deadline.
12
- * On expiry the call rejects with a gRPC DEADLINE_EXCEEDED error.
11
+ * overridden by the call's `timeoutMs` argument. Undefined means no
12
+ * deadline. Expired calls reject with gRPC DEADLINE_EXCEEDED.
13
13
  */
14
14
  defaultTimeoutMs?: number;
15
15
  }
16
16
  export declare class HermesClient {
17
- private address;
17
+ private readonly address;
18
+ private readonly defaultTimeoutMs?;
18
19
  private channel;
19
20
  private indexClient;
20
21
  private searchClient;
21
- private defaultTimeoutMs?;
22
22
  constructor(address?: string, options?: HermesClientOptions);
23
- /** Per-call options with the effective deadline (call override > default). */
24
- private callOptions;
25
23
  /** Connect to the server. */
26
24
  connect(): void;
27
25
  /** Close the connection. */
28
26
  close(): void;
27
+ /** Per-call options with the effective deadline (call override > default). */
28
+ private callOptions;
29
29
  private ensureConnected;
30
30
  /** Create a new index. */
31
31
  createIndex(indexName: string, schema: string, timeoutMs?: number): Promise<boolean>;
@@ -35,58 +35,31 @@ export declare class HermesClient {
35
35
  listIndexes(timeoutMs?: number): Promise<string[]>;
36
36
  /** Get information about an index. */
37
37
  getIndexInfo(indexName: string, timeoutMs?: number): Promise<IndexInfo>;
38
- /** Index multiple documents in batch. Returns [indexedCount, errorCount, errors]. */
39
- indexDocuments(indexName: string, documents: Record<string, any>[], timeoutMs?: number): Promise<[number, number, Array<{
38
+ /** Index multiple documents. Returns [indexedCount, errorCount, errors]. */
39
+ indexDocuments(indexName: string, documents: Record<string, unknown>[], timeoutMs?: number): Promise<[number, number, Array<{
40
40
  index: number;
41
41
  error: string;
42
42
  }>]>;
43
43
  /** Index a single document. */
44
- indexDocument(indexName: string, document: Record<string, any>, timeoutMs?: number): Promise<void>;
44
+ indexDocument(indexName: string, document: Record<string, unknown>, timeoutMs?: number): Promise<void>;
45
45
  /** Stream documents for indexing. Returns number of indexed documents. */
46
- indexDocumentsStream(indexName: string, documents: AsyncIterable<Record<string, any>>, timeoutMs?: number): Promise<number>;
46
+ indexDocumentsStream(indexName: string, documents: AsyncIterable<Record<string, unknown>>, timeoutMs?: number): Promise<number>;
47
47
  /** Commit pending changes. Returns total number of documents. */
48
48
  commit(indexName: string, timeoutMs?: number): Promise<number>;
49
49
  /** Force merge all segments. Returns number of segments after merge. */
50
50
  forceMerge(indexName: string, timeoutMs?: number): Promise<number>;
51
51
  /** Retrain vector index centroids/codebooks from current data. */
52
52
  retrainVectorIndex(indexName: string, timeoutMs?: number): Promise<boolean>;
53
- /** Reorder BMP blocks by SimHash similarity for better pruning. Returns number of segments. */
53
+ /** Reorder BMP blocks by SimHash similarity. */
54
54
  reorder(indexName: string, timeoutMs?: number): Promise<number>;
55
55
  /**
56
56
  * Search for documents.
57
57
  *
58
58
  * @example
59
- * // Term query
60
- * await client.search("articles", {
61
- * query: { term: { field: "title", term: "hello" } },
62
- * });
63
- *
64
- * // Match query (full-text, tokenized server-side)
65
59
  * await client.search("articles", {
66
- * query: { match: { field: "title", text: "what is hemoglobin" } },
67
- * });
68
- *
69
- * // Sparse vector query with server-side tokenization + pruning
70
- * await client.search("docs", {
71
- * query: { sparseVector: { field: "embedding", text: "machine learning", pruning: 0.5 } },
60
+ * query: { match: { field: "title", text: "search engine" } },
72
61
  * fieldsToLoad: ["title"],
73
62
  * });
74
- *
75
- * // Dense vector query
76
- * await client.search("docs", {
77
- * query: { denseVector: { field: "embedding", vector: [0.1, 0.2, ...], nprobe: 10 } },
78
- * reranker: { field: "embedding", vector: [0.1, 0.2, ...] },
79
- * limit: 50,
80
- * candidateLimit: 100,
81
- * });
82
- *
83
- * // Boolean query
84
- * await client.search("articles", {
85
- * query: { boolean: {
86
- * must: [{ match: { field: "title", text: "hello" } }],
87
- * should: [{ match: { field: "body", text: "world" } }],
88
- * }},
89
- * });
90
63
  */
91
64
  search(indexName: string, request: SearchRequest, timeoutMs?: number): Promise<SearchResponse>;
92
65
  /** Get a document by address. Returns null if not found. */
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}