@aiquants/fuzzy-search
Version:
Advanced fuzzy search library with Levenshtein distance, n-gram indexing, and Web Worker support
1 lines • 152 kB
Source Map (JSON)
{"version":3,"sources":["../src/types/index.ts","../src/core/FuzzySearchManager.ts","../src/react/index.ts"],"sourcesContent":["/**\n * @module types/index\n * @description Core types for the fuzzy search library.\n * @description あいまい検索ライブラリのコア型定義。\n */\n\n// ===== Basic Configuration Types =====\n\n/**\n * @interface FuzzySearchOptions\n * @description Configuration options for fuzzy search functionality.\n * @description あいまい検索機能の設定オプション。\n */\nexport interface FuzzySearchOptions {\n /**\n * @property {number} threshold - Similarity threshold (0.0-1.0).\n * @description 類似度の閾値。この値以上のスコアを持つアイテムが結果に含まれます。\n */\n threshold: number\n\n /**\n * @property {boolean} caseSensitive - Whether to perform case-sensitive search.\n * @description 大文字小文字を区別するかどうか。true の場合、区別して検索します。\n */\n caseSensitive: boolean\n\n /**\n * @property {number} learningWeight - Learning weight for user behavior (0.0-2.0).\n * @description ユーザーの選択行動に基づく学習による重み付け。1.0より大きいと学習効果が強まります。\n */\n learningWeight: number\n\n /**\n * @property {number} debounceMs - Debounce time in milliseconds.\n * @description 検索入力のデバウンス時間(ミリ秒)。連続入力時の不要な検索処理を防ぎます。\n */\n debounceMs: number\n\n /**\n * @property {'and' | 'or'} multiTermOperator - Logical operator applied to space-separated query terms.\n * @description スペース区切りのクエリ語に適用する論理演算子。\n */\n multiTermOperator: \"and\" | \"or\"\n\n /**\n * @property {boolean} autoSearchOnIndexRebuild - Whether to trigger a search immediately after the index is rebuilt.\n * @description インデックス再構築直後に自動的に検索を実行するかどうか。\n */\n autoSearchOnIndexRebuild: boolean\n\n /**\n * @property {Record<string, number>} customWeights - Custom field weights for scoring.\n * @description 特定のフィールドに対するカスタムの重み設定。スコアリングに影響します。\n */\n customWeights: Record<string, number>\n\n /**\n * @property {number} ngramSize - N-gram size for indexing (default: 2).\n * @description インデックス作成に使用する N-gram のサイズ。通常は2または3が使われます。\n */\n ngramSize: number\n\n /**\n * @property {number} minNgramOverlap - Minimum n-gram overlap for candidate filtering.\n * @description 候補フィルタリングのための最小 N-gram 重複数。\n */\n minNgramOverlap: number\n\n /**\n * @property {'score' | 'relevance' | 'original'} sortBy - Sort results by field.\n * @description 検索結果のソート基準。'score' (類似度スコア)、'relevance' (関連性)、'original' (元の順序) から選択します。\n */\n sortBy: \"score\" | \"relevance\" | \"original\"\n\n /**\n * @property {'asc' | 'desc'} sortOrder - Sort order (ascending or descending).\n * @description ソート順序 (昇順または降順)。\n */\n sortOrder: \"asc\" | \"desc\"\n\n /**\n * @property {boolean} enableIndexFiltering - Enable index-based filtering stage.\n * @description インデックスベースのフィルタリングステージを有効にするかどうか。\n */\n enableIndexFiltering: boolean\n\n /**\n * @property {boolean} enableLevenshtein - Enable Levenshtein distance calculation stage.\n * @description レーベンシュタイン距離計算ステージを有効にするかどうか。\n */\n enableLevenshtein: boolean\n\n /**\n * @property {'index-first' | 'levenshtein-first' | 'balanced'} parallelSearchStrategy - Parallel search strategy.\n * @description 並列検索の戦略。'index-first' (インデックス優先)、'levenshtein-first' (レーベンシュタイン優先)、'balanced' (バランス) から選択します。\n */\n parallelSearchStrategy: \"index-first\" | \"levenshtein-first\" | \"balanced\"\n\n /**\n * @property {object} [workerUrls] - Custom Worker URLs for external hosting.\n * @description 外部ホスティング用のカスタム Worker URL。\n */\n workerUrls?: {\n /**\n * @property {string} [indexWorker] - URL for the index worker.\n * @description Index Worker の URL。\n */\n indexWorker?: string\n /**\n * @property {string} [levenshteinWorker] - URL for the levenshtein worker.\n * @description Levenshtein Worker の URL。\n */\n levenshteinWorker?: string\n }\n\n /**\n * @property {object} [indexWorkerOptions] - Index Worker specific parameters.\n * @description Index Worker 固有のパラメータ。\n */\n indexWorkerOptions?: {\n /**\n * @property {IndexStrategy} [strategy] - Index Worker search strategy ('fast' | 'accurate' | 'hybrid').\n * @description Index Worker の検索戦略。\n */\n strategy?: IndexStrategy\n /**\n * @property {number} [threshold] - Similarity threshold for Index Worker (0.0-1.0).\n * @description Index Worker 用の類似度閾値。\n */\n threshold?: number\n /**\n * @property {number} [ngramOverlapThreshold] - N-gram overlap threshold ratio (0.0-1.0).\n * @description N-gram 重複閾値の比率。\n */\n ngramOverlapThreshold?: number\n /**\n * @property {number} [minCandidatesRatio] - Minimum candidates ratio for fast search (0.0-1.0).\n * @description 高速検索用の最小候補比率。\n */\n minCandidatesRatio?: number\n /**\n * @property {number} [maxCandidatesRatio] - Maximum candidates ratio to trigger filtering (0.0-1.0).\n * @description フィルタリングを発動するための最大候補比率。\n */\n maxCandidatesRatio?: number\n /**\n * @property {number} [jaroWinklerPrefix] - Jaro-Winkler prefix scaling factor (0.0-0.25).\n * @description Jaro-Winkler アルゴリズムの接頭辞スケーリング係数。\n */\n jaroWinklerPrefix?: number\n /**\n * @property {number} [maxResults] - Maximum number of results for Index Worker.\n * @description Index Worker 用の最大結果数。\n */\n maxResults?: number\n /**\n * @property {number} [relevanceFieldWeight] - Weight for matched field count in relevance score (0.0-1.0).\n * @description relevance スコア計算時のマッチフィールド数の重み。\n */\n relevanceFieldWeight?: number\n /**\n * @property {number} [relevancePerfectMatchBonus] - Bonus for perfect match in relevance score (0.0-1.0).\n * @description relevance スコア計算時の完全一致ボーナス。\n */\n relevancePerfectMatchBonus?: number\n }\n\n /**\n * @property {object} [levenshteinWorkerOptions] - Levenshtein Worker specific parameters.\n * @description Levenshtein Worker 固有のパラメータ。\n */\n levenshteinWorkerOptions?: {\n /**\n * @property {number} [threshold] - Similarity threshold for Levenshtein Worker (0.0-1.0).\n * @description Levenshtein Worker 用の類似度閾値。\n */\n threshold?: number\n /**\n * @property {number} [lengthSimilarityThreshold] - Length-based similarity threshold for early rejection.\n * @description 長さベースの類似度閾値(早期リジェクション用)。\n */\n lengthSimilarityThreshold?: number\n /**\n * @property {number} [partialMatchBonus] - Partial match bonus score.\n * @description 部分一致が検出された場合に加算されるボーナススコア。\n */\n partialMatchBonus?: number\n /**\n * @property {number} [lengthDiffPenalty] - Length difference penalty ratio.\n * @description 文字列長の差によるペナルティの比率。\n */\n lengthDiffPenalty?: number\n /**\n * @property {number} [maxResults] - Maximum number of results for Levenshtein Worker.\n * @description Levenshtein Worker 用の最大結果数。\n */\n maxResults?: number\n /**\n * @property {number} [relevanceFieldWeight] - Weight for matched field count in relevance score (0.0-1.0).\n * @description relevance スコア計算時のマッチフィールド数の重み。\n */\n relevanceFieldWeight?: number\n }\n}\n\n/**\n * @type PartialFuzzySearchOptions\n * @description Partial options for initializing fuzzy search. Allows overriding a subset of the default options.\n * @description あいまい検索初期化用の部分オプション。デフォルトオプションの一部を上書きできます。\n */\nexport type PartialFuzzySearchOptions = Partial<FuzzySearchOptions>\n\n/**\n * @const DEFAULT_FUZZY_SEARCH_OPTIONS\n * @description Default options for fuzzy search. Used when no options are provided.\n * @description あいまい検索のデフォルトオプション。オプションが指定されなかった場合に使用されます。\n */\nexport const DEFAULT_FUZZY_SEARCH_OPTIONS: FuzzySearchOptions = {\n threshold: 0.4,\n caseSensitive: false,\n learningWeight: 1.2,\n debounceMs: 300,\n multiTermOperator: \"and\",\n autoSearchOnIndexRebuild: true,\n customWeights: {},\n ngramSize: 2,\n minNgramOverlap: 1,\n sortBy: \"relevance\",\n sortOrder: \"desc\",\n enableIndexFiltering: true,\n enableLevenshtein: true,\n parallelSearchStrategy: \"balanced\",\n indexWorkerOptions: {\n strategy: \"hybrid\",\n threshold: 0.4,\n ngramOverlapThreshold: 0.3,\n minCandidatesRatio: 0.1,\n maxCandidatesRatio: 0.5,\n jaroWinklerPrefix: 0.1,\n maxResults: 1000,\n relevanceFieldWeight: 0.2,\n relevancePerfectMatchBonus: 0.1,\n },\n levenshteinWorkerOptions: {\n threshold: 0.3,\n lengthSimilarityThreshold: 0.1,\n partialMatchBonus: 0.1,\n lengthDiffPenalty: 1.0,\n maxResults: 1000,\n relevanceFieldWeight: 0.15,\n },\n}\n\n// ===== Search Result Types =====\n\n/**\n * @interface SearchResultItem\n * @description Represents an individual search result item with metadata.\n * @description メタデータを含む個別の検索結果アイテムを表します。\n * @template T - The type of the original item.\n */\nexport interface SearchResultItem<T = unknown> {\n /**\n * @property {T} item - The original item from the search dataset.\n * @description 検索データセットの元のアイテム。\n */\n item: T\n\n /**\n * @property {number} score - Final similarity score (0.0-1.0+). May exceed 1.0 in Relevance mode due to bonuses.\n * @description 最終的な類似度スコア (0.0-1.0+)。Relevance モードではボーナスにより 1.0 を超える場合があります。\n */\n score: number\n\n /**\n * @property {number} baseScore - Base similarity score before any adjustments (0.0-1.0).\n * @description 調整前の基本類似度スコア (0.0-1.0)。Relevance モードの重み付け前の値です。\n */\n baseScore: number\n\n /**\n * @property {string[]} matchedFields - Fields that matched the search query.\n * @description 検索クエリにマッチしたフィールドのリスト。\n */\n matchedFields: string[]\n\n /**\n * @property {number} [originalIndex] - Original index in the source dataset.\n * @description 元のデータセットにおけるインデックス。\n */\n originalIndex?: number\n\n /**\n * @property {Record<string, unknown>} [metadata] - Additional metadata for the result.\n * @description 結果に関連する追加のメタデータ。\n */\n metadata?: Record<string, unknown>\n}\n\n/**\n * @interface SearchResults\n * @description Represents the complete search results, including items and metadata.\n * @description アイテムとメタデータを含む完全な検索結果を表します。\n * @template T - The type of the items in the results.\n */\nexport interface SearchResults<T = unknown> {\n /**\n * @property {SearchResultItem<T>[]} results - Array of search result items.\n * @description 検索結果アイテムの配列。\n */\n results: SearchResultItem<T>[]\n\n /**\n * @property {number} totalItems - Total number of items searched.\n * @description 検索対象となった総アイテム数。\n */\n totalItems: number\n\n /**\n * @property {number} processingTime - Time taken for the search operation (ms).\n * @description 検索処理全体にかかった時間(ミリ秒)。\n */\n processingTime: number\n\n /**\n * @property {string} query - The search query that was used.\n * @description この検索で使用された検索クエリ。\n */\n query: string\n\n /**\n * @property {Partial<FuzzySearchOptions>} [options] - Search options used for this search.\n * @description この検索で使用された検索オプション。\n */\n options?: Partial<FuzzySearchOptions>\n\n /**\n * @property {SearchStats} [stats] - Additional search statistics.\n * @description 追加の検索統計情報。\n */\n stats?: SearchStats\n}\n\n// ===== Index and Strategy Types =====\n\n/**\n * @type IndexStrategy\n * @description Enumeration of available index strategies.\n * @description 利用可能なインデックス戦略の列挙。\n * - `fast`: N-gram index only for quick filtering. 高速なフィルタリングのためのN-gramインデックスのみ。\n * - `accurate`: Word-based and phonetic indexes for higher accuracy. 高精度な単語ベースおよび音韻インデックス。\n * - `hybrid`: A combination of fast and accurate strategies. 高速戦略と高精度戦略の組み合わせ。\n */\nexport type IndexStrategy = \"fast\" | \"accurate\" | \"hybrid\"\n\n/**\n * @interface IndexMetadata\n * @description Interface for metadata about the search index.\n * @description 検索インデックスに関するメタデータのインターフェース。\n */\nexport interface IndexMetadata {\n /**\n * @property {number} itemCount - Number of items indexed.\n * @description インデックス化されたアイテムの総数。\n */\n itemCount: number\n\n /**\n * @property {number} buildTime - Time taken to build the index (ms).\n * @description インデックスの構築にかかった時間(ミリ秒)。\n */\n buildTime: number\n\n /**\n * @property {number} createdAt - Index creation timestamp.\n * @description インデックスが作成されたときのタイムスタンプ。\n */\n createdAt: number\n\n /**\n * @property {string[]} indexedFields - Fields that were indexed.\n * @description インデックス化の対象となったフィールドのリスト。\n */\n indexedFields: string[]\n\n /**\n * @property {number} ngramSize - N-gram size used for indexing.\n * @description インデックス化に使用された N-gram のサイズ。\n */\n ngramSize: number\n\n /**\n * @property {IndexStrategy} [strategy] - Strategy used for indexing.\n * @description インデックス化に使用された戦略。\n */\n strategy?: IndexStrategy\n\n /**\n * @property {number} [totalIndexSize] - Total size of the index.\n * @description インデックス全体の合計サイズ。\n */\n totalIndexSize?: number\n\n /**\n * @property {object} [indexSizes] - Individual index sizes.\n * @description 各インデックスタイプ(ngram, word, phonetic)の個別サイズ。\n */\n indexSizes?: {\n ngram: number\n word: number\n phonetic: number\n }\n}\n\n// ===== Parallel Search Types =====\n\n/**\n * @type ParallelSearchStrategy\n * @description Options for parallel search strategy.\n * @description 並列検索戦略のオプション。\n *\n * @x-type-gen-skip-doc\n */\nexport type ParallelSearchStrategy = \"index-first\" | \"levenshtein-first\" | \"balanced\"\n\n/**\n * @interface ParallelSearchStageResult\n * @description Represents the result from a single stage in the parallel search pipeline.\n * @description 並列検索パイプラインの単一ステージからの結果を表します。\n * @template T - The type of the items in the results.\n */\nexport interface ParallelSearchStageResult<T = unknown> {\n /**\n * @property {'index' | 'levenshtein'} stage - Stage identifier.\n * @description ステージの識別子 ('index' または 'levenshtein')。\n */\n stage: \"index\" | \"levenshtein\"\n\n /**\n * @property {SearchResultItem<T>[]} results - Search results from this stage.\n * @description このステージからの検索結果。\n */\n results: SearchResultItem<T>[]\n\n /**\n * @property {number} processingTime - Processing time for this stage.\n * @description このステージの処理時間。\n */\n processingTime: number\n\n /**\n * @property {number} confidence - Confidence score for the results of this stage.\n * @description このステージの結果に対する信頼度スコア。\n */\n confidence: number\n\n /**\n * @property {number} candidatesProcessed - Number of candidates processed in this stage.\n * @description このステージで処理された候補数。\n */\n candidatesProcessed: number\n}\n\n/**\n * @interface MergedParallelSearchResult\n * @description Represents the merged result from the parallel search pipeline.\n * @description 並列検索パイプラインからマージされた結果を表します。\n * @template T - The type of the items in the results.\n */\nexport interface MergedParallelSearchResult<T = unknown> {\n /**\n * @property {SearchResultItem<T>[]} results - Final merged search results.\n * @description 最終的にマージされた検索結果。\n */\n results: SearchResultItem<T>[]\n\n /**\n * @property {ParallelSearchStageResult<T>} indexStage - Results from the index stage.\n * @description インデックスステージからの結果。\n */\n indexStage: ParallelSearchStageResult<T>\n\n /**\n * @property {ParallelSearchStageResult<T>} levenshteinStage - Results from the Levenshtein stage.\n * @description レーベンシュタインステージからの結果。\n */\n levenshteinStage: ParallelSearchStageResult<T>\n\n /**\n * @property {number} totalProcessingTime - Total processing time for the entire pipeline.\n * @description パイプライン全体の総処理時間。\n */\n totalProcessingTime: number\n\n /**\n * @property {ParallelSearchStrategy} mergeStrategy - Merge strategy used.\n * @description 使用されたマージ戦略。\n */\n mergeStrategy: ParallelSearchStrategy\n\n /**\n * @property {number} mergeQuality - Quality score of the merged results.\n * @description マージされた結果の品質スコア。\n */\n mergeQuality: number\n}\n\n// ===== Worker Communication Types =====\n\n/**\n * @interface IndexWorkerRequest\n * @description Defines the request structure for communication with the Indexing Worker.\n * @description インデックス作成 Worker との通信のためのリクエスト構造を定義します。\n * @template T - The type of items to be indexed or searched.\n */\nexport interface IndexWorkerRequest<T = unknown> {\n type: \"ping\" | \"buildIndex\" | \"indexSearch\" | \"getStats\" | \"resetStats\"\n id: string\n query?: string\n items?: T[]\n searchFields?: string[]\n options?: Partial<FuzzySearchOptions>\n}\n\n/**\n * @interface UnifiedWorkerResponse\n * @description Completely unified response structure for all worker types.\n * @description すべてのワーカータイプに対応する完全統一レスポンス構造。\n * @template T - The type of items in the search results.\n */\nexport interface UnifiedWorkerResponse<T = unknown> {\n type: string\n id: string\n\n // 必須フィールド\n stats: UnifiedWorkerStats\n metrics: WorkerPerformanceMetrics\n processingTime: number\n\n // 条件付きフィールド\n results?: SearchResultItem<T>[]\n error?: string\n\n // ワーカー固有情報\n workerType: \"index\" | \"levenshtein\"\n operationMeta?: Record<string, unknown>\n}\n\n/**\n * @interface LevenshteinWorkerRequest\n * @description Defines the request structure for communication with the Levenshtein Distance Worker.\n * @description Levenshtein 距離計算 Worker との通信のためのリクエスト構造を定義します。\n * @template T - The type of items to be processed.\n */\nexport interface LevenshteinWorkerRequest<T = unknown> {\n type: \"ping\" | \"setData\" | \"calculateSimilarity\" | \"batchSimilarity\" | \"getStats\" | \"levenshteinSearch\" | \"resetStats\"\n id: string\n query?: string\n items?: T[]\n searchFields?: string[]\n candidateIndices?: number[]\n targets?: string[]\n target?: string\n options?: Partial<FuzzySearchOptions>\n threshold?: number\n enablePhonetic?: boolean\n}\n\n// ===== Worker-Specific Types =====\n\n/**\n * @interface WorkerSearchStatistics\n * @description Enhanced search statistics provided by a Worker.\n * @description Worker から提供される拡張検索統計。\n */\nexport interface WorkerSearchStatistics {\n candidatesFound: number\n indexSize: number\n ngrams: number\n searchStrategy: string\n}\n\n/**\n * @interface DetailedWorkerStats\n * @description Detailed performance metrics and statistics from a Worker.\n * @description Worker からの詳細なパフォーマンス指標と統計。\n */\nexport interface DetailedWorkerStats {\n type: \"stats\"\n requestId: string\n workerStats: {\n totalSearches: number\n totalIndexBuilds: number\n currentDataSize: number\n lastIndexTime: number\n }\n memory: {\n estimatedMemoryUsage: number\n heapUsage?: number\n maxMemoryUsage?: number\n }\n performanceMetrics: {\n timings: {\n averageSearchTime: number\n averageIndexTime: number\n maxSearchTime: number\n minSearchTime: number\n }\n throughput: {\n searchesPerSecond: number\n indexOperationsPerSecond: number\n }\n }\n indexMetadata?: {\n totalTokens: number\n uniqueTokens: number\n buildTime: number\n size: number\n }\n timestamp: number\n}\n\n/**\n * @interface QueryLengthStat\n * @description Statistics related to query length.\n * @description クエリ長に関連する統計。\n */\nexport interface QueryLengthStat {\n count: number\n avgTime: number\n totalTime: number\n}\n\n/**\n * @interface FieldMatchStat\n * @description Statistics related to matches in specific fields.\n * @description 特定のフィールドでのマッチに関する統計。\n */\nexport interface FieldMatchStat {\n matches: number\n avgScore: number\n totalScore: number\n}\n\n/**\n * @interface SimilarityThresholdStats\n * @description Statistics on the distribution of similarity scores.\n * @description 類似度スコアの分布に関する統計。\n */\nexport interface SimilarityThresholdStats {\n above80: number\n above60: number\n above40: number\n below40: number\n}\n\n/**\n * @interface PerformanceInsights\n * @description Data providing insights into search performance.\n * @description 検索パフォーマンスに関する洞察を提供するデータ。\n */\nexport interface PerformanceInsights {\n recentAverageProcessingTime: number\n recentAverageResultCount: number\n throughputPerSecond: number\n totalHistoryEntries: number\n mostCommonQueryLength: number\n bestPerformingField: string\n similarityDistribution: {\n highSimilarity: number\n mediumSimilarity: number\n lowSimilarity: number\n veryLowSimilarity: number\n }\n}\n\n/**\n * @interface PerformanceHistoryEntry\n * @description Individual performance history entry for tracking operation performance.\n * @description 操作パフォーマンスを追跡するための個別のパフォーマンス履歴エントリ。\n */\nexport interface PerformanceHistoryEntry {\n timestamp: number\n queryLength: number\n itemCount: number\n processingTime: number\n resultCount: number\n operationType: string\n additionalMetrics?: Record<string, unknown>\n}\n\n/**\n * @interface FieldStatistic\n * @description Statistics for individual search fields across all operations.\n * @description 全操作における個別検索フィールドの統計。\n */\nexport interface FieldStatistic {\n matchCount: number\n averageScore: number\n totalScore: number\n bestScore: number\n worstScore: number\n}\n\n/**\n * @interface UnifiedWorkerStats\n * @description Completely unified statistics interface for all worker types.\n * @description すべてのワーカータイプに対応する完全統一統計インターフェース。\n */\nexport interface UnifiedWorkerStats {\n /**\n * @property basic Basic statistics common to all workers.\n * @description 全ワーカー共通の基本統計。\n */\n basic: {\n totalSearches: number\n totalProcessingTime: number\n averageSearchTime: number\n lastSearchTime: number\n errorCount: number\n timestamp: number\n }\n\n /**\n * @property workerSpecific Worker-type specific statistics.\n * @description ワーカータイプ固有の統計。\n */\n workerSpecific: {\n workerType: \"index\" | \"levenshtein\"\n operationCount: number\n operationDistribution: Record<string, number>\n specificMetrics: Record<string, unknown>\n }\n\n /**\n * @property analysis Advanced analysis and insights.\n * @description 高度な分析とインサイト。\n */\n analysis: {\n fieldPerformance: Record<string, FieldStatistic>\n performanceInsights: PerformanceInsights\n recentHistory: PerformanceHistoryEntry[]\n }\n}\n\n/**\n * @type WorkerStats\n * @description Unified alias for basic worker statistics.\n * @description 基本ワーカー統計の統一エイリアス。\n */\nexport type WorkerStats = UnifiedWorkerStats[\"basic\"]\n\n/**\n * @type WorkerSpecificStats\n * @description Alias for worker-specific statistics section.\n * @description ワーカー固有統計セクションのエイリアス。\n */\nexport type WorkerSpecificStats = UnifiedWorkerStats[\"workerSpecific\"]\n\n/**\n * @type WorkerAnalysis\n * @description Alias for worker analysis section.\n * @description ワーカー分析セクションのエイリアス。\n */\nexport type WorkerAnalysis = UnifiedWorkerStats[\"analysis\"]\n\n/**\n * @interface WorkerPerformanceMetrics\n * @description Performance metrics for detailed monitoring of a Worker.\n * @description Worker の詳細な監視のためのパフォーマンスメトリクス。\n */\nexport interface WorkerPerformanceMetrics {\n timings: {\n averageProcessingTime: number\n fastestProcessing: number\n slowestProcessing: number\n lastProcessingTime: number\n }\n throughput: {\n operationsPerSecond: number\n totalOperations: number\n }\n quality: {\n successRate: number\n errorRate: number\n timeoutRate: number\n }\n}\n\n// ===== Search Statistics Types =====\n\n/**\n * @interface SearchStats\n * @description Overall search statistics for performance monitoring.\n * @description パフォーマンス監視のための全体的な検索統計。\n */\nexport interface SearchStats {\n /**\n * @property {number} totalSearches - Total number of searches performed.\n * @description 実行された総検索数。\n */\n totalSearches: number\n\n /**\n * @property {number} averageSearchTime - Average search time in milliseconds.\n * @description 平均検索時間(ミリ秒)。\n */\n averageSearchTime: number\n\n /**\n * @property {Array<{ query: string; count: number }>} popularQueries - Popular search queries.\n * @description 人気のある検索クエリとその実行回数。\n */\n popularQueries: Array<{ query: string; count: number }>\n\n /**\n * @property {Record<string, { averageTime: number; sampleSize: number }>} performanceBySize - Performance statistics by dataset size.\n * @description データセットのサイズ別のパフォーマンス統計。\n */\n performanceBySize: Record<string, { averageTime: number; sampleSize: number }>\n\n /**\n * @property {object} workers - Worker statistics information.\n * @description 各ワーカーの統計情報。\n */\n workers: {\n index: {\n stats: UnifiedWorkerStats\n }\n levenshtein: {\n stats: UnifiedWorkerStats\n }\n }\n}\n\n/**\n * @interface SearchStatistics\n * @description Search statistics provided by a worker.\n * @description Worker から提供される検索統計。\n */\nexport interface SearchStatistics {\n candidatesFound: number\n indexSize: number\n ngrams: number\n words?: number\n phonetic?: number\n searchStrategy: string\n}\n\n/**\n * @interface SearchHistory\n * @description Represents a single entry in the search history for learning and analytics.\n * @description 学習と分析のための検索履歴の単一エントリを表します。\n */\nexport interface SearchHistory {\n /**\n * @property {string} query - The search query.\n * @description 検索クエリ。\n */\n query: string\n\n /**\n * @property {number} timestamp - Timestamp of the search.\n * @description 検索が実行されたときのタイムスタンプ。\n */\n timestamp: number\n\n /**\n * @property {number} resultCount - Number of results found.\n * @description 見つかった結果の数。\n */\n resultCount: number\n\n /**\n * @property {number} processingTime - Processing time in milliseconds.\n * @description 処理にかかった時間(ミリ秒)。\n */\n processingTime: number\n\n /**\n * @property {number} [searchTime] - Alias for processingTime.\n * @description processingTime のエイリアス。\n */\n searchTime?: number\n\n /**\n * @property {number} [selectedIndex] - Index of the selected result, if any.\n * @description ユーザーが選択した結果のインデックス(存在する場合)。\n */\n selectedIndex?: number\n\n /**\n * @property {string} [selectedItemId] - ID of the selected item, if any.\n * @description ユーザーが選択したアイテムのID(存在する場合)。\n */\n selectedItemId?: string\n\n /**\n * @property {number} [satisfactionScore] - User satisfaction score (1-5).\n * @description ユーザー満足度スコア(1-5)。\n */\n satisfactionScore?: number\n\n /**\n * @property {boolean} successful - Whether the search was considered successful.\n * @description 検索が成功したと見なされたかどうか。\n */\n successful: boolean\n\n /**\n * @property {string} [context] - Context or source of the search.\n * @description 検索が実行されたコンテキストまたはソース。\n */\n context?: string\n}\n\n/**\n * @interface LearningData\n * @description Data structure for storing learned information to improve search results.\n * @description 検索結果を改善するために学習した情報を格納するためのデータ構造。\n */\nexport interface LearningData {\n /**\n * @property {Record<string, number>} fieldWeights - Field-specific weights learned from user behavior.\n * @description ユーザーの行動から学習したフィールド固有の重み。\n */\n fieldWeights: Record<string, number>\n\n /**\n * @property {Record<string, number>} queryPatterns - Query patterns and their frequency count.\n * @description クエリのパターンとその出現頻度。\n */\n queryPatterns: Record<string, number>\n\n /**\n * @property {object} userPreferences - User preferences and behavior.\n * @description ユーザーの好みと行動の記録。\n */\n userPreferences: {\n preferredThreshold: number\n frequentFields: string[]\n searchStyle: \"exact\" | \"fuzzy\" | \"mixed\"\n lastUsedOptions: Partial<FuzzySearchOptions>\n }\n\n /**\n * @property {object} performanceMetrics - Performance metrics for learning optimization.\n * @description 学習の最適化に使用されるパフォーマンス指標。\n */\n performanceMetrics: {\n averageSearchTime: Record<string, number>\n indexBuildTime: number\n }\n}\n\n// ===== Utility Types =====\n\n/**\n * @type ExtractItemType\n * @description Utility type to extract the item type from a SearchResultItem.\n * @description SearchResultItem からアイテムの型を抽出するためのユーティリティ型。\n * @template T - The type of the SearchResultItem.\n */\nexport type ExtractItemType<T> = T extends SearchResultItem<infer U> ? U : never\n\n/**\n * @type SearchFields\n * @description Defines the search fields with type safety, accepting keys of T or strings.\n * @description 型安全性を備えた検索フィールドを定義します。T のキーまたは文字列を受け入れます。\n * @template T - The type of the items being searched.\n */\nexport type SearchFields<T> = Array<keyof T | string>\n\n/**\n * @type SearchResultCallback\n * @description Callback function type for handling search results.\n * @description 検索結果を処理するためのコールバック関数型。\n * @template T - The type of the items in the results.\n */\nexport type SearchResultCallback<T> = (results: SearchResults<T>) => void\n\n// ===== Type Guards =====\n\n/**\n * @function isSearchResultItem\n * @description Type guard to check if an object is a SearchResultItem.\n * @description オブジェクトが SearchResultItem かどうかをチェックする型ガード。\n * @param {unknown} obj - The object to check.\n * @returns {boolean} - True if the object is a SearchResultItem.\n * @template T - The expected type of the item within the SearchResultItem.\n */\nexport function isSearchResultItem<T>(obj: unknown): obj is SearchResultItem<T> {\n // オブジェクトであり、null でなく、必須プロパティが存在するかをチェック\n return typeof obj === \"object\" && obj !== null && \"item\" in obj && \"score\" in obj && \"matchedFields\" in obj\n}\n\n// ===== React Hook Types =====\n\n/**\n * @type UseFuzzySearchOptions\n * @description Options type for the useFuzzySearch hook, extending PartialFuzzySearchOptions.\n * @description useFuzzySearch フック用のオプション型。PartialFuzzySearchOptions を拡張します。\n */\nexport type UseFuzzySearchOptions = PartialFuzzySearchOptions\n\n/**\n * @interface SearchHistoryResult\n * @description The return type for a hook that manages search history.\n * @description 検索履歴を管理するフックの戻り値の型。\n */\nexport interface SearchHistoryResult {\n searchHistory: SearchHistory[]\n addToHistory: (term: string) => void\n clearHistory: () => void\n}\n\n/**\n * @interface PerformanceMetrics\n * @description Data type for the performance metrics callback.\n * @description パフォーマンス指標コールバック用のデータ型。\n */\nexport interface PerformanceMetrics {\n totalSearches: number\n averageSearchTime: number\n itemCount: number\n resultsCount: number\n workerStats?: {\n index?: UnifiedWorkerStats\n levenshtein?: UnifiedWorkerStats\n }\n}\n\n/**\n * @interface FuzzySearchResult\n * @description The return type for the simplified fuzzy search hook.\n * @description 簡易版のあいまい検索フックの戻り値の型。\n * @template T - The type of the items being searched.\n */\nexport interface FuzzySearchResult<T> {\n searchTerm: string\n setSearchTerm: (term: string) => void\n filteredItems: SearchResultItem<T>[]\n isSearching: boolean\n isIndexBuilding: boolean\n error: string | null\n searchHistory: SearchHistory[]\n options: FuzzySearchOptions\n selectItem: (item: T, index: number) => void\n clearSearch: () => void\n clearHistory: () => void\n resetStats: () => Promise<void>\n getSearchSuggestions: (query: string, limit?: number) => string[]\n performanceMetrics: PerformanceMetrics\n rebuildIndex: (customOptions?: UseFuzzySearchOptions) => Promise<void>\n}\n","/**\n * @module core/FuzzySearchManager\n * @description Advanced fuzzy search manager with Web Worker integration and learning capabilities.\n * @description Web Worker統合と学習機能を備えた高度なあいまい検索管理クラス。\n */\n\nimport type {\n FuzzySearchOptions,\n IndexWorkerRequest,\n LearningData,\n LevenshteinWorkerRequest,\n MergedParallelSearchResult,\n ParallelSearchStageResult,\n ParallelSearchStrategy,\n SearchHistory,\n SearchResultItem,\n SearchResults,\n SearchStats,\n UnifiedWorkerResponse,\n UnifiedWorkerStats,\n} from \"../types/index.ts\"\nimport { DEFAULT_FUZZY_SEARCH_OPTIONS } from \"../types/index.ts\"\n\nconst LOG_PREFIX = \"[fuzzy-search]\"\n\n/**\n * @class GlobalWorkerManager\n * @description Manages global singleton instances of Web Workers to prevent duplication across multiple FuzzySearchManager instances.\n * @description 複数の FuzzySearchManager インスタンス間での重複を防ぐため、Web Worker のグローバルなシングルトンインスタンスを管理します。\n */\nclass GlobalWorkerManager {\n private static instance: GlobalWorkerManager | null = null\n private indexWorker: Worker | null = null\n private levenshteinWorker: Worker | null = null\n private initializationPromise: Promise<void> | null = null\n private referenceCount = 0\n\n private constructor() {}\n\n /**\n * @static\n * @method getInstance\n * @description Gets the singleton instance of the GlobalWorkerManager.\n * @description GlobalWorkerManager のシングルトンインスタンスを取得します。\n * @returns {GlobalWorkerManager} The singleton instance.\n */\n static getInstance(): GlobalWorkerManager {\n // インスタンスが存在しない場合、新しいインスタンスを作成\n if (!GlobalWorkerManager.instance) {\n GlobalWorkerManager.instance = new GlobalWorkerManager()\n }\n return GlobalWorkerManager.instance\n }\n\n /**\n * @method getWorkers\n * @description Retrieves or initializes the global Web Workers.\n * @description グローバルな Web Worker を取得または初期化します。\n * @param {FuzzySearchOptions} options - Configuration options for worker initialization.\n * @returns {Promise<{ indexWorker: Worker | null; levenshteinWorker: Worker | null }>} A promise that resolves with the worker instances.\n */\n async getWorkers(options: FuzzySearchOptions): Promise<{ indexWorker: Worker | null; levenshteinWorker: Worker | null }> {\n // 参照カウンタをインクリメント\n this.referenceCount++\n\n // ワーカーが既に存在する場合、既存のインスタンスを返す\n if (this.indexWorker && this.levenshteinWorker) {\n console.log(`${LOG_PREFIX} 🔧 Using existing global Workers (ref count: ${this.referenceCount})`)\n return { indexWorker: this.indexWorker, levenshteinWorker: this.levenshteinWorker }\n }\n\n // 初期化が進行中でない場合、初期化を開始\n if (!this.initializationPromise) {\n this.initializationPromise = this.initializeWorkers(options)\n }\n\n // 初期化が完了するのを待つ\n await this.initializationPromise\n return { indexWorker: this.indexWorker, levenshteinWorker: this.levenshteinWorker }\n }\n\n /**\n * @private\n * @method initializeWorkers\n * @description Initializes the Index and Levenshtein Web Workers.\n * @description Index Worker と Levenshtein Worker を初期化します。\n * @param {FuzzySearchOptions} options - Configuration options containing worker URLs.\n * @returns {Promise<void>} A promise that resolves when workers are initialized.\n */\n private async initializeWorkers(options: FuzzySearchOptions): Promise<void> {\n // Worker がサポートされていない環境では何もしない\n if (typeof Worker === \"undefined\") return\n\n try {\n // Index Strategy Worker を初期化\n if (!this.indexWorker) {\n const indexWorkerUrl = options.workerUrls?.indexWorker ? new URL(options.workerUrls.indexWorker, window.location.origin) : new URL(\"./worker/indexWorker.mjs\", import.meta.url)\n console.log(LOG_PREFIX, \"🔧 Global Index Worker URL:\", indexWorkerUrl.toString())\n this.indexWorker = new Worker(indexWorkerUrl, { type: \"module\" })\n console.log(LOG_PREFIX, \"🔧 Global Index Worker: Initialized successfully\")\n }\n\n // Levenshtein Distance Worker を初期化\n if (!this.levenshteinWorker) {\n const levenshteinWorkerUrl = options.workerUrls?.levenshteinWorker ? new URL(options.workerUrls.levenshteinWorker, window.location.origin) : new URL(\"./worker/levenshteinWorker.mjs\", import.meta.url)\n console.log(LOG_PREFIX, \"🔧 Global Levenshtein Worker URL:\", levenshteinWorkerUrl.toString())\n this.levenshteinWorker = new Worker(levenshteinWorkerUrl, { type: \"module\" })\n console.log(LOG_PREFIX, \"🔧 Global Levenshtein Worker: Initialized successfully\")\n }\n } catch (error) {\n console.warn(LOG_PREFIX, \"⚠️ Failed to initialize global Workers:\", error)\n }\n }\n\n /**\n * @method releaseReference\n * @description Decrements the reference count for the global workers.\n * @description グローバルワーカーへの参照カウントをデクリメントします。\n */\n releaseReference(): void {\n this.referenceCount--\n console.log(`${LOG_PREFIX} 🔧 Worker reference released (ref count: ${this.referenceCount})`)\n\n // 再作成を避けるため、参照カウントが0でもWorkerを維持\n }\n\n /**\n * @method dispose\n * @description Terminates the global workers and resets the manager state.\n * @description グローバルワーカーを終了し、マネージャーの状態をリセットします。\n */\n dispose(): void {\n // Index Worker を終了\n if (this.indexWorker) {\n this.indexWorker.terminate()\n this.indexWorker = null\n }\n // Levenshtein Worker を終了\n if (this.levenshteinWorker) {\n this.levenshteinWorker.terminate()\n this.levenshteinWorker = null\n }\n // 状態をリセット\n this.initializationPromise = null\n this.referenceCount = 0\n console.log(LOG_PREFIX, \"🔧 Global Workers disposed\")\n }\n}\n\n/**\n * @class SearchEventEmitter\n * @description A simple event emitter for handling search-related events.\n * @description 検索関連のイベントを処理するためのシンプルなイベントエミッター。\n */\nclass SearchEventEmitter {\n private listeners = new Map<string, Set<(...args: unknown[]) => void>>()\n\n /**\n * @method on\n * @description Registers an event listener.\n * @description イベントリスナーを登録します。\n * @param {string} event - The name of the event.\n * @param {Function} listener - The callback function.\n */\n on(event: string, listener: (...args: unknown[]) => void): void {\n // 指定されたイベントのリスナーセットが存在しない場合、新しく作成\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set())\n }\n // リスナーをセットに追加\n this.listeners.get(event)?.add(listener)\n }\n\n /**\n * @method off\n * @description Unregisters an event listener.\n * @description イベントリスナーを登録解除します。\n * @param {string} event - The name of the event.\n * @param {Function} listener - The callback function to remove.\n */\n off(event: string, listener: (...args: unknown[]) => void): void {\n const eventListeners = this.listeners.get(event)\n // イベントリスナーが存在すれば、セットから削除\n if (eventListeners) {\n eventListeners.delete(listener)\n }\n }\n\n /**\n * @method emit\n * @description Emits an event, calling all registered listeners.\n * @description イベントを発行し、登録されているすべてのリスナーを呼び出します。\n * @param {string} event - The name of the event to emit.\n * @param {unknown} [data] - Data to pass to the listeners.\n */\n emit(event: string, data?: unknown): void {\n const eventListeners = this.listeners.get(event)\n if (eventListeners) {\n // 登録されている各リスナーを安全に呼び出す\n eventListeners.forEach((listener) => {\n try {\n listener(data)\n } catch (error) {\n console.error(`${LOG_PREFIX} ❌ Error in event listener for ${event}:`, error)\n }\n })\n }\n }\n\n /**\n * @method removeAllListeners\n * @description Removes all event listeners.\n * @description すべてのイベントリスナーを削除します。\n */\n removeAllListeners(): void {\n this.listeners.clear()\n }\n}\n\n/**\n * @class FuzzySearchManager\n * @description Manages fuzzy search operations, integrating Web Workers for performance and including learning capabilities.\n * @description Web Worker を統合してパフォーマンスを向上させ、学習機能も備えた、あいまい検索操作を管理するクラス。\n * @template T - The type of items to be searched.\n */\nexport class FuzzySearchManager<T = unknown> {\n // 2つのワーカーを管理\n private indexWorker: Worker | null = null\n private levenshteinWorker: Worker | null = null\n\n // 検索履歴と学習データを保持\n private searchHistory: SearchHistory[] = []\n private learningData: LearningData = {\n fieldWeights: {},\n queryPatterns: {},\n userPreferences: {\n preferredThreshold: 0.4,\n frequentFields: [],\n searchStyle: \"fuzzy\",\n lastUsedOptions: {},\n },\n performanceMetrics: {\n averageSearchTime: {},\n indexBuildTime: 0,\n },\n }\n // 非同期リクエストの管理\n private requestId = 0\n private pendingRequests = new Map<\n string,\n {\n resolve: (result: SearchResultItem<T>[]) => void\n reject: (error: Error) => void\n timestamp: number\n }\n >()\n private eventEmitter = new SearchEventEmitter()\n private workersInitialized: Promise<void>\n private isIndexBuilt = false\n private lastIndexedDataHash: string | null = null\n // 検索統計情報\n private stats: SearchStats = {\n totalSearches: 0,\n averageSearchTime: 0,\n popularQueries: [],\n performanceBySize: {},\n workers: {\n index: {\n stats: {} as UnifiedWorkerStats,\n },\n levenshtein: {\n stats: {} as UnifiedWorkerStats,\n },\n },\n }\n\n /**\n * Constructor for the FuzzySearchManager.\n * @param {FuzzySearchOptions} [defaultOptions] - Default options for fuzzy search.\n */\n constructor(private defaultOptions: FuzzySearchOptions = DEFAULT_FUZZY_SEARCH_OPTIONS) {\n // 学習データをロードし、ワーカーを初期化\n this.loadLearningData()\n this.workersInitialized = this.initializeWorkers()\n\n // 定期的にタイムアウトしたリクエストをクリーンアップ\n setInterval(() => this.cleanupPendingRequests(), 30000)\n }\n\n /**\n * @private\n * @method initializeWorkers\n * @description Initializes both Index Strategy and Levenshtein Distance Workers using the global manager.\n * @description グローバルマネージャーを使用して Index Strategy と Levenshtein Distance Worker の両方を初期化します。\n * @returns {Promise<void>} A promise that resolves when workers are initialized and configured.\n */\n private async initializeWorkers(): Promise<void> {\n // Worker がサポートされていない場合は何もしない\n if (typeof Worker === \"undefined\") return\n\n try {\n // グローバルマネージャーからワーカーインスタンスを取得\n const globalManager = GlobalWorkerManager.getInstance()\n const workers = await globalManager.getWorkers(this.defaultOptions)\n\n this.indexWorker = workers.indexWorker\n this.levenshteinWorker = workers.levenshteinWorker\n\n // Worker が存在する場合のみイベントハンドラーを設定\n if (this.indexWorker) {\n this.indexWorker.onmessage = this.handleIndexWorkerMessage.bind(this)\n this.indexWorker.onerror = this.handleIndexWorkerError.bind(this)\n // 追加のエラーをキャッチするための 'error' イベントリスナー\n this.indexWorker.addEventListener(\"error\", (error) => {\n console.error(LOG_PREFIX, \"🔧 Index Worker Error (Event Listener):\", error)\n })\n }\n\n if (this.levenshteinWorker) {\n this.levenshteinWorker.onmessage = this.handleLevenshteinWorkerMessage.bind(this)\n this.levenshteinWorker.onerror = this.handleLevenshteinWorkerError.bind(this)\n // 追加のエラーをキャッチするための 'error' イベントリスナー\n this.levenshteinWorker.addEventListener(\"error\", (error) => {\n console.error(LOG_PREFIX, \"🔧 Levenshtein Worker Error (Event Listener):\", error)\n })\n }\n\n // ワーカーの初期化完了を通知\n this.eventEmitter.emit(\"workers:initialized\", {\n indexWorker: !!this.indexWorker,\n levenshteinWorker: !!this.levenshteinWorker,\n })\n\n // Worker の接続性を ping でテスト\n await this.testWorkerConnectivity()\n } catch (error) {\n console.warn(LOG_PREFIX, \"⚠️ Failed to initialize Workers:\", error)\n }\n }\n\n /**\n * @private\n * @method testWorkerConnectivity\n * @description Tests worker connectivity by sending ping messages and waiting for pong responses.\n * @description ping メッセージを送信し、pong 応答を待つことでワーカーの接続性をテストします。\n * @returns {Promise<void>} A promise that resolves when both workers have responded.\n */\n private async testWorkerConnectivity(): Promise<void> {\n try {\n const connectivityTests: Promise<void>[] = []\n\n if (this.indexWorker) {\n connectivityTests.push(this.pingWorker(this.indexWorker, \"index\"))\n }\n\n if (this.levenshteinWorker) {\n connectivityTests.push(this.pingWorker(this.levenshteinWorker, \"levenshtein\"))\n }\n\n await Promise.all(connectivityTests)\n } catch (error) {\n console.error(LOG_PREFIX, \"🔧 Worker connectivity test failed:\", error)\n }\n }\n\n /**\n * @private\n * @method pingWorker\n * @description Sends a ping message to a worker and waits for the pong response.\n * @description ワーカーに ping メッセージを送り、pong 応答を待機します。\n * @param {Worker} worker - The worker instance to ping.\n * @param {'index' | 'levenshtein'} workerName - The worker identifier.\n * @returns {Promise<void>} A promise that resolves when the worker responds.\n */\n private async pingWorker(worker: Worker, workerName: \"index\" | \"levenshtein\"): Promise<void> {\n const timeout = 5000\n const pingId = `ping-${workerName}-${Date.now()}`\n\n await new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => {\n console.warn(`${LOG_PREFIX} 🔧 ${workerName === \"index\" ? \"Index\" : \"Levenshtein\"} Worker: Ping timeout`)\n worker.removeEventListener(\"message\", messageHandler)\n reject(new Error(`${workerName === \"index\" ? \"Index\" : \"Levenshtein\"} Worker ping timeout`))\n }, timeout)\n\n const messageHandler = (event: MessageEvent) => {\n const data = event.data as UnifiedWorkerResponse\n if (data.type === \"pong\" && data.id === pingId) {\n clearTimeout(timer)\n worker.removeEventListener(\"message\", messageHandler)\n console.log(`${LOG_PREFIX} 🔧 ${workerName === \"index\" ? \"Index\" : \"Levenshtein\"} Worker: Ping successful`)\n resolve()\n }\n }\n\n worker.addEventListener(\"message\", messageHandler)\n console.log(`${LOG_PREFIX} 🔧 ${workerName === \"index\" ? \"Index\" : \"Levenshtein\"} Worker: Sending ping message:`, pingId)\n worker.postMessage({ type: \"ping\", id: pingId })\n })\n }\n\n /**\n * @private\n * @method handleIndexWorkerMessage\n * @description Handles messages received from the Index Strategy Worker.\n * @description Index Strategy Worker から受信したメッセージを処理します。\n * @param {MessageEvent} event - The message event from the worker.\n */\n private handleIndexWorkerMessage(event: MessageEvent): void {\n const data = event.data as UnifiedWorkerResponse<T>\n console.log(LOG_PREFIX, \"🔧 Index Worker message received:\", data.type, data)\n\n if (data.type === \"indexSearchResults\") {\n // 保留中のリクエストを解決\n const pending = this.pendingRequests.get(data.id)\n if (pending) {\n this.pendingRequests.delete(data.id)\n\n if (data.error) {\n pending.reject(new Error(data.error))\n } else {\n pending.resolve(data.results || [])\n }\n }\n\n // Index Worker からの統計を更新\n // if (data.stats) {\n // this.stats.workers.index.stats = data.stats;\n // this.eventEmitter.emit(\"worker:statsUpdated\", {\n // worker: 'index',\n // stats: this.stats.workers.index.stats\n // });\n // }\n\n // 検索完了イベントを発行\n this.eventEmitter.emit(\"indexWorker:searchCompleted\", {\n requestId: data.id,\n resultsCount: data.results?.length || 0,\