@aventii/runtime (1.0.3)
Installation
@aventii:registry=npm install @aventii/runtime@1.0.3"@aventii/runtime": "1.0.3"About this package
@aventii/runtime
Runtime HTTP opinionated pour les microservices Node.js Aventii.
@aventii/runtime standardise l'enveloppe HTTP et le cycle de vie d'un microservice : création d'Express, serveur HTTP, conventions communes, CORS optionnel, healthcheck, démarrage, arrêt gracieux et gestion des erreurs fatales.
Runtime sait comment un microservice Aventii vit. Il ne sait pas ce que ce microservice fait.
Installation
npm install @aventii/runtime
Prérequis : Node.js >= 22. Le package utilise Express 5.
Utilisation minimale
const { createService } = require('@aventii/runtime');
const service = createService({
name: 'pmc-mail',
port: 8444
});
service.app.get('/hello', (req, res) => {
res.json({ hello: 'world' });
});
service.run();
Le runtime prend notamment en charge :
express()et le serveur HTTP ;trust proxyetX-Powered-By;- les body parsers ;
- CORS lorsqu'il est configuré ;
- le healthcheck ;
- le 404 et le handler 500 ;
SIGINT/SIGTERM;- les erreurs fatales Node ;
- le démarrage et l'arrêt gracieux.
API publique
const {
createService,
RUNTIME_STATES,
RuntimeError
} = require('@aventii/runtime');
createService(options) retourne :
{
app,
server,
start,
run,
stop,
getStatus
}
app et server existent immédiatement après createService(), avant même le démarrage.
Architecture recommandée
Pour les microservices simples, la configuration du runtime reste dans infrastructure/runtime.js :
// infrastructure/runtime.js
const { createService } = require('@aventii/runtime');
const config = require('./config');
const { database, getDatabaseStatus } = require('./db');
const service = createService({
name: 'pmc-example',
port: config.EXAMPLE_PORT,
health: {
path: '/example/health',
check: () => {
const etatDB = getDatabaseStatus();
return {
healthy: etatDB.connecte,
status: etatDB.connecte ? 'ok' : 'mongo_down',
etat: etatDB
};
}
},
initialize: async () => {
await database.initialize();
},
close: async () => {
await database.close();
}
});
module.exports = service;
Le point d'entrée reste alors minimal :
// index.js
const service = require('./infrastructure/runtime');
const { internalContextMiddleware } = require('./infrastructure/link');
service.app.use(internalContextMiddleware);
service.app.use('/example', require('./routes'));
service.run();
Le principe retenu est que la consommation et la configuration des packages @aventii/* restent autant que possible dans infrastructure/, tandis que index.js décrit essentiellement l'assemblage métier.
initialize et setup
initialize prépare les ressources avant l'ouverture HTTP :
initialize: async () => {
await database.initialize();
initModels();
}
setup est exécuté après initialize et avant l'écoute HTTP. Il est utile lorsque les routes dépendent de ressources ou modèles initialisés.
function createEntrepriseRuntime(setup) {
return createService({
name: 'pmc-entreprise',
port: config.ENTREPRISE_PORT,
initialize: async () => {
await database.initialize();
initModels();
},
setup,
close: async () => {
await database.close();
}
});
}
Puis :
const service = createEntrepriseRuntime(({ app }) => {
const { User, Entreprise } = models;
app.use('/entreprise', require('./modules/User/UserRoutes')({ User }));
app.use('/entreprise', require('./modules/Entreprise/EntrepriseRoutes')({ Entreprise }));
});
service.run();
Le callback setup reçoit { app, server } et peut être synchrone ou asynchrone.
Ordre de démarrage
createService()
↓
start() / run()
↓
initialize()
↓
setup({ app, server })
↓
finalisation Express
↓
listen()
↓
READY
Le service n'accepte aucune requête avant la fin de initialize et setup.
Priorité du healthcheck
Le healthcheck du runtime est installé de façon à rester prioritaire sur les routes dynamiques ajoutées par setup.
Ainsi :
health: {
path: '/entreprise/health'
}
ne doit pas être capturé par :
router.get('/:entrepriseId', handler);
GET /entreprise/health reste donc le healthcheck et non une recherche de l'entreprise health.
Les autres middlewares ajoutés explicitement par le consommateur conservent la sémantique et l'ordre normaux d'Express.
Options principales
name
Obligatoire, chaîne non vide :
name: 'pmc-pics'
port
Obligatoire, entier de 1 à 65535 :
port: config.PICS_PORT
host
Défaut :
host: '0.0.0.0'
trustProxy
Défaut :
trustProxy: 1
Équivalent à app.set('trust proxy', 1). La valeur reste configurable.
disablePoweredBy
Défaut :
disablePoweredBy: true
Le runtime exécute alors app.disable('x-powered-by').
Body parsers
Par défaut :
body: {
json: true,
jsonLimit: '10mb',
urlencoded: true,
urlencodedLimit: '10mb',
urlencodedExtended: true
}
Exemple :
body: {
json: true,
jsonLimit: '20mb',
urlencoded: false
}
Pour tout désactiver :
body: false
C'est notamment utile lorsqu'un microservice doit installer lui-même son parser pour conserver un body brut :
const service = createService({
name: 'access',
port: config.ACCESS_PORT,
body: false
});
service.app.use(express.json({
limit: '100kb',
verify: (req, res, buffer) => {
if (req.originalUrl.startsWith('/webhooks/notchpay')) {
req.rawBody = buffer.toString('utf8');
}
}
}));
CORS
CORS est optionnel. Sans configuration, le runtime ne l'active pas.
cors: false
Lorsqu'il est activé, cors doit être un objet contenant au minimum origin :
cors: {
origin: 'https://example.com',
credentials: true
}
Une fonction est également acceptée :
cors: {
credentials: true,
origin: (origin, callback) => {
if (!origin) return callback(null, true);
if (config.CORS_ORIGINS.includes(origin)) return callback(null, true);
return callback(new Error(`Origin refusée: ${origin}`));
}
}
Le runtime fournit la mécanique CORS ; la politique des origines reste au microservice.
Healthcheck
Par défaut :
GET /health
Chemin personnalisé :
health: {
path: '/pics/health'
}
Désactivation :
health: false
Healthcheck enrichi
health: {
path: '/pics/health',
check: () => {
const etatDB = getDatabaseStatus();
if (!etatDB.connecte) {
return {
healthy: false,
status: 'mongo_down',
message: 'Base de donnees non connectee',
etat: etatDB
};
}
return {
healthy: true,
message: 'Connexion Mongo active',
etat: etatDB
};
}
}
check peut être synchrone ou asynchrone et reçoit { req }.
Il peut retourner un booléen ou un objet. Dans un objet, healthy contrôle le code HTTP (200 ou 503), status peut remplacer le statut standard et les autres propriétés sont ajoutées à la réponse.
Si check lève une exception, le runtime répond en 503 avec status: 'check_failed' et journalise l'erreur.
start() et run()
start()
await service.start();
À utiliser lorsque l'appelant veut gérer lui-même une erreur de démarrage :
try {
await service.start();
} catch (err) {
// gestion personnalisée
}
Un second démarrage produit une RuntimeError de code INVALID_START_STATE.
run()
service.run();
API recommandée pour les points d'entrée simples. run() appelle start() et, en cas d'échec, journalise l'erreur puis termine le processus avec le code 1.
stop() et close()
Arrêt manuel :
await service.stop('maintenance');
Cycle :
READY
↓
SHUTTING_DOWN
↓
server.close()
↓
close()
↓
STOPPED
Plusieurs stop() concurrents partagent la même opération : close() n'est exécuté qu'une fois.
close est le hook de libération des ressources :
close: async () => {
closeMetrics();
await database.close();
}
Il peut fermer une DB, des métriques, timers, pollers, clients externes ou toute autre ressource appartenant au microservice.
Timeout d'arrêt
Défaut :
shutdownTimeoutMs: 15000
Si l'arrêt dépasse ce délai, une RuntimeError de code SHUTDOWN_TIMEOUT est produite, les connexions restantes sont fermées lorsque le serveur le permet et l'état passe à failed.
Signaux et erreurs fatales
Par défaut :
handleSignals: true
exitOnSignal: true
handleFatalErrors: true
exitOnFatalError: true
Le runtime gère SIGINT et SIGTERM. Il appelle stop(signal), puis termine normalement avec le code 0, ou 1 si l'arrêt échoue.
Il gère également uncaughtException et unhandledRejection : arrêt gracieux puis code 1.
Pour les tests ou une gestion externe :
handleSignals: false,
handleFatalErrors: false
exitOnSignal: false et exitOnFatalError: false permettent d'exécuter le cleanup sans appeler ensuite process.exit().
États et statut
RUNTIME_STATES.CREATED // 'created'
RUNTIME_STATES.STARTING // 'starting'
RUNTIME_STATES.READY // 'ready'
RUNTIME_STATES.SHUTTING_DOWN // 'shutting_down'
RUNTIME_STATES.STOPPED // 'stopped'
RUNTIME_STATES.FAILED // 'failed'
RUNTIME_STATES est figé.
service.getStatus();
Retour typique :
{
name: 'pmc-pdf',
state: 'ready',
port: 8448,
host: '0.0.0.0',
listening: true,
startedAt: '2026-08-21T08:00:00.000Z',
stoppedAt: null,
uptimeSeconds: 42.317
}
404 et erreurs Express
Le runtime installe le fallback 404 final :
{
"error": "Route inconnue : /foo"
}
Une erreur Express non gérée est journalisée côté serveur. Le client reçoit seulement :
{
"error": "Internal server error"
}
avec HTTP 500. Les détails internes ne sont pas exposés.
Logger
Défaut :
logger: console
Le logger fournit principalement log et error. Pour désactiver les logs :
logger: null
Les appels sont optionnels et les messages du package sont préfixés par [runtime].
Socket.IO et serveur exposé
service.server existe avant start() :
const service = createService({
name: 'access',
port: config.ACCESS_PORT
});
initSockets(service.server);
service.run();
Le runtime conserve la responsabilité du listen(), des signaux et de l'arrêt HTTP.
serverFactory
Défaut conceptuel :
serverFactory: app => http.createServer(app)
Une fabrique personnalisée peut être injectée. Le serveur retourné doit être compatible Node et fournir au minimum listen() et close().
Defaults principaux
host: 0.0.0.0, trustProxy: 1, disablePoweredBy: true, body JSON/URL-encoded à 10mb, health /health, timeout de shutdown 15000ms, gestion des signaux et erreurs fatales activée, logger: console. CORS reste désactivé tant qu’aucune configuration cors n’est fournie.
Articulation avec @aventii/config
@aventii/config
↓
configuration validée et typée
↓
infrastructure/runtime.js
↓
@aventii/runtime
↓
Express + serveur + lifecycle
Le runtime reçoit normalement des valeurs déjà validées :
const config = require('./config');
const service = createService({
name: 'pmc-pdf',
port: config.PDF_PORT
});
Ce que Runtime ne fait pas
@aventii/runtime ne doit pas :
- lire
process.env; - construire la configuration ;
- connaître MongoDB ou Mongoose ;
- connaître
pmc-keys; - créer les clients de communication inter-services ;
- authentifier les utilisateurs ;
- définir les routes ou modèles métier ;
- connaître Rapidoo, Gomada ou un autre produit ;
- gérer Docker ;
- décider de la politique métier d'un healthcheck ;
- cacher Express derrière une DSL propriétaire. Ces responsabilités restent dans le microservice ou dans les autres packages Aventii.
Résumé
Sans runtime, chaque microservice réimplémente une partie de :
Express
+ conventions HTTP
+ CORS éventuel
+ body parsers
+ health
+ listen
+ initialize / setup
+ SIGINT / SIGTERM
+ shutdown
+ fatal errors
+ 404 / 500
Avec @aventii/runtime :
createService()
│
├── service.app
├── service.server
├── initialize()
├── setup()
├── health
├── start() / run()
├── stop()
└── close()
Le métier reste du JavaScript/Express ordinaire. Le package retire la plomberie répétitive et impose un cycle de vie commun aux microservices Aventii.
Dependencies
Dependencies
| ID | Version |
|---|---|
| cors | ^2.8.6 |
| dotenv | ^17.4.2 |
| express | ^5.2.1 |