الانتقال إلى المحتوى الرئيسي

التخزين البعيد

إن إرسال حالة المحرر كاملة مع كل تغيير أمر غير فعّال.
ولهذه الحالة، يوفر المحرر واجهة برمجة تطبيقات للنسخ المتماثل للبيانات في الوقت الفعلي.

التثبيت​

npm install @dineug/erd-editor

يوجد مخزن النسخ المتماثل في المسار الفرعي @dineug/erd-editor/engine.js، لا في جذر الحزمة.
لا تمس نقطة الدخول هذه DOM إطلاقًا، لذا تعمل في Web Worker أو في أي مكان آخر خارج الخيط الرئيسي.
أما استيراد جذر الحزمة بدلًا منها فيسجّل العنصر المخصص <erd-editor>، الذي يحتاج إلى مستند DOM.

الاستخدام​

type ReplicationStore = {
readonly value: string;
readonly runtimeValue: string;
on: (
listeners: Partial<{ change: (change: ReplicationChange) => void }>
) => Unsubscribe;
setInitialValue: (value: string) => void;
dispatch: (actions: Array<AnyAction> | AnyAction) => void;
dispatchSync: (actions: Array<AnyAction> | AnyAction) => void;
destroy: () => void;
};

type ReplicationChange = {
value: string;
changed: boolean;
};

type InjectEngineContext = {
toWidth: (text: string) => number;
};

type CreateReplicationStore = (
context: InjectEngineContext
) => ReplicationStore;

// example
import { createReplicationStore } from '@dineug/erd-editor/engine.js';

const replicationStore = createReplicationStore({
toWidth: text => text.length * 10,
});

النسخ المتماثل لمحرر قيد التشغيل​

تأتي الإجراءات من المخزن المشترك لمحرر قيد التشغيل.
راجع التحرير التعاوني.

import { createReplicationStore } from '@dineug/erd-editor/engine.js';

const replicationStore = createReplicationStore({ toWidth });
replicationStore.setInitialValue(editor.runtimeValue);

replicationStore.on({
change: ({ value, changed }) => {
if (changed) save(value);
},
});

const sharedStore = editor.getSharedStore();
sharedStore.subscribe(actions => {
replicationStore.dispatch(actions);
});

لا يعبر الحد الفاصل إلا التغيير، ولا يعبره المستند كاملًا أبدًا.
تطبّقه النسخة المتماثلة وتحوّل الناتج إلى سلسلة من جهتها.
يسلّمك الحدث change هذا الناتج في value، ويخبرك الحقل changed متى يستحق أن يُكتب.

منذ الإصدار 4.0.0، هيّئ النسخة المتماثلة من runtimeValue للمحرر كما سبق، لا من الملف المخزّن ولا من value للمحرر.
فالملف يفتقر إلى التعديلات التي أُجريت منذ حفظه، وvalue يفتقر إلى الكيانات التي أُزيلت خلال الجلسة.
والنسخة المتماثلة التي هُيّئت دون جدول مُزال تعيده بلا اسمه ولا أعمدته حين يتراجع محرر عن إزالته، وتحفظه على هذه الحال.

InjectEngineContext​

toWidth​

تقيس سلسلة نصية بالبكسل.
ليس للمخزن DOM، لذا لا يستطيع قياس النص بنفسه، ويعيد حساب عرض الأعمدة بهذه الدالة في كل مرة يتغير فيها اسم أو نوع بيانات أو قيمة افتراضية أو تعليق.
ويعيد أيضًا قياس كل جدول وعمود كلما حمّلت الدالة setInitialValue مستندًا.
منذ الإصدار 4.0.0، لا يُحتفظ بقيم العرض إلا في runtimeValue، ولا تُحفظ في value أبدًا.
لذا يعطي المستند الذي حفظه هذا الإصدار عند قراءته مجددًا value نفسه بايتًا ببايت، أينما حُفظ وكيفما قيس النص هناك.

قِس كما يقيس المحرر — 400 12px بقائمة خطوط المحرر، مع التقريب، وإضافة بكسلين من الحشو.
وإلا ابتعدت قيم العرض في runtimeValue عن تلك الظاهرة على الشاشة.

const TEXT_PADDING = 2;
const FONT =
"400 12px -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, " +
"'Helvetica Neue', 'Open Sans', system-ui, sans-serif, " +
"'Apple Color Emoji', 'Segoe UI Emoji'";

let context = null;

function getContext() {
if (context) return context;

try {
context = new OffscreenCanvas(0, 0).getContext('2d');
if (context) context.font = FONT;
} catch {
// no canvas available
}

return context;
}

function toWidth(text) {
const context = getContext();
const width = context ? context.measureText(text).width : text.length * 10;

return Math.round(width) + TEXT_PADDING;
}

const replicationStore = createReplicationStore({ toWidth });

التعبير text.length * 10 هو البديل الاحتياطي حين لا يتوفر canvas.
وهو يكفي لمخزن يُخزَّن مستنده فقط ولا يُعرض أبدًا.

ReplicationStore​

value getter​

يحوّل حالة المحرر الحالية إلى سلسلة.
ويُرجع سلسلة نصية بصيغة JSON جاهزة للتخزين كما هي — لا كائنًا محلَّلًا.

const data = replicationStore.value;

منذ الإصدار 4.0.0، يمثل value صيغة التخزين: المستند كما يحمله الملف، وهو النص نفسه الذي تُرجعه الخاصية value للعنصر.
ولا يحمل سوى الكيانات القائمة، بترتيب ثابت، دون الكيانات المُزالة ولا ترتيب التراص ولا أي شيء يعيد التحميل حسابه، مثل قيم العرض التي تقيسها الدالة toWidth.
ويُكتب بإزاحة مسافتين وينتهي بسطر جديد، لذا يعطي المستند نفسه دائمًا البايتات نفسها.
لمعرفة ما يحمله، راجع صيغة التخزين وقيمة وقت التشغيل.

يُكتب كل إعداد مفعّل عليه القفل بالقيمة التي قُفل عليها، وكل إعداد آخر بقيمته الحالية.
يشمل القفل Viewport أصل العرض ومستوى التكبير معًا، ولكل من علامة التبويب ولغة منشئ التعليمات البرمجية ونمطَي التسمية كليهما ونوع علامات الاقتباس قفله الخاص.
تحل الأقفال محل الحقل ignoreSaveSettings في المستند، وهو حقل لم يعد يُقرأ ولا يُكتب منذ الإصدار 4.0.0.
قبل الإصدار 4.0.0، كان value هو المستند كله كما يحمله المخزن، مع تطبيق بتّات التمرير والتكبير في ذلك الحقل.

runtimeValue getter​

منذ الإصدار 4.0.0، يحوّل المستند إلى سلسلة كما تحمله النسخة المتماثلة، بما في ذلك الكيانات المُزالة وكل ما يعيد التحميل حسابه.
ويُكتب هنا أيضًا كل إعداد مقفل بقيمته المقفلة.

const snapshot = replicationStore.runtimeValue;

استخدمه لتهيئة محرر أو مخزن آخر ينضم لاحقًا، عبر الدالة setInitialValue الخاصة به؛ راجع التحرير التعاوني.
لا تخزّنه أبدًا على أنه الملف: فهو يحمل الكيانات المُزالة، ويرفضه JSON Schema المنشور.
وهذه الكيانات المُزالة هي ما يتيح لتراجع عن إزالة، يُرسَل لاحقًا، أن يعيد الكيان كاملًا في المخزن الذي هُيّئ منه.
وتبقى إلى أن يُفتح المستند في المرة التالية من ملفه، الذي تستبعدها صيغته.

setInitialValue​

تحمّل حالة المحرر المخزّنة سابقًا.

replicationStore.setInitialValue('json...');

تقبل صيغة التخزين أو قيمة وقت تشغيل أو مستندًا حفظه إصدار سابق، بما في ذلك مستند v2.
وتحمّل السلسلة الفارغة، أو أي شيء ليس سلسلة نصية، مستندًا جديدًا فارغًا بدلًا من إثارة خطأ.
ويبدأ المخزن الذي لم يتلقَّ قيمة قط بالمستند الجديد نفسه.
منذ الإصدار 4.0.0، تكون جميع الأقفال مفعّلة في ذلك المستند، لذا يحتفظ value الخاص به بالعرض وعلامة التبويب ولغة منشئ التعليمات البرمجية وأنماط التسمية ونوع علامات الاقتباس التي بدأ بها، بينما تمرّر المحررات التي ينسخها العرض أو تغيّر مستوى التكبير أو تبدّل علامات التبويب أو تعدّل تلك الإعدادات. في الإصدار 3.10.0 لم يكن يحفظ التمرير ولا التكبير، وقبل ذلك كان يحفظهما كليهما.
ويحتفظ المستند المخزّن بالأقفال التي يذكرها.
أما المستند الذي لا يذكر أيًّا منها، وهو أي مستند حُفظ قبل الإصدار 4.0.0، بما في ذلك مستند v2، فيُحمَّل وجميع الأقفال مفعّلة: أصل العرض عند 0, 0 ومستوى التكبير عند 1 وعلامة التبويب على ERD، أيًّا كان ما حفظه، ولغة منشئ التعليمات البرمجية وأنماط التسمية ونوع علامات الاقتباس كما حفظها.
منذ الإصدار 4.0.0، تتخلص الدالة setInitialValue أيضًا من مسجّلات التعاون المحفوظة للمستند السابق، كما تفعل الدالة setInitialValue للعنصر.
ولا يثير النص الذي ليس JSON، مثل ملف بقيت فيه علامات تعارض الدمج، خطأً هو الآخر: إذ يُسجَّل خطأ التحليل في وحدة التحكم فحسب، ويُفرَّغ المستند، وتبقى الإعدادات على حالها.

منذ الإصدار 4.0.0، لا يُجري التحميل أي جمع للبيانات المهملة، لذا تبقى الكيانات المُزالة في runtimeValue.
وقبل ذلك، كان يتخلص من الكيانات التي لم تعد تظهر في doc ولم تُلمس منذ أربعة أيام أو أكثر.

تكتمل عمليات إعادة الكتابة التي يجريها التحميل قبل أن تعود الدالة setInitialValue: علامات المفاتيح على الأعمدة، مقروءةً من خيارات كل عمود ومن العلاقات؛ وعرض كل جدول وعمود، مُقاسًا من جديد بالدالة toWidth؛ والنقاط التي يلتقي فيها كل خط ربط بالجداول التي يربطها؛ وعلامة كون كل علاقة مُعرِّفة، والرمز عند طرف الجدول الأصل منها.
ليس أيٌّ منها جزءًا من value، لكن replicationStore.runtimeValue، إذا قُرئ مباشرة بعد الاستدعاء، يحملها كلها بالفعل.
أما الإجراء الذي يصل خلف التحميل مباشرة، مثل تمرير يُعاد تشغيله، فيُقارَن بالمستند المحمَّل، لذا لا يُبلَّغ أبدًا عن التحميل نفسه على أنه changed.

لا يُطلق التحميل ولا أيٌّ من عمليات إعادة الكتابة هذه الحدث change.
اقرأ replicationStore.value بنفسك بعد أن تعود الدالة setInitialValue إذا أردت أن يُكتب المستند المحمَّل من جديد بصيغة التخزين.

on@change​

تشترك في التغييرات التي تطرأ على حالة المحرر.

const unsubscribe = replicationStore.on({
change: ({ value, changed }) => {
if (changed) {
// save value...
}
},
});

يُؤخَّر إطلاق الحدث change بمقدار 200 مللي ثانية، لذا تصل الإجراءات المتلاحقة في إشعار واحد.
ولا يُطلَق إلا لإجراءات من نوع يمكنه تغيير المستند، إضافة إلى settings.changeIgnoreSaveSettings الذي قد لا يزال يرسله محرر أقدم (راجع dispatch, dispatchSync).
ولا يُطلقه أبدًا إجراء حضور مُمرَّر ولا editor.mergeLWW.
استدعِ الدالة المُرجَعة لإلغاء الاشتراك.

منذ الإصدار 3.10.0، يتلقى المستمع ReplicationChange، وهو نوع يصدّره @dineug/erd-editor/engine.js إلى جانب ReplicationStore.
قبل الإصدار 3.10.0 لم يكن يتلقى شيئًا، والمستمع الذي يتجاهل الوسيطة يظل يعمل.
الحقل value هو المستند محوَّلًا إلى سلسلة بصيغة التخزين، وهو السلسلة نفسها التي يُرجعها replicationStore.value في تلك اللحظة، فلا حاجة إلى تحويله إلى سلسلة مرة أخرى.
وتكون قيمة changed هي false عندما تترك الإجراءات التي وقعت منذ change السابق، أو منذ التحميل، value على حاله بايتًا ببايت.

والتغيير الذي يستبعده القفل من value يظل يُطلق change، مع تعيين changed إلى false، لذا لا تكتب إلا عندما تكون قيمة changed هي true.
ويعني ذلك التمرير أو التكبير أثناء قفل Viewport، وتبديل علامة التبويب أثناء قفل Canvas Type، وتغيير لغة منشئ التعليمات البرمجية أو نمط تسمية أو نوع علامات الاقتباس أثناء تفعيل قفله الخاص.
يستبعد الحدث change للعنصر مثل هذا التغيير، لكن النسخة المتماثلة تظل تبلّغ عنه.
تكون جميع الأقفال مفعّلة في المستند الجديد وفي المستند المحفوظ قبل الإصدار 4.0.0.
وعند إلغاء قفل Viewport يغيّر التمرير أو التكبير value، وعند إلغاء قفل Canvas Type يغيّره كذلك فتح علامة تبويب أخرى من علامات تبويب المحرر.
ويغيّر تفعيل قفل أو تعطيله value أيضًا.

منذ الإصدار 4.0.0، يقارن changed صيغة التخزين، التي لا تحمل الكيانات المُزالة ولا ترتيب التراص.
لذا فإن الجدول الذي يُضاف ثم يُزال مجددًا ضمن change واحد، والتعديل الذي يقع على كيان أزاله متعاون آخر، والتحميل المُمرَّر الذي لا يغيّر سوى ترتيب التراص، كلها تنتهي أيضًا بتعيين changed إلى false.

لا يقارن changed أبدًا بملفك المخزّن، الذي قد يختلف بالفعل عن value بمجرد تحميله: فالملف الذي حفظه إصدار سابق يختلف، بما في ذلك ملف v2، وكذلك الملف الذي يبدأ بعلامة ترتيب البايتات والملف المُعدَّل يدويًا.
ومقارنة البايتات بمثل هذا الملف ستعدّ حتى التمرير الذي يستبعده القفل تعديلًا.
وأول change تكون فيه قيمة changed هي true يعيد مثل هذا الملف بصيغة التخزين، لذا فإن كتابته تعيد كتابة الملف مرة واحدة.
أما الملف الذي حفظه هذا الإصدار فلا يختلف، أينما حُفظ، كما يوضح القسم toWidth.
وإذا نُفّذت الدالة setInitialValue بينما لا يزال change معلّقًا، ولم يُرسَل شيء قبل موعده، فإن ذلك change يصل على أي حال، حاملًا value المحمَّل وchanged بالقيمة false.

dispatch, dispatchSync​

تطبّق تغييرات المحرر البعيد على replicationStore.

replicationStore.dispatch(actions); // async
replicationStore.dispatchSync(actions); // sync

تؤجّل الدالة dispatch التطبيق إلى مهمة دقيقة، بينما تطبّق الدالة dispatchSync الدفعة فورًا.

يمكنك تسليم دفق المخزن المشترك كما هو دون تصفية.
لا تحتفظ الدالتان إلا بالإجراءات من نوع يمكنه تغيير المستند، إضافة إلى الإجراءين الموصوفين في نهاية هذا القسم، وتُسقطان الباقي، لذا تمر بيانات الحضور التي تحملها الجلسة التعاونية — مؤشر الماوس لكل مستخدم وتركيزه وتحديده ومربع السحب لديه — دون أي أثر.

منذ الإصدار 3.10.0، تشمل الإجراءات المحتفظ بها relationship.changeOnDelete وrelationship.changeOnUpdate، ويحمل relationship.add الحقلين onDelete وonUpdate.
والمخزن من إصدار سابق يُسقط تلك الإجراءات ويتجاهل تلك الحقول، فيفقد إجراءات ON DELETE وON UPDATE للعلاقة.

منذ الإصدار 4.0.0، تشمل الإجراءات المحتفظ بها أيضًا table.changeGroup، وسبعة من إجراءات tableGroup الثمانية (tableGroup.add وtableGroup.move وtableGroup.moveTo وtableGroup.remove وtableGroup.resize وtableGroup.changeName وtableGroup.changeColor، أي كلها عدا تغيير ترتيب التراص tableGroup.changeZIndex)، وrelationship.changeColumns وsettings.changeLockSettings وsettings.changeDDLScript.
لم يعد settings.changeRelationshipOptimization موجودًا، لذا تُسقطه الدالتان كلتاهما.
والمخزن من إصدار سابق يُسقط الإجراءات الجديدة، فيفقد مجموعات الجداول والأقفال وسكربتات مخطط SQL والأعمدة التي يمنحها تعيين الأعمدة لعلاقة موجودة.
شغّل النسخة المتماثلة على الإصدار نفسه الذي تعمل عليه المحررات، أو على إصدار أحدث.

لا يضيف الإصدار 4.1.0 أي نوع إجراء، ويترك @dineug/erd-editor/engine.js كما كان، لذا تحتفظ نسخة متماثلة من الإصدار 4.0.0 بكل تغيير يرسله محرر من الإصدار 4.1.0، تمامًا كما تفعل نسخة متماثلة من الإصدار 4.1.0.
منذ الإصدار 4.1.0، يعيد Replace وReplace All في البحث والاستبدال تسمية الفهارس ومجموعات الجداول أيضًا، عبر الإجراءين index.changeName وtableGroup.changeName اللذين تحتفظ بهما نسخة متماثلة من الإصدار 4.0.0 أصلًا.
لا تُرسَل حالة المستكشف الخاصة به أبدًا، أما تبديل علامة التبويب أو التمرير الذي يجريه فيصل كما يصل أي تبديل آخر لعلامة التبويب أو تمرير آخر؛ راجع التحرير التعاوني.

منذ الإصدار 4.0.0، تطبّق الدالتان أيضًا editor.mergeLWW، الذي يحمل مسجّلات التعاون التي يردّ بها محرر عندما ينضم محرر آخر إلى الجلسة، لذا تحسم النسخة المتماثلة التعديلات المتزامنة بالطريقة التي تحسمها بها المحررات.
ولا يغيّر أي بايت من value ولا يُطلق أي change.
أما settings.changeIgnoreSaveSettings، الذي قد لا يزال يرسله محرر صدر قبل الإصدار 4.0.0، فيُحتفظ به لكنه لا يغيّر شيئًا: إذ يُطلق change مع تعيين changed إلى false.

destroy​

تدمّر مثيل replicationStore بالكامل.

replicationStore.destroy();

بعد ذلك يتوقف المخزن عن إطلاق change ويتجاهل أي إرسال لاحق.
ويتجمّد المستند عند آخر قيمة له بدلًا من إثارة خطأ.
أنشئ مخزنًا جديدًا للبدء من جديد.