Привет, Хабр! Меня зовут Андрей и я развиваю портал для разработчиков в корпоративном мессенджере Пачка. Эта статья про то, как мы превратили рукописную ноду для n8n в артефакт, который генерируется из одного файла спецификации, и как она прошла официальную верификацию и встала в каталог n8n со статусом verified by n8n.
По дороге мы выбросили целиком первую версию ноды и перевели всю документацию API на новый процесс генерации. Ниже — как это устроено внутри, с кодом и граблями.
Если коротко, что получилось: правка в одном .tsp-файле автоматически расходится в документацию, CLI, SDK и n8n-ноду, а CI публикует всё по реестрам. Нода больше не может отстать от API, потому что собирается из того же источника.
Точка отсчёта: документация без единого источника
Публичная документация API Пачки существует с 2020 года. Технически это был отдельный самописный сайт на рукописных HTML и JS. Каждый эндпоинт описывался HTML-таблицей вручную. Единого машиночитаемого источника, например OpenAPI, не было.
Со временем справочника перестало хватать. API рос, у разработчиков появлялись запросы на SDK под разные языки, кто-то собирал свои сторонние библиотеки под Пачку сам. Но из рукописных HTML-таблиц ничего не сгенерируешь: как источник для кодогенерации они бесполезны. Нам нужен был один машиночитаемый контракт, из которого можно собирать и документацию, и SDK. Источником правды должна стать спецификация, а всё остальное — производным от неё.
Зачем нам вообще n8n
n8n — open-source платформа для no-code-автоматизаций: визуальный редактор, сотни готовых нод, события и действия соединяются мышкой. Мы заметили, что ей уже пользуются наши клиенты, многие разворачивают её на собственном сервере. Для продукта с открытым API это очевидный канал: дать пользователям силу API без единой строчки кода, чтобы собрать бота, согласование по кнопкам, выгрузку задач во внешние сервисы. Отдельно подкупает, что готовый сценарий в n8n — это просто JSON: им можно поделиться и импортировать в пару кликов, а собрать его теперь способен и ИИ — он знает ноды и выдаёт рабочий workflow. И всё крутится внутри n8n, без отдельного хостинга под каждую интеграцию.
Чтобы Пачка нормально жила внутри n8n, ей нужна своя нода. И мы сделали её. Руками.
Первая версия: рукописная нода и её потолок
Осенью 2025-го собрали рукописную ноду на TypeScript, выложили на npm и запустили закрытую бету на пятерых добровольцах. Базовые методы (сообщения, пользователи, чаты) поехали быстро, сложные (формы, разворачивание ссылок, загрузка файлов через S3) — со скрипом. В цифрах первая версия выглядела так:
-
~5300 строк рукописного кода, 33 коммита в отдельном репозитории;
-
12 ресурсов, частичный CRUD;
-
distribution только через npm — пользователи ставили её вручную в self-hosted n8n.
В ноябре 2025-го отправили ноду на верификацию в n8n, чтобы она прошла все проверки и стала доступна в облаке, а не только в коробке. И тут начался цикл, знакомый всем, кто заводил пакет во внешний каталог. Каждую отправленную версию сначала проверяет алгоритм, а дальше она уходит на ручное ревью командой n8n.
Первая проблема, что каждое такое ревью — примерно 3-4 недели ожидания. Любая, даже мелкая правка означает новый круг. Вторая проблема вытекает из первой — нужно основательно готовить ноду к отправке, учитывать все официальные гайдлайны n8n и даже опыт коммьюнити, что вручную делать оказалось непросто. Мы отправляли ноду раз пять, и каждый раз её возвращали на очередной мелочи, при этом API за это время развивался: получалось, что новый эндпоинт в ноду надо дописать руками, замечание ревьюера — поправить и пересобрать. Рукописная нода к живому API превратилась в догонялку.
Вывод, который мы для себя зафиксировали: пока рукописная v1 пройдёт ревью, она уже устареет относительно API. Так и вышло — первая версия верификацию так и не прошла. Мы остановили правки и поняли, что менять надо сам процесс.
Разворот: один источник на TypeSpec
Здесь история перестаёт быть про ноду и становится про процесс. И начинается она с документации публичного API.
На 2025 год и начало 2026-го пришёлся рост агентских инструментов разработки: Claude Code, Cursor, Codex. На этой волне сошлись два желания: проверить на большой реальной задаче, на что способна агентская разработка, и наконец собрать тот самый машиночитаемый источник, к которому мы подступались выше.
Под это мы выбрали TypeSpec — типизированный язык, который компилируется в openapi.yaml. Из коробки он даёт всё, что нужно для единого источника: операции, модели, переиспользуемые типы, enum’ы, union’ы под разные коды ответа, описания и примеры через @doc и @example.
Но «голый» OpenAPI ничего не знает про устройство именно нашего API: какие у операции нужны права, на каком тарифе она доступна, как устроена пагинация. Это доменное знание мы зашили в спеку своими расширениями (x-*) и научили каждый генератор их читать:
-
**x-requirements**(68 операций) — нужный OAuth-скоуп и тариф (scope: "messages:create",plan: "corporation"). В ноде превращается вnotice: «requires admin permissions», «Corporation plan». -
**x-scope-roles**— матрица «скоуп → роли» (owner / admin / user / bot). В ноде даёт подсказку, кто вообще может вызвать операцию. -
enum’ы (25 штук: типы файлов, сущностей, сортировок, событий вебхуков) плюс
x-enum-descriptions— в ноде становятся выпадающими списками с понятными подписями, а не строковым полем, куда надо угадать значение. -
**x-paginated**(15 эндпоинтов) — курсорная пагинация, в ноде из неё вырастает переключатель Return All / Limit с автодокачкой страниц. -
**@example**на каждом поле — дефолтные значения и примеры в полях ноды.
И главное — пишется это один раз, а читают расширения все генераторы. Те же x-requirements и x-scope-roles в документации превращаются в бейджи требований на странице метода и интерактивный справочник «скоуп → роль». @example растекается в примеры запросов в доке и в CLI. x-paginated даёт флаг --all в CLI и пагинаторы в SDK. То есть TypeSpec дал позвоночник, а «мясо» — это наши расширения поверх него, и одно и то же знание из спеки расходится во все артефакты сразу.
Схематично весь пайплайн выглядит так:
typespec.tsp │ tsp compile ▼openapi.yaml ──────────────┬──────────────────┬─────────────────────┐ │ overlay:apply ▼ ▼ ▼ ▼ packages/cli sdk/* (6 языков) integrations/n8nopenapi.en.yaml │ generate-cli │ CI: generate │ generate-n8n │ ▼ ▼ ▼ ▼ npm (@pachca/cli) npm, PyPI, JitPack, npm (n8n-нода)apps/docs SPM, Go modules │ generate-llms, next build ▼ портал + llms.txt + per-endpoint/per-guide .md
Зависимости между шагами описаны декларативно в turbo.json, так что это не набор скриптов, которые надо запускать по очереди. Меняешь .tsp, для новых строк добавляешь английский перевод в overlay (про него ниже), запускаешь turbo build — он одной командой пересобирает доку, CLI и ноду в правильном порядке, а CI раскатывает результат: пакеты в реестры, документацию на портал. SDK на шесть языков генерируются из той же спеки, но собираются отдельным шагом: каждому языку нужен свой тулчейн (go build, gradle, dotnet, swift), поэтому они вынесены в свою CI-джобу. Вся эта пересборка — чистая детерминированная генерация, агент тут не нужен.
Где агенты действительно нужны — это новый код и аудит. Сам проект (генераторы, тесты, миграции) мы писали с активной помощью агентов. Spec-driven проект с десятком артефактов раньше потребовал бы отдельной команды, а так его реально вести малыми силами. Кстати, основной объём я по сути веду один, но интерес к порталу в команде общий: генерацию и публикацию SDK сразу на шесть языков (от TypeScript до Swift) завёл другой наш инженер.
Заодно поменялся и сам сайт. Из справочника по методам он вырос в полноценный портал для разработчиков: гайды, примеры на разных языках, CLI, SDK, готовые сценарии и нода.
Как нода генерируется из спеки
Нода живёт в монорепозитории как ещё один артефакт (integrations/n8n/): генератор generate-n8n.ts читает уже разрешённый OpenAPI и строит описания ресурсов, главный класс ноды и credentials. Сложные вещи — кнопки, формы, загрузка файлов, поиск — генерируются в программном стиле execute(): в декларативном их нормально не реализовать.
Параллельно нода стала просто удобнее для no-code-пользователя, и это тоже выпало из спеки:
-
автопагинация —
Return All/Limitвместо ручныхper/page; -
Simplify — переключатель, оставляющий в ответе только ключевые поля (меньше шума при drag-and-drop);
-
searchable dropdowns — поиск по чатам и сотрудникам прямо в выпадающих списках вместо ввода ID;
-
полный CRUD по задачам вместо одного создания;
-
загрузка файлов через S3 (по URL или из бинарных данных предыдущего узла).
Покрыли всё тестами: 42 mock-теста на 52 операции и 33 интеграционных против реального API, плюс юнит-тесты — суммарно под 400.
Вторая нода: триггер и особенность вебхуков Пачки
Рукописная v1 умела только действовать: отправить сообщение, создать задачу, загрузить файл. Реагировать на события в самой Пачке она не могла. В новой версии появилась вторая нода — Pachca Trigger, которая слушает вебхуки и запускает workflow на событие (новое сообщение, нажатие кнопки, реакция и т.д., всего 16 типов).
Здесь нас ждала особенность самой Пачки: у бота только один слот под webhook-URL, и новая запись затирает предыдущую. Поэтому у триггера два режима регистрации:
-
автоматический — нода сама прописывает URL боту через API при активации workflow;
-
ручной — вы вставляете URL в настройки бота сами.
Ручной режим выручает, когда боту нельзя давать права на запись или когда одним ботом пользуются и тест, и продакшен (иначе тестовый запуск перезатрёт продовый URL). Это, кстати, был источник одного из багов ранних версий — автоматический режим пытался прописать URL бот-токеном без нужного скоупа и получал 403. В итоге мы развели режимы явно.
Обратная совместимость: VersionedNodeType и замороженный v1
Самым нервным куском оказалась обратная совместимость. На v1 уже работали реальные автоматизации у клиентов, и переименование ресурсов и операций (в v2 мы причесали имена: reactions → reaction, send → create и т.д.) не должно было сломать ни одного существующего workflow.
Решение нашлось в механизме самого n8n — VersionedNodeType. Одна нода, внутри несколько версий, по умолчанию отдаётся новая:
export class Pachca extends VersionedNodeType { constructor() { const baseDescription = { name: 'pachca', defaultVersion: 2, // ... }; const nodeVersions = { 1: new PachcaV1(baseDescription), 2: new PachcaV2(baseDescription), }; super(nodeVersions, baseDescription); }}
Старый класс мы заморозили буквально как есть — скопировали из последней опубликованной версии 1.0.27 и закрыли от правок комментарием:
// ============================================================================// PachcaV1.node.ts — FROZEN V1 node class (from n8n-nodes-pachca@1.0.27)// DO NOT EDIT — this file is frozen for backward compatibility// ============================================================================
Дальше работает так:
-
существующая нода в чужом workflow сохраняет
typeVersion: 1и использует V1-класс — все параметры и поведение прежние; -
новые ноды по умолчанию создаются на V2;
-
общий
SharedRouterна лету транслирует v1-имена ресурсов и операций в v2, так что под капотом обе версии ходят в один и тот же код; -
в Node Creator показываются только v2-операции, без дубликатов;
-
на v1-нодах n8n сам рисует жёлтый баннер «доступна новая версия» — это просто уведомление, переделывать ничего не нужно.
В генераторе под это пришлось завести несколько слоёв совместимости (таблицы соответствий v1↔v2 для ресурсов, операций, параметров, коллекций), чтобы старые значения сохранялись байт в байт. Зато обновление для пользователя получилось безболезненным: пакет называется так же, новая версия встаёт поверх старой, переезд на v2 — опциональный и постепенный.
Английский интерфейс через OpenAPI Overlay
Verified-нода обязана быть полностью на английском: интерфейс и документация. При этом основная спека у нас русская (это контракт с бэкендом и наша основная аудитория). Переводить руками по второму кругу не вариант, поэтому мы используем стандартный механизм самой спецификации, OpenAPI Overlay. Это отдельный файл, который адресует строки к нужным местам спеки по JSONPath и подменяет описания:
overlay: 1.0.0info: title: Pachca API — English translations version: 1.0.0actions: - target: $.paths['/profile'].get update: description: |- Get profile Retrieve information about your own profile.
При сборке overlay:apply накладывает слой на русскую спеку и получает openapi.en.yaml, из которой и генерируется нода. Русская спека питает документацию и API-референс, английская — ноду. Отдельный скрипт-валидатор следит, что overlay покрывает все поля, так что новое поле в API нельзя забыть перевести: сборка просто упадёт.
Что требует n8n для верификации
Раз уж прошли этот путь — вот сжатый чек-лист требований к verified-ноде (гайдлайны n8n):
|
Требование |
Деталь |
|---|---|
|
Один сервис на пакет |
Одна интеграция + опционально триггер. Не дубль, не Logic/Flow-ноды |
|
TypeScript |
Проверка пакета инструментом |
|
Никаких рантайм-зависимостей |
Запрещены внешние зависимости, доступ к env и файловой системе — данные только через параметры |
|
English only |
Интерфейс и вся документация: параметры, описания, ошибки, README |
|
Источник пакета |
Открытый репозиторий с лицензией MIT, npm привязан к GitHub (URL и автор совпадают), имя |
|
Иконки и codex |
Светлая/тёмная иконки, codex-файл с категориями |
|
Provenance |
Публикация из GitHub Actions с provenance-statement (с весны 2026 — обязательно) |
Конкретно нам ревьюеры прислали список обязательных правок: убрать эмодзи из названий опций, убрать недокументированное поле в codex, почистить мёртвый код, убрать шаблоны форм. Все правки легли в один цикл: чиним в спеке или генераторе, проверяем разом в SDK, CLI и ноде.
Как держим всё в синхроне: аудит API
Чтобы нода (и остальные артефакты) не отставала от бэкенда, у нас описан процесс внутреннего аудита API, он лежит в открытом репозитории, в папке docs/ рядом с инженерными регламентами. По этому регламенту агент регулярно сверяет дефолтную ветку бэкенда со спекой: новые эндпоинты, параметры, поля ответов, payload вебхуков, коды ошибок, версии и changelog’и пакетов. Любое расхождение вносится в .tsp, прогоняется генерацией и расходится во все артефакты сразу. Та самая ситуация «нода отстала от API», из-за которой умерла v1, теперь технически невозможна: нода обновляется в том же цикле, что и сам API.
Цикл замыкается на стороне n8n. После верификации не нужно проходить её заново на каждый релиз: n8n сам подхватывает новые npm-версии ноды, прогоняет быстрое ревью и включает в ближайший релиз каталога. Получается сквозной конвейер: правка в спеке → регенерация → публикация в npm → обновление ноды в облаке.
Результат
В начале мая 2026-го Пачка появилась в официальном каталоге n8n со статусом verified by n8n, двумя карточками — Pachca и Pachca Trigger. Попасть туда непросто — российских сервисов в каталоге единицы. Контраст со временем нагляден. Рукописную v1 ревью гоняло по кругам несколько месяцев, и в каталог она так и не попала. v2 мы начали в марте, 8 апреля получили правки от ревью, внесли их в тот же день — и нас сразу одобрили. 1 мая ноду выложили в официальный каталог, ещё и поблагодарили за то, что так основательно её пересобрали.

В витрину встала не только нода, но и документация: со страницы ноды в каталоге ссылка ведёт прямо в наш раздел гайдов: установка, ресурсы, триггер, миграция, тестирование, разбор ошибок и готовые workflow для импорта. Английский остаётся у самой ноды, развёрнутые гайды и API-референс — русскими, для наших разработчиков.
Для пользователя обновление безболезненное: в облаке нода ставится из каталога в один клик, в self-hosted — через Community Nodes → Update. Существующие workflow остаются на своей версии, новые создаются на v2.
Что дальше: агент поверх ноды
Verified-нода и триггер — это фундамент, а не финал. Поверх них мы уже собираем AI-агента, который живёт прямо в Пачке: его зовут из треда или лички, он ищет нужные чаты, людей и сообщения, подтягивает контекст обсуждения и отвечает по делу. Технически это обычный workflow: Pachca Trigger ловит обращение, дальше языковая модель с набором инструментов, а инструменты — это наши же ноды Pachca.

Главный вывод по агентам оказался неочевидным. Просто пометить операции ноды как usable-as-tool и отдать их агенту — мало: на широком наборе инструментов он путается, какой вызвать. Гораздо стабильнее заворачивать конкретный навык в отдельный sub-workflow-инструмент со строгим контрактом. Например, «найти сотрудника»: снаружи у инструмента один вход, а внутри sub-workflow сам решает, искать по имени или по user_id. Агент не думает лишний раз и реже ошибается. Для n8n это нормальная практика — от него ждут строгости и предсказуемости, а не магии.
Такие навыки-сабворкфлоу собираются из тех же нод (Get a message, Create a thread, Get many messages) и тоже шарятся как JSON — заготовку можно отдать желающим и смотреть, как они дорабатывают её под свои кейсы.
А дальше — самое интересное для нас: научиться генерировать и эти агентские сценарии из той же спецификации. Тогда и сам агент, и его навыки станут не отдельным кодом, который поддерживаешь руками, а ещё одним артефактом из одного источника. Тот же принцип, что вытащил нас из догонялки с нодой, мы хотим распространить на уровень выше.
Что мы из этого вынесли
-
Рукописная интеграция к живому API — это технический долг, который растёт быстрее, чем его гасишь. Внешнее ревью просто обнажает то, что и так копилось.
-
Один источник правды окупается, когда артефактов много. Когда из спеки растут доки, CLI, шесть SDK и нода, одна правка чинит всё разом — а раньше это была бы куча отдельных задач и столько же способов разойтись.
-
Требования витрины — это бесплатный внешний код-ревью. Запрет рантайм-зависимостей и мёртвого кода, английский UI, provenance-публикация: после такого прохода качество объективно выше.
-
Актуальность — это процесс, а не разовое действие. Регулярный аудит плюс генерация держат библиотеки синхронными.
-
Рутину делает генерация, а не агенты. Пересборка детерминирована и в агенте не нуждается. Агенты подключаются там, где нужна голова — новый код и анализ изменений бэкенда. Именно эта связка позволила небольшой команде вести проект уровня, который раньше потребовал бы отдела.
Если пользуетесь n8n — нода Пачки уже в каталоге. А посмотреть, как из одного .tsp-файла вырастают доки, CLI, SDK и нода, можно в открытом репозитории: github.com/pachca/openapi.
ссылка на оригинал статьи https://habr.com/ru/articles/1044888/