goblin-laboratory 4.5.2 → 4.6.2

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.
@@ -0,0 +1,125 @@
1
+ # 📘 Blueprints – Documentation technique
2
+
3
+ ## 🧠 Définition
4
+
5
+ Un **blueprint** est un fichier `.js` déclaratif décrivant les **métadonnées fonctionnelles, structurelles et UI** d’une entité métier du système Xcraft.
6
+
7
+ Il sert de base à :
8
+
9
+ - l’interface utilisateur dynamique (UI générée à partir des métadonnées),
10
+ - les systèmes de recherche, de filtre et d’export,
11
+ - l’internationalisation via `T(...)`,
12
+ - l’édition low-code ou runtime,
13
+ - et l’assistance intelligente (LLM, agents contextuels...).
14
+
15
+ ## 📦 Gestion
16
+
17
+ Les blueprints sont :
18
+
19
+ - des **fichiers `.js`** exportant un objet conforme au `BlueprintShape`,
20
+ - **éditables dynamiquement** par un acteur de gestion de ressources Xcraft,
21
+ - **versionnés** dans un **sous-module Git** (`share/blueprints`),
22
+ - **validés** à l’exécution via `parse(BlueprintShape, blueprint)`.
23
+
24
+ ## 🧱 Structure
25
+
26
+ Un blueprint contient les sections suivantes :
27
+
28
+ ### entity
29
+
30
+ Nom logique de l’entité cible (ex : `case`, `contact`).
31
+
32
+ ### fields
33
+
34
+ Liste des champs déclarés, avec :
35
+
36
+ - `type` (`text`, `enum`, `date`, etc.)
37
+ - `label` (clé nabu via `T(...)`)
38
+ - options UI (`filter`, `search`, `hintText`, etc.)
39
+ - pour les `enum`, la liste des `values` (label, icône, couleur)
40
+
41
+ ### references
42
+
43
+ Liste des **références unitaires** vers d'autres entités, avec configuration d'affichage (`lookup`).
44
+
45
+ ### collections
46
+
47
+ Listes d’identifiants liés à d'autres entités, aussi enrichies par `lookup`.
48
+
49
+ ### ui
50
+
51
+ Paramètres UI globaux pour l'entité : icône, tri par défaut, label principal, etc.
52
+
53
+ ## 🎛️ Philosophies clés
54
+
55
+ - Simplicité : uniquement des données, aucune logique métier ou interface
56
+ - Interopérabilité : utilisables dans le backend, le CLI, l’UI, et les assistants
57
+ - Internationalisation native : chaque `label`, `tooltip`, `hintText` est une clé traduisible
58
+ - Dialecte clair : pas de `many-to-one`, on utilise `references` et `collections`
59
+
60
+ ## 🔍 Exemples d’usages
61
+
62
+ | Objectif | Utilisation dans blueprint |
63
+ | ----------------------- | ---------------------------------------- |
64
+ | Générer une table | `fields` avec `ui.list = true` |
65
+ | Créer un menu déroulant | `fields.kind.values` |
66
+ | Filtrer les résultats | `fields[].ui.filter = true` |
67
+ | Chercher par texte | `ui.search = 'fulltext'` |
68
+ | Afficher une relation | `references.contactId.lookup.labelPaths` |
69
+ | Export CSV | `fields` avec `label` et `values` |
70
+
71
+ ## ✅ Exemple minimal
72
+
73
+ ```
74
+ const {T} = require('nabu');
75
+ const {parse} = require('xcraft-core-stones');
76
+ const {BlueprintShape} = require('goblin-laboratory/lib/blueprint.js');
77
+
78
+ module.exports = parse(BlueprintShape, {
79
+ entity: 'case',
80
+ fields: {
81
+ status: {
82
+ type: 'enum',
83
+ label: T('Statut'),
84
+ values: {
85
+ open: {label: T('Ouvert'), icon: 'mdiLockOpen'},
86
+ closed: {label: T('Fermé'), icon: 'mdiLock'}
87
+ },
88
+ ui: {filter: true, list: true}
89
+ }
90
+ },
91
+ references: {
92
+ contactId: {
93
+ entity: 'contact',
94
+ label: T('Contact lié'),
95
+ lookup: {
96
+ labelPaths: ['firstname', 'lastname'],
97
+ drilldown: true
98
+ }
99
+ }
100
+ }
101
+ });
102
+ ```
103
+
104
+ ## 🛡️ Validation
105
+
106
+ Chaque blueprint doit être validé au chargement :
107
+
108
+ ```
109
+ const {parse} = require('xcraft-core-stones');
110
+ const {BlueprintShape} = require('goblin-laboratory/lib/blueprint.js');
111
+
112
+ module.exports = parse(BlueprintShape, blueprint);
113
+ ```
114
+
115
+ Une erreur sera levée si le format est invalide.
116
+
117
+ ## 📁 Emplacement
118
+
119
+ Tous les blueprints sont stockés dans le dossier :
120
+
121
+ ```
122
+ share/blueprints/
123
+ ```
124
+
125
+ Ils peuvent être mis à jour dynamiquement, commités dans le sous-module, et chargés au runtime.
@@ -0,0 +1,107 @@
1
+ //Exemple de blueprint pour l'entité Case
2
+ const {T} = require('nabu');
3
+ const {parse} = require('xcraft-core-stones');
4
+ const {BlueprintShape} = require('goblin-laboratory/lib/blueprint.js');
5
+
6
+ const blueprint = {
7
+ entity: 'case',
8
+
9
+ fields: {
10
+ reference: {
11
+ type: 'text',
12
+ label: T('Référence'),
13
+ required: true,
14
+ ui: {
15
+ filter: true,
16
+ sort: true,
17
+ list: true,
18
+ search: 'fulltext',
19
+ hintText: T('Identifiant interne du dossier.'),
20
+ tooltip: T('Utilisé pour retrouver rapidement un dossier.'),
21
+ },
22
+ },
23
+
24
+ status: {
25
+ type: 'enum',
26
+ label: T('Statut'),
27
+ required: true,
28
+
29
+ values: {
30
+ open: {label: T('Ouvert'), icon: 'mdiLockOpen', color: 'green'},
31
+ closed: {label: T('Fermé'), icon: 'mdiLock', color: 'gray'},
32
+ archived: {label: T('Archivé'), icon: 'mdiArchive', color: 'brown'},
33
+ },
34
+
35
+ ui: {
36
+ filter: true,
37
+ sort: true,
38
+ list: true,
39
+ },
40
+ },
41
+
42
+ kind: {
43
+ type: 'enum',
44
+ label: T('Type de dossier'),
45
+ required: true,
46
+
47
+ values: {
48
+ issue: {label: T('Problème'), icon: 'mdiBug', color: 'red'},
49
+ question: {
50
+ label: T('Question'),
51
+ icon: 'mdiHelpCircleOutline',
52
+ color: 'blue',
53
+ },
54
+ project: {label: T('Projet'), icon: 'mdiChartGantt', color: 'indigo'},
55
+ },
56
+
57
+ ui: {
58
+ filter: true,
59
+ list: true,
60
+ hintText: T('Catégorie fonctionnelle du dossier.'),
61
+ },
62
+ },
63
+ },
64
+
65
+ references: {
66
+ contactId: {
67
+ entity: 'contact',
68
+ label: T('Contact lié'),
69
+ lookup: {
70
+ labelPaths: ['firstname', 'lastname'],
71
+ iconPath: 'avatar',
72
+ tooltipPath: 'email',
73
+ drilldown: true,
74
+ },
75
+ },
76
+
77
+ customerFolderId: {
78
+ entity: 'customer-folder',
79
+ label: T('Dossier client'),
80
+ lookup: {
81
+ labelPaths: ['name'],
82
+ iconPath: 'icon',
83
+ drilldown: true,
84
+ },
85
+ },
86
+ },
87
+
88
+ collections: {
89
+ followerIds: {
90
+ entity: 'user',
91
+ label: T('Suivi par'),
92
+ lookup: {
93
+ labelPaths: ['username'],
94
+ iconPath: 'avatar',
95
+ },
96
+ },
97
+ },
98
+
99
+ ui: {
100
+ icon: 'mdiFolder',
101
+ primaryLabel: 'reference',
102
+ secondaryLabel: ['kind', 'status'],
103
+ defaultSort: ['lastEventDate', 'desc'],
104
+ },
105
+ };
106
+
107
+ module.exports = parse(BlueprintShape, blueprint);
@@ -0,0 +1,146 @@
1
+ // @ts-check
2
+ const {Elf} = require('xcraft-core-goblin');
3
+ const {
4
+ string,
5
+ boolean,
6
+ option,
7
+ enumeration,
8
+ array,
9
+ record,
10
+ } = require('xcraft-core-stones');
11
+
12
+ /**
13
+ * Décrit comment représenter une entité liée dans l'UI
14
+ */
15
+ class LookupShape {
16
+ labelPaths = array(string); // ex: ['firstname', 'lastname']
17
+ iconPath = option(string); // ex: 'avatar'
18
+ drilldown = option(boolean); // true pour lien vers la fiche
19
+ tooltipPath = option(string); // champ pour info-bulle
20
+ descriptionPath = option(string); // champ pour résumé
21
+ }
22
+
23
+ /**
24
+ * Champ enum (valeur, label, icône…)
25
+ */
26
+ class EnumValueShape {
27
+ label = string; // clé i18n
28
+ icon = option(string);
29
+ color = option(string);
30
+ }
31
+
32
+ /**
33
+ * Hints pour aider l’UI à générer des interfaces intelligentes
34
+ */
35
+ class FieldUiShape {
36
+ filter = option(boolean);
37
+ sort = option(boolean);
38
+ list = option(boolean);
39
+ search = option(enumeration('fulltext', 'exact', 'none'));
40
+ hintText = option(string); // clé i18n
41
+ tooltip = option(string); // clé i18n
42
+ }
43
+
44
+ /**
45
+ * Décrit un champ d'entité
46
+ */
47
+ class FieldShape {
48
+ type = string; // 'text', 'enum', 'date', 'number', etc.
49
+ label = string; // clé i18n
50
+ required = option(boolean);
51
+ readonly = option(boolean);
52
+ hidden = option(boolean);
53
+ values = option(record(string, EnumValueShape)); // enum uniquement
54
+ ui = option(FieldUiShape);
55
+ }
56
+
57
+ /**
58
+ * Pointeur vers une autre entité (ex: contactId)
59
+ */
60
+ class ReferenceShape {
61
+ entity = string; // cible : 'contact'
62
+ label = option(string); // clé i18n
63
+ lookup = option(LookupShape); // représentation dans l’UI
64
+ }
65
+
66
+ /**
67
+ * Collection de pointeurs (ex: contactIds)
68
+ */
69
+ class CollectionShape {
70
+ entity = string;
71
+ label = option(string);
72
+ lookup = option(LookupShape);
73
+ }
74
+
75
+ /**
76
+ * Configuration UI globale de l’entité
77
+ */
78
+ class UiConfigShape {
79
+ icon = option(string); // ex: 'mdiFolder'
80
+ primaryLabel = option(string); // champ principal
81
+ secondaryLabel = option(array(string)); // champs secondaires
82
+ defaultSort = option(array(string)); // ex: ['createdAt', 'desc']
83
+ }
84
+
85
+ /**
86
+ * Blueprint final pour une entité
87
+ */
88
+ class BlueprintShape {
89
+ id = string;
90
+ entity = string;
91
+ fields = record(string, FieldShape);
92
+ references = option(record(string, ReferenceShape));
93
+ collections = option(record(string, CollectionShape));
94
+ ui = option(UiConfigShape);
95
+ }
96
+
97
+ class BlueprintState extends Elf.Sculpt(BlueprintShape) {}
98
+
99
+ class BlueprintLogic extends Elf.Archetype {
100
+ static db = 'blueprints';
101
+
102
+ state = new BlueprintState({
103
+ id: undefined,
104
+ entity: undefined,
105
+ fields: {},
106
+ references: undefined,
107
+ collections: undefined,
108
+ ui: undefined,
109
+ meta: {status: 'published'},
110
+ });
111
+
112
+ create(id) {
113
+ const {state} = this;
114
+ state.id = id;
115
+ }
116
+
117
+ change(path, newValue) {
118
+ const {state} = this;
119
+ state._state.set(path, newValue);
120
+ }
121
+ }
122
+
123
+ class Blueprint extends Elf {
124
+ logic = Elf.getLogic(BlueprintLogic);
125
+ state = new BlueprintState();
126
+
127
+ async create(id, desktopId) {
128
+ this.logic.create(id);
129
+ await this.persist();
130
+ return this;
131
+ }
132
+
133
+ async change(path, newValue) {
134
+ this.logic.change(path, newValue);
135
+ await this.persist();
136
+ }
137
+
138
+ delete() {}
139
+ }
140
+
141
+ module.exports = {
142
+ BlueprintShape,
143
+ BlueprintState,
144
+ Blueprint,
145
+ BlueprintLogic,
146
+ };
@@ -0,0 +1,34 @@
1
+ // @ts-check
2
+ const {Elf} = require('xcraft-core-goblin');
3
+ const {string} = require('xcraft-core-stones');
4
+ const {BlueprintLogic, BlueprintShape, Blueprint} = require('./blueprint.js');
5
+
6
+ class BlueprintsShape {
7
+ id = string;
8
+ }
9
+
10
+ class BlueprintsState extends Elf.Sculpt(BlueprintsShape) {}
11
+
12
+ class BlueprintsLogic extends Elf.Spirit {
13
+ state = new BlueprintsState({id: 'blueprints'});
14
+ }
15
+
16
+ class Blueprints extends Elf.Alone {
17
+ logic = Elf.getLogic(BlueprintsLogic);
18
+ state = new BlueprintsState();
19
+
20
+ async loadAll(desktopId) {
21
+ const reader = await this.cryo.reader(BlueprintLogic.db);
22
+ const blueprintIds = reader
23
+ .queryArchetype('blueprint', BlueprintShape)
24
+ .field('id')
25
+ .all();
26
+
27
+ //Mount all blueprint in the desktop session
28
+ await Promise.all(
29
+ blueprintIds.map((id) => new Blueprint(this).create(id, desktopId))
30
+ );
31
+ }
32
+ }
33
+
34
+ module.exports = {Blueprints, BlueprintsLogic};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "goblin-laboratory",
3
- "version": "4.5.2",
3
+ "version": "4.6.2",
4
4
  "description": "Laboratory",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -17,7 +17,8 @@
17
17
  "goblin-theme": "^2.0.0",
18
18
  "xcraft-core-goblin": "^5.0.0",
19
19
  "xcraft-core-probe": "^2.0.0",
20
- "xcraft-core-transport": "^4.0.0"
20
+ "xcraft-core-transport": "^4.0.0",
21
+ "xcraft-core-stones": "^0.4.16"
21
22
  },
22
23
  "devDependencies": {
23
24
  "aphrodite": "^2.2.2",