hlquery-node-client 1.0.0 → 1.0.2

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
@@ -4,30 +4,39 @@
4
4
 
5
5
  <div align="center">
6
6
 
7
- **A modular Node.js client library for hlquery.**
7
+ **A modular Node.js client library for hlquery, designed with a familiar and intuitive API structure.**
8
8
 
9
9
  [![Follow hlquery](https://img.shields.io/badge/Follow-%40hlquery-blue?logo=x&logoColor=white)](https://x.com/hlquery)
10
10
  [![Commit Activity](https://img.shields.io/github/commit-activity/m/hlquery/node-api)](https://github.com/hlquery/node-api/pulse)
11
- [![node-api](https://img.shields.io/badge/GitHub-node--api-181717?logo=github&logoColor=white)](https://github.com/hlquery/node-api/stargazers)
11
+ [![GitHub](https://img.shields.io/badge/GitHub-node--api-181717?logo=github&logoColor=white)](https://github.com/hlquery/node-api/stargazers)
12
+ [![hlquery](https://img.shields.io/badge/GitHub-hlquery-blue?logo=github&logoColor=white)](https://github.com/hlquery/hlquery/stargazers)
12
13
  [![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause)
13
14
 
14
15
  </div>
15
16
 
16
- # hlquery Node.js API Client
17
+ ### What is the hlquery Node.js API?
17
18
 
18
- ### Installation
19
+ The hlquery Node.js API is the official Node.js client for hlquery. It wraps the REST interface in a modular service-style client with helpers for collections, documents, search, SQL, and SAM.
19
20
 
20
- Core client usage has no external dependencies. CSV support is built in.
21
+ It is a good fit for backend services, scripts, dashboards, and apps that want hlquery integration without repeating request code.
21
22
 
22
- ```bash
23
- npm install hlquery-node-client
24
- ```
23
+ ### Why use it?
25
24
 
26
- ```javascript
27
- const Client = require('hlquery-node-client');
25
+ Use the Node.js client when you want hlquery calls to read like regular application code. The client is organized around familiar modules such as `client.collections()`, `client.documents()`, and `client.sam()`, so collection management, document indexing, search, SQL, and SAM workflows stay easy to find.
26
+
27
+ It also keeps the repetitive parts in one place: authentication, request parameters, endpoint paths, and parsed responses are handled consistently across the client. Common hlquery workflows are covered by default, while raw request access is still available when you need a custom route.
28
+
29
+ ### Why choose it over raw HTTP?
30
+
31
+ Choose the Node.js client over raw HTTP when you want less repetitive `fetch` or `axios` boilerplate, cleaner handling for auth headers and endpoint paths, and search or indexing code that stays easier to read.
32
+
33
+ ### Install
34
+
35
+ ```bash
36
+ $ npm install hlquery-node-client
28
37
  ```
29
38
 
30
- For local development in this repository:
39
+ For local development inside this repository:
31
40
 
32
41
  ```javascript
33
42
  const Client = require('./lib/Client');
@@ -38,279 +47,147 @@ const Client = require('./lib/Client');
38
47
  ```javascript
39
48
  const Client = require('hlquery-node-client');
40
49
 
41
- const client = new Client(process.env.HLQ_BASE_URL || 'http://localhost:9200', {
50
+ const client = new Client(process.env.HLQ_BASE_URL || process.env.HLQUERY_BASE_URL || 'http://localhost:9200', {
42
51
  token: process.env.HLQ_TOKEN,
43
- auth_method: 'bearer' // or 'api-key'
52
+ auth_method: 'bearer'
44
53
  });
45
54
 
46
55
  const health = await client.system().health();
56
+ /* Print the HTTP status code from the health response. */
47
57
  console.log('status:', health.getStatusCode());
48
58
 
49
59
  const collections = await client.collections().list(0, 10);
60
+ /* Print the collection list response body. */
50
61
  console.log(collections.getBody());
51
-
52
- await client.collections().create('books', {
53
- fields: [
54
- { name: 'title', type: 'string' },
55
- { name: 'content', type: 'string' }
56
- ]
57
- });
58
-
59
- await client.documents().add('books', {
60
- id: 'book-1',
61
- title: 'Designing Data-Intensive Applications',
62
- content: 'Distributed systems, data models, replication, and indexing.'
63
- });
64
-
65
- const results = await client.searchApi().search('books', {
66
- q: 'distributed systems',
67
- query_by: 'title,content',
68
- limit: 10
69
- });
70
- console.log(results.getBody());
71
62
  ```
72
63
 
73
- You can also set authentication later:
64
+ ### Auth
74
65
 
75
66
  ```javascript
67
+ const client = new Client('http://localhost:9200', {
68
+ token: 'your_token_here',
69
+ auth_method: 'bearer'
70
+ });
71
+
76
72
  client.setAuthToken('your_token_here', 'bearer');
77
73
  client.setAuthToken('your_api_key_here', 'api-key');
78
74
  ```
79
75
 
80
- ### API Surface
76
+ ### Operational routes
81
77
 
82
- Use nested API objects: `client -> resource -> action`, for example `client.collections().create(...)`.
78
+ The client includes wrappers for the common server and cluster routes used by dashboards, scripts, and maintenance jobs:
83
79
 
84
80
  ```javascript
85
- client.system().health();
86
- client.system().stats();
87
- client.system().info();
88
- client.system().status();
89
- client.system().metrics();
90
- client.system().connections();
91
- client.system().rocksdb();
92
-
93
- client.collections().list(offset, limit);
94
- client.collections().get(name);
95
- client.collections().create(name, schema);
96
- client.collections().update(name, schema);
97
- client.collections().delete(name);
98
- client.collections().getFields(name);
99
-
100
- client.documents().list(collection, params);
101
- client.documents().get(collection, id);
102
- client.documents().add(collection, document);
103
- client.documents().update(collection, id, document);
104
- client.documents().delete(collection, id);
105
- client.documents().import(collection, documents);
106
- client.documents().export(collection, params);
107
- client.documents().facetCounts(collection, params);
108
- client.documents().addCSV(collection, filePath, options);
109
-
110
- client.searchApi().search(collection, params);
111
- client.searchApi().vectorSearch(collection, params);
112
- client.searchApi().multiSearch(searches);
113
- client.searchApi().globalSearch(params);
114
- client.searchApi().sql(collection, sql);
115
-
116
- client.aliases().create(name, params);
117
- client.aliases().update(name, params);
118
- client.aliases().delete(name);
119
- client.synonyms().create(collection, id, synonym);
120
- client.stopwords().create(collection, params);
121
- client.overrides().create(collection, id, override);
122
- client.keys().create(params);
123
- client.executeRequest(method, path, body, query);
124
- ```
81
+ const status = await client.status();
82
+ const health = await client.health();
83
+ const etc = await client.etc();
125
84
 
126
- ### Collections
127
-
128
- ```javascript
129
- const collections = client.collections();
130
-
131
- await collections.create('books', {
132
- fields: [
133
- { name: 'title', type: 'string' },
134
- { name: 'content', type: 'string' },
135
- { name: 'category', type: 'string', facet: true }
136
- ]
137
- });
85
+ const links = await client.links();
86
+ const ping = await client.linksPing();
87
+ const connect = await client.linksConnect('http://node-b:9200');
88
+ const disconnect = await client.linksDisconnect('http://node-b:9200');
138
89
 
139
- await collections.get('books');
140
- await collections.getFields('books');
141
- await collections.update('books', schema);
142
- await collections.delete('books');
90
+ const flush = await client.flush();
143
91
  ```
144
92
 
145
- ### Documents
93
+ Routes that do not have a dedicated wrapper can still be called through `executeRequest()`:
146
94
 
147
95
  ```javascript
148
- const documents = client.documents();
149
-
150
- await documents.add('books', {
151
- id: 'book-1',
152
- title: 'Designing Data-Intensive Applications',
153
- content: 'Distributed systems, data models, replication, and indexing.'
96
+ const response = await client.executeRequest('GET', '/modules/<name>/<route>', null, {
97
+ q: 'example query'
154
98
  });
155
99
 
156
- await documents.update('books', 'book-1', { title: 'DDIA' });
157
- await documents.delete('books', 'book-1');
158
-
159
- await documents.import('books', [
160
- { id: 'book-2', title: 'Database Internals', content: 'Storage engines and indexes.' }
161
- ]);
162
-
163
- await documents.addCSV('reports', './files/metrics.csv', {
164
- id: 'metrics_q1',
165
- document: { source_type: 'csv' }
166
- });
100
+ /* Print the server status response body. */
101
+ console.log(status.getBody());
102
+ /* Print the health response body. */
103
+ console.log(health.getBody());
104
+ /* Print the runtime configuration response body. */
105
+ console.log(etc.getBody());
106
+ /* Print the configured cluster links response body. */
107
+ console.log(links.getBody());
108
+ /* Print the link ping response body. */
109
+ console.log(ping.getBody());
110
+ /* Print the link connect response body. */
111
+ console.log(connect.getBody());
112
+ /* Print the link disconnect response body. */
113
+ console.log(disconnect.getBody());
114
+ /* Print the flush response body. */
115
+ console.log(flush.getBody());
116
+ /* Print the custom route response body. */
117
+ console.log(response.getBody());
167
118
  ```
168
119
 
169
- `addCSV()` parses a local CSV file, flattens rows into text, and indexes the generated document. It does not require extra packages.
120
+ ### SAM
170
121
 
171
- ### Search
122
+ SAM is separate from vector search. It performs term and intent-style lookup, not vector similarity search.
172
123
 
173
124
  ```javascript
174
- const search = client.searchApi();
175
-
176
- await search.search('books', {
177
- q: 'title:database OR content:index*',
178
- query_by: 'title,content',
179
- filter_by: 'category:technical',
180
- sort_by: '_text_match:desc',
181
- facet_by: 'category',
182
- limit: 10,
183
- offset: 0
184
- });
185
-
186
- await search.multiSearch([
187
- { collection: 'books', q: 'database', query_by: 'title,content' },
188
- { collection: 'articles', q: 'database', query_by: 'title,body' }
189
- ]);
125
+ const sam = client.sam();
190
126
 
191
- await search.vectorSearch('books', {
192
- vector_query: [0.12, 0.34, 0.56],
193
- vector_by: 'embedding',
127
+ const status = await sam.status('books');
128
+ const history = await sam.history('books', 5);
129
+ const results = await sam.search('books', 'distributed systems', {
194
130
  limit: 10
195
131
  });
196
- ```
197
-
198
- Common search parameters:
199
132
 
200
- - `q`: query string, including field clauses, phrases, boolean operators, and wildcards.
201
- - `query_by`: searchable fields as a string or array.
202
- - `filter_by`: filter expression.
203
- - `sort_by`: sort expression.
204
- - `facet_by`: facet fields.
205
- - `limit` / `offset`: pagination.
206
- - `page` / `per_page`: alternate pagination.
133
+ /* Print the SAM status response body. */
134
+ console.log(status.getBody());
135
+ /* Print the SAM search history response body. */
136
+ console.log(history.getBody());
137
+ /* Print the SAM search results response body. */
138
+ console.log(results.getBody());
139
+ ```
207
140
 
208
141
  ### SQL
209
142
 
210
- SQL queries are supported through the search API and through the interactive talk shell.
211
-
212
143
  ```javascript
213
- const response = await client.searchApi().sql(
214
- 'products',
215
- 'SELECT id, title, price FROM products ORDER BY price DESC LIMIT 5;'
144
+ const rows = await client.sql('SHOW COLLECTIONS;');
145
+ const execResult = await client.execSql(
146
+ "INSERT INTO logs_archive (id, title) VALUES ('row-1', 'warm cache');"
147
+ );
148
+ const books = await client.sqlSearch(
149
+ 'books',
150
+ 'SELECT id, title FROM books ORDER BY title ASC LIMIT 3;'
216
151
  );
217
152
 
218
- const rows = response.getBody().rows || [];
219
-
220
- await client.sql('SHOW COLLECTIONS;');
221
- await client.execSql("INSERT INTO products (id, title) VALUES ('sku-9', 'Camp Stove');");
153
+ /* Print the SQL query response body. */
154
+ console.log(rows.getBody());
155
+ /* Print the SQL execution response body. */
156
+ console.log(execResult.getBody());
157
+ /* Print the collection SQL search response body. */
158
+ console.log(books.getBody());
222
159
  ```
223
160
 
224
- Talk shell example:
225
-
226
- ```text
227
- localhost:9200> sql: select title,id from music LIMIT 2;
228
-
229
- SQL rows for `select title,id from music LIMIT 2;`:
230
- +--------------------------------+------------------------------------------+
231
- | id | title |
232
- +--------------------------------+------------------------------------------+
233
- | music_artist-profile-beyonce | Artist Profile: Beyonce |
234
- | music_artist-profile-kendrick- | Artist Profile: Kendrick Lamar |
235
- | lamar | |
236
- +--------------------------------+------------------------------------------+
237
- 2 results shown.
238
- Search completed in 19 ms.
239
- ```
161
+ ### Reduce Text Example
240
162
 
241
- ### Search Management
163
+ Use the raw request helper for custom module routes:
242
164
 
243
165
  ```javascript
244
- const aliases = client.aliases();
245
- const synonyms = client.synonyms();
246
- const stopwords = client.stopwords();
247
- const overrides = client.overrides();
248
-
249
- await aliases.create('books_current', { collection_name: 'books_v2' });
250
-
251
- await synonyms.create('books', 'cars', {
252
- root: 'car',
253
- synonyms: ['auto', 'automobile']
166
+ const response = await client.executeRequest('GET', '/modules/<name>/<route>', null, {
167
+ q: 'example query'
254
168
  });
255
169
 
256
- await stopwords.create('books', { word: 'the' });
257
-
258
- await overrides.create('books', 'boost_featured', {
259
- rule: { query: 'featured', match: 'exact' },
260
- includes: [{ id: 'doc-1', position: 1 }],
261
- excludes: []
262
- });
170
+ /* Print the custom module route response body. */
171
+ console.log(response.getBody());
263
172
  ```
264
173
 
265
- ### Responses
174
+ ### Contributing
266
175
 
267
- All API methods return a `Response` object.
176
+ We welcome contributions from the community! All contributions must be released under the BSD 3-Clause license.
268
177
 
269
- ```javascript
270
- const response = await client.system().health();
271
-
272
- response.getStatusCode();
273
- response.getBody();
274
- response.isSuccess();
275
- response.isError();
276
- response.getError();
277
- response.toArray();
278
- ```
279
-
280
- ### Errors
178
+ ### How to Contribute
281
179
 
282
- ```javascript
283
- const { RequestException, AuthenticationException, ValidationException } = require('./lib/Exceptions');
284
-
285
- try {
286
- const result = await client.searchApi().search('books', { q: 'test' });
287
- if (result.isError()) {
288
- console.log(result.getError());
289
- }
290
- } catch (error) {
291
- if (error instanceof RequestException) {
292
- console.log('request failed:', error.message, error.statusCode);
293
- } else if (error instanceof AuthenticationException) {
294
- console.log('auth failed:', error.message);
295
- } else if (error instanceof ValidationException) {
296
- console.log('validation failed:', error.message);
297
- } else {
298
- throw error;
299
- }
300
- }
301
- ```
180
+ - Check existing [issues](https://github.com/hlquery/hlquery/issues) or create new ones
181
+ - Contribute to client libraries (Node.js, Go, Java, Python, PHP, Ruby, Rust, Perl, C++)
182
+ - Test and report bugs
183
+ - Improve documentation
302
184
 
303
- ### Requirements
185
+ ### Community
304
186
 
305
- - Node.js >= 12.0.0
306
- - No external dependencies required for core client usage
187
+ - 📖 [Documentation](https://docs.hlquery.com)
188
+ - 🐦 [X (Twitter)](https://x.com/hlquery)
189
+ - 📦 [GitHub](https://github.com/hlquery/hlquery)
307
190
 
308
- ### Features
191
+ ### License
309
192
 
310
- - Modular classes for collections, documents, search, aliases, overrides, synonyms, stopwords, and system APIs.
311
- - Bearer token and X-API-Key authentication.
312
- - Flexible parameter formats.
313
- - Automatic searchable-field detection when fields are not specified.
314
- - SQL helpers for collection-bound and top-level SQL execution.
315
- - Response wrapper with status, body, and error helpers.
316
- - Built-in validation and async/await support.
193
+ hlquery is licensed under the [BSD 3-Clause License](https://opensource.org/licenses/BSD-3-Clause).
package/example.js CHANGED
@@ -14,6 +14,7 @@
14
14
  * Commands:
15
15
  * cols - Run collections API examples
16
16
  * docs - Run documents API examples
17
+ * sql - Run SQL API examples
17
18
  * open - List and open collections (interactive)
18
19
  * status - Show server health and status information
19
20
  * help - Show this help message
@@ -34,7 +35,7 @@ let collectionName = null;
34
35
 
35
36
  if (process.argv.length > 2) {
36
37
  const firstArg = process.argv[2];
37
- if (['cols', 'docs', 'open', 'status', 'help', 'all'].includes(firstArg)) {
38
+ if (['cols', 'docs', 'sql', 'open', 'status', 'help', 'all'].includes(firstArg)) {
38
39
  command = firstArg;
39
40
  // Parse pagination for cols command: cols [offset] [limit] [token]
40
41
  if (command === 'cols' && process.argv.length >= 4) {
@@ -84,6 +85,7 @@ if (command === 'help') {
84
85
  console.log(' docs - Run documents API examples');
85
86
  console.log(' Usage: docs [collection_name] [token]');
86
87
  console.log(' Example: docs my_collection');
88
+ console.log(' sql - Run SQL API examples');
87
89
  console.log(' open - List and open collections (interactive)');
88
90
  console.log(' status - Show server health and status information');
89
91
  console.log(' help - Show this help message');
@@ -221,6 +223,34 @@ async function main() {
221
223
  process.exit(0);
222
224
  }
223
225
 
226
+ // ----------------------------------------------------------------====================================
227
+ // SQL API
228
+ // ----------------------------------------------------------------====================================
229
+ if (command === 'all' || command === 'sql') {
230
+ console.log('\n' + '#'.repeat(70));
231
+ console.log('# SQL API');
232
+ console.log('#'.repeat(70) + '\n');
233
+
234
+ try {
235
+ // GET /sql
236
+ printResult('GET /sql (SHOW COLLECTIONS)', await client.sql('SHOW COLLECTIONS;'));
237
+
238
+ // Collection-bound SQL SELECT through /collections/{name}/documents/search
239
+ const sqlCollection = await getFirstCollection(client);
240
+ if (!sqlCollection) {
241
+ console.log('No collections available - skipping collection-bound SQL test\n');
242
+ } else {
243
+ const query = `SELECT id FROM ${sqlCollection} LIMIT 5;`;
244
+ printResult(
245
+ 'GET /collections/{name}/documents/search (SQL SELECT)',
246
+ await client.sqlSearch(sqlCollection, query)
247
+ );
248
+ }
249
+ } catch (error) {
250
+ console.log(`Error in SQL API: ${error.message}\n`);
251
+ }
252
+ }
253
+
224
254
  // ----------------------------------------------------------------====================================
225
255
  // System APIs (only for 'all' command)
226
256
  // ----------------------------------------------------------------====================================
@@ -621,6 +651,7 @@ async function main() {
621
651
  console.log(' docs - Run documents API examples');
622
652
  console.log(' Usage: docs [collection_name] [token]');
623
653
  console.log(' Example: docs my_collection');
654
+ console.log(' sql - Run SQL API examples');
624
655
  console.log(' open - List and open collections');
625
656
  console.log(' status - Show server health and status information');
626
657
  console.log(' help - Show help message');
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Basic SAM example for the hlquery Node.js client.
3
+ */
4
+
5
+ const Client = require('../index');
6
+
7
+ async function main() {
8
+ const baseUrl = process.env.HLQ_BASE_URL || process.env.HLQUERY_BASE_URL || 'http://localhost:9200';
9
+ const token = process.argv[2] || null;
10
+ const collection = process.argv[3] || 'music';
11
+ const query = process.argv[4] || 'queen of pop';
12
+
13
+ const client = new Client(baseUrl);
14
+
15
+ if (token) {
16
+ client.setAuthToken(token, 'bearer');
17
+ }
18
+
19
+ const sam = client.sam();
20
+
21
+ const status = await sam.status(collection);
22
+ console.log('=== SAM STATUS ===');
23
+ console.log(status.getBody());
24
+ console.log('');
25
+
26
+ const history = await sam.history(collection, 5);
27
+ console.log('=== SAM HISTORY ===');
28
+ console.log(history.getBody());
29
+ console.log('');
30
+
31
+ const results = await sam.search(collection, query, { limit: 10 });
32
+ console.log('=== SAM SEARCH ===');
33
+ console.log(results.getBody());
34
+ }
35
+
36
+ main().catch((error) => {
37
+ console.error(error);
38
+ process.exit(1);
39
+ });
@@ -0,0 +1,31 @@
1
+ /**
2
+ * SQL Examples
3
+ *
4
+ * Demonstrates top-level and collection-bound SQL usage with the hlquery Node.js client
5
+ */
6
+
7
+ const Client = require('../lib/Client');
8
+
9
+ async function main() {
10
+ const client = new Client('http://localhost:9200');
11
+
12
+ // Top-level SQL through /sql
13
+ const rows = await client.sql('SHOW COLLECTIONS;');
14
+ console.log('SHOW COLLECTIONS:', rows.getBody());
15
+
16
+ // Top-level SQL statement execution through POST /sql
17
+ const execResult = await client.execSql(
18
+ "INSERT INTO logs_archive (id, title) VALUES ('row-1', 'warm cache');"
19
+ );
20
+ console.log('Exec result:', execResult.getBody());
21
+
22
+ // Collection-bound SQL SELECT through /collections/{name}/documents/search
23
+ const products = await client.sqlSearch(
24
+ 'products',
25
+ 'SELECT id, title, price FROM products WHERE price > 100 ORDER BY price DESC LIMIT 3;',
26
+ { highlight: false }
27
+ );
28
+ console.log('Products SQL results:', products.getBody());
29
+ }
30
+
31
+ main().catch(console.error);
package/index.js CHANGED
@@ -24,6 +24,7 @@ module.exports.Overrides = require('./lib/Overrides');
24
24
  module.exports.Synonyms = require('./lib/Synonyms');
25
25
  module.exports.Stopwords = require('./lib/Stopwords');
26
26
  module.exports.System = require('./lib/System');
27
+ module.exports.SAM = require('./lib/SAM');
27
28
 
28
29
  // Export exceptions
29
30
  module.exports.Exceptions = require('./lib/Exceptions');
package/lib/Client.js CHANGED
@@ -17,6 +17,7 @@ const Overrides = require('./Overrides');
17
17
  const Synonyms = require('./Synonyms');
18
18
  const Stopwords = require('./Stopwords');
19
19
  const System = require('./System');
20
+ const SAM = require('./SAM');
20
21
  const Config = require('../utils/Config');
21
22
 
22
23
  /**
@@ -49,6 +50,7 @@ class Client {
49
50
  this._synonyms = new Synonyms(this.request);
50
51
  this._stopwords = new Stopwords(this.request);
51
52
  this._system = new System(this.request);
53
+ this._sam = new SAM(this.request);
52
54
  }
53
55
 
54
56
  /**
@@ -462,6 +464,59 @@ class Client {
462
464
  return this._search;
463
465
  }
464
466
 
467
+ /**
468
+ * Get SAM API instance
469
+ *
470
+ * @returns {SAM}
471
+ */
472
+ sam() {
473
+ return this._sam;
474
+ }
475
+
476
+ async samSearch(collectionName, query, params = {}) {
477
+ return await this._sam.search(collectionName, query, params);
478
+ }
479
+
480
+ async samSearchAll(query, params = {}) {
481
+ return await this._sam.searchAll(query, params);
482
+ }
483
+
484
+ async samRebuild(collectionName, params = {}) {
485
+ return await this._sam.rebuild(collectionName, params);
486
+ }
487
+
488
+ async samStatus(collectionName = null, params = {}) {
489
+ return await this._sam.status(collectionName, params);
490
+ }
491
+
492
+ async samDebug(collectionName = null, params = {}) {
493
+ return await this._sam.debug(collectionName, params);
494
+ }
495
+
496
+ async samHistory(collectionName = null, limit = 100, params = {}) {
497
+ return await this._sam.history(collectionName, limit, params);
498
+ }
499
+
500
+ async samPause(pauseUntilMs, params = {}) {
501
+ return await this._sam.pause(pauseUntilMs, params);
502
+ }
503
+
504
+ async samClearPause(params = {}) {
505
+ return await this._sam.clearPause(params);
506
+ }
507
+
508
+ async samDocuments(collectionName, offset = 0, limit = 20, params = {}) {
509
+ return await this._sam.listDocuments(collectionName, offset, limit, params);
510
+ }
511
+
512
+ async samDocument(collectionName, documentId, params = {}) {
513
+ return await this._sam.getDocument(collectionName, documentId, params);
514
+ }
515
+
516
+ async samOpenDocument(collectionName, documentId, interactionQuery = null, params = {}) {
517
+ return await this._sam.openDocument(collectionName, documentId, interactionQuery, params);
518
+ }
519
+
465
520
  /**
466
521
  * Get aliases API instance
467
522
  *
package/lib/SAM.js ADDED
@@ -0,0 +1,161 @@
1
+ /**
2
+ * hlquery Node.js Client - SAM API
3
+ *
4
+ * Copyright (C) 2021-2026, Carlos F. Ferry <carlos.ferry@gmail.com>
5
+ *
6
+ * This file is part of hlquery, released under the BSD License version 3.
7
+ */
8
+
9
+ const Validator = require('../utils/Validator');
10
+
11
+ /**
12
+ * SAM API operations
13
+ */
14
+ class SAM {
15
+ constructor(request) {
16
+ this.request = request;
17
+ }
18
+
19
+ _validateParams(params, label) {
20
+ if (params === null || typeof params !== 'object' || Array.isArray(params)) {
21
+ throw new Error(`${label} must be an object`);
22
+ }
23
+ }
24
+
25
+ async search(collectionName, query, params = {}) {
26
+ if (typeof query !== 'string' || query.trim() === '') {
27
+ throw new Error('SAM query must be a non-empty string');
28
+ }
29
+
30
+ this._validateParams(params, 'SAM search params');
31
+
32
+ const queryParams = {
33
+ ...params,
34
+ q: query
35
+ };
36
+
37
+ if (collectionName !== null && collectionName !== undefined && collectionName !== '') {
38
+ Validator.validateCollectionName(collectionName);
39
+ queryParams.collection = collectionName;
40
+ } else if (!queryParams.all && !queryParams.collections) {
41
+ throw new Error('Collection name is required unless all=true or collections is provided');
42
+ }
43
+
44
+ return await this.request.execute('GET', '/sam/search', null, queryParams);
45
+ }
46
+
47
+ async searchAll(query, params = {}) {
48
+ this._validateParams(params, 'SAM search params');
49
+ return await this.search(null, query, { ...params, all: true });
50
+ }
51
+
52
+ async rebuild(collectionName, params = {}) {
53
+ Validator.validateCollectionName(collectionName);
54
+ this._validateParams(params, 'SAM rebuild params');
55
+
56
+ return await this.request.execute('POST', '/sam/rebuild', null, {
57
+ ...params,
58
+ collection: collectionName
59
+ });
60
+ }
61
+
62
+ async status(collectionName = null, params = {}) {
63
+ this._validateParams(params, 'SAM status params');
64
+
65
+ const queryParams = { ...params };
66
+
67
+ if (collectionName !== null && collectionName !== undefined && collectionName !== '') {
68
+ Validator.validateCollectionName(collectionName);
69
+ queryParams.collection = collectionName;
70
+ }
71
+
72
+ return await this.request.execute('GET', '/sam/status', null, queryParams);
73
+ }
74
+
75
+ async debug(collectionName = null, params = {}) {
76
+ this._validateParams(params, 'SAM debug params');
77
+
78
+ const queryParams = { ...params };
79
+
80
+ if (collectionName !== null && collectionName !== undefined && collectionName !== '') {
81
+ Validator.validateCollectionName(collectionName);
82
+ queryParams.collection = collectionName;
83
+ }
84
+
85
+ return await this.request.execute('GET', '/sam/debug', null, queryParams);
86
+ }
87
+
88
+ async history(collectionName = null, limit = 100, params = {}) {
89
+ this._validateParams(params, 'SAM history params');
90
+
91
+ const queryParams = {
92
+ ...params,
93
+ limit: Number(limit)
94
+ };
95
+
96
+ if (collectionName !== null && collectionName !== undefined && collectionName !== '') {
97
+ Validator.validateCollectionName(collectionName);
98
+ queryParams.collection = collectionName;
99
+ }
100
+
101
+ return await this.request.execute('GET', '/sam/history', null, queryParams);
102
+ }
103
+
104
+ async pause(pauseUntilMs, params = {}) {
105
+ this._validateParams(params, 'SAM pause params');
106
+
107
+ const pause = Number(pauseUntilMs);
108
+ if (!Number.isFinite(pause) || pause < 0) {
109
+ throw new Error('SAM pause value must be a non-negative unix millisecond timestamp, or 0 to clear');
110
+ }
111
+
112
+ return await this.request.execute('POST', '/sam/pause', null, {
113
+ ...params,
114
+ pause
115
+ });
116
+ }
117
+
118
+ async clearPause(params = {}) {
119
+ return await this.pause(0, params);
120
+ }
121
+
122
+ async listDocuments(collectionName, offset = 0, limit = 20, params = {}) {
123
+ Validator.validateCollectionName(collectionName);
124
+ this._validateParams(params, 'SAM document list params');
125
+
126
+ return await this.request.execute('GET', '/sam/documents', null, {
127
+ ...params,
128
+ collection: collectionName,
129
+ offset: Number(offset),
130
+ limit: Number(limit)
131
+ });
132
+ }
133
+
134
+ async getDocument(collectionName, documentId, params = {}) {
135
+ Validator.validateCollectionName(collectionName);
136
+ Validator.validateDocumentId(documentId);
137
+ this._validateParams(params, 'SAM document params');
138
+
139
+ return await this.request.execute(
140
+ 'GET',
141
+ `/sam/documents/${encodeURIComponent(collectionName)}/${encodeURIComponent(documentId)}`,
142
+ null,
143
+ params
144
+ );
145
+ }
146
+
147
+ async openDocument(collectionName, documentId, interactionQuery = null, params = {}) {
148
+ const queryParams = { ...params };
149
+
150
+ if (interactionQuery !== null && interactionQuery !== undefined && interactionQuery !== '') {
151
+ if (typeof interactionQuery !== 'string') {
152
+ throw new Error('SAM interaction query must be a string');
153
+ }
154
+ queryParams.interaction_query = interactionQuery;
155
+ }
156
+
157
+ return await this.getDocument(collectionName, documentId, queryParams);
158
+ }
159
+ }
160
+
161
+ module.exports = SAM;
@@ -44,6 +44,15 @@ const NODE_CLIENT_ROUTE_COVERAGE = [
44
44
  { path: '/collections/{name}/vector_search', methods: ['GET', 'POST'], status: 'supported', client: 'search.vectorSearch' },
45
45
  { path: '/multi_search', methods: ['GET', 'POST'], status: 'supported', client: 'search.multiSearch' },
46
46
  { path: '/search', methods: ['GET', 'POST'], status: 'supported', client: 'search.globalSearch' },
47
+ { path: '/sql', methods: ['GET', 'POST'], status: 'supported', client: 'system.sql/execSql' },
48
+ { path: '/sam/rebuild', methods: ['POST'], status: 'supported', client: 'sam.rebuild' },
49
+ { path: '/sam/search', methods: ['GET'], status: 'supported', client: 'sam.search' },
50
+ { path: '/sam/status', methods: ['GET'], status: 'supported', client: 'sam.status' },
51
+ { path: '/sam/debug', methods: ['GET'], status: 'supported', client: 'sam.debug' },
52
+ { path: '/sam/history', methods: ['GET'], status: 'supported', client: 'sam.history' },
53
+ { path: '/sam/pause', methods: ['POST'], status: 'supported', client: 'sam.pause/clearPause' },
54
+ { path: '/sam/documents', methods: ['GET'], status: 'supported', client: 'sam.listDocuments' },
55
+ { path: '/sam/documents/{collection}/{id}', methods: ['GET'], status: 'supported', client: 'sam.getDocument/openDocument' },
47
56
  { path: '/collections/{name}/documents/facet_counts', methods: ['GET', 'POST'], status: 'supported', client: 'documents.facetCounts' },
48
57
  { path: '/collections/{name}/documents/export', methods: ['GET', 'POST'], status: 'supported', client: 'documents.export' },
49
58
  { path: '/collections/{name}/synonyms', methods: ['GET'], status: 'supported', client: 'synonyms.list' },
@@ -64,6 +73,10 @@ const NODE_CLIENT_ROUTE_COVERAGE = [
64
73
  { path: '/keys/{id}', methods: ['GET', 'PUT', 'DELETE'], status: 'supported', client: 'keys.get/update/delete' },
65
74
  { path: '/update-counters', methods: ['GET', 'POST'], status: 'omitted', reason: 'Internal maintenance endpoint; intentionally left off the public Node client.' },
66
75
  { path: '/query', methods: ['GET'], status: 'omitted', reason: 'Alias of /status; client exposes status() instead of duplicate naming.' },
76
+ { path: '/search-config', methods: ['GET'], status: 'omitted', reason: 'Diagnostic configuration endpoint; stable client exposes raw executeRequest() for this route.' },
77
+ { path: '/ready', methods: ['GET'], status: 'omitted', reason: 'Readiness alias; client exposes startup() and bootStatus() instead of duplicate naming.' },
78
+ { path: '/llm', methods: ['GET', 'POST'], status: 'omitted', reason: 'LLM runtime endpoint is module/config dependent and not part of the stable SDK surface yet.' },
79
+ { path: '/repair', methods: ['POST'], status: 'omitted', reason: 'Administrative repair endpoint; intentionally left off the public Node client.' },
67
80
  { path: '/modules', methods: ['GET'], status: 'omitted', reason: 'Module APIs are intentionally omitted until the module surface is documented and stabilized for SDKs.' },
68
81
  { path: '/modules/{name}', methods: ['ANY'], status: 'omitted', reason: 'Module-specific routes are dynamic and intentionally not wrapped in the stable Node client.' },
69
82
  { path: '/modules/{name}/syntax', methods: ['GET'], status: 'omitted', reason: 'Module-specific routes are dynamic and intentionally not wrapped in the stable Node client.' }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hlquery-node-client",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Node.js client library for hlquery search engine",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
@@ -9,6 +9,7 @@
9
9
  "test:conformance": "node ../tests/test_node_client_conformance.js",
10
10
  "test:offline": "node test/offline-smoke.js",
11
11
  "example": "node example.js",
12
+ "example:sql": "node examples/sql.js",
12
13
  "example:pdf": "node examples/pdf.js",
13
14
  "example:csv": "node examples/csv.js"
14
15
  },
@@ -28,15 +29,6 @@
28
29
  },
29
30
  "repository": {
30
31
  "type": "git",
31
- "url": "git+https://github.com/cferry/hlquery.git"
32
- },
33
- "directories": {
34
- "example": "examples",
35
- "lib": "lib",
36
- "test": "test"
37
- },
38
- "bugs": {
39
- "url": "https://github.com/cferry/hlquery/issues"
40
- },
41
- "homepage": "https://github.com/cferry/hlquery#readme"
32
+ "url": "https://github.com/cferry/hlquery"
33
+ }
42
34
  }
@@ -36,6 +36,48 @@ async function main() {
36
36
  return;
37
37
  }
38
38
 
39
+ if (req.url === '/status') {
40
+ res.writeHead(200, { 'Content-Type': 'application/json' });
41
+ res.end(JSON.stringify({ status: 'ready' }));
42
+ return;
43
+ }
44
+
45
+ if (req.url === '/etc') {
46
+ res.writeHead(200, { 'Content-Type': 'application/json' });
47
+ res.end(JSON.stringify({ protocol: 'http' }));
48
+ return;
49
+ }
50
+
51
+ if (req.url === '/links') {
52
+ res.writeHead(200, { 'Content-Type': 'application/json' });
53
+ res.end(JSON.stringify({ links: [] }));
54
+ return;
55
+ }
56
+
57
+ if (req.url === '/links/ping') {
58
+ res.writeHead(200, { 'Content-Type': 'application/json' });
59
+ res.end(JSON.stringify({ ok: true }));
60
+ return;
61
+ }
62
+
63
+ if (req.url === '/links/connect' && req.method === 'POST') {
64
+ res.writeHead(200, { 'Content-Type': 'application/json' });
65
+ res.end(JSON.stringify({ connected: true }));
66
+ return;
67
+ }
68
+
69
+ if (req.url === '/links/disconnect' && req.method === 'POST') {
70
+ res.writeHead(200, { 'Content-Type': 'application/json' });
71
+ res.end(JSON.stringify({ disconnected: true }));
72
+ return;
73
+ }
74
+
75
+ if (req.url === '/flush' && req.method === 'POST') {
76
+ res.writeHead(200, { 'Content-Type': 'application/json' });
77
+ res.end(JSON.stringify({ flushed: true }));
78
+ return;
79
+ }
80
+
39
81
  if (req.url === '/echo-auth') {
40
82
  res.writeHead(200, { 'Content-Type': 'application/json' });
41
83
  res.end(JSON.stringify({
@@ -73,6 +115,43 @@ async function main() {
73
115
  const clientHealth = await client.health();
74
116
  assert.strictEqual(clientHealth.isSuccess(), true);
75
117
  assert.deepStrictEqual(clientHealth.getBody(), { status: 'ok' });
118
+
119
+ const clientStatus = await client.status();
120
+ assert.deepStrictEqual(clientStatus.getBody(), { status: 'ready' });
121
+
122
+ const clientEtc = await client.etc();
123
+ assert.deepStrictEqual(clientEtc.getBody(), { protocol: 'http' });
124
+
125
+ const clientLinks = await client.links();
126
+ assert.deepStrictEqual(clientLinks.getBody(), { links: [] });
127
+
128
+ const clientLinksPing = await client.linksPing();
129
+ assert.deepStrictEqual(clientLinksPing.getBody(), { ok: true });
130
+
131
+ const clientLinksConnect = await client.linksConnect('http://node-b:9200');
132
+ assert.deepStrictEqual(clientLinksConnect.getBody(), { connected: true });
133
+
134
+ const clientLinksDisconnect = await client.linksDisconnect('http://node-b:9200');
135
+ assert.deepStrictEqual(clientLinksDisconnect.getBody(), { disconnected: true });
136
+
137
+ const clientFlush = await client.flush();
138
+ assert.deepStrictEqual(clientFlush.getBody(), { flushed: true });
139
+
140
+ assert.strictEqual(typeof client.executeRequest, 'function');
141
+ assert.strictEqual(typeof client.sql, 'function');
142
+ assert.strictEqual(typeof client.execSql, 'function');
143
+ assert.strictEqual(typeof client.sqlSearch, 'function');
144
+ assert.strictEqual(typeof client.sam().search, 'function');
145
+ assert.strictEqual(typeof client.sam().searchAll, 'function');
146
+ assert.strictEqual(typeof client.sam().rebuild, 'function');
147
+ assert.strictEqual(typeof client.sam().status, 'function');
148
+ assert.strictEqual(typeof client.sam().debug, 'function');
149
+ assert.strictEqual(typeof client.sam().history, 'function');
150
+ assert.strictEqual(typeof client.sam().pause, 'function');
151
+ assert.strictEqual(typeof client.sam().clearPause, 'function');
152
+ assert.strictEqual(typeof client.sam().listDocuments, 'function');
153
+ assert.strictEqual(typeof client.sam().getDocument, 'function');
154
+ assert.strictEqual(typeof client.sam().openDocument, 'function');
76
155
  } finally {
77
156
  await new Promise((resolve) => server.close(resolve));
78
157
  }