La Frond

Une Frond est un module métier qui regroupe les éléments responsables d'un même domaine.

La frontière de propriété

Une Frond contient :

  • ses entités et schémas,
  • ses opérations — commands et queries (handlers),
  • ses policies — les règles d'accès vérifiées dans les opérations,
  • ses events — les faits que ses commands annoncent, auxquels rien ne s'abonne par défaut,
  • ses adapters de persistance — comment ses entités se réalisent,
  • ses contrats publics — ce que les autres Fronds peuvent appeler,
  • ses règles d'invalidation — ce qu'une command revalide.

Une Frond n'importe pas les fichiers internes d'une autre. Les interactions passent par des contrats explicites, de préférence des opérations ou des events. Une App compose les Fronds et conserve la logique métier dans ces modules plutôt que dans ses pages, fetchs ou stores.

Les six couches

Le modèle suit six étapes :

#CoucheCe qui s'y passe
1Déclarationl'auteur déclare entités, vues, opérations, events — ni HTTP ni SQL ici
2Dérivationvalidation, types, vues io, metadata lisible par les adapters en dérivent
3Capacité localela déclaration devient exécutable in-process — le mode de référence
4Projectiondes façades sont générées vers les transports : HTTP, GraphQL, CLI, workers
5Compositionles Fronds sont scannées et reliées dans un runtime — module → contrat → interaction
6Distributionune capacité part hors process ; sérialisation, timeouts, retries apparaissent ici, explicitement

La page sur le gradient décrit le passage de l'exécution locale à l'exécution distante.

Généré vs écrit à la main

La répartition est la suivante :

Fougere génèreVous écrivez
validation, vues dérivées, tables/metadatala logique métier non triviale
types et inputs GraphQL, CRUD trivialles invariants riches, les workflows multi-étapes
les façades de projection (contrat → transport)les politiques d'erreur, les décisions de cohérence
les clients typés, les manifests de compositionl'orchestration inter-Fronds sensible

Fougere prend en charge les intégrations répétitives autour d'un contrat. Le développeur écrit le comportement propre à l'application. Une couche service reste optionnelle : un handler peut contenir la logique ou la déléguer.

Dans le système de fichiers

fronds/blog/
  entities/             ← les déclarations
  handlers/             ← les opérations   (handlers/<surface>/ = une audience nommée)
  presenters/           ← les champs calculés ajoutés à la sortie d'une entité
  collectors/           ← la résolution de paramètres par type
  services/             ← des classes ordinaires, injectées par type
  repositories/         ← la même chose, sous un second nom
  seeds/                ← les données créées au démarrage
  versions/             ← ce que ses formes étaient (`fougere freeze`)

Le scanner reconnaît une Frond à ces dossiers. La racine du projet peut suivre la même convention : une app avec un seul domaine n'a alors pas besoin de répertoire fronds/ (la forme à plat). Un deuxième domaine peut ensuite être ajouté sous fronds/ sans déplacer le premier.

Ces noms sont le seul endroit de Fougere où un nom EST la déclaration, donc les seuls qu'un projet puisse redire. conventions: dans fougere.config.ts nomme ce qui diffère, et rien d'autre :

export default defineFougere({
  conventions: {
    scope: '@domaines',                       // défaut : '@fronds'
    fronds: 'domaines',                       // défaut : 'fronds'
    dirs: { entities: 'modeles' },            // les sept autres gardent leur nom
  },
})

Il est lu avant la découverte des fronds, ce qui est précisément ce qui lui permet de nommer le scope sous lequel elles sont trouvées — donc rien dans fougere.config.ts ne peut importer @fronds/*. Tout le reste en découle : le scan, les alias qu'une page résout, le watcher, et le package.json que fougere sync écrit chez un consommateur.

Nommer et importer une Frond

Le nom du dossier est le nom de la Frond, et @fougere/nuxt en fait un alias : fronds/blog/ donne @fronds/blog, donc une page écrit import Post from '@fronds/blog/entities/Post' sans aucun fichier à ajouter. Ce nom sert aussi de clé aux enregistrements d'entités et à toute entrée remotes:.

Un package.json dans la Frond répond aux deux questions que le dossier ne sait pas dire.

{
  "name": "@fronds/blog",
  "fougere": { "frond": "blog" },
  "exports": { "./entities/*": "./entities/*.ts" }
}

fougere.frond permet d'utiliser un nom différent de celui du dossier. Par exemple, fronds/blog-v2/ peut rester enregistrée sous le nom blog, ce qui conserve ses clés d'entité, ses imports @fronds/* et ses entrées remotes:. Sans cette propriété, Fougere utilise le nom du dossier.

name et exports rendent la Frond résolvable en dehors d'une app Nuxt — un script Node, un consommateur non-Nuxt — ce qui demande aussi de la déclarer comme paquet du workspace (packages: [fronds/*] dans pnpm-workspace.yaml). Dans une app Nuxt, l'alias suffit.

services/ et repositories/ sont tous deux scannés comme providers, résolus par type. Chaque entité a déjà un repository par défaut : Crud(Entity) le reçoit automatiquement, et le reste du code demande RepositoryOf<Entity> ou un <Entité>Repository déclaré. L'injection directe d'Storage dans un handler, un presenter ou un collector est refusée au boot. Écrivez un repository quand une requête mérite un nom ou quand plusieurs entités forment un agrégat ; écrivez un service pour une source qui n'est pas un stockage (une API externe ou un index de recherche).

Quand l'une de ces classes en étend une autre, la base devient un port : le handler déclare la base et reçoit l'implémentation, et laquelle répond est une ligne de fougere.config.ts — jamais de la Frond.

Construit avec Fougere — ce site tourne sur le framework qu'il documente.