This guide helps you migrate your application from v0.1.x/v0.2.x to the new v0.3.x architecture in quran-search-engine.
Version 0.3.x introduces significant architectural changes including:
quranData is now a Map<number, TVerse> instead of an arrayquranData is Now a MapOld (v0.1.x/v0.2.x):
const [quranData, setQuranData] = useState<QuranText[]>([]);
New (v0.3.x):
const [quranData, setQuranData] = useState<Map<number, QuranText> | null>(null);
search() Function Signature ChangedThe search() function now uses a Search Context Pattern instead of positional arguments.
Old (v0.1.x/v0.2.x):
const response = search(
query,
quranData, // QuranText[]
morphologyMap, // Map<number, MorphologyAya>
wordMap, // WordMap
options, // SearchOptions
{ page, limit }, // PaginationOptions
undefined, // Previously unused
searchCache, // LRUCache
);
New (v0.3.x):
const response = search(
query, // string
{
quranData, // Map<number, TVerse>
morphologyMap, // Map<number, MorphologyAya>
wordMap, // WordMap
invertedIndex, // InvertedIndex (required)
semanticMap, // Map<string, string[]> (optional)
phoneticMap, // Map<string, string[]> (optional)
},
options, // SearchOptions
{ page, limit }, // PaginationOptions
undefined, // Unused
searchCache, // LRUCache
);
You must build the inverted index before searching.
Old (v0.1.x/v0.2.x):
// No inverted index needed
New (v0.3.x):
import { buildInvertedIndex, loadSemanticData } from 'quran-search-engine';
const semanticMap = await loadSemanticData();
const invertedIndex = buildInvertedIndex(morphologyMap, quranData, semanticMap);
Old (v0.1.x/v0.2.x):
const [data, morphology, dictionary] = await Promise.all([
loadQuranData(),
loadMorphology(),
loadWordMap(),
]);
New (v0.3.x):
const [data, morphology, dictionary, semantic] = await Promise.all([
loadQuranData(),
loadMorphology(),
loadWordMap(),
loadSemanticData(), // New! Required for inverted index
]);
Old (v0.1.x/v0.2.x):
const client = createSearchWorker({
workerUrl: new URL('quran-search-engine/worker', import.meta.url),
});
await client.initData();
New (v0.3.x):
// Dynamic import for worker URL
const mod = await import('quran-search-engine/worker?url');
const client = createSearchWorker({ workerUrl: mod.default });
await client.initData();
Old (v0.1.x/v0.2.x):
import type { SearchWorkerClient } from 'quran-search-engine';
New (v0.3.x):
// Still imported as SearchWorkerClient, but implementation is now simpler
import type { SearchWorkerClient } from 'quran-search-engine';
The following have been removed in v0.3.x:
| Feature | Removed | Alternative |
|---|---|---|
| Phonetic Search | loadPhoneticData() |
Use exact Arabic text search |
| English-to-Arabic Search | loadEnglishToArabic() |
Semantic search with English concepts |
| Lemma Index | lemma-index.json |
Use semantic search instead |
| Root Index | root-index.json |
Use semantic search instead |
| Word Index | word-index.json |
Use semantic search instead |
Error files now use kebab-case:
| Old | New |
|---|---|
base.error.ts |
base-error.ts |
search.error.ts |
search-error.ts |
validation.error.ts |
validation-error.ts |
tokenization.error.ts |
tokenization-error.ts |
Change quranData from array to Map:
const [quranData, setQuranData] = useState<Map<number, QuranText> | null>(null);
const [invertedIndex, setInvertedIndex] = useState<InvertedIndex | null>(null);
const [semanticMap, setSemanticMap] = useState<Map<string, string[]> | null>(null);
const [data, morphology, dictionary, semantic] = await Promise.all([
loadQuranData(),
loadMorphology(),
loadWordMap(),
loadSemanticData(),
]);
setQuranData(data);
setMorphologyMap(morphology);
setWordMap(dictionary);
setSemanticMap(semantic);
// Build inverted index
const index = buildInvertedIndex(morphology, data, semantic);
setInvertedIndex(index);
const response = search(
debouncedQuery,
{
quranData: quranData,
morphologyMap: morphologyMap,
wordMap: wordMap,
invertedIndex: invertedIndex,
semanticMap: semanticMap,
},
options,
{ page: currentPage, limit: PAGE_SIZE },
undefined,
searchCache,
);
let workerUrl: string | null = null;
async function loadWorkerUrl() {
if (!workerUrl) {
const mod = await import('quran-search-engine/worker?url');
workerUrl = mod.default;
}
return workerUrl;
}
// Then when initializing:
const url = await loadWorkerUrl();
const client = createSearchWorker({ workerUrl: url });
await client.initData();
Remove any references to loadPhoneticData() and phonetic search options:
// REMOVED - no longer needed
const phoneticMap = await loadPhoneticData();
You can now track whether search runs on a worker or main thread:
const [usingWorker, setUsingWorker] = useState<boolean | null>(null);
// In search effect:
setUsingWorker(true); // or false for main thread
Track index build performance:
const [indexStats, setIndexStats] = useState<{
lemmaCount: number;
rootCount: number;
wordCount: number;
semanticCount: number;
} | null>(null);
const [indexBuildTime, setIndexBuildTime] = useState<number | null>(null);
const buildStart = performance.now();
const index = buildInvertedIndex(morphologyMap, quranData, semanticMap);
const buildMs = performance.now() - buildStart;
setIndexStats({
lemmaCount: index.lemmaIndex.size,
rootCount: index.rootIndex.size,
wordCount: index.wordIndex.size,
semanticCount: index.semanticIndex?.size ?? 0,
});
setIndexBuildTime(buildMs);
New error handling for worker-related errors:
import { WorkerError, ErrorCode } from 'quran-search-engine';
try {
await client.runSearch(query, options, pagination);
} catch (error) {
if (error instanceof WorkerError) {
console.error('Worker error:', error.code, error.message);
}
}
Before (v0.2.x):
const [quranData, setQuranData] = useState<QuranText[]>([]);
const [morphologyMap, setMorphologyMap] = useState<Map<number, MorphologyAya> | null>(null);
const [wordMap, setWordMap] = useState<WordMap | null>(null);
useEffect(() => {
async function init() {
const [data, morphology, dictionary] = await Promise.all([
loadQuranData(),
loadMorphology(),
loadWordMap(),
]);
setQuranData(data);
setMorphologyMap(morphology);
setWordMap(dictionary);
}
init();
}, []);
const response = search(
query,
quranData,
morphologyMap!,
wordMap!,
options,
{ page: 1, limit: 10 },
undefined,
searchCache,
);
After (v0.3.x):
const [quranData, setQuranData] = useState<Map<number, QuranText> | null>(null);
const [morphologyMap, setMorphologyMap] = useState<Map<number, MorphologyAya> | null>(null);
const [wordMap, setWordMap] = useState<WordMap | null>(null);
const [semanticMap, setSemanticMap] = useState<Map<string, string[]> | null>(null);
const [invertedIndex, setInvertedIndex] = useState<InvertedIndex | null>(null);
useEffect(() => {
async function init() {
const [data, morphology, dictionary, semantic] = await Promise.all([
loadQuranData(),
loadMorphology(),
loadWordMap(),
loadSemanticData(),
]);
setQuranData(data);
setMorphologyMap(morphology);
setWordMap(dictionary);
setSemanticMap(semantic);
const index = buildInvertedIndex(morphology, data, semantic);
setInvertedIndex(index);
}
init();
}, []);
const response = search(
query,
{
quranData: quranData,
morphologyMap: morphologyMap,
wordMap: wordMap,
invertedIndex: invertedIndex,
semanticMap: semanticMap,
},
options,
{ page: 1, limit: 10 },
undefined,
searchCache,
);
If you encounter issues during migration, please open an issue or reach out to the community.