@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
package/docs/servers.md CHANGED
@@ -22,7 +22,7 @@ tags:
22
22
  ]
23
23
  version: "doc"
24
24
  status: stable
25
- updated: 2026-07-19
25
+ updated: 2026-10-30
26
26
  source: "src/packages/@nodefony/http/docs/servers.md"
27
27
  coverageModule: http
28
28
  coverageFiles: servers/server-http.ts,servers/server-https.ts,servers/server-websocket.ts,certificates.ts
@@ -428,7 +428,7 @@ souvent **moins** de requêtes par seconde qu'en HTTP/1.1. Le choix se fait sur
428
428
 
429
429
  | Forme du trafic | h2c ? | Pourquoi |
430
430
  | -------------------------------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
431
- | gRPC | **requis** | gRPC n'existe qu'en HTTP/2. |
431
+ | gRPC natif derrière le proxy | **requis** | gRPC n'existe qu'en HTTP/2 ; gRPC-Web et Connect passent aussi en HTTP/1.1 — voir [gRPC](../../framework/docs/grpc.md). |
432
432
  | Beaucoup de flux longs (SSE) par pod | **oui** | En HTTP/1.1, chaque flux occupe une connexion du proxy ; en h2c, ils partagent quelques connexions. |
433
433
  | En-têtes lourds et répétés (cookies, jetons) | probable | HPACK les compresse d'une requête à l'autre. |
434
434
  | REST classique, petites réponses | **mesurer** | Souvent neutre, parfois perdant. |
@@ -473,9 +473,9 @@ automatiquement un éventuel décalage de port.
473
473
  le battement de cœur (`server-websocket.ts#Websocket.createServer`).
474
474
  - **Keep-alive** armé à la création (`startHeartbeat()`, `server-websocket.ts#Websocket.createServer`) et par connexion
475
475
  (`trackPong()`, `http-kernel.ts#HttpKernel.handleWebsocket`, juste après l'acceptation du handshake).
476
- - **Arrêt** : il s'inscrit en tête des écouteurs de terminaison
477
- (`prependOnceListener`, `server-websocket.ts#Websocket.createServer`) — l'ordre
478
- compte, voir la section Arrêt gracieux.
476
+ - **Arrêt** : il s'inscrit à l'étape « congé » de l'arrêt du trafic
477
+ (`addShutdownStep("dismiss")`, `server-websocket.ts#Websocket.createServer`), attendue avant le
478
+ drain HTTP — voir la section Arrêt gracieux.
479
479
 
480
480
  ### `server-websocket-secure` — WebSocket sur TLS
481
481
 
@@ -522,16 +522,16 @@ Défauts matérialisés dans `defaultAppConfig` (`src/nodefony/src/config/defaul
522
522
  Table dérivée de `httpServerSchema` (`config.ts#httpServerSchema`) ; la section `https` reprend les mêmes clés et en
523
523
  ajoute une (`httpsServerSchema`, `config.ts#httpsServerSchema`).
524
524
 
525
- | Option | Type | Défaut | Effet |
526
- | ---------------------------- | ----- | ------- | -------------------------------------------------------------------------------- |
527
- | `maxHeadersCount` | int | `2000` | Nombre maximum d'en-têtes par requête — anti _header flooding_. |
528
- | `keepAliveTimeout` | ms | `5000` | Délai de réutilisation de la socket TCP entre deux requêtes. |
529
- | `timeout` | ms | `30000` | Timeout global de socket, aligné sur `responseTimeout`. `0` = désactivé. |
530
- | `requestTimeout` | ms | `30000` | Délai de réception de la requête complète — **anti slow-loris**. |
531
- | `responseTimeout` | ms | `30000` | Délai d'envoi de la réponse complète (couche pipeline). |
532
- | `shutdownTimeout` | ms | `5000` | Drain au shutdown avant destruction forcée. Garder < grâce orchestrateur. |
533
- | `headers` | objet | `null` | En-têtes ajoutés à toutes les réponses. |
534
- | `rejectUnauthorized` (https) | bool | `false` | Rejette les certificats invalides. `false` en dev (auto-signés), `true` en prod. |
525
+ | Option | Type | Défaut | Effet |
526
+ | ---------------------------- | ----- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
527
+ | `maxHeadersCount` | int | `2000` | Nombre maximum d'en-têtes par requête — anti _header flooding_. |
528
+ | `keepAliveTimeout` | ms | `5000` | Délai de réutilisation de la socket TCP entre deux requêtes. |
529
+ | `timeout` | ms | `30000` | Timeout global de socket, aligné sur `responseTimeout`. En HTTP/2 (5152, h2c), une connexion inactive au-delà de ce délai est fermée par GOAWAY ; les flux en cours vont à leur terme. `0` = désactivé. |
530
+ | `requestTimeout` | ms | `30000` | Délai de réception de la requête complète — **anti slow-loris**, en HTTP/1.1 (Node) comme en HTTP/2 (framework : 408, puis flux fermé). `0` le désactive. |
531
+ | `responseTimeout` | ms | `30000` | Délai d'inactivité d'une action (couche pipeline) : au-delà, 503 et `ctx.signal` annulé. Réglable par route (`responseTimeout` d'une option de route). |
532
+ | `shutdownTimeout` | ms | `5000` | Drain au shutdown avant destruction forcée. Garder < grâce orchestrateur. |
533
+ | `headers` | objet | `null` | En-têtes ajoutés à toutes les réponses. |
534
+ | `rejectUnauthorized` (https) | bool | `false` | Rejette les certificats invalides. `false` en dev (auto-signés), `true` en prod. |
535
535
 
536
536
  Ces deux sections sont **permissives** (`z.looseObject`) : toute option supplémentaire de
537
537
  `http.Server` / `net.Server` / TLS (`insecureHTTPParser`, `ciphers`, `minVersion`…) est transmise telle
@@ -552,22 +552,27 @@ Depuis `http2Schema` (`config.ts#http2Schema`), appliqué seulement si défini
552
552
  Depuis `websocketSchema` (`config.ts#websocketSchema`). Les deux sections partagent la forme et les défauts ; le WSS
553
553
  lit `websocketSecure` (`config.ts#httpConfigSchema`).
554
554
 
555
- | Option | Type | Défaut | Effet |
556
- | ------------------------ | ------------------- | ------- | ---------------------------------------------------------------------------------------- |
557
- | `keepaliveInterval` | ms | `20000` | Intervalle des pings — détecte les connexions zombies. |
558
- | `keepaliveGracePeriod` | ms | `10000` | Délai de grâce après un ping sans réponse avant fermeture. |
559
- | `closeTimeout` | ms | `5000` | Délai de fermeture propre avant destruction de la socket. |
560
- | `maxPayload` | octets | `1 MiB` | Taille max d'un message entrant → au-delà, **close 1009** (`config.ts#websocketSchema`). |
561
- | `allowedOrigins` | bool \| str \| list | `false` | Allowlist d'`Origin` au handshake — **anti-CSWSH** (`config.ts#websocketSchema`). |
562
- | `perMessageDeflate` | bool \| objet | `false` | Compression RFC 7692. Désactivée par défaut : coût CPU/RAM + risque de _zip bomb_. |
563
- | `skipUTF8Validation` | bool | `false` | Désactive la validation UTF-8 des frames texte (RFC 6455 §8.1). À laisser `false`. |
564
- | `autoPong` | bool | `true` | Répond automatiquement aux pings entrants (RFC 6455 §5.5.2-3). À laisser `true`. |
565
- | `allowSynchronousEvents` | bool | `true` | Émet plusieurs frames d'un même chunk réseau de façon synchrone (débit vs équité). |
566
- | `maxBackpressure` | octets | `4 MiB` | Seuil du tampon d'envoi par connexion (client lent à recevoir) — anti-OOM. |
567
- | `backpressurePolicy` | `drop` \| `close` | `drop` | Au-delà du seuil : sauter la frame, ou fermer le client (close 1013). |
555
+ | Option | Type | Défaut | Effet |
556
+ | ----------------------------- | ------------------- | ------- | ----------------------------------------------------------------------------------------------- |
557
+ | `keepaliveInterval` | ms | `20000` | Intervalle des pings — détecte les connexions zombies. |
558
+ | `keepaliveGracePeriod` | ms | `10000` | Délai de grâce après un ping sans réponse avant fermeture. |
559
+ | `closeTimeout` | ms | `5000` | Délai de fermeture propre avant destruction de la socket. |
560
+ | `maxPayload` | octets | `1 MiB` | Taille max d'un message entrant → au-delà, **close 1009** (`config.ts#websocketSchema`). |
561
+ | `allowedOrigins` | bool \| str \| list | `false` | Allowlist d'`Origin` au handshake — **anti-CSWSH** (`config.ts#websocketSchema`). |
562
+ | `perMessageDeflate` | bool \| objet | `false` | Compression RFC 7692. Désactivée par défaut : coût CPU/RAM + risque de _zip bomb_. |
563
+ | `skipUTF8Validation` | bool | `false` | Désactive la validation UTF-8 des frames texte (RFC 6455 §8.1). À laisser `false`. |
564
+ | `autoPong` | bool | `true` | Répond automatiquement aux pings entrants (RFC 6455 §5.5.2-3). À laisser `true`. |
565
+ | `maxPendingAutoPongs` | entier ≥ 0 | `60` | Pongs en attente au-delà desquels la connexion est coupée — anti-DoS mémoire. `0` = sans borne. |
566
+ | `allowSynchronousEvents` | bool | `true` | Émet plusieurs frames d'un même chunk réseau de façon synchrone (débit vs équité). |
567
+ | `maxBackpressure` | octets | `4 MiB` | Seuil du tampon d'envoi par connexion (client lent à recevoir) — anti-OOM. |
568
+ | `backpressurePolicy` | `drop` \| `close` | `drop` | Au-delà du seuil : sauter la frame, ou fermer le client (close 1013). |
569
+ | `backpressureCloseAfterDrops` | frames | `1000` | Solde de frames refusées (+1 refus, −1 envoi) qui ferme en 1013 ; `0` = jamais. |
570
+ | `maxFramesPerSecond` | trames / s | `0` | Trames REÇUES admises par connexion ; au-delà, écartées avant décodage. `0` = coupé. |
571
+ | `frameRateCloseAfterRejects` | trames | `1000` | Solde de trames écartées (+1 écartée, −1 admise) qui ferme en 1008 ; `0` = jamais. |
568
572
 
569
573
  > [!WARNING]
570
- > `keepalive*`, `closeTimeout`, `maxBackpressure` et `backpressurePolicy` sont des **réglages
574
+ > `keepalive*`, `closeTimeout`, `maxBackpressure`, `backpressurePolicy`,
575
+ > `backpressureCloseAfterDrops`, `maxFramesPerSecond` et `frameRateCloseAfterRejects` sont des **réglages
571
576
  > Nodefony**, pas des options de `ws` : la bibliothèque les ignore, c'est le framework qui les
572
577
  > implémente. À l'inverse, `server`, `noServer`, `clientTracking`, `host`, `port` et `backlog` sont
573
578
  > **gérés par Nodefony** et non exposés — les forcer n'aurait pas d'effet.
@@ -851,7 +856,9 @@ serveurs (HTTP et HTTPS) servent ces chemins — un kubelet configuré en `schem
851
856
  ## Arrêt gracieux — la séquence du SIGTERM
852
857
 
853
858
  Un arrêt brutal coupe les requêtes en vol : à chaque mise à jour progressive, des utilisateurs voient
854
- une erreur. L'arrêt de Nodefony est une **séquence ordonnée**, et l'ordre est le cœur du sujet.
859
+ une erreur. L'arrêt de Nodefony est une **séquence ordonnée**, et l'ordre est le cœur du sujet. Il
860
+ est écrit dans **une seule fonction**, `http-kernel.ts#HttpKernel.drainTraffic`, l'écouteur `onDrain` du
861
+ module (ADR-0027) : les serveurs s'y inscrivent à leur création, ils n'écoutent pas le noyau.
855
862
 
856
863
  ```mermaid
857
864
  sequenceDiagram
@@ -860,21 +867,22 @@ sequenceDiagram
860
867
  participant W as server-websocket
861
868
  participant H as server-http / https
862
869
  O->>K: SIGTERM
863
- K->>K: readyz → 503 (+ shutdownDelay)
870
+ K->>K: isTerminating → readyz 503 (+ shutdownDelay)
864
871
  Note over K: le load balancer retire le pod
865
- K->>W: onTerminate (prepend)
872
+ K->>W: onDrain — étape « congé »
866
873
  W->>W: close 1001 « Going Away » à chaque client
867
- Note over W: fenêtre ~300 ms
868
- K->>H: onTerminate (once)
874
+ Note over W: fenêtre ~300 ms, fin des flux longs en même temps
875
+ K->>H: onDrain — étape « drain », serveurs ensemble
869
876
  H->>H: drain — in-flight terminées, sockets idle fermées
870
877
  Note over H: destruction forcée après shutdownTimeout
878
+ K->>K: onTerminate — sessions, base, Redis (dernier inscrit d'abord)
871
879
  K->>O: exit 0
872
880
  ```
873
881
 
874
- **Étape 1 — la disponibilité bascule en premier.** L'écouteur est posé à `onPostReady` pour être
875
- inséré **en tête** et donc s'exécuter **en premier** (`http-kernel.ts#HttpKernel.init`). `readyz` répond `503`, le
876
- load balancer cesse d'envoyer du trafic, et `shutdownDelay` laisse à cette information le temps de se
877
- propager.
882
+ **Étape 1 — la disponibilité bascule en premier.** Le noyau pose `kernel.isTerminating` avant toute
883
+ phase d'arrêt (`Kernel.ts#Kernel.isTerminating`), y compris pour un arrêt reçu pendant le démarrage :
884
+ `readyz` répond `503`, le load balancer cesse d'envoyer du trafic, et `shutdownDelay` (attendu si le
885
+ pod a été annoncé disponible) laisse à cette information le temps de se propager.
878
886
 
879
887
  **Étape 2 — les clients WebSocket sont prévenus.** `Websocket.terminate()` (`server-websocket.ts#Websocket.terminate`)
880
888
  envoie un message applicatif puis une **frame Close 1001 « Going Away »** (`server-websocket.ts#Websocket.terminate`).
@@ -891,11 +899,16 @@ indiscernable d'une panne réseau. Avec 1001, le client sait qu'il doit simpleme
891
899
  fermées.
892
900
 
893
901
  Ce qui reste au-delà de `shutdownTimeout` (défaut `5000` ms, `serverShutdown.ts#DEFAULT_SHUTDOWN_TIMEOUT`)
894
- est détruit.
902
+ est détruit. Les serveurs (HTTP, HTTPS, h2c) se drainent **ensemble** : ports distincts, aucune
903
+ dépendance — l'arrêt dure un `shutdownTimeout`, pas un par serveur.
904
+
905
+ **Étape 4 — les ressources sont libérées.** Une fois plus aucune requête en vol, `onTerminate` ferme
906
+ le stockage des sessions, les pools du proxy inverse, les connexions aux bases et à Redis — du
907
+ dernier inscrit au premier.
895
908
 
896
909
  > [!IMPORTANT]
897
- > L'ordre WS-avant-HTTP n'est pas cosmétique. Les serveurs WebSocket s'inscrivent en
898
- > `prependOnceListener` (`server-websocket.ts#Websocket.createServer`), les serveurs HTTP en `once`
910
+ > L'ordre WS-avant-HTTP n'est pas cosmétique. `drainTraffic` attend la fin de l'étape « congé »
911
+ > (`server-websocket.ts#Websocket.createServer`) avant de lancer l'étape « drain »
899
912
  > (`server-http.ts#ServerHttp.createServer`). Inversé, le drain détruirait les sockets déjà upgradées **sans** frame
900
913
  > Close : retour du 1006 pour tous les clients temps réel.
901
914
 
@@ -914,8 +927,12 @@ transport.
914
927
  Dès qu'un écouteur `clientError` est attaché, **Node cesse de fermer la socket automatiquement**. Un
915
928
  écouteur naïf (« je journalise et je passe ») transforme donc une requête malformée en **fuite de
916
929
  socket et de descripteur de fichier** — c'est-à-dire un vecteur de déni de service. Nodefony répond et
917
- ferme explicitement (`handleClientError()`, `clientError.ts#handleClientError`) : `431` si les en-têtes débordent
918
- (RFC 6585 §5), `400` sinon (`clientError.ts#handleClientError`), et rien du tout si la socket est déjà morte.
930
+ ferme explicitement (`clientError.ts#handleClientError`), avec la réponse que Node aurait donnée sans
931
+ écouteur : `431` si les en-têtes débordent (RFC 6585 §5) ou `414` si c'est la cible (RFC 9112 §3),
932
+ `413` si les extensions de chunk débordent, `408` si la réception dépasse `requestTimeout`
933
+ (RFC 9110 §15.5.9), `400` sinon. Rien n'est écrit si la socket est déjà morte — ni si une réponse en
934
+ vol a déjà envoyé ses en-têtes : une ligne de statut écrite après elle corromprait ce que lit le
935
+ client ; la socket est alors détruite, comme le fait Node.
919
936
 
920
937
  ### Connexions WebSocket zombies
921
938
 
@@ -952,6 +969,13 @@ descripteur et du TLS, et couvre tous les pods. Ne l'activer que sur une machine
952
969
  l'OOM — un seul lent peut plomber une diffusion générale. La politique par défaut, `drop`, saute la
953
970
  frame et garde le client connecté.
954
971
 
972
+ Dans l'autre sens, `maxFramesPerSecond` borne le **nombre** de trames qu'une connexion envoie
973
+ (désactivé par défaut). Le compte se fait à l'arrivée de la trame brute
974
+ (`wsFrameRate.ts#admitFrame`) : une trame écartée ne paie ni décodage ni routage, elle est
975
+ comptée (`frameRateRejectedTotal()`, relayé par la sonde du hub temps réel) et la connexion reste
976
+ ouverte — jusqu'à ce que le solde de refus atteigne `frameRateCloseAfterRejects`, qui ferme en
977
+ **1008**. Seul un flux au-delà du double de la borne fait monter ce solde ; un pic passager non.
978
+
955
979
  ## ⚡ Performance & mémoire
956
980
 
957
981
  Le transport est du **chemin chaud absolu** : ce qui coûte ici est multiplié par le nombre de requêtes
package/docs/session.md CHANGED
@@ -469,8 +469,8 @@ timer (`gcIntervalS: 0`).
469
469
 
470
470
  ## Entités de persistance
471
471
 
472
- **Drizzle (SQL).** La table est décrite une seule fois en spec logique (`SESSION_TABLE_SPEC`,
473
- `sessionEntity.ts#SESSION_TABLE_SPEC`) et déclinée par dialecte par `buildFrameworkTable()` (`colKit.ts#buildFrameworkTable`) — mêmes **noms** de
472
+ **Drizzle (SQL).** La table est décrite une seule fois en spec logique (`SESSION_TABLE`,
473
+ `sessionEntity.ts#SESSION_TABLE`) et déclinée par dialecte par `defineTable()` (`colKit.ts#defineTable`) — mêmes **noms** de
474
474
  colonnes partout, donc un store dialect-agnostique.
475
475
 
476
476
  | Colonne | Type logique | SQLite | PostgreSQL | MySQL / MariaDB | Rôle |
@@ -485,7 +485,7 @@ colonnes partout, donc un store dialect-agnostique.
485
485
 
486
486
  En MySQL/MariaDB, une colonne texte indexée devient `varchar` (un `TEXT` InnoDB n'est pas indexable
487
487
  sans préfixe) et le type JSON passe par un type compatible qui tolère MariaDB, laquelle stocke le JSON
488
- en `LONGTEXT` (`colKit.ts#mysqlColumn`).
488
+ en `LONGTEXT` (`colKit.mysql.ts#buildMySqlTable`).
489
489
 
490
490
  **Mongoose (MongoDB).** Schéma équivalent (`@nodefony/mongoose/nodefony/entity/sessionEntity.ts#schema`) :
491
491
  `session_id` (String, index **unique**), `Attributes`/`flashBag`/`metaBag` (Object, défaut `{}`), `user`
package/docs/sse.md CHANGED
@@ -23,7 +23,7 @@ status: stable
23
23
  updated: 2026-10-08
24
24
  source: "src/packages/@nodefony/http/docs/sse.md"
25
25
  coverageModule: http
26
- coverageFiles: context/http/SseStream.ts
26
+ coverageFiles: context/http/SseStream.ts,context/http/StreamLifecycle.ts
27
27
  ---
28
28
 
29
29
  📍 [Documentation](../../../../../docs/index.md) › [@nodefony/http](index.md) › **Flux SSE**
@@ -153,16 +153,16 @@ data: {"step":"écriture"}
153
153
 
154
154
  ### Côté serveur — `this.renderSse()` et l'objet `SseStream`
155
155
 
156
- | Membre | Rôle |
157
- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
158
- | `this.renderSse(options?)` | ouvre le flux (`Controller.renderSse()`, `Controller.ts#Controller.renderSse`) ; lève sur WebSocket |
159
- | `this.acceptsSse()` | le client a-t-il demandé `text/event-stream` ? (`Controller.acceptsSse()`, `Controller.ts#Controller.acceptsSse`) |
160
- | `sse.send(data, { event, id })` | un événement ; texte tel quel, objet en JSON (`SseStream.send()`, `SseStream.ts#SseStream.send`) |
161
- | `sse.comment(text)` | un commentaire, ignoré par le client |
162
- | `sse.retry(ms)` | impose le délai de reconnexion du client |
163
- | `sse.onClose(fn)` | appelé à la fermeture, quel que soit le bout qui ferme (`SseStream.onClose()`, `SseStream.ts#SseStream.onClose`) |
164
- | `sse.close()` | termine la requête, idempotent (`SseStream.close()`, `SseStream.ts#SseStream.close`) |
165
- | `sse.closed` | le flux est-il fermé — à tester dans une boucle longue |
156
+ | Membre | Rôle |
157
+ | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
158
+ | `this.renderSse(options?)` | ouvre le flux (`Controller.renderSse()`, `Controller.ts#Controller.renderSse`) ; lève sur WebSocket |
159
+ | `this.acceptsSse()` | le client a-t-il demandé `text/event-stream` ? (`Controller.acceptsSse()`, `Controller.ts#Controller.acceptsSse`) |
160
+ | `sse.send(data, { event, id })` | un événement ; texte tel quel, objet en JSON ; des octets (`Uint8Array`, `Buffer`) lèvent un `TypeError` : les encoder soi-même (base64) (`SseStream.send()`, `SseStream.ts#SseStream.send`) |
161
+ | `sse.comment(text)` | un commentaire, ignoré par le client |
162
+ | `sse.retry(ms)` | impose le délai de reconnexion du client |
163
+ | `sse.onClose(fn)` | appelé à la fermeture, quel que soit le bout qui ferme (`SseStream.onClose()`, `SseStream.ts#SseStream.onClose`) |
164
+ | `sse.close()` | termine la requête, idempotent (`SseStream.close()`, `SseStream.ts#SseStream.close`) |
165
+ | `sse.closed` | le flux est-il fermé — à tester dans une boucle longue |
166
166
 
167
167
  Options de `renderSse()` :
168
168
 
@@ -214,18 +214,25 @@ le même qui relit le flux dans les tests du serveur.
214
214
 
215
215
  ## 🏗️ Architecture interne — ce que fait l'ouverture
216
216
 
217
+ L'ouverture repose sur le **cycle de vie commun aux réponses en flux**
218
+ (`StreamLifecycle.ts#openStreamLifecycle`, non exporté) — la même brique sert le [flux d'octets](byte-stream.md) :
219
+
217
220
  1. **La réponse est prise** (`context.sended`) : plus rien ne peut répondre à la place du flux.
218
- 2. **La session est sauvée** s'il y en a une — ses cookies partent avec les en-têtes.
219
- 3. **Le transport se constate** : flux HTTP/2, ou réponse HTTP/1.1 ; ni l'un ni l'autre → 500,
220
- avant d'avoir rien écrit (`startSseStream()`, `SseStream.ts#startSseStream`).
221
- 4. **Les en-têtes partent** — `respond()` en HTTP/2, `flushHeaders()` en HTTP/1.1 — et le délai
221
+ 2. **Le transport se constate** : flux HTTP/2, ou réponse HTTP/1.1 ; ni l'un ni l'autre → 500,
222
+ avant d'avoir rien écrit.
223
+ 3. **La fermeture de la réponse est écoutée AVANT toute attente** : un client parti pendant la
224
+ sauvegarde de la session est vu, et le flux naît fermé (`closed`) sans rien écrire. Un client
225
+ déjà parti ne prend pas de place dans le plafond par IP.
226
+ 4. **La session est sauvée** s'il y en a une — ses cookies partent avec les en-têtes.
227
+ 5. **Les en-têtes partent** — `respond()` en HTTP/2, `flushHeaders()` en HTTP/1.1 — et le délai
222
228
  d'inactivité de la requête est **désarmé** : sans cela, le flux vivant prendrait un 408 au bout
223
- de 30 s.
224
- 5. **Le battement démarre** sur un minuteur `unref()`.
229
+ de 30 s (`SseStream.ts#startSseStream`).
230
+ 6. **Le battement démarre** sur un minuteur `unref()`.
225
231
 
226
- La fin, quel que soit le bout qui ferme, passe par **un seul** nettoyage (`SseStream.ts#SseStream.send`) :
227
- minuteur arrêté, écouteurs `close` et `drain` retirés, attente de `drain` libérée, puis les
228
- écouteurs `onClose`.
232
+ La fin, quel que soit le bout qui ferme, passe par **un seul** nettoyage
233
+ (`StreamLifecycle.ts#StreamLifecycle.finish`) : écouteur `close` retiré, minuteurs de durée de vie et
234
+ de blocage arrêtés, sortie du registre de révocation, puis le nettoyage propre au SSE (battement,
235
+ écouteur `drain`, attente libérée), puis les écouteurs `onClose`.
229
236
 
230
237
  ## 🔐 Sécurité
231
238
 
package/docs/upload.md CHANGED
@@ -297,6 +297,30 @@ du calque de configuration — une application peut les changer pour une requêt
297
297
  sont lus au moment de lire le corps (`Request.readBodyLimits()`), donc un calque posé après ce moment
298
298
  (au pare-feu, dans un contrôleur) n'agit pas. Détail : [configuration](../../../../../docs/guides/configuration.md).
299
299
 
300
+ ### Par route — l'option `upload`
301
+
302
+ Une route règle ses propres bornes d'envoi, sans toucher à celles des autres : un dépôt de vidéos
303
+ relève `maxFileSize`, un avatar l'abaisse. Chaque clé déclarée remplace celle de la configuration,
304
+ **dans les deux sens** ; une clé absente garde la valeur de la configuration
305
+ (`framework/nodefony/src/Route.ts#RouteOptions.upload`).
306
+
307
+ ```ts ignore
308
+ // un avatar : 256 Kio au plus, un seul fichier, son empreinte calculée pendant le flux
309
+ @Post("/avatar", { upload: { maxFileSize: 262_144, maxFiles: 1, hashAlgorithm: "sha256" } })
310
+ async avatar(@UploadedFile() file: IUploadedFile | undefined) {
311
+ return { size: file?.size ?? 0, sha256: file?.hash ?? null };
312
+ }
313
+ ```
314
+
315
+ - **Le fichier trop gros est coupé PENDANT son flux** (413) : la route est résolue avant la lecture
316
+ du corps, ses bornes s'appliquent donc à busboy comme celles de la configuration — jamais un
317
+ fichier écrit en entier puis refusé (`context/http/Request.ts#HttpRequest.readBodyLimits`).
318
+ - **Une route sans l'option ne paie rien** : aucune fusion, aucune allocation.
319
+ - **Validée à la déclaration** (`framework/nodefony/decorators/routerDecorators.ts#checkedUpload`) :
320
+ entiers ≥ 1, empreinte connue, aucune clé inconnue — une faute de frappe (`maxFilesize`) laisserait
321
+ sinon la route sur la borne de l'application. `uploadDir` et `encoding` restent ceux de la
322
+ configuration. Exclue avec `bodyStream: true`, dont le corps n'est jamais lu par le pipeline.
323
+
300
324
  ### Fichiers multipart — `upload.*`
301
325
 
302
326
  Table dérivée de `uploadSchema` (`config/config.ts#uploadSchema`). Toutes les bornes sont `runtimeMutable`.
@@ -342,15 +366,15 @@ Les décorateurs `@UploadedFile` / `@UploadedFiles` sont des fabriques de param
342
366
  Chaque entrée de `files` est un `UploadedFile` (`service/upload/upload-service.ts#UploadedFile`), déjà écrit
343
367
  sur disque. Ses membres utiles :
344
368
 
345
- | Membre | Ce qu'il donne | Ancrage |
346
- | ------------------------ | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
347
- | `filename` | Nom **d'origine** déclaré par le client (métadonnée, jamais le chemin). | `realName()` (`service/upload/upload-service.ts#UploadedFile.realName`) |
348
- | `size` | Taille réellement écrite (octets). | `getSize()` (`service/upload/upload-service.ts#UploadedFile.getSize`) |
349
- | `prettySize` | Taille lisible (`« 1.2 MB »`). | `getPrettySize()` (`service/upload/upload-service.ts#UploadedFile.getPrettySize`) |
350
- | `mimeType` | Type MIME déclaré (`image/png`…), sinon deviné de l'extension. | `getMimeType()` (`service/upload/upload-service.ts#UploadedFile.getMimeType`) |
351
- | `hash` / `hashAlgorithm` | Empreinte d'intégrité si `upload.hashAlgorithm` est réglé. | `interfaces/IUpload.ts#IUploadedFile.hashAlgorithm` |
352
- | `moveAsync(target)` | Déplace le temp — **non bloquant, recommandé** dans le pipeline. | `moveAsync()` (`service/upload/upload-service.ts#UploadedFile.moveAsync`) |
353
- | `move(target)` | Variante **synchrone** (compat) — bloque l'event-loop. | `move()` (`service/upload/upload-service.ts#UploadedFile.move`) |
369
+ | Membre | Ce qu'il donne | Ancrage |
370
+ | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
371
+ | `filename` | Nom **d'origine** déclaré par le client (métadonnée, jamais le chemin). | `realName()` (`service/upload/upload-service.ts#UploadedFile.realName`) |
372
+ | `size` | Taille réellement écrite (octets). | `getSize()` (`service/upload/upload-service.ts#UploadedFile.getSize`) |
373
+ | `prettySize` | Taille lisible (`« 1.2 MB »`). | `getPrettySize()` (`service/upload/upload-service.ts#UploadedFile.getPrettySize`) |
374
+ | `mimeType` | Type MIME déclaré (`image/png`…), sinon deviné de l'extension ; `false` si ni l'un ni l'autre. Déclaratif : jamais une preuve du contenu. | `getMimeType()` (`service/upload/upload-service.ts#UploadedFile.getMimeType`) |
375
+ | `hash` / `hashAlgorithm` | Empreinte d'intégrité si `upload.hashAlgorithm` est réglé. | `interfaces/IUpload.ts#IUploadedFile.hashAlgorithm` |
376
+ | `moveAsync(target)` | Déplace le temp — **non bloquant, recommandé** dans le pipeline. Rend le `FileClass` de la destination (sans nom d'origine ni empreinte : ils restent sur le fichier reçu). | `moveAsync()` (`service/upload/upload-service.ts#UploadedFile.moveAsync`) |
377
+ | `move(target)` | Variante **synchrone** (compat) — bloque l'event-loop. | `move()` (`service/upload/upload-service.ts#UploadedFile.move`) |
354
378
 
355
379
  `move`/`moveAsync` acceptent un **fichier cible** ou un **dossier existant** : sur un dossier, la
356
380
  destination est bâtie avec `filename` — d'où l'avertissement de sécurité ci-dessous.
@@ -429,6 +453,7 @@ pipeline : `npm run test:memory` (skill `nodefony-check-memory-health`).
429
453
  | `@Body()` est vide sur un upload de fichier | Les fichiers vont dans `files`, **pas** dans `body` | Lire les fichiers avec `@UploadedFiles()` / `this.files` ; `@Body()` = champs texte |
430
454
  | Un `POST` JSON de 1,5 Mo est refusé en `413` | `maxBodySize` vaut **1 MiB** par défaut | Augmenter `maxBodySize` (pas `upload.maxFileSize`) |
431
455
  | Un gros fichier passe alors que `maxBodySize` est petit | Le multipart **n'écoute pas** `maxBodySize` | Régler `upload.maxFileSize` / `maxTotalFileSize` |
456
+ | Un avatar de 2 Go remplit le disque avant d'être refusé | La borne de l'application (500 Mo) vaut pour toutes les routes | Option de route `upload: { maxFileSize }` — coupé pendant le flux |
432
457
  | `413` sur upload sans message clair | Une borne busboy atteinte en streaming (fichier, cumul, nombre) | Vérifier les bornes `upload.*` — 413 émis sur `stream.on("limit")` (`context/http/Request.ts#HttpRequest.enforceBodyLimit`) |
433
458
  | Un fichier écrit `../../etc/…` après un `move` | `move(dossier)` utilise le nom **client** (`upload-service.ts#UploadedFile.move`) | Passer une cible complète, ou `path.basename(file.filename)` |
434
459
  | Des fichiers temporaires s'accumulent dans `uploadDir` | Le contrôleur ne déplace jamais le temp | Appeler `moveAsync()` (ou purger l'ancien temp par TTL) |
@@ -441,14 +466,15 @@ pipeline : `npm run test:memory` (skill `nodefony-check-memory-health`).
441
466
  Les chiffres exacts vivent dans la carte de tests de cette page (régénérée depuis vitest, jamais figés
442
467
  dans le Markdown).
443
468
 
444
- | Type | Où |
445
- | --------------------------- | ------------------------------------------------------------------------------------------------------------------- |
446
- | Unitaires — `UploadedFile` | `unit/UploadedFile.test.ts` — `create()` async, `move`/`moveAsync`, `mimeType`, `realName` |
447
- | Unitaires — `@Body` stream | `framework` `unit/BodyStream.test.ts` — `@Body({ stream })`, `resolveParamArg`, `routeExpectsBodyStream` |
448
- | Intégration — multipart | `http/upload.test.ts` — upload simple, multi-fichiers, mime non-octet, champ + fichier, `413` par fichier et cumulé |
449
- | Intégration — content-types | `http/body-content-types.test.ts` — JSON, urlencoded, `@Body("champ")`, XML → `body` |
450
- | Intégration — bornes | `http/body-limit.test.ts` — sous la limite `200`, `Content-Length` > 1 MiB → `413`, chunked > 1 MiB refusé |
451
- | Intégration — flux brut | `integration/bodyStream.test.ts` — `@Body({ stream })` bout-en-bout (TLS + clair), non-régression `@Body()` |
469
+ | Type | Où |
470
+ | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
471
+ | Unitaires — `UploadedFile` | `unit/UploadedFile.test.ts` — `create()` async, `move`/`moveAsync`, `mimeType`, `realName` |
472
+ | Unitaires — `@Body` stream | `framework` `unit/BodyStream.test.ts` — `@Body({ stream })`, `resolveParamArg`, `routeExpectsBodyStream` |
473
+ | Intégration — multipart | `http/upload.test.ts` — upload simple, multi-fichiers, mime non-octet, champ + fichier, `413` par fichier et cumulé ; bornes et empreinte PAR ROUTE (abaissée, relevée), aucun temporaire laissé par un refus |
474
+ | Unitaires — option `upload` | `framework` `unit/httpMethodDecorators.test.ts` — portée jusqu'à la route, figée ; refus à la déclaration |
475
+ | Intégration — content-types | `http/body-content-types.test.ts` — JSON, urlencoded, `@Body("champ")`, XML → `body` |
476
+ | Intégration — bornes | `http/body-limit.test.ts` — sous la limite `200`, `Content-Length` > 1 MiB → `413`, chunked > 1 MiB refusé |
477
+ | Intégration — flux brut | `integration/bodyStream.test.ts` — `@Body({ stream })` bout-en-bout (TLS + clair), non-régression `@Body()` |
452
478
 
453
479
  Ce qui **manque** aujourd'hui : pas de banc de **charge** dédié à l'upload (le streaming multipart n'est
454
480
  exercé que fonctionnellement), pas de test **d'attaque** (`*.attack.test.ts`) sur le path traversal de
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nodefony/http",
3
- "version": "10.0.0-beta.5",
3
+ "version": "10.0.0-beta.7",
4
4
  "description": "Serveurs HTTP, HTTPS, HTTP/2 et WebSocket natifs pour Nodefony : sessions, contextes de requête, certificats TLS",
5
5
  "author": "Christophe CAMENSULI <ccamensuli@gmail.com>",
6
6
  "main": "./dist/index.js",
@@ -12,6 +12,12 @@
12
12
  "types": "./dist/types/index.d.ts",
13
13
  "import": "./dist/index.js",
14
14
  "default": "./dist/index.js"
15
+ },
16
+ "./grpc": {
17
+ "nodefony-source": "./nodefony/grpc/index.ts",
18
+ "types": "./dist/types/nodefony/grpc/index.d.ts",
19
+ "import": "./dist/nodefony/grpc/index.js",
20
+ "default": "./dist/nodefony/grpc/index.js"
15
21
  }
16
22
  },
17
23
  "scripts": {
@@ -27,7 +33,7 @@
27
33
  },
28
34
  "private": false,
29
35
  "engines": {
30
- "node": ">=24.14.0"
36
+ "node": "^24.16.0 || >=26.0.0"
31
37
  },
32
38
  "keywords": [
33
39
  "nodefony",
@@ -51,38 +57,52 @@
51
57
  "test": "./nodefony/tests"
52
58
  },
53
59
  "dependencies": {
54
- "@fastify/busboy": "3.2.4",
55
- "cookie": "2.0.1",
60
+ "@fastify/busboy": "3.2.5",
61
+ "@types/ms": "2.1.0",
62
+ "@types/qs": "6.15.1",
63
+ "@types/serve-static": "2.2.0",
64
+ "@types/ws": "8.18.2",
65
+ "@types/xml2js": "0.4.14",
66
+ "cookie": "2.0.2",
56
67
  "mime-types": "3.0.2",
57
68
  "ms": "^2.1.3",
58
- "qs": "^6.16.1",
69
+ "qs": "^6.17.1",
59
70
  "serve-static": "2.2.1",
60
- "tslib": "2.8.1",
61
- "ws": "8.22.0",
71
+ "tslib": "2.9.1",
72
+ "ws": "8.23.0",
62
73
  "xml2js": "0.6.2"
63
74
  },
64
75
  "devDependencies": {
65
76
  "@types/chai": "5.2.3",
66
77
  "@types/mime-types": "3.0.1",
67
- "@types/ms": "2.1.0",
68
- "@types/node": "26.6.5",
69
- "@types/qs": "6.15.1",
70
- "@types/serve-static": "2.2.0",
78
+ "@types/node": "26.6.6",
71
79
  "@types/websocket": "1.0.10",
72
- "@types/ws": "8.18.2",
73
- "@types/xml2js": "0.4.14",
74
- "@vitest/coverage-v8": "5.0.5",
75
- "nodefony": "^10.0.0-beta.5",
80
+ "@vitest/coverage-v8": "5.1.0",
81
+ "nodefony": "^10.0.0-beta.7",
76
82
  "tsx": "4.23.15",
77
- "vitest": "5.0.5"
83
+ "vitest": "5.1.0"
78
84
  },
79
85
  "license": "Apache-2.0",
80
86
  "readmeFilename": "README.md",
81
87
  "contributors": [],
82
88
  "peerDependencies": {
83
- "nodefony": "^10.0.0-beta.5",
89
+ "@bufbuild/protobuf": "^2.7.0",
90
+ "@connectrpc/connect": "^2.3.0",
91
+ "@connectrpc/connect-node": "^2.3.0",
92
+ "nodefony": "^10.0.0-beta.7",
84
93
  "zod": "^4.6.7"
85
94
  },
95
+ "peerDependenciesMeta": {
96
+ "@bufbuild/protobuf": {
97
+ "optional": true
98
+ },
99
+ "@connectrpc/connect": {
100
+ "optional": true
101
+ },
102
+ "@connectrpc/connect-node": {
103
+ "optional": true
104
+ }
105
+ },
86
106
  "files": [
87
107
  "dist",
88
108
  "docs"
@@ -90,7 +110,7 @@
90
110
  "publishConfig": {
91
111
  "access": "public"
92
112
  },
93
- "homepage": "https://nodefony.github.io/nodefony-core/",
113
+ "homepage": "https://nodefony.net/",
94
114
  "bugs": {
95
115
  "url": "https://github.com/nodefony/nodefony-core/issues"
96
116
  }