ydb

n8n node for Yandex Database (YDB) — Yandex Cloud managed/serverless YDB, with IAM token / service account authentication

Package Information

Released: this month
Downloads: 0 weekly / 0 monthly
Latest Version: 0.2.1
Author: Sergei Kharin

Documentation

n8n-nodes-ydb

Нода для n8n, которая подключается к Yandex Database (YDB) — serverless или выделенной базе в Yandex Cloud — и выполняет YQL-запросы.

Поддерживаемые операции:

  • Execute Query — произвольный YQL-запрос (SELECT, UPSERT, DDL и т.д.) с именованными параметрами, выполняется по одному разу на каждый входящий item;
  • Bulk Upsert — вставка/обновление всех входящих items в таблицу одним пакетным запросом (UPSERT ... SELECT * FROM AS_TABLE($rows)).

Авторизация — через IAM Yandex Cloud:

  • IAM Token — статичный токен (yc iam create-token), самый простой способ, но токен нужно обновлять вручную раз в ~12 часов;
  • Service Account Key (JSON) — авторизованный ключ сервисного аккаунта (yc iam key create), нода сама получает и обновляет IAM-токен — рекомендуется для продакшен-workflow.

Под капотом используется официальный современный SDK @ydbjs/* (@ydbjs/core, @ydbjs/query, @ydbjs/auth, @ydbjs/auth-yandex-cloud), который сейчас продвигается самой Yandex Cloud в консоли и документации YDB.

Требования

  • n8n (self-hosted, community-ноды устанавливаются только в self-hosted инсталляции; в n8n Cloud community-ноды недоступны);
  • Node.js >= 20.19 (требование @ydbjs/*; актуальные self-hosted версии n8n этому требованию удовлетворяют).

Установка

Вариант 1 — через интерфейс n8n

Settings → Community Nodes → Install → указать n8n-nodes-ydb.

Вариант 2 — вручную (если ставите из исходников)

# в папке проекта
npm install
npm run build

Скопируйте содержимое папки dist/ (или весь пакет целиком) в директорию с community-нодами вашего n8n, либо опубликуйте пакет в npm и установите его как обычно через Community Nodes.

Настройка credentials

  1. В консоли Yandex Cloud откройте карточку вашей базы YDB и скопируйте значения Endpoint (например, grpcs://ydb.serverless.yandexcloud.net:2135) и Database (например, /ru-central1/b1g.../etn...).
  2. В n8n создайте credentials типа Yandex Database API, вставьте Endpoint и Database Path.
  3. Выберите способ авторизации:
    • IAM Token — выполните yc iam create-token и вставьте полученную строку;
    • Service Account Key (JSON) — выполните yc iam key create --folder-id <folder-id> --service-account-name <sa-name> --output key.json и вставьте содержимое файла key.json целиком.

У сервисного аккаунта должна быть роль, дающая доступ к базе (например, ydb.editor на нужную базу или каталог).

Использование ноды

Execute Query

  • Query — YQL-запрос, например:
    SELECT id, name FROM users WHERE id = $id;
    
  • Query Parameters — JSON-объект с параметрами, например {"id": 42}. Объявлять параметры через DECLARE не нужно — SDK делает это автоматически на основе переданных значений.

Каждая строка результата запроса становится отдельным item на выходе ноды. Если запрос не возвращает строк (например, UPSERT или CREATE TABLE), на выходе будет один item { "success": true }.

Поле Query и Query Parameters можно наполнять expressions ({{ $json.id }} и т.п.) — нода выполняется по одному разу на каждый входящий item.

Bulk Upsert

  • Table — путь к таблице (например, users или dir/users).

Все входящие items (их поля json) одним запросом загружаются в таблицу через UPSERT INTO ... SELECT * FROM AS_TABLE($rows). На выходе — один item { "success": true, "upserted": N }.

Особенности и ограничения

  • Значения Int64/Uint64 из YDB приходят как JavaScript BigInt; нода автоматически конвертирует их в обычные числа, если они укладываются в диапазон Number.isSafeInteger, иначе — в строку, чтобы не терять точность.

  • Строки: Utf8 vs String. Значения колонок типа Utf8 возвращаются как обычные JS-строки. Колонки типа String/Bytes возвращаются как base64-строки — так сохраняется точность для бинарных данных (хеши, произвольные ID, любые байты), которые могут не быть валидным UTF-8. Если нужно получить именно текст из колонки String, приведите её в самом запросе: CAST(col AS Utf8). Имейте в виду и обратное: строковые литералы '...' в YQL по умолчанию имеют тип String, а не Utf8 — чтобы получить Utf8 из литерала, пишите Utf8('...') или CAST('...' AS Utf8).

  • Bulk Upsert: как это работает. Нода выполняет настоящий UPSERT (колонки, которых нет во входных items, сохраняют прежние значения — в отличие от REPLACE, который перезаписывает строку целиком). Массив items передаётся через интерполяцию тегированного шаблона SDK (sql`...AS_TABLE(${rows})`). SDK выводит тип каждого поля из JS-значения и оборачивает все поля в Optional<...>, а YDB не принимает Optional<Int32> в NOT NULL колонку (в том числе первичный ключ) — поэтому нода автоматически оборачивает каждую колонку в Unwrap(...): итоговый запрос выглядит так (см. блок ниже). Благодаря этому работают и NOT NULL, и nullable целевые колонки.

    DECLARE $p0 AS List<Struct<id:Optional<Int32>,name:Optional<Utf8>>>;
    UPSERT INTO `t` SELECT Unwrap(`id`) AS `id`, Unwrap(`name`) AS `name` FROM AS_TABLE($p0);
    
  • Bulk Upsert: требования к данным. (1) У всех items должен быть одинаковый набор полей. (2) Значения null/undefined не поддерживаются (Unwrap упал бы на NULL) — для строк с NULL используйте Execute Query. (3) Имена полей должны совпадать с колонками таблицы.

  • Bulk Upsert: числовые типы. Тип выводится из JS-значения: целое число → Int32, дробное → Double, строка → Utf8. YDB неявно расширяет Int32 до Int64, но, например, Int32 → Uint64/Timestamp/Uint8 может дать Type annotation. Если целевая колонка не Int32/Int64/Double/Utf8 — используйте Execute Query с явным CAST в запросе.

  • Пакеты @ydbjs/* — чистый ESM; нода загружает их через динамический import(), поэтому сама нода собирается и работает как обычный CommonJS-модуль n8n без дополнительной настройки.

  • Версии @ydbjs/* в package.json зафиксированы точно (без ^), а не диапазоном: это активно развивающийся SDK, и разные патч-версии заметно отличались поведением параметров/типов. Перед осознанным обновлением версии — прогоняйте npm test и вручную проверяйте Bulk Upsert на реальной базе.

  • Bulk Upsert собирает все входящие items в один запрос — для очень больших объёмов (десятки тысяч строк за одно выполнение) может потребоваться предварительно разбить данные на батчи через Split In Batches.

  • Bulk Upsert выполняется одной операцией на весь набор items (а не по одному item, как Execute Query): при включённом «Continue On Fail» ошибка одного упавшего batch-запроса помечает все items как failed — частичного результата внутри одного вызова не будет.

  • Query Parameters (для Execute Query) должен быть JSON-объектом ({"id": 1}), а не массивом или скалярным значением.

  • Имя таблицы (Table) не должно содержать обратные кавычки (`) или управляющие символы — под капотом оно подставляется в UPSERT INTO `table`. Дефисы и другие символы, допустимые в путях YDB, разрешены.

  • IAM-токен (IAM Token в credentials) не обновляется автоматически — для длительно работающих production-workflow используйте Service Account Key (JSON).

  • Соединение с YDB открывается один раз в начале выполнения ноды и переиспользуется для всех items, а закрывается по завершении (даже если выполнение упало с ошибкой).

Changelog

0.2.1

  • Исправлен ERESOLVE-конфликт при чистой установке (rm -rf node_modules package-lock.json && npm install), возникавший после npm audit fix --force: vitest@5.0.2 требует @types/node >=22, что конфликтовало с зафиксированной ^20.14.0.
  • vitest обновлён ^2.1.4 → ^3.2.7, добавлен явный devDependency vite: ^7.3.6 — устраняет исходную уязвимость в esbuild (была в vite@5.x, который тянул vitest@2.x), не требуя @types/node >=22.
  • n8n-workflow (devDependency) обновлён ^1.70.0 → ^1.120.31 — намеренно не ^2.x, обоснование см. в разделе «Поддержка зависимостей (dev-only) и npm audit».
  • Добавлен overrides.form-data: "^4.0.6" — закрывает критическую уязвimость в form-data, которую транзитивно тянет n8n-workflow@1.x.
  • Один пункт npm audit (@vitest/mocker, moderate) осознанно оставлен открытым — dev-only, не используется в тестах проекта, полноценный фикс требует vitest@5.x. Подробности в README.
  • Никаких изменений в коде ноды/credentials — только dev-инструментарий и package.json.

0.2.0

  • Нода теперь может использоваться как AI-инструмент для узла AI Agent (usableAsTool: true), без отдельного пакета/ноды-инструмента. Поля Query, Query Parameters, Table можно заполнять через $fromAI(...), при этом ручной ввод в обычном режиме работает как раньше (см. раздел «Использование как AI-инструмент (Tool)»).
  • Operation намеренно осталась фиксированной на экземпляр ноды (см. обоснование в том же разделе) — для доступа агента к обеим операциям нужно добавить ноду дважды.
  • Field descriptions для Query/Query Parameters/Table переведены на английский (n8n-конвенция для полей, которые видит в том числе AI-модель).
  • Добавлены регрессионные тесты, проверяющие usableAsTool, noDataExpression на operation и наличие $fromAI(...) в дефолтах трёх полей.
  • ⚠️ Не проверено на живом n8n с реальным AI Agent — см. предупреждение в README.

0.1.2

  • Исправлен Bulk Upsert для таблиц с NOT NULL первичным ключом (BAD_REQUEST, ERROR(1030): Type annotation): SDK выводит для всех полей Optional<...>, а YDB не приводит его к Int32 NOT NULL. Теперь каждая колонка оборачивается в Unwrap(...) в SELECT. Операция остаётся настоящим UPSERT (частичные строки не затирают остальные колонки).
  • Добавлены понятные ошибки: разный набор полей у items и null/undefined значения.
  • Тесты на генерацию запроса против реального @ydbjs/query.

0.1.1

  • Исправлен Bulk Upsert: запрос UPSERT ... SELECT * FROM AS_TABLE($rows) мог падать с GENERIC_ERROR, Issues: ERROR(0): Unknown name: $rows, если резолвилась версия @ydbjs/query с иным поведением .parameter(). Теперь Bulk Upsert строит запрос через настоящую интерполяцию тегированного шаблона SDK, что и есть официально задокументированный способ передавать AS_TABLE-параметры, и версии @ydbjs/* зафиксированы точно, чтобы исключить дрейф версий.
  • Добавлен регрессионный тест на этот сценарий (test/GenericFunctions.test.ts).
  • Execute Query не менялся — параметры через .parameter() там по-прежнему работают штатно (YDB сам выводит скалярные типы из контекста запроса).

Использование как AI-инструмент (Tool)

Начиная с версии 0.2.0 эту же ноду можно подключить к порту Tools узла AI Agent — без отдельного пакета или отдельной ноды-инструмента. Обычный режим работы (workflow → нода → workflow) при этом не меняется.

Требование к окружению

Community-ноды по умолчанию не могут использоваться как AI-инструменты. На инстансе n8n должна быть выставлена переменная окружения:

N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true

Без неё нода не появится в списке доступных инструментов у AI Agent, даже если она установлена.

Как это работает

  • Поля Query, Query Parameters и Table по умолчанию содержат выражение вида ={{ $fromAI('query', 'описание для агента', 'string', '') }}. Это официальный механизм n8n: когда нода вызывается агентом, $fromAI(key, ...) возвращает значение, которое агент передал для параметра key; при обычном (не-agent) выполнении — четвёртый аргумент (fallback), в точности как раньше было пустое значение по умолчанию. Так одно и то же поле остаётся удобным для ручного ввода и одновременно доступно агенту: если вы вводите в поле своё значение — оно просто заменяет это выражение целиком, как с любым дефолтным значением.
  • Operation остаётся зафиксированной для конкретного экземпляра ноды. Поле Operation — это noDataExpression: true выпадающий список, а $fromAI(...) можно использовать только внутри выражения — то есть агент не может «на лету» переключать Execute Query / Bulk Upsert в рамках одного и того же инстанса ноды на холсте. Чтобы дать агенту выбор между двумя операциями, добавьте ноду дважды: один экземпляр с Operation = Execute Query, второй — с Operation = Bulk Upsert, и переименуйте каждый (через Rename и/или переопределение имени/описания инструмента в настройках подключения к Tools), чтобы агент понимал, какой инструмент для чего. Это стандартный n8n-паттерн для нод с несколькими режимами работы, а не обходной путь.
  • Ошибки (некорректный SQL, несуществующая таблица и т.п.) при обычном выполнении по-прежнему обрабатываются через continueOnFail(), как описано выше. При вызове со стороны AI Agent сама обёртка n8n перехватывает исключение и возвращает агенту текст ошибки как результат вызова инструмента, не прерывая работу агента целиком — это стандартное поведение usableAsTool, а не логика, реализованная в этой ноде.

⚠️ Не проверено на живом n8n

Я не смог протестировать это на реальном инстансе n8n с AI Agent (в этой среде разработки нет доступа к запущенному n8n). Реализация основана на официальном публичном API n8n-workflow (usableAsTool, $fromAI, FromAIArgument) и его исходном коде — но перед продакшен-использованием обязательно проверьте вручную:

  1. Нода появляется в списке инструментов при подключении к AI Agent (с N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true).
  2. Агент действительно может вызвать инструмент, заполнить Query/Query Parameters/Table, и результат приходит в разумном виде.
  3. При обычном (не-agent) использовании поведение не изменилось: пустая строка в Query по-прежнему считается пустой (ошибка «Query не может быть пустым»), а не выражением, зависшим в expression-режиме.

Поддержка зависимостей (dev-only) и npm audit

Не запускайте npm audit fix --force в этом проекте не глядя — именно эта команда однажды привела к ERESOLVE-конфликту (vitest@5 требует @types/node >=22, а в проекте была ^20.14.0) после того, как форсированно подняла vitest и n8n-workflow на несовместимые мажорные версии. --force игнорирует peer-зависимости и берёт «последнюю подходящую по уязвимости» версию, не проверяя, ломает ли она граф зависимостей или требования к окружению — если аудит находит уязвимость, разбирайтесь, что конкретно предлагается обновить, и обновляйте осознанно, а не через --force.

Что зафиксировано в devDependencies и почему:

  • n8n-workflow: ^1.120.31, а не ^2.x. n8n-workflow объявлен как peerDependencies: "*" — в реальном n8n-инстансе его версию всегда предоставляет хост, наш devDependency нужен только для локальной типизации/тестов и никогда не публикуется и не попадает к пользователям пакета (files: ["dist"]). При этом n8n-workflow@2.x (через @n8n/expression-runtime) тянет нативный isolated-vm, которому нужен Node.js >= 24 — то есть просто ради типов пришлось бы поднимать требование к окружению для контрибьюторов вдвое выше, чем реально нужно этому проекту (engines.node: ">=20.19", продиктовано @ydbjs/*). Ради нуля функциональной пользы это не оправдано; линейка 1.x даёт нам ровно те же интерфейсы (INodeType, NodeConnectionTypes, usableAsTool, $fromAI/FromAIArgument и т.д.), которые мы используем — они не менялись в мажорном бампе.
  • vitest: ^3.2.7 + явный vite: ^7.3.6, а не ^2.x (был раньше) и не ^5.x (то, что форсированно поставил audit fix --force). Исходная уязвимость в esbuild (GHSA-67mh-4wv8-2f99, dev-сервер) живёт в esbuild < 0.25.0, который тянется вместе с vite@5.x (используется vitest@2.x по умолчанию). vitest@3.2.7 поддерживает vite@6/7, поэтому мы явно фиксируем vite@^7.3.6 (несёт esbuild@^0.25.0, уже без этой уязвимости), не поднимаясь до vitest@5, которому для этого пришлось бы требовать @types/node >=22.
  • overrides.form-data: "^4.0.6". n8n-workflow@1.x тянет form-data@4.0.0, попадающий в диапазон двух реальных уязвимостей (GHSA-fjxv-7rqg-78g4, GHSA-hmw2-7cc7-3qxx). npm overrides форсирует патч-версию 4.0.6 для всех вложенных зависимостей без необходимости ждать обновления n8n-workflow и без смены его мажорной версии.

Один пункт npm audit осознанно не закрыт: @vitest/mocker (moderate, path traversal через vi.mock()-редирект, GHSA-82fw-gwwq-j7x9) исправлен только в версии, жёстко привязанной к vitest@5.x (vitest@3.2.7 пинит @vitest/mocker на точную 3.2.7, а не диапазон — переопределять через overrides несовместимую мажорную версию внутреннего, точно запиненного пакета рискованно и может незаметно сломать мокинг). Тесты этого проекта не используют vi.mock() вовсе, а сама уязвимость — dev-only и не публикуется вместе с пакетом, так что риск для пользователей ноды нулевой. Если по какой-то причине понадобится закрыть и этот пункт — единственный чистый путь — переход на vitest@5.x, что означает синхронный бамп @types/node до >=22.

Разработка

npm install
npm run build   # tsc + копирование иконки в dist/
npm test        # vitest: unit-тесты чистых функций (sanitizeForOutput, assertSafeIdentifier)
npm pack --dry-run  # проверить, что в npm-пакет попадает только dist/, LICENSE, README, package.json

В репозитории есть минимальный GitHub Actions workflow (.github/workflows/ci.yml), который на каждый push/PR прогоняет build + test + pack --dry-run.

Перед публикацией в npm замените плейсхолдеры your-org / Your Name / you@example.com в package.json на реальные данные (поля author, homepage, repository, bugs).

Лицензия

MIT — см. файл LICENSE.

Discussion