{"id":20270029,"url":"https://github.com/xcraft-inc/goblin-warehouse","last_synced_at":"2026-06-20T08:31:31.005Z","repository":{"id":47989889,"uuid":"151210413","full_name":"Xcraft-Inc/goblin-warehouse","owner":"Xcraft-Inc","description":null,"archived":false,"fork":false,"pushed_at":"2026-03-28T20:46:43.000Z","size":397,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2026-03-28T22:29:19.526Z","etag":null,"topics":["cache-service","core-services","feeds","goblins","shared-state"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Xcraft-Inc.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2018-10-02T06:33:41.000Z","updated_at":"2026-03-28T20:46:45.000Z","dependencies_parsed_at":"2024-01-26T13:39:07.217Z","dependency_job_id":"6ebdf3d9-5ad4-4dc1-bb06-afa69e2779be","html_url":"https://github.com/Xcraft-Inc/goblin-warehouse","commit_stats":null,"previous_names":[],"tags_count":55,"template":false,"template_full_name":null,"purl":"pkg:github/Xcraft-Inc/goblin-warehouse","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Xcraft-Inc%2Fgoblin-warehouse","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Xcraft-Inc%2Fgoblin-warehouse/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Xcraft-Inc%2Fgoblin-warehouse/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Xcraft-Inc%2Fgoblin-warehouse/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Xcraft-Inc","download_url":"https://codeload.github.com/Xcraft-Inc/goblin-warehouse/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Xcraft-Inc%2Fgoblin-warehouse/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34563535,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-20T02:00:06.407Z","response_time":98,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cache-service","core-services","feeds","goblins","shared-state"],"created_at":"2024-11-14T12:28:32.859Z","updated_at":"2026-06-20T08:31:31.000Z","avatar_url":"https://github.com/Xcraft-Inc.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 📘 goblin-warehouse\n\n## Aperçu\n\nLe module `goblin-warehouse` est un composant central de l'écosystème Xcraft qui fournit un système de stockage et de gestion d'état partagé entre les différents acteurs (Elf et Goblin). Il implémente un mécanisme sophistiqué de gestion des relations entre les entités (branches), avec un système de propriété (ownership) qui permet de suivre les dépendances entre les objets et d'effectuer un nettoyage automatique lorsque les objets ne sont plus référencés.\n\n## Sommaire\n\n- [Structure du module](#structure-du-module)\n- [Fonctionnement global](#fonctionnement-global)\n- [Exemples d'utilisation](#exemples-dutilisation)\n- [Interactions avec d'autres modules](#interactions-avec-dautres-modules)\n- [Détails des sources](#détails-des-sources)\n- [Licence](#licence)\n\n## Structure du module\n\nLe module est organisé autour de plusieurs composants clés :\n\n- **Service principal** (`lib/service.js`) : Le cœur du module, acteur Goblin singleton qui gère l'état global du warehouse\n- **Garbage Collector** (`lib/garbageCollector.js`) : Système de nettoyage automatique des entités non référencées\n- **Explorateur visuel** (`widgets/warehouse-explorer/`) : Interface graphique pour visualiser et analyser l'état du warehouse\n- **Utilitaires graphiques** (`lib/dotHelpers.js`) : Génération de représentations visuelles des relations entre branches\n- **Tests** (`test/subscriptions.spec.js`) : Suite de tests validant le comportement des subscriptions et du garbage collector\n\n## Fonctionnement global\n\nLe warehouse fonctionne comme une base de données en mémoire qui stocke l'état des acteurs Goblin et Elf. Chaque entité stockée est appelée une **branche** (branch) et peut entretenir des relations parent-enfant avec d'autres branches au sein de **feeds** (canaux de données).\n\n### Architecture des données\n\n```\nWarehouse State\n├── _creators: {}           # Créateurs de chaque branche (traçabilité)\n├── _generations: {}        # Numéros de génération et flags de dispatch\n├── _subscriptions: {}      # Abonnements aux feeds avec relations parent/enfant\n├── _patchFeeds: {}         # Feeds configurés pour la livraison de patches\n├── _maintenance: {}        # Configuration du mode maintenance\n├── _lines: {}              # Gestion des notifications de lignes\n├── _linesNotifyPayload: bool  # Flag de notification de payload\n├── _feedsAggregator: ...   # Agrégateur pour optimiser les notifications\n└── [branch@id]: {}         # Données des branches elles-mêmes\n```\n\n### Système de propriété (Ownership)\n\nLe warehouse implémente un graphe orienté de relations :\n\n- **Branches** : Entités identifiées par `namespace@id` (instances) ou `namespace` (singletons)\n- **Parents/Enfants** : Relations hiérarchiques maintenues dans les deux sens\n- **Feeds** : Canaux regroupant des branches pour diffusion aux clients\n- **Générations** : Versioning pour le suivi des changements et les acknowledgments\n\n### Feeds et système de patches\n\nLes feeds sont des canaux de données auxquels les clients s'abonnent pour recevoir les mises à jour en temps réel :\n\n1. **Première livraison** : L'état complet du feed est envoyé via l'événement `\u003cfeed\u003e.changed` avec `_xcraftPatch: false`\n2. **Mises à jour suivantes** : Seuls les diffs (patches) sont envoyés avec `_xcraftPatch: true`\n3. **Agrégation** : Un `MapAggregator` regroupe les notifications en fenêtres de 50ms pour réduire la charge\n\n### Garbage Collection automatique\n\nLe `GarbageCollector` surveille les relations et supprime automatiquement :\n\n- Les branches sans parents valides (immédiatement collectées à l'upsert)\n- Les cascades de suppression lors de la suppression d'un parent\n- Les feeds vides après suppression de toutes leurs branches\n- Les branches ORC (`goblin-orc@`) qui déclenchent un auto-release\n\nLe debouncing à 50ms évite les suppressions prématurées lors d'opérations en rafale. L'événement `warehouse.released` est émis en batch pour notifier les branches collectées.\n\n### Mode maintenance\n\nLe mode maintenance permet de restreindre les opérations du warehouse à un ORC spécifique (`orcName`), empêchant les modifications concurrentes lors des mises à jour critiques.\n\n## Exemples d'utilisation\n\n### Gestion basique des branches\n\n```javascript\n// S'abonner à un feed\nawait this.quest.warehouse.subscribe({\n  feed: 'myFeed',\n  branches: ['entity@1', 'entity@2'],\n});\n\n// Ajouter une entité avec relations de propriété\nawait this.quest.warehouse.upsert({\n  branch: 'entity@3',\n  data: {id: 'entity@3', name: 'Mon entité', status: 'active'},\n  feeds: 'myFeed',\n  parents: 'entity@1',\n  generation: 1,\n});\n\n// Récupérer des données\nconst entity = await this.quest.warehouse.get({path: 'entity@3'});\n\n// Vérifier l'existence\nconst exists = await this.quest.warehouse.has({path: 'entity@3'});\n```\n\n### Requêtes avancées\n\n```javascript\n// Recherche par type avec filtres (AND entre critères)\nconst activeEntities = await this.quest.warehouse.query({\n  feed: 'myFeed',\n  type: 'entity',\n  filter: {status: 'active'},\n  view: ['id', 'name', 'status'],\n});\n\n// Recherche par IDs spécifiques\nconst specificEntities = await this.quest.warehouse.query({\n  ids: ['entity@1', 'entity@3'],\n  view: ['id', 'name'],\n});\n\n// Recherche multi-types\nconst mixed = await this.quest.warehouse.query({\n  feed: 'myFeed',\n  type: ['actor1', 'actor2'],\n});\n```\n\n### Gestion des relations hiérarchiques\n\n```javascript\n// Attacher une branche à un parent supplémentaire\nawait this.quest.warehouse.attachToParents({\n  branch: 'child@1',\n  parents: ['parent@1', 'parent@2'],\n  feeds: 'myFeed',\n  generation: 2,\n});\n\n// Détacher d'un parent (déclenche une collecte si plus aucun parent)\nawait this.quest.warehouse.detachFromParents({\n  branch: 'child@1',\n  parents: ['parent@1'],\n  feed: 'myFeed',\n});\n\n// Vérifier les abonnements d'une branche\nconst feeds = await this.quest.warehouse.getBranchSubscriptions({\n  branch: 'child@1',\n  filters: ['system'],\n});\n```\n\n### Opérations par lot\n\n```javascript\n// Mise à jour de plusieurs branches en une fois\nawait this.quest.warehouse.upsertInBatch({\n  branches: {\n    'item@1': {id: 'item@1', value: 100},\n    'item@2': {id: 'item@2', value: 200},\n    'item@3': {id: 'item@3', value: 300},\n  },\n  parents: 'container@1',\n  feeds: 'itemsFeed',\n});\n\n// Suppression en lot (contourne le système d'ownership)\nawait this.quest.warehouse.removeBatch({\n  branches: ['item@1', 'item@2', 'item@3'],\n});\n```\n\n### Greffage entre feeds\n\n```javascript\n// Copier une branche et ses ancêtres vers un autre feed (top-down)\nawait this.quest.warehouse.graft({\n  branch: 'entity@4',\n  fromFeed: 'sourceFeed',\n  toFeed: 'targetFeed',\n});\n\n// Copier une branche et ses descendants (bottom-up)\nawait this.quest.warehouse.graft({\n  branch: 'entity@4',\n  fromFeed: 'sourceFeed',\n  toFeed: 'targetFeed',\n  reverse: true,\n});\n```\n\n### Maintenance et diagnostic\n\n```javascript\n// Activer le mode maintenance\nawait this.quest.warehouse.maintenance({\n  enable: true,\n  description: 'Mise à jour système en cours',\n  orcName: 'maintenance-orc',\n});\n\n// Vérifier l'intégrité du warehouse\nawait this.quest.warehouse.check();\n\n// Lister les feeds actifs\nconst feeds = await this.quest.warehouse.listFeeds();\n\n// Forcer la synchronisation immédiate d'un feed\nawait this.quest.warehouse.syncChanges({feed: 'myFeed'});\n\n// Renvoyer l'état complet d'un feed\nawait this.quest.warehouse.resend({feed: 'myFeed'});\n\n// Générer des graphiques de visualisation\nawait this.quest.warehouse.graph({output: '/tmp/warehouse-graphs'});\n```\n\n## Interactions avec d'autres modules\n\nLe warehouse est un composant fondamental qui interagit avec l'ensemble de l'écosystème Xcraft :\n\n- **[xcraft-core-goblin]** : Fournit les mécanismes de base Goblin (quêtes, Shredder, dispatch)\n- **[xcraft-core-utils]** : `MapAggregator` pour l'agrégation efficace des notifications de changement\n- **[xcraft-core-busclient]** : Communication événementielle pour l'émission des événements `warehouse.released`\n- **[xcraft-core-log]** : Journalisation des avertissements et erreurs internes\n- **[xcraft-immutablediff]** : Calcul optimisé des diffs entre états Immutable.js pour les patches\n- **[xcraft-jsonviz]** : Génération de graphiques au format DOT (Graphviz)\n\nLe warehouse alimente indirectement tous les composants React de l'application via le mécanisme de feeds, qui transmet les mises à jour d'état aux clients abonnés ([goblin-laboratory] agit comme pont entre le warehouse et les widgets).\n\n## Détails des sources\n\n### `warehouse.js` et `warehouse-explorer.js`\n\nPoints d'entrée exposant les commandes Xcraft via `exports.xcraftCommands`. `warehouse.js` redirige vers `lib/service.js` et `warehouse-explorer.js` redirige vers `widgets/warehouse-explorer/service.js`. Ces fichiers permettent au framework de découvrir et charger dynamiquement les commandes sur le bus Xcraft au démarrage.\n\n### `lib/service.js`\n\nService principal implémentant un acteur **Goblin singleton**. Il est initialisé via `Goblin.createSingle` et expose ses quêtes sur le bus Xcraft.\n\n#### État et modèle de données\n\n| Champ                 | Type                                                               | Description                                                           |\n| --------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------- |\n| `_creators`           | `{[branch]: creator}`                                              | Créateur de chaque branche en cours de création                       |\n| `_generations`        | `{[branch]: {generation, hasDispatched}}`                          | Versioning et flag de dispatch de chaque branche                      |\n| `_subscriptions`      | `{[feed]: {branches: {[branch]: {parents, children}}, views: {}}}` | Structure complète des abonnements                                    |\n| `_patchFeeds`         | `{[feed]: true}`                                                   | Feeds activés pour la livraison de patches                            |\n| `_maintenance`        | `{enable, description, orcName}`                                   | Configuration du mode maintenance                                     |\n| `_lines`              | `{[lineId]: {[orcName$token]: count}}`                             | Compteurs de lignes de mise à jour                                    |\n| `_linesNotifyPayload` | `boolean`                                                          | Indique si le payload doit être inclus dans la notification de lignes |\n| `_feedsAggregator`    | `MapAggregator`                                                    | Instance d'agrégateur pour les notifications de changement            |\n| `[branch@id]`         | `object`                                                           | Données effectives de chaque branche                                  |\n\n#### Méthodes publiques\n\n- **`upsert(branch, data, parents, feeds, generation)`** — Ajoute ou met à jour une branche. Gère les relations d'ownership, met à jour les générations, et notifie les feeds abonnés via le `_feedsAggregator`. Les branches sans parents valides dans aucun feed sont immédiatement collectées.\n\n- **`upsertInBatch(branches, parents, feeds)`** — Effectue plusieurs `upsert` successifs pour un ensemble de branches partageant les mêmes parents et feeds. Optimisé pour les mises à jour groupées.\n\n- **`get(path, view)`** — Récupère la valeur à un chemin donné dans l'état. Supporte un paramètre `view` (liste de propriétés) pour filtrer le résultat via `Shredder.pluck`.\n\n- **`has(path)`** — Vérifie l'existence d'un chemin dans l'état du warehouse.\n\n- **`hasFeed(feedName)`** — Vérifie si un feed est actuellement actif dans les subscriptions.\n\n- **`query(feed, ids, type, filter, view)`** — Requête avancée sur l'état. Filtre par type (préfixe avant `@`), par IDs, et applique des filtres de propriétés (AND). Supporte les vues pour réduire les données retournées. Si `feed` est fourni, restreint la recherche aux branches de ce feed.\n\n- **`subscribe(feed, branches)`** — Crée ou met à jour un abonnement à un feed. Enregistre le feed comme patch-feed et initialise les relations d'ownership pour chaque branche. Émet `\u003cfeed-subscriptions-changed\u003e`.\n\n- **`unsubscribe(feed)`** — Supprime un abonnement et déclenche la collecte en cascade de toutes les branches du feed. Émet `\u003cfeed-subscriptions-changed\u003e`.\n\n- **`feedSubscriptionAdd(feed, branch, parents)`** — Ajoute dynamiquement une branche à un feed existant via `attach-to-parents`.\n\n- **`feedSubscriptionDel(feed, branch, parents)`** — Retire dynamiquement une branche d'un feed via `detach-from-parents`.\n\n- **`attachToParents(branch, generation, parents, feeds, view)`** — Établit des relations parent-enfant avec validation rigoureuse. Si un parent est introuvable, l'attachement est refusé. Retourne `true` si l'attachement est effectif. Supporte optionnellement une vue pour restreindre les données diffusées.\n\n- **`detachFromParents(branch, parents, feed)`** — Supprime des relations parent-enfant. Si la branche n'a plus de parents dans un feed, elle est collectée. Déclenche la désinscription du feed si la branche principale du feed disparaît.\n\n- **`deleteBranch(branch)`** — Supprime une branche en déclenchant `unsubscribeBranch` sur l'ensemble des feeds. Les branches `goblin-orc@` déclenchent un auto-release.\n\n- **`graft(branch, fromFeed, toFeed, reverse)`** — Copie une sous-arborescence d'un feed à un autre. En mode normal (top-down), copie la branche et tous ses ancêtres. En mode `reverse` (bottom-up), copie la branche et tous ses descendants.\n\n- **`acknowledge(branch, generation)`** — Confirme la suppression d'une branche en vérifiant que la génération correspond. Si validé, supprime la branche et sa génération de l'état.\n\n- **`release(branch)`** — Supprime explicitement une branche de l'état via `removeBatch` et émet l'événement `released`.\n\n- **`removeBatch(branches)`** — Supprime plusieurs branches et leurs générations directement de l'état, sans passer par le système d'ownership.\n\n- **`resend(feed)`** — Renvoie l'état complet d'un feed à tous ses abonnés, en réinitialisant l'historique des patches pour forcer une livraison complète.\n\n- **`getCreator(branch)`** — Retourne le créateur enregistré d'une branche en cours de création.\n\n- **`delCreator(branch)`** — Supprime le créateur d'une branche et la détache du parent virtuel `new`.\n\n- **`getBranchSubscriptions(branch, filters)`** — Retourne la liste des feeds contenant une branche donnée, avec filtrage optionnel par préfixe.\n\n- **`requestLineUpdate(type, lineId, orcName, token, generation)`** — Gère les compteurs de lignes de mise à jour (add/delete). Émet `lines-updated` avec le payload des lignes si nécessaire.\n\n- **`maintenance(enable, description, orcName)`** — Active ou désactive le mode maintenance. En mode actif, seul l'ORC désigné peut effectuer des opérations.\n\n- **`check()`** — Analyse l'état pour détecter les branches orphelines (parents manquants dans les feeds) et les branches pendantes (présentes dans l'état mais absentes de tous les feeds). Journalise les résultats.\n\n- **`checkOrphan()`** — Retourne la liste des branches dont un parent est référencé dans un feed mais absent de l'état.\n\n- **`checkDangling()`** — Retourne la liste des branches présentes dans l'état mais non référencées dans aucun feed.\n\n- **`status()`** — Journalise l'état complet des subscriptions et des générations pour débogage.\n\n- **`graph(output)`** — Génère deux fichiers `.dot` dans le répertoire `output` : un graphique simple (layout `fdp`) et un graphique détaillé (layout `dot`), horodatés.\n\n- **`syncChanges(feed)`** — Force le `_feedsAggregator` à livrer immédiatement les changements en attente pour un feed.\n\n- **`listFeeds()`** — Retourne la liste des feeds actifs, en excluant les feeds système (`system*` et `null`).\n\n### `lib/garbageCollector.js`\n\nClasse `GarbageCollector` spécialisée dans la gestion du cycle de vie des branches. Elle est instanciée une seule fois au niveau du module avec un callback `feedDispose`.\n\n#### Fonctionnement du garbage collection\n\n1. **`_collect`** : Retire une branche d'un feed en mettant à jour les relations bidirectionnelles parents/enfants. Si un enfant n'a plus de parents dans ce feed, il est ajouté à la liste de collecte pour traitement en cascade. Si la branche n'est plus dans aucun feed, elle est ajoutée à `_collectable` pour émission de l'événement `warehouse.released` avec debounce de 50ms.\n\n2. **`_purgeCollectable`** : Émet l'événement `warehouse.released` en batches de 50 entrées contenant les branches collectées et leur génération.\n\n3. **Auto-release** : Les branches `goblin-orc@` sont supprimées de l'état immédiatement sans attendre un `acknowledge`.\n\n#### Méthodes principales\n\n- **`updateOwnership(state, immState, branch, parents, feeds, isCreating, creator)`** — Met à jour les relations de propriété pour une branche dans plusieurs feeds. Valide l'existence de tous les parents ; si un parent est manquant, la branche est immédiatement collectée. Gère le marqueur `new` pour les branches en cours de création.\n\n- **`unsubscribeBranch(state, branch, feed, autoRelease)`** — Point d'entrée principal du GC. Lance une boucle de collecte qui propage les suppressions aux enfants orphelins.\n\n- **`getOwnership(state, path)`** — Récupère ou initialise une structure d'ownership `{parents: {}, children: {}}`.\n\n- **`inFeeds(state, branch)`** — Vérifie si une branche est présente dans au moins un feed.\n\n- **`inFeed(state, feed, branch)`** — Vérifie si une branche est présente dans un feed spécifique.\n\n- **`inPatchFeed(state, feed, branch)`** — Vérifie si une branche est dans un feed configuré pour les patches.\n\n- **`extractFeeds(state, branch)`** — Retourne un mapping `{feed: {branch: true}}` pour tous les feeds contenant la branche.\n\n- **`extractPatchFeeds(state, branch)`** — Retourne la liste des feeds patch contenant la branche.\n\n### `lib/dotHelpers.js`\n\nUtilitaires pour la génération de graphiques Graphviz (format DOT) via [xcraft-jsonviz].\n\n**`generateGraph({type, layout}, state)`** — Génère un objet `JsonViz` représentant l'ensemble des feeds et leurs relations. Supporte deux modes de rendu : `'simple'` (nœuds circulaires compacts) et `'complexe'` (tableaux HTML détaillés avec métadonnées de génération et d'état).\n\nLa coloration sémantique des nœuds reflète le type d'acteur selon le suffixe du namespace : `-worker` (cyan), `-workitem` (magenta), `-feeder` (jaune), `-updater` (vert), `-dispatcher` (bleu), autres (gris).\n\n### `widgets/warehouse-explorer/service.js`\n\nService Goblin (acteur instanciable, non singleton) dédié à l'interface d'exploration. Expose trois quêtes principales :\n\n- **`create(desktopId)`** — Initialise l'explorateur en récupérant la liste des feeds actifs depuis le warehouse.\n- **`explore(type, value)`** — Charge la structure d'un feed spécifique pour visualisation (branches, parents, enfants).\n- **`check()`** — Lance les vérifications d'intégrité (`checkDangling`, `checkOrphan`) et stocke les résultats dans l'état.\n- **`delete()`** — Destructeur de l'instance (vide).\n\n### `widgets/warehouse-explorer/widget.js`\n\nComposant React principal (`WarehouseExplorer`) intégrant une interface à deux panneaux :\n\n**Panneau gauche** : liste cliquable des feeds actifs (boutons) et arbre hiérarchique (`SubscriptionTree`) affichant les branches du feed sélectionné via le composant `Tree` de goblin-gadgets.\n\n**Panneau droit** : graphique interactif (`Graph` / `SubGraph`) utilisant [react-cytoscapejs] avec l'algorithme de layout `dagre` (orientation gauche-droite). Les nœuds représentent les branches, les arêtes les relations parent/enfant. Le composant `Graph` gère l'adaptation Immutable.js via les méthodes `getImm`, `toJsonImm` et `diffImm`.\n\nLes deux sous-composants (`SubGraph`, `Tree1`, `WarehouseExplorer`) utilisent `Widget.connect` pour se connecter à l'état du backend warehouse-explorer.\n\n**Exemple d'utilisation** :\n\n```jsx\n\u003cExplorer id={workitemId} desktopId={desktopId} /\u003e\n```\n\nProps connectées via `Widget.connect` : `subs` (liste des feeds), `sub` (feed sélectionné), `orphan` (résultats de vérification), `dangling` (résultats de vérification).\n\n### `widgets/warehouse-explorer/view.js`\n\nVue principale (`WarehouseExplorerView`) qui compose l'interface complète : un titre avec icône de base de données et le widget `Explorer`. Dérive de `View` fourni par goblin-laboratory.\n\n### `widgets/warehouse-explorer/styles.js`\n\nDéfinit le style `tree` pour le conteneur de l'arbre hiérarchique : `flexGrow: 1`, `display: flex`, `flexDirection: column`, permettant à l'arbre d'occuper tout l'espace disponible.\n\n### `test/subscriptions.spec.js`\n\nSuite de tests Mocha/Chai validant le comportement du warehouse via `Elf.Runner`. Les tests couvrent :\n\n**Tests de base** : `upsertHas` (ajout et vérification), `collectSingle` (collecte d'une branche sans parent valide), `collectedBecauseNoSubscriber` (collecte immédiate si le feed parent n'existe pas dans les subscriptions).\n\n**Tests de cascade** : `collectSimpleCascade` (suppression en cascade linéaire), `collectMultiCascade` (suppression en cascade arborescente multi-niveaux).\n\n**Tests multi-feeds** : `subTwoFeeds` (partage de branche entre feeds avec suppression sélective), `subTwoDeepFeeds` (propriété multiple avec conservation inter-feeds), `subExtraDeepFeeds` (hiérarchies profondes avec détachement partiel), `subAndUnsubFeed` (cycle complet d'abonnement/désabonnement).\n\n**Tests de greffage** : `graft topdown` (copie ascendante d'une branche vers un autre feed), `graft bottomup` (copie descendante avec `reverse: true`).\n\n**Tests de relations** : `attachParents` (attachement dynamique à un parent supplémentaire).\n\n## Licence\n\nCe module est distribué sous [licence MIT](./LICENSE).\n\n---\n\n[xcraft-core-goblin]: https://github.com/Xcraft-Inc/xcraft-core-goblin\n[xcraft-core-utils]: https://github.com/Xcraft-Inc/xcraft-core-utils\n[xcraft-core-busclient]: https://github.com/Xcraft-Inc/xcraft-core-busclient\n[xcraft-core-log]: https://github.com/Xcraft-Inc/xcraft-core-log\n[xcraft-immutablediff]: https://github.com/Xcraft-Inc/immutable-js-diff\n[xcraft-jsonviz]: https://github.com/Xcraft-Inc/jsonviz\n[goblin-laboratory]: https://github.com/Xcraft-Inc/goblin-laboratory\n[react-cytoscapejs]: https://github.com/plotly/react-cytoscapejs\n\n_Ce contenu a été généré par IA_\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxcraft-inc%2Fgoblin-warehouse","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxcraft-inc%2Fgoblin-warehouse","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxcraft-inc%2Fgoblin-warehouse/lists"}