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 :
| # | Couche | Ce qui s'y passe |
|---|---|---|
| 1 | Déclaration | l'auteur déclare entités, vues, opérations, events — ni HTTP ni SQL ici |
| 2 | Dérivation | validation, types, vues io, metadata lisible par les adapters en dérivent |
| 3 | Capacité locale | la déclaration devient exécutable in-process — le mode de référence |
| 4 | Projection | des façades sont générées vers les transports : HTTP, GraphQL, CLI, workers |
| 5 | Composition | les Fronds sont scannées et reliées dans un runtime — module → contrat → interaction |
| 6 | Distribution | une 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ère | Vous écrivez |
|---|---|
| validation, vues dérivées, tables/metadata | la logique métier non triviale |
| types et inputs GraphQL, CRUD trivial | les 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 composition | l'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.