Перейти к основному содержимому

Clepit

Кратко

Категория
Платформа для разработчиков
Сайт
clepit.com
Консоль
app.clepit.com
Документация
clepit.com/en/docs
GraphQL API
api.clepit.com/graphql
Реальное время
ws.clepit.com
Интерфейс MCP
api.clepit.com/mcp
Опубликованные страницы
clepit.space

Большая часть форматированного текста попадает в базу данных одним куском HTML. Этого достаточно, пока вы не захотите сделать с ним что-то помимо показа: найти все страницы, где упомянут определённый клиент, вывести один и тот же документ в виде веб-страницы, письма и экрана мобильного приложения или дать двоим редактировать его одновременно так, чтобы никто не потерял абзац. К этому моменту слова и их оформление уже перепутаны, и единственное, что способно надёжно прочитать документ, это редактор, который его написал.

Clepit разделяет эти две вещи. Страница есть список блоков, каждый из которых небольшой типизированный объект, а сам документ есть JSON: никакой разметки, которую надо разбирать, и никакого редактора, чтобы его прочесть. По той же причине редактор существует и сам по себе. @clepit/core опубликован в npm под лицензией MIT и ничего не знает о размещённой части, тогда как рабочее пространство по адресу app.clepit.com есть то, чем становятся те же документы, когда у них появляются соавторы, права доступа, история версий и адрес в открытой сети.

Страница есть список блоков

Блок состоит из четырёх полей: идентификатора, типа, данных, которые этот тип определяет, и применённых к нему настроек. Данные абзаца имеют иную форму, нежели данные таблицы, и именно тип говорит, какой формы ждать, так что сохранённый документ можно проверить, а не просто разобрать. Целая страница есть отметка времени, версия и блоки по порядку, и этого достаточно мало, чтобы прочитать глазами и просмотреть в pull request. Ничто в ней не описывает, как страница должна выглядеть: это дело того, кто её рисует, и именно поэтому один документ становится веб-страницей, письмом и экраном телефона, не существуя в трёх копиях.

Как нарисовать документ без браузера

Отрисовщик никогда не пишет прямо в браузер. Он рисует через тонкий слой, за которым стоят две основы: одна собирает настоящие узлы страницы, другая собирает строку, и один и тот же код блока работает на обеих. Именно так опубликованная страница рисуется на сервере, где браузера нет вовсе, и именно поэтому то, что выдаёт сервер, есть тот же документ, который показал бы редактор, а не вторая реализация, которую оставили расходиться с первой. Строковая основа принимает очиститель обязательным аргументом и намеренно не имеет значения по умолчанию: обычный очиститель пакета сначала собирает страницу, чтобы разобрать её, а значит там работать не может, а молчаливый откат к экранированию снял бы с каждого документа внутреннее оформление, ничего об этом не сказав. Поэтому пробел оставлен на виду в месте использования, а не спрятан в значении по умолчанию.

Чтение не стоит читателю ни одного JavaScript

Адаптер для React поставляет свои две половины по отдельности, потому что им нужно противоположное. Компонент содержимого работает на сервере и выдаёт готовую разметку прямо во время сборки страницы, так что читатель получает документ уже в первом ответе. Компонент редактора существует только на клиенте, поскольку он ведёт жизненный цикл редактора, а вести его не над чем, пока нет браузера. Поэтому чтение документа Clepit не требует никакого JavaScript. Среда выполнения появляется тогда, когда начинают писать.

Что может вместить страница

В пакете идёт двадцать шесть видов блоков. Большинство из них нужны любому редактору: заголовки, абзацы, списки, списки задач, цитаты, код, таблицы, изображения, звук, видео, файлы, врезки и разделители. Остальные существуют потому, что документация просит вещей, которые инструмент для письма обычно оставляет без внимания. Оглавление, которое само собирается из заголовков документа и ведёт к каждому из них. Сворачиваемые разделы и колонки. Карточка, стоящая вместо другой страницы. Набросок от руки. И блок активности, который хранит, за каким документом следить, а не копию его активности, и потому продолжает показывать происходящее сейчас, а не застывает на дате, когда его вставили. Оформление внутри блока охватывает обычные знаки: полужирный, курсив, подчёркивание, зачёркивание, код в строке, выделение и ссылки, а также подсказки, метки состояния и упоминания. У самого пакета нет никаких зависимостей времени выполнения.

Формулы и схемы, нарисованные внутри пакета

Два из этих блоков отображают LaTeX и Mermaid, и оба делают всю работу внутри пакета: разбирают исходный текст, вычисляют вёрстку, рисуют результат. Под ними нет никакой графической библиотеки и нет обращения к службе, которая превратила бы формулу или блок-схему в картинку. Это не столько предпочтение насчёт зависимостей, сколько следствие строковой основы. Блок, который потянулся бы к тому, что даёт только браузер, или к сети, не смог бы нарисоваться на сервере, отдающем опубликованные страницы, и тогда одна и та же страница выглядела бы по-разному в зависимости от того, кто её запросил.

Справочник по API, живущий прямо на странице

Дайте блоку OpenAPI спецификацию, вставленную целиком или указанную адресом, и он нарисует то, что она описывает: операции, их пути и параметры, схемы запроса и ответа, а также способ проверки подлинности. Для каждой операции он ещё и строит пример запроса на cURL, TypeScript, Dart и Python, порождённый из спецификации, а не набранный автором, который забудет его обновить. Блок встраивания смотрит на внешний мир так же: он узнаёт горстку служб, которые действительно способен показать, для всего остального считает простую ссылку полноценным исходом, а не неудачей, и наотрез отказывается рисовать адрес, которому не доверяет.

Двое в одном абзаце

Страницу, которую правят прямо сейчас, держит одна задача на сервере, по одной на страницу, и каждое изменение проходит через неё по очереди. Именно это позволяет рассуждать об одновременном редактировании: нет второго пишущего, который состязался бы с первым. Сам документ есть CRDT, поэтому двое, печатающие в одном абзаце, сливаются, а не затирают друг друга, а отставший клиент нагоняет, обменявшись тем, чего недостаёт каждой стороне. Каждое изменение дописывается в журнал упреждающей записи прежде, чем будет разослано хоть кому-то, так что увиденное остальными в документе уже надёжно записано, а не просто передано дальше. Права проверяет сервер, а не интерфейс: участник, вошедший без права правки, понижается до чтения, и сеанс периодически перепроверяет это право, пока документ открыт, так что отнятый доступ настигает того, кто уже печатает, а не ждёт, пока он перезагрузит страницу.

Всякая запись проходит через одну дверь

Документ может изменить и человек, печатающий в нём, и программа, вызывающая API, и прежде эти два пути могли писать в одну страницу независимо друг от друга. Больше не могут. Запись через API передаётся в тот же сеанс, который держит живой документ, и применяется там одной транзакцией наравне с текущими правками, так что порядок событий один, а не два пишущих с разными мнениями о том, что на странице написано. Идентификаторы блоков сохраняются при обратной записи документа, потому что комментарии закреплены за ними, и сверка, породившая бы новые идентификаторы, оставила бы каждый комментарий указывающим в пустоту. А если получившийся набор блоков совпадает с уже сохранённым, не пишется ничего.

Единственный канал, куда не дотянется машинный ключ

Личный ключ API работает с REST, GraphQL, сокетом подписок GraphQL и MCP. С сокетом совместного редактирования он не работает, и это сделано намеренно. На каждом совместном изменении ставится отметка того, кто его внёс, и эти отметки становятся авторством, записанным в историю страницы. Машинный субъект, правящий там, вписал бы автора, которого не писал ни один человек, а отменить это позже означает переписать историю, а не удалить строку. Граница проходит не между websocket и HTTP: она проходит по тому, пишет ли канал историю с авторством. Правило держит форма кода, а не память, потому что принять ключ можно лишь намеренно перейдя на другой вызов проверки подлинности, и когда канал так делает, падает тест.

Рабочее пространство по собственному адресу

Каждое рабочее пространство есть арендатор со своим поддоменом с момента создания, и арендатор определяется по адресу, на который пришёл запрос. То, в каком арендаторе вы находитесь, решено ещё до того, как прочитан хоть один ваш байт, а не фильтром, приделанным потом, о котором кто-нибудь может забыть. Ниже границу держит сама база данных через защиту на уровне строк: каждый запрос берёт соединение, ставит на нём отметку с личностью вызывающего, а пул стирает это состояние при возврате соединения, так что личность одного запроса не просочится в запросы следующего.

Заявить домен не значит его доказать

Рабочее пространство на корпоративном тарифе может отдавать свои страницы с собственного домена. Заявить домен и отдавать с него намеренно разведены на два шага: домен сохраняется неподтверждённым, и распознаватель полностью его игнорирует, пока в DNS не появится подтверждающая запись. Вписать в форму адрес чужой компании может кто угодно. Опубликовать запись, которая делает его действующим, может только тот, кто этим доменом распоряжается.

Каждая версия, что была у страницы

Clepit хранит версии, а не единственное текущее состояние. Снимок делается сам собой, пока люди работают, но с придержкой, чтобы обычный набор текста не породил их сотни: новый пишется, когда прошло десять минут или изменилось десять блоков, смотря что случится раньше. Восстановление есть одна транзакция: применяется старый снимок, блоки сверяются, и само восстановление записывается новой версией, так что возврат назад остаётся в записи, а не отменяется молча. Сверка намеренно сохраняет идентификаторы блоков источника, потому что комментарии закреплены за блоками, и восстановление страницы со свежими идентификаторами оставило бы каждый комментарий на ней без опоры.

Как найти это снова

Поиск идёт по проекции страниц, а запрос проходит через собственный веб-поисковый разборщик Postgres, а не собирается в SQL вручную, так что человек может печатать кавычки и минусы, и ничто из этого не становится поверхностью для внедрения. Но в общем рабочем пространстве важно другое: где стоит проверка прав. Поиск соединяется с таблицей страниц, и защита на уровне строк этой таблицы действует на само соединение, так что результаты уже ограничены страницами, которые спрашивающий вправе видеть. Фильтры (ветвь дерева страниц, кто правил последним, когда правили в последний раз) ложатся сверху дополнительными условиями. Каждый из них сужает; расширить не может ни один, потому что все они стоят за той же самой проверкой.

Публикация замораживает, а поделиться нет

Это две разные вещи, и Clepit намеренно обходится с ними по-разному. Публикация страницы замораживает нынешний документ как версию, направляет страницу на неё и делает её общедоступной: то, что посетитель читает на clepit.space, по адресу вида acme.clepit.space/handbook, есть та самая замороженная версия, а не правки, сделанные после. Снятие с публикации очищает эти указатели, но сохраняет общедоступный адрес, так что повторная публикация позже возвращается к тому же URL, а не ломает каждую ссылку, что вела туда. Ссылка для доступа устроена наоборот: она отдаёт живой документ, так что увиденное получателем меняется вместе со страницей.

Ссылка, которую можно забрать назад

Ссылка для доступа есть отзываемый маркер, и при выдаче ему можно назначить срок. Хранится только хеш маркера, поэтому ссылка показывается один раз при создании и потом не восстанавливается из базы данных ни нами, ни тем, кто до неё доберётся. Такие ссылки дают чтение, а не комментирование: у комментария должен быть автор, а держатель ссылки им не является.

Вход из вашего собственного каталога

Рабочее пространство может передать проверку подлинности своему поставщику удостоверений: OpenID Connect на тарифе Business, SAML на корпоративном, а рядом SCIM для сверки с каталогом. SCIM покрывает тот пользовательский ресурс, которым Okta и Entra действительно управляют, и в одном месте намеренно отходит от очевидного прочтения стандарта: удаление отключает участника, а не стирает его. Спецификация это позволяет, а иначе получилась бы сверка каталога, способная уничтожить содержимое рабочего пространства из-за того, что кого-то вывели из группы.

Четыре способа с ним говорить

REST покрывает сорок четыре операции под /v1, описанные документом OpenAPI, который порождается из самих маршрутов, а не пишется рядом с ними, и непрерывная интеграция сверяет этот вывод с хранимой копией, так что изменение маршрута в обход реестра не пройдёт молча. GraphQL покрывает модель приложения и несёт подписки по собственному сокету. Реальное время есть отдельный процесс, потому перезапуск шлюза не утаскивает за собой поверхность запросов, и разрешения выдаются по сокету, а не по комнате: на каждое событие все соединения арендатора оцениваются одной пакетной проверкой, и получают его только те, кому дозволено его видеть. MCP открывает те же операции агентам искусственного интеллекта как инструменты, и ни один инструмент не доверяет пришедшему от вызывающего идентификатору при выборе рабочего пространства, в котором действует; записи идут через те же службы, что использует интерфейс, поэтому проверки прав и журнал аудита те же самые.

Для кого это

Командам, которым нужен редактор под собственным контролем, и разработчикам, встраивающим структурированный контент в свой продукт.

Перейти Clepit: clepit.com