118 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			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;
 | |
| }
 |