Nodefony
Framework Node.js fullstack — HTTP & WebSocket, même contexte

Nodefony — le framework Node.js fullstack

Temps réel natif, développement agentic-ready, sur un socle TypeScript isomorphe. Dix minutes pour comprendre ce que le framework couvre, ce qu'il coûte et ce qui lui manque.

Vous êtes…

Directeur techniqueMon équipe pourra-t-elle reprendre le code seule ?

Libre, une seule personne, bénévole : seule la dernière majeure reçoit des correctifs.

Le parcours complet →
ExploitationComment ça se déploie en conteneur et s'orchestre ?
Développeur frontComment l'interface se bâtit et se recharge à chaud ?
Développeur backComment je persiste mes données, sur quelle base ?
ArchitecteQuels choix ont été faits, et pourquoi ?

Ou directement : la documentation · les mesures · le dépôt, sous licence Apache 2.0.

L'essentiel en trente secondes

Un framework Node.js fullstack, en TypeScript strict, bâti directement sur les modules natifs de la plateforme. Pensé pour le temps réel : le serveur HTTP et le serveur WebSocket partagent le même contexte de contrôleur.

Version
10.0.0
monodépôt, paquets verrouillés
Node
≥ 24.14.0
ESM uniquement
Domaines
15
cœur + paquets publiables
Symboles
4 914
graphe relationnel indexé

Trois propriétés le définissent, et chacune se vérifie dans le code plutôt qu'elle ne se raconte : le temps réel de première classe, l'isomorphisme serveur/navigateur, et une application qui sait se décrire à un outil comme à une personne.

Ce qu'est Nodefony

A note on language. Nodefony is written by a French-speaking author. The code — identifiers, public API, types — is in English, because it travels: npm, editor autocompletion, search. The prose — documentation, agent skills, messages — is in French, the language in which the author explains the why most precisely. Coding agents read it without difficulty; human readers may not, and translating the framework’s texts is planned (#378).

Et NestJS ? Nodefony en partage le modèle — injection, décorateurs, modules — et s’en inspire volontiers. Ses priorités portent ailleurs : une même route sert HTTP et WebSocket, avec la même session et le même pare-feu ; et l’application est pensée pour être lue par un agent de code — générateurs pilotables, catalogue, AGENTS.md dérivé du projet. Deux frameworks, deux accents : le bon choix dépend de ce que votre projet met en avant.

État — version 10.0.0 · préversion 10.0.0-beta.6 en ligne, sous le canal beta uniquement : npm install nodefony sert toujours la 7.0.2 (JavaScript), et le fera jusqu’à la sortie stable · branche par défaut main (dernière publication), développement sur dev · ~7 700 tests quand toute l’infrastructure répond (npm run test:all).

⚠️ Ce dépôt est l’atelier du framework, pas ce qu’une application installe — agent : AGENTS.md. Pour juger le produit, lire une application générée (github.com/nodefony/nodefony, sortie brute de create app) ou en générer une : npm create nodefony@beta <app>.


Nodefony en bref

Nodefony est un framework serveur fullstack pour Node.js, écrit en TypeScript strict et bâti directement sur les modules natifs de la plateforme — node:http, node:http2, WebSocket. Il apporte un noyau à injection de dépendances qui donne à chaque requête son propre conteneur, un système de modules, un pare-feu applicatif, une persistance portable, une console d’administration et la construction des frontends.

Sa particularité tient en une propriété : le WebSocket n’y est pas un ajout. C’est un transport de première classe, servi par le même pipeline, la même table de routes et la même sécurité que le HTTP. Une application temps réel s’y écrit comme une application web ordinaire.

npm create nodefony@beta mon-app

Chaque requête a son propre conteneur

Un processus Node sert des centaines de requêtes entrelacées : pendant que l’une attend sa base de données, une autre s’exécute. Nodefony donne donc à chaque requête son propre conteneur de services, posé comme un calque sur celui de l’application.

  • Le mécanisme. Le conteneur du kernel est la carte ; à l’entrée d’une requête, le pipeline pose un calque (enterScope) — un objet dont le prototype est la carte. À la fin, leaveScope le jette.
  • Ce qu’on y fait. On lit toute la carte à travers le calque (les services de l’application s’héritent) ; on n’écrit que sur son calque. Deux requêtes concurrentes ont deux calques et ne se voient jamais ; rien de ce qui est posé sur un calque ne survit à la requête.
  • Le fil qui relie tout : AsyncLocalStorage. Chaque requête s’exécute dans une bulle ALS de Node (RequestContext) : son identifiant, l’utilisateur, la trace W3C et le calque suivent chaque await, jusque dans un service ou un adaptateur ORM, sans qu’on les passe d’appel en appel.
  • Comment on l’atteint. Depuis n’importe quel code de la requête, sans passer le contexte : RequestContext.getScope() rend le calque courant (ou undefined), requireScope() lève en nommant la cause. La bulle ALS de la requête dit lequel est le tien. Un hook onAfterResponse le voit encore ouvert ; une promesse non attendue qui continue après la réponse, non.
  • HTTP et WebSocket. Une requête HTTP = un calque. Une connexion WebSocket = un calque, partagé par tous ses messages, concurrents compris.
  • Des services par requête. @injectable({ scope: "request" }) donne un exemplaire par requête, créé seulement si la requête le demande, et nettoyé (clean(), dans l’ordre inverse des créations) à sa fermeture. Un singleton qui en dépendrait est refusé dès le démarrage, plutôt que de devenir lui-même, sans qu’on l’ait écrit, un service par requête.
  • Le coût, avec son décor. Poser puis jeter un calque ≈ 1,43 µs (sonde dans le serveur, un processus en production, paires alternées) ; une requête qui ne résout aucun service par requête ne paie rien de plus.
import { Service, injectable, RequestContext } from "nodefony";
import type { Scope } from "nodefony";

@injectable({ name: "requestStamp", scope: "request" })
export class RequestStamp extends Service {
  readonly requestId: string;
  constructor(scope: Scope) {
    super("requestStamp", scope, false); // un exemplaire pour CETTE requête
    this.requestId = RequestContext.getRequestId() ?? "?";
  }
}

Le détail — les trois durées de vie, l’accès par RequestContext, les pièges : Injection & portées.

Les frontends ne sont pas laissés dehors. Nodefony pilote Vite : en développement il démarre les serveurs de développement — React, Vue, Angular, Svelte —, les surveille, relaie leur rechargement à chaud et sert leurs pages ; en production il construit les bundles et les distribue. Une application peut en porter plusieurs, chacun monté sur sa route.

Le socle, lui, est isomorphe : le même paquet s’importe côté serveur et côté navigateur. Le client temps réel, les règles d’autorisation, les types d’une ressource sont écrits une fois et s’exécutent là où ils servent — une règle corrigée l’est des deux côtés.

Le temps réel, de bout en bout

Nodefony est un framework temps réel. Une application vivante pose toujours les mêmes questions : pousser au navigateur, dans un sens ou dans les deux ; ouvrir des salons et savoir qui s’y trouve ; décider qui entre ; faire passer des octets ; tenir à plusieurs instances ; voir ce qui se passe quand ça coince. Chacune a sa réponse dans le framework, écrite contre la norme qui la définit, et éprouvée par des tests.

Ce qu’il vous faut Ce que Nodefony fournit
Échanger dans les deux sens La socket Nodefony : des canaux à volonté sur une seule connexion — le serveur diffuse, le client appelle et attend la réponse, ou pousse sans attendre. JSON-RPC 2.0 sur WebSocket (RFC 6455), dans le pipeline HTTP
Pousser dans un seul sens Les flux SSE (text/event-stream) : une action répond par this.renderSse(), en HTTP/1.1 comme en HTTP/2. Le client isomorphe se reconnecte avec Last-Event-ID, et sait envoyer un POST et des en-têtes
Des salons et leurs membres Canaux à membres : lister qui est là, abonner d’office, éjecter un compte, fermer le salon, sur toutes les instances. Un message privé atteint une personne par publishToUser
Décider qui entre Une règle d’admission par canal (allow, fonction ou service injecté), sous le même pare-feu que le HTTP. Origine vérifiée à la poignée de main ; un jeton expiré ferme la socket avec un code dédié
Faire passer des octets CBOR (RFC 8949), négocié connexion par connexion — détaillé ci-dessous
Tenir à plusieurs instances Un bus de diffusion entre workers ou par Redis, messages scellés (HMAC-SHA256). Un seul producteur par canal et par processus, quel que soit le nombre d’abonnés
Tenir la charge Contre-pression en sortie, débit borné en entrée, cadence de diffusion qui s’adapte à chaque client
Des types de bout en bout Un contrat écrit une fois type le contrôleur, la socket et les hooks React, Vue, Svelte et Angular. Les routes qu’il annonce sont vérifiées au démarrage
Voir et tester Une sonde, la console d’administration, les onglets Socket et SSE de la barre de débogage, un traceur de trames ; une identité d’essai pour tester un canal protégé
gRPC — à venir en 10.0.0-beta.7 Des services gRPC servis par le même pipeline HTTP/2, déclarés depuis un module, avec la réflexion et la santé standard

Chaque besoin a son exemple complet et testé : jeu (déplacements, recherche de partie), salon privé sur invitation, sonde industrielle, tableau de bord partagé, notification privée, tâche d’agent.

Le binaire, sans conversion

JSON reste le format par défaut. Dès que des octets circulent — image, son, mesures de capteur, état d’une partie —, le serveur accepte CBOR, un JSON binaire. Nodefony embarque son propre codec : isomorphe, sans dépendance, environ 5,5 Ko compressés, et téléchargé seulement par la page qui l’importe. Il rend exactement ce que JSON rendrait, à une exception près : un Uint8Array voyage en octets et revient en Uint8Array. Un message ordinaire pèse 30 à 45 % de moins, et 64 Ko d’octets en occupent 65,6 au lieu des 87,4 du base64.

Le codec se négocie connexion par connexion. Le client qui l’offre parle CBOR ; celui qui ne l’offre pas reste en texte et reçoit les mêmes données. Aucun client ne casse, et un canal suivi par les deux sortes de connexions paie un encodage par codec, jamais un par abonné.

// nodefony.config.ts — le serveur accepte CBOR
use("@nodefony/realtime", { codec: "cbor" });
// navigateur — le client l'offre à l'ouverture
import { NodefonySocket } from "nodefony/client";
import { CBOR_FRAME_CODEC } from "nodefony/cbor";

const socket = NodefonySocket.shared({
  url: "/chat/realtime",
  codec: CBOR_FRAME_CODEC,
});

→ La socket Nodefony · Les cas d’usage · Les flux SSE · Le codec CBOR

L’IA : votre application, ouverte aux agents

Un agent d’IA atteint une application par une porte standard : le Model Context Protocol (MCP). Il demande quels outils elle offre, reçoit pour chacun un nom, une description et un schéma d’entrée, puis les appelle. @nodefony/ai donne cette porte à votre application et tient le protocole à votre place — versions, erreurs, autorisation, progression, annulation. Vous écrivez une fonction ; elle devient un outil, que Claude, ChatGPT, Copilot ou l’agent d’un client savent appeler.

Ce qu’il vous faut Ce que Nodefony fournit
Ouvrir une porte MCP npx nodefony create controller mcp --kind mcp génère la porte, deux outils d’exemple et leur test. MCP révision 2026-07-28, clients plus anciens acceptés
Suivre un outil long Sur la même requête HTTP, la réponse passe en flux SSE : l’agent reçoit l’avancement (notifications/progress), puis le résultat — et peut annuler en route, l’outil s’arrête proprement
Servir un agent sans serveur Le transport stdio : npx nodefony ai:stdio, pour une intégration continue ou un agent qui lance lui-même son serveur. Les mêmes outils, la même fonction
Ouvrir en production --expose oauth : jeton exigé, serveur d’autorisation annoncé (RFC 9728), un scope par outil — un appelant ne voit que ce qu’il peut appeler. --expose mixte : des outils publics, les autres réservés
Agir au nom de quelqu’un actsForUser : l’outil s’exécute avec le compte de l’appelant, sous les mêmes règles qu’une route protégée
Dire ce que fait l’outil Chaque outil annonce s’il lit ou s’il modifie, pour que l’agent sache quand demander confirmation ; le test généré refuse un outil qui ne le dit pas
Des outils dans chaque module Un module déclare les siens par getMcpTools() ; une porte les ramasse tous, sans registre à tenir
Développer avec un agent de code @nodefony/devkit ouvre en développement la porte /nodefony/mcp : l’agent interroge l’application qui tourne — carte, santé, routes, trafic, profil d’une route — au lieu de deviner

@nodefony/ai ne monte aucune route tant que vous n’ouvrez pas de porte ; paquet expérimental, il suit la spécification MCP et peut évoluer en version mineure, toujours annoncé au journal des versions.

→ Le module IA · La boîte à outils MCP

Démarrage

Une préversion 10.0.0-beta.6 est en ligne sous le canal beta. Une bêta n’est pas une version stable : une rupture reste possible d’une bêta à la suivante — chacune est alors annoncée en tête du CHANGELOG — et elle n’est pas destinée à la production. Elle s’installe en nommant ce canal :

npm create nodefony@beta mon-app
cd mon-app
npm run dev

Le canal se nomme tant que la 10 n’est pas stable : c’est lui qui garantit de recevoir la préversion, et non la 7.0.2 que npm install nodefony sert encore — la version JavaScript d’avant, sans rapport avec ce que cette page décrit.

L’application répond sur http://127.0.0.1:5151 — ou le premier port libre, annoncé au démarrage — et la console d’administration sur /nodefony.

À quoi ressemble ce que la commande produit : github.com/nodefony/nodefony. Ce dépôt n’est pas une démonstration écrite à la main pour la vitrine — c’est la sortie littérale de nodefony create app --preset complete --frontend react, poussée telle quelle et régénérée à chaque publication. Base de données et migration initiale, sécurité et comptes, temps réel, console d’administration, front React, infra docker de développement, chaîne d’intégration continue : tout ce que vous y lisez a été engendré, rien n’y a été retouché. C’est le moyen le plus court de juger le code qu’on aura à maintenir, avant d’installer quoi que ce soit.

Pour travailler sur le framework lui-même, c’est le dépôt qu’on clone :

git clone https://github.com/nodefony/nodefony-core.git
cd nodefony-core
npm install && npm run build
npm run dev

Générer du code — une application, un module, un contrôleur, une entité et toute sa chaîne :

npx nodefony create module blog --frontend react
npx nodefony create entity Article title:string body:text views:int
npx nodefony create controller admin --kind rest --role ROLE_ADMIN

Chaque générateur montre le plan et le diff avant d’écrire quoi que ce soit. Un refus ne laisse rien derrière lui. Il publie aussi son catalogue en JSON et accepte ses réponses par fichier : la même porte sert la personne au terminal, la console d’administration et un agent.

Ce qu’il y a dans la boîte

Brique Rôle
nodefony Noyau : modules, injection de dépendances, configuration validée au démarrage, journalisation structurée corrélée, CLI — et le client temps réel partagé avec le navigateur
@nodefony/http Serveurs HTTP, HTTPS, HTTP/2 et WebSocket natifs, flux SSE, sessions, contextes de requête, certificats TLS
@nodefony/framework Routeur, contrôleurs, décorateurs, vues — le modèle de programmation
@nodefony/security · @nodefony/user Pare-feu par zones, authentification, autorisation par rôles, CSRF, audit
@nodefony/realtime Socket à canaux, salons et leurs membres, admission par canal, binaire CBOR, contre-pression, diffusion entre plusieurs instances
@nodefony/ai Porte MCP : les outils de l’application offerts aux agents d’IA, en HTTP avec flux SSE ou en stdio
@nodefony/orm-core Un contrat de dépôt de données, plusieurs moteurs : Drizzle (SQLite, PostgreSQL, MySQL), Mongoose, Redis
@nodefony/frontend Construction et rechargement à chaud des frontends de chaque module
@nodefony/studio Console d’administration

Par défaut, un processus Node égale une instance : le passage à l’échelle revient à l’orchestrateur, et les journaux partent sur la sortie standard. Sans orchestrateur, nodefony cluster -w N démarre N workers isolés, relancés s’ils tombent.

État du projet

Le cœur — serveurs, routage, sécurité, temps réel, persistance, console d’administration, construction des frontends — est couvert par des suites de tests exécutables sur infrastructure réelle (npm run test:all), et le dépôt versionne des seuils de fuite mémoire et de charge opposables à chaque exécution.

Ce que ça donne en charge. À travail égal — mêmes journaux, même contexte de requête, mêmes en-têtes de sécurité, même protection CSRF —, Nodefony se tient dans la même zone qu’Express et NestJS munis des mêmes intergiciels : entre 93,6 et 112,7 % de leur débit sur une route qui ne fait rien (21 686 requêtes/s, p99 4,4 ms sur la machine de référence), entre 95,9 et 96,7 % sur une route qui lit et écrit une base — pour une résolution de banc de 3 %. Quatre-vingt-huit minutes de charge continue et 97,5 millions de requêtes laissent le tas, l’empreinte mémoire et le rss plats : 0,021 Mo par million de requêtes. La page de mesures de la version courante — peut-on partir en production ? — porte le comparatif, la tenue dans la durée, le dimensionnement d’un pod et un calculateur ; elle nomme aussi ce que ces chiffres ne prouvent pas. Le dossier docs/performance/ donne le protocole, les scripts qui rejouent chaque chiffre et les instruments qui ont menti avant qu’on s’en aperçoive — aucun absolu pris derrière un chemin virtualisé n’est transposable.

Ce qu’il faut savoir avant de bâtir dessus : les versions JavaScript historiques (≤ 7.x) ne reçoivent plus de correctifs. Projet libre, développé bénévolement par une seule personne.

Licence Apache 2.0 — permissive, avec concession de brevet explicite. Christophe Camensuli · ccamensuli@gmail.com


La suite est une couche d’agents IA construite sur ce socle : le même pipeline, la même sécurité, le même temps réel — et une application qui sait déjà se décrire à une machine.

D'où ça vient — de 2017 à la réécriture

Nodefony est publié depuis 2017, en JavaScript, et a mûri jusqu'à sa version 7 : des applications réelles tournent encore dessus. Fin 2023, plutôt qu'une migration par petits pas, le choix a été fait d'une réécriture complète en TypeScript — en gardant les concepts, en jetant tout le reste.

Pourquoi ? Parce qu'un framework ne se contente pas d'exécuter du code : il contraint celui qu'on écrit contre lui. Une couche de types posée après coup décrit ce que le code fait ; elle ne garantit rien. Chaque parti pris de la réécriture ferme une porte :

DécisionCe qu'elle rend impossible
TypeScript strict, zéro anyqu'un contrat se dégrade en silence entre deux modules
ESM exclusivementla double résolution CommonJS/ESM et ses pièges de chargement
Décorateurs plutôt que conventionqu'une route existe sans être déclarée là où on la lit
Configuration validée au démarragequ'une clé mal orthographiée soit ignorée sans un mot
Un processus = une instancela supervision maison ; l'échelle revient à l'orchestrateur
La version 10 est l'aboutissement de cette réécriture. Ce n'est pas un portage : c'est le même projet, repensé pour ce que Node.js et TypeScript sont devenus.

Le pari : un transport n'est pas une architecture

La plupart des piles traitent le WebSocket comme une annexe : une seconde table de routes, une seconde pile d'authentification, une passerelle entre les deux. Nodefony refuse cette séparation. La pseudo-méthode WEBSOCKET est un verbe comme GET :

@controller("/api/blog")
class BlogController extends Controller {
  @route("blog-index", { path: "", method: "GET" })
  async index(@CurrentUser() user?: { identifier?: string }) {
    return this.renderJson({ hello: "blog", who: user?.identifier ?? "anonyme" });
  }

  // Même classe, même décorateur : seul le transport déclaré change.
  @route("blog-echo", { path: "/echo", requirements: { methods: ["WEBSOCKET"] } })
  async echo(message: string | Buffer | null) {
    return this.renderJson({ echo: message?.toString() ?? null });
  }
}
Ce que ça change. Une règle d'autorisation protège l'action quel que soit le transport ; la session ouverte en HTTP est celle que voit la socket ; le contexte asynchrone — identifiant de requête, utilisateur — reste stable du handshake à la fermeture. Pas de passerelle, pas de logique dupliquée, pas de seconde surface à sécuriser.

L'isomorphisme — et pourquoi le cœur reste en TypeScript

Le client n'est pas une bibliothèque à part, publiée séparément et rattrapée à chaque version : il vit dans le même paquet que le serveur et partage ses types. Le contrat d'un canal, la forme d'un message, la hiérarchie des rôles s'écrivent une fois.

Sous-cheminCe qu'il donne au navigateur
nodefony/clientsocket temps réel : canaux, appels de service, reconnexion
nodefony/reacthooks (useNodefony, état de connexion, identité)
nodefony/rolesévaluation d'autorisation avec la règle exacte du serveur
nodefony/debugbarbarre de diagnostic branchée sur le contexte de la requête

Un bouton masqué faute de rôle l'est par la même logique que celle qui refusera l'appel. Il n'y a pas deux vérités à synchroniser.

Pourquoi ne pas réécrire le cœur dans un langage plus rapide ? Parce que l'isomorphisme se paierait exactement là. Un portage ferait gagner des microsecondes et perdrait la seule chose qu'un framework fullstack peut vraiment offrir : un contrat unique, vérifié par le compilateur, du contrôleur jusqu'au composant. Ce qui coûte cher dans une application temps réel, ce n'est pas le langage — c'est la frontière entre deux mondes qui doivent se redire la même chose, et qui finissent toujours par diverger. Nodefony supprime la frontière au lieu d'optimiser le passage.

Prêt pour les agents — ce qui tranche pour la suite

Une application est aujourd'hui écrite à deux mains : la personne, et l'agent qu'elle pilote. Un agent lâché dans un projet bâti sur un framework qu'il connaît mal invente — et il invente du plausible : un CRUD écrit à la main là où un générateur existait, un import direct du driver de base qui contourne la façade, une socket bas niveau là où le framework offre un canal. Ce code compile. Il passe même les tests. Il a vieilli avant d'être relu.

La réponse n'est pas un assistant intégré, c'est de rendre l'application capable de se décrire :

Ce qui est poséCe que ça évite
Un AGENTS.md généré à la racinela convention périmée : le fichier est dérivé du projet réel, il ne peut pas mentir
La documentation voyage dans les paquetsl'agent qui cherche sur le web une version qui n'est pas la vôtre
Un catalogue des briques publiéle choix de module fait au jugé, sans savoir ce qu'un adaptateur ne couvre pas
inspect · check · envla déduction depuis le code : routes, services, configuration effective et sa provenance
Des générateurs pilotables en JSONl'imitation d'un fichier d'exemple au lieu de l'appel à l'outil qui produit le vrai code
Un graphe symbolique du codela fouille par recherche textuelle : qui étend quoi, qui implémente quoi, en une lecture
Le standard retenu — AGENTS.md — est celui que lisent la plupart des outils de codage, avec la règle « le plus proche gagne ». Rien de propriétaire : c'est un index court qui pointe vers la documentation installée au lieu de la recopier. Une règle recopiée dérive ; une règle pointée reste vraie.
Et surtout : c'est MESURÉ. Une application témoin est générée, puis un agent y reçoit des tâches réelles — ajouter un CRUD, protéger une route, écrire une commande, configurer par l'environnement, choisir la bonne brique. Le harnais lit le transcript et le code produit pour répondre à une seule question : a-t-il lu, ou deviné ? La métrique n'est pas « le code marche » — il marche souvent, c'est tout le piège. Le banc n'est pas entièrement vert aujourd'hui, et chaque échec désigne un endroit précis où l'application ne se rend pas assez évidente. C'est à ça qu'il sert.

Aucun framework backend Node n'offre aujourd'hui d'équivalent officiel. C'est un espace vide, et c'est délibérément là que Nodefony se place.

Ce que les suites éprouvent — les angles, pas seulement le nombre

Un compte de tests ne dit rien de ce qu'ils regardent. Ces chiffres sont comptés dans le dépôt au moment de la génération de cette page, fichier par fichier.

Cas d'attaque
524
38 suites offensives : on essaie de casser ses propres garanties
Intégration
1 425
216 suites contre un serveur qui tourne pour de vrai
Bout en bout
271
52 suites : une application générée, démarrée, interrogée
Charge & mémoire
53
seuils de fuite et de débit, tenus par un gate séparé

S'y ajoutent 12 flux d'intégration continue — analyse statique de sécurité, gate mémoire, suites ORM sur plusieurs moteurs, bout-en-bout autonomes, contrôle avant publication et installation à blanc des paquets depuis leurs archives.

Ce que ces chiffres ne prouvent pas. Un test vert ne vaut que pour ce qu'il exerce : une suite qui se passe de son infrastructure se SKIPPE, et un skip compte comme un vert. C'est pourquoi le dépôt fait échouer une passe d'intégration continue dont une cible déclarée n'a pas été exercée — une absence voulue s'énonce, elle ne s'oublie pas.

La matrice — ce qui est couvert, domaine par domaine

Calculé depuis le dépôt au moment de la génération de cette page : pour chaque domaine, le code, les suites de tests, la documentation destinée aux humains, et la fiche d'entrée du paquet.

Cas de test
14 439
exécutés, répartis sur 1 153 fichiers
Pages de doc
149
dont 63 transverses et 26 décisions d'architecture
Fiches de paquet
15/15
README à jour
Modules de code
1 857
fichiers TypeScript de source
Noyau, injection, configuration, journalisation, CLInodefony730couvert ailleurs17✓
Transports HTTP · HTTPS · HTTP/2 · WebSocket, sessions@nodefony/http317couvert ailleurs11✓
Canaux, appels bidirectionnels, diffusion multi-instances@nodefony/realtime153couvert ailleurs13✓
Pare-feu, authentification, autorisation, audit@nodefony/security14812019✓
Routage, contrôleurs, décorateurs, vues@nodefony/framework133couvert ailleurs7✓
Moteur SQL — SQLite, PostgreSQL, MySQL@nodefony/drizzle841402✓
Console d'administration@nodefony/studio53couvert ailleurs2✓
Construction des frontends, rechargement à chaud@nodefony/frontend50couvert ailleurs1✓
Cache, sessions, diffusion entre instances@nodefony/redis37couvert ailleurs3✓
Contrat de dépôt de données, transactions, critères@nodefony/orm-core35212✓
Socle utilisateur, encodeurs, service de comptes@nodefony/user33242✓
Moteur documentaire — MongoDB@nodefony/mongoose30332✓
Portail de documentation@nodefony/documentation21couvert ailleurs2✓
devkit@nodefony/devkit17201✓
ai@nodefony/ai16142✓
Un zéro dans la colonne Tests désigne un paquet dont la couverture vient d'ailleurs (ses consommateurs, ou les suites d'intégration à la racine) — pas une zone non éprouvée. La colonne se trie : c'est fait pour être regardé, pas pour être cru.

La sécurité s'éprouve par l'attaque, pas par la lecture

Le pare-feu découpe l'application en zones, chacune avec sa chaîne d'authentification, et il refuse plutôt que d'ouvrir : une configuration invalide capture le trafic et répond 401.

SurfaceCe qui est en place
Identitéssession serveur pour le web · jetons et clés d'API pour les machines · OAuth2/OIDC · WebAuthn · TOTP
CryptographieArgon2id pour les mots de passe · signatures Ed25519 · secrets chiffrés au repos
DéfensesCSRF par métadonnées de requête · en-têtes de sécurité · limitation de débit · journal d'audit persistant
Autorisationhiérarchie de rôles vérifiée au démarrage, refus par défaut, isomorphe côté navigateur

Équipe rouge, équipe bleue

« Le login fonctionne » ne prouve rien. Les campagnes se mènent en deux passes, et l'ordre est le cœur du dispositif :

  1. Passe rouge — la menace d'abord. La matrice d'attaque est construite depuis les standards et les faiblesses connues avant d'avoir lu le code. C'est un garde-fou contre son propre biais : qui lit l'implémentation en premier ne teste que ce qu'elle prévoit, et rate précisément ce qu'elle a oublié.
  2. Passe bleue — le code ensuite. On lit l'implémentation, on couvre les branches restantes, on regarde les chemins que la passe rouge n'imaginait pas.
  3. Le cycle. Faille trouvée → corrigée → re-prouvée par un test qui échouait avant elle. Un correctif sans son test de non-retour ne compte pas comme corrigé.

Ce qui distingue ces attaques d'un scan générique : beaucoup visent des surfaces propres à cette architecture — les portées de l'injection de dépendances, les messages du pipeline WebSocket partagé, le jeton porté par le contexte asynchrone, les zones du pare-feu et leurs contournements. Aucun outil sur étagère ne connaît ces surfaces : il faut concevoir les attaques.

À savoir avant de concevoir une application. Le refus par défaut opère par zone du pare-feu, pas route par route : une route hors de toute zone déclarée est publique. Déclarez vos zones.
Deux disciplines qui vont plus loin que l'usage
  • Un test qu'on n'a jamais vu échouer ne prouve rien. Un test neuf est débranché volontairement une fois, pour vérifier qu'il tombe. Sinon il est complaisant par construction.
  • L'historique du dépôt est scanné à la recherche de secrets — pas seulement l'état courant : passer un dépôt en public expose chaque révision.

Le framework se regarde tourner

Une console d'administration est livrée avec le framework : topologie du runtime, journaux en direct avec rejeu, suivi d'une requête de bout en bout par son identifiant, schéma de la base, graphe des classes par module, gouvernance de la sécurité, et les générateurs de code pilotables à la souris — la sortie diffusée comme un terminal.

Supervision du runtime — topologie, ressources, journaux en direct
Supervision du runtime — topologie, ressources, journaux en direct

Chaque requête porte un identifiant propagé dans tout le pipeline : la console rejoue son trajet complet — phases, requêtes de base de données, décisions du pare-feu.

Suivi d'une requête de bout en bout par son identifiant
Suivi d'une requête de bout en bout par son identifiant
Sa force est en dessous. Elle ne consomme aucune API privée : tout vient d'un plan de données JSON protégé par les mêmes règles d'accès que le reste, auto-décrit — un appel en renvoie le catalogue — et duplex : le même point d'accès répond en HTTP et par la socket. Ce qu'affiche la console, un script ou un outil peut le lire tel quel. C'est la même porte, pas une porte de service.

Même principe pour les générateurs de code : ils publient leur catalogue, montrent le plan et le diff avant d'écrire quoi que ce soit, et acceptent leurs réponses par fichier. Un refus ne laisse rien derrière lui.

Performance : la question honnête

Le framework fait à chaque requête ce qu'une pile minimale ne fait pas : contexte asynchrone corrélé, identifiant de requête, en-têtes de sécurité, contrôle CSRF, résolution de zone de pare-feu. Comparer sans ce travail ne compare rien. Les constantes ci-dessous servent à dimensionner, pas à gagner un concours.

Graphique en barres1 message WebSocket : 70 µs de boucle d'événements · 1 requête HTTP/2 (multiplexée) : 154 µs de boucle d'événements · 1 requête HTTP/1.1 : 207 µs de boucle d'événements1 message WebSocket 70 µs de boucle d'événements1 requête HTTP/2 (multiplexé… 154 µs de boucle d'événements1 requête HTTP/1.1 207 µs de boucle d'événements
Coût d'une socket
17,5 Ko
empreinte mémoire par connexion
Message WS
3×
moins cher qu'une requête HTTP
Diffusion
≈100×
moins chère qu'un aller-retour
Ces constantes valent pour le pipeline nu, sans session ni base de données. Une route authentifiée paie en plus son magasin de sessions, qui domine tout le reste. La règle : un canal se dimensionne sur les livraisons (publications × abonnés), jamais sur les publications. Rejouez la mesure sur vos propres routes — l'outillage de banc est dans le dépôt.

Ce que le dépôt garantit à chaque commit

BarrièreCe qu'elle empêche
Typage strict, sur tous les espaces de travailaucun avertissement du compilateur mis sous le tapis ; les exceptions de type ne vivent que dans les tests, où elles servent d'assertions
Seuils de mémoire et de charge versionnésune fuite ou une régression de latence passe en échec, pas en discussion
Suites exécutables sur infrastructure réelleun test sauté faute de base compte comme vert : le lanceur dit ce qu'il n'a pas exercé
Graphe symbolique régénéré à chaque commitun renommage silencieux, une relation cassée entre modules
Intégration continue multi-systèmesle « ça marche chez moi » sur une seule plateforme
La discipline la plus utile n'est pas dans cette liste : avant de dire « c'est fait », nommer ce qui n'a pas été lancé. Une phrase suffit. C'est ce qui distingue un vert qui prouve d'un vert qui rassure.

Les preuves existent, et elles sont rejouables

Chaque affirmation de ce support s'adosse à un artefact produit par le dépôt lui-même, pas à une déclaration d'intention. Ces rapports se régénèrent par une commande :

ArtefactCe qu'il établit
Rapport de capacitéles constantes de dimensionnement d'une instance, et le nombre d'instances pour une charge donnée
Banc de charge HTTP et WebSocketle débit soutenable, les percentiles, le point de rupture
Audit du pipeline de requêteoù passe le temps, phase par phase, profileur éteint
Scan de l'historiqueaucun secret dans les révisions du dépôt, pas seulement dans l'état courant
Banc du générateur de codele code produit compile, ses tests passent, sa ressource répond réellement en HTTP
Registre des écarts documentation ↔ codechaque page de référence est confrontée au code qu'elle décrit
Le format n'est pas neutre : un rapport destiné à un humain est manipulable — tableaux triables, calculateurs, graphes — parce qu'un tableau de 200 lignes en texte se fait approuver sans être lu. Ce que vous lisez est produit par ce principe.

Ce qui manque — dit avant qu'on le demande

ManquePortée réelle
Couche d'agents IAseule la porte MCP est livrée (@nodefony/ai : exposer les outils de l'application à un agent) ; modèle, mémoire, recherche augmentée et boucle d'agent restent une vision — rien dans la version courante n'en dépend
Console d'administration sans tests de composantsses écrans sont couverts par le typage, des tests unitaires de leur logique et une sonde navigateur sur deux pages en intégration continue — aucun composant n'est éprouvé en rendu
Projet à un seul contributeurbénévole ; les délais sont dits, pas promis (politique de sécurité), et seule la dernière version majeure reçoit des correctifs (compatibilité)

Dire les manques n'est pas une précaution rhétorique : c'est ce qui rend le reste vérifiable. Un dossier sans zone d'ombre décrit rarement un vrai logiciel.

La direction

La suite est une couche d'agents construite sur ce socle : le même pipeline, la même sécurité, le même temps réel — et une application qui sait déjà se décrire à une machine.

Rien de cette couche n'est promis dans la version courante. Ce qui est acquis, en revanche, c'est le terrain sur lequel elle se posera : un plan de données auto-décrit, un graphe symbolique du code, un générateur pilotable par fichier, et un transport duplex qui fait du flux un cas ordinaire plutôt qu'un montage.