Расширение бэкенда
Для настройки генерируемого репозитория не нужно отключаться. Точки расширения в зонах owned и shared доступны при сохранённом подключении к Dee Wan. Отключайтесь, только чтобы остановить управляемую регенерацию. Автономный бэкенд: обзор описывает генерируемый бэкенд, а детали реализации перечисляют его зависимости.
Работа с подключённым репозиторием
Заголовок раздела «Работа с подключённым репозиторием»- Сгенерируйте проект локально:
npm run backend:generateилиnpm run backend:deliver. Раздел Настройки → Код и публикация не компилирует исходники (source_build_runtime_unavailable). - Клонируйте репозиторий владельца.
- Меняйте файлы в зонах owned и shared, начиная с
src/owned/backend.tsдля API. - Выполните проверки артефакта, создайте коммит и отправьте изменения.
- Разверните именно этот коммит через
deploy-from-repository.yml. - Позже запустите регенерацию из Dee Wan. Owned/shared-изменения сохранятся. Изменённые generated-файлы остановят её.
- Отключайтесь, только если владелец хочет прекратить управляемую регенерацию.
Матрица аутентификации GitHub объясняет полномочия для доставки. Руководство по отключению описывает проверки независимости. Настройка не требует отключения.
Что можно менять при сохранённом подключении?
Заголовок раздела «Что можно менять при сохранённом подключении?»| Изменение | Где | Поведение при регенерации |
|---|---|---|
| Включение/отключение операции, hooks, override с тем же контрактом | src/owned/backend.ts |
Сохраняется. Удалённые ID, конфликты настроек и изменения состава групп вызывают отказ при проверке/конструировании. Generated-защита остаётся вне owned-политики. |
| Декларативные API-маршруты | Тот же реестр, route() |
Сохраняются. Совпадение нормализованных путей приводит к отказу. Корректные схемы включаются в OpenAPI и проверяют вход/выход. |
| Raw Hono | src/owned/routes.ts |
Сохраняется, монтируется последним. Не заменяет generated-пути, включая отключённые операции. Для прямых маршрутов нет гарантии отсутствия коллизий и автоматического описания в OpenAPI. |
| Общая бизнес-логика | Другие файлы src/owned/ |
Сохраняются побайтно. Импортируйте из реестра или raw-маршрутов. |
| Свои таблицы и миграции | SQL/runner в src/owned/, свой workflow |
Исходники и посторонние таблицы/строки сохраняются. Миграцию запускайте явно. Она не добавляется автоматически в generated Prisma schema или migration runner. Не меняйте generated-таблицы. |
| Зависимости и выбор версий | Корневой и apps/site/package.json |
Дополняющее слияние сохраняет ваши значения. Добавляет недостающие обязательные зависимости/overrides. Выбранная версия должна удовлетворять контрактам сборки/runtime. |
| Привязки Wrangler и секреты | Общие root/site wrangler.jsonc, свой workflow и хранилище секретов |
Общие исходники сохраняются. Дополнительные локальные привязки не создаются и не копируются автоматически в wrangler.deploy.jsonc. Build/deploy-конфиги остаются generated. Не коммитьте секреты. |
| Свои CI workflows | .github/workflows/<your-name>.yml |
Сохраняются, кроме двух generated-имён. |
| Astro head/body и слоты | apps/site/src/owned/Head.astro, BodyEnd.astro, slots/ |
Сохраняются. Generated-layout импортирует их. |
| Обёртки и замены секций | apps/site/src/owned/sections/ |
Сохраняются. Generated-контракты секций продолжают проверять типы вашей реализации. |
| Интеграции и islands | apps/site/src/owned/config.mjs, owned-компоненты, зависимости сайта |
Сохраняются. Добавьте интеграцию вместе с зависимостями, затем проверьте/соберите Astro. |
| Свои страницы | apps/site/src/pages/owned/ |
Сохраняются и обслуживаются в /owned/. Generated-страницы/redirects не могут занять это пространство. |
| Generated-файлы и строки контента | Схема, клиенты/маршруты/защита, снимки, данные страниц, таблицы контента | Не редактируйте, пока подключение сохранено. Проверки отпечатков исходников, хешей снимков и наборов строк обнаружат расхождения и остановят регенерацию. Меняйте модели и контент в CMS или используйте зону owned. |
README.md, .nvmrc, .npmrc, .gitignore и существующие общие Wrangler-файлы сохраняются. deploy-from-repository.yml генерируется, а в артефакте целого сайта вместе с ним — dee-wan-build.yml (у артефакта --model нет управляемой CMS-сборки сайта). Остальные ваши workflows являются shared. История SQL-миграций сохраняется. Не переписывайте применённую миграцию.
Зоны на примерах путей
Заголовок раздела «Зоны на примерах путей»| Путь | Зона |
|---|---|
prisma/schema.prisma |
generated |
graduation.manifest.json |
generated |
src/app.ts |
generated |
src/worker.ts |
generated |
src/security.ts |
generated |
src/owned/backend.ts |
owned |
src/owned/routes.ts |
owned |
src/backend-customization.ts |
generated |
.dee-wan-customization.json |
generated |
src/serve-cli.ts |
generated |
migrations/0002_graduation_ledger.sql |
generated |
scripts/regenerate.mjs |
generated |
scripts/emit-migration.mjs |
generated |
scripts/file-manifest.mjs |
generated |
scripts/portable-client.mjs |
generated |
scripts/configure-deployment.mjs |
generated |
tsconfig.json |
generated |
wrangler.example.jsonc |
generated |
wrangler.build.jsonc |
generated |
scripts/load-snapshot.mjs |
generated |
data/published-snapshot.json |
generated |
.github/workflows/dee-wan-build.yml |
generated |
.github/workflows/deploy-from-repository.yml |
generated |
.github/workflows/anything-else.yml |
shared |
package.json |
shared |
package-lock.json |
shared |
.npmrc |
shared |
.nvmrc |
shared |
wrangler.jsonc |
shared |
.gitignore |
shared |
README.md |
shared |
apps/site/src/owned/config.mjs |
owned |
apps/site/src/pages/owned/thanks.astro |
owned |
apps/site/package.json |
shared |
apps/site/package-lock.json |
shared |
apps/site/.npmrc |
shared |
apps/site/wrangler.jsonc |
shared |
apps/site/src/lib/pages.json |
generated |
Типизированный реестр операций
Заголовок раздела «Типизированный реестр операций»src/owned/backend.ts создаётся только при отсутствии. Регенерация не переписывает существующий реестр. npm run customization:check восстанавливает отсутствующий реестр без пользовательских настроек и проверяет каталог. Сгенерированное приложение импортирует его. Поведение не зависит от порядка регистрации маршрутов.
Примеры ниже предполагают модель Article. Используйте фактические ID и поля Prisma из src/backend-customization.ts. Каждый пример — самостоятельный вариант реестра. Для нескольких возможностей объедините записи в одном defineBackend().
Обработчик запроса (hook)
Заголовок раздела «Обработчик запроса (hook)»import { defineBackend } from '../backend-customization'
export default defineBackend({ operations: { 'Article.findMany': { before: [(c) => { c.header('x-content-policy', 'owned') }], }, },})before и after — обработчики этапов выполнения запроса. Они не являются обработчиками жизненного цикла Prisma, транзакциями, блоками finally или функциями, вызываемыми после фиксации транзакции. Окончательный ответ или исключение останавливает оставшуюся часть этапа. При ошибке вызов after не гарантирован; его выполнение не подтверждает фиксацию транзакции.
Обернуть генерируемую реализацию
Заголовок раздела «Обернуть генерируемую реализацию»import { defineBackend } from '../backend-customization'
export default defineBackend({ operations: { 'Article.findMany': { override: async ({ input, args, context, prisma, core }) => { void input void args void context void prisma return [...await core()].reverse() }, }, },})input и args содержат одни и те же разобранные аргументы Prisma. context содержит проверенные данные субъекта доступа (principal). Через prisma доступен только delegate целевой операции с применённой защитой: прямой SQL, исходный клиент и другие модели или операции недоступны. Для постраничного чтения доступны защищённые findMany и count. core() выполняет сгенерированную реализацию с тем же состоянием запроса и проверок доступа. Сам вызов не создаёт транзакцию и не организует повторную попытку. Вызывайте его один раз, если повторное выполнение не требуется намеренно.
Заменить одну операцию
Заголовок раздела «Заменить одну операцию»import { defineBackend } from '../backend-customization'
export default defineBackend({ operations: { 'Article.findMany': { override: async () => [], }, },})Замена возвращает пустой массив по сгенерированному контракту списка и не вызывает core(). Аутентификация, полномочия и разрешённые guards остаются обязательными. Для другой структуры ответа, другого набора статусов или пути нужен новый декларативный маршрут. Типы выводятся из generated-аргументов/результатов Prisma. После изменения или удаления модели либо операции устаревшие ссылки выявляются при проверке типов или создании приложения.
Отключить операцию
Заголовок раздела «Отключить операцию»import { defineBackend } from '../backend-customization'
export default defineBackend({ operations: { 'Article.delete': { enabled: false } },})enabled: true сохраняет сгенерированную операцию. Это не позволяет включить маршрут, для которого генератор не создал защиту. Отключённые пути остаются зарезервированными. Массив деклараций одного ID может объединять hooks. Несколько overrides, противоречивые enable-настройки или disable вместе с override вызывают отказ.
Группы чтения и записи
Заголовок раздела «Группы чтения и записи»import { defineBackend } from '../backend-customization'
export default defineBackend({ groups: [ { name: 'content-reads', select: 'reads', accepted: ['Article.findMany', 'Article.findManyPaginated', 'Article.findUnique'], before: [(c) => { c.header('x-content-read', 'owned') }], }, { name: 'content-writes', select: 'writes', accepted: ['Article.create', 'Article.update', 'Article.delete'], after: [(c) => { c.header('x-content-write', 'owned') }], }, ],})Пример предполагает, что Article — единственная модель без переводов/связующих таблиц. select: ['Article.findMany', 'Article.findUnique'] выбирает явные ID. Селекторы reads/writes охватывают весь каталог сгенерированных операций, включая модели только для чтения. Имена групп уникальны. Hooks выполняются в порядке объявления групп.
accepted фиксирует точный набор. customization:check записывает раскрытые ID в .dee-wan-customization.json. Добавление/удаление модели или операции вызывает отказ, если состав изменился. Проверьте точный предлагаемый список в ошибке, измените accepted нужной группы в owned-реестре и повторите регенерацию/проверку. Не редактируйте сгенерированный файл состояния и не вычисляйте принимаемый набор фильтрацией текущего каталога: иначе будущие изменения будут приниматься без проверки.
Группа также может содержать типизированные overrides: { 'Article.findMany': async ({ core }) => core() } для ID из принятого набора. Второй override из другой группы или настроек операции вызывает отказ. Порядок объявления не даёт одному override приоритета над другим.
Существующие варианты и порядок
Заголовок раздела «Существующие варианты и порядок»Variant hooks задаются через operations['Article.findMany'].variants.reader.before/after или generated-вариант editor. Они используют существующий механизм вариантов генератора. Неизвестные варианты и попытки задать owned-формы или router-level защиту отвергаются. Второго диспетчера вариантов нет.
Порядок: платформенная аутентификация/полномочия, разрешение guard, group before, operation before, variant before, затем generated/override handler. После обработчика: variant after, operation after, group after, внешняя защита ответа/ошибки. Окончательный ответ или ошибка сохраняет предусмотренное вышележащими обработчиками досрочное завершение. Hooks должны сохранять документированную форму запроса/результата.
Добавить декларативный маршрут
Заголовок раздела «Добавить декларативный маршрут»import { defineBackend, route } from '../backend-customization'
export default defineBackend({ routes: [route({ method: 'POST', path: '/echo', access: { mode: 'service', operation: 'create' }, request: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'], additionalProperties: false, }, response: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'], additionalProperties: false, }, handler: async ({ input }) => ({ text: input.text }), })],})Путь задаётся относительно /api. Методы: GET, POST, PUT, PATCH, DELETE. access.operation — read/create/update/delete. Необязательный access.model указывает сгенерированную модель. Без модели токену чтения нужна область *. Обработчик получает проверенные типизированные данные, подтверждённого субъекта доступа и параметры пути. Исходный клиент ему не передаётся.
Объявляйте каждый пользовательский маршрут через route(). Скопированные или собранные вручную описания маршрутов вызывают отказ при конструировании. Схемы запроса/ответа фиксируются при объявлении, поэтому последующая мутация не меняет валидацию или OpenAPI.
Схемы поддерживают string/enum, конечное число, безопасное целое, boolean, null, массивы и закрытые объекты с обязательными полями. Неподдерживаемые ключи отклоняются, чтобы OpenAPI не описывал правила, которые на деле не проверяются. GET query-поля должны быть строками. Остальные методы разбирают JSON body. Неверный вход возвращает 400, результат вне схемы — 500.
Совпадение метода и нормализованного пути между сгенерированными и декларативными маршрутами приводит к отказу, даже если сгенерированная операция отключена. Повторные/конечные слеши нормализуются. Wildcard, кодированные пути, query/fragment и traversal запрещены. Пути, различающиеся только именем параметра, и пересечения статических путей с параметризованными тоже отклоняются. Совпадение со сгенерированным маршрутом разрешается только через переопределение операции. Корректные декларативные схемы включаются в /api/openapi.json, доступный после аутентификации; прямые маршруты Hono — нет.
Прямое использование Hono
Заголовок раздела «Прямое использование Hono»Императивные интеграции сохраняйте в src/owned/routes.ts. Они монтируются после generated/declarative-маршрутов и зарезервированных generated-путей. На прямых маршрутах выполняются внешняя аутентификация и защита от раскрытия данных в ошибках. Для них нет декларативной гарантии отсутствия коллизий, автоматической проверки схем или сгенерированного описания OpenAPI.
Совместимый mount по-прежнему определяет модель по префиксу пути. Raw-путь без модели требует wildcard read scope. Явно применяйте .guard(shape, variant) к Prisma-запросам. Расширение клиента само по себе не защищает произвольные вызовы. Предпочитайте декларативные метаданные доступа, если путь не представляет generated-модель.
Доступ проверяется до вашего кода
Заголовок раздела «Доступ проверяется до вашего кода»Политика определяет анонимный, сессионный или сервисный доступ. Для модели без настройки действует service. Токены чтения хранятся в виде хешей с ограничением по моделям в ACCESS_CONFIG. Маршруты без модели и /api/openapi.json требуют wildcard scope. Сервисная операция записи без настроенной аутентификации для записи возвращает 503 writes_unavailable, отсутствие учётных данных — 401, отказ полномочий — 403. Необязательный Better Auth предоставляет сессии локальному runtime и Worker. Локальный runtime и Worker принимают один и тот же сервисный bearer API_BEARER_TOKEN.
Нормализация/скрытие внутренних данных в ошибках остаётся вне overrides и hooks. Для новой формы ответа нужен новый маршрут. Дополнительная авторизация относится к бизнес-логике и не отменяет платформенные проверки.
Добавить расширение сайта
Заголовок раздела «Добавить расширение сайта»Измените apps/site/src/owned/Head.astro:
<meta name="color-scheme" content="light dark" />Новую страницу добавьте в apps/site/src/pages/owned/thanks.astro. Для island добавьте интеграцию/зависимости фреймворка и настройте apps/site/src/owned/config.mjs. Generated-конфигурация Astro импортирует её. Head.astro, BodyEnd.astro, слоты и wrappers/overrides секций — независимые точки расширения. Мост предпросмотра остаётся необязательным в extensions.ts.
Зависимости, миграции и регенерация
Заголовок раздела «Зависимости, миграции и регенерация»Оба package-манифеста сохраняют значения dependencies/overrides и добавляют недостающие обязательные записи. В корневом манифесте также добавляются отсутствующие команды в раздел scripts. Существующие команды остаются вашими. Generated build/deploy workflows запускают проверку кастомизации напрямую, даже если сохранена прежняя owned-команда typecheck. Выбор package manager и lifecycle-политики остаётся вашим. Несовместимая версия не проходит проверку/сборку. В корневом манифесте фиксируются версии инструментов генератора; диапазоны версий сайта закреплены его lock-файлом. Если манифест не удаётся прочитать, сборка останавливается. После изменений используйте проверенный установщик.
Храните бизнес-логику, собственный SQL и migration runners в src/owned/. Запускайте свои миграции из собственного workflow. Generated Prisma-модели описывают только CMS-контент: новая таблица приложения не создаёт модель или endpoint автоматически. Сохраняйте порядок миграций и план отката.
При регенерации сравниваются отпечатки сгенерированных файлов, переносятся файлы owned/shared, добавляются миграции и обновляется сгенерированный контент. Изменённые generated-файлы/снимки вызывают отказ. Когда загружается изменившийся снимок, он заменяет строки каждой генерируемой контентной таблицы: строка, которой нет в снимке, удаляется, кто бы её ни записал. Ваши собственные таблицы не входят в снимок и сохраняют свои строки.
Команде npm run regenerate нужны определения сайта в definitions.json и GENERATION_SOURCE_VERSION. Управляемый CI получает версию от CMS и скачивает определения. Как получить их самому, показано в Локальная перегенерация. Когда файл и переменная подготовлены:
node scripts/install-dependencies.mjsGENERATION_SOURCE_VERSION=<version> npm run regeneratenpm run migrate:emitnpm run typechecknode scripts/install-dependencies.mjs apps/sitenpm run --prefix apps/site checknpm run --prefix apps/site buildnpm run buildnpm run files:manifestРазвёртывание
Заголовок раздела «Развёртывание»Закоммитьте/отправьте проверенный репозиторий и запустите deploy-from-repository.yml для точного git_sha. Настройте CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, API_BEARER_TOKEN, PUBLIC_HOSTNAME, как описано в разделе ресурсы и конфигурация Cloudflare. Управляемая регенерация вызывает тот же workflow после коммита обновлений.
Для локального сервера используйте db:init, build:serve, serve:local с локальными настройками доступа и bearer-токеном записи, как описано в сгенерированном README. npm run dev запускает Wrangler локально. До развёртывания проверьте отказ без аутентификации, разрешённые чтения/записи, guard-отказы, overrides и скрытие деталей ошибок. Старый коммит кода — не резервная копия базы: откат должен учитывать совместимость миграций и медиа.
Необязательная аутентификация и доступ артефакта
Заголовок раздела «Необязательная аутентификация и доступ артефакта»В разделе Настройки → Код и публикация → Параметры разработчика → Сгенерированный бэкенд включите Better Auth один раз для бэкенда. Выберите нужные способы входа: Пароль, Ссылка для входа (Resend), Google или GitHub. По умолчанию он выключен: зависимость, runtime, маршруты, таблицы, миграция, настройки провайдеров и секреты Better Auth не создаются. Аккаунты CMS, сессии, приглашения, хеши паролей и ключи провайдеров не переносятся. Прикладная модель User отделена от таблиц идентификации артефакта.
| Режим доступа | Кто может отправить запрос |
|---|---|
public |
Явный типизированный анонимный principal без учётных данных. |
session |
Действующая сессия Better Auth этого артефакта. |
service |
API_BEARER_TOKEN или scoped-токен, разрешающий чтение. |
session_or_service |
Сессия артефакта либо сервисная аутентификация. |
Для модели без настройки явно действует service. Операция без override наследует режим модели. Используются точные ID: User.findMany, User.findManyPaginated, User.findUnique, User.create, User.update, User.delete. Удалённые или переименованные модели/операции вызывают отказ валидации. Сгенерированные типы тоже отклоняют устаревшие ID. Доступность операций остаётся в owned-реестре: политика доступа не включает выключенную операцию. Политики требуют сессию независимо от способа входа.
Аутентификация и проверка полномочий выполняются до hooks и overrides. Guards, существующие границы владения и защита от раскрытия данных в ошибках обязательны. Публичная операция создания отклоняет системные поля, владельца, арендатора, роли, администратора, пароль, верификацию и связи провайдера, включая вложенные значения. Она создаёт прикладные данные. Регистрация идёт только через маршруты Better Auth. Raw-маршруты используют service. Декларативные маршруты обязаны указать access.mode и access.operation. Модель необязательна. OpenAPI показывает режим каждой операции.
| Необязательный корень установки | Пакет | Блок | Точная версия |
|---|---|---|---|
root, только при включении |
better-auth |
dependencies |
1.6.25 |
При включении создаются src/better-auth.ts, проверка провайдеров, существующие правила ограничений/блокировки входа и отдельная миграция инфраструктурных таблиц _artifact_auth_*. У них нет CRUD-маршрутов. Node/SQLite и Worker/D1 используют одинаковые методы, ограничения пароля, проверенные учётные записи OAuth, хешированные токены и отзыв сессий через БД. В обеих средах сессия ограничена семью днями, а для запросов, изменяющих данные, проверяется origin. Access/refresh/ID-токены провайдеров удаляются. Ответы не раскрывают внутренние ошибки провайдера.
npm run auth:init создаёт новый случайный секрет в игнорируемом .dev.vars, сохраняя уже существующий секрет артефакта. Node загружает файл через node --env-file=.dev.vars src/serve.local.mjs, Wrangler — автоматически. Сначала выполните db:init. ARTIFACT_ORIGIN — origin API, например локальный http://127.0.0.1:8787. Удалённый origin требует HTTPS. Развёртывание берёт его из проверенного целевого hostname, а не из CMS.
| Выбранный метод | Обязательные runtime-значения | Целевой callback |
|---|---|---|
| Password | BETTER_AUTH_SECRET |
Маршруты пароля под /api/auth |
| Magic link | BETTER_AUTH_SECRET, RESEND_API_KEY, MAIL_FROM |
<ARTIFACT_ORIGIN>/api/auth/magic-link/verify |
BETTER_AUTH_SECRET, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET |
<ARTIFACT_ORIGIN>/api/auth/callback/google |
|
| GitHub | BETTER_AUTH_SECRET, GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET |
<ARTIFACT_ORIGIN>/api/auth/callback/github |
Зарегистрируйте callbacks в OAuth-приложениях владельца артефакта. Resend требует подтверждённый домен отправителя. Ключи нужны только выбранным методам. Их отсутствие останавливает preflight до развёртывания. Имена Actions secrets получают префикс ARTIFACT_, обходя зарезервированный GitHub префикс GITHUB_. При публикации система создаёт новый секрет артефакта и шифрует его целевым публичным ключом. Секреты, уже существующие под целевыми именами, сохраняются без ротации. npm run backend:deliver читает ключи провайдеров только из переменных окружения ARTIFACT_*. Значения секретов не попадают в исходники, манифесты, входные данные генерации, дайджесты, отслеживаемые переменные, логи и журнал развёртывания.
Регенерация сохраняет owned/shared-файлы. Для включения auth в существующем shared-пакете и смены методов нужна повторная генерация, обновляющая зависимости и bindings вызывающего workflow. Выключить аутентификацию в существующем целевом каталоге нельзя: используйте новый каталог, сохранив учётные записи и историю миграций прежнего. Изменения доступа учитываются в дайджестах исходных данных генерации и манифесте.
verify:deployment учитывает политику: публичное чтение проверяется анонимно, сервисное — bearer-токеном, session-only требует действующую целевую сессию в ARTIFACT_VERIFY_SESSION_COOKIE. Без неё проверка возвращает verify_session_required и не объявляет непроведённую проверку успешной. При первом развёртывании инициализируйте целевой бэкенд, зарегистрируйтесь через его auth-маршруты и повторите проверку с сессией. Не сохраняйте краткоживущую сессию в исходниках или материалах проверки. Проверка в браузере и приёмка рабочего развёртывания остаются отдельными этапами.
Следующий шаг
Заголовок раздела «Следующий шаг»Определите запросы, которые принимают ваши маршруты, в разделе варианты запросов и guard-правила.
Стабильная версия ещё не выпущена (pre-MVP)
Коммит исходного кода: e207e63239a627fbd2ae9c414ef7c6eb19fcfe5a (с локальными изменениями)
Дата сборки (UTC): 2026-10-21T09:28:24.214Z
Коммит для обновления: не определён: в рабочей копии есть изменения. Примечания к выпуску