Orbito - Dokumentacja modułówOrbito - Dokumentacja modułów
Wprowadzenie
Pierwsze kroki
API
Komunikacja
Widoki
Wdrożenie
Społeczność
For AI agents
Wprowadzenie
Pierwsze kroki
API
Komunikacja
Widoki
Wdrożenie
Społeczność
For AI agents
  • API biblioteki

    • API biblioteki @orbito/api-lib
    • Core
    • Log
    • Utils
    • Pages
    • Automation
    • DB
    • Config
    • Files
    • Problems
    • History
    • OAuth
    • Time
    • Mail
    • Templates
    • Lock
    • Uprawnienia

Templates

Moduł Templates (od SDK 4.0.0) pozwala modułowi udostępnić zmienne do szablonów wiadomości firmy. Szablony (np. e-maile wysyłane przez automatyzacje akcją send-template-email) edytuje się w jednym, globalnym edytorze w panelu Orbito, który nie zna żadnego modułu. Dlatego moduł deklaruje grupy zmiennych: nazwany zestaw pól (customer → email, fullName) oraz handler, który zwraca ich wartości.

const { Templates } = require('@orbito/api-lib');

Templates.registerVarGroup(code, definition)   // -> Promise<void>

Rdzeń zapisuje schemat grup (wszystko poza handler) w trwałym indeksie per wersja modułu, więc edytor pokazuje zmienne, waliduje szablon i renderuje podgląd bez pytania kontenerów. Handler jest wołany dopiero przy wysyłce prawdziwej wiadomości.

Jak zmienne wyglądają w szablonie

Ścieżka zmiennej to <kodModułu>:<kodGrupy>.<kluczPola> i zawsze ma prefiks modułu:

<p>Dzień dobry {{ core/orders:customer.fullName }},</p>
<p>zamówienie {{ core/orders:order.number }} czeka na płatność: {{ core/orders:order.total-to-pay }} {{ core/orders:order.currency }}.</p>

<ul>
{{#core/orders:order.items}}
    <li>{{ name }} x {{ quantity }}</li>
{{/core/orders:order.items}}
</ul>
{{^core/orders:order.items}}<p>Brak pozycji.</p>{{/core/orders:order.items}}

<p>Numer przesyłki: {{ core/shipping:shipment.trackingNumber }}</p>
  • Pole listy (type: 'list') używa się jako sekcji mustache {{#…}}…{{/…}}. Wewnątrz sekcji pola elementu podajesz krótko ({{ name }}, bez prefiksu). Sekcja odwrócona {{^…}}…{{/…}} renderuje się, gdy lista jest pusta.
  • Zmienne działają w temacie i w treści HTML szablonu. Wartości w HTML są escapowane; temat to zwykły tekst.
  • Zmienna spoza zadeklarowanych grup, lista użyta jak zwykła wartość oraz pole elementu poza sekcją listy to błąd walidacji – edytor nie zapisze takiego szablonu.
  • Zakazane są tagi wstawiające nieescapowany HTML lub zmieniające składnię: {{{ }}}, {{& }}, {{> }} (partiale) i {{= =}}.
  • Obiekt lub lista wstawione przez {{ }} renderują się jako pusty tekst.

Templates.registerVarGroup

await Templates.registerVarGroup('customer', {
    name: { pl: 'Klient', en: 'Customer' },
    requires: ['ORDER_ID'],
    fields: {
        email: { name: 'E-mail', format: 'email', example: 'jan@example.com' },
        fullName: { name: { pl: 'Imię i nazwisko', en: 'Full name' }, example: 'Jan Kowalski' }
    },
    handler: async (params) => {
        const order = await Order.findOne({ _id: params.ORDER_ID });
        return { email: order.customer.email, fullName: order.customer.name };
    }
});
Parametr / poleTypWymaganeOpis
code (1. argument)stringtakKod grupy, ^[a-z][a-z0-9_-]*$. Środkowy człon ścieżki moduł:grupa.pole, więc bez kropek i dwukropków
namestring lub { pl, en }takNazwa grupy w edytorze
requiresstring[]nieKlucze zdarzenia potrzebne handlerowi (domyślnie []). Format i reguła prefiksów jak w emits i requires automatyzacji – patrz niżej
fieldsobjecttakNiepusta mapa pól. Klucz pola: ^[A-Za-z][A-Za-z0-9_-]*$ (np. total-to-pay)
fields.*.namestring lub { pl, en }takNazwa pola w pickerze edytora
fields.*.typestringnie'string' (domyślnie), 'number', 'boolean', 'date', 'list'
fields.*.formatstringniePodpowiedź wyświetlania, np. 'email', 'url'
fields.*.exampledowolnynieWartość do podglądu w edytorze; dla listy tablica przykładowych elementów
fields.*.fieldsobjecttylko dla 'list'Pola elementu listy, te same reguły
handlerasync (params) => objecttakZwraca wartości pól grupy – patrz niżej

Definicja jest sprawdzana lokalnie przed wysłaniem: błędna rzuca wyjątkiem (odrzuca Promise), tak jak Automation.registerAction. Rdzeń waliduje schemat jeszcze raz i przy odrzuceniu też zwraca błąd do modułu. Ponowna rejestracja tego samego kodu podmienia grupę, a po ponownym połączeniu z rdzeniem SDK wysyła grupy automatycznie. Rejestruj grupy przy starcie modułu, obok akcji i triggerów.

Klucze requires

requires mówi, jakich wartości zdarzenia automatyzacji potrzebuje handler (np. ORDER_ID). Obowiązuje ta sama reguła prefiksów co w Automation: klucz bez prefiksu należy do modułu deklarującego grupę (ORDER_ID w core/orders to core/orders:ORDER_ID), a klucz innego modułu musi mieć prefiks ('core/orders:ORDER_ID').

Rdzeń pilnuje, żeby trigger automatyzacji emitował wszystkie klucze z requires grup użytych w szablonie. Automatyzacja z niepasującym triggerem nie zapisze się (422), a edycja szablonu, po której jakaś automatyzacja przestaje pasować, zwraca ostrzeżenie z jej listą.

Kontrakt handlera

handler: async (params) => ({ email: '...', fullName: '...' })
  • params zawiera wartości kluczy z requires: pełne klucze (params['core/orders:ORDER_ID']), a klucze własnego modułu także krótko (params.ORDER_ID) – jak ctx.event.params akcji.
  • Zwróć obiekt z kluczami pól grupy. Listy jako tablice obiektów o kluczach z fields listy. Pole, którego handler nie zwróci, renderuje się pusto.
  • Zwracaj wartości gotowe do wstawienia do wiadomości (np. kwotę jako tekst '149.97', datę w strefie firmy) – rdzeń niczego nie formatuje. Funkcje są pomijane, a obiekty klas (Date, ObjectId) zamieniane na tekst.
  • Wyjątek albo wynik inny niż obiekt to błąd grupy (patrz Błędy).
  • Handler nie dostaje companyId ani instanceId – tak jak reszta modułu (patrz Kontekst firmy i instancji).

Podgląd z przykładów

Edytor renderuje podgląd z wartości example – bez wołania modułów. Podaj więc realistyczny example dla każdego pola. Pole bez przykładu pokazuje w podglądzie placeholder [klucz], a lista bez przykładu jeden element złożony z takich placeholderów.

Błędy i problemy

Błąd jednej grupy nie zatrzymuje wysyłki. Gdy rdzeń nie może pobrać wartości grupy, grupa renderuje się pusto, a firma dostaje wpis w rejestrze problemów z kodem MESSAGE_TEMPLATE_VAR_UNRESOLVED i linkiem do szablonu (albo do automatyzacji, gdy chodzi o parametr TEMPLATE_VAR). Powody:

  • handler rzucił wyjątek lub zwrócił coś innego niż obiekt (treść błędu trafia do opisu problemu),
  • grupa nie jest już dostępna (moduł odinstalowany albo grupa wycofana po zapisie szablonu),
  • nie da się wybrać instancji modułu (AMBIGUOUS_MODULE_TARGET, patrz niżej).

Wiadomości nie da się natomiast wysłać, gdy szablon został usunięty albo zdarzenie nie niesie kluczy z requires użytych grup. Akcja send-template-email kończy się wtedy błędem, a firma dostaje problem AUTOMATION_TEMPLATE_EMAIL_NOT_SENT na automatyzacji.

Wybór instancji modułu

Firma może mieć kilka instancji tego samego modułu, a handler działa w kontenerze jednej z nich. Rdzeń wybiera ją tak:

  1. Jeśli grupa należy do tego samego modułu, którego trigger uruchomił automatyzację, pytana jest instancja wskazana w tym triggerze (__instanceId) – czyli ta, która wyemitowała zdarzenie.
  2. W pozostałych przypadkach pytana jest jedyna włączona instancja modułu grupy. Przy kilku włączonych rdzeń zgłasza błąd AMBIGUOUS_MODULE_TARGET i problem, a grupa renderuje się pusto.

Przykład: trigger core/shipping z zamówieniem i przesyłką – grupa core/shipping:shipment pyta instancję, która wyemitowała zdarzenie, a grupa core/orders:order jedyną instancję core/orders.

Zmienne w parametrach akcji

Zmienną skalarną można też wybrać w parametrze akcji typu PropertyType.TEMPLATE_VAR – rdzeń rozwiązuje ją przed execute, więc akcja dostaje gotowy tekst. Szczegóły w Automation.

Pełny przykład

Moduł core/orders udostępnia grupy customer i order (z listą pozycji), a core/shipping grupę shipment.

// core/orders – src/template-vars.js
const { Templates } = require('@orbito/api-lib');
const Order = require('./models/Order');

async function findOrder(params) {
    // ORDER_ID to klucz własny modułu, więc handler dostaje go także krótko
    const order = await Order.findOne({ _id: params.ORDER_ID });
    if (!order) throw new Error(`Order ${params.ORDER_ID} not found`);
    return order;
}

async function registerOrderTemplateVars() {
    await Templates.registerVarGroup('customer', {
        name: { pl: 'Klient', en: 'Customer' },
        requires: ['ORDER_ID'],
        fields: {
            email: { name: 'E-mail', format: 'email', example: 'jan.kowalski@example.com' },
            fullName: { name: { pl: 'Imię i nazwisko', en: 'Full name' }, example: 'Jan Kowalski' }
        },
        handler: async (params) => {
            const order = await findOrder(params);
            return { email: order.customer.email || '', fullName: order.customer.name || '' };
        }
    });

    await Templates.registerVarGroup('order', {
        name: { pl: 'Zamówienie', en: 'Order' },
        requires: ['ORDER_ID'],
        fields: {
            number: { name: { pl: 'Numer', en: 'Number' }, example: 'K7P-2QX' },
            'total-to-pay': { name: { pl: 'Do zapłaty', en: 'To pay' }, example: '149.97' },
            items: {
                name: { pl: 'Pozycje', en: 'Items' },
                type: 'list',
                fields: {
                    name: { name: { pl: 'Nazwa', en: 'Name' }, example: 'Kubek ceramiczny' },
                    quantity: { name: { pl: 'Ilość', en: 'Quantity' }, type: 'number', example: 2 }
                },
                example: [{ name: 'Kubek ceramiczny', quantity: 2 }]
            }
        },
        handler: async (params) => {
            const order = await findOrder(params);
            return {
                number: order.name,
                'total-to-pay': order.toPay.toFixed(2),
                items: order.products.map(product => ({ name: product.name, quantity: product.qty }))
            };
        }
    });
}

module.exports = { registerOrderTemplateVars };
// core/shipping – src/template-vars.js
const { Templates } = require('@orbito/api-lib');
const { findShipmentById } = require('./shipments');

async function registerTemplateVars() {
    await Templates.registerVarGroup('shipment', {
        name: { pl: 'Przesyłka', en: 'Shipment' },
        requires: ['SHIPMENT_ID'],
        fields: {
            trackingNumber: {
                name: { pl: 'Numer śledzenia', en: 'Tracking number' },
                example: '1000634582412U'
            },
            trackingUrl: {
                name: { pl: 'Link do śledzenia', en: 'Tracking link' },
                format: 'url',
                example: 'https://tracktrace.example.com/1000634582412U'
            }
        },
        handler: async (params) => {
            const shipment = await findShipmentById(params.SHIPMENT_ID);
            return { trackingNumber: shipment.trackNo || '', trackingUrl: shipment.trackingUrl || '' };
        }
    });
}

module.exports = { registerTemplateVars };

Szablon używający {{ core/orders:customer.email }} i {{ core/shipping:shipment.trackingNumber }} da się podpiąć tylko pod trigger, który emituje core/orders:ORDER_ID i core/shipping:SHIPMENT_ID (np. „Nowa przesyłka” z modułu core/shipping).

Ostatnio zaktualizowano:: 14.10.2026, 10:36
Contributors: Wiktor Koźmiński, Claude Fable 5.1
Prev
Mail
Next
Lock