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/README.md +84 -45
- package/lib/Aliases.js +5 -0
- package/lib/Analytics.js +30 -0
- package/lib/Client.js +109 -61
- 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 +157 -15
- package/lib/Response.js +28 -2
- package/lib/Route.js +111 -0
- package/lib/Search.js +21 -9
- package/lib/System.js +38 -0
- package/lib/Users.js +51 -0
- package/lib/conformance.js +25 -19
- package/package.json +9 -20
- package/test/route-conformance.test.js +83 -0
- package/utils/Config.js +17 -2
- package/utils/Validator.js +12 -29
- package/examples/sam.js +0 -39
- package/index.js +0 -40
- package/lib/SAM.js +0 -174
- package/test/offline-smoke.js +0 -194
package/lib/SAM.js
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* hlquery Node.js Client - SAM 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
|
-
/**
|
|
12
|
-
* SAM API operations
|
|
13
|
-
*/
|
|
14
|
-
class SAM {
|
|
15
|
-
constructor(request) {
|
|
16
|
-
this.request = request;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
_validateParams(params, label) {
|
|
20
|
-
if (params === null || typeof params !== 'object' || Array.isArray(params)) {
|
|
21
|
-
throw new Error(`${label} must be an object`);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async search(collectionName, query, params = {}) {
|
|
26
|
-
if (typeof query !== 'string' || query.trim() === '') {
|
|
27
|
-
throw new Error('SAM query must be a non-empty string');
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
this._validateParams(params, 'SAM search params');
|
|
31
|
-
|
|
32
|
-
const queryParams = {
|
|
33
|
-
...params,
|
|
34
|
-
q: query
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
if (collectionName !== null && collectionName !== undefined && collectionName !== '') {
|
|
38
|
-
Validator.validateCollectionName(collectionName);
|
|
39
|
-
queryParams.collection = collectionName;
|
|
40
|
-
} else if (!queryParams.all && !queryParams.collections) {
|
|
41
|
-
throw new Error('Collection name is required unless all=true or collections is provided');
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return await this.request.execute('GET', '/sam/search', null, queryParams);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async searchAll(query, params = {}) {
|
|
48
|
-
this._validateParams(params, 'SAM search params');
|
|
49
|
-
return await this.search(null, query, { ...params, all: true });
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async rebuild(collectionName, params = {}) {
|
|
53
|
-
Validator.validateCollectionName(collectionName);
|
|
54
|
-
this._validateParams(params, 'SAM rebuild params');
|
|
55
|
-
|
|
56
|
-
return await this.request.execute('POST', '/sam/rebuild', null, {
|
|
57
|
-
...params,
|
|
58
|
-
collection: collectionName
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async status(collectionName = null, params = {}) {
|
|
63
|
-
this._validateParams(params, 'SAM status params');
|
|
64
|
-
|
|
65
|
-
const queryParams = { ...params };
|
|
66
|
-
|
|
67
|
-
if (collectionName !== null && collectionName !== undefined && collectionName !== '') {
|
|
68
|
-
Validator.validateCollectionName(collectionName);
|
|
69
|
-
queryParams.collection = collectionName;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return await this.request.execute('GET', '/sam/status', null, queryParams);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async debug(collectionName = null, params = {}) {
|
|
76
|
-
this._validateParams(params, 'SAM debug params');
|
|
77
|
-
|
|
78
|
-
const queryParams = { ...params };
|
|
79
|
-
|
|
80
|
-
if (collectionName !== null && collectionName !== undefined && collectionName !== '') {
|
|
81
|
-
Validator.validateCollectionName(collectionName);
|
|
82
|
-
queryParams.collection = collectionName;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return await this.request.execute('GET', '/sam/debug', null, queryParams);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async history(collectionName = null, limit = 100, params = {}) {
|
|
89
|
-
this._validateParams(params, 'SAM history params');
|
|
90
|
-
const safeLimit = Number(limit);
|
|
91
|
-
if (!Number.isInteger(safeLimit) || safeLimit < 1) {
|
|
92
|
-
throw new Error('SAM history limit must be a positive integer');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const queryParams = {
|
|
96
|
-
...params,
|
|
97
|
-
limit: safeLimit
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
if (collectionName !== null && collectionName !== undefined && collectionName !== '') {
|
|
101
|
-
Validator.validateCollectionName(collectionName);
|
|
102
|
-
queryParams.collection = collectionName;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return await this.request.execute('GET', '/sam/history', null, queryParams);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
async pause(pauseUntilMs, params = {}) {
|
|
109
|
-
this._validateParams(params, 'SAM pause params');
|
|
110
|
-
|
|
111
|
-
const pause = Number(pauseUntilMs);
|
|
112
|
-
if (!Number.isFinite(pause) || pause < 0) {
|
|
113
|
-
throw new Error('SAM pause value must be a non-negative unix millisecond timestamp, or 0 to clear');
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return await this.request.execute('POST', '/sam/pause', null, {
|
|
117
|
-
...params,
|
|
118
|
-
pause
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async clearPause(params = {}) {
|
|
123
|
-
return await this.pause(0, params);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async listDocuments(collectionName, offset = 0, limit = 20, params = {}) {
|
|
127
|
-
Validator.validateCollectionName(collectionName);
|
|
128
|
-
this._validateParams(params, 'SAM document list params');
|
|
129
|
-
const safeOffset = Number(offset);
|
|
130
|
-
const safeLimit = Number(limit);
|
|
131
|
-
|
|
132
|
-
if (!Number.isInteger(safeOffset) || safeOffset < 0) {
|
|
133
|
-
throw new Error('SAM document offset must be a non-negative integer');
|
|
134
|
-
}
|
|
135
|
-
if (!Number.isInteger(safeLimit) || safeLimit < 1) {
|
|
136
|
-
throw new Error('SAM document limit must be a positive integer');
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return await this.request.execute('GET', '/sam/documents', null, {
|
|
140
|
-
...params,
|
|
141
|
-
collection: collectionName,
|
|
142
|
-
offset: safeOffset,
|
|
143
|
-
limit: safeLimit
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
async getDocument(collectionName, documentId, params = {}) {
|
|
148
|
-
Validator.validateCollectionName(collectionName);
|
|
149
|
-
Validator.validateDocumentId(documentId);
|
|
150
|
-
this._validateParams(params, 'SAM document params');
|
|
151
|
-
|
|
152
|
-
return await this.request.execute(
|
|
153
|
-
'GET',
|
|
154
|
-
`/sam/documents/${encodeURIComponent(collectionName)}/${encodeURIComponent(documentId)}`,
|
|
155
|
-
null,
|
|
156
|
-
params
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
async openDocument(collectionName, documentId, interactionQuery = null, params = {}) {
|
|
161
|
-
const queryParams = { ...params };
|
|
162
|
-
|
|
163
|
-
if (interactionQuery !== null && interactionQuery !== undefined && interactionQuery !== '') {
|
|
164
|
-
if (typeof interactionQuery !== 'string') {
|
|
165
|
-
throw new Error('SAM interaction query must be a string');
|
|
166
|
-
}
|
|
167
|
-
queryParams.interaction_query = interactionQuery;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return await this.getDocument(collectionName, documentId, queryParams);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
module.exports = SAM;
|
package/test/offline-smoke.js
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const assert = require('assert');
|
|
4
|
-
const http = require('http');
|
|
5
|
-
|
|
6
|
-
const Hlquery = require('..');
|
|
7
|
-
const Client = require('../lib/Client');
|
|
8
|
-
const Request = require('../lib/Request');
|
|
9
|
-
const Response = require('../lib/Response');
|
|
10
|
-
const Config = require('../utils/Config');
|
|
11
|
-
const Validator = require('../utils/Validator');
|
|
12
|
-
const CSVParser = require('../utils/CSVParser');
|
|
13
|
-
|
|
14
|
-
async function main() {
|
|
15
|
-
assert.strictEqual(typeof Hlquery, 'function', 'default export should be the Client constructor');
|
|
16
|
-
assert.strictEqual(Hlquery.Client, Client, 'named Client export should match default export');
|
|
17
|
-
|
|
18
|
-
assert.strictEqual(Config.normalizeUrl('localhost:9200/'), 'http://localhost:9200');
|
|
19
|
-
assert.strictEqual(Config.isValidUrl('http://localhost:9200'), true);
|
|
20
|
-
assert.strictEqual(Config.isValidUrl('notaurl'), false);
|
|
21
|
-
|
|
22
|
-
Validator.validateCollectionName('books');
|
|
23
|
-
Validator.validateDocumentId('doc_123');
|
|
24
|
-
Validator.validateSearchParams({ limit: 10, offset: 0 });
|
|
25
|
-
|
|
26
|
-
assert.throws(() => Validator.validateCollectionName('123bad'), /letter or underscore/);
|
|
27
|
-
assert.throws(() => Validator.validateSearchParams(null), /object/);
|
|
28
|
-
assert.throws(() => Validator.validateSearchParams({ limit: NaN }), /positive integer/);
|
|
29
|
-
assert.throws(() => Validator.validateSearchParams({ offset: 1.5 }), /non-negative integer/);
|
|
30
|
-
assert.throws(() => Validator.validateDocumentFields({ title: 'bad,value' }), /comma \(\,\)/);
|
|
31
|
-
assert.throws(() => CSVParser.parseCSV('a||b', '||'), /single character/);
|
|
32
|
-
|
|
33
|
-
const response = new Response(200, { ok: true }, { 'content-type': 'application/json' });
|
|
34
|
-
assert.strictEqual(response.isSuccess(), true);
|
|
35
|
-
assert.deepStrictEqual(response.getBody(), { ok: true });
|
|
36
|
-
|
|
37
|
-
const server = http.createServer((req, res) => {
|
|
38
|
-
if (req.url === '/health') {
|
|
39
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
40
|
-
res.end(JSON.stringify({ status: 'ok' }));
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (req.url === '/status') {
|
|
45
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
46
|
-
res.end(JSON.stringify({ status: 'ready' }));
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (req.url === '/etc') {
|
|
51
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
52
|
-
res.end(JSON.stringify({ protocol: 'http' }));
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (req.url === '/links') {
|
|
57
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
58
|
-
res.end(JSON.stringify({ links: [] }));
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (req.url === '/links/ping') {
|
|
63
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
64
|
-
res.end(JSON.stringify({ ok: true }));
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (req.url === '/links/connect' && req.method === 'POST') {
|
|
69
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
70
|
-
res.end(JSON.stringify({ connected: true }));
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (req.url === '/links/disconnect' && req.method === 'POST') {
|
|
75
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
76
|
-
res.end(JSON.stringify({ disconnected: true }));
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (req.url === '/flush' && req.method === 'POST') {
|
|
81
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
82
|
-
res.end(JSON.stringify({ flushed: true }));
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (req.url.startsWith('/echo-auth')) {
|
|
87
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
88
|
-
res.end(JSON.stringify({
|
|
89
|
-
authorization: req.headers.authorization || null,
|
|
90
|
-
apiKey: req.headers['x-api-key'] || null,
|
|
91
|
-
contentType: req.headers['content-type'] || null,
|
|
92
|
-
url: req.url
|
|
93
|
-
}));
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (req.url === '/keys/key%2Fwith%20space') {
|
|
98
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
99
|
-
res.end(JSON.stringify({ id: 'key/with space' }));
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
104
|
-
res.end(JSON.stringify({ error: 'not found' }));
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
await new Promise((resolve, reject) => {
|
|
108
|
-
server.listen(0, '127.0.0.1', (err) => (err ? reject(err) : resolve()));
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
const { port } = server.address();
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
const request = new Request(`http://127.0.0.1:${port}`);
|
|
115
|
-
const health = await request.execute('GET', '/health');
|
|
116
|
-
assert.strictEqual(health.getStatusCode(), 200);
|
|
117
|
-
assert.deepStrictEqual(health.getBody(), { status: 'ok' });
|
|
118
|
-
|
|
119
|
-
request.setAuthToken('secret-token', 'bearer');
|
|
120
|
-
const bearerEcho = await request.execute('GET', '/echo-auth');
|
|
121
|
-
assert.strictEqual(bearerEcho.getBody().authorization, 'Bearer secret-token');
|
|
122
|
-
|
|
123
|
-
request.setAuthToken('scoped-key', 'api-key');
|
|
124
|
-
const apiKeyEcho = await request.execute('GET', '/echo-auth');
|
|
125
|
-
assert.strictEqual(apiKeyEcho.getBody().apiKey, 'scoped-key');
|
|
126
|
-
|
|
127
|
-
const queryEcho = await request.execute('GET', '/echo-auth', undefined, {
|
|
128
|
-
fields: ['title', 'body'],
|
|
129
|
-
filter: { published: true },
|
|
130
|
-
skip: null
|
|
131
|
-
});
|
|
132
|
-
assert.strictEqual(queryEcho.getBody().contentType, null);
|
|
133
|
-
assert.match(queryEcho.getBody().url, /fields=title%2Cbody/);
|
|
134
|
-
assert.match(queryEcho.getBody().url, /filter=%7B%22published%22%3Atrue%7D/);
|
|
135
|
-
assert.doesNotMatch(queryEcho.getBody().url, /skip=/);
|
|
136
|
-
|
|
137
|
-
const client = new Client(`http://127.0.0.1:${port}`);
|
|
138
|
-
const clientHealth = await client.health();
|
|
139
|
-
assert.strictEqual(clientHealth.isSuccess(), true);
|
|
140
|
-
assert.deepStrictEqual(clientHealth.getBody(), { status: 'ok' });
|
|
141
|
-
|
|
142
|
-
const clientStatus = await client.status();
|
|
143
|
-
assert.deepStrictEqual(clientStatus.getBody(), { status: 'ready' });
|
|
144
|
-
|
|
145
|
-
const clientEtc = await client.etc();
|
|
146
|
-
assert.deepStrictEqual(clientEtc.getBody(), { protocol: 'http' });
|
|
147
|
-
|
|
148
|
-
const clientLinks = await client.links();
|
|
149
|
-
assert.deepStrictEqual(clientLinks.getBody(), { links: [] });
|
|
150
|
-
|
|
151
|
-
const clientLinksPing = await client.linksPing();
|
|
152
|
-
assert.deepStrictEqual(clientLinksPing.getBody(), { ok: true });
|
|
153
|
-
|
|
154
|
-
const clientLinksConnect = await client.linksConnect('http://node-b:9200');
|
|
155
|
-
assert.deepStrictEqual(clientLinksConnect.getBody(), { connected: true });
|
|
156
|
-
|
|
157
|
-
const clientLinksDisconnect = await client.linksDisconnect('http://node-b:9200');
|
|
158
|
-
assert.deepStrictEqual(clientLinksDisconnect.getBody(), { disconnected: true });
|
|
159
|
-
|
|
160
|
-
const clientFlush = await client.flush();
|
|
161
|
-
assert.deepStrictEqual(clientFlush.getBody(), { flushed: true });
|
|
162
|
-
|
|
163
|
-
assert.strictEqual(typeof client.executeRequest, 'function');
|
|
164
|
-
assert.strictEqual(typeof client.sql, 'function');
|
|
165
|
-
assert.strictEqual(typeof client.execSql, 'function');
|
|
166
|
-
assert.strictEqual(typeof client.sqlSearch, 'function');
|
|
167
|
-
assert.strictEqual(typeof client.sam().search, 'function');
|
|
168
|
-
assert.strictEqual(typeof client.sam().searchAll, 'function');
|
|
169
|
-
assert.strictEqual(typeof client.sam().rebuild, 'function');
|
|
170
|
-
assert.strictEqual(typeof client.sam().status, 'function');
|
|
171
|
-
assert.strictEqual(typeof client.sam().debug, 'function');
|
|
172
|
-
assert.strictEqual(typeof client.sam().history, 'function');
|
|
173
|
-
assert.strictEqual(typeof client.sam().pause, 'function');
|
|
174
|
-
assert.strictEqual(typeof client.sam().clearPause, 'function');
|
|
175
|
-
assert.strictEqual(typeof client.sam().listDocuments, 'function');
|
|
176
|
-
assert.strictEqual(typeof client.sam().getDocument, 'function');
|
|
177
|
-
assert.strictEqual(typeof client.sam().openDocument, 'function');
|
|
178
|
-
await assert.rejects(() => client.sam().history(null, 0), /positive integer/);
|
|
179
|
-
await assert.rejects(() => client.sam().listDocuments('books', NaN, 20), /non-negative integer/);
|
|
180
|
-
|
|
181
|
-
const keyResponse = await client.keys().get('key/with space');
|
|
182
|
-
assert.deepStrictEqual(keyResponse.getBody(), { id: 'key/with space' });
|
|
183
|
-
await assert.rejects(() => client.documents().import('books', { id: 'not-array' }), /array/);
|
|
184
|
-
} finally {
|
|
185
|
-
await new Promise((resolve) => server.close(resolve));
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
process.stdout.write('Node offline smoke tests passed.\n');
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
main().catch((error) => {
|
|
192
|
-
process.stderr.write(`${error.stack || error.message}\n`);
|
|
193
|
-
process.exit(1);
|
|
194
|
-
});
|