@nodefony/devkit 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.
Files changed (32) hide show
  1. package/dist/_virtual/{_@oxc-project_runtime@0.157.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorate.js +1 -1
  2. package/dist/_virtual/{_@oxc-project_runtime@0.157.0 → _@oxc-project_runtime@0.158.0}/helpers/esm/decorateParam.js +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/nodefony/controllers/DevkitController.js +1 -1
  5. package/dist/nodefony/controllers/McpController.js +2 -2
  6. package/dist/nodefony/controllers/ProfileSessionController.js +1 -1
  7. package/dist/nodefony/service/DevkitService.js +3 -3
  8. package/package.json +11 -11
  9. package/skills/nodefony-add-grpc-service/SKILL.md +179 -0
  10. package/skills/nodefony-add-grpc-service/scripts/call.mjs +218 -0
  11. package/skills/nodefony-add-realtime-binary/SKILL.md +301 -0
  12. package/skills/nodefony-add-realtime-binary/references/messages.md +20 -0
  13. package/skills/nodefony-add-realtime-binary/references/mesures.md +59 -0
  14. package/skills/nodefony-add-realtime-channel/SKILL.md +408 -326
  15. package/skills/nodefony-add-realtime-channel/references/contrat.md +22 -0
  16. package/skills/nodefony-add-realtime-channel/references/production.md +21 -2
  17. package/skills/nodefony-add-realtime-presence/SKILL.md +328 -0
  18. package/skills/nodefony-authentication/SKILL.md +2 -2
  19. package/skills/nodefony-browser/SKILL.md +11 -1
  20. package/skills/nodefony-browser/references/sondes.md +19 -0
  21. package/skills/nodefony-browser/scripts/audit.mjs +5 -2
  22. package/skills/nodefony-browser/scripts/inspect.mjs +69 -7
  23. package/skills/nodefony-browser/scripts/lib/actions.mjs +54 -3
  24. package/skills/nodefony-browser/scripts/lib/browser.mjs +18 -1
  25. package/skills/nodefony-browser/scripts/lib/probes.mjs +52 -3
  26. package/skills/nodefony-dev/SKILL.md +20 -15
  27. package/skills/nodefony-devops/references/dimensionnement.md +1 -1
  28. package/skills/nodefony-devops/references/frontal.md +5 -2
  29. package/skills/nodefony-devops/references/image.md +23 -15
  30. package/skills/nodefony-migrate-schema/references/verdicts.md +1 -0
  31. package/skills/nodefony-protect-route/SKILL.md +27 -32
  32. package/skills/nodefony-add-realtime-channel/references/binaire.md +0 -191
@@ -1,191 +0,0 @@
1
- # Des octets dans le temps réel — mode d'emploi complet
2
-
3
- > Chargé depuis `SKILL.md` dès qu'une charge contient des octets (`Uint8Array`, `Buffer`, CBOR,
4
- > `$bytes`). Tout ce qui suit se vérifie dans la documentation installée :
5
- > `node_modules/@nodefony/realtime/docs/protocole.md` (section « Des octets dans la charge »),
6
- > `node_modules/@nodefony/realtime/docs/configuration.md` (clé `codec`),
7
- > `node_modules/nodefony/docs/client.md` (« Choisir le codec de trame »),
8
- > `node_modules/nodefony/docs/cbor.md` (le codec lui-même).
9
-
10
- ## Sommaire
11
-
12
- 1. [Le modèle](#1-le-modèle--une-frame-comme-les-autres)
13
- 2. [Mise en place](#2-mise-en-place--deux-lignes-une-par-côté)
14
- 3. [Publier et recevoir](#3-publier-et-recevoir)
15
- 4. [Canal ou flux brut ?](#4-canal-ou-flux-brut-)
16
- 5. [Lire des octets sans se tromper](#5-lire-des-octets-sans-se-tromper)
17
- 6. [Plusieurs serveurs](#6-plusieurs-serveurs)
18
- 7. [Chaque message, sa cause, ton geste](#7-chaque-message-sa-cause-ton-geste)
19
- 8. [Ce qui arrive SANS message](#8-ce-qui-arrive-sans-message)
20
- 9. [Prouver que ça marche](#9-prouver-que-ça-marche)
21
-
22
- ## 1. Le modèle — une frame comme les autres
23
-
24
- - Une trame qui porte des octets est une **notification JSON-RPC ordinaire** dont la charge est, ou
25
- contient, un `Uint8Array`. Il n'y a pas de seconde grammaire.
26
- - Le **codec** ne dit que la forme sur le fil : en CBOR, les octets voyagent tels quels ; sur une
27
- connexion restée en texte, ils voyagent sous la convention `{"$bytes":"<base64url>"}`, que le
28
- client relit en `Uint8Array` sans rien importer.
29
- - Le codec se **négocie** par connexion : le navigateur l'offre, le serveur choisit et l'annonce
30
- dans `realtime:welcome`. Un client qui ne l'offre pas reste en texte — rien ne casse.
31
- - Parce que c'est la même frame, les octets d'un canal passent par **les mêmes gardes** que le
32
- texte : politique du canal, rôles, audit, sonde.
33
-
34
- **N'invente jamais d'en-tête binaire maison** (`[n° de canal][charge]`) : il contournerait toutes
35
- les gardes et serait à maintenir des deux côtés.
36
-
37
- ## 2. Mise en place — deux lignes, une par côté
38
-
39
- Serveur (`nodefony.config.ts`) :
40
-
41
- ```ts
42
- use("@nodefony/realtime", { codec: "cbor" });
43
- ```
44
-
45
- Navigateur — à la **PREMIÈRE** création de la socket partagée (souvent le noyau client) :
46
-
47
- ```ts
48
- import { NodefonySocket } from "nodefony/client";
49
- import { CBOR_FRAME_CODEC } from "nodefony/cbor";
50
-
51
- const socket = NodefonySocket.shared({
52
- url: "/api/live/realtime",
53
- codec: CBOR_FRAME_CODEC,
54
- });
55
- ```
56
-
57
- | Serveur | Navigateur offre CBOR | Fil retenu | Octets reçus en |
58
- | ------------------------ | --------------------- | ---------- | ---------------------------- |
59
- | `codec: "cbor"` | oui | CBOR | `Uint8Array` |
60
- | `codec: "cbor"` | non | texte | `Uint8Array` (via `$bytes`) |
61
- | `codec: "json"` (défaut) | oui ou non | texte | 🔴 objet déformé `{"0":1,…}` |
62
-
63
- D'où la règle : **dès que des octets circulent, le serveur passe en `codec: "cbor"`**. Côté
64
- navigateur, offrir CBOR est un gain (≈ 30× plus rapide à recevoir 4 Ko d'octets, ~25 % d'octets en
65
- moins), pas une obligation. Ce gain ne vaut QUE pour des octets : sur des objets ordinaires, CBOR
66
- encode plus lentement que JSON, natif au moteur, et ne décode pas plus vite. N'offre le codec que si
67
- des octets circulent.
68
-
69
- ## 3. Publier et recevoir
70
-
71
- ```ts
72
- // serveur — depuis un service, une tâche, un minuteur
73
- import { getRealtimeHub } from "@nodefony/realtime";
74
- getRealtimeHub().publish("capteur:raw", {
75
- seq: 42,
76
- frame: new Uint8Array([1, 2, 3]),
77
- });
78
- getRealtimeHub().publish("capteur:raw", new Uint8Array([1, 2, 3])); // charge = les octets seuls
79
-
80
- // serveur — dans un RealtimeController, la fabrique du canal reçoit `publish`
81
- // navigateur
82
- socket.subscribe("capteur:raw");
83
- socket.on("capteur:raw", (charge) => {
84
- /* charge.frame est un Uint8Array */
85
- });
86
- socket.publish("doc:42", { maj: new Uint8Array(update) }); // canal entrant déclaré côté serveur
87
- ```
88
-
89
- - `subscribe(canal)` prend **un seul argument** ; le handler se pose par `on(canal, handler)`.
90
- - Une charge est un objet, un tableau, des octets, ou absente — jamais un scalaire
91
- (`publish("c", 42)` lève une `TypeError` qui nomme le canal).
92
- - Un `Buffer` de Node est un `Uint8Array` : il part en octets, jamais en `{type:"Buffer",data:[…]}`.
93
- - Une `Date` revient en chaîne et un `bigint` est refusé — exactement comme en JSON.
94
-
95
- ## 4. Canal ou flux brut ?
96
-
97
- | Besoin | Voie |
98
- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
99
- | Des octets qui appartiennent à un canal (cas normal) | `codec: "cbor"` + `publish` / `on(canal)` |
100
- | Un protocole binaire entièrement propre à l'application | `onRealtimeBinary()` (serveur) / `onBinary()` (navigateur), connexion **texte** seulement |
101
-
102
- 🔴 **Le flux brut est HORS des gardes.** Une trame reçue par `onRealtimeBinary(data)` ne passe ni
103
- par le verrou de frame ni par les politiques d'action et de canal. Seule la connexion a franchi
104
- l'Origin, l'authentificateur et le pare-feu. Si tu l'emploies, **toute décision d'accès est à
105
- toi** : lis l'identité par `RequestContext.getAuthenticatedUser()` et refuse explicitement.
106
-
107
- 🔴 **Le flux brut n'existe pas sur une connexion CBOR** : toute trame binaire y est une frame du
108
- codec. `onRealtimeBinary()` n'y est jamais appelée, `sendBinaryToClient()` et `sendBinary()` rendent
109
- `false`. Ne mélange pas les deux voies sur une même application sans le savoir.
110
-
111
- Dans le doute, **préfère un canal + `Uint8Array`** (§3) : même politique, mêmes gardes, même bus
112
- entre serveurs — le flux brut ne se justifie que pour un protocole binaire qui n'a pas de canal.
113
-
114
- ## 5. Lire des octets sans se tromper
115
-
116
- - **`DataView` : passe la tranche entière.** `new DataView(data.buffer, data.byteOffset,
117
- data.byteLength)`. Un `Buffer` de Node ne commence pas à 0 dans son tampon : `new
118
- DataView(data.buffer)` lit des octets qui ne sont pas les tiens.
119
- - Côté serveur, `onRealtimeBinary` reçoit un `Buffer` de `ws` typé `Uint8Array` : écris ton
120
- décodeur sur `Uint8Array`, il servira tel quel dans le navigateur.
121
- - Si tu appelles toi-même `decodeCbor()` sur un tampon que tu réutilises ensuite, **copie** les
122
- octets décodés à garder (`slice()`) : ce sont des vues sur ce tampon.
123
-
124
- ### 🔴 Le piège du `$bytes` forgé
125
-
126
- Quand le serveur a `codec: "cbor"` et qu'une connexion est restée en texte (la convention d'octets
127
- y est armée), un objet de la forme **exacte** `{ $bytes: "<chaîne>" }` (seule clé, valeur chaîne)
128
- est réservé aux octets. L'encodeur **refuse** d'en envoyer un qui ne vient pas
129
- d'octets : un client texte le relirait en octets, un client CBOR en objet.
130
-
131
- ```ts
132
- socket.publish("prix", { montant: { $bytes: "12 euros" } }); // 🔴 TypeError
133
- socket.publish("prix", { montant: { $bytes: "12 euros", devise: "EUR" } }); // ✅ deux clés
134
- socket.publish("prix", { montant: { raw: "12 euros" } }); // ✅ autre clé
135
- ```
136
-
137
- - Navigateur : `emit` / `publish` **lèvent** à la ligne fautive.
138
- - Serveur : la publication n'atteint pas les abonnés texte, une ligne `ERROR` par publication.
139
- - Un tel objet **conservé** (état initial renvoyé à chaque nouvel abonné) est refusé **à chaque
140
- envoi**. Renomme la clé une fois pour toutes. Ne contourne pas en encodant toi-même en base64.
141
- - Avec `codec: "json"`, `$bytes` est une clé comme une autre : la règle ne s'applique pas.
142
-
143
- ## 6. Plusieurs serveurs
144
-
145
- Les octets traversent le bus entre exemplaires (`redis`, `cluster`) **intacts**, scellés comme le
146
- texte si `backplane.secret` est posé : rien à configurer de plus. Un canal suivi à la fois par des
147
- connexions CBOR et texte paie un encodage par codec, pas un par abonné.
148
-
149
- ## 7. Chaque message, sa cause, ton geste
150
-
151
- Les textes entre guillemets sont recopiés du code ; `<…>` marque une partie variable.
152
-
153
- | Où | Message | Cause | Geste |
154
- | -------------------------------------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------- |
155
- | exception (navigateur) / journal `ERROR` (serveur) | `codec JSON : objet {"$bytes": …} non issu d'octets` | objet applicatif de la forme exacte du marqueur, vers une connexion texte | envoyer un vrai `Uint8Array`, ou renommer / ajouter une clé (§5) |
156
- | journal `ERROR` serveur | `rpc <action> : réponse non encodable` | une `@RealtimeAction` a rendu une valeur que le codec refuse (`$bytes` forgé, cycle, `bigint`) ; le client reçoit `-32603` générique | corriger la valeur rendue |
157
- | exception | `Impossible d'encoder un BigInt — JSON.stringify le refuse aussi` | `bigint` dans la charge, sous CBOR | convertir en chaîne ou en nombre |
158
- | exception | `Impossible d'encoder une structure circulaire — JSON.stringify la refuse aussi` | cycle dans la charge | aplatir la donnée |
159
- | notice navigateur (`-32700`) | `Trame binaire écartée : en JSON-RPC texte elle n'a pas de canal` | le serveur écrit du binaire brut sur une connexion texte sans `onBinary()` | lire par `onBinary()`, ou passer par un canal + CBOR |
160
- | notice navigateur (`-32700`) | `Trame illisible écartée : elle ne se décode pas en <protocole>` | trame que le codec ne décode pas | côté serveur : chercher qui écrit hors du codec |
161
- | notice navigateur | `Codec « <protocole> » annoncé sans avoir été offert : la connexion reste en JSON-RPC texte` | serveur qui annonce un codec que ce client n'a pas offert | mettre client et serveur à la même version |
162
- | notice navigateur | `Flux binaire brut indisponible sous <protocole>` | `onBinary()` / `sendBinary()` sur une connexion CBOR | publier les octets sur un canal (§4) |
163
- | journal `WARNING` serveur, une fois par connexion | `WS realtime : trame binaire reçue et écartée` | binaire brut reçu sur une connexion texte, sans `onRealtimeBinary()` | passer par un canal + CBOR, ou surcharger `onRealtimeBinary()` (§4, hors gardes) |
164
- | journal `WARNING` serveur, une fois par connexion | `WS realtime : sendBinaryToClient refusé` | `sendBinaryToClient()` sur une connexion CBOR | publier les octets sur un canal |
165
- | journal `WARNING` serveur, une fois par nature | `WS realtime : trame binaire illisible pour <protocole> reçue et écartée` | trame CBOR malformée ou tronquée ; **aucune réponse** n'est renvoyée (pas d'oracle pour un fuzzer), connexion conservée | côté client : chercher qui écrit hors du codec |
166
- | exception (`CborError`) | `CBOR refusé à l'octet <n> : <raison>` | `decodeCbor()` appelé par l'application sur des octets invalides ; `err.offset` dit où | valider la source ; ne jamais faire confiance à des octets reçus |
167
-
168
- ## 8. Ce qui arrive SANS message
169
-
170
- - **Serveur resté en `codec: "json"`** : un `Uint8Array` part en `{"0":1,"1":2,…}` — aucune erreur.
171
- C'est le piège n°1 : vérifier la clé `codec` avant tout autre diagnostic.
172
- - **`codec` passé à un `shared()` qui n'est pas le premier** : l'instance existante revient,
173
- réglages compris ; la connexion reste en texte (un avertissement hors production seulement).
174
- - **`sendBinary()` sur un transport fermé** : rend `false`, compté dans `framesUnsent`, sans notice.
175
- - **Codec non annoncé par l'accueil** : le client reste en texte, sans notice — c'est le cas
176
- normal d'un serveur en `codec: "json"`.
177
- - **Ne déduis jamais le codec de l'en-tête `Sec-WebSocket-Protocol`** renvoyé au handshake : la
178
- bibliothèque WebSocket le renvoie qu'elle parle le sous-protocole ou non. Seul `socket.codec`
179
- fait foi.
180
-
181
- ## 9. Prouver que ça marche
182
-
183
- - `socket.codec` vaut `"nodefony.jsonrpc.cbor.v1"` une fois l'accueil reçu ; `"nodefony.jsonrpc.v1"`
184
- = texte.
185
- - Compteurs côté navigateur : `socket.binaryFramesDiscarded`, `socket.malformedFramesDiscarded`
186
- doivent rester à 0.
187
- - Un test qui le prouve publie un `Uint8Array` sur un canal et vérifie que l'abonné reçoit un
188
- `Uint8Array` **identique** (`instanceof Uint8Array` et mêmes octets) — sous CBOR ET sous texte.
189
- - Pour lire le fil à l'œil dans l'inspecteur réseau : `NF_REALTIME_CODEC=json` remet le serveur en
190
- texte. Exception de débogage : la taille des trames et la contre-pression changent, aucune mesure
191
- prise ainsi ne vaut pour la production.