@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,4 +1,4 @@
1
- // \0@oxc-project+runtime@0.157.0/helpers/esm/decorate.js
1
+ // \0@oxc-project+runtime@0.158.0/helpers/esm/decorate.js
2
2
  function __decorate(decorators, target, key, desc) {
3
3
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
4
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -1,4 +1,4 @@
1
- // \0@oxc-project+runtime@0.157.0/helpers/esm/decorateParam.js
1
+ // \0@oxc-project+runtime@0.158.0/helpers/esm/decorateParam.js
2
2
  function __decorateParam(paramIndex, decorator) {
3
3
  return function(target, key) {
4
4
  decorator(target, key, paramIndex);
package/dist/index.js CHANGED
@@ -33,12 +33,13 @@ import HttpContext from "./nodefony/src/context/http/HttpContext.js";
33
33
  import WebsocketContext from "./nodefony/src/context/websocket/WebsocketContext.js";
34
34
  import HttpRequest from "./nodefony/src/context/http/Request.js";
35
35
  import Http2Request from "./nodefony/src/context/http2/Request.js";
36
- import __decorate from "./_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.js";
36
+ import __decorate from "./_virtual/_@oxc-project_runtime@0.158.0/helpers/esm/decorate.js";
37
37
  import { parseRequestTarget } from "./nodefony/src/context/http/urlFastPath.js";
38
38
  import { decideSend, readBackpressureOptions } from "./nodefony/src/context/websocket/wsBackpressure.js";
39
39
  import { FRAME_RATE_WINDOW_MS, admitFrame, frameRateRejectedTotal, readFrameRateBound, readFrameRateCloseAfter } from "./nodefony/src/context/websocket/wsFrameRate.js";
40
40
  import { isWebsocketContext } from "./nodefony/src/context/websocket/isWebsocketContext.js";
41
41
  import { REQUEST_TRACE_CHANNEL } from "./nodefony/src/context/requestTrace.js";
42
+ import { rpcCorsLists } from "./nodefony/src/grpc/cors.js";
42
43
  import { compileDomainPattern, compileDomainPatterns, compileTrustedHosts, isDomainAllowed } from "./nodefony/src/context/domainMatcher.js";
43
44
  import { MemoryRateLimitStore } from "./nodefony/src/rateLimit/MemoryRateLimitStore.js";
44
45
  import { SSE_HEARTBEAT_MS, SseStream, acceptsEventStream, openSseStream } from "./nodefony/src/context/http/SseStream.js";
@@ -119,4 +120,4 @@ Http = __decorate([services([
119
120
  upload_service_default
120
121
  ])], Http);
121
122
  var http_default = Http;
122
- export { ACTION_SELF_PHASE, Context, Cookie, DefaultErrorRenderer, DefaultRequestLogger, FRAME_RATE_WINDOW_MS, FrameProfile, Http2Request, Http2Response, HttpContext, HttpError, http_kernel_default as HttpKernel, HttpRequest, HttpResponse, HttpResponse as Response, JsonAuditLogger, MemoryRateLimitStore, NO_ROUTE, N_PLUS_ONE_MIN_REPEATS, PrebuiltUi, PrettyRequestLogger, Profiler, REQUEST_TRACE_CHANNEL, ReverseProxy, SESSION_COLUMN_ALIASES, SESSION_DEFAULT_ORDER, SESSION_DEFAULT_ORDER_SQL, SESSION_SORTABLE_FIELDS, SSE_HEARTBEAT_MS, Session, sessions_service_default as SessionsService, SseStream, TOOLING_URL_PREFIX, WebsocketContext, WebsocketResponse, WebsocketResponse as wsResponse, acceptsEventStream, admitFrame, aggregateProfiles, compileDomainPattern, compileDomainPatterns, compileTrustedHosts, createProfileAggregator, decideSend, http_default as default, defineHttpConfig, frameRateRejectedTotal, httpConfigJsonSchema, httpConfigSchema, isContentlessStatus, isDomainAllowed, isFailedProfile, isWebsocketContext, normalizePrefix, openByteStream, openSseStream, parseProxyOrigin, parseRequestTarget, proxyMountProblems, readBackpressureOptions, readFrameRateBound, readFrameRateCloseAfter, resolveUiDelivery, schemaMismatchOf, sqlShape, toValidationFields };
123
+ export { ACTION_SELF_PHASE, Context, Cookie, DefaultErrorRenderer, DefaultRequestLogger, FRAME_RATE_WINDOW_MS, FrameProfile, Http2Request, Http2Response, HttpContext, HttpError, http_kernel_default as HttpKernel, HttpRequest, HttpResponse, HttpResponse as Response, JsonAuditLogger, MemoryRateLimitStore, NO_ROUTE, N_PLUS_ONE_MIN_REPEATS, PrebuiltUi, PrettyRequestLogger, Profiler, REQUEST_TRACE_CHANNEL, ReverseProxy, SESSION_COLUMN_ALIASES, SESSION_DEFAULT_ORDER, SESSION_DEFAULT_ORDER_SQL, SESSION_SORTABLE_FIELDS, SSE_HEARTBEAT_MS, Session, sessions_service_default as SessionsService, SseStream, TOOLING_URL_PREFIX, WebsocketContext, WebsocketResponse, WebsocketResponse as wsResponse, acceptsEventStream, admitFrame, aggregateProfiles, compileDomainPattern, compileDomainPatterns, compileTrustedHosts, createProfileAggregator, decideSend, http_default as default, defineHttpConfig, frameRateRejectedTotal, httpConfigJsonSchema, httpConfigSchema, isContentlessStatus, isDomainAllowed, isFailedProfile, isWebsocketContext, normalizePrefix, openByteStream, openSseStream, parseProxyOrigin, parseRequestTarget, proxyMountProblems, readBackpressureOptions, readFrameRateBound, readFrameRateCloseAfter, resolveUiDelivery, rpcCorsLists, schemaMismatchOf, sqlShape, toValidationFields };
@@ -1,4 +1,5 @@
1
1
  import { normalizePrefix, proxyMountProblems } from "../src/proxy/rules.js";
2
+ import { GRPC_DEFAULT_MAX_MESSAGE_BYTES, GRPC_MAX_MESSAGE_BYTES } from "../src/grpc/limits.js";
2
3
  import { overlayField } from "nodefony";
3
4
  import { z } from "zod";
4
5
  // nodefony/config/config.ts
@@ -146,8 +147,8 @@ const httpServerSchema = z.looseObject({
146
147
  maxHeadersCount: z.number().int().nonnegative().default(2e3).describe("Nombre max d'en-têtes par requête — anti header flooding."),
147
148
  keepAliveTimeout: z.number().int().nonnegative().default(5e3).describe("Délai keep-alive (ms) entre deux requêtes sur la même socket TCP."),
148
149
  timeout: z.number().int().nonnegative().default(3e4).describe("Timeout global de la socket (ms). 0 = désactivé. Aligné sur responseTimeout : node arme ce timer à la connexion, et le framework ne ré-arme que si la valeur diffère (`socket.timeout !== ms`) — l'alignement économise 1 setTimeout par requête sans changer le comportement effectif (l'arm framework 30 s dominait déjà)."),
149
- requestTimeout: z.number().int().nonnegative().default(3e4).describe("Timeout de réception requête complète (ms) — anti slow-loris."),
150
- responseTimeout: z.number().int().nonnegative().default(3e4).describe("Timeout d'envoi de la réponse complète (ms)."),
150
+ requestTimeout: z.number().int().nonnegative().default(3e4).describe("Timeout de réception requête complète (ms) — anti slow-loris. HTTP/1.1 : borne native de Node ; HTTP/2 : borne du framework (corps lu par le pipeline), 408 puis flux fermé. 0 désactive."),
151
+ responseTimeout: z.number().int().nonnegative().default(3e4).describe("Délai d'inactivité d'une action (ms) : au-delà, 503 et `ctx.signal` annulé. Une route le règle pour elle-même (option `responseTimeout`)."),
151
152
  shutdownTimeout: z.number().int().nonnegative().default(5e3).describe("Drain graceful au shutdown (ms) : délai laissé aux requêtes in-flight avant destruction forcée des sockets (SIGTERM/docker stop). Doit rester < grace period de l'orchestrateur (30 s k8s, 10 s Docker)."),
152
153
  headers: z.record(z.string(), z.string()).nullable().default(null).describe("En-têtes ajoutés à toutes les réponses. `null` = aucun.")
153
154
  }).describe("Serveur HTTP (node:http, port 5151). Loose : toute option `net.Server`/`http.Server` supplémentaire (insecureHTTPParser, maxRequestsPerSocket…) est transmise.");
@@ -221,6 +222,7 @@ const websocketSchema = z.looseObject({
221
222
  })]).default(false).describe("Compression permessage-deflate (RFC 7692). Défaut false : la compression coûte CPU + mémoire et expose au zip-bomb (un petit payload compressé se décompresse en message géant). `true` = options par défaut, ou objet pour régler fenêtres / seuil / concurrence. N'activer que si le gain réseau le justifie."),
222
223
  skipUTF8Validation: z.boolean().default(false).describe("Désactive la validation UTF-8 des frames texte (RFC 6455 §8.1). Gain CPU au prix de l'acceptation de frames texte invalides — laisser false sauf charge extrême maîtrisée."),
223
224
  autoPong: z.boolean().default(true).describe("Répond automatiquement par un pong à chaque ping entrant (RFC 6455 §5.5.2-5.5.3). Laisser true — requis par le protocole et par le heartbeat des pairs."),
225
+ maxPendingAutoPongs: z.number().int().nonnegative().default(60).describe("Pongs automatiques en attente d'écriture au-delà desquels la connexion est COUPÉE (ws@8.23+). Borne la mémoire face à un pair qui inonde de pings sans jamais lire : chaque ping met un pong en file, et la file d'un pair qui ne lit pas grossit jusqu'à l'épuisement du process. Un pair sain n'a jamais plus de quelques pongs en vol. 0 = sans borne (vulnérable). Défaut 60, celui de `ws`."),
224
226
  allowSynchronousEvents: z.boolean().default(true).describe("Émet les events ('message', 'ping'…) de plusieurs frames lues dans un même chunk réseau de façon synchrone (ws@8.18+). true = meilleur débit ; false = un event par tick d'event loop (latence plus régulière, plus équitable entre connexions)."),
225
227
  maxBackpressure: z.number().int().nonnegative().default(4194304).describe("Seuil (octets) du buffer d'envoi par connexion (`ws.bufferedAmount`) au-delà duquel `backpressurePolicy` s'applique. Borne la RAM d'envoi face à un client LENT À RECEVOIR (anti-OOM) ; broadcast() amplifie (un seul lent peut plomber la diffusion). 0 = désactivé. Défaut 4 MiB."),
226
228
  backpressureCloseAfterDrops: z.number().int().nonnegative().default(1e3).describe("SOLDE de frames refusées au-delà duquel la connexion est FERMÉE (1013), en politique 'drop' : +1 par frame refusée, −1 par frame envoyée. POURQUOI un solde et pas un second seuil d'octets : une fois qu'on jette, plus rien n'alimente la file — elle plafonne au seuil de drop et n'atteint jamais un seuil supérieur, qui serait donc inatteignable (mesuré). Et pas une série remise à zéro au premier envoi : la file d'un client bloqué oscille autour du seuil, chaque envoi effacerait la série et il ne serait jamais fermé. Le solde, lui, distingue le pic passager du client mort. 0 = ne jamais fermer. Défaut 1000."),
@@ -329,6 +331,21 @@ const proxySchema = z.strictObject({
329
331
  });
330
332
  }
331
333
  }).describe("Proxy inverse : relaie un préfixe d'URL (requêtes et, sur demande, WebSocket) vers un autre serveur, sur la MÊME origine que la page — un seul certificat, l'amont peut rester sur la boucle locale. En-têtes de connexion jamais transmis, `X-Forwarded-*` et `Via` posés, `trustedHosts` appliqué, 502/504 sur amont injoignable/muet, boucle détectée (508).");
334
+ /**
335
+ * Section `grpc` — ce que servent les points d'entrée gRPC d'une application
336
+ * (ADR-0026 D3, point 5). Lue au premier appel d'un service : une application
337
+ * qui n'en déclare aucun ne la lit jamais.
338
+ *
339
+ * @experimental Surface gRPC, ADR-0026 D11.
340
+ */
341
+ const grpcSchema = z.strictObject({
342
+ grpc: z.boolean().default(true).describe("Sert le protocole gRPC (HTTP/2 seulement : 5152, et 5153 si h2c est activé). Défaut de l'adaptateur Connect."),
343
+ grpcWeb: z.boolean().default(true).describe("Sert le protocole gRPC-Web, sur tous les serveurs actifs."),
344
+ connect: z.boolean().default(true).describe("Sert le protocole Connect (JSON ou binaire, `curl` compris), sur tous les serveurs actifs."),
345
+ readMaxBytes: z.number().int().min(1).max(GRPC_MAX_MESSAGE_BYTES).default(GRPC_DEFAULT_MAX_MESSAGE_BYTES).describe("Taille maximale d'un message REÇU, en octets (défaut 4 Mio), après décompression : au-delà, statut 8 `RESOURCE_EXHAUSTED` et la méthode n'est pas appelée. Sans borne, quelques Ko compressés peuvent se décompresser en plusieurs Gio."),
346
+ writeMaxBytes: z.number().int().min(1).max(GRPC_MAX_MESSAGE_BYTES).default(GRPC_DEFAULT_MAX_MESSAGE_BYTES).describe("Taille maximale d'un message ÉMIS, en octets (défaut 4 Mio) : au-delà, statut 8 `RESOURCE_EXHAUSTED`, journalisé chez nous plutôt que refusé par un client borné à 4 Mio."),
347
+ requireConnectProtocolHeader: z.boolean().default(false).describe("Exige l'en-tête `Connect-Protocol-Version` sur les requêtes Connect (400 sans lui). Défaut `false` : le CSRF est déjà fermé par le type de contenu et CORS, et l'exiger casse `curl` et le JSON écrit à la main.")
348
+ }).refine((grpc) => grpc.grpc || grpc.grpcWeb || grpc.connect, { message: "grpc : au moins un protocole doit rester servi (grpc, grpcWeb ou connect) — pour ne servir aucun service gRPC, n'en déclarer aucun." }).describe("Services gRPC déclarés par `@GrpcService` (ADR-0026) : protocoles servis et bornes des messages, pour toute l'application. Expérimental.");
332
349
  const httpConfigSchema = z.strictObject({
333
350
  headerServer: z.string().nullable().default("nodefony").meta({
334
351
  runtimeMutable: true,
@@ -366,6 +383,12 @@ const httpConfigSchema = z.strictObject({
366
383
  rateLimit: rateLimitSchema.default(() => rateLimitSchema.parse({})),
367
384
  health: healthSchema.default(() => healthSchema.parse({})),
368
385
  proxy: proxySchema.default(() => proxySchema.parse({})),
386
+ /**
387
+ * Services gRPC servis par le pipeline (ADR-0026).
388
+ *
389
+ * @experimental Surface gRPC, ADR-0026 D11.
390
+ */
391
+ grpc: grpcSchema.default(() => grpcSchema.parse({})),
369
392
  wsMaxConnectionsPerIp: z.number().int().positive().nullable().default(null).meta({
370
393
  runtimeMutable: true,
371
394
  description: "Backstop OPT-IN : plafond de connexions LONGUES CONCURRENTES par IP cliente — sockets WebSocket ET flux SSE, un seul budget pour les deux. `null` (DÉFAUT) = désactivé. Distinct de `rateLimit` (qui borne le DÉBIT d'ouverture par fenêtre) : ici on borne le NOMBRE de connexions simultanément ouvertes par IP. Au-delà, l'upgrade WebSocket est fermé (RFC 6455 close 1013) et un flux SSE répond 429. ⚠️ PORTÉE PAR PROCESS (1 pod) : un vrai plafond GLOBAL/IP se fait à l'INGRESS/LB — nginx `limit_conn`, HAProxy `sc_conn_cur`, annotation k8s `nginx.ingress.kubernetes.io/limit-connections` — qui voit TOUT le trafic, rejette AVANT que l'app paie le fd + le handshake TLS, et couvre tous les pods. En cloud-native, laisser `null` et déléguer à l'edge. N'activer (ex. 20) que sur bare-metal/VPS SANS ingress, comme défense en profondeur. IP résolue forwarded-aware (RFC 7239 + trustProxy)."
@@ -0,0 +1,298 @@
1
+ import { grpcStatusName, grpcWireOf } from "../src/grpc/refusal.js";
2
+ import { Code, ConnectError, createContextValues } from "@connectrpc/connect";
3
+ // nodefony/grpc/GrpcCall.ts
4
+ /** Code 0 `OK` : Connect n'en a pas dans son énumération (`Code` part de 1). */
5
+ const OK = 0;
6
+ /**
7
+ * Fiche d'un appel gRPC : ce que Nodefony attache à l'appel, et le
8
+ * `ContextValues` que Connect remet au gestionnaire (`ctx.values`).
9
+ *
10
+ * Connect alloue à chaque appel un `ContextValues` fait d'un objet littéral et
11
+ * de trois fermetures (`context-values.js#createContextValues`), dont la forme
12
+ * change à chaque `set`. La fiche le REMPLACE (option `contextValues` de
13
+ * l'adaptateur) : une classe à champs déclarés, méthodes au prototype, une
14
+ * allocation par appel au lieu de quatre.
15
+ *
16
+ * Les clés posées par un tiers (intercepteur, porte) sont confiées à un
17
+ * `createContextValues()` créé au premier `set` : aucune allocation tant que
18
+ * personne n'en pose, et aucune conversion de type — la valeur rendue est celle
19
+ * que le magasin de Connect a typée.
20
+ *
21
+ * C'est aussi l'appel RPC du contexte (`HttpContext.rpc`, ADR-0026 D10) : à
22
+ * l'échéance du délai de réponse, le noyau le termine par {@link expire} ; au
23
+ * `close` de la réponse, il en fixe le code par {@link settle}, que lisent la
24
+ * ligne d'accès, l'audit et le canal de traçage.
25
+ *
26
+ * @experimental Surface gRPC, ADR-0026 D11.
27
+ */
28
+ var GrpcCall = class {
29
+ /**
30
+ * L'objet qui implémente le service pour CET appel : le contrôleur, une
31
+ * instance par appel sous `@Scope("request")`. `null` quand l'adaptateur est
32
+ * appelé hors de {@link GrpcEndpoint.serve} (jamais en service normal).
33
+ */
34
+ target;
35
+ /**
36
+ * Le contexte de la requête qui porte l'appel : l'identité y est lue
37
+ * (`authenticatedUser()`) sur son magasin, jamais par l'ALS, pour choisir
38
+ * entre 16 et 7 (ADR-0026 D5, D7). `null` hors de {@link GrpcEndpoint.serve}.
39
+ */
40
+ context;
41
+ /** Magasin des clés de tiers, créé au premier `set`. */
42
+ #values = null;
43
+ /** Contexte d'appel du gestionnaire en cours (unaire, flux). */
44
+ #ctx = null;
45
+ /** Rejet de la promesse rendue à l'adaptateur (réponse unique). */
46
+ #reject = null;
47
+ /**
48
+ * Pourquoi le serveur a interrompu l'appel — délai échu (4), identité
49
+ * révoquée (16), arrêt du serveur (14) —, `null` tant qu'il ne l'a pas fait. Posé une fois : la
50
+ * première cause gagne.
51
+ */
52
+ #reason = null;
53
+ /**
54
+ * Le contexte d'appel que Connect crée AVANT de lire le corps (remis par
55
+ * {@link open}), gardé jusqu'au `close` : son signal porte la cause de la
56
+ * fin, refus de Connect avant le gestionnaire compris. `null` si l'appel n'a
57
+ * jamais commencé (refusé par le transport).
58
+ */
59
+ #call = null;
60
+ /** Code de l'appel, fixé une fois par {@link settle}. */
61
+ #code = null;
62
+ /** `paquet.Service/Méthode`, tiré du chemin à la première lecture. */
63
+ #method = null;
64
+ constructor(target, context = null) {
65
+ this.target = target;
66
+ this.context = context;
67
+ }
68
+ /**
69
+ * Le protocole parlé sur le fil : celui du contexte d'appel (`grpc`,
70
+ * `grpc-web`, `connect`) ; à défaut, celui des en-têtes de la requête.
71
+ */
72
+ get system() {
73
+ const call = this.#call;
74
+ if (call !== null) return call.protocolName;
75
+ const request = this.context?.request.request;
76
+ const wire = request === void 0 ? 0 : grpcWireOf(request.headers);
77
+ if (wire === 1) return "grpc";
78
+ if (wire === 2) return "grpc-web";
79
+ return "connect";
80
+ }
81
+ /**
82
+ * `paquet.Service/Méthode` : le chemin de la route, que l'adaptateur a
83
+ * reconnu à l'octet près. Lu à la première demande, puis retenu.
84
+ */
85
+ get method() {
86
+ if (this.#method !== null) return this.#method;
87
+ const call = this.#call;
88
+ const path = this.context?.request.url.pathname;
89
+ this.#method = path !== void 0 ? path.slice(1) : call !== null ? `${call.service.typeName}/${call.method.name}` : "";
90
+ return this.#method;
91
+ }
92
+ /** Le code de l'appel (0 à 16), `null` avant {@link settle}. */
93
+ get code() {
94
+ return this.#code;
95
+ }
96
+ /** Le nom du code (`OK`, `UNAVAILABLE`…), `null` avec lui. */
97
+ get codeName() {
98
+ return this.#code === null ? null : grpcStatusName(this.#code);
99
+ }
100
+ /**
101
+ * Garde le contexte d'appel que Connect vient de créer, avant le corps
102
+ * (option `requestGate` de l'adaptateur, en simple observateur).
103
+ *
104
+ * @param ctx - le contexte d'appel.
105
+ *
106
+ * @internal
107
+ */
108
+ open(ctx) {
109
+ this.#call = ctx;
110
+ }
111
+ /**
112
+ * Fixe le code de l'appel au `close` de la réponse (ADR-0026 D10 point 2) :
113
+ *
114
+ * - motif `ConnectError` → son code ; sa cause, si c'est une `Error`, part
115
+ * à la ligne d'accès (exception traduite par D7). Réponse NON finie : 1,
116
+ * sauf une fin posée par le serveur (échéance 4, interruption de la fiche)
117
+ * — le client est parti, quel que soit le code de son `RST_STREAM` ;
118
+ * - `AbortError` ou aucun motif → 0 si la réponse est finie, sinon 1 (le
119
+ * client est parti — le 499 du noyau, dit en gRPC) ;
120
+ * - autre valeur → 13, et elle part à la ligne d'accès.
121
+ *
122
+ * Une interruption du serveur (délai échu, identité révoquée) abaisse le
123
+ * signal avec sa `ConnectError` : elle suit la première règle. Sans contexte
124
+ * d'appel, le code reste `null`.
125
+ *
126
+ * @param ended - la réponse a-t-elle été terminée par le serveur ?
127
+ * @returns la cause à porter par la ligne d'accès, `null` sinon.
128
+ */
129
+ settle(ended) {
130
+ const call = this.#call;
131
+ if (this.#code !== null || call === null) return null;
132
+ const reason = call.signal.reason;
133
+ if (reason instanceof ConnectError) {
134
+ if (!ended && reason !== this.#reason && reason.code !== Code.DeadlineExceeded) {
135
+ this.#code = Code.Canceled;
136
+ return null;
137
+ }
138
+ this.#code = reason.code;
139
+ return reason.cause instanceof Error ? reason.cause : null;
140
+ }
141
+ if (endsWithoutError(reason)) {
142
+ this.#code = ended ? OK : Code.Canceled;
143
+ return null;
144
+ }
145
+ this.#code = Code.Internal;
146
+ return reason instanceof Error ? reason : new Error("gRPC : le gestionnaire a levé une valeur qui n'est pas une Error", { cause: reason });
147
+ }
148
+ /**
149
+ * Lit une valeur posée par un tiers, ou la valeur par défaut de sa clé.
150
+ *
151
+ * @param key - la clé (`createContextKey`).
152
+ * @returns la valeur posée, sinon `key.defaultValue`.
153
+ */
154
+ get(key) {
155
+ return this.#values === null ? key.defaultValue : this.#values.get(key);
156
+ }
157
+ /**
158
+ * Pose une valeur pour la durée de l'appel.
159
+ *
160
+ * @param key - la clé (`createContextKey`).
161
+ * @param value - la valeur.
162
+ * @returns la fiche, pour chaîner.
163
+ */
164
+ set(key, value) {
165
+ (this.#values ??= createContextValues()).set(key, value);
166
+ return this;
167
+ }
168
+ /**
169
+ * Retire une valeur ; sa clé rendra de nouveau sa valeur par défaut.
170
+ *
171
+ * @param key - la clé (`createContextKey`).
172
+ * @returns la fiche, pour chaîner.
173
+ */
174
+ delete(key) {
175
+ this.#values?.delete(key);
176
+ return this;
177
+ }
178
+ /**
179
+ * Le délai de réponse du serveur est échu : le signal de l'appel s'abaisse
180
+ * (`ctx.abort`) et l'appel se termine en 4 `DEADLINE_EXCEEDED`, même si le
181
+ * gestionnaire ne lit pas son signal. Échu avant que le gestionnaire ne
182
+ * démarre : il est terminé dès son démarrage. Sans effet après la fin.
183
+ */
184
+ expire() {
185
+ if (this.#reason !== null) return;
186
+ this.#interrupt(new ConnectError("délai de réponse du serveur dépassé", Code.DeadlineExceeded));
187
+ }
188
+ /**
189
+ * L'identité qui a ouvert l'appel n'est plus valable : le signal de l'appel
190
+ * s'abaisse et l'appel se termine en 16 `UNAUTHENTICATED`. Réponse unique :
191
+ * sa promesse est rejetée. Flux : il s'arrête au message suivant, même si le
192
+ * gestionnaire ne lit pas son signal — au plus le message déjà en vol part.
193
+ * Sans effet après la fin, ou après une autre interruption.
194
+ */
195
+ revoke() {
196
+ if (this.#reason !== null) return;
197
+ this.#interrupt(new ConnectError("identité révoquée", Code.Unauthenticated));
198
+ }
199
+ /**
200
+ * Le serveur s'arrête : le signal de l'appel s'abaisse et l'appel se termine
201
+ * en 14 `UNAVAILABLE` — le client le rejoue ailleurs (ADR-0026 D8). Flux : il
202
+ * s'arrête au message suivant, même si le gestionnaire ne lit pas son
203
+ * signal. Un gestionnaire bloqué qui n'écoute rien tombe avec sa connexion, à
204
+ * `shutdownTimeout`. Sans effet après la fin, ou après une autre interruption.
205
+ */
206
+ shutdown() {
207
+ if (this.#reason !== null) return;
208
+ this.#interrupt(new ConnectError("le serveur s'arrête", Code.Unavailable));
209
+ }
210
+ /**
211
+ * L'interruption posée par le serveur, `null` tant qu'il n'y en a pas — lue
212
+ * par l'enrobage d'un flux avant chaque message.
213
+ *
214
+ * @internal
215
+ */
216
+ get interruption() {
217
+ return this.#reason;
218
+ }
219
+ /**
220
+ * Suit un gestionnaire à réponse unique (unaire, flux client) jusqu'à sa
221
+ * fin : une interruption pourra rejeter sa promesse.
222
+ *
223
+ * @param ctx - le contexte d'appel remis au gestionnaire.
224
+ * @param reject - le rejet de la promesse rendue à l'adaptateur.
225
+ *
226
+ * @internal
227
+ */
228
+ attach(ctx, reject) {
229
+ this.#ctx = ctx;
230
+ const reason = this.#reason;
231
+ if (reason !== null) {
232
+ this.#end(reason, reject);
233
+ return;
234
+ }
235
+ this.#reject = reject;
236
+ }
237
+ /**
238
+ * Suit un gestionnaire en flux (serveur, bidirectionnel) : une interruption
239
+ * abaissera son signal. Son itérateur s'arrête par {@link interruption}.
240
+ *
241
+ * @param ctx - le contexte d'appel remis au gestionnaire.
242
+ *
243
+ * @internal
244
+ */
245
+ follow(ctx) {
246
+ this.#ctx = ctx;
247
+ const reason = this.#reason;
248
+ if (reason !== null && hasAbort(ctx)) ctx.abort(reason);
249
+ }
250
+ /**
251
+ * Le gestionnaire a répondu ou levé : plus rien à terminer.
252
+ *
253
+ * @internal
254
+ */
255
+ detach() {
256
+ this.#ctx = null;
257
+ this.#reject = null;
258
+ }
259
+ #interrupt(reason) {
260
+ this.#reason = reason;
261
+ const reject = this.#reject;
262
+ if (reject !== null) {
263
+ this.#end(reason, reject);
264
+ return;
265
+ }
266
+ const ctx = this.#ctx;
267
+ if (ctx !== null && hasAbort(ctx)) ctx.abort(reason);
268
+ }
269
+ #end(reason, reject) {
270
+ const ctx = this.#ctx;
271
+ this.detach();
272
+ if (ctx !== null && hasAbort(ctx)) ctx.abort(reason);
273
+ reject(reason);
274
+ }
275
+ };
276
+ /**
277
+ * Garde de type : le contexte d'appel porte-t-il `abort` ? Connect le pose sur
278
+ * tout contexte qu'il crée ; le garde le CONSTATE au lieu de le supposer, pour
279
+ * qu'une version de Connect qui le retirerait se voie à l'exécution (et au
280
+ * test qui le garde) plutôt que par un appel qui ne s'interrompt plus.
281
+ *
282
+ * @param ctx - le contexte remis au gestionnaire.
283
+ * @returns `true` si `ctx.abort` est une fonction.
284
+ *
285
+ * @experimental Surface gRPC, ADR-0026 D11.
286
+ */
287
+ function hasAbort(ctx) {
288
+ return "abort" in ctx && typeof ctx.abort === "function";
289
+ }
290
+ /**
291
+ * Le motif d'une fin SANS erreur : aucun (le signal n'est pas abaissé — en
292
+ * HTTP/1.1, l'écouteur `close` du noyau passe avant celui de Connect), ou un
293
+ * `AbortError` (fin normale, ou client parti).
294
+ */
295
+ function endsWithoutError(reason) {
296
+ return reason === void 0 || reason instanceof Error && reason.name === "AbortError";
297
+ }
298
+ export { GrpcCall, hasAbort };