hlquery-node-client 1.0.5 → 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 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()`, so collection management, document indexing, search, SQL, and SAM workflows stay easy to find.
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.getBody());
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
- /* Print the server status response body. */
97
- console.log(status.getBody());
98
- /* Print the health response body. */
99
- console.log(health.getBody());
100
- /* Print the runtime configuration response body. */
101
- console.log(etc.getBody());
102
- /* Print the configured cluster links response body. */
103
- console.log(links.getBody());
104
- /* Print the link ping response body. */
105
- console.log(ping.getBody());
106
- /* Print the link connect response body. */
107
- console.log(connect.getBody());
108
- /* Print the link disconnect response body. */
109
- console.log(disconnect.getBody());
110
- /* Print the flush response body. */
111
- console.log(flush.getBody());
112
- /* Print the custom route response body. */
113
- console.log(response.getBody());
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.getBody());
139
+ console.log(status.body);
131
140
  /* Print the SAM search history response body. */
132
- console.log(history.getBody());
141
+ console.log(history.body);
133
142
  /* Print the SAM search results response body. */
134
- console.log(results.getBody());
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.getBody());
159
+ console.log(rows.body);
151
160
  /* Print the SQL execution response body. */
152
- console.log(execResult.getBody());
161
+ console.log(execResult.body);
153
162
  /* Print the collection SQL search response body. */
154
- console.log(books.getBody());
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)}`);
@@ -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
  // ============================================================================
@@ -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
- name: name,
58
- ...schema
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
- const bulkResponse = await this.request.execute('POST', `/collections/${encodeURIComponent(collectionName)}/documents/import`, {
181
- documents: documents
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
- const response = await this.request.execute('POST', `/collections/${encodeURIComponent(collectionName)}/documents`, doc);
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
- const targetCheck = await this.get(collectionName, targetId);
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
- const sourceResponse = await this.get(collectionName, sourceId);
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;