nodefony — le cœur du framework
stablenodefony (cœur)mis à jour 2026-07-19
Le socle dont tous les autres paquets dépendent :
@nodefony/httppour parler HTTP,@nodefony/frameworkpour router,@nodefony/securitypour garder, les adaptateurs de base de données pour persister. Il apporte cinq choses et rien d’autre : une classe de base commune à tout composant, un conteneur de services, un cycle de vie (le kernel et ses modules), une journalisation structurée, et un contexte de requête qui suit le code jusque dans ses recoins asynchrones. Il est aussi la seule brique du framework qui tourne dans le navigateur.
📍 Documentation › Cœur — @nodefony/core
❗ Important
Le paquet npm s’appelle
nodefony, pas@nodefony/core. C’est le piège d’identité numéro un : on lit@nodefony/corepartout — dans cette documentation, dans les fiches de module, dans Studio — parce que c’est le nom interne de la brique. Mais dans unpackage.jsonet dans unimport, c’estnodefonytout court :import { Module, Service, Nodefony } from "nodefony"; // ✅ le vrai nom du paquet import { Module } from "@nodefony/core"; // ❌ ce paquet n'existe pas sur npmL’héritage vient de l’ère JavaScript du framework, et le renommage serait cassant pour tout l’écosystème. Deux autres pièges d’import vivent au même endroit : il n’y a pas d’export par défaut (
import nodefony from "nodefony"échoue — écrireimport { Nodefony } from "nodefony"), et la classe d’erreur s’appellenodefonyError, pasError, pour ne pas entrer en collision avec celle du langage.
🧭 Par où commencer#
Quatre parcours selon ce que tu viens faire. L’ordre compte : chaque étape suppose la précédente.
Je découvre le framework — comprendre le modèle avant d’écrire une ligne.
- Kernel, Module et CliKernel — commence ici. Trois classes suffisent à décrire une application Nodefony : ce que tu écris, ce qui te porte, et la façade qui relie les deux.
- Service — la classe dont hérite absolument tout, kernel et modules compris. Comprendre
Service, c’est comprendre pourquoi chaque composant sait s’injecter, émettre des événements et écrire dans le journal sans qu’on le lui apprenne. - Le cycle de boot — l’ordre d’allumage réel, les hooks où brancher son code, et l’arrêt propre.
- Vue d’ensemble du framework — où se pose le cœur par rapport aux autres modules.
J’écris mon premier module — le chemin le plus court vers du code à toi qui tourne.
- Démarrage rapide — le manifeste de modules, un module, un service injecté. Copiable tel quel.
- Injection et portées — déclarer un service, l’injecter, et surtout choisir sa portée : partagé pour tout le processus, ou recréé à chaque requête. C’est la page canonique du conteneur de dépendances.
- Kernel, Module et CliKernel — les hooks de cycle de vie (
onKernelBoot,onKernelReady), et ce qu’on peut supposer à chacun. - Configuration —
defineConfig,use(), l’environnement typé, et la validation au boot.
Je veux du temps réel dans mon front — le différenciateur du framework, vu du navigateur.
- Le client isomorphe — le même paquet
nodefony, importé côté navigateur. La socket, les canaux, la cadence adaptative, les rôles évalués côté client. - Les hooks React — la même socket, en idiomes React : abonnement, désabonnement au démontage, comptage de références. Ne câble pas le client à la main si tu es en React.
- Les composables Vue — la même surface, en idiomes Vue : un plugin sur l’application, puis des composables dont la portée rend l’abonnement. Même chose si tu es en Vue.
- Les fonctions d’injection Angular — la même surface encore, en idiomes
Angular : un fournisseur dans les
providers, puis des fonctions qui rendent des signals et libèrent l’abonnement à la destruction du composant. La connexion y est ouverte hors zone. - La réactivité Svelte — la même surface, une dernière fois : une
configuration de module, puis des valeurs qui se lisent
.currentet dont l’abonnement est rendu par le système d’effets. Aucune rune n’est publiée, et l’abonnement y est paresseux. @nodefony/realtime— le serveur en face : le hub qui distribue les canaux, et le backplane quand l’application passe à plusieurs répliques.
Je veux tracer une requête de bout en bout — l’enquête, quand quelque chose s’est mal passé.
- Journalisation — la structure d’une entrée de journal, le tampon circulaire qui garde les dernières sans jamais grossir, et les transports qui l’expédient ailleurs.
- RequestContext — ce qui fait qu’un identifiant de requête se retrouve dans une ligne de journal écrite par un service appelé six niveaux plus bas, sans l’avoir passé en paramètre.
- Le pipeline de requête — le trajet complet, de l’octet reçu à l’octet renvoyé, HTTP comme WebSocket.
🗂️ Les pages du cœur#
Le tableau pour choisir en cinq secondes ; les cards en dessous pour savoir ce qu’on y trouve.
| Page | Ce qu’elle résout | Tu en as besoin quand… |
|---|---|---|
| Service | la classe de base : injection, événements, journal | tu écris n’importe quel composant |
| Journalisation | ce qui s’écrit, où ça part, ce qu’on garde en mémoire | tu instrumentes, ou tu enquêtes |
| Kernel & Module | l’API du cœur : démarrer, brancher, étendre | tu écris un module ou une commande |
| CLI | piloter le framework en ligne de commande | tu lances, construis, échafaudes, ou étends |
| Tests | éprouver ce que tu écris — unitaire, bout en bout | tu lances npm test pour la première fois |
| RequestContext | suivre une requête à travers l’asynchrone | tu corrèles des journaux, ou tu lis l’identité |
| Client isomorphe | le même paquet, dans le navigateur | tu écris du front qui parle au serveur |
| Hooks React | la socket en idiomes React | ton front est en React |
| Composables Vue | la socket en idiomes Vue | ton front est en Vue 3 |
| Injection Angular | la socket en idiomes Angular | ton front est en Angular |
ℹ️ Note
L’injection de dépendances n’a pas de page ici, et c’est voulu. Le conteneur, les portées et les décorateurs
@injectable/@inject/@servicessont documentés une seule fois, au niveau transverse : Injection et portées. Le sujet traverse tous les modules — le confiner dans la doc du cœur obligerait chaque autre module à le répéter, et trois vérités valent moins qu’une.
🧩 Ce que le cœur apporte#
Quatre propriétés, toutes vérifiables dans le code — c’est ce qui distingue le cœur d’une simple boîte à outils.
Tout composant partage la même classe de base. Service (Service.ts:43) porte à la fois le
conteneur d’injection, le bus d’événements et l’accès au journal. Conséquence pratique : un module,
un contrôleur et un adaptateur de base de données s’observent, se configurent et se nettoient de la
même façon. Il n’y a pas de composant « à part » dans une application Nodefony.
Le kernel possède les modules, jamais l’inverse. Kernel.boot() (Kernel.ts:799) charge le
manifeste, construit les modules, puis fait passer tout le monde par les mêmes phases. Un Module
(Module.ts:60) déclare s’il est critique — un module non critique dont le démarrage échoue
n’emporte pas le processus, il annonce sa dégradation et le reste continue. Les commandes en ligne
empruntent le même chemin par CliKernel (CliKernel.ts:84).
Le journal est structuré, borné, et il part où tu veux. Syslog (Syslog.ts:628) écrit des
enregistrements typés plutôt que des chaînes, et les conserve dans un tampon circulaire
(CircularBuffer, Syslog.ts:273) : les dernières entrées restent lisibles à chaud, sans que la
mémoire enfle avec le temps de fonctionnement. Les transports décident ensuite de la destination —
sortie standard, fichier, agrégateur.
Le contexte d’une requête voyage sans être passé en paramètre. RequestContext
(RequestContext.ts:115) s’appuie sur le stockage asynchrone de Node : un service appelé profondément
peut lire l’identifiant de requête ou l’utilisateur courant sans que personne n’ait threadé
d’argument. C’est ce qui rend les journaux corrélables et l’identité disponible partout.
Enfin, le cœur est le seul paquet du framework qui tourne aussi côté navigateur : la même distribution publie un build client, ce qui permet de partager du code — et surtout le client temps réel — entre le serveur et l’interface. Le détail des sous-chemins est en Surface publique.
🚀 Démarrage rapide#
Vu depuis une application créée par nodefony create app. Deux fichiers suffisent à voir le cœur
travailler : le manifeste qui décide ce qui est chargé, et un module qui apporte son propre
service.
1. Déclarer ce que l’application charge#
nodefony.config.ts est l’orchestrateur : un seul fichier, et seulement les écarts aux valeurs
d’usine du framework. Le tableau modules est ordonné — c’est l’ordre de chargement.
// nodefony.config.ts — à la racine de l'application
export default defineConfig((ctx) => ({
// Un conteneur doit écouter toutes les interfaces : un port publié par
// l'orchestrateur n'atteint jamais une écoute limitée à la boucle locale.
domain: ctx.isProd ? "0.0.0.0" : "127.0.0.1",
// Le par-environnement passe par `ctx`, jamais par un fichier parallèle.
log: { debug: ctx.isProd ? [] : "*" },
modules: [
"@nodefony/http",
"@nodefony/framework",
// `use()` colocalise la configuration d'un module avec son chargement.
use("@nodefony/realtime", { backplane: { driver: "cluster" } }),
],
}));
2. Écrire un module et son service#
Un module est une classe qui étend Module ; un service est une classe qui étend Service. Le
décorateur @services enregistre le second auprès du premier — le conteneur s’occupe de la
construction et des dépendances.
// nodefony/modules/catalog/index.ts
import { Module, Service, injectable, services } from "nodefony";
import type { Kernel, Container } from "nodefony";
// `@injectable` rend la classe constructible par le conteneur. La portée par
// défaut est partagée : une seule instance pour tout le processus.
@injectable()
class CatalogService extends Service {
constructor(container: Container) {
// Le nom est aussi celui qui apparaît dans le journal — choisis-le lisible.
super("catalog", container);
}
find(sku: string): string {
// Hérité de Service : pas d'importation, pas d'injection de logger.
this.log(`recherche de ${sku}`, "INFO");
return sku;
}
}
// `critical = false` : si ce module échoue à démarrer, l'application monte quand
// même et la dégradation est annoncée. Statique, car lue avant les initialiseurs.
@services([CatalogService])
class CatalogModule extends Module {
static override critical = false;
constructor(kernel: Kernel) {
super("catalog", kernel, import.meta.url, {});
}
// Le kernel est prêt : les autres modules et leurs services existent.
override async onKernelReady(): Promise<this> {
this.log("catalogue prêt", "INFO");
return this;
}
}
export { CatalogService };
export default CatalogModule;
Ce qu’on observe ensuite :
- Au démarrage, chaque phase du kernel est annoncée dans le journal, module par module — c’est le premier endroit à regarder quand quelque chose ne se charge pas.
- Les deux appels à
this.log(…)sortent avec le nomcatalogen identifiant de message : c’est ce qui permet de filtrer par module dans l’écran Journaux de Studio. CatalogServicedevient injectable ailleurs sous le nomcatalog— un contrôleur le reçoit sans jamais l’instancier lui-même. La mécanique complète est en Injection et portées.
🏛️ Place dans le framework#
Chaque couche ne dépend que de celles du dessous : @nodefony/framework connaît @nodefony/http,
jamais l’inverse — ce serait un cycle. Le cœur, lui, ne connaît personne : c’est ce qui lui permet
d’être aussi le paquet qu’on importe dans le navigateur.
🧰 Surface publique#
Le paquet publie plusieurs sous-chemins (subpaths). Chacun est une porte d’entrée distincte, et c’est le sous-chemin qui décide de ce qui atterrit dans ton bundle.
<!-- prettier-ignore -->
| Import | Ce qu’on y trouve | Où ça tourne |
|---|---|---|
nodefony |
Kernel, Module, Service, Container, Syslog, Pdu, defineConfig, use, les décorateurs d’injection, Cli et Command |
serveur (Node.js) |
nodefony/client |
RealtimeClient, le pair JSON-RPC, la cadence adaptative, et les briques isomorphes |
navigateur |
nodefony/react |
NodefonyProvider et les hooks (useNodefony, useNodefonyChannel, …) |
navigateur (React) |
nodefony/roles |
hasRole, RoleSet, RoleRegistry — la même évaluation de rôles des deux côtés |
isomorphe |
nodefony/debugbar |
la barre de débogage embarquable dans une page | navigateur |
nodefony/bundler |
le socle de configuration du bundler, partagé par tous les paquets et par les applications | outillage de build |
L’import racine nodefony est conditionnel : un bundler qui cible le navigateur y trouve
automatiquement le build client, là où Node.js reçoit le build serveur. C’est ce qui rend
l’isomorphisme transparent — tu importes le même nom, tu obtiens la variante qui convient.
⚠️ Attention
nodefony/realtimen’existe pas. Ce sous-chemin apparaît dans plusieurs exemples au fil du dépôt, mais il n’est pas publié : l’import échoue à la résolution. Le client temps réel se prend surnodefony/client, ou directement surnodefonydans un contexte navigateur.import { RealtimeClient } from "nodefony/client"; // ✅ le sous-chemin publié import { RealtimeClient } from "nodefony/realtime"; // ❌ non résolu
Les signatures exactes ne sont jamais recopiées ici — elles divergeraient en silence. Elles vivent
dans les types générés et dans le graphe symbolique du dépôt (jq '.symbols.Service' .ai/symbols.json).
⚙️ Configuration#
Le cœur ne se configure pas comme un module : il porte les valeurs d’usine de l’application entière (domaine, serveurs, journal, manifeste de modules), sur lesquelles ta configuration vient se fondre au démarrage. Deux fichiers seulement, tous deux à la racine de l’application :
nodefony.config.ts— l’orchestrateur. Le par-environnement s’y exprime par une fonction qui reçoit son contexte, jamais par un fichier parallèle.env.ts— le seul endroit qui lit l’environnement du processus, sous forme de catalogue typé et validé au démarrage.
La recette complète — defineConfig, use(), defineEnv, la validation, et comment un module
publie ses propres clés typées — est en
Configuration.
📡 Observabilité — Studio#
Le cœur n’est pas un module chargé : il n’apparaît donc pas dans la liste des modules, mais dans
une carte dédiée (/nodefony/modules/core) qui rend ces pages et le graphe de ses symboles. Ce
qu’il produit se lit ailleurs dans l’administration : l’écran Journaux (/nodefony/logs) montre les entrées
émises par Syslog, l’écran Configuration (/nodefony/config) montre les valeurs effectives
après fusion, et le suivi par
identifiant de requête s’appuie sur ce que RequestContext propage.
🔗 Pour aller plus loin#
- ⬆️ Remonter : Toute la documentation
- 📄 Les pages du cœur : Service · Journalisation · Kernel & Module · CLI · RequestContext · Client isomorphe · Hooks React · Composables Vue · Injection Angular · Réactivité Svelte
- 🏛️ Transverse : injection et portées (la page canonique du conteneur) · cycle de boot · pipeline de requête · configuration · build et distribution · vue d’ensemble
- 🧭 Modules voisins :
@nodefony/http(le transport) ·@nodefony/framework(routes et contrôleurs) ·@nodefony/realtime(le serveur de la socket) ·@nodefony/studio(l’admin qui rend tout ça visible) - 📖 Lexique général du framework.