Reliez une base de données à un schéma et Entity Enricher maintient vos entités enrichies sous forme de tables relationnelles qui vous appartiennent : une véritable table company avec une colonne revenue — et non un fichier d'export. Téléchargez un instantané SQL prêt à l'emploi une seule fois, puis gardez votre base de données synchronisée grâce à un flux delta incrémental d'upserts idempotents.
Stockez une fois, projetez à la demande. Les enrichissements écrivent l'état courant de l'entité dans la couche entité ; chaque base de données liée est une projection de cet état, livrée sous forme d'un instantané ponctuel accompagné d'un flux de deltas que vous accusez réception. Modifier un schéma coûte une reprojection, jamais une migration de données.
database_sync: false) ; les enrichissements des autres utilisateurs continuent d'être routés normalement..sql (tables + données) et appliquez-le à votre PostgreSQL. Le DDL inclut les index de jointure et les clés étrangères (les lignes enfants et de liaison sont supprimées en cascade lorsqu'une entité est supprimée). L'en-tête du fichier indique à partir de quel curseur delta reprendre.Chaque type d'entité possède une clé de base de données — l'ensemble de colonnes que vos tables utilisent comme index unique et comme cible de conflit pour les upserts. Lorsque vous liez une base de données, une passe de classification par IA propose l'intégralité du modèle de base de données (clés, types de colonnes, index, propriété) que vous pourrez examiner, avec un repli sur une cascade simple : l'ID sémantique de l'objet s'il en possède un, sinon un champ de type Id (id, product_id, …), sinon les clés naturelles de l'objet. Dans vos tables, un ID sémantique arrive sous la forme d'une colonne semantic_id — le nom id reste libre pour votre propre usage. Vous pouvez les modifier à tout moment dans l'onglet « Modèle » de la base de données — une modification équivalente à une migration une fois publiée : retéléchargez ensuite l'instantané. Rien n'atteint votre base de données tant que le schéma n'est pas publié depuis cet onglet (le simple fait de lier n'envoie ni table ni ligne).
Une clé de base de données n'est jamais nullable. Une valeur null ne correspond à aucune ligne : au lieu de mettre à jour l'entité, elle insérerait un nouveau doublon à chaque enrichissement — c'est pourquoi un enrichissement dont la clé revient vide est rejeté plutôt qu'enregistré. L'éditeur maintient les deux marqueurs distincts, et un schéma qui définit les deux est refusé lorsque vous l'enregistrez ou liez une base de données, en indiquant les deux façons de corriger le problème : rendre la propriété toujours présente, ou baser la clé du type sur une autre propriété.
Lorsqu'une clé de base de données est un champ texte enrichi plutôt qu'un ID sémantique, attendez-vous à ce que la valeur stockée soit la réponse du modèle — et non le texte que vous avez envoyé. Désigner une entreprise par Embraer dans votre requête ne fige pas ce champ : l'enrichissement peut répondre Embraer S.A. — orthographe corrigée, suffixe juridique développé, élément de désambiguïsation supprimé — et c'est cette valeur qui sert de clé à la ligne. Rechercher la ligne à partir de la valeur que vous avez envoyée peut donc échouer (utilisez les entity_keys renvoyés avec chaque enrichissement enregistré), et une exécution ultérieure formulée autrement constitue une clé différente — elle insère une deuxième ligne au lieu de mettre à jour la vôtre. C'est le comportement attendu pour toute clé constituée de texte enrichi. La solution est un ID sémantique sur ce type : les variantes d'un même nom se résolvent en une seule identité stable, si bien que la ligne subsiste quelle que soit l'orthographe retenue par le modèle. Activez-le au moment de générer le schéma — l'ajouter plus tard oblige à modifier chaque objet.
Les objets imbriqués dans des tableaux deviennent leurs propres tables, avec des lignes de jonction qui préservent l'ordre ; les objets de valeur sans identité restent aplatis dans les colonnes de leur parent. Les noms de propriétés sont utilisés tels quels (entre guillemets) — vos noms de colonnes sont les noms de propriétés de votre schéma, raccourcis uniquement lorsqu'un chemin imbriqué dépasserait ce que PostgreSQL peut nommer.
La projection est déterministe — un même schéma correspond toujours aux mêmes tables et colonnes. Les noms de propriétés deviennent des noms de colonnes tels quels (entre guillemets) ; les noms de types sont convertis en snake_case pour former les noms de tables (VideoGame → video_game). La seule exception est la longueur : PostgreSQL ne peut pas contenir un identifiant de plus de 63 octets, donc un chemin profondément imbriqué raccourcit ses objets parents pour tenir (morphological_description_ → morpdesc_) — le même préfixe pour chaque colonne de cet objet, affiché et modifiable dans l'onglet Modèle avant de lier. Une propriété peut aussi abandonner entièrement son préfixe pour correspondre à une colonne déjà présente dans votre base de données (product_identifiers.stock_keeping_unit → sku) — le commutateur de liaison à côté du nom dans l'onglet Modèle. Chaque table porte une colonne _sync_revision utilisée pour maintenir la convergence des rejeux.
| Dans votre schéma | Dans votre base de données |
|---|---|
| Objet avec identité (ID sémantique ou clés) | Sa propre table ; les clés de base de données deviennent l'index unique et la cible d'upsert |
| Champ scalaire (chaîne, nombre, booléen) | Une colonne typée (TEXT, BIGINT, NUMERIC, BOOLEAN) |
| Ensemble fermé (un champ limité à une liste de valeurs) | Une simple colonne TEXT — pas de CHECK, pas de type enum en base. La liste est appliquée au moment où l'IA répond, donc y ajouter une valeur plus tard ne migre jamais votre base de données. Ajoutez votre propre contrainte si vous en souhaitez une — la synchronisation n'y touche jamais |
| Champ nullable ou non nullable | Par défaut, un champ non nullable devient une colonne NOT NULL, associée au contrôle qualité ci-dessous — au niveau le plus strict, les références requises reçoivent elles aussi des clés étrangères NOT NULL ; désactivez cette contrainte — lors de la déclaration, ou plus tard : une modification après la première synchronisation est livrée sous forme de migration protégée dans le flux — pour garder toutes les colonnes nullables et laisser le contrôle seul garantir la complétude |
| Champ multilingue | Une seule colonne JSONB contenant toutes les langues |
| Objet valeur intégré (sans identité) | Aplati en colonnes préfixées (dimensions_width) |
| Tableau d'objets valeur | Une table enfant indexée sur le parent, ordonnée, avec suppression en cascade |
Tableau d'entités / relation $ref | Une table de jonction reliant les lignes source et cible, l'ordre étant préservé |
Champ clé (identifying) | Un index secondaire pour des recherches rapides |
| Index calqué sur la requête (liste ordonnée de champs) | Un index multi-colonnes par forme déclarée, ordonné comme la requête d'écran de liste qu'il sert — d'abord les facettes et les ensembles fermés, en dernier la colonne de tri ou de plage, champs multilingues inclus (une telle forme est livrée une fois par langue reçue par votre base) ; proposé par la passe de classification avec un motif, ajusté dans l'onglet « Modèle », plusieurs par entité |
| Champ de recherche (intention d'index) | Un index trigramme (pg_trgm) sur les textes que vos champs de recherche filtrent par fragment — par langue et sans plafond sur les colonnes multilingues ; jamais sur une valeur de liste déroulante, qui relève plutôt d'un index calqué sur la requête (y compris multilingue — un tel index est livré une fois par langue reçue par votre base) ; ignoré (sans effet) sur les réplicas dépourvus de l'extension tant qu'un propriétaire de base ne l'a pas installée |
| Paire de coordonnées (latitude + longitude) | Un seul index spatial sur la paire (GiST natif de PostgreSQL, sans extension) — requêtes par rayon, par plus proche voisin et par zone de carte affichée |
| Paire d'intervalle (bornes de début + de fin) | Un seul index de plage sur la paire — requêtes de chevauchement et « quelle valeur était en vigueur à cette date » |
Une base de données peut synchroniser plusieurs schémas. Les types d'entités portant le même nom dans les schémas liés arrivent dans la même table, fusionnés par leur clé de base de données — les enrichissements de chaque schéma ne mettent à jour que ses propres colonnes, si bien qu'une entreprise enrichie par deux schémas devient une seule ligne portant les deux ensembles de colonnes. Les types propres à un schéma ajoutent simplement leurs propres tables, livrées via un delta de migration automatique dans le flux — aucun re-téléchargement nécessaire.
Lorsque vous liez un schéma, une étape de comparaison montre exactement quelles tables seront fusionnées (avec leurs clés et leurs colonnes ajoutées) et lesquelles sont nouvelles ; un schéma qui partage une table adopte les clés de base de données existantes de cette table, présentées pour votre révision. Si les schémas n'ont rien en commun, le flux propose plutôt une base de données dédiée. Délier un schéma ne touche jamais à votre base de données — les tables synchronisées demeurent.
Dissocier un schéma, ou supprimer une synchronisation, laisse deux choses derrière de notre côté : l'état d'entité stocké que plus rien n'écrit, et les propriétés de base de données portées par le schéma (database keys, types de colonnes, index, appartenance). Les deux confirmations proposent de les supprimer, et uniquement pour les schémas qui ne conservent aucune base de données — un schéma encore synchronisé ailleurs garde tout. Le schéma lui-même, ses enregistrements d'enrichissement et ses coûts ne sont jamais affectés.
Un schéma lié possède un contrat publié : la version que vos enrichissements et votre base de données utilisent réellement. Modifier le schéma ne touche qu'une copie de travail — les changements de formulation sont répercutés automatiquement, tandis que les changements structurels (nouveaux champs, changements de type ou de clé) attendent que vous appuyiez sur Publier. La publication prévisualise l'impact exact et envoie la migration adéquate dans le flux de deltas : les nouvelles colonnes arrivent sous forme de deltas ALTER TABLE, et les changements plus lourds (une nouvelle clé de base de données, un changement de type) s'exécutent comme des migrations protégées sur votre propre base de données — si des données les bloquent (une valeur de clé manquante ou en double), le flux se met en pause avec le problème exact et réessaie automatiquement une fois que vous l'avez corrigé.
La publication se trouve dans l'onglet Model de la base de données (l'Éditeur de workflow affiche une bannière l'indiquant tant que le schéma est lié). Avant de publier, il montre les deux versions : le contrat sur lequel votre base de données repose aujourd'hui, et un diff de tout ce que votre copie de travail modifierait. Une modification ne vous convainc pas ? Revenir à la version publiée rétablit le contrat — réversible, et le brouillon que vous avez mis de côté reste restaurable pendant 24 heures.
Re-lier un schéma qui a été modifié pendant qu'il était délié fonctionne de la même manière : la synchronisation se souvient de ce que votre base de données possède déjà et n'envoie que la différence, plus une actualisation du snapshot pour les lignes écrites entre-temps. Jamais de DROP manuel.
La même promesse couvre nos propres mises à niveau. Lorsqu'une nouvelle version améliore la façon dont les schémas sont mappés aux tables, votre synchronisation est migrée pour vous — les changements additifs arrivent dans le flux d'eux-mêmes. Si une mise à niveau devait restructurer des tables que vous possédez déjà, nous ne touchons jamais à vos données sans préavis : la livraison est mise en pause et votre page Database Sync vous demande de l'appliquer, en montrant d'abord exactement ce qui change.
L'enrichissement multilingue est ici aussi de premier ordre : les valeurs localisées arrivent sous forme de colonnes JSONB contenant chaque langue de l'enrichissement — {"en": "Headache", "fr": "Céphalée"} — de sorte qu'une seule base de données sert toutes vos locales à la fois. Choisissez une langue directement dans vos requêtes (name->>'fr'), et les charges utiles de deltas JSON transportent les mêmes objets indexés par langue.
Chaque base de données répond à une question lors de sa déclaration : lorsqu’un enrichissement revient avec des lacunes — des champs non nullables non remplis —, qu’est-ce qui est écrit ? Les trois réponses forment une échelle. Rien : une seule lacune, où que ce soit, y compris dans un objet imbriqué, et l’entité est rejetée. L’entité, sans ses enfants incomplets (valeur par défaut) : la ligne de l’entité elle-même doit être complète, mais un enfant défectueux est ignoré et signalé plutôt que de faire couler tout l’enrichissement. Tout : les lacunes deviennent des NULL et rien n’est rejeté — mais l’état de l’entité suit le principe du dernier écrit, le dernier enrichissement est la ligne : une exécution partielle ultérieure efface donc ce qu’une précédente avait rempli. C’est précisément cet effacement que les deux niveaux stricts servent à empêcher.
Les enrichissements qui échouent au contrôle sont malgré tout sauvegardés comme enregistrements et déclenchent quand même le webhook record.created — avec database.saved à false — en indiquant précisément quels champs obligatoires manquaient, afin que des données incomplètes ne disparaissent jamais silencieusement. Chaque champ manquant précise aussi si un modèle l'a déclaré inconnu ou l'a simplement omis : le premier cas appelle un modèle plus puissant, une recherche web ou un document source, le second un examen du schéma ou des données d'entrée. Seuls les champs de clé de base de données sont toujours obligatoires : un enrichissement dont une valeur de clé manque est rejeté quel que soit l'échelon.
Sur les niveaux stricts, une case à cocher reproduit le même contrat dans votre propre base de données, sous forme de colonnes NOT NULL sur chaque champ toujours présent. Au niveau le plus strict, les clés étrangères des références requises sont elles aussi contraintes — aucune ligne admise ne peut en être privée. Avec ignorer les enfants incomplets, elles restent nullables, délibérément : un élément de liste auquel manque une de ses propres valeurs est supprimé, et une référence un-à-un partagée dont la cible est incomplète (le stade dont personne ne connaît l'année d'ouverture) est détachée — cette cible n'est ni écrite ni mise à jour, et la ligne sauvegardée ne pointe vers rien : NULL est donc écrit exactement dans ces colonnes de clé étrangère. Les deux cas sont signalés dans la réponse d'enrichissement, et les lacunes dans les champs de premier niveau entraînent toujours un rejet. Modifier la politique après la première synchronisation n'est jamais du travail perdu : elle est livrée sous forme de migration protégée dans le flux, validée sur les lignes que votre base de données contient déjà.
Un second contrôle détecte les identités en double : lorsque deux éléments d'une même liste aboutissent à la même clé de base de données — un modèle inventant un même id pour deux entreprises différentes, ou une clé qui ne les distingue pas — une seule ligne peut exister : la dernière est écrite et les précédentes sont abandonnées, selon la même règle du dernier écrit l'emporte qu'ailleurs. Chaque collision est signalée avec les valeurs identifiantes des deux éléments et un verdict : un doublon a répété les mêmes valeurs et n'a rien perdu ; un abandon conflictuel a perdu les valeurs qu'il nomme — soit le modèle a répété une même chose avec du bruit, soit il s'agit de choses différentes et la clé a besoin d'une propriété discriminante (une région, une année, une version). La liste est conservée sur l'enregistrement : une écriture partielle indique donc encore ce qu'elle a perdu, longtemps après la disparition de la réponse.
Une chose à savoir avant de relancer un enrichissement : pour une liste qui appartient à son parent, la liste du dernier enrichissement fait foi. Une ligne enfant que la dernière réponse ne reprend pas est supprimée de votre base de données — c'est ainsi qu'une suppression réelle vous parvient, et la réponse ne la signale pas. Cela compte lorsque la liste est de celles que le modèle restitue de mémoire plutôt qu'il n'énumère : demandez deux fois les isotopes d'un élément ou les récompenses d'une personne, et la seconde réponse peut être plus courte, ce qui supprime des lignes qui étaient exactes. Conservez votre propre historique si vous avez besoin de l'union de toutes les exécutions.
Les enrichissements atteignent d'eux-mêmes une base de données liée. Tout le reste — un résultat refusé par le contrôle d'admission avant que vous ne corrigiez le schéma, une exécution volontairement exclue, ou une sortie que vous souhaitez d'abord vérifier ou corriger — passe par l'envoi d'enregistrements vers la base de données : sélectionnez-les sur la page Historique, ou appelez l'API depuis un workflow.
L’aller-retour. Enrichissez avec la synchronisation de base de données désactivée, remaniez ou validez le résultat dans votre propre workflow, puis envoyez-le. Ce que vous envoyez est revalidé par rapport au contrat publié du schéma et passe par le même contrôle d’admission qu’un enrichissement — une injection ne peut jamais écrire ce qu’un enrichissement n’aurait pas pu écrire.
Deux détails à connaître. Envoyer un enregistrement tel quel le stocke sous cet enregistrement. Envoyer une sortie modifiée crée un nouvel enregistrement qui renvoie à l'original, car les enregistrements constituent une piste d'audit : ils ne changent jamais sous les données qui les citent, si bien que le contenu de votre base de données reste toujours traçable jusqu'à un enregistrement contenant exactement ces valeurs. Et la validation utilise le contrat tel qu'il est aujourd'hui — si le schéma a évolué depuis la production de l'enregistrement, la page Historique le signale avant l'envoi.
La page Historique indique aussi, pour chaque enregistrement, s'il est bien arrivé dans la base de données : envoyé, partiellement envoyé ou refusé avec le motif. Disponible depuis l'application web, l'API, MCP, n8n et Make.
Le flux est une file FIFO stricte par base de données : récupérez une fenêtre (éventuellement réservée, afin que le traitement par lot d'un worker planté soit redistribué avant tout élément plus récent), appliquez, puis accusez réception. Les notifications webhook sont temporisées — chaque nouveau delta réinitialise un minuteur de période calme, si bien qu'une rafale d'enrichissements n'est annoncée qu'une seule fois ; un délai maximal configurable plafonne l'attente, et une page de récupération pleine déclenche l'envoi immédiatement. Deux options de purge déterminent ce que Entity Enricher conserve : supprimer les copies de delta livrées dès l'accusé de réception et — pour la minimisation des données — supprimer l'état de l'entité lui-même une fois que toutes les bases de données liées au schéma l'ont reçu. Chacune accepte un délai facultatif en jours : les copies livrées subsistent alors d'autant après l'accusé de réception (une fenêtre de rejeu), et une entité livrée est conservée jusqu'à ce qu'elle soit restée aussi longtemps sans mise à jour, une purge horaire supprimant ce qui a expiré. Notez que la purge d'état relève de la minimisation, pas de l'effacement : les enregistrements d'enrichissement demeurent jusqu'à ce que vous les supprimiez, et elle désactive la fusion inter-enrichissements pour les entités purgées.
Un point de terminaison de somme de contrôle par table vous permet de vérifier à tout moment que votre réplique a convergé, sans rien retélécharger.
Tout se trouve au même endroit dans l'application — Database Sync, juste en dessous de Historique dans la barre latérale : enregistrez une base de données sur n'importe quel schéma (avec la revue de la clé de base de données), liez ou déliez des schémas, mettez en pause le flux d'enrichissement d'un schéma lié à l'aide de son interrupteur (plus aucune donnée ni notification jusqu'à réactivation — les publications de schéma continuent d'envoyer leur DDL, et les enrichissements exécutés pendant la pause n'atteignent le réplica que par un nouveau tirage d'instantané), modifiez ses options, consultez le point de terminaison webhook et révélez sa clé de signature, téléchargez l'instantané, parcourez l'état actuel des entités, inspectez la file des deltas en attente (en lecture seule — le curseur de votre workflow n'est jamais touché) et affichez un diagramme entité-association des tables générées avec leurs clés et leurs jonctions. Sur une synchronisation multi-bases, le diagramme peut se concentrer sur un seul schéma : les tables, colonnes et liens alimentés par les autres schémas sont grisés — toujours visibles à leur place — pour que vous voyiez exactement ce que chaque schéma apporte aux tables partagées.
Lorsque plusieurs bases de données aboutissent sur la même machine, le bouton Hôtes de synchronisation de la barre d'outils supprime le cérémonial d'appairage base par base : appairez cette machine une seule fois, puis affectez-lui des inscriptions. L'hôte revendique chacune d'elles, crée la base de données physique si elle n'existe pas et lance la synchronisation — inscrire une base de données devient ainsi une décision que vous prenez ici, et non une session de terminal sur le serveur. L'appairage se fait par serveur Entity Enricher, de sorte qu'une même machine peut servir plusieurs instances côte à côte.
Une instruction que votre base refuse — des doublons préexistants sous un nouvel index unique en sont la cause habituelle — ne bloque pas la file derrière elle. Le traitement par lot de cet enrichissement est mis en quarantaine, le flux continue de circuler, et le traitement par lot est listé dans l'onglet Quarantaine avec l'instruction que votre base a rejetée. Corrigez la cause puis réinjectez — ce qui reprojette l'entité depuis son état actuel au lieu de rejouer une instruction périmée — ou abandonnez-le.
GET /api/databases//changes?since=…&format=sql puis POST /api/databases//ack.Vous utilisez Supabase ? Notre comparaison avec Supabase MCP montre comment les règles de relation et de synchronisation d'EE protègent un catalogue produits, avec du JSON et de petits diagrammes de tables.
Rien dans la synchronisation ne s'exécute jamais sur votre serveur de base de données — chaque voie ci-dessus est un consommateur sortant qui se connecte au DSN que vous lui fournissez. Azure Database for PostgreSQL, OVHcloud, AWS RDS, Supabase ou toute autre instance gérée fonctionne exactement comme une instance auto-hébergée : pointez le consommateur vers le DSN cloud (les fournisseurs gérés imposent généralement TLS, ajoutez donc sslmode=require) et appliquez.
--dsn vers l'instance gérée suffit.delta_available réveille une Azure Function / AWS Lambda / fonction OVHcloud qui récupère le flux REST, exécute le SQL et acquitte.Deux règles suffisent à sécuriser tout consommateur écrit à la main : exécutez les instructions de chaque traitement par lot dans l'ordre, au sein d'une seule transaction, et acquittez uniquement après le commit. Les deltas sont idempotents et protégés par révision : un plantage avant l'acquittement signifie simplement que le traitement par lot est redistribué et que sa réapplication converge.
Les bases de données sont disponibles sur les offres payantes (l'offre détermine le nombre que vous pouvez enregistrer). PostgreSQL est le dialecte de lancement ; chaque base de données déclare son dialecte, MySQL / MariaDB, SQL Server et Oracle étant prévus.