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

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 (78) hide show
  1. package/README.md +50 -0
  2. package/dist/_virtual/{_@oxc-project_runtime@0.155.0 → _@oxc-project_runtime@0.157.0}/helpers/esm/decorate.js +1 -1
  3. package/dist/index.js +79 -12
  4. package/dist/nodefony/config/config.js +7 -0
  5. package/dist/nodefony/decorators/realtimeDecorators.js +125 -19
  6. package/dist/nodefony/interfaces/IRealtimeConnection.js +1 -0
  7. package/dist/nodefony/src/backplane/ClusterBackplane.js +32 -4
  8. package/dist/nodefony/src/backplane/RedisBackplane.js +143 -31
  9. package/dist/nodefony/src/backplane/controlOrder.js +57 -0
  10. package/dist/nodefony/src/backplane/envelope.js +113 -12
  11. package/dist/nodefony/src/cluster/ClusterProbeClient.js +2 -0
  12. package/dist/nodefony/src/server/AnonymousRealtimeToken.js +23 -2
  13. package/dist/nodefony/src/server/ContractRealtimeController.js +75 -0
  14. package/dist/nodefony/src/server/RealtimeConnection.js +110 -0
  15. package/dist/nodefony/src/server/RealtimeController.js +340 -81
  16. package/dist/nodefony/src/server/RealtimeHub.js +604 -61
  17. package/dist/nodefony/src/server/ServerRealtimeSocket.js +2 -0
  18. package/dist/nodefony/src/server/admissionBudget.js +176 -0
  19. package/dist/nodefony/src/server/channelPolicyRegistry.js +73 -0
  20. package/dist/nodefony/src/server/connectionAdmission.js +242 -0
  21. package/dist/nodefony/src/server/contractRoutes.js +85 -0
  22. package/dist/nodefony/src/server/deniedDetail.js +10 -3
  23. package/dist/nodefony/src/server/frameAdmission.js +168 -0
  24. package/dist/nodefony/src/server/subscribedAck.js +10 -1
  25. package/dist/nodefony/src/server/syslogUplink.js +3 -4
  26. package/dist/nodefony/src/server/welcomeIdentity.js +37 -0
  27. package/dist/nodefony/src/service/RealtimeService.js +42 -1
  28. package/dist/nodefony/src/service/publicOrigin.js +40 -0
  29. package/dist/nodefony/src/transport/WsConnectionTransport.js +27 -4
  30. package/dist/nodefony/testing/backplaneConformance.js +271 -0
  31. package/dist/nodefony/testing/index.js +21 -4
  32. package/dist/types/index.d.ts +34 -7
  33. package/dist/types/nodefony/config/config.d.ts +9 -0
  34. package/dist/types/nodefony/decorators/realtimeDecorators.d.ts +86 -6
  35. package/dist/types/nodefony/interfaces/IBackplane.d.ts +34 -50
  36. package/dist/types/nodefony/interfaces/IChannelPolicy.d.ts +5 -25
  37. package/dist/types/nodefony/interfaces/IRealtimeConnection.d.ts +22 -0
  38. package/dist/types/nodefony/interfaces/IRealtimeConnectionState.d.ts +41 -2
  39. package/dist/types/nodefony/interfaces/IRealtimeController.d.ts +64 -3
  40. package/dist/types/nodefony/interfaces/IRealtimeProbe.d.ts +7 -166
  41. package/dist/types/nodefony/interfaces/IRealtimeToken.d.ts +21 -0
  42. package/dist/types/nodefony/interfaces/index.d.ts +2 -1
  43. package/dist/types/nodefony/src/backplane/ClusterBackplane.d.ts +22 -4
  44. package/dist/types/nodefony/src/backplane/RedisBackplane.d.ts +79 -16
  45. package/dist/types/nodefony/src/backplane/controlOrder.d.ts +52 -0
  46. package/dist/types/nodefony/src/backplane/envelope.d.ts +34 -0
  47. package/dist/types/nodefony/src/server/AnonymousRealtimeToken.d.ts +10 -0
  48. package/dist/types/nodefony/src/server/ContractRealtimeController.d.ts +138 -0
  49. package/dist/types/nodefony/src/server/RealtimeConnection.d.ts +17 -0
  50. package/dist/types/nodefony/src/server/RealtimeController.d.ts +91 -11
  51. package/dist/types/nodefony/src/server/RealtimeHub.d.ts +321 -24
  52. package/dist/types/nodefony/src/server/ServerRealtimeSocket.d.ts +1 -1
  53. package/dist/types/nodefony/src/server/admissionBudget.d.ts +71 -0
  54. package/dist/types/nodefony/src/server/channelPolicyRegistry.d.ts +34 -0
  55. package/dist/types/nodefony/src/server/connectionAdmission.d.ts +68 -0
  56. package/dist/types/nodefony/src/server/contractRoutes.d.ts +42 -0
  57. package/dist/types/nodefony/src/server/deniedDetail.d.ts +6 -0
  58. package/dist/types/nodefony/src/server/frameAdmission.d.ts +70 -0
  59. package/dist/types/nodefony/src/server/subscribedAck.d.ts +7 -0
  60. package/dist/types/nodefony/src/server/welcomeIdentity.d.ts +19 -0
  61. package/dist/types/nodefony/src/service/RealtimeService.d.ts +10 -2
  62. package/dist/types/nodefony/src/service/publicOrigin.d.ts +19 -0
  63. package/dist/types/nodefony/src/transport/WsConnectionTransport.d.ts +16 -30
  64. package/dist/types/nodefony/testing/backplaneConformance.d.ts +66 -0
  65. package/dist/types/nodefony/testing/index.d.ts +12 -1
  66. package/docs/actions.md +419 -18
  67. package/docs/architecture.md +83 -8
  68. package/docs/cadence-et-contre-pression.md +87 -11
  69. package/docs/canaux-a-membres.md +267 -0
  70. package/docs/cas-d-usage.md +1702 -0
  71. package/docs/configuration.md +137 -17
  72. package/docs/cookbook-chat.md +314 -85
  73. package/docs/index.md +11 -1
  74. package/docs/observabilite.md +71 -14
  75. package/docs/protocole.md +133 -24
  76. package/docs/securite.md +284 -32
  77. package/docs/vocabulaire.md +13 -12
  78. package/package.json +14 -11
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Une route montée, vue par le contrôle des routes de contrat : son chemin
3
+ * complet, les méthodes qu'elle accepte et les schémas qu'elle valide.
4
+ */
5
+ export interface IMountedBridgeRoute {
6
+ /** Chemin complet, préfixe du contrôleur compris (`/chat/send`). */
7
+ readonly path: string;
8
+ /** Méthodes acceptées, en capitales ; `undefined` = toutes. */
9
+ readonly methods: ReadonlySet<string> | undefined;
10
+ /** Schémas d'entrée de l'action (`schema` de la route), ou `null`. */
11
+ readonly schema: {
12
+ readonly body?: unknown;
13
+ readonly query?: unknown;
14
+ readonly params?: unknown;
15
+ } | null;
16
+ }
17
+ /**
18
+ * Lit la table `serverRoutes` qu'une classe de contrôleur déclare en statique —
19
+ * `null` quand elle n'en déclare pas (aucun contrôle).
20
+ *
21
+ * @param ctor - la classe du contrôleur.
22
+ * @returns la table, ou `null`.
23
+ * @throws BootConfigurationError si la table n'est pas un objet.
24
+ */
25
+ export declare function readContractRoutes(ctor: object): Record<string, unknown> | null;
26
+ /**
27
+ * Vérifie, au démarrage, que chaque route du contrat est SERVIE par le pont :
28
+ * une route montée au même chemin, qui accepte le verbe par la socket, et dont
29
+ * les schémas sont LES MÊMES OBJETS que ceux que la table déclare. Coût nul par
30
+ * requête : la vérification n'a lieu qu'une fois.
31
+ *
32
+ * Sans elle, un contrat qui type `chat.post("/chat/send", …)` compilerait
33
+ * contre une route absente, servie en HTTP seulement, ou validant un autre
34
+ * schéma — et l'erreur n'apparaîtrait qu'au premier appel.
35
+ *
36
+ * @param owner - nom de la classe qui déclare la table (pour le message).
37
+ * @param table - la table `serverRoutes` (`"<VERBE> <chemin>"` → `{ body?, query? }`).
38
+ * @param mounted - les routes montées.
39
+ * @throws BootConfigurationError à la première entrée qui n'est pas servie ;
40
+ * le message nomme la classe, l'entrée et la cause.
41
+ */
42
+ export declare function assertContractRoutes(owner: string, table: Record<string, unknown>, mounted: readonly IMountedBridgeRoute[]): void;
@@ -23,3 +23,9 @@
23
23
  export declare function deniedDetail(env: string | undefined | null, detail: string): {
24
24
  detail?: string;
25
25
  };
26
+ /**
27
+ * Le détail d'un refus part-il au client dans ce mode ? Hors production seulement — la règle de {@link deniedDetail}, lue AVANT de rédiger la phrase : en production, aucune chaîne n'est composée pour être jetée.
28
+ *
29
+ * @param env - le mode du kernel serveur, s'il est connu ; une absence vaut production.
30
+ */
31
+ export declare function explainsDenials(env: string | undefined | null): boolean;
@@ -0,0 +1,70 @@
1
+ import type { IJsonRpcNotification, IJsonRpcRequest } from "nodefony";
2
+ import type { IRealtimeConnectionState } from "../../interfaces/IRealtimeConnectionState.js";
3
+ import type { RealtimeHub } from "./RealtimeHub.js";
4
+ /**
5
+ * Verdicts du verrou retenus par connexion, au plus (ADR-0022 §6, décision 1 ter).
6
+ *
7
+ * Borne la table qu'un client remplit en poussant sur des canaux entrants : au-delà,
8
+ * le verrou est consulté à chaque trame, il n'est jamais contourné. Une admission
9
+ * par `allow` n'y est pas soumise — elle est déjà bornée par le plafond de canaux
10
+ * de la connexion.
11
+ */
12
+ export declare const MAX_RETAINED_FRAME_VERDICTS = 64;
13
+ /**
14
+ * Admet les trames entrantes d'un canal que sa règle `allow` vient d'ouvrir à la connexion.
15
+ *
16
+ * Pas de plafond propre : le nombre de canaux ouverts l'est déjà. Sans effet si le
17
+ * canal n'accepte pas d'entrée client.
18
+ *
19
+ * @param state - état de la connexion.
20
+ * @param channel - canal ouvert par `allow`.
21
+ */
22
+ export declare function admitByRule(state: IRealtimeConnectionState, channel: string): void;
23
+ /**
24
+ * Retire TOUTE admission d'un canal à la connexion — marque de sa règle ET verdict retenu du verrou — quand le serveur l'en éjecte ou le termine (ADR-0022 §6 décision 3).
25
+ *
26
+ * Plus fort que {@link releaseRuleAdmission}, qui garde le verdict du verrou :
27
+ * après une éjection, une trame entrante sur ce canal repasse par le verrou.
28
+ *
29
+ * @param state - état de la connexion.
30
+ * @param channel - canal dont la connexion est retirée.
31
+ */
32
+ export declare function revokeAdmission(state: IRealtimeConnectionState, channel: string): void;
33
+ /**
34
+ * Retire l'admission d'un canal à la fin de son abonnement — celle de sa règle
35
+ * `allow`, que rien ne justifie plus. Un verdict du verrou, lui, ne tient qu'à
36
+ * l'identité et survit au désabonnement.
37
+ *
38
+ * @param state - état de la connexion.
39
+ * @param channel - canal quitté.
40
+ */
41
+ export declare function releaseRuleAdmission(state: IRealtimeConnectionState, channel: string): void;
42
+ /**
43
+ * Oublie les verdicts du verrou retenus par la connexion — leur politique vient
44
+ * de changer. Les admissions par `allow` restent : elles valent autant que
45
+ * l'abonnement qui les porte.
46
+ *
47
+ * @param state - état de la connexion.
48
+ */
49
+ export declare function dropRetainedVerdicts(state: IRealtimeConnectionState): void;
50
+ /**
51
+ * Juge une trame entrante que la connexion n'a pas admise d'office (ADR-0022 §6, décision 1 ter).
52
+ *
53
+ * Dans l'ordre :
54
+ * - une REQUÊTE (action, `api.request`) passe par le verrou, toujours ;
55
+ * - `ping` et `unsubscribe` passent sans règle ;
56
+ * - `subscribe` passe par le verrou (`allow` est jugé après, à l'ouverture) ;
57
+ * - un nom hérité du prototype de la table entrante est refusé ;
58
+ * - un canal entrant à `allow` que l'abonnement n'a pas admis est refusé ;
59
+ * - un canal entrant à politique fixe passe par le verrou, dont le « oui » est retenu ;
60
+ * - toute autre notification passe par le verrou.
61
+ *
62
+ * Le premier test du contrôleur (`admitted[method] === true`) a déjà écarté les trames
63
+ * admises : rien ici n'est sur le chemin d'une trame chaude.
64
+ *
65
+ * @param state - état de la connexion.
66
+ * @param frame - trame déjà classée par le pair.
67
+ * @param hub - hub qui porte le verrou et les règles.
68
+ * @returns `true` si la trame peut être distribuée.
69
+ */
70
+ export declare function gateFrame(state: IRealtimeConnectionState, frame: IJsonRpcRequest | IJsonRpcNotification, hub: RealtimeHub): boolean;
@@ -21,3 +21,10 @@
21
21
  * @returns la trame JSON-RPC 2.0 prête à écrire sur le transport.
22
22
  */
23
23
  export declare function subscribedAckFrame(channel: string): string;
24
+ /**
25
+ * Sérialise `realtime:subscribed { channel, imposed: true }` — l'acquittement d'un abonnement imposé par le serveur.
26
+ *
27
+ * @param channel - le canal imposé (échappé par `JSON.stringify`).
28
+ * @returns la trame JSON-RPC 2.0 prête à écrire sur le transport.
29
+ */
30
+ 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 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: unknown): void;
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`.
@@ -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: string): void;
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` (texte non JSON).
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 TEXTE reçues et écartées parce que non JSON — monotone. */
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: string) => void): void;
72
+ onMessage(cb: (raw: RealtimeFrameData) => void): void;
96
73
  onClose(cb: (code: number, reason: string) => void): void;
97
74
  onError(): void;
98
- /** Pump entrant : le pipeline framework pousse ici chaque message reçu. */
99
- feed(raw: string): void;
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,66 @@
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
+ /**
33
+ * Fabrique d'un pair du driver, relié au MÊME bus que les autres pairs de la suite.
34
+ *
35
+ * Appelée plusieurs fois par cas : chaque appel rend un nouveau pair, d'`originId`
36
+ * propre, non démarré (la suite appelle `start` et `stop`).
37
+ *
38
+ * @param secret - secret du sceau, ou `null` pour un bus nu.
39
+ */
40
+ export type BackplanePeerFactory = (secret: string | null) => IBackplane | Promise<IBackplane>;
41
+ /** Noms des cas — stables : un test négatif les désigne. */
42
+ export declare const BACKPLANE_CONFORMANCE_CASES: {
43
+ readonly fanout: "diffusion : chaque pair reçoit TOUT, l'émetteur ne reçoit pas son écho";
44
+ readonly order: "ordre par émetteur : les publications d'un pair arrivent dans l'ordre";
45
+ readonly noReplay: "pas de rejeu : un pair démarré après une publication ne la reçoit pas";
46
+ readonly seal: "sceau : un message non scellé ou mal scellé est refusé";
47
+ readonly control: "ordres de contrôle : le canal de contrôle traverse intact";
48
+ readonly reserved: "espace réservé : refusé et compté à l'entrée du hub, la diffusion passe sans revenir";
49
+ readonly replaceHandler: "onMessage remplace le handler précédent";
50
+ readonly stop: "stop coupe la réception, et se rappelle sans erreur";
51
+ };
52
+ /**
53
+ * Déclare la suite de conformité d'un driver de backplane auprès du lanceur de
54
+ * tests de l'appelant.
55
+ *
56
+ * Vérifie, sur plusieurs pairs reliés au même bus : la diffusion à TOUS les pairs
57
+ * sans écho, l'ordre par émetteur, l'absence de rejeu au démarrage, le sceau
58
+ * (message non scellé ou mal scellé refusé), le transport des ordres de contrôle,
59
+ * le refus de l'espace réservé à l'entrée du hub, le remplacement du handler et
60
+ * l'arrêt.
61
+ *
62
+ * @param name - nom du driver, affiché dans le rapport.
63
+ * @param makePeer - fabrique d'un pair relié au bus commun.
64
+ * @param options - le lanceur (`describe`, `it`) et les réglages.
65
+ */
66
+ export declare function runBackplaneConformance(name: string, makePeer: BackplanePeerFactory, options: IBackplaneConformanceOptions): void;
@@ -29,7 +29,7 @@
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
34
  import type { RealtimeController } from "../src/server/RealtimeController.js";
35
35
  import { type RealtimeHub, type FrameAuthorizer } from "../src/server/RealtimeHub.js";
@@ -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";