Manuel · 11

Workflows

Les Workflows relient de petites étapes en un enchaînement – à la manière de Node-RED, en version allégée. Un flow commence par un node de démarrage (bouton, planification, point de terminaison HTTP ou événement de Collection) ; ensuite, vous récupérez des données, les transformez avec JavaScript, créez des branchements, les enregistrez dans une Collection ou les renvoyez comme réponse HTTP. Cas typiques : un point de terminaison JSON pour un écran d’affichage, un formulaire de contact qui aboutit dans une Collection, une synchronisation nocturne avec un autre système.

L’éditeur

Sous Workflows, chaque Workflow apparaît comme un onglet en haut. En dessous :

  • Palette (à gauche) : faites glisser les Nodes sur la surface ou cliquez dessus.
  • Surface (au centre) : reliez en tirant de la sortie (à droite du node) vers l’entrée (à gauche). Une sortie peut mener à plusieurs Nodes – le message parcourt alors toutes les branches. Suppr supprime les Nodes et fils sélectionnés ; la molette zoome, faire glisser sur une zone vide déplace la vue.
  • Barre latérale (à droite) : Node affiche les paramètres du node cliqué, Debug les sorties de l’exécution, Exécutions les 50 dernières exécutions, Stockage le stockage du Workflow.
  • Copier et coller : vous sélectionnez plusieurs Nodes avec Ctrl (Mac : ⌘) + clic ou avec ⇧ + tracé d’un cadre. Ctrl+C les copie avec les fils qui les relient, Ctrl+V les colle – y compris dans un autre Workflow ou un autre onglet du navigateur ; Ctrl+X coupe. Les copies reçoivent de nouveaux ID et restent ensuite sélectionnées, pour que vous puissiez les déplacer immédiatement.

▶ sur le node de démarrage déclenche le flow – avec l’état de la surface, même non enregistré. Enregistrer active les entrées HTTP, les planifications et les événements ; l’interrupteur Actif les désactive tous ensemble (▶ fonctionne quand même).

Après une exécution, chaque node indique combien de fois il a été exécuté (en rouge en cas d’erreur). Si vous cliquez dessus, vous voyez en bas de la barre latérale son entrée et ce qui est sorti de chaque sortie – vous trouvez ainsi rapidement l’étape qui coince.

Messages

Un message msg circule sur les fils – un objet JSON. Les données proprement dites se trouvent dans msg.payload ; les Nodes peuvent ajouter d’autres champs (msg.req pour l’entrée HTTP, msg.statusCode après une requête HTTP). Chaque node reçoit sa propre copie, les branches ne s’influencent donc pas.

Dans les champs de texte des Nodes (URL, en-têtes, corps, ID de l’entrée), {{ msg.pfad }} insère des valeurs, p. ex. https://api.example/artikel/{{ msg.payload.id }} . Les variables d’environnement du serveur ne sont pas disponibles dans les Workflows – saisissez identifiants et réglages directement dans le Node ou dans le script.

Les Nodes

Node Ce qu’il fait
Démarrage Démarre via ▶ et/ou selon une planification. msg.payload = la valeur définie (JSON ou texte)
Entrée HTTP Point de terminaison propre sous /api/v1/flows/<pfad>. msg.payload = corps JSON ou de formulaire, sans corps les paramètres de requête ; msg.req = method, path, params, query, headers, body
Événement de collection Démarre lorsqu’une entrée est créée, modifiée ou supprimée dans une Collection. msg.payload = l’entrée, msg.event, msg.collection
Fonction JavaScript avec un nombre quelconque de sorties (voir ci-dessous)
Aiguillage Teste une valeur (msg.payload.preis, msg.req.query.format, …) par rapport à des règles – une sortie par règle. Règles : ==, !=, <, >, contient, correspond à RegExp, est vrai/faux/vide, sinon. Normalement, le message va à la première règle correspondante, sur demande à toutes
Requête HTTP Appelle une URL (méthode, en-têtes, corps). La réponse arrive dans msg.payload (le JSON est lu), le statut dans msg.statusCode, les en-têtes dans msg.headers. Les chemins comme /api/v1/… vont à ce CMS. Option : erreur à partir du statut 400
Collection Lire des entrées (toutes ou une), créer, modifier, supprimer. Pour créer et modifier, les champs proviennent de msg.payload, l’ID par défaut de {{ msg.payload.id }}. Résultat dans msg.payload
IllusionDB Lire des enregistrements dans IllusionDB (filtre, tri, regroupement, pages), en lire un via la clé primaire ou une colonne, créer, modifier, supprimer, interroger le schéma et les tables. Résultat dans msg.payload
Retarder Attend la durée définie (en secondes, fractions comprises) avant que le message ne poursuive. Les autres branches continuent pendant ce temps. Au plus jusqu’à la limite de temps de l’exécution
Fusionner Collecte les messages de plusieurs branches et en transmet un seul dès qu’ils sont tous arrivés – par défaut autant qu’il y a de fils entrants. msg.payload devient la liste des payloads (ordre d’arrivée) ou un objet avec msg.topic comme clé. Si tout n’arrive pas, rien ne poursuit (avertissement dans le Debug)
E-mail Envoie un e-mail via l’accès SMTP de l’instance : À (plusieurs séparés par des virgules), Objet, Texte (vide = msg.payload), Répondre à, éventuellement en HTML – partout avec {{ msg.… }}. Le message poursuit sans modification
Réponse HTTP Répond à l’entrée HTTP : msg.payload comme corps (objet → JSON, texte → text/plain), statut et en-têtes configurables
Debug Affiche msg.payload, le msg entier ou un chemin dans le panneau de débogage
Personnalisés Vos Nodes personnalisés – des scripts réutilisables avec paramètres et sorties nommées

Fonction à plusieurs sorties

Le code de la fonction reçoit msg et décide avec return où aller ensuite :

// Paramètre : 3 sorties
if (msg.payload.preis > 5) return [msg, null, null]   // uniquement la sortie 1
if (msg.payload.preis > 2) return [null, msg, null]   // uniquement la sortie 2
return [null, null, msg]                               // uniquement la sortie 3
  • return msg – continuer par la sortie 1
  • return null – cette branche s’arrête ici
  • return [a, null, b] – un message ou null par sortie
  • return [[a, b, c]] – plusieurs messages par la même sortie (p. ex. un par entrée)

Vous représentez ainsi beaucoup de logique dans un seul node, au lieu d’enchaîner de nombreux aiguillages. Le code dispose en outre de :

Nom Signification
node.warn(…), console.log(…) Sortie dans le panneau de débogage
store.get(key, standard), store.set(key, wert), store.delete(key), store.keys() Stockage du Workflow – conservé d’une exécution à l’autre
fetch(url, { method, headers, body }) Requête HTTP, synchrone (pas de await) : réponse avec status, ok, headers, text(), json()
ic.items(slug), ic.item(slug, id) Entrées d’une Collection (brouillons compris – champ published)
ic.createItem(slug, data, { published }) Créer une entrée ; les valeurs sont vérifiées par rapport au schéma, comme dans l’API
ic.updateItem(slug, id, data), ic.deleteItem(slug, id) Modifier (uniquement les champs transmis) ou supprimer
ic.data(slug, params) Comme data() dans le template – Collections et sources REST
ic.api(method, path, body) N’importe quel point de terminaison de l’API REST
idb.records(…), idb.find(…), idb.create(…) … Lire et écrire des enregistrements dans IllusionDB

Le moteur JavaScript (goja) connaît ES5.1 et les nouveautés courantes qui ont suivi – const/let, fonctions fléchées, template strings, déstructuration, méthodes d’Array –, mais pas Intl ni await. Vous formatez les nombres et les dates à la main, par exemple betrag.toFixed(2).replace('.', ',') ou avec une expression régulière pour les séparateurs de milliers.

Si un appel ic.* ou idb.* échoue (p. ex. un champ obligatoire manque), il lève une erreur que vous pouvez intercepter avec try { … } catch (e) { … }. fetch() ne lève une erreur qu’en cas d’erreur réseau – vous vérifiez le statut via ok.

Exemples

Carte des boissons pour l’écran d’un bar – Entrée HTTP (GET, chemin karte, public) → Collection (lire, toutes, publiées uniquement) → Fonction → Réponse HTTP :

msg.payload = msg.payload.map((d) => ({ name: d.data.name, preis: d.data.preis.toFixed(2) + ' €' }))
return msg

https://deine-domain.de/api/v1/flows/karte renvoie alors du JSON, y compris via fetch() depuis un autre domaine. Avec un Aiguillage sur msg.req.query.format, vous pouvez p. ex. répondre à ?format=text par une deuxième branche en texte brut.

Formulaire de contact – Entrée HTTP (POST, chemin kontakt, public) → Fonction avec 2 sorties → sortie 1 : Collection (créer dans « anfragen », brouillon) → Fonction « Weiterleiten » → Réponse HTTP ; sortie 2 directement vers « Weiterleiten » :

<form method="post" action="/api/v1/flows/kontakt">
  <input name="name" required> <textarea name="nachricht" required></textarea>
  <input name="website" style="display:none" tabindex="-1" autocomplete="off"> <!-- Piège à bots -->
  <button>Senden</button>
</form>
// Vérifier : les bots remplissent le champ caché
if (msg.payload.website) return [null, msg]
msg.payload = { name: msg.payload.name, nachricht: msg.payload.nachricht }
return [msg, null]
// « Weiterleiten » (avant la réponse HTTP, y saisir le statut 303)
msg.payload = ''
return msg

Dans la Réponse HTTP, définissez en plus l’en-tête Location: /danke. Pour la Collection « anfragen », cochez Données live dans le schéma – sinon, le prochain Bundle venu du poste local écrase les demandes reçues.

Synchronisation nocturne – Démarrage (planification 0 3 * * *) → Requête HTTP (https://kasse.example/api/artikel, en-tête Authorization: Bearer k123, erreur à partir de 400) → Fonction :

const vorhanden = ic.items('drinks')
let neu = 0, geaendert = 0
for (const a of msg.payload) {
  const d = vorhanden.find((x) => x.data.name === a.name)
  if (!d) { ic.createItem('drinks', { name: a.name, preis: a.preis }); neu++ }
  else if (d.data.preis !== a.preis) { ic.updateItem('drinks', d.id, { preis: a.preis }); geaendert++ }
}
node.warn(neu + ' neu, ' + geaendert + ' geändert')
return null

Appeler depuis un template

Un flow avec une entrée HTTP (GET ou ANY) peut être appelé directement depuis une page ou un Snippet – en interne, sans réseau et sans token (même les entrées protégées) :

<ul>
{% for d in workflow("karte", "format", "kurz") %}
  <li>{{ d.name }} – {{ d.preis }}</li>
{% endfor %}
</ul>
  • La première valeur est le chemin de l’entrée HTTP (karte pour /api/v1/flows/karte ; les espaces réservés comme text/{wer} fonctionnent aussi : workflow("text/Ada")).
  • Les valeurs suivantes arrivent par paires comme paramètres dans msg.req.query et msg.payload – ici { "format": "kurz" }.
  • Le retour est ce qu’enverrait le node Réponse HTTP : du JSON sous forme de données (listes, objets), sinon du texte. Sans réponse HTTP, il n’y a rien.
  • Les erreurs du flow apparaissent comme les autres erreurs de template. Limite de temps ici : 10 secondes – des services externes lents ralentissent la page.
  • Le cache des pages conserve les pages avec workflow() au plus une minute, afin que les données externes restent fraîches. Dans l’aperçu de l’éditeur, le résultat est mémorisé 30 secondes, pour que le flow ne s’exécute pas à chaque frappe.
  • Dans le journal du Workflow, ces exécutions apparaissent avec le déclencheur Page.

Plusieurs paramètres, p. ex. issus de l’URL : ajoutez simplement d’autres paires – workflow("pfad", "a", wert1, "b", wert2, …). Les paramètres de la page appelée sont disponibles dans le template sous route.query. Exemple : une carte des boissons avec recherche et prix maximal sous /karte?suche=mate&max=4.

Page /karte :

<form>
  <input name="suche" value="{{ route.query.suche }}" placeholder="Name">
  <input name="max" type="number" step="0.5" value="{{ route.query.max }}" placeholder="max. Preis">
  <button>Filtern</button>
</form>
<ul>
{% for d in workflow("karte-filter", "suche", route.query.suche, "max", route.query.max) %}
  <li>{{ d.name }} – {{ d.preis }} €</li>
{% empty %}
  <li>Nichts gefunden.</li>
{% endfor %}
</ul>

Workflow : Entrée HTTP (GET, chemin karte-filter ; public n’est pas nécessaire pour l’appel depuis le template) → Collection (lire, toutes, publiées uniquement) → Fonction → Réponse HTTP. Le node Collection écrase msg.payload avec les entrées – la fonction lit donc les paramètres dans msg.req.query :

// Les valeurs de l’URL sont du texte (ou absentes) – les vérifier avant utilisation
const suche = String(msg.req.query.suche || '').trim().toLowerCase()
const max = parseFloat(String(msg.req.query.max || '').replace(',', '.'))
msg.payload = msg.payload
  .filter((d) => !suche || d.data.name.toLowerCase().includes(suche))
  .filter((d) => isNaN(max) || d.data.preis <= max)
  .map((d) => ({ name: d.data.name, preis: d.data.preis }))
return msg
  • Si un paramètre manque dans l’URL, il arrive vide – la carte affiche alors tout.
  • Les valeurs de l’URL sont des saisies de visiteurs : vérifiez-les dans le Workflow avant qu’elles ne partent p. ex. dans une requête HTTP vers un autre service. Dans le template, les sorties sont automatiquement échappées.
  • Le cache des pages distingue selon l’adresse complète, paramètres compris – chaque combinaison passe au plus une fois par minute dans le Workflow.
  • Pour les paramètres présents plusieurs fois (?a=1&a=2), route.query ne contient que la première valeur.

Planification

La planification du node de démarrage est une expression cron à cinq champs – minute heure jour mois jour de la semaine (0 = dimanche). Des modèles sont disponibles ; en haut, à côté d’Actif, figure la prochaine exécution.

Expression Signification
*/15 * * * * toutes les 15 minutes
0 * * * * toutes les heures, à l’heure pile
0 3 * * * tous les jours à 3 h
30 7 * * 1-5 en semaine à 7 h 30
0 6 1 * * le 1er de chaque mois à 6 h

C’est le fuseau horaire du serveur qui s’applique (dans l’image Docker, Europe/Berlin) ; un autre via un préfixe : CRON_TZ=Europe/Berlin 0 3 * * *. Si un node de démarrage est encore en cours à l’échéance suivante, celle-ci est sautée ; les échéances pendant lesquelles le serveur était arrêté ne sont pas rattrapées.

Entrées HTTP

  • Chemin après /api/v1/flows/, avec espaces réservés : drinks/{id} correspond à /api/v1/flows/drinks/5 (msg.req.params.id vaut "5"), dateien/{rest...} en fin de chemin capture le reste. Un chemin avec la même méthode appartient à exactement un Workflow actif.
  • Protégée (par défaut) : uniquement avec un token API (Authorization: Bearer … ou, pour GET, ?token=…) ou en tant qu’administrateur connecté. Publique : sans connexion, y compris via JavaScript depuis d’autres domaines (CORS).
  • La réponse provient du premier node Réponse HTTP atteint. Si le flow se termine sans en rencontrer, le serveur répond 204 ; en cas d’erreur, 500 avec le message.
  • Depuis les pages et les Snippets, vous appelez une entrée HTTP avec workflow().
  • Dans l’éditeur, vous testez une entrée HTTP via Exécution de test dans ses paramètres – avec un payload de votre choix, sans vraie requête.

Événements de collection

Les événements s’exécutent en arrière-plan après l’enregistrement – l’enregistrement dans l’admin ou via l’API ne les attend pas. Ils se déclenchent à chaque modification via l’admin, l’API et les Workflows. Si un Workflow modifie lui-même des entrées qui déclenchent à nouveau des événements, la chaîne s’interrompt au bout de trois niveaux.

Stockage et e-mail

Stockage : chaque Workflow dispose d’un petit stockage clé-valeur conservé d’une exécution à l’autre – pour des compteurs, un « dernier élément traité » ou un résultat intermédiaire. Les valeurs sont des valeurs JSON quelconques (jusqu’à 64 Ko par valeur, jusqu’à 1 000 clés). L’onglet Stockage de la barre latérale affiche le contenu ; vous y supprimez des clés individuelles ou tout. Le stockage appartient à l’instance : il ne voyage pas avec les Bundles, l’état sur le serveur en production est donc conservé lors d’un téléversement. Le stockage est supprimé avec le Workflow.

Exemple – une planification ne signale que les demandes arrivées depuis la dernière exécution (Démarrage avec planification → Collection « lire (tous) » → Fonction → E-mail) :

const zuletzt = store.get("zuletzt", 0)
const neu = msg.payload.filter((e) => e.id > zuletzt)
if (!neu.length) return null
store.set("zuletzt", Math.max(...neu.map((e) => e.id)))
msg.payload = neu.length + " neue Anfrage(n): " + neu.map((e) => e.data.name).join(", ")
return msg

Envoi d’e-mails : vous saisissez l’accès SMTP sous Paramètres → Système → Envoi d’e-mails (serveur, port, utilisateur, mot de passe, expéditeur comme Website <web@firma.de>) et le vérifiez avec Envoyer un e-mail de test. Le port 465 se connecte en TLS, les autres ports en STARTTLS ; sans chiffrement, aucun mot de passe n’est envoyé. L’accès ne vaut que pour cette instance – en local et en production, vous le configurez séparément (en mode distant, directement pour le serveur).

IllusionDB

IllusionDB gère des bases de données existantes (PostgreSQL, MySQL/MariaDB, SQLite) avec une interface et une API REST. Le node IllusionDB y récupère directement des enregistrements – produits, équipes, événements, pages de détail –, sans les copier dans une Collection.

Configurer une connexion : Paramètres → IllusionDB : nom, adresse (p. ex. https://db.firma.de) et token API (dans IllusionDB sous Paramètres → Mon compte ; le droit de lecture sur les tables suffit pour lire). Tester la connexion affiche la version et le nombre de tables lisibles. Sans token, seules les tables lisibles publiquement dans IllusionDB sont accessibles.

  • Les Workflows font référence à la connexion par son nom, et aux tables par leur nom (pour des tables homonymes dans plusieurs sources de données, Datenquelle/tabelle). Créez en local et en production une connexion portant le même nom – le même Workflow utilise alors la base de données correspondante.
  • Les connexions appartiennent à l’instance, pas au Bundle ; le token n’est jamais transmis. En mode live, vous modifiez celles du serveur.

Le node : il n’apparaît dans la palette qu’une fois au moins une connexion configurée. Connexion, table, colonnes et vues enregistrées sont proposées comme choix, en direct depuis le serveur IllusionDB (↻ recharge le schéma, p. ex. après l’ajout de colonnes).

Action Résultat dans msg.payload
lire des enregistrements Liste – avec filtre, tri, regroupement, synthèse, nombre et page, éventuellement à partir d’une vue enregistrée
en lire un un enregistrement via la clé primaire ou via une colonne (p. ex. slug) ; introuvable : null ou, sur demande, une erreur
créer / modifier / supprimer colonnes issues de msg.payload, clé primaire par défaut {{ msg.payload.id }} ; résultat : l’enregistrement sauvegardé
Schéma la table avec toutes ses colonnes (nom, type, clé primaire, …)
lister les tables toutes les tables lisibles de toutes les sources de données
  • Filtre : règles colonne – comparaison – valeur (=, ≠, >, ≥, <, ≤, contient, l’un de a, b, c, aucun de, est vide, n’est pas vide), combinées par toutes les règles ou au moins une. Les valeurs peuvent contenir {{ msg.… }} ; les nombres et les oui/non sont convertis selon le type de colonne. Les règles dont la valeur reste vide sont ignorées – un champ de recherche vide ne filtre donc pas (désactivable).
  • Filtre en JSON remplace les règles, pour des conditions ET/OU imbriquées au format de l’API d’IllusionDB : {"or": [{"col": "name", "op": "like", "val": "{{ msg.payload.q }}"}, {"col": "preis", "op": "lt", "val": {{ msg.payload.max }}}]}. Un espace réservé entre guillemets est inséré comme texte, sans guillemets comme valeur JSON (nombre, true, liste).
  • Uniquement ces colonnes (à la lecture de listes et d’enregistrements individuels) : p. ex. slug, name, preis – IllusionDB n’envoie que ces colonnes et saute le calcul des champs de formule, de recherche et de lien inutiles ; cela économise du volume de données et du temps sur les tables larges ou volumineuses. Un nom de colonne inconnu est une erreur. Vide = toutes (les versions plus anciennes d’IllusionDB renvoient toujours tout).
  • Pages : Nombre (1000 au maximum) et Page (p. ex. {{ msg.payload.seite }}) – ou charger toutes les pages (jusqu’à 10 000 enregistrements).
  • Sortie : la liste des enregistrements ; tout = records, total, page, pages, has_more, summary, groups ; uniquement les groupes ; ou le premier enregistrement.
  • Groupes : pour chaque groupe value, count (sur tous les résultats, pas seulement la page chargée) et records – avec plusieurs colonnes de regroupement, groups un niveau plus bas à la place.
  • Synthèse : SUM, AVG, COUNT, MIN, MAX sur tous les résultats, dans summary.<spalte>. IllusionDB ne calcule SUM et AVG que pour les colonnes de type Nombre ou Nombre décimal.
  • Mémoriser le résultat : durée en secondes pendant laquelle un résultat de lecture est mis en cache – soulage la base de données en cas de fort trafic (0 = interroger à chaque fois). Le CMS mémorise les tables et colonnes pendant une minute.
  • Les noms de colonnes sont ceux qu’IllusionDB utilise dans son API (nom d’affichage, s’il est défini).

Liste et pages de détail directement depuis la base de données – comme le blog du chapitre 10, mais avec IllusionDB au lieu d’une Collection. Deux Workflows :

  • Entrée HTTP (GET, chemin produkte) → IllusionDB (lire des enregistrements, table produkte, règle aktiv = true, tri name, regroupement kategorie, nombre 20, page {{ msg.payload.seite }}, sortie tout) → Réponse HTTP
  • Entrée HTTP (GET, chemin produkt) → IllusionDB (en lire un, table produkte, rechercher par la colonne slug, valeur {{ msg.payload.slug }}) → Réponse HTTP

En plus, une page /produkte avec Catch-All et Désactiver le cache :

{% if route.slug %}
  {% set p = workflow("produkt", "slug", route.slug) %}
  {% if not p %}{{ not_found() }}{% endif %}
  <h1>{{ p.name }}</h1>
  <p>{{ p.preis }} € · {{ p.kategorie }}</p>
  <a href="/produkte">← Alle Produkte</a>
{% else %}
  {% set r = workflow("produkte", "seite", route.query.seite|default:"1") %}
  {% for g in r.groups %}
    <h2>{{ g.value }} ({{ g.count }})</h2>
    {% for p in g.records %}<a href="/produkte/{{ p.slug }}">{{ p.name }}</a>{% endfor %}
  {% endfor %}
  <p>Seite {{ r.page }} von {{ r.pages }} · {{ r.total }} Produkte</p>
  {% if r.has_more %}<a href="?seite={{ r.page + 1 }}">weiter</a>{% endif %}
{% endif %}
  • /produkte/mate affiche l’enregistrement avec slug = mate, un Slug inconnu la page 404 (en lire un renvoie alors null).
  • Avec Désactiver le cache, chaque modification dans IllusionDB est immédiatement visible ; sans, le cache des pages conserve la page au plus une minute. En cas de fort trafic : laissez le cache activé et réglez Mémoriser le résultat dans le node.
  • Sitemap : dans les paramètres de la page, sous Pages de détail pour le sitemap et l’export statique, choisissez la source Table IllusionDB (connexion shop, table produkte, colonne slug, règle aktiv = true) – voir le chapitre 10. Avec Vérifier, vous voyez immédiatement combien de pages cela donne.

Des milliers de landing pages : ce modèle supporte aussi de grands volumes – par exemple une table landingpages avec les colonnes slug (berlin/zahnarzt), stadt, leistung, text, aktiv, geaendert, gérée dans IllusionDB (ou alimentée par import CSV). Une seule page Catch-All /leistungen avec un template, un Workflow en lire un via slug, la source du sitemap sur la même table – et toutes les pages sont prêtes, sitemap compris. Les nouvelles lignes dans IllusionDB sont immédiatement en ligne (avec Désactiver le cache) et figurent dans le sitemap au plus tard au bout de 10 minutes.

Dans le node Function, idb est disponible – pour tout ce qui ne peut pas s’assembler par clics avec le node :

const r = idb.records('shop', 'produkte', {
  where: { and: [{ col: 'aktiv', op: 'eq', val: true }, { col: 'preis', op: 'lt', val: 5 }] },
  sort: '-preis', // plusieurs : 'kategorie,-preis'
  limit: 50,
})
msg.payload = r.records.map((p) => `${p.name}: ${p.preis} €`)
return msg

idb.records(verbindung, tabelle, { where, sort, group_by, summary, fields, view, limit, page, all }) renvoie la même chose que la sortie tout ; s’y ajoutent idb.record(v, t, pk) (ou null), idb.find(v, t, spalte, wert) (premier résultat ou null), idb.create(v, t, daten), idb.update(v, t, pk, daten), idb.delete(v, t, pk), idb.schema(v, t), idb.tables(v) et idb.api(v, methode, pfad, body) pour tout autre point de terminaison de l’API d’IllusionDB (chemin sans /api/v1). Les erreurs d’IllusionDB interrompent l’exécution avec le message.

Nodes personnalisés

Si vous écrivez sans cesse le même script (formater des prix, raccourcir un texte, envoyer une notification à un service), faites-en un node personnalisé : écrit une seule fois, il peut être glissé depuis la palette (groupe Personnalisés) dans n’importe quel Workflow – avec des paramètres propres à chaque utilisation et des sorties nommées. Si le script change, cela s’applique dans tous les Workflows.

Vous les créez et les modifiez sous Workflows → Nodes personnalisés (en haut à droite) :

  • Nom, description, couleur – c’est ainsi que le node apparaît dans la palette et sur la surface ; la description lui sert de texte d’aide.
  • Entrée : avec entrée traite des messages comme un node Function. Sans entrée, c’est un node de démarrage – avec ▶ et une planification facultative comme Démarrage, p. ex. « récupérer un flux ».
  • Sorties : de 0 à 10, chacune avec un libellé facultatif (« ok », « erreur ») affiché sur le node.
  • Paramètres : champs remplis dans le Workflow à chaque utilisation – Texte, Texte, multiligne, Nombre, Oui/Non ou Sélection (avec options), chacun avec une valeur par défaut et un texte d’aide. Dans le code, ils se trouvent dans config.<name> ; dans les textes, {{ msg.… }} est remplacé au préalable.
  • Code : JavaScript comme dans le node Function – avec msg et config, retour msg, [a, null, b] par sortie ou null. Tout le reste (fetch, store, ic.*, idb.*, node.warn) est également disponible.
  • Tester : saisissez les paramètres et un exemple de message ; Exécuter montre ce qui sort de chaque sortie – même non enregistré, sans entrée dans les exécutions.

Exemple Formater le prix – paramètres waehrung (sélection €, $, par défaut €) et stellen (nombre, par défaut 2), sorties « ok » et « sans prix » :

const preis = Number(msg.payload.preis)
if (msg.payload.preis == null || isNaN(preis)) return [null, msg]
msg.payload.preisText = preis.toFixed(config.stellen).replace('.', ',') + ' ' + config.waehrung
return [msg, null]
  • Les Workflows y font référence via la clé (tirée du nom à la création, p. ex. preis-formatieren) – le renommage est donc sans risque.
  • Un node ne peut être supprimé que lorsque plus aucun Workflow ne l’utilise ; Historique montre les versions précédentes du code.
  • Les Nodes personnalisés font partie du contenu : ils sont inclus dans le Bundle et arrivent ainsi sur le serveur en production lors du téléversement.

Limites et sécurité

  • Limites de temps : 30 secondes pour les flows avec entrée HTTP, 5 minutes sinon ; au plus 1 000 exécutions de node par exécution (protection contre les boucles dans le graphe). Si un flow s’appelle lui-même via son propre hook, c’est terminé au bout de trois niveaux.
  • Les Workflows s’exécutent avec tous les droits : ils peuvent modifier tous les contenus et, via une requête HTTP, atteindre aussi des adresses du réseau interne. Quiconque modifie des Workflows mérite la même confiance que pour le HTML et les templates.
  • Le mot de passe SMTP est stocké dans la base de données de l’instance (pas dans les Bundles, plus jamais affiché dans l’admin).
  • Les identifiants figurent dans le Node ou dans le script – donc aussi dans les Bundles et dans l’historique des versions. Ne transmettez donc ni Bundles ni sauvegardes. Les variables d’environnement du serveur ne sont volontairement pas disponibles dans les Workflows.
  • Les entrées publiques sont accessibles à tous – vérifiez les saisies, ne renvoyez pas de données internes.
  • Les entrées de Collection n’ont pas d’historique des versions. Si vous modifiez beaucoup d’entrées via un Workflow, téléchargez d’abord un Bundle comme sauvegarde.