Modules

Registre runtime des packages @fraza/module-* et @fraza-pro/module-* découverts et activés dans le projet consommateur.

core/modules tient le registre des modules @fraza/module-* et @fraza-pro/module-* activés. Il ne charge rien lui-même : il est peuplé par le manifeste statique généré dans le projet consommateur.

Pour écrire un module, voir Créer un module. Pour comprendre la découverte et la synchronisation, voir Cycle de vie d'un module.

Import

import {
  registerModules, registerModule,
  getRegisteredModules, getRegisteredModule, clearRegisteredModules,
  onModulesReady, flushModulesReady, resetModulesReady,
  findInstalledModuleNames, moduleHasClientEntry, loadModulesForCli,
  validateModuleEnvVars, moduleEnvKey, isModuleEnabled,
} from '@fraza/core/modules';

Module serveur : discover.server.js lit le système de fichiers.

API

FonctionRôle
registerModules(modules)Peuple le registre depuis le manifeste. Appelée par initializeFraza({ modules }).
registerModule(mod)Bas niveau, utilisée par registerModules.
getRegisteredModules()Retourne tous les modules connus. Utilisée par fraza-db init et la validation d'environnement.
getRegisteredModule(name)Retourne un module par son nom de package.
clearRegisteredModules()Vide le registre. Usage tests.
onModulesReady(callback)Reporte un traitement au moment où tous les modules sont enregistrés. Voir Enregistrement inter-modules.
flushModulesReady()Exécute les callbacks en attente. Appelée par initializeFraza() seulement.
resetModulesReady()Vide la file et repasse en mode non initialisé. Usage tests.
findInstalledModuleNames({ cwd })Scan en lecture seule du package.json du projet. Utilisée par fraza-modules sync.
moduleHasClientEntry(name)Indique si le package expose une sous-entrée ./client. Décide de son inclusion dans le manifeste client.
moduleHasPublicPageEntry(name)Indique si le package expose une sous-entrée ./public-page. Décide de son inclusion dans le manifeste des pages publiques.
loadModulesForCli()Charge les modules hors contexte Next.js, pour fraza-db.
validateModuleEnvVars(modules)Journalise un avertissement par variable requise absente.
moduleEnvKey(name)Retourne la variable d'activation associée à un package, par exemple FRAZA_MODULE_MEDIAS. Le préfixe est retiré : @fraza-pro/module-billing donne FRAZA_MODULE_BILLING.
isModuleEnabled(name)Indique si le module est activé par sa variable d'environnement.

Forme attendue d'un module

Le point d'entrée d'un package de module, @fraza/module-*, @fraza-pro/module-* ou autre, doit exporter :

ExportTypeObligatoire
manifest{ name, version, requiresCore?, requiresModules?, permissions?, envVars? }oui
register() => void | Promise<void>oui
createTablesasync () => { created?, skipped? }si le module a des tables
dropTablesasync () => voidsi le module a des tables
migrationsArray<{ id, description?, up }>si le schéma a évolué depuis sa création

Activation

La découverte inclut un package installé ; l'activation est un second interrupteur, porté par une variable d'environnement propre au module (FRAZA_MODULE_<NOM>=true). Un module découvert mais non activé n'enregistre rien.

Enregistrement inter-modules

Le manifeste généré déclenche les register() de tous les modules en parallèle :

await Promise.all(modules.map(m => m.exports.register?.()));

L'ordre d'évaluation des register-server.js n'est donc pas garanti. Un module qui lit au niveau racine de son register-server.js un état publié par un autre module lit une valeur non encore écrite, au hasard de la taille du graphe d'imports de chacun. Le symptôme typique est un avertissement « module X n'est pas activé » alors qu'il l'est.

onModulesReady(callback) est le seul point de lecture fiable : initializeFraza() appelle flushModulesReady() après l'import du manifeste et le peuplement du registre.

import { onModulesReady } from '@fraza/core/modules';
import { isMediaEnabled } from '@fraza/core/media-registry';

onModulesReady(() => {
  if (!isMediaEnabled()) {
    console.warn('[@fraza/module-exemple] champs image configurés sans module médias');
  }
});

Un callback enregistré après le flush s'exécute immédiatement, pour qu'un module chargé tardivement ou rechargé à chaud ne reste pas silencieux. Un callback qui lève est journalisé et n'interrompt ni les suivants ni le boot.

Ce mécanisme sert aux diagnostics et au câblage optionnel. Il ne remplace pas une dépendance réelle : un module qui ne peut pas fonctionner sans un autre le déclare dans ses peerDependencies, pour que npm l'installe, et dans manifest.requiresModules.

Dépendances entre modules

manifest.requiresModules liste les noms de paquet des modules qu'un module requiert. src/core/modules/order.js en tire deux choses :

  • L'ordre de création des tables. sortByDependencies place chaque module après ceux qu'il requiert, et garde l'ordre alphabétique de la découverte partout ailleurs. fraza-db s'en sert pour init, migrate, status et, à l'envers, drop.
  • La vérification des prérequis. checkModuleRequirements, dans shared/lib/compat.server.js, signale un module requis absent ou désactivé, ou un cycle. Au boot, initializeFraza lève sur ces constats, comme sur un requiresCore non satisfait. fraza-doctor les affiche dans sa vérification des versions.

L'ordre des register() n'en dépend pas : ils tournent toujours en parallèle, voir ci-dessus.

Notes

Le registre est un singleton sur globalThis. Deux copies du registre voudraient dire deux jeux de modules, l'un peuplé et l'autre vide selon le chemin d'import. C'est aussi la raison pour laquelle le build n'inline jamais ces fichiers. Voir Registres et singletons et Build et exports.

Les modules sont chargés en ESM natif, hors condition react-server. C'est ce qui interdit un import { cookies } from 'next/headers' au top-level dans la chaîne register(). Voir Frontières serveur/client.

Sur cette page