Automation
Moduł Automation umożliwia modułowi integrację z silnikiem automatyzacji Orbito. Pozwala rejestrować akcje (operacje do wykonania) oraz triggery (zdarzenia uruchamiające automatyzacje), a także wywoływać triggery w odpowiedzi na zdarzenia zachodzące w module.
const { Automation } = require('@orbito/api-lib');
Koncepcja
Silnik automatyzacji Orbito działa na zasadzie trigger → akcja:
- Trigger – zdarzenie, które uruchamia automatyzację (np. zmiana statusu zamówienia, nowe zgłoszenie). Moduł rejestruje trigger i wywołuje go, gdy odpowiednie zdarzenie nastąpi.
- Akcja – operacja wykonywana po uruchomieniu automatyzacji (np. wysyłka e-maila, zmiana statusu). Moduł rejestruje akcję, a silnik wywołuje ją z parametrami skonfigurowanymi przez użytkownika.
Automatyzacje a instancje modułu
Firma może mieć kilka instalacji (instancji) tego samego modułu – np. trzy sklepy Prestashop. Schemat akcji i triggera jest wspólny dla całego modułu, ale pojedyncza automatyzacja działa zawsze na jednej instancji.
Dlatego platforma dokłada do schematu parametrów pole __instanceId – listę aktywnych instancji modułu w danej firmie. Użytkownik wybiera instancję jako pierwszy parametr akcji lub triggera, a platforma:
- kieruje
execute,nameGenerator,enableidisablewyłącznie do kontenera wybranej instancji, - usuwa
__instanceIdz parametrów przed wysłaniem ich do modułu – Twój handler dostaje wyłącznie własne parametry i nie musi (ani nie może) obsługiwać wyboru instancji.
__instanceId to nazwa zarezerwowana
Parametr o nazwie __instanceId we własnym schemacie przesłoniłby pole wstrzykiwane przez platformę i zepsuł kierowanie do instancji, dlatego registerAction i registerTrigger odrzucają taki schemat wyjątkiem już przy starcie kontenera.
Automatyzacje sprzed multi-instancji
Automatyzacje zapisane zanim moduł mógł mieć kilka instancji nie mają __instanceId w parametrach. Dopóki firma ma jedną aktywną instancję modułu, działają bez zmian; przy większej liczbie instancji platforma zgłasza błąd AMBIGUOUS_MODULE_TARGET – wtedy trzeba w edytorze wskazać instancję. Szerzej: Kontekst firmy i instancji.
Typy danych
Automation.PropertyType
Określa typ właściwości w schemacie parametrów:
| Stała | Wartość | Opis |
|---|---|---|
PropertyType.STRING | 'string' | Ciąg znaków |
PropertyType.NUMBER | 'number' | Liczba |
PropertyType.BOOLEAN | 'boolean' | Wartość logiczna |
PropertyType.DATETIME | 'datetime' | Data i czas |
PropertyType.ARRAY | 'array' | Tablica (wymaga pola of) |
PropertyType.TEMPLATE_VAR | 'template_var' | Pełna ścieżka zmiennej szablonu (od SDK 4.0.0) – patrz Parametry ze zmiennymi i szablonami |
Automation.DisplayFormat
Opcjonalne wskazówki dla interfejsu użytkownika dotyczące wyświetlania i walidacji wartości:
| Stała | Wartość | Opis |
|---|---|---|
DisplayFormat.EMAIL | 'email' | Adres e-mail |
DisplayFormat.SECRET | 'secret' | Dane wrażliwe – maskowane w UI |
DisplayFormat.CRON | 'cron' | Wyrażenie cron – specjalny edytor w UI |
DisplayFormat.URL | 'url' | Adres URL |
DisplayFormat.PHONE | 'phone' | Numer telefonu |
DisplayFormat.TEXT | 'text' | Długi tekst – textarea w UI |
DisplayFormat.HTML | 'html' | Treść HTML – sanityzowana przed renderowaniem |
DisplayFormat.MARKDOWN | 'markdown' | Treść Markdown – sanityzowana przed renderowaniem |
DisplayFormat.MESSAGE_TEMPLATE | 'message_template' | Identyfikator szablonu wiadomości firmy – lista szablonów w UI (od SDK 4.0.0, dla STRING) |
DisplayFormat.INTEGER | 'integer' | Liczba całkowita |
DisplayFormat.DECIMAL | 'decimal' | Liczba dziesiętna |
DisplayFormat.POSITIVE_INTEGER | 'positive_integer' | Liczba całkowita większa od 0 |
DisplayFormat.NON_NEGATIVE_INTEGER | 'non_negative_integer' | Liczba całkowita ≥ 0 |
DisplayFormat.POSITIVE_DECIMAL | 'positive_decimal' | Liczba dziesiętna większa od 0 |
DisplayFormat.NON_NEGATIVE_DECIMAL | 'non_negative_decimal' | Liczba dziesiętna ≥ 0 |
DisplayFormat.DATE_ONLY | 'date_only' | Tylko data (dla DATETIME) |
DisplayFormat.TIME_ONLY | 'time_only' | Tylko czas (dla DATETIME) |
Schemat parametrów
Parametry akcji i triggerów definiuje się jako obiekt, gdzie każdy klucz to nazwa parametru:
{
nazwaParametru: {
type: Automation.PropertyType.STRING, // wymagane – typ wartości
of: Automation.PropertyType.STRING, // wymagane gdy type === ARRAY – typ elementów tablicy
format: Automation.DisplayFormat.EMAIL, // opcjonalne – wskazówka UI/walidacyjna
// opcjonalne – lista opcji (dropdown)
options: {
// opcje statyczne
values: [
{ id: 'value1', label: 'Etykieta 1' },
{ id: 'value2', label: 'Etykieta 2' }
],
// opcje dynamiczne (ładowane z API modułu)
module: 'core/orders', // moduł dostarczający opcje
method: 'status.search', // metoda do wywołania
params: { onlyActive: true }, // dodatkowe parametry zapytania
responsePath: 'results', // ścieżka do tablicy wyników w odpowiedzi API
idKey: '_id', // klucz identyfikatora opcji (domyślnie 'id')
labelKey: 'name' // klucz etykiety opcji (domyślnie 'label')
},
name: { en: 'Parameter name', pl: 'Nazwa parametru' }, // lub string
required: true
}
}
Opcje dynamiczne vs statyczne
Możesz podać jednocześnie values i pola dynamiczne (module, method). Możesz też podać wyłącznie values dla statycznych dropdownów lub wyłącznie pola dynamiczne.
Parametry ze zmiennymi i szablonami
Od SDK 4.0.0 parametr akcji może wskazywać zmienną albo szablon wiadomości firmy. Rdzeń obsługuje je po typie lub formacie schematu, dla każdej akcji.
params: {
templateId: {
type: Automation.PropertyType.STRING,
format: Automation.DisplayFormat.MESSAGE_TEMPLATE,
name: { en: 'Message template', pl: 'Szablon wiadomości' },
required: true
},
customerEmail: {
type: Automation.PropertyType.TEMPLATE_VAR,
name: { en: 'Customer e-mail', pl: 'E-mail klienta' }
}
}
| Parametr | Co przechowuje | Co dostaje execute |
|---|---|---|
format: DisplayFormat.MESSAGE_TEMPLATE | identyfikator szablonu wiadomości firmy (w UI lista szablonów) | identyfikator – szablon renderuje rdzeń (wbudowana akcja send-template-email) |
type: PropertyType.TEMPLATE_VAR | pełna ścieżka zmiennej skalarnej, np. core/orders:customer.email (w UI wybór spośród zmiennych dostępnych dla triggera) | gotową wartość jako tekst – rdzeń rozwiązuje zmienną przed execute; gdy się nie da, tekst jest pusty i firma dostaje problem |
Zmienne pochodzą z grup deklarowanych przez moduły – patrz Templates. Przy zapisie włączonej automatyzacji rdzeń sprawdza, że zmienna lub szablon istnieje i że trigger emituje klucze wymagane przez ich grupy (requires grupy). Błąd kończy się odpowiedzią 422 z listą błędów per akcja i parametr (validation.paramErrors); wyłączona automatyzacja (szkic) zapisuje się bez sprawdzania.
Klucze zdarzeń i prefiksy modułów
Od SDK 4.0.0. Trigger przekazuje akcjom zmienne zdarzenia (emits), a akcja deklaruje, których potrzebuje (requires). Zmienną może wyemitować trigger jednego modułu, a przeczytać akcja innego, dlatego klucz ma format KEY albo <kodModułu>:KEY:
| Klucz | Znaczenie | Gdzie |
|---|---|---|
ORDER_ID (bez prefiksu) | klucz własny – należy do modułu, który go deklaruje; w core/orders to core/orders:ORDER_ID | emits, requires |
'core/orders:ORDER_ID' | klucz obcy – prefiks jest obowiązkowy | emits triggera i requires akcji innego modułu |
Rdzeń przechowuje parametry zdarzenia wyłącznie z pełnymi kluczami, a SDK sprawdza tylko format (błędny rzuca przy rejestracji). Dwa klucze, które po dopisaniu prefiksu dają ten sam pełny klucz (np. ORDER_ID i 'core/orders:ORDER_ID' w core/orders), to błąd rejestracji elementu.
// core/shipping – id zamówienia należy do core/orders, id przesyłki jest własne
Automation.registerTrigger('shipment_created', {
displayName: 'Nowa przesyłka',
emits: {
'core/orders:ORDER_ID': { type: 'ObjectId', name: 'Zamówienie' },
SHIPMENT_ID: { type: 'ObjectId', name: 'Przesyłka' }
},
enable, disable, clear
});
// core/wfirma – akcja potrzebuje klucza z core/orders
Automation.registerAction('issue_document', {
displayName: 'Wystaw dokument',
requires: ['core/orders:ORDER_ID'],
execute: async (params, ctx) => issue(ctx.event.params['core/orders:ORDER_ID'])
});
ctx.event.params niesie pełne klucze. Klucze modułu, do którego należy akcja, są dodatkowo dostępne w formie krótkiej (alias), a klucze obce tylko z prefiksem:
| Akcja w module | params['core/orders:ORDER_ID'] | params.ORDER_ID |
|---|---|---|
core/orders | tak | tak (alias) |
core/wfirma | tak | nie |
Automation.callTrigger przyjmuje parametry pod tymi samymi kluczami, pod którymi zadeklarowałeś je w emits (w przykładzie wyżej 'core/orders:ORDER_ID' i SHIPMENT_ID).
Runtime: akcja uruchamia się tylko wtedy, gdy zdarzenie niesie wszystkie klucze z jej requires (porównanie na pełnych kluczach); w przeciwnym razie jest pomijana, a firma dostaje wpis w rejestrze problemów (AUTOMATION_ACTION_SKIPPED).
Migracja modułu na SDK 4.0.0 (zmiana niekompatybilna)
Nie ma trybu zgodności – klucz obcy bez prefiksu przestaje pasować. W module:
- W
emitstriggerów irequiresakcji dopisz prefiks do każdego klucza innego modułu (np.ORDER_IDwcore/shipping→'core/orders:ORDER_ID'). Własne klucze zostają krótkie. - Wywołania
callTriggerwysyłaj pod tymi samymi kluczami, co wemits. - Odczyty
ctx.event.paramskluczy obcych zmień na formę z prefiksem (ctx.event.params['core/orders:ORDER_ID']). Własne klucze czytane krótko działają dalej.
Teksty użytkownika w wbudowanej akcji send-email
W parametrach send-email (szablon mustache) krótki klucz, np. {{ $event.ORDER_ID }}, działa dalej, o ile jest jednoznaczny wśród kluczy zdarzenia. Zapis pełny to {{ $event.core/orders:ORDER_ID }}.
Automation.registerAction
Rejestruje akcję, którą użytkownik może wybrać w kreatorze automatyzacji. Silnik wywoła ją z parametrami skonfigurowanymi przez użytkownika.
Automation.registerAction(actionCode, definition)
| Parametr | Typ | Opis |
|---|---|---|
actionCode | string | Unikalny identyfikator akcji w obrębie modułu |
definition | object | Definicja akcji (patrz niżej) |
Definicja akcji
| Pole | Typ | Wymagane | Opis |
|---|---|---|---|
displayName | string | tak | Nazwa wyświetlana w kreatorze automatyzacji |
desc | string | nie | Opis akcji |
params | object | nie | Schemat parametrów konfigurowanych przez użytkownika |
outputs | object[] | nie | Lista wyjść (gałęzi) akcji – patrz Branchowanie |
requires | string[] | nie | Klucze zdarzenia wymagane przez akcję (emitowane przez trigger): własne krótko (['ORDER_ID']), obce z prefiksem modułu (['core/orders:ORDER_ID']) – patrz Klucze zdarzeń |
execute | Function | tak | Asynchroniczna funkcja wykonująca akcję |
nameGenerator | Function | nie | Funkcja generująca czytelną nazwę instancji akcji na podstawie parametrów |
Branchowanie
Akcje obsługują branchowanie – możliwość określenia, którą ścieżkę grafu automatyzacji należy wybrać po wykonaniu akcji. Użytkownik łączy akcje ze sobą w edytorze graficznym, a każde wyjście (branch) odpowiada innemu połączeniu.
Aby akcja miała wiele wyjść, dodaj pole outputs do jej definicji:
outputs: [
{ branch: 'true', label: 'Tak' },
{ branch: 'false', label: 'Nie' }
]
| Pole | Typ | Opis |
|---|---|---|
branch | string | Identyfikator gałęzi – wartość zwracana przez execute |
label | string | Etykieta wyświetlana w edytorze graficznym |
Funkcja execute powinna zwrócić kod gałęzi (branch), która ma zostać wybrana:
return 'true'; // wybierz gałąź "Tak"
return 'false'; // wybierz gałąź "Nie"
Jeśli akcja nie ma pola outputs (domyślny przypadek), execute może zwrócić dowolną wartość lub null – silnik automatyzacji przejdzie do kolejnej akcji w grafie.
Funkcja execute
execute: async (params, ctx) => { ... }
| Argument | Typ | Opis |
|---|---|---|
params | object | Parametry skonfigurowane przez użytkownika |
ctx | object | Kontekst wykonania |
Kontekst ctx:
| Pole | Typ | Opis |
|---|---|---|
ctx.automationId | string | Identyfikator automatyzacji |
ctx.event.params | object | Zmienne wyemitowane przez trigger: pełne klucze (core/orders:ORDER_ID), a klucze własnego modułu akcji także krótko (ORDER_ID) – patrz Klucze zdarzeń |
Funkcja nameGenerator
Generuje czytelną nazwę instancji akcji wyświetlaną w panelu automatyzacji.
nameGenerator: async (lang, params) => {
// zwróć string z nazwą lub null/undefined gdy nie można wygenerować
}
| Argument | Typ | Opis |
|---|---|---|
lang | string | Język interfejsu (np. 'pl', 'en') |
params | object | Aktualne parametry akcji |
Brak companyId i instanceId
Ani ctx, ani params nie zawierają companyId ani instanceId – kontener obsługuje jedną firmę i jedną instancję, a platforma kieruje wywołania na podstawie tożsamości połączenia. Wywołanie trafia tylko do instancji wybranej w automatyzacji; własną tożsamość odczytasz przez Core.getCompanyId() / Core.getInstanceId(). Szczegóły w Kontekst firmy i instancji.
Przykład
Akcja modułu core/orders, więc ORDER_ID to jej klucz własny (krótki).
const { Log, Core, Automation, DB } = require('@orbito/api-lib');
const OrderStatus = require('./src/models/OrderStatus');
const Order = require('./src/models/Order');
Automation.registerAction('change_status', {
displayName: "Zmień status zamówienia",
desc: "Użyj tej akcji, aby zmienić status zamówienia na inny",
params: {
newStatusId: {
type: Automation.PropertyType.STRING,
options: {
module: 'core/orders',
method: 'status.search',
params: { onlyActive: true },
responsePath: 'results',
idKey: '_id',
labelKey: 'name'
},
name: { en: 'New Status', pl: 'Nowy status' },
required: true
}
},
requires: ['ORDER_ID'],
execute: async (params, ctx) => {
if (!params.newStatusId) {
throw new Error('Missing values');
}
const orderId = ctx.event.params.ORDER_ID || ctx.event.params.orderId || ctx.event.params._id;
const order = await Order.findOne({ _id: orderId });
if (!order) {
throw new Error('Order not found');
}
if (order.statusId + '' == params.newStatusId + '') {
return null; // status już ustawiony, nic nie rób
}
return await Core.callModule('core/orders', 'updateStatus',
{ orderId: order._id, statusId: params.newStatusId },
{ automationId: ctx.automationId }
);
},
nameGenerator: async (lang, params) => {
const { newStatusId } = params;
if (!newStatusId) return 'Invalid parameters';
const status = await OrderStatus.findOne({ _id: newStatusId });
if (!status) return 'Status not found';
return `Change order status to ${status.name}`;
}
});
Przykład akcji z branchowaniem
Poniższy przykład pokazuje akcję sprawdzającą wartość zmiennej automatyzacji i wybierającą gałąź na podstawie wyniku:
Automation.registerAction('check_variable', {
displayName: "Warunek zmiennej automatyzacji",
desc: "Sprawdza wartość zmiennej automatyzacji",
outputs: [
{ branch: 'true', label: 'Tak' },
{ branch: 'false', label: 'Nie' }
],
params: {
key: {
type: Automation.PropertyType.STRING,
name: "Klucz zmiennej do sprawdzenia",
required: true
},
checkOperation: {
type: Automation.PropertyType.STRING,
name: "Operacja sprawdzania",
required: true,
options: {
values: [
{ id: 'equals', label: 'Musi być takie samo' },
{ id: 'includes', label: 'Musi zawierać słowo' },
{ id: 'not_equals', label: 'Nie może być takie samo' }
]
}
},
valueToCheck: {
type: Automation.PropertyType.STRING,
name: "Wartość do sprawdzenia",
required: true
}
},
nameGenerator: async (lang, params) => {
let operationLabel = 'jest taka sama jak';
if (params.checkOperation === 'includes') operationLabel = 'zawiera frazę';
if (params.checkOperation === 'not_equals') operationLabel = 'jest inna niż';
return `Czy zmienna "${params.key}" ${operationLabel} "${params.valueToCheck}"?`;
},
execute: async (params, ctx) => {
if (!params.key || !params.valueToCheck) {
throw new Error('Missing key or value to check');
}
const record = await AutomationVariable.findOne({ key: params.key.trim() });
const varValue = record ? record.value : '';
let conditionMet = false;
if (params.checkOperation === 'equals') {
conditionMet = (varValue === params.valueToCheck);
} else if (params.checkOperation === 'includes') {
conditionMet = varValue && varValue.includes(params.valueToCheck);
} else if (params.checkOperation === 'not_equals') {
conditionMet = (varValue !== params.valueToCheck);
}
return conditionMet ? 'true' : 'false';
}
});
Akcje czekające (pauza automatyzacji)
Czasem akcja nie może zakończyć się od razu, bo czeka, aż w systemie zajdzie jakieś zdarzenie (np. aż do zamówienia powstanie przesyłka). Zamiast odpytywać w pętli wewnątrz execute (blokując kontener), akcja może zapauzować cały bieg automatyzacji i oddać sterowanie silnikowi.
Służy do tego Automation.PauseSignal oraz dwa helpery, które go rzucają:
const { Automation } = require('@orbito/api-lib');
Automation.waitAndRetry(seconds); // pauza + ponowne uruchomienie TEJ SAMEJ akcji
Automation.delay(seconds); // pauza + przejście do kolejnego kroku
Obie funkcje nigdy nie zwracają wartości – zawsze rzucają PauseSignal, więc wystarczy je wywołać (bez return) w dowolnym miejscu wewnątrz execute.
Gdy execute rzuci PauseSignal, silnik automatyzacji zapisuje bieg ze statusem paused i polem resumeAt (moment wznowienia), a scheduler sprawdza wygasłe pauzy co 10 sekund i wznawia bieg przez kolejkę firmy. Wznowienie trafia do tej samej instancji modułu co pierwsze wywołanie – routing po __instanceId nie zmienia się.
| Helper | Co się dzieje po czasie | Kiedy używać |
|---|---|---|
Automation.waitAndRetry(seconds) | Silnik uruchamia od nowa całą akcję (execute z tymi samymi parametrami) | Czekanie, aż warunek zewnętrzny stanie się prawdziwy (np. pojawi się przesyłka) |
Automation.delay(seconds) | Silnik przechodzi do kolejnego kroku grafu, bez ponownego wywołania execute | Zwykłe opóźnienie w automatyzacji (odczekaj X, potem rób dalej) |
execute musi być idempotentne
waitAndRetry uruchamia całą funkcję execute od początku, nie tylko sprawdzenie warunku. Jeśli akcja ma efekt uboczny (np. wysyła e-mail) przed sprawdzeniem warunku, ten efekt wykona się ponownie przy każdej kolejnej próbie. Efekty uboczne umieszczaj dopiero po potwierdzeniu warunku, jak najbliżej return.
Rerun a historia biegu
Próba, która ponownie kończy się pauzą (waitAndRetry), nie jest dopisywana jako osobny krok do historii biegu – historia nie puchnie przy wielu podejściach. Krok trafia do historii dopiero wtedy, gdy akcja faktycznie się zakończy (zwróci wynik albo rzuci błąd).
Nie ma wbudowanego limitu czasu ani specjalnej gałęzi timeout – bieg wisi w statusie paused tak długo, jak akcja prosi o kolejny rerun. Dokument AutomationRun ma w bazie TTL (3 dni), ale każda pauza przesuwa go co najmniej do czasu wznowienia + TTL, a każde wznowienie liczy go od nowa – bieg, który regularnie prosi o kolejną próbę, nie wygasa. Bieg kończy się dopiero, gdy: akcja przestanie prosić o rerun (zwróci wynik albo rzuci błąd), automatyzacja zostanie wyłączona (wtedy wznowienie przestawia bieg na aborted) albo ktoś zainterweniuje ręcznie.
Jeśli akcja ma się poddać po pewnym czasie, musi to zrobić sama, licząc czas od ctx.createdAt (moment startu biegu). Kontekst leci do modułu jako JSON, więc ctx.createdAt to string ISO, nie obiekt Date – trzeba go najpierw opakować:
const waitingSince = Date.now() - new Date(ctx.createdAt).getTime();
if (waitingSince > 6 * 60 * 60 * 1000) { // 6h
throw new Error('Shipment did not appear in time');
}
ctx.store nie przeżywa pauzy
Przy każdym wywołaniu execute (również przy rerunie) moduł dostaje kopię kontekstu biegu. Zmiany zapisane w ctx.store podczas zapauzowanej próby nie wracają do rdzenia i giną razem z tą próbą – jeśli akcja musi pamiętać coś między próbami, musi to zapisać we własnych danych (np. w kolekcji modułu), a nie w ctx.store.
Przykład: wait_for_shipment (moduł core.shipping)
Uproszczona wersja rzeczywistej akcji z modules/core.shipping/src/automations.js:
const { Automation } = require('@orbito/api-lib');
const Shipment = require('./models/Shipment');
const RETRY_INTERVAL_SECONDS = 30;
function resolveOrderId(ctx) {
// id zamówienia należy do core/orders, więc w core/shipping czytamy go z prefiksem
const p = (ctx && ctx.event && ctx.event.params) || {};
return p['core/orders:ORDER_ID'] || null;
}
Automation.registerAction('wait_for_shipment', {
displayName: 'Czekaj na przesyłkę',
desc: 'Wstrzymuje automatyzację, dopóki do zamówienia nie powstanie przesyłka',
icon: 'truck',
outputs: ['next'],
requires: ['core/orders:ORDER_ID'],
execute: async (params, ctx) => {
const orderId = resolveOrderId(ctx);
if (!orderId) throw new Error('Missing core/orders:ORDER_ID');
// Sam warunek, żadnego efektu ubocznego - `execute` musi być idempotentne.
const shipmentCount = await Shipment.count({ orderId: String(orderId) });
if (!shipmentCount) {
Automation.waitAndRetry(RETRY_INTERVAL_SECONDS); // sprawdź ponownie za 30 sekund
}
return true;
},
nameGenerator: async (lang) => (
lang === 'pl' ? 'Czekaj na przesyłkę' : 'Wait for a shipment'
)
});
Automation.registerTrigger
Rejestruje trigger – zdarzenie, które uruchamia automatyzację. Użytkownik konfiguruje trigger w kreatorze, a moduł jest odpowiedzialny za jego aktywację (enable) i dezaktywację (disable) oraz wywołanie go przez Automation.callTrigger.
Automation.registerTrigger(triggerCode, definition)
| Parametr | Typ | Opis |
|---|---|---|
triggerCode | string | Unikalny identyfikator triggera w obrębie modułu |
definition | object | Definicja triggera (patrz niżej) |
Definicja triggera
| Pole | Typ | Wymagane | Opis |
|---|---|---|---|
displayName | string | tak | Nazwa wyświetlana w kreatorze automatyzacji |
desc | string | nie | Opis triggera |
emits | object | nie | Zmienne kontekstowe emitowane przez trigger (dostępne w ctx.event.params akcji); domyślnie {}. Klucze własne krótko, obce z prefiksem modułu – patrz Klucze zdarzeń |
params | object | nie | Schemat parametrów konfigurowanych przez użytkownika |
enable | Function | tak | Wywoływana przy aktywacji triggera dla danej akcji |
disable | Function | tak | Wywoływana przy dezaktywacji triggera |
clear | Function | tak | Wywoływana przez silnik przy przeładowaniu modułu – powinna wyczyścić stan wszystkich listenerów |
Pole emits
Opisuje zmienne, które trigger przekazuje do akcji przez Automation.callTrigger:
emits: {
ORDER_ID: { type: 'ObjectId', name: 'Identyfikator zamówienia' },
ORDER_STATUS: { type: 'ObjectId', name: 'Identyfikator statusu zamówienia' },
}
Ten przykład (trigger modułu core/orders) emituje własne klucze, więc są krótkie. Klucz innego modułu zapisz z prefiksem, np. 'core/orders:ORDER_ID' w triggerze core/shipping.
Funkcja enable
Wywoływana, gdy użytkownik aktywuje automatyzację z tym triggerem. Moduł powinien zarejestrować wewnętrzny listener, który w odpowiednim momencie wywoła Automation.callTrigger.
enable(params, actionId) { ... }
| Argument | Typ | Opis |
|---|---|---|
params | object | Parametry triggera skonfigurowane przez użytkownika |
actionId | string | Identyfikator akcji automatyzacji (unikalny per automatyzacja) |
Kluczowanie listenerów
Listenery kluczuj po actionId (w obrębie danego triggerCode). Wcześniejsze wersje SDK wymagały dodatkowego poziomu per companyId – nie jest już potrzebny, bo kontener obsługuje jedną firmę i jedną instancję. enable dostaje wyłącznie automatyzacje wskazujące tę instancję, a zdarzenie zgłoszone przez Automation.callTrigger platforma przypisuje do niej po tożsamości połączenia.
Funkcja disable
Wywoływana, gdy użytkownik dezaktywuje lub usuwa automatyzację.
disable(actionId) { ... }
| Argument | Typ | Opis |
|---|---|---|
actionId | string | Identyfikator akcji automatyzacji – ten sam, który dostał enable |
Funkcja clear
Wywoływana przez silnik automatyzacji przy przeładowaniu modułu. Powinna wyczyścić stan wszystkich aktywnych listenerów (np. opróżnić słownik).
clear() { ... }
clear a instancje
clear jako jedyna z funkcji triggera trafia do wszystkich połączonych kontenerów modułu w firmie – czyści ulotny stan listenerów.
Przykład
const { Log, Automation } = require('@orbito/api-lib');
// słownik aktywnych listenerów: { [actionId]: { statuses: [...] } }
const statusChangeListeners = {};
Automation.registerTrigger('status_changed', {
displayName: "Status zamówienia zmieniony",
desc: "Ten trigger uruchomi się, gdy status zamówienia zostanie zmieniony",
emits: {
ORDER_ID: { type: 'ObjectId', name: 'Identyfikator zamówienia' },
ORDER_STATUS: { type: 'ObjectId', name: 'Identyfikator statusu zamówienia' },
},
params: {
statuses: {
type: Automation.PropertyType.ARRAY,
of: Automation.PropertyType.STRING,
options: {
module: 'core/orders',
method: 'status.search',
params: { onlyActive: true },
responsePath: 'results',
idKey: '_id',
labelKey: 'name'
},
name: "Statusy powodujące wywołanie akcji",
required: true
}
},
enable(params, actionId) {
if (!params.statuses) {
throw new Error(`Missing statuses param for ${actionId}`);
}
Log.debug("Adding status change listener", { actionId, statuses: params.statuses });
statusChangeListeners[actionId + ''] = {
statuses: params.statuses,
};
},
disable(actionId) {
if (statusChangeListeners[actionId + '']) {
Log.info("Removing status change listener", { actionId });
delete statusChangeListeners[actionId + ''];
}
},
clear() {
for (const actionId of Object.keys(statusChangeListeners)) {
delete statusChangeListeners[actionId];
}
},
});
Automation.callTrigger
Wywołuje trigger dla konkretnej akcji. Należy go wywołać z poziomu hooka lub innego zdarzenia, gdy zaistnieje sytuacja opisana przez trigger.
await Automation.callTrigger(triggerCode, actionId, eventKey, eventParams)
| Parametr | Typ | Opis |
|---|---|---|
triggerCode | string | Kod triggera zarejestrowanego przez registerTrigger |
actionId | string | Identyfikator akcji (z parametru enable) |
eventKey | string | Unikalny klucz zdarzenia – zapobiega wielokrotnemu uruchomieniu tej samej automatyzacji dla tego samego zdarzenia |
eventParams | object | Zmienne kontekstowe przekazywane do akcji. Klucze dokładnie takie, jak w emits (własne krótko, obce z prefiksem, np. { 'core/orders:ORDER_ID': id, SHIPMENT_ID: shipmentId }) |
eventKey
eventKey powinien być unikalny dla każdego zdarzenia. Typowy wzorzec to połączenie kodu zdarzenia z jego identyfikatorem i znacznikiem czasu:
const eventKey = `ORDER_STATUS_${payload.statusId}${Date.now()}`;
Przykład wywołania triggera z hooka
Poniższy przykład pokazuje pełny przepływ: hook nasłuchuje zdarzenia platformy i wywołuje trigger dla każdego aktywnego listenera.
const { Core, Log, Automation } = require('@orbito/api-lib');
Core.registerHook('event:order:status_changed', async (payload) => {
for (const actionId of Object.keys(statusChangeListeners)) {
const listener = statusChangeListeners[actionId];
if (listener.statuses.includes(payload.statusId + '')) {
Log.info("Triggering automation for order status change", { actionId, payload });
const eventKey = `ORDER_STATUS_${payload.statusId}${Date.now()}`;
await Automation.callTrigger('status_changed', actionId, eventKey, {
ORDER_ID: payload.orderId,
ORDER_STATUS: payload.statusId
});
}
}
});
Pełny przykład
Poniżej kompletny przykład modułu rejestrującego zarówno akcję, jak i trigger, wraz z hookiem wywołującym trigger:
const { Log, Core, Automation } = require('@orbito/api-lib');
const OrderStatus = require('./src/models/OrderStatus');
const Order = require('./src/models/Order');
// --- AKCJA ---
Automation.registerAction('change_status', {
displayName: "Zmień status zamówienia",
desc: "Użyj tej akcji, aby zmienić status zamówienia na inny",
requires: ['ORDER_ID'],
params: {
newStatusId: {
type: Automation.PropertyType.STRING,
options: {
module: 'core/orders',
method: 'status.search',
params: { onlyActive: true },
responsePath: 'results',
idKey: '_id',
labelKey: 'name'
},
name: { en: 'New Status', pl: 'Nowy status' },
required: true
}
},
execute: async (params, ctx) => {
if (!params.newStatusId) throw new Error('Missing values');
const orderId = ctx.event.params.ORDER_ID;
const order = await Order.findOne({ _id: orderId });
if (!order) throw new Error('Order not found');
if (order.statusId + '' == params.newStatusId + '') return null;
return await Core.callModule('core/orders', 'updateStatus',
{ orderId: order._id, statusId: params.newStatusId },
{ automationId: ctx.automationId }
);
},
nameGenerator: async (lang, params) => {
const status = await OrderStatus.findOne({ _id: params.newStatusId });
return status ? `Change order status to ${status.name}` : 'Invalid parameters';
}
});
// --- TRIGGER ---
const statusChangeListeners = {};
Automation.registerTrigger('status_changed', {
displayName: "Status zamówienia zmieniony",
emits: {
ORDER_ID: { type: 'ObjectId', name: 'Identyfikator zamówienia' },
ORDER_STATUS: { type: 'ObjectId', name: 'Identyfikator statusu zamówienia' },
},
params: {
statuses: {
type: Automation.PropertyType.ARRAY,
of: Automation.PropertyType.STRING,
options: {
module: 'core/orders',
method: 'status.search',
params: { onlyActive: true },
responsePath: 'results',
idKey: '_id',
labelKey: 'name'
},
name: "Statusy powodujące wywołanie akcji",
required: true
}
},
enable(params, actionId) {
if (!params.statuses) throw new Error(`Missing statuses param for ${actionId}`);
statusChangeListeners[actionId + ''] = { statuses: params.statuses };
},
disable(actionId) {
delete statusChangeListeners[actionId + ''];
},
clear() {
for (const actionId of Object.keys(statusChangeListeners)) {
delete statusChangeListeners[actionId];
}
},
});
// --- HOOK wywołujący trigger ---
Core.registerHook('event:order:status_changed', async (payload) => {
for (const actionId of Object.keys(statusChangeListeners)) {
const listener = statusChangeListeners[actionId];
if (listener.statuses.includes(payload.statusId + '')) {
const eventKey = `ORDER_STATUS_${payload.statusId}${Date.now()}`;
await Automation.callTrigger('status_changed', actionId, eventKey, {
ORDER_ID: payload.orderId,
ORDER_STATUS: payload.statusId
});
}
}
});
