@nodefony/realtime 10.0.0-beta.2 → 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.
Files changed (45) hide show
  1. package/README.md +2 -2
  2. package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.153.0}/helpers/esm/decorate.js +1 -1
  3. package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.153.0}/helpers/esm/decorateMetadata.js +1 -1
  4. package/dist/index.js +17 -10
  5. package/dist/nodefony/config/config.js +6 -3
  6. package/dist/nodefony/config/configSchema.js +19 -0
  7. package/dist/nodefony/config/defineModuleConfig.js +42 -15
  8. package/dist/nodefony/decorators/realtimeDecorators.js +68 -17
  9. package/dist/nodefony/src/backplane/RedisBackplane.js +21 -4
  10. package/dist/nodefony/src/backplane/bootNotices.js +43 -0
  11. package/dist/nodefony/src/backplane/publishQueue.js +1 -4
  12. package/dist/nodefony/src/cluster/ClusterProbeClient.js +15 -4
  13. package/dist/nodefony/src/guards.js +41 -0
  14. package/dist/nodefony/src/server/AnonymousRealtimeToken.js +1 -4
  15. package/dist/nodefony/src/server/RealtimeController.js +120 -50
  16. package/dist/nodefony/src/server/RealtimeHub.js +29 -51
  17. package/dist/nodefony/src/server/ServerRealtimeSocket.js +13 -5
  18. package/dist/nodefony/src/server/syslogUplink.js +4 -2
  19. package/dist/nodefony/src/service/RealtimeService.js +3 -2
  20. package/dist/nodefony/testing/index.js +41 -1
  21. package/dist/types/nodefony/config/configSchema.d.ts +12 -0
  22. package/dist/types/nodefony/config/defineModuleConfig.d.ts +29 -6
  23. package/dist/types/nodefony/decorators/realtimeDecorators.d.ts +19 -2
  24. package/dist/types/nodefony/interfaces/IRealtimeController.d.ts +1 -1
  25. package/dist/types/nodefony/interfaces/IRealtimeHandshake.d.ts +1 -1
  26. package/dist/types/nodefony/src/backplane/RedisBackplane.d.ts +7 -0
  27. package/dist/types/nodefony/src/backplane/bootNotices.d.ts +25 -0
  28. package/dist/types/nodefony/src/guards.d.ts +32 -0
  29. package/dist/types/nodefony/src/server/AnonymousRealtimeToken.d.ts +13 -0
  30. package/dist/types/nodefony/src/server/RealtimeController.d.ts +24 -4
  31. package/dist/types/nodefony/src/server/RealtimeHub.d.ts +22 -16
  32. package/dist/types/nodefony/src/service/RealtimeService.d.ts +1 -1
  33. package/dist/types/nodefony/testing/index.d.ts +2 -2
  34. package/docs/actions.md +111 -28
  35. package/docs/architecture.md +77 -75
  36. package/docs/cadence-et-contre-pression.md +634 -0
  37. package/docs/configuration.md +39 -34
  38. package/docs/cookbook-chat.md +31 -27
  39. package/docs/index.md +35 -21
  40. package/docs/observabilite.md +17 -15
  41. package/docs/protocole.md +74 -63
  42. package/docs/securite.md +49 -49
  43. package/docs/vocabulaire.md +76 -75
  44. package/package.json +16 -12
  45. package/docs/couverture-bancs.md +0 -139
@@ -113,7 +113,7 @@ Trois mots du lexique ne sont pas des synonymes de ce que proposent les autres b
113
113
  temps réel. Ce sont les différenciateurs du framework.
114
114
 
115
115
  **`socket` est isomorphe.** Le même contrat `IRealtimeSocket` (`IRealtimeSocket.ts:122`) décrit la
116
- prise **côté navigateur** (`RealtimeClient`, `RealtimeClient.ts:194`) **et côté serveur**
116
+ prise **côté navigateur** (`NodefonySocket`, `NodefonySocket.ts:195`) **et côté serveur**
117
117
  (`ServerRealtimeSocket`, `ServerRealtimeSocket.ts:44`). 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**.
@@ -125,9 +125,9 @@ vers un autre service — et le consommateur ne voit jamais la différence
125
125
  (`IRealtimeChannel`, `IRealtimeSocket.ts:87`).
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:144`) et porte une route WebSocket : HTTP et WebSocket
128
+ `RealtimeController` (`RealtimeController.ts:203`) 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:276`) de rejouer sur la socket
130
+ (`RealtimeController.realtimeApiRequest()`, `RealtimeController.ts:321`) 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
@@ -179,10 +179,10 @@ export class ChatController extends RealtimeController {
179
179
  Côté client — la même **socket**, les quatre verbes :
180
180
 
181
181
  ```ts
182
- import { RealtimeClient } from "nodefony/client";
182
+ import { NodefonySocket } from "nodefony/client";
183
183
 
184
184
  export async function joinChat(): Promise<void> {
185
- const socket = new RealtimeClient({ url: "wss://127.0.0.1:5152/chat" });
185
+ const socket = new NodefonySocket({ url: "wss://127.0.0.1:5152/chat" });
186
186
  await socket.connect();
187
187
 
188
188
  socket.subscribe("chat:room-42"); // DEMANDE le flux au pair
@@ -218,7 +218,7 @@ Le **handle unique** manipulé par le code applicatif : quatre verbes (`subscrib
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
220
  Contrat isomorphe `IRealtimeSocket` (`IRealtimeSocket.ts:122`), implémenté côté navigateur par
221
- `RealtimeClient` (`RealtimeClient.ts:194`) et côté serveur par `ServerRealtimeSocket`
221
+ `NodefonySocket` (`NodefonySocket.ts:394`) et côté serveur par `ServerRealtimeSocket`
222
222
  (`ServerRealtimeSocket.ts:44`). ⚠️ La socket **n'est pas** le [transport](#transport--la-couche-octets).
223
223
 
224
224
  → [Architecture](./architecture.md) pour la pile complète.
@@ -229,7 +229,7 @@ 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:462`) l'indexe ; `IRealtimeSocket.subscribe()`
232
+ `RealtimeHub.subscribe()` (`RealtimeHub.ts:464`) l'indexe ; `IRealtimeSocket.subscribe()`
233
233
  (`IRealtimeSocket.ts:132`) le demande. Plusieurs canaux cohabitent sur **une** connexion : c'est le
234
234
  multiplexage.
235
235
 
@@ -255,22 +255,22 @@ 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:604`).
258
+ `IRealtimeSocket.publish()` (`IRealtimeSocket.ts:160`), `RealtimeHub.publish()` (`RealtimeHub.ts:610`).
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:101`), appelée
263
+ sur expiration. Déclarée côté serveur par `@RealtimeAction` (`realtimeDecorators.ts:191`), appelée
264
264
  côté client par `request()` (`IRealtimeSocket.ts:166`).
265
265
 
266
266
  Un handler qui lève une erreur rend un `-32603` **générique** ; seule une `RpcError`
267
- (`JsonRpcPeer.ts:70`) choisit ce qu'elle expose au pair.
267
+ (`JsonRpcPeer.ts:101`) choisit ce qu'elle expose au pair.
268
268
 
269
269
  ### `canal entrant` (inbound) — le client pousse
270
270
 
271
271
  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:231`) ou
273
- par l'override `realtimeInbound()` (`RealtimeController.ts:262`).
272
+ n'accepte d'entrée que déclaré explicitement, par `@RealtimeInbound` (`realtimeDecorators.ts:278`) ou
273
+ par l'override `realtimeInbound()` (`RealtimeController.ts:307`).
274
274
 
275
275
  Le handler reçoit `(params, reply)` — `params` vient du réseau, donc **jamais fiable** :
276
276
  `RealtimeInboundHandler` (`IRealtimeController.ts:16`).
@@ -285,7 +285,7 @@ point d'accroche des canaux à état — un appel SIP, une connexion pontée.
285
285
 
286
286
  ### `client` — la socket côté navigateur
287
287
 
288
- `RealtimeClient` (`RealtimeClient.ts:161`) : reconnexion automatique, réémission des abonnements,
288
+ `NodefonySocket` (`NodefonySocket.ts:162`) : reconnexion automatique, réémission des abonnements,
289
289
  compteurs par canal, heartbeat. Publié dans le sous-chemin `nodefony/client` du cœur — donc
290
290
  importable **sans** aucune dépendance serveur, ce qui est la condition de l'isomorphisme.
291
291
 
@@ -294,10 +294,10 @@ importable **sans** aucune dépendance serveur, ce qui est la condition de l'iso
294
294
  ### `façade serveur` — la même socket, côté back
295
295
 
296
296
  `ServerRealtimeSocket` (`ServerRealtimeSocket.ts:44`), obtenue par `serverSocket()`
297
- (`ServerRealtimeSocket.ts:223`) : un service métier tient un handle et publie **comme une page
297
+ (`ServerRealtimeSocket.ts:243`) : un service métier tient un handle et publie **comme une page
298
298
  front**. Une différence assumée : `request()` n'y est pas supporté — au-dessus du hub il n'y a pas
299
299
  **un** pair mais N clients. Pour un appel serveur → un client précis, c'est `requestClient()`
300
- (`RealtimeController.ts:329`).
300
+ (`RealtimeController.ts:395`).
301
301
 
302
302
  ### `accueil` (welcome) — la première frame
303
303
 
@@ -335,10 +335,10 @@ handshake, jamais renégociée par frame.
335
335
  ### `hub` — le broker du process
336
336
 
337
337
  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:1451`). Il ne connaît **ni** les
338
+ process = un hub, obtenu par `getRealtimeHub()` (`RealtimeHub.ts:1432`). Il ne connaît **ni** les
339
339
  contrôleurs, **ni** le métier : ce sont les providers qui portent les dépendances.
340
340
 
341
- `RealtimeHub` (`RealtimeHub.ts:213`). ⚠️ « hub » désigne **toujours** le serveur ; ce que tient le
341
+ `RealtimeHub` (`RealtimeHub.ts:214`). ⚠️ « hub » désigne **toujours** le serveur ; ce que tient le
342
342
  code applicatif s'appelle une [socket](#socket--la-prise-que-tient-ton-code).
343
343
 
344
344
  ### `fan-out` — une publication, N livraisons
@@ -346,15 +346,15 @@ code applicatif s'appelle une [socket](#socket--la-prise-que-tient-ton-code).
346
346
  L'action de diffuser : une charge publiée sur un canal part vers **tous** les sinks abonnés
347
347
  localement. Une connexion fautive ne casse pas la diffusion aux autres — chaque livraison est isolée.
348
348
 
349
- `RealtimeHub.publish()` (`RealtimeHub.ts:604`) pour le chemin complet, `publishLocal()`
350
- (`RealtimeHub.ts:623`) pour la diffusion **strictement locale** (voie d'entrée du backplane).
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).
351
351
 
352
352
  ### `sink` — le point de livraison d'une connexion
353
353
 
354
354
  Un `sink` = **une** connexion sur **un** canal : la fonction qui pousse la charge vers son pair. Le
355
355
  mot vient du couple source/puits : le provider est la source, le sink le puits.
356
356
 
357
- `ChannelSink` (`RealtimeHub.ts:155`). Le nombre de sinks d'un canal est son nombre d'abonnés locaux.
357
+ `ChannelSink` (`RealtimeHub.ts:156`). Le nombre de sinks d'un canal est son nombre d'abonnés locaux.
358
358
 
359
359
  ### `provider` — ce qui produit un canal
360
360
 
@@ -362,7 +362,7 @@ La fabrique appelée **au premier abonné** d'un canal, qui démarre ce qui prod
362
362
  ticker, un écouteur) et rend son `dispose`, appelé **au dernier désabonné**. Zéro abonné = zéro
363
363
  timer.
364
364
 
365
- `ChannelFactory` (`RealtimeHub.ts:170`). ⚠️ Un provider est **partagé** : il survit à la connexion qui
365
+ `ChannelFactory` (`RealtimeHub.ts:171`). ⚠️ Un provider est **partagé** : il survit à la connexion qui
366
366
  l'a créé — n'y capturer que des dépendances à longue vie, jamais le contexte d'une connexion.
367
367
 
368
368
  ### `canal partagé` — un provider par canal et par pod
@@ -371,7 +371,7 @@ Le modèle de coût du module : là où une implémentation naïve crée un tick
371
371
  Nodefony en crée **un par canal et par pod** et se contente d'ajouter un sink par abonné. Mille
372
372
  spectateurs d'un tableau de bord coûtent un timer, pas mille.
373
373
 
374
- `RealtimeHub.subscribe()` (`RealtimeHub.ts:462`).
374
+ `RealtimeHub.subscribe()` (`RealtimeHub.ts:464`).
375
375
 
376
376
  ### `canal passif` — écouter n'est pas posséder
377
377
 
@@ -385,8 +385,8 @@ reçoit du vrai producteur ; sinon l'abonnement est refusé, et l'écoute en pla
385
385
  Sans cette distinction, le premier service qui prononce un nom de canal l'ouvrirait pour tout le
386
386
  monde — et les clients suivants s'abonneraient à un flux que rien ne produit, sans jamais l'apprendre.
387
387
 
388
- `RealtimeHub.listen()` (`RealtimeHub.ts:455`) pour l'écoute, `isChannelOwned()`
389
- (`RealtimeHub.ts:550`) pour lire l'état. ⚠️ Un canal passif **n'est pas** un canal fermé : il n'a
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
390
390
  simplement pas encore de propriétaire.
391
391
 
392
392
  ### `canal système` — servi par la plateforme
@@ -395,7 +395,7 @@ Un canal dont la fabrique est déclarée par un module bas niveau (le journal d'
395
395
  qui devient servable par **n'importe quel** endpoint, présent ou futur, sans qu'aucun contrôleur ne
396
396
  le connaisse. Consulté seulement quand la fabrique du contrôleur a dit « inconnu ».
397
397
 
398
- `RealtimeHub.registerSystemChannel()` (`RealtimeHub.ts:1297`).
398
+ `RealtimeHub.registerSystemChannel()` (`RealtimeHub.ts:1278`).
399
399
 
400
400
  ### `canal broadcast` — celui qui traverse le backplane
401
401
 
@@ -403,8 +403,8 @@ Un canal **déclaré** comme franchissant la frontière du process : chat, prés
403
403
  déclaration se fait par **préfixe**, ce qui couvre les variantes de cadence (`chat:` couvre
404
404
  `chat:room-42:1000`).
405
405
 
406
- `RealtimeHub.markBroadcastChannel()` (`RealtimeHub.ts:668`), déclaré côté contrôleur par
407
- `@RealtimeBroadcast` (`realtimeDecorators.ts:342`).
406
+ `RealtimeHub.markBroadcastChannel()` (`RealtimeHub.ts:674`), déclaré côté contrôleur par
407
+ `@RealtimeBroadcast` (`realtimeDecorators.ts:420`).
408
408
 
409
409
  ### `canal instance-local` — le défaut
410
410
 
@@ -412,15 +412,15 @@ Tout canal **non déclaré** broadcast reste dans son process. C'est voulu : les
412
412
  d'observabilité (journaux, sondes, état interne) décrivent **ce pod**, et les agréger silencieusement
413
413
  serait à la fois faux et une fuite. Traverser le process est une capacité qu'un canal **demande**.
414
414
 
415
- Politique de forward du hub — `#broadcastPrefixes` (`RealtimeHub.ts:393`).
415
+ Politique de forward du hub — `#broadcastPrefixes` (`RealtimeHub.ts:395`).
416
416
 
417
417
  ### `contrôleur temps réel` — l'endpoint WebSocket
418
418
 
419
419
  La classe de base d'un endpoint : elle porte tout le protocole (handshake, accueil, discrimination
420
420
  des frames, cycle de vie des canaux) et ne laisse au métier que ses canaux et ses actions.
421
421
 
422
- `RealtimeController` (`RealtimeController.ts:144`), point d'entrée `handleRealtime()`
423
- (`RealtimeController.ts:299`). C'est un **contrôleur** au sens habituel de Nodefony : la même classe
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
424
424
  peut porter des routes HTTP.
425
425
 
426
426
  ### `service realtime` — la façade d'injection
@@ -437,31 +437,32 @@ L'option qui expose la méthode `api.request { path }` : la connexion rejoue **l
437
437
  contrôleur** que celle servie en REST, avec **la même garde**. Le pont n'atteint que les routes qui
438
438
  déclarent explicitement le transport WebSocket — aucun contournement possible.
439
439
 
440
- `realtimeApiRequest()` (`RealtimeController.ts:276`), mise en œuvre `invokeApiRequest()`
441
- (`RealtimeController.ts:949`). Désactivé par défaut.
440
+ `realtimeApiRequest()` (`RealtimeController.ts:321`), mise en œuvre `invokeApiRequest()`
441
+ (`RealtimeController.ts:1070`). Désactivé par défaut.
442
442
 
443
443
  ## 🔌 Le protocole et le transport — ce qui passe sur le fil
444
444
 
445
- | Terme | En une ligne |
446
- | -------------- | ---------------------------------------------------------- |
447
- | `frame` | l'unité atomique qui passe sur le fil |
448
- | `pair` | le moteur de protocole, identique client et serveur |
449
- | `requête` | frame avec `method` **et** `id` — appelle une réponse |
450
- | `notification` | frame avec `method` seul — sans réponse |
451
- | `réponse` | frame avec `id` seul — résout une requête en attente |
452
- | `dispatch` | classer une frame entrante puis la router vers son handler |
453
- | `transport` | la couche octets, interchangeable |
454
- | `enveloppe` | joindre des métadonnées serveur à côté du résultat |
455
- | `cadence` | la fréquence d'un canal d'état, portée par son nom |
456
- | `AIMD` | l'auto-ajustement de cadence face à un client lent |
445
+ | Terme | En une ligne |
446
+ | -------------- | ----------------------------------------------------------- |
447
+ | `frame` | l'unité atomique qui passe sur le fil |
448
+ | `pair` | le moteur de protocole, identique client et serveur |
449
+ | `requête` | frame avec `method` **et** `id` — appelle une réponse |
450
+ | `notification` | frame avec `method` seul — sans réponse |
451
+ | `réponse` | `id` + `result` ou `error`, sans `method` — résout un appel |
452
+ | `dispatch` | classer une frame entrante puis la router vers son handler |
453
+ | `transport` | la couche octets, interchangeable |
454
+ | `enveloppe` | joindre des métadonnées serveur à côté du résultat |
455
+ | `cadence` | la fréquence d'un canal d'état, portée par son nom |
456
+ | `AIMD` | l'auto-ajustement de cadence face à un client lent |
457
457
 
458
458
  ### `frame` — l'unité qui passe sur le fil
459
459
 
460
460
  Une enveloppe JSON-RPC 2.0. Sa **nature se lit sur `method`**, pas sur `id` : `method` + `id` =
461
- requête, `method` seul = notification, `id` seul = réponse. Tout le reste est invalide.
461
+ requête, `method` seul = notification, `id` + `result` ou `error` = réponse. Tout le reste est
462
+ invalide.
462
463
 
463
- `JsonRpcFrameKind` (`JsonRpcPeer.ts:133`). Le classement est fait par `JsonRpcPeer.receive()`
464
- (`JsonRpcPeer.ts:390`), qui **ne lève jamais**.
464
+ `JsonRpcFrameKind` (`jsonrpc/index.ts:138`). Le classement est fait par `JsonRpcPeer.receive()`
465
+ (`JsonRpcPeer.ts:419`), qui **ne lève jamais**.
465
466
 
466
467
  ### `pair` (peer) — le moteur de protocole
467
468
 
@@ -469,14 +470,14 @@ Client et serveur sont des **pairs** : classer une frame, router, corréler les
469
470
  travail des deux côtés. Nodefony l'écrit **une seule fois** ; chaque côté l'entoure de son transport
470
471
  et de ses handlers. Aucune dépendance Node — le même fichier tourne dans le navigateur.
471
472
 
472
- `JsonRpcPeer` (`JsonRpcPeer.ts:280`), contrat `IRealtimePeer` (`JsonRpcPeer.ts:213`).
473
+ `JsonRpcPeer` (`JsonRpcPeer.ts:311`), contrat `IRealtimePeer` (`JsonRpcPeer.ts:261`).
473
474
 
474
475
  ### `dispatch` — classer puis router
475
476
 
476
477
  Le geste du pair sur une frame entrante : décider de sa nature, trouver le handler, l'appeler,
477
478
  renvoyer la réponse s'il en faut une. Ce n'est pas une méthode publique — c'est un **moment**, et
478
479
  c'est précisément là que se greffe le [verrou de frame](#verrou-de-frame--la-décision-par-frame),
479
- par le crochet `beforeDispatch` (`JsonRpcPeer.ts:192`).
480
+ par le crochet `beforeDispatch` (`JsonRpcPeer.ts:219`).
480
481
 
481
482
  ### `transport` — la couche octets
482
483
 
@@ -494,7 +495,7 @@ Quand un handler veut joindre des métadonnées serveur à sa réponse, il rend
494
495
  `result` reste **exactement** ce que rendrait la même route en REST, et la méta voyage dans un champ
495
496
  frère. Un pair qui n'en connaît pas les clés les ignore.
496
497
 
497
- `RpcEnvelope` (`JsonRpcPeer.ts:104`), `RpcMeta` (`JsonRpcPeer.ts:90`).
498
+ `RpcEnvelope` (`JsonRpcPeer.ts:139`), `RpcMeta` (`JsonRpcPeer.ts:125`).
498
499
 
499
500
  ### `cadence` — la fréquence portée par le nom
500
501
 
@@ -535,7 +536,7 @@ publications d'un process aux autres. Un contrat volontairement minuscule — `p
535
536
  `start`, `stop`, `describe` — parce que tout ce qui est riche appartient au hub.
536
537
 
537
538
  `IBackplane` (`IBackplane.ts:107`), message `IBackplaneMessage` (`IBackplane.ts:51`), branchement
538
- `RealtimeHub.setBackplane()` (`RealtimeHub.ts:714`).
539
+ `RealtimeHub.setBackplane()` (`RealtimeHub.ts:720`).
539
540
 
540
541
  > [!TIP]
541
542
  > C'est la pièce qui tient la promesse « une ligne de configuration change tout » : le hub **ne sait
@@ -587,7 +588,7 @@ La règle qui empêche les tempêtes : un message **reçu** du backplane est ré
587
588
  **locale seulement**, jamais renvoyé sur le bus ; et un message qui porte sa propre origine est
588
589
  ignoré à l'arrivée. Sans l'origine, l'anti-écho ferait taire le voisin au lieu de soi-même.
589
590
 
590
- `RealtimeHub.publishLocal()` (`RealtimeHub.ts:623`).
591
+ `RealtimeHub.publishLocal()` (`RealtimeHub.ts:629`).
591
592
 
592
593
  ### `cloison` (namespace) — deux applications, un même bus
593
594
 
@@ -630,7 +631,7 @@ l'authenticator tourne et que l'identité est figée. Tout ce qui coûte cher se
630
631
  frame.
631
632
 
632
633
  DTO neutre `IRealtimeHandshake` (`IRealtimeHandshake.ts:14`) — en-têtes, cookies, URL, origine,
633
- sous-protocoles. Traitement dans `onHandshake()` (`RealtimeController.ts:374`).
634
+ sous-protocoles. Traitement dans `onHandshake()` (`RealtimeController.ts:450`).
634
635
 
635
636
  ### `authenticator` — du handshake à l'identité
636
637
 
@@ -639,7 +640,7 @@ La stratégie qui transforme une poignée de main en identité : `supports` (est
639
640
  HTTP — un seul modèle mental pour les deux transports.
640
641
 
641
642
  `IRealtimeAuthenticator` (`IRealtimeAuthenticator.ts:24`), enregistrement
642
- `RealtimeHub.useAuthenticator()` (`RealtimeHub.ts:943`).
643
+ `RealtimeHub.useAuthenticator()` (`RealtimeHub.ts:925`).
643
644
 
644
645
  ### `matcher` — qui capture quoi
645
646
 
@@ -647,7 +648,7 @@ Le sélecteur (motif d'URL, hôte optionnel) qui décide **quel** authenticator
647
648
  **premier** qui capture gagne : on enregistre donc du plus spécifique au plus général.
648
649
 
649
650
  `IRealtimeAuthenticatorMatcher` (`IRealtimeAuthenticatorMatcher.ts:25`), résolution
650
- `resolveAuthenticator()` (`RealtimeHub.ts:961`).
651
+ `resolveAuthenticator()` (`RealtimeHub.ts:943`).
651
652
 
652
653
  ### `jeton` (token) — l'identité figée
653
654
 
@@ -664,7 +665,7 @@ Les exigences déclarées d'un canal : authentifié, rôles, scopes. Elles s'att
664
665
  canal, pas à la méthode qui l'implémente — parce que c'est le nom que le pare-feu résout.
665
666
 
666
667
  `IChannelPolicy` (`IChannelPolicy.ts:20`), déclaration par `@RealtimeChannel`
667
- (`realtimeDecorators.ts:192`), résolution `resolveChannelPolicy()` (`RealtimeHub.ts:1228`).
668
+ (`realtimeDecorators.ts:240`), résolution `resolveChannelPolicy()` (`RealtimeHub.ts:1209`).
668
669
 
669
670
  ### `verrou de frame` — la décision par frame
670
671
 
@@ -672,8 +673,8 @@ La fonction qui répond « cette frame passe-t-elle ? » à partir du jeton **d
672
673
  **Synchrone par doctrine** : attendre une réponse distante à chaque frame sérialiserait le flux d'une
673
674
  connexion. Un contrôle qui doit interroger le réseau se fait au handshake, pas ici.
674
675
 
675
- `FrameAuthorizer` (`RealtimeHub.ts:39`), pose `setFrameAuthorizer()` (`RealtimeHub.ts:1039`), appel
676
- `runAuthorizer()` (`RealtimeHub.ts:1328`).
676
+ `FrameAuthorizer` (`RealtimeHub.ts:40`), pose `setFrameAuthorizer()` (`RealtimeHub.ts:1020`), appel
677
+ `runAuthorizer()` (`RealtimeHub.ts:1309`).
677
678
 
678
679
  ### `contrôle d'origine` — la défense d'ouverture
679
680
 
@@ -681,7 +682,7 @@ Le contrôle de l'en-tête `Origin` à l'ouverture (RFC 6455 §10.2) : un site t
681
682
  ouvrir une connexion authentifiée dans le dos de l'utilisateur. Correspondance **exacte**, aucun
682
683
  caractère générique.
683
684
 
684
- `OriginGuard` (`RealtimeHub.ts:33`), configuration `csrf.checkOrigin` (`config.ts:255`).
685
+ `OriginGuard` (`RealtimeHub.ts:34`), configuration `csrf.checkOrigin` (`config.ts:255`).
685
686
 
686
687
  ### `révocation` — la connexion qui survit à sa session
687
688
 
@@ -689,8 +690,8 @@ Le problème que ce mot nomme : une connexion ouverte pourrait survivre à la se
689
690
  autorisée, puisque le verrou de frame ne relit rien. Un contrôle périodique referme l'écart en
690
691
  re-validant les identités révocables et en fermant celles qui ne valent plus.
691
692
 
692
- `registerRevocable()` (`RealtimeHub.ts:794`), `revalidateRevocable()` (`RealtimeHub.ts:826`), période
693
- `REVOCATION_REVALIDATE_MS` (`RealtimeHub.ts:111`). Une re-validation en erreur **ferme** — jamais
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
694
695
  l'inverse.
695
696
 
696
697
  ### `refus` (denied) — rendre le « non » visible
@@ -700,7 +701,7 @@ donc `realtime:denied`. Le motif est **générique** : jamais le rôle ou le sco
700
701
  transformerait le refus en oracle d'autorisation.
701
702
 
702
703
  `IRealtimeDenied` (`RealtimeEventMap.ts:269`), conversion en message d'interface `deniedToNotice()`
703
- (`notice.ts:45`).
704
+ (`notice.ts:46`).
704
705
 
705
706
  ### `audit de frame` — tracer le notable
706
707
 
@@ -708,7 +709,7 @@ Le signal émis sur les événements protocolaires qui méritent une trace : fra
708
709
  refusée, méthode inconnue, erreur interne. Émis sans attente, avec le pair — ce qui permet de
709
710
  retrouver **qui** a été refusé, pas seulement d'où venait le paquet.
710
711
 
711
- `FrameAuditReason` (`JsonRpcPeer.ts:155`), crochet `onFrameAudit` (`JsonRpcPeer.ts:210`).
712
+ `FrameAuditReason` (`JsonRpcPeer.ts:182`), crochet `onFrameAudit` (`JsonRpcPeer.ts:236`).
712
713
 
713
714
  ### `seam` — le point de greffe
714
715
 
@@ -716,8 +717,8 @@ Littéralement une **couture** : un point prévu dans une couche basse pour qu'u
716
717
  greffe du comportement **sans modifier la couche basse**. Le module en expose cinq, et c'est ce qui
717
718
  permet à la couche sécurité de se brancher sans qu'aucune ligne de realtime ne la connaisse.
718
719
 
719
- Les deux du protocole sont `beforeDispatch` (`JsonRpcPeer.ts:192`) et `onFrameAudit`
720
- (`JsonRpcPeer.ts:210`) ; les trois du hub sont l'authenticator, le matcher et le contrôle d'origine.
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.
721
722
 
722
723
  → [Sécurité](./securite.md) pour le détail de chacun.
723
724
 
@@ -728,7 +729,7 @@ ressources ; sans borne, une seule connexion peut les épuiser. Au-delà, l'abon
728
729
  client en est informé.
729
730
 
730
731
  `limits.maxChannelsPerConnection` (`config.ts:142`), pose `setMaxChannelsPerConnection()`
731
- (`RealtimeHub.ts:995`).
732
+ (`RealtimeHub.ts:976`).
732
733
 
733
734
  ## 📡 L'observabilité — ce que la socket dit d'elle-même
734
735
 
@@ -747,7 +748,7 @@ L'oscilloscope du module : « voici mon état, maintenant ». Canaux et abonnés
747
748
  livraisons, connexions, octets, back-pressure, carte d'identité du backplane. Lecture **pure**, sans
748
749
  allocation sur le chemin chaud.
749
750
 
750
- `RealtimeHub.probe()` (`RealtimeHub.ts:865`) rend un `IRealtimeProbe` (`IRealtimeProbe.ts:61`).
751
+ `RealtimeHub.probe()` (`RealtimeHub.ts:847`) rend un `IRealtimeProbe` (`IRealtimeProbe.ts:61`).
751
752
 
752
753
  > [!WARNING]
753
754
  > `IRealtimeProbe` est la **forme des données**, pas une interface à implémenter avec une méthode
@@ -759,10 +760,10 @@ La quantité d'octets déjà remise au transport mais pas encore partie. C'est l
759
760
  n° 1 : il monte quand le client n'absorbe plus. Deux seuils distincts existent — un pour **compter**
760
761
  (la sonde), un pour **agir** (abandonner des frames, puis fermer).
761
762
 
762
- Seuil de comptage `SLOW_CONSUMER_BYTES` (`RealtimeHub.ts:63`) — une constante, car il ne
763
+ Seuil de comptage `SLOW_CONSUMER_BYTES` (`RealtimeHub.ts:64`) — une constante, car il ne
763
764
  décide de rien. Les seuils d'ACTION, eux, sont des réglages : `websocket.maxBackpressure`,
764
765
  `websocket.backpressurePolicy` et `websocket.backpressureCloseAfterDrops` (`http`,
765
- `http/nodefony/config/config.ts:1048`).
766
+ `http/nodefony/config/config.ts:1226`).
766
767
 
767
768
  ### `consommateur lent` — la connexion qui décroche
768
769
 
@@ -785,7 +786,7 @@ point d'arrivée des frames, donc réutilisables par n'importe quelle applicatio
785
786
  En multi-worker, chaque process a **sa** sonde. La vue du pod est leur fusion : cumuls additionnés,
786
787
  maxima repris au plus haut, instances listées une à une.
787
788
 
788
- `mergeClusterHealth()` (`ClusterProbeClient.ts:46`), forme `IRealtimeClusterHealth`
789
+ `mergeClusterHealth()` (`ClusterProbeClient.ts:60`), forme `IRealtimeClusterHealth`
789
790
  (`IRealtimeProbe.ts:178`).
790
791
 
791
792
  ### `plan de données admin` — la sonde servie à Studio
@@ -802,14 +803,14 @@ elle-même par le même chemin que les autres modules.
802
803
  | Symptôme | Cause — le mot pris pour un autre | Correction |
803
804
  | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
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`) |
805
- | « Ç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:342`) |
806
- | « 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:231`) |
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`) |
807
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`) |
808
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 |
809
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 |
810
- | 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:1117`) |
811
- | 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:433`) |
812
- | « 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:170`) |
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`) |
813
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 |
814
815
 
815
816
  > [!CAUTION]
package/package.json CHANGED
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "name": "@nodefony/realtime",
3
- "version": "10.0.0-beta.2",
3
+ "version": "10.0.0-beta.3",
4
+ "nodefony": {
5
+ "configSchema": "./dist/nodefony/config/configSchema.js"
6
+ },
4
7
  "description": "Couche temps réel serveur de Nodefony : canaux, appels bidirectionnels JSON-RPC 2.0, contre-pression, et diffusion entre plusieurs instances (boucle locale, IPC de grappe, Redis)",
5
8
  "author": "Christophe CAMENSULI <ccamensuli@gmail.com>",
6
9
  "type": "module",
@@ -41,20 +44,20 @@
41
44
  "esm"
42
45
  ],
43
46
  "peerDependencies": {
44
- "@nodefony/framework": "^10.0.0-beta.2",
45
- "@nodefony/http": "^10.0.0-beta.2",
46
- "nodefony": "^10.0.0-beta.2",
47
+ "@nodefony/framework": "^10.0.0-beta.3",
48
+ "@nodefony/http": "^10.0.0-beta.3",
49
+ "nodefony": "^10.0.0-beta.3",
47
50
  "reflect-metadata": "0.2.2",
48
- "zod": "^4.6.5"
51
+ "zod": "^4.6.7"
49
52
  },
50
53
  "devDependencies": {
51
- "@nodefony/framework": "^10.0.0-beta.2",
52
- "@nodefony/http": "^10.0.0-beta.2",
53
- "@types/node": "26.6.3",
54
- "@vitest/coverage-v8": "5.0.3",
55
- "nodefony": "^10.0.0-beta.2",
54
+ "@nodefony/framework": "^10.0.0-beta.3",
55
+ "@nodefony/http": "^10.0.0-beta.3",
56
+ "@types/node": "26.6.5",
57
+ "@vitest/coverage-v8": "5.0.4",
58
+ "nodefony": "^10.0.0-beta.3",
56
59
  "reflect-metadata": "0.2.2",
57
- "vitest": "5.0.3"
60
+ "vitest": "5.0.4"
58
61
  },
59
62
  "private": false,
60
63
  "engines": {
@@ -66,7 +69,8 @@
66
69
  },
67
70
  "files": [
68
71
  "dist",
69
- "docs"
72
+ "docs",
73
+ "!docs/couverture-bancs.md"
70
74
  ],
71
75
  "publishConfig": {
72
76
  "access": "public"