Middleware и Events

Расширяйте Transport, Endpoint, Service и модули совместной работы универсальными Middleware и Events.

Middleware и Events — общие точки расширения Collaboration SDK. Идентификация и авторизация подключаются через Middleware. Проверки и ограничения до операции выполняйте в Middleware, запись изменений и последующие действия после подтверждения состояния — через Events. Оба механизма поддерживают журналирование, трассировку, метрики и обновление производного состояния.

Два момента расширения

MiddlewareEvent
ВыполнениеВо время операцииПосле подтверждения изменения состояния
Участие в операции или её отклонениеДаНет
Дополнение Context вызоваДаЧитает Context и результат из Event
Ошибка обработчикаМожет завершить текущую операцию ошибкойИзолирована от уже определённого доменного результата
Типичные задачиИдентификация, авторизация, проверка, логи, замер времениПроизводные индексы, кеши, метрики, уведомления внутри процесса

Используйте Middleware, чтобы решить, можно ли продолжить операцию, и Event, чтобы наблюдать уже произошедшее изменение.

Модель выполнения Middleware

Middleware одной Action образуют асинхронную цепочку в порядке регистрации. await next() входит в следующий Middleware; операция SDK выполняется в самой глубокой точке:

TypeScript
service.use("submitChangeset", async (context, next) => {  const startedAt = performance.now();  context.customData.startedAt = startedAt;  try {    await next();  } finally {    metrics.observe("collaboration.submit.duration", performance.now() - startedAt);  }});

Middleware может:

  • читать запрос конкретной Action;
  • делиться локальными данными вызова через customData;
  • проверять вход или опрашивать системы приложения до next();
  • записывать длительность и результаты после возврата из next();
  • выбрасывать CollabError для ожидаемого отказа в совместной операции.

HTTP Middleware Transport может закончить ответ без next(). Middleware Service и Endpoint должны отклонять операцию явной ошибкой, а не молча пропускать next().

Middleware Transport

transport.use() регистрирует Middleware для обычных HTTP-запросов; они выполняются в порядке регистрации. Вызовите await next(), чтобы продолжить обработку, или завершите ответ напрямую. transport.useUpgrade() обрабатывает рукопожатия WebSocket.

Например, сначала измерьте длительность HTTP-запроса, затем выполните аутентификацию и задайте доверенный userID, после чего зарегистрируйте Collaboration Endpoint:

TypeScript
transport.use(async (context, next) => {  const start = performance.now();  context.response.once("finish", () => {    recordHttpDuration({      method: context.incomingMessage.method,      route: context.route?.path ?? "unmatched",      status: context.response.statusCode,      durationMs: performance.now() - start,    });  });  await next();});transport.use(async (context, next) => {  const user = await authenticate(context.incomingMessage);  if (!user) {    context.response.statusCode = 401;    context.response.end("Authentication required");    return;  }  context.userID = user.userId;  await next();});transport.register(collaborationEndpoint);

Приложение предоставляет recordHttpDuration и authenticate. Endpoint использует userID, заданный предыдущими Middleware. Middleware наблюдения регистрируется до аутентификации, поэтому отказы также учитываются.

context.route?.path — полный шаблон совпавшего маршрута, доступный, когда выполнение достигает Endpoint. Событие finish ответа позволяет измерить время до его завершения; отказы до сопоставления маршрута и неизвестные пути записываются как unmatched.

Middleware Endpoint

Участвуют в операциях Session реального времени и Presence:

ActionМоментТипичные задачи
connectПосле погашения билета и создания Session, до HELLOПолитика подключения, имя и аватар участника
joinUnitДо входа Session в Room UnitДопуск в Room и видимость Unit
receivePresenceПосле получения Presence от вошедшей Session, до рассылкиПроверка и фильтрация Presence
sendPresenceДо отправки Presence каждому участникуФильтрация по получателю
TypeScript
endpoint.use("receivePresence", async (context, next) => {  if (JSON.stringify(context.payload).length > 8_000) {    throw new CollabError("INVALID_REQUEST", "Presence payload is too large");  }  await next();});

session.userID и session.customData Endpoint приходят из HTTP-запроса, выдавшего Session Ticket. Endpoint создаёт memberID для текущей Session WebSocket; после переподключения он меняется.

Middleware Service

Участвуют в жизненном цикле эталонных данных совместной работы:

ActionГраницаТипичные задачи
readUnitDataДо чтения снимков, блоков или измененийПолитика чтения, проверки арендатора, аудит
createUnitДо атомарного создания UnitПроверка типов, политика создания, бизнес-правила
deleteUnitsДо записи пакета удаленияПолитика удаления и проверки каждой Unit
recoverUnitsДо записи пакета восстановленияПолитика восстановления и аудит
submitChangesetДо идемпотентности и OT логической отправкиПолитика редактирования, общий размер, трассировка
applyChangesetДо применения преобразованных изменений к состоянию-кандидатуПроверка на уровне мутаций
commitChangesetДо CAS ревизии и коммита AdapterФинальные правила ревизии и метрики
TypeScript
service.use("createUnit", async (context, next) => {  await creationPolicy.assertAllowed({    userID: context.userID,    unitID: context.request.snapshot.unitID,    type: context.request.snapshot.type,  });  context.customData.creationPolicyChecked = true;  await next();});

submitChangeset выполняется один раз для логической отправки. Конкуренция ревизий может повторно запустить applyChangeset и commitChangeset с новым attempt. Эти Middleware должны быть безопасны при повторе и не должны напрямую отправлять необратимые внешние сообщения или выполнять однократные списания средств.

Область customData

customData — доступный приложению для записи объект текущего вызова, подходящий для:

  • ID арендатора, трассировки и логгеров запроса;
  • повторно используемых результатов ACL в вызове;
  • начала замера времени и меток аудита;
  • бизнес-объектов одной цепочки Middleware.

Обычные HTTP-запросы, вызовы Service и Sessions WebSocket имеют разные жизненные циклы. customData не сохраняется, не отправляется браузеру и не журналируется автоматически. Долговременные данные остаются в хранилище приложения.

Модель выполнения Events

Services регистрируют локальные для процесса обработчики через on(), возвращающий освобождаемую подписку:

TypeScript
const subscription = service.on("changesetCommitted", async (event) => {  metrics.increment("collaboration.changeset.committed", {    unitType: String(event.changeset.type),  });  localCache.delete(event.changeset.unitID);});subscription.dispose();

Обработчики одного Event выполняются последовательно в порядке регистрации с ожиданием завершения. Ошибки записываются и изолируются от уже определённого доменного результата. Например, сбой обработчика changesetCommitted не откатывает изменения, уже сохранённые Adapter.

Events подходят для локальных, восстанавливаемых или некритичных последующих действий. Они не разделяют транзакционную границу коммита базы и не гарантируют доставку внешней системе.

Events основного Service

EventПодтверждённый фактТипичное применение
unitCreatedUnit и начальные данные созданыЛокальный кеш каталога или производный индекс
changesetCommittedИзменения подтверждены, ревизия продвинутаРассылка, метрики, производные индексы
unitsDeletedAdapter подтвердил пакет удаленияОчистка связанного состояния процесса
unitsRecoveredAdapter подтвердил пакет восстановленияВосстановление производного состояния процесса

Endpoint слушает changesetCommitted того же Service и рассылает подтверждённые изменения клиентам, вошедшим в Unit в текущем процессе, а при настроенном pub/sub-адаптере — на всех узлах. Он также предоставляет memberLeftUnit для явного выхода, разрыва соединения и освобождения Endpoint.

Независимые точки расширения модулей

History, Thread Comment и Worktree не наследуют Middleware и Events основного Collaboration Service:

  • History предоставляет Middleware чтения для getHistoryList, listHistoryCreators и getHistoryChangesets;
  • Comment — добавления, ответа, правки, удаления, решения и списка, а также commentCommitted;
  • Worktree — жизненного цикла и read/submit/commit черновиков, а также Events создания, статуса, слияния и коммита черновика.

Устанавливайте правила отдельно для каждого включённого модуля. Логи, проверки арендатора и авторизация Middleware ядра не распространяются на них автоматически.

Продолжите с Идентификацией и авторизацией: там показано сопоставление существующих пользователей и ACL этим общим границам.

Насколько полезно это руководство?

© 2026 DreamNum Co., Ltd.