@nodefony/realtime 10.0.0-beta.6 → 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 +5 -0
- package/dist/_virtual/{_@oxc-project_runtime@0.157.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorate.js +1 -1
- package/dist/index.js +15 -3
- package/dist/nodefony/decorators/realtimeDecorators.js +231 -39
- package/dist/nodefony/src/backplane/RedisBackplane.js +33 -40
- package/dist/nodefony/src/backplane/controlOrder.js +34 -5
- package/dist/nodefony/src/backplane/envelope.js +422 -45
- 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/server/RealtimeConnection.js +31 -6
- package/dist/nodefony/src/server/RealtimeController.js +87 -44
- package/dist/nodefony/src/server/RealtimeHub.js +631 -127
- package/dist/nodefony/src/server/admissionBudget.js +8 -8
- package/dist/nodefony/src/server/channelPolicyRegistry.js +35 -13
- package/dist/nodefony/src/server/channelPolicyResolution.js +191 -0
- package/dist/nodefony/src/server/channelPresence.js +488 -0
- package/dist/nodefony/src/server/contractRoutes.js +3 -13
- package/dist/nodefony/src/server/frameAdmission.js +27 -35
- 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 +3 -0
- package/dist/nodefony/src/service/RealtimeService.js +9 -0
- package/dist/nodefony/testing/backplaneConformance.js +79 -3
- package/dist/nodefony/testing/index.js +22 -8
- package/dist/nodefony/testing/presenceClient.js +54 -0
- package/dist/types/index.d.ts +6 -1
- package/dist/types/nodefony/decorators/realtimeDecorators.d.ts +117 -16
- package/dist/types/nodefony/interfaces/IBackplane.d.ts +12 -1
- package/dist/types/nodefony/src/backplane/RedisBackplane.d.ts +10 -2
- package/dist/types/nodefony/src/backplane/controlOrder.d.ts +23 -0
- package/dist/types/nodefony/src/backplane/envelope.d.ts +50 -7
- 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/RealtimeConnection.d.ts +3 -0
- package/dist/types/nodefony/src/server/RealtimeController.d.ts +19 -1
- package/dist/types/nodefony/src/server/RealtimeHub.d.ts +139 -30
- package/dist/types/nodefony/src/server/channelPolicyRegistry.d.ts +9 -2
- 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/contractRoutes.d.ts +1 -1
- package/dist/types/nodefony/src/server/frameAdmission.d.ts +14 -18
- 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 +3 -0
- package/dist/types/nodefony/src/service/RealtimeService.d.ts +8 -1
- package/dist/types/nodefony/testing/backplaneConformance.d.ts +14 -2
- package/dist/types/nodefony/testing/index.d.ts +1 -1
- package/dist/types/nodefony/testing/presenceClient.d.ts +20 -0
- package/docs/actions.md +1 -0
- package/docs/architecture.md +21 -6
- package/docs/canaux-a-membres.md +13 -3
- package/docs/cas-d-usage.md +4 -3
- package/docs/configuration.md +38 -2
- package/docs/cookbook-chat.md +400 -152
- package/docs/observabilite.md +28 -9
- package/docs/protocole.md +10 -10
- package/docs/securite.md +64 -10
- package/package.json +12 -12
package/docs/observabilite.md
CHANGED
|
@@ -261,14 +261,15 @@ Le contrat est `IRealtimeProbe` (`nodefony/src/realtime/platformHealth.ts#IRealt
|
|
|
261
261
|
|
|
262
262
|
### Fan-out — l'amplification réelle du broker
|
|
263
263
|
|
|
264
|
-
| Champ
|
|
265
|
-
|
|
|
266
|
-
| `publishTotal`
|
|
267
|
-
| `fanoutTotal`
|
|
268
|
-
| `inboundTotal`
|
|
269
|
-
| `ingressRejectedTotal`
|
|
270
|
-
| `sealRejectedTotal`
|
|
271
|
-
| `
|
|
264
|
+
| Champ | Ce qu'il dit |
|
|
265
|
+
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
266
|
+
| `publishTotal` | Appels à `publish`. **Ce que le serveur a voulu envoyer.** |
|
|
267
|
+
| `fanoutTotal` | Livraisons effectives (`publish` × abonnés). **Ce que ça a réellement coûté.** |
|
|
268
|
+
| `inboundTotal` | Frames poussées par les clients sur les canaux entrants, via `RealtimeHub.recordInbound()` (`RealtimeHub.ts#RealtimeHub.recordInbound`). |
|
|
269
|
+
| `ingressRejectedTotal` | Messages venus du **backplane** et jetés parce que leur canal n'est pas déclaré diffusable. |
|
|
270
|
+
| `sealRejectedTotal` | Messages venus du **backplane** et jetés parce que leur sceau ne se vérifie pas. `0` sans bus scellé. |
|
|
271
|
+
| `ingressUnknownDiscardedTotal` | Messages venus du **backplane** et jetés parce que d'une version **plus récente** : canal `nodefony:bus:*` ou type d'ordre inconnus. |
|
|
272
|
+
| `localOnlyTotal` | Publications restées sur ce serveur alors qu'un bus est branché (canal non diffusable). `0` sans bus. |
|
|
272
273
|
|
|
273
274
|
Le rapport `fanoutTotal / publishTotal` est la mesure la plus parlante de la page : c'est la **taille
|
|
274
275
|
moyenne d'un salon**. S'il vaut 1, chaque publication ne touche qu'une personne — un canal par client,
|
|
@@ -298,6 +299,21 @@ lectures possibles quand il grimpe :
|
|
|
298
299
|
Le canal visé n'est délibérément pas exposé : un compteur d'observabilité ne doit pas devenir un
|
|
299
300
|
moyen de sonder ce que le système accepte.
|
|
300
301
|
|
|
302
|
+
### Messages du bus d'une version plus récente — compté à part
|
|
303
|
+
|
|
304
|
+
Une version suivante peut ajouter des messages au bus : un canal du territoire `nodefony:bus:`, ou un
|
|
305
|
+
nouveau type d'ordre sur le canal de contrôle. Pendant un déploiement progressif, un serveur resté sur
|
|
306
|
+
l'ancienne version les reçoit sans les connaître. Il les **jette** — aucun abonné n'en reçoit rien —
|
|
307
|
+
et les compte dans `ingressUnknownDiscardedTotal`, **jamais** dans `ingressRejectedTotal` : sinon
|
|
308
|
+
chaque déploiement déclencherait de fausses alertes d'injection, et une vraie attaque passerait
|
|
309
|
+
inaperçue dans ce bruit. Le journal le signale une seule fois par motif, en `INFO`, sans recopier le
|
|
310
|
+
nom reçu.
|
|
311
|
+
|
|
312
|
+
Il monte pendant un déploiement, puis s'arrête quand tous les serveurs portent la même version. S'il
|
|
313
|
+
continue de monter ensuite, un serveur est resté sur une version plus ancienne. Ce qui reste
|
|
314
|
+
suspect reste compté comme une injection : un autre canal de la plateforme, une variante de casse du
|
|
315
|
+
territoire, un ordre d'un type connu mais de forme fausse, ou qui vise un canal de la plateforme.
|
|
316
|
+
|
|
301
317
|
Un message **mal scellé** n'entre pas dans ce compteur : le transport l'écarte avant le hub, et le
|
|
302
318
|
compte à part dans `sealRejectedTotal` (relayé au même niveau de la sonde, et dans
|
|
303
319
|
`backplane.sealRejectedTotal`). Quand celui-là grimpe, trois lectures : une rotation du secret en
|
|
@@ -409,7 +425,10 @@ Trois champs n'apparaissent que lorsqu'ils ont un sens :
|
|
|
409
425
|
signale une perte **volontaire** : la mémoire du processus a été préservée au prix de messages non
|
|
410
426
|
relayés aux autres répliques. Il doit rester à zéro ; s'il bouge, regarder l'état du bus avant de
|
|
411
427
|
relever le plafond (`backplane.maxQueueBytes`) — un plafond plus haut ne rattrape pas un bus en
|
|
412
|
-
retard, il repousse l'échéance.
|
|
428
|
+
retard, il repousse l'échéance. Les **ordres** (terminer un canal, éjecter un compte) ne sont
|
|
429
|
+
jamais abandonnés pour faire place : ils passent hors plafond. Un ordre que le bus refuse (bus
|
|
430
|
+
injoignable) est compté à part, `controlLostTotal`, et annoncé une fois par épisode — s'il bouge,
|
|
431
|
+
une éjection n'a pas été appliquée par les autres répliques.
|
|
413
432
|
|
|
414
433
|
### La couche d'identité, au-dessus
|
|
415
434
|
|
package/docs/protocole.md
CHANGED
|
@@ -484,16 +484,16 @@ Voici **tout** ce que le moteur produit. Les codes de la plage `-32000`…`-3209
|
|
|
484
484
|
la norme aux erreurs applicatives serveur ; Nodefony y place son refus d'autorisation et le défaut
|
|
485
485
|
de `RpcError`.
|
|
486
486
|
|
|
487
|
-
| Code | Émis par | Déclencheur
|
|
488
|
-
| -------- | -------------------------------------------------------------------------------------------- |
|
|
489
|
-
| `-32601` | `JsonRpcPeer.handleRequest()` (`JsonRpcPeer.ts#JsonRpcPeer.handleRequest`) | requête vers une action non enregistrée
|
|
490
|
-
| `-32603` | même méthode (`JsonRpcPeer.ts#JsonRpcPeer.handleRequest`) | le handler a levé une exception **ordinaire**
|
|
491
|
-
| `-32001` | le refus du verrou de frame (`JsonRpcPeer.ts#JsonRpcPeer.receive`) | `beforeDispatch` a dit non **sur une requête**
|
|
492
|
-
| `-32000` | défaut du constructeur de `RpcError` (`JsonRpcPeer.ts#RpcError`) | le handler expose volontairement son refus
|
|
493
|
-
| `-32602` | le pont API, via `RpcError` (`RealtimeController.ts#RealtimeController.actionParamsWrapper`) | `api.request` appelé avec un `params.path` invalide
|
|
494
|
-
| `-32000` | le pont API (`RealtimeController.ts#RealtimeController.invokeApiRequest`) | la route invoquée REFUSE : 401, 403, 404, 405, **422
|
|
495
|
-
|
|
496
|
-
Un refus de la route invoquée par le pont garde la forme d'un `fetch` dont `ok` est faux : le statut HTTP voyage dans `data.status`. Une erreur de validation — `ZodError` levée par l'action, ou entrée refusée par le `schema` de la route — est un **422** dont `data.fields` nomme chaque champ et sa partie (`{ field, message, in }`), exactement le `error.fields` du même appel en HTTP. Tout autre `throw` reste opaque (`-32603`).
|
|
487
|
+
| Code | Émis par | Déclencheur | Ce que voit le pair |
|
|
488
|
+
| -------- | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
489
|
+
| `-32601` | `JsonRpcPeer.handleRequest()` (`JsonRpcPeer.ts#JsonRpcPeer.handleRequest`) | requête vers une action non enregistrée | `method not found: <nom>` |
|
|
490
|
+
| `-32603` | même méthode (`JsonRpcPeer.ts#JsonRpcPeer.handleRequest`) | le handler a levé une exception **ordinaire** | `internal error` — générique, rien d'autre |
|
|
491
|
+
| `-32001` | le refus du verrou de frame (`JsonRpcPeer.ts#JsonRpcPeer.receive`) | `beforeDispatch` a dit non **sur une requête** | `unauthorized`, sans jamais dire pourquoi |
|
|
492
|
+
| `-32000` | défaut du constructeur de `RpcError` (`JsonRpcPeer.ts#RpcError`) | le handler expose volontairement son refus | le message ET le `data` choisis par le handler |
|
|
493
|
+
| `-32602` | le pont API, via `RpcError` (`RealtimeController.ts#RealtimeController.actionParamsWrapper`) | `api.request` appelé avec un `params.path` invalide | message explicite (l'appel est malformé) |
|
|
494
|
+
| `-32000` | le pont API (`RealtimeController.ts#RealtimeController.invokeApiRequest`) | la route invoquée REFUSE : 401, 403, 404, 405, **422**, 429 | le message, `data.status` — `data.fields` sur un 422, `data.retryAfter` si l'action a posé `Retry-After` |
|
|
495
|
+
|
|
496
|
+
Un refus de la route invoquée par le pont garde la forme d'un `fetch` dont `ok` est faux : le statut HTTP voyage dans `data.status`. Une erreur de validation — `ZodError` levée par l'action, ou entrée refusée par le `schema` de la route — est un **422** dont `data.fields` nomme chaque champ et sa partie (`{ field, message, in }`), exactement le `error.fields` du même appel en HTTP. Un refus passager (429, 503) dont l'action a posé `Retry-After` (RFC 9110 §10.2.3) porte l'attente dans `data.retryAfter`, en secondes — une date HTTP y est ramenée à l'écart : une socket ouverte n'a plus d'en-tête, et le même code de contrôleur (`this.response?.setHeader("Retry-After", n)` puis le refus) sert les deux transports. Seul cet en-tête traverse : rien d'autre posé par l'action ne fuit sur une trame. Tout autre `throw` reste opaque (`-32603`).
|
|
497
497
|
|
|
498
498
|
Et un échec qui n'est **pas** une frame : l'expiration. `startCall()` ne reçoit rien dans le délai
|
|
499
499
|
imparti, supprime l'entrée en attente et rejette localement avec `RPC timeout: <méthode>`
|
package/docs/securite.md
CHANGED
|
@@ -449,9 +449,9 @@ mémoire : le token déjà résolu, et la cible de la frame.
|
|
|
449
449
|
| Frame | Contrôle appliqué |
|
|
450
450
|
| --- | --- |
|
|
451
451
|
| `api.request {path}` | Re-match de zone HTTP : zone protégée + anonyme → refus |
|
|
452
|
-
| `subscribe {channel}` | Plancher système, puis policy
|
|
453
|
-
| notification `method` = canal inbound |
|
|
454
|
-
| action `@RealtimeAction` |
|
|
452
|
+
| `subscribe {channel}` | Plancher système, puis policy de **LECTURE** déclarée sur le canal |
|
|
453
|
+
| notification `method` = canal inbound | Policy de lecture **ET** policy d'**ÉCRITURE** du même nom (on ne pousse pas sur un canal qu'on n'a pas le droit de lire) |
|
|
454
|
+
| action `@RealtimeAction` | Lecture **ET** écriture du nom. Sans politique : la lecture d'un canal homonyme si elle est déclarée, sinon authentifié **par défaut** ; ouverte seulement si `{ authenticated: false }` est écrit |
|
|
455
455
|
| `ping`, `unsubscribe` | **passent** — pas de surface de données |
|
|
456
456
|
|
|
457
457
|
Deux détails évitent des faux refus : `authorizeApiRequest()` (`frameAuthorizer.ts#authorizeApiRequest`) laisse
|
|
@@ -474,6 +474,22 @@ peut **resserrer**, jamais desserrer.
|
|
|
474
474
|
Le journal d'audit est un **canal précis**, pas un namespace : sa règle est posée devant celle du
|
|
475
475
|
territoire (premier match gagnant), et elle n'existe que si le hub réserve bien ce territoire.
|
|
476
476
|
|
|
477
|
+
**Les suffixes `:health` et `:stats` sont réservés dans TOUT espace de noms**, pas seulement sous
|
|
478
|
+
`nodefony:`. C'est la convention transverse des canaux de santé et de mesures : `net:health` est
|
|
479
|
+
donc refusé à quiconque ne porte pas `ROLE_NODEFONY_ADMIN`, même si l'application ne déclare rien
|
|
480
|
+
sur ce canal. Le hub, lui, ne réserve que `nodefony:` — d'où la surprise. Le refus le dit : sa
|
|
481
|
+
phrase nomme le suffixe, le rôle exigé et la règle qui le surcharge (`frameAuthorizer.ts#explainReservedSuffix`).
|
|
482
|
+
Pour ouvrir un tel canal à l'application, déclarer une **règle de préfixe** — placée avant les
|
|
483
|
+
défauts, elle gagne et la convention n'est plus consultée :
|
|
484
|
+
|
|
485
|
+
```ts
|
|
486
|
+
use("@nodefony/security", {
|
|
487
|
+
realtimeChannels: [{ pattern: "net:", authenticated: true }],
|
|
488
|
+
});
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
La règle ne descend pas sous ce qu'elle déclare : ici, `net:health` reste fermé à l'anonyme.
|
|
492
|
+
|
|
477
493
|
Trois durcissements méritent d'être connus :
|
|
478
494
|
|
|
479
495
|
- **Match insensible à la casse, sans allocation** — `startsWithCI()` (`frameAuthorizer.ts#buildSystemRules`).
|
|
@@ -494,9 +510,41 @@ jamais de canal d'audit non gardé.
|
|
|
494
510
|
|
|
495
511
|
### La policy métier — déclarer sur le canal
|
|
496
512
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
513
|
+
Deux politiques par **nom** de canal, chacune un `IChannelPolicy` :
|
|
514
|
+
|
|
515
|
+
- la **LECTURE** — `@RealtimeChannel(name, policy)` (`realtimeDecorators.ts#RealtimeChannel`) — exigée au
|
|
516
|
+
`subscribe` ;
|
|
517
|
+
- l'**ÉCRITURE** — `@RealtimeInbound(name, policy)` (`realtimeDecorators.ts#RealtimeInbound`),
|
|
518
|
+
`@RealtimeAction(name, policy)` — exigée **en plus** de la lecture sur toute trame entrante.
|
|
519
|
+
|
|
520
|
+
Ce qui n'est pas déclaré s'hérite, quel que soit l'ordre des méthodes : une lecture absente reprend
|
|
521
|
+
l'écriture (un canal gardé par `@RealtimeInbound` seul reste protégé en lecture), une action sans
|
|
522
|
+
politique reprend la lecture déclarée sous son nom, sinon le défaut fermé. Un canal que tout le monde
|
|
523
|
+
lit et que seuls certains écrivent s'écrit donc :
|
|
524
|
+
|
|
525
|
+
```typescript
|
|
526
|
+
@RealtimeChannel("chat:typing", { authenticated: false }) // tout le monde voit « Untel écrit… »
|
|
527
|
+
typing(): () => void {
|
|
528
|
+
return () => {};
|
|
529
|
+
}
|
|
530
|
+
@RealtimeInbound("chat:typing", { authenticated: true }) // seuls les connectés écrivent
|
|
531
|
+
onTyping(): void {}
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
Deux déclarations **contradictoires** d'une même sorte sur un nom — dans une classe, ou dans deux
|
|
535
|
+
classes — refusent le démarrage en nommant le canal : une seule s'appliquerait, l'autre
|
|
536
|
+
disparaîtrait. Une sous-classe qui redéclare la politique de son parent la surcharge. `allow` est
|
|
537
|
+
une règle de lecture : posé sur une écriture distincte de la lecture, il refuse le démarrage.
|
|
538
|
+
|
|
539
|
+
Avec des **motifs** (`room:*`), la lecture d'un nom appartient à la déclaration la plus spécifique
|
|
540
|
+
qui déclare quelque chose sur lui (le nom exact, sinon le motif au littéral le plus long) ; l'écriture
|
|
541
|
+
vient de la plus spécifique qui en déclare une, motifs compris. Une écriture de famille réservée
|
|
542
|
+
s'applique donc aussi à un nom exact qui n'a déclaré que sa lecture, et une commande déclarée par
|
|
543
|
+
`@RealtimeInbound("task:start")` sous une famille `task:*` à `allow` reste une commande. Le canal
|
|
544
|
+
personnel `user:<id>` garde sa règle « son canal seulement » tant qu'aucune déclaration ne vise
|
|
545
|
+
explicitement `user:` : un motif attrape-tout (`*`) ne la retire pas.
|
|
546
|
+
|
|
547
|
+
Les trois axes d'une politique sont cumulatifs (ET) ; un axe absent n'impose rien :
|
|
500
548
|
|
|
501
549
|
| Axe | Sens | Évalué par |
|
|
502
550
|
| --------------- | ----------------------------------------------------------------------------- | ----------------------------------------------------- |
|
|
@@ -504,14 +552,17 @@ cumulatifs (ET) ; un axe absent n'impose rien :
|
|
|
504
552
|
| `roles` | un des rôles suffit, **hiérarchie comprise** | `Firewall.hasRole()` (`firewall.ts#Firewall.hasRole`) |
|
|
505
553
|
| `scopes` | un des scopes suffit — axe API (JWT, clé API), une session BFF n'en porte pas | comparaison directe |
|
|
506
554
|
|
|
507
|
-
Une policy **vide** n'est pas enregistrée : `
|
|
555
|
+
Une policy **vide** n'est pas enregistrée : `declarePolicy()` (`realtimeDecorators.ts#declarePolicy`) ignore un
|
|
508
556
|
objet sans contrainte — le canal reste libre, le registre reste vide. Les déclarations sont inscrites
|
|
509
557
|
au hub **au démarrage** (`channelPolicyRegistry.ts#registerControllerPolicies`, à `onKernelReady`) :
|
|
510
558
|
un exemplaire qui n'a encore vu aucune poignée de main a déjà ses politiques — celui qui n'émet
|
|
511
559
|
que des webhooks aussi. Un contrôleur échappé au démarrage s'inscrit à sa première poignée de main
|
|
512
560
|
(`RealtimeHub.registerChannelPolicy()`, `RealtimeHub.ts#RealtimeHub.registerChannelPolicy`, idempotent) ;
|
|
513
|
-
le décideur les relit par `RealtimeService.
|
|
514
|
-
(`RealtimeService.ts#RealtimeService.
|
|
561
|
+
le décideur les relit en une résolution par `RealtimeService.resolveChannelPolicies()`
|
|
562
|
+
(`RealtimeService.ts#RealtimeService.resolveChannelPolicies`). Un refus d'écriture est audité sous
|
|
563
|
+
le motif `channel_write_policy`, distinct de `channel_policy` (lecture). Un verrou qui ne sait pas
|
|
564
|
+
juger l'écriture (il ne déclare pas `judgesWrite`) refuse le démarrage dès qu'une écriture distincte
|
|
565
|
+
est déclarée : versions de `@nodefony/realtime` et `@nodefony/security` à aligner.
|
|
515
566
|
|
|
516
567
|
### ⚠️ Une policy est attachée à un nom EXACT — les canaux dérivés n'héritent pas
|
|
517
568
|
|
|
@@ -808,7 +859,10 @@ Le refus doit être observable sans devenir un oracle. Nodefony tranche ainsi :
|
|
|
808
859
|
| Canal sans aucun producteur | `realtime:denied { channel, reason: "unknown" }` |
|
|
809
860
|
|
|
810
861
|
Le message est **générique** dans les trois cas : jamais « il te manque `ROLE_ADMIN` », jamais le nom
|
|
811
|
-
de la zone.
|
|
862
|
+
de la zone. Hors production seulement, un champ `detail` ajoute la phrase qui aide le développeur
|
|
863
|
+
(`deniedDetail.ts#deniedDetail`) ; quand le verrou sait dire pourquoi il a refusé — un suffixe
|
|
864
|
+
`:health`/`:stats` réservé à la plateforme —, `detail` EST cette explication
|
|
865
|
+
(`RealtimeHub.ts#RealtimeHub.explainDenial`). Le journal du serveur la porte, lui, dans tous les modes. Sans `realtime:denied`, une notification refusée serait droppée en silence (elle n'a pas
|
|
812
866
|
de canal de réponse) et le client se croirait abonné. **Aucun abonnement ne reste sans réponse** :
|
|
813
867
|
un canal que personne ne produit — nom mal orthographié, module non chargé — rend `unknown`, qui
|
|
814
868
|
n'ouvre aucun oracle (un canal gardé est tranché en amont, donc `forbidden` qu'il existe ou non).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nodefony/realtime",
|
|
3
|
-
"version": "10.0.0-beta.
|
|
3
|
+
"version": "10.0.0-beta.7",
|
|
4
4
|
"nodefony": {
|
|
5
5
|
"configSchema": "./dist/nodefony/config/configSchema.js"
|
|
6
6
|
},
|
|
@@ -44,27 +44,27 @@
|
|
|
44
44
|
"esm"
|
|
45
45
|
],
|
|
46
46
|
"peerDependencies": {
|
|
47
|
-
"@nodefony/framework": "^10.0.0-beta.
|
|
48
|
-
"@nodefony/http": "^10.0.0-beta.
|
|
49
|
-
"nodefony": "^10.0.0-beta.
|
|
47
|
+
"@nodefony/framework": "^10.0.0-beta.7",
|
|
48
|
+
"@nodefony/http": "^10.0.0-beta.7",
|
|
49
|
+
"nodefony": "^10.0.0-beta.7",
|
|
50
50
|
"reflect-metadata": "0.2.2",
|
|
51
51
|
"zod": "^4.6.7"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@nodefony/drizzle": "^10.0.0-beta.
|
|
55
|
-
"@nodefony/framework": "^10.0.0-beta.
|
|
56
|
-
"@nodefony/http": "^10.0.0-beta.
|
|
57
|
-
"@nodefony/orm-core": "^10.0.0-beta.
|
|
58
|
-
"@nodefony/security": "^10.0.0-beta.
|
|
54
|
+
"@nodefony/drizzle": "^10.0.0-beta.6",
|
|
55
|
+
"@nodefony/framework": "^10.0.0-beta.7",
|
|
56
|
+
"@nodefony/http": "^10.0.0-beta.7",
|
|
57
|
+
"@nodefony/orm-core": "^10.0.0-beta.6",
|
|
58
|
+
"@nodefony/security": "^10.0.0-beta.6",
|
|
59
59
|
"@types/node": "26.6.6",
|
|
60
60
|
"@vitest/coverage-v8": "5.1.0",
|
|
61
|
-
"nodefony": "^10.0.0-beta.
|
|
61
|
+
"nodefony": "^10.0.0-beta.7",
|
|
62
62
|
"reflect-metadata": "0.2.2",
|
|
63
63
|
"vitest": "5.1.0"
|
|
64
64
|
},
|
|
65
65
|
"private": false,
|
|
66
66
|
"engines": {
|
|
67
|
-
"node": "
|
|
67
|
+
"node": "^24.16.0 || >=26.0.0"
|
|
68
68
|
},
|
|
69
69
|
"license": "Apache-2.0",
|
|
70
70
|
"dependencies": {
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"url": "git+https://github.com/nodefony/nodefony-core.git",
|
|
84
84
|
"directory": "src/packages/@nodefony/realtime"
|
|
85
85
|
},
|
|
86
|
-
"homepage": "https://nodefony.
|
|
86
|
+
"homepage": "https://nodefony.net/",
|
|
87
87
|
"bugs": {
|
|
88
88
|
"url": "https://github.com/nodefony/nodefony-core/issues"
|
|
89
89
|
}
|