Перейти к содержимому
Версия 1.0 ещё не выпущена; проект находится на стадии pre-MVP. См. возможности и ограничения версии 1.0.

Предпросмотр и генерируемый TypeScript-клиент

Генерируемый публичный клиент обращается к API /v1 CMS. Он не предназначен для отдельного API автономного бэкенда. Этот API описан в справочнике API автономного бэкенда.

Откройте Настройки → API → Клиент TypeScript. Там показана идентичность схемы клиента для моделей, которые может читать ваша роль, и файл можно скопировать или скачать. Файл начинается с комментария @dee-wan/public-client, где записаны сайт, публичный URL, версия контракта, идентичность схемы и сам контракт. Идентичность схемы — SHA-256-дайджест локалей, политики локалей, а также видимости и типов полей каждой модели. Подписи, названия и порядок полей её не меняют.

Чтобы обновлять клиент из своего репозитория, скачайте dee-wan-client.mjs в той же карточке и закоммитьте его. Нужен Node 18 или новее, пакеты не нужны. Токен API сайта команда читает из DEE_WAN_SITE_TOKEN. Создайте токен в разделе Настройки → API и храните его в секрете сервера или CI. Приватные модели должны входить в его область. Подставьте публичный origin сайта; значения ниже — примеры.

Окно терминала
DEE_WAN_SITE_TOKEN=dw_site_… node dee-wan-client.mjs pull --url https://www.example.com --out src/dee-wan-public.ts

Команда читает GET /v1/_client на публичном origin. Она записывает файл, только если ответ полный и его заголовок совпадает с выданной идентичностью. Полученный клиент охватывает публичные модели и приватные модели из области токена. Его идентичность совпадает с показанной в настройках, только когда оба охватывают одни и те же модели.

Перед каждым развёртыванием, например в CI, проверьте закоммиченный файл:

Окно терминала
DEE_WAN_SITE_TOKEN=dw_site_… node dee-wan-client.mjs check --file src/dee-wan-public.ts

check завершается с кодом 0, если идентичность совпадает или все изменения совместимы: добавлена модель, поле или локаль, поле стало обязательным, модель стала публичной или удалено значение select. Такие изменения он перечисляет. Код 1 — ломающее изменение: удалена модель, поле или локаль, изменился тип поля или его списочность, поле стало необязательным, появилось новое значение select, модель стала приватной, изменились сайт, локаль по умолчанию или политика локалей. Код 2 — проверка не смогла выполниться: неверные аргументы, нет токена, ошибка сети или HTTP, файл без заголовка. После изменения выполните pull, исправьте то, что перестало компилироваться, и закоммитьте.

Административная сессия по-прежнему может получить тот же файл вручную:

Окно терминала
curl --fail --silent --show-error -H "X-Site-Id: $DEE_WAN_SITE_ID" -b "$DEE_WAN_SESSION_COOKIE" 'https://admin.example.com/api/models/public-client.ts' -o /tmp/dee-wan-public.ts

Файл содержит точные поля моделей, типы локалей и запросов, конверты ответов и методы моделей. Это TypeScript без зависимостей, а не устанавливаемый пакет. Ваше приложение никогда не импортирует команду, а Dee Wan никогда не меняет ваш репозиторий.

Пример предполагает, что вы действительно создали публичную модель article и локаль en. Используйте публичный хост сайта, а не административный origin и не базовый URL с /v1 на конце.

import { createClient } from './dee-wan-public';
const client = createClient({ baseUrl: 'https://www.example.com' });
const first = await client.article.list({ locale: 'en', limit: 20 });
const item = await client.article.get('hello', { locale: 'en' });

На сервере и при сборке настройка CORS не нужна. Для браузерного приложения на другом источнике разрешите его в разделе Настройки → API → Источники браузерных запросов. См. настройку источников. Для приватных моделей нужен token в параметрах createClient, хранимый как серверный секрет. Не публикуйте его в браузерном бандле. Проверяйте data и метаданные списка page. Передавайте непрозрачные курсоры без изменений и проверяйте возвращённую локаль при fallback. Контракты ошибок и пагинации — в публичном API.

После управляемой передачи кода и развёртывания раздел Код и публикация показывает владельца и имя репозитория, точный развёрнутый коммит и сообщённый origin Astro. После завершения операции новый адрес помечается Предпросмотр не проверен, а прежний активный адрес предпросмотра сохраняется. Вошедший пользователь с site:settings выбирает доступный сохранённый материал и язык, затем нажимает Проверить и включить предпросмотр. Сервер загружает сохранённый origin и контракт рендерера, после чего выполняет существующую проверку предпросмотра черновика. Браузер не может подставить другой origin в это действие.

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

Действие Предпросмотр в редакторе создаёт токен доступа (capability) к одной записи на 30 минут. Он разрешает читать только этот черновик, а не весь сайт, и не отменяет проверку доступа к приватной модели. Не записывайте токен в журналы, не индексируйте и не распространяйте его как публичный URL. Недействительные токены отклоняются. Получите новую ссылку вместо повторного использования просроченной. Новые ссылки также показывают доступные создателю ссылки сохранённые черновики связанных записей. При каждом открытии проверяются его текущая учётная запись, доступ к сайту и права чтения. Поля и языки основной записи ограничены правами, записанными при создании ссылки. Старые ссылки без этого разрешения показывают только опубликованные связанные записи.

Настройте origin рендерера и шаблон маршрута каждой модели в разделе Настройки → Код и публикация → Дополнительно — свой рендерер. Контракт использует HTTPS origin (HTTP только для loopback-адреса: localhost, 127.0.0.1 или [::1]), подстановки {slug} и {locale} и имя параметра токена в query. Рендерер должен пройти проверку для этого сайта, прежде чем CMS сообщит rendered: true. Одной конфигурации недостаточно. Без проверенного рендерера Предпросмотр открывает JSON публичного API и сообщает rendered: false.

Для генерируемого Astro-сайта явно задайте previewApiOrigin в apps/site/src/owned/extensions.ts равным origin публичного API CMS и проверьте контракт рендерера. По умолчанию там null. Только маршрут /preview использует это подключение к CMS во время выполнения. Обычные опубликованные страницы используют сгенерированный снимок, кроме страниц с секцией «Последние записи» и маршрутов /blog сайта Blog: они читают опубликованные записи во время запроса из /v1 на собственном хосте сайта (в эталонной топологии это публичный Worker Dee Wan) или, при локальной разработке, из owned-значения publicApiOrigin, и отвечают с no-store. Содержимое секции «Последние записи» в общей секции, размещённой в области сайта, фиксируется при генерации. Ответы предпросмотра имеют no-store и не должны попадать в поиск или публичные кэши. Автономному сайту следует отключить мост, когда он перестаёт использовать CMS.

В редакторе записи Предпросмотр здесь открывает сохранённую отрисовку рядом с полями. Действие предлагается, когда проверенный рендерер сайта поддерживает маршруты этой модели и поддерживает мост редактора. Широкие экраны (от 1280 пикселей) показывают поля и отрисовку рядом. Более узкие экраны показывают одну панель за раз, с вкладками Поля и Предпросмотр. Предпросмотр на сайте по-прежнему открывает отрисовку в новой вкладке.

Отрисовка показывает сохранённую версию, если сайт не поддерживает предпросмотр несохранённых изменений (см. «Предпросмотр несохранённых изменений» ниже). В этом случае несохранённые изменения не появятся в ней до сохранения, о чём сообщает панель.

В актуальных управляемом и сгенерированном рендерерах блога текст и форматированный текст можно менять прямо в предпросмотре. Дважды нажмите на значение, выберите его кнопку Edit или переведите фокус на ссылку поля и нажмите Enter. Поверх значения откроется обычное поле редактора с инструментами форматирования. Нажмите Готово, чтобы закрыть поле. Изменения останутся в черновике до нажатия Сохранить. Другие поля и старые рендереры открывают соответствующее поле формы. Собственный рендерер другой модели может включить этот режим атрибутом data-dw-editor-inplace у маркера моста; он должен показывать проверенный черновик и ссылки на поля. Фрейм сообщает только поле и его положение. Право на изменение и само значение проверяет редактор.

С рендерером со ссылками на поля (мост редактора 3, который объявляют сгенерированные сайты):

  • Дважды щёлкните отрисованное значение, щёлкните по нему с Alt, Ctrl или Cmd, нажмите его значок Edit или сфокусируйтесь на нём и нажмите Enter. Если правка прямо в предпросмотре недоступна, редактор открывает нужное поле формы и переводит в него фокус.
  • Сфокусируйтесь на поле в редакторе. Отрисовка обводит каждый элемент, который его показывает, и прокручивает к первому.
  • В модели страницы секции, заголовок и факты редактируются в редакторе страниц. При выборе этих элементов предлагается Открыть редактор страниц.
  • Обычный щелчок в отрисовке не переходит по ссылкам, поэтому предпросмотр остаётся открытым.

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

Встроенные рендереры (сгенерированный сайт и управляемый рендерер) связывают модели страниц и посты блога. В посте заголовок, дата, обложка, автор, ссылки категорий и тегов и каждый блок тела (включая текстовые значения блоков плагинов) связаны со своими полями. В их контрактах рендерера объявлены маршруты модели post, поэтому после повторной генерации или развёртывания снова проверьте и активируйте рендерер. Другие обычные модели получают ссылки на поля только от собственного рендерера, который поддерживает мост 3. См. контракт документа страницы.

Управляемый предпросмотр можно встроить во фрейм только со страницы редактора с origin, указанным в ссылке предпросмотра, и только с nonce редактора. В этом случае политика CSP разрешает встраивание только с этого origin и запуск скрипта моста с указанным хешем. Любой другой предпросмотр или опубликованная страница сохраняет frame-ancestors 'none'.

Если проверенный рендерер сайта объявил, что рисует модель по подписанному черновику, Предпросмотр здесь показывает то, что вы набрали и не сохранили. Текст, компоненты, связи, выбранные изображения и адрес языка обновляются, когда вы перестаёте печатать; одновременно выполняется одно обновление. Управляемый рендерер объявляет это для модели Blog post. Любая другая модель и любой рендерер, который ничего не объявил, показывают сохранённую версию, и панель объясняет почему. После обновления дизайна сайта проверьте и активируйте рендерер заново.

  • Ничего не сохраняется. Значения проверяются так же, как при сохранении, затем рисуются; они не меняют сохранённый черновик, опубликованную версию, ревизию или публичное чтение. Сервер их не журналирует. Запрос больше 1 МиБ отклоняется.
  • Нужно право редактировать запись на показанном языке. Пользователь с правом только на чтение видит сохранённую версию.
  • Для связанных записей берётся последний сохранённый черновик, даже если запись ещё ни разу не публиковалась. Выбранные вами несохранённые связи учитываются; несохранённые изменения в другой записи — нет. Для обычных связей и связей внутри компонентов действуют текущие права на запись, поля и язык; приватные поля скрываются по прежним правилам. Язык выбирается по настройкам сайта. Раскрытие ограничено четырьмя уровнями и 128 записями; при цикле остаётся только идентификатор записи. Связанные записи доступны в item.expanded. Публичный API по-прежнему читает только опубликованные версии.
  • Изображения — это файлы, выбранные в каждом поле изображения, с заданными вами кадрированием и фокусной точкой; рисуются так же, как сохранённая запись. Сервер ищет каждый файл по id в библиотеке этого сайта с вашими текущими правами и никогда не использует адрес, присланный браузером. Файл другого сайта, несуществующий файл и приватный файл там, где сохранённая запись его отклонила бы (то есть не в поле приватной модели), отклоняются, и панель называет причину. Приватный файл в поле приватной модели показывает свои сведения без адреса, как и сохранённая запись. Адрес языка — тот, что вы ввели, запись рисуется по нему. Ничего не резервируется: адрес не занимается, перенаправление не пишется, предпросмотр не меняет опубликованный адрес сайта.
  • Действуют те же связи с полями: щёлкните отрисованное значение, чтобы открыть его поле, и поставьте фокус в поле, чтобы обвести его значение.
  • Модель страницы просматривается в редакторе страниц, а не здесь.
  • Если запись изменилась в другом месте, у языка нет сохранённого содержимого или дизайн сайта не может её нарисовать, панель оставляет последнюю удачную отрисовку и сообщает об этом. Повторите после устранения причины.

Сгенерированный сайт Astro с блогом содержит проверяющий модуль. Он проверяет черновик публичным ключом: в сайт не попадает ни секрет рендера, ни учётная запись, ни сессия, ни учётные данные провайдера, а проверка подписи не обращается к CMS. Установке нужны MANAGED_RENDER_HMAC_KEY и FRONTEND_URL; без них ключ недоступен, и раздел Настройки → Код и публикация → Дополнительно — свой рендерер сообщает об этом.

  1. Откройте Настройки → Код и публикация → Дополнительно — свой рендерер один раз. Ключ предпросмотра несохранённых записей записывает публичный ключ сайта для генерации.
  2. Сгенерируйте сайт заново и разверните его. Сгенерированный сайт несёт этот публичный ключ, вставлять ничего не нужно. Пока ключ не записан, сайт ничего не объявляет, а его конечная точка отвечает 501.
  3. Нажмите Проверить и включить в разделе Настройки → Код и публикация → Дополнительно — свой рендерер. Сайт объявляет entry_preview с моделью блога и идентификатором ключа в /.well-known/dee-wan-renderer.json только пока ключ есть. После этого Предпросмотр здесь показывает несохранённые изменения.

Чтобы переопределить сгенерированный ключ, нажмите Скопировать ключ и вставьте JSON в apps/site/src/owned/entry-preview.config.ts вместо null. Файл ваш: повторная генерация его не заменяет, а значение в нём приоритетнее сгенерированного ключа.

  • Подписанный черновик называет сайт, origin рендерера, запись, язык, загруженную вами версию, точные байты и срок действия 60 секунд. Рендерер отказывает при любом другом сайте, origin, записи, языке, версии, байтах, origin редактора или сроке. Фрейм должен быть открыт с origin редактора, указанного в подписанных данных.
  • Конечная точка — /.well-known/dee-wan-entry-preview. Она принимает форму редактора и ничего не записывает, не кэширует и не журналирует.
  • Astro отклоняет кросс-доменные POST-формы, а фрейм редактора находится на другом origin. Поэтому сгенерированный сайт с блогом отключает эту проверку и повторяет её в src/middleware.ts для всех адресов, кроме этой точки.
  • Заменить ключ создаёт новый ключ и записывается в журнал аудита как изменение настроек сайта. Старый сразу перестаёт работать: сгенерируйте и разверните сайт заново (или обновите своё переопределение) и снова нажмите Проверить и включить. До этого Предпросмотр здесь показывает сохранённую версию и сообщает, что сайт проверяет по старому ключу.
  • Если сайт не объявил ключ или объявил старый, панель показывает сохранённую версию и объясняет причину.

Собственный рендерер копирует apps/site/src/lib/entry-preview-verifier.ts из сгенерированного сайта. В модуле нет импортов, он использует только Web Crypto Ed25519 (Node 24, Cloudflare Workers, современные браузеры). Вызовите verifyEntryPreview({ envelope, body }, config, context) с двумя полями формы envelope и body, вставленной настройкой и контекстом: site_id вашего рендерера, собственный origin, идентичность релиза (renderer_contract_digest), модели models, которые он рисует, now в секундах Unix и параметр editor_origin из адреса фрейма. При успехе вернётся проверенный item, который вы рисуете своим представлением записи; иначе — отказ с status (403, 409, 413, 422 или 501) и reason. Объявите поддержку в /.well-known/dee-wan-renderer.json: entry_preview: { "version": 1, "models": ["post"], "key_ids": [<kid из настройки>] }, и каждая модель тоже должна иметь маршрут; entryPreviewDeclaration(config, models) собирает объявление и возвращает null, пока настройки нет. Не объявляйте поддержку, пока конечная точка не работает. Рендерер, который использует общий ключ рендера установки (управляемый рендерер), key_ids не объявляет.

Сгенерированный сайт Astro рисует каждое изображение по одному правилу — deliverImage в apps/site/src/lib/media-presentation.ts. Маршрут предпросмотра и опубликованные страницы используют одно и то же правило и одни и те же настройки, поэтому дают одинаковые URL.

  • Каждое изображение получает width и height из записанного размера или из обрезки, если она есть у этого использования. Изображение без записанного размера не получает ни их, ни srcset.
  • Применяются обрезка и точка фокуса именно этого использования. Одно изображение, использованное дважды, хранит два кадрирования.
  • Сохранённый файл никогда не меняется.

Выдача преобразованных изображений выключена, пока вы не объявите медиахосты, в зоне Cloudflare которых включены Image Transformations. Задайте imageTransformOrigins в apps/site/src/owned/extensions.ts, например ["https://media.example.com"]. Ошибочная запись останавливает сборку с указанием её имени. Изображение на объявленном хосте тогда отдаётся как https://media.example.com/cdn-cgi/image/width=640,quality=85,format=auto,fit=scale-down,trim=<top>;<right>;<bottom>;<left>/<key> с srcset из 320, 640, 960, 1280 и 1920 пикселей, но не шире области кадрирования, и sizes="100vw". trim обрезает изображение в пикселях перед масштабированием. Любое другое изображение сохраняет исходный URL; обрезку тогда показывает CSS (aspect-ratio, object-fit: cover, object-position), а точку фокуса — object-position.

Приёмка в развёрнутой среде — ручной шаг. Выполните его один раз для каждого медиахоста:

  1. В панели Cloudflare откройте зону, которая обслуживает медиахост, затем Images → Transformations, и включите трансформации для этой зоны. Ожидается: зона показана как включённая.
  2. Добавьте хост в imageTransformOrigins, соберите и разверните сайт.
  3. Откройте опубликованную страницу с кадрированным изображением. На вкладке сети браузера запрос изображения идёт на /cdn-cgi/image/... на медиахосте и отвечает 200 с типом содержимого изображения. Ожидается: картинка показывает обрезку, а width/height у img совпадают с кадрированным размером.
  4. Сузьте окно до 390 пикселей и перезагрузите. Ожидается: загружается меньший вариант из srcset.
  5. Запросите исходный URL. Ожидается: исходные байты без изменений.

Ответ 404 или ошибка 9xxx на /cdn-cgi/image/ означает, что трансформации для этой зоны не включены: уберите хост из imageTransformOrigins, пока их не включат.

Источники: backend/src/api/models.ts, backend/src/lib/public-types/generate.ts, backend/src/lib/public-types/contract.ts, frontend/static/dee-wan-client.mjs, backend/src/api/content_lifecycle.ts, backend/src/lib/site-settings.ts, backend/src/lib/graduate/site-media-presentation.ts, backend/src/lib/graduate/site-emit.ts, backend/src/lib/field-link-runtime.ts и backend/src/lib/page-contract/field-links.ts.

Прочитайте описание публичного API чтения: полный контракт чтения, фильтры и отказы.


Стабильная версия ещё не выпущена (pre-MVP)
Коммит исходного кода: fe23db481c422b40e317b92fc98116b4c0c26a87 (с локальными изменениями)
Дата сборки (UTC): 2026-10-18T09:22:34.630Z
Коммит для обновления: не определён: в рабочей копии есть изменения. Примечания к выпуску