@nodefony/realtime 10.0.0-beta.1 → 10.0.0-beta.3
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.
- package/README.md +2 -2
- package/dist/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.153.0}/helpers/esm/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.151.0 → _@oxc-project_runtime@0.153.0}/helpers/esm/decorateMetadata.js +1 -1
- package/dist/index.js +17 -10
- package/dist/nodefony/config/config.js +6 -3
- package/dist/nodefony/config/configSchema.js +19 -0
- package/dist/nodefony/config/defineModuleConfig.js +42 -15
- package/dist/nodefony/decorators/realtimeDecorators.js +68 -17
- package/dist/nodefony/src/backplane/RedisBackplane.js +21 -4
- package/dist/nodefony/src/backplane/bootNotices.js +43 -0
- package/dist/nodefony/src/backplane/publishQueue.js +1 -4
- package/dist/nodefony/src/cluster/ClusterProbeClient.js +15 -4
- package/dist/nodefony/src/guards.js +41 -0
- package/dist/nodefony/src/server/AnonymousRealtimeToken.js +1 -4
- package/dist/nodefony/src/server/RealtimeController.js +120 -50
- package/dist/nodefony/src/server/RealtimeHub.js +29 -51
- package/dist/nodefony/src/server/ServerRealtimeSocket.js +13 -5
- package/dist/nodefony/src/server/syslogUplink.js +4 -2
- package/dist/nodefony/src/service/RealtimeService.js +3 -2
- package/dist/nodefony/testing/index.js +41 -1
- package/dist/types/nodefony/config/configSchema.d.ts +12 -0
- package/dist/types/nodefony/config/defineModuleConfig.d.ts +29 -6
- package/dist/types/nodefony/decorators/realtimeDecorators.d.ts +19 -2
- package/dist/types/nodefony/interfaces/IRealtimeController.d.ts +1 -1
- package/dist/types/nodefony/interfaces/IRealtimeHandshake.d.ts +1 -1
- package/dist/types/nodefony/src/backplane/RedisBackplane.d.ts +7 -0
- package/dist/types/nodefony/src/backplane/bootNotices.d.ts +25 -0
- package/dist/types/nodefony/src/guards.d.ts +32 -0
- package/dist/types/nodefony/src/server/AnonymousRealtimeToken.d.ts +13 -0
- package/dist/types/nodefony/src/server/RealtimeController.d.ts +24 -4
- package/dist/types/nodefony/src/server/RealtimeHub.d.ts +22 -16
- package/dist/types/nodefony/src/service/RealtimeService.d.ts +1 -1
- package/dist/types/nodefony/testing/index.d.ts +2 -2
- package/docs/actions.md +124 -41
- package/docs/architecture.md +93 -91
- package/docs/cadence-et-contre-pression.md +634 -0
- package/docs/configuration.md +66 -56
- package/docs/cookbook-chat.md +39 -33
- package/docs/index.md +47 -31
- package/docs/observabilite.md +21 -18
- package/docs/protocole.md +79 -65
- package/docs/securite.md +69 -65
- package/docs/vocabulaire.md +80 -79
- package/package.json +16 -12
- package/docs/couverture-bancs.md +0 -139
package/docs/index.md
CHANGED
|
@@ -37,7 +37,7 @@ coverageModule: realtime
|
|
|
37
37
|
|
|
38
38
|
## 🧭 Par où commencer
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
Quatre parcours selon ce que tu viens faire. L'ordre compte : chaque étape suppose la précédente.
|
|
41
41
|
|
|
42
42
|
**Je découvre le temps réel Nodefony** — comprendre le modèle avant d'écrire quoi que ce soit.
|
|
43
43
|
|
|
@@ -69,20 +69,31 @@ abonné connecté à l'autre.
|
|
|
69
69
|
3. [`@nodefony/security`](../../security/docs/index.md) — le pare-feu applicatif, qui protège le
|
|
70
70
|
temps réel avec **le même** modèle de zones que le web.
|
|
71
71
|
|
|
72
|
+
**Un client ne suit plus** — l'écran prend du retard, la mémoire du serveur monte, une connexion
|
|
73
|
+
se ferme en `1013`.
|
|
74
|
+
|
|
75
|
+
1. [Cadence et contre-pression](./cadence-et-contre-pression.md) — les deux étages qui encaissent
|
|
76
|
+
un client lent : la cadence que le client ralentit lui-même, la file que le serveur refuse de
|
|
77
|
+
laisser grossir. **Commence par le schéma général**, puis les courbes.
|
|
78
|
+
2. [Observabilité](./observabilite.md) — lire les clients lents et les trames refusées dans la
|
|
79
|
+
sonde, avant que quelqu'un ne se plaigne.
|
|
80
|
+
3. [Configuration](./configuration.md) — serrer les seuils, sur les **deux** serveurs WebSocket.
|
|
81
|
+
|
|
72
82
|
## 🗂️ Les pages du module
|
|
73
83
|
|
|
74
84
|
Le tableau pour choisir en cinq secondes ; les cards en dessous pour savoir ce qu'on y trouve.
|
|
75
85
|
|
|
76
|
-
| Page
|
|
77
|
-
|
|
|
78
|
-
| [Vocabulaire](./vocabulaire.md)
|
|
79
|
-
| [Architecture](./architecture.md)
|
|
80
|
-
| [Configuration](./configuration.md)
|
|
81
|
-
| [Sécurité](./securite.md)
|
|
82
|
-
| [Protocole](./protocole.md)
|
|
83
|
-
| [Actions RPC](./actions.md)
|
|
84
|
-
| [Observabilité](./observabilite.md)
|
|
85
|
-
| [
|
|
86
|
+
| Page | Ce qu'elle résout | Tu en as besoin quand… |
|
|
87
|
+
| ------------------------------------------------------------- | -------------------------------------------------- | --------------------------------------------- |
|
|
88
|
+
| [Vocabulaire](./vocabulaire.md) | les mots du domaine, une bonne fois | tu lis la doc, ou tu discutes archi |
|
|
89
|
+
| [Architecture](./architecture.md) | le trajet d'une frame, étage par étage | tu veux comprendre plutôt que régler |
|
|
90
|
+
| [Configuration](./configuration.md) | drivers, cloisonnement, bornes, contrôle d'origine | tu déploies, ou tu changes de topologie |
|
|
91
|
+
| [Sécurité](./securite.md) | identité à la poignée de main, droits par canal | ta socket est joignable depuis un navigateur |
|
|
92
|
+
| [Protocole](./protocole.md) | la grammaire d'une frame, et les codes d'erreur | tu débogues le fil, ou tu écris un client |
|
|
93
|
+
| [Actions RPC](./actions.md) | appeler le serveur et attendre une réponse | tu veux savoir si l'appel a marché |
|
|
94
|
+
| [Observabilité](./observabilite.md) | la sonde, les canaux de santé, les écrans | tu te demandes si ta socket va bien |
|
|
95
|
+
| [Cadence et contre-pression](./cadence-et-contre-pression.md) | un client trop lent : ralentir, refuser, couper | un écran prend du retard, ou la mémoire monte |
|
|
96
|
+
| [Cookbook — un chat](./cookbook-chat.md) | l'exemple complet, client et serveur | tu veux du code qui marche tout de suite |
|
|
86
97
|
|
|
87
98
|
```nodefony-cards
|
|
88
99
|
[
|
|
@@ -107,6 +118,9 @@ Le tableau pour choisir en cinq secondes ; les cards en dessous pour savoir ce q
|
|
|
107
118
|
{ "icon": "📡", "title": "observabilite", "href": "./observabilite.md",
|
|
108
119
|
"desc": "Ce que la sonde du hub expose, les canaux de santé et leur cadence, ce que Studio en montre — et comment écrire la sienne sans faire fuir la mémoire ni empêcher le processus de s'arrêter.",
|
|
109
120
|
"meta": "tu te demandes si ta socket va bien" },
|
|
121
|
+
{ "icon": "🚦", "title": "cadence-et-contre-pression", "href": "./cadence-et-contre-pression.md",
|
|
122
|
+
"desc": "Deux étages qui ne se parlent pas et se rencontrent quand même : le client ralentit la cadence de ses canaux d'état (AIMD), le serveur refuse d'empiler au-delà de 4 Mio puis ferme en 1013. Avec les courbes calculées par le vrai code, et les sondes qui le montrent en production.",
|
|
123
|
+
"meta": "un écran prend du retard, ou la mémoire du serveur monte" },
|
|
110
124
|
{ "icon": "🍳", "title": "cookbook-chat", "href": "./cookbook-chat.md",
|
|
111
125
|
"desc": "Un salon de discussion complet : configuration, service métier, contrôleur, client navigateur, puis le passage en cluster.",
|
|
112
126
|
"meta": "du code qui tourne, à déformer vers ton besoin" }
|
|
@@ -119,18 +133,18 @@ Quatre propriétés, toutes vérifiables dans le code — c'est ce qui justifie
|
|
|
119
133
|
WebSocket brute.
|
|
120
134
|
|
|
121
135
|
**Un contrôleur temps réel est un contrôleur.** `RealtimeController`
|
|
122
|
-
(`RealtimeController.ts:
|
|
136
|
+
(`RealtimeController.ts:203`) étend le `Controller` du framework : il se déclare avec les mêmes
|
|
123
137
|
décorateurs de route, reçoit la même injection, passe par le même pare-feu. HTTP et WebSocket ne sont
|
|
124
138
|
pas deux applications à écrire deux fois, mais deux entrées du même pipeline.
|
|
125
139
|
|
|
126
140
|
**Une connexion, N canaux, dans les deux sens.** Le client s'abonne à autant de canaux qu'il veut sur
|
|
127
141
|
la même socket. Trois formes de trafic coexistent : le serveur diffuse (`@RealtimeChannel`,
|
|
128
|
-
`realtimeDecorators.ts:
|
|
129
|
-
`realtimeDecorators.ts:
|
|
130
|
-
`realtimeDecorators.ts:
|
|
142
|
+
`realtimeDecorators.ts:240`), le client appelle et attend une réponse (`@RealtimeAction`,
|
|
143
|
+
`realtimeDecorators.ts:191`), le client pousse sans attendre (`@RealtimeInbound`,
|
|
144
|
+
`realtimeDecorators.ts:278`). Rien n'est ouvert qui n'ait été déclaré.
|
|
131
145
|
|
|
132
146
|
**Le travail est fait une fois par processus, pas une fois par client.** Le `RealtimeHub`
|
|
133
|
-
(`RealtimeHub.ts:
|
|
147
|
+
(`RealtimeHub.ts:214`) tient **un seul producteur par canal** : le premier abonné le démarre, le
|
|
134
148
|
dernier départ l'arrête. Chaque connexion supplémentaire n'ajoute qu'une destination de diffusion.
|
|
135
149
|
Un salon suivi par mille personnes coûte un producteur, pas mille.
|
|
136
150
|
|
|
@@ -143,7 +157,7 @@ toucher au cœur.
|
|
|
143
157
|
> [!IMPORTANT]
|
|
144
158
|
> **Rien ne franchit la frontière du processus sans intention explicite.** Par défaut, un canal reste
|
|
145
159
|
> local. Il faut le déclarer diffusable (`@RealtimeBroadcast`,
|
|
146
|
-
> `realtimeDecorators.ts:
|
|
160
|
+
> `realtimeDecorators.ts:420`) pour qu'il emprunte le backplane. C'est volontaire : un canal
|
|
147
161
|
> d'observation qui décrit l'état d'**un** pod n'aurait aucun sens répliqué sur les autres.
|
|
148
162
|
|
|
149
163
|
Le module se déclare par ailleurs **non critique** (`Realtime.critical`, `index.ts:144`) et son
|
|
@@ -239,11 +253,11 @@ export default ChatController;
|
|
|
239
253
|
Le client est isomorphe : c'est le cœur `nodefony` lui-même, importable côté navigateur.
|
|
240
254
|
|
|
241
255
|
```ts
|
|
242
|
-
import {
|
|
256
|
+
import { NodefonySocket } from "nodefony/client";
|
|
243
257
|
|
|
244
258
|
// `shared` réutilise la connexion existante pour une même URL : dix composants
|
|
245
259
|
// qui écoutent dix canaux ouvrent UNE socket, pas dix.
|
|
246
|
-
const socket =
|
|
260
|
+
const socket = NodefonySocket.shared({
|
|
247
261
|
url: "wss://127.0.0.1:5152/chat/realtime",
|
|
248
262
|
});
|
|
249
263
|
|
|
@@ -295,7 +309,7 @@ Côté serveur : `RealtimeController` (la classe à étendre), les décorateurs
|
|
|
295
309
|
publier depuis n'importe quel service injecté, le `RealtimeHub` et sa sonde, les trois backplanes
|
|
296
310
|
natifs, et le registre de drivers pour brancher le tien.
|
|
297
311
|
|
|
298
|
-
Côté client : `
|
|
312
|
+
Côté client : `NodefonySocket` via le subpath `nodefony/client`, et les hooks React via
|
|
299
313
|
`nodefony/react`.
|
|
300
314
|
|
|
301
315
|
Les signatures exactes vivent dans le graphe généré — `jq '.symbols.RealtimeHub' .ai/symbols.json` —
|
|
@@ -304,7 +318,7 @@ jamais recopiées ici : elles divergeraient en silence.
|
|
|
304
318
|
## ⚙️ Configuration
|
|
305
319
|
|
|
306
320
|
Un seul point d'entrée : `use("@nodefony/realtime", { … })` dans `nodefony.config.ts`, validé au boot
|
|
307
|
-
contre le schéma du module (`realtimeConfigSchema`, `config.ts:259`).
|
|
321
|
+
contre le schéma du module (`realtimeConfigSchema`, `config.ts:259`). Six blocs :
|
|
308
322
|
|
|
309
323
|
- `backplane` — le driver de fan-out et son espace de nommage. Ce cloisonnement devient
|
|
310
324
|
indispensable dès que **deux déploiements partagent le même Redis** : sans lui, leurs publications
|
|
@@ -313,6 +327,7 @@ contre le schéma du module (`realtimeConfigSchema`, `config.ts:259`). Cinq bloc
|
|
|
313
327
|
- `limits` — le plafond de canaux par connexion, garde anti-saturation mémoire.
|
|
314
328
|
- `slowConsumer` — le seuil à partir duquel un client trop lent est signalé par la sonde.
|
|
315
329
|
- `cluster.probe` — la sonde agrégée du pod, en mode multi-workers.
|
|
330
|
+
- `clientLogs` — la réception des journaux du navigateur (canal montant), fermée par défaut.
|
|
316
331
|
|
|
317
332
|
Chaque bloc, ses valeurs d'usine et les situations qui justifient d'en changer sont détaillés dans
|
|
318
333
|
[Configuration](./configuration.md).
|
|
@@ -325,7 +340,7 @@ vue de tous les workers d'un pod.
|
|
|
325
340
|
|
|
326
341
|
Le data plane admin expose `/nodefony/realtime/api/health` — canaux et abonnés, compteurs de
|
|
327
342
|
diffusion, connexions, octets et frames, pression d'écriture. C'est la même donnée que celle rendue à
|
|
328
|
-
l'écran, servie par la sonde du hub (`RealtimeHub.probe()`, `RealtimeHub.ts:
|
|
343
|
+
l'écran, servie par la sonde du hub (`RealtimeHub.probe()`, `RealtimeHub.ts:847`).
|
|
329
344
|
|
|
330
345
|
## 🧪 Tests & couverture
|
|
331
346
|
|
|
@@ -333,24 +348,25 @@ Les compteurs sont régénérés depuis vitest, jamais figés dans cette prose.
|
|
|
333
348
|
ici, c'est **ce que les suites prouvent** — et ce qu'elles ne prouvent qu'à condition d'avoir
|
|
334
349
|
l'infrastructure sous la main.
|
|
335
350
|
|
|
336
|
-
| Type | Où | Ce qui est prouvé
|
|
337
|
-
| --------------- | ------------------------------- |
|
|
338
|
-
| Unitaire | `nodefony/tests/unit/**` | hub, registre de drivers, schéma de configuration, décorateurs
|
|
339
|
-
| Intégration | `nodefony/tests/integration/**` | socket réelle : abonnements, chemins de contrôleur, autorisation
|
|
340
|
-
| Multi-processus | `clusterIpc.e2e.test.ts` | fan-out entre workers, sans
|
|
341
|
-
| Cross-machine | `redisCluster.e2e.test.ts` | fan-out entre pods via pub/sub Redis
|
|
342
|
-
| Tests d'attaque | `*.attack.test.ts` | plafond de canaux, révocation d'identité, politique non appliquée |
|
|
351
|
+
| Type | Où | Ce qui est prouvé |
|
|
352
|
+
| --------------- | ------------------------------- | --------------------------------------------------------------------------------------- |
|
|
353
|
+
| Unitaire | `nodefony/tests/unit/**` | hub, registre de drivers, schéma de configuration, décorateurs |
|
|
354
|
+
| Intégration | `nodefony/tests/integration/**` | socket réelle : abonnements, chemins de contrôleur, autorisation |
|
|
355
|
+
| Multi-processus | `clusterIpc.e2e.test.ts` | fan-out entre workers, sans infrastructure externe (`npm run test:cluster`) |
|
|
356
|
+
| Cross-machine | `redisCluster.e2e.test.ts` | fan-out entre pods via pub/sub Redis |
|
|
357
|
+
| Tests d'attaque | `*.attack.test.ts` | plafond de canaux, révocation d'identité, politique non appliquée, injection sur le bus |
|
|
343
358
|
|
|
344
359
|
> [!WARNING]
|
|
345
360
|
> **Une suite verte ne prouve rien sur le fan-out cross-machine.** Le banc Redis est doublement
|
|
346
361
|
> conditionnel : il ne s'exécute que si tu le demandes (`NF_RUN_CLUSTER_E2E=1`), et il se **saute** de
|
|
347
362
|
> lui-même si aucun Redis n'est joignable. Or un test sauté compte comme un succès — on peut donc
|
|
348
363
|
> lire « tout est vert » sur une suite qui n'a jamais ouvert une seule connexion. Le fan-out entre
|
|
349
|
-
> workers d'un même pod
|
|
364
|
+
> workers d'un même pod ne demande aucune infrastructure externe, mais ne tourne que dans
|
|
365
|
+
> `npm run test:cluster` avec `NF_RUN_CLUSTER_E2E=1`.
|
|
350
366
|
>
|
|
351
367
|
> ```bash
|
|
352
368
|
> # Le seul run qui prouve réellement le fan-out entre machines.
|
|
353
|
-
> NF_RUN_CLUSTER_E2E=1 NF_REDIS_PASSWORD=nodefony-dev npm test
|
|
369
|
+
> NF_RUN_CLUSTER_E2E=1 NF_REDIS_PASSWORD=nodefony-dev npm run test:cluster
|
|
354
370
|
> ```
|
|
355
371
|
|
|
356
372
|
## 🔗 Pour aller plus loin
|
package/docs/observabilite.md
CHANGED
|
@@ -96,7 +96,7 @@ temps de réponse — elle mesure **ce qui n'est pas encore parti**.
|
|
|
96
96
|
Trois partis pris, tous lisibles dans le code.
|
|
97
97
|
|
|
98
98
|
**1. La sonde est une lecture pure, jamais un collecteur.** `RealtimeHub.probe()`
|
|
99
|
-
(`RealtimeHub.ts:
|
|
99
|
+
(`RealtimeHub.ts:847`) ne fait qu'additionner des primitives déjà tenues à jour par le chemin chaud.
|
|
100
100
|
Aucune entrée-sortie, aucune exception possible, aucun état de lecture conservé. Appeler la sonde
|
|
101
101
|
mille fois par seconde ne changerait rien à ce que le hub fait par ailleurs.
|
|
102
102
|
|
|
@@ -228,10 +228,10 @@ Côté navigateur, deux lignes suffisent à consommer le canal :
|
|
|
228
228
|
|
|
229
229
|
```ts
|
|
230
230
|
// frontend/src/shopHealth.ts
|
|
231
|
-
import {
|
|
231
|
+
import { NodefonySocket } from "nodefony/client";
|
|
232
232
|
|
|
233
233
|
const scheme = window.location.protocol === "https:" ? "wss" : "ws";
|
|
234
|
-
const socket = new
|
|
234
|
+
const socket = new NodefonySocket({
|
|
235
235
|
url: `${scheme}://${window.location.host}/shop/realtime`,
|
|
236
236
|
});
|
|
237
237
|
|
|
@@ -265,7 +265,7 @@ Le contrat est `IRealtimeProbe` (`IRealtimeProbe.ts:61`). Quatre familles, un di
|
|
|
265
265
|
| ---------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
266
266
|
| `publishTotal` | Appels à `publish`. **Ce que le serveur a voulu envoyer.** |
|
|
267
267
|
| `fanoutTotal` | Livraisons effectives (`publish` × abonnés). **Ce que ça a réellement coûté.** |
|
|
268
|
-
| `inboundTotal` | Frames poussées par les clients sur les canaux entrants, via `RealtimeHub.recordInbound()` (`RealtimeHub.ts:
|
|
268
|
+
| `inboundTotal` | Frames poussées par les clients sur les canaux entrants, via `RealtimeHub.recordInbound()` (`RealtimeHub.ts:836`). |
|
|
269
269
|
| `ingressRejectedTotal` | Messages venus du **backplane** et jetés parce que leur canal n'est pas déclaré diffusable. |
|
|
270
270
|
|
|
271
271
|
Le rapport `fanoutTotal / publishTotal` est la mesure la plus parlante de la page : c'est la **taille
|
|
@@ -316,13 +316,15 @@ deux autres. Le débit en octets par seconde se dérive de deux photos.
|
|
|
316
316
|
|
|
317
317
|
La source par connexion est `IRealtimeConnProbe` (`IRealtimeProbe.ts:25`), implémentée par le
|
|
318
318
|
transport. Les seuils d'ACTION sont distincts du seuil de comptage, et ils sont
|
|
319
|
-
**configurables** (`@nodefony/http`, `http/nodefony/config/config.ts:
|
|
319
|
+
**configurables** (`@nodefony/http`, `http/nodefony/config/config.ts:1226`) : au-delà de
|
|
320
320
|
`websocket.maxBackpressure` (4 MiB par défaut) la politique `websocket.backpressurePolicy`
|
|
321
|
-
s'applique — `drop` par défaut, la frame est **jetée
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
321
|
+
s'applique — `drop` par défaut, la frame est **jetée**. Sur un canal d'état c'est sans
|
|
322
|
+
conséquence, le prochain instantané la remplace ; mais le rejet ne trie rien, et une réponse RPC
|
|
323
|
+
ou un événement jetés sont **perdus**. La connexion est **fermée** en `1013` (« réessaie plus
|
|
324
|
+
tard ») quand le **solde** de refus atteint `websocket.backpressureCloseAfterDrops` (1000 par
|
|
325
|
+
défaut) — +1 par frame jetée, −1 par frame envoyée, si bien qu'un pic passager redescend — ou
|
|
326
|
+
dès le premier dépassement si la politique est `close`. Le mécanisme complet, avec ses courbes :
|
|
327
|
+
[Cadence et contre-pression](./cadence-et-contre-pression.md).
|
|
326
328
|
|
|
327
329
|
> [!IMPORTANT]
|
|
328
330
|
> `drops` qui croît n'est **pas** une panne : c'est la protection qui fonctionne. Ce qui serait une
|
|
@@ -447,7 +449,8 @@ après fusion et validation — la seule vue qui dit ce qui s'applique, plutôt
|
|
|
447
449
|
|
|
448
450
|
> [!CAUTION]
|
|
449
451
|
> `slowConsumer.bytes` ne pilote **que la métrique**. Les seuils qui jettent une frame ou ferment une
|
|
450
|
-
> connexion
|
|
452
|
+
> connexion se règlent côté `@nodefony/http` (`websocket.maxBackpressure`, `.backpressurePolicy`,
|
|
453
|
+
> `.backpressureCloseAfterDrops`). Baisser cette clé rend le signalement plus précoce ; ça
|
|
451
454
|
> ne change rien au comportement de la socket.
|
|
452
455
|
|
|
453
456
|
Couper la sonde cluster est un **contournement total**, pas une mise en sourdine : sans elle, il n'y a
|
|
@@ -461,11 +464,11 @@ instantané est exact — et ne dit rien des trois autres. Pire : deux appels co
|
|
|
461
464
|
sur deux workers et sembler se contredire.
|
|
462
465
|
|
|
463
466
|
**La réponse de Nodefony est un modèle _push_.** Chaque worker remonte périodiquement sa santé au
|
|
464
|
-
maître via `ClusterProbeClient.start()` (`ClusterProbeClient.ts:
|
|
467
|
+
maître via `ClusterProbeClient.start()` (`ClusterProbeClient.ts:190`) ; le maître fusionne et
|
|
465
468
|
rediffuse ; chaque worker met le résultat en cache. **N'importe lequel** sert alors la vue pod en temps
|
|
466
|
-
constant, sans latence de requête (`ClusterProbeClient.getClusterHealth()`, `ClusterProbeClient.ts:
|
|
469
|
+
constant, sans latence de requête (`ClusterProbeClient.getClusterHealth()`, `ClusterProbeClient.ts:306`).
|
|
467
470
|
|
|
468
|
-
La fusion, `mergeClusterHealth()` (`ClusterProbeClient.ts:
|
|
471
|
+
La fusion, `mergeClusterHealth()` (`ClusterProbeClient.ts:60`), est une fonction pure — et sa règle
|
|
469
472
|
mérite d'être connue :
|
|
470
473
|
|
|
471
474
|
| Grandeur | Agrégation | Pourquoi |
|
|
@@ -482,7 +485,7 @@ garde le détail par worker pour le forage.
|
|
|
482
485
|
### On paie ce qu'on regarde
|
|
483
486
|
|
|
484
487
|
Les sondes coûteuses ne tournent pas en permanence. Le maître peut demander à un worker précis
|
|
485
|
-
d'**enrichir** sa remontée — `ClusterProbeClient.requestEnrich()` (`ClusterProbeClient.ts:
|
|
488
|
+
d'**enrichir** sa remontée — `ClusterProbeClient.requestEnrich()` (`ClusterProbeClient.ts:259`) — quand
|
|
486
489
|
un humain ouvre le forage de ce `pid`. La sonde riche (espaces mémoire, cycles de ramasse-miettes,
|
|
487
490
|
descripteurs actifs) est alors allouée ; à la fermeture de l'écran, elle est libérée et son observateur
|
|
488
491
|
détaché.
|
|
@@ -503,7 +506,7 @@ l'orchestrateur finit par le tuer de force à l'expiration du délai de grâce.
|
|
|
503
506
|
|
|
504
507
|
La règle est tenue partout dans le code, y compris là où on ne l'attendrait pas — le tick de
|
|
505
508
|
revalidation d'identité du hub, qui ne démarre qu'au premier inscrit, est lui aussi détaché
|
|
506
|
-
(`RealtimeHub.ts:
|
|
509
|
+
(`RealtimeHub.ts:443`), tout comme le report de la sonde cluster (`ClusterProbeClient.ts:170`) et le
|
|
507
510
|
minuteur générique des canaux (`providers.ts:465`).
|
|
508
511
|
|
|
509
512
|
```ts ignore
|
|
@@ -552,7 +555,7 @@ aux sondes qui instrumentent chaque opération et se paient donc à l'usage.
|
|
|
552
555
|
| `publishTotal` grimpe, `fanoutTotal` stagne | Tu publies dans le vide : personne n'est abonné au canal visé | Comparer le nom publié et le nom souscrit — presque toujours une faute de frappe |
|
|
553
556
|
| Deux appels consécutifs à `/api/health` donnent des chiffres contradictoires | En cluster, la requête tombe sur un worker au hasard et rend une vue per-instance | Lire le champ `cluster` ; si absent, la sonde pod est coupée ou en démarrage à froid |
|
|
554
557
|
| La mémoire du processus grimpe sans fuite apparente | Une sonde alloue à chaque tick, ou un client lent fait grossir la file d'envoi | Lire `backpressure.totalBufferedAmount` ; simplifier le corps du tick |
|
|
555
|
-
| Baisser `slowConsumer.bytes` ne ferme pas les clients lents | Cette clé pilote le **comptage** de la sonde ; les seuils d'action sont
|
|
558
|
+
| Baisser `slowConsumer.bytes` ne ferme pas les clients lents | Cette clé pilote le **comptage** de la sonde ; les seuils d'action sont ceux du serveur WebSocket | Régler `maxBackpressure` / `backpressureCloseAfterDrops` dans `@nodefony/http` |
|
|
556
559
|
| La cadence demandée n'est pas respectée | Elle a été **ramenée dans les bornes** du canal, silencieusement | Lire les bornes du canal ; vérifier le nom réellement souscrit dans `channels[]` |
|
|
557
560
|
| Deux abonnés au même canal, deux minuteurs | Ils ont demandé des cadences différentes — un canal cadencé est distinct par cadence | Laisser la cadence par défaut (aucun suffixe) quand elle convient : les abonnés partagent un minuteur |
|
|
558
561
|
| Rien n'est visible alors que le trafic est réel | La sonde n'observe **jamais** ce à quoi personne n'est abonné : un canal sans abonné n'existe pas | Ouvrir l'écran ou s'abonner ; c'est le comportement voulu, pas une panne |
|
|
@@ -566,7 +569,7 @@ infrastructure, puisque la sonde est une lecture pure.
|
|
|
566
569
|
| Suite | Ce qui est prouvé |
|
|
567
570
|
| --- | --- |
|
|
568
571
|
| `RealtimeHub.test.ts` | Hub vide → instantané à zéro ; abonnés et publications par canal ; `publishTotal` vs `fanoutTotal` ; comptage des consommateurs lents et `maxBufferedAmount` ; sortie du registre à la déconnexion ; frames entrantes ; descripteur `local` du backplane |
|
|
569
|
-
| `WsConnectionTransport.test.ts` | Les
|
|
572
|
+
| `WsConnectionTransport.test.ts` | Les seuils de contre-pression : abandon « le dernier gagne », puis fermeture `1013` |
|
|
570
573
|
| `ClusterProbeClient.test.ts` | Fusion pod (sommes vs maximum), report périodique et cache du snapshot, forage `enrich`/`rich`, repli per-instance quand la sonde est absente |
|
|
571
574
|
|
|
572
575
|
**Ce qui manque, et qu'il faut savoir** : il n'existe pas de banc de charge dédié à la sonde
|