@nodefony/http 10.0.0-beta.3 → 10.0.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
  2. package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorateParam.js +1 -2
  3. package/dist/index.js +12 -10
  4. package/dist/nodefony/command/assetsPublishCommand.js +5 -3
  5. package/dist/nodefony/command/certificatesCommand.js +5 -3
  6. package/dist/nodefony/command/networkCommand.js +5 -3
  7. package/dist/nodefony/command/proxyGenerateCommand.js +8 -3
  8. package/dist/nodefony/config/config.js +1 -2
  9. package/dist/nodefony/config/defineModuleConfig.js +1 -2
  10. package/dist/nodefony/service/HttpAdminApi.js +2 -2
  11. package/dist/nodefony/service/ProfilerAdminApi.js +1 -2
  12. package/dist/nodefony/service/audit-logger.js +20 -13
  13. package/dist/nodefony/service/certificates.js +97 -13
  14. package/dist/nodefony/service/error-renderer.js +29 -12
  15. package/dist/nodefony/service/http-kernel.js +118 -43
  16. package/dist/nodefony/service/pretty-request-logger.js +5 -4
  17. package/dist/nodefony/service/request-logger.js +24 -16
  18. package/dist/nodefony/service/reverse-proxy.js +3 -3
  19. package/dist/nodefony/service/servers/clientError.js +22 -3
  20. package/dist/nodefony/service/servers/server-h2c.js +126 -0
  21. package/dist/nodefony/service/servers/server-http.js +5 -8
  22. package/dist/nodefony/service/servers/server-https.js +12 -24
  23. package/dist/nodefony/service/servers/server-static.js +3 -3
  24. package/dist/nodefony/service/servers/server-websocket-secure.js +10 -18
  25. package/dist/nodefony/service/servers/server-websocket.js +10 -18
  26. package/dist/nodefony/service/servers/serverShutdown.js +78 -18
  27. package/dist/nodefony/service/servers/wsHeartbeat.js +1 -2
  28. package/dist/nodefony/service/sessions/sessions-service.js +7 -14
  29. package/dist/nodefony/service/trace.js +1 -2
  30. package/dist/nodefony/service/upload/upload-service.js +5 -8
  31. package/dist/nodefony/service/x509.js +1 -2
  32. package/dist/nodefony/src/assets/collectAssets.js +1 -2
  33. package/dist/nodefony/src/assets/prebuiltUi.js +1 -2
  34. package/dist/nodefony/src/context/Context.js +23 -31
  35. package/dist/nodefony/src/context/domainMatcher.js +1 -2
  36. package/dist/nodefony/src/context/forwarded.js +1 -2
  37. package/dist/nodefony/src/context/http/HttpContext.js +7 -6
  38. package/dist/nodefony/src/context/http/Request.js +33 -29
  39. package/dist/nodefony/src/context/http/Response.js +23 -6
  40. package/dist/nodefony/src/context/http/SseStream.js +1 -2
  41. package/dist/nodefony/src/context/http/parser.js +36 -10
  42. package/dist/nodefony/src/context/http/urlFastPath.js +1 -2
  43. package/dist/nodefony/src/context/http2/Request.js +4 -8
  44. package/dist/nodefony/src/context/http2/Response.js +22 -9
  45. package/dist/nodefony/src/context/metaData.js +1 -2
  46. package/dist/nodefony/src/context/requestId.js +1 -2
  47. package/dist/nodefony/src/context/responseEnded.js +1 -2
  48. package/dist/nodefony/src/context/transport.js +45 -0
  49. package/dist/nodefony/src/context/trustProxy.js +1 -2
  50. package/dist/nodefony/src/context/websocket/Response.js +33 -6
  51. package/dist/nodefony/src/context/websocket/WebsocketContext.js +92 -17
  52. package/dist/nodefony/src/context/websocket/isWebsocketContext.js +17 -0
  53. package/dist/nodefony/src/context/websocket/websocketUrl.js +1 -2
  54. package/dist/nodefony/src/context/websocket/wsBackpressure.js +1 -2
  55. package/dist/nodefony/src/context/websocket/wsCloseReason.js +1 -2
  56. package/dist/nodefony/src/context/websocket/wsLogContent.js +1 -2
  57. package/dist/nodefony/src/cookies/cookie.js +3 -7
  58. package/dist/nodefony/src/errors/httpError.js +1 -2
  59. package/dist/nodefony/src/errors/thrownMessage.js +1 -2
  60. package/dist/nodefony/src/profiler/FrameProfile.js +1 -2
  61. package/dist/nodefony/src/profiler/Profiler.js +102 -20
  62. package/dist/nodefony/src/profiler/aggregate.js +422 -0
  63. package/dist/nodefony/src/proxy/forward.js +29 -5
  64. package/dist/nodefony/src/proxy/generateProxyConfig.js +14 -6
  65. package/dist/nodefony/src/proxy/rules.js +12 -6
  66. package/dist/nodefony/src/rateLimit/MemoryRateLimitStore.js +1 -2
  67. package/dist/nodefony/src/rateLimit/WsConnectionCounter.js +1 -2
  68. package/dist/nodefony/src/rateLimit/rateLimitFilters.js +1 -2
  69. package/dist/nodefony/src/servers/http2Limits.js +50 -0
  70. package/dist/nodefony/src/servers/kernelServers.js +12 -3
  71. package/dist/nodefony/src/servers/portBinder.js +41 -10
  72. package/dist/nodefony/src/servers/upgradeDispatch.js +86 -7
  73. package/dist/nodefony/src/session/session.js +2 -3
  74. package/dist/nodefony/src/session/sessionStoreFailure.js +1 -2
  75. package/dist/nodefony/src/session/storage/MemorySessionStorage.js +1 -2
  76. package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +1 -2
  77. package/dist/nodefony/src/session/storage/sessionFilters.js +1 -2
  78. package/dist/nodefony/src/session/storage/sessionSort.js +1 -2
  79. package/dist/types/index.d.ts +7 -3
  80. package/dist/types/nodefony/interfaces/IContext.d.ts +63 -4
  81. package/dist/types/nodefony/interfaces/IRequest.d.ts +9 -4
  82. package/dist/types/nodefony/interfaces/IRouting.d.ts +10 -0
  83. package/dist/types/nodefony/interfaces/index.d.ts +2 -2
  84. package/dist/types/nodefony/service/certificates.d.ts +55 -4
  85. package/dist/types/nodefony/service/error-renderer.d.ts +31 -0
  86. package/dist/types/nodefony/service/http-kernel.d.ts +46 -10
  87. package/dist/types/nodefony/service/servers/clientError.d.ts +16 -0
  88. package/dist/types/nodefony/service/servers/server-h2c.d.ts +51 -0
  89. package/dist/types/nodefony/service/servers/server-http.d.ts +3 -3
  90. package/dist/types/nodefony/service/servers/server-https.d.ts +4 -4
  91. package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +1 -3
  92. package/dist/types/nodefony/service/servers/server-websocket.d.ts +1 -3
  93. package/dist/types/nodefony/service/servers/serverShutdown.d.ts +30 -16
  94. package/dist/types/nodefony/src/context/Context.d.ts +13 -4
  95. package/dist/types/nodefony/src/context/http/HttpContext.d.ts +1 -1
  96. package/dist/types/nodefony/src/context/http/Request.d.ts +20 -15
  97. package/dist/types/nodefony/src/context/http/Response.d.ts +11 -0
  98. package/dist/types/nodefony/src/context/http/parser.d.ts +18 -2
  99. package/dist/types/nodefony/src/context/http2/Request.d.ts +1 -1
  100. package/dist/types/nodefony/src/context/http2/Response.d.ts +15 -5
  101. package/dist/types/nodefony/src/context/transport.d.ts +53 -0
  102. package/dist/types/nodefony/src/context/websocket/Response.d.ts +18 -2
  103. package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +57 -7
  104. package/dist/types/nodefony/src/context/websocket/isWebsocketContext.d.ts +15 -0
  105. package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +13 -12
  106. package/dist/types/nodefony/src/profiler/Profiler.d.ts +68 -15
  107. package/dist/types/nodefony/src/profiler/aggregate.d.ts +215 -0
  108. package/dist/types/nodefony/src/proxy/forward.d.ts +14 -2
  109. package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +16 -0
  110. package/dist/types/nodefony/src/proxy/rules.d.ts +3 -0
  111. package/dist/types/nodefony/src/servers/http2Limits.d.ts +37 -0
  112. package/dist/types/nodefony/src/servers/kernelServers.d.ts +10 -1
  113. package/dist/types/nodefony/src/servers/portBinder.d.ts +29 -5
  114. package/dist/types/nodefony/src/servers/upgradeDispatch.d.ts +33 -3
  115. package/docs/cookies.md +85 -78
  116. package/docs/observabilite.md +80 -80
  117. package/docs/rate-limit.md +83 -83
  118. package/docs/reverse-proxy.md +54 -54
  119. package/docs/servers.md +338 -224
  120. package/docs/session.md +179 -179
  121. package/docs/sse.md +24 -20
  122. package/docs/upload.md +120 -117
  123. package/package.json +8 -9
  124. package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.js +0 -6
@@ -111,19 +111,19 @@ Le différenciateur — **HTTP et WebSocket dans le même pipeline** — se retr
111
111
  un `requestId`, un `traceparent` et un contrat de logger **uniques** couvrent les deux transports.
112
112
 
113
113
  **Le `requestId` est un citoyen du contexte, pas un décor.** Il naît dans le constructeur de base
114
- `Context.requestId = randomUUID()` (`Context.ts:244`), voyage dans l'ALS via `RequestContext.run(...)`
115
- (`http-kernel.ts:519` pour HTTP, `http-kernel.ts:1960` pour WS), et se lit de n'importe où avec
114
+ `Context.requestId = randomUUID()` (`Context.ts#Context.csrfProtect`), voyage dans l'ALS via `RequestContext.run(...)`
115
+ (`http-kernel.ts#HttpKernel.configureRateLimit` pour HTTP, `http-kernel.ts#HttpKernel.finishWebsocket` pour WS), et se lit de n'importe où avec
116
116
  `RequestContext.getRequestId()` — un controller, un service, un adapter ORM, sans jamais le threader.
117
117
 
118
118
  **La ligne de bilan est branchable.** Le kernel ne code pas un format en dur : il consulte un
119
- `IRequestLogger` (`IRequestLogger.ts:25`) résolu au boot depuis la config (`applyRequestLoggerFromConfig`,
120
- `http-kernel.ts:756`), remplaçable à chaud par `httpKernel.setRequestLogger(...)` (`http-kernel.ts:986`).
119
+ `IRequestLogger` (`IRequestLogger.ts#IRequestLogger`) résolu au boot depuis la config (`applyRequestLoggerFromConfig`,
120
+ `http-kernel.ts#HttpKernel.applyRequestLoggerFromConfig`), remplaçable à chaud par `httpKernel.setRequestLogger(...)` (`http-kernel.ts#HttpKernel.setRequestLogger`).
121
121
  Trois formateurs sont livrés ; un quatrième maison s'écrit en implémentant l'interface.
122
122
 
123
123
  **Zero Trust sur l'entrée cliente.** Un `X-Request-Id` fourni par le client finit réfléchi en réponse,
124
124
  écrit dans les logs et propagé en ALS — donc une valeur non assainie ouvrirait log-injection (CR/LF) et
125
125
  throw natif de `setHeader`. Nodefony **valide puis adopte, ou rejette** (`sanitizeRequestId`,
126
- `requestId.ts:38`) : jamais nettoyer (masquerait l'abus), toujours retomber sur l'UUID serveur.
126
+ `requestId.ts#sanitizeRequestId`) : jamais nettoyer (masquerait l'abus), toujours retomber sur l'UUID serveur.
127
127
 
128
128
  ## 🚀 Démarrage rapide
129
129
 
@@ -222,43 +222,43 @@ GET 200 /trace/whoami 3.1ms 127.0.0.1 [demo-abc]
222
222
 
223
223
  ### Le `requestId` — génération, adoption, réflexion
224
224
 
225
- | Étape | Où | Comportement |
226
- | ------------------- | ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
227
- | Génération | `Context.requestId = randomUUID()` (`Context.ts:244`) | UUID v4 posé dans le constructeur de base — HTTP **et** WS. |
228
- | Adoption HTTP | `sanitizeRequestId(headers["x-request-id"])` (`HttpContext.ts:158`) | Remplace l'UUID **si** la valeur cliente est sûre, sinon on garde l'UUID. |
229
- | Adoption WS | `sanitizeRequestId(...)` au handshake (`WebsocketContext.ts:162`) | Même validation, stable sur toute la durée de la socket (handshake → close). |
230
- | Réflexion HTTP/1.1 | `Response.setHeader("x-request-id", …)` (`Response.ts:248`) | Écrit dans `writeHead()`, sur **chaque** réponse. |
231
- | Réflexion HTTP/2 | `this.headers["x-request-id"] = requestId` (`http2/Response.ts:71`) | Sinon les réponses du port 5152 sortiraient sans corrélation. |
232
- | ALS (HTTP) | `RequestContext.run({ requestId, … })` (`http-kernel.ts:519`) | Ouvre la bulle → tout `Pdu` créé dedans est tagué. |
233
- | ALS (WS) | `RequestContext.run({ requestId, … })` (`http-kernel.ts:519`) | Handshake **et** messages : la bulle ouverte à la connexion est reliée à chaque message par `AsyncResource.bind`, sinon l'identité résolue au handshake se perdrait au premier message. |
234
- | Capture dans le log | `Pdu.requestId = Pdu.requestIdProvider?.()` (`Pdu.ts:262`) | Provider injectable branché sur l'ALS côté Node — 0 lecture côté navigateur. |
225
+ | Étape | Où | Comportement |
226
+ | ------------------- | ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
227
+ | Génération | `Context.requestId = randomUUID()` (`Context.ts#Context.csrfProtect`) | UUID v4 posé dans le constructeur de base — HTTP **et** WS. |
228
+ | Adoption HTTP | `sanitizeRequestId(headers["x-request-id"])` (`HttpContext.ts#HttpContext.response`) | Remplace l'UUID **si** la valeur cliente est sûre, sinon on garde l'UUID. |
229
+ | Adoption WS | `sanitizeRequestId(...)` au handshake (`WebsocketContext.ts#WebsocketContext.constructor`) | Même validation, stable sur toute la durée de la socket (handshake → close). |
230
+ | Réflexion HTTP/1.1 | `Response.setHeader("x-request-id", …)` (`Response.ts#HttpResponse.setHeader`) | Écrit dans `writeHead()`, sur **chaque** réponse. |
231
+ | Réflexion HTTP/2 | `this.headers["x-request-id"] = requestId` (`http2/Response.ts#Http2Response.writeHead`) | Sinon les réponses du port 5152 sortiraient sans corrélation. |
232
+ | ALS (HTTP) | `RequestContext.run({ requestId, … })` (`http-kernel.ts#HttpKernel.configureRateLimit`) | Ouvre la bulle → tout `Pdu` créé dedans est tagué. |
233
+ | ALS (WS) | `RequestContext.run({ requestId, … })` (`http-kernel.ts#HttpKernel.configureRateLimit`) | Handshake **et** messages : la bulle ouverte à la connexion est reliée à chaque message par `AsyncResource.bind`, sinon l'identité résolue au handshake se perdrait au premier message. |
234
+ | Capture dans le log | `Pdu.requestId = Pdu.requestIdProvider?.()` (`Pdu.ts#Pdu.sysLogSeverity`) | Provider injectable branché sur l'ALS côté Node — 0 lecture côté navigateur. |
235
235
 
236
236
  > [!IMPORTANT]
237
237
  > Les logs de **fin** de requête (bilan `req`, `onClose`) sont émis **hors** de la bulle ALS (déjà
238
- > refermée). L'override `Context.log()` (`Context.ts:545`) rouvre alors une micro-bulle depuis
238
+ > refermée). L'override `Context.log()` (`Context.ts#Context.log`) rouvre alors une micro-bulle depuis
239
239
  > `this.requestId` pour que le `Pdu` capture quand même la corrélation — sinon la ligne d'entrée d'une
240
240
  > trace serait la seule à ne PAS porter son `requestId`.
241
241
 
242
242
  ### La trace W3C — `traceparent`
243
243
 
244
- Nodefony implémente **W3C Trace Context** (le code s'y réfère explicitement, `trace.ts:1`). À l'entrée :
244
+ Nodefony implémente **W3C Trace Context** (le code s'y réfère explicitement, `trace.ts`). À l'entrée :
245
245
 
246
246
  - Un `traceparent` entrant **valide** est prolongé : on garde `version`/`traceId`/`flags` et on frappe
247
- un nouveau `spanId` (on est un maillon enfant) — `resolveTraceparent()` (`trace.ts:83`).
247
+ un nouveau `spanId` (on est un maillon enfant) — `resolveTraceparent()` (`trace.ts#resolveTraceparent`).
248
248
  - Absent ou malformé → on **forge** une trace neuve (`00-<traceId>-<spanId>-01`, échantillonnée).
249
249
  - La validation refuse `version=ff` et un `traceId`/`spanId` tout-à-zéro — `parseTraceparent()`
250
- (`trace.ts:38`), conforme à la spec (le récepteur NE DOIT PAS propager ces valeurs).
250
+ (`trace.ts#parseTraceparent`), conforme à la spec (le récepteur NE DOIT PAS propager ces valeurs).
251
251
 
252
- Le `traceparent` résolu est propagé en ALS **et** réfléchi sur la réponse HTTP (`context/http/Response.ts:572`). Côté
252
+ Le `traceparent` résolu est propagé en ALS **et** réfléchi sur la réponse HTTP (`context/http/Response.ts#HttpResponse.writeHead`). Côté
253
253
  **WebSocket**, il est propagé en ALS mais **pas** réfléchi dans la réponse de handshake — la bibliothèque
254
- `ws` n'expose pas proprement ce chemin (`http-kernel.ts:1450`) ; la corrélation reste visible côté serveur.
254
+ `ws` n'expose pas proprement ce chemin (`http-kernel.ts#HttpKernel.finishTeardownHttp`) ; la corrélation reste visible côté serveur.
255
255
 
256
256
  ### Le contrat de logger — `IRequestLogger`
257
257
 
258
- Le kernel tient un `IRequestLogger` singleton (`http-kernel.ts:334`) et lui délègue le rendu de la ligne
259
- de bilan, au teardown, via `Context.logRequest()` (`Context.ts:640`) côté HTTP et
260
- `WebsocketContext.logRequest()` (`WebsocketContext.ts:233`) côté WS. Le contrat a trois méthodes
261
- (`IRequestLogger.ts:25`) :
258
+ Le kernel tient un `IRequestLogger` singleton (`http-kernel.ts#HttpKernel.secContentTypeOptions`) et lui délègue le rendu de la ligne
259
+ de bilan, au teardown, via `Context.logRequest()` (`Context.ts#Context.logRequest`) côté HTTP et
260
+ `WebsocketContext.logRequest()` (`WebsocketContext.ts#WebsocketContext.logRequest`) côté WS. Le contrat a trois méthodes
261
+ (`IRequestLogger.ts#IRequestLogger`) :
262
262
 
263
263
  - `renderHttp(context, error?)` → `{ text, severity, msgid }` remis à `context.log()`.
264
264
  - `renderWebsocket(context, error?, acceptedProtocol?)` → idem pour le WS.
@@ -268,38 +268,38 @@ de bilan, au teardown, via `Context.logRequest()` (`Context.ts:640`) côté HTTP
268
268
  ### La trace des frames WebSocket
269
269
 
270
270
  Chaque frame WS (RECEIVE / SEND / BROADCAST) peut être tracée pour le Suivi de requête (Studio). Le
271
- formatage du contenu est **pur** et **borné** — `formatWsLogContent()` (`wsLogContent.ts:55`), appelé par
272
- `WebsocketContext.logMessageContent()` (`WebsocketContext.ts:393`) :
271
+ formatage du contenu est **pur** et **borné** — `formatWsLogContent()` (`wsLogContent.ts#formatWsLogContent`), appelé par
272
+ `WebsocketContext.logMessageContent()` (`WebsocketContext.ts#WebsocketContext.logMessageContent`) :
273
273
 
274
- - `string` → tronquée à `WS_LOG_CONTENT_CAP` (4096, `wsLogContent.ts:15`) + ellipse.
274
+ - `string` → tronquée à `WS_LOG_CONTENT_CAP` (4096, `wsLogContent.ts#WS_LOG_CONTENT_CAP`) + ellipse.
275
275
  - **binaire** (Buffer, ArrayBuffer, TypedArray, Blob, `Buffer[]`) → résumé `[binary N B]`, **jamais**
276
- sérialisé (`binaryByteLength()`, `wsLogContent.ts:27`) — un dump `{"0":..,"1":..}` serait énorme et faux.
276
+ sérialisé (`binaryByteLength()`, `wsLogContent.ts#binaryByteLength`) — un dump `{"0":..,"1":..}` serait énorme et faux.
277
277
  - objet « JSON » → `JSON.stringify` compact tronqué, repli `String(...)` sur cycle/`bigint`.
278
278
 
279
279
  Le tout est **gaté hors production** : `logMessageContent` court-circuite avant toute construction de
280
- chaîne quand l'env est `production` (`WebsocketContext.ts:401`) → 0 surcoût sur le hot path WS.
280
+ chaîne quand l'env est `production` (`WebsocketContext.ts#WebsocketContext.broadcast`) → 0 surcoût sur le hot path WS.
281
281
 
282
282
  ## ⚙️ Configuration
283
283
 
284
284
  Un seul réglage d'app, côté cœur (bloc `log`), pilote le format des lignes.
285
285
 
286
- | Option | Type | Défaut | Effet |
287
- | ------------------- | ----------------------------------------- | ------ | ------------------------------------------------------------------------------------ |
288
- | `log.requestFormat` | `auto` \| `default` \| `pretty` \| `json` | `auto` | Format de la ligne de bilan. `auto` = pretty en dev / json en prod (`schema.ts:39`). |
286
+ | Option | Type | Défaut | Effet |
287
+ | ------------------- | ----------------------------------------- | ------ | ---------------------------------------------------------------------------------------------- |
288
+ | `log.requestFormat` | `auto` \| `default` \| `pretty` \| `json` | `auto` | Format de la ligne de bilan. `auto` = pretty en dev / json en prod (`schema.ts#serverSchema`). |
289
289
 
290
- `auto` est résolu au boot selon l'environnement (`applyRequestLoggerFromConfig`, `http-kernel.ts:756`) :
290
+ `auto` est résolu au boot selon l'environnement (`applyRequestLoggerFromConfig`, `http-kernel.ts#HttpKernel.applyRequestLoggerFromConfig`) :
291
291
 
292
- | Valeur | Formateur | Rendu |
293
- | --------- | ---------------------- | ----------------------------------------------------------------------------------------- |
294
- | `pretty` | `PrettyRequestLogger` | 1 ligne colorée `GET 200 /x 12.3ms 127.0.0.1 [a1b2c3d4]` (`pretty-request-logger.ts:34`). |
295
- | `json` | `JsonAuditLogger` | 1 objet JSON canonique par requête (`audit-logger.ts:136`). |
296
- | `default` | `DefaultRequestLogger` | Format legacy verbeux `URL : … FROM : … ID : <uuid>` (`request-logger.ts:21`). |
292
+ | Valeur | Formateur | Rendu |
293
+ | --------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------- |
294
+ | `pretty` | `PrettyRequestLogger` | 1 ligne colorée `GET 200 /x 12.3ms 127.0.0.1 [a1b2c3d4]` (`pretty-request-logger.ts#PrettyRequestLogger.renderHttp`). |
295
+ | `json` | `JsonAuditLogger` | 1 objet JSON canonique par requête (`audit-logger.ts#JsonAuditLogger`). |
296
+ | `default` | `DefaultRequestLogger` | Format legacy verbeux `URL : … FROM : … ID : <uuid>` (`request-logger.ts#DefaultRequestLogger.renderHttp`). |
297
297
 
298
298
  > [!NOTE]
299
299
  > Le **réglage fin** du logger JSON (`sampleRate`, `includeStack`, `maxCauseDepth`, `nominal`) n'est pas
300
300
  > un champ déclaré du schéma d'app : il se pose **programmatiquement**, en construisant le logger et en
301
301
  > l'injectant — `httpKernel.setRequestLogger(new JsonAuditLogger({ sampleRate: 10 }))` (options :
302
- > `JsonAuditLoggerOptions`, `audit-logger.ts:92`). L'override programmatique gagne toujours sur la config.
302
+ > `JsonAuditLoggerOptions`, `audit-logger.ts#JsonAuditLoggerOptions`). L'override programmatique gagne toujours sur la config.
303
303
 
304
304
  ## 🧰 Les trois formateurs (et le contrat)
305
305
 
@@ -314,31 +314,31 @@ Choisir en cinq secondes :
314
314
  ### `PrettyRequestLogger` — une ligne pour l'humain
315
315
 
316
316
  Le plus grand gain en dev : `méthode statut url durée remote [id]`, avec couleur du statut (vert 2xx,
317
- jaune 4xx, rouge 5xx — `colorizeStatus()`, `pretty-request-logger.ts:100`) et `requestId` tronqué aux 8
318
- premiers caractères (`shortId()`, `pretty-request-logger.ts:116`). La durée est dérivée des phases de
319
- timing (`pretty-request-logger.ts:116`).
317
+ jaune 4xx, rouge 5xx — `colorizeStatus()`, `pretty-request-logger.ts#colorizeStatus`) et `requestId` tronqué aux 8
318
+ premiers caractères (`shortId()`, `pretty-request-logger.ts#shortId`). La durée est dérivée des phases de
319
+ timing (`pretty-request-logger.ts#shortId`).
320
320
 
321
321
  ### `JsonAuditLogger` — un PDU par requête, pour la machine
322
322
 
323
- Émet un `AuditLogEntry` canonique (`audit-logger.ts:28`) : `ts`, `requestId`, `userIdentifier` (appelant authentifié, posé par le pare-feu sur `context.userIdentifier`), `type`, `method`,
323
+ Émet un `AuditLogEntry` canonique (`audit-logger.ts#AuditLogEntry.ts`) : `ts`, `requestId`, `userIdentifier` (appelant authentifié, posé par le pare-feu sur `context.userIdentifier`), `type`, `method`,
324
324
  `url`, `status`, `durationMs`, `remoteAddress`, `host`, `userAgent`, phases, erreur enrichie (nom, code,
325
325
  `errorType`, `cause` bornée à 5, stack **dev seulement**). Deux propriétés majeures :
326
326
 
327
327
  - **Redaction par construction** : `Authorization` et `Cookie` ne sont **jamais** sérialisés — seuls des
328
- drapeaux `hasAuthorization`/`hasCookie` le sont (`audit-logger.ts:226`).
329
- - **Sampling déterministe** : `shouldSample()` (`audit-logger.ts:156`) garde 1 requête nominale sur N mais
328
+ drapeaux `hasAuthorization`/`hasCookie` le sont (`audit-logger.ts#AuditLogEntry.hasCookie`).
329
+ - **Sampling déterministe** : `shouldSample()` (`audit-logger.ts#JsonAuditLogger.shouldSample`) garde 1 requête nominale sur N mais
330
330
  **jamais** une erreur ni un `status >= 400` — on ne perd aucun échec ; compteur, pas de RNG.
331
331
 
332
332
  ### `DefaultRequestLogger` — le format legacy
333
333
 
334
- Singleton sans état, 0 allocation par requête (`request-logger.ts:21`). Conserve le format historique
334
+ Singleton sans état, 0 allocation par requête (`request-logger.ts#DefaultRequestLogger.renderHttp`). Conserve le format historique
335
335
  `URL : … FROM : … ORIGIN : … ID : <uuid>`, avec `Accept-Protocol` en plus côté WebSocket.
336
336
 
337
337
  ### Écrire son propre formateur
338
338
 
339
- Implémenter `IRequestLogger` (`IRequestLogger.ts:25`) et l'injecter — NCSA Common Log Format, syslog RFC
340
- 5424 texte, OpenTelemetry logs… `httpKernel.setRequestLogger(monLogger)` (`http-kernel.ts:986`). Les trois
341
- formateurs et le type sont exportés depuis `@nodefony/http` (`index.ts:221`).
339
+ Implémenter `IRequestLogger` (`IRequestLogger.ts#IRequestLogger`) et l'injecter — NCSA Common Log Format, syslog RFC
340
+ 5424 texte, OpenTelemetry logs… `httpKernel.setRequestLogger(monLogger)` (`http-kernel.ts#HttpKernel.setRequestLogger`). Les trois
341
+ formateurs et le type sont exportés depuis `@nodefony/http` (`index.ts`).
342
342
 
343
343
  ## 🔐 Sécurité
344
344
 
@@ -348,15 +348,15 @@ sensibles à la fois — d'où une validation stricte.
348
348
  <!-- prettier-ignore -->
349
349
  | Menace | Vecteur | Défense |
350
350
  | --- | --- | --- |
351
- | Log injection (CR/LF) | `X-Request-Id: a\r\nFAKE LINE` écrit tel quel dans les logs | Allowlist `[A-Za-z0-9._-]{1,128}` (`requestId.ts:26`) — CR/LF exclus. |
352
- | Response splitting / DoS | Caractère de contrôle / non-ASCII → throw `setHeader` natif (500) | Même allowlist ; valeur non conforme **rejetée**, pas nettoyée — `sanitizeRequestId()` (`requestId.ts:38`). |
353
- | Log flooding | `X-Request-Id` géant | Borne `MAX_REQUEST_ID_LENGTH = 128` (`requestId.ts:18`). |
354
- | Fuite de secret dans l'audit | `Authorization` / `Cookie` sérialisés dans le log JSON | Drapeaux de présence seuls — valeurs jamais écrites (`audit-logger.ts:211`). |
355
- | Fuite de stack en prod | `error.stack` dans les logs d'audit publics | `includeStack` par défaut `false` en production (`audit-logger.ts:133`). |
351
+ | Log injection (CR/LF) | `X-Request-Id: a\r\nFAKE LINE` écrit tel quel dans les logs | Allowlist `[A-Za-z0-9._-]{1,128}` (`requestId.ts#SAFE_REQUEST_ID`) — CR/LF exclus. |
352
+ | Response splitting / DoS | Caractère de contrôle / non-ASCII → throw `setHeader` natif (500) | Même allowlist ; valeur non conforme **rejetée**, pas nettoyée — `sanitizeRequestId()` (`requestId.ts#sanitizeRequestId`). |
353
+ | Log flooding | `X-Request-Id` géant | Borne `MAX_REQUEST_ID_LENGTH = 128` (`requestId.ts#MAX_REQUEST_ID_LENGTH`). |
354
+ | Fuite de secret dans l'audit | `Authorization` / `Cookie` sérialisés dans le log JSON | Drapeaux de présence seuls — valeurs jamais écrites (`audit-logger.ts#JsonAuditLogger.renderHttp`). |
355
+ | Fuite de stack en prod | `error.stack` dans les logs d'audit publics | `includeStack` par défaut `false` en production (`audit-logger.ts#JsonAuditLogger`). |
356
356
 
357
357
  > [!WARNING]
358
358
  > On **rejette** plutôt que d'assainir un `requestId` invalide : nettoyer donnerait au client un faux
359
- > contrôle sur l'identifiant et masquerait la tentative d'abus (`requestId.ts:31`).
359
+ > contrôle sur l'identifiant et masquerait la tentative d'abus (`requestId.ts#sanitizeRequestId`).
360
360
 
361
361
  ## ⚡ Performance & mémoire
362
362
 
@@ -364,15 +364,15 @@ La ligne de bilan et la corrélation sont sur le **chemin chaud** : leur coût e
364
364
  Les choix visibles dans le code :
365
365
 
366
366
  - **`requestId` gratuit à l'entropie** — `randomUUID()` de Node met en cache l'entropie (128 UUID/appel
367
- système) ; les ids W3C amortissent de même via un pool de 4096 o (`randomHex()`, `trace.ts:66`).
367
+ système) ; les ids W3C amortissent de même via un pool de 4096 o (`randomHex()`, `trace.ts#randomHex`).
368
368
  - **Provider ALS lu paresseusement** — un `Pdu` hors bulle ne paie que 1 test de référence ; le provider
369
- reste `null` côté navigateur/debugbar (`Pdu.ts:169`) → 0 lecture, 0 allocation.
369
+ reste `null` côté navigateur/debugbar (`Pdu.ts#guid`) → 0 lecture, 0 allocation.
370
370
  - **Sampling avant rendu** — `shouldSample()` saute la requête **avant** `renderHttp` : 0 objet, 0
371
- `toISOString`, 0 `JSON.stringify`, 0 `Pdu` au ring (`audit-logger.ts:156`).
371
+ `toISOString`, 0 `JSON.stringify`, 0 `Pdu` au ring (`audit-logger.ts#JsonAuditLogger.shouldSample`).
372
372
  - **Audit nominal coupable** — l'option `nominal` coupe le log des 2xx/3xx si le sink texte est `null`
373
- (l'entrée n'atteindrait aucune destination) — ~5,9 % du profil CPU récupérés (`audit-logger.ts:104`).
373
+ (l'entrée n'atteindrait aucune destination) — ~5,9 % du profil CPU récupérés (`audit-logger.ts#JsonAuditLoggerOptions.sampleRate`).
374
374
  - **Trace des frames WS gatée en prod** — `logMessageContent()` court-circuite avant toute construction
375
- de chaîne hors dev (`WebsocketContext.ts:393`) ; les events lifecycle ne créent aucun `Pdu` en production (`Context.ts:67`).
375
+ de chaîne hors dev (`WebsocketContext.ts#WebsocketContext.logMessageContent`) ; les events lifecycle ne créent aucun `Pdu` en production, et ne sont que DEBUG hors production (`Context.ts#lifecycleEventLogging`).
376
376
 
377
377
  Gate mémoire avant tout commit touchant le pipeline : `npm run test:memory` (skill
378
378
  `nodefony-check-memory-health`). Rejouer les chiffres de charge : skill `nodefony-load-test`.
@@ -387,10 +387,10 @@ Le `requestId` est la **clé de jointure** de l'admin Studio (dev). Les écrans
387
387
  | **Suivi de requête** (`TraceView`) | `/nodefony/syslog/api/logs/search?requestId=…&order=asc` | Toutes les lignes corrélées + le profil serveur (phases, requêtes SQL). |
388
388
  | **Logs** (stream) | data plane syslog | Le flux de logs live, filtrable. |
389
389
  | **Audit** | écran Audit | Les événements d'audit persistés. |
390
- | **Profiler** (dev) | `/nodefony/profiler/api/{requestId}` (`ProfilerAdminApi.ts:23`) | Le profil complet (waterfall des phases) d'une requête donnée. |
390
+ | **Profiler** (dev) | `/nodefony/profiler/api/{requestId}` (`ProfilerAdminApi.ts#createProfilerAdminApi`) | Le profil complet (waterfall des phases) d'une requête donnée. |
391
391
 
392
- Le profiler indexe ses instantanés par `requestId` (`Profiler.ts:214`) ; la debug bar lit le
393
- `X-Request-Id` de son propre appel AJAX et va chercher le profil (`Profiler.ts:14`). Le profiler n'est
392
+ Le profiler indexe ses instantanés par `requestId` (`Profiler.ts#Profiler.collect`) ; la debug bar lit le
393
+ `X-Request-Id` de son propre appel AJAX et va chercher le profil (`Profiler.ts#ProfilePhase`). Le profiler n'est
394
394
  instancié **qu'en dev** (fuite d'info + coût en prod).
395
395
 
396
396
  > [!NOTE]
@@ -403,28 +403,28 @@ instancié **qu'en dev** (fuite d'info + coût en prod).
403
403
  <!-- prettier-ignore -->
404
404
  | Domaine | Norme | Ancrage |
405
405
  | --- | --- | --- |
406
- | W3C Trace Context (`traceparent`) | W3C Trace Context | `resolveTraceparent()` (`trace.ts:83`), `parseTraceparent()` (`trace.ts:38`) |
407
- | Sûreté des valeurs d'en-tête (field-value) | RFC 9110 §5.5 | `sanitizeRequestId()` allowlist (`requestId.ts:38`) |
408
- | En-têtes trop volumineux / borne | anti-abus (log flooding) | `MAX_REQUEST_ID_LENGTH` (`requestId.ts:18`) |
409
- | Log structuré (PDU, sévérités) | RFC 5424 | `Pdu` + `requestId`/`pid` (`Pdu.ts:184`) |
410
- | Sévérité dérivée du statut HTTP | RFC 9110 (catégories) | `severityFromStatus()` (`audit-logger.ts:85`) |
411
- | Non-journalisation des secrets | OWASP (logging) | redaction présence-only (`audit-logger.ts:211`) |
406
+ | W3C Trace Context (`traceparent`) | W3C Trace Context | `resolveTraceparent()` (`trace.ts#resolveTraceparent`), `parseTraceparent()` (`trace.ts#parseTraceparent`) |
407
+ | Sûreté des valeurs d'en-tête (field-value) | RFC 9110 §5.5 | `sanitizeRequestId()` allowlist (`requestId.ts#sanitizeRequestId`) |
408
+ | En-têtes trop volumineux / borne | anti-abus (log flooding) | `MAX_REQUEST_ID_LENGTH` (`requestId.ts#MAX_REQUEST_ID_LENGTH`) |
409
+ | Log structuré (PDU, sévérités) | RFC 5424 | `Pdu` + `requestId`/`pid` (`Pdu.ts#Pdu.requestId`) |
410
+ | Sévérité dérivée du statut HTTP | RFC 9110 (catégories) | `severityFromStatus()` (`audit-logger.ts#severityFromStatus`) |
411
+ | Non-journalisation des secrets | OWASP (logging) | redaction présence-only (`audit-logger.ts#JsonAuditLogger.renderHttp`) |
412
412
 
413
413
  > `X-Request-Id` n'est **pas** un en-tête normalisé (convention de-facto) ; c'est la **valeur** qu'il
414
414
  > transporte qui est soumise à la sûreté RFC 9110 §5.5.
415
415
 
416
416
  ## ⚠️ Pièges (symptôme → cause → correction)
417
417
 
418
- | Symptôme | Cause | Correction |
419
- | --------------------------------------------------- | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
420
- | Le `X-Request-Id` que j'envoie n'est pas réfléchi | Valeur non conforme (espace, CR/LF, non-ASCII, > 128) → **rejetée** | Utiliser `[A-Za-z0-9._-]{1,128}` (UUID/nanoid/traceparent OK) — sinon UUID serveur |
421
- | Les logs de fin de requête n'ont pas de `requestId` | Ils sont émis hors bulle ALS | Déjà géré : l'override `log()` rouvre une micro-bulle (`Context.ts:545`) |
422
- | Réponse HTTP/2 sans `x-request-id` | Chemin de réponse h2 distinct du 1.1 | Déjà géré (`http2/Response.ts:106`) — le port 5152 réfléchit aussi |
423
- | Pas de `traceparent` renvoyé sur un WebSocket | `ws` n'expose pas l'écriture d'en-tête au handshake | Attendu — la trace WS reste propagée en ALS (`http-kernel.ts:1990`) |
424
- | Frame WS binaire loggée en `{"0":..,"1":..}` | Sérialisation naïve d'un Buffer | Déjà géré : résumé `[binary N B]` (`wsLogContent.ts:63`) |
425
- | Le format de log ne change pas malgré la config | Un `setRequestLogger(...)` programmatique gagne sur la config | L'override est volontaire (last setter wins) — retirer l'appel, ou le régler |
426
- | Logs d'audit trop volumineux en prod | `stack` sérialisée, ou 100 % des 2xx audités | `includeStack:false` (défaut prod) + `sampleRate` via `setRequestLogger` |
427
- | `Authorization`/`Cookie` attendus dans le log JSON | Redaction : jamais sérialisés | Par conception — lire les drapeaux `hasAuthorization`/`hasCookie` |
418
+ | Symptôme | Cause | Correction |
419
+ | --------------------------------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
420
+ | Le `X-Request-Id` que j'envoie n'est pas réfléchi | Valeur non conforme (espace, CR/LF, non-ASCII, > 128) → **rejetée** | Utiliser `[A-Za-z0-9._-]{1,128}` (UUID/nanoid/traceparent OK) — sinon UUID serveur |
421
+ | Les logs de fin de requête n'ont pas de `requestId` | Ils sont émis hors bulle ALS | Déjà géré : l'override `log()` rouvre une micro-bulle (`Context.ts#Context.log`) |
422
+ | Réponse HTTP/2 sans `x-request-id` | Chemin de réponse h2 distinct du 1.1 | Déjà géré (`http2/Response.ts#Http2Response.writeHead`) — le port 5152 réfléchit aussi |
423
+ | Pas de `traceparent` renvoyé sur un WebSocket | `ws` n'expose pas l'écriture d'en-tête au handshake | Attendu — la trace WS reste propagée en ALS (`http-kernel.ts#HttpKernel.handleWebsocket`) |
424
+ | Frame WS binaire loggée en `{"0":..,"1":..}` | Sérialisation naïve d'un Buffer | Déjà géré : résumé `[binary N B]` (`wsLogContent.ts#formatWsLogContent`) |
425
+ | Le format de log ne change pas malgré la config | Un `setRequestLogger(...)` programmatique gagne sur la config | L'override est volontaire (last setter wins) — retirer l'appel, ou le régler |
426
+ | Logs d'audit trop volumineux en prod | `stack` sérialisée, ou 100 % des 2xx audités | `includeStack:false` (défaut prod) + `sampleRate` via `setRequestLogger` |
427
+ | `Authorization`/`Cookie` attendus dans le log JSON | Redaction : jamais sérialisés | Par conception — lire les drapeaux `hasAuthorization`/`hasCookie` |
428
428
 
429
429
  ## 🧪 Tests & couverture
430
430