


Maintenir un SDK de protocole bidirectionnel dans un monorepo TypeScript sans casser son API publique
Pour modifier un SDK typé, publié en plusieurs paquets, où client et serveur envoient des requêtes. Choisit avec soin ce qui est exporté, ajoute les opérations des deux côtés avec contrôle des capacités, consigne les changements incompatibles et exécute les exemples comme tests.
Pour utiliser ces règles d'IA
## Voir à quelle couche appartient un changement et s’il touche l’API publique
Ce guide régit le travail au sein d'un espace de travail TypeScript multi-paquets qui publie un SDK de protocole : un noyau interne partagé, un paquet client, un paquet serveur, un paquet de schémas autonome, ainsi que de fins paquets d'adaptateurs de framework ou d'environnement d'exécution. On suppose une vérification de types stricte, des modules ES, et des scripts couvrant tout l'espace de travail pilotés par un seul gestionnaire de paquets.
Deux propriétés dominent chaque décision. Premièrement, la surface publiée constitue un contrat : tout ce qui est accessible depuis un point d'entrée de paquet fait partie de l'API publique et ne peut pas être modifié à la légère. Deuxièmement, le protocole est bidirectionnel — les deux pairs peuvent initier des requêtes — de sorte qu'un changement d'un côté entraîne généralement une obligation miroir de l'autre côté.
Avant de modifier quoi que ce soit, identifiez à quelle couche appartient le changement et s'il touche la surface publiée. Si c'est le cas, traitez-le comme un changement d'API et appliquez les règles de surface et de migration ci-dessous, pas seulement les règles de style.
## Lancer un seul test en cours de travail et toutes les vérifications à la fin
L'espace de travail expose des scripts globaux couvrant tout le dépôt : installer les dépendances, compiler tous les paquets, exécuter les vérifications de lint et de formatage, corriger automatiquement le lint et le formatage, vérifier les types de tous les paquets, exécuter la suite de tests complète, et un script de vérification combiné qui associe la vérification de types au lint.
Restreignez la boucle pendant les itérations. Utilisez l'option de filtre du gestionnaire de paquets pour exécuter le script de test d'un seul paquet, passez un chemin de fichier pour exécuter un seul fichier de test, et passez un filtre de nom pour exécuter un seul cas de test. Un script en mode surveillance existe pour les itérations rapprochées.
Réservez les exécutions sur tout l'espace de travail au moment précédant la déclaration de fin de travail, et lancez alors à la fois la vérification combinée et la suite de tests. La vérification de types seule ne prouve pas le comportement à l'exécution — n'affirmez jamais qu'un changement fonctionne sans avoir exécuté les tests qui le mettent à l'épreuve.
## Suivre les règles de nommage et de format du dépôt
- Vérification de types stricte partout ; types de retour explicites sur les fonctions exportées.
- PascalCase pour les classes et les types ; camelCase pour les fonctions et les variables.
- Noms de fichiers en minuscules séparés par des tirets ; les fichiers de test portent un suffixe de test dans leur extension.
- Imports au format module ES. Respectez le paramètre de résolution de modules du projet pour les extensions — avec une résolution de type bundler, les imports relatifs ne portent aucune extension de fichier. Regroupez les imports de façon logique.
- Indentation de deux espaces, points-virgules obligatoires, guillemets simples préférés.
- Les tests se trouvent dans le répertoire de test dédié de chaque paquet afin que le motif d'inclusion de l'exécuteur les détecte ; utilisez des noms de test descriptifs.
- Commentaires de documentation sur les API publiques ; commentaires en ligne réservés à la logique réellement non évidente.
Quand une règle est ambiguë, alignez-vous sur le fichier environnant. Ne reformatez pas et ne refactorisez pas du code sans rapport à l'intérieur d'un changement fonctionnel.
## Tirer les exemples de la documentation de fichiers compilés
Les extraits écrits à la main dans les commentaires de documentation se dégradent silencieusement car rien ne les compile. À la place, tirez le code d'exemple de modules d'exemple compagnons qui sont compilés et vérifiés en types en même temps que le reste du code source.
Un fichier compagnon se trouve à côté de son module sujet, sous un nom correspondant. Des régions nommées à l'intérieur du fichier compagnon sont référencées depuis le bloc de documentation via un attribut source pointant vers le fichier et la région. Les noms de région suivent un schéma prévisible : le nom du symbole exporté plus une variante, ou un nom de classe plus un nom de méthode plus une variante. Omettre la référence de région inclut le fichier entier, ce qui fonctionne pour tout type de fichier.
Exécutez le script de synchronisation des extraits après avoir modifié les exemples afin que le contenu actuel soit copié dans les commentaires de documentation et les pages markdown, puis validez le résultat synchronisé avec le changement.
## Exporter le public par nom et garder la racine du paquet neutre vis-à-vis de l’environnement
Séparez un fichier barrel interne — qui exporte tout, est marqué privé, et n'est consommé que par les paquets voisins au sein de l'espace de travail — d'un module d'export public soigneusement sélectionné exposant les types, les classes d'erreur, les constantes, les gardes et les classes de base partagées. Les points d'entrée des paquets réexportent le module sélectionné, ainsi que leurs propres exports explicitement nommés.
Règles non négociables :
- Utilisez des exports nommés explicites dans les points d'entrée et dans le module sélectionné. N'y utilisez jamais de réexports génériques (wildcard).
- Ajouter un symbole à un point d'entrée de paquet en fait une API publique. Ne le faites que délibérément.
- Les fonctions utilitaires internes restent dans le barrel interne et ne sont jamais ajoutées au module sélectionné ni à un point d'entrée.
- Gardez le point d'entrée racine de chaque paquet neutre vis-à-vis de l'environnement d'exécution, afin que les empaqueteurs pour navigateur et pour edge-runtime puissent le consommer. Tout export dont le graphe de modules atteint de façon transitive des fonctionnalités natives propres à une plateforme, comme le lancement de processus ou les sockets bruts, doit se trouver derrière un export de sous-chemin nommé et être couvert par un test garantissant que le barrel racine en reste exempt.
- Gardez les types de la bibliothèque de validation hors des surfaces publiées qui promettent d'en rester exemptes ; publiez plutôt des schémas d'exécution bruts depuis le paquet de schémas dédié, et laissez les paquets voisins résoudre ce paquet comme une véritable dépendance externe plutôt que de porter des copies privées des schémas.
## S’appuyer sur la couche des schémas et garder des transports interchangeables
Organisez le SDK en trois couches.
1. Couche des types et des schémas — types de messages du protocole, schémas de validation et constantes alignés sur la spécification. C'est la source unique de vérité ; toute autre couche en dérive.
2. Couche protocole — une classe de base abstraite gérant le routage des messages, la corrélation entre requêtes et réponses, la négociation des capacités et le cycle de vie du transport. Les deux implémentations de pair en héritent, ce qui explique pourquoi le comportement bidirectionnel est symétrique par construction.
3. API de haut niveau — les classes client et serveur exposant des méthodes typées, plus une enveloppe serveur de niveau supérieur offrant un enregistrement simplifié des outils, ressources et invites.
Les transports forment une couche enfichable séparée derrière une seule interface : un transport HTTP en streaming recommandé pour les pairs distants, un transport par flux d'événements hérité conservé pour la rétrocompatibilité, et un transport entrée-sortie standard pour les intégrations de processus lancés localement. La validation est enfichable dans le même esprit, avec un validateur par défaut ainsi que des alternatives compatibles avec les environnements d'exécution restreints.
Les paquets d'adaptateurs de framework et d'environnement d'exécution sont de fines couches d'intégration. Ils ne doivent jamais ajouter de fonctionnalité de protocole ; tout ce qui modifie le comportement du protocole doit se trouver dans les couches centrales.
## Suivre le flux sortant et entrant des requêtes des deux côtés
Sortant. Une méthode de haut niveau délègue à la méthode de requête de base, qui attribue un identifiant de message unique, vérifie que le pair distant a déclaré la capacité requise par la méthode, enregistre un gestionnaire de réponse en attente, remet la requête sérialisée au transport, et attend la résolution lorsque la réponse correspondante arrive.
Entrant. Le transport fait remonter un message entrant et le routage de base le distribue en tant que requête, réponse ou notification. Le traitement d'une requête recherche un gestionnaire indexé par nom de méthode, construit un objet de contexte, laisse la sous-classe enrichir ce contexte avec des détails propres au transport, invoque le gestionnaire, puis renvoie le résultat du gestionnaire sous forme de réponse via le transport.
Forme du contexte. Un identifiant de session ; un groupe de requête portant l'identifiant du message, le nom de la méthode, les métadonnées de la requête, un signal d'abandon pour l'annulation, et des utilitaires pour renvoyer une requête ou une notification liée au pair ; ainsi qu'un groupe de transport optionnel portant des détails d'autorisation validés lorsque le transport les fournit. Le contexte côté serveur ajoute des utilitaires de journalisation, de sollicitation de saisie utilisateur et d'échantillonnage de modèle.
L'application des capacités s'effectue dans les deux sens, ainsi qu'au moment de l'enregistrement du gestionnaire, de sorte qu'un pair ne peut pas enregistrer de gestionnaire pour quelque chose qu'il n'a jamais déclaré.
## Ajouter une opération de protocole avec contrôle des capacités des deux côtés
1. Définissez le schéma des paramètres de la requête et le schéma du résultat dans la couche des types.
2. Ajoutez l'indicateur de capacité correspondant au type de capacité client ou serveur.
3. Implémentez la méthode d'envoi sur le pair qui initie l'opération.
4. Ajoutez la vérification de capacité à l'intérieur du contrôle de capacité de ce pair.
5. Enregistrez le gestionnaire de réception sur le pair opposé.
6. Ajoutez une enveloppe de haut niveau sur l'API serveur simplifiée lorsque l'opération le justifie.
7. Ajoutez des tests des deux côtés, ainsi qu'un exemple exécutable lorsque l'opération est visible par l'utilisateur final.
Les étapes deux et quatre sont celles qu'on omet le plus souvent, et les plus coûteuses à omettre : une opération sans négociation de capacité échoue à l'exécution face à des pairs qui ne l'implémentent pas, au lieu d'échouer proprement dès l'initialisation.
## Consigner chaque changement incompatible : quoi, pourquoi et comment migrer
Consignez chaque changement incompatible dans le guide de migration, dans la section du sous-système qu'il affecte. Recherchez les sections apparentées et étoffez-les plutôt que d'ajouter de nouvelles sections isolées — un guide composé d'entrées parallèles ponctuelles devient illisible précisément au moment où on en a besoin. Les changements limités à un seul fichier de notes de version y vont plutôt.
Chaque entrée précise trois choses : ce qui a changé, pourquoi cela a changé, et comment migrer.
Les renommages mécaniques ont leur place dans les fichiers de correspondance de l'outil de migration automatisé. Faites un lien vers cette correspondance plutôt que de reproduire des tableaux de renommage dans le texte ; des tableaux dupliqués finissent par se désynchroniser de l'outil qui effectue réellement le renommage.
Jugement : préférez les changements additifs. Quand une rupture est inévitable, gardez l'ancien chemin fonctionnel derrière une dépréciation pendant un cycle lorsque c'est faisable, et concevez la rupture pour qu'elle se manifeste comme une erreur claire à la compilation plutôt que comme un changement de comportement silencieux.
## Exécuter en CI les exemples client-serveur comme des tests
Traitez les exemples comme des tests, pas comme de la décoration. Chaque exemple est son propre paquet d'espace de travail contenant un serveur et un client appariés ; le client se connecte, vérifie les résultats qu'il attend, et se termine avec un statut d'échec en cas de non-correspondance. Un exécuteur lance chaque exemple à travers ses combinaisons configurées de transport et d'ère de protocole, et cette tâche fait partie de la vérification d'intégration continue déclenchée à chaque changement.
L'infrastructure partagée des exemples se trouve dans un seul paquet partagé dont l'export racine reste minimal — analyse des arguments, utilitaires d'assertion, résolution de chemins voisins. Les dépendances optionnelles plus lourdes, comme un fournisseur d'autorisation de démonstration ou un magasin d'événements en mémoire, se trouvent derrière un export de sous-chemin afin que les exemples sans rapport ne les chargent pas inutilement. Les exemples n'importent que cette infrastructure et configurent les transports directement dans le code, si bien que chaque exemple se lit comme un usage réel plutôt que comme une enveloppe autour d'une configuration cachée.
Les pages de guide de documentation reçoivent des fichiers compagnons d'extraits par page, synchronisés par la même étape de synchronisation des extraits, et celles qui sont exécutables sont également lancées en intégration continue.
