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

Config

Moduł Config to magazyn konfiguracji modułu przechowywany przez platformę Orbito (od SDK 2.3.0). Rozwiązuje dwa problemy:

  1. Przechowywanie ustawień — klucze API, adresy sklepów, mapowania statusów itd. bez tworzenia własnej kolekcji z jednym dokumentem typu store_config.
  2. Ekran ustawień bez pisania UI — moduł deklaruje bloki konfiguracyjne (registerConfigBlocks), a panel Orbito sam renderuje z nich stronę konfiguracji z formularzami, walidacją, obsługą sekretów i własnymi przyciskami akcji (od SDK 3.3.0). Użytkownik znajdzie ją pod Ustawienia → Moduły → Konfiguruj.
const { Config } = require('@orbito/api-lib');

Izolacja konfiguracji

Konfiguracja jest izolowana per instalacja modułu — scope wynika z tożsamości połączenia, moduł nie ma dostępu do konfiguracji innych firm, innych modułów ani innych instalacji tego samego modułu (dwa sklepy tej samej firmy mają osobne klucze API). Wartości przeżywają wyłączenie/włączenie modułu oraz aktualizację wersji.

Odczyt i zapis wartości

MetodaOpis
Config.get(key, defaultValue?)Zwraca wartość klucza lub defaultValue, gdy klucz nie istnieje (zapisany null jest zwracany jako null)
Config.set(key, value)Zapisuje wartość (upsert). Dowolny typ serializowalny do BSON, max 64 KB
Config.delete(key)Usuwa klucz; zwraca true, jeśli istniał
Config.getAll()Zwraca wszystkie wartości scope'u jako obiekt { klucz: wartość }
Config.forUser(userId)Zwraca te same cztery metody w scope użytkownika
const { Config } = require('@orbito/api-lib');

await Config.set('inpost.api_key', 'abc123');
let apiKey = await Config.get('inpost.api_key');
let mode = await Config.get('inpost.mode', 'sandbox'); // wartość domyślna

// ustawienia per użytkownik (userId zwykle z ctx.userId handlera metody)
await Config.forUser(ctx.userId).set('print_label', true);
let autoPrint = await Config.forUser(ctx.userId).get('print_label', false);

Scope globalny ≠ scope użytkownika

Scope globalny i scope'y per-użytkownik to całkowicie rozłączne przestrzenie kluczy — Config.getAll() nie zwraca wpisów zapisanych przez forUser i odwrotnie.

Większe dane → DB

Config służy do małych wartości (limit 64 KB na wartość). Większe dane trzymaj w DB.

registerConfigBlocks — ekran ustawień modułu

Config.registerConfigBlocks(blocks) deklaruje bloki ustawień modułu. Wywołaj ją raz, przy starcie modułu. Panel Orbito:

  • pokaże przycisk Konfiguruj przy module (bez uruchamiania kontenera),
  • wyrenderuje stronę konfiguracji z zakładką per blok,
  • po zapisie przez użytkownika wywoła Twoją funkcję validate, a gdy przejdzie — sam zapisze wartości do magazynu konfiguracji,
  • wyrenderuje zadeklarowane przez blok akcje jako przyciski przy formularzu.
const { Config } = require('@orbito/api-lib');
const { PropertyType, DisplayFormat } = Config;

await Config.registerConfigBlocks({
  general: {
    title: { pl: 'Połączenie z InPost', en: 'InPost connection' },
    scope: 'global',

    schema: async (actualConfig) => ({
      apiKey: {
        type: PropertyType.STRING,
        format: DisplayFormat.SECRET,
        name: { pl: 'Klucz API', en: 'API key' },
        required: true
      },
      sandbox: {
        type: PropertyType.BOOLEAN,
        name: { pl: 'Tryb testowy (sandbox)', en: 'Sandbox mode' }
      },
      hint: {
        type: PropertyType.INFO,
        name: { pl: 'Klucz API znajdziesz w panelu InPost w zakładce Moje konto → API.' }
      }
    }),

    validate: async (params, raport) => {
      if (!params.apiKey) {
        raport.addError('apiKey', 'Klucz API jest wymagany');
        return;
      }
      const ok = await InpostApi.checkKey(params.apiKey, params.sandbox);
      if (!ok) raport.addError('apiKey', 'Niepoprawny klucz API');
    }
  },

  print: {
    title: { pl: 'Preferencje wydruku', en: 'Print preferences' },
    scope: 'user',
    schema: {
      autoPrint: {
        type: PropertyType.BOOLEAN,
        name: { pl: 'Drukuj etykietę automatycznie po utworzeniu' }
      }
    }
    // validate jest opcjonalne — brak oznacza, że każdy zapis przechodzi
  }
});

Klucz bloku jest prefiksem zmiennych

Wartości zapisane przez formularz lądują pod kluczami blok.pole. Blok general z polem apiKey odczytasz tak:

let apiKey = await Config.get('general.apiKey');

// blok scope: 'user' — czytaj w scope użytkownika z ctx
let autoPrint = await Config.forUser(ctx.userId).get('print.autoPrint', false);

Definicja bloku

PoleTypOpis
titleobject | stringTytuł zakładki jako mapa język → tekst, np. { pl: '...', en: '...' }. en jest wymagane (język zapasowy). Sam string jest traktowany jako { en }
scope'global' | 'user'global (domyślne) — jeden zestaw wartości na firmę; user — każdy użytkownik edytuje własne wartości
schemaobject | functionSchema pól formularza (format niżej) albo async (actualConfig) => schema, gdy zestaw pól zależy od obecnej konfiguracji (np. dodatkowe pola dopiero po podaniu poprawnego klucza API)
validatefunction (opcjonalne)async (params, raport) => void — wywoływane przez platformę przed zapisem; jakikolwiek błąd w raporcie odrzuca zapis
actionsobject | function (opcjonalne)Przyciski akcji renderowane przy formularzu bloku — mapa actionKey → definicja albo async (actualConfig) => actions, gdy zestaw przycisków zależy od konfiguracji (od SDK 3.3.0)

Format schemy (DeclarativeSchema)

Ten sam format co parametry akcji i triggerów automatyzacji — panel renderuje z niego kontrolki formularza. Każde pole:

fieldKey: {
  type: PropertyType.STRING,        // string | number | boolean | datetime | array | info
  format: DisplayFormat.SECRET,     // opcjonalna podpowiedź UI/walidacji
  name: { pl: 'Etykieta pola' },    // string lub mapa język → tekst
  description: { pl: 'Dodatkowy opis pod polem' }, // opcjonalne
  required: true,                   // opcjonalne
  options: { values: [ { id: 'a', label: 'A' } ] } // opcjonalny dropdown (statyczny lub dynamiczny)
}

Najważniejsze DisplayFormat: SECRET, EMAIL, URL, PHONE, TEXT (textarea), HTML, MARKDOWN, CRON, formaty liczbowe (INTEGER, POSITIVE_DECIMAL, ...) oraz DATE_ONLY/TIME_ONLY dla DATETIME.

PropertyType.INFO

Pole typu INFO to czysty tekst informacyjny w formularzu (treść w name/description) — nie ma wartości i nigdy nie jest zapisywane do konfiguracji. Używaj go na instrukcje typu „gdzie znaleźć klucz API".

Domyślnie description jest wyświetlany jako zwykły tekst — HTML od modułu nigdy nie jest interpretowany (znaczniki pokażą się dosłownie), a DisplayFormat.HTML w polu INFO nie działa. Aby uzyskać formatowanie, ustaw format: DisplayFormat.MARKDOWN.

Z DisplayFormat.MARKDOWN opis jest renderowany jako ograniczony markdown:

  • akapity (pusta linia) i nowe linie,
  • **pogrubienie**, *kursywa*, `kod`,
  • nagłówki # i listy - ,
  • tabele: wiersz nagłówka, wiersz separatora | --- | --- | i wiersze danych; \| oznacza dosłowną kreskę w komórce.

Treść jest escapowana przed renderowaniem, więc dane z zewnętrznych API można wstawiać bez ryzyka wstrzyknięcia HTML. Znak | w komórkach musisz jednak sam zamienić na \|, inaczej rozbije tabelę.

warehouses: {
  type: PropertyType.INFO,
  format: DisplayFormat.MARKDOWN,
  name: { pl: 'Dostępne magazyny', en: 'Available warehouses' },
  description: {
    pl: '| ID | Nazwa |\n| --- | --- |\n| 1 | Magazyn główny |\n| 2 | Magazyn zwrotów |',
    en: '| ID | Name |\n| --- | --- |\n| 1 | Main warehouse |\n| 2 | Returns warehouse |'
  }
}

Sekrety (DisplayFormat.SECRET)

Pola oznaczone SECRET są traktowane szczególnie:

  • zapisana wartość nigdy nie wraca do przeglądarki — użytkownik widzi tylko maskę •••••••• i informację, że wartość jest ustawiona,
  • puste pole SECRET przy zapisie oznacza „bez zmian" (obecna wartość zostaje),
  • Twoja funkcja validate zawsze dostaje kompletne dane — platforma podstawia aktualną wartość sekretu przed walidacją, więc np. test klucza API działa także wtedy, gdy użytkownik zmienił tylko inne pole.

Pola SECRET służą do danych użytkownika firmy (jego klucz API, hasło do konta). Klucze aplikacji autora modułu, wspólne dla wszystkich firm (np. clientSecret aplikacji marketplace'u), nie należą do konfiguracji — deklarujesz je jako sekrety aplikacji w module.json.

Walidacja — ValidationRaport

validate(params, raport) dostaje pełny obraz bloku po zapisie (stare + nowe wartości) oraz raport z jedną metodą:

raport.addError(fieldKey, 'Komunikat wyświetlany przy polu');

Zgłoszone błędy pokazują się użytkownikowi przy odpowiednich polach formularza, a zapis jest odrzucany w całości. Możesz w validate wykonywać operacje asynchroniczne (np. testowe wywołanie zewnętrznego API).

Akcje bloku (przyciski przy formularzu)

(od SDK 3.3.0)

Blok może wystawić actions — przyciski, które panel renderuje w stopce bloku, obok Zapisz. Kliknięcie uruchamia handler w module, a panel domyślnie pobiera blok ponownie, więc dynamiczna schema od razu pokazuje efekt akcji. Typowe zastosowania: „Dodaj nową regułę", „Testuj połączenie", „Synchronizuj teraz", „Rozłącz konto".

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

await Config.registerConfigBlocks({
  rules: {
    title: { pl: 'Reguły wysyłki', en: 'Shipping rules' },

    // schema dynamiczna — pola generowane per reguła zapisana w configu
    schema: async (actualConfig) => buildRulesSchema(actualConfig),

    actions: {
      addRule: {
        title: { pl: 'Dodaj nową regułę', en: 'Add new rule' },
        style: ActionStyle.PRIMARY,
        handler: async (actualConfig, ctx) => {
          await addEmptyRule(actualConfig);
          return { message: 'Dodano regułę' }; // success i needReload domyślnie true
        }
      },
      clearRules: {
        title: { pl: 'Usuń wszystkie reguły', en: 'Remove all rules' },
        style: ActionStyle.DANGER,
        confirm: { pl: 'Na pewno usunąć wszystkie reguły?', en: 'Really remove all rules?' },
        handler: async () => { await clearRules(); } // brak `return` = sukces + reload
      }
    }
  }
});

Definicja akcji

PoleTypOpis
titleobject | stringEtykieta przycisku jako mapa język → tekst; en jest wymagane. Sam string jest traktowany jako { en }
handlerfunction (wymagane)async (actualConfig, ctx) => result — kod wykonywany po kliknięciu
styleActionStyle (opcjonalne)Podpowiedź wyglądu: ActionStyle.DEFAULT (domyślne), ActionStyle.PRIMARY, ActionStyle.DANGER
confirmobject | string (opcjonalne)Tekst pytania w oknie potwierdzenia; gdy podany, panel prosi o potwierdzenie przed uruchomieniem handlera

Handler akcji

handler: async (actualConfig, ctx) => {
  // actualConfig — ZAPISANE wartości bloku (te same, które dostaje dynamiczna schema)
  // ctx.userId   — id użytkownika, który kliknął przycisk (dostępne też jako this.userId)
  return { success: true, message: 'Gotowe', needReload: true };
}
  • actualConfig to zapisane wartości bloku — dla scope: 'user' wartości klikającego użytkownika. Handler nie widzi niezapisanego stanu formularza, więc jeśli akcja ma działać na świeżych danych, użytkownik musi najpierw zapisać blok.
  • ctx to { userId } wywołującego; ten sam obiekt jest zbindowany jako this (użyj zwykłego function, jeśli chcesz z tego korzystać — funkcja strzałkowa ignoruje bindowanie).
  • Wynik { success, message, needReload } — wszystkie pola opcjonalne:
PoleDomyślnieZnaczenie
successtruefalse oznacza biznesowe niepowodzenie — panel pokaże komunikat błędu
messagebrakTekst pokazywany użytkownikowi jako powiadomienie
needReloadtruePanel pobiera blok ponownie (nowa schema i wartości). Ustaw false, gdy akcja niczego nie zmienia
  • Brak return w handlerze = sukces z odświeżeniem bloku.
  • Wyjątek rzucony z handlera nie jest błędem protokołu — wraca jako { success: false, message: error.message }, a blok i tak się odświeża (akcja mogła coś zmienić, zanim rzuciła).

Dynamiczny zestaw akcji

actions może być funkcją konfiguracji — wtedy zestaw przycisków zależy od stanu modułu:

actions: async (actualConfig) => {
  if (!actualConfig.apiKey) {
    return {}; // brak klucza → żadnych przycisków
  }
  return actualConfig.connected
    ? { disconnect: { title: { pl: 'Rozłącz konto', en: 'Disconnect' }, style: ActionStyle.DANGER, handler: disconnect } }
    : { connect: { title: { pl: 'Połącz konto', en: 'Connect' }, style: ActionStyle.PRIMARY, handler: connect } };
}

Zestaw jest rozwiązywany za każdym razem — i przy pobraniu schemy, i przy wykonaniu akcji. Akcji, której nie ma w aktualnym zestawie, nie da się wykonać, więc ukryty warunkowo przycisk nie zostanie wywołany „na skróty".

Kiedy błąd w definicji akcji wybuchnie

Akcje podane wprost (obiektem) są walidowane już przy registerConfigBlocks — literówka w style czy brak handler przerwie start modułu. Wariant funkcyjny jest z natury dynamiczny, więc waliduje się dopiero przy rozwiązaniu zestawu (otwarcie strony konfiguracji lub kliknięcie).

Akcja blokuje formularz

W trakcie wykonywania akcji panel wyłącza przycisk Zapisz i pozostałe akcje bloku (na klikniętym przycisku widać spinner). Długie operacje uruchamiaj asynchronicznie i tylko raportuj ich start, zamiast trzymać handler otwarty — pamiętaj o 30-sekundowym limicie wywołań RPC.

Zachowanie i dobre praktyki

Metadane a schema

Klucz, tytuł i scope bloku są zgłaszane do platformy przy każdym połączeniu (i automatycznie po reconnect — nie musisz nic robić). Schematy, validate i handlery akcji zostają w module — platforma pyta kontener o schemę dopiero, gdy użytkownik otworzy stronę konfiguracji. Metadane akcji (etykieta, styl, potwierdzenie) jadą razem ze schemą, a nie z metadanymi bloków — dzięki temu zestaw przycisków może być dynamiczny, a lista bloków i flaga „moduł ma konfigurację" są czytane z trwałego indeksu, więc działają też zanim kontener połączy się po restarcie rdzenia.

  • Zapis jest merge, nie replace — pola nieobecne w żądaniu zachowują obecną wartość, a pola spoza schemy są ignorowane.
  • Schema może być dynamiczna: funkcja schema(actualConfig) dostaje aktualne wartości bloku (dla scope: 'user' — wartości edytującego użytkownika).
  • Nie duplikuj bloków w wielu wywołaniach — registerConfigBlocks możesz wywołać kilka razy, ale zestaw zgłoszony do platformy zawsze odzwierciedla sumę zarejestrowanych bloków.
  • Wartości z formularza czytaj zwykłym Config.get('blok.pole') — także we własnych metodach, hookach i akcjach automatyzacji.
  • Akcja bloku to dobre miejsce na operacje, których nie da się wyrazić formularzem (test połączenia, dodanie pozycji do listy, ręczna synchronizacja) — ale sam zapis wartości zostaw przyciskowi Zapisz i funkcji validate.
Ostatnio zaktualizowano:: 14.10.2026, 10:36
Contributors: Wiktor Koźmiński, Claude Opus 5 (1M context), Claude Fable 5.1
Prev
DB
Next
Files