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/README.md
CHANGED
|
@@ -22,7 +22,7 @@ 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
|
-
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()`,
|
|
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()`, with Redis-style dynamic route helpers for module APIs and custom endpoints.
|
|
26
26
|
|
|
27
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
28
|
|
|
@@ -49,12 +49,14 @@ const client = new Client(process.env.HLQ_BASE_URL || process.env.HLQUERY_BASE_U
|
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
const health = await client.system().health();
|
|
52
|
+
|
|
52
53
|
/* Print the HTTP status code from the health response. */
|
|
53
54
|
console.log('status:', health.getStatusCode());
|
|
54
55
|
|
|
55
56
|
const collections = await client.collections().list(0, 10);
|
|
57
|
+
|
|
56
58
|
/* Print the collection list response body. */
|
|
57
|
-
console.log(collections.
|
|
59
|
+
console.log(collections.body);
|
|
58
60
|
```
|
|
59
61
|
|
|
60
62
|
### Auth
|
|
@@ -93,24 +95,31 @@ const response = await client.executeRequest('GET', '/modules/<name>/<route>', n
|
|
|
93
95
|
q: 'example query'
|
|
94
96
|
});
|
|
95
97
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
98
|
+
console.log(response.body);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
For module routes and custom endpoints, the client also supports a Redis-style fluent API:
|
|
102
|
+
|
|
103
|
+
```javascript
|
|
104
|
+
const result = await client.module('<name>').route('<route>').get({
|
|
105
|
+
q: 'example query'
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
console.log(result.body);
|
|
109
|
+
|
|
110
|
+
const indexed = await client.module('<name>').route('index').post({
|
|
111
|
+
id: 'doc_1',
|
|
112
|
+
title: 'Example'
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const modules = await client.modules().list();
|
|
116
|
+
const syntax = await client.modules().syntax('<name>');
|
|
117
|
+
const raw = await client.route('etc').get();
|
|
118
|
+
|
|
119
|
+
console.log(indexed.body);
|
|
120
|
+
console.log(modules.body);
|
|
121
|
+
console.log(syntax.body);
|
|
122
|
+
console.log(raw.body);
|
|
114
123
|
```
|
|
115
124
|
|
|
116
125
|
### SAM
|
|
@@ -127,11 +136,11 @@ const results = await sam.search('books', 'distributed systems', {
|
|
|
127
136
|
});
|
|
128
137
|
|
|
129
138
|
/* Print the SAM status response body. */
|
|
130
|
-
console.log(status.
|
|
139
|
+
console.log(status.body);
|
|
131
140
|
/* Print the SAM search history response body. */
|
|
132
|
-
console.log(history.
|
|
141
|
+
console.log(history.body);
|
|
133
142
|
/* Print the SAM search results response body. */
|
|
134
|
-
console.log(results.
|
|
143
|
+
console.log(results.body);
|
|
135
144
|
```
|
|
136
145
|
|
|
137
146
|
### SQL
|
|
@@ -147,11 +156,11 @@ const books = await client.sqlSearch(
|
|
|
147
156
|
);
|
|
148
157
|
|
|
149
158
|
/* Print the SQL query response body. */
|
|
150
|
-
console.log(rows.
|
|
159
|
+
console.log(rows.body);
|
|
151
160
|
/* Print the SQL execution response body. */
|
|
152
|
-
console.log(execResult.
|
|
161
|
+
console.log(execResult.body);
|
|
153
162
|
/* Print the collection SQL search response body. */
|
|
154
|
-
console.log(books.
|
|
163
|
+
console.log(books.body);
|
|
155
164
|
```
|
|
156
165
|
|
|
157
166
|
### Contributing
|
package/index.js
CHANGED
|
@@ -25,6 +25,10 @@ module.exports.Synonyms = require('./lib/Synonyms');
|
|
|
25
25
|
module.exports.Stopwords = require('./lib/Stopwords');
|
|
26
26
|
module.exports.System = require('./lib/System');
|
|
27
27
|
module.exports.SAM = require('./lib/SAM');
|
|
28
|
+
module.exports.Modules = require('./lib/Modules');
|
|
29
|
+
module.exports.Users = require('./lib/Users');
|
|
30
|
+
module.exports.Analytics = require('./lib/Analytics');
|
|
31
|
+
module.exports.Route = require('./lib/Route');
|
|
28
32
|
|
|
29
33
|
// Export exceptions
|
|
30
34
|
module.exports.Exceptions = require('./lib/Exceptions');
|
package/lib/Aliases.js
CHANGED
|
@@ -20,6 +20,11 @@ class Aliases {
|
|
|
20
20
|
return await this.request.execute('GET', '/aliases');
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
async listCollection(collectionName) {
|
|
24
|
+
Validator.validateCollectionName(collectionName);
|
|
25
|
+
return await this.request.execute('GET', `/collections/${encodeURIComponent(collectionName)}/aliases`);
|
|
26
|
+
}
|
|
27
|
+
|
|
23
28
|
async get(name) {
|
|
24
29
|
Validator.validateCollectionName(name);
|
|
25
30
|
return await this.request.execute('GET', `/aliases/${encodeURIComponent(name)}`);
|
package/lib/Analytics.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hlquery Node.js Client - Analytics 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
|
+
class Analytics {
|
|
12
|
+
constructor(request) {
|
|
13
|
+
this.request = request;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async click(payload) {
|
|
17
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
18
|
+
throw new Error('Analytics click payload must be an object');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
Validator.validateCollectionName(payload.collection);
|
|
22
|
+
|
|
23
|
+
const documentId = payload.doc_id !== undefined ? payload.doc_id : payload.document_id;
|
|
24
|
+
Validator.validateDocumentId(documentId);
|
|
25
|
+
|
|
26
|
+
return await this.request.execute('POST', '/analytics/click', payload);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = Analytics;
|
package/lib/Client.js
CHANGED
|
@@ -18,6 +18,10 @@ const Synonyms = require('./Synonyms');
|
|
|
18
18
|
const Stopwords = require('./Stopwords');
|
|
19
19
|
const System = require('./System');
|
|
20
20
|
const SAM = require('./SAM');
|
|
21
|
+
const Modules = require('./Modules');
|
|
22
|
+
const Users = require('./Users');
|
|
23
|
+
const Analytics = require('./Analytics');
|
|
24
|
+
const { RouteCommand, ModuleRouteCommand } = require('./Route');
|
|
21
25
|
const Config = require('../utils/Config');
|
|
22
26
|
|
|
23
27
|
/**
|
|
@@ -38,7 +42,8 @@ class Client {
|
|
|
38
42
|
baseUrl,
|
|
39
43
|
opts.timeout,
|
|
40
44
|
opts.token || null,
|
|
41
|
-
opts.auth_method || 'bearer'
|
|
45
|
+
opts.auth_method || 'bearer',
|
|
46
|
+
opts
|
|
42
47
|
);
|
|
43
48
|
|
|
44
49
|
this._collections = new Collections(this.request);
|
|
@@ -51,6 +56,9 @@ class Client {
|
|
|
51
56
|
this._stopwords = new Stopwords(this.request);
|
|
52
57
|
this._system = new System(this.request);
|
|
53
58
|
this._sam = new SAM(this.request);
|
|
59
|
+
this._modules = new Modules(this.request);
|
|
60
|
+
this._users = new Users(this.request);
|
|
61
|
+
this._analytics = new Analytics(this.request);
|
|
54
62
|
}
|
|
55
63
|
|
|
56
64
|
/**
|
|
@@ -143,6 +151,10 @@ class Client {
|
|
|
143
151
|
return await this._system.bootStatus();
|
|
144
152
|
}
|
|
145
153
|
|
|
154
|
+
async ready() {
|
|
155
|
+
return await this._system.ready();
|
|
156
|
+
}
|
|
157
|
+
|
|
146
158
|
/**
|
|
147
159
|
* Get metrics.
|
|
148
160
|
*
|
|
@@ -161,6 +173,10 @@ class Client {
|
|
|
161
173
|
return await this._system.metricsJson();
|
|
162
174
|
}
|
|
163
175
|
|
|
176
|
+
async metricsHistory() {
|
|
177
|
+
return await this._system.metricsHistory();
|
|
178
|
+
}
|
|
179
|
+
|
|
164
180
|
/**
|
|
165
181
|
* Get live connections.
|
|
166
182
|
*
|
|
@@ -242,6 +258,26 @@ class Client {
|
|
|
242
258
|
return await this._system.storageStatus();
|
|
243
259
|
}
|
|
244
260
|
|
|
261
|
+
async searchConfig() {
|
|
262
|
+
return await this._system.searchConfig();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async llm() {
|
|
266
|
+
return await this._system.llm();
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async updateCounters(params = {}) {
|
|
270
|
+
return await this._system.updateCounters(params);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async debugCounters() {
|
|
274
|
+
return await this._system.debugCounters();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async repair(params = {}) {
|
|
278
|
+
return await this._system.repair(params);
|
|
279
|
+
}
|
|
280
|
+
|
|
245
281
|
/**
|
|
246
282
|
* Execute a top-level SQL query through GET /sql.
|
|
247
283
|
*
|
|
@@ -383,6 +419,10 @@ class Client {
|
|
|
383
419
|
async getCollection(name) {
|
|
384
420
|
return await this._collections.get(name);
|
|
385
421
|
}
|
|
422
|
+
|
|
423
|
+
async getCollectionLanguage(name) {
|
|
424
|
+
return await this._collections.language(name);
|
|
425
|
+
}
|
|
386
426
|
|
|
387
427
|
/**
|
|
388
428
|
* Get collection fields (formatted)
|
|
@@ -503,6 +543,10 @@ class Client {
|
|
|
503
543
|
return await this._documents.get(collectionName, documentId);
|
|
504
544
|
}
|
|
505
545
|
|
|
546
|
+
async getDocumentContext(collectionName, documentId) {
|
|
547
|
+
return await this._documents.context(collectionName, documentId);
|
|
548
|
+
}
|
|
549
|
+
|
|
506
550
|
/**
|
|
507
551
|
* Copy a document to a new ID within the same collection.
|
|
508
552
|
*
|
|
@@ -548,6 +592,18 @@ class Client {
|
|
|
548
592
|
async facetCounts(collectionName, params = {}) {
|
|
549
593
|
return await this._documents.facetCounts(collectionName, params);
|
|
550
594
|
}
|
|
595
|
+
|
|
596
|
+
async maybeDocuments(collectionName, params = {}) {
|
|
597
|
+
return await this._documents.maybe(collectionName, params);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
async updateDocumentsByQuery(collectionName, body = {}) {
|
|
601
|
+
return await this._documents.updateByQuery(collectionName, body);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
async deleteDocumentsByQuery(collectionName, body = {}) {
|
|
605
|
+
return await this._documents.deleteByQuery(collectionName, body);
|
|
606
|
+
}
|
|
551
607
|
|
|
552
608
|
// ============================================================================
|
|
553
609
|
// SEARCH API
|
|
@@ -603,6 +659,14 @@ class Client {
|
|
|
603
659
|
return await this._sam.clearPause(params);
|
|
604
660
|
}
|
|
605
661
|
|
|
662
|
+
async samImprove(params = {}) {
|
|
663
|
+
return await this._sam.improve(params);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
async samFlushActorMetadata() {
|
|
667
|
+
return await this._sam.flushActorMetadata();
|
|
668
|
+
}
|
|
669
|
+
|
|
606
670
|
async samDocuments(collectionName, offset = 0, limit = 20, params = {}) {
|
|
607
671
|
return await this._sam.listDocuments(collectionName, offset, limit, params);
|
|
608
672
|
}
|
|
@@ -615,6 +679,10 @@ class Client {
|
|
|
615
679
|
return await this._sam.openDocument(collectionName, documentId, interactionQuery, params);
|
|
616
680
|
}
|
|
617
681
|
|
|
682
|
+
async samAddDocumentLabel(collectionName, documentId, labels) {
|
|
683
|
+
return await this._sam.addDocumentLabel(collectionName, documentId, labels);
|
|
684
|
+
}
|
|
685
|
+
|
|
618
686
|
/**
|
|
619
687
|
* Get aliases API instance
|
|
620
688
|
*
|
|
@@ -668,6 +736,57 @@ class Client {
|
|
|
668
736
|
keys() {
|
|
669
737
|
return this._keys;
|
|
670
738
|
}
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Get runtime modules API instance
|
|
742
|
+
*
|
|
743
|
+
* @returns {Modules}
|
|
744
|
+
*/
|
|
745
|
+
modules() {
|
|
746
|
+
return this._modules;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Get users API instance.
|
|
751
|
+
*
|
|
752
|
+
* @returns {Users}
|
|
753
|
+
*/
|
|
754
|
+
users() {
|
|
755
|
+
return this._users;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Get analytics API instance.
|
|
760
|
+
*
|
|
761
|
+
* @returns {Analytics}
|
|
762
|
+
*/
|
|
763
|
+
analytics() {
|
|
764
|
+
return this._analytics;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
async analyticsClick(payload) {
|
|
768
|
+
return await this._analytics.click(payload);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Build a Redis-style dynamic module command.
|
|
773
|
+
*
|
|
774
|
+
* @param {string} name
|
|
775
|
+
* @returns {ModuleRouteCommand}
|
|
776
|
+
*/
|
|
777
|
+
module(name) {
|
|
778
|
+
return new ModuleRouteCommand(this.request, name);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* Build a Redis-style top-level route command.
|
|
783
|
+
*
|
|
784
|
+
* @param {string|Array<string>} route
|
|
785
|
+
* @returns {RouteCommand}
|
|
786
|
+
*/
|
|
787
|
+
route(route) {
|
|
788
|
+
return new RouteCommand(this.request, '/').route(route);
|
|
789
|
+
}
|
|
671
790
|
|
|
672
791
|
/**
|
|
673
792
|
* Search documents
|
|
@@ -722,8 +841,8 @@ class Client {
|
|
|
722
841
|
* @param {object} queryParams Query parameters
|
|
723
842
|
* @returns {Promise<Response>}
|
|
724
843
|
*/
|
|
725
|
-
async executeRequest(method, path, body = null, queryParams = {}) {
|
|
726
|
-
return await this.request.execute(method, path, body, queryParams);
|
|
844
|
+
async executeRequest(method, path, body = null, queryParams = {}, options = {}) {
|
|
845
|
+
return await this.request.execute(method, path, body, queryParams, options);
|
|
727
846
|
}
|
|
728
847
|
|
|
729
848
|
// ============================================================================
|
package/lib/Collections.js
CHANGED
|
@@ -42,6 +42,17 @@ class Collections {
|
|
|
42
42
|
Validator.validateCollectionName(name);
|
|
43
43
|
return await this.request.execute('GET', `/collections/${encodeURIComponent(name)}`);
|
|
44
44
|
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get detected collection language information.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} name Collection name
|
|
50
|
+
* @returns {Promise<Response>}
|
|
51
|
+
*/
|
|
52
|
+
async language(name) {
|
|
53
|
+
Validator.validateCollectionName(name);
|
|
54
|
+
return await this.request.execute('GET', `/collections/${encodeURIComponent(name)}/lang`);
|
|
55
|
+
}
|
|
45
56
|
|
|
46
57
|
/**
|
|
47
58
|
* Create a new collection
|
|
@@ -52,10 +63,14 @@ class Collections {
|
|
|
52
63
|
*/
|
|
53
64
|
async create(name, schema) {
|
|
54
65
|
Validator.validateCollectionName(name);
|
|
66
|
+
if (schema && schema.name !== undefined && schema.name !== name) {
|
|
67
|
+
throw new Error('Schema name must match the collection name argument');
|
|
68
|
+
}
|
|
69
|
+
|
|
55
70
|
// Merge schema with name at top level (server expects fields/searchable_fields at root)
|
|
56
71
|
const requestBody = {
|
|
57
|
-
|
|
58
|
-
|
|
72
|
+
...schema,
|
|
73
|
+
name: name
|
|
59
74
|
};
|
|
60
75
|
return await this.request.execute('POST', '/collections', requestBody);
|
|
61
76
|
}
|
|
@@ -99,6 +114,24 @@ class Collections {
|
|
|
99
114
|
const body = response.getBody();
|
|
100
115
|
const allFields = [];
|
|
101
116
|
const fieldTypes = {};
|
|
117
|
+
|
|
118
|
+
if (Array.isArray(body.fields)) {
|
|
119
|
+
for (const field of body.fields) {
|
|
120
|
+
const fieldName = typeof field === 'string' ? field : field.name;
|
|
121
|
+
if (!fieldName) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (!allFields.includes(fieldName)) {
|
|
125
|
+
allFields.push(fieldName);
|
|
126
|
+
}
|
|
127
|
+
if (!fieldTypes[fieldName]) {
|
|
128
|
+
fieldTypes[fieldName] = [];
|
|
129
|
+
}
|
|
130
|
+
if (field.type && !fieldTypes[fieldName].includes(field.type)) {
|
|
131
|
+
fieldTypes[fieldName].push(field.type);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
102
135
|
|
|
103
136
|
// Collect searchable fields
|
|
104
137
|
if (body.searchable_fields) {
|
|
@@ -142,7 +175,7 @@ class Collections {
|
|
|
142
175
|
// Format fields
|
|
143
176
|
const fields = allFields.map(field => ({
|
|
144
177
|
name: field,
|
|
145
|
-
type: fieldTypes[field].join(', ')
|
|
178
|
+
type: fieldTypes[field].join(', ') || 'unknown'
|
|
146
179
|
}));
|
|
147
180
|
|
|
148
181
|
return new Response(200, {
|
package/lib/Documents.js
CHANGED
|
@@ -18,6 +18,13 @@ class Documents {
|
|
|
18
18
|
constructor(request) {
|
|
19
19
|
this.request = request;
|
|
20
20
|
}
|
|
21
|
+
|
|
22
|
+
_responseFromRequestError(error) {
|
|
23
|
+
if (error && error.response) {
|
|
24
|
+
return error.response;
|
|
25
|
+
}
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
21
28
|
|
|
22
29
|
/**
|
|
23
30
|
* List documents in a collection
|
|
@@ -52,6 +59,20 @@ class Documents {
|
|
|
52
59
|
|
|
53
60
|
return await this.request.execute('GET', `/collections/${encodeURIComponent(collectionName)}/documents/${encodeURIComponent(documentId)}`);
|
|
54
61
|
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get alternate contextual phrases for a document.
|
|
65
|
+
*
|
|
66
|
+
* @param {string} collectionName
|
|
67
|
+
* @param {string} documentId
|
|
68
|
+
* @returns {Promise<Response>}
|
|
69
|
+
*/
|
|
70
|
+
async context(collectionName, documentId) {
|
|
71
|
+
Validator.validateCollectionName(collectionName);
|
|
72
|
+
Validator.validateDocumentId(documentId);
|
|
73
|
+
|
|
74
|
+
return await this.request.execute('GET', `/collections/${encodeURIComponent(collectionName)}/documents/${encodeURIComponent(documentId)}/context`);
|
|
75
|
+
}
|
|
55
76
|
|
|
56
77
|
/**
|
|
57
78
|
* Add a document
|
|
@@ -177,14 +198,24 @@ class Documents {
|
|
|
177
198
|
}
|
|
178
199
|
|
|
179
200
|
// Server expects {documents: [...]} format; fall back to per-doc inserts if bulk route is unavailable.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
201
|
+
let bulkResponse;
|
|
202
|
+
try {
|
|
203
|
+
bulkResponse = await this.request.execute('POST', `/collections/${encodeURIComponent(collectionName)}/documents/import`, {
|
|
204
|
+
documents: documents
|
|
205
|
+
});
|
|
206
|
+
} catch (error) {
|
|
207
|
+
bulkResponse = this._responseFromRequestError(error);
|
|
208
|
+
}
|
|
183
209
|
|
|
184
210
|
if (bulkResponse && bulkResponse.getStatusCode && bulkResponse.getStatusCode() === 404) {
|
|
185
211
|
let inserted = 0;
|
|
186
212
|
for (const doc of documents) {
|
|
187
|
-
|
|
213
|
+
let response;
|
|
214
|
+
try {
|
|
215
|
+
response = await this.request.execute('POST', `/collections/${encodeURIComponent(collectionName)}/documents`, doc);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
response = this._responseFromRequestError(error);
|
|
218
|
+
}
|
|
188
219
|
const status = response.getStatusCode();
|
|
189
220
|
if (status !== 201) {
|
|
190
221
|
return response;
|
|
@@ -215,7 +246,12 @@ class Documents {
|
|
|
215
246
|
throw new Error('Source and target document ids must differ');
|
|
216
247
|
}
|
|
217
248
|
|
|
218
|
-
|
|
249
|
+
let targetCheck;
|
|
250
|
+
try {
|
|
251
|
+
targetCheck = await this.get(collectionName, targetId);
|
|
252
|
+
} catch (error) {
|
|
253
|
+
targetCheck = this._responseFromRequestError(error);
|
|
254
|
+
}
|
|
219
255
|
if (targetCheck.getStatusCode() === 200) {
|
|
220
256
|
return targetCheck;
|
|
221
257
|
}
|
|
@@ -223,7 +259,12 @@ class Documents {
|
|
|
223
259
|
return targetCheck;
|
|
224
260
|
}
|
|
225
261
|
|
|
226
|
-
|
|
262
|
+
let sourceResponse;
|
|
263
|
+
try {
|
|
264
|
+
sourceResponse = await this.get(collectionName, sourceId);
|
|
265
|
+
} catch (error) {
|
|
266
|
+
sourceResponse = this._responseFromRequestError(error);
|
|
267
|
+
}
|
|
227
268
|
if (sourceResponse.getStatusCode() !== 200) {
|
|
228
269
|
return sourceResponse;
|
|
229
270
|
}
|
|
@@ -302,6 +343,57 @@ class Documents {
|
|
|
302
343
|
queryParams
|
|
303
344
|
);
|
|
304
345
|
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Request query suggestions for a collection.
|
|
349
|
+
*
|
|
350
|
+
* @param {string} collectionName
|
|
351
|
+
* @param {object} params
|
|
352
|
+
* @returns {Promise<Response>}
|
|
353
|
+
*/
|
|
354
|
+
async maybe(collectionName, params = {}) {
|
|
355
|
+
Validator.validateCollectionName(collectionName);
|
|
356
|
+
Validator.validateSearchParams(params);
|
|
357
|
+
|
|
358
|
+
const method = params.body ? 'POST' : 'GET';
|
|
359
|
+
const body = params.body || (method === 'POST' ? params : null);
|
|
360
|
+
const queryParams = method === 'GET' ? params : {};
|
|
361
|
+
|
|
362
|
+
return await this.request.execute(
|
|
363
|
+
method,
|
|
364
|
+
`/collections/${encodeURIComponent(collectionName)}/documents/maybe`,
|
|
365
|
+
body,
|
|
366
|
+
queryParams
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Update documents matching a query.
|
|
372
|
+
*
|
|
373
|
+
* @param {string} collectionName
|
|
374
|
+
* @param {object} body
|
|
375
|
+
* @returns {Promise<Response>}
|
|
376
|
+
*/
|
|
377
|
+
async updateByQuery(collectionName, body = {}) {
|
|
378
|
+
Validator.validateCollectionName(collectionName);
|
|
379
|
+
Validator.validateDocumentFields(body);
|
|
380
|
+
|
|
381
|
+
return await this.request.execute('POST', `/collections/${encodeURIComponent(collectionName)}/documents/_update_by_query`, body);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Delete documents matching a query.
|
|
386
|
+
*
|
|
387
|
+
* @param {string} collectionName
|
|
388
|
+
* @param {object} body
|
|
389
|
+
* @returns {Promise<Response>}
|
|
390
|
+
*/
|
|
391
|
+
async deleteByQuery(collectionName, body = {}) {
|
|
392
|
+
Validator.validateCollectionName(collectionName);
|
|
393
|
+
Validator.validateDocumentFields(body);
|
|
394
|
+
|
|
395
|
+
return await this.request.execute('POST', `/collections/${encodeURIComponent(collectionName)}/documents/_delete_by_query`, body);
|
|
396
|
+
}
|
|
305
397
|
|
|
306
398
|
/**
|
|
307
399
|
* Delete documents by filter
|
package/lib/Exceptions.js
CHANGED
|
@@ -10,9 +10,12 @@
|
|
|
10
10
|
* Base exception class for hlquery client
|
|
11
11
|
*/
|
|
12
12
|
class HlqueryException extends Error {
|
|
13
|
-
constructor(message) {
|
|
13
|
+
constructor(message, options = {}) {
|
|
14
14
|
super(message);
|
|
15
15
|
this.name = 'HlqueryException';
|
|
16
|
+
if (options.cause) {
|
|
17
|
+
this.cause = options.cause;
|
|
18
|
+
}
|
|
16
19
|
Error.captureStackTrace(this, this.constructor);
|
|
17
20
|
}
|
|
18
21
|
}
|
|
@@ -31,11 +34,16 @@ class AuthenticationException extends HlqueryException {
|
|
|
31
34
|
* Exception thrown when a request fails
|
|
32
35
|
*/
|
|
33
36
|
class RequestException extends HlqueryException {
|
|
34
|
-
constructor(message, statusCode = 0, responseBody = null) {
|
|
35
|
-
super(message);
|
|
37
|
+
constructor(message, statusCode = 0, responseBody = null, options = {}) {
|
|
38
|
+
super(message, options);
|
|
36
39
|
this.name = 'RequestException';
|
|
37
40
|
this.statusCode = statusCode;
|
|
38
41
|
this.responseBody = responseBody;
|
|
42
|
+
this.response = options.response || null;
|
|
43
|
+
this.headers = options.headers || {};
|
|
44
|
+
this.rawBody = options.rawBody || null;
|
|
45
|
+
this.retryable = Boolean(options.retryable);
|
|
46
|
+
this.code = options.code || null;
|
|
39
47
|
}
|
|
40
48
|
}
|
|
41
49
|
|
package/lib/Modules.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hlquery Node.js Client - Runtime Modules 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 { ModuleRouteCommand, cleanRoute } = require('./Route');
|
|
10
|
+
|
|
11
|
+
function requireNonEmptyString(value, label) {
|
|
12
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
13
|
+
throw new Error(`${label} must be a non-empty string`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Runtime module discovery and dynamic route calls.
|
|
19
|
+
*/
|
|
20
|
+
class Modules {
|
|
21
|
+
constructor(request) {
|
|
22
|
+
this.request = request;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
list() {
|
|
26
|
+
return this.request.execute('GET', '/modules');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
load(name) {
|
|
30
|
+
requireNonEmptyString(name, 'Module name');
|
|
31
|
+
return this.request.execute('POST', `/loadmodule/${encodeURIComponent(name)}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
unload(name) {
|
|
35
|
+
requireNonEmptyString(name, 'Module name');
|
|
36
|
+
return this.request.execute('POST', `/unloadmodule/${encodeURIComponent(name)}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
syntax(name, queryParams = {}) {
|
|
40
|
+
return this.module(name).syntax(queryParams);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module(name) {
|
|
44
|
+
return new ModuleRouteCommand(this.request, name);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
route(name, route = '') {
|
|
48
|
+
return this.module(name).route(route);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
call(name, route = '', method = 'GET', body = null, queryParams = {}, options = {}) {
|
|
52
|
+
requireNonEmptyString(name, 'Module name');
|
|
53
|
+
requireNonEmptyString(method, 'HTTP method');
|
|
54
|
+
|
|
55
|
+
const suffix = cleanRoute(route);
|
|
56
|
+
const path = suffix
|
|
57
|
+
? `/modules/${encodeURIComponent(name)}/${suffix}`
|
|
58
|
+
: `/modules/${encodeURIComponent(name)}`;
|
|
59
|
+
|
|
60
|
+
return this.request.execute(method.toUpperCase(), path, body, queryParams, options);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = Modules;
|