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' ], 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; }