@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.
- package/README.md +55 -0
- package/dist/_virtual/{_@oxc-project_runtime@0.155.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorate.js +1 -1
- package/dist/index.js +91 -12
- package/dist/nodefony/config/config.js +7 -0
- package/dist/nodefony/decorators/realtimeDecorators.js +341 -43
- package/dist/nodefony/interfaces/IRealtimeConnection.js +1 -0
- package/dist/nodefony/src/backplane/ClusterBackplane.js +32 -4
- package/dist/nodefony/src/backplane/RedisBackplane.js +146 -41
- package/dist/nodefony/src/backplane/controlOrder.js +86 -0
- package/dist/nodefony/src/backplane/envelope.js +505 -27
- package/dist/nodefony/src/backplane/ingress.js +93 -0
- package/dist/nodefony/src/backplane/presenceMessage.js +205 -0
- package/dist/nodefony/src/backplane/publishQueue.js +97 -3
- 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 +135 -0
- package/dist/nodefony/src/server/RealtimeController.js +409 -107
- package/dist/nodefony/src/server/RealtimeHub.js +1158 -111
- 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 +95 -0
- package/dist/nodefony/src/server/channelPolicyResolution.js +191 -0
- package/dist/nodefony/src/server/channelPresence.js +488 -0
- package/dist/nodefony/src/server/connectionAdmission.js +242 -0
- package/dist/nodefony/src/server/contractRoutes.js +75 -0
- package/dist/nodefony/src/server/deniedDetail.js +10 -3
- package/dist/nodefony/src/server/frameAdmission.js +160 -0
- package/dist/nodefony/src/server/presenceBus.js +486 -0
- package/dist/nodefony/src/server/realtimeShutdown.js +31 -0
- package/dist/nodefony/src/server/remotePresence.js +541 -0
- package/dist/nodefony/src/server/subscribedAck.js +13 -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 +51 -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 +347 -0
- package/dist/nodefony/testing/index.js +43 -12
- package/dist/nodefony/testing/presenceClient.js +54 -0
- package/dist/types/index.d.ts +39 -7
- package/dist/types/nodefony/config/config.d.ts +9 -0
- package/dist/types/nodefony/decorators/realtimeDecorators.d.ts +198 -17
- package/dist/types/nodefony/interfaces/IBackplane.d.ts +46 -51
- 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 +89 -18
- package/dist/types/nodefony/src/backplane/controlOrder.d.ts +75 -0
- package/dist/types/nodefony/src/backplane/envelope.d.ts +77 -0
- package/dist/types/nodefony/src/backplane/ingress.d.ts +48 -0
- package/dist/types/nodefony/src/backplane/presenceMessage.d.ts +150 -0
- package/dist/types/nodefony/src/backplane/publishQueue.d.ts +25 -1
- 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 +20 -0
- package/dist/types/nodefony/src/server/RealtimeController.d.ts +110 -12
- package/dist/types/nodefony/src/server/RealtimeHub.d.ts +448 -42
- 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 +41 -0
- package/dist/types/nodefony/src/server/channelPolicyResolution.d.ts +112 -0
- package/dist/types/nodefony/src/server/channelPresence.d.ts +155 -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 +66 -0
- package/dist/types/nodefony/src/server/presenceBus.d.ts +112 -0
- package/dist/types/nodefony/src/server/realtimeShutdown.d.ts +21 -0
- package/dist/types/nodefony/src/server/remotePresence.d.ts +151 -0
- package/dist/types/nodefony/src/server/subscribedAck.d.ts +10 -0
- package/dist/types/nodefony/src/server/welcomeIdentity.d.ts +19 -0
- package/dist/types/nodefony/src/service/RealtimeService.d.ts +17 -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 +78 -0
- package/dist/types/nodefony/testing/index.d.ts +13 -2
- package/dist/types/nodefony/testing/presenceClient.d.ts +20 -0
- package/docs/actions.md +420 -18
- package/docs/architecture.md +101 -11
- package/docs/cadence-et-contre-pression.md +87 -11
- package/docs/canaux-a-membres.md +277 -0
- package/docs/cas-d-usage.md +1703 -0
- package/docs/configuration.md +175 -19
- package/docs/cookbook-chat.md +701 -224
- package/docs/index.md +11 -1
- package/docs/observabilite.md +97 -21
- package/docs/protocole.md +142 -33
- package/docs/securite.md +347 -41
- package/docs/vocabulaire.md +13 -12
- package/package.json +16 -13
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ordres de contrôle entre serveurs — la LISTE FERMÉE de ce qui circule sur le
|
|
3
|
+
* canal `PLATFORM_BUS_CHANNELS.control` (ADR-0022 §6 décision 4).
|
|
4
|
+
*
|
|
5
|
+
* Un ordre ne fait qu'ENLEVER : terminer un canal, éjecter un compte d'un
|
|
6
|
+
* canal. Aucun n'admet — seul `allow` fait entrer. Il voyage dans la charge
|
|
7
|
+
* d'un message ordinaire du bus, donc sous le sceau de l'enveloppe
|
|
8
|
+
* (`envelope.ts#sealBackplaneEnvelope`), sans champ neuf dans l'enveloppe ni
|
|
9
|
+
* méthode neuve dans `IBackplane` : un driver tiers le transporte sans le savoir.
|
|
10
|
+
*/
|
|
11
|
+
/** Longueur maximale d'un champ texte d'un ordre (nom de canal, identifiant de compte). */
|
|
12
|
+
export declare const MAX_CONTROL_FIELD_LENGTH = 1024;
|
|
13
|
+
/** Terminer un canal sur chaque serveur : ses membres reçoivent `realtime:closed { reason: "ended" }`. */
|
|
14
|
+
export interface ICloseChannelOrder {
|
|
15
|
+
/** Discriminant de l'ordre. */
|
|
16
|
+
readonly op: "close";
|
|
17
|
+
/** Nom exact du canal. */
|
|
18
|
+
readonly channel: string;
|
|
19
|
+
}
|
|
20
|
+
/** Éjecter d'un canal, sur chaque serveur, toutes les connexions d'un compte. */
|
|
21
|
+
export interface IEvictUserOrder {
|
|
22
|
+
/** Discriminant de l'ordre. */
|
|
23
|
+
readonly op: "evictUser";
|
|
24
|
+
/** Nom exact du canal. */
|
|
25
|
+
readonly channel: string;
|
|
26
|
+
/** Identifiant du compte (`IRealtimeConnection.userId`). */
|
|
27
|
+
readonly user: string;
|
|
28
|
+
/**
|
|
29
|
+
* Génération d'appartenance visée — RÉSERVÉE : aucun exemplaire 10.0 ne
|
|
30
|
+
* l'émet, et un exemplaire qui ne sait pas s'en servir l'ignore et éjecte
|
|
31
|
+
* toutes les générations (plus large, jamais moins). Elle portera en 10.x la
|
|
32
|
+
* parade au rejeu d'une éjection sur un canal durable (ADR-0022 §6 d4, S-rt-48).
|
|
33
|
+
*/
|
|
34
|
+
readonly generation?: number;
|
|
35
|
+
}
|
|
36
|
+
/** Un ordre de contrôle reconnu. */
|
|
37
|
+
export type ControlOrder = ICloseChannelOrder | IEvictUserOrder;
|
|
38
|
+
/**
|
|
39
|
+
* Un texte venu du bus est-il recevable — chaîne non vide d'au plus {@link MAX_CONTROL_FIELD_LENGTH} caractères ?
|
|
40
|
+
*
|
|
41
|
+
* Seule borne des textes du bus (nom de canal, identifiant de compte), lue par
|
|
42
|
+
* les ordres de contrôle ET par les messages de présence (ADR-0025 P2).
|
|
43
|
+
*
|
|
44
|
+
* @param value - champ reçu (forme inconnue).
|
|
45
|
+
*/
|
|
46
|
+
export declare function isBoundedBusText(value: unknown): value is string;
|
|
47
|
+
/**
|
|
48
|
+
* La charge est-elle un ordre d'un TYPE que cette version ne connaît pas — `op` lisible, mais hors de la liste fermée ?
|
|
49
|
+
*
|
|
50
|
+
* C'est ce qu'émet un serveur d'une version plus récente pendant un
|
|
51
|
+
* déploiement progressif (ADR-0025, « Règle de lecture du bus ») : l'ordre
|
|
52
|
+
* n'est pas appliqué, mais il ne se compte pas comme une injection. Rien
|
|
53
|
+
* d'autre que `op` n'est lu — ses autres champs appartiennent à une version
|
|
54
|
+
* qui n'est pas celle-ci. Une charge sans `op` textuel, ou d'un type CONNU de
|
|
55
|
+
* forme fausse, n'est pas « inconnue » : elle est invalide.
|
|
56
|
+
*
|
|
57
|
+
* @param payload - la charge du message reçu (forme inconnue).
|
|
58
|
+
* @returns `true` si le type d'ordre est inconnu de cette version.
|
|
59
|
+
*/
|
|
60
|
+
export declare function isUnknownControlOrder(payload: unknown): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Lit un ordre de contrôle dans une charge venue du bus — `null` si la forme n'est pas celle d'un ordre connu.
|
|
63
|
+
*
|
|
64
|
+
* Une seule garde pour les deux bords : l'émetteur la passe avant de publier
|
|
65
|
+
* (il n'émet jamais ce que ses pairs refuseraient), le récepteur avant
|
|
66
|
+
* d'appliquer. Lecture par champs PROPRES (`ownField`) : une charge dont le
|
|
67
|
+
* prototype porterait `op` ne passe pas. Un champ inconnu est toléré — c'est
|
|
68
|
+
* ce qui laisse une version suivante ajouter un champ sans faire refuser ses
|
|
69
|
+
* ordres pendant un déploiement progressif ; un champ CONNU de forme fausse
|
|
70
|
+
* fait refuser l'ordre entier.
|
|
71
|
+
*
|
|
72
|
+
* @param payload - la charge du message reçu (forme inconnue).
|
|
73
|
+
* @returns l'ordre, ou `null`.
|
|
74
|
+
*/
|
|
75
|
+
export declare function readControlOrder(payload: unknown): ControlOrder | null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type IFrameCodec } from "nodefony";
|
|
1
2
|
import type { IBackplaneMessage } from "../../interfaces/IBackplane.js";
|
|
2
3
|
/**
|
|
3
4
|
* Sérialise un message backplane, scellé si un secret est fourni.
|
|
@@ -19,9 +20,85 @@ export declare function sealBackplaneEnvelope(msg: IBackplaneMessage, secret?: s
|
|
|
19
20
|
* `sig`. Sans secret, l'enveloppe nue est acceptée (compat ; l'alerte de boot qui
|
|
20
21
|
* signale ce mode non authentifié est portée par le wiring du driver).
|
|
21
22
|
*
|
|
23
|
+
* Le sceau se vérifie sur la tranche REÇUE de la charge quand l'enveloppe a la
|
|
24
|
+
* forme qu'écrit {@link sealBackplaneEnvelope} — aucune re-sérialisation ; une
|
|
25
|
+
* autre forme retombe sur la re-sérialisation de la charge lue.
|
|
26
|
+
*
|
|
22
27
|
* @param raw - message brut lu sur le bus.
|
|
23
28
|
* @param secret - secret partagé attendu, ou `null`/`undefined` si aucun.
|
|
24
29
|
* @returns le message vérifié, ou `null` s'il doit être ignoré (malformé, non
|
|
25
30
|
* scellé alors qu'un secret est exigé, sceau invalide).
|
|
26
31
|
*/
|
|
27
32
|
export declare function openBackplaneEnvelope(raw: string, secret?: string | null): IBackplaneMessage | null;
|
|
33
|
+
/**
|
|
34
|
+
* Encode un message backplane pour le **sujet binaire** du bus (ADR-0021 S6) :
|
|
35
|
+
* la MÊME enveloppe `{ channel, payload, originId }`, encodée par le codec
|
|
36
|
+
* binaire du serveur, octets natifs dans `payload`, scellée si un secret est
|
|
37
|
+
* fourni.
|
|
38
|
+
*
|
|
39
|
+
* Scellée, la charge est encodée UNE fois : le sceau se calcule sur ses octets,
|
|
40
|
+
* et l'enveloppe s'assemble autour d'eux. Un codec qui n'écrit pas le CBOR le plus
|
|
41
|
+
* court (constaté, cf {@link framesAsCbor}) encode l'enveloppe entière une seconde
|
|
42
|
+
* fois — correct pour tout format, plus coûteux.
|
|
43
|
+
*
|
|
44
|
+
* @param msg - message à transporter (canal, charge, origine).
|
|
45
|
+
* @param codec - le codec binaire du serveur (`CBOR_FRAME_CODEC` de `nodefony/cbor`).
|
|
46
|
+
* @param secret - secret partagé entre pods ; `null`/`undefined` → enveloppe nue.
|
|
47
|
+
* @returns les octets à publier sur le sujet binaire.
|
|
48
|
+
* @throws TypeError si le codec rend du texte.
|
|
49
|
+
*/
|
|
50
|
+
export declare function sealBackplaneEnvelopeBytes(msg: IBackplaneMessage, codec: IFrameCodec, secret?: string | null): Uint8Array;
|
|
51
|
+
/**
|
|
52
|
+
* Décode et **vérifie** une enveloppe reçue sur le sujet binaire du bus — même
|
|
53
|
+
* règle que {@link openBackplaneEnvelope} : fail-closed strict sous un secret,
|
|
54
|
+
* aucun downgrade en retirant `sig`.
|
|
55
|
+
*
|
|
56
|
+
* Le sceau se vérifie sur la tranche REÇUE de la charge quand l'enveloppe a la
|
|
57
|
+
* forme qu'assemble l'émetteur — aucun ré-encodage ; une autre forme retombe sur
|
|
58
|
+
* le ré-encodage de la charge décodée (le codec CBOR du cœur rend la forme la plus
|
|
59
|
+
* courte de chaque valeur, donc émetteur et récepteur signent les mêmes octets).
|
|
60
|
+
*
|
|
61
|
+
* @param raw - octets lus sur le sujet binaire.
|
|
62
|
+
* @param codec - le codec binaire du serveur.
|
|
63
|
+
* @param secret - secret partagé attendu, ou `null`/`undefined` si aucun.
|
|
64
|
+
* @returns le message vérifié, ou `null` s'il doit être ignoré.
|
|
65
|
+
*/
|
|
66
|
+
export declare function openBackplaneEnvelopeBytes(raw: Uint8Array, codec: IFrameCodec, secret?: string | null): IBackplaneMessage | null;
|
|
67
|
+
/**
|
|
68
|
+
* Reconnaît l'**écho** d'un pod — ce que le bus lui renvoie de ses propres
|
|
69
|
+
* publications — à la FIN du message, sans le décoder (ADR-0021 S6, ADR-0025).
|
|
70
|
+
*
|
|
71
|
+
* Un bus pub/sub comme Redis rend à l'émetteur chacune de ses publications : sur un
|
|
72
|
+
* pod chargé, c'est un message reçu sur deux, et la présence répartie en fait un
|
|
73
|
+
* signal (le bus est vivant tant que l'écho revient). Le reconnaître coûte, sous un
|
|
74
|
+
* secret, UN HMAC sur la tranche de la charge : ni `JSON.parse`, ni décodage CBOR.
|
|
75
|
+
*
|
|
76
|
+
* Sous un secret, l'écho est VÉRIFIÉ avant d'être compté : un tiers qui forgerait
|
|
77
|
+
* l'origine de ce pod pour simuler un bus vivant échoue au sceau. Sans secret, rien
|
|
78
|
+
* ne peut l'être — le bus est ouvert, et le wiring l'annonce au démarrage.
|
|
79
|
+
*
|
|
80
|
+
* Un message que ce lecteur ne reconnaît pas (forme tierce, sceau faux) n'est PAS
|
|
81
|
+
* jeté : il suit la voie ordinaire, qui le vérifie en entier.
|
|
82
|
+
*/
|
|
83
|
+
export declare class BackplaneEcho {
|
|
84
|
+
#private;
|
|
85
|
+
/**
|
|
86
|
+
* @param originId - identité de CE pod sur le bus.
|
|
87
|
+
* @param secret - secret partagé, ou `null` sur un bus non scellé.
|
|
88
|
+
*/
|
|
89
|
+
constructor(originId: string, secret: string | null);
|
|
90
|
+
/**
|
|
91
|
+
* Ce message texte est-il l'écho de ce pod (sceau vérifié sous un secret) ?
|
|
92
|
+
*
|
|
93
|
+
* @param raw - message brut lu sur le sujet texte.
|
|
94
|
+
*/
|
|
95
|
+
text(raw: string): boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Ce message binaire est-il l'écho de ce pod (sceau vérifié sous un secret) ?
|
|
98
|
+
*
|
|
99
|
+
* @param raw - octets lus sur le sujet binaire.
|
|
100
|
+
* @param codec - le codec binaire du serveur ; un codec qui n'écrit pas le CBOR
|
|
101
|
+
* attendu (cf {@link framesAsCbor}) laisse tout message à la voie ordinaire.
|
|
102
|
+
*/
|
|
103
|
+
bytes(raw: Uint8Array, codec: IFrameCodec): boolean;
|
|
104
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { IFrameCodec } from "nodefony";
|
|
2
|
+
import type { IBackplaneMessage } from "../../interfaces/IBackplane.js";
|
|
3
|
+
import type { BackplaneNotice } from "./publishQueue.js";
|
|
4
|
+
/**
|
|
5
|
+
* **Entrée d'un bus partagé** — le tri de ce qu'un pilote reçoit, mutualisé pour
|
|
6
|
+
* que tout pilote à transport réseau (Redis aujourd'hui ; Kafka, NATS, RabbitMQ…
|
|
7
|
+
* demain) l'hérite sans le réécrire : écho de ce pod reconnu et compté, sceau
|
|
8
|
+
* vérifié, refus comptés et annoncés.
|
|
9
|
+
*
|
|
10
|
+
* Le pilote ne garde que son transport : il passe chaque message reçu à
|
|
11
|
+
* {@link text} ou {@link bytes} et remet au hub ce qui en sort.
|
|
12
|
+
*
|
|
13
|
+
* Ordre du tri, du moins cher au plus cher :
|
|
14
|
+
* 1. l'ÉCHO de ce pod se reconnaît à la fin du message, sans le décoder — un HMAC
|
|
15
|
+
* sous un secret (cf {@link BackplaneEcho}) ;
|
|
16
|
+
* 2. sinon, l'enveloppe est ouverte et son sceau vérifié sur la tranche reçue ;
|
|
17
|
+
* 3. un message ouvert qui porte l'origine de ce pod sous une autre forme est
|
|
18
|
+
* encore un écho — compté, jamais réinjecté (anti-boucle).
|
|
19
|
+
*/
|
|
20
|
+
export declare class BackplaneIngress {
|
|
21
|
+
#private;
|
|
22
|
+
/**
|
|
23
|
+
* @param originId - identité de CE pod sur le bus.
|
|
24
|
+
* @param secret - secret partagé, ou `null` sur un bus non scellé.
|
|
25
|
+
* @param notice - annonce des sceaux refusés ; omis = silencieux (tests).
|
|
26
|
+
*/
|
|
27
|
+
constructor(originId: string, secret: string | null, notice?: BackplaneNotice | null);
|
|
28
|
+
/**
|
|
29
|
+
* Trie un message reçu sur le sujet TEXTE du bus.
|
|
30
|
+
*
|
|
31
|
+
* @param raw - message brut tel que lu sur le transport.
|
|
32
|
+
* @returns le message à remettre au hub, ou `null` s'il est jeté (écho de ce
|
|
33
|
+
* pod, malformé, sceau refusé — chacun compté).
|
|
34
|
+
*/
|
|
35
|
+
text(raw: string): IBackplaneMessage | null;
|
|
36
|
+
/**
|
|
37
|
+
* Trie un message reçu sur le sujet BINAIRE du bus (ADR-0021 S6).
|
|
38
|
+
*
|
|
39
|
+
* @param raw - octets tels que lus sur le transport.
|
|
40
|
+
* @param codec - le codec binaire du serveur.
|
|
41
|
+
* @returns le message à remettre au hub, ou `null` s'il est jeté.
|
|
42
|
+
*/
|
|
43
|
+
bytes(raw: Uint8Array, codec: IFrameCodec): IBackplaneMessage | null;
|
|
44
|
+
/** Échos de ce pod reçus du bus (monotone) — le bus rend ce qu'on y publie. */
|
|
45
|
+
get echoTotal(): number;
|
|
46
|
+
/** Messages jetés au sceau refusé, sous un secret (monotone). */
|
|
47
|
+
get sealRejectedTotal(): number;
|
|
48
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { PresencePairs } from "nodefony";
|
|
2
|
+
/**
|
|
3
|
+
* Messages de la présence répartie entre serveurs — la LISTE FERMÉE de ce qui
|
|
4
|
+
* circule sur `PLATFORM_BUS_CHANNELS.presence` (lots, demandes, réponses d'état)
|
|
5
|
+
* et `PLATFORM_BUS_CHANNELS.presenceAlive` (preuve de vie), ADR-0025 P2.
|
|
6
|
+
*
|
|
7
|
+
* Comme un ordre de contrôle (`controlOrder.ts#readControlOrder`), chaque
|
|
8
|
+
* message voyage dans la charge d'un message ordinaire du bus, sous le sceau de
|
|
9
|
+
* l'enveloppe : aucun champ neuf dans l'enveloppe, aucune méthode neuve dans
|
|
10
|
+
* `IBackplane`. L'émetteur (`originId`) se lit dans l'enveloppe, jamais dans la
|
|
11
|
+
* charge.
|
|
12
|
+
*
|
|
13
|
+
* Lecture par champs PROPRES (`ownField`), textes bornés (`isBoundedBusText`),
|
|
14
|
+
* entiers sûrs ; un champ inconnu est toléré (déploiement progressif), un champ
|
|
15
|
+
* connu de forme fausse fait refuser le message entier. Les tableaux rendus
|
|
16
|
+
* sont CEUX reçus, validés en place — aucune copie, aucune allocation par
|
|
17
|
+
* membre : le lecteur ne les modifie pas, l'appelant non plus.
|
|
18
|
+
*/
|
|
19
|
+
/** Plus petit intervalle de preuve de vie qu'un pair peut annoncer (ms). */
|
|
20
|
+
export declare const MIN_PRESENCE_INTERVAL_MS = 100;
|
|
21
|
+
/** Plus grand intervalle de preuve de vie qu'un pair peut annoncer (ms) — 10 min. */
|
|
22
|
+
export declare const MAX_PRESENCE_INTERVAL_MS = 600000;
|
|
23
|
+
/**
|
|
24
|
+
* Plafond d'identités nommées qu'un pod peut annoncer pour UN canal — au-delà,
|
|
25
|
+
* l'entrée est refusée (ADR-0025 P2, « plafond de membres par pod et par canal »).
|
|
26
|
+
*/
|
|
27
|
+
export declare const MAX_PRESENCE_MEMBERS = 100000;
|
|
28
|
+
/** Plafond d'entrées de canal dans un même message (lot, demande ou part de réponse). */
|
|
29
|
+
export declare const MAX_PRESENCE_CHANNELS = 4096;
|
|
30
|
+
/** Plafond de parts d'une réponse d'état (`n`). */
|
|
31
|
+
export declare const MAX_PRESENCE_PARTS = 4096;
|
|
32
|
+
/**
|
|
33
|
+
* Changements d'un pod sur UN canal, en valeurs ABSOLUES : rejouer une entrée
|
|
34
|
+
* déjà comprise dans un état ne change rien.
|
|
35
|
+
*/
|
|
36
|
+
export interface IPresenceChannelDelta {
|
|
37
|
+
/** Nom exact du canal. */
|
|
38
|
+
readonly channel: string;
|
|
39
|
+
/** Identités dont le NOMBRE d'onglets sur ce pod change, à plat `[identité, onglets, …]` (onglets ≥ 1) ; `null` si aucune. */
|
|
40
|
+
readonly set: PresencePairs | null;
|
|
41
|
+
/** Identités qui n'ont plus aucun onglet sur ce pod ; `null` si aucune. */
|
|
42
|
+
readonly gone: readonly string[] | null;
|
|
43
|
+
/** Connexions non nommées de ce pod sur ce canal — valeur absolue. */
|
|
44
|
+
readonly unnamed: number;
|
|
45
|
+
}
|
|
46
|
+
/** État COMPLET d'un pod sur un canal, dans une réponse d'état. */
|
|
47
|
+
export interface IPresenceChannelState {
|
|
48
|
+
/** Nom exact du canal. */
|
|
49
|
+
readonly channel: string;
|
|
50
|
+
/** Toutes les identités nommées de ce pod, à plat `[identité, onglets, …]` (onglets ≥ 1). */
|
|
51
|
+
readonly members: PresencePairs;
|
|
52
|
+
/** Connexions non nommées de ce pod sur ce canal. */
|
|
53
|
+
readonly unnamed: number;
|
|
54
|
+
}
|
|
55
|
+
/** Lot de changements d'un pod, scellé toutes les 50 ms, chaîné par `prev`. */
|
|
56
|
+
export interface IPresenceBatch {
|
|
57
|
+
/** Discriminant. */
|
|
58
|
+
readonly op: "batch";
|
|
59
|
+
/** Vie du processus émetteur (`Date.now() × 1024 + aléa sur 10 bits`). */
|
|
60
|
+
readonly incarnation: number;
|
|
61
|
+
/** Numéro du message, strictement croissant par (`originId`, incarnation), ≥ 1. */
|
|
62
|
+
readonly seq: number;
|
|
63
|
+
/** `seq` du lot précédent de l'émetteur, `0` pour le premier ; `< seq`. */
|
|
64
|
+
readonly prev: number;
|
|
65
|
+
/** Changements par canal. */
|
|
66
|
+
readonly channels: readonly IPresenceChannelDelta[];
|
|
67
|
+
}
|
|
68
|
+
/** Demande d'état — le *fetch* de RFC 2778 §2.1, la *presence probe* de RFC 6121 §4.3. */
|
|
69
|
+
export interface IPresenceRequest {
|
|
70
|
+
/** Discriminant. */
|
|
71
|
+
readonly op: "request";
|
|
72
|
+
/** Vie du processus demandeur. */
|
|
73
|
+
readonly incarnation: number;
|
|
74
|
+
/** Numéro du message (même suite que les lots du demandeur). */
|
|
75
|
+
readonly seq: number;
|
|
76
|
+
/** Canaux dont l'état est demandé. */
|
|
77
|
+
readonly channels: readonly string[];
|
|
78
|
+
/**
|
|
79
|
+
* Pod visé (`originId`) — il répond TOUJOURS, même vide (RFC 6121 §4.3.2) ;
|
|
80
|
+
* `null` = demande non ciblée, servie par les seuls pods qui ont des présents.
|
|
81
|
+
*/
|
|
82
|
+
readonly to: string | null;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Une part d'une réponse d'état. Les `n` parts d'une réponse partent d'un bloc,
|
|
86
|
+
* à `seq` consécutifs (`seq` de la part `i` = `seq` de la part 0 + `i`), et
|
|
87
|
+
* s'appliquent d'un bloc : une part perdue laisse la vue inchangée.
|
|
88
|
+
*/
|
|
89
|
+
export interface IPresenceStatePart {
|
|
90
|
+
/** Discriminant. */
|
|
91
|
+
readonly op: "state";
|
|
92
|
+
/** Vie du processus qui répond. */
|
|
93
|
+
readonly incarnation: number;
|
|
94
|
+
/** Numéro du message. */
|
|
95
|
+
readonly seq: number;
|
|
96
|
+
/** `seq` du dernier lot émis avant la réponse — il ferme le trou (RFC 5263 §4.5) ; `< seq`. */
|
|
97
|
+
readonly last: number;
|
|
98
|
+
/** Rang de la part, `0 ≤ i < n`. */
|
|
99
|
+
readonly i: number;
|
|
100
|
+
/** Nombre de parts de la réponse, `1 ≤ n ≤ MAX_PRESENCE_PARTS`. */
|
|
101
|
+
readonly n: number;
|
|
102
|
+
/** États complets par canal (un canal peut s'étendre sur plusieurs parts). */
|
|
103
|
+
readonly channels: readonly IPresenceChannelState[];
|
|
104
|
+
}
|
|
105
|
+
/** Message lu sur `PLATFORM_BUS_CHANNELS.presence`. */
|
|
106
|
+
export type PresenceMessage = IPresenceBatch | IPresenceRequest | IPresenceStatePart;
|
|
107
|
+
/** Preuve de vie, lue sur `PLATFORM_BUS_CHANNELS.presenceAlive` — quelques dizaines d'octets, indépendante du nombre de canaux. */
|
|
108
|
+
export interface IPresenceAlive {
|
|
109
|
+
/** Vie du processus émetteur. */
|
|
110
|
+
readonly incarnation: number;
|
|
111
|
+
/** Numéro du message (même suite que les lots). */
|
|
112
|
+
readonly seq: number;
|
|
113
|
+
/** `seq` du dernier lot émis, `0` si aucun ; `< seq`. */
|
|
114
|
+
readonly last: number;
|
|
115
|
+
/** Intervalle de l'émetteur (ms), ANNONCÉ : il fixe le délai d'oubli chez les pairs (RFC 6665 §3.1.1). */
|
|
116
|
+
readonly interval: number;
|
|
117
|
+
/** `true` = arrêt propre : les pairs l'oublient aussitôt (RFC 6121 §4.5.1). */
|
|
118
|
+
readonly final: boolean;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Lit un message du canal de présence — `null` si sa forme n'est pas celle d'un message connu.
|
|
122
|
+
*
|
|
123
|
+
* Une seule garde pour les deux bords : l'émetteur la passe en test (il n'émet
|
|
124
|
+
* jamais ce que ses pairs refuseraient), le récepteur avant d'appliquer.
|
|
125
|
+
*
|
|
126
|
+
* @param payload - la charge du message reçu (forme inconnue).
|
|
127
|
+
* @returns le message, ou `null` (forme invalide, ou type inconnu : cf
|
|
128
|
+
* {@link isUnknownPresenceMessage} pour les distinguer).
|
|
129
|
+
*/
|
|
130
|
+
export declare function readPresenceMessage(payload: unknown): PresenceMessage | null;
|
|
131
|
+
/**
|
|
132
|
+
* La charge est-elle un message de présence d'un TYPE que cette version ne connaît pas — `op` lisible, hors de la liste fermée ?
|
|
133
|
+
*
|
|
134
|
+
* C'est ce qu'émet un serveur plus récent pendant un déploiement progressif :
|
|
135
|
+
* jeté et compté dans `ingressUnknownDiscardedTotal`, jamais comme une
|
|
136
|
+
* injection (ADR-0025, « Règle de lecture du bus »).
|
|
137
|
+
*
|
|
138
|
+
* @param payload - la charge du message reçu (forme inconnue).
|
|
139
|
+
*/
|
|
140
|
+
export declare function isUnknownPresenceMessage(payload: unknown): boolean;
|
|
141
|
+
/**
|
|
142
|
+
* Lit une preuve de vie — `null` si sa forme est fausse.
|
|
143
|
+
*
|
|
144
|
+
* `interval` hors de [{@link MIN_PRESENCE_INTERVAL_MS}, {@link MAX_PRESENCE_INTERVAL_MS}]
|
|
145
|
+
* est refusé : un pair qui annoncerait 1 ms se ferait oublier à chaque tic, un
|
|
146
|
+
* qui annoncerait une heure garderait ses fantômes une demi-journée.
|
|
147
|
+
*
|
|
148
|
+
* @param payload - la charge du message reçu (forme inconnue).
|
|
149
|
+
*/
|
|
150
|
+
export declare function readPresenceAlive(payload: unknown): IPresenceAlive | null;
|
|
@@ -24,6 +24,18 @@
|
|
|
24
24
|
* Ce qui est jeté est le message **nouveau**, pas le plus ancien : contrairement au
|
|
25
25
|
* WS (drop latest-wins sur une file qu'on possède), ce qui est déjà remis au client
|
|
26
26
|
* réseau ne peut plus être repris.
|
|
27
|
+
*
|
|
28
|
+
* **Les ordres de contrôle ne sont JAMAIS jetés** (#762) : terminer un canal ou
|
|
29
|
+
* éjecter un compte passe hors quota, même file saturée. Sinon, sous charge, un
|
|
30
|
+
* modérateur éjecte un compte sur un serveur et ce compte continue de lire sur un
|
|
31
|
+
* autre, sans que personne le sache. Les ordres sont rares et petits (une décision
|
|
32
|
+
* du serveur, quelques dizaines d'octets) : les exempter ne rouvre pas la mémoire.
|
|
33
|
+
* La **preuve de vie** de la présence répartie passe aussi hors quota (ADR-0025
|
|
34
|
+
* P2) : jetée, elle ferait oublier un pod vivant chez ses pairs ; elle est petite
|
|
35
|
+
* et d'intervalle borné. Liste des canaux prioritaires : `isPriorityBusChannel`
|
|
36
|
+
* (cœur), seule source.
|
|
37
|
+
* Un ordre que le bus REFUSE malgré tout (bus injoignable) est compté à part
|
|
38
|
+
* (`controlLostTotal`) et annoncé une fois par épisode.
|
|
27
39
|
*/
|
|
28
40
|
import type { IBackplaneQueueInfo } from "../../interfaces/IBackplane.js";
|
|
29
41
|
/** Sévérité d'une transition de la file — mappée telle quelle sur le syslog. */
|
|
@@ -59,12 +71,24 @@ export declare class BackplanePublishQueue {
|
|
|
59
71
|
* (sinon elle ne partirait jamais — famine silencieuse), quitte à dépasser
|
|
60
72
|
* transitoirement. Même règle que le drop WS, qui teste `bufferedAmount` seul.
|
|
61
73
|
*
|
|
74
|
+
* Un canal prioritaire (`isPriorityBusChannel` du cœur : ordre de contrôle,
|
|
75
|
+
* preuve de vie de la présence) est admis hors quota : la file l'applique, pas
|
|
76
|
+
* le driver, pour qu'un driver qui emploie la file la porte sans y penser.
|
|
77
|
+
*
|
|
78
|
+
* @param channel - canal logique de la publication.
|
|
62
79
|
* @param bytes - taille de la charge sérialisée.
|
|
63
80
|
* @param emit - envoi réel ; appelé UNIQUEMENT si la publication est admise.
|
|
64
81
|
* Son retour est suivi s'il s'agit d'une promesse (transport asynchrone).
|
|
65
82
|
* @returns `true` si la publication est partie, `false` si elle a été jetée.
|
|
66
83
|
*/
|
|
67
|
-
send(bytes: number, emit: () => unknown): boolean;
|
|
84
|
+
send(channel: string, bytes: number, emit: () => unknown): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Attend que toutes les publications en vol soient acquittées ou refusées par le bus — à l'arrêt, pour que ce qui est déjà émis (la preuve de vie finale de la présence) parte avant la sortie du processus.
|
|
87
|
+
*
|
|
88
|
+
* Ne borne rien : un bus qui ne répond plus ne la résout jamais. L'appelant
|
|
89
|
+
* pose la limite (l'arrêt du noyau a la sienne).
|
|
90
|
+
*/
|
|
91
|
+
settled(): Promise<void>;
|
|
68
92
|
/** Compteurs pour la carte d'identité du backplane (sonde + Studio). */
|
|
69
93
|
describe(): IBackplaneQueueInfo;
|
|
70
94
|
}
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
+
import type { ISubscriberIdentity } from "nodefony";
|
|
1
2
|
import type { IRealtimeToken } from "../../interfaces/IRealtimeToken.js";
|
|
3
|
+
/**
|
|
4
|
+
* Identité d'admission de l'ANONYME — gelée et partagée par toutes les connexions non authentifiées (0 allocation par connexion).
|
|
5
|
+
*
|
|
6
|
+
* Porte l'invariant de l'ADR-0021 §S10 : anonyme ⇔ `authenticated === false`,
|
|
7
|
+
* `id === null` et `issuer === null`. Un canal qui déclare `allow` refuse
|
|
8
|
+
* cette identité d'office, sauf option explicite `anonymous: true` — ce qui
|
|
9
|
+
* garantit que `user:${who.id}` ne vaut jamais `"user:null"`.
|
|
10
|
+
*/
|
|
11
|
+
export declare const ANONYMOUS_SUBSCRIBER_IDENTITY: ISubscriberIdentity;
|
|
2
12
|
/**
|
|
3
13
|
* Token realtime ANONYME — fallback Zero Trust quand aucun matcher
|
|
4
14
|
* `IRealtimeAuthenticatorMatcher` ne capture le handshake.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { ContractActionNames, ContractClientEmit, ContractServerActions, ContractServerEmit, ContractServerListen, ContractServerMethods, EventNames, IRealtimeContract, TypedRpcActionHandler } from "nodefony";
|
|
2
|
+
import { RealtimeController } from "./RealtimeController.js";
|
|
3
|
+
/**
|
|
4
|
+
* Clé de l'étiquette de type qui relie une classe à son contrat. Déclaration
|
|
5
|
+
* ambiante : aucun symbole n'existe à l'exécution, seul le compilateur la voit.
|
|
6
|
+
*/
|
|
7
|
+
declare const contractType: unique symbol;
|
|
8
|
+
/**
|
|
9
|
+
* Table COMPLÈTE des méthodes que le serveur doit au contrat : une entrée par nom
|
|
10
|
+
* de `serverMethods`, chacune recevant ses paramètres lus par `Jsonify` et rendant
|
|
11
|
+
* le résultat déclaré. Une méthode oubliée, un nom hors contrat ou une forme
|
|
12
|
+
* fausse ne compilent pas.
|
|
13
|
+
*/
|
|
14
|
+
export type ContractRealtimeMethodHandlers<C extends IRealtimeContract> = {
|
|
15
|
+
[K in ContractActionNames<ContractServerMethods<C>>]: TypedRpcActionHandler<ContractServerMethods<C>, K>;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Noms qu'un contrat déclare et qu'une politique peut garder : ses méthodes
|
|
19
|
+
* serveur et les canaux de ses deux directions. Un canal déclaré par gabarit
|
|
20
|
+
* (`user:${string}`) admet aussi son motif (`user:*`). Direction non déclarée :
|
|
21
|
+
* tout nom.
|
|
22
|
+
*/
|
|
23
|
+
export type ContractPolicyNames<C extends IRealtimeContract> = ContractActionNames<ContractServerMethods<C>> | EventNames<ContractServerEmit<C>> | EventNames<ContractClientEmit<C>>;
|
|
24
|
+
/**
|
|
25
|
+
* Le contrat qu'une classe de contrôleur implémente, lu sur son étiquette de
|
|
26
|
+
* type ; `never` pour un contrôleur sans contrat.
|
|
27
|
+
*/
|
|
28
|
+
export type RealtimeContractOf<T> = T extends abstract new (...args: never[]) => {
|
|
29
|
+
readonly [contractType]?: infer C;
|
|
30
|
+
} ? unknown extends C ? never : C extends IRealtimeContract ? C : never : never;
|
|
31
|
+
/**
|
|
32
|
+
* Socle commun des deux formes de {@link ContractRealtimeController} : l'étiquette
|
|
33
|
+
* de contrat et les canaux annoncés. Les actions y gardent le défaut de la base
|
|
34
|
+
* (`{}`) — un contrat SANS `serverMethods` n'a rien à écrire.
|
|
35
|
+
*
|
|
36
|
+
* Ne s'étend pas directement : on étend {@link ContractRealtimeController}, qui
|
|
37
|
+
* choisit cette forme ou la forme stricte selon le contrat.
|
|
38
|
+
*
|
|
39
|
+
* @typeParam C - le contrat de l'application.
|
|
40
|
+
*/
|
|
41
|
+
export declare abstract class ContractRealtimeControllerBase<C extends IRealtimeContract> extends RealtimeController<ContractServerEmit<C>, ContractServerActions<C>, ContractServerListen<C>> {
|
|
42
|
+
/**
|
|
43
|
+
* Étiquette de TYPE : relie la classe à son contrat pour que `@RealtimePolicy`
|
|
44
|
+
* le retrouve. `declare` — aucun champ n'existe à l'exécution.
|
|
45
|
+
*/
|
|
46
|
+
readonly [contractType]?: C;
|
|
47
|
+
/** Canaux annoncés au handshake, contraints aux noms de `serverToClient`. */
|
|
48
|
+
protected realtimeChannels(): EventNames<ContractServerEmit<C>>[];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Forme stricte de {@link ContractRealtimeController}, retenue quand le contrat
|
|
52
|
+
* déclare des `serverMethods` : `realtimeActions()` y devient ABSTRAITE et typée
|
|
53
|
+
* par le contrat — la seule construction qui fait d'une méthode OUBLIÉE une
|
|
54
|
+
* erreur de compilation (un décorateur ne voit jamais la classe entière).
|
|
55
|
+
*
|
|
56
|
+
* Ne s'étend pas directement : on étend {@link ContractRealtimeController}.
|
|
57
|
+
*
|
|
58
|
+
* @typeParam C - le contrat de l'application.
|
|
59
|
+
*/
|
|
60
|
+
export declare abstract class ContractRealtimeControllerStrict<C extends IRealtimeContract> extends ContractRealtimeControllerBase<C> {
|
|
61
|
+
/**
|
|
62
|
+
* Les méthodes du contrat servies par ce contrôleur — toutes, et par cette
|
|
63
|
+
* table SEULE : une action servie ici ne se décore pas en plus de
|
|
64
|
+
* `@RealtimeAction` (handler fabriqué puis écrasé à chaque connexion). Sa
|
|
65
|
+
* politique se pose sur la classe, par `@RealtimePolicy`.
|
|
66
|
+
*
|
|
67
|
+
* Appelée à chaque connexion. Des handlers qui n'ont pas besoin de `this` se
|
|
68
|
+
* rangent dans une table CONSTANTE du module, rendue telle quelle : aucune
|
|
69
|
+
* allocation par connexion.
|
|
70
|
+
*/
|
|
71
|
+
protected abstract realtimeActions(): ContractRealtimeMethodHandlers<C>;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Vrai quand le contrat `C` ne déclare aucune méthode serveur. Le tuple empêche
|
|
75
|
+
* la distribution du conditionnel sur `never`.
|
|
76
|
+
*/
|
|
77
|
+
type HasNoServerMethods<C extends IRealtimeContract> = [
|
|
78
|
+
ContractActionNames<ContractServerMethods<C>>
|
|
79
|
+
] extends [never] ? true : false;
|
|
80
|
+
/**
|
|
81
|
+
* L'instance d'un contrôleur de contrat : forme stricte si `C` déclare des
|
|
82
|
+
* `serverMethods`, socle sinon.
|
|
83
|
+
*
|
|
84
|
+
* @typeParam C - le contrat de l'application.
|
|
85
|
+
*/
|
|
86
|
+
export type ContractRealtimeController<C extends IRealtimeContract> = HasNoServerMethods<C> extends true ? ContractRealtimeControllerBase<C> : ContractRealtimeControllerStrict<C>;
|
|
87
|
+
/**
|
|
88
|
+
* Le constructeur de {@link ContractRealtimeController} : celui de la forme
|
|
89
|
+
* stricte, dont l'instance se lit par le contrat. Les statiques (`scope`,
|
|
90
|
+
* `assertScope`) restent ceux de la classe.
|
|
91
|
+
*/
|
|
92
|
+
type ContractRealtimeControllerConstructor = Omit<typeof ContractRealtimeControllerStrict, "prototype"> & (abstract new <C extends IRealtimeContract>(...args: ConstructorParameters<typeof RealtimeController>) => ContractRealtimeController<C>);
|
|
93
|
+
/**
|
|
94
|
+
* Base d'un contrôleur qui IMPLÉMENTE un contrat temps réel — la porte du
|
|
95
|
+
* serveur. Le contrat s'écrit UNE fois, sur `extends` ; la direction du serveur
|
|
96
|
+
* en est dérivée (il émet `serverToClient`, écoute `clientToServer`, appelle
|
|
97
|
+
* `clientMethods`) et il DOIT servir chaque méthode de `serverMethods`.
|
|
98
|
+
*
|
|
99
|
+
* Quand le contrat déclare des `serverMethods`, `realtimeActions()` est
|
|
100
|
+
* abstraite et typée par lui : une méthode OUBLIÉE ne compile pas. Un contrat
|
|
101
|
+
* SANS `serverMethods` (tableau de bord, chat qui ne fait que diffuser) n'a rien
|
|
102
|
+
* à écrire. Les entrées client (`realtimeInbound()`) restent facultatives canal
|
|
103
|
+
* par canal : accepter une entrée est une permission, pas une promesse. Les
|
|
104
|
+
* politiques d'accès se posent sur la classe par `@RealtimePolicy`, qui lit le
|
|
105
|
+
* contrat sur cette classe.
|
|
106
|
+
*
|
|
107
|
+
* Le fichier du contrat, fait de types seuls, est partagé avec le navigateur, qui
|
|
108
|
+
* entre par `NodefonySocket.fromContract<C>()`.
|
|
109
|
+
*
|
|
110
|
+
* Un contrat qui déclare des `serverRoutes` (routes du pont, typées côté client
|
|
111
|
+
* par le schéma que la route valide) se double d'une table statique de VALEURS,
|
|
112
|
+
* `static readonly serverRoutes = { … } satisfies ContractRouteSchemas<C>` : le
|
|
113
|
+
* compilateur y exige chaque route du contrat avec son schéma, et le démarrage
|
|
114
|
+
* vérifie que chacune est montée, servie par la socket et valide LE MÊME schéma
|
|
115
|
+
* (`BootConfigurationError` sinon). Sans cette table, aucun contrôle.
|
|
116
|
+
*
|
|
117
|
+
* @typeParam C - le contrat de l'application.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```ts
|
|
121
|
+
* @RealtimePolicy({ "chat:send": { roles: ["ROLE_USER"] } })
|
|
122
|
+
* class ChatRt extends ContractRealtimeController<ChatContract> {
|
|
123
|
+
* protected realtimeActions() {
|
|
124
|
+
* return { "chat:send": (p: { text: string }) => ({ id: this.store(p.text) }) };
|
|
125
|
+
* }
|
|
126
|
+
* }
|
|
127
|
+
*
|
|
128
|
+
* class DashRt extends ContractRealtimeController<DashContract> {} // sans serverMethods
|
|
129
|
+
*
|
|
130
|
+
* class ChatRoutesRt extends ContractRealtimeController<ChatContract> {
|
|
131
|
+
* static readonly serverRoutes = {
|
|
132
|
+
* "POST /chat/send": { body: SendBody }, // le schéma de la route, pas une copie
|
|
133
|
+
* } satisfies ContractRouteSchemas<ChatContract>;
|
|
134
|
+
* }
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
export declare const ContractRealtimeController: ContractRealtimeControllerConstructor;
|
|
138
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { RealtimeClosedReason } from "nodefony";
|
|
2
|
+
import type { ChannelFrame } from "./RealtimeHub.js";
|
|
3
|
+
/**
|
|
4
|
+
* Ce que la poignée d'une connexion demande au contrôleur qui la sert — imposer
|
|
5
|
+
* un abonnement, y mettre fin. Implémenté par `RealtimeController`, une fois par
|
|
6
|
+
* connexion.
|
|
7
|
+
*/
|
|
8
|
+
export interface IRealtimeConnectionHost {
|
|
9
|
+
/** Lance l'abonnement imposé à `channel` ; le verdict passe par {@link RealtimeConnection.settleImposed}. */
|
|
10
|
+
impose(channel: string): void;
|
|
11
|
+
/**
|
|
12
|
+
* Met fin à l'abonnement de cette connexion à `channel` (hub ET état de la
|
|
13
|
+
* connexion) et le lui notifie (`realtime:closed`).
|
|
14
|
+
*
|
|
15
|
+
* @returns `false` si la connexion n'est pas abonnée à ce canal.
|
|
16
|
+
*/
|
|
17
|
+
end(channel: string, reason: RealtimeClosedReason): boolean;
|
|
18
|
+
/** Écrit une trame partagée, encodée pour le codec de cette connexion. */
|
|
19
|
+
send(frame: ChannelFrame): void;
|
|
20
|
+
}
|