115 lines
3.0 KiB
JavaScript
115 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'
|
|
],
|
|
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;
|
|
}
|