Qu'est-ce que Fougere
Fougere est un framework TypeScript construit sur une seule idée : vous déclarez le domaine, et tout en dérive — jusqu'au process où il tourne.
Énoncée à la forme négative, elle se vérifie : la déclaration ne nomme rien en dehors d'elle-même. Ni table, ni protocole, ni hôte, ni adresse n'y figurent. Deux conséquences en découlent, et on les vend d'habitude comme deux fonctionnalités distinctes :
| La déclaration ne nomme pas… | donc cette chose est… | son nom usuel |
|---|---|---|
| sa table, son type GraphQL, son formulaire, son juge | dérivée d'elle | single-schema |
| son hôte, son stockage, sa porte, son adresse | choisie en dehors d'elle | le gradient |
Une seule règle lue dans deux directions — ce qu'une déclaration produit, et ce dont on peut l'entourer. Le reste de cette page, ce sont ces deux lectures.
Ce qui en dérive
Single-schema. Une classe d'entité déclare vos données une fois — et juge elle-même
ses entrées : le même validate() tourne dans le navigateur et à la façade. Le juge est
lui-même une projection — dérivée de l'axe shape — mais une projection normative,
embarquée avec la classe : toutes les autres doivent lui obéir, et elle ne peut pas
dériver seule. Les tables
SQLite, les types GraphQL, les contrats de formulaire et les surfaces d'API sont des
projections de cette déclaration — rien n'est écrit deux fois.
import { entity, primary, text, created, oneOf, date, readOnly, optional } from '@fougere/schema';
export default class Post extends entity({
id: primary(),
title: text({ min: 1, max: 160 }),
body: optional(text()),
createdAt: created(),
status: readOnly(oneOf('draft', 'published', { default: 'draft' })),
publishedAt: readOnly(optional(date())),
}) {}
Le schéma — déclaré une fois
class Post extends entity({
id: primary(),
title: text({ min: 1 }),
status: readOnly(oneOf(
'draft', 'published')),
}) {}Post.validate(input)dérivée du shape · embarquée avec la classe
Surface d'API
post.list · post.publish
Table de base de données
auto-DDL → SQLite
Type TypeScript
function render(p: Post)
Contrat de formulaire
useFormFor(Post)
Type GraphQL
type Post { … }
Désignation & DI
useQuery(Post, 'list')
Un noyau, six projections — changez la déclaration, chaque projection suit.
Cette seule classe est à la fois :
- le type TypeScript d'une ligne (
function render(p: Post)— pas d'Infer<typeof …>), - le validateur des entrées client (
Post.validate(input)), - la metadata que chaque adapter lit (
Post.getFields()), - la désignation que les pages utilisent pour appeler les opérations (
useQuery(Post, 'list')), - le type nominal que l'injection de dépendances matche dans les signatures (
user?: User).
Ce qui est choisi en dehors
Le gradient. La logique métier vit dans des Fronds — des modules autonomes d'entités, handlers, collectors et seeds. Une Frond tourne in-process aujourd'hui et dans son propre process demain, derrière JSON-RPC 2.0, avec un code utilisateur identique. L'énoncé de topologie entier tient en une ligne de config :
// fougere.config.ts
remotes: { blog: 'http://127.0.0.1:4100' }
Pas de RPC sans voyage : un appel est une valeur (entity, operation, invocation) ; le
runner l'exécute directement en mémoire quand la Frond est locale et la met sur le fil
quand elle est distante. Les transports déplacent la valeur — ils ne la remodèlent jamais.
Donc le split coûte le saut et le JSON qui voyage avec, et rien d'autre : aucune sérialisation que le chemin local éviterait, aucun impôt du framework par-dessus le réseau.
Les quatre familles que la règle refuse de nommer, dessinées — le gradient étant la quatrième, lue comme un mouvement plutôt que comme une liste :
La Frond — ce que vous avez écrit
entities · operations · its judge · its facts
elle n'en nomme aucune des quatre
L'hôte
Nuxt · Next · SvelteKit
TanStack · React Router · Express · none
Le stockage
SQLite · Postgres
MySQL · SQL Server
La porte
in memory · JSON-RPC
REST · GraphQL
Le lieu
same process · another process
another repo · another language
lue comme un mouvement, celle-ci est le gradient
La Frond ne nomme aucune des quatre familles. Chacune est choisie en dehors d'elle — et la dernière, lue comme un mouvement plutôt que comme une liste, est le gradient.
Et ça se vérifie au diff : les cinq demos qui servent ce même blog sous Next,
TanStack Start, React Router, SvelteKit et Express partagent un répertoire fronds/
identique à l'octet, et trois de ces hôtes ne demandent aucun paquet Fougere. Alors
progressif ne veut dire que ceci : chaque pas vers l'extérieur
énonce son prix, et aucun ne
demande de réécrire ce que vous avez écrit.
Ordre de lecture
Concepts — Philosophie · La Frond · Le socle
Côté serveur — Une app que vous avez déjà · Apportez votre schéma · Démarrer · La CLI · Entités · Vues · Standard Schema · Handlers · Presenters · Collectors · Erreurs · Seeds · Les faits · Le port Storage · Les repositories
Côté client — Queries & commands · Formulaires · Session · invoke
Topologie — Le gradient · Surfaces · Déploiement · Les hôtes · Les sources
Statut. Fougere est en alpha : les paquets
@fougere/*sont sur npm sous le tagalpha, et cette documentation décrit l'API telle qu'elle existe dans le dépôt aujourd'hui. Ce site tourne dessus.