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

Yjs

@dineug/erd-editor-yjs синхронізує діаграми через документ Yjs. Це окремий пакет, перший випуск якого, 0.1.0, вийшов разом із @dineug/erd-editor версії 5.1.0.
bindErdEditor прив’язує елемент <erd-editor>, а bindPeerStore — headless-сховище учасника з @dineug/erd-editor/peer.js, до документа Y.Doc.
Діаграму несе той провайдер, що несе Y.Doc: сервер websocket, WebRTC, IndexedDB або ваш власний. Провайдер і Y.Doc залишаються вашими: прив’язка їх ніколи не підключає, не відключає й не знищує.

Зміни передаються як власні пакети дій редактора, які прив’язка зберігає такими, якими їх надіслало сховище.
Тому регістри співпраці редактора узгоджують зміни, зроблені в один і той самий момент, так само, як у сеансі, підключеному через getSharedStore, і з тими самими винятками, як-от зміна кольору — див. Одночасні зміни.
Курсори, фокус, виділення й рамки перетягування передаються через Awareness провайдера — див. Присутність.
Порівняно з ручним підключенням спільного сховища до транспорту прив’язка додає таке:

  • Клієнт, що приєднується, відкриває діаграму, яку тримає кімната, без runtimeValue, переданого із запущеного редактора.
  • Клієнт, відрізаний від мережі, продовжує редагувати, а його зміни потрапляють у кімнату після повторного підключення — див. Офлайн-зміни й повторні приєднання.
  • Y.Doc, який повертає провайдер постійного зберігання, відкриває діаграму, яку містить, — див. Що зберігає кімната.
  • Необов’язковий допоміжний процес Node (sidecar) згортає історію кімнати в один знімок — див. Ущільнення кімнати.

Прив’язка потребує п’яти методів, які версія 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 є peer-залежностями, тож сторінка містить по одній копії кожного: @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 викидає TypeError, а не генерує error.

Зміни кімнати застосовуються до елемента й генерують 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 у мікрозавданні, тож слухач, доданий одразу після виклику, його почує, а 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​

Надсилає в кімнату перетягування, зміни розміру й зміни кольору, які редактор ще утримує, встановлює поле erd Awareness прив’язки в 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'), надіславши перетягування, зміни розміру й зміни кольору, які його редактор ще утримував. Згортання лише того, що клієнт уже застосував, нічого не відкриває знову.
Повторне приєднання до знімка ініціалізації, до якої приєдналася прив’язка, відтворює кожну зміну, якої знімок не містить, зокрема й зміни самого цього клієнта, зроблені, поки його не було, тож жодна не втрачається. Повторне приєднання до іншої ініціалізації не зберігає нічого, як описано в розділі Ініціалізація кімнати.

Кожне повторне приєднання очищає історію відміни, виділення й фокус і закриває відкрите подання режиму Flow, малювання зв’язку, а також редактор нотатки, назви групи таблиць чи клітинки, бо вони можуть називати те, чого знімок уже не містить.
Назва групи таблиць, яку введено, але ще не підтверджено, відкидається. Клітинка логічної назви, що закривається, формує фізичну назву з логічної назви, яку ввів її читач, якщо відкритий знову документ ще її містить, як і будь-яке закриття цієї клітинки. Це формування є звичайною зміною: кімната записує її в журнал, вона створює запис відміни й генерує change — див. reseed.
Прив’язка, що вже приєдналася до кімнати, зберігає те, що бачить її читач: положення прокручування, рівень масштабу й вкладку, а також налаштування, які змінив читач у режимі лише для читання.
Вона також зберігає відображення назв і налаштування коду (мову генератора коду, стиль назв і тип лапок), які читач показав сам, за умови, що блокування кожного з них було ввімкнено до повторного приєднання й залишається ввімкненим після нього з тим самим заблокованим значенням.
Нічого з цього збереженого не потрапляє у value, яке повідомляє налаштування відкритого знову документа — див. reseed з keepScreen.
Положення прокручування й рівень масштабу елемента, який не показувався від останнього завантаження, а також увесь екран прив’язки, що ще не приєдналася, відкриваються там, де їх розміщує знімок.
Сховище учасника не має екрана, тож його повторне приєднання нічого не зберігає.

Редактори лише для читання​

Елемент лише для читання, прив’язаний до кімнати, нічого не записує в історію кімнати: його спільне сховище не надсилає змін, лише свої трекери та обмін регістрами, а кімната не записує в журнал ні те, ні інше.
Він усе одно застосовує кожну зміну, яку несе кімната, а з awareness курсор, фокус, виділення й рамка перетягування його читача й далі доходять до інших.
Налаштування, які змінює його читач, залишаються на його екрані, як у будь-якому редакторі лише для читання.
seed не відхиляється: елемент лише для читання ініціалізує порожню кімнату документом, який тримає, або value, якщо його задано.

Що зберігає кімната​

Прив’язка зберігає діаграму під двома іменами Y.Doc: erd:snapshot — мапа, що містить знімок документа, і 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 прив’язує headless-сховище учасника з @dineug/erd-editor/peer.js і повертає ErdYjsPeerBinding: ті самі seed, on і destroy, а також compact.
Сховище учасника, прив’язане в процесі Node поруч із сервером, — допоміжний процес — згортає історію в знімок власної діаграми.
Прив’язка елемента не згортає ніколи, а сховище учасника, прив’язане без compactAfter і без виклику compact(), є звичайним headless-учасником.

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НемаєПоказує сховище учасника в Awareness кімнати, як awareness показує елемент. Ущільнювач, що згортає самостійно, позначається там, тож ущільнювачі, які бачать один одного, обирають одного для згортання, а той, що не бачить жодного іншого клієнта, самостійно нічого не згортає — див. Один ущільнювач на кімнату. Без нього ущільнювач згортає самостійно, без виборів і без пільгового періоду.
compactAfterНемаєЗгортати самостійно, щойно журнал міститиме вказану кількість записів, які забрало б згортання. Без нього або без synced згортає лише compact(), а також згортання, що повертає записи (див. Один ущільнювач на кімнату).
syncedНемаєПроміс, що виконується, коли провайдер синхронізувався. Жодне згортання не відбувається до цього, зокрема й compact(). Без нього самостійно нічого не згортається, а compact() згортає одразу.
electionGraceMs3000З awareness — як довго ущільнювач стежить за кімнатою, перш ніж згорнути самостійно, після synced і знову щоразу, коли з’являються інші клієнти після того, як не було жодного.
retainMs600000 (десять хвилин)Як довго записи згортання залишаються утримуваними, щоб їх повернути (див. Один ущільнювач на кімнату), з відліком від першого оновлення, яке кімната надсилає після згортання.

Недійсний параметр або 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, і щонайбільше його останні 1000 записів, якого б розміру кожен не був.
Тож нічого не втрачається, коли ущільнювач, чий знімок програв, — той, що повторно підключається, якщо за час розриву він згорнув не більше 1000 записів.
Коли це той, що залишався онлайн, а його утримання закінчилося, знімок та історія кімнати не містять того, що він згорнув, доки він не згорне знову. Клієнт, який пропустив ці зміни, або той, що тим часом приєднується, їх не має й утримує пізніші зміни кожного автора, чиї зміни згорнув ущільнювач. Наступне згортання ущільнювача повертає їх усі, якщо тільки переможний знімок не містив змін, яких йому бракувало, — тоді він знову відкривається на тому знімку й остаточно втрачає те, що згорнув.

Ущільнювач повертає записи у власному згортанні, зробленому, коли він наздогнав переможний знімок, без виборів, пільгового періоду чи порогу, і кожен клієнт знову відкривається на цьому згортанні з 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.