hlquery-node-client 1.0.5 → 1.0.7

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/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;
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
- this.baseUrl = baseUrl.replace(/\/$/, '');
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,28 @@ 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 + path);
60
+ async execute(method, path, body = null, queryParams = {}, requestOptions = {}) {
61
+ const baseUrl = new URL(`${this.baseUrl}/`);
62
+ const url = new URL(path, baseUrl);
63
+ if (url.origin !== baseUrl.origin) {
64
+ throw new RequestException('Request URL must use the configured server origin', 0, null, {
65
+ code: 'CROSS_ORIGIN_REQUEST',
66
+ retryable: false
67
+ });
68
+ }
69
+ const signal = requestOptions.signal || this.signal;
70
+ const maxResponseBytes = requestOptions.max_response_bytes || requestOptions.maxResponseBytes || this.maxResponseBytes;
71
+ const throwOnError = requestOptions.throw_on_error !== undefined
72
+ ? requestOptions.throw_on_error
73
+ : (requestOptions.throwOnError !== undefined ? requestOptions.throwOnError : this.throwOnError);
74
+ const queryArrayFormat = requestOptions.query_array_format || requestOptions.queryArrayFormat || this.queryArrayFormat;
75
+
76
+ if (signal && signal.aborted) {
77
+ throw new RequestException('Request aborted', 0, null, {
78
+ code: 'ABORT_ERR',
79
+ retryable: false
80
+ });
81
+ }
47
82
 
48
83
  // Add query parameters
49
84
  Object.keys(queryParams).forEach(key => {
@@ -53,7 +88,11 @@ class Request {
53
88
  }
54
89
 
55
90
  if (Array.isArray(value)) {
56
- url.searchParams.append(key, value.join(','));
91
+ if (queryArrayFormat === 'repeat' || queryArrayFormat === 'repeated') {
92
+ value.forEach(item => url.searchParams.append(key, item));
93
+ } else {
94
+ url.searchParams.append(key, value.join(','));
95
+ }
57
96
  return;
58
97
  }
59
98
 
@@ -66,7 +105,9 @@ class Request {
66
105
  });
67
106
 
68
107
  const headers = {
69
- 'Accept': 'application/json'
108
+ 'Accept': 'application/json',
109
+ ...this.defaultHeaders,
110
+ ...(requestOptions.headers || {})
70
111
  };
71
112
 
72
113
  // Prepare body string if present
@@ -89,32 +130,112 @@ class Request {
89
130
  const options = {
90
131
  method: method,
91
132
  headers: headers,
92
- timeout: this.timeout
133
+ timeout: requestOptions.timeout || this.timeout
93
134
  };
94
135
 
95
136
  const client = url.protocol === 'https:' ? https : http;
137
+ const agent = this.agent || (url.protocol === 'https:' ? this.httpsAgent : this.httpAgent);
138
+ if (agent) {
139
+ options.agent = agent;
140
+ }
96
141
 
97
142
  return new Promise((resolve, reject) => {
98
- const req = client.request(url, options, (res) => {
143
+ let settled = false;
144
+ let req = null;
145
+ const rejectOnce = (error) => {
146
+ if (settled) {
147
+ return;
148
+ }
149
+ settled = true;
150
+ if (signal && abortHandler && typeof signal.removeEventListener === 'function') {
151
+ signal.removeEventListener('abort', abortHandler);
152
+ } else if (signal && abortHandler && typeof signal.removeListener === 'function') {
153
+ signal.removeListener('abort', abortHandler);
154
+ }
155
+ reject(error);
156
+ };
157
+ const resolveOnce = (response) => {
158
+ if (settled) {
159
+ return;
160
+ }
161
+ settled = true;
162
+ if (signal && abortHandler && typeof signal.removeEventListener === 'function') {
163
+ signal.removeEventListener('abort', abortHandler);
164
+ } else if (signal && abortHandler && typeof signal.removeListener === 'function') {
165
+ signal.removeListener('abort', abortHandler);
166
+ }
167
+ resolve(response);
168
+ };
169
+ const abortHandler = () => {
170
+ if (req) {
171
+ req.destroy();
172
+ }
173
+ rejectOnce(new RequestException('Request aborted', 0, null, {
174
+ code: 'ABORT_ERR',
175
+ retryable: false
176
+ }));
177
+ };
178
+
179
+ if (signal && typeof signal.addEventListener === 'function') {
180
+ signal.addEventListener('abort', abortHandler, { once: true });
181
+ } else if (signal && typeof signal.once === 'function') {
182
+ signal.once('abort', abortHandler);
183
+ }
184
+
185
+ req = client.request(url, options, (res) => {
99
186
  const responseHeaders = {};
100
187
  Object.keys(res.headers).forEach(key => {
101
188
  responseHeaders[key.toLowerCase()] = res.headers[key];
102
189
  });
103
190
 
104
- let data = '';
191
+ const chunks = [];
192
+ let receivedBytes = 0;
105
193
 
106
194
  res.on('data', (chunk) => {
107
- data += chunk;
195
+ receivedBytes += chunk.length;
196
+ if (maxResponseBytes > 0 && receivedBytes > maxResponseBytes) {
197
+ res.destroy();
198
+ rejectOnce(new RequestException(
199
+ `Response exceeded maximum size of ${maxResponseBytes} bytes`,
200
+ res.statusCode,
201
+ null,
202
+ {
203
+ headers: responseHeaders,
204
+ retryable: false,
205
+ code: 'MAX_RESPONSE_BYTES'
206
+ }
207
+ ));
208
+ return;
209
+ }
210
+ chunks.push(chunk);
108
211
  });
109
212
 
110
213
  res.on('end', () => {
214
+ if (settled) {
215
+ return;
216
+ }
217
+ const rawBuffer = Buffer.concat(chunks);
218
+ const data = rawBuffer.toString('utf8');
111
219
  let decoded;
220
+ let parsed = true;
112
221
  try {
113
222
  decoded = data ? JSON.parse(data) : null;
114
223
  } catch (e) {
115
224
  // If JSON parsing fails, return raw response
116
225
  decoded = data;
226
+ parsed = false;
117
227
  }
228
+
229
+ const response = new Response(res.statusCode, decoded, responseHeaders, {
230
+ statusMessage: res.statusMessage || '',
231
+ rawBody: rawBuffer,
232
+ parsed: parsed,
233
+ request: {
234
+ method: method,
235
+ url: url.toString(),
236
+ path: path
237
+ }
238
+ });
118
239
 
119
240
  // Check for demo mode errors
120
241
  if (res.statusCode === 403 && decoded && typeof decoded === 'object') {
@@ -122,30 +243,51 @@ class Request {
122
243
  if (errorMsg.includes('Demo mode is enabled') ||
123
244
  errorMsg.includes('Operation not allowed') ||
124
245
  errorMsg.includes('Write operations are not allowed')) {
125
- return reject(new DemoModeException(
246
+ return rejectOnce(new DemoModeException(
126
247
  `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
248
  ));
128
249
  }
129
250
  // Check if server rejected token because auth is disabled
130
251
  if (errorMsg.includes('Authentication is disabled') ||
131
252
  errorMsg.includes('Tokens are not accepted when authentication is disabled')) {
132
- return reject(new AuthenticationException(
253
+ return rejectOnce(new AuthenticationException(
133
254
  `Authentication is disabled on the server. Remove the token from your client configuration. Server message: ${decoded.message || decoded.error}`
134
255
  ));
135
256
  }
136
257
  }
258
+
259
+ if (throwOnError && response.isError()) {
260
+ const errorMessage = response.getError() ||
261
+ `${res.statusCode} ${res.statusMessage || 'HTTP error'}`;
262
+ return rejectOnce(new RequestException(errorMessage, res.statusCode, decoded, {
263
+ response: response,
264
+ headers: responseHeaders,
265
+ rawBody: rawBuffer,
266
+ retryable: res.statusCode >= 500
267
+ }));
268
+ }
137
269
 
138
- resolve(new Response(res.statusCode, decoded, responseHeaders));
270
+ resolveOnce(response);
139
271
  });
140
272
  });
141
273
 
142
274
  req.on('error', (error) => {
143
- reject(new RequestException(`Request failed: ${error.message}`, 0));
275
+ if (settled) {
276
+ return;
277
+ }
278
+ rejectOnce(new RequestException(`Request failed: ${error.message}`, 0, null, {
279
+ cause: error,
280
+ retryable: true,
281
+ code: error.code || null
282
+ }));
144
283
  });
145
284
 
146
285
  req.on('timeout', () => {
147
286
  req.destroy();
148
- reject(new RequestException('Request timeout', 0));
287
+ rejectOnce(new RequestException('Request timeout', 0, null, {
288
+ retryable: true,
289
+ code: 'ETIMEDOUT'
290
+ }));
149
291
  });
150
292
 
151
293
  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
+ };