hlquery-node-client 1.0.1 → 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
@@ -22,10 +22,9 @@ It is a good fit for backend services, scripts, dashboards, and apps that want h
22
22
 
23
23
  ### Why use it?
24
24
 
25
- - Familiar modular layout such as `client.collections()`, `client.documents()`, and `client.sam()`.
26
- - Consistent auth, params, and parsed responses.
27
- - Good default coverage for common hlquery workflows.
28
- - Raw request access for custom routes.
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.
29
28
 
30
29
  ### Why choose it over raw HTTP?
31
30
 
@@ -34,7 +33,7 @@ Choose the Node.js client over raw HTTP when you want less repetitive `fetch` or
34
33
  ### Install
35
34
 
36
35
  ```bash
37
- npm install hlquery-node-client
36
+ $ npm install hlquery-node-client
38
37
  ```
39
38
 
40
39
  For local development inside this repository:
@@ -54,9 +53,11 @@ const client = new Client(process.env.HLQ_BASE_URL || process.env.HLQUERY_BASE_U
54
53
  });
55
54
 
56
55
  const health = await client.system().health();
56
+ /* Print the HTTP status code from the health response. */
57
57
  console.log('status:', health.getStatusCode());
58
58
 
59
59
  const collections = await client.collections().list(0, 10);
60
+ /* Print the collection list response body. */
60
61
  console.log(collections.getBody());
61
62
  ```
62
63
 
@@ -72,6 +73,50 @@ client.setAuthToken('your_token_here', 'bearer');
72
73
  client.setAuthToken('your_api_key_here', 'api-key');
73
74
  ```
74
75
 
76
+ ### Operational routes
77
+
78
+ The client includes wrappers for the common server and cluster routes used by dashboards, scripts, and maintenance jobs:
79
+
80
+ ```javascript
81
+ const status = await client.status();
82
+ const health = await client.health();
83
+ const etc = await client.etc();
84
+
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');
89
+
90
+ const flush = await client.flush();
91
+ ```
92
+
93
+ Routes that do not have a dedicated wrapper can still be called through `executeRequest()`:
94
+
95
+ ```javascript
96
+ const response = await client.executeRequest('GET', '/modules/<name>/<route>', null, {
97
+ q: 'example query'
98
+ });
99
+
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());
118
+ ```
119
+
75
120
  ### SAM
76
121
 
77
122
  SAM is separate from vector search. It performs term and intent-style lookup, not vector similarity search.
@@ -85,23 +130,31 @@ const results = await sam.search('books', 'distributed systems', {
85
130
  limit: 10
86
131
  });
87
132
 
133
+ /* Print the SAM status response body. */
88
134
  console.log(status.getBody());
135
+ /* Print the SAM search history response body. */
89
136
  console.log(history.getBody());
137
+ /* Print the SAM search results response body. */
90
138
  console.log(results.getBody());
91
139
  ```
92
140
 
93
141
  ### SQL
94
142
 
95
143
  ```javascript
96
- const sql = client.sql();
97
-
98
- const rows = await sql.query('SHOW COLLECTIONS;');
99
- const books = await sql.search(
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(
100
149
  'books',
101
150
  'SELECT id, title FROM books ORDER BY title ASC LIMIT 3;'
102
151
  );
103
152
 
153
+ /* Print the SQL query response body. */
104
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. */
105
158
  console.log(books.getBody());
106
159
  ```
107
160
 
@@ -114,6 +167,7 @@ const response = await client.executeRequest('GET', '/modules/<name>/<route>', n
114
167
  q: 'example query'
115
168
  });
116
169
 
170
+ /* Print the custom module route response body. */
117
171
  console.log(response.getBody());
118
172
  ```
119
173
 
package/lib/Client.js CHANGED
@@ -473,6 +473,50 @@ class Client {
473
473
  return this._sam;
474
474
  }
475
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
+
476
520
  /**
477
521
  * Get aliases API instance
478
522
  *
package/lib/SAM.js CHANGED
@@ -16,28 +16,51 @@ class SAM {
16
16
  this.request = request;
17
17
  }
18
18
 
19
- async search(collectionName, query, params = {}) {
20
- Validator.validateCollectionName(collectionName);
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
+ }
21
24
 
25
+ async search(collectionName, query, params = {}) {
22
26
  if (typeof query !== 'string' || query.trim() === '') {
23
27
  throw new Error('SAM query must be a non-empty string');
24
28
  }
25
29
 
26
- if (params === null || typeof params !== 'object' || Array.isArray(params)) {
27
- throw new Error('SAM search params must be an object');
28
- }
30
+ this._validateParams(params, 'SAM search params');
29
31
 
30
- return await this.request.execute('GET', '/sam/search', null, {
32
+ const queryParams = {
31
33
  ...params,
32
- collection: collectionName,
33
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
34
59
  });
35
60
  }
36
61
 
37
62
  async status(collectionName = null, params = {}) {
38
- if (params === null || typeof params !== 'object' || Array.isArray(params)) {
39
- throw new Error('SAM status params must be an object');
40
- }
63
+ this._validateParams(params, 'SAM status params');
41
64
 
42
65
  const queryParams = { ...params };
43
66
 
@@ -49,11 +72,22 @@ class SAM {
49
72
  return await this.request.execute('GET', '/sam/status', null, queryParams);
50
73
  }
51
74
 
52
- async history(collectionName = null, limit = 100, params = {}) {
53
- if (params === null || typeof params !== 'object' || Array.isArray(params)) {
54
- throw new Error('SAM history params must be an object');
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;
55
83
  }
56
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
+
57
91
  const queryParams = {
58
92
  ...params,
59
93
  limit: Number(limit)
@@ -66,6 +100,62 @@ class SAM {
66
100
 
67
101
  return await this.request.execute('GET', '/sam/history', null, queryParams);
68
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
+ }
69
159
  }
70
160
 
71
161
  module.exports = SAM;
@@ -44,9 +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' },
47
49
  { path: '/sam/search', methods: ['GET'], status: 'supported', client: 'sam.search' },
48
50
  { path: '/sam/status', methods: ['GET'], status: 'supported', client: 'sam.status' },
51
+ { path: '/sam/debug', methods: ['GET'], status: 'supported', client: 'sam.debug' },
49
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' },
50
56
  { path: '/collections/{name}/documents/facet_counts', methods: ['GET', 'POST'], status: 'supported', client: 'documents.facetCounts' },
51
57
  { path: '/collections/{name}/documents/export', methods: ['GET', 'POST'], status: 'supported', client: 'documents.export' },
52
58
  { path: '/collections/{name}/synonyms', methods: ['GET'], status: 'supported', client: 'synonyms.list' },
@@ -67,6 +73,10 @@ const NODE_CLIENT_ROUTE_COVERAGE = [
67
73
  { path: '/keys/{id}', methods: ['GET', 'PUT', 'DELETE'], status: 'supported', client: 'keys.get/update/delete' },
68
74
  { path: '/update-counters', methods: ['GET', 'POST'], status: 'omitted', reason: 'Internal maintenance endpoint; intentionally left off the public Node client.' },
69
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.' },
70
80
  { path: '/modules', methods: ['GET'], status: 'omitted', reason: 'Module APIs are intentionally omitted until the module surface is documented and stabilized for SDKs.' },
71
81
  { path: '/modules/{name}', methods: ['ANY'], status: 'omitted', reason: 'Module-specific routes are dynamic and intentionally not wrapped in the stable Node client.' },
72
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.1",
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",
@@ -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
  }