@nodefony/realtime 10.0.0-beta.4 → 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.
- package/README.md +79 -10
- package/dist/_virtual/{_@oxc-project_runtime@0.155.0 → _@oxc-project_runtime@0.157.0}/helpers/esm/decorate.js +1 -1
- package/dist/index.js +79 -12
- package/dist/nodefony/config/config.js +7 -0
- package/dist/nodefony/decorators/realtimeDecorators.js +125 -19
- package/dist/nodefony/interfaces/IRealtimeConnection.js +1 -0
- package/dist/nodefony/src/backplane/ClusterBackplane.js +32 -4
- package/dist/nodefony/src/backplane/RedisBackplane.js +143 -31
- package/dist/nodefony/src/backplane/controlOrder.js +57 -0
- package/dist/nodefony/src/backplane/envelope.js +113 -12
- 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 +110 -0
- package/dist/nodefony/src/server/RealtimeController.js +344 -85
- package/dist/nodefony/src/server/RealtimeHub.js +604 -61
- 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 +73 -0
- package/dist/nodefony/src/server/connectionAdmission.js +242 -0
- package/dist/nodefony/src/server/contractRoutes.js +85 -0
- package/dist/nodefony/src/server/deniedDetail.js +10 -3
- package/dist/nodefony/src/server/frameAdmission.js +168 -0
- package/dist/nodefony/src/server/subscribedAck.js +10 -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 +42 -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 +271 -0
- package/dist/nodefony/testing/index.js +21 -4
- package/dist/types/index.d.ts +34 -7
- package/dist/types/nodefony/config/config.d.ts +9 -0
- package/dist/types/nodefony/decorators/realtimeDecorators.d.ts +86 -6
- package/dist/types/nodefony/interfaces/IBackplane.d.ts +34 -50
- 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 +79 -16
- package/dist/types/nodefony/src/backplane/controlOrder.d.ts +52 -0
- package/dist/types/nodefony/src/backplane/envelope.d.ts +34 -0
- 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 +17 -0
- package/dist/types/nodefony/src/server/RealtimeController.d.ts +93 -13
- package/dist/types/nodefony/src/server/RealtimeHub.d.ts +321 -24
- 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 +34 -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 +70 -0
- package/dist/types/nodefony/src/server/subscribedAck.d.ts +7 -0
- package/dist/types/nodefony/src/server/welcomeIdentity.d.ts +19 -0
- package/dist/types/nodefony/src/service/RealtimeService.d.ts +10 -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 +66 -0
- package/dist/types/nodefony/testing/index.d.ts +17 -4
- package/docs/actions.md +442 -19
- package/docs/architecture.md +83 -8
- package/docs/cadence-et-contre-pression.md +87 -11
- package/docs/canaux-a-membres.md +267 -0
- package/docs/cas-d-usage.md +1702 -0
- package/docs/configuration.md +137 -17
- package/docs/cookbook-chat.md +333 -85
- package/docs/index.md +11 -1
- package/docs/observabilite.md +71 -14
- package/docs/protocole.md +133 -24
- package/docs/securite.md +294 -33
- package/docs/vocabulaire.md +13 -12
- package/package.json +14 -11
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import { JsonRpcPeer, isPlatformChannel } from "nodefony";
|
|
1
|
+
import { JsonRpcPeer, isPlatformChannel, type IFrameCodec, type IFrameCodecSet, type RealtimeFrameData } from "nodefony";
|
|
2
|
+
import { RealtimeConnection } from "./RealtimeConnection.js";
|
|
3
|
+
import type { IRealtimeConnection } from "../../interfaces/IRealtimeConnection.js";
|
|
4
|
+
import type { IChannelAdmission, UserChannelPayload } from "nodefony";
|
|
5
|
+
import { AdmissionBudget, type IAdmissionLimits } from "./admissionBudget.js";
|
|
6
|
+
import type { IChannelAdmissionRule } from "./connectionAdmission.js";
|
|
7
|
+
import type { IRealtimeConnectionState } from "../../interfaces/IRealtimeConnectionState.js";
|
|
8
|
+
import type { ContractServerEmit, EventNames, EventPayload, IRealtimeContract } from "nodefony";
|
|
2
9
|
import type { RealtimePublish } from "../../interfaces/IRealtimeController.js";
|
|
3
10
|
import type { IClientLogsLimits } from "../../interfaces/IClientLogsLimits.js";
|
|
4
11
|
import type { IChannelPolicy } from "../../interfaces/IChannelPolicy.js";
|
|
@@ -108,18 +115,62 @@ export interface IRevocableConnection {
|
|
|
108
115
|
* Sink d'un canal : pousse une charge vers UNE connexion abonnée (son `peer.notify`).
|
|
109
116
|
* 1 sink = 1 connexion. Le hub fan-out la charge à tous les sinks d'un canal.
|
|
110
117
|
*
|
|
111
|
-
* `
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
* sink se débrouille comme avant — un sink
|
|
115
|
-
* simplement l'argument.
|
|
118
|
+
* `frame` n'est fourni que si le canal a déclaré un {@link ChannelFrameBuilder} ET
|
|
119
|
+
* qu'il a plusieurs abonnés : la frame est la MÊME pour tous, le sink en demande
|
|
120
|
+
* l'encodage pour le codec de SA connexion ({@link ChannelFrame.encodeFor}) au lieu
|
|
121
|
+
* de refaire le travail. Absent, le sink se débrouille comme avant — un sink à un
|
|
122
|
+
* seul paramètre ignore simplement l'argument.
|
|
116
123
|
*/
|
|
117
|
-
export type ChannelSink = (payload: unknown,
|
|
124
|
+
export type ChannelSink = (payload: unknown, frame?: ChannelFrame) => void;
|
|
118
125
|
/**
|
|
119
|
-
*
|
|
120
|
-
* protocole
|
|
126
|
+
* Fabrique la frame protocolaire d'un canal (l'objet, pas ses octets), fournie par
|
|
127
|
+
* l'abonné : le hub ignore tout du protocole. Appelée **au plus une fois par
|
|
128
|
+
* publication**, jamais par abonné ; l'encodage, lui, se fait une fois par codec.
|
|
121
129
|
*/
|
|
122
|
-
export type
|
|
130
|
+
export type ChannelFrameBuilder = (payload: unknown) => unknown;
|
|
131
|
+
/**
|
|
132
|
+
* Frame d'UNE publication diffusée à plusieurs abonnés, encodée **au plus une fois
|
|
133
|
+
* par codec** (ADR-0022 §2) : un canal suivi par des connexions texte et CBOR paie
|
|
134
|
+
* deux encodages, jamais un par connexion. L'encodage est paresseux — un codec
|
|
135
|
+
* qu'aucun abonné n'utilise ne coûte rien.
|
|
136
|
+
*
|
|
137
|
+
* Deux places suffisent : un hub sert au plus son codec texte et son codec
|
|
138
|
+
* binaire. Un troisième codec reste servi, sans mise en cache.
|
|
139
|
+
*/
|
|
140
|
+
export declare class ChannelFrame {
|
|
141
|
+
private readonly message;
|
|
142
|
+
private codecA;
|
|
143
|
+
private dataA;
|
|
144
|
+
private codecB;
|
|
145
|
+
private dataB;
|
|
146
|
+
private claimedA;
|
|
147
|
+
private claimedB;
|
|
148
|
+
/** @param message - la frame protocolaire, telle que rendue par le {@link ChannelFrameBuilder}. */
|
|
149
|
+
constructor(message: unknown);
|
|
150
|
+
/**
|
|
151
|
+
* Rend la frame encodée pour un codec, en l'encodant à la première demande.
|
|
152
|
+
*
|
|
153
|
+
* @param codec - le codec de la connexion qui va l'écrire.
|
|
154
|
+
* @returns la trame à écrire telle quelle, ou `undefined` si la frame n'est pas
|
|
155
|
+
* encodable : l'abonné qui en {@link ChannelFrame.claimFailure | réclame}
|
|
156
|
+
* l'échec reprend son chemin habituel, qui porte le filet (journal).
|
|
157
|
+
*/
|
|
158
|
+
encodeFor(codec: IFrameCodec): RealtimeFrameData | undefined;
|
|
159
|
+
/**
|
|
160
|
+
* Réclame l'échec d'encodage de cette frame pour un codec : `true` au PREMIER
|
|
161
|
+
* abonné qui le demande, `false` ensuite.
|
|
162
|
+
*
|
|
163
|
+
* Un seul abonné reprend alors son chemin habituel, dont le filet journalise
|
|
164
|
+
* l'échec ; les autres sautent la trame, qu'ils ne pourraient pas encoder non
|
|
165
|
+
* plus. Sans cela, une charge non encodable — un `$bytes` forgé par un client,
|
|
166
|
+
* une valeur cyclique — coûtait un encodage et une ligne de journal PAR abonné.
|
|
167
|
+
* Un codec hors des deux places réclame à chaque fois : il n'est pas retenu.
|
|
168
|
+
*
|
|
169
|
+
* @param codec - le codec dont {@link ChannelFrame.encodeFor} a rendu `undefined`.
|
|
170
|
+
* @returns `true` si l'appelant doit dire l'échec.
|
|
171
|
+
*/
|
|
172
|
+
claimFailure(codec: IFrameCodec): boolean;
|
|
173
|
+
}
|
|
123
174
|
/**
|
|
124
175
|
* Fabrique le provider PARTAGÉ d'un canal (listener/ticker qui pousse via `publish`).
|
|
125
176
|
* Appelée UNE fois, au 1ᵉʳ abonné. Renvoie son `dispose` (appelé au dernier
|
|
@@ -157,6 +208,12 @@ export type ChannelFactory = (channel: string, publish: RealtimePublish) => (()
|
|
|
157
208
|
*/
|
|
158
209
|
export declare class RealtimeHub {
|
|
159
210
|
#private;
|
|
211
|
+
/**
|
|
212
|
+
* Budget des admissions de l'exemplaire (appels d'`allow` en vol, coupe-circuit,
|
|
213
|
+
* `allowTimedOutTotal`/`allowSaturatedTotal`) — partagé par les files de toutes
|
|
214
|
+
* les connexions. Bornes posées par {@link setAdmissionLimits} au boot.
|
|
215
|
+
*/
|
|
216
|
+
readonly admissionBudget: AdmissionBudget;
|
|
160
217
|
/**
|
|
161
218
|
* Abonnement demandé par une **connexion cliente** — même chose que
|
|
162
219
|
* {@link subscribe}, plus le plancher des canaux de plateforme.
|
|
@@ -171,9 +228,11 @@ export declare class RealtimeHub {
|
|
|
171
228
|
* sécurité en pose un, c'est lui qui décide (avec les rôles, ce que le hub ne
|
|
172
229
|
* sait pas faire) et le plancher s'efface.
|
|
173
230
|
*
|
|
231
|
+
* @param member - la connexion qui s'abonne : inscrite au registre du canal,
|
|
232
|
+
* elle devient un membre identifiable ({@link channelMembers}).
|
|
174
233
|
* @returns `true` si abonné, `false` si canal inconnu ou fermé par le plancher.
|
|
175
234
|
*/
|
|
176
|
-
subscribeClient(channel: string, sink: ChannelSink, factory: ChannelFactory,
|
|
235
|
+
subscribeClient(channel: string, sink: ChannelSink, factory: ChannelFactory, buildFrame?: ChannelFrameBuilder, member?: RealtimeConnection): boolean;
|
|
177
236
|
/**
|
|
178
237
|
* Abonne un consommateur qui **répond du canal** : il apporte la `factory`, donc
|
|
179
238
|
* la réponse à « ce canal existe-t-il, et qui le produit ? ». Le provider partagé
|
|
@@ -187,7 +246,7 @@ export declare class RealtimeHub {
|
|
|
187
246
|
*
|
|
188
247
|
* @returns `true` si abonné, `false` si canal inconnu (`factory` a renvoyé `null`).
|
|
189
248
|
*/
|
|
190
|
-
subscribe(channel: string, sink: ChannelSink, factory: ChannelFactory,
|
|
249
|
+
subscribe(channel: string, sink: ChannelSink, factory: ChannelFactory, buildFrame?: ChannelFrameBuilder): boolean;
|
|
191
250
|
/**
|
|
192
251
|
* **Écoute passive** — un service du serveur reçoit ce qui passe sur un canal
|
|
193
252
|
* sans prétendre le produire. C'est la porte de {@link ServerRealtimeSocket} :
|
|
@@ -213,6 +272,99 @@ export declare class RealtimeHub {
|
|
|
213
272
|
* et retire le canal (libère timers/listeners). No-op si non abonné.
|
|
214
273
|
*/
|
|
215
274
|
unsubscribe(channel: string, sink: ChannelSink): void;
|
|
275
|
+
/**
|
|
276
|
+
* Les connexions abonnées à un canal par un client — **local à ce pod** : les membres servis par un autre exemplaire n'y figurent pas.
|
|
277
|
+
*
|
|
278
|
+
* Les écouteurs et services du serveur (`listen`, `subscribe`) ne sont pas
|
|
279
|
+
* des membres. Chemin froid : une copie à chaque appel.
|
|
280
|
+
*
|
|
281
|
+
* @param channel - nom exact du canal.
|
|
282
|
+
* @returns les membres de ce pod, vide si le canal n'en a aucun ici.
|
|
283
|
+
*/
|
|
284
|
+
channelMembers(channel: string): IRealtimeConnection[];
|
|
285
|
+
/**
|
|
286
|
+
* Abonne d'office une connexion à un canal — **local à ce pod** : seule une connexion acceptée par ce processus peut l'être.
|
|
287
|
+
*
|
|
288
|
+
* Même porte qu'un `subscribe` du client (ADR-0022 §6 décision 2) : la
|
|
289
|
+
* politique fixe du canal (rôles, scopes, plancher de plateforme), puis sa
|
|
290
|
+
* règle `allow` s'il en déclare une. Un refus n'abonne pas, et seul
|
|
291
|
+
* l'appelant l'apprend ; jamais un abonnement qui contourne `allow` — le
|
|
292
|
+
* client rejoue ses abonnements à chaque reconnexion, et ce rejeu passe par
|
|
293
|
+
* `allow` : l'application écrit donc l'appartenance là où `allow` la lit.
|
|
294
|
+
*
|
|
295
|
+
* Le client est notifié par `realtime:subscribed { channel, imposed: true }`
|
|
296
|
+
* et inscrit le canal dans ses abonnements ; il peut le quitter.
|
|
297
|
+
*
|
|
298
|
+
* Plusieurs exemplaires : la requête qui décide doit atteindre le processus
|
|
299
|
+
* qui tient la socket — la passer par la socket elle-même (`@RealtimeInbound`,
|
|
300
|
+
* qui reçoit la connexion), ou faire autoriser l'entrée par `allow` (registre
|
|
301
|
+
* partagé) et laisser le navigateur s'abonner.
|
|
302
|
+
*
|
|
303
|
+
* @param channel - nom exact du canal.
|
|
304
|
+
* @param connection - la connexion à abonner (reçue par un gestionnaire `@RealtimeInbound`, ou lue dans {@link channelMembers}).
|
|
305
|
+
* @returns `true` une fois l'abonnement acquitté ; `false` si refusé, si la
|
|
306
|
+
* connexion est fermée ou inconnue de ce processus, ou si le client s'en
|
|
307
|
+
* désabonne avant le verdict.
|
|
308
|
+
*/
|
|
309
|
+
subscribeConnection(channel: string, connection: IRealtimeConnection): Promise<boolean>;
|
|
310
|
+
/**
|
|
311
|
+
* Retire une connexion d'un canal — **local à ce pod** ; elle reçoit `realtime:closed { reason: "evicted" }`.
|
|
312
|
+
*
|
|
313
|
+
* ⚠️ **Éjecter coupe la connexion du canal, seul `allow` exclut**
|
|
314
|
+
* (ADR-0022 §6 décision 3). Le client ne rejoue pas un canal dont il a été
|
|
315
|
+
* éjecté, mais c'est une politesse : une page rechargée refait `subscribe`.
|
|
316
|
+
* Sur un canal à `allow`, l'éjecté revient si l'application n'a pas retiré
|
|
317
|
+
* l'appartenance là où `allow` la lit ; sur un canal sans `allow`, sa
|
|
318
|
+
* politique fixe le laisse rentrer.
|
|
319
|
+
*
|
|
320
|
+
* @param channel - nom exact du canal.
|
|
321
|
+
* @param connection - la connexion à retirer.
|
|
322
|
+
* @returns `false` si la connexion n'est pas membre du canal, est fermée, ou
|
|
323
|
+
* inconnue de ce processus — jamais un geste muet.
|
|
324
|
+
*/
|
|
325
|
+
evictConnection(channel: string, connection: IRealtimeConnection): boolean;
|
|
326
|
+
/**
|
|
327
|
+
* Termine un canal pour tous ses membres, **sur tous les serveurs** : chacun reçoit `realtime:closed { reason: "ended" }`, et le producteur est libéré.
|
|
328
|
+
*
|
|
329
|
+
* **Traverse les pods** : avec un backplane branché, l'ordre part sur le bus
|
|
330
|
+
* (`PLATFORM_BUS_CHANNELS.control`, scellé comme toute publication) et chaque
|
|
331
|
+
* serveur l'applique à SES membres. Livraison « au plus une fois » du bus :
|
|
332
|
+
* un pod qui manque l'ordre, ou qui démarre après, est rattrapé par la
|
|
333
|
+
* fabrique, qui rend `null` pour une instance terminée. Un canal de la
|
|
334
|
+
* plateforme (`nodefony:`) reste local — il décrit un pod, pas une partie.
|
|
335
|
+
*
|
|
336
|
+
* Les écouteurs du serveur (`listen`) restent inscrits, sur un canal redevenu
|
|
337
|
+
* PASSIF : le prochain `subscribe` d'un client rejoue la fabrique, et c'est
|
|
338
|
+
* elle qui tranche. **Seule la fabrique clôt un canal** (ADR-0021 S4) — le
|
|
339
|
+
* hub ne garde aucune marque « fermé » : une fabrique qui rend encore un
|
|
340
|
+
* producteur pour ce nom le rouvre. D'où la règle d'écriture d'un canal à
|
|
341
|
+
* durée de vie : son nom porte l'identifiant de l'instance
|
|
342
|
+
* (`room:42:<partie>`), et la fabrique rend `null` pour une instance terminée.
|
|
343
|
+
*
|
|
344
|
+
* @param channel - nom exact du canal.
|
|
345
|
+
* @returns le nombre de membres de CE pod à qui la fin a été notifiée — les
|
|
346
|
+
* autres serveurs l'appliquent sans compte rendu.
|
|
347
|
+
*/
|
|
348
|
+
closeChannel(channel: string): number;
|
|
349
|
+
/**
|
|
350
|
+
* Éjecte un compte d'un canal, **sur tous les serveurs** : chacune de ses connexions membres reçoit `realtime:closed { reason: "evicted" }`.
|
|
351
|
+
*
|
|
352
|
+
* Tous les onglets du compte sont visés, où qu'ils soient servis ; l'ordre
|
|
353
|
+
* traverse les pods comme {@link closeChannel}. ⚠️ **Éjecter coupe, seul
|
|
354
|
+
* `allow` exclut** (ADR-0022 §6 décision 3) : pour que le compte ne revienne
|
|
355
|
+
* pas, l'application retire l'appartenance là où `allow` la lit.
|
|
356
|
+
*
|
|
357
|
+
* ⚠️ Sur un bus PARTAGÉ, un tiers qui y écrit peut rejouer un ordre capté :
|
|
358
|
+
* sur un canal durable, il recouperait le compte à chaque réadmission (risque
|
|
359
|
+
* accepté et borné, ADR-0022 §6 d4) — bus privé exigé ; changer le secret du
|
|
360
|
+
* bus rend caducs les ordres déjà captés.
|
|
361
|
+
*
|
|
362
|
+
* @param channel - nom exact du canal.
|
|
363
|
+
* @param user - identifiant du compte (`IRealtimeConnection.userId`).
|
|
364
|
+
* @returns le nombre de connexions de CE pod éjectées. Un nom vide ou de plus
|
|
365
|
+
* de 1 024 caractères n'éjecte rien et ne part pas sur le bus.
|
|
366
|
+
*/
|
|
367
|
+
evictUser(channel: string, user: string): number;
|
|
216
368
|
/**
|
|
217
369
|
* Publie une charge sur un canal : **fan-out local** **+** propagation conditionnelle
|
|
218
370
|
* au {@link IBackplane} (autres workers/pods). C'est le point d'entrée des providers
|
|
@@ -226,8 +378,29 @@ export declare class RealtimeHub {
|
|
|
226
378
|
*
|
|
227
379
|
* Anti-boucle : un message **reçu** du backplane ne repasse PAS par ici (il appelle
|
|
228
380
|
* `publishLocal` directement) → aucune ré-émission cross-process, pas de tempête.
|
|
381
|
+
*
|
|
382
|
+
* @param payload - objet, tableau ou octets ; omise = signal sans charge.
|
|
383
|
+
* @throws TypeError si la charge est un scalaire ou `null` ({@link isRealtimePayload},
|
|
384
|
+
* ADR-0021 S1) — un scalaire se publie dans un objet (`{ value }`).
|
|
229
385
|
*/
|
|
230
|
-
publish(channel: string, payload
|
|
386
|
+
publish(channel: string, payload?: object): void;
|
|
387
|
+
/**
|
|
388
|
+
* Livre une charge à UNE identité — tous ses onglets, tous les serveurs, et personne d'autre (ADR-0022 §6, D1).
|
|
389
|
+
*
|
|
390
|
+
* Publie sur son canal personnel `user:<userId>` : seuls les onglets de cette
|
|
391
|
+
* identité ont pu s'y abonner (politique intégrée, admission par égalité de
|
|
392
|
+
* chaîne), et le préfixe traverse le bus dès qu'un bus est branché. Rien à
|
|
393
|
+
* déclarer côté application. Personne n'écoute ? La charge est perdue, sans
|
|
394
|
+
* erreur : c'est une diffusion, pas une boîte aux lettres.
|
|
395
|
+
*
|
|
396
|
+
* @param userId - l'`id` STABLE du compte (`RealtimeIdentity.id`), jamais le
|
|
397
|
+
* login.
|
|
398
|
+
* @param payload - objet, tableau ou octets ; omise = signal sans charge.
|
|
399
|
+
* @throws TypeError si la charge est un scalaire ou `null` (cf {@link publish}).
|
|
400
|
+
*/
|
|
401
|
+
publishToUser(userId: string, payload?: object): void;
|
|
402
|
+
/** Préfixes déclarés diffusables entre serveurs, dans l'ordre de déclaration — lu par la ligne de démarrage. */
|
|
403
|
+
broadcastPrefixes(): readonly string[];
|
|
231
404
|
/**
|
|
232
405
|
* Fan-out d'une charge aux **seuls abonnés locaux** d'un canal (process courant).
|
|
233
406
|
* NE propage PAS au backplane → c'est la voie d'**ingress** d'un message venu d'un
|
|
@@ -269,7 +442,11 @@ export declare class RealtimeHub {
|
|
|
269
442
|
* sur le chemin chaud). À équilibrer par {@link unregisterConnection} au close.
|
|
270
443
|
*/
|
|
271
444
|
registerConnection(conn: IRealtimeConnProbe): void;
|
|
272
|
-
/**
|
|
445
|
+
/**
|
|
446
|
+
* Retire une connexion du registre de la sonde (au close) et verse ses cumuls à
|
|
447
|
+
* ceux des connexions fermées : les totaux de {@link probe} restent monotones.
|
|
448
|
+
* No-op si absente — une double sortie ne compte pas deux fois.
|
|
449
|
+
*/
|
|
273
450
|
unregisterConnection(conn: IRealtimeConnProbe): void;
|
|
274
451
|
/**
|
|
275
452
|
* Inscrit une connexion à identité RÉVOCABLE au registre de
|
|
@@ -361,6 +538,30 @@ export declare class RealtimeHub {
|
|
|
361
538
|
* `defineRealtimeConfig().slowConsumer.bytes` (sinon la clé de config serait morte).
|
|
362
539
|
*/
|
|
363
540
|
setSlowConsumerBytes(bytes: number): void;
|
|
541
|
+
/**
|
|
542
|
+
* Les codecs de frame du serveur (ADR-0022 §2) : le texte, et le codec binaire
|
|
543
|
+
* accordé aux clients qui l'offrent — `null` tant que la configuration n'en
|
|
544
|
+
* active aucun. Lu une fois par connexion, au handshake, jamais par trame.
|
|
545
|
+
*/
|
|
546
|
+
get frameCodecs(): IFrameCodecSet;
|
|
547
|
+
/**
|
|
548
|
+
* Pose le codec binaire du serveur — 1× au boot par `RealtimeService.init()`,
|
|
549
|
+
* depuis la clé `codec` de la configuration. Sans argument : texte seul.
|
|
550
|
+
*
|
|
551
|
+
* @param codec - le codec activé (`CBOR_FRAME_CODEC` de `nodefony/cbor`).
|
|
552
|
+
*/
|
|
553
|
+
setFrameCodec(codec?: IFrameCodec): void;
|
|
554
|
+
/**
|
|
555
|
+
* Le codec des connexions texte — celui qu'emprunte aussi le REST du plan
|
|
556
|
+
* d'administration, pour qu'une valeur lue par `GET` soit celle que la socket
|
|
557
|
+
* livre (#618 D9). Avec un codec binaire activé, c'est le JSON à convention
|
|
558
|
+
* d'octets ; sinon le JSON natif.
|
|
559
|
+
*/
|
|
560
|
+
get textFrameCodec(): IFrameCodec;
|
|
561
|
+
/**
|
|
562
|
+
* Pose les bornes des admissions (`authenticateTimeoutMs`, `allowTimeoutMs`, `maxPendingAllowsPerConnection`, `maxPendingAllowsTotal`) — 1× au boot par `RealtimeService.init()`.
|
|
563
|
+
*/
|
|
564
|
+
setAdmissionLimits(limits: IAdmissionLimits): void;
|
|
364
565
|
/**
|
|
365
566
|
* Associe un token au peer (posé par `RealtimeController.onHandshake()` après
|
|
366
567
|
* succès `authenticator.authenticate()` ou fallback anonyme). Lazy alloc de
|
|
@@ -439,9 +640,18 @@ export declare class RealtimeHub {
|
|
|
439
640
|
*
|
|
440
641
|
* @param name - nom exact du canal (subscribe), de la méthode inbound, ou
|
|
441
642
|
* MOTIF de famille s'il contient `*`.
|
|
643
|
+
* Une politique à `allow` porte sa règle d'admission : une flèche est
|
|
644
|
+
* retenue telle quelle ; une classe exige son instance, résolue au démarrage
|
|
645
|
+
* par l'injecteur, dont la méthode est liée ICI, une fois — le `subscribe`
|
|
646
|
+
* n'appelle qu'une fonction prête.
|
|
647
|
+
*
|
|
442
648
|
* @param policy - exigences ({@link IChannelPolicy}) lues par security.
|
|
649
|
+
* @param admission - instance de la classe d'admission quand `policy.allow`
|
|
650
|
+
* en est une ; ignorée sinon.
|
|
651
|
+
* @throws BootConfigurationError La politique désigne une classe d'admission
|
|
652
|
+
* et aucune instance de CETTE classe n'est fournie.
|
|
443
653
|
*/
|
|
444
|
-
registerChannelPolicy(name: string, policy: IChannelPolicy): void;
|
|
654
|
+
registerChannelPolicy(name: string, policy: IChannelPolicy, admission?: IChannelAdmission): void;
|
|
445
655
|
/**
|
|
446
656
|
* Nombre maximum de `*` dans un motif.
|
|
447
657
|
*
|
|
@@ -459,6 +669,31 @@ export declare class RealtimeHub {
|
|
|
459
669
|
* la frame `subscribe`/inbound. O(1), 0 alloc quand le registre est vide.
|
|
460
670
|
*/
|
|
461
671
|
resolveChannelPolicy(name: string): IChannelPolicy | null;
|
|
672
|
+
/**
|
|
673
|
+
* Règle d'admission prête à appeler pour ce canal — `null` s'il ne déclare pas `allow`. Lue au `subscribe`, jamais par trame.
|
|
674
|
+
*
|
|
675
|
+
* Même résolution que {@link resolveChannelPolicy} (nom exact, puis motif le
|
|
676
|
+
* plus spécifique) : la règle suit TOUJOURS la politique qui s'applique.
|
|
677
|
+
*/
|
|
678
|
+
resolveChannelAdmission(name: string): IChannelAdmissionRule | null;
|
|
679
|
+
/**
|
|
680
|
+
* Les politiques de cette classe de contrôleur sont-elles déjà inscrites ? Une poignée de main n'en paie que ce `has`.
|
|
681
|
+
*
|
|
682
|
+
* @param owner - le constructeur du contrôleur.
|
|
683
|
+
*/
|
|
684
|
+
hasPolicyOwner(owner: object): boolean;
|
|
685
|
+
/**
|
|
686
|
+
* Marque les politiques de cette classe comme inscrites — APRÈS l'inscription réussie, jamais avant : une politique refusée doit l'être de nouveau à la poignée de main suivante, pas laisser le canal ouvert.
|
|
687
|
+
*
|
|
688
|
+
* @param owner - le constructeur du contrôleur.
|
|
689
|
+
*/
|
|
690
|
+
addPolicyOwner(owner: object): void;
|
|
691
|
+
/**
|
|
692
|
+
* Une politique est-elle déclarée sous ce nom EXACT — motifs ignorés ? C'est la question du défaut fermé des actions.
|
|
693
|
+
*
|
|
694
|
+
* @param name - nom d'action ou de canal.
|
|
695
|
+
*/
|
|
696
|
+
hasExactChannelPolicy(name: string): boolean;
|
|
462
697
|
/** Des politiques de canal sont-elles déclarées (au moins une) ? */
|
|
463
698
|
hasChannelPolicies(): boolean;
|
|
464
699
|
/**
|
|
@@ -466,14 +701,14 @@ export declare class RealtimeHub {
|
|
|
466
701
|
* DYNAMIQUE n'est couvert par aucune politique déclarée, alors que d'autres
|
|
467
702
|
* canaux du module en portent une.
|
|
468
703
|
*
|
|
469
|
-
* Le registre des politiques
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
*
|
|
474
|
-
*
|
|
475
|
-
* canaux qu'aucun motif ne couvre
|
|
476
|
-
* de l'outil.
|
|
704
|
+
* Le registre des politiques lit le nom **EXACT**, puis les **MOTIFS**
|
|
705
|
+
* déclarés ({@link resolveChannelPolicy}) : `@RealtimeChannel` déclare
|
|
706
|
+
* `"chat:room"`, mais un canal dérivé (`chat:room:1000` — cadence, forage,
|
|
707
|
+
* suffixe) servi par la fabrique dynamique ne matche ce nom que si un motif
|
|
708
|
+
* (`chat:room:*`) le couvre. Sans motif, l'auteur croit son canal gardé ; il
|
|
709
|
+
* ne l'est pas, et rien ne le lui dit. C'est ce silence qu'on ferme ici :
|
|
710
|
+
* l'avertissement ne parle que des canaux qu'aucun motif ne couvre — un vrai
|
|
711
|
+
* trou, pas une limite de l'outil.
|
|
477
712
|
*
|
|
478
713
|
* Appelé par le contrôleur, seul à savoir par quelle voie le canal a été
|
|
479
714
|
* servi. Muet si aucune politique n'est déclarée (module aux canaux libres :
|
|
@@ -514,6 +749,21 @@ export declare class RealtimeHub {
|
|
|
514
749
|
/** Bornes en vigueur, ou `null` si le canal montant est fermé. */
|
|
515
750
|
get clientLogsLimits(): IClientLogsLimits | null;
|
|
516
751
|
runAuthorizer(frame: unknown, peer: JsonRpcPeer): boolean;
|
|
752
|
+
/**
|
|
753
|
+
* Inscrit une connexion qui vient de retenir son premier « oui » du verrou.
|
|
754
|
+
*
|
|
755
|
+
* Appelé par `gateFrame`, jamais par trame chaude ; à équilibrer par
|
|
756
|
+
* {@link untrackRetainedVerdicts} à la fermeture.
|
|
757
|
+
*
|
|
758
|
+
* @param state - état de la connexion.
|
|
759
|
+
*/
|
|
760
|
+
trackRetainedVerdicts(state: IRealtimeConnectionState): void;
|
|
761
|
+
/**
|
|
762
|
+
* Retire une connexion fermée du registre des verdicts retenus.
|
|
763
|
+
*
|
|
764
|
+
* @param state - état de la connexion.
|
|
765
|
+
*/
|
|
766
|
+
untrackRetainedVerdicts(state: IRealtimeConnectionState): void;
|
|
517
767
|
/**
|
|
518
768
|
* Ce peer pourrait-il s'abonner à ce canal ? — même décision que `subscribe`,
|
|
519
769
|
* sans trace ni effet de bord.
|
|
@@ -533,6 +783,53 @@ export declare class RealtimeHub {
|
|
|
533
783
|
/** Dispose tous les providers et vide le hub (arrêt process / reset de test). */
|
|
534
784
|
clear(): void;
|
|
535
785
|
}
|
|
536
|
-
/**
|
|
786
|
+
/**
|
|
787
|
+
* Le hub lu à travers le contrat `C` : mêmes méthodes que {@link RealtimeHub},
|
|
788
|
+
* mais `publish` n'accepte que les canaux de `serverToClient`, chacun avec la
|
|
789
|
+
* charge qu'il déclare. C'est le MÊME objet que le hub libre — une vue de type,
|
|
790
|
+
* sans un octet ni une instruction de plus.
|
|
791
|
+
*
|
|
792
|
+
* @typeParam C - le contrat de l'application.
|
|
793
|
+
*/
|
|
794
|
+
export interface IContractRealtimeHub<C extends IRealtimeContract> extends Omit<RealtimeHub, "publish" | "publishToUser"> {
|
|
795
|
+
/**
|
|
796
|
+
* Livre à UNE identité la charge que le contrat déclare pour le canal
|
|
797
|
+
* personnel (clé gabarit `` [k: `user:${string}`]: … `` de `serverToClient`)
|
|
798
|
+
* — cf {@link RealtimeHub.publishToUser}. Contrat sans cette clé : ne compile
|
|
799
|
+
* pas, la forme du message privé se déclare d'abord.
|
|
800
|
+
*
|
|
801
|
+
* @param userId - l'`id` STABLE du compte, jamais le login.
|
|
802
|
+
*/
|
|
803
|
+
publishToUser(userId: string, payload: UserChannelPayload<ContractServerEmit<C>> & object): void;
|
|
804
|
+
/**
|
|
805
|
+
* Publie sur un canal déclaré par le contrat (fan-out local, puis backplane
|
|
806
|
+
* si le canal est broadcast) — cf {@link RealtimeHub.publish}.
|
|
807
|
+
*
|
|
808
|
+
* @throws TypeError si la charge n'est pas un objet ({@link isRealtimePayload}).
|
|
809
|
+
*/
|
|
810
|
+
publish<K extends EventNames<ContractServerEmit<C>>>(channel: K, payload: EventPayload<ContractServerEmit<C>, K> & object): void;
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Renvoie le hub realtime du process (singleton lazy) — forme LIBRE : tout nom
|
|
814
|
+
* de canal, toute charge objet.
|
|
815
|
+
*/
|
|
537
816
|
export declare function getRealtimeHub(): RealtimeHub;
|
|
817
|
+
/**
|
|
818
|
+
* Renvoie le hub realtime du process lu à travers le contrat `C` : la diffusion
|
|
819
|
+
* d'un service, d'un job ou d'une boucle de jeu est vérifiée par le contrat
|
|
820
|
+
* partagé avec le navigateur. Même objet que la forme libre : 0 octet, 0 ns.
|
|
821
|
+
*
|
|
822
|
+
* Le contrat est choisi par l'appelant — rien n'empêche d'en nommer un autre que
|
|
823
|
+
* celui du contrôleur qui sert le canal ; c'est le fichier partagé qui fait foi.
|
|
824
|
+
*
|
|
825
|
+
* @typeParam C - le contrat de l'application.
|
|
826
|
+
*
|
|
827
|
+
* @example
|
|
828
|
+
* ```ts
|
|
829
|
+
* const chat = getRealtimeHub<ChatContract>();
|
|
830
|
+
* chat.publish("chat:room", { from: "ana", text: "salut" }); // charge vérifiée
|
|
831
|
+
* getRealtimeHub().publish("n'importe:quoi", { x: 1 }); // forme libre
|
|
832
|
+
* ```
|
|
833
|
+
*/
|
|
834
|
+
export declare function getRealtimeHub<C extends IRealtimeContract>(): IContractRealtimeHub<C>;
|
|
538
835
|
export default RealtimeHub;
|
|
@@ -31,7 +31,7 @@ export declare class ServerRealtimeSocket<Emit extends EventsMap = DefaultEvents
|
|
|
31
31
|
private readonly hub;
|
|
32
32
|
constructor(hub?: import("./RealtimeHub.js").RealtimeHub);
|
|
33
33
|
/** Émet sur un canal — fan-out aux abonnés via le hub (+ backplane si broadcast). */
|
|
34
|
-
publish<K extends string>(channel: K, payload?: K extends EventNames<Emit> ? EventPayload<Emit, K> :
|
|
34
|
+
publish<K extends string>(channel: K, payload?: K extends EventNames<Emit> ? EventPayload<Emit, K> : object): void;
|
|
35
35
|
/**
|
|
36
36
|
* S'abonne (ref-compté) à un canal pour le RECEVOIR. Au 1ᵉʳ consommateur, pose un
|
|
37
37
|
* sink d'**écoute passive** auprès du hub : le service reçoit ce qui passe, sans
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/** Rendu par {@link AdmissionBudget.acquire} quand une place est réservée. */
|
|
2
|
+
export declare const ADMISSION_GRANTED = -1;
|
|
3
|
+
/** Rendu par {@link AdmissionBudget.authenticateWithin} quand l'authentification n'a pas répondu à temps. */
|
|
4
|
+
export declare const AUTHENTICATE_TIMED_OUT: unique symbol;
|
|
5
|
+
/** Bornes des admissions, lues dans la configuration realtime (`authenticateTimeoutMs`, `allowTimeoutMs`, `maxPendingAllowsPerConnection`, `maxPendingAllowsTotal`). */
|
|
6
|
+
export interface IAdmissionLimits {
|
|
7
|
+
readonly authenticateTimeoutMs: number;
|
|
8
|
+
readonly allowTimeoutMs: number;
|
|
9
|
+
readonly maxPendingAllowsPerConnection: number;
|
|
10
|
+
readonly maxPendingAllowsTotal: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Budget des admissions d'UN exemplaire — appels d'`allow` en vol, coupe-circuit et compteurs de refus (ADR-0021 §S10 bis point 3, §S9).
|
|
14
|
+
*
|
|
15
|
+
* Partagé par toutes les connexions du hub ; chaque connexion garde sa propre
|
|
16
|
+
* file ({@link ConnectionAdmission}). Aucune horloge n'est lue tant que le
|
|
17
|
+
* coupe-circuit est fermé : une règle synchrone ne paie que deux incréments.
|
|
18
|
+
*/
|
|
19
|
+
export declare class AdmissionBudget {
|
|
20
|
+
#private;
|
|
21
|
+
/** Délai de l'authentification à la poignée de main (ADR-0021 §S11). */
|
|
22
|
+
authenticateTimeoutMs: number;
|
|
23
|
+
/** Délai d'un appel d'`allow`, compté depuis la réception du `subscribe`, file comprise. */
|
|
24
|
+
timeoutMs: number;
|
|
25
|
+
/** Appels d'`allow` en vol par connexion ; au-delà, la demande attend son tour. */
|
|
26
|
+
maxPerConnection: number;
|
|
27
|
+
/** Applique les bornes de la configuration (boot, cold path). */
|
|
28
|
+
configure(limits: IAdmissionLimits): void;
|
|
29
|
+
/**
|
|
30
|
+
* Réserve une place pour UN appel d'`allow`.
|
|
31
|
+
*
|
|
32
|
+
* @returns {@link ADMISSION_GRANTED} si la place est prise ; sinon la demande
|
|
33
|
+
* est comptée dans `allowSaturatedTotal` et la valeur rendue est le délai à
|
|
34
|
+
* annoncer au client (`retryAfterMs`, coupe-circuit ouvert), ou `0` quand il
|
|
35
|
+
* n'est pas connu (plafond global atteint).
|
|
36
|
+
*/
|
|
37
|
+
acquire(): number;
|
|
38
|
+
/** Rend la place d'un appel terminé, dépassé ou abandonné à la fermeture. */
|
|
39
|
+
release(): void;
|
|
40
|
+
/** Un appel d'`allow` n'a pas répondu dans son délai : compté, et porté au coupe-circuit. */
|
|
41
|
+
recordTimeout(now: number): void;
|
|
42
|
+
/** Une demande a épuisé son délai dans la file, sans appel d'`allow`. */
|
|
43
|
+
recordQueueExpired(): void;
|
|
44
|
+
/** Délai restant du coupe-circuit à `now`, ou `undefined` s'il est fermé. */
|
|
45
|
+
retryAfter(now: number): number | undefined;
|
|
46
|
+
/** Faut-il journaliser cette cause maintenant ? Vrai au plus une fois par fenêtre et par cause. */
|
|
47
|
+
shouldWarn(kind: "timeout" | "throw" | "authenticate", now: number): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Borne l'authentification d'une poignée de main par `authenticateTimeoutMs` (ADR-0021 §S11).
|
|
50
|
+
*
|
|
51
|
+
* Une identité arrivée après le délai est IGNORÉE : la promesse rendue est
|
|
52
|
+
* déjà réglée sur {@link AUTHENTICATE_TIMED_OUT}, et un rejet tardif ne lève
|
|
53
|
+
* rien (la promesse d'origine garde son gestionnaire). Le minuteur est coupé
|
|
54
|
+
* dès la réponse et ne retient pas le processus à l'arrêt. Chaque dépassement
|
|
55
|
+
* compte une poignée de main dans `authenticateTimedOutTotal`.
|
|
56
|
+
*
|
|
57
|
+
* @param pending - la promesse rendue par `authenticate()`
|
|
58
|
+
* @returns le jeton, ou {@link AUTHENTICATE_TIMED_OUT} au dépassement
|
|
59
|
+
*/
|
|
60
|
+
authenticateWithin<T>(pending: Promise<T>): Promise<T | typeof AUTHENTICATE_TIMED_OUT>;
|
|
61
|
+
/** Appels d'`allow` en vol sur l'exemplaire. */
|
|
62
|
+
get pending(): number;
|
|
63
|
+
/** Cumul `allowTimedOutTotal` — unité : un appel d'`allow`. */
|
|
64
|
+
get timedOutTotal(): number;
|
|
65
|
+
/** Cumul `allowSaturatedTotal` — unité : une demande d'abonnement. */
|
|
66
|
+
get saturatedTotal(): number;
|
|
67
|
+
/** Cumul `authenticateTimedOutTotal` — unité : une poignée de main. */
|
|
68
|
+
get authenticateTimedOutTotal(): number;
|
|
69
|
+
/** Remet compteurs, coupe-circuit et bornes à leurs défauts (hub vidé). */
|
|
70
|
+
reset(): void;
|
|
71
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ChannelAdmissionClass, IChannelAdmission } from "nodefony";
|
|
2
|
+
import type RealtimeHub from "./RealtimeHub.js";
|
|
3
|
+
/**
|
|
4
|
+
* Résout l'instance d'une classe d'admission pour un canal — fournie par le module realtime au démarrage, absente à la poignée de main.
|
|
5
|
+
*/
|
|
6
|
+
export type AdmissionResolver = (admissionClass: ChannelAdmissionClass, channel: string) => IChannelAdmission;
|
|
7
|
+
/**
|
|
8
|
+
* Inscrit au hub les politiques déclarées par une classe de contrôleur — une fois par classe et par hub, et seulement si l'inscription entière réussit.
|
|
9
|
+
*
|
|
10
|
+
* Appelée pour chaque contrôleur connu au démarrage du noyau, puis à la
|
|
11
|
+
* poignée de main pour une classe qui y aurait échappé : celle-ci ne paie alors
|
|
12
|
+
* qu'un `has`. Une politique refusée ne marque PAS la classe — elle est refusée
|
|
13
|
+
* de nouveau à la poignée de main suivante plutôt que de laisser le canal
|
|
14
|
+
* ouvert.
|
|
15
|
+
*
|
|
16
|
+
* @param hub - le hub qui porte le registre.
|
|
17
|
+
* @param ctor - constructeur du contrôleur.
|
|
18
|
+
* @param resolveAdmission - résolution des classes d'admission ; `null` hors
|
|
19
|
+
* démarrage, et le hub refuse alors une politique qui en désigne une.
|
|
20
|
+
* @throws BootConfigurationError Politique illisible, `allow` posé sur une
|
|
21
|
+
* action, ou classe d'admission non résolue.
|
|
22
|
+
*/
|
|
23
|
+
export declare function registerControllerPolicies(hub: RealtimeHub, ctor: object, resolveAdmission: AdmissionResolver | null): void;
|
|
24
|
+
/**
|
|
25
|
+
* Fabrique la résolution des classes d'admission du démarrage : portée singleton exigée, instance du conteneur réutilisée, sinon construite UNE fois par l'injecteur.
|
|
26
|
+
*
|
|
27
|
+
* @param get - l'instance déjà construite d'une classe : le démarrage la lit
|
|
28
|
+
* dans le conteneur du noyau par sa clé (`null` tant que la classe n'y a
|
|
29
|
+
* jamais été rangée), le harnais de test parmi les instances que le test lui
|
|
30
|
+
* donne. `undefined` → construite par l'injecteur.
|
|
31
|
+
* @param log - journal du module (une ligne par canal résolu).
|
|
32
|
+
* @returns le résolveur, qui mémorise l'instance de chaque classe.
|
|
33
|
+
*/
|
|
34
|
+
export declare function createAdmissionResolver(get: (key: string | null, admissionClass: ChannelAdmissionClass) => unknown, log: (message: string) => void): AdmissionResolver;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { ChannelAllowFunction, ISubscriberIdentity } from "nodefony";
|
|
2
|
+
import { type AdmissionBudget } from "./admissionBudget.js";
|
|
3
|
+
/**
|
|
4
|
+
* Règle d'admission d'un canal telle que le hub la tient prête — construite UNE fois à l'inscription de la politique, lue au `subscribe`.
|
|
5
|
+
*/
|
|
6
|
+
export interface IChannelAdmissionRule {
|
|
7
|
+
/** La règle, flèche ou méthode `allow` déjà liée à son instance. */
|
|
8
|
+
readonly allow: ChannelAllowFunction;
|
|
9
|
+
/** `anonymous: true` de la politique : l'anonyme est présenté à `allow` au lieu d'être refusé d'office. */
|
|
10
|
+
readonly anonymous: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Ce que la file d'admission demande à sa connexion — ouvrir le canal, le refuser, journaliser. Implémenté par `RealtimeController`, une fois par connexion.
|
|
14
|
+
*/
|
|
15
|
+
export interface IAdmissionHost {
|
|
16
|
+
/** Ouvre l'abonnement admis (et l'acquitte). */
|
|
17
|
+
open(channel: string): void;
|
|
18
|
+
/** Rend le refus observable (`realtime:denied`) ; `detail` n'est rédigé qu'hors production. */
|
|
19
|
+
deny(channel: string, reason: "forbidden" | "unavailable", detail: string | undefined, retryAfterMs?: number): void;
|
|
20
|
+
/** Journal d'exploitation (déjà limité en fréquence par l'appelant). */
|
|
21
|
+
warn(message: string): void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Admission des abonnements d'UNE connexion aux canaux qui déclarent `allow` — appels bornés, file d'attente, délai, verdict (ADR-0021 §S10 et §S10 bis).
|
|
25
|
+
*
|
|
26
|
+
* Coûts, par construction :
|
|
27
|
+
* - une règle qui répond `true`/`false` sans promesse ne crée ni minuteur, ni
|
|
28
|
+
* promesse, ni entrée d'attente ;
|
|
29
|
+
* - au-delà de `maxPendingAllowsPerConnection` appels en vol, la demande ATTEND
|
|
30
|
+
* son tour (jamais `limit` : le rejeu au `welcome` renvoie tous les canaux
|
|
31
|
+
* d'un coup) ; la file reste `null` tant que personne n'attend ;
|
|
32
|
+
* - un seul minuteur par connexion, armé sur l'échéance de tête : les demandes
|
|
33
|
+
* arrivent dans l'ordre de leurs échéances, la plus ancienne non tranchée est
|
|
34
|
+
* donc toujours la prochaine à expirer.
|
|
35
|
+
*
|
|
36
|
+
* Seul `=== true` admet ; une exception ou un rejet valent `forbidden` ; un
|
|
37
|
+
* délai dépassé ou une saturation valent `unavailable`, et un verdict arrivé
|
|
38
|
+
* après coup est ignoré.
|
|
39
|
+
*/
|
|
40
|
+
export declare class ConnectionAdmission {
|
|
41
|
+
#private;
|
|
42
|
+
/**
|
|
43
|
+
* @param host - la connexion qui ouvre ou refuse.
|
|
44
|
+
* @param budget - le budget de l'exemplaire (plafond global, coupe-circuit).
|
|
45
|
+
* @param who - identité d'admission gelée de la connexion.
|
|
46
|
+
* @param explain - rédiger le détail des refus (hors production).
|
|
47
|
+
*/
|
|
48
|
+
constructor(host: IAdmissionHost, budget: AdmissionBudget, who: ISubscriberIdentity, explain: boolean);
|
|
49
|
+
/** Demandes en attente de verdict — comptées dans le plafond de canaux de la connexion. */
|
|
50
|
+
get unsettled(): number;
|
|
51
|
+
/**
|
|
52
|
+
* Un abonnement à ce canal attend-il déjà son verdict ? S'il avait été annulé par un `unsubscribe`, il est ranimé : le verdict à venir répondra.
|
|
53
|
+
*
|
|
54
|
+
* @returns `true` si la demande est déjà en attente (rien d'autre à faire).
|
|
55
|
+
*/
|
|
56
|
+
revive(channel: string): boolean;
|
|
57
|
+
/** `unsubscribe` pendant l'attente : le verdict à venir sera jeté sans réponse. */
|
|
58
|
+
cancel(channel: string): void;
|
|
59
|
+
/**
|
|
60
|
+
* Demande l'admission sur `channel` selon sa règle — l'anonyme est refusé d'office sauf `anonymous: true`.
|
|
61
|
+
*
|
|
62
|
+
* @param channel - nom exact du canal demandé.
|
|
63
|
+
* @param rule - règle résolue par le hub pour ce canal.
|
|
64
|
+
*/
|
|
65
|
+
request(channel: string, rule: IChannelAdmissionRule): void;
|
|
66
|
+
/** Fermeture de la connexion : minuteur coupé, places rendues, verdicts à venir jetés. */
|
|
67
|
+
close(): void;
|
|
68
|
+
}
|