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 / pole | Typ | Wymagane | Opis |
|---|---|---|---|
code (1. argument) | string | tak | Kod grupy, ^[a-z][a-z0-9_-]*$. Środkowy człon ścieżki moduł:grupa.pole, więc bez kropek i dwukropków |
name | string lub { pl, en } | tak | Nazwa grupy w edytorze |
requires | string[] | nie | Klucze zdarzenia potrzebne handlerowi (domyślnie []). Format i reguła prefiksów jak w emits i requires automatyzacji – patrz niżej |
fields | object | tak | Niepusta mapa pól. Klucz pola: ^[A-Za-z][A-Za-z0-9_-]*$ (np. total-to-pay) |
fields.*.name | string lub { pl, en } | tak | Nazwa pola w pickerze edytora |
fields.*.type | string | nie | 'string' (domyślnie), 'number', 'boolean', 'date', 'list' |
fields.*.format | string | nie | Podpowiedź wyświetlania, np. 'email', 'url' |
fields.*.example | dowolny | nie | Wartość do podglądu w edytorze; dla listy tablica przykładowych elementów |
fields.*.fields | object | tylko dla 'list' | Pola elementu listy, te same reguły |
handler | async (params) => object | tak | Zwraca 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: '...' })
paramszawiera wartości kluczy zrequires: pełne klucze (params['core/orders:ORDER_ID']), a klucze własnego modułu także krótko (params.ORDER_ID) – jakctx.event.paramsakcji.- Zwróć obiekt z kluczami pól grupy. Listy jako tablice obiektów o kluczach z
fieldslisty. 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
companyIdaniinstanceId– 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:
- 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. - W pozostałych przypadkach pytana jest jedyna włączona instancja modułu grupy. Przy kilku włączonych rdzeń zgłasza błąd
AMBIGUOUS_MODULE_TARGETi 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).
