@nodefony/realtime 10.0.0-alpha.1

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 (81) hide show
  1. package/LICENSE +544 -0
  2. package/README.md +207 -0
  3. package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js +9 -0
  4. package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js +6 -0
  5. package/dist/index.js +218 -0
  6. package/dist/nodefony/config/config.js +63 -0
  7. package/dist/nodefony/config/defineModuleConfig.js +60 -0
  8. package/dist/nodefony/decorators/realtimeDecorators.js +240 -0
  9. package/dist/nodefony/interfaces/IBackplane.js +1 -0
  10. package/dist/nodefony/interfaces/IChannelPolicy.js +1 -0
  11. package/dist/nodefony/interfaces/IClientLogsLimits.js +1 -0
  12. package/dist/nodefony/interfaces/IRealtimeAuthenticator.js +1 -0
  13. package/dist/nodefony/interfaces/IRealtimeAuthenticatorMatcher.js +1 -0
  14. package/dist/nodefony/interfaces/IRealtimeController.js +1 -0
  15. package/dist/nodefony/interfaces/IRealtimeHandshake.js +1 -0
  16. package/dist/nodefony/interfaces/IRealtimeProbe.js +1 -0
  17. package/dist/nodefony/interfaces/IRealtimeToken.js +1 -0
  18. package/dist/nodefony/interfaces/index.js +1 -0
  19. package/dist/nodefony/src/backplane/ClusterBackplane.js +105 -0
  20. package/dist/nodefony/src/backplane/LoopbackBackplane.js +40 -0
  21. package/dist/nodefony/src/backplane/RedisBackplane.js +176 -0
  22. package/dist/nodefony/src/backplane/backplaneRegistry.js +20 -0
  23. package/dist/nodefony/src/backplane/envelope.js +109 -0
  24. package/dist/nodefony/src/backplane/originId.js +35 -0
  25. package/dist/nodefony/src/backplane/publishQueue.js +96 -0
  26. package/dist/nodefony/src/cluster/ClusterProbeClient.js +284 -0
  27. package/dist/nodefony/src/server/AnonymousRealtimeToken.js +35 -0
  28. package/dist/nodefony/src/server/RealtimeAdminApi.js +87 -0
  29. package/dist/nodefony/src/server/RealtimeController.js +601 -0
  30. package/dist/nodefony/src/server/RealtimeHub.js +926 -0
  31. package/dist/nodefony/src/server/ServerRealtimeSocket.js +168 -0
  32. package/dist/nodefony/src/server/deniedDetail.js +29 -0
  33. package/dist/nodefony/src/server/syslogUplink.js +105 -0
  34. package/dist/nodefony/src/server/welcomeEnv.js +25 -0
  35. package/dist/nodefony/src/service/RealtimeService.js +240 -0
  36. package/dist/nodefony/src/transport/WsConnectionTransport.js +94 -0
  37. package/dist/nodefony/testing/index.js +163 -0
  38. package/dist/types/index.d.ts +99 -0
  39. package/dist/types/nodefony/config/config.d.ts +43 -0
  40. package/dist/types/nodefony/config/defineModuleConfig.d.ts +49 -0
  41. package/dist/types/nodefony/decorators/realtimeDecorators.d.ts +190 -0
  42. package/dist/types/nodefony/interfaces/IBackplane.d.ts +128 -0
  43. package/dist/types/nodefony/interfaces/IChannelPolicy.d.ts +28 -0
  44. package/dist/types/nodefony/interfaces/IClientLogsLimits.d.ts +22 -0
  45. package/dist/types/nodefony/interfaces/IRealtimeAuthenticator.d.ts +53 -0
  46. package/dist/types/nodefony/interfaces/IRealtimeAuthenticatorMatcher.d.ts +46 -0
  47. package/dist/types/nodefony/interfaces/IRealtimeController.d.ts +36 -0
  48. package/dist/types/nodefony/interfaces/IRealtimeHandshake.d.ts +27 -0
  49. package/dist/types/nodefony/interfaces/IRealtimeProbe.d.ts +206 -0
  50. package/dist/types/nodefony/interfaces/IRealtimeToken.d.ts +48 -0
  51. package/dist/types/nodefony/interfaces/index.d.ts +18 -0
  52. package/dist/types/nodefony/src/backplane/ClusterBackplane.d.ts +76 -0
  53. package/dist/types/nodefony/src/backplane/LoopbackBackplane.d.ts +29 -0
  54. package/dist/types/nodefony/src/backplane/RedisBackplane.d.ts +164 -0
  55. package/dist/types/nodefony/src/backplane/backplaneRegistry.d.ts +51 -0
  56. package/dist/types/nodefony/src/backplane/envelope.d.ts +27 -0
  57. package/dist/types/nodefony/src/backplane/originId.d.ts +22 -0
  58. package/dist/types/nodefony/src/backplane/publishQueue.d.ts +71 -0
  59. package/dist/types/nodefony/src/cluster/ClusterProbeClient.d.ts +90 -0
  60. package/dist/types/nodefony/src/server/AnonymousRealtimeToken.d.ts +3 -0
  61. package/dist/types/nodefony/src/server/RealtimeAdminApi.d.ts +18 -0
  62. package/dist/types/nodefony/src/server/RealtimeController.d.ts +165 -0
  63. package/dist/types/nodefony/src/server/RealtimeHub.d.ts +516 -0
  64. package/dist/types/nodefony/src/server/ServerRealtimeSocket.d.ts +63 -0
  65. package/dist/types/nodefony/src/server/deniedDetail.d.ts +25 -0
  66. package/dist/types/nodefony/src/server/syslogUplink.d.ts +58 -0
  67. package/dist/types/nodefony/src/server/welcomeEnv.d.ts +21 -0
  68. package/dist/types/nodefony/src/service/RealtimeService.d.ts +167 -0
  69. package/dist/types/nodefony/src/transport/WsConnectionTransport.d.ts +78 -0
  70. package/dist/types/nodefony/testing/index.d.ts +169 -0
  71. package/docs/actions.md +573 -0
  72. package/docs/architecture.md +889 -0
  73. package/docs/configuration.md +803 -0
  74. package/docs/cookbook-chat.md +1619 -0
  75. package/docs/couverture-bancs.md +139 -0
  76. package/docs/index.md +372 -0
  77. package/docs/observabilite.md +592 -0
  78. package/docs/protocole.md +518 -0
  79. package/docs/securite.md +845 -0
  80. package/docs/vocabulaire.md +845 -0
  81. package/package.json +83 -0
@@ -0,0 +1,165 @@
1
+ import { type RpcActionHandler, type EventsMap, type ActionsMap, type DefaultEventsMap, type DefaultActionsMap, type EventNames, type EventPayload, type ActionNames, type ActionParams, type ActionResult } from "nodefony";
2
+ import { Controller } from "@nodefony/framework";
3
+ import type { IRealtimeController, RealtimePublish, RealtimeInboundHandler } from "../../interfaces/IRealtimeController.js";
4
+ /**
5
+ * RealtimeController — base d'un endpoint WebSocket temps réel SERVEUR (JSON-RPC 2.0).
6
+ *
7
+ * Factorise TOUT le protocole (handshake/welcome, discrimination request/notification/
8
+ * response via {@link JsonRpcPeer}, actions `result`/`error`, pub/sub par canal, cleanup)
9
+ * — écrit UNE fois, partagé par tous les modules. Un contrôleur concret ne déclare que
10
+ * son métier : {@link createRealtimeChannel} (providers de canaux) + {@link realtimeActions}.
11
+ *
12
+ * Chaque connexion compose un `JsonRpcPeer` (le MÊME que `RealtimeClient` côté navigateur
13
+ * — symétrie isomorphe) branché sur un {@link WsConnectionTransport}.
14
+ *
15
+ * Usage : le sous-classe garde sa route WS et délègue —
16
+ * ```ts
17
+ * @route("ws", { path: "/realtime", requirements: { methods: ["WEBSOCKET"] } })
18
+ * async realtime(message: string | Buffer | null) { this.handleRealtime(message); }
19
+ * ```
20
+ *
21
+ * Perf : les canaux sont **partagés** via {@link RealtimeHub} (1 provider/canal/pod, pas
22
+ * 1 par connexion) ; chaque connexion n'ajoute/retire qu'un *sink* (fan-out). Provider
23
+ * créé au 1ᵉʳ abonné, `dispose()` au dernier (`unsubscribe` ou `onFinish`). Le dispatch
24
+ * d'action n'est payé que sur une requête `id`. C'est aussi le seam du backplane Redis.
25
+ */
26
+ export declare abstract class RealtimeController<Emit extends EventsMap = DefaultEventsMap, Actions extends ActionsMap = DefaultActionsMap> extends Controller implements IRealtimeController {
27
+ /**
28
+ * Map des factories de canaux EXACT, lue depuis les décorateurs `@RealtimeChannel`
29
+ * au handshake (cold-path) et mémoïsée par instance. `null` = pas de décorateur
30
+ * sur cette classe → la base court-circuite la lecture metadata par frame.
31
+ */
32
+ private _decoratedChannels;
33
+ /**
34
+ * Crée le provider d'un canal (listener/ticker → `publish`) et renvoie son `dispose`.
35
+ * `null` si le canal est inconnu — la base ne souscrit pas.
36
+ *
37
+ * **Deux façons de déclarer un canal** (coexistent sans casse) :
38
+ * 1. **Décorateur `@RealtimeChannel(name)`** sur une méthode (match EXACT, déclaratif).
39
+ * 2. **Override de cette méthode** (pattern/regex, suffixe `:<ms>`, drill `@<pid>`).
40
+ *
41
+ * La base CONSULTE D'ABORD les décorateurs ; si aucun match, elle appelle
42
+ * cette méthode (fallback). Défaut : `null`. Un controller qui n'utilise QUE
43
+ * des décorateurs peut donc se passer d'override.
44
+ */
45
+ createRealtimeChannel(_channel: string, _publish: RealtimePublish): (() => void) | null;
46
+ /** Actions RPC exposées (requête→réponse). À surcharger ; défaut : aucune. */
47
+ protected realtimeActions(): Record<string, RpcActionHandler>;
48
+ /** Canaux annoncés au handshake. À surcharger ; défaut : aucun. */
49
+ protected realtimeChannels(): string[];
50
+ /**
51
+ * Préfixes de canaux **broadcast** (cross-process) de cet endpoint. À surcharger ;
52
+ * défaut : **aucun** → tous les canaux restent **instance-local** (observabilité,
53
+ * état du pod). Un canal listé ici traverse le {@link IBackplane} (cluster IPC /
54
+ * Redis) : chat, présence, notifications… Cf {@link RealtimeHub.markBroadcastChannel}
55
+ * (défaut sûr : pas de fuite cross-pod de données per-instance sans intention explicite).
56
+ */
57
+ protected realtimeBroadcastChannels(): string[];
58
+ /**
59
+ * Canaux FULL-DUPLEX acceptant une entrée client (`method` = nom du canal). À
60
+ * surcharger ; défaut : **aucun** (sûr — un client ne peut rien pousser au serveur
61
+ * tant qu'un canal n'est pas explicitement déclaré ici). Seam des backings entrants
62
+ * (SIP, bridge). Cf {@link RealtimeInboundHandler} (params NON FIABLES).
63
+ */
64
+ protected realtimeInbound(): Record<string, RealtimeInboundHandler>;
65
+ /**
66
+ * Opt-in du **pont API** (« API souveraine », POC Ph.3) : `true` → la
67
+ * connexion expose la méthode RPC `api.request {path}` qui re-route le path
68
+ * porté par le message vers l'action controller correspondante — la MÊME
69
+ * action que le GET REST (`socket.request("/nodefony/kernel/api/modules")`
70
+ * côté client). Le pont n'atteint QUE les routes déclarant le transport
71
+ * `WEBSOCKET` dans leurs `methods` (zéro bypass : une action dit à quels
72
+ * transports elle répond). À surcharger ; défaut : `false` — aucune surface
73
+ * d'invocation ajoutée à un hub sans intention explicite.
74
+ */
75
+ protected realtimeApiRequest(): boolean;
76
+ /**
77
+ * Point d'entrée à appeler depuis la route WS du contrôleur. `message === null`
78
+ * = handshake (1ʳᵉ invocation) ; sinon = frame entrante.
79
+ *
80
+ * Le handshake est désormais ASYNC (seams sécu #2/#4 : origin check + run
81
+ * d'authenticator au handshake). Les frames texte entrantes pendant
82
+ * l'authentification sont DROP silencieusement — le transport JSON-RPC n'est pas
83
+ * encore branché, il n'existe donc AUCUN canal pour porter un refus. Le silence
84
+ * est structurel, pas un choix : c'est au client d'attendre `realtime:welcome`
85
+ * avant de pousser.
86
+ *
87
+ * ⚠️ Cette phrase a longtemps ajouté « ce que `RealtimeClient` fait nativement ».
88
+ * C'était FAUX : le client rejouait ses abonnements dès l'ouverture de la socket,
89
+ * si bien qu'un `subscribe` posé avant le démarrage et TOUS ceux d'après une
90
+ * reconnexion étaient perdus ici même, sans un mot des deux côtés. Un contrat écrit
91
+ * d'un seul côté du fil n'est pas tenu ; il l'est désormais par
92
+ * `RealtimeClient.replaySubscriptions` et les deux cas « la fenêtre où le serveur
93
+ * écoute (welcome) » de `RealtimeClientCoverage.test.ts`.
94
+ */
95
+ protected handleRealtime(message: string | Buffer | null): void;
96
+ /**
97
+ * **L1 — duplex serveur→client.** Émet une REQUÊTE vers le client de CETTE
98
+ * connexion (sur une action qu'il a exposée via `client.register(...)`) et attend
99
+ * sa réponse. Possible depuis L0 : le client compose le même `JsonRpcPeer`, il
100
+ * est donc un *callee* (avant L0 : `-32601` systématique).
101
+ *
102
+ * Usages : confirmation d'action (le serveur demande au client de valider),
103
+ * invalidation de cache poussée AVEC accusé, health applicatif serveur→client.
104
+ * Pour une notification sans réponse → {@link notifyClient} (ou un canal pub/sub).
105
+ *
106
+ * @returns le `result` du handler client ; rejette sur erreur applicative
107
+ * ({@link RpcError} `code`/`data` préservés), méthode inconnue (`-32601`),
108
+ * timeout, ou connexion fermée (`dispose`).
109
+ * @throws (rejet) si le handshake n'est pas terminé (aucun peer encore).
110
+ */
111
+ protected requestClient<K extends ActionNames<Actions>>(method: K, params?: ActionParams<Actions, K>, timeoutMs?: number): Promise<ActionResult<Actions, K>>;
112
+ /**
113
+ * Notification CIBLÉE serveur→client (sans réponse) vers CETTE connexion, hors
114
+ * canal pub/sub — pendant *fire-and-forget* de {@link requestClient}. Pour un
115
+ * fan-out à N abonnés, préférer un canal (`publish` via le hub). No-op si le
116
+ * handshake n'est pas terminé.
117
+ */
118
+ protected notifyClient<K extends EventNames<Emit>>(method: K, params?: EventPayload<Emit, K>): void;
119
+ /**
120
+ * Handshake : seams sécurité #4 (Origin RFC 6455 §10.2) + #2 (authenticator
121
+ * réseau) PUIS crée peer+transport, enregistre les actions, welcome + cleanup.
122
+ *
123
+ * Pipeline (cold path, 1× par connexion) :
124
+ * 1. Origin check via `hub.checkOrigin()` (bypass si `csrf.checkOrigin.enabled=false`).
125
+ * 2. Resolve authenticator via `hub.resolveAuthenticator(handshake)` (matchers
126
+ * ordonnés, 1ʳᵉ qui matche capture) → `authenticate()` async.
127
+ * 3. Aucun match OU `enabled=false` → `ANONYMOUS_REALTIME_TOKEN`.
128
+ * 4. Pose le token sur `hub.peer → token` (lookup voters P6 hot-path).
129
+ *
130
+ * Échecs (close WebSocket, codes plage applicative RFC 6455 §7.4.2) :
131
+ * - Origin refusée → code 4003 (`forbidden`).
132
+ * - `authenticate` throw → code 4001 (`unauthorized`).
133
+ */
134
+ private onHandshake;
135
+ /**
136
+ * Notifications entrantes : pub/sub (subscribe/unsubscribe), heartbeat (ping), puis
137
+ * canaux FULL-DUPLEX déclarés (entrée client → handler `realtimeInbound`).
138
+ */
139
+ private onRealtimeNotification;
140
+ /** Abonne la connexion à un canal via le hub partagé (idempotent par connexion). */
141
+ private startChannel;
142
+ /**
143
+ * Handler de la méthode RPC `api.request {path}` — le **pont API souverain**
144
+ * (POC Ph.3) : résout le path porté par le message via le Router
145
+ * (`cleanPathOverride` — l'URL de la connexion n'est jamais mutée) puis
146
+ * exécute l'action SANS la rendre (`executeAction`) : la valeur nue est
147
+ * enveloppée `{id, result}` par le peer — snapshot ≡ GET REST par
148
+ * construction.
149
+ *
150
+ * Query : le `?…` du path invoqué est parsé ici (paires plates, clés répétées
151
+ * → array — sémantique `qs` plate) et porté par `resolver.queryOverride`
152
+ * (per-invocation : zéro bleed entre requêtes concurrentes de la même
153
+ * socket). Limitation POC assumée : syntaxe nested `a[b]=c` non supportée
154
+ * par le pont (doc §11 — le parse complet du transport HTTP n'est pas
155
+ * mutualisé tant que Ph.6 n'a pas tranché).
156
+ *
157
+ * Erreurs → {@link RpcError} (`data.status` = statut HTTP équivalent) :
158
+ * `-32602` params invalides, 404 path inconnu, 408 timeout d'action…
159
+ * Tout autre throw de l'action reste opaque (`-32603`, Zero Trust).
160
+ */
161
+ private invokeApiRequest;
162
+ /** Désabonne la connexion d'un canal (le hub dispose le provider au dernier abonné). */
163
+ private stopChannel;
164
+ }
165
+ export default RealtimeController;