@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
@@ -1,5 +1,5 @@
1
1
  import Ws from "ws";
2
- //#region nodefony/service/servers/wsHeartbeat.ts
2
+ // nodefony/service/servers/wsHeartbeat.ts
3
3
  /**
4
4
  * Arme le suivi keep-alive d'UNE connexion : initialise l'horodatage de vie et
5
5
  * attache l'unique listener `pong` qui le rafraîchit.
@@ -76,5 +76,4 @@ const startHeartbeat = (server, options) => {
76
76
  timer.unref();
77
77
  return timer;
78
78
  };
79
- //#endregion
80
79
  export { heartbeatTick, startHeartbeat, trackPong };
@@ -1,14 +1,12 @@
1
1
  import { SESSION_FACETS } from "../../src/session/storage/sessionFilters.js";
2
- import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.js";
3
- import __decorate from "../../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.js";
4
- import http_kernel_default from "../http-kernel.js";
5
- import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateParam.js";
2
+ import __decorate from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorate.js";
3
+ import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorateParam.js";
6
4
  import Session from "../../src/session/session.js";
7
5
  import MemorySessionStorage from "../../src/session/storage/MemorySessionStorage.js";
8
6
  import RevocationGuardStorage from "../../src/session/storage/RevocationGuardStorage.js";
9
- import { AUTO_STORE, EMPTY_INFRA, GcScheduler, Module, Nodefony, RequestContext, Service, countFacets, extend, inject, injectable, readStoreConnector, readStoreLocation, resolveAutoStore, runNeedsExternalServices } from "nodefony";
7
+ import { AUTO_STORE, EMPTY_INFRA, GcScheduler, Inject, Nodefony, RequestContext, Service, countFacets, extend, injectable, readStoreConnector, readStoreLocation, resolveAutoStore, storeConnectivity } from "nodefony";
10
8
  import { createHash, createHmac } from "node:crypto";
11
- //#region nodefony/service/sessions/sessions-service.ts
9
+ // nodefony/service/sessions/sessions-service.ts
12
10
  var _SessionsService;
13
11
  /**
14
12
  * Convertit un timestamp de session (`Date` en mémoire, **string ISO** après
@@ -143,7 +141,7 @@ let SessionsService = class SessionsService extends Service {
143
141
  const configured = storeName;
144
142
  let reason = `store explicitement configuré ("${configured}")`;
145
143
  if (storeName === AUTO_STORE) {
146
- const auto = resolveAutoStore("session", this.kernel?.infra ?? EMPTY_INFRA, _SessionsService.storageHandlers(), "memory", runNeedsExternalServices(this.kernel));
144
+ const auto = resolveAutoStore("session", this.kernel?.infra ?? EMPTY_INFRA, _SessionsService.storageHandlers(), "memory", storeConnectivity(this.kernel));
147
145
  storeName = auto.store;
148
146
  reason = auto.reason;
149
147
  this.log(`session.store "auto" → "${storeName}" (${auto.reason})`, "INFO");
@@ -219,7 +217,7 @@ let SessionsService = class SessionsService extends Service {
219
217
  return resolve(context.session);
220
218
  }
221
219
  }
222
- let inst = null;
220
+ let inst;
223
221
  try {
224
222
  context.sessionStarting = true;
225
223
  inst = this.createSession(this.defaultSessionName);
@@ -598,12 +596,7 @@ let SessionsService = class SessionsService extends Service {
598
596
  this.get("auditService")?.record(event);
599
597
  }
600
598
  };
601
- SessionsService = _SessionsService = __decorate([
602
- injectable(),
603
- __decorateParam(1, inject("HttpKernel")),
604
- __decorateMetadata("design:paramtypes", [typeof Module === "undefined" ? Object : Module, typeof http_kernel_default === "undefined" ? Object : http_kernel_default])
605
- ], SessionsService);
599
+ SessionsService = _SessionsService = __decorate([injectable(), __decorateParam(1, Inject("HttpKernel"))], SessionsService);
606
600
  SessionsService.registerStorage("memory", MemorySessionStorage);
607
601
  var sessions_service_default = SessionsService;
608
- //#endregion
609
602
  export { computeSessionRef, sessions_service_default as default, toSessionSummary };
@@ -1,5 +1,5 @@
1
1
  import { randomFillSync } from "node:crypto";
2
- //#region nodefony/service/trace.ts
2
+ // nodefony/service/trace.ts
3
3
  /**
4
4
  * W3C Trace Context — parse + generate `traceparent` headers (P2.7).
5
5
  *
@@ -69,5 +69,4 @@ function resolveTraceparent(header) {
69
69
  const hex = randomHex(24);
70
70
  return `00-${hex.slice(0, 32)}-${hex.slice(32)}-01`;
71
71
  }
72
- //#endregion
73
72
  export { parseTraceparent, resolveTraceparent };
@@ -1,12 +1,10 @@
1
- import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.js";
2
- import __decorate from "../../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.js";
3
- import http_kernel_default from "../http-kernel.js";
4
- import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateParam.js";
5
- import { Cli, FileClass, Module, Service, inject } from "nodefony";
1
+ import __decorate from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorate.js";
2
+ import __decorateParam from "../../../_virtual/_@oxc-project_runtime@0.155.0/helpers/esm/decorateParam.js";
3
+ import { Cli, FileClass, Inject, Service } from "nodefony";
6
4
  import fs from "node:fs";
7
5
  import fsp from "node:fs/promises";
8
6
  import path from "node:path";
9
- //#region nodefony/service/upload/upload-service.ts
7
+ // nodefony/service/upload/upload-service.ts
10
8
  /** Test d'existence non bloquant (`fsp.access`) — remplace `existsSync`. */
11
9
  const existsAsync = async (p) => {
12
10
  try {
@@ -58,7 +56,7 @@ let upload = class upload extends Service {
58
56
  throw new Error(`Syslog not ready`);
59
57
  }
60
58
  };
61
- upload = __decorate([__decorateParam(1, inject("HttpKernel")), __decorateMetadata("design:paramtypes", [typeof Module === "undefined" ? Object : Module, typeof http_kernel_default === "undefined" ? Object : http_kernel_default])], upload);
59
+ upload = __decorate([__decorateParam(1, Inject("HttpKernel"))], upload);
62
60
  var UploadedFile = class UploadedFile extends FileClass {
63
61
  parsedFile;
64
62
  size;
@@ -168,5 +166,4 @@ var UploadedFile = class UploadedFile extends FileClass {
168
166
  }
169
167
  };
170
168
  var upload_service_default = upload;
171
- //#endregion
172
169
  export { UploadedFile, upload_service_default as default, upload };
@@ -1,6 +1,6 @@
1
1
  import { isIPv4, isIPv6 } from "node:net";
2
2
  import { createHash, sign } from "node:crypto";
3
- //#region nodefony/service/x509.ts
3
+ // nodefony/service/x509.ts
4
4
  /**
5
5
  * Fabrique et lecture de certificats X.509 par `node:crypto` seul — aucune
6
6
  * dépendance tierce.
@@ -320,5 +320,4 @@ async function createSelfSignedCertificate(spec) {
320
320
  const signature = await signAsync(spec.hash, tbs, spec.privateKey);
321
321
  return toPem(sequence(tbs, algorithm, bitString(signature)));
322
322
  }
323
- //#endregion
324
323
  export { SHA1_WITH_RSA_OID, createSelfSignedCertificate, distinguishedNameField, ipAddressBytes, parseSubjectAltName, signatureAlgorithmName, signatureAlgorithmOid };
@@ -1,6 +1,6 @@
1
1
  import { stripTrailingSlashes } from "nodefony";
2
2
  import { join } from "node:path";
3
- //#region nodefony/src/assets/collectAssets.ts
3
+ // nodefony/src/assets/collectAssets.ts
4
4
  /** Normalise un préfixe en segment de chemin (`/_assets/x/` → `_assets/x`). */
5
5
  const prefixToSegment = (prefix) => stripTrailingSlashes(prefix.replace(/^\/+/, ""));
6
6
  /**
@@ -30,5 +30,4 @@ function planAssetPublish(sources, outDir) {
30
30
  }
31
31
  return plan;
32
32
  }
33
- //#endregion
34
33
  export { planAssetPublish };
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- //#region nodefony/src/assets/prebuiltUi.ts
3
+ // nodefony/src/assets/prebuiltUi.ts
4
4
  /**
5
5
  * Résout le mode de livraison de l'UI embarquée d'un module.
6
6
  *
@@ -121,5 +121,4 @@ var PrebuiltUi = class {
121
121
  return this.template.replaceAll("<script", `<script nonce="${nonce}"`);
122
122
  }
123
123
  };
124
- //#endregion
125
124
  export { PrebuiltUi, resolveUiDelivery };
@@ -1,18 +1,14 @@
1
1
  import { buildMetaData } from "./metaData.js";
2
+ import { protocolNameOf } from "./transport.js";
3
+ import { resolveTimingMode } from "./timingMode.js";
2
4
  import Cookie, { cookiesParser } from "../cookies/cookie.js";
3
5
  import { RequestContext, Service, logColor } from "nodefony";
4
6
  import { randomFillSync, randomUUID } from "node:crypto";
5
7
  import { performance } from "node:perf_hooks";
6
8
  import { AsyncResource } from "node:async_hooks";
7
- //#region nodefony/src/context/Context.ts
9
+ // nodefony/src/context/Context.ts
8
10
  const colorLogEvent = () => logColor.cyanBgBlack("EVENT CONTEXT");
9
11
  const PERF_PROBE_SUB = process.env.NF_PERF_PROBE === "1";
10
- const EVENT_SEVERITY = {
11
- onRequest: "INFO",
12
- onSend: "INFO",
13
- onConnect: "INFO",
14
- onClose: "INFO"
15
- };
16
12
  let lifecycleEventLogging = null;
17
13
  /**
18
14
  * Bascule runtime du gate des logs d'events lifecycle. Réservé HORS hot path :
@@ -54,7 +50,15 @@ var Context = class extends Service {
54
50
  requested = false;
55
51
  sessionStarting = false;
56
52
  domain = "";
53
+ /** Famille traitée (`network.protocol.name` d'OpenTelemetry) — union ouverte. */
57
54
  type;
55
+ /** Serveur qui a reçu la requête — choisit la configuration par serveur. */
56
+ serverType;
57
+ /**
58
+ * Protocole du saut local (registre IANA ALPN) — union ouverte. Une connexion
59
+ * WebSocket naît d'un upgrade HTTP/1.1 ; `HttpContext` le constate sur la requête.
60
+ */
61
+ alpnProtocol = "http/1.1";
58
62
  server;
59
63
  httpKernel;
60
64
  request = null;
@@ -103,6 +107,12 @@ var Context = class extends Service {
103
107
  router = this.get("router");
104
108
  resolver = null;
105
109
  /**
110
+ * Module qui signe les lignes de CETTE requête : la ligne d'accès et les
111
+ * lignes du contrôleur. `null` = le module de la route (sans route : le
112
+ * journal du noyau). Un contrôleur le change : `this.context.logModule = "billing"`.
113
+ */
114
+ logModule = null;
115
+ /**
106
116
  * Intent de session de la route courante (posé par le Resolver depuis
107
117
  * `@UseSession` / paramètre `@Session`). Pilote le point d'activation unique
108
118
  * (HTTP + WS). `null` = aucune session sauf cookie existant (reprise L1).
@@ -134,8 +144,20 @@ var Context = class extends Service {
134
144
  profilerQueries = null;
135
145
  profiling = false;
136
146
  securityTrace = null;
147
+ /**
148
+ * Message du canal `nodefony.http.request` pour CETTE requête, ou `null` si
149
+ * personne n'écoutait à son début (le cas par défaut : un champ, aucune
150
+ * allocation). Posé par `openRequestTrace`, remis à `null` par
151
+ * `closeRequestTrace` — c'est ce `null` qui rend la fin idempotente.
152
+ *
153
+ * @internal
154
+ */
155
+ requestTrace = null;
137
156
  _timingEnabled = false;
138
157
  _timingVerbose = false;
158
+ _clockEnabled = false;
159
+ _clockStartMs = 0;
160
+ _durationMs = null;
139
161
  phases = EMPTY_PHASES;
140
162
  _phaseIndex = null;
141
163
  _afterResponseFns = null;
@@ -148,54 +170,48 @@ var Context = class extends Service {
148
170
  };
149
171
  scheme;
150
172
  webSocketState = null;
151
- constructor(container, type) {
152
- super(type, container);
173
+ constructor(container, serverType) {
174
+ super(serverType, container);
153
175
  if (PERF_PROBE_SUB) {
154
176
  const p = globalThis.__nfPerfProbe;
155
177
  if (p && p.t0 !== 0n) p.svcNs += Number(process.hrtime.bigint() - p.t0);
156
178
  }
157
- this.type = type;
179
+ const timing = this.kernel?.options.timing;
180
+ const timingSection = typeof timing === "object" && timing !== null ? timing : null;
181
+ const mode = resolveTimingMode(timingSection !== null && "enabled" in timingSection ? timingSection.enabled : void 0, this.kernel?.environment);
182
+ if (mode !== "off") {
183
+ this._clockEnabled = true;
184
+ this._clockStartMs = performance.now();
185
+ }
186
+ if (mode === "phases") {
187
+ this._timingEnabled = true;
188
+ this.phases = [];
189
+ this._timingVerbose = timingSection !== null && "verbose" in timingSection && timingSection.verbose === true;
190
+ }
191
+ this.serverType = serverType;
192
+ this.type = protocolNameOf(serverType);
158
193
  this.set("context", this);
159
194
  this.httpKernel = this.get("HttpKernel");
160
195
  this.sessionService = this.get("sessions");
161
196
  this.setMetaData();
162
- const explicit = (this.kernel?.options)?.timing?.enabled;
163
- if (typeof explicit === "boolean") this._timingEnabled = explicit;
164
- else this._timingEnabled = this.kernel?.environment !== "production";
165
- if (this._timingEnabled) {
166
- this.phases = [];
167
- const verbose = (this.kernel?.options)?.timing?.verbose;
168
- this._timingVerbose = verbose === true;
169
- }
170
- this.scheme = "https";
171
- switch (this.type) {
197
+ this.scheme = serverType;
198
+ switch (serverType) {
172
199
  case "http": {
173
- this.scheme = "http";
174
200
  const server = this.get("server-http");
175
201
  this.server = server?.server;
176
202
  break;
177
203
  }
178
- case "http2":
179
204
  case "https": {
180
- this.scheme = "https";
181
205
  const server = this.get("server-https");
182
206
  this.server = server?.server;
183
207
  break;
184
208
  }
185
- case "http3": {
186
- this.scheme = "https";
187
- const server = this.get("server-http3");
188
- this.server = server?.server ?? null;
189
- break;
190
- }
191
- case "websocket": {
192
- this.scheme = "ws";
209
+ case "ws": {
193
210
  const server = this.get("server-websocket");
194
211
  this.server = server?.server;
195
212
  break;
196
213
  }
197
- case "websocket-secure": {
198
- this.scheme = "wss";
214
+ case "wss": {
199
215
  const server = this.get("server-websocket-secure");
200
216
  this.server = server?.server;
201
217
  break;
@@ -252,6 +268,24 @@ var Context = class extends Service {
252
268
  p.endMs = performance.now();
253
269
  p.durationMs = p.endMs - p.startMs;
254
270
  }
271
+ /**
272
+ * Arrête l'horloge de la requête et rend sa durée, en millisecondes.
273
+ *
274
+ * La durée court de la construction du contexte au PREMIER appel : le
275
+ * démontage HTTP (`close` de la réponse, dernier octet remis — la mesure
276
+ * d'OpenTelemetry et du `$request_time` de nginx), la ligne d'accès pour un
277
+ * handshake WebSocket. Les appels suivants rendent la même valeur : journal
278
+ * d'accès, profiler et trace verbose lisent UNE durée, jamais trois.
279
+ *
280
+ * Active par défaut dans tous les environnements, production comprise :
281
+ * seul `timing.enabled: false` la coupe (cf `resolveTimingMode`).
282
+ *
283
+ * @returns la durée, ou `null` si `timing.enabled` vaut `false`
284
+ */
285
+ stopClock() {
286
+ if (!this._clockEnabled) return null;
287
+ return this._durationMs ??= performance.now() - this._clockStartMs;
288
+ }
255
289
  onAfterResponse(fn) {
256
290
  const boundFn = AsyncResource.bind(fn);
257
291
  if (this._afterResponseFired) {
@@ -303,10 +337,10 @@ var Context = class extends Service {
303
337
  _abortIfPending(reason) {
304
338
  if (this._abortController && !this._abortController.signal.aborted) this._abortController.abort(new Error(reason ?? "Context aborted"));
305
339
  }
306
- log(pci, severity, msgid, msg) {
307
- if (!msgid) msgid = this.type;
308
- if (this.requestId && RequestContext.getRequestId() === void 0) return RequestContext.run({ requestId: this.requestId }, () => super.log(pci, severity, msgid, msg));
309
- return super.log(pci, severity, msgid, msg);
340
+ log(pci, severity, msgid, msg, moduleName) {
341
+ if (!msgid) msgid = this.serverType;
342
+ if (this.requestId && RequestContext.getRequestId() === void 0) return RequestContext.run({ requestId: this.requestId }, () => super.log(pci, severity, msgid, msg, moduleName));
343
+ return super.log(pci, severity, msgid, msg, moduleName);
310
344
  }
311
345
  /**
312
346
  * Confie au contexte le magasin `RequestContext` de son unité de travail
@@ -334,13 +368,13 @@ var Context = class extends Service {
334
368
  }
335
369
  /**
336
370
  * Log d'un event du cycle de vie — gate boot-time (V2.1) : en production le
337
- * Pdu n'est PAS construit (return avant toute allocation). Hors prod, jalon
338
- * notable → INFO (EVENT_SEVERITY), event technique → DEBUG.
371
+ * Pdu n'est PAS construit (return avant toute allocation). Hors prod : DEBUG,
372
+ * pour le Suivi de requête de Studio — la ligne `req` porte le bilan.
339
373
  */
340
374
  logEvent(event) {
341
375
  lifecycleEventLogging ??= this.kernel?.environment !== "production";
342
376
  if (!lifecycleEventLogging) return;
343
- this.log(`${colorLogEvent()} ${event}`, EVENT_SEVERITY[event] || "DEBUG");
377
+ this.log(`${colorLogEvent()} ${event}`, "DEBUG");
344
378
  }
345
379
  fire(event, ...args) {
346
380
  this.logEvent(event);
@@ -366,7 +400,7 @@ var Context = class extends Service {
366
400
  if (!logger) return;
367
401
  if (logger.shouldSample && !logger.shouldSample(this, err)) return;
368
402
  const entry = logger.renderHttp(this, err);
369
- return this.log(entry.text, entry.severity, entry.msgid);
403
+ return this.log(entry.text, entry.severity, entry.msgid, void 0, this.logModule ?? this.resolver?.route?.module?.name);
370
404
  } catch {
371
405
  return;
372
406
  }
@@ -394,7 +428,9 @@ var Context = class extends Service {
394
428
  } else line += `${p.name}=…`;
395
429
  i++;
396
430
  }
397
- this.log(`TRACE phases [Σ ${total.toFixed(2)}ms] ${line}`, "DEBUG", `${this.type} TIMING`);
431
+ const duration = this.stopClock();
432
+ const head = duration === null ? "" : `total ${duration.toFixed(2)}ms · `;
433
+ this.log(`TRACE phases [${head}Σ ${total.toFixed(2)}ms] ${line}`, "DEBUG", `${this.serverType} TIMING`);
398
434
  }
399
435
  addRequestCookie(cookie) {
400
436
  if (cookie instanceof Cookie) return this.cookies[cookie.name] = cookie;
@@ -453,5 +489,4 @@ var Context = class extends Service {
453
489
  setContextJson(_encoding = "utf-8") {}
454
490
  setContextHtml(_encoding = "utf-8") {}
455
491
  };
456
- //#endregion
457
492
  export { Context as default, nextCspNonce, setLifecycleEventLogging };
@@ -1,5 +1,5 @@
1
1
  import { escapeRegExp } from "nodefony";
2
- //#region nodefony/src/context/domainMatcher.ts
2
+ // nodefony/src/context/domainMatcher.ts
3
3
  /**
4
4
  * Matching de domaine (`Host`) — fonctions PURES, politique UNIQUE partagée par
5
5
  * les deux étages de Nodefony :
@@ -135,5 +135,4 @@ function isDomainAllowed(regAlias, domain) {
135
135
  for (const reg of regAlias) if (reg.test(domain)) return true;
136
136
  return false;
137
137
  }
138
- //#endregion
139
138
  export { compileDomainPattern, compileDomainPatterns, compileTrustedHosts, hostnameOf, isDomainAllowed, resolveTrustedHostNames };
@@ -1,6 +1,6 @@
1
1
  import { extractClientIp, resolveFromRight } from "./trustProxy.js";
2
2
  import { isIPv4 } from "node:net";
3
- //#region nodefony/src/context/forwarded.ts
3
+ // nodefony/src/context/forwarded.ts
4
4
  /**
5
5
  * Découpe une chaîne sur `sep` au **niveau supérieur uniquement**, en respectant
6
6
  * les `quoted-string` RFC 9110 §5.6.4 (`value = token / quoted-string`, RFC 7239 §4) : un
@@ -181,5 +181,4 @@ function resolveForwarded(headers, socketAddress, checker) {
181
181
  function hasForwardingHeaders(headers) {
182
182
  return Boolean(headers.forwarded || headers["x-forwarded-for"] || headers["x-forwarded-proto"] || headers["x-forwarded-scheme"] || headers["x-forwarded-host"]);
183
183
  }
184
- //#endregion
185
184
  export { forwardedNodeIp, hasForwardingHeaders, parseForwarded, resolveForwarded };
@@ -1,5 +1,6 @@
1
1
  import HttpError from "../../errors/httpError.js";
2
2
  import { responseEnded } from "../responseEnded.js";
3
+ import { alpnProtocolOf } from "../transport.js";
3
4
  import Cookie from "../../cookies/cookie.js";
4
5
  import Context from "../Context.js";
5
6
  import { sanitizeRequestId } from "../requestId.js";
@@ -11,7 +12,7 @@ import { describeSessionStoreFailure } from "../../session/sessionStoreFailure.j
11
12
  import { openSseStream } from "./SseStream.js";
12
13
  import { finallyMaybe, isPromise, thenMaybe, typeOf } from "nodefony";
13
14
  import url, { URL } from "node:url";
14
- //#region nodefony/src/context/http/HttpContext.ts
15
+ // nodefony/src/context/http/HttpContext.ts
15
16
  /**
16
17
  * Même URL (chemin, requête, hôte) sous un autre schéma et un autre port.
17
18
  * `port` absent → port par défaut du schéma (omis de l'URL).
@@ -55,15 +56,21 @@ var HttpContext = class extends Context {
55
56
  activeSocket = null;
56
57
  isRedirect = false;
57
58
  sended = false;
59
+ /**
60
+ * Requête relayée par le proxy inverse : la politique de ligne d'accès de
61
+ * son montage. `null` = servie par l'application (cas de toute requête).
62
+ */
63
+ relayAccessLog = null;
58
64
  request;
59
65
  response;
60
66
  uploadService;
61
- constructor(container, request, response, type) {
62
- super(container, type);
67
+ constructor(container, request, response, serverType) {
68
+ super(container, serverType);
69
+ this.alpnProtocol = alpnProtocolOf(request);
63
70
  this.uploadService = this.get("upload");
64
71
  const perfSub = PERF_PROBE_SUB ? globalThis.__nfPerfProbe : void 0;
65
72
  if (perfSub && perfSub.t0 !== 0n) perfSub.uploadNs += Number(process.hrtime.bigint() - perfSub.t0);
66
- if (this.type === "http2") {
73
+ if (this.alpnProtocol === "h2" || this.alpnProtocol === "h2c") {
67
74
  this.request = new Http2Request(request, this);
68
75
  this.response = new Http2Response(response, this);
69
76
  } else {
@@ -124,7 +131,8 @@ var HttpContext = class extends Context {
124
131
  _onTimeout() {
125
132
  if (this.listenerCount("onTimeout")) this.fire("onTimeout", this);
126
133
  this._abortIfPending("Request timeout");
127
- let error = null;
134
+ this.httpKernel?.warnUnsentResponse(this);
135
+ let error;
128
136
  if (this.response.stream) error = new HttpError("Gateway Timeout", 504, this);
129
137
  else error = new HttpError("Request Timeout", 408, this);
130
138
  this.httpKernel?.onError(error, this);
@@ -180,6 +188,21 @@ var HttpContext = class extends Context {
180
188
  }
181
189
  }
182
190
  /**
191
+ * Ligne d'accès — pour une requête RELAYÉE, avec le statut que l'amont a
192
+ * réellement rendu (le relais écrit la réponse brute, pas celle du
193
+ * contexte) ; un montage `accessLog: "errors"` ne l'écrit qu'en échec.
194
+ *
195
+ * @param httpError - l'erreur de la requête, s'il y en a une
196
+ */
197
+ logRequest(httpError) {
198
+ if (this.relayAccessLog !== null) {
199
+ const raw = this.response.response;
200
+ if (raw !== null) this.response.statusCode = raw.statusCode;
201
+ if (this.relayAccessLog === "errors" && this.response.statusCode < 400 && !httpError && !this.error) return;
202
+ }
203
+ return super.logRequest(httpError);
204
+ }
205
+ /**
183
206
  * Nettoie le contexte, et le DÉTACHE de sa socket h1 : la connexion
184
207
  * persistante survit à la requête, et l'entrée de `socketActiveContext`
185
208
  * retiendrait sinon tout le contexte jusqu'à sa fermeture. Seule l'entrée qui
@@ -414,5 +437,4 @@ var HttpContext = class extends Context {
414
437
  this.response.setContentType("html", encoding);
415
438
  }
416
439
  };
417
- //#endregion
418
440
  export { HttpContext as default, switchUrlScheme };
@@ -1,9 +1,9 @@
1
1
  import HttpError from "../../errors/httpError.js";
2
2
  import { hasForwardingHeaders, resolveForwarded } from "../forwarded.js";
3
3
  import { hostnameOf } from "../domainMatcher.js";
4
- import { Parser, ParserJson, ParserQs, ParserXml, acceptParser } from "./parser.js";
4
+ import { Parser, ParserJson, ParserQs, ParserRaw, ParserXml, acceptParser } from "./parser.js";
5
5
  import { isCanonicalAuthority, splitTarget } from "./urlFastPath.js";
6
- import { RequestContext, extend, useConfig } from "nodefony";
6
+ import { RequestContext, useConfig } from "nodefony";
7
7
  import { createHash, randomUUID } from "node:crypto";
8
8
  import { URL } from "node:url";
9
9
  import fs from "node:fs";
@@ -12,7 +12,7 @@ import os from "node:os";
12
12
  import path from "node:path";
13
13
  import QS from "qs";
14
14
  import { Busboy } from "@fastify/busboy";
15
- //#region nodefony/src/context/http/Request.ts
15
+ // nodefony/src/context/http/Request.ts
16
16
  const reg = /(.*)[[][\]]$/u;
17
17
  /**
18
18
  * Supprime un fichier temporaire d'upload sans jamais faire échouer le nettoyage :
@@ -34,6 +34,7 @@ const CHARSET_ALIASES = {
34
34
  "utf-16le": "utf16le"
35
35
  };
36
36
  const QUOTE_TRIM = /^["']|["']$/gu;
37
+ const NO_FILES = Object.freeze([]);
37
38
  const parse = {
38
39
  POST: true,
39
40
  PUT: true,
@@ -104,7 +105,7 @@ var HttpRequest = class {
104
105
  let u = this.#url;
105
106
  if (u === null) {
106
107
  u = this.getUrl(this.sUrl);
107
- u.query = this.queryGet;
108
+ u.query = this.query;
108
109
  this.#url = u;
109
110
  }
110
111
  return u;
@@ -113,24 +114,28 @@ var HttpRequest = class {
113
114
  this.#url = u;
114
115
  }
115
116
  parser = null;
116
- queryPost = {};
117
- queryGet = {};
118
- queryFile = [];
119
- query = {};
120
117
  /**
121
- * Le CORPS de la requête, parsé — nom universel de l'écosystème (Express,
122
- * Fastify, NestJS), alias de {@link queryPost}.
123
- *
124
- * Ne pas confondre avec {@link query}, qui FUSIONNE la query string et le
125
- * corps : `body` ne rend jamais un paramètre d'URL. Le data plane admin
126
- * expose déjà cette sémantique (`IAdminRequest.body`) ; ce getter la rend
127
- * disponible partout.
118
+ * Le CORPS de la requête, parsé (JSON, urlencoded, XML, champs multipart) —
119
+ * nom universel de l'écosystème (Express, Fastify, NestJS) et celui
120
+ * d'`IAdminRequest.body`. Jamais un paramètre d'URL : ceux-là sont dans
121
+ * {@link query}, et les deux ne fusionnent pas.
122
+ */
123
+ body = {};
124
+ /** Fichiers reçus, alloués au PREMIER fichier seulement (cf {@link files}). */
125
+ #uploads = null;
126
+ /**
127
+ * Les fichiers reçus dans un corps multipart, déjà écrits dans le répertoire
128
+ * temporaire — lus par `@UploadedFile()` / `@UploadedFiles()`. Les champs
129
+ * texte du même corps sont dans {@link body}.
128
130
  *
129
- * Getter (aucune allocation) : `queryPost` reste la source unique.
131
+ * Lecture seule : une requête sans upload rend un vide partagé et gelé,
132
+ * aucune allocation.
130
133
  */
131
- get body() {
132
- return this.queryPost;
134
+ get files() {
135
+ return this.#uploads ?? NO_FILES;
133
136
  }
137
+ /** Les paramètres d'URL, seuls — jamais un champ du corps ({@link body}). */
138
+ query = {};
134
139
  queryStringOptions;
135
140
  charset = "utf8";
136
141
  uploadOption = {};
@@ -185,7 +190,6 @@ var HttpRequest = class {
185
190
  if (this.search) query = QS.parse(this.search.slice(1), this.queryStringOptions ?? {});
186
191
  else query = {};
187
192
  if (this.#url !== null) this.#url.query = query;
188
- this.queryGet = query;
189
193
  this.query = query;
190
194
  if (PERF_PROBE_SUB) perfMark("reqQueryNs");
191
195
  this.contentType = this.getContentType(this.request);
@@ -201,11 +205,11 @@ var HttpRequest = class {
201
205
  * remplace le `once("onRequestEnd")` du ctor qui allouait 1 onceWrapper +
202
206
  * 1 closure à CHAQUE requête.
203
207
  *
204
- * Il porte le CORPS (`queryPost`), pas la fusion `query` : un seul sens de
208
+ * Il porte le CORPS (`body`), jamais les paramètres d'URL : un seul sens de
205
209
  * `body` dans tout le framework, celui de l'écosystème et d'`IAdminRequest`.
206
210
  */
207
211
  fireRequestEnd() {
208
- this.request.body = this.queryPost;
212
+ this.request.body = this.body;
209
213
  if (this.context.listenerCount("onRequestEnd") === 0) return false;
210
214
  return this.context.fireAsync("onRequestEnd", this);
211
215
  }
@@ -315,7 +319,7 @@ var HttpRequest = class {
315
319
  await this.fireRequestEnd();
316
320
  return BODY_DONE;
317
321
  }
318
- this.parser = new Parser(this);
322
+ this.parser = new ParserRaw(this);
319
323
  return this.parser;
320
324
  }
321
325
  }
@@ -362,8 +366,7 @@ var HttpRequest = class {
362
366
  return this.parser;
363
367
  }
364
368
  const { fields, files } = await this.streamMultipart(bb);
365
- this.queryPost = fields;
366
- this.query = extend({}, this.query, this.queryPost);
369
+ this.body = fields;
367
370
  for (const pf of files) await this.createFileUpload(pf.field, pf.file);
368
371
  this.context.requestEnded = true;
369
372
  await this.fireRequestEnd();
@@ -478,7 +481,7 @@ var HttpRequest = class {
478
481
  }
479
482
  accepts(Type) {
480
483
  let parts = [];
481
- let subtype = "*";
484
+ let subtype;
482
485
  let type = "*";
483
486
  if (Type) parts = Type.split("/");
484
487
  switch (parts.length) {
@@ -496,7 +499,7 @@ var HttpRequest = class {
496
499
  }
497
500
  async createFileUpload(name, file) {
498
501
  const fileUpload = await this.context.uploadService?.createUploadFile(file, name);
499
- if (fileUpload) this.queryFile.push(fileUpload);
502
+ if (fileUpload) (this.#uploads ??= []).push(fileUpload);
500
503
  return fileUpload;
501
504
  }
502
505
  getMethod() {
@@ -561,7 +564,9 @@ var HttpRequest = class {
561
564
  /**
562
565
  * Scheme effectif côté client : `Forwarded`/`X-Forwarded-*` résolu de façon
563
566
  * canonique (this.forwarded, gated proxy de confiance) ; sinon le transport
564
- * réel. HTTP/2 remplace la lecture du socket par le pseudo-header `:scheme`.
567
+ * réel. Vaut pour HTTP/2 aussi : le pseudo-en-tête `:scheme` vient du client
568
+ * (RFC 9113 §8.3.1) et n'est jamais cru — sur h2c, `:scheme: https` ferait
569
+ * passer une connexion claire pour chiffrée.
565
570
  */
566
571
  resolveScheme(request) {
567
572
  if (this.forwarded?.proto) return this.forwarded.proto;
@@ -589,9 +594,8 @@ var HttpRequest = class {
589
594
  return new URL(sUrl, baseUrl);
590
595
  }
591
596
  log(pci, severity, msgid, msg) {
592
- if (!msgid) msgid = `${this.context.type} REQUEST `;
597
+ if (!msgid) msgid = `${this.context.serverType} REQUEST `;
593
598
  return this.context.log(pci, severity, msgid, msg);
594
599
  }
595
600
  };
596
- //#endregion
597
601
  export { HttpRequest as default };