@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,112 @@
|
|
|
1
|
+
import type { ChannelPresence } from "./channelPresence.js";
|
|
2
|
+
import { RemotePresence } from "./remotePresence.js";
|
|
3
|
+
/** Fenêtre des lots, demandes et réponses entre pods (ms) — ADR-0025 P2. */
|
|
4
|
+
export declare const PRESENCE_BATCH_MS = 50;
|
|
5
|
+
/** Intervalle local de la preuve de vie (ms), annoncé aux pairs — ADR-0025 P2, « Oubli ». */
|
|
6
|
+
export declare const PRESENCE_INTERVAL_MS = 5000;
|
|
7
|
+
/** Délai d'oubli d'un pair muet, en intervalles ANNONCÉS (≥ 2) — ADR-0025 P2. */
|
|
8
|
+
export declare const PRESENCE_FORGET_FACTOR = 3;
|
|
9
|
+
/**
|
|
10
|
+
* Ce que la présence répartie demande au hub qui la porte — le bus, et les canaux à présence DIFFUSÉS qu'il sert.
|
|
11
|
+
*/
|
|
12
|
+
export interface IPresenceBusHost {
|
|
13
|
+
/** `originId` de ce pod (celui de son backplane). */
|
|
14
|
+
readonly originId: string;
|
|
15
|
+
/** Publie sur le bus (la file d'envoi tranche : la preuve de vie passe hors quota). */
|
|
16
|
+
publish(channel: string, payload: object): void;
|
|
17
|
+
/**
|
|
18
|
+
* Résout un canal reçu : sa présence s'il est servi et diffusé ici, `null` s'il déclare la présence sans être servi, `false` s'il ne la déclare pas (refus compté).
|
|
19
|
+
*/
|
|
20
|
+
lookup(channel: string): ChannelPresence | null | false;
|
|
21
|
+
/** Présence d'un canal servi et diffusé ici, `null` sinon. */
|
|
22
|
+
served(channel: string): ChannelPresence | null;
|
|
23
|
+
/** Les présences servies et diffusées ici — itérées aux seules transitions. */
|
|
24
|
+
servedPresences(): Iterable<ChannelPresence>;
|
|
25
|
+
/** Nombre de présences servies et diffusées ici. */
|
|
26
|
+
servedCount(): number;
|
|
27
|
+
/**
|
|
28
|
+
* Échos de ses propres messages comptés par le pilote (`IBackplaneInfo.echoTotal`), `undefined` s'il n'en expose pas — l'oubli reste alors normal.
|
|
29
|
+
*/
|
|
30
|
+
echoTotal(): number | undefined;
|
|
31
|
+
}
|
|
32
|
+
/** Réglages de la présence répartie (défauts de l'ADR). */
|
|
33
|
+
export interface IPresenceBusOptions {
|
|
34
|
+
/** Intervalle de la preuve de vie (ms). */
|
|
35
|
+
readonly interval?: number;
|
|
36
|
+
/** Délai d'oubli, en intervalles annoncés (≥ 2). */
|
|
37
|
+
readonly factor?: number;
|
|
38
|
+
/** Tirage dans [0, 1) — incarnation et gigue ; injecté par les tests. */
|
|
39
|
+
readonly random?: () => number;
|
|
40
|
+
/** Horloge murale (ms) — incarnation seulement ; injectée par les tests. */
|
|
41
|
+
readonly wallClock?: () => number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Présence répartie d'UN pod (ADR-0025 P2) — ce qu'il émet sur le bus (lots de 50 ms, demandes d'état, réponses, preuve de vie) et ce qu'il en reçoit (vue des pairs, {@link RemotePresence}).
|
|
45
|
+
*
|
|
46
|
+
* Sans minuteur ni horloge : le hub la fait avancer à son tic UNIQUE
|
|
47
|
+
* ({@link PresenceBus.tick}) et arme ce tic sur {@link PresenceBus.due}, O(1).
|
|
48
|
+
* Elle ne parle qu'à `IBackplane` par son hôte : un pilote neuf (Kafka,
|
|
49
|
+
* NATS…) n'écrit rien pour la présence.
|
|
50
|
+
*
|
|
51
|
+
* Coût : rien n'existe tant qu'aucun canal à présence n'est diffusé ; un
|
|
52
|
+
* changement local = un ajout dans un `Set` ; un lot = un parcours des seuls
|
|
53
|
+
* canaux changés.
|
|
54
|
+
*/
|
|
55
|
+
export declare class PresenceBus {
|
|
56
|
+
#private;
|
|
57
|
+
/** Vue des pairs. */
|
|
58
|
+
readonly remote: RemotePresence;
|
|
59
|
+
/** Vie de ce processus : `Date.now() × 1024 + aléa sur 10 bits` (entier sûr jusqu'en 2248). */
|
|
60
|
+
readonly incarnation: number;
|
|
61
|
+
/**
|
|
62
|
+
* @throws RangeError si le délai d'oubli est sous 2 intervalles (cf `RemotePresence`).
|
|
63
|
+
*/
|
|
64
|
+
constructor(host: IPresenceBusHost, options?: IPresenceBusOptions);
|
|
65
|
+
/** Prochain instant (ms monotone) où le bus a quelque chose à faire — O(1). */
|
|
66
|
+
due(): number;
|
|
67
|
+
/**
|
|
68
|
+
* Présences à rafraîchir depuis le dernier appel (agrégat changé, état incomplet changé) — `null` si aucune ; le hub les touche.
|
|
69
|
+
*/
|
|
70
|
+
takeTouched(): Set<ChannelPresence> | null;
|
|
71
|
+
/**
|
|
72
|
+
* Un canal à présence diffusé vient d'être servi (premier abonné local) : la vue reste incomplète le temps que la demande d'état groupée soit servie.
|
|
73
|
+
*/
|
|
74
|
+
served(presence: ChannelPresence, now: number): void;
|
|
75
|
+
/** La part locale d'une présence a changé : elle part au prochain lot. */
|
|
76
|
+
changed(presence: ChannelPresence, now: number): void;
|
|
77
|
+
/**
|
|
78
|
+
* Applique un message du canal des lots, demandes et réponses.
|
|
79
|
+
*
|
|
80
|
+
* @returns `true` si c'est un type de message inconnu de cette version (le
|
|
81
|
+
* hub le compte dans `ingressUnknownDiscardedTotal`) ; `false` sinon — y
|
|
82
|
+
* compris refusé, compté ici (`presenceRejectedTotal`).
|
|
83
|
+
*/
|
|
84
|
+
receive(origin: string, payload: unknown, now: number): boolean;
|
|
85
|
+
/** Applique une preuve de vie ; une forme fausse est refusée et comptée. */
|
|
86
|
+
receiveAlive(origin: string, payload: unknown, now: number): void;
|
|
87
|
+
/** Fait avancer la présence répartie à l'instant `now` (tic unique du hub). */
|
|
88
|
+
tick(now: number): void;
|
|
89
|
+
/**
|
|
90
|
+
* Arrêt propre (ADR-0025 P5) : le dernier lot part, puis une preuve de vie FINALE — les pairs retirent aussitôt les présents de ce pod, sans attendre le délai d'oubli. Plus rien ne part ensuite ; idempotent.
|
|
91
|
+
*
|
|
92
|
+
* Rien n'est émis si ce pod ne s'est jamais annoncé (aucune preuve de vie
|
|
93
|
+
* émise ni vue des pairs) : personne n'a rien à retirer.
|
|
94
|
+
*/
|
|
95
|
+
end(): void;
|
|
96
|
+
/** Arrêt propre émis ? */
|
|
97
|
+
get ended(): boolean;
|
|
98
|
+
/** Bus perdu, constaté par l'écho : la vue est figée et `partial` (instantané). */
|
|
99
|
+
get busLost(): boolean;
|
|
100
|
+
/** Pertes du bus constatées par l'écho. */
|
|
101
|
+
get busLostTotal(): number;
|
|
102
|
+
/** Réarmements du délai d'oubli (tic en retard, retour de l'écho). */
|
|
103
|
+
get rearmTotal(): number;
|
|
104
|
+
/** Compteurs de la sonde. */
|
|
105
|
+
get batchTotal(): number;
|
|
106
|
+
/** Demandes d'état émises (ciblées et non ciblées). */
|
|
107
|
+
get requestTotal(): number;
|
|
108
|
+
/** Parts de réponse d'état émises. */
|
|
109
|
+
get answerTotal(): number;
|
|
110
|
+
/** Preuves de vie émises. */
|
|
111
|
+
get aliveTotal(): number;
|
|
112
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { IBackplane } from "../../interfaces/IBackplane.js";
|
|
2
|
+
import type { RealtimeHub } from "./RealtimeHub.js";
|
|
3
|
+
/**
|
|
4
|
+
* Délai laissé au bus pour faire partir ce qui est déjà émis à l'arrêt (ms) — sous la limite d'arrêt du noyau (15 s) et sous le délai de grâce d'un orchestrateur.
|
|
5
|
+
*/
|
|
6
|
+
export declare const BACKPLANE_STOP_TIMEOUT_MS = 2000;
|
|
7
|
+
/**
|
|
8
|
+
* Arrêt propre du temps réel d'un processus — la présence répartie annonce sa fin (dernier lot, puis preuve de vie FINALE, ADR-0025 P5), puis le bus POSSÉDÉ s'arrête en laissant partir ce qui est émis, dans une limite de temps.
|
|
9
|
+
*
|
|
10
|
+
* Sans l'attente du bus, la sortie du processus au tick suivant perdait la
|
|
11
|
+
* preuve de vie finale sur un bus réseau (Redis) : les pairs gardaient ce
|
|
12
|
+
* serveur jusqu'au délai d'oubli. Un bus fourni par l'application (`owned`
|
|
13
|
+
* `null`) n'est pas arrêté ici : c'est à elle de le faire.
|
|
14
|
+
*
|
|
15
|
+
* @param hub - le hub du processus.
|
|
16
|
+
* @param owned - le bus que l'appelant a créé et doit arrêter, `null` sinon.
|
|
17
|
+
* @param timeoutMs - limite de l'arrêt du bus.
|
|
18
|
+
* @returns `false` si le bus n'a pas fini dans la limite (ou a échoué) — ce
|
|
19
|
+
* qui restait peut être perdu, les pairs oublieront ce serveur au délai.
|
|
20
|
+
*/
|
|
21
|
+
export declare function shutdownRealtime(hub: RealtimeHub, owned: IBackplane | null, timeoutMs?: number): Promise<boolean>;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { type IPresenceAlive, type IPresenceRequest, type PresenceMessage } from "../backplane/presenceMessage.js";
|
|
2
|
+
/**
|
|
3
|
+
* Agrégat d'un canal servi par ce pod — ce que la vue distante ajuste quand la présence d'un pair change (ADR-0025 P2).
|
|
4
|
+
*
|
|
5
|
+
* Les deux méthodes reçoivent un DELTA : la vue distante range ce que chaque
|
|
6
|
+
* pod annonce (valeurs absolues) et ne transmet que la différence, si bien que
|
|
7
|
+
* l'agrégat n'a jamais à reparcourir les pods.
|
|
8
|
+
*/
|
|
9
|
+
export interface IPresenceAggregate {
|
|
10
|
+
/**
|
|
11
|
+
* L'identité a `delta` onglets de plus (négatif : de moins) chez les pairs.
|
|
12
|
+
*
|
|
13
|
+
* @param key - identité nommée.
|
|
14
|
+
* @param delta - entier non nul.
|
|
15
|
+
*/
|
|
16
|
+
remoteTabs(key: string, delta: number): void;
|
|
17
|
+
/**
|
|
18
|
+
* Les pairs comptent `delta` connexions non nommées de plus (négatif : de moins).
|
|
19
|
+
*
|
|
20
|
+
* @param delta - entier non nul.
|
|
21
|
+
*/
|
|
22
|
+
remoteUnnamed(delta: number): void;
|
|
23
|
+
/**
|
|
24
|
+
* Un pair PART (oublié, arrêt propre, vie remplacée) avec les `tabs` onglets de l'identité qu'il annonçait — retenu par la grâce du canal s'il en déclare une (ADR-0025 P5), annulé si l'identité revient pendant ce délai.
|
|
25
|
+
*
|
|
26
|
+
* @param key - identité nommée.
|
|
27
|
+
* @param tabs - onglets retirés, entier > 0.
|
|
28
|
+
* @param now - instant monotone local (ms).
|
|
29
|
+
*/
|
|
30
|
+
remoteLeave(key: string, tabs: number, now: number): void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Résout un canal reçu du bus : son agrégat s'il est SERVI ici, `null` s'il déclare la présence sans être servi (lu puis jeté, sans stockage), `false` s'il ne déclare pas la présence sur ce pod (refusé, compté).
|
|
34
|
+
*/
|
|
35
|
+
export type PresenceAggregateLookup = (channel: string) => IPresenceAggregate | null | false;
|
|
36
|
+
/** Réglages de la vue distante. */
|
|
37
|
+
export interface IRemotePresenceOptions {
|
|
38
|
+
/** Résolution d'un canal reçu (cf {@link PresenceAggregateLookup}). */
|
|
39
|
+
readonly lookup: PresenceAggregateLookup;
|
|
40
|
+
/** Délai d'oubli en intervalles ANNONCÉS par le pair (≥ 2, défaut de l'ADR : 3). */
|
|
41
|
+
readonly factor: number;
|
|
42
|
+
/**
|
|
43
|
+
* Intervalle LOCAL (ms) : cadence des demandes répétées pendant un trou, et
|
|
44
|
+
* intervalle supposé d'un pair dont aucune preuve de vie n'est encore arrivée.
|
|
45
|
+
*/
|
|
46
|
+
readonly interval: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Vue de la présence des PAIRS, rangée par pod puis par canal (ADR-0025 P2) — la règle répartie, sans horloge, sans minuteur, sans bus.
|
|
50
|
+
*
|
|
51
|
+
* Elle applique ce que le bus apporte — lots chaînés par `seq`/`prev`, réponses
|
|
52
|
+
* d'état en parts appliquées d'un bloc, preuves de vie —, détecte les trous
|
|
53
|
+
* (règle pure `classifyNumberedChange`, RFC 5263 §4.5), départage les vies d'un
|
|
54
|
+
* même `originId` par l'incarnation, oublie un pair muet au bout de `factor`
|
|
55
|
+
* intervalles ANNONCÉS, et ne transmet aux agrégats que des DELTAS. L'instant
|
|
56
|
+
* (`now`, horloge monotone) est passé à chaque appel : la règle se prouve à
|
|
57
|
+
* l'horloge simulée, au compte exact.
|
|
58
|
+
*
|
|
59
|
+
* Coût : un canal non servi ne stocke rien ; oublier un pod libère ses tables
|
|
60
|
+
* d'un coup ; un message ne parcourt que ses propres entrées. Aucun parcours de
|
|
61
|
+
* tous les pods hors de {@link RemotePresence.expire},
|
|
62
|
+
* {@link RemotePresence.nextDeadline}, {@link RemotePresence.rearm} et
|
|
63
|
+
* {@link RemotePresence.takeRequests}, appelés au tic.
|
|
64
|
+
*/
|
|
65
|
+
export declare class RemotePresence {
|
|
66
|
+
#private;
|
|
67
|
+
/**
|
|
68
|
+
* @throws RangeError si `factor` < 2 (Phoenix Tracker exige le même plancher :
|
|
69
|
+
* sous deux intervalles, une seule preuve de vie retardée fait oublier un pair
|
|
70
|
+
* vivant) ou si `interval` n'est pas un entier > 0.
|
|
71
|
+
*/
|
|
72
|
+
constructor(options: IRemotePresenceOptions);
|
|
73
|
+
/** Pairs connus (pierres tombales comprises). */
|
|
74
|
+
get size(): number;
|
|
75
|
+
/** Pairs dont la vue est en trou — la présence servie est alors `partial`. */
|
|
76
|
+
get gaps(): number;
|
|
77
|
+
/** Messages refusés : canal non déclaré à présence ici, plafond dépassé (`presenceRejectedTotal`). */
|
|
78
|
+
get rejectedTotal(): number;
|
|
79
|
+
/** Messages déjà vus ou d'une vie plus ancienne (`presenceStaleDiscardedTotal`). */
|
|
80
|
+
get staleTotal(): number;
|
|
81
|
+
/** Pairs oubliés faute de signe de vie. */
|
|
82
|
+
get forgottenTotal(): number;
|
|
83
|
+
/** Arrêts propres reçus (preuve de vie `final`) — présents retirés sans attendre l'oubli. */
|
|
84
|
+
get endedTotal(): number;
|
|
85
|
+
/** Trous ouverts depuis le début (lot perdu, pair inconnu, vie neuve). */
|
|
86
|
+
get gapTotal(): number;
|
|
87
|
+
/**
|
|
88
|
+
* Compte un message de présence REFUSÉ par le lecteur à liste fermée (forme invalide) — même compteur que les refus de la vue.
|
|
89
|
+
*/
|
|
90
|
+
countRejected(): void;
|
|
91
|
+
/**
|
|
92
|
+
* Applique un message du canal des lots, demandes et réponses.
|
|
93
|
+
*
|
|
94
|
+
* @param origin - `originId` de l'émetteur, lu dans l'enveloppe.
|
|
95
|
+
* @param message - message validé par `readPresenceMessage`.
|
|
96
|
+
* @param now - instant monotone local (ms).
|
|
97
|
+
* @returns la demande d'état si le message en est une, FRAÎCHE, à servir par
|
|
98
|
+
* le répondeur ; `null` sinon.
|
|
99
|
+
*/
|
|
100
|
+
receive(origin: string, message: PresenceMessage, now: number): IPresenceRequest | null;
|
|
101
|
+
/**
|
|
102
|
+
* Applique une preuve de vie.
|
|
103
|
+
*
|
|
104
|
+
* @param origin - `originId` de l'émetteur.
|
|
105
|
+
* @param alive - preuve de vie validée par `readPresenceAlive`.
|
|
106
|
+
* @param now - instant monotone local (ms).
|
|
107
|
+
*/
|
|
108
|
+
receiveAlive(origin: string, alive: IPresenceAlive, now: number): void;
|
|
109
|
+
/**
|
|
110
|
+
* Pairs à qui demander leur état MAINTENANT — en trou, et pas encore redemandés depuis un intervalle local ; ils sont marqués demandés.
|
|
111
|
+
*
|
|
112
|
+
* @param now - instant monotone local (ms).
|
|
113
|
+
* @param served - canaux à présence servis ici, nommés par la demande ; une
|
|
114
|
+
* réponse ne ferme le trou que si elle les couvre tous.
|
|
115
|
+
* @returns les `originId` à viser (`to`), `null` s'il n'y en a pas.
|
|
116
|
+
*/
|
|
117
|
+
takeRequests(now: number, served: readonly string[]): string[] | null;
|
|
118
|
+
/**
|
|
119
|
+
* Oublie les pairs muets depuis `factor` intervalles ANNONCÉS, et retire les pierres tombales échues.
|
|
120
|
+
*
|
|
121
|
+
* @param now - instant monotone local (ms).
|
|
122
|
+
* @returns le nombre de pairs VIVANTS oubliés (hors pierres tombales).
|
|
123
|
+
*/
|
|
124
|
+
expire(now: number): number;
|
|
125
|
+
/**
|
|
126
|
+
* Plus proche échéance d'oubli APPARUE depuis le dernier appel — celle d'une preuve de vie, dont l'intervalle annoncé peut être plus court que le local ; `Infinity` sinon. O(1), relevée à chaque réception pour que le porteur avance son tic sans attendre le suivant.
|
|
127
|
+
*
|
|
128
|
+
* Tout autre message recule l'échéance, et celle d'un pair neuf tombe au
|
|
129
|
+
* moins deux intervalles LOCAUX plus loin : le tic suivant la relève à temps.
|
|
130
|
+
* {@link RemotePresence.nextDeadline} reste la valeur exacte, recalculée au tic.
|
|
131
|
+
*/
|
|
132
|
+
takeCloserDeadline(): number;
|
|
133
|
+
/** Prochaine échéance d'oubli (ms monotone), `Infinity` sans pair. */
|
|
134
|
+
nextDeadline(): number;
|
|
135
|
+
/**
|
|
136
|
+
* Réarme le délai d'oubli de TOUS les pairs — après un tic en retard (récepteur gelé) ou au retour de l'écho (bus revenu).
|
|
137
|
+
*
|
|
138
|
+
* Sans lui, un pod bloqué 20 s oublierait d'un coup tous les pairs vivants
|
|
139
|
+
* dont les preuves de vie attendent encore dans son tampon : les minuteurs
|
|
140
|
+
* passent avant la lecture des sockets (Lifeguard).
|
|
141
|
+
*
|
|
142
|
+
* @param now - instant monotone local (ms).
|
|
143
|
+
*/
|
|
144
|
+
rearm(now: number): void;
|
|
145
|
+
/**
|
|
146
|
+
* Retire un canal de toutes les vues — il n'est plus servi ici (dernier abonné local parti). L'agrégat n'est pas notifié : il disparaît avec le canal.
|
|
147
|
+
*/
|
|
148
|
+
dropChannel(channel: string): void;
|
|
149
|
+
/** Oublie tout, sans notifier les agrégats (arrêt, réinitialisation). */
|
|
150
|
+
clear(): void;
|
|
151
|
+
}
|
|
@@ -17,7 +17,17 @@
|
|
|
17
17
|
/**
|
|
18
18
|
* Sérialise la notification `realtime:subscribed { channel }`.
|
|
19
19
|
*
|
|
20
|
+
* Connexion TEXTE seulement : sous codec binaire, l'accusé passe par
|
|
21
|
+
* `peer.notify` (`RealtimeController.ts#RealtimeController.acknowledgeSubscription`).
|
|
22
|
+
*
|
|
20
23
|
* @param channel - le canal acquitté (entrée réseau : échappé par `JSON.stringify`).
|
|
21
24
|
* @returns la trame JSON-RPC 2.0 prête à écrire sur le transport.
|
|
22
25
|
*/
|
|
23
26
|
export declare function subscribedAckFrame(channel: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Sérialise `realtime:subscribed { channel, imposed: true }` — l'acquittement d'un abonnement imposé par le serveur.
|
|
29
|
+
*
|
|
30
|
+
* @param channel - le canal imposé (échappé par `JSON.stringify`).
|
|
31
|
+
* @returns la trame JSON-RPC 2.0 prête à écrire sur le transport.
|
|
32
|
+
*/
|
|
33
|
+
export declare function imposedSubscribedAckFrame(channel: string): string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { RealtimeIdentity } from "nodefony";
|
|
2
|
+
import type { IRealtimeToken } from "../../interfaces/IRealtimeToken.js";
|
|
3
|
+
/**
|
|
4
|
+
* Vue PUBLIQUE de l'identité d'une connexion, annoncée au client par `realtime:welcome` — tirée de l'identité d'admission quand le jeton en produit une.
|
|
5
|
+
*
|
|
6
|
+
* Un objet NEUF, champ par champ, et jamais l'identité d'admission elle-même :
|
|
7
|
+
* celle-ci porte `issuer` et `account`, qui restent au serveur — la sérialiser
|
|
8
|
+
* telle quelle les enverrait au client (ADR-0021 §S10 point 1). Les tableaux
|
|
9
|
+
* de rôles et de scopes, eux, sont repris par RÉFÉRENCE : gelés une fois à la
|
|
10
|
+
* construction de l'identité, ils ne se recopient pas.
|
|
11
|
+
*
|
|
12
|
+
* Un jeton sans `getIdentity` (authentificateur tiers) annonce `id: null` : son
|
|
13
|
+
* identifiant stable n'est pas connu, et le déduire du login serait le défaut
|
|
14
|
+
* même que `id` corrige.
|
|
15
|
+
*
|
|
16
|
+
* @param token - jeton figé à la poignée de main.
|
|
17
|
+
* @returns l'identité à placer dans le `welcome`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function welcomeIdentityOf(token: IRealtimeToken): RealtimeIdentity;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Service, Module, type JsonRpcPeer } from "nodefony";
|
|
1
|
+
import { Service, Module, type IChannelPolicies, type IFrameCodec, type JsonRpcPeer } from "nodefony";
|
|
2
2
|
import { type RealtimeHub, type ChannelFactory, type ChannelSink, type FrameAuthorizer } from "../server/RealtimeHub.js";
|
|
3
3
|
import type { IBackplane } from "../../interfaces/IBackplane.js";
|
|
4
4
|
import type { IRealtimeProbe } from "../../interfaces/IRealtimeProbe.js";
|
|
@@ -58,13 +58,21 @@ declare class RealtimeService extends Service {
|
|
|
58
58
|
getConfig(): IRealtimeConfig;
|
|
59
59
|
/** Hub singleton (broker fan-out canaux PARTAGÉS du process courant). */
|
|
60
60
|
getHub(): RealtimeHub;
|
|
61
|
+
/**
|
|
62
|
+
* Le codec texte des connexions socket, lu PAR NOM par le REST du plan
|
|
63
|
+
* d'administration (`AdminApiController`, framework ne dépend pas de realtime) :
|
|
64
|
+
* les octets d'une réponse `GET` y prennent la même forme que sur la socket.
|
|
65
|
+
*/
|
|
66
|
+
get textFrameCodec(): IFrameCodec;
|
|
61
67
|
/** Backplane actuellement branché sur le hub (ou null = Loopback implicite). */
|
|
62
68
|
getBackplane(): IBackplane | null;
|
|
63
69
|
/**
|
|
64
70
|
* Publie une charge sur un canal — fan-out local + propagation backplane si
|
|
65
71
|
* canal broadcast. Délégué à `RealtimeHub.publish`.
|
|
72
|
+
*
|
|
73
|
+
* @throws TypeError si la charge est un scalaire ou `null` (cf `isRealtimePayload`).
|
|
66
74
|
*/
|
|
67
|
-
publish(channel: string, payload
|
|
75
|
+
publish(channel: string, payload?: object): void;
|
|
68
76
|
/**
|
|
69
77
|
* Abonne un sink à un canal partagé — factory invoquée au 1ᵉʳ abonné, dispose
|
|
70
78
|
* appelé au dernier désabonné. Délégué à `RealtimeHub.subscribe`.
|
|
@@ -155,6 +163,13 @@ declare class RealtimeService extends Service {
|
|
|
155
163
|
* @returns la politique déclarée, ou `null`.
|
|
156
164
|
*/
|
|
157
165
|
resolveChannelPolicy(channel: string): IChannelPolicy | null;
|
|
166
|
+
/**
|
|
167
|
+
* **Seam #1b** — lecture ET écriture distincte d'un nom, en une résolution (ADR-0022 §6, décision 1 quater). Lu par `@nodefony/security` sur chaque trame jugée.
|
|
168
|
+
*
|
|
169
|
+
* @param channel - nom du canal, de la méthode entrante ou de l'action.
|
|
170
|
+
* @returns les deux politiques, ou `null` si aucune ne couvre ce nom.
|
|
171
|
+
*/
|
|
172
|
+
resolveChannelPolicies(channel: string): IChannelPolicies | null;
|
|
158
173
|
/**
|
|
159
174
|
* Renvoie le token associé à un peer (posé au handshake par le hub). Jamais
|
|
160
175
|
* `null` (Zero Trust : `ANONYMOUS_REALTIME_TOKEN` si aucun authenticator n'a
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schémas qu'accepte `realtime.publicOrigin` : celui de la socket (`ws:`,
|
|
3
|
+
* `wss:`) ou celui de l'hôte qui la sert (`http:`, `https:`), ramené au
|
|
4
|
+
* premier.
|
|
5
|
+
*/
|
|
6
|
+
export declare const REALTIME_ORIGIN_PROTOCOLS: readonly string[];
|
|
7
|
+
/**
|
|
8
|
+
* Rend la source `connect-src` qui autorise la socket servie à `publicOrigin`,
|
|
9
|
+
* toujours sous son schéma de socket.
|
|
10
|
+
*
|
|
11
|
+
* Une source `wss://hôte` couvre `wss:` ET `https:` du même hôte (CSP Level 3,
|
|
12
|
+
* « scheme-part match ») ; une source `https://hôte` ne couvrirait PAS la
|
|
13
|
+
* socket. D'où la conversion `https:` → `wss:`, `http:` → `ws:`.
|
|
14
|
+
*
|
|
15
|
+
* @param publicOrigin - origine nue où la socket est servie
|
|
16
|
+
* @returns la source à déclarer (`wss://rt.example.com:8443`), ou `null` si la
|
|
17
|
+
* valeur n'est pas une origine nue d'un schéma accepté
|
|
18
|
+
*/
|
|
19
|
+
export declare function realtimeConnectSource(publicOrigin: string): string | null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type IRealtimeTransport } from "nodefony";
|
|
1
|
+
import { type IRealtimeTransport, type RealtimeFrameData, type RealtimeOutboundData } from "nodefony";
|
|
2
2
|
import { type WsBackpressurePolicy } from "@nodefony/http";
|
|
3
3
|
import type { IRealtimeConnProbe } from "../../interfaces/IRealtimeProbe.js";
|
|
4
4
|
/**
|
|
@@ -16,29 +16,6 @@ export interface RawWsConnection {
|
|
|
16
16
|
*/
|
|
17
17
|
readonly bufferedAmount?: number;
|
|
18
18
|
}
|
|
19
|
-
/**
|
|
20
|
-
* Contre-pression WS — **la règle vit dans `@nodefony/http`**, ce transport ne
|
|
21
|
-
* fait que l'appliquer (`decideSend`). Deux implémentations de la même
|
|
22
|
-
* protection avaient déjà divergé en silence : 4 MiB côté `http`, 1 MiB ici.
|
|
23
|
-
*
|
|
24
|
-
* Réglage = config du serveur WebSocket (`websocket.maxBackpressure`,
|
|
25
|
-
* `.backpressurePolicy`, `.backpressureCloseAfterDrops`) — une seule source pour
|
|
26
|
-
* `send`/`broadcast` HTTP **et** pour le realtime.
|
|
27
|
-
*
|
|
28
|
-
* ⚠️ NE PAS confondre avec `slowConsumer.bytes` (config realtime) : celle-ci ne
|
|
29
|
-
* règle QUE le seuil de COMPTAGE de la sonde, jamais l'action.
|
|
30
|
-
*/
|
|
31
|
-
/**
|
|
32
|
-
* WsConnectionTransport — transport {@link IRealtimeTransport} CÔTÉ SERVEUR :
|
|
33
|
-
* wrap d'une connexion `ws` (1 par client connecté). Pendant serveur du
|
|
34
|
-
* `BrowserWsTransport` → la connexion serveur compose le MÊME `JsonRpcPeer` que
|
|
35
|
-
* le client (symétrie isomorphe).
|
|
36
|
-
*
|
|
37
|
-
* Particularité serveur : l'inbound n'est PAS piloté par un event socket mais par
|
|
38
|
-
* le **pipeline framework** (la route WS appelle `feed(raw)` à chaque message) ;
|
|
39
|
-
* la fermeture est signalée par `fireClose()` (hook `onFinish` du Context). Le
|
|
40
|
-
* `connect`/`onOpen` sont des no-op (la connexion est déjà ouverte au handshake).
|
|
41
|
-
*/
|
|
42
19
|
export declare class WsConnectionTransport implements IRealtimeTransport, IRealtimeConnProbe {
|
|
43
20
|
private readonly conn;
|
|
44
21
|
private _onMessage;
|
|
@@ -57,7 +34,7 @@ export declare class WsConnectionTransport implements IRealtimeTransport, IRealt
|
|
|
57
34
|
closeAfterDrops?: number;
|
|
58
35
|
});
|
|
59
36
|
connect(): void;
|
|
60
|
-
send(raw:
|
|
37
|
+
send(raw: RealtimeOutboundData): void;
|
|
61
38
|
/**
|
|
62
39
|
* Émet des octets tels quels — trame binaire RFC 6455, hors JSON-RPC (#617).
|
|
63
40
|
* Même contre-pression (`decideSend`, la règle unique) et mêmes compteurs que
|
|
@@ -73,13 +50,13 @@ export declare class WsConnectionTransport implements IRealtimeTransport, IRealt
|
|
|
73
50
|
* Compte une trame ENTRANTE écartée et rend le cumul de sa nature — `1` dit
|
|
74
51
|
* « première », le seul instant où l'appelant journalise.
|
|
75
52
|
*
|
|
76
|
-
* @param kind - `binary` (sans lecteur) ou `malformed` (
|
|
53
|
+
* @param kind - `binary` (sans lecteur) ou `malformed` (illisible par le codec).
|
|
77
54
|
* @returns le cumul de cette nature, après incrément.
|
|
78
55
|
*/
|
|
79
56
|
discard(kind: "binary" | "malformed"): number;
|
|
80
57
|
/** Cumul de trames BINAIRES reçues et écartées faute de lecteur — monotone. */
|
|
81
58
|
get binaryDiscarded(): number;
|
|
82
|
-
/** Cumul de trames
|
|
59
|
+
/** Cumul de trames reçues et écartées parce qu'illisibles par le codec de la connexion — monotone. */
|
|
83
60
|
get malformedDiscarded(): number;
|
|
84
61
|
close(code?: number, reason?: string): void;
|
|
85
62
|
get readyState(): number;
|
|
@@ -92,11 +69,20 @@ export declare class WsConnectionTransport implements IRealtimeTransport, IRealt
|
|
|
92
69
|
/** Cumul de frames jetées par back-pressure (drop + close slow-consumer) — monotone. */
|
|
93
70
|
get dropped(): number;
|
|
94
71
|
onOpen(): void;
|
|
95
|
-
onMessage(cb: (raw:
|
|
72
|
+
onMessage(cb: (raw: RealtimeFrameData) => void): void;
|
|
96
73
|
onClose(cb: (code: number, reason: string) => void): void;
|
|
97
74
|
onError(): void;
|
|
98
|
-
/**
|
|
99
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Pump entrant : le pipeline framework pousse ici chaque frame reçue — texte,
|
|
77
|
+
* ou octets d'une connexion qui a négocié un codec binaire (ADR-0022 §2).
|
|
78
|
+
*
|
|
79
|
+
* Une vue sur un `SharedArrayBuffer` n'est pas une trame : `ws` livre
|
|
80
|
+
* toujours un `Buffer` sur un `ArrayBuffer`, le type de Node l'admet
|
|
81
|
+
* seulement. Elle est comptée comme illisible, jamais transmise au pair.
|
|
82
|
+
*
|
|
83
|
+
* @param raw - la trame telle que reçue.
|
|
84
|
+
*/
|
|
85
|
+
feed(raw: string | Uint8Array): void;
|
|
100
86
|
/** Fermeture : appelée par le owner sur `onFinish` (close WS). */
|
|
101
87
|
fireClose(code?: number, reason?: string): void;
|
|
102
88
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { IBackplane } from "../interfaces/IBackplane.js";
|
|
2
|
+
/**
|
|
3
|
+
* Lanceur de tests de l'appelant — le sous-ensemble que la suite emploie.
|
|
4
|
+
*
|
|
5
|
+
* Formes compatibles telles quelles : `{ describe, it }` de vitest, de
|
|
6
|
+
* `node:test` et de `bun:test`.
|
|
7
|
+
*/
|
|
8
|
+
export interface IConformanceRunner {
|
|
9
|
+
/** Ouvre un groupe de cas. */
|
|
10
|
+
describe(name: string, body: () => void): unknown;
|
|
11
|
+
/** Déclare un cas asynchrone ; un rejet le fait échouer. */
|
|
12
|
+
it(name: string, body: () => Promise<void>): unknown;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Réglages de {@link runBackplaneConformance}.
|
|
16
|
+
*/
|
|
17
|
+
export interface IBackplaneConformanceOptions extends IConformanceRunner {
|
|
18
|
+
/**
|
|
19
|
+
* Le driver scelle-t-il ses messages (`sealBackplaneEnvelope`) ? Défaut `true`.
|
|
20
|
+
* `false` n'est admis que pour un driver dont le bus ne quitte pas la machine :
|
|
21
|
+
* un driver qui se déclare `crossPod` sans sceller fait échouer la suite.
|
|
22
|
+
*/
|
|
23
|
+
sealed?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Silence attendu, en millisecondes, avant de conclure qu'un message n'arrivera
|
|
26
|
+
* PAS (refus, rejeu, écho). Défaut 100 ; à relever pour un bus lent.
|
|
27
|
+
*/
|
|
28
|
+
quietMs?: number;
|
|
29
|
+
/** Délai maximal d'arrivée d'un message attendu, en millisecondes. Défaut 2 000. */
|
|
30
|
+
timeoutMs?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Octets publiés d'une traite avant l'ordre du cas « ordre sous saturation ».
|
|
33
|
+
* Défaut : deux fois le seuil par défaut de la file d'envoi (16 Mio). Un driver
|
|
34
|
+
* dont la file est bornée plus haut relève cette valeur : si la rafale ne
|
|
35
|
+
* sature pas la file que `describe().queue` déclare, le cas échoue plutôt que
|
|
36
|
+
* de passer sans rien prouver.
|
|
37
|
+
*/
|
|
38
|
+
floodBytes?: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Fabrique d'un pair du driver, relié au MÊME bus que les autres pairs de la suite.
|
|
42
|
+
*
|
|
43
|
+
* Appelée plusieurs fois par cas : chaque appel rend un nouveau pair, d'`originId`
|
|
44
|
+
* propre, non démarré (la suite appelle `start` et `stop`).
|
|
45
|
+
*
|
|
46
|
+
* @param secret - secret du sceau, ou `null` pour un bus nu.
|
|
47
|
+
*/
|
|
48
|
+
export type BackplanePeerFactory = (secret: string | null) => IBackplane | Promise<IBackplane>;
|
|
49
|
+
/** Noms des cas — stables : un test négatif les désigne. */
|
|
50
|
+
export declare const BACKPLANE_CONFORMANCE_CASES: {
|
|
51
|
+
readonly fanout: "diffusion : chaque pair reçoit TOUT, l'émetteur ne reçoit pas son écho";
|
|
52
|
+
readonly order: "ordre par émetteur : les publications d'un pair arrivent dans l'ordre";
|
|
53
|
+
readonly noReplay: "pas de rejeu : un pair démarré après une publication ne la reçoit pas";
|
|
54
|
+
readonly seal: "sceau : un message non scellé ou mal scellé est refusé";
|
|
55
|
+
readonly control: "ordres de contrôle : le canal de contrôle traverse intact";
|
|
56
|
+
readonly controlUnderLoad: "ordre sous saturation : un ordre de contrôle n'est jamais jeté pour faire place aux publications";
|
|
57
|
+
readonly aliveUnderLoad: "preuve de vie sous saturation : la preuve de vie de la présence n'est jamais jetée pour faire place aux publications";
|
|
58
|
+
readonly reserved: "espace réservé : refusé et compté à l'entrée du hub, la diffusion passe sans revenir";
|
|
59
|
+
readonly presence: "présence entre deux pods : chaque hub voit les présents de l'autre, l'arrêt propre les retire sans attendre l'oubli, sans doublon ni rejeu";
|
|
60
|
+
readonly replaceHandler: "onMessage remplace le handler précédent";
|
|
61
|
+
readonly stop: "stop coupe la réception, et se rappelle sans erreur";
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Déclare la suite de conformité d'un driver de backplane auprès du lanceur de
|
|
65
|
+
* tests de l'appelant.
|
|
66
|
+
*
|
|
67
|
+
* Vérifie, sur plusieurs pairs reliés au même bus : la diffusion à TOUS les pairs
|
|
68
|
+
* sans écho, l'ordre par émetteur, l'absence de rejeu au démarrage, le sceau
|
|
69
|
+
* (message non scellé ou mal scellé refusé), le transport des ordres de contrôle,
|
|
70
|
+
* même quand une rafale de publications sature la file d'envoi, le refus de
|
|
71
|
+
* l'espace réservé à l'entrée du hub, la présence répartie entre deux hubs
|
|
72
|
+
* (ADR-0025), le remplacement du handler et l'arrêt.
|
|
73
|
+
*
|
|
74
|
+
* @param name - nom du driver, affiché dans le rapport.
|
|
75
|
+
* @param makePeer - fabrique d'un pair relié au bus commun.
|
|
76
|
+
* @param options - le lanceur (`describe`, `it`) et les réglages.
|
|
77
|
+
*/
|
|
78
|
+
export declare function runBackplaneConformance(name: string, makePeer: BackplanePeerFactory, options: IBackplaneConformanceOptions): void;
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
*
|
|
30
30
|
* @packageDocumentation
|
|
31
31
|
*/
|
|
32
|
-
import { type IRealtimeDenied } from "nodefony";
|
|
32
|
+
import { type IChannelAdmission, type IRealtimeDenied } from "nodefony";
|
|
33
33
|
import type { ContextType } from "@nodefony/http";
|
|
34
|
-
import type
|
|
34
|
+
import { type RealtimeController } from "../src/server/RealtimeController.js";
|
|
35
35
|
import { type RealtimeHub, type FrameAuthorizer } from "../src/server/RealtimeHub.js";
|
|
36
36
|
import type { IRealtimeToken } from "../interfaces/IRealtimeToken.js";
|
|
37
37
|
/**
|
|
@@ -102,6 +102,16 @@ export interface IRealtimeHarnessOptions {
|
|
|
102
102
|
* ainsi qu'on éprouve un fan-out.
|
|
103
103
|
*/
|
|
104
104
|
readonly resetHub?: boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Les classes d'admission du controller (`@RealtimeChannel(nom, { allow:
|
|
107
|
+
* MaClasse })`), déjà construites avec leurs services. En production, le
|
|
108
|
+
* démarrage les résout dans le conteneur ; le harnais n'a pas de noyau, et
|
|
109
|
+
* sans elles une politique qui désigne une classe est REFUSÉE à la poignée de
|
|
110
|
+
* main. Les passer ici les inscrit par le même chemin que le démarrage, gardes
|
|
111
|
+
* comprises (`@injectable`, singleton, méthode `allow`). Le test construit
|
|
112
|
+
* ainsi l'admission avec le MÊME service que le controller.
|
|
113
|
+
*/
|
|
114
|
+
readonly admissions?: readonly IChannelAdmission[];
|
|
105
115
|
}
|
|
106
116
|
/**
|
|
107
117
|
* Vue de test d'un controller temps réel connecté : ce qu'il a reçu, ce qu'il a
|
|
@@ -174,3 +184,4 @@ export interface IRealtimeHarness<C extends RealtimeController> {
|
|
|
174
184
|
* @returns le harnais, prêt pour `connect()`.
|
|
175
185
|
*/
|
|
176
186
|
export declare function createRealtimeHarness<C extends RealtimeController>(create: (context: ContextType) => C, options?: IRealtimeHarnessOptions): IRealtimeHarness<C>;
|
|
187
|
+
export { runBackplaneConformance, BACKPLANE_CONFORMANCE_CASES, type BackplanePeerFactory, type IBackplaneConformanceOptions, type IConformanceRunner, } from "./backplaneConformance.js";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { PresenceView, type NumberedChangeOutcome, type RealtimePresenceFrame } from "nodefony";
|
|
2
|
+
import type { RealtimeHub } from "../src/server/RealtimeHub.js";
|
|
3
|
+
/** Un client de présence branché sur un hub : ses trames `realtime:presence`, sa vue, et son départ. */
|
|
4
|
+
export interface IPresenceClient {
|
|
5
|
+
/** Trames reçues, dans l'ordre. */
|
|
6
|
+
frames: RealtimePresenceFrame[];
|
|
7
|
+
/** Sort de chaque trame appliquée à {@link view} (même index que {@link frames}). */
|
|
8
|
+
outcomes: NumberedChangeOutcome[];
|
|
9
|
+
/**
|
|
10
|
+
* Vue du canal tenue par la vue PURE du cœur — celle du navigateur (`NodefonySocket`) : ce que le serveur écrit, le navigateur le lit ainsi.
|
|
11
|
+
*/
|
|
12
|
+
view: PresenceView;
|
|
13
|
+
leave: () => void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Abonne à `channel` une connexion de `hub` — nommée `user`, ou anonyme (`null`) — et relève les trames de présence qu'elle reçoit, lues et appliquées par le lecteur et la vue du cœur, comme le ferait un navigateur. Une trame que le lecteur refuse est un défaut du serveur : le banc lève en la citant.
|
|
17
|
+
*/
|
|
18
|
+
export declare function presenceClient(hub: RealtimeHub, channel: string, user: string | null): IPresenceClient;
|
|
19
|
+
/** Présents nommés d'un canal vus par `hub` (identité → onglets), triés. */
|
|
20
|
+
export declare function presenceView(hub: RealtimeHub, channel: string): Record<string, number>;
|