@nodefony/http 10.0.0-beta.2 → 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 (139) hide show
  1. package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
  2. package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorateParam.js +1 -2
  3. package/dist/index.js +19 -11
  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 +45 -6
  9. package/dist/nodefony/config/defineModuleConfig.js +2 -3
  10. package/dist/nodefony/interfaces/IReverseProxy.js +1 -0
  11. package/dist/nodefony/service/HttpAdminApi.js +2 -3
  12. package/dist/nodefony/service/ProfilerAdminApi.js +1 -2
  13. package/dist/nodefony/service/audit-logger.js +22 -16
  14. package/dist/nodefony/service/certificates.js +228 -19
  15. package/dist/nodefony/service/error-renderer.js +30 -13
  16. package/dist/nodefony/service/http-kernel.js +331 -82
  17. package/dist/nodefony/service/pretty-request-logger.js +5 -4
  18. package/dist/nodefony/service/request-logger.js +24 -16
  19. package/dist/nodefony/service/reverse-proxy.js +336 -0
  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 +7 -10
  25. package/dist/nodefony/service/servers/server-websocket-secure.js +18 -18
  26. package/dist/nodefony/service/servers/server-websocket.js +18 -18
  27. package/dist/nodefony/service/servers/serverShutdown.js +78 -18
  28. package/dist/nodefony/service/servers/wsHeartbeat.js +32 -17
  29. package/dist/nodefony/service/sessions/sessions-service.js +34 -20
  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 +51 -32
  36. package/dist/nodefony/src/context/domainMatcher.js +21 -4
  37. package/dist/nodefony/src/context/forwarded.js +3 -4
  38. package/dist/nodefony/src/context/http/HttpContext.js +98 -46
  39. package/dist/nodefony/src/context/http/Request.js +34 -38
  40. package/dist/nodefony/src/context/http/Response.js +39 -7
  41. package/dist/nodefony/src/context/http/SseStream.js +349 -0
  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 +22 -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/responseEnded.js +26 -0
  49. package/dist/nodefony/src/context/transport.js +45 -0
  50. package/dist/nodefony/src/context/trustProxy.js +1 -2
  51. package/dist/nodefony/src/context/websocket/Response.js +42 -6
  52. package/dist/nodefony/src/context/websocket/WebsocketContext.js +94 -20
  53. package/dist/nodefony/src/context/websocket/isWebsocketContext.js +17 -0
  54. package/dist/nodefony/src/context/websocket/websocketUrl.js +20 -0
  55. package/dist/nodefony/src/context/websocket/wsBackpressure.js +1 -2
  56. package/dist/nodefony/src/context/websocket/wsCloseReason.js +1 -2
  57. package/dist/nodefony/src/context/websocket/wsLogContent.js +1 -2
  58. package/dist/nodefony/src/cookies/cookie.js +3 -7
  59. package/dist/nodefony/src/errors/httpError.js +1 -2
  60. package/dist/nodefony/src/errors/thrownMessage.js +1 -2
  61. package/dist/nodefony/src/profiler/FrameProfile.js +1 -2
  62. package/dist/nodefony/src/profiler/Profiler.js +108 -30
  63. package/dist/nodefony/src/profiler/aggregate.js +422 -0
  64. package/dist/nodefony/src/proxy/forward.js +500 -0
  65. package/dist/nodefony/src/proxy/generateProxyConfig.js +14 -6
  66. package/dist/nodefony/src/proxy/rules.js +188 -0
  67. package/dist/nodefony/src/rateLimit/MemoryRateLimitStore.js +1 -2
  68. package/dist/nodefony/src/rateLimit/WsConnectionCounter.js +1 -2
  69. package/dist/nodefony/src/rateLimit/rateLimitFilters.js +1 -2
  70. package/dist/nodefony/src/servers/http2Limits.js +50 -0
  71. package/dist/nodefony/src/servers/kernelServers.js +12 -3
  72. package/dist/nodefony/src/servers/portBinder.js +41 -10
  73. package/dist/nodefony/src/servers/upgradeDispatch.js +128 -0
  74. package/dist/nodefony/src/session/session.js +30 -15
  75. package/dist/nodefony/src/session/sessionStoreFailure.js +1 -2
  76. package/dist/nodefony/src/session/storage/MemorySessionStorage.js +1 -2
  77. package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +1 -2
  78. package/dist/nodefony/src/session/storage/sessionFilters.js +1 -2
  79. package/dist/nodefony/src/session/storage/sessionSort.js +1 -2
  80. package/dist/types/index.d.ts +12 -3
  81. package/dist/types/nodefony/config/config.d.ts +16 -0
  82. package/dist/types/nodefony/interfaces/IContext.d.ts +65 -4
  83. package/dist/types/nodefony/interfaces/IErrorRenderer.d.ts +1 -1
  84. package/dist/types/nodefony/interfaces/IRequest.d.ts +9 -4
  85. package/dist/types/nodefony/interfaces/IReverseProxy.d.ts +67 -0
  86. package/dist/types/nodefony/interfaces/IRouting.d.ts +26 -0
  87. package/dist/types/nodefony/interfaces/ISecurity.d.ts +6 -0
  88. package/dist/types/nodefony/interfaces/index.d.ts +3 -2
  89. package/dist/types/nodefony/service/audit-logger.d.ts +2 -1
  90. package/dist/types/nodefony/service/certificates.d.ts +147 -5
  91. package/dist/types/nodefony/service/error-renderer.d.ts +32 -1
  92. package/dist/types/nodefony/service/http-kernel.d.ts +157 -15
  93. package/dist/types/nodefony/service/reverse-proxy.d.ts +156 -0
  94. package/dist/types/nodefony/service/servers/clientError.d.ts +16 -0
  95. package/dist/types/nodefony/service/servers/server-h2c.d.ts +51 -0
  96. package/dist/types/nodefony/service/servers/server-http.d.ts +3 -3
  97. package/dist/types/nodefony/service/servers/server-https.d.ts +4 -4
  98. package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +2 -3
  99. package/dist/types/nodefony/service/servers/server-websocket.d.ts +2 -3
  100. package/dist/types/nodefony/service/servers/serverShutdown.d.ts +30 -16
  101. package/dist/types/nodefony/service/servers/wsHeartbeat.d.ts +26 -0
  102. package/dist/types/nodefony/service/sessions/sessions-service.d.ts +19 -1
  103. package/dist/types/nodefony/src/context/Context.d.ts +35 -6
  104. package/dist/types/nodefony/src/context/domainMatcher.d.ts +11 -1
  105. package/dist/types/nodefony/src/context/http/HttpContext.d.ts +48 -1
  106. package/dist/types/nodefony/src/context/http/Request.d.ts +20 -15
  107. package/dist/types/nodefony/src/context/http/Response.d.ts +14 -0
  108. package/dist/types/nodefony/src/context/http/SseStream.d.ts +155 -0
  109. package/dist/types/nodefony/src/context/http/parser.d.ts +18 -2
  110. package/dist/types/nodefony/src/context/http2/Request.d.ts +1 -1
  111. package/dist/types/nodefony/src/context/http2/Response.d.ts +15 -5
  112. package/dist/types/nodefony/src/context/responseEnded.d.ts +20 -0
  113. package/dist/types/nodefony/src/context/transport.d.ts +53 -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 +57 -7
  116. package/dist/types/nodefony/src/context/websocket/isWebsocketContext.d.ts +15 -0
  117. package/dist/types/nodefony/src/context/websocket/websocketUrl.d.ts +16 -0
  118. package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +13 -12
  119. package/dist/types/nodefony/src/profiler/Profiler.d.ts +69 -15
  120. package/dist/types/nodefony/src/profiler/aggregate.d.ts +215 -0
  121. package/dist/types/nodefony/src/proxy/forward.d.ts +168 -0
  122. package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +16 -0
  123. package/dist/types/nodefony/src/proxy/rules.d.ts +88 -0
  124. package/dist/types/nodefony/src/servers/http2Limits.d.ts +37 -0
  125. package/dist/types/nodefony/src/servers/kernelServers.d.ts +10 -1
  126. package/dist/types/nodefony/src/servers/portBinder.d.ts +29 -5
  127. package/dist/types/nodefony/src/servers/upgradeDispatch.d.ts +69 -0
  128. package/dist/types/nodefony/src/session/session.d.ts +2 -0
  129. package/docs/cookies.md +85 -78
  130. package/docs/index.md +5 -1
  131. package/docs/observabilite.md +81 -81
  132. package/docs/rate-limit.md +83 -83
  133. package/docs/reverse-proxy.md +522 -0
  134. package/docs/servers.md +378 -230
  135. package/docs/session.md +180 -180
  136. package/docs/sse.md +287 -0
  137. package/docs/upload.md +120 -117
  138. package/package.json +10 -11
  139. package/dist/_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateMetadata.js +0 -6
@@ -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,22 +71,31 @@ 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";
77
78
  export type { IRateLimitStore, IRateLimitOptions, RateLimitVerdict, } from "./nodefony/src/rateLimit/IRateLimitStore.js";
78
79
  export { Context, Session, SessionsService, HttpError, HttpKernel, Response, Http2Response, Response as HttpResponse, wsResponse, wsResponse as WebsocketResponse, Cookie, HttpContext, WebsocketContext, HttpRequest, Http2Request, DefaultErrorRenderer, DefaultRequestLogger, JsonAuditLogger, PrettyRequestLogger, Profiler, };
80
+ export { SseStream, openSseStream, acceptsEventStream, SSE_HEARTBEAT_MS, } from "./nodefony/src/context/http/SseStream.js";
81
+ export type { ISseStreamOptions, ISseSendOptions, } from "./nodefony/src/context/http/SseStream.js";
79
82
  export { schemaMismatchOf, type SchemaMismatchConfidence, } from "./nodefony/service/error-renderer.js";
80
- 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";
81
87
  export { FrameProfile } from "./nodefony/src/profiler/FrameProfile.js";
82
88
  export type { FrameProfileInit, ProfiledArea, ProfiledResolver, } from "./nodefony/src/profiler/FrameProfile.js";
89
+ export { default as ReverseProxy } from "./nodefony/service/reverse-proxy.js";
90
+ export { parseProxyOrigin, normalizePrefix, proxyMountProblems, } from "./nodefony/src/proxy/rules.js";
91
+ export type { IProxyMount, IProxyMountOptions, ProxyTarget, } from "./nodefony/interfaces/IReverseProxy.js";
83
92
  export { resolveUiDelivery, PrebuiltUi, } from "./nodefony/src/assets/prebuiltUi.js";
84
93
  export type { UiDeliveryMode, UiDeliveryResolved, IUiDeliveryResolution, IUiDeliveryOptions, IPrebuiltUiOptions, } from "./nodefony/src/assets/prebuiltUi.js";
85
- 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";
86
95
  export type { ICookie, ICookieOptions, IWsCookie, SameSiteType, PriorityType, } from "./nodefony/interfaces/ICookie.js";
87
96
  export type { ISession, ISessionStorage, ISerializedSession, ISessionSummary, ISessionRecord, ISessionListFilter, ISessionListQuery, SessionIntent, SessionStatusType, SessionStrategyType, FlashBagType, MetaBagType, } from "./nodefony/interfaces/ISession.js";
88
97
  export { SESSION_SORTABLE_FIELDS, SESSION_DEFAULT_ORDER, SESSION_DEFAULT_ORDER_SQL, SESSION_COLUMN_ALIASES, } from "./nodefony/src/session/storage/sessionSort.js";
89
- export type { IHttpRequest, IHttp2Request, IWsRequest, HTTPMethodType, } from "./nodefony/interfaces/IRequest.js";
98
+ export type { IHttpRequest, RequestBody, IHttp2Request, IWsRequest, HTTPMethodType, } from "./nodefony/interfaces/IRequest.js";
90
99
  export type { IHttpResponse, IWebsocketResponse, } from "./nodefony/interfaces/IResponse.js";
91
100
  export type { IHttpKernel } from "./nodefony/interfaces/IHttpKernel.js";
92
101
  export type { IResolvedRoute, IRouteResolver, IRequestRouter, } from "./nodefony/interfaces/IRouting.js";
@@ -67,6 +67,7 @@ export declare const httpConfigSchema: z.ZodObject<{
67
67
  key: z.ZodDefault<z.ZodString>;
68
68
  cert: z.ZodDefault<z.ZodString>;
69
69
  privateKeyMode: z.ZodDefault<z.ZodNumber>;
70
+ extraCaFiles: z.ZodDefault<z.ZodArray<z.ZodString>>;
70
71
  san: z.ZodDefault<z.ZodObject<{
71
72
  dns: z.ZodDefault<z.ZodArray<z.ZodString>>;
72
73
  ip: z.ZodDefault<z.ZodArray<z.ZodString>>;
@@ -184,6 +185,21 @@ export declare const httpConfigSchema: z.ZodObject<{
184
185
  readinessPath: z.ZodDefault<z.ZodString>;
185
186
  shutdownDelay: z.ZodDefault<z.ZodNumber>;
186
187
  }, z.core.$loose>>;
188
+ proxy: z.ZodDefault<z.ZodObject<{
189
+ timeoutMs: z.ZodDefault<z.ZodNumber>;
190
+ connectTimeoutMs: z.ZodDefault<z.ZodNumber>;
191
+ maxSockets: z.ZodDefault<z.ZodNumber>;
192
+ mounts: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
193
+ target: z.ZodString;
194
+ methods: z.ZodOptional<z.ZodArray<z.ZodString>>;
195
+ websocket: z.ZodDefault<z.ZodBoolean>;
196
+ stripPrefix: z.ZodDefault<z.ZodBoolean>;
197
+ preserveHost: z.ZodDefault<z.ZodBoolean>;
198
+ stripHeaders: z.ZodDefault<z.ZodArray<z.ZodString>>;
199
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
200
+ secure: z.ZodDefault<z.ZodBoolean>;
201
+ }, z.core.$strict>>>;
202
+ }, z.core.$strict>>;
187
203
  wsMaxConnectionsPerIp: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
188
204
  }, z.core.$strict>;
189
205
  /** Type de sortie (config normalisée + défauts appliqués). */
@@ -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;
@@ -86,6 +120,8 @@ export interface IContext {
86
120
  cookieSession: ICookie | null | undefined;
87
121
  cookies: CookiesMap;
88
122
  user: unknown;
123
+ /** Identifiant de l'appelant authentifié (pare-feu), lisible au démontage. */
124
+ userIdentifier: string | null;
89
125
  error: Error | null | undefined;
90
126
  metaData: object;
91
127
  readonly phases: PhaseTiming[];
@@ -137,13 +173,38 @@ export interface IHttpContext extends IContext {
137
173
  getHost(): string | undefined;
138
174
  getUserAgent(): string | undefined;
139
175
  }
140
- 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 {
141
202
  acceptedProtocol?: string | undefined;
142
203
  connection: unknown;
143
204
  rejected: boolean;
144
205
  port: number | string;
145
206
  wsUrl: URL | null;
146
- queryGet: Record<string, string>;
207
+ query: Record<string, string>;
147
208
  connect(): Promise<unknown>;
148
209
  handle(data?: unknown[]): Promise<object>;
149
210
  close(reasonCode: number, description: string): unknown;
@@ -46,7 +46,7 @@ export interface IErrorWebsocketResult {
46
46
  *
47
47
  * Override via `httpKernel.setErrorRenderer(custom)` to customise:
48
48
  * - hide stack traces in prod
49
- * - emit RFC 7807 problem+json
49
+ * - emit RFC 9457 problem+json
50
50
  * - inject auth challenge headers
51
51
  */
52
52
  export interface IErrorRenderer {
@@ -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
  }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Origine d'un amont relayé : `scheme://hôte[:port]`, sans chemin.
3
+ *
4
+ * Une fonction quand l'origine n'est connue qu'à l'exécution (un serveur
5
+ * enfant qui choisit son port au démarrage) : elle est rappelée à chaque
6
+ * requête, et `undefined` signifie « l'amont n'est pas prêt » — la requête
7
+ * reçoit `503` (`Retry-After: 1`), l'upgrade WebSocket `503` avant tout `101` :
8
+ * le préfixe appartient à l'amont, le routage n'a rien à y répondre.
9
+ */
10
+ export type ProxyTarget = string | (() => string | undefined);
11
+ /**
12
+ * Réglages d'un montage du proxy inverse (`ReverseProxy.mount`).
13
+ *
14
+ * Les défauts sont ceux d'un relais sans surprise : toutes les méthodes, le
15
+ * chemin transmis tel quel, l'en-tête `Host` réécrit sur la cible, aucun
16
+ * en-tête retiré au-delà des en-têtes de connexion, certificat de la cible
17
+ * vérifié.
18
+ */
19
+ export interface IProxyMountOptions {
20
+ /** Origine de l'amont (cf {@link ProxyTarget}). */
21
+ target: ProxyTarget;
22
+ /**
23
+ * Méthodes relayées (majuscules). Une autre méthode n'est PAS relayée : la
24
+ * requête poursuit jusqu'au routage. Absent = toutes.
25
+ */
26
+ methods?: readonly string[] | undefined;
27
+ /**
28
+ * Relaie aussi l'upgrade WebSocket du préfixe. Sans ce drapeau, un upgrade
29
+ * sur le préfixe reste au serveur WebSocket de Nodefony.
30
+ */
31
+ websocket?: boolean | undefined;
32
+ /**
33
+ * Retire le préfixe avant de transmettre le chemin (`/svc/a` → `/a`).
34
+ * Défaut `false` : l'amont reçoit le chemin complet — c'est le cas d'un
35
+ * serveur qui connaît déjà son préfixe (Vite et son `base`).
36
+ */
37
+ stripPrefix?: boolean | undefined;
38
+ /**
39
+ * Garde l'en-tête `Host` du client au lieu de le réécrire sur la cible.
40
+ * Défaut `false` : un amont qui filtre les noms d'hôte (Vite et son
41
+ * `allowedHosts`) reçoit le sien, et le nom d'origine part dans
42
+ * `X-Forwarded-Host`.
43
+ */
44
+ preserveHost?: boolean | undefined;
45
+ /**
46
+ * En-têtes de requête à ne pas transmettre (insensible à la casse) — ce
47
+ * dont l'amont n'a pas l'usage ne doit pas lui parvenir (`cookie`,
48
+ * `authorization` vers un serveur de sources, par exemple).
49
+ */
50
+ stripHeaders?: readonly string[] | undefined;
51
+ /**
52
+ * Délai d'inactivité de l'échange avec l'amont, en ms. Dépassé avant la
53
+ * réponse : 504. Absent = `proxy.timeoutMs` de la configuration (30 s par
54
+ * défaut). Ne s'applique pas à un WebSocket établi.
55
+ */
56
+ timeoutMs?: number | undefined;
57
+ /**
58
+ * Vérifie le certificat d'une cible `https:`. Défaut `true` — `false` n'est
59
+ * ACCEPTÉ que vers la boucle locale (refus au montage sinon).
60
+ */
61
+ secure?: boolean | undefined;
62
+ }
63
+ /** Un montage tel que le proxy le garde : préfixe normalisé + réglages. */
64
+ export interface IProxyMount extends IProxyMountOptions {
65
+ /** Préfixe d'URL normalisé (`/` en tête et en fin). */
66
+ readonly prefix: string;
67
+ }
@@ -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
@@ -75,6 +85,22 @@ export interface IRequestRouter {
75
85
  * @returns le résolveur (`resolve === true` si une route a matché)
76
86
  */
77
87
  resolve(context: ContextType, cleanPathOverride?: string, methodOverride?: string): IRouteResolver;
88
+ /**
89
+ * Dit si au moins une route déclare le transport `WEBSOCKET` pour ce chemin.
90
+ *
91
+ * Lu AVANT le `101` : une socket vers un chemin sans route se refuse par un
92
+ * statut HTTP (RFC 6455 §4.2.2), jamais par une ouverture suivie d'une
93
+ * fermeture — un client qui sonde par une ouverture conclurait que le
94
+ * serveur l'a acceptée. La réponse est un SUR-ENSEMBLE de ce que
95
+ * {@link IRequestRouter.resolve} acceptera : l'hôte (`@Domain`), le
96
+ * sous-protocole et les exigences de variables ne sont pas jugés ici, ils
97
+ * restent des fermetures après ouverture — refuser à tort coûterait une
98
+ * connexion légitime.
99
+ *
100
+ * @param pathname - chemin de l'URL d'upgrade (WHATWG), barres finales comprises
101
+ * @returns `false` si aucune route WebSocket ne peut répondre à ce chemin
102
+ */
103
+ servesWebsocket(pathname: string): boolean;
78
104
  /**
79
105
  * Instance partagée d'un controller `scope: "singleton"`, créée une seule fois.
80
106
  *
@@ -42,4 +42,10 @@ export interface IFirewallGate {
42
42
  handleCors(context: ContextType): number | undefined;
43
43
  /** Pose les en-têtes de sécurité (CSP, HSTS…) de la réponse. */
44
44
  applySecurityHeaders(context: ContextType): void;
45
+ /**
46
+ * Le revalidateur de l'identité authentifiée de la requête courante, pour une
47
+ * connexion longue qui la gardera (flux SSE) : la même règle que les sockets
48
+ * WebSocket. `null` pour un anonyme — rien à révoquer.
49
+ */
50
+ currentRevalidator?(): ((nowMs?: number) => Promise<boolean>) | null;
45
51
  }
@@ -1,9 +1,10 @@
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";
10
+ export type { IProxyMount, IProxyMountOptions, ProxyTarget, } from "./IReverseProxy.js";
@@ -17,7 +17,8 @@ import type { IHttpContext, IWebsocketContext, PhaseTiming } from "../interfaces
17
17
  export interface AuditLogEntry {
18
18
  ts: string;
19
19
  requestId: string;
20
- userId: string | null;
20
+ /** Identifiant de l'appelant authentifié (`context.userIdentifier`), `null` sinon. */
21
+ userIdentifier: string | null;
21
22
  type: "http" | "ws";
22
23
  scheme: string;
23
24
  method: string | null;
@@ -1,6 +1,138 @@
1
1
  import { Service, Module } from "nodefony";
2
2
  import { type KeyObject } from "node:crypto";
3
3
  import { type ICertificateAttribute } from "./x509.js";
4
+ /**
5
+ * Accès à la liste d'ancres TLS par défaut du processus — injectable pour
6
+ * éprouver la décision sans toucher la confiance du processus de test.
7
+ */
8
+ export interface ITlsTrustStore {
9
+ getCACertificates?: (type?: "default") => string[];
10
+ setDefaultCACertificates?: (certs: ReadonlyArray<string>) => void;
11
+ }
12
+ /** Issue de {@link addDefaultCaCertificate}, CONSTATÉE à l'exécution. */
13
+ export type CaTrustVerdict = "added" | "present" | "unsupported";
14
+ /**
15
+ * Ajoute une autorité aux ancres TLS par défaut du processus (requêtes sortantes
16
+ * `fetch`, `https`, `tls.connect`), sans en retirer aucune.
17
+ *
18
+ * C'est l'équivalent en cours d'exécution de `NODE_EXTRA_CA_CERTS`, qui n'est lu
19
+ * qu'au lancement du processus — trop tôt pour une CA que le boot vient de
20
+ * générer, et que seul un script de banc posait. La capacité se CONSTATE
21
+ * (`tls.setDefaultCACertificates` n'existe qu'à partir de Node 24.5), elle ne se
22
+ * déduit pas d'un numéro de version. Idempotent : une autorité déjà présente
23
+ * n'est pas ajoutée deux fois.
24
+ *
25
+ * @param pem - autorité au format PEM.
26
+ * @param store - accès aux ancres ; `node:tls` par défaut.
27
+ * @returns `added`, `present` (déjà de confiance), ou `unsupported`.
28
+ */
29
+ export declare function addDefaultCaCertificate(pem: string, store?: ITlsTrustStore): CaTrustVerdict;
30
+ /**
31
+ * Découpe un fichier PEM en ses certificats — une chaîne d'entreprise est
32
+ * souvent livrée en un seul fichier (racine + intermédiaires).
33
+ *
34
+ * Chaque certificat est ajouté SÉPARÉMENT : c'est ce qui rend l'ajout
35
+ * idempotent certificat par certificat, la liste par défaut du processus
36
+ * n'étant faite que de certificats isolés.
37
+ *
38
+ * @param pem - contenu d'un fichier PEM (un ou plusieurs certificats)
39
+ * @returns les certificats, dans l'ordre du fichier ; vide s'il n'y en a aucun
40
+ */
41
+ export declare function splitPemCertificates(pem: string): string[];
42
+ /** Bilan de {@link trustCaFile}. */
43
+ export interface ICaFileTrust {
44
+ /** Fichier lu, chemin absolu. */
45
+ readonly file: string;
46
+ /** Certificats ajoutés aux ancres du processus. */
47
+ readonly added: number;
48
+ /** Certificats déjà de confiance. */
49
+ readonly present: number;
50
+ }
51
+ /**
52
+ * Ajoute les autorités d'un fichier PEM aux ancres TLS du processus
53
+ * (`certificates.extraCaFiles`) — l'équivalent, porté par l'APPLICATION, de
54
+ * `NODE_EXTRA_CA_CERTS`.
55
+ *
56
+ * `NODE_EXTRA_CA_CERTS` n'est lu qu'au lancement de Node, et se pose dans la
57
+ * syntaxe du shell — `VAR=… npm run dev` n'existe pas sous Windows. Une autorité
58
+ * d'entreprise (Keycloak interne, proxy sortant qui ré-signe) se déclare donc
59
+ * dans la configuration, et sert à TOUTES les requêtes sortantes du processus :
60
+ * découverte OpenID, point de jeton, jeu de clés, appels d'administration.
61
+ *
62
+ * @param file - chemin du fichier, absolu ou relatif à `root`
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
+ *
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
69
+ * @returns le bilan de l'ajout
70
+ * @throws BootConfigurationError - fichier illisible, sans certificat, ou Node
71
+ * incapable d'élargir ses ancres en cours d'exécution
72
+ */
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
+ }
114
+ /**
115
+ * Ajoute aux ancres TLS du processus les autorités déclarées
116
+ * (`certificates.extraCaFiles`), au plus tôt : appelé à l'enregistrement du
117
+ * module, avant tout service — toute requête sortante, commande console
118
+ * comprise, les trouve en place.
119
+ *
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}).
128
+ *
129
+ * @param files - `certificates.extraCaFiles`
130
+ * @param root - racine de l'application
131
+ * @param log - journal du module
132
+ * @param context - décor (environnement, autorité de dev, ancres)
133
+ * @throws BootConfigurationError - cf {@link trustCaFile}
134
+ */
135
+ export declare function trustOutboundAuthorities(files: readonly string[], root: string, log: (message: string, severity: "INFO") => void, context?: IOutboundTrustContext): Promise<void>;
4
136
  /** Paire de clés RSA du certificat auto-signé. */
5
137
  export interface IRsaKeyPair {
6
138
  privateKey: KeyObject;
@@ -102,7 +234,7 @@ export interface CertificateInfo {
102
234
  *
103
235
  * Conformité (génération auto-signée) : signature SHA-256 (jamais SHA-1,
104
236
  * RFC 5280 / CA-B Forum), numéro de série aléatoire 128 bits unique
105
- * (RFC 5280 §4.1.2.2), SAN qui fait foi (RFC 6125), `notBefore` reculé,
237
+ * (RFC 5280 §4.1.2.2), SAN qui fait foi (RFC 9525), `notBefore` reculé,
106
238
  * clé privée écrite en `0600`.
107
239
  */
108
240
  declare class Certificate extends Service {
@@ -114,8 +246,6 @@ declare class Certificate extends Service {
114
246
  cert: Buffer | string | null;
115
247
  fullchainPem: Buffer | string | null;
116
248
  publicKeyPem: Buffer | string | null;
117
- rootCertPem: Buffer | string | null;
118
- intermediateCertPem: Buffer | string | null;
119
249
  /** CAROOT résolu de mkcert (null tant que non détecté / indisponible). */
120
250
  private mkcertCaRoot;
121
251
  path: string;
@@ -173,6 +303,18 @@ declare class Certificate extends Service {
173
303
  * @param force - forcer la régénération même si un certificat valide existe
174
304
  */
175
305
  generateServerCertificates(force?: boolean): Promise<this>;
306
+ /**
307
+ * En développement, fait confiance à la CA que Nodefony vient de générer ou de
308
+ * relire, pour les requêtes SORTANTES du processus.
309
+ *
310
+ * Sans elle, un service du décor qui sert le certificat de l'application
311
+ * (Keycloak, l'auto-vérification d'un jeton MCP) est injoignable depuis
312
+ * `nodefony development` — `fetch failed` / `SELF_SIGNED_CERT_IN_CHAIN` — alors
313
+ * que `curl -k` répond : la confiance n'existait que sous le script de banc.
314
+ * Réservé au DÉVELOPPEMENT et aux certificats que Nodefony fabrique : un
315
+ * certificat `explicit` (production) n'élargit jamais la confiance du processus.
316
+ */
317
+ private trustDevelopmentAuthority;
176
318
  /**
177
319
  * Résout la stratégie effective à partir de `certificates.strategy` :
178
320
  * - `explicit` : `key` + `cert` fournis (prod, Let's Encrypt…). Forcé →
@@ -193,7 +335,7 @@ declare class Certificate extends Service {
193
335
  /**
194
336
  * SAN effectif : config explicite si fournie, sinon dérivé du kernel
195
337
  * (localhost + domain en DNS ; loopback en IP). Une IP littérale (ex. domain
196
- * `127.0.0.1` en dev) est classée en `ip`, pas en `dns` (RFC 6125).
338
+ * `127.0.0.1` en dev) est classée en `ip`, pas en `dns` (RFC 9525).
197
339
  */
198
340
  private derivedSan;
199
341
  /** Hostnames DNS couverts par le SAN. */
@@ -217,7 +359,7 @@ declare class Certificate extends Service {
217
359
  * @returns false si absent, illisible ou inadéquat → déclenche la régénération.
218
360
  */
219
361
  private isCertAdequate;
220
- /** Le SAN présent couvre-t-il tous les noms DNS requis (RFC 6125) ? */
362
+ /** Le SAN présent couvre-t-il tous les noms DNS requis (RFC 9525) ? */
221
363
  private sanCovers;
222
364
  /** Charge un certificat fourni en config (chemin fichier ou Buffer). */
223
365
  private loadExplicitCert;
@@ -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
  *
@@ -69,7 +100,7 @@ export declare function schemaMismatchOf(error: unknown): SchemaMismatchConfiden
69
100
  * reason: error.message
70
101
  *
71
102
  * Stateless singleton — zero per-request allocation. Override via
72
- * `httpKernel.setErrorRenderer(custom)` for prod hardening or RFC 7807.
103
+ * `httpKernel.setErrorRenderer(custom)` for prod hardening or RFC 9457.
73
104
  */
74
105
  declare class DefaultErrorRenderer implements IErrorRenderer {
75
106
  renderHttp(error: Error, context: IHttpContext): IErrorHttpResult;