@nodefony/http 10.0.0-alpha.1

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 (155) hide show
  1. package/LICENSE +544 -0
  2. package/README.md +77 -0
  3. package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js +9 -0
  4. package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js +6 -0
  5. package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateParam.js +8 -0
  6. package/dist/index.js +108 -0
  7. package/dist/nodefony/command/assetsPublishCommand.js +102 -0
  8. package/dist/nodefony/command/certificatesCommand.js +47 -0
  9. package/dist/nodefony/command/networkCommand.js +27 -0
  10. package/dist/nodefony/command/proxyGenerateCommand.js +66 -0
  11. package/dist/nodefony/config/config.js +335 -0
  12. package/dist/nodefony/config/defineModuleConfig.js +93 -0
  13. package/dist/nodefony/interfaces/IContext.js +1 -0
  14. package/dist/nodefony/interfaces/ICookie.js +1 -0
  15. package/dist/nodefony/interfaces/IErrorRenderer.js +1 -0
  16. package/dist/nodefony/interfaces/IHttpConfig.js +1 -0
  17. package/dist/nodefony/interfaces/IHttpKernel.js +1 -0
  18. package/dist/nodefony/interfaces/IRequest.js +1 -0
  19. package/dist/nodefony/interfaces/IRequestLogger.js +1 -0
  20. package/dist/nodefony/interfaces/IResponse.js +1 -0
  21. package/dist/nodefony/interfaces/ISession.js +1 -0
  22. package/dist/nodefony/interfaces/IUpload.js +1 -0
  23. package/dist/nodefony/interfaces/index.js +1 -0
  24. package/dist/nodefony/service/HttpAdminApi.js +376 -0
  25. package/dist/nodefony/service/ProfilerAdminApi.js +73 -0
  26. package/dist/nodefony/service/audit-logger.js +159 -0
  27. package/dist/nodefony/service/certificates.js +545 -0
  28. package/dist/nodefony/service/error-renderer.js +320 -0
  29. package/dist/nodefony/service/http-kernel.js +948 -0
  30. package/dist/nodefony/service/pretty-request-logger.js +72 -0
  31. package/dist/nodefony/service/request-logger.js +54 -0
  32. package/dist/nodefony/service/servers/clientError.js +20 -0
  33. package/dist/nodefony/service/servers/server-http.js +135 -0
  34. package/dist/nodefony/service/servers/server-https.js +204 -0
  35. package/dist/nodefony/service/servers/server-static.js +192 -0
  36. package/dist/nodefony/service/servers/server-websocket-secure.js +104 -0
  37. package/dist/nodefony/service/servers/server-websocket.js +104 -0
  38. package/dist/nodefony/service/servers/serverShutdown.js +31 -0
  39. package/dist/nodefony/service/servers/wsHeartbeat.js +64 -0
  40. package/dist/nodefony/service/sessions/sessions-service.js +580 -0
  41. package/dist/nodefony/service/trace.js +72 -0
  42. package/dist/nodefony/service/upload/upload-service.js +171 -0
  43. package/dist/nodefony/src/assets/collectAssets.js +34 -0
  44. package/dist/nodefony/src/assets/prebuiltUi.js +125 -0
  45. package/dist/nodefony/src/context/Context.js +415 -0
  46. package/dist/nodefony/src/context/domainMatcher.js +88 -0
  47. package/dist/nodefony/src/context/forwarded.js +185 -0
  48. package/dist/nodefony/src/context/http/HttpContext.js +309 -0
  49. package/dist/nodefony/src/context/http/Request.js +543 -0
  50. package/dist/nodefony/src/context/http/Response.js +368 -0
  51. package/dist/nodefony/src/context/http/parser.js +188 -0
  52. package/dist/nodefony/src/context/http/urlFastPath.js +103 -0
  53. package/dist/nodefony/src/context/http2/Request.js +29 -0
  54. package/dist/nodefony/src/context/http2/Response.js +97 -0
  55. package/dist/nodefony/src/context/metaData.js +47 -0
  56. package/dist/nodefony/src/context/requestId.js +41 -0
  57. package/dist/nodefony/src/context/trustProxy.js +167 -0
  58. package/dist/nodefony/src/context/websocket/Response.js +181 -0
  59. package/dist/nodefony/src/context/websocket/WebsocketContext.js +389 -0
  60. package/dist/nodefony/src/context/websocket/wsBackpressure.js +56 -0
  61. package/dist/nodefony/src/context/websocket/wsLogContent.js +68 -0
  62. package/dist/nodefony/src/cookies/cookie.js +258 -0
  63. package/dist/nodefony/src/errors/httpError.js +69 -0
  64. package/dist/nodefony/src/profiler/FrameProfile.js +95 -0
  65. package/dist/nodefony/src/profiler/Profiler.js +139 -0
  66. package/dist/nodefony/src/proxy/generateProxyConfig.js +157 -0
  67. package/dist/nodefony/src/rateLimit/IRateLimitStore.js +1 -0
  68. package/dist/nodefony/src/rateLimit/MemoryRateLimitStore.js +146 -0
  69. package/dist/nodefony/src/rateLimit/WsConnectionCounter.js +64 -0
  70. package/dist/nodefony/src/rateLimit/rateLimitFilters.js +20 -0
  71. package/dist/nodefony/src/servers/portBinder.js +114 -0
  72. package/dist/nodefony/src/session/session.js +390 -0
  73. package/dist/nodefony/src/session/storage/MemorySessionStorage.js +185 -0
  74. package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +137 -0
  75. package/dist/nodefony/src/session/storage/sessionFilters.js +83 -0
  76. package/dist/nodefony/src/session/storage/sessionSort.js +53 -0
  77. package/dist/types/index.d.ts +83 -0
  78. package/dist/types/nodefony/command/assetsPublishCommand.d.ts +23 -0
  79. package/dist/types/nodefony/command/certificatesCommand.d.ts +17 -0
  80. package/dist/types/nodefony/command/networkCommand.d.ts +8 -0
  81. package/dist/types/nodefony/command/proxyGenerateCommand.d.ts +19 -0
  82. package/dist/types/nodefony/config/config.d.ts +197 -0
  83. package/dist/types/nodefony/config/defineModuleConfig.d.ts +39 -0
  84. package/dist/types/nodefony/interfaces/IContext.d.ts +138 -0
  85. package/dist/types/nodefony/interfaces/ICookie.d.ts +47 -0
  86. package/dist/types/nodefony/interfaces/IErrorRenderer.d.ts +55 -0
  87. package/dist/types/nodefony/interfaces/IHttpConfig.d.ts +12 -0
  88. package/dist/types/nodefony/interfaces/IHttpKernel.d.ts +10 -0
  89. package/dist/types/nodefony/interfaces/IRequest.d.ts +35 -0
  90. package/dist/types/nodefony/interfaces/IRequestLogger.d.ts +31 -0
  91. package/dist/types/nodefony/interfaces/IResponse.d.ts +39 -0
  92. package/dist/types/nodefony/interfaces/ISession.d.ts +283 -0
  93. package/dist/types/nodefony/interfaces/IUpload.d.ts +66 -0
  94. package/dist/types/nodefony/interfaces/index.d.ts +7 -0
  95. package/dist/types/nodefony/service/HttpAdminApi.d.ts +18 -0
  96. package/dist/types/nodefony/service/ProfilerAdminApi.d.ts +23 -0
  97. package/dist/types/nodefony/service/audit-logger.d.ts +143 -0
  98. package/dist/types/nodefony/service/certificates.d.ts +246 -0
  99. package/dist/types/nodefony/service/error-renderer.d.ts +74 -0
  100. package/dist/types/nodefony/service/http-kernel.d.ts +377 -0
  101. package/dist/types/nodefony/service/pretty-request-logger.d.ts +25 -0
  102. package/dist/types/nodefony/service/request-logger.d.ts +18 -0
  103. package/dist/types/nodefony/service/servers/clientError.d.ts +14 -0
  104. package/dist/types/nodefony/service/servers/server-http.d.ts +42 -0
  105. package/dist/types/nodefony/service/servers/server-https.d.ts +41 -0
  106. package/dist/types/nodefony/service/servers/server-static.d.ts +62 -0
  107. package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +29 -0
  108. package/dist/types/nodefony/service/servers/server-websocket.d.ts +29 -0
  109. package/dist/types/nodefony/service/servers/serverShutdown.d.ts +27 -0
  110. package/dist/types/nodefony/service/servers/wsHeartbeat.d.ts +46 -0
  111. package/dist/types/nodefony/service/sessions/sessions-service.d.ts +218 -0
  112. package/dist/types/nodefony/service/trace.d.ts +39 -0
  113. package/dist/types/nodefony/service/upload/upload-service.d.ts +61 -0
  114. package/dist/types/nodefony/src/assets/collectAssets.d.ts +35 -0
  115. package/dist/types/nodefony/src/assets/prebuiltUi.d.ts +99 -0
  116. package/dist/types/nodefony/src/context/Context.d.ts +195 -0
  117. package/dist/types/nodefony/src/context/domainMatcher.d.ts +67 -0
  118. package/dist/types/nodefony/src/context/forwarded.d.ts +95 -0
  119. package/dist/types/nodefony/src/context/http/HttpContext.d.ts +85 -0
  120. package/dist/types/nodefony/src/context/http/Request.d.ts +203 -0
  121. package/dist/types/nodefony/src/context/http/Response.d.ts +68 -0
  122. package/dist/types/nodefony/src/context/http/parser.d.ts +65 -0
  123. package/dist/types/nodefony/src/context/http/urlFastPath.d.ts +52 -0
  124. package/dist/types/nodefony/src/context/http2/Request.d.ts +14 -0
  125. package/dist/types/nodefony/src/context/http2/Response.d.ts +20 -0
  126. package/dist/types/nodefony/src/context/metaData.d.ts +58 -0
  127. package/dist/types/nodefony/src/context/requestId.d.ts +28 -0
  128. package/dist/types/nodefony/src/context/trustProxy.d.ts +77 -0
  129. package/dist/types/nodefony/src/context/websocket/Response.d.ts +53 -0
  130. package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +125 -0
  131. package/dist/types/nodefony/src/context/websocket/wsBackpressure.d.ts +73 -0
  132. package/dist/types/nodefony/src/context/websocket/wsLogContent.d.ts +37 -0
  133. package/dist/types/nodefony/src/cookies/cookie.d.ts +88 -0
  134. package/dist/types/nodefony/src/errors/httpError.d.ts +15 -0
  135. package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +110 -0
  136. package/dist/types/nodefony/src/profiler/Profiler.d.ts +192 -0
  137. package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +76 -0
  138. package/dist/types/nodefony/src/rateLimit/IRateLimitStore.d.ts +98 -0
  139. package/dist/types/nodefony/src/rateLimit/MemoryRateLimitStore.d.ts +40 -0
  140. package/dist/types/nodefony/src/rateLimit/WsConnectionCounter.d.ts +37 -0
  141. package/dist/types/nodefony/src/rateLimit/rateLimitFilters.d.ts +18 -0
  142. package/dist/types/nodefony/src/servers/portBinder.d.ts +102 -0
  143. package/dist/types/nodefony/src/session/session.d.ts +171 -0
  144. package/dist/types/nodefony/src/session/storage/MemorySessionStorage.d.ts +77 -0
  145. package/dist/types/nodefony/src/session/storage/RevocationGuardStorage.d.ts +81 -0
  146. package/dist/types/nodefony/src/session/storage/sessionFilters.d.ts +102 -0
  147. package/dist/types/nodefony/src/session/storage/sessionSort.d.ts +45 -0
  148. package/docs/cookies.md +365 -0
  149. package/docs/index.md +163 -0
  150. package/docs/observabilite.md +460 -0
  151. package/docs/rate-limit.md +372 -0
  152. package/docs/servers.md +935 -0
  153. package/docs/session.md +768 -0
  154. package/docs/upload.md +460 -0
  155. package/package.json +101 -0
@@ -0,0 +1,377 @@
1
+ import { Service, Module, Scope, EnvironmentType, DebugType, nodefonyError } from "nodefony";
2
+ import type { Resolver, Router } from "@nodefony/framework";
3
+ import type { Controller } from "@nodefony/framework";
4
+ import HttpError from "../src/errors/httpError.js";
5
+ import { type TrustProxyChecker } from "../src/context/trustProxy.js";
6
+ import type { IRateLimitStore } from "../src/rateLimit/IRateLimitStore.js";
7
+ import { type TrustedHostsConfig } from "../src/context/domainMatcher.js";
8
+ import http from "node:http";
9
+ import http2 from "node:http2";
10
+ import type { IncomingMessage } from "node:http";
11
+ import Ws from "ws";
12
+ import httpServer from "../service/servers/server-http.js";
13
+ import httpsServer from "../service/servers/server-https.js";
14
+ import websocketServer from "../service/servers/server-websocket.js";
15
+ import websocketSecureServer from "../service/servers/server-websocket-secure.js";
16
+ import Statics from "./servers/server-static.js";
17
+ import WebsocketContext from "../src/context/websocket/WebsocketContext.js";
18
+ import HttpContext from "../src/context/http/HttpContext.js";
19
+ import Context, { HTTPMethod, WebSocketState } from "../src/context/Context.js";
20
+ import Certicates from "./certificates.js";
21
+ import SessionsService from "./sessions/sessions-service.js";
22
+ import Session from "../src/session/session.js";
23
+ import { Firewall } from "@nodefony/security";
24
+ import type { IErrorRenderer } from "../interfaces/IErrorRenderer.js";
25
+ import type { IRequestLogger } from "../interfaces/IRequestLogger.js";
26
+ export type ProtocolType = "1.1" | "2.0" | "3.0";
27
+ export type httpRequest = http.IncomingMessage | http2.Http2ServerRequest;
28
+ export type httpResponse = http.ServerResponse | http2.Http2ServerResponse;
29
+ export type ContextType = WebsocketContext | HttpContext | Context;
30
+ export type ServerType = "http" | "https" | "http2" | "http3" | "websocket" | "websocket-secure";
31
+ export type responseTimeoutType = "http" | "https" | "http2" | "http3";
32
+ export type SchemeType = "http" | "https" | "ws" | "wss";
33
+ export interface WsMetaData {
34
+ type?: "message" | "handshake";
35
+ state?: WebSocketState;
36
+ messageType?: "utf8" | "binary";
37
+ protocol?: string | null;
38
+ id?: string;
39
+ }
40
+ /**
41
+ * Vue minimale d'une route exposée dans l'enveloppe `nodefony.route` (frame WS
42
+ * + réponse JSON). Volontairement découplée de la classe `Route` de
43
+ * `@nodefony/framework` : on ne diffuse PAS l'instance partagée (statique) —
44
+ * seulement un snapshot per-requête. Casse aussi l'import valeur runtime
45
+ * `http → framework` (cycle).
46
+ */
47
+ export interface RouteMetaData {
48
+ name: string;
49
+ path?: string;
50
+ variablesMap: Record<string, unknown>;
51
+ }
52
+ export interface MetaData {
53
+ name?: string;
54
+ version?: string;
55
+ url?: URL | string;
56
+ environment?: EnvironmentType;
57
+ debug?: DebugType;
58
+ token?: string;
59
+ method?: HTTPMethod;
60
+ scheme?: SchemeType;
61
+ requestId?: string;
62
+ websocket?: WsMetaData;
63
+ route?: RouteMetaData;
64
+ }
65
+ export interface Data {
66
+ error?: Error;
67
+ nodefony: MetaData;
68
+ message?: unknown;
69
+ code?: number;
70
+ result: unknown;
71
+ }
72
+ import type { IHttpKernel as IHttpKernelInterface } from "../interfaces/IHttpKernel.js";
73
+ declare class HttpKernel extends Service implements IHttpKernelInterface {
74
+ #private;
75
+ certificates: unknown;
76
+ serviceCerticats: Certicates | null;
77
+ key: string;
78
+ cert: string;
79
+ ca: string;
80
+ serverStatic: Statics | null;
81
+ domain: string;
82
+ trustedHosts?: TrustedHostsConfig;
83
+ domainCheck: boolean;
84
+ regAlias: RegExp[];
85
+ module: Module;
86
+ httpsPort?: number;
87
+ httpPort?: number;
88
+ responseTimeout: {
89
+ http: number;
90
+ https: number;
91
+ http2: number;
92
+ http3: number;
93
+ };
94
+ closeTimeOutWs: {
95
+ ws: number;
96
+ wss: number;
97
+ };
98
+ private secContentTypeOptions;
99
+ private secFrameOptions;
100
+ private secHsts;
101
+ sessionService?: SessionsService | null;
102
+ router?: Router | null;
103
+ firewall?: Firewall | null;
104
+ private errorRenderer;
105
+ private requestLogger;
106
+ private profiler;
107
+ private _trustProxyChecker;
108
+ private _wsOriginPolicy;
109
+ private rateLimiter;
110
+ private rateLimitGc;
111
+ constructor(module: Module);
112
+ /**
113
+ * (Re)calcule les caches dérivés des en-têtes de sécurité depuis `this.options`
114
+ * — au boot ET à chaque édition live ({@link onConfigChanged}). Lus tels quels
115
+ * dans `onHttpRequest` (0 alloc/concat par requête).
116
+ */
117
+ private computeSecurityHeaderCaches;
118
+ /**
119
+ * Ce que ce kernel ÉMET réellement comme en-têtes de sécurité transport.
120
+ *
121
+ * Ces trois en-têtes (HSTS, X-Frame-Options, X-Content-Type-Options) sont posés
122
+ * ici, à l'entrée brute, pour couvrir aussi les statiques et les pages d'erreur
123
+ * — pas par `@nodefony/security`, dont les clés homonymes sont inertes. Toute
124
+ * console d'administration doit donc lire CE point, sinon elle affiche une
125
+ * intention de configuration au lieu de l'état appliqué.
126
+ *
127
+ * Lu depuis les caches recalculés au boot ET à chaque édition live
128
+ * ({@link onConfigChanged}) : la valeur rendue suit la config à chaud.
129
+ *
130
+ * @returns les valeurs d'en-tête, `null` quand l'en-tête n'est pas émis.
131
+ */
132
+ describeTransportSecurityHeaders(): {
133
+ strictTransportSecurity: string | null;
134
+ frameOptions: string | null;
135
+ contentTypeOptions: string | null;
136
+ };
137
+ /**
138
+ * Hook appelé par le data plane admin APRÈS une édition LIVE de la config
139
+ * (`PATCH /nodefony/kernel/api/config/http`, dev only). Recompute les valeurs
140
+ * DÉRIVÉES mises en cache (en-têtes sécurité, checker trust-proxy, trustedHosts) :
141
+ * sans lui, l'édition d'un champ `runtimeMutable` porté par un cache serait
142
+ * ignorée. Les champs lus DIRECTEMENT depuis `options` (`headerServer`,
143
+ * `maxBodySize`) n'en ont pas besoin — l'appel reste idempotent et bon marché.
144
+ */
145
+ onConfigChanged(): void;
146
+ /**
147
+ * (Re)configure le rate-limit général par IP depuis `options.rateLimit` — au
148
+ * boot (`onReady`) ET à chaque édition live ({@link onConfigChanged}). Désarme
149
+ * le GC précédent puis reconstruit compteur + scheduler UNIQUEMENT si
150
+ * `enabled` ; sinon `rateLimiter` reste `null` → 0 coût sur le hot path.
151
+ */
152
+ /**
153
+ * Compteur de rate-limit actif, ou `null` s'il est désactivé — surface de
154
+ * LECTURE pour le data plane admin (introspection des IP suivies). `null` est
155
+ * l'information « fonction désarmée », pas une erreur : l'appelant l'affiche
156
+ * tel quel plutôt que d'inventer un état.
157
+ */
158
+ get rateLimitStore(): IRateLimitStore | null;
159
+ private configureRateLimit;
160
+ /**
161
+ * (Re)configure le backstop OPT-IN de connexions WS concurrentes par IP (F6c)
162
+ * depuis `options.wsMaxConnectionsPerIp` — au boot ET à l'édition live. `null` (ou
163
+ * ≤ 0) = désactivé → `#wsConnCounter` reste `null` (0 alloc / 0 tracking, cas
164
+ * cloud-native par défaut : le cap concurrent est délégué à l'ingress). Reconstruit
165
+ * un compteur neuf : les sockets déjà ouvertes décrémentent l'ancienne instance
166
+ * (capturée dans leur closure de fermeture) → pas de dérive sur la nouvelle.
167
+ */
168
+ private configureWsConnectionLimit;
169
+ /**
170
+ * (Re)configure les probes de santé `/livez` + `/readyz` depuis
171
+ * `options.health` — au boot (`onReady`) ET à l'édition live. `enabled:false`
172
+ * → `#healthPaths` reste `null` (0 coût sur le hot path).
173
+ */
174
+ private configureHealth;
175
+ init(): Promise<this>;
176
+ /**
177
+ * Checker de confiance reverse-proxy, compilé une seule fois depuis
178
+ * `options.trustProxy` (lazy — pas de `BlockList` allouée par requête).
179
+ * Consommé par `HttpRequest`/`HttpContext` pour décider de l'adoption des
180
+ * en-têtes `X-Forwarded-*`.
181
+ *
182
+ * @returns le {@link TrustProxyChecker} partagé.
183
+ */
184
+ getTrustProxyChecker(): TrustProxyChecker;
185
+ /**
186
+ * Politique d'Origin WS (B4) compilée paresseusement pour un type de serveur
187
+ * (`websocket` / `websocketSecure`), depuis `options.<type>.allowedOrigins` :
188
+ * - `true` → contrôle désactivé (`disabled`) ;
189
+ * - `false` (défaut) → same-origin seul (`extra` vide) ;
190
+ * - string/liste → Origins cross-origin additionnelles (compilées en RegExp).
191
+ *
192
+ * @param cfgKey - `"websocket"` ou `"websocketSecure"`.
193
+ * @returns la politique mémoïsée (0 alloc après le 1er handshake).
194
+ */
195
+ private getWsOriginPolicy;
196
+ /**
197
+ * B4 — Validation d'`Origin` au handshake WebSocket (anti-CSWSH, OWASP
198
+ * WSTG-CLNT-10). Les navigateurs n'appliquent PAS CORS aux WebSockets : sans
199
+ * ce contrôle, une page tierce peut ouvrir un WS **authentifié par le cookie de
200
+ * session de la victime** (reprise L1). On exige donc, par défaut, que
201
+ * l'`Origin` du handshake corresponde au `Host` servi (same-origin), avec
202
+ * tolérance loopback en development (Studio Vite cross-port) et allowlist
203
+ * explicite pour les SPA cross-origin (`allowedOrigins`).
204
+ *
205
+ * Une requête SANS `Origin` (client non-navigateur) est acceptée : un attaquant
206
+ * non-navigateur n'a pas besoin de CSWSH, il se connecte directement.
207
+ *
208
+ * @param context - contexte WebSocket au handshake (avant `connect()`).
209
+ * @throws {HttpError} code WS 1008 (Policy Violation) si l'Origin est refusée.
210
+ */
211
+ checkWebsocketOrigin(context: WebsocketContext): void;
212
+ /**
213
+ * Reads the **kernel-level** syslog config (`kernel.options.log`) and swaps
214
+ * the request logger accordingly. The decision belongs to syslog (not to
215
+ * the http module) because EVERY request log flows through syslog, and
216
+ * the format choice is an operator concern shared across all transports.
217
+ *
218
+ * Config lue : `kernel.options.log.requestFormat`
219
+ * "auto" → dev=pretty, production=json, autre=default (DEFAULT)
220
+ * "default" → DefaultRequestLogger (legacy verbeux)
221
+ * "pretty" → PrettyRequestLogger (1 ligne colorée, P3.2)
222
+ * "json" → JsonAuditLogger (PDU canonique, P3.1)
223
+ *
224
+ * Programmatic override via `setRequestLogger(custom)` reste possible et
225
+ * gagne toujours sur la config (idempotent — last setter wins).
226
+ */
227
+ private applyRequestLoggerFromConfig;
228
+ handle(request: httpRequest, response: httpResponse | null, type: ServerType): Promise<HttpContext>;
229
+ /**
230
+ * Arme le contexte pour l'exécution : zone firewall (`context.secure`) et
231
+ * résolveur de route — **sans instancier le controller**.
232
+ *
233
+ * Séparé de {@link handleFrontController} parce que les deux gestes n'ont pas
234
+ * le même moment légitime. Le MATCH doit être précoce : il pose
235
+ * `context.sessionIntent` (depuis `@UseSession`) et `resolver.bypassFirewall`,
236
+ * que le point session unique et le firewall lisent juste après. L'INSTANCE,
237
+ * elle, exécute du code utilisateur (`initialize()`) et résout des dépendances
238
+ * DI : elle n'a rien à faire avant que la requête soit autorisée.
239
+ *
240
+ * @param checkFirewall - calcule `context.secure` (zone protégée) ; laissé à
241
+ * `false` par un appelant qui l'a déjà tranché.
242
+ * @throws HttpError 404 quand aucune route ne matche, ou l'exception portée
243
+ * par le résolveur (405…) — inchangé, et toujours avant le firewall : une
244
+ * route inexistante ne devient pas un 401.
245
+ */
246
+ prepareFrontController(context: ContextType, checkFirewall?: boolean): Promise<Resolver>;
247
+ /**
248
+ * {@link prepareFrontController} + instanciation immédiate du controller
249
+ * (DI + hook `initialize()`).
250
+ *
251
+ * @remarks Réservé au **WebSocket** : le controller doit exister avant
252
+ * `context.connect()`, puisqu'il porte le protocole négocié et peut encore
253
+ * toucher la réponse du handshake (cookies, en-têtes) — après l'accept, il
254
+ * est trop tard. Le HTTP, lui, arme la route ici et laisse
255
+ * `Resolver.executeAction` créer l'instance une fois la requête autorisée.
256
+ */
257
+ handleFrontController(context: ContextType, checkFirewall?: boolean): Promise<Controller>;
258
+ /**
259
+ * Handles errors by setting appropriate response codes and messages.
260
+ *
261
+ * @param error - The error object, which can be of type Error, HttpError, or nodefonyError.
262
+ * @param context - Optional context parameter, which can be of type ContextType.
263
+ * @param extraHeaders - Optional additional headers to be included in the response.
264
+ * @returns A promise that resolves to either HttpContext or WebsocketContext.
265
+ *
266
+ * WebSocket error codes and their meanings:
267
+ * - code >= 1000 && code <= 2999:
268
+ * - 1000: Normal connection closure
269
+ * - 1001: Remote peer is going away
270
+ * - 1002: Protocol error
271
+ * - 1003: Unprocessable input
272
+ * - 1004: Reserved
273
+ * - 1005: Reason not provided
274
+ * - 1006: Abnormal closure, no further detail available
275
+ * - 1007: Invalid data received
276
+ * - 1008: Policy violation
277
+ * - 1009: Message too big
278
+ * - 1010: Extension requested by client is required
279
+ * - 1011: Internal Server Error
280
+ * - 1015: TLS Handshake Failed
281
+ * - code >= 3000 && code <= 3999:
282
+ * Reserved for use by libraries, frameworks, and applications.
283
+ * Should be registered with IANA. Interpretation of these codes is
284
+ * undefined by the WebSocket protocol.
285
+ * - code >= 4000 && code <= 4999:
286
+ * Reserved for private use. Interpretation of these codes is
287
+ * undefined by the WebSocket protocol.
288
+ */
289
+ /**
290
+ * Override the default error renderer — e.g. to hide stack traces in prod
291
+ * or emit RFC 7807 problem+json. Stateless singleton expected.
292
+ */
293
+ setErrorRenderer(renderer: IErrorRenderer): void;
294
+ getErrorRenderer(): IErrorRenderer;
295
+ /**
296
+ * Override the default per-request logger — JSON access log, NCSA combined,
297
+ * pretty single-line formatter, etc. Stateless singleton expected.
298
+ */
299
+ setRequestLogger(logger: IRequestLogger): void;
300
+ getRequestLogger(): IRequestLogger;
301
+ onError(error: Error | HttpError | nodefonyError, context?: ContextType, _extraHeaders?: Record<string, unknown> | object): Promise<HttpContext | WebsocketContext>;
302
+ compileAlias(): RegExp[];
303
+ onHttpRequest(request: httpRequest, response: httpResponse, type: ServerType): Promise<unknown>;
304
+ initServers(): Promise<(httpServer | httpsServer | websocketServer | websocketSecureServer)[]>;
305
+ /**
306
+ * Publie les ports EFFECTIFS du runtime (state file), une fois l'écoute établie.
307
+ *
308
+ * C'est le canal qui rend `servers.portPolicy: "auto"` utilisable : `nodefony
309
+ * status`, `nodefony stop` et la readiness `--detach` sondaient `[5151, 5152]`
310
+ * **en dur**. Dès qu'un port glisse, cette convention ment — ils sonderaient un
311
+ * port que personne n'écoute et conclueraient « serveur down » sur un serveur
312
+ * parfaitement vivant. Ici, la topologie réelle est écrite noir sur blanc.
313
+ *
314
+ * Le port DÉSIRÉ est publié à côté du port obtenu : un outil peut ainsi dire
315
+ * « tu voulais 5151, tu écoutes sur 5153 » sans deviner.
316
+ *
317
+ * **Publié dans TOUS les environnements, production comprise.** Le glissement de
318
+ * port (`auto`) n'est pas la seule façon de sortir de la convention : une app qui
319
+ * déclare son port (PaaS `PORT`, ingress, `servers.http.port`) écoute ailleurs
320
+ * que `[5151, 5152]` **en `strict` aussi**. Sans ce canal, `production --detach
321
+ * --wait` sonderait la convention, ne verrait rien, et group-killerait au bout de
322
+ * son plafond un serveur qui écoutait parfaitement (faux négatif — même famille
323
+ * que « une liste de ports sondés est une CONVENTION, pas la topologie »).
324
+ * L'écriture est best-effort : un `node_modules` en lecture seule (image
325
+ * conteneur) ne fait jamais tomber un serveur qui, lui, écoute très bien.
326
+ *
327
+ * En **cluster**, le pid publié est celui du MASTER, pas du worker qui écrit :
328
+ * les workers sont recyclés (respawn), et un pid mort fait purger le canal par
329
+ * {@link readRuntimeState} — `status`/`stop` redeviendraient aveugles alors que
330
+ * l'app écoute toujours. Le master, lui, est le propriétaire du runtime.
331
+ */
332
+ private publishRuntimePorts;
333
+ /**
334
+ * Point d'activation UNIQUE de session — symétrique HTTP **et** WS. Ouvre une
335
+ * session si, et seulement si, la route la déclare (`context.sessionIntent`,
336
+ * posé par le Resolver depuis `@UseSession` / un paramètre `@Session`) **ou**
337
+ * si un cookie de session entrant existe déjà (reprise — L1). Sinon : aucune
338
+ * session (lazy). Remplace l'ancien `sessionAutoStart` global « démarre sur
339
+ * toutes les routes » (le moteur du ×23).
340
+ *
341
+ * @param context - contexte HTTP/HTTP2/WS courant.
342
+ * @returns la session active, ou `null` si la requête n'en requiert aucune.
343
+ */
344
+ startSession(context: WebsocketContext | HttpContext): Promise<Session | null>;
345
+ /**
346
+ * Teardown post-réponse — fire-and-forget depuis le `once("close")` posé par
347
+ * `createHttpContext` (un seul fire possible : once auto-détaché). Loggue la
348
+ * requête, draine les hooks afterResponse/onFinish, libère le scope DI.
349
+ */
350
+ private teardownHttp;
351
+ createHttpContext(scope: Scope, request: httpRequest, response: httpResponse, type: ServerType): HttpContext;
352
+ handleHttp(scope: Scope, request: httpRequest, response: httpResponse, type: ServerType): Promise<HttpContext>;
353
+ onRequestEnd(context: HttpContext, error?: Error | null | undefined): Promise<HttpContext | number>;
354
+ createWebsocketContext(scope: Scope, req: IncomingMessage, ws: Ws, type: ServerType): WebsocketContext;
355
+ onWebsocketRequest(ws: Ws, req: IncomingMessage, type: ServerType): Promise<unknown>;
356
+ handleWebsocket(scope: Scope, ws: Ws, req: IncomingMessage, type: ServerType): Promise<unknown>;
357
+ private releaseOrphanWsScope;
358
+ onConnect(context: WebsocketContext, error?: null | undefined | unknown): Promise<Ws | number>;
359
+ checkValidDomain(context: ContextType): number;
360
+ isValidDomain(context: ContextType): boolean;
361
+ /**
362
+ * Ce nom d'hôte franchit-il la barrière `trustedHosts` ? Même liste compilée
363
+ * que `isValidDomain`, mais interrogeable **sans contexte** — un module qui
364
+ * doit décider si un `Host` reçu est légitime (ex. `@nodefony/frontend`, qui
365
+ * en dérive l'origine des assets Vite) appelle CETTE méthode plutôt que de
366
+ * recompiler les motifs de son côté : deux copies divergeraient en silence.
367
+ *
368
+ * Résolution par NOM (`container.get("HttpKernel")`) chez l'appelant — aucun
369
+ * import de `@nodefony/http`, donc aucun cycle.
370
+ *
371
+ * @param hostname - nom d'hôte SANS port (`Context.domain`).
372
+ * @returns `true` si un motif de `trustedHosts` (ou le domaine canonique)
373
+ * couvre ce nom.
374
+ */
375
+ isTrustedHostname(hostname: string): boolean;
376
+ }
377
+ export default HttpKernel;
@@ -0,0 +1,25 @@
1
+ import type { IRequestLogger, IRequestLogEntry } from "../interfaces/IRequestLogger.js";
2
+ import type { IHttpContext, IWebsocketContext } from "../interfaces/IContext.js";
3
+ /**
4
+ * Pretty single-line logger for dev (P3.2) — the biggest gain for humans.
5
+ *
6
+ * Format (no padding, ANSI-colored):
7
+ * `GET 200 /api/test 12.3ms 127.0.0.1 [a1b2c3d4]`
8
+ *
9
+ * - method : cyan
10
+ * - status : green (2xx) / yellow (3xx) / yellow-bold (4xx) / red (5xx)
11
+ * - url : default
12
+ * - duration : dim
13
+ * - remote : dim
14
+ * - requestId : magenta-dim, truncated to first 8 chars
15
+ *
16
+ * Activate with `httpKernel.setRequestLogger(new PrettyRequestLogger())`.
17
+ *
18
+ * Stateless singleton. Allocates a few strings per request (terminal log
19
+ * path) — zero overhead when not activated.
20
+ */
21
+ declare class PrettyRequestLogger implements IRequestLogger {
22
+ renderHttp(context: IHttpContext, error?: Error | null): IRequestLogEntry;
23
+ renderWebsocket(context: IWebsocketContext, error?: Error | null, acceptedProtocol?: string | null): IRequestLogEntry;
24
+ }
25
+ export default PrettyRequestLogger;
@@ -0,0 +1,18 @@
1
+ import type { IRequestLogger, IRequestLogEntry } from "../interfaces/IRequestLogger.js";
2
+ import type { IHttpContext, IWebsocketContext } from "../interfaces/IContext.js";
3
+ /**
4
+ * Default Nodefony per-request logger — preserves the legacy colored format.
5
+ *
6
+ * HTTP success: `URL : <url> FROM : <remote> ORIGIN : <host> ID : <uuid>`
7
+ * HTTP error: adds the error toString (in dev), single-line in prod
8
+ * WebSocket: adds `Accept-Protocol : <proto>` on success
9
+ *
10
+ * Stateless singleton — zero per-request allocation. Override via
11
+ * `httpKernel.setRequestLogger(custom)` for JSON access logs (P3.1),
12
+ * pretty formatter (P3.2), or NCSA combined format (P3.10).
13
+ */
14
+ declare class DefaultRequestLogger implements IRequestLogger {
15
+ renderHttp(context: IHttpContext, error?: Error | null): IRequestLogEntry;
16
+ renderWebsocket(context: IWebsocketContext, error?: Error | null, acceptedProtocol?: string | null): IRequestLogEntry;
17
+ }
18
+ export default DefaultRequestLogger;
@@ -0,0 +1,14 @@
1
+ import type { Duplex } from "node:stream";
2
+ /**
3
+ * Gère l'event `clientError` d'un `http.Server` / `https.Server`.
4
+ *
5
+ * ⚠️ Doc Node (`http.Server` event `'clientError'`) : dès qu'un listener
6
+ * `clientError` est attaché, Node **cesse de fermer le socket automatiquement**
7
+ * (le défaut aurait répondu `400` + détruit le socket). Sans fermeture
8
+ * explicite, un socket malformé reste ouvert → fuite de socket/FD = DoS sur
9
+ * requête mal formée. On répond donc une réponse minimale et on ferme.
10
+ *
11
+ * @param error - erreur émise (codes `llhttp` : `HPE_*`, ou `ECONNRESET`…).
12
+ * @param socket - flux client brut (l'event `clientError` fournit un `Duplex`).
13
+ */
14
+ export declare function handleClientError(error: NodeJS.ErrnoException, socket: Duplex): void;
@@ -0,0 +1,42 @@
1
+ import { Service, Module, FamilyType } from "nodefony";
2
+ import HttpKernel, { ProtocolType, ServerType, SchemeType } from "../http-kernel.js";
3
+ import http from "node:http";
4
+ import http2 from "node:http2";
5
+ import { AddressInfo } from "node:net";
6
+ import { HttpTerminator } from "./serverShutdown.js";
7
+ declare class ServerHttp extends Service {
8
+ private httpKernel;
9
+ module: Module;
10
+ server: http.Server | http2.Http2Server | null;
11
+ httpTerminator: HttpTerminator | null;
12
+ port: number;
13
+ protocol: ProtocolType;
14
+ ready: boolean;
15
+ type: ServerType;
16
+ domain: string;
17
+ scheme: SchemeType;
18
+ address: string | null;
19
+ family: FamilyType | null;
20
+ active: boolean;
21
+ infos: AddressInfo | null;
22
+ constructor(module: Module, httpKernel: HttpKernel);
23
+ setPort(): number;
24
+ createServer(): Promise<http.Server | http2.Http2Server>;
25
+ /**
26
+ * Handler d'erreur DURABLE — posé une fois le serveur en écoute.
27
+ *
28
+ * Il ne traite donc plus `EADDRINUSE` au bind (le binder s'en charge, avec ou
29
+ * sans repli) : il couvre les erreurs de la VIE du serveur.
30
+ */
31
+ private attachErrorHandler;
32
+ /**
33
+ * Le bind a échoué pour de bon — soit `portPolicy: "strict"`, soit tous les
34
+ * ports de repli étaient pris, soit une erreur qui n'est pas un conflit de port.
35
+ *
36
+ * Reste FATAL (contrat inchangé) : un serveur qui n'écoute pas ne doit jamais
37
+ * laisser le process traîner en se croyant démarré.
38
+ */
39
+ private reportBindError;
40
+ showBanner(): void;
41
+ }
42
+ export default ServerHttp;
@@ -0,0 +1,41 @@
1
+ import { Service, Module, FamilyType } from "nodefony";
2
+ import HttpKernel, { ProtocolType, ServerType, SchemeType } from "../http-kernel.js";
3
+ import https from "node:https";
4
+ import http2 from "node:http2";
5
+ import { HttpTerminator } from "./serverShutdown.js";
6
+ import { AddressInfo } from "node:net";
7
+ declare class ServerHttps extends Service {
8
+ private httpKernel;
9
+ httpTerminator: HttpTerminator | null;
10
+ module: Module;
11
+ server: https.Server | http2.Http2SecureServer | null;
12
+ port: number;
13
+ protocol: ProtocolType;
14
+ ready: boolean;
15
+ type: ServerType;
16
+ domain: string;
17
+ scheme: SchemeType;
18
+ address: string | null;
19
+ family: FamilyType | null;
20
+ active: boolean;
21
+ infos: AddressInfo | null;
22
+ constructor(module: Module, httpKernel: HttpKernel);
23
+ terminator(): HttpTerminator;
24
+ setPort(): number;
25
+ createServer(): Promise<https.Server | http2.Http2SecureServer>;
26
+ createServerH2(): Promise<http2.Http2SecureServer>;
27
+ /**
28
+ * Écoute selon `servers.portPolicy`, commune aux deux branches TLS (HTTP/1.1 et
29
+ * HTTP/2) — leur seule différence est le type du serveur, pas la politique.
30
+ *
31
+ * Pose le handler d'erreur durable une fois en écoute (donc APRÈS les éventuels
32
+ * `EADDRINUSE` de repli, qui ne sont pas des pannes).
33
+ */
34
+ private listenWithPolicy;
35
+ /** Erreurs de la VIE du serveur (le bind est déjà passé — cf server-http.ts). */
36
+ private attachErrorHandler;
37
+ /** Bind définitivement impossible → FATAL (contrat inchangé). */
38
+ private reportBindError;
39
+ showBanner(): void;
40
+ }
41
+ export default ServerHttps;
@@ -0,0 +1,62 @@
1
+ import serveStatic from "serve-static";
2
+ import http from "node:http";
3
+ import http2 from "node:http2";
4
+ import { Service, Module } from "nodefony";
5
+ type serveStaticType = serveStatic.RequestHandler<http.ServerResponse>;
6
+ type ServersStatic = Record<string, serveStaticType>;
7
+ /** Un dossier monté sous un préfixe public (`/_assets/x/` → dir). */
8
+ type StaticMount = {
9
+ prefix: string;
10
+ server: serveStaticType;
11
+ dir: string;
12
+ };
13
+ declare class Statics extends Service {
14
+ module: Module;
15
+ servers: ServersStatic;
16
+ /** Montages préfixés (prod frontend). Lazy : `[]` rempli à `addMount`. */
17
+ mounts: StaticMount[];
18
+ /**
19
+ * Serveur statique config-driven actif. `false` (config `statics.enabled`) =
20
+ * aucun montage `web`/`assets`, 0 listener — quand un reverse-proxy/CDN sert
21
+ * les statiques. Ne gate PAS les `addMount()` programmatiques (frontend prod).
22
+ */
23
+ enabled: boolean;
24
+ defaultOptions: serveStatic.ServeStaticOptions;
25
+ constructor(module: Module);
26
+ /**
27
+ * Auto-monte le `public/` de chaque module applicatif sous le préfixe natif
28
+ * `/<module>/` (basename du nom — `@nodefony/test` → `/test/`). Appelé une fois
29
+ * à `onReady`. Idempotent via {@link addMount} (un même préfixe est remplacé).
30
+ *
31
+ * Exclusions :
32
+ * - **app root** (`isApp`) — son `public/` est servi à la racine `/` par la
33
+ * racine statique `web` (favicon…), sans préfixe.
34
+ * - **modules frontend-managed** (présents dans `frontend.listEntries()`) —
35
+ * leurs assets buildés sont servis sous `/_assets/<name>/` par
36
+ * `@nodefony/frontend` ; on ne double-sert pas leur `public/dist`.
37
+ * - **modules sans `public/`** — rien à monter (http, framework, security…).
38
+ *
39
+ * Enregistré dans {@link mounts} quel que soit `enabled` → la carte
40
+ * préfixe→dossier reste lisible par `proxy:generate` même statiques désactivés
41
+ * (reverse-proxy/CDN en prod).
42
+ */
43
+ mountModulePublics(): void;
44
+ initStaticFiles(): void;
45
+ /**
46
+ * Monte un dossier sous un préfixe public (ex `/_assets/studio/` → outDir).
47
+ * Le préfixe est normalisé (leading + trailing `/`). Idempotent : un même
48
+ * préfixe est remplacé. Consommé par `@nodefony/frontend` en prod (assets
49
+ * Vite buildés) — résolu par nom via le Container, sans import croisé.
50
+ *
51
+ * @param prefix préfixe d'URL public
52
+ * @param dir dossier absolu à servir
53
+ */
54
+ addMount(prefix: string, dir: string): void;
55
+ /** `true` si au moins un montage préfixé est actif (gate du pipeline). */
56
+ hasMounts(): boolean;
57
+ addDirectory(Path: string, options: serveStatic.ServeStaticOptions): serveStatic.RequestHandler<http.ServerResponse<http.IncomingMessage>>;
58
+ getStatic(server: serveStaticType, request: http.IncomingMessage | http2.Http2ServerRequest, response: http.ServerResponse | http2.Http2ServerResponse): Promise<http.ServerResponse | http2.Http2ServerResponse>;
59
+ getUrl(request: http.IncomingMessage | http2.Http2ServerRequest): string;
60
+ handle(request: http.IncomingMessage | http2.Http2ServerRequest, response: http.ServerResponse | http2.Http2ServerResponse): Promise<http.ServerResponse | http2.Http2ServerResponse>;
61
+ }
62
+ export default Statics;
@@ -0,0 +1,29 @@
1
+ import Ws, { WebSocketServer } from "ws";
2
+ import { Service, Module, FamilyType } from "nodefony";
3
+ import HttpKernel, { ProtocolType, ServerType, SchemeType } from "../http-kernel.js";
4
+ import { AddressInfo } from "node:net";
5
+ import type { IncomingMessage } from "node:http";
6
+ import httpsServers from "./server-https.js";
7
+ declare class WebsocketSecure extends Service {
8
+ private httpKernel;
9
+ module: Module;
10
+ ready: boolean;
11
+ server: WebSocketServer | null;
12
+ port: number;
13
+ domain: string;
14
+ protocol: ProtocolType;
15
+ family: FamilyType | null;
16
+ scheme: SchemeType;
17
+ address: string | null;
18
+ type: ServerType;
19
+ infos: AddressInfo | null;
20
+ /** Timer keep-alive (UN par serveur). `null` si désactivé ou pas démarré. */
21
+ heartbeatTimer: ReturnType<typeof setInterval> | null;
22
+ constructor(module: Module, httpKernel: HttpKernel);
23
+ setPort(): number;
24
+ createServer(serverHttps: httpsServers): Promise<WebSocketServer>;
25
+ onConnection(ws: Ws, req: IncomingMessage): void;
26
+ terminate(): Promise<boolean>;
27
+ showBanner(): void;
28
+ }
29
+ export default WebsocketSecure;
@@ -0,0 +1,29 @@
1
+ import Ws, { WebSocketServer } from "ws";
2
+ import { Service, Module, FamilyType } from "nodefony";
3
+ import HttpKernel, { ProtocolType, ServerType, SchemeType } from "../http-kernel.js";
4
+ import { AddressInfo } from "node:net";
5
+ import type { IncomingMessage } from "node:http";
6
+ import httpServer from "./server-http.js";
7
+ declare class Websocket extends Service {
8
+ private httpKernel;
9
+ module: Module;
10
+ ready: boolean;
11
+ server: WebSocketServer | null;
12
+ port: number;
13
+ domain: string;
14
+ protocol: ProtocolType;
15
+ family: FamilyType | null;
16
+ scheme: SchemeType;
17
+ address: string | null;
18
+ type: ServerType;
19
+ infos: AddressInfo | null;
20
+ /** Timer keep-alive (UN par serveur). `null` si désactivé ou pas démarré. */
21
+ heartbeatTimer: ReturnType<typeof setInterval> | null;
22
+ constructor(module: Module, httpKernel: HttpKernel);
23
+ setPort(): number;
24
+ createServer(serverHttp: httpServer): Promise<WebSocketServer>;
25
+ onConnection(ws: Ws, req: IncomingMessage): void;
26
+ terminate(): Promise<boolean>;
27
+ showBanner(): void;
28
+ }
29
+ export default Websocket;