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

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 (138) 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 +13 -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 +3 -3
  9. package/dist/nodefony/config/defineModuleConfig.js +1 -2
  10. package/dist/nodefony/interfaces/IRequestTrace.js +1 -0
  11. package/dist/nodefony/service/HttpAdminApi.js +2 -2
  12. package/dist/nodefony/service/ProfilerAdminApi.js +13 -3
  13. package/dist/nodefony/service/audit-logger.js +23 -30
  14. package/dist/nodefony/service/certificates.js +97 -13
  15. package/dist/nodefony/service/error-renderer.js +29 -12
  16. package/dist/nodefony/service/http-kernel.js +198 -50
  17. package/dist/nodefony/service/pretty-request-logger.js +15 -13
  18. package/dist/nodefony/service/request-logger.js +24 -16
  19. package/dist/nodefony/service/reverse-proxy.js +3 -3
  20. package/dist/nodefony/service/servers/clientError.js +22 -3
  21. package/dist/nodefony/service/servers/server-h2c.js +126 -0
  22. package/dist/nodefony/service/servers/server-http.js +5 -8
  23. package/dist/nodefony/service/servers/server-https.js +12 -24
  24. package/dist/nodefony/service/servers/server-static.js +3 -3
  25. package/dist/nodefony/service/servers/server-websocket-secure.js +10 -18
  26. package/dist/nodefony/service/servers/server-websocket.js +10 -18
  27. package/dist/nodefony/service/servers/serverShutdown.js +78 -18
  28. package/dist/nodefony/service/servers/wsHeartbeat.js +1 -2
  29. package/dist/nodefony/service/sessions/sessions-service.js +7 -14
  30. package/dist/nodefony/service/trace.js +1 -2
  31. package/dist/nodefony/service/upload/upload-service.js +5 -8
  32. package/dist/nodefony/service/x509.js +1 -2
  33. package/dist/nodefony/src/assets/collectAssets.js +1 -2
  34. package/dist/nodefony/src/assets/prebuiltUi.js +1 -2
  35. package/dist/nodefony/src/context/Context.js +78 -43
  36. package/dist/nodefony/src/context/domainMatcher.js +1 -2
  37. package/dist/nodefony/src/context/forwarded.js +1 -2
  38. package/dist/nodefony/src/context/http/HttpContext.js +28 -6
  39. package/dist/nodefony/src/context/http/Request.js +33 -29
  40. package/dist/nodefony/src/context/http/Response.js +23 -6
  41. package/dist/nodefony/src/context/http/SseStream.js +1 -2
  42. package/dist/nodefony/src/context/http/parser.js +36 -10
  43. package/dist/nodefony/src/context/http/urlFastPath.js +1 -2
  44. package/dist/nodefony/src/context/http2/Request.js +4 -8
  45. package/dist/nodefony/src/context/http2/Response.js +41 -9
  46. package/dist/nodefony/src/context/metaData.js +1 -2
  47. package/dist/nodefony/src/context/requestId.js +1 -2
  48. package/dist/nodefony/src/context/requestTrace.js +104 -0
  49. package/dist/nodefony/src/context/responseEnded.js +1 -2
  50. package/dist/nodefony/src/context/timingMode.js +21 -0
  51. package/dist/nodefony/src/context/transport.js +45 -0
  52. package/dist/nodefony/src/context/trustProxy.js +1 -2
  53. package/dist/nodefony/src/context/unsentResponse.js +30 -0
  54. package/dist/nodefony/src/context/websocket/Response.js +33 -6
  55. package/dist/nodefony/src/context/websocket/WebsocketContext.js +104 -25
  56. package/dist/nodefony/src/context/websocket/isWebsocketContext.js +17 -0
  57. package/dist/nodefony/src/context/websocket/websocketUrl.js +1 -2
  58. package/dist/nodefony/src/context/websocket/wsBackpressure.js +1 -2
  59. package/dist/nodefony/src/context/websocket/wsCloseReason.js +1 -2
  60. package/dist/nodefony/src/context/websocket/wsLogContent.js +1 -2
  61. package/dist/nodefony/src/cookies/cookie.js +3 -7
  62. package/dist/nodefony/src/errors/httpError.js +1 -2
  63. package/dist/nodefony/src/errors/thrownMessage.js +1 -2
  64. package/dist/nodefony/src/profiler/FrameProfile.js +23 -2
  65. package/dist/nodefony/src/profiler/Profiler.js +104 -32
  66. package/dist/nodefony/src/profiler/aggregate.js +442 -0
  67. package/dist/nodefony/src/proxy/forward.js +29 -5
  68. package/dist/nodefony/src/proxy/generateProxyConfig.js +14 -6
  69. package/dist/nodefony/src/proxy/rules.js +12 -6
  70. package/dist/nodefony/src/rateLimit/MemoryRateLimitStore.js +1 -2
  71. package/dist/nodefony/src/rateLimit/WsConnectionCounter.js +1 -2
  72. package/dist/nodefony/src/rateLimit/rateLimitFilters.js +1 -2
  73. package/dist/nodefony/src/servers/http2Limits.js +50 -0
  74. package/dist/nodefony/src/servers/kernelServers.js +12 -3
  75. package/dist/nodefony/src/servers/portBinder.js +41 -10
  76. package/dist/nodefony/src/servers/upgradeDispatch.js +86 -7
  77. package/dist/nodefony/src/session/session.js +2 -3
  78. package/dist/nodefony/src/session/sessionStoreFailure.js +1 -2
  79. package/dist/nodefony/src/session/storage/MemorySessionStorage.js +1 -2
  80. package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +1 -2
  81. package/dist/nodefony/src/session/storage/sessionFilters.js +1 -2
  82. package/dist/nodefony/src/session/storage/sessionSort.js +1 -2
  83. package/dist/types/index.d.ts +9 -3
  84. package/dist/types/nodefony/config/config.d.ts +4 -0
  85. package/dist/types/nodefony/interfaces/IContext.d.ts +72 -4
  86. package/dist/types/nodefony/interfaces/IRequest.d.ts +9 -4
  87. package/dist/types/nodefony/interfaces/IRequestTrace.d.ts +56 -0
  88. package/dist/types/nodefony/interfaces/IReverseProxy.d.ts +8 -0
  89. package/dist/types/nodefony/interfaces/IRouting.d.ts +26 -0
  90. package/dist/types/nodefony/interfaces/index.d.ts +3 -2
  91. package/dist/types/nodefony/service/ProfilerAdminApi.d.ts +3 -0
  92. package/dist/types/nodefony/service/audit-logger.d.ts +3 -11
  93. package/dist/types/nodefony/service/certificates.d.ts +55 -4
  94. package/dist/types/nodefony/service/error-renderer.d.ts +31 -0
  95. package/dist/types/nodefony/service/http-kernel.d.ts +96 -10
  96. package/dist/types/nodefony/service/servers/clientError.d.ts +16 -0
  97. package/dist/types/nodefony/service/servers/server-h2c.d.ts +51 -0
  98. package/dist/types/nodefony/service/servers/server-http.d.ts +3 -3
  99. package/dist/types/nodefony/service/servers/server-https.d.ts +4 -4
  100. package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +1 -3
  101. package/dist/types/nodefony/service/servers/server-websocket.d.ts +1 -3
  102. package/dist/types/nodefony/service/servers/serverShutdown.d.ts +30 -16
  103. package/dist/types/nodefony/src/context/Context.d.ts +38 -5
  104. package/dist/types/nodefony/src/context/http/HttpContext.d.ts +16 -2
  105. package/dist/types/nodefony/src/context/http/Request.d.ts +20 -15
  106. package/dist/types/nodefony/src/context/http/Response.d.ts +11 -0
  107. package/dist/types/nodefony/src/context/http/parser.d.ts +18 -2
  108. package/dist/types/nodefony/src/context/http2/Request.d.ts +1 -1
  109. package/dist/types/nodefony/src/context/http2/Response.d.ts +27 -5
  110. package/dist/types/nodefony/src/context/requestTrace.d.ts +64 -0
  111. package/dist/types/nodefony/src/context/timingMode.d.ts +23 -0
  112. package/dist/types/nodefony/src/context/transport.d.ts +53 -0
  113. package/dist/types/nodefony/src/context/unsentResponse.d.ts +16 -0
  114. package/dist/types/nodefony/src/context/websocket/Response.d.ts +18 -2
  115. package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +58 -8
  116. package/dist/types/nodefony/src/context/websocket/isWebsocketContext.d.ts +15 -0
  117. package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +28 -12
  118. package/dist/types/nodefony/src/profiler/Profiler.d.ts +79 -111
  119. package/dist/types/nodefony/src/profiler/aggregate.d.ts +132 -0
  120. package/dist/types/nodefony/src/proxy/forward.d.ts +14 -2
  121. package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +16 -0
  122. package/dist/types/nodefony/src/proxy/rules.d.ts +3 -0
  123. package/dist/types/nodefony/src/servers/http2Limits.d.ts +37 -0
  124. package/dist/types/nodefony/src/servers/kernelServers.d.ts +10 -1
  125. package/dist/types/nodefony/src/servers/portBinder.d.ts +29 -5
  126. package/dist/types/nodefony/src/servers/upgradeDispatch.d.ts +33 -3
  127. package/docs/cookies.md +85 -78
  128. package/docs/diagnostics-channel.md +303 -0
  129. package/docs/index.md +6 -1
  130. package/docs/observabilite.md +159 -81
  131. package/docs/rate-limit.md +83 -83
  132. package/docs/reverse-proxy.md +72 -70
  133. package/docs/servers.md +338 -224
  134. package/docs/session.md +179 -179
  135. package/docs/sse.md +24 -20
  136. package/docs/upload.md +120 -117
  137. package/package.json +8 -9
  138. package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.js +0 -6
@@ -10,6 +10,9 @@ import type { Profiler } from "../src/profiler/Profiler.js";
10
10
  *
11
11
  * Endpoints :
12
12
  * - `GET /nodefony/profiler/api/recent` → derniers profils (résumés, récent → ancien)
13
+ * `?connection=<requestId>` : les seules frames de cette socket WebSocket
14
+ * - `GET /nodefony/profiler/api/summary` → les profils de l'anneau agrégés par
15
+ * route (percentiles, phases, formes SQL, N+1, erreurs — `aggregateProfiles`)
13
16
  * - `GET /nodefony/profiler/api/{id}` → profil complet (phases) d'un requestId
14
17
  * - `DELETE /nodefony/profiler/api/recent` → vide le ring buffer
15
18
  *
@@ -1,6 +1,7 @@
1
1
  import type { Severity } from "nodefony";
2
2
  import type { IRequestLogger, IRequestLogEntry } from "../interfaces/IRequestLogger.js";
3
- import type { IHttpContext, IWebsocketContext, PhaseTiming } from "../interfaces/IContext.js";
3
+ import type { IContext, IHttpContext, IWebsocketContext } from "../interfaces/IContext.js";
4
+ declare const statusOf: (context: IContext) => number | null;
4
5
  /**
5
6
  * Canonical audit log entry — 1 JSON PDU per request (P3.1).
6
7
  *
@@ -52,15 +53,6 @@ export interface AuditErrorEntry {
52
53
  /** Recursive — capped to depth 5 to avoid pathological cycles. */
53
54
  cause?: AuditErrorEntry;
54
55
  }
55
- /**
56
- * Durée totale de la requête, de la première phase (`startMs`) à maintenant.
57
- * `null` si le chronométrage est coupé (aucune phase enregistrée). Partagée
58
- * par le journal d'audit et le journal lisible — une seule définition.
59
- *
60
- * @param phases - phases chronométrées du contexte
61
- * @returns durée en millisecondes, ou `null`
62
- */
63
- declare function computeDurationMs(phases: PhaseTiming[]): number | null;
64
56
  /**
65
57
  * Severity derived from HTTP status code — RFC 9110 categories.
66
58
  * 1xx/2xx/3xx → INFO ; 4xx → WARNING ; 5xx → ERROR.
@@ -145,4 +137,4 @@ declare class JsonAuditLogger implements IRequestLogger {
145
137
  private serializeError;
146
138
  }
147
139
  export default JsonAuditLogger;
148
- export { computeDurationMs, severityFromStatus };
140
+ export { severityFromStatus, statusOf };
@@ -61,27 +61,78 @@ export interface ICaFileTrust {
61
61
  *
62
62
  * @param file - chemin du fichier, absolu ou relatif à `root`
63
63
  * @param root - racine de l'application
64
+ * Chaque refus dit le problème, d'où vient le chemin quand on le sait, puis le
65
+ * geste qui répare ({@link caFileRemedy}).
66
+ *
64
67
  * @param store - accès aux ancres ; `node:tls` par défaut
68
+ * @param origin - phrase qui dit d'où vient le chemin (` Valeur venue de …`), insérée dans un refus
65
69
  * @returns le bilan de l'ajout
66
70
  * @throws BootConfigurationError - fichier illisible, sans certificat, ou Node
67
71
  * incapable d'élargir ses ancres en cours d'exécution
68
72
  */
69
- export declare function trustCaFile(file: string, root: string, store?: ITlsTrustStore): Promise<ICaFileTrust>;
73
+ export declare function trustCaFile(file: string, root: string, store?: ITlsTrustStore, origin?: string): Promise<ICaFileTrust>;
74
+ /**
75
+ * Le geste qui répare une autorité déclarée qu'on ne peut pas reconnaître.
76
+ *
77
+ * Un refus au démarrage qui ne dit que la cause envoie chercher ; celui-ci dit
78
+ * quoi faire. Pure : éprouvée cause par cause, y compris celles qu'une machine
79
+ * de test ne sait pas produire (un droit refusé ne l'est pas à `root`, ni
80
+ * sous Windows).
81
+ *
82
+ * @param cause - code système (`ENOENT`, `EACCES`, `EPERM`, `EISDIR`),
83
+ * `NOPEM` pour un fichier lu sans certificat, ou tout autre motif
84
+ * @returns la phrase du geste
85
+ */
86
+ export declare function caFileRemedy(cause: string): string;
87
+ /**
88
+ * Dossier où Nodefony range les certificats qu'il fabrique en développement,
89
+ * relatif au répertoire courant.
90
+ *
91
+ * @returns le chemin absolu de `nodefony/config/certificates`
92
+ */
93
+ export declare function certificatesDirectory(): string;
94
+ /**
95
+ * Chemin de l'autorité de DÉVELOPPEMENT : le certificat racine que Nodefony
96
+ * fabrique lui-même pour signer le certificat du serveur local. Généré, jamais
97
+ * versionné — une seule convention, que le service et la confiance sortante lisent.
98
+ *
99
+ * @param directory - dossier des certificats ; {@link certificatesDirectory} par défaut
100
+ * @returns le chemin absolu du fichier PEM de l'autorité
101
+ */
102
+ export declare function developmentAuthorityPath(directory?: string): string;
103
+ /** Décor de {@link trustOutboundAuthorities} — injectable pour l'éprouver. */
104
+ export interface IOutboundTrustContext {
105
+ /** Vrai en développement : seul décor où l'autorité de dev absente est tolérée. */
106
+ readonly development?: boolean;
107
+ /** Chemin absolu de l'autorité de développement ; {@link developmentAuthorityPath} par défaut. */
108
+ readonly developmentAuthority?: string;
109
+ /** Environnement lu pour nommer la variable d'origine ; `process.env` par défaut. */
110
+ readonly env?: NodeJS.ProcessEnv;
111
+ /** Accès aux ancres ; `node:tls` par défaut. */
112
+ readonly store?: ITlsTrustStore;
113
+ }
70
114
  /**
71
115
  * Ajoute aux ancres TLS du processus les autorités déclarées
72
116
  * (`certificates.extraCaFiles`), au plus tôt : appelé à l'enregistrement du
73
117
  * module, avant tout service — toute requête sortante, commande console
74
118
  * comprise, les trouve en place.
75
119
  *
76
- * La CA de DÉVELOPPEMENT n'y figure pas : le service de certificats la pose
77
- * lui-même ({@link Certificate.generateServerCertificates}).
120
+ * L'autorité de DÉVELOPPEMENT déclarée mais pas encore engendrée est ignorée en
121
+ * développement, et le journal le dit : elle n'a signé aucun certificat, il n'y
122
+ * a donc rien à lui faire confiance, et le serveur de développement la pose
123
+ * lui-même quand il la fabrique ({@link Certificate.generateServerCertificates}).
124
+ * Rien n'est fabriqué ici : une commande console n'écrit pas de certificat.
125
+ * Une autre autorité absente, ou n'importe laquelle hors développement, reste
126
+ * un refus — qui nomme la variable d'origine et le geste qui répare
127
+ * ({@link caFileRemedy}).
78
128
  *
79
129
  * @param files - `certificates.extraCaFiles`
80
130
  * @param root - racine de l'application
81
131
  * @param log - journal du module
132
+ * @param context - décor (environnement, autorité de dev, ancres)
82
133
  * @throws BootConfigurationError - cf {@link trustCaFile}
83
134
  */
84
- export declare function trustOutboundAuthorities(files: readonly string[], root: string, log: (message: string, severity: "INFO") => void): Promise<void>;
135
+ export declare function trustOutboundAuthorities(files: readonly string[], root: string, log: (message: string, severity: "INFO") => void, context?: IOutboundTrustContext): Promise<void>;
85
136
  /** Paire de clés RSA du certificat auto-signé. */
86
137
  export interface IRsaKeyPair {
87
138
  privateKey: KeyObject;
@@ -1,5 +1,36 @@
1
1
  import type { IErrorRenderer, IErrorHttpResult, IErrorWebsocketResult } from "../interfaces/IErrorRenderer.js";
2
2
  import type { IHttpContext, IWebsocketContext } from "../interfaces/IContext.js";
3
+ /** Champ fautif, tel qu'exposé au client dans le corps JSON (`error.fields`). */
4
+ export interface IValidationField {
5
+ /** Chemin pointé du champ (`"author.email"`). */
6
+ field: string;
7
+ /** Message lisible. */
8
+ message: string;
9
+ /** Contrôle qui a échoué (`too_small`…) — utile au client pour réagir finement. */
10
+ rule?: string | undefined;
11
+ /**
12
+ * Partie de l'entrée qui porte le champ (`body`, `query`, `params`,
13
+ * `headers`) — présente pour une route à schéma, où deux parties peuvent
14
+ * porter le même nom. `field` reste relatif à sa partie : un formulaire le
15
+ * relie à son champ de saisie sans rien retirer.
16
+ */
17
+ in?: string | undefined;
18
+ }
19
+ /**
20
+ * Reconnaît une erreur de validation — `ZodError`, ou `SchemaValidationError`
21
+ * d'une route à schéma — et en extrait les champs fautifs.
22
+ *
23
+ * Reconnaissance **structurelle** (`name` + `issues`), pas `instanceof ZodError` :
24
+ * une application peut embarquer sa propre copie de zod (résolutions npm multiples),
25
+ * et `instanceof` échouerait alors en silence — la validation retomberait en 500.
26
+ * Même parti-pris que le duck-typing de `isPromise` dans le framework.
27
+ *
28
+ * Ne coûte rien au chemin nominal : ce code ne tourne que sur une erreur déjà levée.
29
+ *
30
+ * @param error - erreur remontée par le pipeline.
31
+ * @returns les champs fautifs, ou `null` si ce n'est pas une erreur de validation.
32
+ */
33
+ export declare function toValidationFields(error: Error): IValidationField[] | null;
3
34
  /**
4
35
  * Reconnaît un identifiant MAL FORMÉ refusé par la base, et rend la valeur fautive.
5
36
  *
@@ -11,11 +11,13 @@ import type { IncomingHttpHeaders, IncomingMessage } from "node:http";
11
11
  import Ws from "ws";
12
12
  import httpServer from "../service/servers/server-http.js";
13
13
  import httpsServer from "../service/servers/server-https.js";
14
+ import h2cServer from "../service/servers/server-h2c.js";
14
15
  import websocketServer from "../service/servers/server-websocket.js";
15
16
  import websocketSecureServer from "../service/servers/server-websocket-secure.js";
16
17
  import Statics from "./servers/server-static.js";
17
18
  import type ReverseProxy from "./reverse-proxy.js";
18
19
  import WebsocketContext from "../src/context/websocket/WebsocketContext.js";
20
+ import type { IUpgradeAdmission } from "../src/servers/upgradeDispatch.js";
19
21
  import HttpContext from "../src/context/http/HttpContext.js";
20
22
  import Context, { HTTPMethod, WebSocketState } from "../src/context/Context.js";
21
23
  import Certicates from "./certificates.js";
@@ -42,9 +44,8 @@ export type httpRequest = http.IncomingMessage | http2.Http2ServerRequest;
42
44
  export type httpResponse = http.ServerResponse | http2.Http2ServerResponse;
43
45
  export { responseEnded } from "../src/context/responseEnded.js";
44
46
  export type ContextType = WebsocketContext | HttpContext | Context;
45
- export type ServerType = "http" | "https" | "http2" | "http3" | "websocket" | "websocket-secure";
46
- export type responseTimeoutType = "http" | "https" | "http2" | "http3";
47
- export type SchemeType = "http" | "https" | "ws" | "wss";
47
+ export type { ServerType, SchemeType } from "../interfaces/IContext.js";
48
+ import type { ServerType, SchemeType } from "../interfaces/IContext.js";
48
49
  export interface WsMetaData {
49
50
  type?: "message" | "handshake" | undefined;
50
51
  state?: WebSocketState | undefined;
@@ -119,8 +120,6 @@ declare class HttpKernel extends Service implements IHttpKernelInterface {
119
120
  responseTimeout: {
120
121
  http: number;
121
122
  https: number;
122
- http2: number;
123
- http3: number;
124
123
  };
125
124
  closeTimeOutWs: {
126
125
  ws: number;
@@ -135,6 +134,8 @@ declare class HttpKernel extends Service implements IHttpKernelInterface {
135
134
  private errorRenderer;
136
135
  private requestLogger;
137
136
  private profiler;
137
+ /** `timing.tooling` : profiler aussi l'outillage (lu une fois, à onReady). */
138
+ private profileTooling;
138
139
  private _trustProxyChecker;
139
140
  private _wsOriginPolicy;
140
141
  private rateLimiter;
@@ -187,6 +188,32 @@ declare class HttpKernel extends Service implements IHttpKernelInterface {
187
188
  * tel quel plutôt que d'inventer un état.
188
189
  */
189
190
  get rateLimitStore(): IRateLimitStore | null;
191
+ /**
192
+ * Cette requête — ou cette frame du pont — se profile-t-elle ? Jamais sans
193
+ * profileur (production). L'outillage (console, profileur, journaux, Vite :
194
+ * `isProfiledUrl`, sur la règle `isConsolePath` du cœur) seulement sous
195
+ * `timing.tooling` — on ne
196
+ * mesure pas la façon dont on mesure : ni tampon SQL, ni instantané, ni place
197
+ * dans les anneaux du profileur.
198
+ *
199
+ * @param url - URL ou chemin de la requête
200
+ * @returns `true` quand le profil doit être pris
201
+ */
202
+ shouldProfile(url: string): boolean;
203
+ /**
204
+ * En développement, signale une action qui a rendu une valeur que le
205
+ * Resolver ne sait pas envoyer (`null`, `undefined`, instance de classe)
206
+ * et n'a rien envoyé elle-même : la requête attend son délai d'expiration.
207
+ * Le message nomme l'action, ce qu'elle a rendu et le geste qui convient
208
+ * (`unsentResponseWarning`).
209
+ *
210
+ * Appelé au timeout, AVANT que le 504/408 parte — après, `sended` est vrai et
211
+ * plus rien ne se signalerait — et au démontage, pour un client parti avant.
212
+ * Gratuit en production : rien n'est lu au-delà de `waitAsync`.
213
+ *
214
+ * @param context - le contexte HTTP de la requête
215
+ */
216
+ warnUnsentResponse(context: HttpContext): void;
190
217
  private configureRateLimit;
191
218
  /**
192
219
  * (Re)configure le backstop OPT-IN de connexions WS concurrentes par IP (F6c)
@@ -383,7 +410,7 @@ declare class HttpKernel extends Service implements IHttpKernelInterface {
383
410
  * pipeline.
384
411
  */
385
412
  private dispatchHttpRequest;
386
- initServers(): Promise<(httpServer | httpsServer | websocketServer | websocketSecureServer)[]>;
413
+ initServers(): Promise<(httpServer | httpsServer | h2cServer | websocketServer | websocketSecureServer)[]>;
387
414
  /**
388
415
  * Publie les ports EFFECTIFS du runtime (state file), une fois l'écoute établie.
389
416
  *
@@ -475,6 +502,18 @@ declare class HttpKernel extends Service implements IHttpKernelInterface {
475
502
  * @returns le contexte servi, ou sa promesse.
476
503
  */
477
504
  handleHttp(scope: Scope, request: httpRequest, response: httpResponse, type: ServerType): MaybePromise<HttpContext>;
505
+ /**
506
+ * Sert un contexte HTTP fraîchement créé : le crochet `onCreateContext`
507
+ * s'il a des écouteurs, puis le pipeline.
508
+ *
509
+ * Hot path : `fireAsync` est une fonction async → 1 Promise + 1 microtask
510
+ * MÊME à 0 listener. En prod sans @nodefony/security ces seams
511
+ * (onCreateContext/beforeResolve/afterAuth) n'ont aucun listener → garde
512
+ * `listenerCount` (O(1), 0 alloc) pour ne RIEN planifier.
513
+ *
514
+ * @returns le contexte servi, ou sa promesse
515
+ */
516
+ private dispatchHttpContext;
478
517
  /**
479
518
  * Traceparent, tampon du profileur, puis le pipeline dans la bulle ALS de la
480
519
  * requête (`RequestContext.run`).
@@ -542,7 +581,7 @@ declare class HttpKernel extends Service implements IHttpKernelInterface {
542
581
  * @throws (rejet) le refus du pare-feu, après `onAuthFailure`.
543
582
  */
544
583
  private runFirewall;
545
- createWebsocketContext(scope: Scope, req: IncomingMessage, ws: Ws, type: ServerType): WebsocketContext;
584
+ createWebsocketContext(scope: Scope, req: IncomingMessage, ws: Ws | null, type: ServerType): WebsocketContext;
546
585
  /**
547
586
  * Teardown d'une connexion WS à sa fermeture : signal d'abandon, hooks
548
587
  * post-réponse, persistance de la session, puis libération du scope.
@@ -550,10 +589,57 @@ declare class HttpKernel extends Service implements IHttpKernelInterface {
550
589
  * écouteur d'événement synchrone, qui n'attend rien).
551
590
  */
552
591
  private finishWebsocket;
553
- onWebsocketRequest(ws: Ws, req: IncomingMessage, type: ServerType): Promise<unknown>;
554
- handleWebsocket(scope: Scope, ws: Ws, req: IncomingMessage, type: ServerType): Promise<unknown>;
592
+ /**
593
+ * Point d'entrée d'un upgrade vers une route WebSocket de Nodefony, appelé
594
+ * par le répartiteur d'upgrade AVANT le `101` (#596) : rien ne s'ouvre tant
595
+ * que le pipeline n'a pas dit oui.
596
+ *
597
+ * Les bornes par IP passent en premier, avant tout scope, ALS ou pipeline
598
+ * (symétrie avec le 429 HTTP et avec le proxy inverse) : le vrai vecteur DoS
599
+ * est d'accumuler des sockets ouvertes. La place est rendue à la fermeture du
600
+ * socket du client — même règle que pour un tunnel relayé.
601
+ *
602
+ * @param req - requête d'upgrade
603
+ * @param admission - le verdict à rendre : accepter (`101`) ou refuser (HTTP)
604
+ * @param type - serveur qui a reçu l'upgrade
605
+ * @returns la promesse du traitement de la connexion
606
+ */
607
+ onWebsocketUpgrade(req: IncomingMessage, admission: IUpgradeAdmission, type: ServerType): Promise<unknown>;
608
+ /**
609
+ * Pipeline d'un handshake WebSocket, dans la bulle ALS de la connexion :
610
+ * jugement (`onConnect`), pare-feu, gardes de l'action — PUIS seulement
611
+ * l'acceptation, la socket et l'action de handshake. Tout refus antérieur à
612
+ * l'acceptation part en statut HTTP (`onError` → `refuseHandshake`).
613
+ *
614
+ * @param scope - scope DI de la connexion
615
+ * @param req - requête d'upgrade
616
+ * @param admission - le verdict à rendre
617
+ * @param type - serveur qui a reçu l'upgrade
618
+ * @returns ce que rend l'action de handshake
619
+ */
620
+ handleWebsocket(scope: Scope, req: IncomingMessage, admission: IUpgradeAdmission, type: ServerType): Promise<unknown>;
621
+ /**
622
+ * Fin du canal `nodefony.http.request` pour un handshake interrompu par une
623
+ * exception — sans effet si un refus l'a déjà publiée (`refuseHandshake`).
624
+ * Le statut est un fait, pas une déduction : une socket rattachée dit que
625
+ * le `101` est parti ; sinon c'est le `500` du filet qui a été rendu.
626
+ *
627
+ * @param context - le contexte du handshake, `null` s'il n'a pu être construit
628
+ * @param error - l'exception qui a interrompu le pipeline
629
+ */
630
+ private closeFailedWebsocketTrace;
555
631
  private releaseOrphanWsScope;
556
- onConnect(context: WebsocketContext | null, error?: unknown): Promise<Ws | number>;
632
+ /**
633
+ * Juge un handshake WebSocket AVANT son acceptation : hôte, origine,
634
+ * `beforeResolve`, contrôleur (protocole négocié), session. N'ouvre rien —
635
+ * l'acceptation n'a lieu qu'après le pare-feu et les gardes de l'action
636
+ * (cf {@link handleWebsocket}).
637
+ *
638
+ * @param context - contexte du handshake, encore sans socket
639
+ * @param error - erreur levée à la construction du contexte, relancée telle quelle
640
+ * @throws la première erreur du jugement — rendue en statut HTTP par `onError`
641
+ */
642
+ onConnect(context: WebsocketContext | null, error?: unknown): Promise<void>;
557
643
  checkValidDomain(context: ContextType): number;
558
644
  isValidDomain(context: ContextType): boolean;
559
645
  /**
@@ -12,3 +12,19 @@ import type { Duplex } from "node:stream";
12
12
  * @param socket - flux client brut (l'event `clientError` fournit un `Duplex`).
13
13
  */
14
14
  export declare function handleClientError(error: NodeJS.ErrnoException, socket: Duplex): void;
15
+ /**
16
+ * Gère l'event `sessionError` d'un serveur HTTP/2 (h2c ou TLS) : un pair PARTI
17
+ * se tait, toute autre erreur est rapportée.
18
+ *
19
+ * Une session réinitialisée par le pair n'a rien à diagnostiquer. C'est la
20
+ * signature d'une sonde de port — `nodefony status`, `doctor`, le superviseur
21
+ * de développement, la sonde `tcpSocket` d'un orchestrateur — qui se connecte
22
+ * puis ferme sans lire le `SETTINGS` que le serveur envoie dès l'ouverture : le
23
+ * système ferme alors par un RST plutôt qu'un FIN, et Node lève `ECONNRESET`.
24
+ * La rapporter imprimait une pile entière par sonde. La règle « client parti »
25
+ * est celle des frames WebSocket ({@link isPeerGoneError}), pas une copie.
26
+ *
27
+ * @param error - erreur de session émise par `node:http2`
28
+ * @param report - rapporte une erreur réelle, à la sévérité choisie par le serveur
29
+ */
30
+ export declare function handleSessionError(error: Error, report: (error: Error) => void): void;
@@ -0,0 +1,51 @@
1
+ import { Service, type Module, FamilyType } from "nodefony";
2
+ import HttpKernel, { ProtocolType, ServerType, SchemeType } from "../http-kernel.js";
3
+ import http2 from "node:http2";
4
+ import { AddressInfo } from "node:net";
5
+ import { type IDrainTerminator } from "./serverShutdown.js";
6
+ import type { IHttpConfig } from "../../config/config.js";
7
+ import type { DefaultOptionsService } from "nodefony";
8
+ /**
9
+ * Serveur HTTP/2 en clair (h2c, connaissance préalable — RFC 9113 §3.3), sur un
10
+ * port DÉDIÉ (`servers.h2c`, 5153 par défaut), pour un proxy inverse qui parle
11
+ * HTTP/2 à ses amonts.
12
+ *
13
+ * Il partage le chemin HTTP/2 du 5152 — même `onHttpRequest`, mêmes limites
14
+ * anti-DoS (`http2ServerLimits`), même drain par GOAWAY — sans TLS : `serverType`
15
+ * vaut `"http"`, le schéma vient du transport ou d'un proxy de confiance, jamais
16
+ * du pseudo-en-tête `:scheme`. Le protocole du saut (`"h2c"`) se constate sur la
17
+ * requête (`transport.ts#alpnProtocolOf`).
18
+ *
19
+ * Hors de son périmètre, délibérément : HTTP/1.1 (refusé par la préface HTTP/2 —
20
+ * sondes et clients HTTP/1.1 restent sur le port HTTP), WebSocket (aucun CONNECT
21
+ * étendu, RFC 8441 : un proxy route `Upgrade: websocket` vers le port HTTP) et la
22
+ * bascule `Upgrade: h2c` (retirée par RFC 9113 §3.1).
23
+ */
24
+ declare class ServerH2c extends Service {
25
+ private readonly httpKernel;
26
+ options: IHttpConfig["http"] & DefaultOptionsService;
27
+ module: Module<IHttpConfig>;
28
+ server: http2.Http2Server | null;
29
+ httpTerminator: IDrainTerminator | null;
30
+ port: number;
31
+ protocol: ProtocolType;
32
+ ready: boolean;
33
+ type: ServerType;
34
+ domain: string | null;
35
+ scheme: SchemeType;
36
+ address: string | null;
37
+ family: FamilyType | null;
38
+ active: boolean;
39
+ infos: AddressInfo | null;
40
+ constructor(module: Module<IHttpConfig>, httpKernel: HttpKernel);
41
+ /**
42
+ * Crée et met en écoute le serveur h2c.
43
+ *
44
+ * @returns le serveur à l'écoute
45
+ * @throws BootConfigurationError si `servers.h2c.port` est celui du serveur
46
+ * HTTP ou HTTPS — un port partagé n'est pas un conflit à contourner
47
+ */
48
+ createServer(): Promise<http2.Http2Server>;
49
+ showBanner(): void;
50
+ }
51
+ export default ServerH2c;
@@ -1,9 +1,9 @@
1
- import { Service, Module, FamilyType } from "nodefony";
1
+ import { Service, type Module, FamilyType } from "nodefony";
2
2
  import HttpKernel, { ProtocolType, ServerType, SchemeType } from "../http-kernel.js";
3
3
  import http from "node:http";
4
4
  import http2 from "node:http2";
5
5
  import { AddressInfo } from "node:net";
6
- import { HttpTerminator } from "./serverShutdown.js";
6
+ import { type IDrainTerminator } from "./serverShutdown.js";
7
7
  import type { IHttpConfig } from "../../config/config.js";
8
8
  import type { DefaultOptionsService } from "nodefony";
9
9
  declare class ServerHttp extends Service {
@@ -11,7 +11,7 @@ declare class ServerHttp extends Service {
11
11
  options: IHttpConfig["http"] & DefaultOptionsService;
12
12
  module: Module;
13
13
  server: http.Server | http2.Http2Server | null;
14
- httpTerminator: HttpTerminator | null;
14
+ httpTerminator: IDrainTerminator | null;
15
15
  port: number;
16
16
  protocol: ProtocolType;
17
17
  ready: boolean;
@@ -1,15 +1,15 @@
1
- import { Service, Module, FamilyType } from "nodefony";
1
+ import { Service, type Module, FamilyType } from "nodefony";
2
2
  import HttpKernel, { ProtocolType, ServerType, SchemeType } from "../http-kernel.js";
3
3
  import https from "node:https";
4
4
  import http2 from "node:http2";
5
- import { HttpTerminator } from "./serverShutdown.js";
5
+ import { type IDrainTerminator } from "./serverShutdown.js";
6
6
  import { AddressInfo } from "node:net";
7
7
  import type { IHttpConfig } from "../../config/config.js";
8
8
  import type { DefaultOptionsService } from "nodefony";
9
9
  declare class ServerHttps extends Service {
10
10
  private readonly httpKernel;
11
11
  options: IHttpConfig["https"] & DefaultOptionsService;
12
- httpTerminator: HttpTerminator | null;
12
+ httpTerminator: IDrainTerminator | null;
13
13
  module: Module;
14
14
  server: https.Server | http2.Http2SecureServer | null;
15
15
  port: number;
@@ -23,7 +23,7 @@ declare class ServerHttps extends Service {
23
23
  active: boolean;
24
24
  infos: AddressInfo | null;
25
25
  constructor(module: Module, httpKernel: HttpKernel);
26
- terminator(): HttpTerminator;
26
+ terminator(): IDrainTerminator;
27
27
  setPort(): number;
28
28
  createServer(): Promise<https.Server | http2.Http2SecureServer>;
29
29
  createServerH2(): Promise<http2.Http2SecureServer>;
@@ -1,10 +1,9 @@
1
- import Ws, { WebSocketServer } from "ws";
1
+ import { WebSocketServer } from "ws";
2
2
  import { Service, Module, FamilyType } from "nodefony";
3
3
  import type { DefaultOptionsService } from "nodefony";
4
4
  import type { IHttpConfig } from "../../config/config.js";
5
5
  import HttpKernel, { ProtocolType, ServerType, SchemeType } from "../http-kernel.js";
6
6
  import { AddressInfo } from "node:net";
7
- import type { IncomingMessage } from "node:http";
8
7
  import httpsServers from "./server-https.js";
9
8
  declare class WebsocketSecure extends Service {
10
9
  #private;
@@ -26,7 +25,6 @@ declare class WebsocketSecure extends Service {
26
25
  constructor(module: Module, httpKernel: HttpKernel);
27
26
  setPort(): number;
28
27
  createServer(serverHttps: httpsServers): Promise<WebSocketServer>;
29
- onConnection(ws: Ws, req: IncomingMessage): void;
30
28
  terminate(): Promise<boolean>;
31
29
  showBanner(): void;
32
30
  }
@@ -1,10 +1,9 @@
1
- import Ws, { WebSocketServer } from "ws";
1
+ import { WebSocketServer } from "ws";
2
2
  import { Service, Module, FamilyType } from "nodefony";
3
3
  import type { DefaultOptionsService } from "nodefony";
4
4
  import type { IHttpConfig } from "../../config/config.js";
5
5
  import HttpKernel, { ProtocolType, ServerType, SchemeType } from "../http-kernel.js";
6
6
  import { AddressInfo } from "node:net";
7
- import type { IncomingMessage } from "node:http";
8
7
  import httpServer from "./server-http.js";
9
8
  declare class Websocket extends Service {
10
9
  #private;
@@ -26,7 +25,6 @@ declare class Websocket extends Service {
26
25
  constructor(module: Module, httpKernel: HttpKernel);
27
26
  setPort(): number;
28
27
  createServer(serverHttp: httpServer): Promise<WebSocketServer>;
29
- onConnection(ws: Ws, req: IncomingMessage): void;
30
28
  terminate(): Promise<boolean>;
31
29
  showBanner(): void;
32
30
  }
@@ -1,27 +1,41 @@
1
- import { HttpTerminator } from "http-terminator";
2
1
  import type { Server as HttpServer } from "node:http";
3
2
  import type { Server as HttpsServer } from "node:https";
4
- import type { Http2SecureServer } from "node:http2";
3
+ import type { Http2SecureServer, Http2Server } from "node:http2";
4
+ /** Serveur que `createDrainTerminator` sait drainer. */
5
+ export type DrainableServer = HttpServer | HttpsServer | Http2Server | Http2SecureServer;
6
+ /** Arrêt gracieux d'un serveur, rendu par `createDrainTerminator`. */
7
+ export interface IDrainTerminator {
8
+ /**
9
+ * Draine puis ferme le serveur ; idempotent (le deuxième appel rend la même promesse).
10
+ *
11
+ * @returns une promesse résolue quand toutes les connexions sont fermées
12
+ */
13
+ terminate(): Promise<void>;
14
+ }
5
15
  /**
6
- * Fabrique le terminator de drain graceful d'un serveur HTTP/HTTPS/HTTP2.
16
+ * Fabrique l'arrêt gracieux d'un serveur HTTP/1.1, HTTPS, HTTP/2 sur TLS ou h2c.
7
17
  *
8
- * Au `terminate()` (shutdown SIGTERM/docker stop) : les requêtes in-flight se
9
- * terminent (header `connection: close` injecté sur les réponses en cours), les
10
- * sockets idle sont fermées immédiatement, et tout ce qui reste après
11
- * `shutdownTimeout` ms est détruit de force. Le terminator appelle lui-même
12
- * `server.close()` — ne pas le rappeler derrière.
18
+ * Au `terminate()` (SIGTERM, `docker stop`), le serveur cesse d'accepter, puis :
19
+ * - **HTTP/2** : chaque session reçoit un GOAWAY (`session.close()`) — les flux
20
+ * en vol se terminent, les nouveaux sont refusés, et le client sait qu'il doit
21
+ * rouvrir ailleurs au lieu de constater une coupure ;
22
+ * - **HTTP/1.1** : la réponse en vol part avec `Connection: close`, la socket
23
+ * inactive est fermée tout de suite, une requête qui arrive encore sur une
24
+ * socket gardée ouverte reçoit aussi `Connection: close`.
25
+ *
26
+ * Ce qui reste après `shutdownTimeout` ms est détruit. `server.close()` est
27
+ * appelé ici : ne pas le rappeler derrière.
13
28
  *
14
29
  * ⚠️ Les sockets WebSocket upgradées (sans réponse HTTP en cours) sont détruites
15
- * SANS frame Close par le terminator → les serveurs WS doivent fermer leurs
16
- * clients (close 1001) AVANT ce drain. Garanti par l'ordre des listeners
17
- * `onTerminate` : WS/WSS s'attachent en `prependOnceListener`, les serveurs
18
- * HTTP en `once`.
30
+ * SANS frame Close → les serveurs WS doivent fermer leurs clients (close 1001)
31
+ * AVANT ce drain. Garanti par l'ordre des listeners `onTerminate` : WS/WSS
32
+ * s'attachent en `prependOnceListener`, les serveurs HTTP en `once`.
19
33
  *
20
- * @param server - serveur Node à drainer (créé, pas forcément listening)
34
+ * @param server - serveur Node à drainer, créé AVANT son `listen` (le suivi des
35
+ * connexions commence ici)
21
36
  * @param shutdownTimeout - délai de drain en ms avant destruction forcée
22
- * @returns le terminator à invoquer au shutdown
37
+ * @returns l'arrêt à invoquer au shutdown
23
38
  */
24
- export declare function createDrainTerminator(server: HttpServer | HttpsServer | Http2SecureServer, shutdownTimeout?: number): HttpTerminator;
39
+ export declare function createDrainTerminator(server: DrainableServer, shutdownTimeout?: number): IDrainTerminator;
25
40
  /** Drain par défaut (ms) — même valeur que le défaut Zod `servers.*.shutdownTimeout`. */
26
41
  export declare const DEFAULT_SHUTDOWN_TIMEOUT = 5000;
27
- export type { HttpTerminator };