hlquery-node-client 1.0.4 → 1.0.6
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 +35 -26
- package/index.js +4 -0
- package/lib/Aliases.js +5 -0
- package/lib/Analytics.js +30 -0
- package/lib/Client.js +122 -3
- package/lib/Collections.js +36 -3
- package/lib/Documents.js +98 -6
- package/lib/Exceptions.js +11 -3
- package/lib/Modules.js +64 -0
- package/lib/Request.js +150 -15
- package/lib/Response.js +28 -2
- package/lib/Route.js +111 -0
- package/lib/SAM.js +25 -0
- package/lib/Search.js +13 -7
- package/lib/System.js +32 -0
- package/lib/Users.js +51 -0
- package/lib/conformance.js +29 -8
- package/package.json +1 -1
- package/test/offline-smoke.js +263 -2
- package/utils/Config.js +17 -2
- package/utils/Validator.js +12 -29
package/lib/Request.js
CHANGED
|
@@ -16,11 +16,26 @@ const Response = require('./Response');
|
|
|
16
16
|
* HTTP request handler
|
|
17
17
|
*/
|
|
18
18
|
class Request {
|
|
19
|
-
constructor(baseUrl, timeout = 30000, authToken = null, authMethod = 'bearer') {
|
|
20
|
-
|
|
19
|
+
constructor(baseUrl, timeout = 30000, authToken = null, authMethod = 'bearer', options = {}) {
|
|
20
|
+
if (timeout && typeof timeout === 'object') {
|
|
21
|
+
options = timeout;
|
|
22
|
+
timeout = options.timeout || 30000;
|
|
23
|
+
authToken = options.token || null;
|
|
24
|
+
authMethod = options.auth_method || options.authMethod || 'bearer';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
this.baseUrl = baseUrl.replace(/\/+$/, '');
|
|
21
28
|
this.timeout = timeout;
|
|
22
29
|
this.authToken = authToken;
|
|
23
30
|
this.authMethod = authMethod;
|
|
31
|
+
this.agent = options.agent || null;
|
|
32
|
+
this.httpAgent = options.http_agent || options.httpAgent || null;
|
|
33
|
+
this.httpsAgent = options.https_agent || options.httpsAgent || null;
|
|
34
|
+
this.defaultHeaders = options.headers || {};
|
|
35
|
+
this.maxResponseBytes = options.max_response_bytes || options.maxResponseBytes || 0;
|
|
36
|
+
this.throwOnError = Boolean(options.throw_on_error || options.throwOnError);
|
|
37
|
+
this.queryArrayFormat = options.query_array_format || options.queryArrayFormat || 'comma';
|
|
38
|
+
this.signal = options.signal || null;
|
|
24
39
|
}
|
|
25
40
|
|
|
26
41
|
setAuthToken(token, method = 'bearer') {
|
|
@@ -42,8 +57,21 @@ class Request {
|
|
|
42
57
|
* @returns {Promise<Response>}
|
|
43
58
|
* @throws {RequestException}
|
|
44
59
|
*/
|
|
45
|
-
async execute(method, path, body = null, queryParams = {}) {
|
|
46
|
-
const url = new URL(this.baseUrl
|
|
60
|
+
async execute(method, path, body = null, queryParams = {}, requestOptions = {}) {
|
|
61
|
+
const url = new URL(path, `${this.baseUrl}/`);
|
|
62
|
+
const signal = requestOptions.signal || this.signal;
|
|
63
|
+
const maxResponseBytes = requestOptions.max_response_bytes || requestOptions.maxResponseBytes || this.maxResponseBytes;
|
|
64
|
+
const throwOnError = requestOptions.throw_on_error !== undefined
|
|
65
|
+
? requestOptions.throw_on_error
|
|
66
|
+
: (requestOptions.throwOnError !== undefined ? requestOptions.throwOnError : this.throwOnError);
|
|
67
|
+
const queryArrayFormat = requestOptions.query_array_format || requestOptions.queryArrayFormat || this.queryArrayFormat;
|
|
68
|
+
|
|
69
|
+
if (signal && signal.aborted) {
|
|
70
|
+
throw new RequestException('Request aborted', 0, null, {
|
|
71
|
+
code: 'ABORT_ERR',
|
|
72
|
+
retryable: false
|
|
73
|
+
});
|
|
74
|
+
}
|
|
47
75
|
|
|
48
76
|
// Add query parameters
|
|
49
77
|
Object.keys(queryParams).forEach(key => {
|
|
@@ -53,7 +81,11 @@ class Request {
|
|
|
53
81
|
}
|
|
54
82
|
|
|
55
83
|
if (Array.isArray(value)) {
|
|
56
|
-
|
|
84
|
+
if (queryArrayFormat === 'repeat' || queryArrayFormat === 'repeated') {
|
|
85
|
+
value.forEach(item => url.searchParams.append(key, item));
|
|
86
|
+
} else {
|
|
87
|
+
url.searchParams.append(key, value.join(','));
|
|
88
|
+
}
|
|
57
89
|
return;
|
|
58
90
|
}
|
|
59
91
|
|
|
@@ -66,7 +98,9 @@ class Request {
|
|
|
66
98
|
});
|
|
67
99
|
|
|
68
100
|
const headers = {
|
|
69
|
-
'Accept': 'application/json'
|
|
101
|
+
'Accept': 'application/json',
|
|
102
|
+
...this.defaultHeaders,
|
|
103
|
+
...(requestOptions.headers || {})
|
|
70
104
|
};
|
|
71
105
|
|
|
72
106
|
// Prepare body string if present
|
|
@@ -89,32 +123,112 @@ class Request {
|
|
|
89
123
|
const options = {
|
|
90
124
|
method: method,
|
|
91
125
|
headers: headers,
|
|
92
|
-
timeout: this.timeout
|
|
126
|
+
timeout: requestOptions.timeout || this.timeout
|
|
93
127
|
};
|
|
94
128
|
|
|
95
129
|
const client = url.protocol === 'https:' ? https : http;
|
|
130
|
+
const agent = this.agent || (url.protocol === 'https:' ? this.httpsAgent : this.httpAgent);
|
|
131
|
+
if (agent) {
|
|
132
|
+
options.agent = agent;
|
|
133
|
+
}
|
|
96
134
|
|
|
97
135
|
return new Promise((resolve, reject) => {
|
|
98
|
-
|
|
136
|
+
let settled = false;
|
|
137
|
+
let req = null;
|
|
138
|
+
const rejectOnce = (error) => {
|
|
139
|
+
if (settled) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
settled = true;
|
|
143
|
+
if (signal && abortHandler && typeof signal.removeEventListener === 'function') {
|
|
144
|
+
signal.removeEventListener('abort', abortHandler);
|
|
145
|
+
} else if (signal && abortHandler && typeof signal.removeListener === 'function') {
|
|
146
|
+
signal.removeListener('abort', abortHandler);
|
|
147
|
+
}
|
|
148
|
+
reject(error);
|
|
149
|
+
};
|
|
150
|
+
const resolveOnce = (response) => {
|
|
151
|
+
if (settled) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
settled = true;
|
|
155
|
+
if (signal && abortHandler && typeof signal.removeEventListener === 'function') {
|
|
156
|
+
signal.removeEventListener('abort', abortHandler);
|
|
157
|
+
} else if (signal && abortHandler && typeof signal.removeListener === 'function') {
|
|
158
|
+
signal.removeListener('abort', abortHandler);
|
|
159
|
+
}
|
|
160
|
+
resolve(response);
|
|
161
|
+
};
|
|
162
|
+
const abortHandler = () => {
|
|
163
|
+
if (req) {
|
|
164
|
+
req.destroy();
|
|
165
|
+
}
|
|
166
|
+
rejectOnce(new RequestException('Request aborted', 0, null, {
|
|
167
|
+
code: 'ABORT_ERR',
|
|
168
|
+
retryable: false
|
|
169
|
+
}));
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
if (signal && typeof signal.addEventListener === 'function') {
|
|
173
|
+
signal.addEventListener('abort', abortHandler, { once: true });
|
|
174
|
+
} else if (signal && typeof signal.once === 'function') {
|
|
175
|
+
signal.once('abort', abortHandler);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
req = client.request(url, options, (res) => {
|
|
99
179
|
const responseHeaders = {};
|
|
100
180
|
Object.keys(res.headers).forEach(key => {
|
|
101
181
|
responseHeaders[key.toLowerCase()] = res.headers[key];
|
|
102
182
|
});
|
|
103
183
|
|
|
104
|
-
|
|
184
|
+
const chunks = [];
|
|
185
|
+
let receivedBytes = 0;
|
|
105
186
|
|
|
106
187
|
res.on('data', (chunk) => {
|
|
107
|
-
|
|
188
|
+
receivedBytes += chunk.length;
|
|
189
|
+
if (maxResponseBytes > 0 && receivedBytes > maxResponseBytes) {
|
|
190
|
+
res.destroy();
|
|
191
|
+
rejectOnce(new RequestException(
|
|
192
|
+
`Response exceeded maximum size of ${maxResponseBytes} bytes`,
|
|
193
|
+
res.statusCode,
|
|
194
|
+
null,
|
|
195
|
+
{
|
|
196
|
+
headers: responseHeaders,
|
|
197
|
+
retryable: false,
|
|
198
|
+
code: 'MAX_RESPONSE_BYTES'
|
|
199
|
+
}
|
|
200
|
+
));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
chunks.push(chunk);
|
|
108
204
|
});
|
|
109
205
|
|
|
110
206
|
res.on('end', () => {
|
|
207
|
+
if (settled) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const rawBuffer = Buffer.concat(chunks);
|
|
211
|
+
const data = rawBuffer.toString('utf8');
|
|
111
212
|
let decoded;
|
|
213
|
+
let parsed = true;
|
|
112
214
|
try {
|
|
113
215
|
decoded = data ? JSON.parse(data) : null;
|
|
114
216
|
} catch (e) {
|
|
115
217
|
// If JSON parsing fails, return raw response
|
|
116
218
|
decoded = data;
|
|
219
|
+
parsed = false;
|
|
117
220
|
}
|
|
221
|
+
|
|
222
|
+
const response = new Response(res.statusCode, decoded, responseHeaders, {
|
|
223
|
+
statusMessage: res.statusMessage || '',
|
|
224
|
+
rawBody: rawBuffer,
|
|
225
|
+
parsed: parsed,
|
|
226
|
+
request: {
|
|
227
|
+
method: method,
|
|
228
|
+
url: url.toString(),
|
|
229
|
+
path: path
|
|
230
|
+
}
|
|
231
|
+
});
|
|
118
232
|
|
|
119
233
|
// Check for demo mode errors
|
|
120
234
|
if (res.statusCode === 403 && decoded && typeof decoded === 'object') {
|
|
@@ -122,30 +236,51 @@ class Request {
|
|
|
122
236
|
if (errorMsg.includes('Demo mode is enabled') ||
|
|
123
237
|
errorMsg.includes('Operation not allowed') ||
|
|
124
238
|
errorMsg.includes('Write operations are not allowed')) {
|
|
125
|
-
return
|
|
239
|
+
return rejectOnce(new DemoModeException(
|
|
126
240
|
`Demo mode is enabled on the server. Write operations are blocked. Only search and read operations are permitted. Server message: ${decoded.message || decoded.error}`
|
|
127
241
|
));
|
|
128
242
|
}
|
|
129
243
|
// Check if server rejected token because auth is disabled
|
|
130
244
|
if (errorMsg.includes('Authentication is disabled') ||
|
|
131
245
|
errorMsg.includes('Tokens are not accepted when authentication is disabled')) {
|
|
132
|
-
return
|
|
246
|
+
return rejectOnce(new AuthenticationException(
|
|
133
247
|
`Authentication is disabled on the server. Remove the token from your client configuration. Server message: ${decoded.message || decoded.error}`
|
|
134
248
|
));
|
|
135
249
|
}
|
|
136
250
|
}
|
|
251
|
+
|
|
252
|
+
if (throwOnError && response.isError()) {
|
|
253
|
+
const errorMessage = response.getError() ||
|
|
254
|
+
`${res.statusCode} ${res.statusMessage || 'HTTP error'}`;
|
|
255
|
+
return rejectOnce(new RequestException(errorMessage, res.statusCode, decoded, {
|
|
256
|
+
response: response,
|
|
257
|
+
headers: responseHeaders,
|
|
258
|
+
rawBody: rawBuffer,
|
|
259
|
+
retryable: res.statusCode >= 500
|
|
260
|
+
}));
|
|
261
|
+
}
|
|
137
262
|
|
|
138
|
-
|
|
263
|
+
resolveOnce(response);
|
|
139
264
|
});
|
|
140
265
|
});
|
|
141
266
|
|
|
142
267
|
req.on('error', (error) => {
|
|
143
|
-
|
|
268
|
+
if (settled) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
rejectOnce(new RequestException(`Request failed: ${error.message}`, 0, null, {
|
|
272
|
+
cause: error,
|
|
273
|
+
retryable: true,
|
|
274
|
+
code: error.code || null
|
|
275
|
+
}));
|
|
144
276
|
});
|
|
145
277
|
|
|
146
278
|
req.on('timeout', () => {
|
|
147
279
|
req.destroy();
|
|
148
|
-
|
|
280
|
+
rejectOnce(new RequestException('Request timeout', 0, null, {
|
|
281
|
+
retryable: true,
|
|
282
|
+
code: 'ETIMEDOUT'
|
|
283
|
+
}));
|
|
149
284
|
});
|
|
150
285
|
|
|
151
286
|
if (bodyStr !== null) {
|
package/lib/Response.js
CHANGED
|
@@ -10,10 +10,15 @@
|
|
|
10
10
|
* Response wrapper for API responses
|
|
11
11
|
*/
|
|
12
12
|
class Response {
|
|
13
|
-
constructor(statusCode, body, headers = {}) {
|
|
13
|
+
constructor(statusCode, body, headers = {}, options = {}) {
|
|
14
14
|
this.statusCode = statusCode;
|
|
15
15
|
this.body = body;
|
|
16
16
|
this.headers = headers;
|
|
17
|
+
this.statusMessage = options.statusMessage || '';
|
|
18
|
+
this.rawBody = options.rawBody || null;
|
|
19
|
+
this.request = options.request || null;
|
|
20
|
+
this.contentType = headers['content-type'] || headers['Content-Type'] || null;
|
|
21
|
+
this.parsed = options.parsed !== undefined ? options.parsed : true;
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
getStatusCode() {
|
|
@@ -27,6 +32,26 @@ class Response {
|
|
|
27
32
|
getHeaders() {
|
|
28
33
|
return this.headers;
|
|
29
34
|
}
|
|
35
|
+
|
|
36
|
+
getStatusMessage() {
|
|
37
|
+
return this.statusMessage;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getRawBody() {
|
|
41
|
+
return this.rawBody;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
getRequest() {
|
|
45
|
+
return this.request;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
getContentType() {
|
|
49
|
+
return this.contentType;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
isParsed() {
|
|
53
|
+
return this.parsed;
|
|
54
|
+
}
|
|
30
55
|
|
|
31
56
|
isSuccess() {
|
|
32
57
|
return this.statusCode >= 200 && this.statusCode < 300;
|
|
@@ -46,7 +71,8 @@ class Response {
|
|
|
46
71
|
toArray() {
|
|
47
72
|
return {
|
|
48
73
|
status: this.statusCode,
|
|
49
|
-
body: this.body
|
|
74
|
+
body: this.body,
|
|
75
|
+
headers: this.headers
|
|
50
76
|
};
|
|
51
77
|
}
|
|
52
78
|
}
|
package/lib/Route.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hlquery Node.js Client - Fluent Route 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
|
+
function requireNonEmptyString(value, label) {
|
|
10
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
11
|
+
throw new Error(`${label} must be a non-empty string`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function cleanRoute(route) {
|
|
16
|
+
if (route === null || route === undefined) {
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (Array.isArray(route)) {
|
|
21
|
+
return route
|
|
22
|
+
.map(segment => {
|
|
23
|
+
requireNonEmptyString(String(segment), 'Route segment');
|
|
24
|
+
return encodeURIComponent(String(segment).replace(/^\/+|\/+$/g, ''));
|
|
25
|
+
})
|
|
26
|
+
.filter(Boolean)
|
|
27
|
+
.join('/');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return String(route).replace(/^\/+|\/+$/g, '');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function joinPath(basePath, route) {
|
|
34
|
+
const base = String(basePath || '').replace(/\/+$/g, '');
|
|
35
|
+
const suffix = cleanRoute(route);
|
|
36
|
+
|
|
37
|
+
if (!suffix) {
|
|
38
|
+
return base || '/';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!base || base === '/') {
|
|
42
|
+
return `/${suffix}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return `${base}/${suffix}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Small Redis-style command builder for dynamic hlquery routes.
|
|
50
|
+
*/
|
|
51
|
+
class RouteCommand {
|
|
52
|
+
constructor(request, path) {
|
|
53
|
+
this.request = request;
|
|
54
|
+
this.path = path || '/';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
route(route) {
|
|
58
|
+
return new RouteCommand(this.request, joinPath(this.path, route));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async call(method, body = null, queryParams = {}, options = {}) {
|
|
62
|
+
requireNonEmptyString(method, 'HTTP method');
|
|
63
|
+
return await this.request.execute(method.toUpperCase(), this.path, body, queryParams, options);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async get(queryParams = {}, options = {}) {
|
|
67
|
+
return await this.call('GET', null, queryParams, options);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async post(body = null, queryParams = {}, options = {}) {
|
|
71
|
+
return await this.call('POST', body, queryParams, options);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async put(body = null, queryParams = {}, options = {}) {
|
|
75
|
+
return await this.call('PUT', body, queryParams, options);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async patch(body = null, queryParams = {}, options = {}) {
|
|
79
|
+
return await this.call('PATCH', body, queryParams, options);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async delete(queryParams = {}, options = {}) {
|
|
83
|
+
return await this.call('DELETE', null, queryParams, options);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async del(queryParams = {}, options = {}) {
|
|
87
|
+
return await this.delete(queryParams, options);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Fluent command builder rooted at /modules/{name}.
|
|
93
|
+
*/
|
|
94
|
+
class ModuleRouteCommand extends RouteCommand {
|
|
95
|
+
constructor(request, name) {
|
|
96
|
+
requireNonEmptyString(name, 'Module name');
|
|
97
|
+
super(request, `/modules/${encodeURIComponent(name)}`);
|
|
98
|
+
this.name = name;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async syntax(queryParams = {}, options = {}) {
|
|
102
|
+
return await this.route('syntax').get(queryParams, options);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = {
|
|
107
|
+
RouteCommand,
|
|
108
|
+
ModuleRouteCommand,
|
|
109
|
+
cleanRoute,
|
|
110
|
+
joinPath
|
|
111
|
+
};
|
package/lib/SAM.js
CHANGED
|
@@ -123,6 +123,15 @@ class SAM {
|
|
|
123
123
|
return await this.pause(0, params);
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
async improve(params = {}) {
|
|
127
|
+
this._validateParams(params, 'SAM improve params');
|
|
128
|
+
return await this.request.execute('POST', '/sam/improve', null, params);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async flushActorMetadata() {
|
|
132
|
+
return await this.request.execute('POST', '/sam/flush_actor_metadata');
|
|
133
|
+
}
|
|
134
|
+
|
|
126
135
|
async listDocuments(collectionName, offset = 0, limit = 20, params = {}) {
|
|
127
136
|
Validator.validateCollectionName(collectionName);
|
|
128
137
|
this._validateParams(params, 'SAM document list params');
|
|
@@ -169,6 +178,22 @@ class SAM {
|
|
|
169
178
|
|
|
170
179
|
return await this.getDocument(collectionName, documentId, queryParams);
|
|
171
180
|
}
|
|
181
|
+
|
|
182
|
+
async addDocumentLabel(collectionName, documentId, labels) {
|
|
183
|
+
Validator.validateCollectionName(collectionName);
|
|
184
|
+
Validator.validateDocumentId(documentId);
|
|
185
|
+
|
|
186
|
+
const normalizedLabels = Array.isArray(labels) ? labels : [labels];
|
|
187
|
+
if (normalizedLabels.length === 0 || normalizedLabels.some(label => typeof label !== 'string' || label.trim() === '')) {
|
|
188
|
+
throw new Error('SAM document labels must contain at least one non-empty string');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return await this.request.execute(
|
|
192
|
+
'POST',
|
|
193
|
+
`/sam/label/add/${encodeURIComponent(collectionName)}/${encodeURIComponent(documentId)}`,
|
|
194
|
+
{ labels: normalizedLabels }
|
|
195
|
+
);
|
|
196
|
+
}
|
|
172
197
|
}
|
|
173
198
|
|
|
174
199
|
module.exports = SAM;
|
package/lib/Search.js
CHANGED
|
@@ -15,6 +15,7 @@ class Search {
|
|
|
15
15
|
constructor(request, collections) {
|
|
16
16
|
this.request = request;
|
|
17
17
|
this.collections = collections;
|
|
18
|
+
this.searchableFieldsCache = new Map();
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
/**
|
|
@@ -96,13 +97,18 @@ class Search {
|
|
|
96
97
|
queryParams.query_by = Array.isArray(params.query_by)
|
|
97
98
|
? params.query_by.join(',')
|
|
98
99
|
: params.query_by;
|
|
99
|
-
} else if (params.q && params.q !== '') {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
100
|
+
} else if (params.q && params.q !== '' && params.auto_detect_query_by !== false) {
|
|
101
|
+
const cacheKey = collectionName;
|
|
102
|
+
if (this.searchableFieldsCache.has(cacheKey)) {
|
|
103
|
+
queryParams.query_by = this.searchableFieldsCache.get(cacheKey);
|
|
104
|
+
} else {
|
|
105
|
+
const collection = await this.collections.get(collectionName);
|
|
106
|
+
if (collection.getStatusCode() === 200) {
|
|
107
|
+
const body = collection.getBody();
|
|
108
|
+
if (body.searchable_fields && body.searchable_fields.length > 0) {
|
|
109
|
+
queryParams.query_by = body.searchable_fields.join(',');
|
|
110
|
+
this.searchableFieldsCache.set(cacheKey, queryParams.query_by);
|
|
111
|
+
}
|
|
106
112
|
}
|
|
107
113
|
}
|
|
108
114
|
}
|
package/lib/System.js
CHANGED
|
@@ -30,6 +30,10 @@ class System {
|
|
|
30
30
|
return await this.request.execute('GET', '/boot-status');
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
async ready() {
|
|
34
|
+
return await this.request.execute('GET', '/ready');
|
|
35
|
+
}
|
|
36
|
+
|
|
33
37
|
async info() {
|
|
34
38
|
return await this.request.execute('GET', '/');
|
|
35
39
|
}
|
|
@@ -46,6 +50,14 @@ class System {
|
|
|
46
50
|
return await this.request.execute('GET', '/metrics.json');
|
|
47
51
|
}
|
|
48
52
|
|
|
53
|
+
async metricsHistory() {
|
|
54
|
+
return await this.request.execute('GET', '/metrics/history');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async metricsHistoryAlias() {
|
|
58
|
+
return await this.request.execute('GET', '/metrics-history');
|
|
59
|
+
}
|
|
60
|
+
|
|
49
61
|
async connections() {
|
|
50
62
|
return await this.request.execute('GET', '/connections');
|
|
51
63
|
}
|
|
@@ -105,6 +117,26 @@ class System {
|
|
|
105
117
|
async storageStatus() {
|
|
106
118
|
return await this.request.execute('GET', '/admin/storage_status');
|
|
107
119
|
}
|
|
120
|
+
|
|
121
|
+
async searchConfig() {
|
|
122
|
+
return await this.request.execute('GET', '/search-config');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async llm() {
|
|
126
|
+
return await this.request.execute('GET', '/llm');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async updateCounters(params = {}) {
|
|
130
|
+
return await this.request.execute('POST', '/update-counters', null, params);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async debugCounters() {
|
|
134
|
+
return await this.request.execute('GET', '/debug/counters');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async repair(params = {}) {
|
|
138
|
+
return await this.request.execute('POST', '/repair', null, params);
|
|
139
|
+
}
|
|
108
140
|
}
|
|
109
141
|
|
|
110
142
|
module.exports = System;
|
package/lib/Users.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hlquery Node.js Client - Users 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
|
+
class Users {
|
|
10
|
+
constructor(request) {
|
|
11
|
+
this.request = request;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
_validateName(name) {
|
|
15
|
+
if (typeof name !== 'string' || name.trim() === '') {
|
|
16
|
+
throw new Error('User name is required');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async list() {
|
|
21
|
+
return await this.request.execute('GET', '/users');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async get(name) {
|
|
25
|
+
this._validateName(name);
|
|
26
|
+
return await this.request.execute('GET', `/users/${encodeURIComponent(name)}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async create(params) {
|
|
30
|
+
if (!params || typeof params !== 'object' || Array.isArray(params)) {
|
|
31
|
+
throw new Error('User parameters must be an object');
|
|
32
|
+
}
|
|
33
|
+
this._validateName(params.name);
|
|
34
|
+
return await this.request.execute('POST', '/users', params);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async update(name, params) {
|
|
38
|
+
this._validateName(name);
|
|
39
|
+
if (!params || typeof params !== 'object' || Array.isArray(params)) {
|
|
40
|
+
throw new Error('User parameters must be an object');
|
|
41
|
+
}
|
|
42
|
+
return await this.request.execute('PUT', `/users/${encodeURIComponent(name)}`, params);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async delete(name) {
|
|
46
|
+
this._validateName(name);
|
|
47
|
+
return await this.request.execute('DELETE', `/users/${encodeURIComponent(name)}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = Users;
|