Inscription d’une étape de plug-in serveur
Identifiant du schéma : plugin-step@1
Schéma lisible par machine : plugin-step.schema.json
L’enveloppe d’inscription canonique d’une étape de plug-in serveur (section 6.6) — une logique côté serveur qui s’exécute lorsqu’un enregistrement d’une table est créé, mis à jour ou supprimé. L’enveloppe indique quand et comment la logique s’exécute ; une implémentation BS Lang porte en plus le script en ligne, qui est validé par la grammaire BS Lang en plus de ce schéma. Une inscription est validée de manière identique, qu’elle provienne d’un concepteur, d’un appel d’API, d’un assistant IA ou d’une importation de solution.
Structure
| Champ | Type | Obligatoire | Remarques |
|---|---|---|---|
logicalName | string | oui | Préfixé par l’éditeur, en snake-case minuscule. |
displayName | string | oui | Nom lisible de l’étape. |
publisherPrefix | string | oui | Le préfixe de l’éditeur propriétaire, dans l’espace de noms duquel doit se trouver logicalName — et non celui de la table surveillée. Une table standard telle que account est volontairement sans préfixe, et valider par rapport à elle rendait impossible de nommer une étape sur une telle table. |
entity | string | oui | La table dont les écritures déclenchent l’étape. |
message | string | oui | Create, Update ou Delete. |
stage | string | oui | PreValidation, PreOperation ou PostOperation. |
executionMode | string | oui | Synchronous (en ligne, au sein de l’écriture) ou Asynchronous (mise en file via l’outbox et exécutée après la validation de l’écriture). Une étape asynchrone doit être PostOperation : elle s’exécute alors que l’enregistrement est déjà sauvegardé, elle n’a donc plus rien à refuser. |
rank | integer | oui | Ordre d’exécution parmi les étapes portant sur la même table + message + phase ; la plus basse s’exécute en premier. |
isEnabled | boolean | oui | Une étape désactivée est stockée mais jamais exécutée. |
implementation | object | oui | Exactement une implémentation (voir ci-dessous). |
Implémentation
| Champ | Type | Obligatoire | Remarques |
|---|---|---|---|
type | string | oui | BsLangScript ou DotNetAssembly. |
script | string | si BsLangScript | Le code source BS Lang. |
assemblyReference | string | si DotNetAssembly | La clé de stockage de l’assembly téléversé. |
typeName | string | si DotNetAssembly | Le type de plug-in à instancier. |
Phases d’exécution
- PreValidation — s’exécute avant l’opération ; vérifications grossières de forme et de sécurité.
- PreOperation — s’exécute juste avant l’écriture ; peut modifier des valeurs de champ de l’enregistrement ou rejeter toute l’opération.
- PostOperation — s’exécute juste après l’écriture ; pour les modifications en cascade ou dépendantes.
Une étape rejette une opération en appelant throwValidationError("reason"), ce qui arrête
l’écriture et renvoie le motif à l’appelant.
Exemple
{
"logicalName": "par_invoice_reject_negative",
"displayName": "Reject negative invoice total",
"entity": "par_invoice",
"message": "Create",
"stage": "PreOperation",
"executionMode": "Synchronous",
"rank": 1,
"isEnabled": true,
"implementation": {
"type": "BsLangScript",
"script": "if (getValue(\"par_total\") < 0) { throwValidationError(\"Total cannot be negative.\"); }"
}
}
Fonctions hôtes des scripts BS Lang
Une étape BsLangScript lit et modifie l’enregistrement au moyen de ces fonctions hôtes :
| Fonction | Rôle |
|---|---|
getValue("field") | La valeur actuelle d’un champ de l’enregistrement en cours d’écriture. |
setValue("field", value) | Modifie la valeur d’un champ (prise en compte uniquement en PreOperation). |
getPreValue("field") | La valeur précédente du champ (mises à jour et suppressions). |
throwValidationError("message") | Rejette l’opération et l’annule. |
message() / stage() / entityName() | L’opération, la phase et la table en cours. |
userId() / depth() | L’utilisateur déclencheur et la profondeur de récursion. |
Accéder à d’autres tables
Les fonctions ci-dessus ne voient que l’enregistrement en cours d’écriture. Celles-ci accèdent au reste de l’environnement, et chacune s’exécute en tant qu’utilisateur ayant déclenché l’étape — un script n’est pas un moyen de contourner une autorisation, et une étape dont l’appelant ne peut pas lire une table n’en obtient rien.
| Fonction | Rôle |
|---|---|
getRecord("table", id) | Un enregistrement sous forme d’objet, ou vide s’il n’existe pas. Il porte .id. |
queryRecords("table", "field", value) | Les lignes où un champ est égal à une valeur, sous forme de liste d’objets. Limité à 200. |
runQuery({ … }) | Exécute un document parsware-query@1 complet — filtres et/ou imbriqués avec dix opérateurs, jointures, tris, pagination, ou un aggregate qui renvoie des groupes. Le même langage de requête que celui qu’émet la recherche avancée et que stocke une requête enregistrée. Lignes limitées à 200 ; un agrégat ne l’est pas. Une colonne jointe se lit sous son alias (row.acct.par_industry), et le 'from' d’un lien doit être entre guillemets, car from est un mot-clé de BS Lang. |
runSavedQuery("name") | Exécute une requête enregistrée par son nom logique. La complexité réside dans un concepteur et voyage dans la solution : le script ne fait que nommer la question. |
countRecords("table", "field", value) | Combien il y en a, sans les récupérer — un vrai COUNT, donc exact au-delà de la limite de 200 lignes qui borne queryRecords. |
createRecord("table", { field: value, … }) | Crée un enregistrement et renvoie son id. |
updateRecord("table", id, { field: value, … }) | Modifie les champs nommés, et eux seuls. |
deleteRecord("table", id) | Supprime un enregistrement. |
Une écriture effectuée ainsi applique les règles ordinaires : une étape inscrite sur la
table écrite s’exécutera à son tour. Cette imbrication est limitée à une profondeur de
huit, et depth() indique au script où il se trouve — mais la limite est un filet de
sécurité, pas une conception. Une étape qui écrit dans la table qu’elle surveille se
déclenchera elle-même ; la solution est une condition ou une autre table.
Importer une logique partagée
Le script d’une étape peut import un script de solution par son nom, exactement comme
une règle métier client, afin qu’une règle appliquée par plusieurs étapes ne soit écrite
qu’une fois :
import 'par_approval_rules';
if (getValue('par_total') > par_approval_rules.limit()) { … }
Les deux styles de guillemets fonctionnent. Le module doit être un script de solution du même environnement ; toute autre chose échoue à l’enregistrement de l’étape plutôt qu’à son exécution.