@nodefony/http 10.0.0-beta.5 → 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 (130) hide show
  1. package/README.md +24 -0
  2. package/dist/_virtual/{_@oxc-project_runtime@0.155.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorate.js +1 -1
  3. package/dist/_virtual/{_@oxc-project_runtime@0.155.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorateParam.js +1 -1
  4. package/dist/index.js +30 -26
  5. package/dist/nodefony/command/proxyGenerateCommand.js +2 -2
  6. package/dist/nodefony/config/config.js +29 -4
  7. package/dist/nodefony/grpc/GrpcCall.js +298 -0
  8. package/dist/nodefony/grpc/GrpcEndpoint.js +528 -0
  9. package/dist/nodefony/grpc/index.js +4 -0
  10. package/dist/nodefony/interfaces/IRpcCall.js +1 -0
  11. package/dist/nodefony/service/ProfilerAdminApi.js +5 -2
  12. package/dist/nodefony/service/audit-logger.js +52 -6
  13. package/dist/nodefony/service/error-renderer.js +47 -13
  14. package/dist/nodefony/service/http-kernel.js +306 -49
  15. package/dist/nodefony/service/pretty-request-logger.js +5 -3
  16. package/dist/nodefony/service/request-logger.js +21 -6
  17. package/dist/nodefony/service/reverse-proxy.js +8 -3
  18. package/dist/nodefony/service/servers/clientError.js +53 -4
  19. package/dist/nodefony/service/servers/server-h2c.js +6 -6
  20. package/dist/nodefony/service/servers/server-http.js +6 -4
  21. package/dist/nodefony/service/servers/server-https.js +14 -7
  22. package/dist/nodefony/service/servers/server-static.js +1 -6
  23. package/dist/nodefony/service/servers/server-websocket-secure.js +7 -11
  24. package/dist/nodefony/service/servers/server-websocket.js +7 -11
  25. package/dist/nodefony/service/servers/serverShutdown.js +43 -6
  26. package/dist/nodefony/service/sessions/sessions-service.js +7 -7
  27. package/dist/nodefony/service/upload/upload-service.js +2 -2
  28. package/dist/nodefony/src/assets/prebuiltUi.js +23 -5
  29. package/dist/nodefony/src/context/Context.js +12 -1
  30. package/dist/nodefony/src/context/http/ByteStream.js +364 -0
  31. package/dist/nodefony/src/context/http/HttpContext.js +139 -32
  32. package/dist/nodefony/src/context/http/Request.js +111 -22
  33. package/dist/nodefony/src/context/http/Response.js +459 -37
  34. package/dist/nodefony/src/context/http/SseStream.js +83 -148
  35. package/dist/nodefony/src/context/http/StreamLifecycle.js +582 -0
  36. package/dist/nodefony/src/context/http/parser.js +29 -11
  37. package/dist/nodefony/src/context/http/urlFastPath.js +80 -1
  38. package/dist/nodefony/src/context/http/webResponse.js +56 -0
  39. package/dist/nodefony/src/context/http2/Request.js +1 -1
  40. package/dist/nodefony/src/context/http2/Response.js +55 -12
  41. package/dist/nodefony/src/context/requestTrace.js +6 -2
  42. package/dist/nodefony/src/context/rpcOutcome.js +20 -0
  43. package/dist/nodefony/src/context/transport.js +20 -1
  44. package/dist/nodefony/src/context/websocket/Response.js +15 -3
  45. package/dist/nodefony/src/context/websocket/WebsocketContext.js +86 -18
  46. package/dist/nodefony/src/context/websocket/websocketUrl.js +7 -0
  47. package/dist/nodefony/src/context/websocket/wsBackpressure.js +15 -15
  48. package/dist/nodefony/src/context/websocket/wsFrameRate.js +71 -0
  49. package/dist/nodefony/src/context/websocket/wsLogContent.js +55 -17
  50. package/dist/nodefony/src/cookies/cookie.js +57 -14
  51. package/dist/nodefony/src/errors/httpError.js +58 -25
  52. package/dist/nodefony/src/grpc/cors.js +59 -0
  53. package/dist/nodefony/src/grpc/limits.js +16 -0
  54. package/dist/nodefony/src/grpc/refusal.js +271 -0
  55. package/dist/nodefony/src/profiler/Profiler.js +14 -1
  56. package/dist/nodefony/src/profiler/aggregate.js +2 -2
  57. package/dist/nodefony/src/proxy/forward.js +109 -9
  58. package/dist/nodefony/src/proxy/generateProxyConfig.js +97 -17
  59. package/dist/nodefony/src/proxy/rules.js +29 -14
  60. package/dist/nodefony/src/servers/http2Goaway.js +76 -0
  61. package/dist/nodefony/src/servers/http2Limits.js +41 -15
  62. package/dist/nodefony/src/servers/http2StreamGrace.js +100 -0
  63. package/dist/nodefony/src/servers/receiveDeadline.js +177 -0
  64. package/dist/nodefony/src/servers/upgradeDispatch.js +62 -4
  65. package/dist/types/index.d.ts +11 -2
  66. package/dist/types/nodefony/config/config.d.ts +14 -0
  67. package/dist/types/nodefony/grpc/GrpcCall.d.ts +142 -0
  68. package/dist/types/nodefony/grpc/GrpcEndpoint.d.ts +98 -0
  69. package/dist/types/nodefony/grpc/index.d.ts +15 -0
  70. package/dist/types/nodefony/interfaces/IContext.d.ts +9 -0
  71. package/dist/types/nodefony/interfaces/IErrorRenderer.d.ts +14 -0
  72. package/dist/types/nodefony/interfaces/IRequest.d.ts +2 -1
  73. package/dist/types/nodefony/interfaces/IRequestTrace.d.ts +10 -0
  74. package/dist/types/nodefony/interfaces/IRouting.d.ts +19 -0
  75. package/dist/types/nodefony/interfaces/IRpcCall.d.ts +83 -0
  76. package/dist/types/nodefony/interfaces/ISecurity.d.ts +27 -2
  77. package/dist/types/nodefony/interfaces/IUpload.d.ts +51 -4
  78. package/dist/types/nodefony/interfaces/index.d.ts +2 -1
  79. package/dist/types/nodefony/service/ProfilerAdminApi.d.ts +2 -1
  80. package/dist/types/nodefony/service/audit-logger.d.ts +33 -3
  81. package/dist/types/nodefony/service/error-renderer.d.ts +27 -5
  82. package/dist/types/nodefony/service/http-kernel.d.ts +36 -3
  83. package/dist/types/nodefony/service/request-logger.d.ts +10 -0
  84. package/dist/types/nodefony/service/servers/clientError.d.ts +3 -2
  85. package/dist/types/nodefony/service/servers/serverShutdown.d.ts +7 -5
  86. package/dist/types/nodefony/src/assets/prebuiltUi.d.ts +9 -4
  87. package/dist/types/nodefony/src/context/Context.d.ts +9 -1
  88. package/dist/types/nodefony/src/context/http/ByteStream.d.ts +109 -0
  89. package/dist/types/nodefony/src/context/http/HttpContext.d.ts +99 -3
  90. package/dist/types/nodefony/src/context/http/Request.d.ts +46 -9
  91. package/dist/types/nodefony/src/context/http/Response.d.ts +188 -8
  92. package/dist/types/nodefony/src/context/http/SseStream.d.ts +33 -8
  93. package/dist/types/nodefony/src/context/http/StreamLifecycle.d.ts +106 -0
  94. package/dist/types/nodefony/src/context/http/parser.d.ts +17 -3
  95. package/dist/types/nodefony/src/context/http/urlFastPath.d.ts +40 -0
  96. package/dist/types/nodefony/src/context/http/webResponse.d.ts +31 -0
  97. package/dist/types/nodefony/src/context/http2/Response.d.ts +40 -5
  98. package/dist/types/nodefony/src/context/requestTrace.d.ts +3 -1
  99. package/dist/types/nodefony/src/context/rpcOutcome.d.ts +11 -0
  100. package/dist/types/nodefony/src/context/transport.d.ts +17 -0
  101. package/dist/types/nodefony/src/context/websocket/Response.d.ts +6 -1
  102. package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +48 -0
  103. package/dist/types/nodefony/src/context/websocket/websocketUrl.d.ts +4 -0
  104. package/dist/types/nodefony/src/context/websocket/wsBackpressure.d.ts +12 -2
  105. package/dist/types/nodefony/src/context/websocket/wsFrameRate.d.ts +69 -0
  106. package/dist/types/nodefony/src/context/websocket/wsLogContent.d.ts +16 -8
  107. package/dist/types/nodefony/src/cookies/cookie.d.ts +20 -2
  108. package/dist/types/nodefony/src/errors/httpError.d.ts +29 -4
  109. package/dist/types/nodefony/src/grpc/cors.d.ts +32 -0
  110. package/dist/types/nodefony/src/grpc/limits.d.ts +14 -0
  111. package/dist/types/nodefony/src/grpc/refusal.d.ts +136 -0
  112. package/dist/types/nodefony/src/profiler/Profiler.d.ts +11 -1
  113. package/dist/types/nodefony/src/proxy/forward.d.ts +37 -2
  114. package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +15 -2
  115. package/dist/types/nodefony/src/proxy/rules.d.ts +20 -0
  116. package/dist/types/nodefony/src/servers/http2Goaway.d.ts +41 -0
  117. package/dist/types/nodefony/src/servers/http2Limits.d.ts +27 -14
  118. package/dist/types/nodefony/src/servers/http2StreamGrace.d.ts +34 -0
  119. package/dist/types/nodefony/src/servers/receiveDeadline.d.ts +77 -0
  120. package/dist/types/nodefony/src/servers/upgradeDispatch.d.ts +41 -1
  121. package/docs/byte-stream.md +406 -0
  122. package/docs/cookies.md +13 -3
  123. package/docs/diagnostics-channel.md +45 -1
  124. package/docs/index.md +8 -0
  125. package/docs/reverse-proxy.md +15 -3
  126. package/docs/servers.md +67 -43
  127. package/docs/session.md +3 -3
  128. package/docs/sse.md +27 -20
  129. package/docs/upload.md +43 -17
  130. package/package.json +38 -18
@@ -108,6 +108,7 @@ export declare const httpConfigSchema: z.ZodObject<{
108
108
  }, z.core.$loose>]>>;
109
109
  skipUTF8Validation: z.ZodDefault<z.ZodBoolean>;
110
110
  autoPong: z.ZodDefault<z.ZodBoolean>;
111
+ maxPendingAutoPongs: z.ZodDefault<z.ZodNumber>;
111
112
  allowSynchronousEvents: z.ZodDefault<z.ZodBoolean>;
112
113
  maxBackpressure: z.ZodDefault<z.ZodNumber>;
113
114
  backpressureCloseAfterDrops: z.ZodDefault<z.ZodNumber>;
@@ -115,6 +116,8 @@ export declare const httpConfigSchema: z.ZodObject<{
115
116
  close: "close";
116
117
  drop: "drop";
117
118
  }>>;
119
+ maxFramesPerSecond: z.ZodDefault<z.ZodNumber>;
120
+ frameRateCloseAfterRejects: z.ZodDefault<z.ZodNumber>;
118
121
  }, z.core.$loose>>;
119
122
  websocketSecure: z.ZodDefault<z.ZodObject<{
120
123
  keepaliveInterval: z.ZodDefault<z.ZodNumber>;
@@ -132,6 +135,7 @@ export declare const httpConfigSchema: z.ZodObject<{
132
135
  }, z.core.$loose>]>>;
133
136
  skipUTF8Validation: z.ZodDefault<z.ZodBoolean>;
134
137
  autoPong: z.ZodDefault<z.ZodBoolean>;
138
+ maxPendingAutoPongs: z.ZodDefault<z.ZodNumber>;
135
139
  allowSynchronousEvents: z.ZodDefault<z.ZodBoolean>;
136
140
  maxBackpressure: z.ZodDefault<z.ZodNumber>;
137
141
  backpressureCloseAfterDrops: z.ZodDefault<z.ZodNumber>;
@@ -139,6 +143,8 @@ export declare const httpConfigSchema: z.ZodObject<{
139
143
  close: "close";
140
144
  drop: "drop";
141
145
  }>>;
146
+ maxFramesPerSecond: z.ZodDefault<z.ZodNumber>;
147
+ frameRateCloseAfterRejects: z.ZodDefault<z.ZodNumber>;
142
148
  }, z.core.$loose>>;
143
149
  statics: z.ZodDefault<z.ZodObject<{
144
150
  enabled: z.ZodDefault<z.ZodBoolean>;
@@ -204,6 +210,14 @@ export declare const httpConfigSchema: z.ZodObject<{
204
210
  }>>;
205
211
  }, z.core.$strict>>>;
206
212
  }, z.core.$strict>>;
213
+ grpc: z.ZodDefault<z.ZodObject<{
214
+ grpc: z.ZodDefault<z.ZodBoolean>;
215
+ grpcWeb: z.ZodDefault<z.ZodBoolean>;
216
+ connect: z.ZodDefault<z.ZodBoolean>;
217
+ readMaxBytes: z.ZodDefault<z.ZodNumber>;
218
+ writeMaxBytes: z.ZodDefault<z.ZodNumber>;
219
+ requireConnectProtocolHeader: z.ZodDefault<z.ZodBoolean>;
220
+ }, z.core.$strict>>;
207
221
  wsMaxConnectionsPerIp: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
208
222
  }, z.core.$strict>;
209
223
  /** Type de sortie (config normalisée + défauts appliqués). */
@@ -0,0 +1,142 @@
1
+ import type { ContextKey, ContextValues, HandlerContext } from "@connectrpc/connect";
2
+ import type { IRpcCall, RpcSystem } from "../interfaces/IRpcCall.js";
3
+ import type HttpContext from "../src/context/http/HttpContext.js";
4
+ /**
5
+ * Fiche d'un appel gRPC : ce que Nodefony attache à l'appel, et le
6
+ * `ContextValues` que Connect remet au gestionnaire (`ctx.values`).
7
+ *
8
+ * Connect alloue à chaque appel un `ContextValues` fait d'un objet littéral et
9
+ * de trois fermetures (`context-values.js#createContextValues`), dont la forme
10
+ * change à chaque `set`. La fiche le REMPLACE (option `contextValues` de
11
+ * l'adaptateur) : une classe à champs déclarés, méthodes au prototype, une
12
+ * allocation par appel au lieu de quatre.
13
+ *
14
+ * Les clés posées par un tiers (intercepteur, porte) sont confiées à un
15
+ * `createContextValues()` créé au premier `set` : aucune allocation tant que
16
+ * personne n'en pose, et aucune conversion de type — la valeur rendue est celle
17
+ * que le magasin de Connect a typée.
18
+ *
19
+ * C'est aussi l'appel RPC du contexte (`HttpContext.rpc`, ADR-0026 D10) : à
20
+ * l'échéance du délai de réponse, le noyau le termine par {@link expire} ; au
21
+ * `close` de la réponse, il en fixe le code par {@link settle}, que lisent la
22
+ * ligne d'accès, l'audit et le canal de traçage.
23
+ *
24
+ * @experimental Surface gRPC, ADR-0026 D11.
25
+ */
26
+ export declare class GrpcCall implements ContextValues, IRpcCall {
27
+ #private;
28
+ /**
29
+ * L'objet qui implémente le service pour CET appel : le contrôleur, une
30
+ * instance par appel sous `@Scope("request")`. `null` quand l'adaptateur est
31
+ * appelé hors de {@link GrpcEndpoint.serve} (jamais en service normal).
32
+ */
33
+ readonly target: object | null;
34
+ /**
35
+ * Le contexte de la requête qui porte l'appel : l'identité y est lue
36
+ * (`authenticatedUser()`) sur son magasin, jamais par l'ALS, pour choisir
37
+ * entre 16 et 7 (ADR-0026 D5, D7). `null` hors de {@link GrpcEndpoint.serve}.
38
+ */
39
+ readonly context: HttpContext | null;
40
+ constructor(target: object | null, context?: HttpContext | null);
41
+ /**
42
+ * Le protocole parlé sur le fil : celui du contexte d'appel (`grpc`,
43
+ * `grpc-web`, `connect`) ; à défaut, celui des en-têtes de la requête.
44
+ */
45
+ get system(): RpcSystem;
46
+ /**
47
+ * `paquet.Service/Méthode` : le chemin de la route, que l'adaptateur a
48
+ * reconnu à l'octet près. Lu à la première demande, puis retenu.
49
+ */
50
+ get method(): string;
51
+ /** Le code de l'appel (0 à 16), `null` avant {@link settle}. */
52
+ get code(): number | null;
53
+ /** Le nom du code (`OK`, `UNAVAILABLE`…), `null` avec lui. */
54
+ get codeName(): string | null;
55
+ /**
56
+ * Fixe le code de l'appel au `close` de la réponse (ADR-0026 D10 point 2) :
57
+ *
58
+ * - motif `ConnectError` → son code ; sa cause, si c'est une `Error`, part
59
+ * à la ligne d'accès (exception traduite par D7). Réponse NON finie : 1,
60
+ * sauf une fin posée par le serveur (échéance 4, interruption de la fiche)
61
+ * — le client est parti, quel que soit le code de son `RST_STREAM` ;
62
+ * - `AbortError` ou aucun motif → 0 si la réponse est finie, sinon 1 (le
63
+ * client est parti — le 499 du noyau, dit en gRPC) ;
64
+ * - autre valeur → 13, et elle part à la ligne d'accès.
65
+ *
66
+ * Une interruption du serveur (délai échu, identité révoquée) abaisse le
67
+ * signal avec sa `ConnectError` : elle suit la première règle. Sans contexte
68
+ * d'appel, le code reste `null`.
69
+ *
70
+ * @param ended - la réponse a-t-elle été terminée par le serveur ?
71
+ * @returns la cause à porter par la ligne d'accès, `null` sinon.
72
+ */
73
+ settle(ended: boolean): Error | null;
74
+ /**
75
+ * Lit une valeur posée par un tiers, ou la valeur par défaut de sa clé.
76
+ *
77
+ * @param key - la clé (`createContextKey`).
78
+ * @returns la valeur posée, sinon `key.defaultValue`.
79
+ */
80
+ get<T>(key: ContextKey<T>): T;
81
+ /**
82
+ * Pose une valeur pour la durée de l'appel.
83
+ *
84
+ * @param key - la clé (`createContextKey`).
85
+ * @param value - la valeur.
86
+ * @returns la fiche, pour chaîner.
87
+ */
88
+ set<T>(key: ContextKey<T>, value: T): this;
89
+ /**
90
+ * Retire une valeur ; sa clé rendra de nouveau sa valeur par défaut.
91
+ *
92
+ * @param key - la clé (`createContextKey`).
93
+ * @returns la fiche, pour chaîner.
94
+ */
95
+ delete(key: ContextKey<unknown>): this;
96
+ /**
97
+ * Le délai de réponse du serveur est échu : le signal de l'appel s'abaisse
98
+ * (`ctx.abort`) et l'appel se termine en 4 `DEADLINE_EXCEEDED`, même si le
99
+ * gestionnaire ne lit pas son signal. Échu avant que le gestionnaire ne
100
+ * démarre : il est terminé dès son démarrage. Sans effet après la fin.
101
+ */
102
+ expire(): void;
103
+ /**
104
+ * L'identité qui a ouvert l'appel n'est plus valable : le signal de l'appel
105
+ * s'abaisse et l'appel se termine en 16 `UNAUTHENTICATED`. Réponse unique :
106
+ * sa promesse est rejetée. Flux : il s'arrête au message suivant, même si le
107
+ * gestionnaire ne lit pas son signal — au plus le message déjà en vol part.
108
+ * Sans effet après la fin, ou après une autre interruption.
109
+ */
110
+ revoke(): void;
111
+ /**
112
+ * Le serveur s'arrête : le signal de l'appel s'abaisse et l'appel se termine
113
+ * en 14 `UNAVAILABLE` — le client le rejoue ailleurs (ADR-0026 D8). Flux : il
114
+ * s'arrête au message suivant, même si le gestionnaire ne lit pas son
115
+ * signal. Un gestionnaire bloqué qui n'écoute rien tombe avec sa connexion, à
116
+ * `shutdownTimeout`. Sans effet après la fin, ou après une autre interruption.
117
+ */
118
+ shutdown(): void;
119
+ }
120
+ /**
121
+ * Le contexte d'appel que Connect remet réellement au gestionnaire : son type
122
+ * public (`HandlerContext`) ne dit pas `abort`, que porte pourtant l'objet créé
123
+ * par `createHandlerContext` (`HandlerContextController`, non exporté).
124
+ *
125
+ * @experimental Surface gRPC, ADR-0026 D11.
126
+ */
127
+ export interface IAbortableHandlerContext extends HandlerContext {
128
+ /** Interrompt l'appel : `ctx.signal` s'abaisse avec `reason`. */
129
+ abort(reason?: unknown): void;
130
+ }
131
+ /**
132
+ * Garde de type : le contexte d'appel porte-t-il `abort` ? Connect le pose sur
133
+ * tout contexte qu'il crée ; le garde le CONSTATE au lieu de le supposer, pour
134
+ * qu'une version de Connect qui le retirerait se voie à l'exécution (et au
135
+ * test qui le garde) plutôt que par un appel qui ne s'interrompt plus.
136
+ *
137
+ * @param ctx - le contexte remis au gestionnaire.
138
+ * @returns `true` si `ctx.abort` est une fonction.
139
+ *
140
+ * @experimental Surface gRPC, ADR-0026 D11.
141
+ */
142
+ export declare function hasAbort(ctx: HandlerContext): ctx is IAbortableHandlerContext;
@@ -0,0 +1,98 @@
1
+ import type { DescService } from "@bufbuild/protobuf";
2
+ import type HttpContext from "../src/context/http/HttpContext.js";
3
+ /**
4
+ * Protocoles servis — la section `grpc` de la configuration http
5
+ * (ADR-0026 D3, point 5).
6
+ *
7
+ * @experimental Surface gRPC, ADR-0026 D11.
8
+ */
9
+ export interface IGrpcProtocols {
10
+ readonly grpc: boolean;
11
+ readonly grpcWeb: boolean;
12
+ readonly connect: boolean;
13
+ }
14
+ /**
15
+ * La section `grpc` de la configuration http : protocoles servis (D3, point
16
+ * 5) et bornes des messages (D4, points 1, 2 et 6).
17
+ *
18
+ * @experimental Surface gRPC, ADR-0026 D11.
19
+ */
20
+ export interface IGrpcSettings extends IGrpcProtocols {
21
+ /** Taille maximale d'un message reçu, après décompression (octets). */
22
+ readonly readMaxBytes: number;
23
+ /** Taille maximale d'un message émis (octets). */
24
+ readonly writeMaxBytes: number;
25
+ /** Exige l'en-tête `Connect-Protocol-Version` sur les requêtes Connect. */
26
+ readonly requireConnectProtocolHeader: boolean;
27
+ }
28
+ /**
29
+ * Options d'un {@link GrpcEndpoint}.
30
+ *
31
+ * @experimental Surface gRPC, ADR-0026 D11.
32
+ */
33
+ export interface IGrpcEndpointOptions {
34
+ /**
35
+ * `true` : la cible change à chaque appel (contrôleur `@Scope("request")`),
36
+ * chaque méthode la relit dans la fiche d'appel. `false` (défaut) : la cible
37
+ * du premier appel sert tous les suivants (contrôleur singleton) et ses
38
+ * méthodes sont liées une fois — aucune indirection par appel.
39
+ */
40
+ readonly perCall?: boolean;
41
+ }
42
+ /**
43
+ * Point d'entrée gRPC d'un service : possède l'adaptateur Connect
44
+ * (`connectNodeAdapter`) et lui confie la requête et la réponse BRUTES de Node
45
+ * d'un contexte déjà passé par le pipeline (pare-feu, gardes).
46
+ *
47
+ * L'adaptateur est construit au premier appel, depuis la section `grpc` de la
48
+ * configuration http. Il reçoit la fiche d'appel ({@link GrpcCall}) par son
49
+ * option `contextValues`, qu'il appelle de façon SYNCHRONE avant toute
50
+ * attente (`connect-node-adapter.js#nodeRequestHandler`) : la cible passe par
51
+ * une case lue à cet instant — ni ALS, ni promesse, ni écouteur de plus.
52
+ *
53
+ * Les méthodes s'inscrivent une à une par `router.rpc`, SANS options : des
54
+ * options passées à l'inscription rallumeraient gRPC et gRPC-Web coupés par la
55
+ * configuration (connectrpc/connect-es#1835). La restriction de protocoles
56
+ * vit sur l'adaptateur seul.
57
+ *
58
+ * @experimental Surface gRPC, ADR-0026 D11.
59
+ */
60
+ export declare class GrpcEndpoint<S extends DescService = DescService> {
61
+ #private;
62
+ /** Le contrat servi (descripteur généré par `buf`). */
63
+ readonly service: S;
64
+ /** Cible relue à chaque appel (`@Scope("request")`). */
65
+ readonly perCall: boolean;
66
+ constructor(service: S, options?: IGrpcEndpointOptions);
67
+ /**
68
+ * Remet la requête du contexte à l'adaptateur, qui écrit lui-même la
69
+ * réponse et ses trailers. Synchrone : rien n'est attendu ici.
70
+ *
71
+ * Avant de la remettre : le contexte est marqué répondu (`sended`), si bien
72
+ * que le pipeline n'attend aucune réponse de l'action.
73
+ *
74
+ * - Appel unaire : le délai de réponse de Nodefony reste armé, et termine
75
+ * l'appel en 4 `DEADLINE_EXCEEDED` par sa fiche (`HttpContext.rpc`,
76
+ * ADR-0026 D4 point 3).
77
+ * - Flux : aucune échéance. Il s'ouvre par le cycle de vie d'un flux
78
+ * (`openStreamLifecycle`, D4 point 4) — délai de réponse désarmé (à
79
+ * l'expiration il écrirait un 503 par-dessus l'adaptateur), place dans
80
+ * le budget de connexions longues par IP, client qui ne lit plus coupé
81
+ * par le balayage de blocage. Synchrone, sauf quand une session est à
82
+ * sauver : l'appel n'est remis qu'après.
83
+ *
84
+ * @param context - le contexte HTTP de la requête, pas encore répondu.
85
+ * @param target - l'objet qui implémente le service pour cet appel.
86
+ * @returns rien, ou la promesse de la remise quand une session est à sauver.
87
+ * @throws HttpError 500 quand la réponse est déjà partie, ou que son
88
+ * transport n'est pas reconnu.
89
+ * @throws HttpError 429 quand l'IP du client a déjà tous ses flux et sockets
90
+ * (`wsMaxConnectionsPerIp`) — rendu par le pipeline en 14 `UNAVAILABLE`.
91
+ * @throws HttpError 400 quand l'adaptateur refuse la requête avant d'avoir
92
+ * rien écrit (requête sans autorité `Host`) — la réponse revient alors au
93
+ * pipeline.
94
+ * @throws HttpError 404 quand aucune méthode ne correspond au chemin exact
95
+ * (casse modifiée) — rendu par le pipeline en 12 `UNIMPLEMENTED`.
96
+ */
97
+ serve(context: HttpContext, target: object): void | Promise<void>;
98
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `@nodefony/http/grpc` — la primitive de transport gRPC (ADR-0026).
3
+ *
4
+ * Sous-chemin à part : le point d'entrée principal de `@nodefony/http` ne cite
5
+ * ni Connect ni protobuf, même en type (D12). Seule une application qui
6
+ * déclare un service l'importe — par `@nodefony/framework/grpc` — et charge
7
+ * alors Connect au démarrage.
8
+ *
9
+ * @experimental Surface gRPC, ADR-0026 D11.
10
+ */
11
+ export { GrpcCall, hasAbort } from "./GrpcCall.js";
12
+ export type { IAbortableHandlerContext } from "./GrpcCall.js";
13
+ export { GrpcEndpoint } from "./GrpcEndpoint.js";
14
+ export type { IGrpcEndpointOptions, IGrpcProtocols, IGrpcSettings, } from "./GrpcEndpoint.js";
15
+ export { markGrpcRoute } from "../src/grpc/refusal.js";
@@ -3,6 +3,7 @@ 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
+ import type { IRpcCall } from "./IRpcCall.js";
6
7
  /**
7
8
  * Identité du serveur qui a reçu la requête, nommée par le schéma d'URI qu'il
8
9
  * sert en LOCAL (RFC 9110 §4.2, RFC 6455 §3) : elle choisit la section de
@@ -156,6 +157,14 @@ export interface IContext {
156
157
  export interface IHttpContext extends IContext {
157
158
  proxy: object | null;
158
159
  isRedirect: boolean;
160
+ /**
161
+ * L'appel RPC servi par cette requête (gRPC, gRPC-Web, Connect), `null` hors
162
+ * appel RPC — ADR-0026 D10. Son code est fixé au `close`, avant la ligne
163
+ * d'accès.
164
+ *
165
+ * @experimental Surface gRPC, ADR-0026 D11.
166
+ */
167
+ rpc: IRpcCall | null;
159
168
  /**
160
169
  * Sert la requête — synchrone quand l'action et son rendu le sont.
161
170
  *
@@ -11,6 +11,20 @@ export interface IErrorHttpResult {
11
11
  body: unknown;
12
12
  headers?: Record<string, string | number>;
13
13
  }
14
+ /**
15
+ * Ce qu'une route HTTP rendrait pour une erreur — son statut et le message
16
+ * destiné au client —, sans rien écrire. Sert les transports qui n'ont pas de
17
+ * corps d'erreur à rendre : un appel gRPC traduit ce statut en code
18
+ * (ADR-0026 D7).
19
+ *
20
+ * @experimental Surface gRPC, ADR-0026 D11.
21
+ */
22
+ export interface IErrorDescription {
23
+ /** Statut HTTP émettable (100 à 599). */
24
+ readonly status: number;
25
+ /** Message pour le client : opaque pour un 5xx en production. */
26
+ readonly message: string;
27
+ }
14
28
  /**
15
29
  * Aide jointe au corps d'erreur — ce que le développeur doit faire, en clair.
16
30
  *
@@ -1,5 +1,6 @@
1
1
  import type { URL } from "node:url";
2
2
  import type { ICookie } from "./ICookie.js";
3
+ import type { IUploadedFile } from "./IUpload.js";
3
4
  export type HTTPMethodType = "GET" | "HEAD" | "POST" | "PUT" | "DELETE" | "CONNECT" | "OPTIONS" | "TRACE" | "PATCH" | "WEBSOCKET";
4
5
  export interface IRequest {
5
6
  url: URL | string;
@@ -19,7 +20,7 @@ export interface IHttpRequest extends IRequest {
19
20
  url: URL;
20
21
  query: Record<string, unknown>;
21
22
  body: RequestBody;
22
- files: readonly unknown[];
23
+ files: readonly IUploadedFile[];
23
24
  remoteAddress: string | null | undefined;
24
25
  acceptHtml: boolean;
25
26
  getRemoteAddress(): string | null | undefined;
@@ -1,5 +1,6 @@
1
1
  import type { SchemeType } from "./IContext.js";
2
2
  import type { HTTPMethodType } from "./IRequest.js";
3
+ import type { IRpcOutcome } from "./IRpcCall.js";
3
4
  /**
4
5
  * Nature de l'opération tracée : une requête HTTP, ou le handshake d'une
5
6
  * connexion WebSocket.
@@ -53,4 +54,13 @@ export interface IRequestTrace {
53
54
  * sans exception n'en porte pas ; c'est à l'abonné de classer un statut.
54
55
  */
55
56
  readonly error: unknown;
57
+ /**
58
+ * L'appel RPC (gRPC, gRPC-Web, Connect) servi par la requête, `null` hors
59
+ * RPC : système, méthode, code et son nom (ADR-0026 D10 point 5). Un flux
60
+ * est UN message, de l'ouverture au `close` ; rien par message.
61
+ * `statusCode` reste le statut HTTP, vrai sur le fil.
62
+ *
63
+ * @experimental Surface gRPC, ADR-0026 D11.
64
+ */
65
+ readonly rpc: IRpcOutcome | null;
56
66
  }
@@ -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
+ }
@@ -40,12 +40,37 @@ export interface IFirewallGate {
40
40
  enforceCsrf(context: ContextType): void;
41
41
  /** Répond au preflight CORS ; `204` quand la réponse est déjà envoyée. */
42
42
  handleCors(context: ContextType): number | undefined;
43
- /** Pose les en-têtes de sécurité (CSP, HSTS…) de la réponse. */
44
- applySecurityHeaders(context: ContextType): void;
43
+ /**
44
+ * Pose les en-têtes de sécurité applicatifs (CSP, Permissions-Policy…) de la
45
+ * réponse, appelé par la réponse juste avant son `writeHead`.
46
+ *
47
+ * @param contentType - `Content-Type` de la réponse, qui choisit la politique ;
48
+ * `null` = réponse sans contenu ; omis = type inconnu (politique complète).
49
+ * @param servedAsFile - corps servi tel quel (fichier statique, relais) : aucun
50
+ * gabarit ne l'a écrit, il ne peut pas porter de nonce.
51
+ */
52
+ applySecurityHeaders(context: ContextType, contentType?: string | null, servedAsFile?: boolean): void;
45
53
  /**
46
54
  * Le revalidateur de l'identité authentifiée de la requête courante, pour une
47
55
  * connexion longue qui la gardera (flux SSE) : la même règle que les sockets
48
56
  * WebSocket. `null` pour un anonyme — rien à révoquer.
49
57
  */
50
58
  currentRevalidator?(): ((nowMs?: number) => Promise<boolean>) | null;
59
+ /**
60
+ * Prévenu quand le noyau HTTP a recalculé ses en-têtes de transport (édition à
61
+ * chaud de `securityHeaders`) : la politique de contenu suit `frameOptions`,
62
+ * qui pilote aussi `frame-ancestors`.
63
+ */
64
+ onTransportHeadersChanged?(): void;
65
+ /**
66
+ * Déclare les sources qu'un module ajoute à la politique de contenu
67
+ * (`{ "connect-src": ["wss://rt.example.com"] }`). Un second appel du même
68
+ * module REMPLACE sa contribution. Absent quand aucun pare-feu ne pose de CSP.
69
+ *
70
+ * @param moduleName - module qui contribue, clé de sa contribution
71
+ * @param fragment - sources par directive, origines exactes (sans joker)
72
+ */
73
+ registerCspOrigins?(moduleName: string, fragment: Readonly<Record<string, readonly string[]>>): void;
74
+ /** Retire la contribution CSP de `moduleName`. */
75
+ unregisterCspOrigins?(moduleName: string): void;
51
76
  }
@@ -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";
@@ -13,7 +13,8 @@ import type { Profiler } from "../src/profiler/Profiler.js";
13
13
  * `?connection=<requestId>` : les seules frames de cette socket WebSocket
14
14
  * - `GET /nodefony/profiler/api/summary` → les profils de l'anneau agrégés par
15
15
  * route (percentiles, phases, formes SQL, N+1, erreurs — `aggregateProfiles`)
16
- * - `GET /nodefony/profiler/api/{id}` → profil complet (phases) d'un requestId
16
+ * - `GET /nodefony/profiler/api/{id}` → profil complet (phases) d'un requestId ;
17
+ * absent → 404 `{ startedAt, clearedAt }` : depuis quand l'anneau sait quelque chose
17
18
  * - `DELETE /nodefony/profiler/api/recent` → vide le ring buffer
18
19
  *
19
20
  * La debug bar (toute page, dev) lit `X-Request-Id` de SON appel AJAX puis