Xuda Plugins

La catégorie d'un plugin n'est pas une étiquette. C'est la règle de routage.

Soixante deux packages first party sont livrés aujourd'hui, répartis en exactement six catégories : 27 widget, 15 library, 10 ui, 5 graphics, 3 dbs et 2 framework. La catégorie n'est ni une convention de nommage ni un tag. C'est un champ plugin_type sur l'enregistrement du plugin, et trois systèmes distincts le lisent : l'éditeur décide dans quel emplacement le plugin apparaît, la validation décide quels fichiers il doit exporter, et le runtime décide sur laquelle des trois surfaces d'exécution son code s'exécute. Rien n'est câblé à la main après une installation. Un package du catalogue prouve que c'est le champ qui fait autorité et non le nom, car son répertoire et ses exports disent tous deux widget alors que son nom npm publié dit ui, et chaque surface de Studio le route selon le champ. Un nouveau projet démarre avec deux plugins déjà installés, le driver de données natif et le framework Tailwind. Tout ce qui vient après ces deux là est une installation, et la catégorie décide où elle atterrit.

Six catégories, six emplacements différents
Un plugin enfant a besoin que son parent le reconnaisse en retour
Le code serveur tiers est refusé par défaut
La catégorie d'un plugin n'est pas une étiquette. C'est la règle de routage.
62

Packages de plugins livrés, répartis en exactement six catégories

111

Méthodes déclarées par les 42 packages qui en exportent, sur 632 champs

6

Noms de fichiers que la plateforme sonde après une installation. La plupart en livrent deux

15

Packages qui livrent du code serveur. Aucun plugin widget ou ui ne le fait

La règle de routage

Six catégories, et la catégorie décide des deux extrémités.

Installez un plugin et il apparaît au bon endroit sans rien à configurer, car un seul champ est lu par six éléments différents de l'éditeur et par trois points d'entrée d'exécution différents. Le schéma ci-dessous est le contrat, pas le sélecteur : à gauche, comment un plugin de cette catégorie est atteint, et à droite, l'export exact que le runtime appelle et avec quelle signature. Lisez-le comme six contrats indépendants suspendus à un seul champ plutôt que comme une interface de plugin unique déclinée en six variantes, car c'est bien ce que c'est. Un plugin widget et un plugin ui n'exportent pas la même forme, ne reçoivent pas les mêmes arguments et n'apparaissent pas au même endroit, et un plugin graphics ne livre même pas le fichier autour duquel les cinq autres s'articulent.

Deux notes de bas de page, toutes deux vérifiables. La catégorie est un champ de l'enregistrement, et non le mot dans le nom du package, et un package prouve la différence : son répertoire et son contrat disent widget, son nom npm publié dit ui, et chaque emplacement ci-dessus le route selon le champ. Et le badge Installé au-dessus du panneau d'insertion ne compte pas les six catégories. Il compte uniquement framework, graphics et widget, car ce sont les trois seules à apporter quelque chose de déposable.
Le contrat

Un contrat, six formes, et les cellules vides sont l'argument.

Après l'installation npm, la plateforme sonde six noms de fichiers : index.mjs, setup.mjs, studio.mjs, runtime.mjs, server.mjs et style.css. Elle regarde d'abord à la racine du package puis dans dist, enregistre lequel des deux elle a trouvé pour que le runtime sache s'il doit aussi charger la feuille de style compilée, puis importe chaque fichier trouvé sauf style.css et server.mjs. Cet import est le véritable test est-ce que ça charge : un plugin dont l'index.mjs lève une erreur pendant l'import échoue la validation sur le champ, et le texte de l'erreur est stocké sur son enregistrement, ce qui explique pourquoi un plugin cassé vous montre une raison plutôt qu'un emplacement vide. Le tableau ci-dessous montre ce que livrent réellement les six catégories. Les croix portent autant l'argument que le texte : la colonne server.mjs est vide pour chaque catégorie côté navigateur, et c'est le fait sur lequel reposent à la fois les surfaces d'exécution et le verrou tiers. Un fichier est délibérément laissé hors du tableau. studio.mjs n'est livré que par un seul package de tout le catalogue, le driver de données natif, où il constitue une surface serveur distincte en temps de conception pour l'éditeur, ce qui lui vaut cette phrase plutôt qu'une colonne de cinq croix.

index.mjs doit exporter
runtime.mjs doit exporter
server.mjs
setup.mjs
widget, 27 livrésmethods, plus des capabilities optionnellesune fonction par méthode
library, 15 livrésmethodsun stub par méthode12 sur 1512 sur 15
ui, 10 livréspropertiesfn, et optionnellement destroy
graphics, 5 livréstabs
dbs, 3 livréstabs et public_filter3 sur 3
framework, 2 livréstabs, core et les primitives UIcore, obligatoire. resources, optionnel
L'installation

Une installation est un véritable npm install dans un répertoire qui appartient à votre projet.

Rien ici n'est une métaphore d'une installation. Une entrée est écrite dans la carte des plugins achetés du projet, portant le drapeau installed, la version, le nom du plugin parent s'il en existe un, le manifeste de fichiers, le type de plugin et le drapeau background. Ensuite npm install s'exécute dans un répertoire plugins appartenant à ce seul projet, avec chaque argument échappé en shell. Puis ce répertoire est rsyncé vers les serveurs régionaux. Ensuite le package installé est scanné jusqu'à une profondeur de dix, et chaque fichier est lu dans une base CouchDB appartenant au projet, indexé par son chemin relatif, et une configuration existante est reportée lors d'une mise à jour plutôt qu'écrasée.

  • Chaque projet a son propre arbre node_modules, et un package qui échoue à se télécharger ne supprime que lui-même. Le code contient une note expliquant pourquoi : une version antérieure supprimait l'arbre entier en cas d'échec, ce qui désinstallait tous les autres plugins du projet parce qu'un seul package n'avait pas pu être téléchargé.
  • Désinstaller bascule le drapeau installed, exécute npm uninstall, et vide le document de plugin côté projet jusqu'à son id, sa révision et son setup. La configuration survit à une désinstallation et à une réinstallation ultérieure, ce qui fait toute la différence entre retirer un plugin et perdre les identifiants que vous y aviez saisis.
  • Rien n'est intégré dans un build d'app. Le runtime récupère chaque fichier de plugin par session via une route /plugins, qui sert depuis l'arbre propre au projet et exige soit un app id résolvable, soit un token valide. Pour un fichier source non empaqueté, cette route réécrit les imports ESM relatifs à la volée, afin qu'un navigateur puisse les charger. Ce que le déploiement empaquette, c'est la carte de configuration, rassemblée dans les données de démarrage de l'app.
  • En session de développement, le runtime essaie src avant dist pour tout fichier .mjs et retombe sur dist, si bien que modifier le code source du plugin se répercute sans rebuild. Partout ailleurs, c'est le manifeste qui décide, et là où il enregistre que c'est la copie compilée qui a été trouvée, dist l'emporte.
Une installation
  1. L'enregistrement est écrit
    installed, version, nom du parent, manifeste de fichiers, type de plugin et drapeau background
  2. npm install s'exécute
    Dans le répertoire plugins propre à ce projet, chaque argument échappé en shell
  3. L'arbre est synchronisé
    Ce répertoire de plugins du projet est poussé vers les serveurs régionaux distants
  4. Le package est capturé en instantané
    Chaque fichier lu dans la base de données du projet, indexé par son chemin relatif
Composition

Un plugin enfant nomme son parent. Le parent doit nommer l'enfant en retour.

Un plugin peut déclarer un seul parent, et la plateforme refuse de l'installer à moins que deux déclarations distinctes ne concordent. L'enfant nomme le parent via un champ de dépendance sur son propre enregistrement. Le parent doit lister l'enfant en retour, dans un tableau authorized plugins sur l'enregistrement du parent. Aucune déclaration seule ne suffit. Les vérifications s'exécutent dans l'ordre et chaque échec revient sous forme d'un refus numéroté plutôt que d'une trace d'appels, et toutes s'exécutent avant que npm ne soit invoqué, si bien qu'une installation rejetée ne laisse rien sur le disque. Les deux mêmes vérifications sont rejouées plus tard, à chaque lecture et chaque écriture d'une configuration de plugin, et à nouveau sur le serveur avant qu'une méthode de plugin ne soit autorisée à s'exécuter.

Le couple décide de deux choses au-delà de la permission. Un enfant voit sa recherche de configuration redirigée vers le document du parent, si bien qu'il n'existe qu'une seule configuration Stripe plutôt qu'une par widget qui affiche un champ carte. Et il décide de la confiance : parce que le serveur fusionne le fichier serveur du parent dans le même objet que l'enfant, un plugin first party qui dépend d'un plugin tiers compte lui-même comme tiers.
Configuration

Le plugin décide de ce que sa propre configuration peut révéler au navigateur.

La configuration d'un plugin n'est pas conservée dans le registre partagé. Elle vit comme un document setup à l'intérieur de la base CouchDB du projet qui l'a configuré, écrit par le formulaire de configuration et relu par les deux côtés. Quand la lecture est publique, la plateforme importe le plugin et appelle une fonction que le plugin exporte lui-même, et ne renvoie que ce que cette fonction a retourné. Il n'existe aucune liste blanche de clés sûres à l'échelle de la plateforme, et c'est à la fois la force de ce dispositif et sa limite : c'est l'auteur du plugin qui sait lequel des champs est le secret, et c'est l'auteur du plugin qui écrit le filtre.

La lecture honnête est que cette frontière vaut exactement ce que vaut le plugin qui l'a tracée. Un filtre absent signifie un document non filtré, et la plateforme ne le remet pas en question. Le même document de configuration est ce que l'étape de déploiement rassemble, une entrée par plugin installé, dans les données de démarrage avec lesquelles une app est construite.
Exécution

Trois surfaces, et celle du serveur est vérifiée avant l'import, pas après.

Le code de plugin s'exécute en exactement trois endroits. Dans le navigateur, comme widget qui possède un conteneur que le runtime a créé pour lui, comme plugin ui décorant un élément déjà existant, comme framework, ou comme plugin background accroché au cycle de vie de l'app. Sur un service de plugins dédié, son propre processus sur le port 3008, pour les méthodes serveur library. Et sur un dispatch séparé pour les drivers de données, qui ne passe jamais par ce service et met en cache sa connexion active par app et par plugin. Les deux chemins serveur s'exécutent sous un plafond de soixante secondes.

  • Une méthode library, ce sont deux fichiers qui font deux travaux. Le stub navigateur convertit les champs déclarés, impose ceux qui sont obligatoires et poste ; le fichier serveur fait le travail et détient le secret. Cette séparation est toute la raison pour laquelle une clé secrète Stripe n'a aucun chemin navigateur : elle est lue dans server.mjs, à partir du document setup remis, et nulle part ailleurs.
  • Avant que le service ne charge un fichier serveur de plugin, il vérifie si l'éditeur est first party, et il le fait avant l'import plutôt qu'après. Importer un module exécute déjà son code de premier niveau, donc une vérification placée après ne vérifie rien. Les méthodes serveur tierces sont refusées par défaut et l'interrupteur qui les autoriserait est réglé sur false dans les deux configurations, si bien que les plugins tiers restent aujourd'hui une surface produit côté navigateur uniquement.
  • Un plugin background reçoit cinq hooks de cycle de vie, déclenchés dans l'ordre beforeInit, initialized, beforeMounted, mounted et systemReady autour du démarrage de l'app, chacun recevant la session, l'objet app, l'api et les données de setup. La case run in background n'est proposée que sur un plugin library, et l'enregistrement est entièrement sauté en mode slim bundle.
  • Les champs qu'une méthode déclare font double usage. Sur un Full Stack VPS, la plateforme parcourt les plugins installés et enregistre chaque méthode comme outil MCP, nommé plugin puis method, avec son schéma d'entrée construit à partir de ces mêmes champs déclarés et sa description tirée de l'infobulle que la méthode portait déjà.
Un appel library
  1. L'étape de workflow se déclenche
    Nommant un plugin, une méthode et les propriétés à lui passer
  2. Le stub navigateur valide
    Champs convertis vers leurs types déclarés, les obligatoires imposés
  3. Il poste au service de plugins
    Le token vérifié contre une session active dans votre propre base de projet, jamais depuis un cache
  4. Le fichier serveur s'exécute, avec un plafond
    Soixante secondes, avec le document setup remis
Ce que le code de plugin peut atteindre

Un plugin navigateur reçoit un seul objet, et c'est tout ce qu'il a.

Le runtime construit un objet de paramètres par appel et le remet en second argument. Un widget ne va pas chercher une variable globale pour trouver la plateforme, et il n'y a pas d'API de plugin séparée à apprendre au-delà de ce qui arrive dans cet objet. L'hôte crée le div conteneur, lit l'id du plugin et le nom de la méthode sur le nœud, importe index.mjs et vérifie les capabilities déclarées face à l'environnement courant, résout et convertit chaque champ déclaré et impose les obligatoires, charge style.css et dist/runtime.css quand le manifeste les a enregistrés, récupère le setup du plugin, et ce n'est qu'alors qu'il importe runtime.mjs et attend la méthode. Une erreur levée dans cette méthode est interceptée et signalée plutôt que de faire planter la page. La forme ci-dessous est celle du widget. Un plugin ui reçoit quelque chose d'assez différent pour mériter d'être imprimé à côté, car cette différence constitue toute la distinction entre les deux catégories : un widget reçoit un conteneur qu'il possède, un plugin ui reçoit un élément déjà existant.

runtime.mjs · export async function <method>(fields, params)
fields les champs déclarés, convertis vers leurs types et vérifiés
params :
SESSION_ID, method, _session
dsP, sourceP, propsP datasource, ligne source, props résolues
plugin_name
container_node le div que le runtime a créé pour ce widget
container_data
plugin_setup le setup filtré, jamais le document brut
report_error(desc, warn)
log_error(desc, warn)
call_plugin_api(name, data) la seule route vers le serveur
set_SYS_GLOBAL_OBJ_WIDGET_INFO(doc)
run_widgetCallbackEvent() déclenche l'id d'événement que vous avez déclaré comme champ
api_utils
un plugin ui reçoit une forme entièrement différente :
fn(plugin_name, element, properties) pas de conteneur, il décore le vôtre
C'est une convention d'appel, pas un bac à sable. Le code d'un widget s'exécute dans votre page comme n'importe quel autre script qui s'y trouve, et ce que cet objet vous apporte, c'est que la plateforme décide de ce qui y entre. Un plugin ui peut aussi exporter destroy, pour libérer ce qu'il a construit quand son élément disparaît, et le runtime rejoue exactement les propriétés que fn avait reçues plutôt que de les résoudre à nouveau au démontage, car à ce moment-là la datasource derrière cette ligne peut déjà avoir été retirée.
En construire un

Le chemin du développeur, c'est npm, et rien là-dedans n'est fait maison.

Il n'y a ici aucune chaîne d'outils propriétaire et aucun CLI de plugin à apprendre. Tout le chemin repose sur un package sandbox publié contre lequel vous développez, un simple build de librairie Vite, et npm. La plateforme ne rencontre votre plugin qu'à la toute fin, quand vous enregistrez le nom du package.

  1. 01

    Développer contre le sandbox

    Le harnais est un véritable package publié, @xuda.io/plugin-sandbox en version 1.0.27. Il construit une page à deux volets : un formulaire Plugin Configurator à gauche, généré à partir des champs que votre méthode déclare, un redimensionneur par glisser au milieu, et un volet Live Preview à droite avec le div conteneur que votre méthode recevra. Un menu déroulant choisit laquelle de vos méthodes tester. Tout champ déclarant le type de rendu editor reçoit un véritable éditeur Monaco relié à son événement de changement, avec le langage tiré des paramètres de rendu et javascript par défaut. L'adoption est partielle, et il vaut mieux le dire clairement : treize des soixante deux packages livrés développent contre ce harnais, et vingt livrent leur propre page de test sous tests.

  2. 02

    Compiler avec Vite, vers dist

    Chaque package qui utilise le sandbox porte la même configuration vite. Le point d'entrée de la librairie est src/runtime.mjs, le format est ES, les imports dynamiques sont inlinés, la sortie atterrit dans dist, toute feuille de style émise est renommée en runtime.css, et une petite étape de copie déplace tout ce qui se trouve dans src, hormis runtime.mjs, vers dist à ses côtés. Cette convention explique exactement pourquoi la plateforme enregistre un drapeau dist et un drapeau css par fichier quand elle sonde votre package, et pourquoi le runtime sait qu'il doit charger dist/runtime.css en plus du module.

  3. 03

    Publier, puis l'enregistrer

    Les soixante deux fichiers package.json livrés portent tous le même script de publication : npm version patch, puis npm publish. Du semver simple sans rien ajouté par-dessus, et les records donnent une idée de l'usage que ça reçoit. Le driver de données natif en est à 1.0.320, le framework Tailwind à 1.0.128, le générateur d'app par IA à 1.0.74. S'enregistrer auprès de la plateforme est une étape distincte qui récupère le manifeste en direct depuis npm, prend la version de là plutôt que de votre formulaire, et prend l'icône marketplace dans le champ svg_icon de votre package.json si vous avez laissé l'icône vide dans le formulaire.

L'autre chemin

Ou décrivez-le, et le système de plugins construit un plugin avec un plugin.

Le builder n'est pas un outil que l'assistant de chat peut atteindre de lui-même. Ses deux méthodes sont toutes deux marquées privées, donc rien ne les expose comme outils d'agent ; l'onglet plugins prépare l'action et l'assistant l'exécute dans le cadre de cette tâche. Ce qui fait le travail est lui-même un plugin library livré, le studio AI app builder, appelé de la même façon que n'importe quelle autre méthode library. Chaque contrainte ci-dessous se trouve dans le générateur plutôt que dans un document de politique, et l'état final est un plugin privé, délibérément.

Les versions disent l'intention : un plugin créé est en 0.1.0 et une modification incrémente le patch. Il obtient son propre scope npm plutôt que le scope first party, il s'installe depuis l'artefact plutôt que depuis le registre, et devenir public est une action explicite distincte sur la carte du plugin, ce que la réponse de l'assistant dit en toutes lettres. Maintenant la correction honnête. Ce scan est une correspondance de motifs statique sur du texte généré, posée sur un schéma de réponse et une liste blanche de sept noms de fichiers. Ce n'est ni une analyse dynamique ni un bac à sable.
Qui peut faire quoi

Cinq actions, et aucune de deux n'est verrouillée de la même façon.

Deux de ces éléments sont couramment décrits comme une seule et même chose, et ils ne le sont pas. Un membership Agency verrouille l'enregistrement même d'un plugin, et il porte sur la méthode d'upload. Un compte vendeur Stripe Connect actif verrouille la mise en public, et il porte sur la méthode publish, que quiconque est connecté peut appeler. Ils arrêtent des personnes différentes à des moments différents. Installer un plugin sur un projet n'est verrouillé par aucun des deux : cela nécessite une habilitation admin sur ce projet, et si le plugin est payant, cela passe d'abord par une étape de paiement.

Habilitation
Plan de membership
L'autre verrou
Installer ou désinstaller sur un projetAdmin du projetUn plugin payant passe d'abord par une étape de paiement
Lire ou écrire une configuration de pluginAdmin du projetLa chaîne parent est revérifiée avant que quoi que ce soit ne soit touché
Enregistrer un plugin, tout courtAdmin du projetAgencyPropriété appliquée à chaque mise à jour
Le publier sur le marketplaceTout utilisateur connectéUn compte vendeur Stripe Connect actif, ou un 403
L'archiverAdmin du projetPropriétaire uniquement
Le verrou

Publier est une action distincte de mettre à jour le code, délibérément.

Téléverser une nouvelle version, ou laisser le builder IA en reconstruire une, ne repasse jamais par la revue. Le cycle de vie existant est reporté tel quel : un plugin qui était privé reste privé, un plugin qui était public reste public, et l'état de revue qu'il avait déjà est celui qu'il conserve. Devenir public est une méthode distincte avec une habilitation distincte, exécutée une fois, par le propriétaire, délibérément, et c'est le seul appel qui déclenche le verrou. Tout ce qui suit dépend de cet appel unique.

Dix vérifications reviennent du relecteur, chacune portant sa propre sévérité : activité de bot, intention commerciale non divulguée, code malveillant, activité suspecte, nom de compte malformé, spam et qualité insuffisante, usurpation d'identité et marque déposée, contenu interdit, plagiat et duplication, et secrets ou données personnelles divulgués. Ce que reçoit le relecteur mérite d'être dit clairement, car cela fixe le plafond de ce que tout verdict peut signifier : le nom, la description, les tags, la catégorie, le prix, et un résumé de code qui se limite au nom du package npm, sa version et la liste des plateformes. C'est une revue de métadonnées, pas une lecture de votre code source. Un éditeur first party la saute purement et simplement. Tout ce qui est retenu part vers une personne, avec un email opérateur, un ticket de support dédupliqué sur l'id de l'objet, et un lien d'appel en cas de rejet, et aucun délai cible n'est écrit nulle part dans le code.
Limites honnêtes

Neuf choses que le système de plugins ne fait pas.

  • Les plugins tiers ne peuvent pas exécuter de code serveur aujourd'hui. La politique provisoire est le refus, c'est un interrupteur de configuration, et cet interrupteur est désactivé dans les deux configurations, si bien que tout ce que vous construisez pour qu'un tiers l'installe reste une surface côté navigateur uniquement.
  • Le contrat widget porte une capability headless, pour du code de plugin qui s'exécute là où il n'y a pas de document. Aucun des 62 packages livrés ne la déclare. Le contrat existe, la preuve livrée non.
  • Le runtime prend en charge un export destroy optionnel sur un plugin ui. Aucun des dix plugins ui livrés n'en exporte un.
  • Deux des trois drivers de données sont en lecture seule tels que livrés. Seul le natif implémente les six services. MySQL et CouchDB exportent un read et un test de connexion et rien d'autre, et demander autre chose à l'un ou l'autre lève une erreur nommant le service manquant.
  • Le sélecteur d'icônes cherche à l'intérieur du kit sélectionné, pas à travers les kits installés. Et un plugin graphics peut déclarer au plus une dépendance npm, ce qui explique pourquoi chaque kit livré est un unique package amont et son arbre de répertoires.
  • Installer un composant partagé ne vérifie que deux types de dépendance de plugin, le framework d'interface sur un programme et le driver de données sur une table. Un composant qui utilise un plugin widget ne le déclare pas, si bien que cette installation peut réussir alors même que le widget manque toujours.
  • Le builder IA est verrouillé avant que le code ne soit empaqueté, jamais pendant son exécution. Treize motifs interdits et une énumération fixe de noms de fichiers, tous deux appliqués au texte. Aucune isolation d'exécution ne se cache derrière.
  • Rien dans la plateforme ne décide quelles clés de configuration sont sûres à publier. Ce jugement est entièrement délégué à l'auteur du plugin, et un plugin qui ne pose aucun jugement remet tout son document de configuration au navigateur.
  • Le chemin d'enregistrement accepte deux chaînes de type de plus que ce que le produit expose, et rien dans le catalogue n'en livre aucune des deux. Les six du sélecteur sont les six qui mènent quelque part.
Avant de vous engager

Les questions que vous êtes en train de vous poser

Cinq de ces six réponses se terminent sur une limite. Elles sont ici parce que vous les rencontreriez de toute façon dans la première semaine.

Regarder

Regardez un plugin s'installer et apparaître dans son emplacement.

Installez deux plugins et vous avez une couche de données et un système de design.

C'est littéralement ce avec quoi un nouveau projet est livré : le driver de données natif et le framework Tailwind, et rien d'autre. Tout ce qui vient après ces deux là est une installation, et la catégorie sur l'enregistrement décide où elle apparaît, que ce soit le panneau d'insertion, le sélecteur d'icônes, le sélecteur de driver de table, un switch sur un élément, ou la liste des actions de workflow.