Quand on a démarré NHS Finances en début 2025, l'enjeu était clair : donner à la direction d'un groupe hôtelier une vision consolidée temps réel de 5 sociétés dont les données comptables vivaient dans Cegid Loop. Sur le papier, c'est un problème classique d'ETL. En pratique, ça nous a pris plusieurs itérations pour trouver la bonne architecture.
Le problème de départ
Cegid Loop expose bien une API REST, mais avec quelques particularités :
- Authentification OAuth 2.0 par société (et non par utilisateur global)
- Payloads volumineux : une seule sync peut renvoyer 8000 à 10 000 lignes
- Format variable selon le type de donnée (paie, compta, établissement)
- Pas de pagination cursor-based : tout est renvoyé en un seul bloc
La première version naïve était une fonction serverless classique sur Vercel. Elle a tenu 3 jours avant de péter sur la limite de mémoire de 1 Go.
Quand on traite 8000 lignes en mémoire avant de pousser en base, on tape vite dans le mur sur du serverless.
Bascule vers Supabase Edge Functions
On a migré sur les Edge Functions Supabase (basées sur Deno) pour deux raisons :
- Proximité avec la base : les Edge Functions tournent dans la même région que Postgres, latence négligeable
- Support natif du streaming : Deno gère nativement les
ReadableStreamet les Server-Sent Events
Architecture finale
L'architecture qu'on a fini par retenir tient en 3 couches :
1. Authentification mutualisée
Plutôt qu'un OAuth par utilisateur, on a une table cegid_urls qui stocke les credentials chiffrés pour chaque société. L'Edge Function s'authentifie côté serveur, le frontend ne voit jamais les tokens.
2. Streaming SSE
L'Edge Function "smooth-api" lit la réponse Cegid en streaming, parse ligne par ligne, et émet des événements SSE vers le frontend. Trois bénéfices :
- Le frontend peut afficher la progression en temps réel
- Aucune accumulation en mémoire, on traite à la volée
- Si la sync échoue à 80%, on garde les 80% déjà persistés
3. RLS dynamique côté Postgres
Les permissions sont gérées au niveau Postgres via Row Level Security. Une table permissions_role définit ce que chaque rôle peut voir (RAF voit tout, économe ne voit que son périmètre). Aucune logique de droits côté frontend : impossible de "bypasser".
Les pièges qu'on a évités
Piège 1 : faire confiance au timeout Cegid
Cegid peut mettre 30 secondes à répondre sur les gros payloads. La doc dit 10 secondes max — c'est faux. On retry avec un timeout de 60s et un backoff exponentiel.
Piège 2 : parser le JSON en une seule passe
JSON.parse charge tout en mémoire. On utilise un parser streaming (oboe.js équivalent en Deno) qui émet des événements à chaque objet rencontré dans le tableau.
Piège 3 : oublier la dédup
Si l'utilisateur relance une sync en cours, on peut se retrouver avec des doublons. Solution : un lock applicatif via une table sync_locks avec timeout automatique.
Le résultat
Aujourd'hui, une sync complète sur les 5 sociétés prend 4,2 secondes en moyenne, traite environ 8740 lignes, et n'a jamais hit la memory limit depuis qu'on est en production. Le frontend affiche une barre de progression en temps réel, et les utilisateurs ont l'impression que c'est instantané.
Si vous travaillez sur un projet similaire et que vous voulez en discuter, écrivez-nous. On adore parler d'ETL avec d'autres devs.