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

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 (96) hide show
  1. package/README.md +55 -0
  2. package/dist/_virtual/{_@oxc-project_runtime@0.155.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorate.js +1 -1
  3. package/dist/index.js +91 -12
  4. package/dist/nodefony/config/config.js +7 -0
  5. package/dist/nodefony/decorators/realtimeDecorators.js +341 -43
  6. package/dist/nodefony/interfaces/IRealtimeConnection.js +1 -0
  7. package/dist/nodefony/src/backplane/ClusterBackplane.js +32 -4
  8. package/dist/nodefony/src/backplane/RedisBackplane.js +146 -41
  9. package/dist/nodefony/src/backplane/controlOrder.js +86 -0
  10. package/dist/nodefony/src/backplane/envelope.js +505 -27
  11. package/dist/nodefony/src/backplane/ingress.js +93 -0
  12. package/dist/nodefony/src/backplane/presenceMessage.js +205 -0
  13. package/dist/nodefony/src/backplane/publishQueue.js +97 -3
  14. package/dist/nodefony/src/cluster/ClusterProbeClient.js +2 -0
  15. package/dist/nodefony/src/server/AnonymousRealtimeToken.js +23 -2
  16. package/dist/nodefony/src/server/ContractRealtimeController.js +75 -0
  17. package/dist/nodefony/src/server/RealtimeConnection.js +135 -0
  18. package/dist/nodefony/src/server/RealtimeController.js +409 -107
  19. package/dist/nodefony/src/server/RealtimeHub.js +1158 -111
  20. package/dist/nodefony/src/server/ServerRealtimeSocket.js +2 -0
  21. package/dist/nodefony/src/server/admissionBudget.js +176 -0
  22. package/dist/nodefony/src/server/channelPolicyRegistry.js +95 -0
  23. package/dist/nodefony/src/server/channelPolicyResolution.js +191 -0
  24. package/dist/nodefony/src/server/channelPresence.js +488 -0
  25. package/dist/nodefony/src/server/connectionAdmission.js +242 -0
  26. package/dist/nodefony/src/server/contractRoutes.js +75 -0
  27. package/dist/nodefony/src/server/deniedDetail.js +10 -3
  28. package/dist/nodefony/src/server/frameAdmission.js +160 -0
  29. package/dist/nodefony/src/server/presenceBus.js +486 -0
  30. package/dist/nodefony/src/server/realtimeShutdown.js +31 -0
  31. package/dist/nodefony/src/server/remotePresence.js +541 -0
  32. package/dist/nodefony/src/server/subscribedAck.js +13 -1
  33. package/dist/nodefony/src/server/syslogUplink.js +3 -4
  34. package/dist/nodefony/src/server/welcomeIdentity.js +37 -0
  35. package/dist/nodefony/src/service/RealtimeService.js +51 -1
  36. package/dist/nodefony/src/service/publicOrigin.js +40 -0
  37. package/dist/nodefony/src/transport/WsConnectionTransport.js +27 -4
  38. package/dist/nodefony/testing/backplaneConformance.js +347 -0
  39. package/dist/nodefony/testing/index.js +43 -12
  40. package/dist/nodefony/testing/presenceClient.js +54 -0
  41. package/dist/types/index.d.ts +39 -7
  42. package/dist/types/nodefony/config/config.d.ts +9 -0
  43. package/dist/types/nodefony/decorators/realtimeDecorators.d.ts +198 -17
  44. package/dist/types/nodefony/interfaces/IBackplane.d.ts +46 -51
  45. package/dist/types/nodefony/interfaces/IChannelPolicy.d.ts +5 -25
  46. package/dist/types/nodefony/interfaces/IRealtimeConnection.d.ts +22 -0
  47. package/dist/types/nodefony/interfaces/IRealtimeConnectionState.d.ts +41 -2
  48. package/dist/types/nodefony/interfaces/IRealtimeController.d.ts +64 -3
  49. package/dist/types/nodefony/interfaces/IRealtimeProbe.d.ts +7 -166
  50. package/dist/types/nodefony/interfaces/IRealtimeToken.d.ts +21 -0
  51. package/dist/types/nodefony/interfaces/index.d.ts +2 -1
  52. package/dist/types/nodefony/src/backplane/ClusterBackplane.d.ts +22 -4
  53. package/dist/types/nodefony/src/backplane/RedisBackplane.d.ts +89 -18
  54. package/dist/types/nodefony/src/backplane/controlOrder.d.ts +75 -0
  55. package/dist/types/nodefony/src/backplane/envelope.d.ts +77 -0
  56. package/dist/types/nodefony/src/backplane/ingress.d.ts +48 -0
  57. package/dist/types/nodefony/src/backplane/presenceMessage.d.ts +150 -0
  58. package/dist/types/nodefony/src/backplane/publishQueue.d.ts +25 -1
  59. package/dist/types/nodefony/src/server/AnonymousRealtimeToken.d.ts +10 -0
  60. package/dist/types/nodefony/src/server/ContractRealtimeController.d.ts +138 -0
  61. package/dist/types/nodefony/src/server/RealtimeConnection.d.ts +20 -0
  62. package/dist/types/nodefony/src/server/RealtimeController.d.ts +110 -12
  63. package/dist/types/nodefony/src/server/RealtimeHub.d.ts +448 -42
  64. package/dist/types/nodefony/src/server/ServerRealtimeSocket.d.ts +1 -1
  65. package/dist/types/nodefony/src/server/admissionBudget.d.ts +71 -0
  66. package/dist/types/nodefony/src/server/channelPolicyRegistry.d.ts +41 -0
  67. package/dist/types/nodefony/src/server/channelPolicyResolution.d.ts +112 -0
  68. package/dist/types/nodefony/src/server/channelPresence.d.ts +155 -0
  69. package/dist/types/nodefony/src/server/connectionAdmission.d.ts +68 -0
  70. package/dist/types/nodefony/src/server/contractRoutes.d.ts +42 -0
  71. package/dist/types/nodefony/src/server/deniedDetail.d.ts +6 -0
  72. package/dist/types/nodefony/src/server/frameAdmission.d.ts +66 -0
  73. package/dist/types/nodefony/src/server/presenceBus.d.ts +112 -0
  74. package/dist/types/nodefony/src/server/realtimeShutdown.d.ts +21 -0
  75. package/dist/types/nodefony/src/server/remotePresence.d.ts +151 -0
  76. package/dist/types/nodefony/src/server/subscribedAck.d.ts +10 -0
  77. package/dist/types/nodefony/src/server/welcomeIdentity.d.ts +19 -0
  78. package/dist/types/nodefony/src/service/RealtimeService.d.ts +17 -2
  79. package/dist/types/nodefony/src/service/publicOrigin.d.ts +19 -0
  80. package/dist/types/nodefony/src/transport/WsConnectionTransport.d.ts +16 -30
  81. package/dist/types/nodefony/testing/backplaneConformance.d.ts +78 -0
  82. package/dist/types/nodefony/testing/index.d.ts +13 -2
  83. package/dist/types/nodefony/testing/presenceClient.d.ts +20 -0
  84. package/docs/actions.md +420 -18
  85. package/docs/architecture.md +101 -11
  86. package/docs/cadence-et-contre-pression.md +87 -11
  87. package/docs/canaux-a-membres.md +277 -0
  88. package/docs/cas-d-usage.md +1703 -0
  89. package/docs/configuration.md +175 -19
  90. package/docs/cookbook-chat.md +701 -224
  91. package/docs/index.md +11 -1
  92. package/docs/observabilite.md +97 -21
  93. package/docs/protocole.md +142 -33
  94. package/docs/securite.md +347 -41
  95. package/docs/vocabulaire.md +13 -12
  96. package/package.json +16 -13
@@ -1,8 +1,25 @@
1
- import { type RpcActionHandler, type EventsMap, type ActionsMap, type DefaultEventsMap, type DefaultActionsMap, type EventNames, type EventPayload, type ActionNames, type ActionParams, type ActionResult } from "nodefony";
1
+ import { type EventsMap, type ActionsMap, type DefaultEventsMap, type DefaultActionsMap, type EventNames, type EventPayload, type ActionNames, type ActionParams, type ActionResult } from "nodefony";
2
2
  import type { ContextType } from "@nodefony/http";
3
3
  import { Controller } from "@nodefony/framework";
4
4
  import type { ControllerScope } from "@nodefony/framework";
5
- import type { IRealtimeController, RealtimePublish, RealtimeInboundHandler } from "../../interfaces/IRealtimeController.js";
5
+ import type { IRealtimeController, RealtimePublish, RealtimeInboundHandlers, RealtimeActionHandler } from "../../interfaces/IRealtimeController.js";
6
+ /**
7
+ * Préfixe de la ligne `ERROR` écrite quand la poignée de main lève (socket fermée
8
+ * en 1011). Le harnais de test la relit pour citer l'exception : le client, lui,
9
+ * ne reçoit que « internal error » — rien d'interne ne sort par la socket.
10
+ */
11
+ export declare const HANDSHAKE_FAILED_LOG = "WS realtime handshake failed";
12
+ /**
13
+ * L'attente d'un `Retry-After` (RFC 9110 §10.2.3), en secondes : un délai
14
+ * (`120`) tel quel, une date HTTP ramenée à l'écart avec `now` (jamais
15
+ * négatif). Une valeur illisible, ou absente, ne dit rien : `null`.
16
+ *
17
+ * @param value - l'en-tête posé par l'action, s'il l'a été
18
+ * @param now - l'instant de référence d'une date HTTP, en ms (défaut : l'horloge,
19
+ * lue seulement pour une date)
20
+ * @returns les secondes à attendre, ou `null`
21
+ */
22
+ export declare function retryAfterSeconds(value: string | undefined, now?: number): number | null;
6
23
  /**
7
24
  * RealtimeController — base d'un endpoint WebSocket temps réel SERVEUR (JSON-RPC 2.0).
8
25
  *
@@ -25,7 +42,7 @@ import type { IRealtimeController, RealtimePublish, RealtimeInboundHandler } fro
25
42
  * créé au 1ᵉʳ abonné, `dispose()` au dernier (`unsubscribe` ou `onFinish`). Le dispatch
26
43
  * d'action n'est payé que sur une requête `id`. C'est aussi le seam du backplane Redis.
27
44
  */
28
- export declare abstract class RealtimeController<Emit extends EventsMap = DefaultEventsMap, Actions extends ActionsMap = DefaultActionsMap> extends Controller implements IRealtimeController {
45
+ export declare abstract class RealtimeController<Emit extends EventsMap = DefaultEventsMap, Actions extends ActionsMap = DefaultActionsMap, Listen extends EventsMap = DefaultEventsMap> extends Controller implements IRealtimeController {
29
46
  #private;
30
47
  /**
31
48
  * Une instance PAR CONNEXION, par construction — le défaut `singleton` des
@@ -72,7 +89,7 @@ export declare abstract class RealtimeController<Emit extends EventsMap = Defaul
72
89
  */
73
90
  createRealtimeChannel(_channel: string, _publish: RealtimePublish): (() => void) | null;
74
91
  /** Actions RPC exposées (requête→réponse). À surcharger ; défaut : aucune. */
75
- protected realtimeActions(): Record<string, RpcActionHandler>;
92
+ protected realtimeActions(): Record<string, RealtimeActionHandler>;
76
93
  /** Canaux annoncés au handshake. À surcharger ; défaut : aucun. */
77
94
  protected realtimeChannels(): string[];
78
95
  /**
@@ -88,13 +105,16 @@ export declare abstract class RealtimeController<Emit extends EventsMap = Defaul
88
105
  * surcharger ; défaut : **aucun** (sûr — un client ne peut rien pousser au serveur
89
106
  * tant qu'un canal n'est pas explicitement déclaré ici). Seam des backings entrants
90
107
  * (SIP, bridge). Cf {@link RealtimeInboundHandler} (params NON FIABLES).
108
+ *
109
+ * Typée par `Listen` (ce que le client émet, 3ᵉ générique) : un canal hors de
110
+ * la carte ne compile pas, et chaque handler reçoit la forme de SON canal.
91
111
  */
92
- protected realtimeInbound(): Record<string, RealtimeInboundHandler>;
112
+ protected realtimeInbound(): RealtimeInboundHandlers<Listen, Emit>;
93
113
  /**
94
114
  * Opt-in du **pont API** (une action, deux transports) : `true` → la
95
115
  * connexion expose la méthode RPC `api.request {path}` qui re-route le path
96
116
  * porté par le message vers l'action controller correspondante — la MÊME
97
- * action que le GET REST (`socket.request("/nodefony/kernel/api/modules")`
117
+ * action que le GET REST (`socket.get("/nodefony/kernel/api/modules")`
98
118
  * côté client). Le pont n'atteint QUE les routes déclarant le transport
99
119
  * `WEBSOCKET` dans leurs `methods` (zéro bypass : une action dit à quels
100
120
  * transports elle répond). À surcharger ; défaut : `false` — aucune surface
@@ -120,7 +140,7 @@ export declare abstract class RealtimeController<Emit extends EventsMap = Defaul
120
140
  * `NodefonySocket.replaySubscriptions` et les deux cas « la fenêtre où le serveur
121
141
  * écoute (welcome) » de `NodefonySocketCoverage.test.ts`.
122
142
  */
123
- protected handleRealtime(message: string | Buffer | null): void;
143
+ protected handleRealtime(message: string | Uint8Array | null): void;
124
144
  /**
125
145
  * Lit les trames BINAIRES de cette connexion — le point d'extension d'un
126
146
  * protocole binaire écrit par l'application, à côté du JSON-RPC ; pendant
@@ -128,7 +148,7 @@ export declare abstract class RealtimeController<Emit extends EventsMap = Defaul
128
148
  *
129
149
  * Le protocole temps réel est du JSON-RPC texte : il ne sait pas à quel canal
130
150
  * une trame binaire appartient. Sans cette méthode, une trame binaire est
131
- * écartée, comptée (`binaryDiscarded` de la sonde de connexion) et journalisée
151
+ * écartée, comptée (`binaryDiscardedTotal` de la sonde du hub) et journalisée
132
152
  * une fois par connexion. La surcharger la reçoit telle quelle, octets exacts,
133
153
  * en `Uint8Array` — le type que livre aussi `NodefonySocket.onBinary` dans le
134
154
  * navigateur : un décodeur s'écrit une fois pour les deux côtés. À l'exécution
@@ -140,6 +160,10 @@ export declare abstract class RealtimeController<Emit extends EventsMap = Defaul
140
160
  * et le pare-feu. L'identité se lit par `RequestContext.getAuthenticatedUser()`,
141
161
  * et toute décision d'accès sur une trame binaire appartient à l'application.
142
162
  *
163
+ * ⚠️ Connexions TEXTE seulement : une connexion qui a négocié un codec binaire
164
+ * (configuration `codec: "cbor"`) lit toute trame binaire comme une frame du
165
+ * codec, et cette méthode n'y est jamais appelée (ADR-0022 §2).
166
+ *
143
167
  * ⚠️ Un `DataView` se construit sur `data.buffer` AVEC `data.byteOffset` et
144
168
  * `data.byteLength` : un `Buffer` issu du pool de Node ne commence pas à 0.
145
169
  *
@@ -183,10 +207,20 @@ export declare abstract class RealtimeController<Emit extends EventsMap = Defaul
183
207
  * client. Même contre-pression que les frames JSON-RPC.
184
208
  *
185
209
  * @param data - les octets à émettre (`Uint8Array`, donc un `Buffer` aussi).
186
- * @returns `false` si le handshake n'est pas terminé, la connexion fermée ou
187
- * la trame jetée par la contre-pression.
210
+ * Sur une connexion qui a négocié un codec BINAIRE, refusé (ADR-0022 §2) : le
211
+ * client y lirait ces octets comme une frame du codec. Des octets passent
212
+ * alors par un canal, avec ses portes.
213
+ *
214
+ * @returns `false` si le handshake n'est pas terminé, la connexion fermée, la
215
+ * trame jetée par la contre-pression, ou la connexion en codec binaire.
188
216
  */
189
217
  protected sendBinaryToClient(data: Uint8Array): boolean;
218
+ /**
219
+ * Journalise, une fois par connexion, le flux brut refusé sous codec binaire.
220
+ *
221
+ * @param state - l'état de la connexion.
222
+ */
223
+ private noticeRawBinaryUnderCodec;
190
224
  /**
191
225
  * État realtime de CETTE connexion, `undefined` avant le welcome — pour les
192
226
  * chemins FROIDS (requête sortante, binaire). `notifyClient` et la réception
@@ -207,6 +241,8 @@ export declare abstract class RealtimeController<Emit extends EventsMap = Defaul
207
241
  * Échecs (close WebSocket, codes plage applicative RFC 6455 §7.4.2) :
208
242
  * - Origin refusée → code 4003 (`forbidden`).
209
243
  * - `authenticate` throw → code 4001 (`unauthorized`).
244
+ * - `authenticate` sans réponse dans `authenticateTimeoutMs` → code 1013
245
+ * (`try again later`, ADR-0021 §S11) : le client recule puis revient.
210
246
  */
211
247
  private onHandshake;
212
248
  /**
@@ -222,8 +258,69 @@ export declare abstract class RealtimeController<Emit extends EventsMap = Defaul
222
258
  * par connexion).
223
259
  */
224
260
  private acknowledgeSubscription;
225
- /** Abonne la connexion à un canal via le hub partagé (idempotent par connexion). */
261
+ /**
262
+ * Poignée publique de la connexion, créée au premier besoin (abonnement, trame entrante) puis gardée sur son état.
263
+ *
264
+ * Cold path : une fois par connexion. Les deux fermetures la relient à CE
265
+ * contrôleur, dont les fabriques servent ses canaux.
266
+ */
267
+ private connectionOf;
268
+ /**
269
+ * Abonnement imposé par le serveur (`RealtimeHub.subscribeConnection`) — la politique fixe par le verrou des trames, puis le chemin d'un `subscribe` du client, `allow` compris (ADR-0022 §6 décision 2).
270
+ *
271
+ * Le verdict est rendu à l'appelant par `RealtimeConnection.settleImposed` :
272
+ * à l'acquittement ({@link acknowledgeSubscription}) ou au refus
273
+ * ({@link refuseChannel}) — qui n'envoie alors rien au client, qui n'a rien
274
+ * demandé.
275
+ */
276
+ private imposeChannel;
277
+ /**
278
+ * Met fin à l'abonnement de la connexion à un canal, sur décision du serveur, et le lui notifie (`realtime:closed`).
279
+ *
280
+ * Même chemin que l'`unsubscribe` du client (hub ET état de la connexion),
281
+ * jamais un second : retirer le seul sink du hub laisserait l'entrée de la
282
+ * connexion, et le `subscribe` suivant serait acquitté sans rien rouvrir.
283
+ * Retire aussi la marque « admise » et le verdict retenu de ce canal.
284
+ *
285
+ * @returns `false` si la connexion n'est pas abonnée à ce canal.
286
+ */
287
+ private endChannel;
288
+ /**
289
+ * Retire la connexion d'un canal : hub, état de la connexion, admission de sa règle — le chemin UNIQUE d'une sortie, demandée par le client ou décidée par le serveur.
290
+ *
291
+ * @returns `false` si la connexion n'était pas abonnée.
292
+ */
293
+ private leaveChannel;
294
+ /**
295
+ * Refus d'un abonnement : rendu à l'appelant serveur s'il s'agit d'une imposition, au client sinon — et aux deux quand le client avait aussi demandé ce canal.
296
+ */
297
+ private refuseChannel;
298
+ /**
299
+ * Abonne la connexion à un canal via le hub partagé (idempotent par connexion).
300
+ *
301
+ * Un canal dont la politique déclare `allow` passe d'abord par la file
302
+ * d'admission de la connexion ; tout autre canal s'ouvre tout de suite, sans
303
+ * rien lire de plus qu'aujourd'hui (une recherche de politique au `subscribe`,
304
+ * trame de contrôle rare).
305
+ */
226
306
  private startChannel;
307
+ /**
308
+ * Soumet l'abonnement à la règle `allow` du canal — APRÈS le verrou des trames, qui a déjà jugé `authenticated`/`roles`/`scopes` (ADR-0022 §6, décision 1 ter, moment 2).
309
+ *
310
+ * `allow` s'applique même sans module de sécurité : il ne fait que retirer
311
+ * des abonnés, jamais en ajouter — l'ignorer ouvrirait `user:<id>` à tous.
312
+ * La file de la connexion n'est créée qu'à son premier canal à `allow`.
313
+ */
314
+ private admitChannel;
315
+ /**
316
+ * Rend un refus d'abonnement observable (`realtime:denied`) — le détail ne part qu'hors production.
317
+ *
318
+ * @param detail - phrase pour le développeur ; `undefined` quand l'appelant
319
+ * ne l'a pas rédigée (production).
320
+ */
321
+ private denyChannel;
322
+ /** Ouvre l'abonnement au hub et l'acquitte — canal sans `allow`, ou admis par sa règle. */
323
+ private openChannel;
227
324
  /**
228
325
  * Enveloppe des actions `@RealtimeAction` dont un paramètre est décoré
229
326
  * (`@Body`, `@CurrentUser`…) : l'action reçoit ses arguments comme une route,
@@ -260,7 +357,8 @@ export declare abstract class RealtimeController<Emit extends EventsMap = Defaul
260
357
  * mutualisé tant que Ph.6 n'a pas tranché).
261
358
  *
262
359
  * Erreurs → {@link RpcError} (`data.status` = statut HTTP équivalent) :
263
- * `-32602` params invalides, 404 path inconnu, 408 timeout d'action…
360
+ * `-32602` params invalides, 404 path inconnu, 408 timeout d'action… Un
361
+ * `Retry-After` posé par l'action part en `data.retryAfter` (secondes).
264
362
  * Tout autre throw de l'action reste opaque (`-32603`, Zero Trust).
265
363
  */
266
364
  private invokeApiRequest;