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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
  2. package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorateParam.js +1 -2
  3. package/dist/index.js +12 -10
  4. package/dist/nodefony/command/assetsPublishCommand.js +5 -3
  5. package/dist/nodefony/command/certificatesCommand.js +5 -3
  6. package/dist/nodefony/command/networkCommand.js +5 -3
  7. package/dist/nodefony/command/proxyGenerateCommand.js +8 -3
  8. package/dist/nodefony/config/config.js +1 -2
  9. package/dist/nodefony/config/defineModuleConfig.js +1 -2
  10. package/dist/nodefony/service/HttpAdminApi.js +2 -2
  11. package/dist/nodefony/service/ProfilerAdminApi.js +1 -2
  12. package/dist/nodefony/service/audit-logger.js +20 -13
  13. package/dist/nodefony/service/certificates.js +97 -13
  14. package/dist/nodefony/service/error-renderer.js +29 -12
  15. package/dist/nodefony/service/http-kernel.js +118 -43
  16. package/dist/nodefony/service/pretty-request-logger.js +5 -4
  17. package/dist/nodefony/service/request-logger.js +24 -16
  18. package/dist/nodefony/service/reverse-proxy.js +3 -3
  19. package/dist/nodefony/service/servers/clientError.js +22 -3
  20. package/dist/nodefony/service/servers/server-h2c.js +126 -0
  21. package/dist/nodefony/service/servers/server-http.js +5 -8
  22. package/dist/nodefony/service/servers/server-https.js +12 -24
  23. package/dist/nodefony/service/servers/server-static.js +3 -3
  24. package/dist/nodefony/service/servers/server-websocket-secure.js +10 -18
  25. package/dist/nodefony/service/servers/server-websocket.js +10 -18
  26. package/dist/nodefony/service/servers/serverShutdown.js +78 -18
  27. package/dist/nodefony/service/servers/wsHeartbeat.js +1 -2
  28. package/dist/nodefony/service/sessions/sessions-service.js +7 -14
  29. package/dist/nodefony/service/trace.js +1 -2
  30. package/dist/nodefony/service/upload/upload-service.js +5 -8
  31. package/dist/nodefony/service/x509.js +1 -2
  32. package/dist/nodefony/src/assets/collectAssets.js +1 -2
  33. package/dist/nodefony/src/assets/prebuiltUi.js +1 -2
  34. package/dist/nodefony/src/context/Context.js +23 -31
  35. package/dist/nodefony/src/context/domainMatcher.js +1 -2
  36. package/dist/nodefony/src/context/forwarded.js +1 -2
  37. package/dist/nodefony/src/context/http/HttpContext.js +7 -6
  38. package/dist/nodefony/src/context/http/Request.js +33 -29
  39. package/dist/nodefony/src/context/http/Response.js +23 -6
  40. package/dist/nodefony/src/context/http/SseStream.js +1 -2
  41. package/dist/nodefony/src/context/http/parser.js +36 -10
  42. package/dist/nodefony/src/context/http/urlFastPath.js +1 -2
  43. package/dist/nodefony/src/context/http2/Request.js +4 -8
  44. package/dist/nodefony/src/context/http2/Response.js +22 -9
  45. package/dist/nodefony/src/context/metaData.js +1 -2
  46. package/dist/nodefony/src/context/requestId.js +1 -2
  47. package/dist/nodefony/src/context/responseEnded.js +1 -2
  48. package/dist/nodefony/src/context/transport.js +45 -0
  49. package/dist/nodefony/src/context/trustProxy.js +1 -2
  50. package/dist/nodefony/src/context/websocket/Response.js +33 -6
  51. package/dist/nodefony/src/context/websocket/WebsocketContext.js +92 -17
  52. package/dist/nodefony/src/context/websocket/isWebsocketContext.js +17 -0
  53. package/dist/nodefony/src/context/websocket/websocketUrl.js +1 -2
  54. package/dist/nodefony/src/context/websocket/wsBackpressure.js +1 -2
  55. package/dist/nodefony/src/context/websocket/wsCloseReason.js +1 -2
  56. package/dist/nodefony/src/context/websocket/wsLogContent.js +1 -2
  57. package/dist/nodefony/src/cookies/cookie.js +3 -7
  58. package/dist/nodefony/src/errors/httpError.js +1 -2
  59. package/dist/nodefony/src/errors/thrownMessage.js +1 -2
  60. package/dist/nodefony/src/profiler/FrameProfile.js +1 -2
  61. package/dist/nodefony/src/profiler/Profiler.js +102 -20
  62. package/dist/nodefony/src/profiler/aggregate.js +422 -0
  63. package/dist/nodefony/src/proxy/forward.js +29 -5
  64. package/dist/nodefony/src/proxy/generateProxyConfig.js +14 -6
  65. package/dist/nodefony/src/proxy/rules.js +12 -6
  66. package/dist/nodefony/src/rateLimit/MemoryRateLimitStore.js +1 -2
  67. package/dist/nodefony/src/rateLimit/WsConnectionCounter.js +1 -2
  68. package/dist/nodefony/src/rateLimit/rateLimitFilters.js +1 -2
  69. package/dist/nodefony/src/servers/http2Limits.js +50 -0
  70. package/dist/nodefony/src/servers/kernelServers.js +12 -3
  71. package/dist/nodefony/src/servers/portBinder.js +41 -10
  72. package/dist/nodefony/src/servers/upgradeDispatch.js +86 -7
  73. package/dist/nodefony/src/session/session.js +2 -3
  74. package/dist/nodefony/src/session/sessionStoreFailure.js +1 -2
  75. package/dist/nodefony/src/session/storage/MemorySessionStorage.js +1 -2
  76. package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +1 -2
  77. package/dist/nodefony/src/session/storage/sessionFilters.js +1 -2
  78. package/dist/nodefony/src/session/storage/sessionSort.js +1 -2
  79. package/dist/types/index.d.ts +7 -3
  80. package/dist/types/nodefony/interfaces/IContext.d.ts +63 -4
  81. package/dist/types/nodefony/interfaces/IRequest.d.ts +9 -4
  82. package/dist/types/nodefony/interfaces/IRouting.d.ts +10 -0
  83. package/dist/types/nodefony/interfaces/index.d.ts +2 -2
  84. package/dist/types/nodefony/service/certificates.d.ts +55 -4
  85. package/dist/types/nodefony/service/error-renderer.d.ts +31 -0
  86. package/dist/types/nodefony/service/http-kernel.d.ts +46 -10
  87. package/dist/types/nodefony/service/servers/clientError.d.ts +16 -0
  88. package/dist/types/nodefony/service/servers/server-h2c.d.ts +51 -0
  89. package/dist/types/nodefony/service/servers/server-http.d.ts +3 -3
  90. package/dist/types/nodefony/service/servers/server-https.d.ts +4 -4
  91. package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +1 -3
  92. package/dist/types/nodefony/service/servers/server-websocket.d.ts +1 -3
  93. package/dist/types/nodefony/service/servers/serverShutdown.d.ts +30 -16
  94. package/dist/types/nodefony/src/context/Context.d.ts +13 -4
  95. package/dist/types/nodefony/src/context/http/HttpContext.d.ts +1 -1
  96. package/dist/types/nodefony/src/context/http/Request.d.ts +20 -15
  97. package/dist/types/nodefony/src/context/http/Response.d.ts +11 -0
  98. package/dist/types/nodefony/src/context/http/parser.d.ts +18 -2
  99. package/dist/types/nodefony/src/context/http2/Request.d.ts +1 -1
  100. package/dist/types/nodefony/src/context/http2/Response.d.ts +15 -5
  101. package/dist/types/nodefony/src/context/transport.d.ts +53 -0
  102. package/dist/types/nodefony/src/context/websocket/Response.d.ts +18 -2
  103. package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +57 -7
  104. package/dist/types/nodefony/src/context/websocket/isWebsocketContext.d.ts +15 -0
  105. package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +13 -12
  106. package/dist/types/nodefony/src/profiler/Profiler.d.ts +68 -15
  107. package/dist/types/nodefony/src/profiler/aggregate.d.ts +215 -0
  108. package/dist/types/nodefony/src/proxy/forward.d.ts +14 -2
  109. package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +16 -0
  110. package/dist/types/nodefony/src/proxy/rules.d.ts +3 -0
  111. package/dist/types/nodefony/src/servers/http2Limits.d.ts +37 -0
  112. package/dist/types/nodefony/src/servers/kernelServers.d.ts +10 -1
  113. package/dist/types/nodefony/src/servers/portBinder.d.ts +29 -5
  114. package/dist/types/nodefony/src/servers/upgradeDispatch.d.ts +33 -3
  115. package/docs/cookies.md +85 -78
  116. package/docs/observabilite.md +80 -80
  117. package/docs/rate-limit.md +83 -83
  118. package/docs/reverse-proxy.md +54 -54
  119. package/docs/servers.md +338 -224
  120. package/docs/session.md +179 -179
  121. package/docs/sse.md +24 -20
  122. package/docs/upload.md +120 -117
  123. package/package.json +8 -9
  124. package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.js +0 -6
@@ -1,4 +1,4 @@
1
- //#region nodefony/src/servers/kernelServers.ts
1
+ // nodefony/src/servers/kernelServers.ts
2
2
  /**
3
3
  * Rétrécit la section `servers` de la configuration du kernel (lue dans le sac
4
4
  * de config non typé du cœur) en {@link ServersConfig}, sans propager `any`.
@@ -13,6 +13,15 @@ function asServersConfig(servers) {
13
13
  return servers !== null && typeof servers === "object" ? servers : void 0;
14
14
  }
15
15
  /**
16
+ * Le serveur h2c démarre-t-il ? Seul un `enabled: true` EXPLICITE l'allume : la
17
+ * section existe toujours (défauts), un port seul ne vaut pas activation.
18
+ *
19
+ * @param entry - `servers.h2c`.
20
+ */
21
+ function isH2cEnabled(entry) {
22
+ return entry?.enabled === true;
23
+ }
24
+ /**
16
25
  * Port DEMANDÉ pour un serveur (`servers.<type>.port`), ou `undefined` si le
17
26
  * serveur est désactivé, absent, ou sans port numérique.
18
27
  *
@@ -22,8 +31,8 @@ function asServersConfig(servers) {
22
31
  function configuredServerPort(servers, type) {
23
32
  const entry = servers?.[type];
24
33
  if (!entry) return void 0;
34
+ if (type === "h2c" && !isH2cEnabled(servers.h2c)) return void 0;
25
35
  const port = entry.port;
26
36
  return typeof port === "number" ? port : void 0;
27
37
  }
28
- //#endregion
29
- export { asServersConfig, configuredServerPort };
38
+ export { asServersConfig, configuredServerPort, isH2cEnabled };
@@ -1,6 +1,7 @@
1
+ import { isH2cEnabled } from "./kernelServers.js";
1
2
  import { isPortListening, resolvePortPolicy } from "nodefony";
2
3
  import cluster from "node:cluster";
3
- //#region nodefony/src/servers/portBinder.ts
4
+ // nodefony/src/servers/portBinder.ts
4
5
  /**
5
6
  * **portBinder** — écoute sur le port désiré, ou sur le prochain port libre.
6
7
  *
@@ -48,27 +49,58 @@ import cluster from "node:cluster";
48
49
  */
49
50
  /** Nombre de ports essayés après le désiré, en `auto`. */
50
51
  const DEFAULT_PORT_RETRY_ATTEMPTS = 20;
52
+ const BOUND_SERVERS = [
53
+ "http",
54
+ "https",
55
+ "h2c"
56
+ ];
51
57
  /** Port configuré d'un serveur, ou `0` (désactivé / non précisé → choix noyau). */
52
- function configuredPort(entry) {
58
+ function configuredPort(servers, which) {
59
+ if (which === "h2c") {
60
+ const entry = servers?.h2c;
61
+ return isH2cEnabled(entry) ? entry?.port ?? 0 : 0;
62
+ }
63
+ const entry = servers?.[which];
53
64
  if (!entry) return 0;
54
65
  return entry.port ?? 0;
55
66
  }
56
67
  /**
68
+ * Serveur actif qui convoite le MÊME port que h2c, ou `null`.
69
+ *
70
+ * Un port partagé avec h2c n'est pas un conflit à contourner : sur ce port, le
71
+ * client HTTP/1.1 et le proxy h2c se marchent dessus, et le repli `auto` ferait
72
+ * glisser l'un des deux là où personne ne l'attend. Le boot le refuse.
73
+ *
74
+ * @param servers - `kernel.options.servers`.
75
+ * @returns `"http"` ou `"https"` en conflit, sinon `null` (h2c inactif, port 0).
76
+ */
77
+ function h2cPortConflict(servers) {
78
+ const port = configuredPort(servers, "h2c");
79
+ if (port === 0) return null;
80
+ if (configuredPort(servers, "http") === port) return "http";
81
+ if (configuredPort(servers, "https") === port) return "https";
82
+ return null;
83
+ }
84
+ /**
57
85
  * Compose le plan de bind d'un serveur depuis la config du kernel — source UNIQUE
58
- * (les deux serveurs l'appellent ; la politique n'est décidée qu'ici).
86
+ * (les serveurs l'appellent ; la politique n'est décidée qu'ici).
59
87
  *
60
88
  * @param which - le serveur qu'on borne.
61
89
  * @param servers - `kernel.options.servers`.
62
90
  * @param environment - `kernel.environment` (arbitre le défaut de la politique).
63
91
  */
64
92
  function buildBindPlan(which, servers, environment) {
65
- const desired = configuredPort(servers?.[which]);
66
- const other = configuredPort(servers?.[which === "http" ? "https" : "http"]);
67
- const policy = resolvePortPolicy(environment, servers?.portPolicy);
93
+ const desired = configuredPort(servers, which);
94
+ const reserved = [];
95
+ for (const other of BOUND_SERVERS) {
96
+ if (other === which) continue;
97
+ const port = configuredPort(servers, other);
98
+ if (port > 0 && port !== desired) reserved.push(port);
99
+ }
68
100
  return {
69
101
  desired,
70
- reserved: other > 0 && other !== desired ? [other] : [],
71
- attempts: policy === "auto" ? servers?.portRetryAttempts ?? 20 : 0
102
+ reserved,
103
+ attempts: resolvePortPolicy(environment, servers?.portPolicy) === "auto" ? servers?.portRetryAttempts ?? 20 : 0
72
104
  };
73
105
  }
74
106
  /** Adresses qui désignent « toutes les interfaces » — rien ne s'y connecte. */
@@ -224,5 +256,4 @@ async function bindWithFallback(server, host, plan, probe = isPortListening, sha
224
256
  candidate = next;
225
257
  }
226
258
  }
227
- //#endregion
228
- export { DEFAULT_PORT_RETRY_ATTEMPTS, bindWithFallback, buildBindPlan, conflictProbeTargets, detectPortConflict, resolvePortPolicy };
259
+ export { DEFAULT_PORT_RETRY_ATTEMPTS, bindWithFallback, buildBindPlan, conflictProbeTargets, detectPortConflict, h2cPortConflict, resolvePortPolicy };
@@ -1,6 +1,79 @@
1
+ import { websocketHandshakeProblem } from "../proxy/rules.js";
1
2
  import { refuseUpgrade } from "../proxy/forward.js";
2
3
  import { STATUS_CODES } from "node:http";
3
- //#region nodefony/src/servers/upgradeDispatch.ts
4
+ // nodefony/src/servers/upgradeDispatch.ts
5
+ /** Défaut sans noyau : tout upgrade conforme est accepté puis émis en `connection`. */
6
+ function acceptAll(wss) {
7
+ return (req, admission) => {
8
+ const accepted = admission.accept();
9
+ if (accepted instanceof Promise) accepted.then((ws) => {
10
+ if (ws !== null) wss.emit("connection", ws, req);
11
+ });
12
+ else if (accepted !== null) wss.emit("connection", accepted, req);
13
+ };
14
+ }
15
+ /**
16
+ * Le verdict d'UN upgrade. Pendant que le noyau décide, le socket n'a plus
17
+ * d'écouteur `error` (`node:http` retire les siens à l'émission d'`upgrade`,
18
+ * `ws` ne pose les siens qu'à `handleUpgrade`) : une coupure du client
19
+ * (`ECONNRESET`) y deviendrait une erreur non gérée qui abat le process. D'où
20
+ * l'écouteur de la fenêtre d'attente, retiré quand le verdict tombe.
21
+ *
22
+ * Une classe, pas un objet de fermetures : une allocation par handshake (plus
23
+ * l'écouteur), méthodes partagées par le prototype.
24
+ */
25
+ var UpgradeAdmission = class {
26
+ wss;
27
+ req;
28
+ socket;
29
+ head;
30
+ #accepted = void 0;
31
+ #settled = false;
32
+ #onPendingError;
33
+ constructor(wss, req, socket, head) {
34
+ this.wss = wss;
35
+ this.req = req;
36
+ this.socket = socket;
37
+ this.head = head;
38
+ this.#onPendingError = () => socket.destroy();
39
+ socket.on("error", this.#onPendingError);
40
+ }
41
+ #settle() {
42
+ if (this.#settled) return false;
43
+ this.#settled = true;
44
+ this.socket.removeListener("error", this.#onPendingError);
45
+ return true;
46
+ }
47
+ accept() {
48
+ if (this.#accepted !== void 0) return this.#accepted;
49
+ if (!this.#settle()) return this.#accepted = null;
50
+ const { socket } = this;
51
+ if (socket.destroyed || !socket.writable) {
52
+ socket.destroy();
53
+ return this.#accepted = null;
54
+ }
55
+ let ws;
56
+ let late = null;
57
+ const onClose = () => late?.(null);
58
+ this.wss.handleUpgrade(this.req, socket, this.head, (accepted) => {
59
+ if (late === null) {
60
+ ws = accepted;
61
+ return;
62
+ }
63
+ socket.removeListener("close", onClose);
64
+ late(accepted);
65
+ });
66
+ if (ws !== void 0) return this.#accepted = ws;
67
+ socket.once("close", onClose);
68
+ return this.#accepted = new Promise((resolve) => {
69
+ late = resolve;
70
+ });
71
+ }
72
+ refuse(status, headers) {
73
+ if (!this.#settle()) return;
74
+ refuseUpgrade(this.socket, status, STATUS_CODES[status] ?? "Refused", headers);
75
+ }
76
+ };
4
77
  /**
5
78
  * Pose l'UNIQUE écouteur `upgrade` d'un serveur : le proxy inverse d'abord,
6
79
  * puis le serveur WebSocket de Nodefony.
@@ -14,7 +87,10 @@ import { STATUS_CODES } from "node:http";
14
87
  *
15
88
  * Entre les deux, `refusal` tranche ce qui doit être refusé AVANT le `101`
16
89
  * (RFC 6455 §4.2.2) : un chemin sans route WebSocket reçoit un statut HTTP,
17
- * jamais une ouverture suivie d'une fermeture.
90
+ * jamais une ouverture suivie d'une fermeture. Puis la FORME du handshake est
91
+ * contrôlée (`websocketHandshakeProblem`, la règle du proxy inverse) : un
92
+ * handshake malformé reçoit `400` sans jamais atteindre le noyau. Enfin
93
+ * `admit` décide — pare-feu, garde de l'action — et seul son verdict ouvre.
18
94
  *
19
95
  * Sans montage, le coût par connexion est une lecture de champ.
20
96
  *
@@ -23,9 +99,11 @@ import { STATUS_CODES } from "node:http";
23
99
  * @param relay - proxy inverse, lu à chaque upgrade (il peut naître après)
24
100
  * @param refusal - statut HTTP qui refuse l'upgrade, `null` pour l'admettre
25
101
  * (cf `HttpKernel.websocketUpgradeRefusal`)
102
+ * @param admit - décide d'un upgrade admis (cf `HttpKernel.onWebsocketUpgrade`) ;
103
+ * par défaut, tout upgrade conforme est accepté et émis en `connection`
26
104
  * @returns la fonction qui retire l'écouteur — à appeler à l'arrêt
27
105
  */
28
- function attachUpgradeDispatch(server, wss, relay, refusal) {
106
+ function attachUpgradeDispatch(server, wss, relay, refusal, admit = acceptAll(wss)) {
29
107
  const onUpgrade = (req, socket, head) => {
30
108
  const proxy = relay();
31
109
  if (proxy !== null && proxy.mounts !== null) {
@@ -36,14 +114,15 @@ function attachUpgradeDispatch(server, wss, relay, refusal) {
36
114
  refuseUpgrade(socket, status, STATUS_CODES[status] ?? "Refused");
37
115
  return;
38
116
  }
39
- wss.handleUpgrade(req, socket, head, (ws) => {
40
- wss.emit("connection", ws, req);
41
- });
117
+ if (websocketHandshakeProblem(req.method, req.headers) !== null) {
118
+ refuseUpgrade(socket, 400, STATUS_CODES[400] ?? "Bad Request");
119
+ return;
120
+ }
121
+ admit(req, new UpgradeAdmission(wss, req, socket, head));
42
122
  };
43
123
  server.on("upgrade", onUpgrade);
44
124
  return () => {
45
125
  server.removeListener("upgrade", onUpgrade);
46
126
  };
47
127
  }
48
- //#endregion
49
128
  export { attachUpgradeDispatch };
@@ -1,7 +1,7 @@
1
1
  import Cookie from "../cookies/cookie.js";
2
2
  import { extend } from "nodefony";
3
3
  import { randomBytes } from "node:crypto";
4
- //#region nodefony/src/session/session.ts
4
+ // nodefony/src/session/session.ts
5
5
  const defaultSessionOptions = {
6
6
  name: "nodefony",
7
7
  strictMode: true,
@@ -238,7 +238,7 @@ var Session = class {
238
238
  }
239
239
  isValidSession(_data, context) {
240
240
  if (this.options.refererCheck) {
241
- let sameHost = false;
241
+ let sameHost;
242
242
  try {
243
243
  sameHost = this.checkSecureReferer(context);
244
244
  } catch {
@@ -410,5 +410,4 @@ var Session = class {
410
410
  }
411
411
  }
412
412
  };
413
- //#endregion
414
413
  export { Session as default };
@@ -1,5 +1,5 @@
1
1
  import { thrownMessage } from "../errors/thrownMessage.js";
2
- //#region nodefony/src/session/sessionStoreFailure.ts
2
+ // nodefony/src/session/sessionStoreFailure.ts
3
3
  /**
4
4
  * Reconnaît une table de session absente, quel que soit le dialecte.
5
5
  *
@@ -26,5 +26,4 @@ function describeSessionStoreFailure(error) {
26
26
  message: `La session n'a pas pu être persistée — la requête est servie en 500 plutôt que laissée sans réponse. Cause : ${cause}.${looksLikeMissingTable(cause) ? " La table de session est probablement absente : appliquer les migrations (`nodefony orm:migrate`) avant de servir, ou choisir un autre stockage (`http.session.store`)." : " Vérifier que le stockage de session déclaré sous `http.session.store` est joignable ; à défaut, appliquer les migrations (`nodefony orm:migrate`)."}`
27
27
  };
28
28
  }
29
- //#endregion
30
29
  export { describeSessionStoreFailure };
@@ -1,6 +1,6 @@
1
1
  import { SESSION_DEFAULT_ORDER, SESSION_SORTABLE_FIELDS } from "./sessionSort.js";
2
2
  import { assertPageQuery, compareByOrder, pickOrder } from "nodefony";
3
- //#region nodefony/src/session/storage/MemorySessionStorage.ts
3
+ // nodefony/src/session/storage/MemorySessionStorage.ts
4
4
  /**
5
5
  * Store de sessions **en mémoire** (Map process) — implémentation de référence
6
6
  * d'{@link ISessionStorage}, pendant `session` des `Memory*Store` de sécurité.
@@ -181,5 +181,4 @@ var MemorySessionStorage = class {
181
181
  return Promise.resolve(users.size);
182
182
  }
183
183
  };
184
- //#endregion
185
184
  export { MemorySessionStorage as default };
@@ -1,4 +1,4 @@
1
- //#region nodefony/src/session/storage/RevocationGuardStorage.ts
1
+ // nodefony/src/session/storage/RevocationGuardStorage.ts
2
2
  /**
3
3
  * Durée de vie (ms) d'une pierre tombale de révocation. Couvre la fenêtre
4
4
  * pendant laquelle une requête « en vol » — qui avait chargé la session AVANT
@@ -148,5 +148,4 @@ var RevocationGuardStorage = class {
148
148
  if (t.size === 0) this.#tombstones = null;
149
149
  }
150
150
  };
151
- //#endregion
152
151
  export { RevocationGuardStorage as default };
@@ -1,4 +1,4 @@
1
- //#region nodefony/src/session/storage/sessionFilters.ts
1
+ // nodefony/src/session/storage/sessionFilters.ts
2
2
  /**
3
3
  * **Le vocabulaire de filtre des sessions**, en noms PUBLICS — celui que la
4
4
  * console d'administration écrit dans l'URL (`?user=alice`).
@@ -79,5 +79,4 @@ const SESSION_FACETS = {
79
79
  * rouvert du même geste.
80
80
  */
81
81
  const SESSION_STATS_FILTERS = { user: "string" };
82
- //#endregion
83
82
  export { SESSION_FACETS, SESSION_FILTERS, SESSION_STATS_FILTERS };
@@ -1,5 +1,5 @@
1
1
  import { renameOrderFields } from "nodefony";
2
- //#region nodefony/src/session/storage/sessionSort.ts
2
+ // nodefony/src/session/storage/sessionSort.ts
3
3
  /**
4
4
  * **Le vocabulaire de tri des sessions**, en noms PUBLICS — ceux qu'un client
5
5
  * écrit dans l'URL (`?order=updatedAt:DESC`), jamais des noms de colonne.
@@ -49,5 +49,4 @@ const SESSION_COLUMN_ALIASES = { id: "session_id" };
49
49
  * `session_id` — {@link SESSION_DEFAULT_ORDER} déjà traduit.
50
50
  */
51
51
  const SESSION_DEFAULT_ORDER_SQL = renameOrderFields(SESSION_DEFAULT_ORDER, SESSION_COLUMN_ALIASES);
52
- //#endregion
53
52
  export { SESSION_COLUMN_ALIASES, SESSION_DEFAULT_ORDER, SESSION_DEFAULT_ORDER_SQL, SESSION_SORTABLE_FIELDS };
@@ -71,6 +71,7 @@ export { defineHttpConfig, httpConfigJsonSchema, } from "./nodefony/config/defin
71
71
  export { httpConfigSchema } from "./nodefony/config/config.js";
72
72
  export type { IHttpConfig, IHttpConfigInput, } from "./nodefony/interfaces/IHttpConfig.js";
73
73
  export { decideSend, readBackpressureOptions, type WsBackpressurePolicy, type WsSendDecision, type IWsBackpressureOptions, type IBackpressureSocket, type IBackpressureTarget, } from "./nodefony/src/context/websocket/wsBackpressure.js";
74
+ export { isWebsocketContext } from "./nodefony/src/context/websocket/isWebsocketContext.js";
74
75
  export { compileDomainPattern, compileDomainPatterns, compileTrustedHosts, isDomainAllowed, } from "./nodefony/src/context/domainMatcher.js";
75
76
  export type { DomainPattern, ITrustedHostsConfig, } from "./nodefony/src/context/domainMatcher.js";
76
77
  export { MemoryRateLimitStore } from "./nodefony/src/rateLimit/MemoryRateLimitStore.js";
@@ -79,7 +80,10 @@ export { Context, Session, SessionsService, HttpError, HttpKernel, Response, Htt
79
80
  export { SseStream, openSseStream, acceptsEventStream, SSE_HEARTBEAT_MS, } from "./nodefony/src/context/http/SseStream.js";
80
81
  export type { ISseStreamOptions, ISseSendOptions, } from "./nodefony/src/context/http/SseStream.js";
81
82
  export { schemaMismatchOf, type SchemaMismatchConfidence, } from "./nodefony/service/error-renderer.js";
82
- export type { ProfileEntry, ProfileSummary, ProfilePhase, ProfileQuery, ProfileSecurity, } from "./nodefony/src/profiler/Profiler.js";
83
+ export { toValidationFields, type IValidationField, } from "./nodefony/service/error-renderer.js";
84
+ export type { ProfileEntry, ProfileSummary, ProfilePhase, ProfileQuery, ProfileSecurity, IProfileBatch, IProfilerReader, } from "./nodefony/src/profiler/Profiler.js";
85
+ export { aggregateProfiles, createProfileAggregator, isFailedProfile, sqlShape, N_PLUS_ONE_MIN_REPEATS, ACTION_SELF_PHASE, NO_ROUTE, TOOLING_URL_PREFIX, } from "./nodefony/src/profiler/aggregate.js";
86
+ export type { IProfileAggregator, IProfileAggregatorOptions, IProfileReport, IRouteStats, IStatusBreakdown, IPhaseTotal, INPlusOne, ISqlShapeStats, IErrorSample, } from "./nodefony/src/profiler/aggregate.js";
83
87
  export { FrameProfile } from "./nodefony/src/profiler/FrameProfile.js";
84
88
  export type { FrameProfileInit, ProfiledArea, ProfiledResolver, } from "./nodefony/src/profiler/FrameProfile.js";
85
89
  export { default as ReverseProxy } from "./nodefony/service/reverse-proxy.js";
@@ -87,11 +91,11 @@ export { parseProxyOrigin, normalizePrefix, proxyMountProblems, } from "./nodefo
87
91
  export type { IProxyMount, IProxyMountOptions, ProxyTarget, } from "./nodefony/interfaces/IReverseProxy.js";
88
92
  export { resolveUiDelivery, PrebuiltUi, } from "./nodefony/src/assets/prebuiltUi.js";
89
93
  export type { UiDeliveryMode, UiDeliveryResolved, IUiDeliveryResolution, IUiDeliveryOptions, IPrebuiltUiOptions, } from "./nodefony/src/assets/prebuiltUi.js";
90
- export type { IContext, IHttpContext, IWebsocketContext, ServerType, SchemeType, WebSocketStateType, CookiesMap, ISecurityTrace, SecurityOutcome, } from "./nodefony/interfaces/IContext.js";
94
+ export type { IContext, IHttpContext, IWebsocketContext, IWebsocketConnectionState, ServerType, ProtocolName, AlpnProtocol, SchemeType, WebSocketStateType, CookiesMap, ISecurityTrace, SecurityOutcome, } from "./nodefony/interfaces/IContext.js";
91
95
  export type { ICookie, ICookieOptions, IWsCookie, SameSiteType, PriorityType, } from "./nodefony/interfaces/ICookie.js";
92
96
  export type { ISession, ISessionStorage, ISerializedSession, ISessionSummary, ISessionRecord, ISessionListFilter, ISessionListQuery, SessionIntent, SessionStatusType, SessionStrategyType, FlashBagType, MetaBagType, } from "./nodefony/interfaces/ISession.js";
93
97
  export { SESSION_SORTABLE_FIELDS, SESSION_DEFAULT_ORDER, SESSION_DEFAULT_ORDER_SQL, SESSION_COLUMN_ALIASES, } from "./nodefony/src/session/storage/sessionSort.js";
94
- export type { IHttpRequest, IHttp2Request, IWsRequest, HTTPMethodType, } from "./nodefony/interfaces/IRequest.js";
98
+ export type { IHttpRequest, RequestBody, IHttp2Request, IWsRequest, HTTPMethodType, } from "./nodefony/interfaces/IRequest.js";
95
99
  export type { IHttpResponse, IWebsocketResponse, } from "./nodefony/interfaces/IResponse.js";
96
100
  export type { IHttpKernel } from "./nodefony/interfaces/IHttpKernel.js";
97
101
  export type { IResolvedRoute, IRouteResolver, IRequestRouter, } from "./nodefony/interfaces/IRouting.js";
@@ -3,7 +3,35 @@ import type { MaybePromise } from "nodefony";
3
3
  import type { ICookie } from "./ICookie.js";
4
4
  import type { ISession } from "./ISession.js";
5
5
  import type { HTTPMethodType } from "./IRequest.js";
6
- export type ServerType = "http" | "https" | "http2" | "http3" | "websocket" | "websocket-secure";
6
+ /**
7
+ * Identité du serveur qui a reçu la requête, nommée par le schéma d'URI qu'il
8
+ * sert en LOCAL (RFC 9110 §4.2, RFC 6455 §3) : elle choisit la section de
9
+ * configuration du serveur (`http`, `https`, `websocket`, `websocketSecure`).
10
+ * Ce n'est PAS un protocole — `https` sert HTTP/1.1 et h2 sur le même port ;
11
+ * le protocole du saut se lit dans {@link AlpnProtocol}.
12
+ */
13
+ export type ServerType = "http" | "https" | "ws" | "wss";
14
+ /**
15
+ * Famille du protocole que le contrôleur traite, nommée comme l'attribut
16
+ * OpenTelemetry `network.protocol.name`.
17
+ *
18
+ * Union OUVERTE : une valeur peut apparaître en version mineure
19
+ * (`"webtransport"` sur HTTP/3) — pas de `switch` exhaustif sans `default`.
20
+ */
21
+ export type ProtocolName = "http" | "websocket";
22
+ /**
23
+ * Protocole du saut qui atteint CE processus, nommé par le registre IANA des
24
+ * identifiants ALPN — la valeur même que Node expose (`TLSSocket.alpnProtocol`,
25
+ * `Http2Session.alpnProtocol`, qui vaut `"h2c"` sur une session en clair).
26
+ *
27
+ * Union OUVERTE : `"h3"` s'ajoutera en version mineure, sans rupture.
28
+ */
29
+ export type AlpnProtocol = "http/1.1" | "h2" | "h2c";
30
+ /**
31
+ * Schéma d'URI vu par le CLIENT (RFC 9110 §4.2, RFC 6455 §3) : derrière un
32
+ * proxy de confiance, c'est le schéma qu'il a annoncé (`Forwarded`), pas
33
+ * celui du saut local. Toute décision de sécurité lit ce champ.
34
+ */
7
35
  export type SchemeType = "http" | "https" | "ws" | "wss";
8
36
  export type WebSocketStateType = "handshake" | "connected" | "closed" | "error" | "message" | null;
9
37
  export type CookiesMap = Record<string, ICookie>;
@@ -67,8 +95,14 @@ export interface IContext {
67
95
  csrfProtect: boolean;
68
96
  csrfExempt: boolean;
69
97
  csrfToken: string | null;
70
- type: ServerType;
98
+ /** Famille traitée : `"http"` ou `"websocket"` (union ouverte). */
99
+ type: ProtocolName;
100
+ /** Schéma vu par le client — seul champ que lit une décision de sécurité. */
71
101
  scheme: SchemeType;
102
+ /** Protocole du saut local : `"http/1.1"`, `"h2"`, `"h2c"` (union ouverte). */
103
+ alpnProtocol: AlpnProtocol;
104
+ /** Serveur qui a reçu la requête — choisit la configuration par serveur. */
105
+ serverType: ServerType;
72
106
  method: HTTPMethodType | null;
73
107
  url: string;
74
108
  domain: string;
@@ -139,13 +173,38 @@ export interface IHttpContext extends IContext {
139
173
  getHost(): string | undefined;
140
174
  getUserAgent(): string | undefined;
141
175
  }
142
- export interface IWebsocketContext extends IContext {
176
+ /**
177
+ * Ce qu'un module attache à une connexion WebSocket pour toute sa durée — interface
178
+ * VIDE par construction, qu'un module AUGMENTE par fusion de déclarations.
179
+ *
180
+ * `@nodefony/http` ignore les protocoles bâtis au-dessus de la socket (le JSON-RPC
181
+ * de `@nodefony/realtime`, par exemple) et ne peut pas les importer. Le module qui
182
+ * en porte un déclare son champ ici : {@link IWebsocketContext} et `WebsocketContext`
183
+ * le reçoivent typé, sans conversion chez le lecteur et sans rien coûter à
184
+ * l'exécution — la lecture reste un accès de champ ordinaire.
185
+ *
186
+ * Chaque champ se déclare OPTIONNEL : il n'existe qu'une fois posé par son module
187
+ * (à la poignée de main, en général), et vaut `undefined` avant, comme sur toute
188
+ * connexion que ce module ne sert pas.
189
+ *
190
+ * @example
191
+ * ```ts
192
+ * declare module "@nodefony/http" {
193
+ * interface IWebsocketConnectionState {
194
+ * myProtocol?: IMyProtocolState;
195
+ * }
196
+ * }
197
+ * ```
198
+ */
199
+ export interface IWebsocketConnectionState {
200
+ }
201
+ export interface IWebsocketContext extends IContext, IWebsocketConnectionState {
143
202
  acceptedProtocol?: string | undefined;
144
203
  connection: unknown;
145
204
  rejected: boolean;
146
205
  port: number | string;
147
206
  wsUrl: URL | null;
148
- queryGet: Record<string, string>;
207
+ query: Record<string, string>;
149
208
  connect(): Promise<unknown>;
150
209
  handle(data?: unknown[]): Promise<object>;
151
210
  close(reasonCode: number, description: string): unknown;
@@ -8,12 +8,18 @@ export interface IRequest {
8
8
  getHeader(name: string): string | string[] | undefined;
9
9
  getHeaders(): Record<string, string | string[] | undefined>;
10
10
  }
11
+ /**
12
+ * Le corps DÉCODÉ d'une requête : un objet (JSON, formulaire, champs
13
+ * multipart, XML), une chaîne (`text/*`), ou des octets (`Uint8Array` — tout
14
+ * autre type, et un corps sans `Content-Type`). Le même type d'octets que la
15
+ * socket temps réel : un schéma de route valide l'un comme l'autre.
16
+ */
17
+ export type RequestBody = Record<string, unknown> | string | Uint8Array;
11
18
  export interface IHttpRequest extends IRequest {
12
19
  url: URL;
13
20
  query: Record<string, unknown>;
14
- queryGet: Record<string, unknown>;
15
- queryPost: Record<string, unknown>;
16
- queryFile: unknown[];
21
+ body: RequestBody;
22
+ files: readonly unknown[];
17
23
  remoteAddress: string | null | undefined;
18
24
  acceptHtml: boolean;
19
25
  getRemoteAddress(): string | null | undefined;
@@ -29,7 +35,6 @@ export interface IHttp2Request extends IHttpRequest {
29
35
  export interface IWsRequest extends IRequest {
30
36
  url: URL;
31
37
  query: Record<string, string>;
32
- queryGet: Record<string, string>;
33
38
  path: string;
34
39
  cookies?: Record<string, ICookie>;
35
40
  }
@@ -60,6 +60,16 @@ export interface IRouteResolver {
60
60
  * n'attendent, sinon sa promesse
61
61
  */
62
62
  callController(data?: unknown[], reload?: boolean): MaybePromise<unknown>;
63
+ /**
64
+ * Juge les gardes de l'action résolue (`@IsGranted`, `@RequireScope`,
65
+ * step-up, rôle de la zone) SANS l'exécuter — le pipeline WebSocket l'appelle
66
+ * avant d'accepter le handshake, pour qu'un refus parte en statut HTTP. Un
67
+ * verdict favorable vaut pour la PROCHAINE exécution, qui ne le rejuge pas.
68
+ *
69
+ * @returns `undefined` quand l'action n'est pas gardée ; sinon la promesse du
70
+ * verdict, rompue par un refus (401/403)
71
+ */
72
+ authorizeAction(): Promise<void> | undefined;
63
73
  }
64
74
  /**
65
75
  * Ce que le pipeline HTTP/WS attend du service `router` — implémenté par
@@ -1,9 +1,9 @@
1
1
  export type { ICookie, ICookieOptions, IWsCookie, SameSiteType, PriorityType, } from "./ICookie.js";
2
2
  export type { ISession, ISessionStorage, ISerializedSession, SessionIntent, SessionStatusType, SessionStrategyType, FlashBagType, MetaBagType, } from "./ISession.js";
3
3
  export type { IUploadedFile, IUploadService, IParsedUploadFile, IUploadOptions, } from "./IUpload.js";
4
- export type { IRequest, IHttpRequest, IHttp2Request, IWsRequest, HTTPMethodType, } from "./IRequest.js";
4
+ export type { IRequest, IHttpRequest, RequestBody, IHttp2Request, IWsRequest, HTTPMethodType, } from "./IRequest.js";
5
5
  export type { IResponse, IHttpResponse, IWebsocketResponse } from "./IResponse.js";
6
- export type { IContext, IHttpContext, IWebsocketContext, ServerType, SchemeType, WebSocketStateType, CookiesMap, } from "./IContext.js";
6
+ export type { IContext, IHttpContext, IWebsocketContext, ServerType, ProtocolName, AlpnProtocol, SchemeType, WebSocketStateType, CookiesMap, } from "./IContext.js";
7
7
  export type { IHttpKernel } from "./IHttpKernel.js";
8
8
  export type { IResolvedRoute, IRouteResolver, IRequestRouter, } from "./IRouting.js";
9
9
  export type { ISecurityZone, IFirewallGate } from "./ISecurity.js";
@@ -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
  *