Документация Triggo
Узел Code

Миграция с n8n

Перенос скриптов узла Code и шаблонов-выражений из n8n в Triggo.

Автоперевод — в процессе проверки

Миграция с n8n

Если вы приходите из n8n, поверхность выглядит знакомо — есть язык шаблонов со скобками {{... }}, есть узел Code и есть ссылки на узлы выше по потоку. Модель выполнения под капотом, впрочем, не та же. Эта страница проецирует уже знакомые концепции на эквиваленты Triggo и указывает места, где ментальной модели придётся сместиться.

Беглым взглядом

Два отличия определяют почти каждое изменение:

  1. Итерация. Узел Code в n8n по умолчанию запускается per input item — когда приходит 50 элементов, ваш код запускается 50 раз, а $input.item.json — текущий элемент. Узел Code в Triggo запускается один раз за вызов с единым объектом inputs, построенным из сопоставлений полей. Если нужна итерация — либо делайте цикл внутри кода (inputs.items.map(...)), либо оборачивайте узел в Loop.
  2. Выражения. {{... }} в n8n оборачивает настоящее JavaScript-выражение — {{ $json.x + 1 }}, тернарники, вызовы методов, математика Luxon, $jmespath(...), расширения вроде .removeMarkdown(). {{... }} в Triggo — только путь: {{trigger.email}}, {{action_1.order.id}}. Он разрешает точечный путь и останавливается. Никакой арифметики, условий, вызовов методов. Для всего нетривиального — переходите в узел Code.

Держите эти две вещи в голове, и остальное — в основном синтаксическая подстановка.

Перевод синтаксиса выражений

Левая колонка — то, что было в n8n. Правая — эквивалент в Triggo: шаблонная форма, где возможно, иначе узел Code.

n8nTriggoЗаметки
{{ $json.field }}{{trigger.field}} или {{<stepId>.field}}$json в n8n означает «выход предыдущего узла». В Triggo вы адресуете каждый источник явно по step id (или trigger). См. Передача данных.
{{ $input.item.json.field }}{{trigger.field}} / {{<stepId>.field}}Осмысленно только в per-item-режиме n8n. У Triggo на уровне шаблонов нет per-item-контекста — если действительно нужна итерация, используйте Loop или вынесите логику в узел Code.
{{ $input.first().json.field }}{{<stepId>.field}}Та же подстановка. first() нет, потому что выход шага в Triggo — один объект, а не массив элементов.
{{ $input.all() }}Прямого эквивалента нетМодель данных другая — Triggo не оборачивает выходы в [{ json, binary },...]. Если узел выше по потоку возвращает массив, адресуйте его напрямую, например {{action_1.items}}.
{{ $node["HTTP Request"].json.field }}{{<stepId-of-HTTP-Request>.field}}Адресация по step id, а не по отображаемому имени узла. Скопируйте step id из инспектора.
{{ $('HTTP Request').first().json.field }}{{<stepId>.field}}То же, что выше. Форма $('Name') — чисто n8n.
{{ $now.toISO() }}Узел Code: utils.dayjs().toISOString()$now в шаблонах нет. ISO-метод dayjs — toISOString(), а не toISO().
{{ DateTime.now().plus({ hours: 1 }).toISO() }}Узел Code: utils.dayjs().add(1, 'hour').toISOString()Dayjs вместо Luxon. Набор плагинов — тот, что инъектируется, — проверьте, прежде чем предполагать наличие фичи из Luxon.
{{ $jmespath($json, 'results[*].id') }}Эквивалента нетИспользуйте узел Code: inputs.results.map(r => r.id) или обычный JS.
{{ $json.x + 1 }}Узел CodeАрифметика в шаблонах не поддерживается.
{{ $json.x > 0 ? 'a': 'b' }}Узел Code или узел Condition на ветвиТернарники в шаблонах не поддерживаются.
{{ $json.name.toUpperCase() }}Узел CodeВызовов методов в шаблонах нет.
{{ $workflow.id }}, {{ $execution.id }}НедоступноМетаданные workflow / execution сегодня не экспонируются ни в узел Code, ни в шаблоны.
{{ $env.MY_VAR }}НедоступноУ песочницы нет доступа к process.env. Пробрасывайте значения через инспектор (сопоставление полей из предыдущего шага).
{{ $vars.myVar }}НедоступноФича переменных из n8n в Triggo эквивалента не имеет.

Узел Code: форма входа

Это самый крупный сдвиг ментальной модели. Прочтите внимательно.

n8n

В режиме Run Once for Each Item (по умолчанию для многих шаблонов) узел Code запускается один раз на элемент, и вы пишете:

// n8n — per-item-режим
const qty = $input.item.json.qty;
const price = $input.item.json.price;
return { json: { total: qty * price } };

В режиме Run Once for All Items вы пишете:

// n8n — all-items-режим
const items = $input.all(); // Array<{ json, binary? }>
return items.map((i) => ({ json: { total: i.json.qty * i.json.price } }));

Triggo

Узел Code в Triggo запускается один раз, и точка. Никакой per-item-концепции на уровне узла. Ваша функция принимает один объект inputs — обычный JS-объект, построенный из того, что вы сопоставили в инспекторе:

function run(inputs, utils) {
  // inputs — то, что вы объявили в сопоставлениях полей инспектора.
  // Никаких $input, $json, $node.
  return { total: inputs.qty * inputs.price };
}

Если в инспекторе сопоставления полей такие:

{
  "qty":   "{{trigger.qty}}",
  "price": "{{trigger.price}}"
}

...то inputs === { qty: <number>, price: <number> }. Это и есть весь контракт. Подробности — в Справочник → Входы.

Узел Code: паттерн миграции per-item

Типичный случай — узел n8n, который запускается на каждый элемент и трансформирует его. Допустим, оригинал такой:

// n8n — Run Once for Each Item
return { json: { total: $input.item.json.qty * $input.item.json.price } };

...и массив позиций приходит в триггере как trigger.lines. Два способа портирования.

Вариант A — цикл вокруг узла Code

  1. Вставьте узел Loop, итерирующий {{trigger.lines}}.

  2. Внутри цикла добавьте узел Code, чьи сопоставления полей привязаны к текущему элементу цикла (например, qty: "{{loop_1.item.qty}}", price: "{{loop_1.item.price}}").

  3. Тело узла Code:

    function run(inputs) {
      return { total: inputs.qty * inputs.price };
    }

Это сохраняет ментальную модель n8n «работа на каждый элемент» и подходит, когда каждой итерации нужны другие нижестоящие шаги (вызов коннектора на элемент и т. п.).

Вариант B — сделать весь map внутри узла Code

Чаще всего, если per-item-работа без побочных эффектов, делайте всё в одном узле Code:

function run(inputs) {
  return {
    lines: inputs.lines.map((l) => ({ ...l, total: l.qty * l.price })),
  };
}

Сопоставление полей в инспекторе: lines: "{{trigger.lines}}". Нижестоящие узлы адресуют результат как {{code_1.lines}}.

Вариант B проще, быстрее (один запуск изолята вместо N) и легче для рассуждения. Тянитесь за вариантом A только тогда, когда телу действительно нужен fan-out через другие узлы.

Глобалы и помощники

n8nTriggo
$now (Luxon DateTime)utils.dayjs()dayjs. Цепочечный API (.add, .subtract, .format) похож по духу. Имена методов отличаются от Luxon (toISOString, valueOf, format). Доступны только плагины, вшитые в песочницу.
$todayutils.dayjs().startOf('day')
DateTime.now().plus({ hours: 1 })utils.dayjs().add(1, 'hour')
DateTime.fromISO(str)utils.dayjs(str)
Duration, IntervalНе предоставляются — используйте математику dayjs или обычную арифметику над valueOf().
$jmespath(obj, path)Не предоставляется — используйте обычный JS (obj.results.map(r => r.id) и т. п.).
$varsНедоступно.
$envНедоступно — у песочницы нет process.env.
$input.contextЭквивалента нет — Triggo не экспонирует per-run-контекстный объект.
$workflow, $executionНедоступно в песочнице.
$runIndex, $itemIndexНедоступно.
$getWorkflowStaticData(...)Недоступно — нет состояния между запусками. Если нужна персистентность, храните значение в коннекторе (таблица, KV и т. п.).

Утилиты Triggo, у которых в n8n нет прямого эквивалента

n8n опирается на расширения выражений (.removeMarkdown(), .isEmpty() и т. п.), привинченные к выводам выражений. У Triggo вместо этого — небольшой пояс с утилитами:

ПомощникНазначение
utils.uuid()UUID v4.
utils.pick(obj, keys) / utils.omit(obj, keys)Подмножество / исключение свойств объекта.
utils.groupBy(arr, key) / utils.keyBy(arr, key) / utils.uniqBy(arr, key)Частые операции массив → объект / дедупликация.
utils.base64Encode(str) / utils.base64Decode(str)UTF-8 ↔ base64. (Buffer в песочнице нет.)
utils.hash(str, algo?)Hex-дайджест; algo по умолчанию sha256.

Полные сигнатуры — в Справочник → __utils.

Пример миграции — от и до

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

Версия n8n (Run Once for All Items)

// n8n — JavaScript, all-items-режим
const items = $input.all();
const order = items[0].json;

const validLines = order.lines.filter((l) => l.qty > 0);
const subtotal = validLines.reduce((s, l) => s + l.qty * l.price, 0);

return [
  {
    json: {
      orderId: order.id,
      customerEmail: order.customer.email.toLowerCase(),
      lineCount: validLines.length,
      subtotal,
      currency: order.currency ?? 'RUB',
      capturedAt: DateTime.now().toISO(),
      fingerprint: $jmespath(order, 'lines[*].sku').join('|'),
    },
  },
];

Версия Triggo с комментариями

Сопоставление полей в инспекторе (одна запись):

{ "order": "{{trigger}}" }

Тело кода:

function run(inputs, utils) {
  // 1. `inputs.order` — вся полезная нагрузка триггера. Ни $input.all(),
  //    ни обёртки «массив элементов».
  const order = inputs.order;

  // 2. Обычный JS для фильтрации и свёртки — никаких расширений выражений.
  const validLines = order.lines.filter((l) => l.qty > 0);
  const subtotal = validLines.reduce((s, l) => s + l.qty * l.price, 0);

  // 3. Возвращаем один обычный объект. Никакой обёртки `{ json: ... }`.
  //    Нижестоящие узлы адресуют поля как `{{code_1.orderId}}` и т. д.
  return {
    orderId: order.id,
    customerEmail: order.customer.email.toLowerCase(),
    lineCount: validLines.length,
    subtotal,
    currency: order.currency ?? "RUB",
    // 4. Dayjs вместо Luxon DateTime. `toISOString()`, а не `toISO()`.
    capturedAt: utils.dayjs().toISOString(),
    // 5. Заменяем $jmespath обычным JS.
    fingerprint: order.lines.map((l) => l.sku).join("|"),
  };
}

Механические подстановки:

  • $input.all()[0].json → один inputs.order, который вы сопоставили в инспекторе.
  • DateTime.now().toISO()utils.dayjs().toISOString().
  • $jmespath(order, 'lines[*].sku')order.lines.map((l) => l.sku).
  • Обёртка возврата [{ json:... }] → голый объект.

Что не переводится

Некоторые фичи n8n просто отсутствуют в песочнице Triggo. Не пытайтесь их портировать — нужно перепроектировать поток или принять потерю.

  • Binary-данные. n8n экспонирует binary-данные на каждом элементе как $input.item.binary или $node["X"].binary, и узел Code может читать/писать их напрямую. У узла Code в Triggo binary-канала нет — всё идёт через JSON-сериализованные сопоставления полей, а Buffer недоступен. Если нужно работать с файлами, загружайте/скачивайте их через коннектор и пробрасывайте URL или ID между узлами.
  • Кросс-узловой доступ к binary. $node["X"].binary[...] эквивалента не имеет. То же лечение: держите работу с binary внутри коннектора.
  • Credentials из кода. this.getCredentials('myCred') из n8n не экспонирован — у песочницы нет хост-API. Разрешение credentials происходит вне узла Code в Triggo; действие коннектора читает credential, а в код вы через сопоставление полей пробрасываете только нужные поля.
  • $getWorkflowStaticData() / персистентность между запусками. Слота статических данных нет. Песочница по дизайну без состояния (см. Обзор → Модель песочницы). Для персистентности пишите во внешнее хранилище через коннектор.
  • Require / import. n8n можно настроить на разрешение внешних npm-модулей; Triggo — нет. Внутри изолята нет резолвера модулей.
  • Сетевые вызовы. fetch, http, axios — всё удалено. Используйте коннектор HTTP Request выше по потоку, пробрасывайте результат в узел Code как вход.
  • Таймеры. setTimeout и компания удалены. Никакого опроса, никакой задержки. Если нужна задержка, вплетите в DAG шаг Delay / Schedule.

Связанное

On this page