@nodefony/realtime 10.0.0-beta.5 → 10.0.0-beta.6
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 +50 -0
- package/dist/_virtual/{_@oxc-project_runtime@0.155.0 → _@oxc-project_runtime@0.157.0}/helpers/esm/decorate.js +1 -1
- package/dist/index.js +79 -12
- package/dist/nodefony/config/config.js +7 -0
- package/dist/nodefony/decorators/realtimeDecorators.js +125 -19
- package/dist/nodefony/interfaces/IRealtimeConnection.js +1 -0
- package/dist/nodefony/src/backplane/ClusterBackplane.js +32 -4
- package/dist/nodefony/src/backplane/RedisBackplane.js +143 -31
- package/dist/nodefony/src/backplane/controlOrder.js +57 -0
- package/dist/nodefony/src/backplane/envelope.js +113 -12
- package/dist/nodefony/src/cluster/ClusterProbeClient.js +2 -0
- package/dist/nodefony/src/server/AnonymousRealtimeToken.js +23 -2
- package/dist/nodefony/src/server/ContractRealtimeController.js +75 -0
- package/dist/nodefony/src/server/RealtimeConnection.js +110 -0
- package/dist/nodefony/src/server/RealtimeController.js +340 -81
- package/dist/nodefony/src/server/RealtimeHub.js +604 -61
- package/dist/nodefony/src/server/ServerRealtimeSocket.js +2 -0
- package/dist/nodefony/src/server/admissionBudget.js +176 -0
- package/dist/nodefony/src/server/channelPolicyRegistry.js +73 -0
- package/dist/nodefony/src/server/connectionAdmission.js +242 -0
- package/dist/nodefony/src/server/contractRoutes.js +85 -0
- package/dist/nodefony/src/server/deniedDetail.js +10 -3
- package/dist/nodefony/src/server/frameAdmission.js +168 -0
- package/dist/nodefony/src/server/subscribedAck.js +10 -1
- package/dist/nodefony/src/server/syslogUplink.js +3 -4
- package/dist/nodefony/src/server/welcomeIdentity.js +37 -0
- package/dist/nodefony/src/service/RealtimeService.js +42 -1
- package/dist/nodefony/src/service/publicOrigin.js +40 -0
- package/dist/nodefony/src/transport/WsConnectionTransport.js +27 -4
- package/dist/nodefony/testing/backplaneConformance.js +271 -0
- package/dist/nodefony/testing/index.js +21 -4
- package/dist/types/index.d.ts +34 -7
- package/dist/types/nodefony/config/config.d.ts +9 -0
- package/dist/types/nodefony/decorators/realtimeDecorators.d.ts +86 -6
- package/dist/types/nodefony/interfaces/IBackplane.d.ts +34 -50
- package/dist/types/nodefony/interfaces/IChannelPolicy.d.ts +5 -25
- package/dist/types/nodefony/interfaces/IRealtimeConnection.d.ts +22 -0
- package/dist/types/nodefony/interfaces/IRealtimeConnectionState.d.ts +41 -2
- package/dist/types/nodefony/interfaces/IRealtimeController.d.ts +64 -3
- package/dist/types/nodefony/interfaces/IRealtimeProbe.d.ts +7 -166
- package/dist/types/nodefony/interfaces/IRealtimeToken.d.ts +21 -0
- package/dist/types/nodefony/interfaces/index.d.ts +2 -1
- package/dist/types/nodefony/src/backplane/ClusterBackplane.d.ts +22 -4
- package/dist/types/nodefony/src/backplane/RedisBackplane.d.ts +79 -16
- package/dist/types/nodefony/src/backplane/controlOrder.d.ts +52 -0
- package/dist/types/nodefony/src/backplane/envelope.d.ts +34 -0
- package/dist/types/nodefony/src/server/AnonymousRealtimeToken.d.ts +10 -0
- package/dist/types/nodefony/src/server/ContractRealtimeController.d.ts +138 -0
- package/dist/types/nodefony/src/server/RealtimeConnection.d.ts +17 -0
- package/dist/types/nodefony/src/server/RealtimeController.d.ts +91 -11
- package/dist/types/nodefony/src/server/RealtimeHub.d.ts +321 -24
- package/dist/types/nodefony/src/server/ServerRealtimeSocket.d.ts +1 -1
- package/dist/types/nodefony/src/server/admissionBudget.d.ts +71 -0
- package/dist/types/nodefony/src/server/channelPolicyRegistry.d.ts +34 -0
- package/dist/types/nodefony/src/server/connectionAdmission.d.ts +68 -0
- package/dist/types/nodefony/src/server/contractRoutes.d.ts +42 -0
- package/dist/types/nodefony/src/server/deniedDetail.d.ts +6 -0
- package/dist/types/nodefony/src/server/frameAdmission.d.ts +70 -0
- package/dist/types/nodefony/src/server/subscribedAck.d.ts +7 -0
- package/dist/types/nodefony/src/server/welcomeIdentity.d.ts +19 -0
- package/dist/types/nodefony/src/service/RealtimeService.d.ts +10 -2
- package/dist/types/nodefony/src/service/publicOrigin.d.ts +19 -0
- package/dist/types/nodefony/src/transport/WsConnectionTransport.d.ts +16 -30
- package/dist/types/nodefony/testing/backplaneConformance.d.ts +66 -0
- package/dist/types/nodefony/testing/index.d.ts +12 -1
- package/docs/actions.md +419 -18
- package/docs/architecture.md +83 -8
- package/docs/cadence-et-contre-pression.md +87 -11
- package/docs/canaux-a-membres.md +267 -0
- package/docs/cas-d-usage.md +1702 -0
- package/docs/configuration.md +137 -17
- package/docs/cookbook-chat.md +314 -85
- package/docs/index.md +11 -1
- package/docs/observabilite.md +71 -14
- package/docs/protocole.md +133 -24
- package/docs/securite.md +284 -32
- package/docs/vocabulaire.md +13 -12
- package/package.json +14 -11
package/docs/configuration.md
CHANGED
|
@@ -281,13 +281,14 @@ regarder quand un message ne traverse pas.
|
|
|
281
281
|
|
|
282
282
|
## ⚙️ Le schéma, clé par clé
|
|
283
283
|
|
|
284
|
-
|
|
284
|
+
Vingt et une clés au total. Le tableau donne la vérité complète ; les sections qui suivent
|
|
285
285
|
expliquent quand et pourquoi en changer.
|
|
286
286
|
|
|
287
287
|
<!-- prettier-ignore -->
|
|
288
288
|
| Clé | Type | Défaut | Effet |
|
|
289
289
|
| --- | --- | --- | --- |
|
|
290
290
|
| `enabled` | `boolean` | `true` | `false` = module chargé mais inerte : ni API d'administration, ni backplane, ni sonde, ni garde |
|
|
291
|
+
| `codec` | `"json"` \| `"cbor"` | `"json"` | forme des frames sur le fil. `"cbor"` = accordé à tout client qui l'offre, les autres restent en texte avec la convention d'octets `$bytes` |
|
|
291
292
|
| `backplane.driver` | `string` | `"loopback"` | nom résolu dans le registre de drivers ; inconnu → avertissement, le hub reste local |
|
|
292
293
|
| `backplane.namespace` | `string?` | _absent_ → nom d'app | cloison du transport partagé ; suffixe le canal pub/sub. Motif `^[\w.-]+$` |
|
|
293
294
|
| `backplane.secret` | `string?` (≥ 32) | _absent_ | scelle les messages du transport partagé (HMAC) ; identique sur tous les pods. Absent = bus ouvert + avertissement au boot |
|
|
@@ -295,9 +296,14 @@ expliquent quand et pourquoi en changer.
|
|
|
295
296
|
| `cluster.probe.enabled` | `boolean` | `true` | branche la sonde agrégée du pod en worker de cluster. `false` = aucun minuteur, aucun IPC |
|
|
296
297
|
| `slowConsumer.bytes` | `number` entier > 0 | `1048576` (1 MiB) | seuil de **comptage** des consommateurs lents dans la sonde. Observe, n'agit pas |
|
|
297
298
|
| `limits.maxChannelsPerConnection` | `number > 0` \| `null` | `256` | plafond de canaux par connexion ; au-delà le `subscribe` est refusé. `null` = illimité |
|
|
299
|
+
| `authenticateTimeoutMs` | `number` entier > 0 | `10000` | délai de l'authentification à la poignée de main ; dépassé, fermeture `1013` et le client revient |
|
|
300
|
+
| `allowTimeoutMs` | `number` entier > 0 | `5000` | délai d'une règle d'admission `allow`, attente en file comprise ; dépassé, motif `unavailable` |
|
|
301
|
+
| `maxPendingAllowsPerConnection` | `number` entier > 0 | `8` | appels d'`allow` en vol pour une connexion ; au-delà la demande attend son tour |
|
|
302
|
+
| `maxPendingAllowsTotal` | `number` entier > 0 | `512` | appels d'`allow` en vol pour l'exemplaire ; au-delà `unavailable` sans appel |
|
|
298
303
|
| `csrf.checkOrigin.enabled` | `boolean` | `false` | active le contrôle d'`Origin` à l'ouverture de la socket |
|
|
299
304
|
| `csrf.checkOrigin.allowList` | `string[]` | `[]` | origines acceptées, **comparaison exacte**. Vide + activé = tout est refusé |
|
|
300
305
|
| `csrf.checkOrigin.allowMissingOrigin` | `boolean` | `false` | accepter une ouverture sans en-tête `Origin` (clients non-navigateur) |
|
|
306
|
+
| `publicOrigin` | `string` (origine nue) | aucun | où la socket est servie quand ce n'est pas l'origine de la page ; déclarée seule dans `connect-src` |
|
|
301
307
|
| `clientLogs.enabled` | `boolean` | `false` | accepte les journaux du navigateur sur le canal montant `nodefony:syslog:uplink`. Surface d'écriture, fermée par défaut ; une connexion anonyme ne pousse rien |
|
|
302
308
|
| `clientLogs.maxEntriesPerBatch` | `number` | `50` | entrées acceptées par lot |
|
|
303
309
|
| `clientLogs.maxEntriesPerWindow` | `number` | `300` | entrées acceptées par fenêtre de débit |
|
|
@@ -325,6 +331,47 @@ inerte (`RealtimeService.init()`, `RealtimeService.ts#RealtimeService.init`).
|
|
|
325
331
|
Le bon usage : un environnement où le temps réel n'a pas de sens (une commande CLI, un banc). Pour
|
|
326
332
|
désactiver vraiment, retire le module du manifeste.
|
|
327
333
|
|
|
334
|
+
### `codec` — la forme des frames sur le fil
|
|
335
|
+
|
|
336
|
+
Le codec ne change pas ce qu'une frame **dit**, seulement la forme qu'elle prend sur le fil : c'est
|
|
337
|
+
toujours du JSON-RPC 2.0, écrit en texte ou en CBOR (ADR-0022 §2). Le défaut `"json"` est le bon
|
|
338
|
+
réglage tant que les charges sont des objets ordinaires. Passe à `"cbor"` dès que des **octets**
|
|
339
|
+
circulent dans un canal (document partagé, série de mesures, audio) :
|
|
340
|
+
|
|
341
|
+
```ts ignore
|
|
342
|
+
// nodefony.config.ts
|
|
343
|
+
use("@nodefony/realtime", { codec: "cbor" });
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Ce que le serveur fait alors, connexion par connexion (`frameCodecNegotiation.ts#negotiateFrameCodec`) :
|
|
347
|
+
|
|
348
|
+
| Le client… | Fil retenu | Les octets d'une charge |
|
|
349
|
+
| ------------------------------------------------------------ | ----------------------------------------------- | --------------------------------------------------------------------- |
|
|
350
|
+
| offre `nodefony.jsonrpc.cbor.v1` (`codec: CBOR_FRAME_CODEC`) | CBOR, annoncé dans `realtime:welcome` (`codec`) | chaîne d'octets native, sans base64 |
|
|
351
|
+
| n'offre rien (client ancien, autre langage, `curl`) | JSON-RPC texte | `{"$bytes":"<base64url>"}`, annoncé par le champ `bytes` de l'accueil |
|
|
352
|
+
|
|
353
|
+
Aucun client ne casse : celui qui ne connaît pas CBOR reste en texte et reçoit les mêmes données.
|
|
354
|
+
Un canal suivi par les deux sortes de connexions paie **un encodage par codec**, jamais un par
|
|
355
|
+
abonné (`RealtimeHub.ts#ChannelFrame`).
|
|
356
|
+
|
|
357
|
+
Quatre conséquences à connaître avant d'activer :
|
|
358
|
+
|
|
359
|
+
- **Le flux binaire brut ne sert plus les connexions CBOR.** Sur une telle connexion, toute trame
|
|
360
|
+
binaire est une frame du codec : `onRealtimeBinary()` n'y est jamais appelée et
|
|
361
|
+
`sendBinaryToClient()` n'y écrit rien. Il ne sert plus que les connexions restées en texte.
|
|
362
|
+
- **Un objet applicatif de la forme exacte `{"$bytes": "<chaîne>"}` est refusé** à l'envoi vers une
|
|
363
|
+
connexion texte : le client le relirait en octets. La règle est détaillée dans
|
|
364
|
+
[Protocole](./protocole.md#des-octets-dans-la-charge--une-frame-comme-les-autres).
|
|
365
|
+
- **Le REST du plan d'administration prend la même convention.** Une réponse `GET
|
|
366
|
+
/nodefony/<module>/api/*` passe par le codec des connexions texte et porte l'en-tête
|
|
367
|
+
`x-nodefony-bytes: $bytes` (`AdminApiController.ts#AdminApiController.dispatch`). Un client la
|
|
368
|
+
relit par `textCodecForMarker(response.headers.get(BYTES_MARKER_HEADER))`, exporté par
|
|
369
|
+
`nodefony` : des octets y reviennent à l'identique, que la valeur arrive par `fetch` ou par la
|
|
370
|
+
socket. Sans l'en-tête, le corps est du JSON natif.
|
|
371
|
+
- **`NF_REALTIME_CODEC=json` remet le fil en texte** pour le lire dans l'inspecteur réseau du
|
|
372
|
+
navigateur. C'est une exception de débogage : en texte, la taille des trames et la contre-pression
|
|
373
|
+
changent, donc une mesure prise ainsi ne vaut pas pour la production.
|
|
374
|
+
|
|
328
375
|
### `backplane.driver` — qui relaie les publications
|
|
329
376
|
|
|
330
377
|
C'est **le** réglage du module. Le champ est une chaîne libre et non une énumération, volontairement :
|
|
@@ -382,8 +429,15 @@ déploiement, passe-le par `NF_REALTIME_BACKPLANE_SECRET` (secret k8s, variable
|
|
|
382
429
|
a la précédence sur la configuration, et un secret n'a rien à faire dans un fichier versionné.
|
|
383
430
|
|
|
384
431
|
Sans secret, rien ne casse : le transport reste ouvert et le démarrage l'annonce en avertissement.
|
|
385
|
-
La bonne façon de vérifier que le scellement fonctionne est le compteur `
|
|
386
|
-
sonde de santé
|
|
432
|
+
La bonne façon de vérifier que le scellement fonctionne est le compteur `sealRejectedTotal` de la
|
|
433
|
+
sonde de santé : il compte les messages reçus dont le sceau ne se vérifie pas, et reste à zéro en
|
|
434
|
+
régime normal. Chaque refus est aussi journalisé — le premier, puis un sur cent.
|
|
435
|
+
|
|
436
|
+
> [!WARNING]
|
|
437
|
+
> **Changer le secret, c'est couper le fan-out entre les deux générations.** Pendant une rotation, les
|
|
438
|
+
> exemplaires qui n'ont pas encore le même secret se refusent leurs messages : `sealRejectedTotal`
|
|
439
|
+
> monte sur chacun d'eux jusqu'à ce que tous portent le nouveau. Un second secret accepté en
|
|
440
|
+
> lecture, pour une rotation sans coupure, viendra dans une version 10.x.
|
|
387
441
|
|
|
388
442
|
> [!NOTE]
|
|
389
443
|
> Le driver `cluster` (IPC entre les workers d'un même pod) n'a pas besoin de secret : ce canal ne
|
|
@@ -484,6 +538,43 @@ les clients — un tableau de bord interne qui compose des dizaines de flux, par
|
|
|
484
538
|
> Le plafond est **par connexion**, pas global. Cent connexions à 256 canaux restent possibles :
|
|
485
539
|
> c'est une garde contre un client abusif, pas contre une charge légitime mal dimensionnée.
|
|
486
540
|
|
|
541
|
+
### Les délais et plafonds de l'admission — quand la base ne répond plus
|
|
542
|
+
|
|
543
|
+
Deux moments demandent une réponse à l'extérieur du processus : l'**authentification** à la poignée
|
|
544
|
+
de main (relire une session, vérifier une clé distante) et la règle d'**admission** `allow` d'un canal
|
|
545
|
+
(lire une appartenance en base). Quand la base ralentit ou tombe, ces quatre clés décident de ce que
|
|
546
|
+
le serveur retient, et de ce qu'il rend au client. Elles sont appliquées par
|
|
547
|
+
`AdmissionBudget` (`admissionBudget.ts#AdmissionBudget`), posées par `RealtimeService.init()`
|
|
548
|
+
(`RealtimeService.ts#RealtimeService.init`).
|
|
549
|
+
|
|
550
|
+
<!-- prettier-ignore -->
|
|
551
|
+
| Clé | Défaut | Ce qui se passe au-delà | Pourquoi ce défaut |
|
|
552
|
+
| --- | --- | --- | --- |
|
|
553
|
+
| `authenticateTimeoutMs` | `10000` | la socket est fermée en `1013` « try again later » ; le client recule puis revient ; une identité arrivée après coup est ignorée | l'authentification relit souvent une session en base ou une clé distante ; 10 s couvrent une base chargée, et au-delà retenir la connexion ne l'authentifierait pas plus vite — elle occuperait seulement une place |
|
|
554
|
+
| `allowTimeoutMs` | `5000` | l'abonné reçoit `realtime:denied` motif `unavailable` et réessaie ; un verdict tardif est ignoré, jamais une admission | une lecture saine répond en millisecondes ; 5 s couvrent une base chargée sans laisser un écran vide au-delà de ce qu'un humain attend |
|
|
555
|
+
| `maxPendingAllowsPerConnection` | `8` | la demande d'abonnement **attend son tour** — jamais refusée | assez pour ouvrir plusieurs canaux en parallèle, assez peu pour qu'une connexion ne monopolise pas le pool de la base (souvent 10) ; refuser casserait le rejeu au `welcome`, qui renvoie tous les canaux d'un coup |
|
|
556
|
+
| `maxPendingAllowsTotal` | `512` | `unavailable` sans appeler la règle | à ce niveau la base est déjà l'étranglement : empiler davantage d'appels n'admettrait personne plus vite, et retarderait la reprise |
|
|
557
|
+
|
|
558
|
+
Deux comportements ne se règlent pas, et c'est voulu : le **coupe-circuit** (50 délais dépassés en
|
|
559
|
+
5 s suspendent les appels pendant 5 s, avec un `retryAfterMs` rendu au client) et le **réessai
|
|
560
|
+
client** (trois au plus, recul exponentiel à gigue complète). Exposés en réglage, ils
|
|
561
|
+
deviendraient le premier levier tourné pendant un incident — exactement le moment où ils
|
|
562
|
+
protègent la base.
|
|
563
|
+
|
|
564
|
+
```typescript
|
|
565
|
+
// nodefony.config.ts — une base lente, assumée : on attend plus, on retient moins
|
|
566
|
+
use("@nodefony/realtime", {
|
|
567
|
+
authenticateTimeoutMs: 15_000,
|
|
568
|
+
allowTimeoutMs: 8000,
|
|
569
|
+
maxPendingAllowsTotal: 256,
|
|
570
|
+
});
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
Les quatre clés refusent `0`, un nombre négatif ou non entier, et tout délai au-delà de
|
|
574
|
+
`2 147 483 647` ms : `setTimeout` ramènerait ce délai à 1 ms, et chaque appel échouerait aussitôt.
|
|
575
|
+
Le comportement vu par l'abonné : [`securite.md`](./securite.md) ; les compteurs qui disent quand
|
|
576
|
+
ces bornes travaillent : [`observabilite.md`](./observabilite.md).
|
|
577
|
+
|
|
487
578
|
### `csrf.checkOrigin` — qui a le droit d'ouvrir la socket
|
|
488
579
|
|
|
489
580
|
Trois clés qui forment une seule politique, appliquée à l'ouverture de la connexion
|
|
@@ -515,6 +606,31 @@ non-navigateur pouvant toujours forger l'en-tête — appartient à [Sécurité]
|
|
|
515
606
|
> vérifier. Et `allowMissingOrigin: true` sans authentificateur fort rouvre exactement la brèche que
|
|
516
607
|
> le contrôle ferme.
|
|
517
608
|
|
|
609
|
+
### `publicOrigin` — une socket servie sur une autre origine
|
|
610
|
+
|
|
611
|
+
La politique de contenu que pose `@nodefony/security` autorise les connexions vers l'origine de la
|
|
612
|
+
page seulement (`connect-src 'self'`). Une socket servie au même endroit que la page — le cas
|
|
613
|
+
courant, une adresse relative — passe sans rien régler.
|
|
614
|
+
|
|
615
|
+
Une socket servie **ailleurs** (un autre domaine, un autre port) serait bloquée par le navigateur.
|
|
616
|
+
Tu ne l'écris pas dans la politique : tu dis au module où elle est servie, et il s'y déclare
|
|
617
|
+
lui-même au démarrage (`RealtimeService.ts#RealtimeService.init`).
|
|
618
|
+
|
|
619
|
+
```ts
|
|
620
|
+
use("@nodefony/realtime", {
|
|
621
|
+
publicOrigin: "wss://rt.example.com",
|
|
622
|
+
});
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
La valeur est une **origine nue** : schéma, hôte, port facultatif — ni chemin, ni joker. Une valeur
|
|
626
|
+
qui n'en est pas une arrête le démarrage en nommant la clé. `https://rt.example.com` est accepté et
|
|
627
|
+
déclaré `wss://rt.example.com` : c'est cette forme qui couvre la socket ET le même hôte en
|
|
628
|
+
`https:` (`publicOrigin.ts#realtimeConnectSource`).
|
|
629
|
+
|
|
630
|
+
Deux clés, deux questions : `publicOrigin` dit où la socket **est** ; `csrf.checkOrigin.allowList`
|
|
631
|
+
dit quelles pages ont le droit de l'**ouvrir**. Une socket sur un autre domaine a souvent besoin
|
|
632
|
+
des deux.
|
|
633
|
+
|
|
518
634
|
## 🔌 Choisir le driver de backplane — trois situations
|
|
519
635
|
|
|
520
636
|
Le choix ne se déduit pas d'un tableau de performances : il se déduit de **ta topologie de
|
|
@@ -684,7 +800,7 @@ Le détail du contrat, de l'anti-écho et du cycle de vie d'un driver est dans
|
|
|
684
800
|
|
|
685
801
|
## ⚙️ Variables d'environnement et précédence
|
|
686
802
|
|
|
687
|
-
|
|
803
|
+
Neuf variables influent sur le module. Six lui sont propres, les autres viennent du cœur ou du
|
|
688
804
|
mode cluster. Les trois premières décrivent un **déploiement** — le fond de panier auquel ce
|
|
689
805
|
processus se raccorde — et c'est pour cela qu'elles ne vivent pas dans un fichier versionné.
|
|
690
806
|
|
|
@@ -693,6 +809,8 @@ processus se raccorde — et c'est pour cela qu'elles ne vivent pas dans un fich
|
|
|
693
809
|
| `NF_REALTIME_DRIVER` | ce module | remplace `backplane.driver` — nom libre, un pilote inconnu est signalé |
|
|
694
810
|
| `NF_REALTIME_BACKPLANE_NAMESPACE` | ce module | remplace `backplane.namespace` — sépare deux déploiements de la même app |
|
|
695
811
|
| `NF_REALTIME_BACKPLANE_SECRET` | ce module | remplace `backplane.secret` — scelle le transport partagé (≥ 32 car.) |
|
|
812
|
+
| `NF_REALTIME_CODEC` | ce module | remplace `codec` — `json` remet le fil en texte, pour déboguer seulement |
|
|
813
|
+
| `NF_REALTIME_PUBLIC_ORIGIN` | ce module | remplace `publicOrigin` — où la socket est servie, propre au déploiement |
|
|
696
814
|
| `NF__REALTIME__<CHEMIN>` | mécanisme du cœur | remplace n'importe quelle clé, par son chemin. Ex. `NF__REALTIME__ENABLED` |
|
|
697
815
|
| `NF_CLUSTER` | posée par le lancement | à `1`, le driver `cluster` s'active en worker. Ne la pose pas à la main |
|
|
698
816
|
| `NF_CLUSTER_PROBE` | ce module | à `0`, coupe la sonde de pod même si `cluster.probe.enabled` est vrai |
|
|
@@ -702,14 +820,14 @@ L'ordre de recouvrement, du plus faible au plus fort :
|
|
|
702
820
|
|
|
703
821
|
1. **Les valeurs d'usine du schéma** — ce que tu obtiens sans rien écrire.
|
|
704
822
|
2. **La configuration de l'application** — ton `use("@nodefony/realtime", { … })`.
|
|
705
|
-
3. **`NF_REALTIME_DRIVER`**, **`NF_REALTIME_BACKPLANE_NAMESPACE
|
|
706
|
-
**`NF_REALTIME_BACKPLANE_SECRET`** — posées par le Kernel après la fusion et **avant** la
|
|
823
|
+
3. **`NF_REALTIME_DRIVER`**, **`NF_REALTIME_BACKPLANE_NAMESPACE`**,
|
|
824
|
+
**`NF_REALTIME_BACKPLANE_SECRET`**, **`NF_REALTIME_CODEC`** et **`NF_REALTIME_PUBLIC_ORIGIN`** — posées par le Kernel après la fusion et **avant** la
|
|
707
825
|
validation Zod (`applyEnvShortcuts`, `Kernel.ts#Kernel.applyEnvConfigOverrides`) : une valeur fausse **arrête le démarrage** en nommant la
|
|
708
826
|
variable (un secret de moins de 32 caractères, un espace de noms qui contient `:`). Chacune
|
|
709
827
|
accepte aussi sa forme `<NOM>_FILE` (secret monté par Docker ou Kubernetes).
|
|
710
828
|
4. **`NF__REALTIME__<CHEMIN>`** — même moment, même validation. Un chemin introuvable produit un
|
|
711
829
|
avertissement avec une suggestion, jamais une clé fantôme. Viser le **même** champ qu'une des
|
|
712
|
-
|
|
830
|
+
quatre variables ci-dessus est une ambiguïté : refusée en production, et en développement
|
|
713
831
|
`NF__…` l'emporte avec un avertissement qui nomme les deux.
|
|
714
832
|
|
|
715
833
|
Le mécanisme générique mérite d'être connu : le double tiret bas sépare les niveaux, les segments
|
|
@@ -782,16 +900,18 @@ Trois endroits, par ordre de fiabilité décroissante.
|
|
|
782
900
|
|
|
783
901
|
## ⚠️ Pièges
|
|
784
902
|
|
|
785
|
-
| Symptôme | Cause | Correction
|
|
786
|
-
| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
787
|
-
| Un message n'arrive qu'à la moitié des clients, sans aucune erreur | plusieurs répliques avec `driver: "loopback"` | passer à `redis` et déclarer `@nodefony/redis` dans le manifeste
|
|
788
|
-
| Driver `redis` configuré, journal `RealtimeHub reste local` | module `@nodefony/redis` absent du manifeste, ou connexions `publish`/`subscribe` indisponibles | déclarer le module ; vérifier que Redis répond
|
|
789
|
-
| Le fan-out traverse, mais des messages d'un autre environnement arrivent | `backplane.namespace` non posé : deux déploiements de la même application dérivent le même canal | poser un namespace explicite et distinct par environnement
|
|
790
|
-
| Le démarrage échoue avec `clé inconnue : …` | clé absente du schéma strict (faute de frappe, ou clé retirée) | corriger ou retirer la clé que nomme le message
|
|
791
|
-
| Baisser `slowConsumer.bytes` ne ferme pas les clients lents | cette clé ne pilote que le **comptage** de la sonde | régler `websocketSecure.maxBackpressure` / `.backpressureCloseAfterDrops` (@nodefony/http)
|
|
792
|
-
| Toutes les connexions tombent en `4003` après activation du contrôle d'origine | `allowList` vide, ou origine non identique au caractère près (port, schéma, sous-domaine) | inscrire l'origine exacte ; un client non-navigateur relève de `allowMissingOrigin`
|
|
793
|
-
| `enabled: false` posé « pour désactiver », et la garde d'origine ne s'applique plus | le service devient inerte et ne pose plus les politiques sur le hub | retirer le module du manifeste plutôt que l'éteindre
|
|
794
|
-
| Le plafond de canaux semble ignoré en test | il ne l'est pas : le hub porte le même défaut de 256 sans configuration (`RealtimeHub.ts#RealtimeHub`) | vérifier le motif de refus `realtime:denied` côté client
|
|
903
|
+
| Symptôme | Cause | Correction |
|
|
904
|
+
| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
905
|
+
| Un message n'arrive qu'à la moitié des clients, sans aucune erreur | plusieurs répliques avec `driver: "loopback"` | passer à `redis` et déclarer `@nodefony/redis` dans le manifeste |
|
|
906
|
+
| Driver `redis` configuré, journal `RealtimeHub reste local` | module `@nodefony/redis` absent du manifeste, ou connexions `publish`/`subscribe` indisponibles | déclarer le module ; vérifier que Redis répond |
|
|
907
|
+
| Le fan-out traverse, mais des messages d'un autre environnement arrivent | `backplane.namespace` non posé : deux déploiements de la même application dérivent le même canal | poser un namespace explicite et distinct par environnement |
|
|
908
|
+
| Le démarrage échoue avec `clé inconnue : …` | clé absente du schéma strict (faute de frappe, ou clé retirée) | corriger ou retirer la clé que nomme le message |
|
|
909
|
+
| Baisser `slowConsumer.bytes` ne ferme pas les clients lents | cette clé ne pilote que le **comptage** de la sonde | régler `websocketSecure.maxBackpressure` / `.backpressureCloseAfterDrops` (@nodefony/http) |
|
|
910
|
+
| Toutes les connexions tombent en `4003` après activation du contrôle d'origine | `allowList` vide, ou origine non identique au caractère près (port, schéma, sous-domaine) | inscrire l'origine exacte ; un client non-navigateur relève de `allowMissingOrigin` |
|
|
911
|
+
| `enabled: false` posé « pour désactiver », et la garde d'origine ne s'applique plus | le service devient inerte et ne pose plus les politiques sur le hub | retirer le module du manifeste plutôt que l'éteindre |
|
|
912
|
+
| Le plafond de canaux semble ignoré en test | il ne l'est pas : le hub porte le même défaut de 256 sans configuration (`RealtimeHub.ts#RealtimeHub`) | vérifier le motif de refus `realtime:denied` côté client |
|
|
913
|
+
| `codec: "cbor"` posé, et `onRealtimeBinary()` ne reçoit plus rien | sur une connexion CBOR, toute trame binaire est une frame du codec (ADR-0022 §2) | publier les octets dans un canal ; le flux brut ne sert que les connexions texte |
|
|
914
|
+
| `codec: "cbor"` posé, et le navigateur reste en texte | le client n'a pas offert le codec : `codec: CBOR_FRAME_CODEC` absent, ou passé après la 1ʳᵉ création | l'offrir à la PREMIÈRE création de la socket ([client](../../../../nodefony/docs/client.md#choisir-le-codec-de-trame--texte-ou-cbor)) |
|
|
795
915
|
|
|
796
916
|
## 🧪 Tests & couverture
|
|
797
917
|
|