Skip to content

Commit 60370f5

Browse files
committed
Add 1 new function(s) or class(es) in README.md, Client.js, and Presets.js.
1 parent 0f55a23 commit 60370f5

8 files changed

Lines changed: 138 additions & 2 deletions

File tree

README.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,16 @@ await client.updateCounters({ force: true }, 'GET');
9696
await client.repair({}, 'POST');
9797
```
9898

99-
The client also wraps readiness, startup, metrics, connection, storage, integrity, counter, user, key, module, and analytics routes through `client.system()`, `client.users()`, `client.keys()`, `client.modules()`, and `client.analytics()`.
99+
The client also wraps readiness, startup, metrics, cache, active configuration files, connection, storage, integrity, counter, user, key, module, analytics, and search-preset routes through `client.system()`, `client.users()`, `client.keys()`, `client.modules()`, `client.analytics()`, and `client.presets()`.
100+
101+
```javascript
102+
await client.presets().update('catalog-default', {
103+
q: '*',
104+
query_by: 'title,description',
105+
limit: 20
106+
});
107+
const preset = await client.presets().get('catalog-default');
108+
```
100109

101110
Routes that do not have a dedicated wrapper can still be called through `executeRequest()`:
102111

@@ -203,6 +212,15 @@ We welcome contributions from the community! All contributions must be released
203212
- Test and report bugs against the Node.js client
204213
- Improve Node.js-specific documentation and examples
205214

215+
### Search all collections
216+
217+
```javascript
218+
const result = await client.searchAll({ q: 'research', limit: 20 });
219+
const selected = await client.searchAll({ q: 'research', collections: 'universities,science' });
220+
```
221+
222+
`globalSearch` remains available as an equivalent name. Results are globally merged and each hit includes `document._collection`.
223+
206224
### Community
207225

208226
- 📖 [Documentation](https://docs.hlquery.com)

lib/Client.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const System = require('./System');
2020
const Modules = require('./Modules');
2121
const Users = require('./Users');
2222
const Analytics = require('./Analytics');
23+
const Presets = require('./Presets');
2324
const { RouteCommand, ModuleRouteCommand } = require('./Route');
2425
const Config = require('../utils/Config');
2526

@@ -57,6 +58,7 @@ class Client {
5758
this._modules = new Modules(this.request);
5859
this._users = new Users(this.request);
5960
this._analytics = new Analytics(this.request);
61+
this._presets = new Presets(this.request);
6062
}
6163

6264
/**
@@ -693,6 +695,15 @@ class Client {
693695
return this._analytics;
694696
}
695697

698+
/**
699+
* Get search presets API instance.
700+
*
701+
* @returns {Presets}
702+
*/
703+
presets() {
704+
return this._presets;
705+
}
706+
696707
async analyticsClick(payload) {
697708
return await this._analytics.click(payload);
698709
}
@@ -760,6 +771,10 @@ class Client {
760771
async globalSearch(params = {}) {
761772
return await this._search.globalSearch(params);
762773
}
774+
775+
async searchAll(params = {}) {
776+
return await this._search.searchAll(params);
777+
}
763778

764779
/**
765780
* Execute arbitrary HTTP request

lib/Presets.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* hlquery Node.js Client - Search presets API
3+
*/
4+
5+
const { requireNonEmptyString } = require('./Route');
6+
7+
class Presets {
8+
constructor(request) {
9+
this.request = request;
10+
}
11+
12+
async list() {
13+
return await this.request.execute('GET', '/presets');
14+
}
15+
16+
async get(name) {
17+
requireNonEmptyString(name, 'Preset name');
18+
return await this.request.execute('GET', `/presets/${encodeURIComponent(name)}`);
19+
}
20+
21+
async create(name, preset) {
22+
requireNonEmptyString(name, 'Preset name');
23+
return await this.request.execute('POST', `/presets/${encodeURIComponent(name)}`, preset);
24+
}
25+
26+
async update(name, preset) {
27+
requireNonEmptyString(name, 'Preset name');
28+
return await this.request.execute('PUT', `/presets/${encodeURIComponent(name)}`, preset);
29+
}
30+
31+
async upsert(name, preset) {
32+
return await this.update(name, preset);
33+
}
34+
35+
async delete(name) {
36+
requireNonEmptyString(name, 'Preset name');
37+
return await this.request.execute('DELETE', `/presets/${encodeURIComponent(name)}`);
38+
}
39+
}
40+
41+
module.exports = Presets;

lib/Route.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ class ModuleRouteCommand extends RouteCommand {
106106
module.exports = {
107107
RouteCommand,
108108
ModuleRouteCommand,
109+
requireNonEmptyString,
109110
cleanRoute,
110111
joinPath
111112
};

lib/Search.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,11 @@ class Search {
245245

246246
return await this.request.execute(method, '/search', body, queryParams);
247247
}
248+
249+
/** Alias for globalSearch: search one merged result set across all collections. */
250+
async searchAll(params = {}) {
251+
return await this.globalSearch(params);
252+
}
248253

249254
/**
250255
* Vector search

lib/System.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,14 @@ class System {
122122
return await this.request.execute('GET', '/search-config');
123123
}
124124

125+
async configFiles() {
126+
return await this.request.execute('GET', '/config-files');
127+
}
128+
129+
async cache() {
130+
return await this.request.execute('GET', '/cache');
131+
}
132+
125133
async updateCounters(params = {}, method = 'POST') {
126134
method = this._getOrPostMethod(method, 'Update counters');
127135
return await this.request.execute(method, '/update-counters', null, params);

lib/conformance.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ const NODE_CLIENT_ROUTE_COVERAGE = [
2626
{ path: '/consistency', methods: ['GET'], status: 'supported', client: 'system.consistency' },
2727
{ path: '/self-check', methods: ['GET'], status: 'supported', client: 'system.selfCheck' },
2828
{ path: '/admin/storage_status', methods: ['GET'], status: 'supported', client: 'system.storageStatus' },
29+
{ path: '/config-files', methods: ['GET'], status: 'supported', client: 'system.configFiles' },
30+
{ path: '/cache', methods: ['GET'], status: 'supported', client: 'system.cache' },
2931
{ path: '/links', methods: ['GET'], status: 'supported', client: 'client.links' },
3032
{ path: '/links/ping', methods: ['GET'], status: 'supported', client: 'client.linksPing' },
3133
{ path: '/links/connect', methods: ['POST'], status: 'supported', client: 'client.linksConnect' },
@@ -48,22 +50,38 @@ const NODE_CLIENT_ROUTE_COVERAGE = [
4850
{ path: '/collections/{name}/documents/search', methods: ['GET', 'POST'], status: 'supported', client: 'search.searchLegacy' },
4951
{ path: '/collections/{name}/vector_search', methods: ['GET', 'POST'], status: 'supported', client: 'search.vectorSearch' },
5052
{ path: '/multi_search', methods: ['GET', 'POST'], status: 'supported', client: 'search.multiSearch' },
51-
{ path: '/search', methods: ['GET', 'POST'], status: 'supported', client: 'search.globalSearch' },
53+
{ path: '/search', methods: ['GET', 'POST'], status: 'supported', client: 'search.globalSearch/searchAll' },
5254
{ path: '/sql', methods: ['GET', 'POST'], status: 'supported', client: 'system.sql/execSql' },
5355
{ path: '/collections/{name}/documents/facet_counts', methods: ['GET', 'POST'], status: 'supported', client: 'documents.facetCounts' },
5456
{ path: '/collections/{name}/documents/export', methods: ['GET', 'POST'], status: 'supported', client: 'documents.export' },
5557
{ path: '/collections/{name}/synonyms', methods: ['GET'], status: 'supported', client: 'synonyms.list' },
5658
{ path: '/collections/{name}/synonyms/{id}', methods: ['GET', 'POST', 'PUT', 'DELETE'], status: 'supported', client: 'synonyms.get/create/update/delete' },
59+
{ path: '/collections/{name}/synonym_sets', methods: ['GET'], status: 'omitted', reason: 'Compatibility alias; synonyms.list uses /collections/{name}/synonyms.' },
60+
{ path: '/collections/{name}/synonym_sets/{id}', methods: ['GET', 'POST', 'PUT', 'DELETE'], status: 'omitted', reason: 'Compatibility alias; synonym item methods use /collections/{name}/synonyms/{id}.' },
5761
{ path: '/synonyms', methods: ['GET'], status: 'supported', client: 'synonyms.listAll' },
5862
{ path: '/synonyms/global', methods: ['GET'], status: 'supported', client: 'synonyms.listGlobal' },
5963
{ path: '/synonyms/global/{id}', methods: ['GET', 'POST', 'PUT', 'DELETE'], status: 'supported', client: 'synonyms.getGlobal/createGlobal/updateGlobal/deleteGlobal' },
64+
{ path: '/synonym_sets', methods: ['GET'], status: 'omitted', reason: 'Compatibility alias; synonyms.listAll uses /synonyms.' },
65+
{ path: '/synonym_sets/global', methods: ['GET'], status: 'omitted', reason: 'Compatibility alias; synonyms.listGlobal uses /synonyms/global.' },
66+
{ path: '/synonym_sets/global/{id}', methods: ['GET', 'POST', 'PUT', 'DELETE'], status: 'omitted', reason: 'Compatibility alias; global synonym methods use /synonyms/global/{id}.' },
67+
{ path: '/synonym_sets/global/items/{id}', methods: ['GET', 'POST', 'PUT', 'DELETE'], status: 'omitted', reason: 'Compatibility alias; global synonym methods use /synonyms/global/{id}.' },
6068
{ path: '/collections/{name}/stopwords', methods: ['GET', 'POST'], status: 'supported', client: 'stopwords.list/create' },
6169
{ path: '/collections/{name}/stopwords/{word}', methods: ['DELETE'], status: 'supported', client: 'stopwords.delete' },
70+
{ path: '/collections/{name}/stopword_sets', methods: ['GET', 'POST'], status: 'omitted', reason: 'Compatibility alias; stopword methods use /collections/{name}/stopwords.' },
71+
{ path: '/collections/{name}/stopword_sets/{word}', methods: ['DELETE'], status: 'omitted', reason: 'Compatibility alias; stopwords.delete uses /collections/{name}/stopwords/{word}.' },
6272
{ path: '/stopwords', methods: ['GET'], status: 'supported', client: 'stopwords.listAll' },
6373
{ path: '/stopwords/global', methods: ['GET', 'POST'], status: 'supported', client: 'stopwords.listGlobal/createGlobal' },
6474
{ path: '/stopwords/global/{word}', methods: ['DELETE'], status: 'supported', client: 'stopwords.deleteGlobal' },
75+
{ path: '/stopword_sets', methods: ['GET'], status: 'omitted', reason: 'Compatibility alias; stopwords.listAll uses /stopwords.' },
76+
{ path: '/stopword_sets/global', methods: ['GET', 'POST'], status: 'omitted', reason: 'Compatibility alias; global stopword methods use /stopwords/global.' },
77+
{ path: '/stopword_sets/global/{word}', methods: ['DELETE'], status: 'omitted', reason: 'Compatibility alias; stopwords.deleteGlobal uses /stopwords/global/{word}.' },
78+
{ path: '/stopword_sets/global/items/{word}', methods: ['DELETE'], status: 'omitted', reason: 'Compatibility alias; stopwords.deleteGlobal uses /stopwords/global/{word}.' },
6579
{ path: '/collections/{name}/overrides', methods: ['GET'], status: 'supported', client: 'overrides.list' },
6680
{ path: '/collections/{name}/overrides/{id}', methods: ['GET', 'POST', 'PUT', 'DELETE'], status: 'supported', client: 'overrides.get/create/update/delete' },
81+
{ path: '/collections/{name}/curations', methods: ['GET'], status: 'omitted', reason: 'Compatibility alias; overrides.list uses /collections/{name}/overrides.' },
82+
{ path: '/collections/{name}/curations/{id}', methods: ['GET', 'POST', 'PUT', 'DELETE'], status: 'omitted', reason: 'Compatibility alias; override item methods use /collections/{name}/overrides/{id}.' },
83+
{ path: '/collections/{name}/curation_sets', methods: ['GET'], status: 'omitted', reason: 'Compatibility alias; overrides.list uses /collections/{name}/overrides.' },
84+
{ path: '/collections/{name}/curation_sets/{id}', methods: ['GET', 'POST', 'PUT', 'DELETE'], status: 'omitted', reason: 'Compatibility alias; override item methods use /collections/{name}/overrides/{id}.' },
6785
{ path: '/aliases', methods: ['GET'], status: 'supported', client: 'aliases.list' },
6886
{ path: '/aliases/{name}', methods: ['GET', 'POST', 'PUT', 'DELETE'], status: 'supported', client: 'aliases.get/create/update/delete' },
6987
{ path: '/keys', methods: ['GET', 'POST'], status: 'supported', client: 'keys.list/create' },
@@ -77,6 +95,8 @@ const NODE_CLIENT_ROUTE_COVERAGE = [
7795
{ path: '/search-config', methods: ['GET'], status: 'supported', client: 'system.searchConfig' },
7896
{ path: '/ready', methods: ['GET'], status: 'supported', client: 'system.ready' },
7997
{ path: '/repair', methods: ['GET', 'POST'], status: 'supported', client: 'system.repair' },
98+
{ path: '/presets', methods: ['GET'], status: 'supported', client: 'presets.list' },
99+
{ path: '/presets/{name}', methods: ['GET', 'POST', 'PUT', 'DELETE'], status: 'supported', client: 'presets.get/create/update/delete' },
80100
{ path: '/modules', methods: ['GET'], status: 'supported', client: 'modules.list' },
81101
{ path: '/modules/load', methods: ['POST'], status: 'omitted', reason: 'Prefix alias; modules.load(name) uses the explicit /loadmodule/{name} form.' },
82102
{ path: '/modules/unload', methods: ['POST'], status: 'omitted', reason: 'Prefix alias; modules.unload(name) uses the explicit /unloadmodule/{name} form.' },

test/route-conformance.test.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const assert = require('assert');
44
const Request = require('../lib/Request');
55
const Search = require('../lib/Search');
66
const System = require('../lib/System');
7+
const Presets = require('../lib/Presets');
78
const { NODE_CLIENT_ROUTE_COVERAGE } = require('../lib/conformance');
89

910
class RecordingRequest {
@@ -41,6 +42,7 @@ async function main() {
4142
const recorder = new RecordingRequest();
4243
const search = new Search(recorder, null);
4344
const system = new System(recorder);
45+
const presets = new Presets(recorder);
4446

4547
assert.deepStrictEqual(route('/multi_search').methods, ['GET', 'POST']);
4648
await search.multiSearch([], 'GET');
@@ -50,6 +52,18 @@ async function main() {
5052
await search.multiSearch([], 'POST');
5153
assert.strictEqual(recorder.last().method, 'POST');
5254

55+
assert.deepStrictEqual(route('/search').methods, ['GET', 'POST']);
56+
await search.searchAll({ q: 'research', collections: 'universities,science', limit: 20 });
57+
assert.deepStrictEqual(recorder.last(), {
58+
method: 'GET',
59+
path: '/search',
60+
body: null,
61+
query: { q: 'research', collections: 'universities,science', limit: 20 }
62+
});
63+
await search.searchAll({ body: { q: 'research', collections: ['universities', 'science'] } });
64+
assert.strictEqual(recorder.last().method, 'POST');
65+
assert.strictEqual(recorder.last().path, '/search');
66+
5367
for (const [path, invoke] of [
5468
['/update-counters', method => system.updateCounters({ force: 1 }, method)],
5569
['/repair', method => system.repair({ collection: 'books' }, method)]
@@ -62,6 +76,20 @@ async function main() {
6276
assert.strictEqual(recorder.last().method, 'POST');
6377
}
6478

79+
await system.configFiles();
80+
assert.strictEqual(recorder.last().path, '/config-files');
81+
await system.cache();
82+
assert.strictEqual(recorder.last().path, '/cache');
83+
84+
await presets.list();
85+
assert.deepStrictEqual(recorder.last(), {
86+
method: 'GET', path: '/presets', body: null, query: {}
87+
});
88+
await presets.update('daily/research', { query_by: 'title' });
89+
assert.strictEqual(recorder.last().method, 'PUT');
90+
assert.strictEqual(recorder.last().path, '/presets/daily%2Fresearch');
91+
await assert.rejects(presets.get(' '), /non-empty string/);
92+
6593
const request = new Request('https://search.example.test', {
6694
token: 'secret-token'
6795
});

0 commit comments

Comments
 (0)