Yjs
@dineug/erd-editor-yjs نمودارها را از طریق یک سند Yjs همگام نگه میدارد. این بسته مستقل است و نخستین نسخهٔ آن، 0.1.0، در کنار @dineug/erd-editor نسخهٔ 5.1.0 منتشر شد.
bindErdEditor یک عنصر <erd-editor> را و bindPeerStore یک مخزن همتای بدون رابط کاربری از @dineug/erd-editor/peer.js را به یک Y.Doc متصل میکند.
هر فراهمکنندهای که Y.Doc را حمل کند، نمودار را هم حمل میکند: سرور websocket، WebRTC، IndexedDB یا فراهمکنندهٔ خودتان. فراهمکننده و Y.Doc در اختیار شما میمانند: اتصال هرگز آنها را وصل، قطع یا نابود نمیکند.
ویرایشها بهصورت دستههای کنش خود ویرایشگر منتقل میشوند و اتصال آنها را همانطور که مخزن فرستاده نگه میدارد.
بنابراین ثباتهای همکاری ویرایشگر، ویرایشهایی را که در یک لحظه انجام میشوند همانطور حلوفصل میکنند که در نشستی برپاشده با getSharedStore، با همان استثناها، مانند تغییر رنگ؛ ویرایشهای همزمان را ببینید.
نشانگر ماوس، فوکوس، انتخابها و مستطیلهای انتخاب از طریق Awareness فراهمکننده منتقل میشوند؛ حضور را ببینید.
در مقایسه با اینکه خودتان یک مخزن مشترک را به یک لایهٔ انتقال وصل کنید، این اتصال موارد زیر را اضافه میکند:
- کلاینتی که میپیوندد، نموداری را که اتاق نگه میدارد باز میکند، بدون اینکه runtimeValue از یک ویرایشگر در حال اجرا تحویل داده شود.
- کلاینتی که قطع شده است به ویرایش ادامه میدهد و ویرایشهایش هنگام اتصال دوباره ثبت میشوند؛ ویرایشهای آفلاین و پیوستن دوباره را ببینید.
- یک
Y.Docکه یک فراهمکنندهٔ ماندگاری برمیگرداند، نموداری را که نگه میدارد باز میکند؛ آنچه اتاق نگه میدارد را ببینید. - یک فرایند جانبی اختیاری در Node تاریخچهٔ اتاق را در یک تصویر لحظهای تلفیق میکند؛ فشردهسازی یک اتاق را ببینید.
اتصال به پنج متدی نیاز دارد که نسخهٔ 5.1.0 به مخزن مشترک و مخزن همتا اضافه کرد — getLWW، mergeLWW، getVersion، forgetPeer و reseed — و همهٔ فراخوانیهای لازم از آنها را خودش انجام میدهد.
نصب
npm install @dineug/erd-editor-yjs @dineug/erd-editor yjs y-protocols
npm install y-websocket # or another provider
@dineug/erd-editor، yjs و y-protocols وابستگیهای همتا هستند، بنابراین یک صفحه از هر کدام یک نسخه دارد: @dineug/erd-editor نسخهٔ 5.1.0 یا بالاتر در 5.x، yjs نسخهٔ 13.6 یا بالاتر در 13.x و y-protocols نسخهٔ 1.0 یا بالاتر در 1.x.
این بسته فقط نوعهای آنها را به کار میگیرد و روی همان Y.Doc و Awareness کار میکند که شما میدهید، بنابراین هیچ کد Yjs، y-protocols یا ویرایشگری را خودش بارگذاری نمیکند.
دو نسخه از Yjs در یک صفحه بررسیهای سازندهٔ یکدیگر را خراب میکنند و نسخهٔ دوم پیام Yjs was already imported را ثبت میکند. وقتی کد شما و یک فراهمکننده yjs را از دو مکان متفاوت بارگذاری میکنند، آن را در باندلر یکی کنید (Vite: resolve.dedupe: ['yjs']).
این بسته فقط ESM است ("type": "module")، یک نقطهٔ ورود دارد، یعنی ریشهٔ بسته، و بدون اثر جانبی علامتگذاری شده است، بنابراین باندلی که فقط bindErdEditor را وارد میکند، فشردهساز را کنار میگذارد.
bindErdEditor به DOM نیاز دارد، چون یک عنصر <erd-editor> تعریفشده را متصل میکند. bindPeerStore در Node ساده هم اجرا میشود، با Node.js نسخهٔ 22.12 یا بالاتر، همانطور که فرایند جانبی فشردهسازی اجرا میشود.
نحوهٔ استفاده
function bindErdEditor(
editor: ErdEditorElement,
doc: Y.Doc,
options?: BindOptions
): ErdYjsBinding;
import '@dineug/erd-editor/erd-editor.js';
import { bindErdEditor } from '@dineug/erd-editor-yjs';
import { WebsocketProvider } from 'y-websocket';
import * as Y from 'yjs';
const doc = new Y.Doc();
const provider = new WebsocketProvider('wss://yjs.example.com', 'my-diagram', doc);
const editor = document.createElement('erd-editor');
document.body.appendChild(editor);
provider.on('sync', function onSync(synced) {
if (!synced) return;
provider.off('sync', onSync);
const binding = bindErdEditor(editor, doc, {
awareness: provider.awareness,
getNickname: () => 'Ada',
});
binding.on('error', error => console.error(error));
binding.on('rejoin', reason => console.info('reopened on the room', reason));
binding.seed(); // false, and nothing written, once the room holds a diagram
});
wss://yjs.example.com و my-diagram جای سرور و اتاق خودتان هستند.
اتصال و مقداردهی اولیه را فقط پس از همگام شدن فراهمکننده انجام دهید، همانطور که رویداد sync در y-websocket با true به شما میگوید. مقداردهی اولیهای که زودتر انجام شود میتواند نمودار اتاق را جایگزین کند؛ مقداردهی اولیهٔ یک اتاق را ببینید.
وقتی Y.Doc هنگام اتصال تصویر لحظهای یک اتاق را نگه میدارد، اتصال آن را بلافاصله در عنصر باز میکند: تصویر لحظهای را با ثباتها و نسخهاش بارگذاری میکند، همانطور که reseed انجام میدهد، و هر ویرایشی را که از آن پس در گزارش ثبت شده اعمال میکند.
این بازشدن آنچه را پیوستن دوباره پاک و بسته میکند، پاک و بسته میکند — ویرایشهای آفلاین و پیوستن دوباره را ببینید — و عنصر همانجا باز میشود که تصویر لحظهای تعیین میکند. برای این بازشدن نخست، rejoin ارسال نمیشود.
وقتی Y.Doc هیچ تصویر لحظهای ندارد، مانند اتاق خالی یا پیش از همگام شدن فراهمکننده، عنصر سند خودش را نگه میدارد و اتصال چیزی در اتاق نمینویسد تا وقتی که یک تصویر لحظهای ظاهر شود، چه حاصل مقداردهی اولیهٔ خودش باشد چه حاصل مقداردهی اولیهٔ کلاینتی دیگر.
bindErdEditor با getSharedStore یک مخزن مشترک از عنصر برمیدارد و destroy آن را رها میکند.
عنصر باید از پیش یک <erd-editor> باشد، یعنی نقطهٔ ورود آن وارد شده یا defineErdEditor() فراخوانی شده باشد؛ ثبت عنصرها را ببینید. تا وقتی برچسب تعریف نشده است، یک <erd-editor> فقط یک HTMLElement ساده است که مرورگر پس از تعریف برچسب و قرار گرفتن عنصر در سند آن را ارتقا میدهد. برای عنصری که هنوز ارتقا نیافته است، bindErdEditor بهجای ارسال error یک TypeError پرتاب میکند.
ویرایشهای اتاق مانند ویرایشهای یک همکار روی عنصر اعمال میشوند و change را ارسال میکنند، از جمله ویرایشهایی که پس از باز شدن یک تصویر لحظهای دوباره اجرا میشوند.
باز کردن یک تصویر لحظهای، هنگام اتصال یا در پیوستن دوباره، هیچ گام واگردی نمیسازد، change ارسال نمیکند و چیزی در اتاق نمینویسد، جز استخراج نام فیزیکی از نام منطقی که ویرایشهای آفلاین و پیوستن دوباره شرح میدهد.
برای بارگذاری سند دیگری در اتاقی که مقداردهی اولیه شده است، تغییری انجام دهید که هر کلاینت اعمال کند: value را مقداردهی کنید، clear را فراخوانی کنید یا یک سند وارد کنید.
setInitialValue هرگز در اتاق نوشته نمیشود، چون مخزن مشترک آن را نمیفرستد، بنابراین فقط همان عنصر را تغییر میدهد و هر کلاینت دیگر نمودار اتاق را نگه میدارد. اتاق خالی را بهجای آن با seed مقداردهی اولیه کنید.
یک Y.Doc یک نمودار را نگه میدارد. اتصال آن را زیر نامهای erd: در Y.Doc نگه میدارد و فیلد erd از وضعیت Awareness محلی را تنظیم میکند و هر نام و فیلد دیگر را دستنخورده میگذارد.
به هر عنصر متصل یک Y.Doc و یک Awareness جداگانه بدهید، همانطور که هر کلاینت یک اتاق دارد: دو اتصال که یک Awareness را مشترکاً دارند، فیلد erd یکدیگر را بازنویسی میکنند.
مقداردهی اولیهٔ یک اتاق
یک اتاق نموداری ندارد تا وقتی کلاینتی آن را مقداردهی اولیه کند.
آنچه ویرایشگر در این فاصله نگه میدارد وارد مقداردهی اولیهٔ خودش میشود و اگر مقداردهی اولیهٔ کلاینتی دیگر زودتر برسد، جای خود را به آن میدهد: اتصال روی آن با rejoin('snapshot') دوباره باز میشود و سندی که عنصر نگه میداشت، همراه با آنچه پس از اتصال در آن ویرایش شده، از بین میرود.
وقتی دو کلاینت همزمان مقداردهی اولیه میکنند، یک مقداردهی اولیه در همهٔ کلاینتها برنده میشود.
مقداردهندهٔ دیگر روی آن با rejoin('seedLost') دوباره باز میشود و ویرایشهایش را از زمان مقداردهی اولیهٔ خودش از دست میدهد، و همین برای کلاینتی که به مقداردهی اولیهٔ بازنده پیوسته بود با rejoin('snapshot') رخ میدهد. ویرایشهای آنها در مقداردهی اولیهٔ بازنده به هیچکس نمیرسد.
مقداردهی اولیهای که پیش از همگام شدن فراهمکننده انجام شود، هملحظه با مقداردهی اولیهای به شمار میآید که نمودار اتاق از آن آمده است، بنابراین میتواند بر آن برنده شود و نمودار اتاق را جایگزین کند.
BindOptions
import type { Awareness } from 'y-protocols/awareness';
type BindOptions = {
awareness?: Awareness;
getNickname?: () => string;
};
awareness
شیء Awareness که نشانگر ماوس، فوکوس، انتخابها و مستطیلهای انتخاب را حمل میکند، مانند Awareness فراهمکننده.
با آن، اتصال ردیابهای ماوس و فوکوس عنصر را روشن میکند و حضور کلاینتهای دیگر را روی عنصر نشان میدهد؛ حضور را ببینید.
بدون آن فقط نمودار همگام میشود: عنصر نه نشانگر ماوس میفرستد، نه فوکوس و نه انتخاب، و حضور هیچکس دیگر را نشان نمیدهد.
bindErdEditor(editor, doc, { awareness: provider.awareness });
getNickname
نامی که دیگران کنار نشانگر ماوس این ویرایشگر میبینند.
مخزن مشترک عنصر آن را هر بار که میفرستد میخواند، مانند getSharedStore، و وقتی وجود نداشته باشد یا خالی باشد، کاربران دیگر user را میبینند.
bindErdEditor(editor, doc, {
awareness: provider.awareness,
getNickname: () => 'nickname...',
});
ErdYjsBinding
type ErdYjsBinding = {
seed: (value?: string) => boolean;
on: <K extends keyof BindingEvents>(
event: K,
listener: BindingEvents[K]
) => () => void;
destroy: () => void;
};
type BindingEvents = {
rejoin: (reason: RejoinReason) => void;
error: (error: Error) => void;
};
type RejoinReason = 'snapshot' | 'seedLost';
on(event, listener) تابعی برمیگرداند که شنونده را برمیدارد.
seed
نخستین تصویر لحظهای اتاق را از سند ویرایشگر مینویسد و true برمیگرداند.
اگر value داده شود، یعنی متن یک سند به هر شکلی که setInitialValue میخواند، ابتدا آن را در ویرایشگر بارگذاری میکند.
سپس کشیدنها، تغییر اندازهها و تغییر رنگهایی را که ویرایشگر هنوز نگه داشته است بدون ثبت در گزارش رها میکند، تا هیچکدام پس از تصویر لحظهای وارد اتاق نشود. بدون value، تصویر لحظهای از پیش آنها را نشان میدهد، چون سند ویرایشگر را برمیدارد؛ با value، آنها به سندی تعلق داشتند که value جایگزینش کرد.
سپس همان تصویر لحظهای را مینویسد که آنچه اتاق نگه میدارد شرح میدهد.
وقتی اتاق از پیش یک تصویر لحظهای دارد، false برمیگرداند و بدون بارگذاری value چیزی نمینویسد، بنابراین هر کلاینت میتواند پس از اتصال آن را فراخوانی کند. همچنین وقتی اتصال نابود یا متوقف شده باشد، و در اتصالی که هنگام اتصال رد شده است، false برمیگرداند.
binding.seed(); // the document the editor holds
// or
binding.seed(await (await fetch('/diagrams/shop.erd.json')).text());
on@rejoin
اتصال ویرایشگر را روی تصویر لحظهای اتاق دوباره باز کرد، همانطور که reseed انجام میدهد؛ ویرایشهای آفلاین و پیوستن دوباره میگوید چه چیزی را نگه میدارد و چه چیزی را از دست میدهد.
reason دلیل را میگوید:
'snapshot': تصویر لحظهای ویرایشهایی دارد که اتصال اعمال نکرده بود، چون در زمانی که فشردهساز آنها را تلفیق میکرد غایب بود، یا چون یک فشردهساز آنچه را در یک تصویر لحظهای تلفیق کرده بود که تصویر لحظهای فشردهساز دیگری جای آن را گرفت، در تلفیق جداگانهٔ خودش دوباره گذاشت (یک فشردهساز برای هر اتاق را ببینید). همچنین وقتی تصویر لحظهای نخستین چیزی است که به اتصال یک اتاق خالی میرسد، و وقتی به مقداردهی اولیهٔ دیگری تعلق دارد و این اتصال به مقداردهی اولیهای پیوسته بود که خودش ننوشته بود، همین دلیل است.'seedLost': مقداردهی اولیهٔ کلاینتی دیگر بر مقداردهی اولیهای که این اتصال نوشته بود برنده شد.
binding.on('rejoin', reason => {
console.info('reopened on the room', reason);
});
on@error
اتصال از متصل شدن خودداری کرد، متوقف شد یا یک گزینه را رد کرد. پیام هر کدام با erd-editor-yjs: شروع میشود.
هنگام اتصال، در این موارد چیزی را متصل نمیکند:
- ویرایشگر یا مخزن همتا از پیش به این
Y.Docمتصل است؛ @dineug/erd-editorآن پیش از نسخهٔ5.1.0ساخته شده است؛ اتصال، مخزن مشترکی را که از یک عنصر گرفته بود نابود میکند؛- مخزن همتا پیش از اتصال نابود شده بود؛
- اتاق یک تصویر لحظهای با قالبی دارد که این نسخه آن را نمیخواند (آنچه اتاق نگه میدارد را ببینید).
چنین اتصالی error را در یک microtask ارسال میکند، پس شنوندهای که درست پس از فراخوانی اضافه شود آن را میشنود، و هیچ rejoin هرگز روی آن ارسال نمیشود.
پس از آن، اتصال در این موارد متوقف میشود:
- تصویر لحظهای اتاق به قالبی تبدیل میشود که این نسخه آن را نمیخواند. هر اتصال این نسخه متوقف میشود، هر ویرایشگر آنچه را اعمال کرده نگه میدارد و هیچ ویرایش بعدی نوشته نمیشود؛
- مخزن همتای آن بیخبر از اتصال نابود شده است، که دفعهٔ بعد که اتاق تغییر میکند یا
seed()یاcompact()فراخوانی میشود، تشخیص داده میشود.
اتصالی که متوقف میشود، مخزن مشترکی را که گرفته بود، و مخزن و Y.Doc خود را بهصورت یک جفت رها میکند، همانطور که destroy انجام میدهد، و شنوندههایش را نگه میدارد.
فقط برای bindPeerStore، این موارد error را ارسال میکنند و اتصال ادامه مییابد:
compactAfterیک عدد صحیح مثبت نیست: اتصال فقط وقتی خواسته شود تلفیق میکند؛electionGraceMsیاretainMsعددی از 0 تا 2,147,483,647 میلیثانیه نیست: مقدار پیشفرض آن جای آن را میگیرد، چون زمانسنجی فراتر از این محدوده بلافاصله فعال میشود؛syncedرد میشود: اتصال هرگز تلفیق نمیکند و انتخاب را به فشردهسازهای دیگر وامیگذارد (یک فشردهساز برای هر اتاق را ببینید).
binding.on('error', error => {
console.error(error.message);
});
destroy
کشیدنها، تغییر اندازهها و تغییر رنگهایی را که ویرایشگر هنوز نگه داشته است به اتاق میفرستد، فیلد Awareness با نام erd این اتصال را null میکند تا دیگران نشانگر ماوس، فوکوس و انتخاب آن را بلافاصله کنار بگذارند، و مشاهدهٔ Y.Doc و Awareness را متوقف میکند.
برای یک عنصر، مخزن مشترکی را هم که گرفته بود نابود میکند و عنصر و سندش را همانطور که هستند میگذارد.
فراخوانی دوم کاری انجام نمیدهد. پس از آن آنچه ویرایشگر ویرایش میکند به هیچکس نمیرسد.
مخزن همتا، Y.Doc و فراهمکننده در اختیار شما میمانند تا پس از اتصال نابودشان کنید: مخزن همتایی که زودتر نابود شود، کشیدنهایی را که هنوز نگه داشته بود هرگز نمیفرستد.
destroy خود عنصر هر مخزن مشترکی را که تحویل داده، از جمله مخزن این اتصال، نابود میکند و آنچه را نگه داشتهاند دور میریزد، پس ابتدا اتصال را نابود کنید.
اتصال را پیش از اینکه همان ویرایشگر یا مخزن همتا را دوباره به همان Y.Doc متصل کنید، نابود کنید؛ اتصالی که با error متوقف شده است از پیش رها کرده است.
binding.destroy();
editor.destroy();
حضور
با awareness، نشانگر ماوس ویرایشگر، جدول یا خانهٔ دارای فوکوس، انتخاب و مستطیل انتخاب در فیلد erd از وضعیت Awareness محلی آن منتقل میشوند، نه در Y.Doc، پس هرگز وارد تاریخچهٔ اتاق، تصویر لحظهای آن یا یک Y.Doc ماندگار نمیشوند.
دیگران آنها را همانطور نشان میدهند که حضور یک همکار را نشان میدهند — focusTracker را ببینید — و کلاینتی که میپیوندد حضوری را نشان میدهد که اتاق از پیش نشان میدهد.
عنصر فوکوس و انتخاب خود را هر 30 ثانیه و مستطیل انتخاب خود را هر ثانیه دوباره میفرستد. هر یک از این ارسالهای دوباره به دیگران میرسد، حتی وقتی آخرین ارسال را تکرار کند، تا آنچه کاربر هنوز نگه داشته است هرگز منقضی نشود.
کلاینتی که اتاق را ترک میکند بلافاصله فراموش میشود.
وقتی Awareness وضعیت آن را کنار میگذارد، یا فیلد erd آن null میشود، همانطور که destroy تنظیم میکند، یا نام ویرایشگر دیگری را میگیرد، اتصال بهجای صبر برای منقضی شدن، نشانگر ماوس، فوکوس، انتخاب و مستطیل انتخاب آن ویرایشگر را با forgetPeer از عنصر برمیدارد.
تا وقتی کلاینت دیگری هنوز همان ویرایشگر را نشان میدهد، آنها را نگه میدارد، مانند عنصری که دوباره به Y.Doc دیگری از اتاق متصل شده است.
ویرایشهای آفلاین و پیوستن دوباره
کلاینتی که از فراهمکننده قطع شده است به ویرایش ادامه میدهد. ویرایشهایش در Y.Doc محلی آن منتظر میمانند، نه در مخزن مشترک، و وقتی فراهمکننده دوباره وصل و همگام شود به دیگران میرسند.
وقتی یک فشردهساز ویرایشهایی را تلفیق کرده است که کلاینت در غیاب خود هرگز اعمال نکرده بود، تصویر لحظهای آنها را دارد، پس هنگام اتصال دوباره، کلاینت روی تصویر لحظهای با rejoin('snapshot') دوباره باز میشود، پس از فرستادن کشیدنها، تغییر اندازهها و تغییر رنگهایی که ویرایشگرش هنوز نگه داشته بود. تلفیقی که فقط شامل چیزهایی باشد که کلاینت اعمال کرده بود، چیزی را دوباره باز نمیکند.
پیوستن دوباره روی تصویر لحظهای مقداردهی اولیهای که اتصال به آن پیوسته بود، هر ویرایشی را که تصویر لحظهای ندارد دوباره اجرا میکند، از جمله ویرایشهای خود این کلاینت در زمان غیبت، پس هیچکدام از دست نمیرود. پیوستن دوباره روی مقداردهی اولیهٔ دیگر چیزی را نگه نمیدارد، همانطور که مقداردهی اولیهٔ یک اتاق شرح میدهد.
هر پیوستن دوباره، تاریخچهٔ واگرد، انتخاب و فوکوس را پاک میکند و نمای جریان باز را میبندد، کشیدن رابطه را پایان میدهد و ویرایشگر یادداشت، نام گروه جدول یا خانه را میبندد، چون ممکن است به چیزی اشاره کنند که تصویر لحظهای دیگر ندارد.
نام گروه جدولی که تایپ شده و هنوز ثبت نشده است دور ریخته میشود. خانهٔ نام منطقی که بسته میشود، نام فیزیکی را از نام منطقیای که خوانندهاش تایپ کرده استخراج میکند، اگر سند دوباره بازشده هنوز آن را نگه دارد، همانطور که هر بسته شدن آن خانه انجام میدهد. آن استخراج یک ویرایش معمولی است: اتاق آن را در گزارش ثبت میکند، یک گام واگرد میسازد و change را ارسال میکند؛ reseed را ببینید.
اتصالی که به اتاق پیوسته بود آنچه را خوانندهاش میبیند نگه میدارد: موقعیت پیمایش، سطح بزرگنمایی، زبانه و تنظیماتی که یک خوانندهٔ فقط خواندنی تغییر داده است.
همچنین نمایش نامها و تنظیمات کد (زبان مولد کد، شیوهٔ نگارش نامها و نوع علامت نقلقول) را که خواننده برای خودش نشان داده بود نگه میدارد، به شرط اینکه قفل هر کدام پیش از پیوستن دوباره روشن بوده باشد و پس از آن هم با همان مقدار قفلشده روشن بماند.
هیچکدام از آنچه نگه میدارد به value نمیرسد، که تنظیمات سند دوباره بازشده را گزارش میکند؛ reseed را با keepScreen ببینید.
موقعیت پیمایش و سطح بزرگنمایی عنصری که از آخرین بارگذاریاش نمایش داده نشده است، و کل صفحهٔ نمایش اتصالی که هنوز نپیوسته بود، همانجا باز میشوند که تصویر لحظهای تعیین میکند.
مخزن همتا صفحهٔ نمایش ندارد، پس پیوستن دوبارهٔ آن هیچ صفحهای را نگه نمیدارد.
ویرایشگرهای فقط خواندنی
عنصر فقط خواندنی که به یک اتاق متصل است چیزی در تاریخچهٔ اتاق نمینویسد: مخزن مشترک آن هیچ تغییری نمیفرستد، فقط ردیابها و دستدهی ثباتها را میفرستد، و اتاق هیچکدام را در گزارش ثبت نمیکند.
با این حال هر ویرایشی را که اتاق حمل میکند اعمال میکند، و با awareness، نشانگر ماوس، فوکوس، انتخاب و مستطیل انتخاب خوانندهٔ آن همچنان به دیگران میرسد.
تنظیماتی که خوانندهاش تغییر میدهد روی صفحهٔ همان ویرایشگر میماند، مانند هر ویرایشگر فقط خواندنی.
seed رد نمیشود: عنصر فقط خواندنی یک اتاق خالی را با سندی که نگه میدارد، یا با value در صورت داده شدن، مقداردهی اولیه میکند.
آنچه اتاق نگه میدارد
اتصال نمودار را در دو نام از Y.Doc نگه میدارد: erd:snapshot، یک map که تصویر لحظهای سند را نگه میدارد، و erd:log، یک آرایه از دستههای کنش ویرایشگر از آن پس. فراهمکنندهٔ ماندگاری هر دو را همراه با بقیهٔ Y.Doc ذخیره میکند.
تصویر لحظهای runtimeValue سند را نگه میدارد، از جمله موجودیتهای حذفشده، ثباتها و نسخهٔ همکاری آن، و اینکه ویرایشهای هر نویسنده تا کجا در آن تلفیق شده است. مقداردهی اولیه آن را مینویسد و هر تلفیق یک فشردهساز هم همینطور.
هر ورودی گزارش یک دسته است، همانطور که ویرایشگر فرستاده، همراه با فرادادهاش که شناسهٔ ویرایشگر و نام فرستنده را حمل میکند.
پیمایشها، بزرگنماییها، تعویض زبانهها، حضور و دستدهی ثباتها هرگز در گزارش ثبت نمیشوند. هر تغییر دیگری که ویرایشگر میفرستد ثبت میشود، از جمله تغییر نمایش نامها با روشن بودن قفل آن، که هر کلاینت دیگر آن را کنار میگذارد.
زبان مولد کد، شیوهٔ نگارش نام یا نوع علامت نقلقولی که با روشن بودن قفلش تغییر کند هرگز فرستاده نمیشود، پس روی صفحهٔ همان ویرایشگر میماند و هرگز به اتاق نمیرسد.
Y.Doc که یک فراهمکنندهٔ ماندگاری برمیگرداند نموداری را که نگه میدارد باز میکند، همانطور که یک اتصال هر Y.Doc دارای تصویر لحظهای را باز میکند؛ نحوهٔ استفاده را ببینید.
تصویر لحظهای فایل نیست: مقدار زمان اجرای آن موجودیتهای حذفشده را حمل میکند، که یک فایل نباید نگه دارد.
برای نوشتن نمودار اتاق در محل ذخیرهسازی، value عنصر متصل را هر وقت رویداد change آن ارسال شد ذخیره کنید.
آن را یک بار هم پس از اتصال، پس از فراخوانی seed با value، و در شنوندهٔ rejoin ذخیره کنید، چون هیچکدام از آن بارگذاریها change را ارسال نمیکنند.
عنصر فقط خواندنی هرگز change ارسال نمیکند، پس از عنصری که فقط خواندنی نیست ذخیره کنید، یا value را از یک مخزن همتای متصل بخوانید که ویرایشهای اتاق را به محض رسیدن اعمال میکند.
replicationStore را نمیتوان متصل کرد، چون متدهایی را که اتصال لازم دارد ندارد، و مخزنی که از مخزن مشترک یک عنصر متصل تغذیه شود ویرایشهای اتاق را از دست میدهد؛ رونوشتبرداری از ویرایشگر در حال اجرا را ببینید.
قالب خصوصی است و ممکن است با ERD_YJS_FORMAT تغییر کند.
اتاق را فقط از طریق یک اتصال بخوانید و نامهای erd: و فیلد Awareness با نام erd را به آن واگذارید.
فشردهسازی یک اتاق
تاریخچهٔ اتاق با هر ویرایش بزرگ میشود و هر کلاینتی که میپیوندد همهٔ آن را دوباره اجرا میکند، تا وقتی چیزی آن را تلفیق کند.
bindPeerStore یک مخزن همتای بدون رابط کاربری از @dineug/erd-editor/peer.js را متصل میکند و یک ErdYjsPeerBinding برمیگرداند: همان seed، on و destroy، همراه با compact.
مخزن همتایی که در یک فرایند Node در کنار سرور متصل شده باشد، یعنی یک فرایند جانبی، تاریخچه را در تصویر لحظهای از نمودار خودش تلفیق میکند.
اتصال یک عنصر هرگز تلفیق نمیکند، و مخزن همتایی که بدون compactAfter و بدون فراخوانی compact() متصل شده است یک همتای بدون رابط کاربری معمولی است.
function bindPeerStore(
peer: PeerStore,
doc: Y.Doc,
options?: PeerBindOptions
): ErdYjsPeerBinding;
type ErdYjsPeerBinding = ErdYjsBinding & {
compact: () => CompactResult;
};
npm install @dineug/erd-editor-yjs @dineug/erd-editor yjs y-protocols y-websocket ws
import { createPeerStore } from '@dineug/erd-editor/peer.js';
import { bindPeerStore } from '@dineug/erd-editor-yjs';
import WebSocket from 'ws';
import { WebsocketProvider } from 'y-websocket';
import * as Y from 'yjs';
const doc = new Y.Doc();
const provider = new WebsocketProvider('wss://yjs.example.com', 'my-diagram', doc, {
WebSocketPolyfill: WebSocket,
});
const synced = new Promise(resolve => {
provider.on('sync', function onSync(isSynced) {
if (!isSynced) return;
provider.off('sync', onSync);
resolve();
});
});
const peer = createPeerStore({ nickname: 'compactor', presence: false });
const binding = bindPeerStore(peer, doc, {
awareness: provider.awareness,
compactAfter: 1000,
synced,
});
binding.on('error', error => console.error(error));
process.on('SIGTERM', () => {
binding.destroy(); // first, so the peer store's held drags still reach the room
peer.destroy();
provider.destroy();
doc.destroy(); // ends the provider's Awareness timer, so the process can exit
});
مخزن همتا در اختیار شما میماند: اتصال هرگز آن را نابود نمیکند. نام آن همانی است که به createPeerStore داده شد و presence: false مانع میشود فوکوس خودش را بفرستد.
یک تلفیق، تصویر لحظهای را از مقدار زمان اجرا، ثباتها و نسخهٔ خود مخزن همتا مینویسد، و ورودیهای گزارشی را که پوشش میدهد و هر ورودی مقداردهی اولیهٔ دیگر را حذف میکند، همه در یک تراکنش.
کلاینتهایی که همهچیز را اعمال کرده بودند میبینند که ورودیها حذف میشوند؛ کلاینتی که اعمال نکرده بود روی تصویر لحظهای دوباره باز میشود (ویرایشهای آفلاین و پیوستن دوباره را ببینید).
هر تلفیق کل نمودار را در تصویر لحظهای بازنویسی میکند، پس هر کدام به هر کلاینت یک بهروزرسانی میفرستد به اندازهٔ تقریبی JSON زمان اجرای نمودار و ثباتها روی هم، هر قدر هم ورودیهای کمی را بگیرد. compactAfter را آنقدر بالا بگذارید که ورودیهایی که یک تلفیق میگیرد از یک نسخهٔ نمودار سنگینتر باشند.
PeerBindOptions
type PeerBindOptions = CompactOptions & {
awareness?: Awareness;
};
type CompactOptions = {
compactAfter?: number;
synced?: Promise<void>;
electionGraceMs?: number;
retainMs?: number;
};
| گزینه | پیشفرض | توضیح |
|---|---|---|
awareness | None | مخزن همتا را در Awareness اتاق نشان میدهد، همانطور که awareness یک عنصر را نشان میدهد. فشردهسازی که خودش تلفیق میکند آنجا علامتگذاری میشود، پس فشردهسازهایی که یکدیگر را میبینند یکی را برای تلفیق انتخاب میکنند، و فشردهسازی که هیچ کلاینت دیگری نمیبیند خودش چیزی تلفیق نمیکند؛ یک فشردهساز برای هر اتاق را ببینید. بدون آن، فشردهساز بدون انتخاب و بدون مهلت خودش تلفیق میکند. |
compactAfter | None | وقتی گزارش این تعداد ورودی دارد که یک تلفیق میگیرد، خودش تلفیق میکند. بدون آن، یا بدون synced، فقط compact() تلفیق میکند، و تلفیقی که دوباره میگذارد (یک فشردهساز برای هر اتاق را ببینید). |
synced | None | یک promise که پس از همگام شدن فراهمکننده حل میشود. پیش از آن هیچ تلفیقی انجام نمیشود، compact() هم همینطور. بدون آن، چیزی خودش تلفیق نمیشود و compact() بلافاصله تلفیق میکند. |
electionGraceMs | 3000 | با awareness، فشردهساز چه مدت اتاق را زیر نظر میگیرد تا خودش تلفیق کند، پس از synced و دوباره هر وقت کلاینتهای دیگر پس از نبودن ظاهر شوند. |
retainMs | 600000 (ده دقیقه) | ورودیهای یک تلفیق چه مدت برای گذاشتن دوباره نگه داشته میشوند (یک فشردهساز برای هر اتاق را ببینید)، از نخستین بهروزرسانیای که اتاق پس از تلفیق میفرستد شمرده میشود. |
گزینهٔ نامعتبر، یا رد شدن synced، error را ارسال میکند.
compact
type CompactResult =
| { compacted: true; generation: number }
| {
compacted: false;
reason: 'not-synced' | 'no-snapshot' | 'destroyed';
};
بلافاصله تلفیق میکند، بدون انتخاب، مهلت یا آستانه.
ابتدا تصویر لحظهای فعلی اتاق را میگیرد، از جمله یک پیوستن دوباره، و آنچه را مخزن همتایش هنوز نگه داشته است میفرستد.
{ compacted: true, generation } پاسخ میدهد، که generation تعداد تلفیقهای تصویر لحظهای را میشمارد: 0 برای مقداردهی اولیه، بهعلاوهٔ یک برای هر تلفیق.
در غیر این صورت { compacted: false, reason } پاسخ میدهد:
'destroyed'وقتی اتصال نابود یا متوقف شده باشد، و در اتصالی که هنگام اتصال رد شده است، حتی پیش از حل شدنsynced؛'not-synced'پیش از حل شدنsynced؛'no-snapshot'در اتاقی که هیچ کلاینتی آن را مقداردهی اولیه نکرده است.
const result = binding.compact();
if (!result.compacted) console.warn(result.reason);
یک فشردهساز برای هر اتاق
دقیقاً یک فشردهساز برای هر اتاق اجرا کنید و فقط پس از توقف فشردهساز قدیمی، فشردهساز تازهای راهاندازی کنید. هیچ چیز این را اجباری نمیکند.
میان فشردهسازهایی که یکدیگر را در Awareness میبینند، فقط آن که کوچکترین شناسهٔ کلاینت Yjs را دارد خودش تلفیق میکند، و وقتی میرود، یا synced آن رد میشود، بعدی بلافاصله جای آن را میگیرد.
فشردهسازی که Awareness آن هیچ کلاینت دیگری نشان نمیدهد، مثل وقتی که سوکت خودش بسته میشود و همه را کنار میگذارد، خودش چیزی تلفیق نمیکند تا وقتی که دیگران دوباره ظاهر شوند و اتاق را به مدت electionGraceMs زیر نظر گرفته باشد.
دو فشردهساز که از هم جدا شدهاند، یا یک راهاندازی دوباره که پیشین آن هنوز اجرا میشود، همچنان میتوانند هر کدام تلفیق کنند.
آنگاه اتاق یکی از دو تصویر لحظهای را نگه میدارد، و فشردهسازی که تصویر لحظهایاش باخت، آنچه را تلفیق کرده بود و برنده ندارد دوباره میگذارد، تا وقتی که آن را نگه دارد: retainMs، و حداکثر 1,000 ورودی آخر آن، هر قدر هم هر ورودی بزرگ باشد.
بنابراین وقتی فشردهسازی که تصویر لحظهایاش میبازد همان است که دوباره وصل میشود، چیزی از دست نمیرود، به شرط اینکه در زمان جدایی بیش از 1,000 ورودی تلفیق نکرده باشد.
وقتی همان است که آنلاین ماند و مهلت نگهداشتنش به پایان رسیده است، تصویر لحظهای و تاریخچهٔ اتاق آنچه را تلفیق کرده بود ندارند تا وقتی دوباره تلفیق کند. کلاینتی که آن ویرایشها را از دست داده بود، یا کلاینتی که در این فاصله میپیوندد، آنها را ندارد و ویرایشهای بعدی هر نویسندهای را که ویرایشهایش را فشردهساز تلفیق کرده بود نگه میدارد و اعمال نمیکند. تلفیق بعدی فشردهساز همه را برمیگرداند، مگر اینکه تصویر لحظهای برنده ویرایشهایی داشته باشد که آن نداشت، که آن را روی آن تصویر لحظهای دوباره باز میکند و آنچه را تلفیق کرده بود برای همیشه از دست میدهد.
فشردهساز در تلفیق جداگانهٔ خودش دوباره میگذارد، که پس از رسیدن به تصویر لحظهای برنده انجام میشود، بدون انتخاب، مهلت یا آستانه، و هر کلاینت روی آن تلفیق با rejoin('snapshot') دوباره باز میشود.
ویرایشهایی که دوباره میگذارد جدا از آن تلفیق به هیچ کلاینتی نمیرسند، پس کلاینتی که همیشه آنلاین بود با دیگران و با کلاینتی که بعداً میپیوندد یکسان تمام میشود، حتی وقتی ویرایش بعدی نویسندهای دیگر بر یکی از آنها بنا شده باشد، مانند تغییر نام جدولی که کلاینت دیگری اضافه کرده بود.
هزینهٔ هر گذاشتن دوباره برای هر کلاینت همان پیوستن دوباره است، همراه با آنچه یک پیوستن دوباره از دست میدهد. وقتی دو فشردهساز همزمان دوباره میگذارند، هر کلاینت باز هم یکسان تمام میشود، هرچند ممکن است ویرایشی پیش از ویرایشی که بر آن بنا شده است اعمالشده بماند.
اتصالی که پیش از اجرای آن تلفیق نابود یا متوقف شود، مثل وقتی که یک شنوندهٔ rejoin آن را نابود میکند، ویرایشها را جداگانه دوباره میگذارد. چیزی از دست نمیرود، اما آنگاه کلاینتی که همیشه آنلاین بود ممکن است یکی را پس از ویرایش نویسندهای دیگر که بر آن بنا شده است اعمال کند و با کلاینتی که بعداً میپیوندد متفاوت تمام شود.
محدودیتها
- ناحیهٔ دید روی هر صفحهٔ نمایش میماند. پیمایشها، بزرگنماییها و تعویض زبانهها هرگز در گزارش ثبت نمیشوند، هرچند نشستی برپاشده با
getSharedStoreآنها را میفرستد (آنجا هم نما یا زبانهٔ کاربر دیگری را جابهجا نمیکنند). هر خواننده نمای خودش را نگه میدارد و اتصال آن را فقط در وضعیت Awareness خود نشان میدهد، که فعلاً چیزی آن را نمیخواند. وقتی قفلViewportیاCanvas Typeخاموش باشد،valueهر کلاینت نمای خودش را ذخیره میکند؛ با قفلهای روشن، که در سند تازه روشن هستند، هر کلاینت مقدار قفلشده را ذخیره میکند. - تصویر لحظهای به ازای هر اتصال یک ورودی بزرگ میشود. برای هر اتصالی که تا به حال در اتاق نوشته است نگه میدارد که ویرایشهایش تا کجا تلفیق شدهاند، و هرگز هیچکدام را کنار نمیگذارد. اتصالی که دوباره ساخته شود، یا اتصالی که به مقداردهی اولیهٔ دیگری رفته باشد، از نو شمرده میشود، و هر تلفیقی که دوباره میگذارد هم همینطور. هر ورودی یک شناسهٔ 32 نویسهای و یک عدد است، پس ده هزار نشست نویسنده چند صد کیلوبایت به هر تصویر لحظهای و هر تلفیق اضافه میکنند.
- هاب و اتاق چیزی میان هم بازارسال نمیکنند. عنصری که هم به یک اتاق و هم به هاب یک عامل کدنویسی، یا هر لایهٔ انتقال دیگری از طریق
getSharedStore()متصل است، ویرایشهای هر دو را اعمال میکند، اما هیچکدام ویرایشهای دیگری را نمیشنود، چون مخزن مشترک هرگز آنچه را دریافت کرده است به دیگران بازارسال نمیکند. - افزودنهای هملحظه ممکن است با ترتیبهای متفاوت فهرست شوند. جدولها یا یادداشتهایی که دو کلاینت در یک لحظه اضافه میکنند ممکن است در نمودار هر کلاینت به ترتیب دیگری قرار بگیرند، همانطور که در نشستی برپاشده با
getSharedStore؛ ویرایشهای همزمان را ببینید. - رنگها و چند تغییر دیگر که در یک لحظه انجام شوند میتوانند متفاوت تمام شوند. تغییر رنگ بدون نسخه در گزارش ثبت میشود و هر کلاینت هنگام اعمال آن را با نسخهٔ خودش مهر میزند. بنابراین دو رنگآمیزی یک جدول، یادداشت یا گروه جدول در یک لحظه میتواند کلاینتها، و کلاینتی را که بعداً میپیوندد و گزارش را دوباره اجرا میکند، با رنگهای متفاوت رها کند. تغییرهای دیگری هم که هر کلاینت به ترتیب شنیدنش اعمال میکند چنیناند، مانند دو جابهجایی ستون در یک جدول یا شاخص، یا دو تغییر پایگاه داده؛ ویرایشهای همزمان را ببینید.
- نشانگر ماوسی که میرود منتظر سرور میماند. اتصال نشانگر ماوس همتا را به محض اینکه Awareness آن را کنار بگذارد پاک میکند. سروری که وقتی سوکتی بسته میشود فقط آن وضعیتهای Awareness را حذف میکند که آن سوکت نخست اضافه کرده بود، کلاینتی را که دوباره وصل شده بود پس از رفتنش روی صفحهٔ دیگران نگه میدارد، تا وقتی که Awareness آن را 30 ثانیه بعد منقضی کند. سروری که هر وضعیتی را که یک سوکت آخرین بار نوشته است حذف میکند، چه یک بهروزرسانی و چه یک افزودن، آن را بلافاصله پاک میکند.
صادرات
| صادرات | توضیح |
|---|---|
bindErdEditor(editor, doc, options?) | یک عنصر <erd-editor> را متصل میکند؛ نحوهٔ استفاده را ببینید. یک ErdYjsBinding برمیگرداند. |
bindPeerStore(peer, doc, options?) | یک مخزن همتا از @dineug/erd-editor/peer.js را متصل میکند؛ فشردهسازی یک اتاق را ببینید. همان را همراه با compact برمیگرداند (ErdYjsPeerBinding). |
ERD_YJS_FORMAT | قالبی که این نسخه برای Y.Doc مینویسد، 1؛ اتصال اتاقی را که با قالب دیگری نوشته شده باشد رد میکند. |
نوعها: BindOptions، PeerBindOptions، CompactOptions، CompactResult، BindingEvents، RejoinReason، ErdYjsBinding، ErdYjsPeerBinding.