Hôtes
Un hôte, c'est ce qui sert le HTTP : Nuxt, Next, TanStack Start, React Router,
SvelteKit, Express. Une Frond ignore lequel la fait tourner, et rien dans fronds/
n'en importe un — les six démos de ce dépôt partagent un dossier fronds/ identique à
l'octet près.
Ce qu'un hôte fait, c'est monter trois portes et, s'il rend des pages, offrir les quatre
primitives client. Tout le reste — le boot, le juge, la table REST, le contrat de
formulaire — vit dans @fougere/app et ne change pas.
Ce que coûte chaque hôte
| Hôte | Paquet | Ce que vous écrivez |
|---|---|---|
| Nuxt | @fougere/nuxt | une ligne dans nuxt.config |
| Next | @fougere/next | trois fichiers de route d'une ligne + withFougere() |
| TanStack Start | aucun | trois fichiers de route + le plugin Vite |
| React Router | aucun | trois routes ressource + le plugin Vite |
| SvelteKit | aucun | trois +server.ts + le plugin Vite |
| Express | aucun | app.use(fougere()) |
Trois hôtes n'ont besoin d'aucun paquet, et ce n'est pas un hasard : ils servent des
Request/Response standard, donc les portes s'y branchent telles quelles.
@fougere/next existe parce que Next a next/headers — un seul import — et le module
Nuxt est plus gros parce qu'un événement h3 n'est pas une Request.
Ce qu'une app publie
Quels adaptateurs de protocole sont servis est un fait sur l'app, déclaré une fois
à côté de db, remotes et auth :
// fougere.config.ts
export default defineFougere({
db: 'sqlite',
adapters: { rest: true, graphql: true },
});
graphql: true est toute la configuration GraphQL : le schéma est construit depuis les
entités que l'app a déjà scannées, donc il n'y a ni builder à monter ni types à
enregistrer. @fougere/adapter-graphql, @pothos/core et graphql ne sont importés que
si vous le déclarez — une app qui n'en sert pas ne transporte pas un constructeur de
schéma pour le découvrir.
Absent veut dire non servi. La porte peut être montée — le fichier de route existe, le
middleware est installé — elle déclinera quand même, parce que ce n'est pas à l'hôte de
décider. serveRest lit la déclaration lui-même, donc les six hôtes héritent d'une
seule réponse au lieu de l'épeler chacun à sa façon.
L'enveloppe n'y figure pas : c'est le fil que les primitives client utilisent, pas une projection qu'une app choisit de publier.
Monter les portes
Monter une porte n'est pas publier un adaptateur : ce que vous écrivez ci-dessous met
la porte en place, et adapters: décide si elle sert quelque chose.
Un hôte au standard Web — Next, TanStack Start, React Router, SvelteKit — monte les handlers directement :
// SvelteKit — src/routes/_fougere/call/+server.ts
import { fougereCall } from '@fougere/app/web';
export const POST = ({ request }: { request: Request }) => fougereCall(request);
fougereCall, fougereRest et fougereSession prennent une Request et rendent une
Response. Le fichier que vous créez est le consentement : pas de fichier, pas de porte.
Express les ajoute en middlewares, comme express.json() ou cors() :
import { fougere, fougereCall, fougereRest } from '@fougere/app/express';
app.use(fougere()); // les trois portes
app.use(fougereCall()); // ou une par une — l'enveloppe sert vos pages,
app.use('/admin', fougereRest()); // REST est publique. Ce n'est pas une seule décision.
Un chemin que Fougere ne sert pas appelle next(), donc les routes de l'app
continuent de répondre, qu'elles soient déclarées avant ou après.
Nuxt les monte lui-même — son module a une API pour ça, et il ajoute les composables au passage.
Les primitives client
useQuery, useCommand, useFormFor et useCurrentUser existent par framework d'UI, pas
par hôte :
@fougere/react— React, quel que soit ce qui le rend@fougere/svelte— Svelte, sous forme de stores@fougere/nuxt— Vue, via la couche données de Nuxt
Les règles qu'elles suivent sont communes : la désignation est classe + verbe, et une commande sur une entité revalide toutes les requêtes montées sur cette entité. Seul le modèle d'état change.
La seule chose à dire à tout bundler
La désignation lit le nom de classe de l'entité, et ce nom voyage — c'est la méthode
JSON-RPC (post.list) et le chemin REST. Un minifieur renomme les classes, donc un build
de production désignerait une entité que personne n'héberge :
Entity 'j' is not hosted here. Hosted here: post.
Vous n'avez rien à déclarer pour autant. @fougere/nuxt et @fougere/vite réservent les
noms d'entités pour vous — le premier depuis le scan qu'il vient de faire, le second en
lisant fronds/<frond>/entities/. Sur Next, withFougere() fait la même chose via webpack :
// next.config.mjs
import { withFougere } from '@fougere/next/config';
export default withFougere();
// vite.config.ts — TanStack Start, React Router, SvelteKit
import { fougere } from '@fougere/vite';
export default defineConfig({ plugins: [fougere(), sveltekit()] });
Express n'a besoin de rien : il exécute vos sources, rien n'est minifié.
Deux hôtes à la fois
Rien n'empêche une Frond d'être servie par plusieurs hôtes : faites-la tourner dans son
propre process avec serve(), et que les autres l'atteignent par remotes:. C'est le
gradient, et l'hôte n'entre pas dans la décision.