@nodefony/realtime 10.0.0-beta.3 → 10.0.0-beta.5

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 (51) hide show
  1. package/README.md +29 -10
  2. package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
  3. package/dist/index.js +4 -6
  4. package/dist/nodefony/config/config.js +1 -2
  5. package/dist/nodefony/config/configSchema.js +1 -2
  6. package/dist/nodefony/config/defineModuleConfig.js +1 -2
  7. package/dist/nodefony/decorators/realtimeDecorators.js +1 -2
  8. package/dist/nodefony/interfaces/IRealtimeConnectionState.js +1 -0
  9. package/dist/nodefony/src/backplane/ClusterBackplane.js +1 -2
  10. package/dist/nodefony/src/backplane/LoopbackBackplane.js +1 -2
  11. package/dist/nodefony/src/backplane/RedisBackplane.js +1 -2
  12. package/dist/nodefony/src/backplane/backplaneRegistry.js +1 -2
  13. package/dist/nodefony/src/backplane/bootNotices.js +1 -2
  14. package/dist/nodefony/src/backplane/envelope.js +1 -2
  15. package/dist/nodefony/src/backplane/originId.js +1 -2
  16. package/dist/nodefony/src/backplane/publishQueue.js +0 -1
  17. package/dist/nodefony/src/cluster/ClusterProbeClient.js +1 -2
  18. package/dist/nodefony/src/guards.js +1 -2
  19. package/dist/nodefony/src/server/AnonymousRealtimeToken.js +1 -2
  20. package/dist/nodefony/src/server/RealtimeAdminApi.js +1 -2
  21. package/dist/nodefony/src/server/RealtimeController.js +158 -84
  22. package/dist/nodefony/src/server/RealtimeHub.js +3 -3
  23. package/dist/nodefony/src/server/ServerRealtimeSocket.js +18 -9
  24. package/dist/nodefony/src/server/deniedDetail.js +1 -2
  25. package/dist/nodefony/src/server/subscribedAck.js +29 -0
  26. package/dist/nodefony/src/server/syslogUplink.js +1 -2
  27. package/dist/nodefony/src/server/welcomeEnv.js +1 -2
  28. package/dist/nodefony/src/service/RealtimeService.js +1 -2
  29. package/dist/nodefony/src/transport/WsConnectionTransport.js +42 -2
  30. package/dist/nodefony/testing/index.js +77 -4
  31. package/dist/types/index.d.ts +1 -0
  32. package/dist/types/nodefony/interfaces/IRealtimeConnectionState.d.ts +40 -0
  33. package/dist/types/nodefony/interfaces/IRealtimeToken.d.ts +13 -0
  34. package/dist/types/nodefony/interfaces/index.d.ts +1 -0
  35. package/dist/types/nodefony/src/server/RealtimeController.d.ts +60 -2
  36. package/dist/types/nodefony/src/server/ServerRealtimeSocket.d.ts +18 -8
  37. package/dist/types/nodefony/src/server/subscribedAck.d.ts +23 -0
  38. package/dist/types/nodefony/src/transport/WsConnectionTransport.d.ts +26 -1
  39. package/dist/types/nodefony/testing/index.d.ts +11 -4
  40. package/docs/actions.md +67 -44
  41. package/docs/architecture.md +125 -119
  42. package/docs/cadence-et-contre-pression.md +31 -22
  43. package/docs/configuration.md +106 -82
  44. package/docs/cookbook-chat.md +56 -34
  45. package/docs/index.md +35 -24
  46. package/docs/observabilite.md +49 -49
  47. package/docs/protocole.md +91 -68
  48. package/docs/securite.md +135 -115
  49. package/docs/vocabulaire.md +117 -114
  50. package/package.json +10 -10
  51. package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.js +0 -6
@@ -112,9 +112,9 @@ Nodefony **fige** donc trois séparations, et tout le reste en découle :
112
112
  Trois mots du lexique ne sont pas des synonymes de ce que proposent les autres bibliothèques
113
113
  temps réel. Ce sont les différenciateurs du framework.
114
114
 
115
- **`socket` est isomorphe.** Le même contrat `IRealtimeSocket` (`IRealtimeSocket.ts:122`) décrit la
116
- prise **côté navigateur** (`NodefonySocket`, `NodefonySocket.ts:195`) **et côté serveur**
117
- (`ServerRealtimeSocket`, `ServerRealtimeSocket.ts:44`). Un service back publie exactement comme une
115
+ **`socket` est isomorphe.** Le même contrat `IRealtimeSocket` (`IRealtimeSocket.ts#IRealtimeSocket`) décrit la
116
+ prise **côté navigateur** (`NodefonySocket`, `NodefonySocket.ts#NodefonySocket`) **et côté serveur**
117
+ (`ServerRealtimeSocket`, `ServerRealtimeSocket.ts#ServerRealtimeSocket`). Un service back publie exactement comme une
118
118
  page front : `publish("chat:room-42", payload)`. Il n'y a pas une API cliente et une API serveur à
119
119
  apprendre, il y en a **une**.
120
120
 
@@ -122,12 +122,12 @@ apprendre, il y en a **une**.
122
122
  bidirectionnel, chacun avec son cycle de vie et son compteur d'abonnés. Le backing d'un canal est
123
123
  **pluggable côté serveur** — pub/sub, encapsulation d'un autre protocole, pont vers TCP/UDP, relais
124
124
  vers un autre service — et le consommateur ne voit jamais la différence
125
- (`IRealtimeChannel`, `IRealtimeSocket.ts:87`).
125
+ (`IRealtimeChannel`, `IRealtimeSocket.ts#IRealtimeChannel.name`).
126
126
 
127
127
  **`contrôleur` est le même mot qu'en HTTP.** Un endpoint temps réel est un contrôleur qui étend
128
- `RealtimeController` (`RealtimeController.ts:203`) et porte une route WebSocket : HTTP et WebSocket
128
+ `RealtimeController` (`RealtimeController.ts#RealtimeController`) et porte une route WebSocket : HTTP et WebSocket
129
129
  vivent dans le **même contexte de contrôleur**. C'est ce qui permet au pont `api.request`
130
- (`RealtimeController.realtimeApiRequest()`, `RealtimeController.ts:321`) de rejouer sur la socket
130
+ (`RealtimeController.realtimeApiRequest()`, `RealtimeController.ts#RealtimeController.realtimeApiRequest`) de rejouer sur la socket
131
131
  **la même action** que celle servie en REST.
132
132
 
133
133
  ## 🚀 Démarrage rapide — les mots en situation
@@ -217,9 +217,9 @@ export async function joinChat(): Promise<void> {
217
217
  Le **handle unique** manipulé par le code applicatif : quatre verbes (`subscribe`, `on`, `publish`,
218
218
  `request`) et une vue par canal. Le mot vient de la prise murale : on branche, on ignore le câblage.
219
219
 
220
- Contrat isomorphe `IRealtimeSocket` (`IRealtimeSocket.ts:122`), implémenté côté navigateur par
221
- `NodefonySocket` (`NodefonySocket.ts:394`) et côté serveur par `ServerRealtimeSocket`
222
- (`ServerRealtimeSocket.ts:44`). ⚠️ La socket **n'est pas** le [transport](#transport--la-couche-octets).
220
+ Contrat isomorphe `IRealtimeSocket` (`IRealtimeSocket.ts#IRealtimeSocket`), implémenté côté navigateur par
221
+ `NodefonySocket` (`NodefonySocket.ts#NodefonySocket`) et côté serveur par `ServerRealtimeSocket`
222
+ (`ServerRealtimeSocket.ts#ServerRealtimeSocket`). ⚠️ La socket **n'est pas** le [transport](#transport--la-couche-octets).
223
223
 
224
224
  → [Architecture](./architecture.md) pour la pile complète.
225
225
 
@@ -229,8 +229,8 @@ Un **tuyau bidirectionnel nommé**, multiplexé sur la connexion : `"chat:room-4
229
229
  `"nodefony:orm:health"`, `"nodefony:socket"`. Convention de nommage par `:` (espace de noms, puis
230
230
  précision). Ce n'est pas une classe — c'est une **chaîne de caractères indexée par le hub**.
231
231
 
232
- `RealtimeHub.subscribe()` (`RealtimeHub.ts:464`) l'indexe ; `IRealtimeSocket.subscribe()`
233
- (`IRealtimeSocket.ts:132`) le demande. Plusieurs canaux cohabitent sur **une** connexion : c'est le
232
+ `RealtimeHub.subscribe()` (`RealtimeHub.ts#RealtimeHub.subscribe`) l'indexe ; `IRealtimeSocket.subscribe()`
233
+ (`IRealtimeSocket.ts#IRealtimeSocket.subscribe`) le demande. Plusieurs canaux cohabitent sur **une** connexion : c'est le
234
234
  multiplexage.
235
235
 
236
236
  ### `abonnement` (subscribe) — demander le flux
@@ -238,8 +238,8 @@ multiplexage.
238
238
  **Ref-compté** : la demande part vers le pair au **premier** consommateur seulement, et le flux se
239
239
  coupe au **dernier** désabonnement. Réémise automatiquement à chaque reconnexion.
240
240
 
241
- `IRealtimeSocket.subscribe()` (`IRealtimeSocket.ts:132`). Le plafond par connexion est réglable
242
- (`limits.maxChannelsPerConnection`, `config.ts:142`) — au-delà, le canal n'est pas ouvert.
241
+ `IRealtimeSocket.subscribe()` (`IRealtimeSocket.ts#IRealtimeSocket.subscribe`). Le plafond par connexion est réglable
242
+ (`limits.maxChannelsPerConnection`, `config.ts#limitsSchema`) — au-delà, le canal n'est pas ouvert.
243
243
 
244
244
  ### `réception` (on) — brancher un handler
245
245
 
@@ -247,7 +247,7 @@ coupe au **dernier** désabonnement. Réémise automatiquement à chaque reconne
247
247
  module : `on` ne demande rien au serveur, `subscribe` si. S'abonner sans écouter reçoit des frames
248
248
  que personne ne lit ; écouter sans s'abonner n'en reçoit aucune.
249
249
 
250
- `IRealtimeSocket.on(channel, handler)` (`IRealtimeSocket.ts:141`).
250
+ `IRealtimeSocket.on(channel, handler)` (`IRealtimeSocket.ts#IRealtimeSocket.on`).
251
251
 
252
252
  ### `publication` (publish) — émettre sans réponse
253
253
 
@@ -255,25 +255,28 @@ que personne ne lit ; écouter sans s'abonner n'en reçoit aucune.
255
255
  client, la charge part vers le serveur (un seul pair) ; depuis le serveur, elle part en
256
256
  [fan-out](#fan-out--une-publication-n-livraisons) vers tous les abonnés.
257
257
 
258
- `IRealtimeSocket.publish()` (`IRealtimeSocket.ts:160`), `RealtimeHub.publish()` (`RealtimeHub.ts:610`).
258
+ `IRealtimeSocket.publish()` (`IRealtimeSocket.ts#IRealtimeSocket.publish`), `RealtimeHub.publish()` (`RealtimeHub.ts#RealtimeHub.publish`).
259
259
 
260
260
  ### `action RPC` (request) — l'appel corrélé
261
261
 
262
262
  L'appel qui **attend une réponse** : une `Promise` résolue avec le `result`, rejetée sur `error` ou
263
- sur expiration. Déclarée côté serveur par `@RealtimeAction` (`realtimeDecorators.ts:191`), appelée
264
- côté client par `request()` (`IRealtimeSocket.ts:166`).
263
+ sur expiration. Déclarée côté serveur par `@RealtimeAction` (`realtimeDecorators.ts#RealtimeAction`), appelée
264
+ côté client par `request()` (`NodefonySocket.ts#NodefonySocket.request`), côté serveur vers UN
265
+ client par `requestClient()` (`RealtimeController.ts#RealtimeController.requestClient`). Elle
266
+ n'appartient pas au contrat commun `IRealtimeSocket` : une requête attend UN pair, et un handle
267
+ serveur au-dessus du hub en a N.
265
268
 
266
269
  Un handler qui lève une erreur rend un `-32603` **générique** ; seule une `RpcError`
267
- (`JsonRpcPeer.ts:101`) choisit ce qu'elle expose au pair.
270
+ (`JsonRpcPeer.ts#RpcError`) choisit ce qu'elle expose au pair.
268
271
 
269
272
  ### `canal entrant` (inbound) — le client pousse
270
273
 
271
274
  Un canal où le **client a le droit d'émettre** vers le serveur. Défaut sûr : **aucun**. Un canal
272
- n'accepte d'entrée que déclaré explicitement, par `@RealtimeInbound` (`realtimeDecorators.ts:278`) ou
273
- par l'override `realtimeInbound()` (`RealtimeController.ts:307`).
275
+ n'accepte d'entrée que déclaré explicitement, par `@RealtimeInbound` (`realtimeDecorators.ts#RealtimeInbound`) ou
276
+ par l'override `realtimeInbound()` (`RealtimeController.ts#RealtimeController.realtimeInbound`).
274
277
 
275
278
  Le handler reçoit `(params, reply)` — `params` vient du réseau, donc **jamais fiable** :
276
- `RealtimeInboundHandler` (`IRealtimeController.ts:16`).
279
+ `RealtimeInboundHandler` (`IRealtimeController.ts#RealtimeInboundHandler`).
277
280
 
278
281
  ### `handle de canal` — la vue objet d'un canal
279
282
 
@@ -281,11 +284,11 @@ Le handler reçoit `(params, reply)` — `params` vient du réseau, donc **jamai
281
284
  `pubsub`, `protocol`, `bridge`, `proxy`) et son cycle de vie (`open`/`close`/`send`/`on`). C'est le
282
285
  point d'accroche des canaux à état — un appel SIP, une connexion pontée.
283
286
 
284
- `IRealtimeChannel` (`IRealtimeSocket.ts:87`), `channel()` (`IRealtimeSocket.ts:178`).
287
+ `IRealtimeChannel` (`IRealtimeSocket.ts#IRealtimeChannel.name`), `channel()` (`IRealtimeSocket.ts#IRealtimeSocket.channel`).
285
288
 
286
289
  ### `client` — la socket côté navigateur
287
290
 
288
- `NodefonySocket` (`NodefonySocket.ts:162`) : reconnexion automatique, réémission des abonnements,
291
+ `NodefonySocket` (`NodefonySocket.ts#NodefonySocket`) : reconnexion automatique, réémission des abonnements,
289
292
  compteurs par canal, heartbeat. Publié dans le sous-chemin `nodefony/client` du cœur — donc
290
293
  importable **sans** aucune dépendance serveur, ce qui est la condition de l'isomorphisme.
291
294
 
@@ -293,11 +296,11 @@ importable **sans** aucune dépendance serveur, ce qui est la condition de l'iso
293
296
 
294
297
  ### `façade serveur` — la même socket, côté back
295
298
 
296
- `ServerRealtimeSocket` (`ServerRealtimeSocket.ts:44`), obtenue par `serverSocket()`
297
- (`ServerRealtimeSocket.ts:243`) : un service métier tient un handle et publie **comme une page
299
+ `ServerRealtimeSocket` (`ServerRealtimeSocket.ts#ServerRealtimeSocket`), obtenue par `serverSocket()`
300
+ (`ServerRealtimeSocket.ts#serverSocket`) : un service métier tient un handle et publie **comme une page
298
301
  front**. Une différence assumée : `request()` n'y est pas supporté — au-dessus du hub il n'y a pas
299
302
  **un** pair mais N clients. Pour un appel serveur → un client précis, c'est `requestClient()`
300
- (`RealtimeController.ts:395`).
303
+ (`RealtimeController.ts#RealtimeController.requestClient`).
301
304
 
302
305
  ### `accueil` (welcome) — la première frame
303
306
 
@@ -305,7 +308,7 @@ La notification `realtime:welcome`, poussée par le serveur juste après le hand
305
308
  protocole, les canaux que ce visiteur peut obtenir, les actions de l'endpoint, et l'identité résolue. Un client
306
309
  attend cette frame avant de pousser quoi que ce soit.
307
310
 
308
- `IRealtimeWelcome` (`RealtimeEventMap.ts:231`).
311
+ `IRealtimeWelcome` (`RealtimeEventMap.ts#IRealtimeWelcome`).
309
312
 
310
313
  ### `identité` — qui est cette connexion
311
314
 
@@ -313,7 +316,7 @@ La vue « sur soi » d'une connexion : type de jeton, authentifié ou non, ident
313
316
  Aucun secret — seulement ce que le porteur sait déjà de lui-même. Elle est **résolue une fois** au
314
317
  handshake, jamais renégociée par frame.
315
318
 
316
- `RealtimeIdentity` (`RealtimeEventMap.ts:212`).
319
+ `RealtimeIdentity` (`RealtimeEventMap.ts#RealtimeIdentity`).
317
320
 
318
321
  ## 🏗️ Le serveur — ce qui aiguille derrière la prise
319
322
 
@@ -335,10 +338,10 @@ handshake, jamais renégociée par frame.
335
338
  ### `hub` — le broker du process
336
339
 
337
340
  Le **standard téléphonique** du pod : il tient la table « canal → abonnés locaux » et diffuse. Un
338
- process = un hub, obtenu par `getRealtimeHub()` (`RealtimeHub.ts:1432`). Il ne connaît **ni** les
341
+ process = un hub, obtenu par `getRealtimeHub()` (`RealtimeHub.ts#getRealtimeHub`). Il ne connaît **ni** les
339
342
  contrôleurs, **ni** le métier : ce sont les providers qui portent les dépendances.
340
343
 
341
- `RealtimeHub` (`RealtimeHub.ts:214`). ⚠️ « hub » désigne **toujours** le serveur ; ce que tient le
344
+ `RealtimeHub` (`RealtimeHub.ts#RealtimeHub`). ⚠️ « hub » désigne **toujours** le serveur ; ce que tient le
342
345
  code applicatif s'appelle une [socket](#socket--la-prise-que-tient-ton-code).
343
346
 
344
347
  ### `fan-out` — une publication, N livraisons
@@ -346,15 +349,15 @@ code applicatif s'appelle une [socket](#socket--la-prise-que-tient-ton-code).
346
349
  L'action de diffuser : une charge publiée sur un canal part vers **tous** les sinks abonnés
347
350
  localement. Une connexion fautive ne casse pas la diffusion aux autres — chaque livraison est isolée.
348
351
 
349
- `RealtimeHub.publish()` (`RealtimeHub.ts:610`) pour le chemin complet, `publishLocal()`
350
- (`RealtimeHub.ts:629`) pour la diffusion **strictement locale** (voie d'entrée du backplane).
352
+ `RealtimeHub.publish()` (`RealtimeHub.ts#RealtimeHub.publish`) pour le chemin complet, `publishLocal()`
353
+ (`RealtimeHub.ts#RealtimeHub.publishLocal`) pour la diffusion **strictement locale** (voie d'entrée du backplane).
351
354
 
352
355
  ### `sink` — le point de livraison d'une connexion
353
356
 
354
357
  Un `sink` = **une** connexion sur **un** canal : la fonction qui pousse la charge vers son pair. Le
355
358
  mot vient du couple source/puits : le provider est la source, le sink le puits.
356
359
 
357
- `ChannelSink` (`RealtimeHub.ts:156`). Le nombre de sinks d'un canal est son nombre d'abonnés locaux.
360
+ `ChannelSink` (`RealtimeHub.ts#ChannelSink`). Le nombre de sinks d'un canal est son nombre d'abonnés locaux.
358
361
 
359
362
  ### `provider` — ce qui produit un canal
360
363
 
@@ -362,7 +365,7 @@ La fabrique appelée **au premier abonné** d'un canal, qui démarre ce qui prod
362
365
  ticker, un écouteur) et rend son `dispose`, appelé **au dernier désabonné**. Zéro abonné = zéro
363
366
  timer.
364
367
 
365
- `ChannelFactory` (`RealtimeHub.ts:171`). ⚠️ Un provider est **partagé** : il survit à la connexion qui
368
+ `ChannelFactory` (`RealtimeHub.ts#ChannelFactory`). ⚠️ Un provider est **partagé** : il survit à la connexion qui
366
369
  l'a créé — n'y capturer que des dépendances à longue vie, jamais le contexte d'une connexion.
367
370
 
368
371
  ### `canal partagé` — un provider par canal et par pod
@@ -371,7 +374,7 @@ Le modèle de coût du module : là où une implémentation naïve crée un tick
371
374
  Nodefony en crée **un par canal et par pod** et se contente d'ajouter un sink par abonné. Mille
372
375
  spectateurs d'un tableau de bord coûtent un timer, pas mille.
373
376
 
374
- `RealtimeHub.subscribe()` (`RealtimeHub.ts:464`).
377
+ `RealtimeHub.subscribe()` (`RealtimeHub.ts#RealtimeHub.subscribe`).
375
378
 
376
379
  ### `canal passif` — écouter n'est pas posséder
377
380
 
@@ -385,8 +388,8 @@ reçoit du vrai producteur ; sinon l'abonnement est refusé, et l'écoute en pla
385
388
  Sans cette distinction, le premier service qui prononce un nom de canal l'ouvrirait pour tout le
386
389
  monde — et les clients suivants s'abonneraient à un flux que rien ne produit, sans jamais l'apprendre.
387
390
 
388
- `RealtimeHub.listen()` (`RealtimeHub.ts:531`) pour l'écoute, `isChannelOwned()`
389
- (`RealtimeHub.ts:552`) pour lire l'état. ⚠️ Un canal passif **n'est pas** un canal fermé : il n'a
391
+ `RealtimeHub.listen()` (`RealtimeHub.ts#RealtimeHub.listen`) pour l'écoute, `isChannelOwned()`
392
+ (`RealtimeHub.ts#RealtimeHub.isChannelOwned`) pour lire l'état. ⚠️ Un canal passif **n'est pas** un canal fermé : il n'a
390
393
  simplement pas encore de propriétaire.
391
394
 
392
395
  ### `canal système` — servi par la plateforme
@@ -395,7 +398,7 @@ Un canal dont la fabrique est déclarée par un module bas niveau (le journal d'
395
398
  qui devient servable par **n'importe quel** endpoint, présent ou futur, sans qu'aucun contrôleur ne
396
399
  le connaisse. Consulté seulement quand la fabrique du contrôleur a dit « inconnu ».
397
400
 
398
- `RealtimeHub.registerSystemChannel()` (`RealtimeHub.ts:1278`).
401
+ `RealtimeHub.registerSystemChannel()` (`RealtimeHub.ts#RealtimeHub.registerSystemChannel`).
399
402
 
400
403
  ### `canal broadcast` — celui qui traverse le backplane
401
404
 
@@ -403,8 +406,8 @@ Un canal **déclaré** comme franchissant la frontière du process : chat, prés
403
406
  déclaration se fait par **préfixe**, ce qui couvre les variantes de cadence (`chat:` couvre
404
407
  `chat:room-42:1000`).
405
408
 
406
- `RealtimeHub.markBroadcastChannel()` (`RealtimeHub.ts:674`), déclaré côté contrôleur par
407
- `@RealtimeBroadcast` (`realtimeDecorators.ts:420`).
409
+ `RealtimeHub.markBroadcastChannel()` (`RealtimeHub.ts#RealtimeHub.markBroadcastChannel`), déclaré côté contrôleur par
410
+ `@RealtimeBroadcast` (`realtimeDecorators.ts#RealtimeBroadcast`).
408
411
 
409
412
  ### `canal instance-local` — le défaut
410
413
 
@@ -412,15 +415,15 @@ Tout canal **non déclaré** broadcast reste dans son process. C'est voulu : les
412
415
  d'observabilité (journaux, sondes, état interne) décrivent **ce pod**, et les agréger silencieusement
413
416
  serait à la fois faux et une fuite. Traverser le process est une capacité qu'un canal **demande**.
414
417
 
415
- Politique de forward du hub — `#broadcastPrefixes` (`RealtimeHub.ts:395`).
418
+ Politique de forward du hub — `#broadcastPrefixes` (`RealtimeHub.ts#RealtimeHub.#broadcastPrefixes`).
416
419
 
417
420
  ### `contrôleur temps réel` — l'endpoint WebSocket
418
421
 
419
422
  La classe de base d'un endpoint : elle porte tout le protocole (handshake, accueil, discrimination
420
423
  des frames, cycle de vie des canaux) et ne laisse au métier que ses canaux et ses actions.
421
424
 
422
- `RealtimeController` (`RealtimeController.ts:203`), point d'entrée `handleRealtime()`
423
- (`RealtimeController.ts:344`). C'est un **contrôleur** au sens habituel de Nodefony : la même classe
425
+ `RealtimeController` (`RealtimeController.ts#RealtimeController`), point d'entrée `handleRealtime()`
426
+ (`RealtimeController.ts#RealtimeController.handleRealtime`). C'est un **contrôleur** au sens habituel de Nodefony : la même classe
424
427
  peut porter des routes HTTP.
425
428
 
426
429
  ### `service realtime` — la façade d'injection
@@ -429,7 +432,7 @@ Le service exposé au conteneur sous le nom `realtimeService` : `publish`, `subs
429
432
  config gelée. C'est l'API stable pour le code utilisateur, quand `getRealtimeHub()` est l'accès
430
433
  interne.
431
434
 
432
- `RealtimeService` (`RealtimeService.ts:48`).
435
+ `RealtimeService` (`RealtimeService.ts#RealtimeService`).
433
436
 
434
437
  ### `pont API` — la même action, sur la socket
435
438
 
@@ -437,8 +440,8 @@ L'option qui expose la méthode `api.request { path }` : la connexion rejoue **l
437
440
  contrôleur** que celle servie en REST, avec **la même garde**. Le pont n'atteint que les routes qui
438
441
  déclarent explicitement le transport WebSocket — aucun contournement possible.
439
442
 
440
- `realtimeApiRequest()` (`RealtimeController.ts:321`), mise en œuvre `invokeApiRequest()`
441
- (`RealtimeController.ts:1070`). Désactivé par défaut.
443
+ `realtimeApiRequest()` (`RealtimeController.ts#RealtimeController.realtimeApiRequest`), mise en œuvre `invokeApiRequest()`
444
+ (`RealtimeController.ts#RealtimeController.invokeApiRequest`). Désactivé par défaut.
442
445
 
443
446
  ## 🔌 Le protocole et le transport — ce qui passe sur le fil
444
447
 
@@ -461,8 +464,8 @@ Une enveloppe JSON-RPC 2.0. Sa **nature se lit sur `method`**, pas sur `id` : `m
461
464
  requête, `method` seul = notification, `id` + `result` ou `error` = réponse. Tout le reste est
462
465
  invalide.
463
466
 
464
- `JsonRpcFrameKind` (`jsonrpc/index.ts:138`). Le classement est fait par `JsonRpcPeer.receive()`
465
- (`JsonRpcPeer.ts:419`), qui **ne lève jamais**.
467
+ `JsonRpcFrameKind` (`jsonrpc/index.ts#JsonRpcFrameKind`). Le classement est fait par `JsonRpcPeer.receive()`
468
+ (`JsonRpcPeer.ts#JsonRpcPeer.receive`), qui **ne lève jamais**.
466
469
 
467
470
  ### `pair` (peer) — le moteur de protocole
468
471
 
@@ -470,22 +473,22 @@ Client et serveur sont des **pairs** : classer une frame, router, corréler les
470
473
  travail des deux côtés. Nodefony l'écrit **une seule fois** ; chaque côté l'entoure de son transport
471
474
  et de ses handlers. Aucune dépendance Node — le même fichier tourne dans le navigateur.
472
475
 
473
- `JsonRpcPeer` (`JsonRpcPeer.ts:311`), contrat `IRealtimePeer` (`JsonRpcPeer.ts:261`).
476
+ `JsonRpcPeer` (`JsonRpcPeer.ts#JsonRpcPeer`), contrat `IRealtimePeer` (`JsonRpcPeer.ts#IRealtimePeer`).
474
477
 
475
478
  ### `dispatch` — classer puis router
476
479
 
477
480
  Le geste du pair sur une frame entrante : décider de sa nature, trouver le handler, l'appeler,
478
481
  renvoyer la réponse s'il en faut une. Ce n'est pas une méthode publique — c'est un **moment**, et
479
482
  c'est précisément là que se greffe le [verrou de frame](#verrou-de-frame--la-décision-par-frame),
480
- par le crochet `beforeDispatch` (`JsonRpcPeer.ts:219`).
483
+ par le crochet `beforeDispatch` (`JsonRpcPeer.ts#JsonRpcPeerOptions.beforeDispatch`).
481
484
 
482
485
  ### `transport` — la couche octets
483
486
 
484
487
  La techno réseau qui porte les frames, **interchangeable** : `WsConnectionTransport`
485
- (`WsConnectionTransport.ts:46`) côté serveur, `BrowserWsTransport` (`BrowserWsTransport.ts:12`) côté
488
+ (`WsConnectionTransport.ts#WsConnectionTransport`) côté serveur, `BrowserWsTransport` (`BrowserWsTransport.ts#BrowserWsTransport`) côté
486
489
  navigateur. Le pair ignore laquelle est branchée.
487
490
 
488
- Contrat `IRealtimeTransport` (`IRealtimeTransport.ts:34`). ⚠️ C'est **le seul** endroit du lexique où
491
+ Contrat `IRealtimeTransport` (`IRealtimeTransport.ts#IRealtimeTransport`). ⚠️ C'est **le seul** endroit du lexique où
489
492
  « socket » pourrait désigner la connexion réseau : ici, ce mot est réservé à la
490
493
  [prise applicative](#socket--la-prise-que-tient-ton-code).
491
494
 
@@ -495,7 +498,7 @@ Quand un handler veut joindre des métadonnées serveur à sa réponse, il rend
495
498
  `result` reste **exactement** ce que rendrait la même route en REST, et la méta voyage dans un champ
496
499
  frère. Un pair qui n'en connaît pas les clés les ignore.
497
500
 
498
- `RpcEnvelope` (`JsonRpcPeer.ts:139`), `RpcMeta` (`JsonRpcPeer.ts:125`).
501
+ `RpcEnvelope` (`JsonRpcPeer.ts#RpcEnvelope`), `RpcMeta` (`JsonRpcPeer.ts#RpcMeta`).
499
502
 
500
503
  ### `cadence` — la fréquence portée par le nom
501
504
 
@@ -503,7 +506,7 @@ La fréquence d'un canal d'état vit **dans son nom** : `nodefony:orm:health` =
503
506
  `nodefony:orm:health:2000` = un rythme explicite. Conséquence voulue : **un canal, une cadence, un
504
507
  comptage** — deux cadences sont deux canaux, jamais réconciliés.
505
508
 
506
- `rateChannel()` (`channelRate.ts:44`) côté client, `parseRate()` (`channelRate.ts:63`) côté serveur —
509
+ `rateChannel()` (`channelRate.ts#rateChannel`) côté client, `parseRate()` (`channelRate.ts#parseRate`) côté serveur —
507
510
  même fichier, donc même règle des deux côtés.
508
511
 
509
512
  ### `AIMD` — s'ajuster au client lent
@@ -512,8 +515,8 @@ _Additive Increase, Multiplicative Decrease_ : accélérer par petits pas quand
512
515
  d'un coup à la moindre congestion. Le principe de régulation de TCP, appliqué **par canal** à partir
513
516
  de ce qui reste en attente dans la connexion.
514
517
 
515
- `AdaptiveRate` (`AdaptiveRate.ts:78`), liaison sur un canal par `bindAdaptiveChannel()`
516
- (`AdaptiveRate.ts:239`).
518
+ `AdaptiveRate` (`AdaptiveRate.ts#AdaptiveRate`), liaison sur un canal par `bindAdaptiveChannel()`
519
+ (`AdaptiveRate.ts#bindAdaptiveChannel`).
517
520
 
518
521
  ## 🧩 Le cluster — franchir la frontière du process
519
522
 
@@ -535,8 +538,8 @@ Le **fond de panier** d'un rack : la carte qui relie toutes les autres. Ici, le
535
538
  publications d'un process aux autres. Un contrat volontairement minuscule — `publish`, `onMessage`,
536
539
  `start`, `stop`, `describe` — parce que tout ce qui est riche appartient au hub.
537
540
 
538
- `IBackplane` (`IBackplane.ts:107`), message `IBackplaneMessage` (`IBackplane.ts:51`), branchement
539
- `RealtimeHub.setBackplane()` (`RealtimeHub.ts:720`).
541
+ `IBackplane` (`IBackplane.ts#IBackplane`), message `IBackplaneMessage` (`IBackplane.ts#IBackplaneMessage`), branchement
542
+ `RealtimeHub.setBackplane()` (`RealtimeHub.ts#RealtimeHub.setBackplane`).
540
543
 
541
544
  > [!TIP]
542
545
  > C'est la pièce qui tient la promesse « une ligne de configuration change tout » : le hub **ne sait
@@ -545,11 +548,11 @@ publications d'un process aux autres. Un contrat volontairement minuscule — `p
545
548
  ### `driver` — l'implémentation du transport
546
549
 
547
550
  Le backplane est un **contrat** ; un driver en est une réalisation, désignée par son **nom** dans la
548
- configuration (`backplane.driver`, `config.ts:47`). Un registre associe nom → fabrique : aucune
551
+ configuration (`backplane.driver`, `config.ts#backplaneSchema`). Un registre associe nom → fabrique : aucune
549
552
  cascade de conditions, et n'importe qui peut enregistrer le sien.
550
553
 
551
- `registerBackplaneDriver()` (`backplaneRegistry.ts:55`), `listBackplaneDrivers()`
552
- (`backplaneRegistry.ts:68`). Un nom inconnu au démarrage n'arrête rien : le hub reste local, avec un
554
+ `registerBackplaneDriver()` (`backplaneRegistry.ts#registerBackplaneDriver`), `listBackplaneDrivers()`
555
+ (`backplaneRegistry.ts#listBackplaneDrivers`). Un nom inconnu au démarrage n'arrête rien : le hub reste local, avec un
553
556
  avertissement.
554
557
 
555
558
  ### `loopback` — un seul process
@@ -557,14 +560,14 @@ avertissement.
557
560
  Le driver du cas mono-process : il n'y a **rien à transporter**, le hub fait tout localement. C'est
558
561
  le défaut, et il ne coûte rien.
559
562
 
560
- `LoopbackBackplane` (`LoopbackBackplane.ts:24`).
563
+ `LoopbackBackplane` (`LoopbackBackplane.ts#LoopbackBackplane`).
561
564
 
562
565
  ### `cluster` — entre workers d'une machine
563
566
 
564
567
  Le driver qui relie les workers d'un même hôte par le canal de communication inter-process de Node.
565
568
  Multi-process sans aucune infrastructure à installer.
566
569
 
567
- `ClusterBackplane` (`ClusterBackplane.ts:89`).
570
+ `ClusterBackplane` (`ClusterBackplane.ts#ClusterBackplane`).
568
571
 
569
572
  ### `redis` — entre machines
570
573
 
@@ -572,7 +575,7 @@ Le driver qui propage les publications en pub/sub, donc entre pods de machines d
572
575
  consomme les connexions du module Redis — jamais une dépendance directe. Absent ou injoignable, le
573
576
  hub reste local et le démarrage continue.
574
577
 
575
- `RedisBackplane` (`RedisBackplane.ts:193`), canal `resolveRedisChannel()` (`RedisBackplane.ts:31`).
578
+ `RedisBackplane` (`RedisBackplane.ts#RedisBackplane`), canal `resolveRedisChannel()` (`RedisBackplane.ts#resolveRedisChannel`).
576
579
 
577
580
  ### `origine` (originId) — qui a émis
578
581
 
@@ -580,7 +583,7 @@ L'identifiant **unique dans le cluster** du process émetteur, joint à chaque m
580
583
  du nom du pod ou de l'hôte, jamais du seul identifiant de processus : deux conteneurs peuvent tous
581
584
  deux être le processus n° 1.
582
585
 
583
- `resolveBackplaneOriginId()` (`originId.ts:24`), champ `IBackplane.originId` (`IBackplane.ts:77`).
586
+ `resolveBackplaneOriginId()` (`originId.ts#resolveBackplaneOriginId`), champ `IBackplane.originId` (`IBackplane.ts#IBackplaneInfo.kind`).
584
587
 
585
588
  ### `anti-écho` — ne pas rejouer sa propre voix
586
589
 
@@ -588,7 +591,7 @@ La règle qui empêche les tempêtes : un message **reçu** du backplane est ré
588
591
  **locale seulement**, jamais renvoyé sur le bus ; et un message qui porte sa propre origine est
589
592
  ignoré à l'arrivée. Sans l'origine, l'anti-écho ferait taire le voisin au lieu de soi-même.
590
593
 
591
- `RealtimeHub.publishLocal()` (`RealtimeHub.ts:629`).
594
+ `RealtimeHub.publishLocal()` (`RealtimeHub.ts#RealtimeHub.publishLocal`).
592
595
 
593
596
  ### `cloison` (namespace) — deux applications, un même bus
594
597
 
@@ -596,8 +599,8 @@ Le suffixe qui isole les canaux d'une application sur un transport mutualisé. N
596
599
  numéro de base d'un serveur Redis **ne cloisonne pas** le pub/sub : sans cloison, deux déploiements
597
600
  partageant le même serveur se parleraient.
598
601
 
599
- `backplane.namespace` (`config.ts:59`), constante de base `REDIS_RT_CHANNEL`
600
- (`RedisBackplane.ts:20`).
602
+ `backplane.namespace` (`config.ts#backplaneSchema`), constante de base `REDIS_RT_CHANNEL`
603
+ (`RedisBackplane.ts#REDIS_RT_CHANNEL`).
601
604
 
602
605
  ### `cross-pod` — la propriété qui compte
603
606
 
@@ -605,7 +608,7 @@ L'information que chaque driver déclare sur lui-même : sait-il franchir la mac
605
608
  dans la carte d'identité du backplane, journalisée au démarrage et lisible dans la sonde — c'est ce
606
609
  qui permet de vérifier en une ligne qu'un déploiement diffuse bien au-delà d'un pod.
607
610
 
608
- `IBackplaneInfo` (`IBackplane.ts:74`).
611
+ `IBackplaneInfo` (`IBackplane.ts#IBackplaneInfo`).
609
612
 
610
613
  ## 🔐 La sécurité — qui parle, qui a le droit
611
614
 
@@ -630,8 +633,8 @@ Le moment de l'ouverture : c'est **là**, et une seule fois, que l'origine est c
630
633
  l'authenticator tourne et que l'identité est figée. Tout ce qui coûte cher se paie ici, jamais par
631
634
  frame.
632
635
 
633
- DTO neutre `IRealtimeHandshake` (`IRealtimeHandshake.ts:14`) — en-têtes, cookies, URL, origine,
634
- sous-protocoles. Traitement dans `onHandshake()` (`RealtimeController.ts:450`).
636
+ DTO neutre `IRealtimeHandshake` (`IRealtimeHandshake.ts#IRealtimeHandshake`) — en-têtes, cookies, URL, origine,
637
+ sous-protocoles. Traitement dans `onHandshake()` (`RealtimeController.ts#RealtimeController.onHandshake`).
635
638
 
636
639
  ### `authenticator` — du handshake à l'identité
637
640
 
@@ -639,24 +642,24 @@ La stratégie qui transforme une poignée de main en identité : `supports` (est
639
642
  `authenticate` (voici le jeton), `onSuccess` / `onFailure`. Exactement le motif des authenticators
640
643
  HTTP — un seul modèle mental pour les deux transports.
641
644
 
642
- `IRealtimeAuthenticator` (`IRealtimeAuthenticator.ts:24`), enregistrement
643
- `RealtimeHub.useAuthenticator()` (`RealtimeHub.ts:925`).
645
+ `IRealtimeAuthenticator` (`IRealtimeAuthenticator.ts#IRealtimeAuthenticator`), enregistrement
646
+ `RealtimeHub.useAuthenticator()` (`RealtimeHub.ts#RealtimeHub.useAuthenticator`).
644
647
 
645
648
  ### `matcher` — qui capture quoi
646
649
 
647
650
  Le sélecteur (motif d'URL, hôte optionnel) qui décide **quel** authenticator prend la connexion. Le
648
651
  **premier** qui capture gagne : on enregistre donc du plus spécifique au plus général.
649
652
 
650
- `IRealtimeAuthenticatorMatcher` (`IRealtimeAuthenticatorMatcher.ts:25`), résolution
651
- `resolveAuthenticator()` (`RealtimeHub.ts:943`).
653
+ `IRealtimeAuthenticatorMatcher` (`IRealtimeAuthenticatorMatcher.ts#IRealtimeAuthenticatorMatcher`), résolution
654
+ `resolveAuthenticator()` (`RealtimeHub.ts#RealtimeHub.resolveAuthenticator`).
652
655
 
653
656
  ### `jeton` (token) — l'identité figée
654
657
 
655
658
  Ce que l'authenticator produit : type, identifiant, rôles, scopes, et parfois une méthode de
656
659
  re-validation. Il est mis en cache sur la connexion et **jamais** recalculé par frame.
657
660
 
658
- `IRealtimeToken` (`IRealtimeToken.ts:18`). La lecture ne rend **jamais** `null` : sans authenticator,
659
- c'est le jeton anonyme gelé `ANONYMOUS_REALTIME_TOKEN` (`AnonymousRealtimeToken.ts:18`) qui répond —
661
+ `IRealtimeToken` (`IRealtimeToken.ts#IRealtimeToken`). La lecture ne rend **jamais** `null` : sans authenticator,
662
+ c'est le jeton anonyme gelé `ANONYMOUS_REALTIME_TOKEN` (`AnonymousRealtimeToken.ts#ANONYMOUS_REALTIME_TOKEN`) qui répond —
660
663
  un refus a donc toujours un auteur identifiable.
661
664
 
662
665
  ### `politique de canal` — ce qu'un canal exige
@@ -664,8 +667,8 @@ un refus a donc toujours un auteur identifiable.
664
667
  Les exigences déclarées d'un canal : authentifié, rôles, scopes. Elles s'attachent au **nom** du
665
668
  canal, pas à la méthode qui l'implémente — parce que c'est le nom que le pare-feu résout.
666
669
 
667
- `IChannelPolicy` (`IChannelPolicy.ts:20`), déclaration par `@RealtimeChannel`
668
- (`realtimeDecorators.ts:240`), résolution `resolveChannelPolicy()` (`RealtimeHub.ts:1209`).
670
+ `IChannelPolicy` (`IChannelPolicy.ts#IChannelPolicy`), déclaration par `@RealtimeChannel`
671
+ (`realtimeDecorators.ts#RealtimeChannel`), résolution `resolveChannelPolicy()` (`RealtimeHub.ts#RealtimeHub.resolveChannelPolicy`).
669
672
 
670
673
  ### `verrou de frame` — la décision par frame
671
674
 
@@ -673,8 +676,8 @@ La fonction qui répond « cette frame passe-t-elle ? » à partir du jeton **d
673
676
  **Synchrone par doctrine** : attendre une réponse distante à chaque frame sérialiserait le flux d'une
674
677
  connexion. Un contrôle qui doit interroger le réseau se fait au handshake, pas ici.
675
678
 
676
- `FrameAuthorizer` (`RealtimeHub.ts:40`), pose `setFrameAuthorizer()` (`RealtimeHub.ts:1020`), appel
677
- `runAuthorizer()` (`RealtimeHub.ts:1309`).
679
+ `FrameAuthorizer` (`RealtimeHub.ts#FrameAuthorizer`), pose `setFrameAuthorizer()` (`RealtimeHub.ts#RealtimeHub.setFrameAuthorizer`), appel
680
+ `runAuthorizer()` (`RealtimeHub.ts#RealtimeHub.runAuthorizer`).
678
681
 
679
682
  ### `contrôle d'origine` — la défense d'ouverture
680
683
 
@@ -682,7 +685,7 @@ Le contrôle de l'en-tête `Origin` à l'ouverture (RFC 6455 §10.2) : un site t
682
685
  ouvrir une connexion authentifiée dans le dos de l'utilisateur. Correspondance **exacte**, aucun
683
686
  caractère générique.
684
687
 
685
- `OriginGuard` (`RealtimeHub.ts:34`), configuration `csrf.checkOrigin` (`config.ts:255`).
688
+ `OriginGuard` (`RealtimeHub.ts#OriginGuard`), configuration `csrf.checkOrigin` (`config.ts#csrfSchema`).
686
689
 
687
690
  ### `révocation` — la connexion qui survit à sa session
688
691
 
@@ -690,8 +693,8 @@ Le problème que ce mot nomme : une connexion ouverte pourrait survivre à la se
690
693
  autorisée, puisque le verrou de frame ne relit rien. Un contrôle périodique referme l'écart en
691
694
  re-validant les identités révocables et en fermant celles qui ne valent plus.
692
695
 
693
- `registerRevocable()` (`RealtimeHub.ts:796`), `revalidateRevocable()` (`RealtimeHub.ts:815`), période
694
- `REVOCATION_REVALIDATE_MS` (`RealtimeHub.ts:112`). Une re-validation en erreur **ferme** — jamais
696
+ `registerRevocable()` (`RealtimeHub.ts#RealtimeHub.registerRevocable`), `revalidateRevocable()` (`RealtimeHub.ts#RealtimeHub.revalidateRevocable`), période
697
+ `REVOCATION_REVALIDATE_MS` (`RealtimeHub.ts#channelFamily`). Une re-validation en erreur **ferme** — jamais
695
698
  l'inverse.
696
699
 
697
700
  ### `refus` (denied) — rendre le « non » visible
@@ -700,8 +703,8 @@ Une notification refusée serait abandonnée en silence, laissant le client aveu
700
703
  donc `realtime:denied`. Le motif est **générique** : jamais le rôle ou le scope manquant, qui
701
704
  transformerait le refus en oracle d'autorisation.
702
705
 
703
- `IRealtimeDenied` (`RealtimeEventMap.ts:269`), conversion en message d'interface `deniedToNotice()`
704
- (`notice.ts:46`).
706
+ `IRealtimeDenied` (`RealtimeEventMap.ts#IRealtimeDenied`), conversion en message d'interface `deniedToNotice()`
707
+ (`notice.ts#deniedToNotice`).
705
708
 
706
709
  ### `audit de frame` — tracer le notable
707
710
 
@@ -709,7 +712,7 @@ Le signal émis sur les événements protocolaires qui méritent une trace : fra
709
712
  refusée, méthode inconnue, erreur interne. Émis sans attente, avec le pair — ce qui permet de
710
713
  retrouver **qui** a été refusé, pas seulement d'où venait le paquet.
711
714
 
712
- `FrameAuditReason` (`JsonRpcPeer.ts:182`), crochet `onFrameAudit` (`JsonRpcPeer.ts:236`).
715
+ `FrameAuditReason` (`JsonRpcPeer.ts#FrameAuditReason`), crochet `onFrameAudit` (`JsonRpcPeer.ts#JsonRpcPeerOptions.onFrameAudit`).
713
716
 
714
717
  ### `seam` — le point de greffe
715
718
 
@@ -717,8 +720,8 @@ Littéralement une **couture** : un point prévu dans une couche basse pour qu'u
717
720
  greffe du comportement **sans modifier la couche basse**. Le module en expose cinq, et c'est ce qui
718
721
  permet à la couche sécurité de se brancher sans qu'aucune ligne de realtime ne la connaisse.
719
722
 
720
- Les deux du protocole sont `beforeDispatch` (`JsonRpcPeer.ts:219`) et `onFrameAudit`
721
- (`JsonRpcPeer.ts:236`) ; les trois du hub sont l'authenticator, le matcher et le contrôle d'origine.
723
+ Les deux du protocole sont `beforeDispatch` (`JsonRpcPeer.ts#JsonRpcPeerOptions.beforeDispatch`) et `onFrameAudit`
724
+ (`JsonRpcPeer.ts#JsonRpcPeerOptions.onFrameAudit`) ; les trois du hub sont l'authenticator, le matcher et le contrôle d'origine.
722
725
 
723
726
  → [Sécurité](./securite.md) pour le détail de chacun.
724
727
 
@@ -728,8 +731,8 @@ Le nombre maximal de canaux qu'**une** connexion peut ouvrir. Chaque canal ouver
728
731
  ressources ; sans borne, une seule connexion peut les épuiser. Au-delà, l'abonnement est refusé et le
729
732
  client en est informé.
730
733
 
731
- `limits.maxChannelsPerConnection` (`config.ts:142`), pose `setMaxChannelsPerConnection()`
732
- (`RealtimeHub.ts:976`).
734
+ `limits.maxChannelsPerConnection` (`config.ts#limitsSchema`), pose `setMaxChannelsPerConnection()`
735
+ (`RealtimeHub.ts#RealtimeHub.setMaxChannelsPerConnection`).
733
736
 
734
737
  ## 📡 L'observabilité — ce que la socket dit d'elle-même
735
738
 
@@ -748,7 +751,7 @@ L'oscilloscope du module : « voici mon état, maintenant ». Canaux et abonnés
748
751
  livraisons, connexions, octets, back-pressure, carte d'identité du backplane. Lecture **pure**, sans
749
752
  allocation sur le chemin chaud.
750
753
 
751
- `RealtimeHub.probe()` (`RealtimeHub.ts:847`) rend un `IRealtimeProbe` (`IRealtimeProbe.ts:61`).
754
+ `RealtimeHub.probe()` (`RealtimeHub.ts#RealtimeHub.probe`) rend un `IRealtimeProbe` (`IRealtimeProbe.ts#IRealtimeProbe`).
752
755
 
753
756
  > [!WARNING]
754
757
  > `IRealtimeProbe` est la **forme des données**, pas une interface à implémenter avec une méthode
@@ -760,34 +763,34 @@ La quantité d'octets déjà remise au transport mais pas encore partie. C'est l
760
763
  n° 1 : il monte quand le client n'absorbe plus. Deux seuils distincts existent — un pour **compter**
761
764
  (la sonde), un pour **agir** (abandonner des frames, puis fermer).
762
765
 
763
- Seuil de comptage `SLOW_CONSUMER_BYTES` (`RealtimeHub.ts:64`) — une constante, car il ne
766
+ Seuil de comptage `SLOW_CONSUMER_BYTES` (`RealtimeHub.ts#SLOW_CONSUMER_BYTES`) — une constante, car il ne
764
767
  décide de rien. Les seuils d'ACTION, eux, sont des réglages : `websocket.maxBackpressure`,
765
768
  `websocket.backpressurePolicy` et `websocket.backpressureCloseAfterDrops` (`http`,
766
- `http/nodefony/config/config.ts:1226`).
769
+ `http/nodefony/config/config.ts#httpConfigSchema`).
767
770
 
768
771
  ### `consommateur lent` — la connexion qui décroche
769
772
 
770
773
  Une connexion dont le back-pressure dépasse le seuil de comptage. C'est une **métrique**, pas une
771
774
  sanction : le seuil de comptage se règle indépendamment des seuils d'action.
772
775
 
773
- `slowConsumer.bytes` (`config.ts:121`), champ `backpressure.slowConsumers`
774
- (`IRealtimeProbe.ts:102`).
776
+ `slowConsumer.bytes` (`config.ts#slowConsumerSchema`), champ `backpressure.slowConsumers`
777
+ (`IRealtimeProbe.ts#IRealtimeProbe.backpressure`).
775
778
 
776
779
  ### `compteurs de canal` — mesurés à l'arrivée
777
780
 
778
781
  Nombre de messages, date du dernier, débit instantané, historique glissant — par canal, calculés au
779
782
  point d'arrivée des frames, donc réutilisables par n'importe quelle application.
780
783
 
781
- `IChannelStats` (`IRealtimeSocket.ts:64`), côté serveur `IRealtimeChannelStat`
782
- (`IRealtimeProbe.ts:47`).
784
+ `IChannelStats` (`IRealtimeSocket.ts#IChannelStats.method`), côté serveur `IRealtimeChannelStat`
785
+ (`IRealtimeProbe.ts#IRealtimeChannelStat`).
783
786
 
784
787
  ### `santé agrégée` — la vue du pod entier
785
788
 
786
789
  En multi-worker, chaque process a **sa** sonde. La vue du pod est leur fusion : cumuls additionnés,
787
790
  maxima repris au plus haut, instances listées une à une.
788
791
 
789
- `mergeClusterHealth()` (`ClusterProbeClient.ts:60`), forme `IRealtimeClusterHealth`
790
- (`IRealtimeProbe.ts:178`).
792
+ `mergeClusterHealth()` (`ClusterProbeClient.ts#mergeClusterHealth`), forme `IRealtimeClusterHealth`
793
+ (`IRealtimeProbe.ts#IRealtimeClusterHealth`).
791
794
 
792
795
  ### `plan de données admin` — la sonde servie à Studio
793
796
 
@@ -795,23 +798,23 @@ Le producteur qui expose la sonde sous `/nodefony/realtime/api/health`, selon la
795
798
  routage des administrations de modules. C'est de l'**auto-observabilité** : la socket se regarde
796
799
  elle-même par le même chemin que les autres modules.
797
800
 
798
- `createRealtimeAdminApi()` (`RealtimeAdminApi.ts:98`), construction `buildOwnHealth()`
799
- (`RealtimeAdminApi.ts:52`).
801
+ `createRealtimeAdminApi()` (`RealtimeAdminApi.ts#createRealtimeAdminApi`), construction `buildOwnHealth()`
802
+ (`RealtimeAdminApi.ts#buildOwnHealth`).
800
803
 
801
804
  ## ⚠️ Pièges — les faux-amis du vocabulaire
802
805
 
803
- | Symptôme | Cause — le mot pris pour un autre | Correction |
804
- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
805
- | « Je suis abonné mais je ne reçois rien » | `subscribe` confondu avec `on` : le flux est demandé, aucun écouteur ne le lit | Les deux : `subscribe(canal)` **et** `on(canal, handler)` (`IRealtimeSocket.ts:131`) |
806
- | « Ça marche en local, plus rien dès qu'on passe à plusieurs pods » | canal resté **instance-local** — le défaut. Traverser le process est une capacité qu'on **demande** | Déclarer le préfixe dans `@RealtimeBroadcast` (`realtimeDecorators.ts:420`) |
807
- | « Le client publie, le serveur ignore » | canal non déclaré **entrant**. Un client ne peut rien pousser tant qu'aucun handler n'existe | `@RealtimeInbound("mon:canal")` (`realtimeDecorators.ts:278`) |
808
- | Deux déploiements se mélangent sur un même serveur Redis | pas de **cloison** — le numéro de base ne cloisonne pas le pub/sub | Poser `backplane.namespace` (`config.ts:59`) |
809
- | Le fan-out disparaît entre conteneurs identiques | **origine** dérivée du seul identifiant de processus : deux conteneurs sont tous deux le n° 1, l'anti-écho avale tout | `resolveBackplaneOriginId()` (`originId.ts:24`) dérive du pod ou de l'hôte |
810
- | Deux tickers pour le même tableau de bord | **cadence** différente = **canal** différent, jamais réconcilié | Fabriquer le nom via `rateChannel()` (`channelRate.ts:44`) des deux côtés |
811
- | Un canal se croit protégé mais laisse tout passer | **politique** déclarée sans **verrou de frame** posé pour la faire respecter | Le hub le détecte et avertit : `hasUnenforcedChannelPolicies()` (`RealtimeHub.ts:1098`) |
812
- | Une connexion garde ses flux après une déconnexion applicative | le **verrou de frame** ne relit rien : par construction, il ne voit pas la session mourir | Inscrire l'identité au registre de **révocation** (`RealtimeHub.ts:439`) |
813
- | « Le provider a planté après la fermeture d'un onglet » | **provider** confondu avec **sink** : le provider est partagé, il survit à la connexion créatrice | N'y capturer que des dépendances à longue vie (`ChannelFactory`, `RealtimeHub.ts:171`) |
814
- | « Notre store realtime est indisponible » | **store** employé pour **driver** : le backplane ne conserve rien, il fait passer | Dire « le driver de backplane » — les données ont des stores, les flux ont des drivers |
806
+ | Symptôme | Cause — le mot pris pour un autre | Correction |
807
+ | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
808
+ | « Je suis abonné mais je ne reçois rien » | `subscribe` confondu avec `on` : le flux est demandé, aucun écouteur ne le lit | Les deux : `subscribe(canal)` **et** `on(canal, handler)` (`IRealtimeSocket.ts#IRealtimeSocket.subscribe`) |
809
+ | « Ça marche en local, plus rien dès qu'on passe à plusieurs pods » | canal resté **instance-local** — le défaut. Traverser le process est une capacité qu'on **demande** | Déclarer le préfixe dans `@RealtimeBroadcast` (`realtimeDecorators.ts#RealtimeBroadcast`) |
810
+ | « Le client publie, le serveur ignore » | canal non déclaré **entrant**. Un client ne peut rien pousser tant qu'aucun handler n'existe | `@RealtimeInbound("mon:canal")` (`realtimeDecorators.ts#RealtimeInbound`) |
811
+ | Deux déploiements se mélangent sur un même serveur Redis | pas de **cloison** — le numéro de base ne cloisonne pas le pub/sub | Poser `backplane.namespace` (`config.ts#backplaneSchema`) |
812
+ | Le fan-out disparaît entre conteneurs identiques | **origine** dérivée du seul identifiant de processus : deux conteneurs sont tous deux le n° 1, l'anti-écho avale tout | `resolveBackplaneOriginId()` (`originId.ts#resolveBackplaneOriginId`) dérive du pod ou de l'hôte |
813
+ | Deux tickers pour le même tableau de bord | **cadence** différente = **canal** différent, jamais réconcilié | Fabriquer le nom via `rateChannel()` (`channelRate.ts#rateChannel`) des deux côtés |
814
+ | Un canal se croit protégé mais laisse tout passer | **politique** déclarée sans **verrou de frame** posé pour la faire respecter | Le hub le détecte et avertit : `hasUnenforcedChannelPolicies()` (`RealtimeHub.ts#RealtimeHub.hasUnenforcedChannelPolicies`) |
815
+ | Une connexion garde ses flux après une déconnexion applicative | le **verrou de frame** ne relit rien : par construction, il ne voit pas la session mourir | Inscrire l'identité au registre de **révocation** (`RealtimeHub.ts#RealtimeHub.subscribeClient`) |
816
+ | « Le provider a planté après la fermeture d'un onglet » | **provider** confondu avec **sink** : le provider est partagé, il survit à la connexion créatrice | N'y capturer que des dépendances à longue vie (`ChannelFactory`, `RealtimeHub.ts#ChannelFactory`) |
817
+ | « Notre store realtime est indisponible » | **store** employé pour **driver** : le backplane ne conserve rien, il fait passer | Dire « le driver de backplane » — les données ont des stores, les flux ont des drivers |
815
818
 
816
819
  > [!CAUTION]
817
820
  > Deux mots à ne jamais intervertir en réunion : le **hub** tombe et **un** pod perd sa diffusion ;