hlquery-node-client 1.0.0

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.
@@ -0,0 +1,465 @@
1
+ /**
2
+ * Vector Search Example
3
+ *
4
+ * Demonstrates vector search capabilities with multiple collections:
5
+ * - Creates 4 collections with vector fields
6
+ * - Inserts 10 documents per collection with random embeddings
7
+ * - Performs vector searches to find relationships
8
+ * - Shows similarity-based document retrieval
9
+ *
10
+ * Usage: node examples/vector.js [token]
11
+ */
12
+
13
+ const Client = require('../lib/Client');
14
+
15
+ // Configuration
16
+ const baseUrl = 'http://localhost:9200';
17
+ const testToken = process.argv[2] || null;
18
+
19
+ // Helper function to generate random vector
20
+ function generateRandomVector(dimensions = 128) {
21
+ const vector = [];
22
+ for (let i = 0; i < dimensions; i++) {
23
+ vector.push(Math.random() * 2 - 1); // Random value between -1 and 1
24
+ }
25
+ // Normalize the vector
26
+ const magnitude = Math.sqrt(vector.reduce((sum, val) => sum + val * val, 0));
27
+ return vector.map(val => val / magnitude);
28
+ }
29
+
30
+ // Helper function to generate related vectors (similar to a base vector)
31
+ function generateRelatedVector(baseVector, similarity = 0.8) {
32
+ const related = baseVector.map(val => val * similarity);
33
+ const random = generateRandomVector(baseVector.length);
34
+ const orthogonal = random.map((val, i) => val * (1 - similarity));
35
+ return related.map((val, i) => val + orthogonal[i]);
36
+ }
37
+
38
+ // Helper function to print results
39
+ function printResult(title, response, printBody = true) {
40
+ console.log('='.repeat(80));
41
+ console.log(`TEST: ${title}`);
42
+ console.log('-'.repeat(80));
43
+
44
+ if (response) {
45
+ const status = response.getStatusCode();
46
+ const body = response.getBody();
47
+
48
+ console.log(`Status Code: ${status}`);
49
+
50
+ if (printBody && body) {
51
+ console.log('Response Body:');
52
+ if (typeof body === 'object') {
53
+ console.log(JSON.stringify(body, null, 2));
54
+ } else {
55
+ console.log(body);
56
+ }
57
+ }
58
+
59
+ if (status >= 200 && status < 300) {
60
+ console.log('✓ SUCCESS');
61
+ } else {
62
+ console.log('✗ FAILED');
63
+ }
64
+ } else {
65
+ console.log('Invalid response');
66
+ }
67
+ console.log('\n');
68
+ }
69
+
70
+ // Helper function to print vector query
71
+ function printVectorQuery(collectionName, vectorQuery, params = {}) {
72
+ console.log('\n' + '='.repeat(80));
73
+ console.log('VECTOR QUERY');
74
+ console.log('='.repeat(80));
75
+ console.log(`Collection: ${collectionName}`);
76
+ console.log(`Vector Dimensions: ${vectorQuery.length}`);
77
+ console.log(`Vector (first 10 values): [${vectorQuery.slice(0, 10).map(v => v.toFixed(4)).join(', ')}...]`);
78
+ console.log(`Limit: ${params.limit || 10}`);
79
+ console.log(`Threshold: ${params.threshold || 0.0}`);
80
+ console.log(`Normalize: ${params.normalize !== undefined ? params.normalize : true}`);
81
+ if (params.field_name) {
82
+ console.log(`Field Name: ${params.field_name}`);
83
+ }
84
+ console.log('='.repeat(80) + '\n');
85
+ }
86
+
87
+ // Main function
88
+ async function main() {
89
+ console.log('=== hlquery Vector Search Example ===\n');
90
+
91
+ // Create client
92
+ const client = new Client(baseUrl);
93
+ if (testToken) {
94
+ client.setAuthToken(testToken, 'bearer');
95
+ console.log(`Using authentication token: ${testToken.substring(0, 8)}...\n`);
96
+ }
97
+
98
+ // Collection names
99
+ const collections = [
100
+ 'products',
101
+ 'articles',
102
+ 'images',
103
+ 'documents'
104
+ ];
105
+
106
+ // Collection schemas with vector fields
107
+ const schemas = {
108
+ products: {
109
+ fields: [
110
+ { name: 'name', type: 'string' },
111
+ { name: 'description', type: 'string' },
112
+ { name: 'embedding', type: 'float[]' }
113
+ ]
114
+ },
115
+ articles: {
116
+ fields: [
117
+ { name: 'title', type: 'string' },
118
+ { name: 'content', type: 'string' },
119
+ { name: 'embedding', type: 'float[]' }
120
+ ]
121
+ },
122
+ images: {
123
+ fields: [
124
+ { name: 'filename', type: 'string' },
125
+ { name: 'caption', type: 'string' },
126
+ { name: 'embedding', type: 'float[]' }
127
+ ]
128
+ },
129
+ documents: {
130
+ fields: [
131
+ { name: 'title', type: 'string' },
132
+ { name: 'text', type: 'string' },
133
+ { name: 'embedding', type: 'float[]' }
134
+ ]
135
+ }
136
+ };
137
+
138
+ // Generate base vectors for each collection (for creating related documents)
139
+ const baseVectors = {};
140
+ collections.forEach(col => {
141
+ baseVectors[col] = generateRandomVector(128);
142
+ });
143
+
144
+ // ----------------------------------------------------------------====================================
145
+ // CREATE COLLECTIONS
146
+ // ----------------------------------------------------------------====================================
147
+ console.log('\n' + '#'.repeat(80));
148
+ console.log('# CREATING COLLECTIONS');
149
+ console.log('#'.repeat(80) + '\n');
150
+
151
+ for (const collectionName of collections) {
152
+ try {
153
+ // Check if collection exists, delete if it does
154
+ const existing = await client.getCollection(collectionName);
155
+ if (existing.getStatusCode() === 200) {
156
+ console.log(`Collection ${collectionName} exists, deleting...`);
157
+ await client.collections().delete(collectionName);
158
+ }
159
+ } catch (e) {
160
+ // Collection doesn't exist, that's fine
161
+ }
162
+
163
+ // Create collection
164
+ const createResult = await client.collections().create(collectionName, schemas[collectionName]);
165
+ if (createResult.isSuccess()) {
166
+ console.log(`✓ Created collection: ${collectionName}`);
167
+ } else {
168
+ console.log(`✗ Failed to create collection: ${collectionName}`);
169
+ console.log(` Error: ${createResult.getError()}`);
170
+ }
171
+ }
172
+ console.log('');
173
+
174
+ // ----------------------------------------------------------------====================================
175
+ // INSERT DOCUMENTS
176
+ // ----------------------------------------------------------------====================================
177
+ console.log('\n' + '#'.repeat(80));
178
+ console.log('# INSERTING DOCUMENTS');
179
+ console.log('#'.repeat(80) + '\n');
180
+
181
+ const allDocuments = {};
182
+
183
+ for (const collectionName of collections) {
184
+ console.log(`Inserting documents into ${collectionName}...`);
185
+ const documents = [];
186
+ const baseVector = baseVectors[collectionName];
187
+
188
+ for (let i = 1; i <= 10; i++) {
189
+ // Create documents with varying similarity to base vector
190
+ const similarity = 0.5 + (i / 10) * 0.4; // Range from 0.5 to 0.9
191
+ const embedding = generateRelatedVector(baseVector, similarity);
192
+
193
+ let doc;
194
+ switch (collectionName) {
195
+ case 'products':
196
+ doc = {
197
+ id: `product_${i}`,
198
+ name: `Product ${i}`,
199
+ description: `This is product number ${i} with similarity ${similarity.toFixed(2)}`,
200
+ embedding: embedding
201
+ };
202
+ break;
203
+ case 'articles':
204
+ doc = {
205
+ id: `article_${i}`,
206
+ title: `Article ${i}`,
207
+ content: `This is article number ${i} with similarity ${similarity.toFixed(2)}`,
208
+ embedding: embedding
209
+ };
210
+ break;
211
+ case 'images':
212
+ doc = {
213
+ id: `image_${i}`,
214
+ filename: `image_${i}.jpg`,
215
+ caption: `Image ${i} with similarity ${similarity.toFixed(2)}`,
216
+ embedding: embedding
217
+ };
218
+ break;
219
+ case 'documents':
220
+ doc = {
221
+ id: `doc_${i}`,
222
+ title: `Document ${i}`,
223
+ text: `This is document number ${i} with similarity ${similarity.toFixed(2)}`,
224
+ embedding: embedding
225
+ };
226
+ break;
227
+ }
228
+
229
+ documents.push(doc);
230
+ }
231
+
232
+ // Bulk import
233
+ const importResult = await client.documents().import(collectionName, documents);
234
+ if (importResult.isSuccess()) {
235
+ console.log(`✓ Inserted ${documents.length} documents into ${collectionName}`);
236
+ allDocuments[collectionName] = documents;
237
+ } else {
238
+ console.log(`✗ Failed to insert documents into ${collectionName}`);
239
+ console.log(` Error: ${importResult.getError()}`);
240
+ }
241
+ }
242
+ console.log('');
243
+
244
+ // Wait a bit for indexing
245
+ console.log('Waiting for documents to be indexed...');
246
+ await new Promise(resolve => setTimeout(resolve, 2000));
247
+ console.log('');
248
+
249
+ // ----------------------------------------------------------------====================================
250
+ // VECTOR SEARCH - Find Similar Documents
251
+ // ----------------------------------------------------------------====================================
252
+ console.log('\n' + '#'.repeat(80));
253
+ console.log('# VECTOR SEARCH - Finding Relationships');
254
+ console.log('#'.repeat(80) + '\n');
255
+
256
+ // Test 1: Search in products collection using base vector
257
+ console.log('TEST 1: Search in products collection using base vector\n');
258
+ const productsQuery = baseVectors.products;
259
+ printVectorQuery('products', productsQuery, { limit: 5, threshold: 0.0, normalize: true });
260
+
261
+ const productsSearch = await client.vectorSearch('products', {
262
+ vector_query: productsQuery,
263
+ limit: 5,
264
+ threshold: 0.0,
265
+ normalize: true
266
+ });
267
+
268
+ if (productsSearch.isSuccess()) {
269
+ const body = productsSearch.getBody();
270
+ console.log('RESULTS:');
271
+ if (body.hits && body.hits.length > 0) {
272
+ body.hits.forEach((hit, index) => {
273
+ console.log(`\n ${index + 1}. Document ID: ${hit.document?.id || hit.id}`);
274
+ console.log(` Name: ${hit.document?.name || 'N/A'}`);
275
+ console.log(` Similarity Score: ${hit.similarity_score !== undefined ? hit.similarity_score.toFixed(4) : 'N/A'}`);
276
+ if (hit.document?.embedding) {
277
+ const emb = hit.document.embedding;
278
+ if (Array.isArray(emb) && emb.length > 0) {
279
+ console.log(` Embedding (first 5): [${emb.slice(0, 5).map(v => v.toFixed(4)).join(', ')}...]`);
280
+ }
281
+ }
282
+ });
283
+ } else {
284
+ console.log(' No results found');
285
+ }
286
+ console.log(`\nTotal found: ${body.found || 0}`);
287
+ } else {
288
+ console.log(`✗ Search failed: ${productsSearch.getError()}`);
289
+ }
290
+ console.log('');
291
+
292
+ // Test 2: Search in articles collection using a related vector
293
+ console.log('TEST 2: Search in articles collection using related vector\n');
294
+ const articlesQuery = generateRelatedVector(baseVectors.articles, 0.85);
295
+ printVectorQuery('articles', articlesQuery, { limit: 5, threshold: 0.5, normalize: true });
296
+
297
+ const articlesSearch = await client.vectorSearch('articles', {
298
+ vector_query: articlesQuery,
299
+ limit: 5,
300
+ threshold: 0.5,
301
+ normalize: true
302
+ });
303
+
304
+ if (articlesSearch.isSuccess()) {
305
+ const body = articlesSearch.getBody();
306
+ console.log('RESULTS:');
307
+ if (body.hits && body.hits.length > 0) {
308
+ body.hits.forEach((hit, index) => {
309
+ console.log(`\n ${index + 1}. Document ID: ${hit.document?.id || hit.id}`);
310
+ console.log(` Title: ${hit.document?.title || 'N/A'}`);
311
+ console.log(` Similarity Score: ${hit.similarity_score !== undefined ? hit.similarity_score.toFixed(4) : 'N/A'}`);
312
+ });
313
+ } else {
314
+ console.log(' No results found (threshold too high)');
315
+ }
316
+ console.log(`\nTotal found: ${body.found || 0}`);
317
+ } else {
318
+ console.log(`✗ Search failed: ${articlesSearch.getError()}`);
319
+ }
320
+ console.log('');
321
+
322
+ // Test 3: Cross-collection search - find similar documents across collections
323
+ console.log('TEST 3: Cross-collection search - Find similar documents in images collection\n');
324
+ const imagesQuery = baseVectors.images;
325
+ printVectorQuery('images', imagesQuery, { limit: 10, threshold: 0.0, normalize: true });
326
+
327
+ const imagesSearch = await client.vectorSearch('images', {
328
+ vector_query: imagesQuery,
329
+ limit: 10,
330
+ threshold: 0.0,
331
+ normalize: true
332
+ });
333
+
334
+ if (imagesSearch.isSuccess()) {
335
+ const body = imagesSearch.getBody();
336
+ console.log('RESULTS:');
337
+ if (body.hits && body.hits.length > 0) {
338
+ body.hits.forEach((hit, index) => {
339
+ console.log(`\n ${index + 1}. Document ID: ${hit.document?.id || hit.id}`);
340
+ console.log(` Filename: ${hit.document?.filename || 'N/A'}`);
341
+ console.log(` Caption: ${hit.document?.caption || 'N/A'}`);
342
+ console.log(` Similarity Score: ${hit.similarity_score !== undefined ? hit.similarity_score.toFixed(4) : 'N/A'}`);
343
+ });
344
+ } else {
345
+ console.log(' No results found');
346
+ }
347
+ console.log(`\nTotal found: ${body.found || 0}`);
348
+ } else {
349
+ console.log(`✗ Search failed: ${imagesSearch.getError()}`);
350
+ }
351
+ console.log('');
352
+
353
+ // Test 4: Search with high threshold to find only very similar documents
354
+ console.log('TEST 4: High threshold search - Find only very similar documents\n');
355
+ const documentsQuery = baseVectors.documents;
356
+ printVectorQuery('documents', documentsQuery, { limit: 5, threshold: 0.7, normalize: true });
357
+
358
+ const documentsSearch = await client.vectorSearch('documents', {
359
+ vector_query: documentsQuery,
360
+ limit: 5,
361
+ threshold: 0.7,
362
+ normalize: true
363
+ });
364
+
365
+ if (documentsSearch.isSuccess()) {
366
+ const body = documentsSearch.getBody();
367
+ console.log('RESULTS:');
368
+ if (body.hits && body.hits.length > 0) {
369
+ body.hits.forEach((hit, index) => {
370
+ console.log(`\n ${index + 1}. Document ID: ${hit.document?.id || hit.id}`);
371
+ console.log(` Title: ${hit.document?.title || 'N/A'}`);
372
+ console.log(` Similarity Score: ${hit.similarity_score !== undefined ? hit.similarity_score.toFixed(4) : 'N/A'}`);
373
+ });
374
+ } else {
375
+ console.log(' No results found (threshold too high - no documents with similarity > 0.7)');
376
+ }
377
+ console.log(`\nTotal found: ${body.found || 0}`);
378
+ } else {
379
+ console.log(`✗ Search failed: ${documentsSearch.getError()}`);
380
+ }
381
+ console.log('');
382
+
383
+ // Test 5: Find relationships between collections
384
+ console.log('TEST 5: Find relationships - Search products using articles base vector\n');
385
+ const crossQuery = baseVectors.articles;
386
+ printVectorQuery('products', crossQuery, {
387
+ limit: 3,
388
+ threshold: 0.0,
389
+ normalize: true,
390
+ note: 'Using articles base vector to search products (cross-collection similarity)'
391
+ });
392
+
393
+ const crossSearch = await client.vectorSearch('products', {
394
+ vector_query: crossQuery,
395
+ limit: 3,
396
+ threshold: 0.0,
397
+ normalize: true
398
+ });
399
+
400
+ if (crossSearch.isSuccess()) {
401
+ const body = crossSearch.getBody();
402
+ console.log('RESULTS (Cross-collection similarity):');
403
+ if (body.hits && body.hits.length > 0) {
404
+ body.hits.forEach((hit, index) => {
405
+ console.log(`\n ${index + 1}. Document ID: ${hit.document?.id || hit.id}`);
406
+ console.log(` Name: ${hit.document?.name || 'N/A'}`);
407
+ console.log(` Similarity Score: ${hit.similarity_score !== undefined ? hit.similarity_score.toFixed(4) : 'N/A'}`);
408
+ console.log(` Note: Lower scores indicate less similarity across collections`);
409
+ });
410
+ } else {
411
+ console.log(' No results found');
412
+ }
413
+ console.log(`\nTotal found: ${body.found || 0}`);
414
+ } else {
415
+ console.log(`✗ Search failed: ${crossSearch.getError()}`);
416
+ }
417
+ console.log('');
418
+
419
+ // ----------------------------------------------------------------====================================
420
+ // SUMMARY
421
+ // ----------------------------------------------------------------====================================
422
+ console.log('\n' + '#'.repeat(80));
423
+ console.log('# SUMMARY');
424
+ console.log('#'.repeat(80) + '\n');
425
+
426
+ console.log('Created Collections:');
427
+ collections.forEach(col => {
428
+ console.log(` - ${col}`);
429
+ });
430
+ console.log('');
431
+
432
+ console.log('Documents Inserted:');
433
+ collections.forEach(col => {
434
+ const count = allDocuments[col] ? allDocuments[col].length : 0;
435
+ console.log(` - ${col}: ${count} documents`);
436
+ });
437
+ console.log('');
438
+
439
+ console.log('Vector Search Tests Performed:');
440
+ console.log(' 1. Products collection search (base vector, threshold 0.0)');
441
+ console.log(' 2. Articles collection search (related vector, threshold 0.5)');
442
+ console.log(' 3. Images collection search (base vector, all results)');
443
+ console.log(' 4. Documents collection search (base vector, high threshold 0.7)');
444
+ console.log(' 5. Cross-collection search (articles vector → products collection)');
445
+ console.log('');
446
+
447
+ console.log('Key Observations:');
448
+ console.log(' - Documents with higher similarity scores are more related');
449
+ console.log(' - Threshold parameter filters results by minimum similarity');
450
+ console.log(' - Vector normalization ensures consistent similarity calculations');
451
+ console.log(' - Cross-collection searches can find relationships across different data types');
452
+ console.log('');
453
+
454
+ console.log('Usage: node examples/vector.js [token]');
455
+ console.log('');
456
+ }
457
+
458
+ // Run main function
459
+ main().catch(error => {
460
+ console.error('Fatal error:', error);
461
+ if (error.stack) {
462
+ console.error(error.stack);
463
+ }
464
+ process.exit(1);
465
+ });
package/index.js ADDED
@@ -0,0 +1,39 @@
1
+ /**
2
+ * hlquery Node.js Client - Main Entry Point
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 Client = require('./lib/Client');
10
+
11
+ // Export main client class
12
+ module.exports = Client;
13
+
14
+ // Export all classes for advanced usage
15
+ module.exports.Client = Client;
16
+ module.exports.Request = require('./lib/Request');
17
+ module.exports.Response = require('./lib/Response');
18
+ module.exports.Collections = require('./lib/Collections');
19
+ module.exports.Documents = require('./lib/Documents');
20
+ module.exports.Search = require('./lib/Search');
21
+ module.exports.Keys = require('./lib/Keys');
22
+ module.exports.Aliases = require('./lib/Aliases');
23
+ module.exports.Overrides = require('./lib/Overrides');
24
+ module.exports.Synonyms = require('./lib/Synonyms');
25
+ module.exports.Stopwords = require('./lib/Stopwords');
26
+ module.exports.System = require('./lib/System');
27
+
28
+ // Export exceptions
29
+ module.exports.Exceptions = require('./lib/Exceptions');
30
+
31
+ // Export utilities
32
+ module.exports.Utils = {
33
+ Config: require('./utils/Config'),
34
+ Validator: require('./utils/Validator'),
35
+ Auth: require('./utils/Auth'),
36
+ Ranker: require('./utils/ranker'),
37
+ PDFParser: require('./utils/PDFParser'),
38
+ CSVParser: require('./utils/CSVParser')
39
+ };
package/lib/Aliases.js ADDED
@@ -0,0 +1,48 @@
1
+ /**
2
+ * hlquery Node.js Client - Aliases 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
+ * Aliases API operations
13
+ */
14
+ class Aliases {
15
+ constructor(request) {
16
+ this.request = request;
17
+ }
18
+
19
+ async list() {
20
+ return await this.request.execute('GET', '/aliases');
21
+ }
22
+
23
+ async get(name) {
24
+ Validator.validateCollectionName(name);
25
+ return await this.request.execute('GET', `/aliases/${encodeURIComponent(name)}`);
26
+ }
27
+
28
+ async create(name, params) {
29
+ Validator.validateCollectionName(name);
30
+ return await this.request.execute('POST', `/aliases/${encodeURIComponent(name)}`, params);
31
+ }
32
+
33
+ async update(name, params) {
34
+ Validator.validateCollectionName(name);
35
+ return await this.request.execute('PUT', `/aliases/${encodeURIComponent(name)}`, params);
36
+ }
37
+
38
+ async upsert(name, params) {
39
+ return await this.create(name, params);
40
+ }
41
+
42
+ async delete(name) {
43
+ Validator.validateCollectionName(name);
44
+ return await this.request.execute('DELETE', `/aliases/${encodeURIComponent(name)}`);
45
+ }
46
+ }
47
+
48
+ module.exports = Aliases;