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

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 (130) hide show
  1. package/README.md +24 -0
  2. package/dist/_virtual/{_@oxc-project_runtime@0.155.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorate.js +1 -1
  3. package/dist/_virtual/{_@oxc-project_runtime@0.155.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorateParam.js +1 -1
  4. package/dist/index.js +30 -26
  5. package/dist/nodefony/command/proxyGenerateCommand.js +2 -2
  6. package/dist/nodefony/config/config.js +29 -4
  7. package/dist/nodefony/grpc/GrpcCall.js +298 -0
  8. package/dist/nodefony/grpc/GrpcEndpoint.js +528 -0
  9. package/dist/nodefony/grpc/index.js +4 -0
  10. package/dist/nodefony/interfaces/IRpcCall.js +1 -0
  11. package/dist/nodefony/service/ProfilerAdminApi.js +5 -2
  12. package/dist/nodefony/service/audit-logger.js +52 -6
  13. package/dist/nodefony/service/error-renderer.js +47 -13
  14. package/dist/nodefony/service/http-kernel.js +306 -49
  15. package/dist/nodefony/service/pretty-request-logger.js +5 -3
  16. package/dist/nodefony/service/request-logger.js +21 -6
  17. package/dist/nodefony/service/reverse-proxy.js +8 -3
  18. package/dist/nodefony/service/servers/clientError.js +53 -4
  19. package/dist/nodefony/service/servers/server-h2c.js +6 -6
  20. package/dist/nodefony/service/servers/server-http.js +6 -4
  21. package/dist/nodefony/service/servers/server-https.js +14 -7
  22. package/dist/nodefony/service/servers/server-static.js +1 -6
  23. package/dist/nodefony/service/servers/server-websocket-secure.js +7 -11
  24. package/dist/nodefony/service/servers/server-websocket.js +7 -11
  25. package/dist/nodefony/service/servers/serverShutdown.js +43 -6
  26. package/dist/nodefony/service/sessions/sessions-service.js +7 -7
  27. package/dist/nodefony/service/upload/upload-service.js +2 -2
  28. package/dist/nodefony/src/assets/prebuiltUi.js +23 -5
  29. package/dist/nodefony/src/context/Context.js +12 -1
  30. package/dist/nodefony/src/context/http/ByteStream.js +364 -0
  31. package/dist/nodefony/src/context/http/HttpContext.js +139 -32
  32. package/dist/nodefony/src/context/http/Request.js +111 -22
  33. package/dist/nodefony/src/context/http/Response.js +459 -37
  34. package/dist/nodefony/src/context/http/SseStream.js +83 -148
  35. package/dist/nodefony/src/context/http/StreamLifecycle.js +582 -0
  36. package/dist/nodefony/src/context/http/parser.js +29 -11
  37. package/dist/nodefony/src/context/http/urlFastPath.js +80 -1
  38. package/dist/nodefony/src/context/http/webResponse.js +56 -0
  39. package/dist/nodefony/src/context/http2/Request.js +1 -1
  40. package/dist/nodefony/src/context/http2/Response.js +55 -12
  41. package/dist/nodefony/src/context/requestTrace.js +6 -2
  42. package/dist/nodefony/src/context/rpcOutcome.js +20 -0
  43. package/dist/nodefony/src/context/transport.js +20 -1
  44. package/dist/nodefony/src/context/websocket/Response.js +15 -3
  45. package/dist/nodefony/src/context/websocket/WebsocketContext.js +86 -18
  46. package/dist/nodefony/src/context/websocket/websocketUrl.js +7 -0
  47. package/dist/nodefony/src/context/websocket/wsBackpressure.js +15 -15
  48. package/dist/nodefony/src/context/websocket/wsFrameRate.js +71 -0
  49. package/dist/nodefony/src/context/websocket/wsLogContent.js +55 -17
  50. package/dist/nodefony/src/cookies/cookie.js +57 -14
  51. package/dist/nodefony/src/errors/httpError.js +58 -25
  52. package/dist/nodefony/src/grpc/cors.js +59 -0
  53. package/dist/nodefony/src/grpc/limits.js +16 -0
  54. package/dist/nodefony/src/grpc/refusal.js +271 -0
  55. package/dist/nodefony/src/profiler/Profiler.js +14 -1
  56. package/dist/nodefony/src/profiler/aggregate.js +2 -2
  57. package/dist/nodefony/src/proxy/forward.js +109 -9
  58. package/dist/nodefony/src/proxy/generateProxyConfig.js +97 -17
  59. package/dist/nodefony/src/proxy/rules.js +29 -14
  60. package/dist/nodefony/src/servers/http2Goaway.js +76 -0
  61. package/dist/nodefony/src/servers/http2Limits.js +41 -15
  62. package/dist/nodefony/src/servers/http2StreamGrace.js +100 -0
  63. package/dist/nodefony/src/servers/receiveDeadline.js +177 -0
  64. package/dist/nodefony/src/servers/upgradeDispatch.js +62 -4
  65. package/dist/types/index.d.ts +11 -2
  66. package/dist/types/nodefony/config/config.d.ts +14 -0
  67. package/dist/types/nodefony/grpc/GrpcCall.d.ts +142 -0
  68. package/dist/types/nodefony/grpc/GrpcEndpoint.d.ts +98 -0
  69. package/dist/types/nodefony/grpc/index.d.ts +15 -0
  70. package/dist/types/nodefony/interfaces/IContext.d.ts +9 -0
  71. package/dist/types/nodefony/interfaces/IErrorRenderer.d.ts +14 -0
  72. package/dist/types/nodefony/interfaces/IRequest.d.ts +2 -1
  73. package/dist/types/nodefony/interfaces/IRequestTrace.d.ts +10 -0
  74. package/dist/types/nodefony/interfaces/IRouting.d.ts +19 -0
  75. package/dist/types/nodefony/interfaces/IRpcCall.d.ts +83 -0
  76. package/dist/types/nodefony/interfaces/ISecurity.d.ts +27 -2
  77. package/dist/types/nodefony/interfaces/IUpload.d.ts +51 -4
  78. package/dist/types/nodefony/interfaces/index.d.ts +2 -1
  79. package/dist/types/nodefony/service/ProfilerAdminApi.d.ts +2 -1
  80. package/dist/types/nodefony/service/audit-logger.d.ts +33 -3
  81. package/dist/types/nodefony/service/error-renderer.d.ts +27 -5
  82. package/dist/types/nodefony/service/http-kernel.d.ts +36 -3
  83. package/dist/types/nodefony/service/request-logger.d.ts +10 -0
  84. package/dist/types/nodefony/service/servers/clientError.d.ts +3 -2
  85. package/dist/types/nodefony/service/servers/serverShutdown.d.ts +7 -5
  86. package/dist/types/nodefony/src/assets/prebuiltUi.d.ts +9 -4
  87. package/dist/types/nodefony/src/context/Context.d.ts +9 -1
  88. package/dist/types/nodefony/src/context/http/ByteStream.d.ts +109 -0
  89. package/dist/types/nodefony/src/context/http/HttpContext.d.ts +99 -3
  90. package/dist/types/nodefony/src/context/http/Request.d.ts +46 -9
  91. package/dist/types/nodefony/src/context/http/Response.d.ts +188 -8
  92. package/dist/types/nodefony/src/context/http/SseStream.d.ts +33 -8
  93. package/dist/types/nodefony/src/context/http/StreamLifecycle.d.ts +106 -0
  94. package/dist/types/nodefony/src/context/http/parser.d.ts +17 -3
  95. package/dist/types/nodefony/src/context/http/urlFastPath.d.ts +40 -0
  96. package/dist/types/nodefony/src/context/http/webResponse.d.ts +31 -0
  97. package/dist/types/nodefony/src/context/http2/Response.d.ts +40 -5
  98. package/dist/types/nodefony/src/context/requestTrace.d.ts +3 -1
  99. package/dist/types/nodefony/src/context/rpcOutcome.d.ts +11 -0
  100. package/dist/types/nodefony/src/context/transport.d.ts +17 -0
  101. package/dist/types/nodefony/src/context/websocket/Response.d.ts +6 -1
  102. package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +48 -0
  103. package/dist/types/nodefony/src/context/websocket/websocketUrl.d.ts +4 -0
  104. package/dist/types/nodefony/src/context/websocket/wsBackpressure.d.ts +12 -2
  105. package/dist/types/nodefony/src/context/websocket/wsFrameRate.d.ts +69 -0
  106. package/dist/types/nodefony/src/context/websocket/wsLogContent.d.ts +16 -8
  107. package/dist/types/nodefony/src/cookies/cookie.d.ts +20 -2
  108. package/dist/types/nodefony/src/errors/httpError.d.ts +29 -4
  109. package/dist/types/nodefony/src/grpc/cors.d.ts +32 -0
  110. package/dist/types/nodefony/src/grpc/limits.d.ts +14 -0
  111. package/dist/types/nodefony/src/grpc/refusal.d.ts +136 -0
  112. package/dist/types/nodefony/src/profiler/Profiler.d.ts +11 -1
  113. package/dist/types/nodefony/src/proxy/forward.d.ts +37 -2
  114. package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +15 -2
  115. package/dist/types/nodefony/src/proxy/rules.d.ts +20 -0
  116. package/dist/types/nodefony/src/servers/http2Goaway.d.ts +41 -0
  117. package/dist/types/nodefony/src/servers/http2Limits.d.ts +27 -14
  118. package/dist/types/nodefony/src/servers/http2StreamGrace.d.ts +34 -0
  119. package/dist/types/nodefony/src/servers/receiveDeadline.d.ts +77 -0
  120. package/dist/types/nodefony/src/servers/upgradeDispatch.d.ts +41 -1
  121. package/docs/byte-stream.md +406 -0
  122. package/docs/cookies.md +13 -3
  123. package/docs/diagnostics-channel.md +45 -1
  124. package/docs/index.md +8 -0
  125. package/docs/reverse-proxy.md +15 -3
  126. package/docs/servers.md +67 -43
  127. package/docs/session.md +3 -3
  128. package/docs/sse.md +27 -20
  129. package/docs/upload.md +43 -17
  130. package/package.json +38 -18
@@ -1,5 +1,7 @@
1
- import { isLoopbackHostname } from "nodefony";
1
+ import { isLoopbackHostname, parseBareOrigin } from "nodefony";
2
2
  // nodefony/src/proxy/rules.ts
3
+ /** Schémas d’amont d’un relais : un proxy inverse ne parle que HTTP. */
4
+ const PROXY_ORIGIN_PROTOCOLS = ["http:", "https:"];
3
5
  /**
4
6
  * Règles d'un montage du proxy inverse — UNIQUE source, lue par le schéma de
5
7
  * configuration (`proxy.mounts`) ET par `ReverseProxy.mount()` : un montage
@@ -49,17 +51,7 @@ function normalizePrefix(prefix) {
49
51
  * @returns l'URL analysée, ou `null` si ce n'est pas une origine http(s) nue
50
52
  */
51
53
  function parseProxyOrigin(origin) {
52
- let url;
53
- try {
54
- url = new URL(origin);
55
- } catch {
56
- return null;
57
- }
58
- if (url.protocol !== "http:" && url.protocol !== "https:") return null;
59
- if (!/^https?:\/\/[^/?#]+\/?$/i.test(origin.trim())) return null;
60
- if (url.search !== "" || url.hash !== "") return null;
61
- if (url.username !== "" || url.password !== "") return null;
62
- return url;
54
+ return parseBareOrigin(origin, PROXY_ORIGIN_PROTOCOLS);
63
55
  }
64
56
  /**
65
57
  * Examine un montage. Rend TOUS les refus, pas le premier : une configuration
@@ -163,6 +155,29 @@ const UPGRADE_WEBSOCKET = /^\s*websocket\s*$/i;
163
155
  /** Le jeton `upgrade` dans une liste `Connection` (RFC 9110 §7.6.1). */
164
156
  const CONNECTION_UPGRADE = /(?:^|,)\s*upgrade\s*(?:,|$)/i;
165
157
  /**
158
+ * Le corps reçu porte-t-il un codage de transfert que le serveur ne sait pas
159
+ * défaire ? Seul `chunked` est compris : Node le retire, mais laisse en place
160
+ * tout codage qui le précède (`gzip, chunked`) — le corps livré serait encore
161
+ * compressé. RFC 9112 §6.1 : un tel message reçoit 501. Un `chunked` qui n'est
162
+ * pas le DERNIER codage est déjà refusé par Node en 400 (§6.3).
163
+ *
164
+ * Seule règle, pour le serveur d'origine et pour le proxy inverse. Chemin de
165
+ * chaque requête : une lecture d'en-tête, rien d'autre sans `Transfer-Encoding`.
166
+ *
167
+ * @param coding - valeur de l'en-tête `Transfer-Encoding`, absente le plus souvent
168
+ * @returns `true` quand le corps ne peut pas être lu tel qu'annoncé
169
+ */
170
+ function isUnsupportedTransferCoding(coding) {
171
+ if (coding === void 0 || coding === "chunked") return false;
172
+ return coding.trim().toLowerCase() !== "chunked";
173
+ }
174
+ /**
175
+ * Raison rendue par {@link websocketHandshakeProblem} quand le client parle une
176
+ * autre version du protocole : la seule qui appelle une réponse distincte —
177
+ * le client doit apprendre vers quelle version se rabattre (RFC 6455 §4.4).
178
+ */
179
+ const WS_UNSUPPORTED_VERSION = "Sec-WebSocket-Version ≠ 13";
180
+ /**
166
181
  * Un upgrade est-il un handshake WebSocket conforme (RFC 6455 §4.2.1) ? Seul
167
182
  * un tel handshake est relayé : un `Upgrade` quelconque (`h2c`…) raccordé
168
183
  * octet pour octet ouvrirait un tunnel que rien n'inspecte.
@@ -180,9 +195,9 @@ function websocketHandshakeProblem(method, headers) {
180
195
  if (typeof upgrade !== "string" || !UPGRADE_WEBSOCKET.test(upgrade)) return "Upgrade autre que websocket";
181
196
  const connection = headers.connection;
182
197
  if (!(typeof connection === "string" ? CONNECTION_UPGRADE.test(connection) : Array.isArray(connection) && connection.some((token) => CONNECTION_UPGRADE.test(token)))) return "Connection sans upgrade";
183
- if (headers["sec-websocket-version"] !== "13") return "Sec-WebSocket-Version ≠ 13";
198
+ if (headers["sec-websocket-version"] !== "13") return WS_UNSUPPORTED_VERSION;
184
199
  const key = headers["sec-websocket-key"];
185
200
  if (typeof key !== "string" || !WS_KEY.test(key)) return "Sec-WebSocket-Key invalide";
186
201
  return null;
187
202
  }
188
- export { MODULE_PROXY_PREFIXES, RESERVED_PROXY_PREFIXES, isAmbiguousPath, normalizePrefix, parseProxyOrigin, proxyMountProblems, websocketHandshakeProblem };
203
+ export { MODULE_PROXY_PREFIXES, RESERVED_PROXY_PREFIXES, WS_UNSUPPORTED_VERSION, isAmbiguousPath, isUnsupportedTransferCoding, normalizePrefix, parseProxyOrigin, proxyMountProblems, websocketHandshakeProblem };
@@ -0,0 +1,76 @@
1
+ import { constants } from "node:http2";
2
+ // nodefony/src/servers/http2Goaway.ts
3
+ /** Plus long délai d'annonce (ms) : un pair qui ne répond pas au PING ne retient pas la fermeture. */
4
+ const ANNOUNCE_CAP_MS = 1e3;
5
+ /** Dernier identifiant de flux possible (RFC 9113 §6.8) : « aucun flux n'est encore écarté ». */
6
+ const LAST_POSSIBLE_STREAM = 2 ** 31 - 1;
7
+ /**
8
+ * Annonce à UNE session HTTP/2 qu'elle va fermer, SANS écarter un flux déjà en route.
9
+ *
10
+ * Un PING mesure l'aller-retour, puis GOAWAY à `2^31-1`, puis l'attente de cet
11
+ * aller-retour : après elle, tout flux ouvert par le client avant l'annonce est
12
+ * arrivé, et le GOAWAY définitif (`session.close()`, dernier flux traité) peut
13
+ * suivre. RFC 9113 §6.8 : « SHOULD send an initial GOAWAY frame with the last
14
+ * stream identifier set to 2^31-1 […] After allowing time for any in-flight
15
+ * stream creation (at least one round-trip time) ». Sans annonce, un flux croisé
16
+ * avec le GOAWAY est refusé alors qu'il aurait pu être servi.
17
+ *
18
+ * L'aller-retour se MESURE avant l'annonce : après un GOAWAY, un client Node ne
19
+ * répond plus aux PING (constaté sur Node 26.13).
20
+ *
21
+ * @param session - la session à annoncer, ni fermée ni détruite
22
+ * @param settle - appelé UNE fois : après l'attente, à la fermeture de la
23
+ * session, ou aussitôt si le PING ne part pas. Un pair muet ne l'appelle
24
+ * jamais — le délai maximal appartient à l'appelant.
25
+ */
26
+ function announceSessionClose(session, settle) {
27
+ let settled = false;
28
+ let wait = null;
29
+ const done = () => {
30
+ if (settled) return;
31
+ settled = true;
32
+ if (wait !== null) clearTimeout(wait);
33
+ session.removeListener("close", done);
34
+ settle();
35
+ };
36
+ session.once("close", done);
37
+ if (!session.ping((error, rttMs) => {
38
+ if (error !== null || settled || session.closed) return done();
39
+ session.goaway(constants.NGHTTP2_NO_ERROR, LAST_POSSIBLE_STREAM);
40
+ wait = setTimeout(done, Math.max(1, Math.ceil(rttMs)));
41
+ })) {
42
+ session.goaway(constants.NGHTTP2_NO_ERROR, LAST_POSSIBLE_STREAM);
43
+ done();
44
+ }
45
+ }
46
+ /**
47
+ * Ferme EN DOUCEUR une session HTTP/2 restée inactive au-delà du délai du serveur.
48
+ *
49
+ * RFC 9113 §9.1 : un serveur est « permitted to terminate idle connections »,
50
+ * et « SHOULD first send a GOAWAY ». La fermeture suit l'annonce en deux temps
51
+ * ({@link announceSessionClose}), puis `session.close()` : aucun nouveau flux,
52
+ * mais les flux en cours vont à leur terme — un flux long et muet (abonnement
53
+ * gRPC) n'est pas coupé, seuls les NOUVEAUX flux repartent sur une autre
54
+ * connexion, ce que tout client HTTP/2 fait sur GOAWAY.
55
+ *
56
+ * Le délai de la session est désarmé d'abord : Node (26.13) marque une session
57
+ * détruite AVANT de désarmer son minuteur, qui la retiendrait alors jusqu'à son
58
+ * échéance (nodejs/node#66885).
59
+ *
60
+ * @param session - la session inactive
61
+ * @param capMs - délai maximal d'attente de l'écho du PING (ms)
62
+ */
63
+ function closeIdleSession(session, capMs = ANNOUNCE_CAP_MS) {
64
+ if (session.closed || session.destroyed) return;
65
+ session.setTimeout(0);
66
+ let finished = false;
67
+ const finish = () => {
68
+ if (finished) return;
69
+ finished = true;
70
+ clearTimeout(timer);
71
+ if (!session.closed && !session.destroyed) session.close();
72
+ };
73
+ const timer = setTimeout(finish, capMs);
74
+ announceSessionClose(session, finish);
75
+ }
76
+ export { ANNOUNCE_CAP_MS, announceSessionClose, closeIdleSession };
@@ -1,3 +1,4 @@
1
+ import { closeIdleSession } from "./http2Goaway.js";
1
2
  // nodefony/src/servers/http2Limits.ts
2
3
  /**
3
4
  * Limites anti-DoS d'un serveur HTTP/2, lues dans la section `http2` du module :
@@ -18,29 +19,54 @@ function http2ServerLimits(limits) {
18
19
  return options;
19
20
  }
20
21
  /**
21
- * Borne l'inactivité des sessions d'un serveur HTTP/2 — TLS (5152) comme h2c —
22
- * et contourne un défaut de Node qui garde chaque session fermée en vie jusqu'à
23
- * l'échéance de ce délai.
22
+ * Une session HTTP/2, et non une socket HTTP/1.1 : sur 5152 (`allowHTTP1`),
23
+ * l'événement `timeout` du serveur reçoit les deux. `node:http2` n'exporte pas
24
+ * la classe de session — elle se reconnaît à sa capacité (`goaway`).
25
+ */
26
+ function isHttp2Session(target) {
27
+ return typeof target === "object" && target !== null && "goaway" in target;
28
+ }
29
+ /**
30
+ * Ferme les sessions d'un serveur HTTP/2 — TLS (5152) comme h2c — restées
31
+ * inactives au-delà de `timeoutMs`, et contourne un défaut de Node qui garde
32
+ * chaque session fermée en vie jusqu'à l'échéance de ce délai.
24
33
  *
25
- * Le défaut (Node 24, 26) : `closeSession` (`internal/http2/core`) marque la
26
- * session détruite PUIS appelle `session.setTimeout(0)` ; or `setStreamTimeout`
27
- * sort aussitôt sur un objet détruit. Le minuteur n'est jamais désarmé et retient
28
- * la session (~2,4 Ko) pendant `timeoutMs` : sous un flux de connexions neuves,
29
- * connexions/s × délai × 2,4 Ko. Signalé à Node : https://github.com/nodejs/node/issues/66885
34
+ * **La fermeture est À NOUS.** Node arme chaque session par
35
+ * `session.setTimeout(server.timeout, sessionOnTimeout)` — un écouteur `once` —
36
+ * et `sessionOnTimeout` ne détruit la session que si le serveur n'a AUCUN
37
+ * écouteur `timeout` ; s'il en a un, il lui passe la main, UNE fois par session
38
+ * (`internal/http2/core`, Node 26.13). Le serveur 5152 en a toujours un
39
+ * (`routeSocketTimeout`, pour ses sockets HTTP/1.1), et ce délai doit prévenir
40
+ * l'application (`onTimeout`) : sans fermeture ici, une session inactive vivait
41
+ * jusqu'à ce que le CLIENT la ferme (#763). À l'échéance, la session est fermée
42
+ * en douceur — GOAWAY annoncé puis définitif, les flux en cours vont à leur
43
+ * terme ({@link closeIdleSession}, RFC 9113 §9.1 et §6.8).
30
44
  *
31
- * Le contournement : désarmer à la réception du GOAWAY, AVANT la destruction —
32
- * c'est ainsi que ferment un proxy et un client Node, même par `destroy()`. Rien
33
- * n'est retiré à la protection : Node n'agit déjà plus à l'échéance sur une
34
- * session fermée (`sessionOnTimeout` sort sur `closed`). Une coupure TCP sans
35
- * GOAWAY garde la rétention, bornée au délai.
45
+ * **Le contournement** (Node 24, 26) : `closeSession` marque la session détruite
46
+ * PUIS appelle `session.setTimeout(0)`, qui sort aussitôt sur un objet détruit ;
47
+ * le minuteur retient alors la session (~2,4 Ko) pendant `timeoutMs`. Il est
48
+ * désarmé à la réception du GOAWAY, AVANT la destruction — c'est ainsi que
49
+ * ferment un proxy et un client Node, même par `destroy()` ; et
50
+ * `closeIdleSession` le désarme avant de fermer. Signalé à Node :
51
+ * https://github.com/nodejs/node/issues/66885 (corrigé par nodejs/node#66886,
52
+ * absent de la 26.13). Une coupure TCP sans GOAWAY garde la rétention, bornée
53
+ * au délai.
54
+ *
55
+ * Coût par requête : nul — un écouteur par serveur, et la fermeture n'agit qu'à
56
+ * l'échéance.
36
57
  *
37
58
  * @param server - le serveur HTTP/2.
38
59
  * @param timeoutMs - délai d'inactivité ; 0 ou absent le laisse désactivé.
39
- * @param onTimeout - appelé à l'échéance (événement `timeout` du serveur).
60
+ * @param onTimeout - appelé à l'échéance d'une session (événement `timeout` du
61
+ * serveur), après le début de sa fermeture.
40
62
  */
41
63
  function setHttp2SessionTimeout(server, timeoutMs, onTimeout) {
42
64
  if (!timeoutMs) return;
43
- server.setTimeout(timeoutMs, onTimeout);
65
+ server.setTimeout(timeoutMs, (target) => {
66
+ if (!isHttp2Session(target)) return;
67
+ closeIdleSession(target);
68
+ onTimeout();
69
+ });
44
70
  server.on("session", (session) => {
45
71
  session.once("goaway", () => {
46
72
  session.setTimeout(0);
@@ -0,0 +1,100 @@
1
+ import http2 from "node:http2";
2
+ // nodefony/src/servers/http2StreamGrace.ts
3
+ /**
4
+ * Délai avant de retenter un `PING` que Node a refusé (plafond de `PING` en vol
5
+ * atteint par d'autres émetteurs de la session) : ceux-là reviennent en un
6
+ * aller-retour, une boucle `setImmediate` brûlerait le processeur pendant ce
7
+ * temps sur un lien lent.
8
+ */
9
+ const PING_RETRY_MS = 10;
10
+ /**
11
+ * Les flux d'une session qui attendent leur grâce. `inFlight` : couverts par le
12
+ * `PING` en vol ; `next` : arrivés après son envoi, donc couverts par le
13
+ * suivant — un `PING` envoyé AVANT leurs en-têtes ne prouve rien pour eux.
14
+ */
15
+ var SessionGrace = class {
16
+ inFlight = null;
17
+ next = null;
18
+ };
19
+ /** Chemin d'erreur seulement : aucune session n'a d'entrée hors refus en cours. */
20
+ const graces = /* @__PURE__ */ new WeakMap();
21
+ /** Coupe le flux en `NO_ERROR` s'il est encore ouvert et que le client émet encore. */
22
+ function decide(stream) {
23
+ if (stream.closed || stream.destroyed || stream.state.remoteClose) return;
24
+ stream.close(http2.constants.NGHTTP2_NO_ERROR);
25
+ }
26
+ function sendPing(session, grace) {
27
+ if (session.destroyed) {
28
+ graces.delete(session);
29
+ return;
30
+ }
31
+ if (session.closed) {
32
+ const pending = grace.inFlight;
33
+ const later = grace.next;
34
+ grace.inFlight = null;
35
+ grace.next = null;
36
+ graces.delete(session);
37
+ setImmediate(() => {
38
+ if (pending !== null) for (const stream of pending) decide(stream);
39
+ if (later !== null) for (const stream of later) decide(stream);
40
+ });
41
+ return;
42
+ }
43
+ if (!session.ping(() => settle(session, grace))) setTimeout(() => sendPing(session, grace), PING_RETRY_MS).unref();
44
+ }
45
+ function settle(session, grace) {
46
+ const batch = grace.inFlight;
47
+ grace.inFlight = grace.next;
48
+ grace.next = null;
49
+ if (batch !== null) for (const stream of batch) decide(stream);
50
+ if (grace.inFlight !== null) sendPing(session, grace);
51
+ else graces.delete(session);
52
+ }
53
+ /**
54
+ * Ferme en `NO_ERROR` un flux HTTP/2 dont la réponse est partie mais dont le
55
+ * client émet encore, après la grâce d'un aller-retour (`PING`, RFC 9113 §6.7).
56
+ *
57
+ * RFC 9113 §8.1 : après une réponse complète, le serveur PEUT demander au
58
+ * client d'arrêter d'émettre par un `RST_STREAM` `NO_ERROR`. La coupe ne doit
59
+ * jamais précéder l'envoi de la réponse : un `RST_STREAM` soumis pendant que
60
+ * les en-têtes attendent encore dans la file de nghttp2 les fait JETER, et le
61
+ * client reçoit une fin de flux sans statut (un client gRPC la lit INTERNAL).
62
+ * Le `PING` part derrière la réponse ; son écho prouve qu'elle est arrivée.
63
+ *
64
+ * **Un seul `PING` par session** couvre tous les flux qui attendent : un flux
65
+ * arrivé pendant qu'un `PING` est en vol attend le suivant. Le plafond de
66
+ * `PING` en vol de Node (10 par session) n'est donc jamais atteint par ce
67
+ * chemin, et aucun flux n'est coupé sans grâce — ce que faisait la coupe
68
+ * immédiate quand `ping()` rendait `false`, au-delà de 10 refus simultanés.
69
+ *
70
+ * Session en fermeture GRACIEUSE (délai d'inactivité, arrêt) : Node n'envoie
71
+ * plus de `PING`, et la session attend la fin de ses flux — un client muet la
72
+ * retiendrait. La coupe part alors au tour de boucle suivant, le temps que les
73
+ * réponses déjà soumises quittent la file de nghttp2.
74
+ *
75
+ * Après la grâce : client qui a fini d'émettre → rien (le flux se ferme seul,
76
+ * sans `RST_STREAM`) ; sinon → coupé. L'écho d'un `PING` passe avant les autres
77
+ * trames (§6.7) : une trame DATA encore en route peut arriver après lui, la
78
+ * coupe reste conforme (§8.1).
79
+ *
80
+ * Coût : chemin d'erreur seulement — une entrée de `WeakMap` par session tant
81
+ * que des flux attendent, un `PING` par aller-retour et par session.
82
+ *
83
+ * @param stream - flux dont la réponse est ÉCRITE (`finish` passé).
84
+ */
85
+ function closeStreamAfterGrace(stream) {
86
+ if (stream.closed || stream.destroyed || stream.state.remoteClose) return;
87
+ const session = stream.session;
88
+ if (session === void 0) return;
89
+ let grace = graces.get(session);
90
+ if (grace === void 0) {
91
+ grace = new SessionGrace();
92
+ graces.set(session, grace);
93
+ }
94
+ if (grace.inFlight === null) {
95
+ grace.inFlight = [stream];
96
+ sendPing(session, grace);
97
+ } else if (grace.next === null) grace.next = [stream];
98
+ else grace.next.push(stream);
99
+ }
100
+ export { closeStreamAfterGrace };
@@ -0,0 +1,177 @@
1
+ // nodefony/src/servers/receiveDeadline.ts
2
+ /** Capacité de départ de l'anneau, et celle où il revient une fois vide. */
3
+ const INITIAL_CAPACITY = 64;
4
+ /**
5
+ * Une échéance ABSOLUE pour des réceptions en cours, sans un minuteur par
6
+ * réception : un seul balayage périodique pour toutes.
7
+ *
8
+ * ## Pourquoi
9
+ *
10
+ * `node:http2` ne borne pas la réception d'une requête : il n'a que des délais
11
+ * d'INACTIVITÉ (`http2stream.setTimeout`, `http2session.setTimeout`), qu'un
12
+ * client tient en vie avec un octet avant chaque échéance. `requestTimeout`,
13
+ * la borne absolue anti « slow-loris », n'existe que pour HTTP/1.1.
14
+ *
15
+ * ## Le coût, et pourquoi cette forme
16
+ *
17
+ * C'est la forme que Node donne lui-même à `requestTimeout` : aucun minuteur
18
+ * par requête, un contrôle périodique (`connectionsCheckingInterval`). Les
19
+ * réceptions suivies vivent dans un ANNEAU — un tableau des éléments et un
20
+ * `Int32Array` des tours d'entrée, indexés par un numéro de séquence :
21
+ * - suivre = deux écritures ; libérer = une comparaison et une écriture. Ni
22
+ * minuteur, ni fermeture, ni lecture d'horloge (le temps se compte en TOURS),
23
+ * ni champ ajouté à l'élément suivi ;
24
+ * - mesuré contre une `Map` (hachage d'identité de la clé) : ~26–32 ns par
25
+ * réception contre ~90–150 ns, de 1 à 10 000 réceptions simultanées.
26
+ *
27
+ * Toutes les entrées ont le même délai : l'ordre d'insertion EST l'ordre
28
+ * d'échéance. Un tour avance la tête et s'arrête à la première entrée vivante
29
+ * non échue — il ne coûte que ce qu'il expire ou saute.
30
+ *
31
+ * Contrepartie, bornée : une case libérée DERRIÈRE une entrée vivante attend
32
+ * que la tête la dépasse, soit au plus un délai de réception — 12 octets par
33
+ * réception commencée dans cette fenêtre. Sous charge, l'anneau garde sa
34
+ * capacité d'un tour à l'autre (jamais rétréci puis recrû) ; il la rend au
35
+ * premier tour sans réception nouvelle. Le minuteur unique est `unref`, et s'arrête au
36
+ * même moment.
37
+ *
38
+ * ## Précision
39
+ *
40
+ * Une entrée expire entre `timeoutMs` et `timeoutMs + periodMs` après son
41
+ * suivi — jamais AVANT le délai configuré.
42
+ *
43
+ * @typeParam T - l'élément suivi (un contexte de requête)
44
+ */
45
+ var ReceiveDeadline = class {
46
+ #items = new Array(INITIAL_CAPACITY).fill(null);
47
+ #ticks = new Int32Array(INITIAL_CAPACITY);
48
+ #mask = 63;
49
+ /** Numéro de séquence de la plus ancienne case non balayée. */
50
+ #head = 0;
51
+ /** Numéro de séquence de la prochaine entrée. */
52
+ #tail = 0;
53
+ #tick = 0;
54
+ #timer = null;
55
+ #onExpire;
56
+ #periodMs;
57
+ /** Tours pleins qu'une entrée doit voir passer avant d'expirer. */
58
+ #ttl;
59
+ /**
60
+ * @param timeoutMs - le délai de réception (> 0)
61
+ * @param onExpire - appelé une fois par entrée échue, déjà retirée
62
+ * @param periodMs - la période du balayage, bornée par le délai (défaut 1 s)
63
+ * @throws RangeError si `timeoutMs` ou `periodMs` n'est pas un entier > 0
64
+ */
65
+ constructor(timeoutMs, onExpire, periodMs = 1e3) {
66
+ if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) throw new RangeError(`délai de réception invalide : ${timeoutMs}`);
67
+ if (!Number.isInteger(periodMs) || periodMs <= 0) throw new RangeError(`période de balayage invalide : ${periodMs}`);
68
+ this.#onExpire = onExpire;
69
+ this.#periodMs = Math.min(periodMs, timeoutMs);
70
+ this.#ttl = Math.ceil(timeoutMs / this.#periodMs);
71
+ }
72
+ /** Cases occupées, libérées ou non, entre la tête et la queue. */
73
+ get size() {
74
+ return this.#tail - this.#head;
75
+ }
76
+ /** Capacité courante de l'anneau. */
77
+ get capacity() {
78
+ return this.#mask + 1;
79
+ }
80
+ /** Vrai tant que le balayage tourne. */
81
+ get running() {
82
+ return this.#timer !== null;
83
+ }
84
+ /**
85
+ * Suit une réception qui commence ; démarre le balayage s'il dormait.
86
+ *
87
+ * @param item - la réception
88
+ * @returns le numéro à rendre à {@link ReceiveDeadline.release}
89
+ */
90
+ track(item) {
91
+ if (this.#tail - this.#head > this.#mask) this.#grow();
92
+ const seq = this.#tail++;
93
+ const index = seq & this.#mask;
94
+ this.#items[index] = item;
95
+ this.#ticks[index] = this.#tick;
96
+ if (this.#timer === null) {
97
+ this.#timer = setInterval(() => this.#sweep(), this.#periodMs);
98
+ this.#timer.unref();
99
+ }
100
+ return seq;
101
+ }
102
+ /**
103
+ * Cesse de suivre une réception terminée (reçue, refusée ou échue). Sans
104
+ * effet si la case a déjà été balayée ou réattribuée : l'identité de
105
+ * l'élément est vérifiée. Le balayage ne s'arrête pas ici mais au tour
106
+ * suivant s'il ne reste rien — jamais un minuteur armé puis désarmé par
107
+ * requête.
108
+ *
109
+ * @param seq - le numéro rendu par {@link ReceiveDeadline.track}
110
+ * @param item - la réception suivie
111
+ */
112
+ release(seq, item) {
113
+ const index = seq & this.#mask;
114
+ if (this.#items[index] === item) this.#items[index] = null;
115
+ }
116
+ /** Arrête le balayage et oublie toutes les réceptions (arrêt du serveur). */
117
+ close() {
118
+ if (this.#timer !== null) {
119
+ clearInterval(this.#timer);
120
+ this.#timer = null;
121
+ }
122
+ this.#reset();
123
+ }
124
+ #sweep() {
125
+ const tick = ++this.#tick;
126
+ const items = this.#items;
127
+ const carried = this.#tail - this.#head;
128
+ while (this.#head < this.#tail) {
129
+ const index = this.#head & this.#mask;
130
+ const item = items[index] ?? null;
131
+ if (item !== null && tick - (this.#ticks[index] ?? 0) <= this.#ttl) break;
132
+ items[index] = null;
133
+ this.#head++;
134
+ if (item !== null) this.#onExpire(item);
135
+ }
136
+ if (this.#head !== this.#tail) return;
137
+ if (carried > 0) {
138
+ this.#head = 0;
139
+ this.#tail = 0;
140
+ return;
141
+ }
142
+ if (this.#timer !== null) {
143
+ clearInterval(this.#timer);
144
+ this.#timer = null;
145
+ }
146
+ this.#reset();
147
+ }
148
+ /**
149
+ * Anneau au repos ou fermé : numéros remis à zéro et capacité de départ
150
+ * rendue. Un numéro encore détenu par un appelant ne désigne plus son
151
+ * élément : `release` le reconnaît à l'identité.
152
+ */
153
+ #reset() {
154
+ this.#head = 0;
155
+ this.#tail = 0;
156
+ if (this.#mask + 1 !== INITIAL_CAPACITY) {
157
+ this.#items = new Array(INITIAL_CAPACITY).fill(null);
158
+ this.#ticks = new Int32Array(INITIAL_CAPACITY);
159
+ this.#mask = 63;
160
+ } else this.#items.fill(null);
161
+ }
162
+ /** Double la capacité, les entrées gardant leur numéro de séquence. */
163
+ #grow() {
164
+ const capacity = (this.#mask + 1) * 2;
165
+ const mask = capacity - 1;
166
+ const items = new Array(capacity).fill(null);
167
+ const ticks = new Int32Array(capacity);
168
+ for (let seq = this.#head; seq < this.#tail; seq++) {
169
+ items[seq & mask] = this.#items[seq & this.#mask] ?? null;
170
+ ticks[seq & mask] = this.#ticks[seq & this.#mask] ?? 0;
171
+ }
172
+ this.#items = items;
173
+ this.#ticks = ticks;
174
+ this.#mask = mask;
175
+ }
176
+ };
177
+ export { ReceiveDeadline };
@@ -1,7 +1,60 @@
1
+ import { refuseMalformedHandshake, refuseUpgrade } from "../proxy/forward.js";
1
2
  import { websocketHandshakeProblem } from "../proxy/rules.js";
2
- import { refuseUpgrade } from "../proxy/forward.js";
3
3
  import { STATUS_CODES } from "node:http";
4
+ import { WebSocketServer } from "ws";
4
5
  // nodefony/src/servers/upgradeDispatch.ts
6
+ /**
7
+ * Sous-protocole choisi par handshake, entre {@link IUpgradeAdmission.selectSubprotocol}
8
+ * et le rappel `handleProtocols` de `ws` — qui ne reçoit que la requête. Une
9
+ * table faible plutôt qu'une variable « en cours » : un `verifyClient`
10
+ * asynchrone sépare les deux instants, et deux handshakes s'y croiseraient.
11
+ * Une entrée n'existe que pour un handshake qui a choisi.
12
+ */
13
+ const chosenSubprotocols = /* @__PURE__ */ new WeakMap();
14
+ /**
15
+ * Fabrique l'option `handleProtocols` du serveur `ws` de Nodefony : le choix
16
+ * retenu par l'admission ({@link IUpgradeAdmission.selectSubprotocol}) s'il y
17
+ * en a un, sinon `fallback` (celle de la configuration), sinon le premier
18
+ * offert — le défaut de `ws` 8, conservé pour tout contrôleur qui ne choisit
19
+ * pas.
20
+ *
21
+ * @param fallback - l'option `handleProtocols` de la configuration, s'il y en a une
22
+ * @returns le rappel à passer à `new WebSocketServer({ handleProtocols })`
23
+ */
24
+ function createSubprotocolHandler(fallback) {
25
+ return (protocols, request) => {
26
+ const chosen = chosenSubprotocols.get(request);
27
+ if (chosen !== void 0) {
28
+ chosenSubprotocols.delete(request);
29
+ return chosen !== false && protocols.has(chosen) ? chosen : false;
30
+ }
31
+ if (fallback !== void 0) return fallback(protocols, request);
32
+ const first = protocols.values().next();
33
+ return first.done === true ? false : first.value;
34
+ };
35
+ }
36
+ /**
37
+ * Construit le serveur `ws` de Nodefony à partir des options de la
38
+ * configuration, en forçant ce que Nodefony gère — seul endroit où ces
39
+ * options sont posées, pour le serveur clair comme pour le sécurisé :
40
+ *
41
+ * - `noServer` : l'upgrade passe d'abord par le proxy inverse, puis par
42
+ * {@link attachUpgradeDispatch} ;
43
+ * - `clientTracking` : requis par `broadcast()` et le heartbeat ;
44
+ * - `handleProtocols` : le sous-protocole CHOISI par le contrôleur
45
+ * ({@link createSubprotocolHandler}), celui de la configuration à défaut.
46
+ *
47
+ * @param options - options `ws` issues de la configuration du serveur WebSocket
48
+ * @returns le serveur, sans écoute propre
49
+ */
50
+ function createNodefonyWebSocketServer(options) {
51
+ return new WebSocketServer({
52
+ ...options,
53
+ noServer: true,
54
+ clientTracking: true,
55
+ handleProtocols: createSubprotocolHandler(options.handleProtocols)
56
+ });
57
+ }
5
58
  /** Défaut sans noyau : tout upgrade conforme est accepté puis émis en `connection`. */
6
59
  function acceptAll(wss) {
7
60
  return (req, admission) => {
@@ -38,6 +91,9 @@ var UpgradeAdmission = class {
38
91
  this.#onPendingError = () => socket.destroy();
39
92
  socket.on("error", this.#onPendingError);
40
93
  }
94
+ selectSubprotocol(protocol) {
95
+ if (!this.#settled) chosenSubprotocols.set(this.req, protocol);
96
+ }
41
97
  #settle() {
42
98
  if (this.#settled) return false;
43
99
  this.#settled = true;
@@ -71,6 +127,7 @@ var UpgradeAdmission = class {
71
127
  }
72
128
  refuse(status, headers) {
73
129
  if (!this.#settle()) return;
130
+ chosenSubprotocols.delete(this.req);
74
131
  refuseUpgrade(this.socket, status, STATUS_CODES[status] ?? "Refused", headers);
75
132
  }
76
133
  };
@@ -114,8 +171,9 @@ function attachUpgradeDispatch(server, wss, relay, refusal, admit = acceptAll(ws
114
171
  refuseUpgrade(socket, status, STATUS_CODES[status] ?? "Refused");
115
172
  return;
116
173
  }
117
- if (websocketHandshakeProblem(req.method, req.headers) !== null) {
118
- refuseUpgrade(socket, 400, STATUS_CODES[400] ?? "Bad Request");
174
+ const problem = websocketHandshakeProblem(req.method, req.headers);
175
+ if (problem !== null) {
176
+ refuseMalformedHandshake(socket, problem);
119
177
  return;
120
178
  }
121
179
  admit(req, new UpgradeAdmission(wss, req, socket, head));
@@ -125,4 +183,4 @@ function attachUpgradeDispatch(server, wss, relay, refusal, admit = acceptAll(ws
125
183
  server.removeListener("upgrade", onUpgrade);
126
184
  };
127
185
  }
128
- export { attachUpgradeDispatch };
186
+ export { attachUpgradeDispatch, createNodefonyWebSocketServer, createSubprotocolHandler };
@@ -68,12 +68,19 @@ declare class Http extends Module<IHttpConfig> {
68
68
  }
69
69
  export default Http;
70
70
  export { defineHttpConfig, httpConfigJsonSchema, } from "./nodefony/config/defineModuleConfig.js";
71
+ export { parseRequestTarget } from "./nodefony/src/context/http/urlFastPath.js";
72
+ export { isContentlessStatus } from "./nodefony/src/context/http/Response.js";
73
+ export type { ISplitTarget } from "./nodefony/src/context/http/urlFastPath.js";
71
74
  export { httpConfigSchema } from "./nodefony/config/config.js";
72
75
  export type { IHttpConfig, IHttpConfigInput, } from "./nodefony/interfaces/IHttpConfig.js";
73
76
  export { decideSend, readBackpressureOptions, type WsBackpressurePolicy, type WsSendDecision, type IWsBackpressureOptions, type IBackpressureSocket, type IBackpressureTarget, } from "./nodefony/src/context/websocket/wsBackpressure.js";
77
+ export { admitFrame, frameRateRejectedTotal, readFrameRateBound, readFrameRateCloseAfter, FRAME_RATE_WINDOW_MS, type IFrameRateWindow, type IWsFrameRateOptions, } from "./nodefony/src/context/websocket/wsFrameRate.js";
74
78
  export { isWebsocketContext } from "./nodefony/src/context/websocket/isWebsocketContext.js";
75
79
  export { REQUEST_TRACE_CHANNEL } from "./nodefony/src/context/requestTrace.js";
80
+ export { rpcCorsLists } from "./nodefony/src/grpc/cors.js";
81
+ export type { IRpcCorsLists } from "./nodefony/src/grpc/cors.js";
76
82
  export type { IRequestTrace, RequestTraceProtocol, } from "./nodefony/interfaces/IRequestTrace.js";
83
+ export type { IRpcCall, IRpcOutcome, RpcSystem, } from "./nodefony/interfaces/IRpcCall.js";
77
84
  export { compileDomainPattern, compileDomainPatterns, compileTrustedHosts, isDomainAllowed, } from "./nodefony/src/context/domainMatcher.js";
78
85
  export type { DomainPattern, ITrustedHostsConfig, } from "./nodefony/src/context/domainMatcher.js";
79
86
  export { MemoryRateLimitStore } from "./nodefony/src/rateLimit/MemoryRateLimitStore.js";
@@ -81,6 +88,8 @@ export type { IRateLimitStore, IRateLimitOptions, RateLimitVerdict, } from "./no
81
88
  export { Context, Session, SessionsService, HttpError, HttpKernel, Response, Http2Response, Response as HttpResponse, wsResponse, wsResponse as WebsocketResponse, Cookie, HttpContext, WebsocketContext, HttpRequest, Http2Request, DefaultErrorRenderer, DefaultRequestLogger, JsonAuditLogger, PrettyRequestLogger, Profiler, };
82
89
  export { SseStream, openSseStream, acceptsEventStream, SSE_HEARTBEAT_MS, } from "./nodefony/src/context/http/SseStream.js";
83
90
  export type { ISseStreamOptions, ISseSendOptions, } from "./nodefony/src/context/http/SseStream.js";
91
+ export { openByteStream } from "./nodefony/src/context/http/ByteStream.js";
92
+ export type { ByteStreamEndReason, ByteStreamSource, IByteStreamOptions, IByteStreamResult, } from "./nodefony/src/context/http/ByteStream.js";
84
93
  export { schemaMismatchOf, type SchemaMismatchConfidence, } from "./nodefony/service/error-renderer.js";
85
94
  export { toValidationFields, type IValidationField, } from "./nodefony/service/error-renderer.js";
86
95
  export type { ProfileEntry, ProfileSummary, ProfilePhase, ProfileQuery, ProfileSecurity, IProfileBatch, IProfilerReader, } from "./nodefony/src/profiler/Profiler.js";
@@ -102,8 +111,8 @@ export type { IHttpResponse, IWebsocketResponse, } from "./nodefony/interfaces/I
102
111
  export type { IHttpKernel } from "./nodefony/interfaces/IHttpKernel.js";
103
112
  export type { IResolvedRoute, IRouteResolver, IRequestRouter, } from "./nodefony/interfaces/IRouting.js";
104
113
  export type { ISecurityZone, IFirewallGate, } from "./nodefony/interfaces/ISecurity.js";
105
- export type { IUploadedFile, IUploadService, IParsedUploadFile, IUploadOptions, } from "./nodefony/interfaces/IUpload.js";
106
- export type { IErrorRenderer, IErrorHttpResult, IErrorWebsocketResult, } from "./nodefony/interfaces/IErrorRenderer.js";
114
+ export type { IUploadedFile, IUploadService, IParsedUploadFile, IUploadOptions, IRouteUploadOptions, } from "./nodefony/interfaces/IUpload.js";
115
+ export type { IErrorRenderer, IErrorHttpResult, IErrorWebsocketResult, IErrorDescription, } from "./nodefony/interfaces/IErrorRenderer.js";
107
116
  export type { IRequestLogger, IRequestLogEntry, } from "./nodefony/interfaces/IRequestLogger.js";
108
117
  export type { AuditLogEntry, AuditErrorEntry, JsonAuditLoggerOptions, } from "./nodefony/service/audit-logger.js";
109
118
  export type { ContextType, httpRequest, httpResponse, ProtocolType, } from "./nodefony/service/http-kernel.js";