ObsiViewer/server/meilisearch.client.mjs

118 lines
3.0 KiB
JavaScript

import { MeiliSearch } from 'meilisearch';
import { MEILI_HOST, MEILI_API_KEY, VAULT_PATH as CFG_VAULT_PATH } from './config.mjs';
const MEILI_KEY = MEILI_API_KEY;
console.log('[Meili] Config:', {
host: MEILI_HOST,
keyLength: MEILI_KEY?.length,
keyPreview: MEILI_KEY ? `${MEILI_KEY.slice(0, 8)}...` : 'none'
});
if (!MEILI_KEY) {
console.warn('[Meili] No API key provided; running without authentication. Set MEILI_API_KEY or MEILI_MASTER_KEY when securing Meilisearch.');
}
/**
* Create and return a Meilisearch client instance
*/
export function meiliClient() {
const options = MEILI_KEY ? { host: MEILI_HOST, apiKey: MEILI_KEY } : { host: MEILI_HOST };
console.log('[Meili] Creating client with options:', {
host: options.host,
hasApiKey: !!options.apiKey,
apiKeyLength: options.apiKey?.length,
apiKeyHex: options.apiKey ? Buffer.from(options.apiKey).toString('hex') : 'none'
});
return new MeiliSearch(options);
}
/**
* Generate index name from vault path (supports multi-vault)
*/
export function vaultIndexName(vaultPath = CFG_VAULT_PATH ?? './vault') {
// Simple safe name generation; adjust to hash if paths can collide
const safe = vaultPath.replace(/[^a-z0-9]+/gi, '_').toLowerCase();
return `notes_${safe}`;
}
/**
* Ensure index exists and configure settings for optimal search
*/
export async function ensureIndexSettings(client, indexUid) {
// Create the index if it doesn't exist, then fetch it.
// Some Meilisearch operations are task-based; we wait for completion to avoid 404s.
let index;
try {
index = await client.getIndex(indexUid);
} catch (e) {
const task = await client.createIndex(indexUid, { primaryKey: 'id' });
if (task?.taskUid !== undefined) {
await client.waitForTask(task.taskUid, { timeOutMs: 30_000 });
}
index = await client.getIndex(indexUid);
}
// Configure searchable, filterable, sortable, and faceted attributes
const settingsTask = await index.updateSettings({
searchableAttributes: [
'title',
'content',
'file',
'path',
'tags',
'properties.*',
'headings'
],
displayedAttributes: [
'id',
'title',
'path',
'file',
'tags',
'properties',
'updatedAt',
'createdAt',
'excerpt'
],
filterableAttributes: [
'tags',
'file',
'path',
'parentDirs',
'properties.*',
'year',
'month',
'favoris',
'template',
'task'
],
sortableAttributes: [
'updatedAt',
'createdAt',
'title',
'file',
'path'
],
faceting: {
maxValuesPerFacet: 1000
},
typoTolerance: {
enabled: true,
minWordSizeForTypos: {
oneTypo: 3,
twoTypos: 6
}
},
pagination: {
maxTotalHits: 10000
}
});
if (settingsTask?.taskUid !== undefined) {
await client.waitForTask(settingsTask.taskUid, { timeOutMs: 30_000 });
}
console.log(`[Meili] Index "${indexUid}" configured successfully`);
return index;
}