@fondation-io/fast-db-batch-search-client
Version:
TypeScript client for Fast-DB batch search API with support for fuzzy search
261 lines • 9.46 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BatchSearchClient = void 0;
const axios_1 = __importDefault(require("axios"));
/**
* Client for Fast-DB Batch Search API
*/
class BatchSearchClient {
constructor(options = {}) {
const { baseUrl = 'http://localhost:8080', timeout = 30000, includeMetrics = true } = options;
this.axios = axios_1.default.create({
baseURL: baseUrl,
timeout: timeout,
headers: {
'Content-Type': 'application/json',
},
});
this.includeMetrics = includeMetrics;
}
/**
* Execute a batch search query
* @param table The table/collection to search in
* @param nodeField The field containing node information
* @param nodeQuery The node to search for
* @param targetField The field containing target information
* @param targetQueries Array of target queries to search for
* @param projection Fields to return in results
* @param fuzzy Whether to use fuzzy search (default: true)
* @param resultsPerQuery Maximum results per target query (default: 10)
*/
async batchSearch(table, nodeField, nodeQuery, targetField, targetQueries, projection = ['*'], fuzzy = true, resultsPerQuery = 10) {
const query = {
query: {
$select: projection,
$from: table,
$where: {
$batch: {
$node_field: nodeField,
$node_query: nodeQuery,
$target_field: targetField,
$target_queries: targetQueries,
$fuzzy: fuzzy,
$results_per_query: resultsPerQuery,
},
},
},
include_metrics: this.includeMetrics,
};
try {
const startTime = Date.now();
const response = await this.axios.post('/query', query);
const elapsedTime = Date.now() - startTime;
if (!response.data.success) {
throw new Error(response.data.error || 'Query failed');
}
const data = response.data.data;
if (!data) {
return {
results: [],
grouped: {},
metrics: response.data.metrics,
totalResults: 0,
};
}
// Convert DataFrame response to array of objects
const results = this.dataFrameToObjects(data);
// Group results by search_group_hash
const grouped = this.groupResultsByHash(results);
return {
results,
grouped,
metrics: {
...(response.data.metrics || {}),
client_elapsed_ms: elapsedTime,
},
totalResults: results.length,
};
}
catch (error) {
const axiosError = error;
if (axiosError.response) {
throw new Error(`API Error: ${axiosError.response.data?.error || axiosError.response.statusText}`);
}
else if (axiosError.request) {
throw new Error('Network error: No response from server');
}
else {
throw error;
}
}
}
/**
* Convert DataFrame response to array of objects
*/
dataFrameToObjects(data) {
const { columns, rows } = data;
return rows.map((row) => {
const obj = { search_group_hash: '' };
columns.forEach((col, index) => {
obj[col] = row[index];
});
return obj;
});
}
/**
* Group results by search_group_hash
*/
groupResultsByHash(results) {
const groups = {};
for (const result of results) {
const hash = result.search_group_hash || 'unknown';
if (!groups[hash]) {
groups[hash] = [];
}
groups[hash].push(result);
}
return groups;
}
/**
* Execute a batch search query with joins across multiple tables
* @param params Join search parameters
*/
async batchSearchWithJoins(params) {
const query = {
query: {
$select: params.projection || ['*'],
$from: params.tables,
$join: params.joins,
$where: {
$batch: {
$node_field: params.nodeField,
$node_query: params.nodeQuery,
$target_field: params.targetField,
$target_queries: params.targetQueries,
$fuzzy: params.fuzzy !== false,
$results_per_query: params.resultsPerQuery || 10,
},
},
$orderBy: params.orderBy,
$limit: params.limit,
},
include_metrics: this.includeMetrics,
};
try {
const startTime = Date.now();
const response = await this.axios.post('/query', query);
const elapsedTime = Date.now() - startTime;
if (!response.data.success) {
throw new Error(response.data.error || 'Query failed');
}
const data = response.data.data;
if (!data) {
return {
results: [],
grouped: {},
metrics: response.data.metrics,
totalResults: 0,
};
}
// Convert DataFrame response to array of objects
const results = this.dataFrameToObjects(data);
// Group results by search_group_hash
const grouped = this.groupResultsByHash(results);
return {
results,
grouped,
metrics: {
...(response.data.metrics || {}),
client_elapsed_ms: elapsedTime,
},
totalResults: results.length,
};
}
catch (error) {
const axiosError = error;
if (axiosError.response) {
throw new Error(`API Error: ${axiosError.response.data?.error || axiosError.response.statusText}`);
}
else if (axiosError.request) {
throw new Error('Network error: No response from server');
}
else {
throw error;
}
}
}
/**
* Convenience method for searching book series by author
* @deprecated Use searchRelatedItems instead
*/
async searchBookSeries(author, seriesTitles, maxPerTitle = 3) {
return this.batchSearch('books', 'auteurs', author, 'titre', seriesTitles, ['titre', 'auteurs'], true, maxPerTitle);
}
/**
* Generic method for searching related items by a common node
*/
async searchRelatedItems(table, nodeField, nodeValue, targetField, targetValues, projection, maxPerTarget = 3) {
return this.batchSearch(table, nodeField, nodeValue, targetField, targetValues, projection || ['*'], true, maxPerTarget);
}
/**
* Convenience method for searching albums by artist using joins
*/
async searchAlbumsByArtist(artist, albumTitles, maxPerAlbum = 3) {
return this.batchSearchWithJoins({
tables: ['id_artists', 'album_artist', 'albums'],
joins: [
{
$type: 'inner',
$left: 'id_artists',
$right: 'album_artist',
$on: ['id_artists.id', 'album_artist.artist_id'],
},
{
$type: 'inner',
$left: 'album_artist',
$right: 'albums',
$on: ['album_artist.cb', 'albums.cb'],
},
],
nodeField: 'id_artists.artiste',
nodeQuery: artist,
targetField: 'albums.album',
targetQueries: albumTitles,
projection: {
artist_name: 'artiste',
album_title: 'album',
release_year: 'street_date',
},
fuzzy: true,
resultsPerQuery: maxPerAlbum,
});
}
/**
* Get search statistics from grouped results
*/
getSearchStats(grouped) {
const groupSizes = {};
let totalItems = 0;
let emptyGroups = 0;
for (const [hash, items] of Object.entries(grouped)) {
groupSizes[hash] = items.length;
totalItems += items.length;
if (items.length === 0) {
emptyGroups++;
}
}
const totalGroups = Object.keys(grouped).length;
const averageGroupSize = totalGroups > 0 ? totalItems / totalGroups : 0;
return {
totalGroups,
groupSizes,
averageGroupSize,
emptyGroups,
};
}
}
exports.BatchSearchClient = BatchSearchClient;
//# sourceMappingURL=client.js.map