Перейти до основного вмісту

Спільне редагування

Метод getSharedStore() перетворює редактор на вузол сеансу спільного редагування.
Він генерує кожну зміну як потік дій і застосовує дії, які йому надсилають інші редактори, — транспорт між ними (WebSocket, WebRTC чи будь-що інше, здатне передавати JSON) забезпечуєте ви.
Це один із методів ErdEditorElement, і його поєднують із віддаленим зберіганням, коли переслані зміни треба ще й зберігати.
Починаючи з версії 5.1.0, щоб ділитися діаграмою через документ Yjs і будь-який з його провайдерів, не налагоджуючи транспорт самостійно, прив’яжіть редактор за допомогою @dineug/erd-editor-yjs; див. Yjs.

interface ErdEditorElement extends HTMLElement {
// ...
getSharedStore: (
config?: SharedStoreConfig & {
mouseTracker?: boolean;
focusTracker?: boolean;
}
) => SharedStore;
}

type SharedStoreConfig = {
getNickname?: () => string;
};

type SharedStore = {
connection: () => void;
disconnect: () => void;
dispatch: (actions: Array<AnyAction> | AnyAction) => void;
dispatchSync: (actions: Array<AnyAction> | AnyAction) => void;
subscribe: (fn: (value: AnyAction[]) => void) => Unsubscribe;
flushStreamBuffers: () => void;
getLWW: () => SharedRegisters;
mergeLWW: (lww: SharedRegisters, version: number) => void;
getVersion: () => number;
forgetPeer: (editorId: string) => void;
reseed: (value: string, options: ReseedOptions) => void;
destroy: () => void;
};

// a JSON object whose shape is the editor's own: hand it on as it is
type SharedRegisters = Record<string, unknown> & {
readonly [registersBrand]: true;
};

type ReseedOptions = {
lww: SharedRegisters;
version: number;
keepScreen?: boolean; // default false
};

// example
const sharedStore = editor.getSharedStore();

@dineug/erd-editor не експортує жодного з цих типів за іменем. Код TypeScript дістається до них через ErdEditorElement, як-от ReturnType<ErdEditorElement['getSharedStore']>.

Дія — це простий об’єкт, придатний до серіалізації, який описує одну зміну документа, тож вона без втрат проходить через JSON.stringify дорогою до транспорту.
Дії надходять пакетами: метод subscribe передає вам масив, і цей масив є однією одиницею — пересилайте його цілим і цілим передавайте в dispatch на іншому боці.
Вважайте дію непрозорою й передавайте її без змін, не читаючи й не переписуючи, адже саме службові дані, які вона несе, як-от її версія, підтримують синхронізацію редакторів.

Частина того, що робить редактор, залишається в ньому самому.
Виведення таблиці, нотатки чи групи таблиць на передній план, як це робить їх виділення, не надсилається.
Не надсилається й вибір у Statements, Header, Write logical names into empty comments (починаючи з версії 4.2.0) і Tables на вкладці схеми SQL, який належить вікну, де його зроблено.
Починаючи з версії 4.1.0, оглядач теж залишається у своєму редакторі: не надсилається, чи він відкритий, а також його ширина, згорнуті рядки, рядок, на якому перебуває клавіатурний фокус, положення прокручування дерева, його пошук і параметри.
Не надсилається й підсвічування, яке перехід до зовнішнього ключа вмикає на лінії його зв’язку.
Починаючи з версії 4.2.0, вкладка словника теж залишається у своєму редакторі: не надсилається, який аркуш показано, а також пошук, виділення, положення прокручування й ширини стовпців кожного аркуша. Зміни, сортування та імпорт CSV на ній надсилаються.
Прокручування, зміна масштабу чи перемикання вкладки надсилаються, але ніколи не зсувають область перегляду іншого користувача й не перемикають його вкладку. Це стосується й прокручування, яке спричиняє перехід з оглядача, перемикання на вкладку ERD, коли оглядач відкривається з іншої вкладки, а також перемикання вкладки й прокручування під час переходу з аркуша Checks вкладки словника до таблиці чи стовпця. Перехід із цього аркуша до слова, терміна чи домену залишається на вкладці словника, тож із нього нічого не надсилається.

Починаючи з версії 4.2.0, надсилається й перемикання відображення назв, а його блокування визначає, що з ним роблять інші редактори. Блокування Name View увімкнено в новій діаграмі та в будь-якому документі, збереженому до версії 4.2.0.
Поки блокування увімкнено, перемикання видно лише в редакторі, де його зроблено: воно однаково надсилається, але кожен інший редактор і кожна репліка його відкидають, тож кожен користувач може бачити інший варіант, а value зберігає заблокований.
Поки блокування вимкнено, перемикання, зроблене в редакторі, який може редагувати, змінює відображення назв у кожному редакторі й кожній репліці, причому переважає останнє перемикання, і кожен із них його зберігає.
Власне перемикання редактора в режимі лише для читання завжди залишається в цьому редакторі, а сам він і далі застосовує спільні перемикання інших редакторів.

Починаючи з версії 5.1.0, зміна мови генератора коду, стилю назв таблиць, стилю назв стовпців чи типу лапок так само підпорядковується власному блокуванню (Language, Table Name Case, Column Name Case або Bracket Type), з однією відмінністю: поки блокування увімкнено, зміна не надсилається взагалі.
Її видно лише в редакторі, де її зроблено, і за нею стежать його вкладки генератора коду та схеми SQL, а жоден інший редактор чи репліка її не бачить. value і runtimeValue зберігають заблоковане значення, тож новий учасник, ініціалізований із будь-якого редактора, її теж не бачить, і поки блокування увімкнено, кожен користувач може бачити іншу мову, стиль назв чи тип лапок.
Поки блокування вимкнено, зміна, зроблена в редакторі, який може редагувати, надсилається з позначкою спільної (shared: true у її корисному навантаженні). Вона змінює налаштування в кожному редакторі й кожній репліці, причому переважає остання зміна, і кожен із них її зберігає. Редактор чи репліка версії 5.1.0 застосовує позначену зміну незалежно від власного блокування, тож зміна, зроблена одразу після розблокування, потрапляє всюди, навіть туди, куди доходить раніше за розблокування.
До версії 5.1.0 така зміна доходила до кожного редактора й кожної репліки незалежно від того, чи ввімкнено блокування, тоді як файл зберігав заблоковане значення.

Починаючи з версії 5.0.0, редактор у режимі лише для читання не надсилає жодних змін. Прокручування, масштаб, вкладка, відображення назв, база даних, мова генератора коду, стилі назв, тип лапок і рядки View Option, які змінює його читач, залишаються на його екрані незалежно від блокувань; до версії 5.0.0 його прокручування, зміни масштабу, перемикання вкладок, база даних і налаштування коду надсилалися так само, як у редактора з правом редагування.
Його сховище й далі надсилає те, що надсилають його трекери про курсор, фокус, виділення й рамку перетягування читача, надсилає регістри, яких просить редактор, що приєднується, і застосовує кожну зміну, яку надсилають інші. Зміна бази даних, налаштування генератора коду чи рядка View Option потрапляє в його документ, а також на його екран, якщо тільки читач не змінив це налаштування саме там, а спільне перемикання відображення назв показується на його екрані в будь-якому разі. Починаючи з версії 5.1.0, він отримує налаштування генератора коду лише тоді, коли це налаштування змінили за вимкненого блокування або його несе розблокування.
Коли редактор переходить у режим лише для читання, він спершу надсилає те, що сховище ще утримує від перетягування чи прокручування, як це робить flushStreamBuffers, тож інші зберігають його останню частину.

Блокування спільні: останнє блокування чи розблокування налаштування переважає в кожному редакторі, а блокування несе значення редактора, який його ввімкнув, тож кожен редактор і кожна репліка зберігають однакове значення.
Тому блокування Viewport зберігає область перегляду редактора, який його ввімкнув, а блокування Canvas Type — останню вкладку, на якій був цей редактор перед вкладкою налаштувань, де вмикаються блокування.
Вимкнення блокування Language, Table Name Case, Column Name Case, Bracket Type чи, починаючи з версії 4.2.0, Name View також задає цьому налаштуванню в кожному редакторі значення, показане в редакторі, який вимкнув блокування, разом із його власною зміною, зробленою під блокуванням, тож усі вони й далі зберігають одне значення. Вимкнення Viewport чи Canvas Type не зсуває нічиєї області перегляду й не перемикає нічиєї вкладки.

Зміна скрипта Before tables чи After tables надсилається повністю, коли його поле втрачає фокус. Два скрипти узгоджуються окремо, тож зміна, яку співавтор вносить в один із них, ніколи не перезаписує інший.

Ніщо, зроблене в режимі Flow на вкладці візуалізації, не є зміною документа, тож редактор у режимі Flow не надсилає іншим жодних змін, а зміни, які надсилають вони, і далі застосовуються, поки він перебуває в цьому режимі.
Є два винятки. Вихід із режиму Flow на іншу вкладку — це перемикання вкладки, яке надсилається, як і будь-яке інше. Починаючи з версії 4.2.0, вибір у списку відображення назв на панелі інструментів режиму Flow — це перемикання відображення назв, яке надсилається й застосовується так само, як зроблене на полотні.
Кнопки Previous table і Next table, а також клавіші відміни й повторення, якими в режимі Flow переходять кроками між поданнями, не надсилаються.
Кнопка зовнішнього посилання на картці, результат швидкого пошуку, вибраний після #, @ чи :, і відкриття оглядача — усе це переводить із режиму Flow на вкладку ERD. Прокручування, яке там спричиняє кнопка на картці чи результат швидкого пошуку, теж надсилається.

Два редактори на одній сторінці​

Тут з’єднання subscribe із dispatch замінює мережу, тож структуру сеансу можна побачити, не відволікаючись на транспорт.

import '@dineug/erd-editor/erd-editor.js';

const editor1 = document.createElement('erd-editor');
const editor2 = document.createElement('erd-editor');

const sharedStore1 = editor1.getSharedStore({
getNickname: () => 'editor1',
});
const sharedStore2 = editor2.getSharedStore({
getNickname: () => 'editor2',
});

sharedStore1.subscribe(actions => {
sharedStore2.dispatch(actions);
});

sharedStore2.subscribe(actions => {
sharedStore1.dispatch(actions);
});

Обидва редактори тут починають із того самого нового порожнього документа, тож жодного з них не треба ініціалізувати.
Якщо редакторів три або більше, надсилайте пакети кожного з них усім іншим: редактор ніколи не передає далі того, що отримав.

Ініціалізація співавтора​

Спільне сховище пересилає зміни в міру їх внесення й ніколи не передає новому учасникові документ у його поточному стані, тож редакторові, що приєднується до вже запущеного сеансу, спершу треба передати документ.
Починаючи з версії 4.0.0, ініціалізуйте його значенням runtimeValue редактора, який уже є в сеансі, а не значенням value чи збереженим файлом.
Кімната, прив’язана за допомогою @dineug/erd-editor-yjs, сама містить документ, щойно клієнт її ініціалізував, тож кожен редактор, прив’язаний до неї, відкриває цей документ, і runtimeValue передавати не треба.

editor2.setInitialValue(editor1.runtimeValue);

const sharedStore2 = editor2.getSharedStore();

Значення часу виконання досі містить те, що було видалено після відкриття документа, тож, коли інший редактор згодом відмінить видалення, новий учасник поверне таблицю з її назвою й стовпцями.
value — це форма, яку містить файл, без видалених сутностей, тож новий учасник, ініціалізований із неї чи з файлу, натомість повернув би порожню таблицю без назви.
Починаючи з версії 4.2.0, те саме стосується слова, терміна чи домену, видаленого з аркуша словника: коли інший редактор відміняє видалення, новий учасник, ініціалізований із runtimeValue, знову містить рядок цілим, а ініціалізований із value — порожній рядок, який його власне value потім пропускає.
І надалі зберігайте значення value: значення runtimeValue призначене лише для ініціалізації іншого редактора, другого подання чи replicationStore.
Редактори у VS Code, Obsidian, IDE від JetBrains і вебзастосунку саме так ініціалізують друге подання відкритого документа.

Виклик setInitialValue не пересилається, тож ініціалізація нового учасника нічого не змінює в інших редакторах.
Починаючи з версії 4.0.0, він також відкидає регістри співпраці цього редактора, які узгоджують одночасні зміни. Якщо спільне сховище редактора вже мало підписника, сховище знову запитує в інших редакторів їхні регістри, як і під час першого виклику subscribe, тож редактор, повторно завантажений посеред сеансу, узгоджує зміни так само, як інші.
Присвоєння значення value зберігає регістри, але, на відміну від виклику setInitialValue, пересилається: щойно редактор має спільне сховище, кожен редактор у сеансі завантажує присвоєний документ, тож ніколи не ініціалізуйте нового учасника в такий спосіб.

Ні value, ні runtimeValue не містять регістрів і версії, якої досяг годинник редактора, тож новий учасник, ініціалізований лише з runtimeValue, бере їх із відповіді, яку редактори в сеансі надсилають на його першу підписку.
Доки ця відповідь не надійде, зміна, яку вносить новий учасник, може нести нижчу версію, ніж у решти, тож вони відхиляють її, поки вона залишається лише в новому учасникові, а старіший запис, що ще в дорозі, лягає поверх новішого, який містить ініціалізація. Якщо жоден запущений редактор не відповідає, як-от тоді, коли документ сеансу зберігає сервер чи сховище документів, новий учасник їх ніколи не отримає.
Починаючи з версії 5.1.0, передавайте їх разом зі значенням. У редакторі сеансу викличте flushStreamBuffers(), щоб перетягування чи зміна кольору, які він ще притримує, пішли першими, а потім в один і той самий момент прочитайте runtimeValue та getLWW() і getVersion() його сховища. Новий учасник завантажує значення через setInitialValue, а потім бере решту через mergeLWW власного сховища, саме в такому порядку, бо кожен setInitialValue відкидає регістри. Тоді його наступна зміна несе версію, більшу за кожну з тих, що містить ініціалізація, тож вона лягає без очікування відповіді. У сховища, що вже існує, reseed робить і те, і те одним викликом.

// on an editor already in the session
sharedStore1.flushStreamBuffers();
const seed = {
value: editor1.runtimeValue,
lww: sharedStore1.getLWW(),
version: sharedStore1.getVersion(),
};

// on the joiner
editor2.setInitialValue(seed.value);
const sharedStore2 = editor2.getSharedStore();
sharedStore2.mergeLWW(seed.lww, seed.version);

Ініціалізація, збережена так, щоб відкривати пізніших учасників, містить усі три значення разом. Це не файл: для зберігання й далі треба записувати value.
replicationStore не передає регістрів, тож учасник, ініціалізований його runtimeValue, і далі бере їх із відповіді редакторів.

Одночасні зміни​

Кожен редактор і кожна репліка зберігає для кожної назви й майже кожного іншого поля версію запису, що його встановив: це його регістри співпраці. Запис лягає, лише якщо його версія не нижча за збережену, тож редактори, які чують ті самі зміни в різному порядку, завершують з одним і тим самим документом.
Починаючи з версії 5.1.0, кожна версія під лічильником несе слот самого редактора-автора, взятий із його id. Тому версії зростають кроками по 4 194 304 і виходять за межі 32-бітного цілого, щойно лічильник сягає 512. JSON.stringify і JSON.parse передають їх без втрат, але ретранслятор чи сховище, що розбирає їх іншою мовою, має зберігати їх як 64-бітні числа, а не як 32-бітні цілі.

Починаючи з версії 5.1.0, два редактори, які змінюють одну назву чи одне поле в один і той самий момент, за винятком наведених нижче, завершують з однаковим значенням у кожному редакторі й кожній репліці: записуючи за однакового значення лічильника, вони ставлять дві різні версії, і більша перемагає всюди. До версії 5.1.0 обидва могли поставити одну версію, і кожен редактор зберігав ту, яку почув останньою, тож вони могли завершити по-різному. Два редактори мають спільний слот із імовірністю 1 до 4 194 304 на пару, і тоді версії збігаються, як і раніше.

Починаючи з версії 5.1.0, положення таблиці, положення й розмір нотатки чи групи таблиць, а також початок координат і рівень масштабу області перегляду версіонуються так само, а те, що їх рухає, надсилається як місце, де вони опиняються:

  • перетягування — як по одному table.moveTo, memo.moveTo чи tableGroup.moveTo на кожну переміщену сутність в одному пакеті, тож перетягування групи несе точки її таблиць разом із точкою групи;
  • відміна чи повторення перетягування — як точка, в яку воно ставить кожну сутність;
  • панорамування — як settings.scrollTo, а зміна масштабу — як settings.changeZoomLevel;
  • зміна розміру нотатки чи групи таблиць — як її положення й розмір разом.

Тож два користувачі, які в один і той самий момент переміщують або переміщують і змінюють розмір однієї таблиці, нотатки чи групи таблиць, отримують її в одній точці й з одним розміром у кожному редакторі й кожній репліці. Точку задає запис із більшою версією, обидві осі разом. Переміщення не записує розміру, тож зміна розміру зберігає свій розмір, навіть якщо переміщення, зроблене одночасно, забирає точку.
До версії 5.1.0 перетягування надсилалося як пройдена відстань, тож два перетягування однієї таблиці додавалися, а переміщення й зміна розміру однієї нотатки чи групи таблиць, зроблені одночасно, могли завершитися по-різному.
Перетягування менше ніж на 20 px, яке не залишає запису відміни, надсилається як і будь-яке інше, тож як точка воно може перекрити перетягування тієї самої сутності, яке в той самий момент зробив інший користувач. Пакет перетягування, панорамування чи зміни масштабу, який транспорт доставляє двічі, нічого не зсуває двічі.

Зміна кольору — виняток. Вона надсилається без версії, а кожен редактор і кожна репліка ставить власну й зберігає колір, який почула останнім, тож два користувачі, які в один і той самий момент змінюють колір однієї таблиці, нотатки чи групи таблиць, можуть завершити з різними кольорами.
Кілька змін не мають версії взагалі, тож кожен редактор і кожна репліка застосовує їх у порядку, в якому чує, і дві зміни, зроблені в один і той самий момент, теж можуть завершитися по-різному: переміщення стовпця в межах його таблиці чи індексу (column.move, indexColumn.move), а також зміна бази даних, рядка View Option чи параметрів Relationship DataType Sync, Maximum comment width або Column Order на вкладці налаштувань.
Сутності, які два користувачі додають в один і той самий момент, як-от дві таблиці чи два стовпці однієї таблиці, також можуть стояти в різному порядку в кожному редакторі й кожній репліці, і value кожного зберігає власний порядок.

Усе це стосується редакторів і реплік версії 5.1.0 або новішої; що змінює старіший редактор чи репліка, сказано в розділі Змішані випуски.

Змішані випуски​

Редактор застосовує лише ті типи дій, які знає його власний випуск, тож у сеансі зі змішаними випусками старіші редактори пропускають усе, що передається діями новішого типу.

Починаючи з версії 3.10.0, зміна ON DELETE чи ON UPDATE зв’язку передається як один із двох нових типів дій, relationship.changeOnDelete і relationship.changeOnUpdate, а дія relationship.add може містити обидва поля. Ретранслятору, який передає дії без змін, для них не потрібно нічого нового.
Редактор, випущений до версії 3.10.0, ігнорує ці типи дій і поля: він ніколи не дізнається ON DELETE чи ON UPDATE зв’язку, а документ, збережений у такому редакторі, не містить жодного з них. Зв’язок, який додає старіший редактор, надходить без них, тож і ON DELETE, і ON UPDATE спочатку мають значення Not set.

Усі інші зміни, які надсилає редактор версії 3.10.0, передаються типами дій, які старіші редактори вже знають.
Кнопка Replace All у пошуку й заміні надсилає свої зміни одним пакетом тих самих дій перейменування, коментаря й нотатки, які надсилає зміна, внесена вручну. Параметри відображення Alternate Key і Referential Actions передаються наявною дією settings.changeShow.

Починаючи з версії 4.0.0, такі зміни передаються новими типами дій, і ретранслятору, який передає дії без змін, для них не потрібно нічого нового:

  • додавання, переміщення, зміна розміру, перейменування, зміна кольору чи видалення групи таблиць: tableGroup.add, tableGroup.move, tableGroup.moveTo, tableGroup.resize, tableGroup.changeName, tableGroup.changeColor і tableGroup.remove; приєднання таблиці до групи чи її вихід із групи: table.changeGroup;
  • натискання Save у вікні зіставлення стовпців для наявного зв’язку, яке задає обидва його списки стовпців одночасно: relationship.changeColumns;
  • увімкнення чи вимкнення блокування: settings.changeLockSettings;
  • зміна скрипта Before tables чи After tables на вкладці схеми SQL: settings.changeDDLScript.

Параметри відображення Table Groups і Memos передаються наявною дією settings.changeShow.
Дія settings.changeIgnoreSaveSettings, яку надсилали перемикачі Save Scroll Information і Save Zoom Information, доки їх не замінили блокування, більше не надсилається, а дії settings.changeRelationshipOptimization більше немає. Редактор версії 4.0.0, який отримує будь-яку з них, нічого не змінює.

Редактор, випущений до версії 4.0.0, ігнорує нові типи дій: він ніколи не бачить ні груп таблиць, ні того, які таблиці до них входять, хоча перетягування групи все одно переміщує в ньому таблиці групи, бо воно також надсилає переміщення кожної з цих таблиць.
Після натискання Save у вікні зіставлення стовпців він зберігає старі стовпці зв’язку, а коли в ньому видаляють стовпець цього старого зіставлення, зв’язок зникає в усіх редакторах. Блокувань і скриптів у ньому немає.
Документ, збережений у такому редакторі, не містить нічого з цього, а редактор версії 4.0.0 відкриває його з усіма ввімкненими блокуваннями.
Тримайте кожен редактор у сеансі, а також будь-яке сховище replicationStore, що зберігає документ сеансу, на версії 4.0.0 або новішій.

Редактор версії 4.1.0 не надсилає жодного нового типу дій, тож редактори й репліки версій 4.0.0 і 4.1.0 працюють в одному сеансі, нічого не пропускаючи.
Його пошук і заміна охоплюють також назви індексів і назви груп таблиць, а кнопки Replace і Replace All перейменовують їх діями index.changeName і tableGroup.changeName, які редактор версії 4.0.0 уже застосовує.

Починаючи з версії 4.2.0, такі зміни передаються новими типами дій, і ретранслятору, який передає дії без змін, для них не потрібно нічого нового:

Блокування Name View передається наявною дією settings.changeLockSettings, параметр відображення Domain — дією settings.changeShow, а переміщення рядка Logical Name чи Domain у порядку стовпців — дією settings.changeColumnOrder.
Дії editor.clear і editor.loadJson імпорту схеми SQL, GraphQL, DBML чи AML через Import містять keepDictionaries: true, що зберігає слова, терміни й домени в кожному редакторі. Так само й ці дії виклику setSchemaSQL, setSchemaGraphQL, setSchemaDBML чи setSchemaAML у режимі заміни.
Фізична назва, яку формують словники, передається дією table.changeName чи column.changeName, а тип даних, який домен задає своїм стовпцям, — дією column.changeDataType, тобто тими самими діями, які надсилає зміна, внесена вручну.

Редактор, випущений до версії 4.2.0, ігнорує нові типи дій: у ньому немає ні логічних назв, ні прив’язок до доменів, ні словників, ні відображення назв. Блокування чи розблокування Name View нічого в ньому не змінює, як і переміщення рядка Logical Name чи Domain у порядку стовпців або переміщення іншого рядка на місце будь-якого з них. Не змінює нічого й блокування Canvas Type, яке ввімкнув редактор, що перейшов на вкладку налаштувань із вкладки словника: старіший редактор не знає цієї вкладки, тож залишає Canvas Type розблокованим. Він і далі показує фізичні назви, які формують словники, і типи даних, які задають домени.
Його власні зміни нічого не знають про логічні назви, домени чи словники, і ось як це впливає на новіші редактори:

  • Таблиця чи стовпець, які він перейменовує вручну, зберігають позначку сформованої назви, тож логічна назва, пізніше введена для них у новішому редакторі, може знову сформувати назву поверх тієї, яку він увів.
  • Тип даних, який він задає стовпцю, прив’язаному до домену, залишає прив’язку, тож аркуш Checks показує перевірку Domain type mismatch, а наступна зміна типу домену знову задає стовпцю тип домену.
  • Стовпці зовнішнього ключа зв’язку, який він додає, не отримують від свого ключа ні логічної назви, ні домену.
  • Таблиця, яку він вставляє чи дублює, надходить без логічних назв і прив’язок до доменів.
  • Імпорт схеми SQL, GraphQL, DBML чи AML у ньому не надсилає keepDictionaries, тож спорожнює слова, терміни й домени в кожному редакторі.
  • Коли він відміняє завантаження, імпорт або виклик clear(), він завантажує в кожен редактор власну копію документа, а в цій копії немає ні логічних назв, ні прив’язок до доменів, ні словників.
  • Ця копія, а також документ, який завантажує імпорт схеми SQL, GraphQL, DBML чи AML у ньому, повертають блокування Name View у кожному редакторі до стану, у якому його застав цей редактор. Після цього кожен редактор зберігає як відображення назв варіант Physical Names, а якщо блокування вимкнено, то й показує його.

Документ, збережений у такому редакторі, не містить ні логічних назв, ні прив’язок до доменів, ні словників, ні відображення назв, тож редактор версії 4.2.0 відкриває його у варіанті Physical Names. Утім, старіший редактор зберігає блокування Name View таким, яким його застав, тож документ, розблокований у редакторі версії 4.2.0, залишається розблокованим.
Тримайте кожен редактор у сеансі, а також будь-яке сховище replicationStore, що зберігає документ сеансу, на версії 4.2.0 або новішій.

Редактор версії 5.1.0 не надсилає жодного нового типу дій, тож редактори й репліки версій до 5.1.0 і далі працюють із ним в одному сеансі, а ретранслятору, який передає дії без змін, нічого нового не потрібно. Проте те, що описує розділ Одночасні зміни, діє лише серед редакторів і реплік версії 5.1.0:

  • Перетягування редактора версії 5.1.0 передаються як точки в table.moveTo, memo.moveTo і tableGroup.moveTo, які старіші редактори вже застосовують, а його панорамування й зміни масштабу — як точки в settings.scrollTo і settings.changeZoomLevel, які, як і кожне панорамування та зміну масштабу, не застосовує жоден інший редактор і беруть лише репліки. Старіший редактор застосовує кожну точку перетягування в порядку надходження, поза будь-яким регістром, а старіша репліка робить те саме з кожною точкою перетягування, панорамування чи зміни масштабу, тож дві точки, надіслані одночасно для однієї сутності чи для області перегляду, там можуть завершитися по-різному.
  • Перетягування старішого редактора і далі передається як пройдена відстань, яку кожен редактор і кожна репліка додає поза своїми регістрами, а його панорамування чи зміна масштабу — теж як відстань, яку так само додає кожна репліка. Відстань і точка для однієї сутності, зроблені одночасно, можуть завершитися по-різному в редакторах і репліках, які чують їх у різному порядку, а відстань, яку транспорт доставляє двічі, додається двічі.
  • За винятком тих, що називає розділ Одночасні зміни, запис назви чи поля редактором версії 5.1.0, зроблений в один і той самий момент із записом старішого редактора, майже завжди перемагає, і однаково в кожному редакторі й кожній репліці. Два старіші редактори все ж можуть завершити по-різному через одне поле, записане одночасно.
  • Редактор версії 5.1.0 надсилає зміну мови генератора коду, стилю назв чи типу лапок, лише поки її блокування вимкнено, з позначкою shared: true, яку старіший редактор ігнорує, застосовуючи зміну як раніше. Старіший редактор і далі надсилає зміну, зроблену ним під блокуванням, без позначки: редактор чи репліка версії 5.1.0 відкидає її, поки це налаштування там заблоковане, а старіші редактори застосовують. Тож під блокуванням старіші редактори стежать за виборами одне одного, а редактори версії 5.1.0 зберігають власні, і файл у будь-якому разі зберігає заблоковане значення. Розблокування, зроблене в будь-якому випуску, несе значення того, хто розблокував, до кожного редактора й кожної репліки.
  • Завантаження, що замінює документ, як-от присвоєне value, clear() чи імпорт із заміною, завершує малювання зв’язку в кожному редакторі версії 5.1.0, як у тому, що його зробив, так і в кожному, що його отримує. Старіший редактор зберігає своє малювання активним і після нього.

Тримайте кожен редактор у сеансі, а також будь-яке сховище replicationStore, що зберігає документ сеансу, на версії 5.1.0 або новішій, щоб зміни, зроблені в один і той самий момент, скрізь завершувалися однаково, за винятком тих, що називає розділ Одночасні зміни; версія 4.2.0 або новіша, як і раніше, потрібна, щоб зберегти логічні назви й словники. MCP-сервер версії до 1.2.0 тут вважається редактором версії до 5.1.0, а редактор версії до 5.1.0 узагалі не можна прив’язати до кімнати Yjs.

SharedStoreConfig​

getNickname​

Задає псевдонім, який показується поруч із курсором миші цього користувача в інших редакторах.
Якщо псевдонім відсутній або порожній, інші користувачі бачать user.

editor.getSharedStore({
getNickname: () => 'nickname...',
});

mouseTracker​

Транслює курсор миші цього користувача іншим редакторам. Типове значення — true.
Курсори, надіслані іншими користувачами, завжди показуються на полотні ERD.

editor.getSharedStore({ mouseTracker: false });

focusTracker​

Транслює те, з чим працює цей користувач. Типове значення — true.
Таблиця й клітинка, на яких у вас фокус, виділені вами таблиці, нотатки й групи таблиць, а також рамка, яку ви розтягуєте на полотні, надсилаються іншим редакторам і відображаються там.
Кожен користувач отримує власний колір, тож їх можна розрізнити: контур на таблиці у фокусі, підкреслення клітинки у фокусі, обвідка навколо виділених таблиць, нотаток і груп таблиць та штриховий прямокутник для рамки перетягування.
Починаючи з версії 4.2.0, користувачі можуть бачити різні варіанти відображення назв, поки його блокування увімкнено або після власного перемикання редактора в режимі лише для читання. Клітинка у фокусі, якої ваш варіант не показує, підкреслюється на клітинці, яку він показує на її місці: назва й логічна назва заміняють одна одну, так само як тип даних і домен.
Дані присутності, що надходять від інших користувачів, завжди відображаються на вашому полотні незалежно від того, чи ввімкнено цей параметр.
У режимі Flow на вкладці візуалізації фокус і виділення інших користувачів відображаються на картках таблиць, а їхні рамки перетягування — ні. Таблиці, які ви там виділяєте, і далі надсилаються; рамка, яку ви там розтягуєте, — ні.

editor.getSharedStore({ focusTracker: false });

Вимкніть обидва трекери, щоб використовувати спільне сховище як звичайний ретранслятор, який не публікує власних даних присутності.
Дані присутності, надіслані іншими редакторами, і далі отримуються й відображаються.
Саме так роблять редактори у VS Code, Obsidian, IDE від JetBrains і Google Drive.

editor.getSharedStore({ mouseTracker: false, focusTracker: false });

Курсор, фокус, виділення й рамка перетягування передаються тим самим потоком, що й зміни документа, але вони ефемерні.
Вони ніколи не потрапляють ні в документ, ні в історію змін, ні в editor.value, тож транспорт може спокійно їх відкидати, а replicationStore їх ігнорує.

SharedStore​

connection, disconnect​

Ці методи задають поточний стан з’єднання.
Поки з’єднання розірвано, зміни зберігаються у внутрішньому буфері.
Буферизовані зміни надсилаються підписникам, щойно передавання знову стає можливим.
Типовий стан — з’єднано (connection).

sharedStore.connection();
sharedStore.disconnect();

dispatch, dispatchSync​

Застосовує зміни з інших екземплярів редактора до поточного екземпляра редактора.
Коли пакет містить запит іншого редактора на регістри, як-от той, що надсилає перший виклик subscribe нового учасника, сховище відповідає через своїх підписників своїми регістрами та своєю версією, хіба що в нього немає жодних регістрів.
Починаючи з версії 5.1.0, спершу воно надсилає кожен пакет потоку, який ще притримує, як це робить flushStreamBuffers, щоб кожна версія у відповіді спиралася на запис, який уже надіслано іншим. Тому прихід нового учасника змушує кожен редактор у сеансі одразу надіслати перетягування, панорамування, зміну масштабу, зміну кольору чи зміну розміру нотатки, які він ще притримує.

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

subscribe​

Підписується на зміни в редакторі.
Доки немає хоча б одного підписника, ніщо не виходить за межі редактора, а зміни, внесені до того, буферизуються.
Перша підписка також запитує в інших редакторів їхні регістри співпраці, які узгоджують одночасні зміни, тож учасник, що приєднався пізніше, узгоджує їх так само.
Документа вона не передає, тож спершу ініціалізуйте учасника, що приєднався пізніше; починаючи з версії 5.1.0, ініціалізація може нести й регістри.
Починаючи з версії 4.0.0, сховище запитує їх знову щоразу, коли метод setInitialValue чи, починаючи з версії 5.1.0, метод reseed завантажує документ у його редактор після цієї першої підписки.

const unsubscribe = sharedStore.subscribe(actions => {
// send...
});

flushStreamBuffers​

Надсилає пакети потоків, які це сховище ще притримує, негайно, а не після їхнього періоду затишшя.
Починаючи з версії 3.9.2, метод працює в сховищі, отриманому з getSharedStore; у версії 3.9.1 він є в типі, але нічого не робить.
Переміщення таблиці, нотатки чи групи таблиць, зміна їхнього кольору, зміна розміру нотатки, а також прокручування чи масштабування області перегляду — кожне з них робиться як потік дрібних змін. Зміна розміру групи таблиць — ні: вона надсилається один раз, коли перетягування завершується.
Сховище збирає кожен потік в один пакет і притримує його, доки в потоці не настане затишшя тривалістю 200 мс, у два етапи. Зміна кольору чи зміна розміру нотатки проходить обидва, тож надсилається приблизно через 400 мс після останньої зміни, як і кожен потік до версії 5.1.0. Починаючи з версії 5.1.0, перетягування, панорамування чи зміна масштабу надсилається після першого етапу, приблизно через 200 мс після останнього кроку, як місце, де воно завершується (див. Одночасні зміни).
Виклик завершує це очікування на обох етапах. Поки сховище з’єднане, підписник отримує пакет ще до того, як виклик поверне керування. Якщо нічого не притримано, виклик нічого не надсилає.
Метод destroy, як сховища, так і редактора, відкидає пакет, який ще притримується, тож спершу викличте цей метод, коли відмовляєтеся від сховища, а інші редактори мають зберегти останню зміну, як-от під час закриття вкладки.

sharedStore.flushStreamBuffers();
sharedStore.destroy();

getLWW​

Починаючи з версії 5.1.0, повертає копію регістрів співпраці редактора (див. Одночасні зміни).
Копію зроблено через JSON, тож це простий об’єкт, готовий до JSON.stringify, а зміна в ньому залишає регістри сховища такими, як були.
Передайте його без змін до mergeLWW чи reseed іншого сховища. Його структура належить самому редакторові, тож не читайте жодного його поля.
Читайте його разом із getVersion і runtimeValue редактора в один і той самий момент, щоб ініціалізувати нового учасника.

const lww = sharedStore.getLWW();

mergeLWW​

Починаючи з версії 5.1.0, бере регістри, видані іншим сховищем через getLWW, у регістри цього сховища, зберігаючи більшу версію кожного, а version бере в годинник редактора, який зберігає більшу з власної та переданої.
Він нічого не змінює в документі: жодного байта value чи runtimeValue, жодного запису відміни й жодної події change. Він лише підвищує версії, які мусить перевершити пізніший запис, тож запис, старіший за передані регістри, потім відхиляється, а наступна зміна редактора несе версію, більшу за version.
Він нічого не надсилає й ні на що не відповідає. Це те, що перша підписка нового учасника отримує з відповіді інших, прийняте безпосередньо.
Викликайте його після setInitialValue, ніколи до: кожен setInitialValue і кожен reseed відкидає збережені регістри, а разом із ними й те, що взяв mergeLWW перед ним.

sharedStore.mergeLWW(lww, version);

getVersion​

Починаючи з версії 5.1.0, повертає найвищу версію, яку злив годинник редактора: найбільшу з версій змін, які він зробив або прийняв, запитів на регістри й відповідей, якими обмінюється приєднання, а також кожної version, переданої в mergeLWW чи reseed.
Курсори, фокус, виділення й рамки перетягування — його власні чи іншого редактора — її не зсувають.
Передайте її разом із getLWW(), прочитаним в один і той самий момент, у mergeLWW чи reseed нового учасника. Це версія, така сама, як ті, що несуть його дії.

const version = sharedStore.getVersion();

forgetPeer​

Починаючи з версії 5.1.0, негайно прибирає курсор, фокус, виділення й рамку перетягування, надіслані редактором із наведеним id, а також очікування закінчення терміну кожного з них. Надіслане всіма іншими редакторами лишається.
Id — це meta.editorId, яке несе кожна дія, що її надсилає спільне сховище цього редактора. Id, для якого цей редактор нічого не тримає, нічого не змінює.
Викликайте його, коли транспорт дізнається, що учасник пішов. Інакше те, що надіслав учасник, лишається намальованим, доки не мине термін: курсор — через 30 секунд після останнього отриманого, фокус і виділення — через 90 секунд, а рамка перетягування — через 3 секунди.

sharedStore.forgetPeer(editorId);

reseed​

Починаючи з версії 5.1.0, знову відкриває в редакторі документ сеансу, як це робить повторне приєднання. Прив’язка Yjs викликає його, щоб відкрити документ кімнати, коли прив’язується до кімнати, яку вже ініціалізовано, і знову за кожного повторного приєднання, залишаючи екран як є, якщо вже приєдналася до кімнати.
Він завантажує value так, як це робить setInitialValue, і бере lww та version так, як це робить mergeLWW, тож сховище тоді містить саме передані регістри, а його годинник — більшу з власної версії та version. Він також спорожнює історію відміни і, на відміну від setInitialValue, відкидає виділення й фокус, бо вони можуть називати те, чого значення вже не містить.
Окрім наведеного нижче формування фізичної назви з логічної, він не створює запису відміни, не генерує події change і не надсилає жодної зміни. Якщо сховище вже мало підписника, воно надсилає запит на регістри, який надсилає кожне таке завантаження і який транспорт, що вже передав регістри, може відкинути, а за ввімкненого focusTracker — очищені виділення й фокус.
Як і setInitialValue, він працює, коли задано readonly, відкидає подання режиму Flow, завершує малювання зв’язку та закриває редактори нотатки, назви групи таблиць і клітинки, зберігаючи чи відкидаючи введене в них так само; назва, яку закриття клітинки логічної назви формує з того, що ввів її читач, — це звичайна зміна: вона надсилається, її можна відмінити, і вона генерує подію change.

Без keepScreen екран показує те, що містить значення, як після setInitialValue. З keepScreen: true екран читача накладається назад на завантажений документ, лише на цьому екрані:

  • положення прокручування й рівень масштабу, якщо елемент їх уже показував; а якщо елемент їх ніколи не показував або був завантажений прихованим і відтоді не показувався, вони відкриваються там, де їх ставить значення, як у пізнього учасника;
  • вкладка, якщо завантаження її зсунуло, а на вкладці налаштувань — ще й вкладка, з якої прийшов читач, яку називає блокування Canvas Type, ввімкнене там;
  • відображення назв і кожне налаштування генератора коду, для яких читач показував власне значення під їхнім блокуванням, але лише якщо це блокування було ввімкнено до завантаження, лишилося ввімкненим після нього й містить те саме заблоковане значення. Тож коли учасник тим часом розблокував, змінив і знову заблокував налаштування з іншим значенням, редактор відкривається з новим заблокованим значенням, а там, де блокування вимкнено до завантаження або після нього, він показує завантажене значення;
  • кожне налаштування, яке читач у режимі лише для читання змінив на власному екрані, незалежно від блокувань.

Нічого з цього не надсилається, а value і runtimeValue і далі повідомляють про завантажений документ. Пізніше розблокування, зроблене учасником, замінює налаштування коду, збережене таким чином, як і будь-яке розблокування.
Наступне прокручування чи зміна масштабу читача, зроблені, поки редактор може редагувати, знову роблять збережене положення прокручування й рівень масштабу частиною документа і надсилаються, виходячи з того, де стоїть екран, тож репліки теж беруть цей масштаб.

Повторна ініціалізація залишає те, що притримує сховище, як є. Перетягування, панорамування чи зміна масштабу, які ще притримуються, надсилаються після неї, прочитані з документа після повторної ініціалізації; зміна кольору чи зміна розміру нотатки надсилається такою, якою була зроблена, хоч редактор її вже не показує; а те, що притримував disconnect(), надсилається після connection().
Тож спершу викличте flushStreamBuffers, надішліть те, що він генерує, і повторно ініціалізуйте зі значенням, що це містить. Ніколи не викликайте reseed, поки з’єднання розірвано.

sharedStore.reseed(seed.value, {
lww: seed.lww,
version: seed.version,
keepScreen: true,
});

destroy​

Повністю знищує екземпляр sharedStore.
Сам редактор лишається недоторканим, тож після виходу із сеансу документ можна й далі редагувати.
Трекери миші й фокусу зупиняються, щойно знищено останній екземпляр sharedStore, виданий редактором.
Пакет потоку, який сховище ще притримує, відкидається разом із ним; щоб надіслати його, спершу викличте flushStreamBuffers.
Курсори, фокус, виділення й рамки перетягування, надіслані іншими редакторами, лишаються намальованими, доки не мине їхній термін; починаючи з версії 5.1.0, forgetPeer прибирає їх для одного редактора негайно.

sharedStore.destroy();