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.
|
|
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",
|