# Transform

Exécute un court script **JavaScript** ou **Python** sur la sortie du nœud précédent et envoie le résultat aux nœuds aval.

## Vue d'ensemble

Transform est un nœud de code sandboxé. Il s'exécute dans une sandbox isolée (pas de réseau, pas de filesystem hôte, environnement éphémère), donc tu peux écrire du code arbitraire sans risque pour l'environnement hôte.

À utiliser dès que tu as besoin d'une transformation déterministe et libre qu'un simple template ne peut pas exprimer :

- Reshape JSON (renommer des champs, aplatir, restructurer)
- Filtrer / map / trier des tableaux
- Arithmétique (taxes, pourcentages, totaux)
- Parsing de date / string
- Combiner des champs de `input`, `state` et `event` en un seul payload

Pour les décisions floues pilotées par LLM (classer, résumer, traduire…), préfère un nœud **Agent**.

## Configuration

| Champ | Description |
| --- | --- |
| **Language** | `JavaScript (Node.js)` ou `Python 3`. Changeable par nœud. |
| **Code** | Le snippet. **Assigne la sortie à une variable nommée `result`.** Sinon le nœud renvoie `last_output` inchangé. L’éditeur affiche les variables runtime sous forme de commentaires et complète leurs noms et chemins connus pendant la saisie ; **Ctrl+Espace** ouvre toutes les suggestions. |

## Variables disponibles

Le wrapper injecte ces variables avant l'exécution de ton code :

| Variable | Contenu |
| --- | --- |
| `input` | Entrée initiale du workflow |
| `last_output` | Sortie du nœud précédent |
| `state` | Toutes les variables Set state — `state.customer_email`, `state.page`… |
| `event` | Payload du trigger — `event.body.foo`, `event.headers.X`… |
| `result` | **À toi de l'assigner.** Sa valeur devient la sortie du nœud. |

En Python, les objets injectés sont des dictionnaires : utilise `state["clé"]`, `event["body"]["email"]` ou `input.get("champ")`. La notation attribut `state.clé`, courante en JavaScript, provoque une `AttributeError` en Python. `state["clé"]` provoque une `KeyError` si la clé est absente ; utilise `state.get("clé")` pour un état optionnel. L’autocomplétion insère automatiquement la notation adaptée au langage sélectionné.

Pour tester un Transform depuis son bouton **Run**, choisis **Previous** afin d’exécuter les nœuds Set state en amont. Le mode **Node** exécute volontairement le Transform seul : `state` est donc vide, sauf si tu le fournis dans le JSON Input ctx.

## Exemples

### Reshape JSON (JS)

```javascript
result = {
  name: input.user.full_name,
  email: input.user.email.toLowerCase(),
  signed_up: input.created_at,
}
```

### Filtrer un tableau (JS)

```javascript
result = input.tickets.filter(t => t.priority === "urgent")
```

### Calcul + pourcentage (JS)

```javascript
result = {
  total: input.subtotal + input.tax + input.shipping,
  discount_pct: Math.round((input.discount / input.subtotal) * 100),
}
```

### Aplatir + dédupliquer (JS)

```javascript
result = [...new Set(input.flatMap(group => group.tags))]
```

### List comprehension (Python)

```python
result = [item["name"].upper() for item in input if item.get("active")]
```

### Combiner state + event en un payload (Python)

```python
result = {
    "to": state["customer_email"],
    "subject": f"Re: ticket #{event['body']['id']}",
    "body": last_output,
}
```

### Regroupement JSON (Python)

```python
from collections import defaultdict
grouped = defaultdict(list)
for row in input:
    grouped[row["category"]].append(row)
result = dict(grouped)
```

## Combo Transform + Set state

Les deux marchent souvent ensemble : **Transform fait le calcul**, **Set state garde la mémoire**.

```
HTTP Request (API paginée)
   ↓
Transform   (extraire les rows + flag has_more)
            code: result = { rows: input.items, more: !!input.next_page }
   ↓
Set state   (page_count = résultat d'un Transform arithmétique en amont)
```

Set state ne sait pas faire `{state.x}+1` — passe par Transform d'abord.

## Pièges

| Symptôme | Cause | Correction |
| --- | --- | --- |
| Sortie `null` ou inchangée | Tu n'as pas assigné `result` | Ajoute `result = ...` |
| Python : `KeyError` sur `state["key"]` | La clé n’a pas été créée, ou le Transform a été testé seul | Lance le mode **Previous**, fournis `{"state":{"key":"value"}}`, ou utilise `state.get("key")` si elle est optionnelle |
| `Cannot read property X of undefined` | Le chemin n'existe pas dans `input` | JS : `input?.user?.email ?? ""` — Python : `input.get("user", {}).get("email", "")` |
| Timeout | Script > 20 secondes | Déplace le gros travail vers ton backend via HTTP Request |
| `fetch is not defined` / `urlopen failed` | Sandbox **sans réseau** | Utilise un nœud HTTP Request pour les appels sortants |
| Module natif introuvable | Le sandbox n'embarque que la stdlib + quelques packages | Reste dans la lib standard |
| Output qui ressemble à du JSON stringifié | Ton `result` est une string qui se trouve être du JSON valide | Parse-la explicitement (`JSON.parse(result)` / `json.loads(result)`) avant d'assigner |

## Sécurité

Le script tourne dans une sandbox isolée et fraîche :

- Pas d'accès réseau (par défaut)
- Pas d'accès au filesystem hôte, aux variables d'env ou aux données d'autres tenants
- Timeout dur de 20 secondes
- Conteneur détruit après chaque appel
- Même infra que les outils agent **Code interpreter** et **Shell**

## Transform vs Agent (mode JSON)

| Tâche | Utilise |
| --- | --- |
| Reshape mécanique, math, filtre, conversion de format | **Transform** — rapide, gratuit, déterministe |
| Travail flou / interprétatif (classer, résumer, traduire) | **Agent** — lent, payant, créatif |

> Si tu peux écrire la règle, utilise Transform. Si tu dois interpréter du langage naturel, utilise Agent.

---

*Source: https://agentbuilder.systalink.sn/docs/node-transform — human documentation.*
*Other language: [/docs-md/en/node-transform.md](/docs-md/en/node-transform.md).*
*Machine-readable index: [/llms.txt](/llms.txt).*
