@nodefony/realtime 10.0.0-beta.2 → 10.0.0-beta.4

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 (63) hide show
  1. package/README.md +2 -2
  2. package/dist/_virtual/{_@oxc-project_runtime@0.152.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
  3. package/dist/index.js +18 -13
  4. package/dist/nodefony/config/config.js +7 -5
  5. package/dist/nodefony/config/configSchema.js +18 -0
  6. package/dist/nodefony/config/defineModuleConfig.js +43 -17
  7. package/dist/nodefony/decorators/realtimeDecorators.js +69 -19
  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 +22 -6
  12. package/dist/nodefony/src/backplane/backplaneRegistry.js +1 -2
  13. package/dist/nodefony/src/backplane/bootNotices.js +42 -0
  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 +1 -5
  17. package/dist/nodefony/src/cluster/ClusterProbeClient.js +16 -6
  18. package/dist/nodefony/src/guards.js +40 -0
  19. package/dist/nodefony/src/server/AnonymousRealtimeToken.js +2 -6
  20. package/dist/nodefony/src/server/RealtimeAdminApi.js +1 -2
  21. package/dist/nodefony/src/server/RealtimeController.js +226 -82
  22. package/dist/nodefony/src/server/RealtimeHub.js +32 -54
  23. package/dist/nodefony/src/server/ServerRealtimeSocket.js +30 -13
  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 +5 -4
  27. package/dist/nodefony/src/server/welcomeEnv.js +1 -2
  28. package/dist/nodefony/src/service/RealtimeService.js +4 -4
  29. package/dist/nodefony/src/transport/WsConnectionTransport.js +42 -2
  30. package/dist/nodefony/testing/index.js +118 -5
  31. package/dist/types/index.d.ts +1 -0
  32. package/dist/types/nodefony/config/configSchema.d.ts +12 -0
  33. package/dist/types/nodefony/config/defineModuleConfig.d.ts +29 -6
  34. package/dist/types/nodefony/decorators/realtimeDecorators.d.ts +19 -2
  35. package/dist/types/nodefony/interfaces/IRealtimeConnectionState.d.ts +40 -0
  36. package/dist/types/nodefony/interfaces/IRealtimeController.d.ts +1 -1
  37. package/dist/types/nodefony/interfaces/IRealtimeHandshake.d.ts +1 -1
  38. package/dist/types/nodefony/interfaces/IRealtimeToken.d.ts +13 -0
  39. package/dist/types/nodefony/interfaces/index.d.ts +1 -0
  40. package/dist/types/nodefony/src/backplane/RedisBackplane.d.ts +7 -0
  41. package/dist/types/nodefony/src/backplane/bootNotices.d.ts +25 -0
  42. package/dist/types/nodefony/src/guards.d.ts +32 -0
  43. package/dist/types/nodefony/src/server/AnonymousRealtimeToken.d.ts +13 -0
  44. package/dist/types/nodefony/src/server/RealtimeController.d.ts +82 -4
  45. package/dist/types/nodefony/src/server/RealtimeHub.d.ts +22 -16
  46. package/dist/types/nodefony/src/server/ServerRealtimeSocket.d.ts +18 -8
  47. package/dist/types/nodefony/src/server/subscribedAck.d.ts +23 -0
  48. package/dist/types/nodefony/src/service/RealtimeService.d.ts +1 -1
  49. package/dist/types/nodefony/src/transport/WsConnectionTransport.d.ts +26 -1
  50. package/dist/types/nodefony/testing/index.d.ts +8 -3
  51. package/docs/actions.md +129 -45
  52. package/docs/architecture.md +143 -135
  53. package/docs/cadence-et-contre-pression.md +643 -0
  54. package/docs/configuration.md +126 -97
  55. package/docs/cookbook-chat.md +50 -43
  56. package/docs/index.md +63 -38
  57. package/docs/observabilite.md +58 -56
  58. package/docs/protocole.md +110 -76
  59. package/docs/securite.md +128 -117
  60. package/docs/vocabulaire.md +133 -129
  61. package/package.json +17 -13
  62. package/dist/_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateMetadata.js +0 -6
  63. package/docs/couverture-bancs.md +0 -139
@@ -57,8 +57,8 @@ est validé puis **gelé** avant que le moindre canal existe.
57
57
  flowchart TD
58
58
  SCHEMA["Schéma Zod du module<br/>realtimeConfigSchema — les valeurs d'usine"]
59
59
  APP["Ton application<br/>use('@nodefony/realtime', { … }) dans nodefony.config.ts"]
60
- ENVGEN["Déploiement<br/>NF__REALTIME__&lt;CHEMIN&gt;=valeur"]
61
- BUILD["Builder<br/>defineRealtimeConfig() : parse Zod → NF_REALTIME_DRIVER → freeze"]
60
+ ENVGEN["Déploiement<br/>NF_REALTIME_DRIVER · NF__REALTIME__&lt;CHEMIN&gt;=valeur"]
61
+ BUILD["Builder<br/>defineRealtimeConfig() : parse Zod → freeze"]
62
62
  HUB["RealtimeHub<br/>garde d'origine · plafond de canaux · seuil de sonde"]
63
63
  BP["Driver de backplane<br/>résolu par nom dans le registre"]
64
64
  SCHEMA --> APP --> ENVGEN --> BUILD
@@ -125,18 +125,19 @@ fichiers, jamais trois**.
125
125
  | `nodefony/config/config.ts` | **le quoi** | le schéma Zod commenté, source **unique** des valeurs d'usine, et les défauts matérialisés |
126
126
  | `nodefony/config/defineModuleConfig.ts` | **le comment** | le builder pur : analyse, surcharge d'environnement, gel — il ne retape jamais une valeur d'usine |
127
127
 
128
- Le schéma `realtimeConfigSchema` (`config.ts:259`) porte chaque valeur en `.default()` et chaque
128
+ Le schéma `realtimeConfigSchema` (`config.ts#realtimeConfigSchema`) porte chaque valeur en `.default()` et chaque
129
129
  explication en `.describe()`. Les défauts effectifs sont **dérivés** du schéma lui-même
130
- (`realtimeConfigSchema.parse({})`, `config.ts:231`) : il n'existe pas de second endroit où une valeur
130
+ (`realtimeConfigSchema.parse({})`, `config.ts#clientLogsSchema`) : il n'existe pas de second endroit où une valeur
131
131
  d'usine serait écrite, donc pas de dérive silencieuse possible entre la doc du champ et son
132
132
  comportement.
133
133
 
134
- Le builder `defineRealtimeConfig()` (`defineModuleConfig.ts:35`) reste **pur** : il analyse l'entrée,
135
- applique les variables d'environnement dédiées (`NF_REALTIME_DRIVER`,
136
- `NF_REALTIME_BACKPLANE_NAMESPACE`, `NF_REALTIME_BACKPLANE_SECRET` — toutes prioritaires sur le
137
- fichier), et gèle le résultat. Il ne décide rien.
134
+ Le builder `defineRealtimeConfig()` (`defineModuleConfig.ts#defineRealtimeConfig`) reste **pur** : il analyse l'entrée
135
+ et gèle le résultat. Il ne lit aucune variable d'environnement. Les trois variables du module
136
+ (`NF_REALTIME_DRIVER`, `NF_REALTIME_BACKPLANE_NAMESPACE`, `NF_REALTIME_BACKPLANE_SECRET`) sont
137
+ déclarées sur leur champ dans le schéma (`.meta({ env })`, `config.ts#backplaneSchema`) et posées
138
+ par le Kernel **avant** l'analyse : le schéma les valide comme une valeur écrite dans le fichier.
138
139
 
139
- Le module **augmente le registre de types** (`index.ts:167`) : `use("@nodefony/realtime", { … })`
140
+ Le module **augmente le registre de types** (`index.ts#tick`) : `use("@nodefony/realtime", { … })`
140
141
  propose ses clés en autocomplétion et **refuse** une clé inconnue à la compilation.
141
142
 
142
143
  > [!IMPORTANT]
@@ -156,31 +157,42 @@ On garde le fond de panier local, on ferme la porte d'entrée, on abaisse le pla
156
157
  dont l'application a réellement besoin.
157
158
 
158
159
  ```ts
160
+ // nodefony/config/realtime.ts — le fragment du module, importé par le manifeste
161
+ import type { IRealtimeConfigInput } from "@nodefony/realtime";
162
+
163
+ export const realtimeConfig = () =>
164
+ ({
165
+ // Un seul processus : le hub diffuse localement, aucun transport n'est ouvert.
166
+ backplane: { driver: "loopback" },
167
+
168
+ // Défense CSWSH (RFC 6455 §10.2). Sans elle, une page tierce ouvre une socket
169
+ // avec les cookies de ton utilisateur : le navigateur n'applique pas CORS ici.
170
+ csrf: {
171
+ checkOrigin: {
172
+ enabled: true,
173
+ allowList: ["https://127.0.0.1:5152"],
174
+ // Un client non-navigateur n'envoie pas d'en-tête Origin. Le laisser à
175
+ // false n'a de sens que si tout ton trafic vient d'un navigateur.
176
+ allowMissingOrigin: false,
177
+ },
178
+ },
179
+
180
+ // Garde anti-saturation : le défaut de 256 est généreux. Un écran qui suit
181
+ // dix canaux n'a aucune raison d'en ouvrir plus de quelques dizaines.
182
+ limits: { maxChannelsPerConnection: 32 },
183
+ }) satisfies IRealtimeConfigInput;
184
+ ```
185
+
186
+ ```ts ignore
159
187
  // nodefony.config.ts — le manifeste de l'application
188
+ import { defineConfig, use } from "nodefony";
189
+ import { realtimeConfig } from "./nodefony/config/realtime";
190
+
160
191
  export default defineConfig(() => ({
161
192
  modules: [
162
193
  "@nodefony/http",
163
194
  "@nodefony/framework",
164
- use("@nodefony/realtime", {
165
- // Un seul processus : le hub diffuse localement, aucun transport n'est ouvert.
166
- backplane: { driver: "loopback" },
167
-
168
- // Défense CSWSH (RFC 6455 §10.2). Sans elle, une page tierce ouvre une socket
169
- // avec les cookies de ton utilisateur : le navigateur n'applique pas CORS ici.
170
- csrf: {
171
- checkOrigin: {
172
- enabled: true,
173
- allowList: ["https://127.0.0.1:5152"],
174
- // Un client non-navigateur n'envoie pas d'en-tête Origin. Le laisser à
175
- // false n'a de sens que si tout ton trafic vient d'un navigateur.
176
- allowMissingOrigin: false,
177
- },
178
- },
179
-
180
- // Garde anti-saturation : le défaut de 256 est généreux. Un écran qui suit
181
- // dix canaux n'a aucune raison d'en ouvrir plus de quelques dizaines.
182
- limits: { maxChannelsPerConnection: 32 },
183
- }),
195
+ use("@nodefony/realtime", realtimeConfig()),
184
196
  ],
185
197
  }));
186
198
  ```
@@ -190,8 +202,39 @@ export default defineConfig(() => ({
190
202
  Passer à N pods sur N machines ne change **ni le contrôleur, ni le client, ni un seul canal** : on
191
203
  remplace le nom du driver, et on déclare le module qui fournit les connexions pub/sub.
192
204
 
193
- ```ts
205
+ ```ts ignore
206
+ // nodefony/config/realtime.ts — le fragment du module, importé par le manifeste
207
+ import type { ConfigContext } from "nodefony";
208
+ import type { IRealtimeConfigInput } from "@nodefony/realtime";
209
+ import type { env } from "../../env";
210
+
211
+ export const realtimeConfig = (ctx: ConfigContext<typeof env>) =>
212
+ ({
213
+ backplane: {
214
+ // En production seulement : en développement, le fond de panier local évite
215
+ // d'exiger une infrastructure pour lancer l'application.
216
+ driver: ctx.isProd ? "redis" : "loopback",
217
+
218
+ // La cloison. Le numéro de base Redis ne cloisonne PAS le pub/sub : sans ce
219
+ // nom, préproduction et production sur un même Redis échangeraient leurs
220
+ // diffusions. À poser explicitement, et différemment par déploiement.
221
+ namespace: ctx.isProd ? "boutique.prod" : "boutique.dev",
222
+ },
223
+ csrf: {
224
+ checkOrigin: {
225
+ enabled: true,
226
+ allowList: ["https://boutique.example.com"],
227
+ },
228
+ },
229
+ limits: { maxChannelsPerConnection: 32 },
230
+ }) satisfies IRealtimeConfigInput;
231
+ ```
232
+
233
+ ```ts ignore
194
234
  // nodefony.config.ts — la même application, déployée en plusieurs répliques
235
+ import { defineConfig, use } from "nodefony";
236
+ import { realtimeConfig } from "./nodefony/config/realtime";
237
+
195
238
  export default defineConfig((ctx) => ({
196
239
  modules: [
197
240
  "@nodefony/http",
@@ -201,25 +244,7 @@ export default defineConfig((ctx) => ({
201
244
  // module. Sans lui déclaré, le hub reste local — et le dit dans les journaux.
202
245
  "@nodefony/redis",
203
246
 
204
- use("@nodefony/realtime", {
205
- backplane: {
206
- // En production seulement : en développement, le fond de panier local évite
207
- // d'exiger une infrastructure pour lancer l'application.
208
- driver: ctx.isProd ? "redis" : "loopback",
209
-
210
- // La cloison. Le numéro de base Redis ne cloisonne PAS le pub/sub : sans ce
211
- // nom, préproduction et production sur un même Redis échangeraient leurs
212
- // diffusions. À poser explicitement, et différemment par déploiement.
213
- namespace: ctx.isProd ? "boutique.prod" : "boutique.dev",
214
- },
215
- csrf: {
216
- checkOrigin: {
217
- enabled: true,
218
- allowList: ["https://boutique.example.com"],
219
- },
220
- },
221
- limits: { maxChannelsPerConnection: 32 },
222
- }),
247
+ use("@nodefony/realtime", realtimeConfig(ctx)),
223
248
  ],
224
249
  }));
225
250
  ```
@@ -251,7 +276,7 @@ regarder quand un message ne traverse pas.
251
276
 
252
277
  > [!TIP]
253
278
  > Ces quatre lignes viennent du même endroit du code (`Realtime.#wireBackplane()`,
254
- > `src/packages/@nodefony/realtime/index.ts:279`) et de la carte d'identité que chaque driver publie.
279
+ > `src/packages/@nodefony/realtime/index.ts#Realtime.#wireBackplane`) et de la carte d'identité que chaque driver publie.
255
280
  > Aucun repli n'est silencieux : si le fan-out est dégradé, c'est écrit.
256
281
 
257
282
  ## ⚙️ Le schéma, clé par clé
@@ -288,8 +313,8 @@ contre-pression, eux, se règlent sur le serveur WebSocket de `@nodefony/http`
288
313
 
289
314
  Poser `false` charge le module sans rien activer. Concrètement : pas d'API d'administration
290
315
  enregistrée, pas de backplane câblé, pas de sonde de pod
291
- (`Realtime.onKernelBoot()`, `src/packages/@nodefony/realtime/index.ts:239`), et le service devient
292
- inerte (`RealtimeService.init()`, `RealtimeService.ts:77`).
316
+ (`Realtime.onKernelBoot()`, `src/packages/@nodefony/realtime/index.ts#Realtime.onKernelBoot`), et le service devient
317
+ inerte (`RealtimeService.init()`, `RealtimeService.ts#RealtimeService.init`).
293
318
 
294
319
  > [!WARNING]
295
320
  > **`enabled: false` désarme aussi tes gardes.** Le hub garde son plafond interne de 256 canaux,
@@ -304,7 +329,7 @@ désactiver vraiment, retire le module du manifeste.
304
329
 
305
330
  C'est **le** réglage du module. Le champ est une chaîne libre et non une énumération, volontairement :
306
331
  la liste réelle est celle du **registre** de drivers (`listBackplaneDrivers()`,
307
- `backplaneRegistry.ts:68`), ouverte aux drivers que tu écris toi-même. Une énumération figée dans le
332
+ `backplaneRegistry.ts#listBackplaneDrivers`), ouverte aux drivers que tu écris toi-même. Une énumération figée dans le
308
333
  schéma fermerait cette porte.
309
334
 
310
335
  Trois noms sont enregistrés par le module au chargement :
@@ -326,10 +351,10 @@ de frappe dans une variable de déploiement ne doit pas empêcher l'application
326
351
  ### `backplane.namespace` — la cloison sur un bus partagé
327
352
 
328
353
  Ne concerne que les drivers qui traversent les machines. Le canal de transport devient
329
- `nodefony:realtime:<namespace>` (`resolveRedisChannel()`, `RedisBackplane.ts:31`).
354
+ `nodefony:realtime:<namespace>` (`resolveRedisChannel()`, `RedisBackplane.ts#resolveRedisChannel`).
330
355
 
331
356
  Le défaut est **dérivé du nom de l'application**
332
- (`src/packages/@nodefony/realtime/index.ts:132`) — ce qui suffit à séparer deux applications
357
+ (`src/packages/@nodefony/realtime/index.ts`) — ce qui suffit à séparer deux applications
333
358
  distinctes sur un Redis mutualisé, et **ne suffit pas** à séparer deux déploiements de la **même**
334
359
  application. Préproduction et production portent le même nom d'application, donc le même canal
335
360
  dérivé, donc le même flux.
@@ -341,7 +366,7 @@ dérivé, donc le même flux.
341
366
  > utilisateurs. Pose-la dès que deux environnements partagent un Redis.
342
367
 
343
368
  Caractères acceptés : lettres, chiffres, `_`, `.`, `-` — le motif est validé par le schéma
344
- (`backplaneSchema`, `config.ts:45`), donc un nom fautif arrête le démarrage plutôt que de produire un
369
+ (`backplaneSchema`, `config.ts#backplaneSchema`), donc un nom fautif arrête le démarrage plutôt que de produire un
345
370
  canal exotique.
346
371
 
347
372
  ### `backplane.secret` — sceller ce qui circule sur le bus
@@ -349,7 +374,7 @@ canal exotique.
349
374
  Ne concerne, là aussi, que les transports **partagés**. Un bus pub/sub ne dit pas _qui_ a publié :
350
375
  sans secret, tout ce qui atterrit sur le canal — venu d'un pod ou d'un tiers qui sait écrire dans ce
351
376
  Redis — est traité comme un message légitime. Poser un secret fait porter à chaque message un sceau
352
- HMAC-SHA256, vérifié à l'arrivée (`openBackplaneEnvelope()`, `envelope.ts:112`) : un message non
377
+ HMAC-SHA256, vérifié à l'arrivée (`openBackplaneEnvelope()`, `envelope.ts#openBackplaneEnvelope`) : un message non
353
378
  scellé, altéré ou repointé vers un autre canal est **ignoré**, sans exception possible.
354
379
 
355
380
  Le secret doit être **identique sur tous les pods** — c'est lui qui les reconnaît entre eux. En
@@ -369,7 +394,7 @@ sonde de santé — il reste à zéro en régime normal.
369
394
  Sans effet hors d'un worker lancé par `nodefony cluster`. Dans ce contexte, la sonde remonte
370
395
  périodiquement la santé de **son** processus au maître, qui la consolide, et redistribue
371
396
  l'instantané : n'importe quel worker peut alors servir la vue « pod entier » sans interroger les
372
- autres (`ClusterProbeClient`, `ClusterProbeClient.ts:138`).
397
+ autres (`ClusterProbeClient`, `ClusterProbeClient.ts#ClusterProbeClient`).
373
398
 
374
399
  Le coût est faible mais réel : un minuteur (déréférencé, il n'empêche pas le processus de sortir) et
375
400
  un écouteur IPC par worker. Poser `false` produit un **contournement complet** — le client n'est
@@ -377,13 +402,13 @@ jamais instancié, donc zéro minuteur, zéro message IPC — et l'endpoint de s
377
402
  de l'instance courante.
378
403
 
379
404
  Deux leviers coupent la sonde, et **l'un ou l'autre suffit** : cette clé, ou la variable
380
- `NF_CLUSTER_PROBE=0` (`src/packages/@nodefony/realtime/index.ts:391`). C'est ce qui permet
405
+ `NF_CLUSTER_PROBE=0` (`src/packages/@nodefony/realtime/index.ts#Realtime.#wireClusterProbe`). C'est ce qui permet
381
406
  d'éteindre une sonde sur un pod en incident sans redéployer une configuration.
382
407
 
383
408
  ### `slowConsumer.bytes` — un compteur, pas un frein
384
409
 
385
410
  Seuil de `bufferedAmount` — les octets en attente d'envoi sur une socket — au-delà duquel la sonde
386
- compte la connexion comme « lente » (`RealtimeHub.probe()`, `RealtimeHub.ts:865`).
411
+ compte la connexion comme « lente » (`RealtimeHub.probe()`, `RealtimeHub.ts#RealtimeHub.probe`).
387
412
 
388
413
  > [!IMPORTANT]
389
414
  > **Cette clé observe, elle n'agit pas.** Elle ne change que le compteur `slowConsumers` de la
@@ -439,7 +464,7 @@ des canaux d'état, où seul le dernier instantané compte.
439
464
  Chaque canal ouvert coûte un producteur côté hub, un minuteur le plus souvent, et une entrée de table
440
465
  côté connexion. Sans borne, **un seul** client peut s'abonner jusqu'à épuiser la mémoire du
441
466
  processus. Le plafond est vérifié à chaque abonnement (`RealtimeController.startChannel()`,
442
- `RealtimeController.ts:815`).
467
+ `RealtimeController.ts#RealtimeController.startChannel`).
443
468
 
444
469
  Quatre propriétés qui décident du bon réglage :
445
470
 
@@ -450,7 +475,7 @@ Quatre propriétés qui décident du bon réglage :
450
475
  - Un réabonnement à un canal **déjà tenu** ne consomme pas de place — l'idempotence est vérifiée
451
476
  avant le plafond. Un client qui redemande son canal n'est pas puni.
452
477
  - La garde existe **même sans configuration** : le hub porte le même défaut de 256
453
- (`RealtimeHub.ts:217`), il n'y a pas de fenêtre où elle serait absente.
478
+ (`RealtimeHub.ts#RealtimeHub`), il n'y a pas de fenêtre où elle serait absente.
454
479
 
455
480
  `null` retire la borne. C'est un retrait explicite, à réserver aux déploiements dont tu maîtrises
456
481
  les clients — un tableau de bord interne qui compose des dizaines de flux, par exemple.
@@ -462,13 +487,13 @@ les clients — un tableau de bord interne qui compose des dizaines de flux, par
462
487
  ### `csrf.checkOrigin` — qui a le droit d'ouvrir la socket
463
488
 
464
489
  Trois clés qui forment une seule politique, appliquée à l'ouverture de la connexion
465
- (`RealtimeHub.checkOrigin()`, `RealtimeHub.ts:986`). Une origine refusée ferme la socket avec le code
490
+ (`RealtimeHub.checkOrigin()`, `RealtimeHub.ts#RealtimeHub.checkOrigin`). Une origine refusée ferme la socket avec le code
466
491
  `4003`.
467
492
 
468
493
  Le défaut est **désactivé**. C'est le seul défaut du module qui n'est pas le réglage recommandé :
469
494
  active-le dès que ta socket est joignable depuis un navigateur.
470
495
 
471
- Le comportement se lit en trois lignes (`buildOriginGuard()`, `RealtimeService.ts:313`) :
496
+ Le comportement se lit en trois lignes (`buildOriginGuard()`, `RealtimeService.ts#buildOriginGuard`) :
472
497
 
473
498
  | Situation | Résultat |
474
499
  | ------------------------------------------------ | ------------------------------------------------------------------------ |
@@ -527,7 +552,7 @@ use("@nodefony/realtime", { backplane: { driver: "cluster" } });
527
552
  **Ce qui se passe.** Un worker Node ne peut parler qu'au maître. Il lui envoie ses publications, et le
528
553
  maître les redistribue aux autres workers. Le driver ne s'active qu'en **rôle worker** et avec la
529
554
  variable `NF_CLUSTER=1`, posée par la commande de cluster
530
- (`src/packages/@nodefony/realtime/index.ts:99`) ; ailleurs, il rend `null` et le hub reste local.
555
+ (`src/packages/@nodefony/realtime/index.ts`) ; ailleurs, il rend `null` et le hub reste local.
531
556
 
532
557
  **Pourquoi c'est précieux.** C'est le banc d'essai qui stabilise l'architecture multi-processus avant
533
558
  d'ajouter du réseau. Ton contrôleur, tes canaux diffusables et ton client voient un vrai fan-out
@@ -552,7 +577,7 @@ use("@nodefony/realtime", {
552
577
 
553
578
  **Comment il se branche.** Le driver ne dépend pas de la bibliothèque `redis` : il consomme deux
554
579
  connexions du module `@nodefony/redis` par un adaptateur purement structurel
555
- (`createRedisServiceTransport()`, `RedisBackplane.ts:100`). Deux et non une, parce qu'un client Redis
580
+ (`createRedisServiceTransport()`, `RedisBackplane.ts#createRedisServiceTransport`). Deux et non une, parce qu'un client Redis
556
581
  abonné ne peut plus émettre de commandes ordinaires — le module fournit précisément des connexions
557
582
  nommées `publish` et `subscribe` dans ses défauts.
558
583
 
@@ -562,7 +587,7 @@ coupure. C'est acceptable pour un salon de discussion — le message est en base
562
587
  un bus d'événements critiques. Ne construis pas au-dessus une fiabilité que le support n'offre pas.
563
588
 
564
589
  **Si Redis est injoignable.** Le démarrage du backplane est attendu **explicitement**, et **borné à
565
- cinq secondes** (`src/packages/@nodefony/realtime/index.ts:83`). Au-delà, ou en cas d'échec, le
590
+ cinq secondes** (`src/packages/@nodefony/realtime/index.ts#BACKPLANE_START_TIMEOUT_MS`). Au-delà, ou en cas d'échec, le
566
591
  module renonce, ferme proprement ce qu'il avait ouvert, et laisse le hub local — le démarrage
567
592
  continue. Une base de messages en panne ne doit pas empêcher tes serveurs HTTP de monter.
568
593
 
@@ -636,21 +661,21 @@ Quatre points que la fabrique doit respecter :
636
661
  délai.
637
662
  2. Elle peut rendre **`null`** pour dire « inactif dans ce contexte » — mauvais rôle, infrastructure
638
663
  absente. Le hub reste alors local, sans erreur.
639
- 3. Elle reçoit un contexte complet (`IBackplaneFactoryContext`, `backplaneRegistry.ts:27`) : le
664
+ 3. Elle reçoit un contexte complet (`IBackplaneFactoryContext`, `backplaneRegistry.ts#IBackplaneFactoryContext`) : le
640
665
  module, l'`originId` déjà résolu, le rôle dans la topologie et la configuration validée.
641
666
  4. `describe()` alimente **trois** sorties depuis une source unique : la ligne de journal, la sonde
642
667
  de santé et l'écran Studio. Remplis-la honnêtement, en particulier `crossPod`.
643
668
 
644
669
  **L'autre voie : fournir une instance déjà construite.** Déclare un service nommé
645
670
  `realtimeBackplane` dans le conteneur d'injection de ton module. `RealtimeService.init()`
646
- (`RealtimeService.ts:77`) le lit et le branche **avant** que le registre soit consulté : une instance
671
+ (`RealtimeService.ts#RealtimeService.init`) le lit et le branche **avant** que le registre soit consulté : une instance
647
672
  présente court-circuite la sélection par nom.
648
673
 
649
674
  > [!WARNING]
650
675
  > Le builder accepte aussi une instance en second argument (`defineRealtimeConfig(config, {
651
676
  backplane })`), et la documentation d'architecture présente cette voie. En pratique elle n'est pas
652
677
  > atteignable depuis `nodefony.config.ts` : le module appelle le builder avec la seule configuration
653
- > fusionnée (`src/packages/@nodefony/realtime/index.ts:216`), et le schéma Zod est strict : une clé
678
+ > fusionnée (`src/packages/@nodefony/realtime/index.ts#Realtime.onKernelRegister`), et le schéma Zod est strict : une clé
654
679
  > inconnue, dont une instance de classe posée dans `backplane`, fait échouer le boot. Pour brancher un objet déjà construit, utilise
655
680
  > le service `realtimeBackplane`.
656
681
 
@@ -665,7 +690,7 @@ processus se raccorde — et c'est pour cela qu'elles ne vivent pas dans un fich
665
690
 
666
691
  | Variable | Portée | Effet |
667
692
  | --------------------------------- | ---------------------- | -------------------------------------------------------------------------- |
668
- | `NF_REALTIME_DRIVER` | ce module | remplace `backplane.driver`. **Précédence maximale** |
693
+ | `NF_REALTIME_DRIVER` | ce module | remplace `backplane.driver` — nom libre, un pilote inconnu est signalé |
669
694
  | `NF_REALTIME_BACKPLANE_NAMESPACE` | ce module | remplace `backplane.namespace` — sépare deux déploiements de la même app |
670
695
  | `NF_REALTIME_BACKPLANE_SECRET` | ce module | remplace `backplane.secret` — scelle le transport partagé (≥ 32 car.) |
671
696
  | `NF__REALTIME__<CHEMIN>` | mécanisme du cœur | remplace n'importe quelle clé, par son chemin. Ex. `NF__REALTIME__ENABLED` |
@@ -677,13 +702,15 @@ L'ordre de recouvrement, du plus faible au plus fort :
677
702
 
678
703
  1. **Les valeurs d'usine du schéma** — ce que tu obtiens sans rien écrire.
679
704
  2. **La configuration de l'application** — ton `use("@nodefony/realtime", { … })`.
680
- 3. **`NF__REALTIME__<CHEMIN>`** — appliqué après la fusion et **avant** la validation Zod, donc la
681
- valeur venue de l'environnement est validée comme les autres. Un chemin introuvable produit un
682
- avertissement avec une suggestion, jamais une clé fantôme.
683
- 4. **`NF_REALTIME_DRIVER`**, **`NF_REALTIME_BACKPLANE_NAMESPACE`** et
684
- **`NF_REALTIME_BACKPLANE_SECRET`** — appliqués **après** l'analyse, dans le builder
685
- (`defineModuleConfig.ts:41`). Ils gagnent sur tout le reste, y compris sur le mécanisme
686
- générique `NF__REALTIME__BACKPLANE__…`.
705
+ 3. **`NF_REALTIME_DRIVER`**, **`NF_REALTIME_BACKPLANE_NAMESPACE`** et
706
+ **`NF_REALTIME_BACKPLANE_SECRET`** — posées par le Kernel après la fusion et **avant** la
707
+ validation Zod (`applyEnvShortcuts`, `Kernel.ts#Kernel.applyEnvConfigOverrides`) : une valeur fausse **arrête le démarrage** en nommant la
708
+ variable (un secret de moins de 32 caractères, un espace de noms qui contient `:`). Chacune
709
+ accepte aussi sa forme `<NOM>_FILE` (secret monté par Docker ou Kubernetes).
710
+ 4. **`NF__REALTIME__<CHEMIN>`** — même moment, même validation. Un chemin introuvable produit un
711
+ avertissement avec une suggestion, jamais une clé fantôme. Viser le **même** champ qu'une des
712
+ trois variables ci-dessus est une ambiguïté : refusée en production, et en développement
713
+ `NF__…` l'emporte avec un avertissement qui nomme les deux.
687
714
 
688
715
  Le mécanisme générique mérite d'être connu : le double tiret bas sépare les niveaux, les segments
689
716
  sont insensibles à la casse, et les valeurs sont converties (booléens, nombres, listes séparées par
@@ -700,17 +727,19 @@ NF_CLUSTER_PROBE=0 # couper la sonde de pod sur une réplique en incident
700
727
 
701
728
  > [!TIP]
702
729
  > `NF_REALTIME_DRIVER` existe **en plus** du mécanisme générique parce qu'il est la variable qu'on
703
- > pose en premier sur un déploiement, et qu'elle doit rester fiable quel que soit le moment où la
704
- > configuration de l'application fusionne. C'est le levier d'urgence : ramener un cluster à
705
- > `loopback` pour isoler une panne de bus se fait par une variable, sans redéployer de code.
730
+ > pose en premier sur un déploiement : son nom ne dépend pas de la structure interne de la
731
+ > configuration. C'est le levier d'urgence : ramener un cluster à `loopback` pour isoler une panne
732
+ > de bus se fait par une variable, sans redéployer de code. `npx nodefony env` la liste avec sa
733
+ > valeur et sa provenance.
706
734
 
707
735
  ## 🧰 Le builder et le schéma exportable
708
736
 
709
737
  Deux fonctions publiques, utiles surtout aux tests et aux outils.
710
738
 
711
- `defineRealtimeConfig(config?, options?)` (`defineModuleConfig.ts:35`) analyse, applique la variable
712
- de driver et **gèle**. Le module l'appelle lui-même à l'enregistrement
713
- (`Realtime.onKernelRegister()`, `src/packages/@nodefony/realtime/index.ts:213`) : tu n'as pas à
739
+ `defineRealtimeConfig(config?, options?)` (`defineModuleConfig.ts#defineRealtimeConfig`) analyse et **gèle**. Appelé
740
+ hors Kernel (un test, un outil), il ne voit pas les variables d'environnement : c'est le Kernel
741
+ qui les pose. Le module l'appelle lui-même à l'enregistrement
742
+ (`Realtime.onKernelRegister()`, `src/packages/@nodefony/realtime/index.ts#Realtime.onKernelRegister`) : tu n'as pas à
714
743
  l'invoquer dans une application. En revanche, c'est l'outil qui permet de vérifier une configuration
715
744
  sans démarrer un serveur.
716
745
 
@@ -722,15 +751,15 @@ const cfg = defineRealtimeConfig({ backplane: { driver: "redis" } });
722
751
  // Une valeur invalide ou une clé inconnue lève une BootConfigurationError, avec le chemin exact du champ fautif.
723
752
  ```
724
753
 
725
- `realtimeConfigJsonSchema()` (`defineModuleConfig.ts:69`) produit le schéma JSON du module, chaque
754
+ `realtimeConfigJsonSchema()` (`defineModuleConfig.ts#realtimeConfigJsonSchema`) produit le schéma JSON du module, chaque
726
755
  champ portant sa description. C'est ce que consomme la page module de Studio pour afficher la
727
756
  configuration attendue. L'instance de backplane éventuelle en est absente : une classe n'a rien à
728
757
  faire dans un schéma sérialisable.
729
758
 
730
759
  Un message d'erreur de validation est reformaté par le cœur avant d'être levé (`parseModuleConfig`,
731
760
  appelé par `defineRealtimeConfig`) : chaque problème apparaît sous la forme `chemin : message`
732
- (`src/nodefony/src/kernel/moduleConfig.ts:42`), séparés par des points médians
733
- (`src/nodefony/src/kernel/moduleConfig.ts:58`). Tu lis quel champ est fautif, pas une trace Zod
761
+ (`src/nodefony/src/kernel/moduleConfig.ts#formatConfigIssue`), séparés par des points médians
762
+ (`src/nodefony/src/kernel/moduleConfig.ts#formatConfigIssues`). Tu lis quel champ est fautif, pas une trace Zod
734
763
  brute.
735
764
 
736
765
  ## 📡 Observabilité — relire ce qui s'applique vraiment
@@ -753,16 +782,16 @@ Trois endroits, par ordre de fiabilité décroissante.
753
782
 
754
783
  ## ⚠️ Pièges
755
784
 
756
- | Symptôme | Cause | Correction |
757
- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ |
758
- | 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 |
759
- | 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 |
760
- | 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 |
761
- | 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 |
762
- | 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) |
763
- | 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` |
764
- | `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 |
765
- | 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:217`) | vérifier le motif de refus `realtime:denied` côté client |
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 |
766
795
 
767
796
  ## 🧪 Tests & couverture
768
797
 
@@ -134,7 +134,7 @@ le moment venu.
134
134
 
135
135
  ```bash
136
136
  # 1. Créer l'application (profil complet : http, framework, realtime, sécurité, frontend)
137
- npx nodefony create app mon-chat --preset complete
137
+ npm create nodefony@beta mon-chat -- --preset complete
138
138
  cd mon-chat
139
139
 
140
140
  # 2. Créer le module qui portera le salon
@@ -353,7 +353,7 @@ gaspillerait une socket par écran.
353
353
 
354
354
  ```ts
355
355
  // frontend/src/realtime.ts
356
- import { RealtimeClient } from "nodefony/client";
356
+ import { NodefonySocket } from "nodefony/client";
357
357
 
358
358
  // `wss` si la page est en HTTPS, `ws` sinon. Une application Nodefony sert en
359
359
  // HTTPS par défaut, y compris en développement : ce sera donc `wss` chez toi.
@@ -366,7 +366,7 @@ const scheme = window.location.protocol === "https:" ? "wss" : "ws";
366
366
  * La reconnexion automatique, le renvoi des abonnements après une coupure et le
367
367
  * heartbeat sont inclus — rien à écrire pour ça.
368
368
  */
369
- export const chatSocket = new RealtimeClient({
369
+ export const chatSocket = new NodefonySocket({
370
370
  url: `${scheme}://${window.location.host}/chat/realtime`,
371
371
  });
372
372
  ```
@@ -445,7 +445,9 @@ import { ChatRoom } from "./ChatRoom";
445
445
 
446
446
  // La connexion s'ouvre UNE fois, au démarrage de l'application. Les hooks ne
447
447
  // gèrent que les abonnements : le cycle de vie de la socket reste à toi.
448
- void chatSocket.connect();
448
+ // `connect()` rejette si cette première tentative échoue (serveur absent) ;
449
+ // la socket se reconnecte seule ensuite, et l'état se lit par les hooks.
450
+ chatSocket.connect().catch(() => {});
449
451
 
450
452
  createRoot(document.getElementById("app")!).render(
451
453
  <NodefonyProvider client={chatSocket}>
@@ -779,20 +781,23 @@ couvre le HTTP **et** le WebSocket qu'elle recouvre — c'est le défaut, et c'e
779
781
  zone protégée qui laisserait passer le WebSocket serait une porte dérobée.
780
782
 
781
783
  ```ts ignore
782
- // nodefony.config.ts (ajout de l'étape 5)
783
- use("@nodefony/security", {
784
- areas: {
785
- chat: {
786
- pattern: "^/chat",
787
- // Cookie de session (BFF) : le navigateur n'a aucun jeton à manipuler,
788
- // et le cookie voyage naturellement avec la poignée de main WebSocket.
789
- authenticators: ["session"],
784
+ // nodefony/config/security.ts — (ajout de l'étape 5)
785
+ import type { ISecurityConfigInput } from "@nodefony/security";
786
+
787
+ export const securityConfig = () =>
788
+ ({
789
+ areas: {
790
+ chat: {
791
+ pattern: "^/chat",
792
+ // Cookie de session (BFF) : le navigateur n'a aucun jeton à manipuler,
793
+ // et le cookie voyage naturellement avec la poignée de main WebSocket.
794
+ authenticators: ["session"],
795
+ },
790
796
  },
791
- },
792
- roleHierarchy: {
793
- ROLE_ADMIN: ["ROLE_USER"],
794
- },
795
- });
797
+ roleHierarchy: {
798
+ ROLE_ADMIN: ["ROLE_USER"],
799
+ },
800
+ }) satisfies ISecurityConfigInput;
796
801
  ```
797
802
 
798
803
  > [!NOTE]
@@ -847,6 +852,7 @@ fonctionnent :
847
852
  // modules/chat/nodefony/controllers/ChatController.ts
848
853
  // → remplace `@RealtimeInbound("chat:send")` par une ACTION
849
854
  import { Body, CurrentUser, IsGranted } from "@nodefony/framework";
855
+ import type { IUser } from "@nodefony/user";
850
856
 
851
857
  /**
852
858
  * L'envoi devient une action, joignable en POST HTTP comme par la socket.
@@ -860,7 +866,7 @@ import { Body, CurrentUser, IsGranted } from "@nodefony/framework";
860
866
  @IsGranted(["ROLE_USER"])
861
867
  async send(
862
868
  @Body() payload: { text?: string },
863
- @CurrentUser() user?: { identifier?: string },
869
+ @CurrentUser() user: IUser,
864
870
  ) {
865
871
  const text = typeof payload?.text === "string" ? payload.text.trim() : "";
866
872
  if (text.length === 0 || text.length > 2000) {
@@ -869,7 +875,7 @@ async send(
869
875
 
870
876
  // L'auteur vient du SERVEUR. Le client ne peut plus mentir dessus.
871
877
  const message: ChatMessage = {
872
- author: user?.identifier ?? "inconnu",
878
+ author: user.identifier,
873
879
  text,
874
880
  sentAt: Date.now(),
875
881
  };
@@ -1218,6 +1224,7 @@ import {
1218
1224
  import type { RealtimePublish } from "@nodefony/realtime";
1219
1225
  import { HttpError } from "@nodefony/http";
1220
1226
  import type { ContextType, IUploadedFile } from "@nodefony/http";
1227
+ import type { IUser } from "@nodefony/user";
1221
1228
 
1222
1229
  /** Un message tel qu'il circule sur le canal du salon. */
1223
1230
  export interface ChatMessage {
@@ -1288,14 +1295,14 @@ class ChatController extends RealtimeController {
1288
1295
  @IsGranted(["ROLE_USER"])
1289
1296
  async send(
1290
1297
  @Body() payload: { text?: string },
1291
- @CurrentUser() user?: { identifier?: string },
1298
+ @CurrentUser() user: IUser,
1292
1299
  ): Promise<{ ok: true; sentAt: number }> {
1293
1300
  const text = typeof payload?.text === "string" ? payload.text.trim() : "";
1294
1301
  if (text.length === 0 || text.length > 2000) {
1295
1302
  throw new HttpError("message vide ou trop long", 400);
1296
1303
  }
1297
1304
  const message: ChatMessage = {
1298
- author: user?.identifier ?? "inconnu",
1305
+ author: user.identifier,
1299
1306
  text,
1300
1307
  sentAt: Date.now(),
1301
1308
  };
@@ -1312,13 +1319,13 @@ class ChatController extends RealtimeController {
1312
1319
  @IsGranted(["ROLE_USER"])
1313
1320
  async upload(
1314
1321
  @UploadedFile() file?: IUploadedFile,
1315
- @CurrentUser() user?: { identifier?: string },
1322
+ @CurrentUser() user: IUser,
1316
1323
  ): Promise<{ ok: true; filename: string; size: number }> {
1317
1324
  if (!file) {
1318
1325
  throw new HttpError("aucun fichier reçu", 400);
1319
1326
  }
1320
1327
  const announcement: ChatAttachment = {
1321
- author: user?.identifier ?? "inconnu",
1328
+ author: user.identifier,
1322
1329
  filename: file.filename,
1323
1330
  size: file.size,
1324
1331
  mimeType: file.mimeType,
@@ -1599,23 +1606,23 @@ Pour qui veut lire l'implémentation derrière chaque étape :
1599
1606
  <!-- prettier-ignore -->
1600
1607
  | Ce que tu as utilisé | Où c'est écrit |
1601
1608
  | --- | --- |
1602
- | Les trois décorateurs | `RealtimeAction()` (`realtimeDecorators.ts:101`), `RealtimeChannel()` (`realtimeDecorators.ts:192`), `RealtimeInbound()` (`realtimeDecorators.ts:231`) |
1603
- | La porte WebSocket | `RealtimeController.handleRealtime()` (`RealtimeController.ts:299`) |
1604
- | Le pont HTTP ⇄ WebSocket | `RealtimeController.realtimeApiRequest()` (`RealtimeController.ts:276`) |
1605
- | L'abonnement et son cycle de vie | `RealtimeHub.subscribe()` (`RealtimeHub.ts:462`), `RealtimeController.startChannel()` (`RealtimeController.ts:815`) |
1606
- | La publication et le fan-out | `RealtimeHub.publish()` (`RealtimeHub.ts:604`), réinjection locale `RealtimeHub.publishLocal()` (`RealtimeHub.ts:623`) |
1607
- | Les canaux diffusables | `RealtimeBroadcast` (`realtimeDecorators.ts:342`), `RealtimeHub.markBroadcastChannel()` (`RealtimeHub.ts:668`) |
1608
- | Le branchement du backplane | `RealtimeHub.setBackplane()` (`RealtimeHub.ts:714`), registre `registerBackplaneDriver()` (`backplaneRegistry.ts:55`), cloison `resolveBackplaneOriginId()` (`originId.ts:24`) |
1609
- | Les politiques de canal | `IChannelPolicy` (`IChannelPolicy.ts:20`), `RealtimeHub.registerChannelPolicy()` (`RealtimeHub.ts:1143`), garde-fou `RealtimeHub.hasUnenforcedChannelPolicies()` (`RealtimeHub.ts:1117`) |
1610
- | Le branchement automatique du firewall | `Firewall.#wireRealtime()` (`firewall.ts:279`) |
1611
- | Les refus | `IRealtimeDenied` (`RealtimeEventMap.ts:269`), erreur `unauthorized` (`JsonRpcPeer.ts:417`), fermetures `origin not allowed` (`RealtimeController.ts:327`) et `unauthorized` (`RealtimeController.ts:397`) |
1612
- | La révocation en cours de session | `RealtimeHub.revalidateRevocable()` (`RealtimeHub.ts:826`) |
1613
- | Le consommateur lent | seuil `SLOW_CONSUMER_BYTES` (`RealtimeHub.ts:56`), fermeture `slow consumer` (`RealtimeHub.ts:63`) |
1614
- | Le plafond de canaux | `RealtimeHub.maxChannelsPerConnection` (`RealtimeHub.ts:1000`) |
1615
- | L'accueil et l'identité | `IRealtimeWelcome` (`RealtimeEventMap.ts:231`) |
1616
- | La sonde | `RealtimeHub.probe()` (`RealtimeHub.ts:865`), `IRealtimeProbe` (`IRealtimeProbe.ts:61`), `IRealtimeChannelStat` (`IRealtimeProbe.ts:47`) |
1617
- | Le point de mesure d'administration | `createRealtimeAdminApi()` (`RealtimeAdminApi.ts:98`), `buildRealtimeHealth()` (`RealtimeAdminApi.ts:74`), `buildOwnHealth()` (`RealtimeAdminApi.ts:52`) |
1618
- | Le service injectable | `RealtimeService.publish()` (`RealtimeService.ts:161`), `RealtimeService.probe()` (`RealtimeService.ts:213`), `RealtimeService.markBroadcastChannel()` (`RealtimeService.ts:222`) |
1619
- | Les hooks React | `useNodefony()` (`client/react/index.ts:145`), `useNodefonyState()` (`client/react/index.ts:163`), `useNodefonyIdentity()` (`client/react/index.ts:182`), `useNodefonyChannel()` (`client/react/index.ts:198`), `useNodefonyChannelData()` (`client/react/index.ts:221`), `useNodefonyAdaptiveChannel()` (`client/react/index.ts:255`) |
1620
- | Le client navigateur | `RealtimeClient.connect()` (`RealtimeClient.ts:389`), `RealtimeClient.subscribe()` (`RealtimeClient.ts:530`), `RealtimeClient.request()` (`RealtimeClient.ts:729`), `RealtimeClient.onDenied()` (`RealtimeClient.ts:471`) |
1621
- | L'upload | `UploadedFile` (`routerDecorators.ts:1286`), `IUploadedFile` (`IUpload.ts:49`), `queryFile` (`Request.ts:226`) |
1609
+ | Les trois décorateurs | `RealtimeAction()` (`realtimeDecorators.ts#RealtimeAction`), `RealtimeChannel()` (`realtimeDecorators.ts#RealtimeChannel`), `RealtimeInbound()` (`realtimeDecorators.ts#RealtimeInbound`) |
1610
+ | La porte WebSocket | `RealtimeController.handleRealtime()` (`RealtimeController.ts#RealtimeController.handleRealtime`) |
1611
+ | Le pont HTTP ⇄ WebSocket | `RealtimeController.realtimeApiRequest()` (`RealtimeController.ts#RealtimeController.realtimeApiRequest`) |
1612
+ | L'abonnement et son cycle de vie | `RealtimeHub.subscribe()` (`RealtimeHub.ts#RealtimeHub.subscribe`), `RealtimeController.startChannel()` (`RealtimeController.ts#RealtimeController.startChannel`) |
1613
+ | La publication et le fan-out | `RealtimeHub.publish()` (`RealtimeHub.ts#RealtimeHub.publish`), réinjection locale `RealtimeHub.publishLocal()` (`RealtimeHub.ts#RealtimeHub.publishLocal`) |
1614
+ | Les canaux diffusables | `RealtimeBroadcast` (`realtimeDecorators.ts#RealtimeBroadcast`), `RealtimeHub.markBroadcastChannel()` (`RealtimeHub.ts#RealtimeHub.markBroadcastChannel`) |
1615
+ | Le branchement du backplane | `RealtimeHub.setBackplane()` (`RealtimeHub.ts#RealtimeHub.setBackplane`), registre `registerBackplaneDriver()` (`backplaneRegistry.ts#registerBackplaneDriver`), cloison `resolveBackplaneOriginId()` (`originId.ts#resolveBackplaneOriginId`) |
1616
+ | Les politiques de canal | `IChannelPolicy` (`IChannelPolicy.ts#IChannelPolicy`), `RealtimeHub.registerChannelPolicy()` (`RealtimeHub.ts#RealtimeHub.registerChannelPolicy`), garde-fou `RealtimeHub.hasUnenforcedChannelPolicies()` (`RealtimeHub.ts#RealtimeHub.hasUnenforcedChannelPolicies`) |
1617
+ | Le branchement automatique du firewall | `Firewall.#wireRealtime()` (`firewall.ts#Firewall.#wireRealtime`) |
1618
+ | Les refus | `IRealtimeDenied` (`RealtimeEventMap.ts#IRealtimeDenied`), erreur `unauthorized` (`JsonRpcPeer.ts#JsonRpcPeer.receive`), fermetures `origin not allowed` (`RealtimeController.ts#RealtimeController.notifyClient`) et `unauthorized` (`RealtimeController.ts#RealtimeController.onHandshake`) |
1619
+ | La révocation en cours de session | `RealtimeHub.revalidateRevocable()` (`RealtimeHub.ts#RealtimeHub.revalidateRevocable`) |
1620
+ | Le consommateur lent | seuil `SLOW_CONSUMER_BYTES` (`RealtimeHub.ts#SLOW_CONSUMER_BYTES`), fermeture `slow consumer` (`RealtimeHub.ts#SLOW_CONSUMER_BYTES`) |
1621
+ | Le plafond de canaux | `RealtimeHub.maxChannelsPerConnection` (`RealtimeHub.ts#RealtimeHub.maxChannelsPerConnection`) |
1622
+ | L'accueil et l'identité | `IRealtimeWelcome` (`RealtimeEventMap.ts#IRealtimeWelcome`) |
1623
+ | La sonde | `RealtimeHub.probe()` (`RealtimeHub.ts#RealtimeHub.probe`), `IRealtimeProbe` (`IRealtimeProbe.ts#IRealtimeProbe`), `IRealtimeChannelStat` (`IRealtimeProbe.ts#IRealtimeChannelStat`) |
1624
+ | Le point de mesure d'administration | `createRealtimeAdminApi()` (`RealtimeAdminApi.ts#createRealtimeAdminApi`), `buildRealtimeHealth()` (`RealtimeAdminApi.ts#buildRealtimeHealth`), `buildOwnHealth()` (`RealtimeAdminApi.ts#buildOwnHealth`) |
1625
+ | Le service injectable | `RealtimeService.publish()` (`RealtimeService.ts#RealtimeService.publish`), `RealtimeService.probe()` (`RealtimeService.ts#RealtimeService.probe`), `RealtimeService.markBroadcastChannel()` (`RealtimeService.ts#RealtimeService.markBroadcastChannel`) |
1626
+ | Les hooks React | `useNodefony()` (`client/react/index.ts#useNodefony`), `useNodefonyState()` (`client/react/index.ts#useNodefonyState`), `useNodefonyIdentity()` (`client/react/index.ts#useNodefonyIdentity`), `useNodefonyChannel()` (`client/react/index.ts#useNodefonyChannel`), `useNodefonyChannelData()` (`client/react/index.ts#useNodefonyChannelData`), `useNodefonyAdaptiveChannel()` (`client/react/index.ts#useNodefonyAdaptiveChannel`) |
1627
+ | Le client navigateur | `NodefonySocket.connect()` (`NodefonySocket.ts#NodefonySocket.connect`), `NodefonySocket.subscribe()` (`NodefonySocket.ts#NodefonySocket.subscribe`), `NodefonySocket.request()` (`NodefonySocket.ts#NodefonySocket.request`), `NodefonySocket.onDenied()` (`NodefonySocket.ts#NodefonySocket.onDenied`) |
1628
+ | L'upload | `UploadedFile` (`routerDecorators.ts#UploadedFile`), `IUploadedFile` (`IUpload.ts#IUploadedFile`), `files` (`Request.ts#HttpRequest.files`) |