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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/_virtual/{_@oxc-project_runtime@0.157.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorate.js +1 -1
  2. package/dist/_virtual/{_@oxc-project_runtime@0.157.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorateParam.js +1 -1
  3. package/dist/index.js +3 -2
  4. package/dist/nodefony/config/config.js +25 -2
  5. package/dist/nodefony/grpc/GrpcCall.js +298 -0
  6. package/dist/nodefony/grpc/GrpcEndpoint.js +528 -0
  7. package/dist/nodefony/grpc/index.js +4 -0
  8. package/dist/nodefony/interfaces/IRpcCall.js +1 -0
  9. package/dist/nodefony/service/audit-logger.js +52 -6
  10. package/dist/nodefony/service/error-renderer.js +41 -9
  11. package/dist/nodefony/service/http-kernel.js +198 -19
  12. package/dist/nodefony/service/pretty-request-logger.js +5 -3
  13. package/dist/nodefony/service/request-logger.js +21 -6
  14. package/dist/nodefony/service/reverse-proxy.js +1 -1
  15. package/dist/nodefony/service/servers/clientError.js +26 -1
  16. package/dist/nodefony/service/servers/server-h2c.js +3 -3
  17. package/dist/nodefony/service/servers/server-http.js +3 -3
  18. package/dist/nodefony/service/servers/server-https.js +4 -4
  19. package/dist/nodefony/service/servers/server-websocket-secure.js +3 -3
  20. package/dist/nodefony/service/servers/server-websocket.js +3 -3
  21. package/dist/nodefony/service/servers/serverShutdown.js +14 -36
  22. package/dist/nodefony/service/sessions/sessions-service.js +6 -6
  23. package/dist/nodefony/service/upload/upload-service.js +2 -2
  24. package/dist/nodefony/src/context/Context.js +12 -1
  25. package/dist/nodefony/src/context/http/ByteStream.js +9 -4
  26. package/dist/nodefony/src/context/http/HttpContext.js +86 -8
  27. package/dist/nodefony/src/context/http/Request.js +96 -16
  28. package/dist/nodefony/src/context/http/Response.js +120 -17
  29. package/dist/nodefony/src/context/http/SseStream.js +4 -4
  30. package/dist/nodefony/src/context/http/StreamLifecycle.js +328 -19
  31. package/dist/nodefony/src/context/http/parser.js +29 -11
  32. package/dist/nodefony/src/context/http/webResponse.js +56 -0
  33. package/dist/nodefony/src/context/http2/Response.js +47 -8
  34. package/dist/nodefony/src/context/requestTrace.js +6 -2
  35. package/dist/nodefony/src/context/rpcOutcome.js +20 -0
  36. package/dist/nodefony/src/context/websocket/Response.js +11 -1
  37. package/dist/nodefony/src/context/websocket/WebsocketContext.js +15 -4
  38. package/dist/nodefony/src/cookies/cookie.js +3 -1
  39. package/dist/nodefony/src/errors/httpError.js +58 -25
  40. package/dist/nodefony/src/grpc/cors.js +59 -0
  41. package/dist/nodefony/src/grpc/limits.js +16 -0
  42. package/dist/nodefony/src/grpc/refusal.js +271 -0
  43. package/dist/nodefony/src/profiler/aggregate.js +2 -2
  44. package/dist/nodefony/src/proxy/forward.js +1 -1
  45. package/dist/nodefony/src/proxy/generateProxyConfig.js +91 -15
  46. package/dist/nodefony/src/servers/http2Goaway.js +76 -0
  47. package/dist/nodefony/src/servers/http2Limits.js +41 -15
  48. package/dist/nodefony/src/servers/http2StreamGrace.js +100 -0
  49. package/dist/nodefony/src/servers/receiveDeadline.js +177 -0
  50. package/dist/types/index.d.ts +5 -2
  51. package/dist/types/nodefony/config/config.d.ts +10 -0
  52. package/dist/types/nodefony/grpc/GrpcCall.d.ts +142 -0
  53. package/dist/types/nodefony/grpc/GrpcEndpoint.d.ts +98 -0
  54. package/dist/types/nodefony/grpc/index.d.ts +15 -0
  55. package/dist/types/nodefony/interfaces/IContext.d.ts +9 -0
  56. package/dist/types/nodefony/interfaces/IErrorRenderer.d.ts +14 -0
  57. package/dist/types/nodefony/interfaces/IRequest.d.ts +2 -1
  58. package/dist/types/nodefony/interfaces/IRequestTrace.d.ts +10 -0
  59. package/dist/types/nodefony/interfaces/IRouting.d.ts +19 -0
  60. package/dist/types/nodefony/interfaces/IRpcCall.d.ts +83 -0
  61. package/dist/types/nodefony/interfaces/IUpload.d.ts +51 -4
  62. package/dist/types/nodefony/interfaces/index.d.ts +2 -1
  63. package/dist/types/nodefony/service/audit-logger.d.ts +33 -3
  64. package/dist/types/nodefony/service/error-renderer.d.ts +22 -1
  65. package/dist/types/nodefony/service/http-kernel.d.ts +21 -1
  66. package/dist/types/nodefony/service/request-logger.d.ts +10 -0
  67. package/dist/types/nodefony/service/servers/serverShutdown.d.ts +2 -2
  68. package/dist/types/nodefony/src/context/Context.d.ts +9 -1
  69. package/dist/types/nodefony/src/context/http/ByteStream.d.ts +1 -1
  70. package/dist/types/nodefony/src/context/http/HttpContext.d.ts +56 -2
  71. package/dist/types/nodefony/src/context/http/Request.d.ts +38 -8
  72. package/dist/types/nodefony/src/context/http/Response.d.ts +61 -7
  73. package/dist/types/nodefony/src/context/http/SseStream.d.ts +1 -1
  74. package/dist/types/nodefony/src/context/http/StreamLifecycle.d.ts +53 -0
  75. package/dist/types/nodefony/src/context/http/parser.d.ts +17 -3
  76. package/dist/types/nodefony/src/context/http/webResponse.d.ts +31 -0
  77. package/dist/types/nodefony/src/context/http2/Response.d.ts +35 -2
  78. package/dist/types/nodefony/src/context/requestTrace.d.ts +3 -1
  79. package/dist/types/nodefony/src/context/rpcOutcome.d.ts +11 -0
  80. package/dist/types/nodefony/src/context/websocket/Response.d.ts +6 -1
  81. package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +8 -0
  82. package/dist/types/nodefony/src/errors/httpError.d.ts +29 -4
  83. package/dist/types/nodefony/src/grpc/cors.d.ts +32 -0
  84. package/dist/types/nodefony/src/grpc/limits.d.ts +14 -0
  85. package/dist/types/nodefony/src/grpc/refusal.d.ts +136 -0
  86. package/dist/types/nodefony/src/proxy/forward.d.ts +7 -0
  87. package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +15 -2
  88. package/dist/types/nodefony/src/servers/http2Goaway.d.ts +41 -0
  89. package/dist/types/nodefony/src/servers/http2Limits.d.ts +27 -14
  90. package/dist/types/nodefony/src/servers/http2StreamGrace.d.ts +34 -0
  91. package/dist/types/nodefony/src/servers/receiveDeadline.d.ts +77 -0
  92. package/docs/byte-stream.md +52 -17
  93. package/docs/diagnostics-channel.md +45 -1
  94. package/docs/servers.md +57 -44
  95. package/docs/upload.md +43 -17
  96. package/package.json +26 -6
@@ -1,5 +1,6 @@
1
1
  import type { MaybePromise } from "nodefony";
2
2
  import type { ContextType } from "../service/http-kernel.js";
3
+ import type { IRouteUploadOptions } from "./IUpload.js";
3
4
  /**
4
5
  * Ce que le pipeline HTTP/WS lit d'une route résolue — et rien de plus.
5
6
  *
@@ -22,6 +23,24 @@ export interface IResolvedRoute {
22
23
  } | undefined;
23
24
  /** Corps laissé en flux (`@Body({ stream: true })`) : le parse est sauté. */
24
25
  readonly bodyStream?: boolean | undefined;
26
+ /**
27
+ * Route servie par un point d'entrée gRPC (`markGrpcRoute`) : une requête
28
+ * gRPC native n'est acceptée que sur une telle route (ADR-0026 D13 point 3).
29
+ *
30
+ * @experimental Surface gRPC, ADR-0026 D11.
31
+ */
32
+ readonly grpc?: boolean | undefined;
33
+ /**
34
+ * Délai de réponse propre à la route (ms) ; `null` ou absent = celui du
35
+ * serveur. Ré-armé par `HttpContext.handle` une fois la route résolue.
36
+ */
37
+ readonly responseTimeout?: number | null | undefined;
38
+ /**
39
+ * Bornes d'envoi propres à la route (option `upload`), fusionnées sur celles
40
+ * de la configuration à la lecture du corps ; `null` ou absent = celles de
41
+ * la configuration.
42
+ */
43
+ readonly upload?: IRouteUploadOptions | null | undefined;
25
44
  }
26
45
  /**
27
46
  * Ce que le pipeline HTTP/WS attend du résolveur d'une requête, posé sur
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Le protocole RPC parlé sur le fil : `grpc`, `grpc-web` ou `connect` pour un
3
+ * appel gRPC (ADR-0026 D10). Union OUVERTE : un appel JSON-RPC (porte MCP,
4
+ * A2A) pourra s'y ajouter (`jsonrpc`) sans rupture.
5
+ *
6
+ * @experimental Surface gRPC, ADR-0026 D11.
7
+ */
8
+ export type RpcSystem = "grpc" | "grpc-web" | "connect" | (string & {});
9
+ /**
10
+ * Le résultat d'un appel RPC tel que le voient la ligne d'accès, l'audit et le
11
+ * canal de traçage (ADR-0026 D10) : quatre champs, jamais la fiche d'appel
12
+ * entière — elle porte le contrôleur et le contexte.
13
+ *
14
+ * @experimental Surface gRPC, ADR-0026 D11.
15
+ */
16
+ export interface IRpcOutcome {
17
+ /** Le protocole parlé sur le fil. */
18
+ readonly system: RpcSystem;
19
+ /**
20
+ * La méthode appelée, `paquet.Service/Méthode` — tirée de la route : jamais
21
+ * une valeur inventée (`_OTHER`), la route est connue.
22
+ */
23
+ readonly method: string;
24
+ /**
25
+ * Le code de l'appel (gRPC : 0 à 16), lu une fois au `close` de la réponse.
26
+ * `null` avant, ou quand l'appel n'a jamais commencé (requête refusée par le
27
+ * transport avant tout contexte d'appel : le statut HTTP dit alors tout).
28
+ */
29
+ readonly code: number | null;
30
+ /** Le nom du code (`OK`, `UNAVAILABLE`…), `null` avec lui. */
31
+ readonly codeName: string | null;
32
+ }
33
+ /**
34
+ * Un appel RPC servi sur une requête HTTP — gRPC, gRPC-Web ou Connect —, porté
35
+ * par `HttpContext.rpc`, `null` hors appel RPC (ADR-0026 D10).
36
+ *
37
+ * Le contrat vit dans `@nodefony/http` et ne nomme rien de l'adaptateur : un
38
+ * appel JSON-RPC pourra le porter sans rupture. La fiche d'appel gRPC
39
+ * (`GrpcCall`, sous-chemin `@nodefony/http/grpc`) l'implémente : aucune
40
+ * allocation de plus par appel.
41
+ *
42
+ * @experimental Surface gRPC, ADR-0026 D11.
43
+ */
44
+ export interface IRpcCall extends IRpcOutcome {
45
+ /**
46
+ * Le délai de réponse du serveur est échu : termine l'appel au format de
47
+ * son protocole (gRPC : 4 `DEADLINE_EXCEEDED`). Appelé par
48
+ * `HttpContext._onTimeout` À LA PLACE du 503, qui serait un second
49
+ * écrivain sur une réponse que l'adaptateur possède (ADR-0026 D4, point 3).
50
+ * Sans effet sur un appel déjà terminé.
51
+ */
52
+ expire(): void;
53
+ /**
54
+ * L'identité qui a ouvert l'appel n'est plus valable (jeton révoqué,
55
+ * session fermée, droits changés) : termine l'appel au format de son
56
+ * protocole (gRPC : 16 `UNAUTHENTICATED`). Appelé par le cycle de vie d'un
57
+ * flux À LA PLACE de la destruction du transport, qui ne dirait au client
58
+ * qu'une coupure (ADR-0026 D6). Sans effet sur un appel déjà terminé.
59
+ */
60
+ revoke(): void;
61
+ /**
62
+ * Le serveur s'arrête : termine l'appel au format de son protocole (gRPC :
63
+ * 14 `UNAVAILABLE`, « réessaie, ailleurs »). Appelé par le noyau à l'arrêt,
64
+ * AVANT le drain des connexions, pour chaque flux long encore ouvert
65
+ * (ADR-0026 D8) — à la place de la destruction du transport à
66
+ * `shutdownTimeout`, qui ne dirait au client qu'une coupure. Sans effet sur
67
+ * un appel déjà terminé, ou déjà interrompu.
68
+ */
69
+ shutdown(): void;
70
+ /**
71
+ * Fixe le code de l'appel, une fois, au `close` de la réponse — appelé par
72
+ * le noyau AVANT la ligne d'accès (`HttpKernel.teardownHttp`, ADR-0026 D10
73
+ * point 2). Les appels suivants ne changent rien.
74
+ *
75
+ * @param ended - la réponse a-t-elle été terminée par le serveur
76
+ * (`responseEnded`) ? Un appel sans motif d'interruption est alors réussi
77
+ * (0), sinon annulé par le client (1).
78
+ * @returns la cause à porter par la ligne d'accès (`context.error`) quand
79
+ * l'appel a échoué sur une exception — UNE ligne par appel en erreur
80
+ * (D10 point 3) —, `null` sinon.
81
+ */
82
+ settle(ended: boolean): Error | null;
83
+ }
@@ -1,3 +1,4 @@
1
+ import type { FileClass } from "nodefony";
1
2
  /**
2
3
  * Forme neutre d'un fichier multipart parsé (temp écrit sur disque), découplée
3
4
  * du parser sous-jacent. Remplace l'ex-couplage à `formidable.File` : le moteur
@@ -44,18 +45,64 @@ export interface IUploadOptions {
44
45
  /** Encodage par défaut des parts texte. */
45
46
  encoding?: string | undefined;
46
47
  }
48
+ /**
49
+ * Les bornes d'envoi qu'une ROUTE règle pour elle-même (option de route
50
+ * `upload`). Chaque clé déclarée remplace celle de la configuration http,
51
+ * dans les deux sens : un dépôt de vidéos relève `maxFileSize` sans le
52
+ * relever pour les autres routes, un avatar l'abaisse à quelques centaines de
53
+ * kilo-octets — et le fichier trop gros est coupé PENDANT le flux, avant
54
+ * d'avoir rempli le disque. Une clé absente garde la valeur de la
55
+ * configuration.
56
+ *
57
+ * Lues au moment de lire le corps, quand la route est déjà résolue : une route
58
+ * qui ne déclare rien ne paie rien. Le dossier de dépôt et l'encodage restent
59
+ * ceux de la configuration.
60
+ */
61
+ export interface IRouteUploadOptions {
62
+ /** Taille max d'UN fichier (octets). */
63
+ readonly maxFileSize?: number | undefined;
64
+ /** Taille max CUMULÉE des fichiers de la requête (octets). */
65
+ readonly maxTotalFileSize?: number | undefined;
66
+ /** Nombre max de fichiers. */
67
+ readonly maxFiles?: number | undefined;
68
+ /** Nombre max de champs texte. */
69
+ readonly maxFields?: number | undefined;
70
+ /** Taille max d'un champ texte (octets). */
71
+ readonly maxFieldsSize?: number | undefined;
72
+ /** Empreinte calculée pendant le flux (`false` = aucune). */
73
+ readonly hashAlgorithm?: false | "sha1" | "md5" | "sha256" | undefined;
74
+ }
75
+ /**
76
+ * Un fichier reçu dans un corps multipart, déjà écrit dans le dossier
77
+ * d'upload sous un nom temporaire.
78
+ *
79
+ * Le temporaire est EFFACÉ à la fin de la requête (réponse envoyée ou client
80
+ * parti) : pour garder le fichier, l'action le déplace avec `moveAsync()`
81
+ * avant de répondre. Un refus (pare-feu, garde, erreur de l'action) ne laisse
82
+ * donc rien sur le disque.
83
+ */
47
84
  export interface IUploadedFile {
48
85
  filename: string;
49
86
  size: number;
50
87
  prettySize: string;
51
- mimeType: string | null | undefined;
88
+ /**
89
+ * Le type DÉCLARÉ par le client, sinon deviné de l'extension ; `false`
90
+ * quand ni l'un ni l'autre ne le disent. Déclaratif : jamais une preuve du
91
+ * contenu.
92
+ */
93
+ mimeType: string | false;
52
94
  hash: string | null | undefined;
53
95
  hashAlgorithm: false | "sha1" | "md5" | "sha256";
54
96
  lastModifiedDate: Date | null | undefined;
55
- move(target: string): IUploadedFile;
97
+ /**
98
+ * Déplace le fichier vers `target` (un fichier, ou un dossier existant) et
99
+ * rend le fichier de DESTINATION — un `FileClass`, pas un fichier reçu : le
100
+ * nom d'origine, la taille déclarée et l'empreinte restent sur celui-ci.
101
+ */
102
+ move(target: string): FileClass;
56
103
  /** Variante non bloquante de `move()` (recommandée dans le pipeline). */
57
- moveAsync(target: string): Promise<IUploadedFile>;
58
- getMimeType(): string | null | undefined;
104
+ moveAsync(target: string): Promise<FileClass>;
105
+ getMimeType(): string | false;
59
106
  getSize(): number;
60
107
  getPrettySize(): string;
61
108
  }
@@ -1,6 +1,6 @@
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
- export type { IUploadedFile, IUploadService, IParsedUploadFile, IUploadOptions, } from "./IUpload.js";
3
+ export type { IUploadedFile, IUploadService, IParsedUploadFile, IUploadOptions, IRouteUploadOptions, } from "./IUpload.js";
4
4
  export type { IRequest, IHttpRequest, RequestBody, IHttp2Request, IWsRequest, HTTPMethodType, } from "./IRequest.js";
5
5
  export type { IResponse, IHttpResponse, IWebsocketResponse } from "./IResponse.js";
6
6
  export type { IContext, IHttpContext, IWebsocketContext, ServerType, ProtocolName, AlpnProtocol, SchemeType, WebSocketStateType, CookiesMap, } from "./IContext.js";
@@ -8,4 +8,5 @@ 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
10
  export type { IRequestTrace, RequestTraceProtocol } from "./IRequestTrace.js";
11
+ export type { IRpcCall, IRpcOutcome, RpcSystem } from "./IRpcCall.js";
11
12
  export type { IProxyMount, IProxyMountOptions, ProxyTarget, } from "./IReverseProxy.js";
@@ -1,6 +1,7 @@
1
1
  import type { Severity } from "nodefony";
2
2
  import type { IRequestLogger, IRequestLogEntry } from "../interfaces/IRequestLogger.js";
3
3
  import type { IContext, IHttpContext, IWebsocketContext } from "../interfaces/IContext.js";
4
+ import type { IRpcOutcome } from "../interfaces/IRpcCall.js";
4
5
  declare const statusOf: (context: IContext) => number | null;
5
6
  /**
6
7
  * Canonical audit log entry — 1 JSON PDU per request (P3.1).
@@ -35,6 +36,15 @@ export interface AuditLogEntry {
35
36
  name: string;
36
37
  durationMs: number | null;
37
38
  }[] | undefined;
39
+ /**
40
+ * L'appel RPC de la requête (système, méthode, code, nom), `null` hors
41
+ * RPC et pour une WebSocket : forme stable (ADR-0026 D10 point 4) —
42
+ * toujours posé par `JsonAuditLogger` ; optionnel au type, pour qu'un
43
+ * journal maison qui bâtit une entrée ne casse pas.
44
+ *
45
+ * @experimental Surface gRPC, ADR-0026 D11.
46
+ */
47
+ rpc?: IRpcOutcome | null | undefined;
38
48
  error?: AuditErrorEntry | undefined;
39
49
  protocol?: string | null | undefined;
40
50
  }
@@ -59,6 +69,25 @@ export interface AuditErrorEntry {
59
69
  * Unknown/missing status → INFO.
60
70
  */
61
71
  declare function severityFromStatus(status: number | null | undefined): Severity;
72
+ /**
73
+ * Gravité d'un appel RPC selon son code (ADR-0026 D10 point 4) : 0 → INFO ;
74
+ * une erreur du serveur ({@link RPC_SERVER_ERRORS}) → ERROR ; les autres —
75
+ * refus, échecs métier ou client — → WARNING.
76
+ *
77
+ * @param code - le code de l'appel (gRPC : 0 à 16).
78
+ * @returns la gravité de la ligne.
79
+ */
80
+ declare function severityFromRpcCode(code: number): Severity;
81
+ /**
82
+ * Gravité de la ligne d'une requête HTTP : celle du code de son appel RPC
83
+ * quand il est fixé — le statut HTTP d'un appel gRPC reste 200 quand l'appel
84
+ * échoue —, sinon celle de son statut.
85
+ *
86
+ * @param context - le contexte de la requête.
87
+ * @param status - son statut HTTP.
88
+ * @returns la gravité de la ligne.
89
+ */
90
+ declare function severityOfHttp(context: IHttpContext, status: number | null | undefined): Severity;
62
91
  export interface JsonAuditLoggerOptions {
63
92
  /**
64
93
  * Whether to include `error.stack` and recursive `error.cause.stack`.
@@ -115,8 +144,9 @@ declare class JsonAuditLogger implements IRequestLogger {
115
144
  /**
116
145
  * Decide whether the current HTTP request must be logged (audit sampling).
117
146
  *
118
- * Always `true` when `sampleRate <= 1`, on errors, and for `status >= 400`
119
- * (failures are never sampled out). Otherwise selects 1 in `sampleRate` of
147
+ * Always `true` when `sampleRate <= 1`, on errors, for `status >= 400` and
148
+ * for a failed RPC call — whose HTTP status may stay 200 (failures are
149
+ * never sampled out). Otherwise selects 1 in `sampleRate` of
120
150
  * the 2xx/3xx requests with a deterministic counter.
121
151
  *
122
152
  * Called by `Context.logRequest()` **before** `renderHttp`, so a sampled-out
@@ -137,4 +167,4 @@ declare class JsonAuditLogger implements IRequestLogger {
137
167
  private serializeError;
138
168
  }
139
169
  export default JsonAuditLogger;
140
- export { severityFromStatus, statusOf };
170
+ export { severityFromRpcCode, severityFromStatus, severityOfHttp, statusOf };
@@ -1,4 +1,4 @@
1
- import type { IErrorRenderer, IErrorHttpResult, IErrorWebsocketResult } from "../interfaces/IErrorRenderer.js";
1
+ import type { IErrorRenderer, IErrorHttpResult, IErrorWebsocketResult, IErrorDescription } from "../interfaces/IErrorRenderer.js";
2
2
  import type { IHttpContext, IWebsocketContext } from "../interfaces/IContext.js";
3
3
  /** Champ fautif, tel qu'exposé au client dans le corps JSON (`error.fields`). */
4
4
  export interface IValidationField {
@@ -106,6 +106,27 @@ export declare function schemaMismatchOf(error: unknown): SchemaMismatchConfiden
106
106
  declare class DefaultErrorRenderer implements IErrorRenderer {
107
107
  renderHttp(error: Error, context: IHttpContext): IErrorHttpResult;
108
108
  renderWebsocket(error: Error, context: IWebsocketContext): IErrorWebsocketResult;
109
+ /**
110
+ * Le statut HTTP et le message client qu'une route HTTP rendrait pour cette
111
+ * erreur — mêmes classements que {@link renderHttp} (validation 422, conflit
112
+ * 409, identifiant malformé, code numérique) et même opacité d'un 5xx en
113
+ * production —, sans RIEN écrire : ni statut sur la réponse, ni `metaData`,
114
+ * ni pile formatée. Sert les transports sans corps d'erreur : un appel gRPC
115
+ * traduit ce statut en code (ADR-0026 D7).
116
+ *
117
+ * Un identifiant malformé y vaut 400 : sans contexte, aucun chemin ne le
118
+ * désigne.
119
+ *
120
+ * @param error - l'erreur levée.
121
+ * @returns son statut émettable et le message destiné au client.
122
+ *
123
+ * @experimental Surface gRPC, ADR-0026 D11.
124
+ */
125
+ describe(error: Error): IErrorDescription;
126
+ /**
127
+ * Ramène une erreur à une `HttpError`. Sans contexte (`describe`), rien
128
+ * n'est écrit : `HttpError` ne pose le statut sur la réponse qu'avec lui.
129
+ */
109
130
  private toHttpError;
110
131
  /**
111
132
  * Ramène un `code` d'erreur à un statut HTTP réellement émettable.
@@ -216,7 +216,7 @@ declare class HttpKernel extends Service implements IHttpKernelInterface {
216
216
  * Le message nomme l'action, ce qu'elle a rendu et le geste qui convient
217
217
  * (`unsentResponseWarning`).
218
218
  *
219
- * Appelé au timeout, AVANT que le 504/408 parte — après, `sended` est vrai et
219
+ * Appelé au timeout, AVANT que le 503 parte — après, `sended` est vrai et
220
220
  * plus rien ne se signalerait — et au démontage, pour un client parti avant.
221
221
  * Gratuit en production : rien n'est lu au-delà de `waitAsync`.
222
222
  *
@@ -483,6 +483,17 @@ declare class HttpKernel extends Service implements IHttpKernelInterface {
483
483
  * @returns `undefined` quand le démontage est terminé ; sinon la promesse de
484
484
  * sa fin, qui ne rejette pas.
485
485
  */
486
+ /**
487
+ * Fixe le code d'un appel RPC au `close` : « réponse finie » est la règle
488
+ * unique du noyau ({@link responseEnded}, celle du 499), jamais
489
+ * `writableFinished`, qui dit « finie » sur une annulation HTTP/2. Une cause
490
+ * rendue par l'appel devient l'erreur de la requête : la ligne d'accès la
491
+ * porte, le canal la publie — une ligne par appel en erreur (D10 point 3).
492
+ *
493
+ * @param context - le contexte de la requête.
494
+ * @param rpc - son appel RPC.
495
+ */
496
+ private settleRpc;
486
497
  private teardownHttp;
487
498
  /**
488
499
  * Suite ASYNCHRONE du démontage, quand il y a des hooks à attendre :
@@ -558,6 +569,15 @@ declare class HttpKernel extends Service implements IHttpKernelInterface {
558
569
  * N'attend que le repli statique, et seulement quand aucune route ne matche.
559
570
  */
560
571
  private routeHttpRequest;
572
+ /**
573
+ * L'échéance de réception HTTP/2 du serveur qui sert (`requestTimeout` de sa
574
+ * section), créée au premier corps HTTP/2 lu ; `null` si le délai vaut 0.
575
+ *
576
+ * À l'échéance, la réception en cours est interrompue (`abortBody`) et la
577
+ * requête rendue en 408 par le chemin d'erreur habituel — comme un corps qui
578
+ * dépasse sa limite (413).
579
+ */
580
+ private http2ReceiveDeadline;
561
581
  /**
562
582
  * Le corps — lu seulement s'il y en a un à lire —, puis les gardes
563
583
  * ({@link onRequestEnd}) et l'action. Chacun n'est attendu que s'il attend.
@@ -1,5 +1,15 @@
1
1
  import type { IRequestLogger, IRequestLogEntry } from "../interfaces/IRequestLogger.js";
2
2
  import type { IHttpContext, IWebsocketContext } from "../interfaces/IContext.js";
3
+ import type { IRpcOutcome } from "../interfaces/IRpcCall.js";
4
+ /**
5
+ * Segment d'un appel RPC sur la ligne d'accès : `RPC : <méthode> <NOM>`
6
+ * (ADR-0026 D10 point 4) — le statut HTTP reste à sa place. Sans code (appel
7
+ * jamais commencé), la méthode seule.
8
+ *
9
+ * @param rpc - le résultat de l'appel.
10
+ * @returns le segment, sans espace de tête.
11
+ */
12
+ export declare function rpcSegment(rpc: IRpcOutcome): string;
3
13
  /**
4
14
  * Default Nodefony per-request logger — preserves the legacy colored format.
5
15
  *
@@ -30,8 +30,8 @@ export interface IDrainTerminator {
30
30
  *
31
31
  * ⚠️ Les sockets WebSocket upgradées (sans réponse HTTP en cours) sont détruites
32
32
  * SANS frame Close → les serveurs WS doivent fermer leurs clients (close 1001)
33
- * AVANT ce drain. Garanti par l'ordre des listeners `onTerminate` : WS/WSS
34
- * s'attachent en `prependOnceListener`, les serveurs HTTP en `once`.
33
+ * AVANT ce drain. Garanti par `HttpKernel.drainTraffic` (ADR-0027 D2) : l'étape
34
+ * `dismiss` (1001) est attendue avant l'étape `drain`.
35
35
  *
36
36
  * @param server - serveur Node à drainer, créé AVANT son `listen` (le suivi des
37
37
  * connexions commence ici)
@@ -1,5 +1,5 @@
1
1
  import { Container, Service, Severity, Msgid, Message, Pci, Pdu, KernelEventsType, nodefonyError, Scope } from "nodefony";
2
- import type { IProfilerQuery, RequestContextPayload } from "nodefony";
2
+ import type { IProfilerQuery, IRequestUser, RequestContextPayload } from "nodefony";
3
3
  import type { IRouteResolver, IRequestRouter } from "../../interfaces/IRouting.js";
4
4
  import type { ISecurityZone } from "../../interfaces/ISecurity.js";
5
5
  import { WebSocketServer } from "ws";
@@ -213,6 +213,14 @@ declare class Context extends Service implements IContextInterface {
213
213
  * @param store - le magasin passé à `RequestContext.run`
214
214
  */
215
215
  holdRequestStore(store: RequestContextPayload): void;
216
+ /**
217
+ * L'utilisateur authentifié de l'unité de travail, lu sur son magasin et
218
+ * non sur l'ALS : valable là où la bulle n'est plus active (rendu d'une
219
+ * erreur, démontage). Même règle que `RequestContext.getAuthenticatedUser`.
220
+ *
221
+ * @returns l'utilisateur connecté, ou `null` (anonyme, hors zone, nettoyé).
222
+ */
223
+ authenticatedUser(): IRequestUser | null;
216
224
  clean(): void;
217
225
  /**
218
226
  * Log d'un event du cycle de vie — gate boot-time (V2.1) : en production le
@@ -25,7 +25,7 @@ import type HttpContext from "./HttpContext.js";
25
25
  * Raison de la fin d'un flux d'octets.
26
26
  *
27
27
  * - `end` : la source est finie, ou le serveur a fermé le flux (durée de vie,
28
- * révocation de l'identité) ;
28
+ * révocation de l'identité, arrêt du serveur pour un flux `live`) ;
29
29
  * - `client` : le client est parti ;
30
30
  * - `error` : la source a échoué ;
31
31
  * - `timeout` : la source est restée muette plus de `idleTimeout` ;
@@ -45,7 +45,7 @@ export type HttpRequestType = Http2Request | HttpRequest;
45
45
  export type HttpRsponseType = Http2Response | HttpResponse;
46
46
  /**
47
47
  * Route le délai d'inactivité d'une socket HTTP/1.1 vers la requête qu'elle
48
- * porte : 408/504 si une requête est en cours, fermeture si la connexion
48
+ * porte : 503 si une requête est en cours, fermeture si la connexion
49
49
  * persistante est inactive. Écouteur `timeout` du SERVEUR — un seul pour
50
50
  * toutes ses connexions —, que Node appelle avec la socket en argument
51
51
  * (`socketOnTimeout` → `server.emit("timeout", socket)`).
@@ -61,10 +61,12 @@ export type HttpRsponseType = Http2Response | HttpResponse;
61
61
  * serveur supprime aussi une fermeture et une entrée de registre par connexion.
62
62
  *
63
63
  * @param socket - la socket dont le délai a expiré ; une session HTTP/2 (même
64
- * événement sur un serveur `allowHTTP1`) est laissée à son propre délai
64
+ * événement sur un serveur `allowHTTP1`) est fermée par l'écouteur que pose
65
+ * `setHttp2SessionTimeout` (`src/servers/http2Limits.ts`)
65
66
  */
66
67
  export declare function routeSocketTimeout(socket: unknown): void;
67
68
  import type { IHttpContext as IHttpContextInterface } from "../../../interfaces/IContext.js";
69
+ import type { IRpcCall } from "../../../interfaces/IRpcCall.js";
68
70
  import { type ISseStreamOptions, type SseStream } from "./SseStream.js";
69
71
  import { type ByteStreamSource, type IByteStreamOptions, type IByteStreamResult } from "./ByteStream.js";
70
72
  declare class HttpContext extends Context implements IHttpContextInterface {
@@ -78,6 +80,15 @@ declare class HttpContext extends Context implements IHttpContextInterface {
78
80
  * son montage. `null` = servie par l'application (cas de toute requête).
79
81
  */
80
82
  relayAccessLog: "all" | "errors" | null;
83
+ /**
84
+ * L'appel RPC servi sur cette requête (gRPC, gRPC-Web, Connect), `null`
85
+ * hors appel RPC (ADR-0026 D10) : un champ, aucune allocation. Posé par le
86
+ * point d'entrée gRPC ; à l'échéance du délai de réponse, {@link _onTimeout}
87
+ * le termine au lieu d'écrire un 503.
88
+ *
89
+ * @experimental Surface gRPC, ADR-0026 D11.
90
+ */
91
+ rpc: IRpcCall | null;
81
92
  request: HttpRequestType;
82
93
  response: HttpRsponseType;
83
94
  uploadService: uploadService | null;
@@ -97,6 +108,18 @@ declare class HttpContext extends Context implements IHttpContextInterface {
97
108
  * d'éventuels listeners externes — guard 0-listener : 0 alloc sinon.
98
109
  */
99
110
  _onTimeout(): void;
111
+ /**
112
+ * Ré-arme le délai de réponse de la requête à la valeur propre de sa route
113
+ * (`responseTimeout` d'une option de route), une fois celle-ci résolue.
114
+ *
115
+ * Même mécanisme que {@link setTimeout} — délai d'inactivité de la socket
116
+ * (HTTP/1.1) ou du flux (HTTP/2) —, sans second écouteur : sous HTTP/2,
117
+ * `setTimeout(ms)` sans rappel remplace le minuteur et garde celui posé par
118
+ * {@link setTimeout}. `0` désactive le délai pour la requête.
119
+ *
120
+ * @param ms - délai de la route, en millisecondes.
121
+ */
122
+ rearmTimeout(ms: number): void;
100
123
  setScheme(): SchemeType;
101
124
  /**
102
125
  * Sert la requête : délai de réponse, hooks `onRequest`, puis l'action
@@ -117,9 +140,21 @@ declare class HttpContext extends Context implements IHttpContextInterface {
117
140
  * réellement rendu (le relais écrit la réponse brute, pas celle du
118
141
  * contexte) ; un montage `accessLog: "errors"` ne l'écrit qu'en échec.
119
142
  *
143
+ * Le statut natif n'est recopié que si la réponse a été TERMINÉE
144
+ * ({@link responseEnded}) : sinon il n'a jamais atteint le client (200 par
145
+ * défaut de Node), et le statut du contexte — 499 d'un client parti — dit
146
+ * vrai. Règle unique pour toute réponse écrite par un tiers (#695, critère
147
+ * 38).
148
+ *
120
149
  * @param httpError - l'erreur de la requête, s'il y en a une
121
150
  */
122
151
  logRequest(httpError?: Error | HttpError | nodefonyError | null): import("nodefony").Pdu | undefined;
152
+ /**
153
+ * Reprend le statut de la réponse native, écrite par un tiers (relais,
154
+ * adaptateur RPC) — si la réponse est TERMINÉE seulement : sinon ce statut
155
+ * n'a jamais atteint le client, et celui du contexte (499) dit vrai.
156
+ */
157
+ private adoptNativeStatus;
123
158
  /**
124
159
  * Nettoie le contexte, et le DÉTACHE de sa socket h1 : la connexion
125
160
  * persistante survit à la requête, et l'entrée de `socketActiveContext`
@@ -204,6 +239,25 @@ declare class HttpContext extends Context implements IHttpContextInterface {
204
239
  * Quand l'appel lève, la source est DÉTRUITE (un flux web est annulé).
205
240
  */
206
241
  openStream(source: ByteStreamSource, options?: IByteStreamOptions): Promise<IByteStreamResult>;
242
+ /**
243
+ * Répond par une `Response` Web standard — celle que rend `fetch()`, ou une
244
+ * réponse construite (`new Response(…)`, `Response.json(…)`) : son statut,
245
+ * ses en-têtes, et son corps EN FLUX, jamais lu d'avance.
246
+ *
247
+ * C'est ce que fait le framework quand une action renvoie une `Response`.
248
+ * Une réponse relayée (issue de `fetch`) perd `content-encoding`,
249
+ * `content-length` et `set-cookie` de l'amont ; une réponse construite garde
250
+ * les siens. Les en-têtes de connexion ne partent jamais. Le reste — arrêt au
251
+ * départ du client, contre-pression, délais, HEAD sans lire le corps — est
252
+ * celui de {@link HttpContext.openStream}.
253
+ *
254
+ * @param response - une `Response` dont le corps n'a pas été consommé.
255
+ * @param options - délais et écouteur de fin ; statut et en-têtes viennent de la réponse.
256
+ * @returns la raison de la fin et le nombre d'octets ; ne rejette jamais.
257
+ * @throws HttpError 502 pour `Response.error()` (statut 0, aucune réponse) ;
258
+ * les cas de {@link HttpContext.openStream} sinon.
259
+ */
260
+ openWebResponse(response: Response, options?: Omit<IByteStreamOptions, "status" | "headers">): Promise<IByteStreamResult>;
207
261
  flush(chunk: unknown, encoding: BufferEncoding): MaybePromise<HttpResponse>;
208
262
  /**
209
263
  * Termine la requête : hook `onClose`, puis fin de la réponse si l'envoi ne
@@ -10,9 +10,20 @@ import { UploadedFile } from "../../../service/upload/upload-service.js";
10
10
  import type { IParsedUploadFile, IUploadOptions } from "../../../interfaces/IUpload.js";
11
11
  import { Pci, Pdu, Message, Severity, Msgid } from "nodefony";
12
12
  import Session from "../../session/session.js";
13
+ import HttpError from "../../errors/httpError.js";
13
14
  import { type ResolvedProxy } from "../forwarded.js";
14
15
  declare class BodyHandled {
15
16
  }
17
+ /**
18
+ * Une réception multipart EN COURS, interruptible de l'extérieur : `fail` rompt
19
+ * la lecture comme le ferait un dépassement de limite (temporaires nettoyés).
20
+ * Posée dans `request.parser` le temps de la lecture seulement — l'allocation
21
+ * est réservée au multipart, déjà lourd.
22
+ */
23
+ declare class MultipartReception {
24
+ readonly fail: (error: HttpError) => boolean;
25
+ constructor(fail: (error: HttpError) => boolean);
26
+ }
16
27
  declare module "url" {
17
28
  interface URL {
18
29
  query: QS.ParsedQs;
@@ -31,7 +42,7 @@ declare module "http2" {
31
42
  session: Session;
32
43
  }
33
44
  }
34
- type ParserType = ParserXml | ParserQs | Parser | BodyHandled;
45
+ type ParserType = ParserXml | ParserQs | Parser | BodyHandled | MultipartReception;
35
46
  declare class HttpRequest {
36
47
  #private;
37
48
  context: HttpContext;
@@ -88,6 +99,9 @@ declare class HttpRequest {
88
99
  * temporaire — lus par `@UploadedFile()` / `@UploadedFiles()`. Les champs
89
100
  * texte du même corps sont dans {@link body}.
90
101
  *
102
+ * Ces temporaires sont EFFACÉS à la fin de la requête : pour garder un
103
+ * fichier, l'action le déplace (`moveAsync`) avant de répondre.
104
+ *
91
105
  * Lecture seule : une requête sans upload rend un vide partagé et gelé,
92
106
  * aucune allocation.
93
107
  */
@@ -150,6 +164,16 @@ declare class HttpRequest {
150
164
  * @returns `undefined` quand tout est fait, sinon la promesse à attendre.
151
165
  */
152
166
  private endWithoutBody;
167
+ /**
168
+ * Lit le plafond du corps et les limites d'envoi tels que les voit CETTE
169
+ * requête : la configuration http, surchargée par le calque que la requête a
170
+ * pu poser avant la lecture du corps (`overlayConfig`, #494), puis par les
171
+ * bornes que la route déclare pour elle-même (option de route `upload`). La
172
+ * route est déjà résolue à ce moment : une route sans bornes propres
173
+ * n'alloue rien. Hors pipeline (aucun scope ouvert), la configuration du
174
+ * module telle quelle.
175
+ */
176
+ private readBodyLimits;
153
177
  /**
154
178
  * B1 — Pré-contrôle de taille du corps via l'en-tête `Content-Length` :
155
179
  * rejette en 413 (RFC 9110 §15.5.14 « Content Too Large ») AVANT toute lecture
@@ -159,13 +183,6 @@ declare class HttpRequest {
159
183
  *
160
184
  * @throws {HttpError} 413 si `Content-Length` dépasse `maxBodySize`.
161
185
  */
162
- /**
163
- * Lit le plafond du corps et les limites d'envoi tels que les voit CETTE
164
- * requête : la configuration http, surchargée par le calque que la requête a
165
- * pu poser avant la lecture du corps (`overlayConfig`, #494). Hors pipeline
166
- * (aucun scope ouvert), la configuration du module telle quelle.
167
- */
168
- private readBodyLimits;
169
186
  private enforceBodyLimit;
170
187
  parseRequest(): Promise<ParserType | null>;
171
188
  /**
@@ -200,6 +217,19 @@ declare class HttpRequest {
200
217
  * déjà posés — à la moindre erreur ou dépassement de limite.
201
218
  */
202
219
  private streamMultipart;
220
+ /**
221
+ * Interrompt la réception du corps en cours : la promesse de `initialize()`
222
+ * rejette avec `error`, rendue par le chemin d'erreur habituel — comme un
223
+ * corps qui dépasse sa limite (413).
224
+ *
225
+ * Sert le délai de réception HTTP/2 (`requestTimeout`), que `node:http2` ne
226
+ * borne pas. Sans effet hors réception : corps déjà reçu, lu en flux par
227
+ * l'action (`bodyStream`), ou méthode sans corps.
228
+ *
229
+ * @param error - la cause rendue au client (408 au délai dépassé)
230
+ * @returns vrai si une réception en cours a été interrompue
231
+ */
232
+ abortBody(error: HttpError): boolean;
203
233
  /**
204
234
  * Accumule un champ texte multipart. `foo[]` (ou répétition de `foo`) →
205
235
  * tableau ; sinon valeur scalaire. (Anciennement délégué à formidable.)