في المقال السابق تحدثنا عن الفكرة الأساسية لفصل منطق البحث القرآني عن الواجهة، وكيف أن quran-search-engine تُتيح لأي مطوّر تركيب محرك بحث متكامل في مشروعه.
في هذا المقال نتعمّق أكثر: نتحدث عن مبادرة الأثر التي ألهمت التحديث الكبير، ونكشف ما الذي تغيّر في النسخة v0.3.x-(athar) من بنية معمارية جديدة كلياً، وكيف تهاجر إليها من v0.1.5.
1. مبادرة الأثر - القصة خلف الإصدار
ما هي مبادرة الأثر؟
مبادرة الأثر هي مبادرة تعاونية انطلقت من مجتمع إتقان بهدف بناء تقنيات قرآنية مفتوحة المصدر بشكل جماعي ومستدام. اجتمع فيها مطوّرون من خلفيات مختلفة لمناقشة، اقتراح، وتشكيل الاتجاه التقني لمشاريع تخدم القرآن الكريم.
يمكن قراءة النقاشات في صفحة المبادرة على المجتمع: محرك البحث القرآني | مبادرة رمضان الأثر
لماذا سُمّي الإصدار "athar"؟
كلمة "أثر" تعني في العربية: الأثر الذي يتركه شيء ما، أثر في الأرض، في القلوب، في التاريخ.
أُهدي اسم هذا الإصدار لمجتمع إتقان تقديراً لتأثيرهم الحقيقي على مسار تطوير الحزمة. مشاركتهم في المبادرة أثّرت مباشرة في:
- التصميم الصارم للأنواع (strict typings)
- نهج التحميل الديناميكي للبيانات
- المعمارية الطبقية للبحث التي تراها اليوم
2. التحول المعماري الكبير، ما الذي تغيّر في v0.3.x؟
2.1 من المصفوفات إلى الـ Maps: أداء O(1)
في النسخ السابقة، كانت بيانات القرآن تُخزَّن كمصفوفة QuranText[]. مع تعقّد الاستعلامات، أصبح التكرار على المصفوفة عنق زجاجة.
في v0.3.x تم الانتقال إلى Maps:
// قبل — v0.1.5
QuranText[]
// بعد — v0.3.x
Map<number, QuranText>
هذا التغيير البسيط يعطي وصولاً فورياً O(1) لأي آية بدلاً من البحث عبر المصفوفة، مما يُقلّل التأخير بشكل جذري عند الاستعلامات المعقدة.
2.2 نمط كائن السياق (Context Object Pattern)
الدالة search() تغيّرت كلياً. بدل قائمة طويلة من الوسائط المتسلسلة، أصبح هناك كائن سياق واحد يجمع كل المعطيات:
// قبل — v0.1.5
const response = search(
query,
quranData,
morphologyMap,
wordMap,
options,
{ page: 1, limit: 10 },
undefined,
searchCache
);
// بعد — v0.3.x
const response = search(
query,
{
quranData,
morphologyMap,
wordMap,
invertedIndex,
semanticMap,
transliterationMap,
},
{
...options,
isRegex: false,
isBoolean: false,
phonetic: false,
},
{ page: 1, limit: 10 },
undefined,
searchCache
);
النتيجة: كود أكثر قراءةً، وتوسّعاً دون كسر الـ API.
2.3 الفهرس المعكوس الإلزامي (Mandatory Inverted Index)
أحد أكبر التغييرات في v0.3.x هو الفهرس المعكوس. بدل حساب مسارات البحث الصرفي والدلالي عند كل استعلام، تبني الفهرس مرة واحدة عند التهيئة:
import { buildInvertedIndex } from 'quran-search-engine';
// تبنيه مرة واحدة فقط عند تحميل التطبيق
const invertedIndex = buildInvertedIndex(morphology, quranData, semanticMap);
بعدها تمرّره مع كل استعلام ضمن كائن السياق. هذا يعني:
| السيناريو | v0.1.5 | v0.3.x |
| أول استعلام | بطيء (حساب فوري) | سريع (فهرس جاهز) |
| ثاني استعلام | بطيء (إعادة حساب) | سريع (فهرس مُعاد الاستخدام) |
| استعلام معقد | أبطأ بشكل ملحوظ | سرعة ثابتة |
2.4 المعمارية الطبقية للبحث (Layered Search Pipeline)
تُنظّم v0.3.x منطق البحث بالكامل في 8 طبقات متتالية. كل طبقة وحدة مستقلة قابلة للاختبار بمفردها. المنسّق (search()) يمرّر الاستعلام عبر هذه الطبقات بترتيب صارم:
query
│
├─ 1. Range Layer ── مثال: 2:255 (آية الكرسي مباشرة)
├─ 2. Boolean Layer ── مثال: رحمن + رحيم - عذاب
├─ 3. Regex Layer ── مثال: /حسن.*/
├─ 4. Simple Layer ── المطابقة النصية المباشرة
├─ 5. Linguistic Layer── الجذر + الصيغة عبر بيانات الصرف
├─ 6. Fuse Layer ── البحث التقريبي (fuzzy) للكلمات الخاطئة
├─ 7. Semantic Layer ── التوسّع الدلالي والبحث بين اللغات
└─ 8. Phonetic Layer ── تحويل اللاتيني إلى عربي (Alhamdulillah → الحمد لله)
كل طبقة تشغل مسارها الخاص وترجع أو تمرّر التنفيذ للطبقة التالية. هذا يعني:
- إضافة ميزة جديدة = إضافة طبقة جديدة دون لمس الباقي
- اختبار ميزة = اختبار الطبقة باستقلالية كاملة
- صيانة الكود = كل طبقة لها مسؤولية واحدة فقط
2.5 دعم Web Worker المحسّن وإدارة الأخطاء
لتفادي تجميد الواجهة أثناء الاستعلامات الكبيرة، تُبسّط v0.3.x الاندماج مع Web Workers وتُقدّم الفئة WorkerError لأخطاء منظّمة وآمنة من ناحية الأنواع:
import { WorkerError } from 'quran-search-engine';
// تهيئة محسّنة مع Vite/Webpack
const mod = await import('quran-search-engine/worker?url');
const client = createSearchWorker({ workerUrl: mod.default });
await client.initData();
// معالجة الأخطاء بشكل منظّم
try {
await client.runSearch(query, options, pagination);
} catch (error) {
if (error instanceof WorkerError) {
console.error(`كود الخطأ: ${error.code}`);
}
}
2.6 حذف ملفات الفهارس الثابتة
لتقليل حجم الحزمة وضمان تناسق البيانات، لم تعد توزَّع ملفات JSON الثابتة كـ lemma-index.json وroot-index.json. هذه الفهارس تُبنى الآن ديناميكياً في الذاكرة عبر buildInvertedIndex().
3. تعدّدية البيانات — "أحضر لغتك الخاصة"
التحميل الديناميكي
كل مجموعة بيانات — نص القرآن، بيانات الصرف، الخرائط الدلالية، والصوتية — تُحمَّل بشكل ديناميكي حسب الحاجة. حزمتك الأولية تبقى صغيرة جداً. تُحمَّل بيانات اللغات الإضافية فقط عند طلبها:
import {
loadQuranData,
loadMorphology,
loadWordMap,
loadSemanticData, // للبحث الدلالي بين اللغات
loadPhoneticData, // للبحث الصوتي
buildInvertedIndex,
} from 'quran-search-engine';
const [data, morphology, dictionary, semantic, phonetic] = await Promise.all([
loadQuranData(),
loadMorphology(),
loadWordMap(),
loadSemanticData(),
loadPhoneticData(),
]);
const invertedIndex = buildInvertedIndex(morphology, data, semantic);
"أحضر لغتك الخاصة" (BYOL)
لست مُقيّداً بالخريطة الدلالية الإنجليزية الافتراضية. يمكنك استبدالها بخريطتك الخاصة بالفرنسية أو الأردية أو التركية أو أي لغة تريد، وطالما أن بياناتك تجتاز مخطط التحقق المدمج، تضمن الحزمة عملها دون توقّف.
4. قوة TypeScript — الأنواع في كل مكان
الحزمة مكتوبة بـ TypeScript من الجذر وتوفّر أنواعاً صريحة لكل شيء:
import type {
InvertedIndex,
QuranText,
HighlightRanges,
SearchContext,
SearchOptions,
SearchResponse,
WorkerError,
} from 'quran-search-engine';
فوائد هذا النهج عملياً:
- الإكمال التلقائي في IDE: اكتشف الخيارات والحقول بدون الرجوع للتوثيق
- الأمان في وقت التشغيل: يمنع أخطاء هيكلة البيانات قبل أن تحدث
- ثقة كاملة: تعرف بالضبط ما يعيده
search() قبل أن تكتب سطر واجهة
5. التوثيق المدمج والصديق للذكاء الاصطناعي
من الميزات التي لا يراها أغلب المطوّرين: الحزمة تشحن مع مجلد docs/ داخلها مباشرة.
هذا التوثيق مقسّم إلى:
- Guides: أدلة عملية خطوة بخطوة
- References: مرجع تقني لكل دالة وخيار
لماذا يهم هذا؟
لأن سير العمل الحديث يشمل مساعدي الذكاء الاصطناعي (Copilot، Trae، Cursor). هذه الملفات مُنظّمة بحيث يمكن لأي مساعد ذكاء اصطناعي قراءتها كسياق وتقديم اقتراحات دقيقة بدون الحاجة لاتصال إنترنت:
# يمكنك إعطاء المساعد هذا المجلد كسياق
node_modules/quran-search-engine/docs/
6. دليل الهجرة الكامل: من v0.1.5 إلى v0.3.x-(athar)
⚠️ إصدار v0.3.x يحتوي على تغييرات جذرية غير متوافقة مع v0.1.5. اتبّع الخطوات التالية بالترتيب.
الخطوة 1: تحديث تعريفات الحالة (State)
// ❌ قبل — v0.1.5
const [quranData, setQuranData] = useState<QuranText[]>([]);
// ✅ بعد — v0.3.x
import { InvertedIndex, QuranText } from 'quran-search-engine';
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 [phoneticMap, setPhoneticMap] = useState<Map<string, string[]> | null>(null);
الخطوة 2: تحديث تحميل البيانات وبناء الفهرس
// ❌ قبل — v0.1.5
const [data, morphology, dictionary] = await Promise.all([
loadQuranData(),
loadMorphology(),
loadWordMap(),
]);
// ✅ بعد — v0.3.x
const [data, morphology, dictionary, semantic, phonetic] = await Promise.all([
loadQuranData(),
loadMorphology(),
loadWordMap(),
loadSemanticData(), // جديد — مطلوب
loadPhoneticData(), // جديد — للبحث الصوتي
]);
setQuranData(data);
setMorphologyMap(morphology);
setWordMap(dictionary);
setSemanticMap(semantic);
setPhoneticMap(phonetic);
// بناء الفهرس مرة واحدة
const index = buildInvertedIndex(morphology, data, semantic);
setInvertedIndex(index);
الخطوة 3: تحديث نداء دالة البحث
// ❌ قبل — v0.1.5
const response = search(
query,
quranData,
morphologyMap,
wordMap,
options,
{ page: 1, limit: 10 },
undefined,
searchCache
);
// ✅ بعد — v0.3.x
const response = search(
query,
{
quranData,
morphologyMap,
wordMap,
invertedIndex,
semanticMap,
transliterationMap,
},
{
...options,
isRegex: false, // طبقة Regex الجديدة
isBoolean: false, // طبقة Boolean الجديدة
phonetic: false, // طبقة Phonetic الجديدة
},
{ page: 1, limit: 10 },
undefined,
searchCache
);
الخطوة 4: تحديث Web Worker (إن وجد)
// ❌ قبل — v0.1.5
const client = createSearchWorker({
workerUrl: new URL('quran-search-engine/worker', import.meta.url),
});
await client.initData();
// ✅ بعد — v0.3.x
const mod = await import('quran-search-engine/worker?url');
const client = createSearchWorker({ workerUrl: mod.default });
await client.initData();
المقارنة الكاملة: قبل وبعد في مكوّن React
// ❌ الطريقة القديمة — v0.1.5
useEffect(() => {
async function init() {
const [data, morphology, dictionary] = await Promise.all([
loadQuranData(),
loadMorphology(),
loadWordMap(),
]);
setQuranData(data);
setMorphologyMap(morphology);
setWordMap(dictionary);
}
init();
}, []);
// في معالج البحث:
search(query, quranData, morphologyMap, wordMap, options, pagination);
// ✅ الطريقة الجديدة — v0.3.x-(athar)
useEffect(() => {
async function init() {
const [data, morphology, dictionary, semantic, phonetic] = await Promise.all([
loadQuranData(),
loadMorphology(),
loadWordMap(),
loadSemanticData(),
loadPhoneticData(),
]);
setQuranData(data);
setMorphologyMap(morphology);
setWordMap(dictionary);
setSemanticMap(semantic);
setPhoneticMap(phonetic);
const index = buildInvertedIndex(morphology, data, semantic);
setInvertedIndex(index);
}
init();
}, []);
// في معالج البحث:
search(
query,
{ quranData, morphologyMap, wordMap, invertedIndex, semanticMap, phoneticMap },
options,
pagination
);
7. الأمثلة العملية (مجلد examples/)
تضمّ الحزمة أمثلة حقيقية قابلة للتشغيل لأكثر بيئات الاستخدام شيوعاً:
لتشغيل أي مثال محلياً:
yarn playground:react # لمثال React
yarn playground:node # لمثال Node.js
8. الاستخدام الإنتاجي — Open Mushaf Native
الحزمة ليست تجريبية. هي تعمل في بيئة إنتاج حقيقية داخل تطبيق Open Mushaf Native، وهو تطبيق قرآن كامل مبني بـ React Native وExpo.
| المنصة | الحالة |
| Android | ✅ يعمل |
| الويب (Web) | ✅ يعمل |
| iOS | ✅ متوافق نظرياً (stateless + TypeScript) |
نفس الكود، نفس search()، نفس النتائج — على كل المنصات.
الخلاصة
إصدار v0.3.x-(athar) ليس مجرد إضافة ميزات — هو إعادة تأسيس لـ quran-search-engine كمحرك بحث قرآني ناضج:
| الجانب | v0.1.5 | v0.3.x-(athar) |
| هيكل البيانات | Array | Map (O(1)) |
| الفهرس | فوري (بطيء) | مبنى مسبقاً (سريع) |
| طريقة استدعاء البحث | وسائط متسلسلة | كائن سياق |
| طبقات البحث | مونوليث | 8 طبقات مستقلة |
| أنواع | جزئية | كاملة (TypeScript-first) |
| Web Worker | معقّد | مبسّط |
| التوثيق | خارجي | مدمج + AI-friendly |
للترقية: yarn add quran-search-engine@latest
لأسئلة أو اقتراحات افتح issue في المستودع الرسمي.