Vues
Une vue est une classe de schéma créée à partir d'une entité :
/** Ce qu'un auteur peut écrire. */
export class PostDraft extends Post.pick('slug', 'title', 'summary', 'body') {}
/** Ce que l'index public montre — pas de body. */
export class PostCard extends Post.pick('id', 'slug', 'title', 'summary', 'publishedAt') {}
/** L'entrée d'une opération de lecture custom. */
export class BySlugInput extends Post.pick('slug') {}
Une vue est une classe de schéma complète : elle a getFields(), validate(), et
s'utilise comme type TypeScript. Les vues se chaînent : Post.pick(…).partial().
Les cinq dérivations
| Dérivation | Produit |
|---|---|
Post.pick('a', 'b') | ces champs seulement |
Post.omit('a') | tous les champs sauf ceux-là |
Post.rename({ a: 'b' }) | les mêmes champs sous d'autres noms — les contraintes et ce qui est adressé à chaque adaptateur suivent |
Post.partial() | chaque champ optionnel : un champ absent n'est pas mis à jour |
Post.extend({ extra: text() }) | les mêmes champs plus d'autres |
Le mode partial est conservé lorsque la vue est utilisée comme entrée d'une opération.
Une vue décrit les lignes d'un autre schéma, et c'est pourquoi elle n'a pas de table. Celle
qui n'a rien retiré décrit exactement les mêmes lignes que son origine : elle n'est donc ni
l'une ni l'autre, et elle est refusée au démarrage tant qu'elle ne dit pas laquelle.
.anchor() détient ses propres lignes — c'est ainsi que User étend AuthUser.
Ce qu'une vue retient
Une vue garde deux choses de son origine : source, la déclaration dont elle est tirée, et
survived, ce que la coupe a laissé — indexé par les noms de champs de l'origine. Les
deux vivent sur derivation, d'où anchor remonte jusqu'au schéma dont la vue décrit les
lignes.
class PostCard extends Post.pick('id', 'title') {}
PostCard.derivation.source // Post
PostCard.derivation.survived // { id: 'id', title: 'title', body: undefined, status: undefined }
PostCard.derivation.anchor // Post — de qui sont les lignes que cette forme décrit
Un champ retiré figure à undefined : la trace dit donc ce qui manque, pas seulement ce
qui reste. Un rename y porte le nouveau nom ({ note: 'comment' }), et les deux se
composent avec celle du parent — Post.pick('a','b','c').omit('c') se rapporte à Post et
jamais à l'intermédiaire, exactement comme source saute celui-ci.
partial() et extend() laissent la trace inchangée : rendre un champ optionnel ne change
pas son origine, et un champ ajouté n'en a aucune à déclarer.
C'est ce qui permet à la carte d'identité de publier une vue comme
un Post sans body plutôt que comme une forme anonyme — deux vues d'une même entité se
décrivaient jusqu'ici à l'identique.
Où les vues se branchent
- Entrées de handler — déclarez la vue comme type du paramètre ; la façade valide le body du fil contre elle avant votre code (Handlers).
- Sorties de handler —
Crud(Post, { list: PostCard })nomme la vue d'une opération ;Crud(Post, PostPublic)restreint tout le handler (Handlers). - Formulaires —
useFormFor(PostDraft)dérive ses champs de la vue (Formulaires).
Les projections io
L'axe boundary dérive deux ensembles de champs que toutes les surfaces utilisent :
Visibility.of(Post.getFields()).input // les champs qu'un client peut ÉCRIRE — readOnly exclus
Visibility.of(Post.getFields()).output // les champs qu'un client peut LIRE — writeOnly exclus
C'est pourquoi un formulaire bâti sur Post n'affiche pas d'inputs status ou
publishedAt, sans configuration supplémentaire par formulaire.
Choisir entre un champ et une vue
readOnly / writeOnly et pick / omit peuvent tous retirer un champ d'une surface,
mais leur portée diffère.
- boundary (sur le champ) définit une règle globale. Avec
writeOnly(password), le mot de passe est exclu de toutes les sorties. - pick / omit (sur une vue) choisit les champs d'un usage précis. Par exemple,
Post.pick('id', 'title')définit la réponse de l'index public.
Ces deux mécanismes sont complémentaires.
- Sans
writeOnlysur le champ, chaque vue de sortie devrait penser à omettrepassword. La règle globale évite cet oubli. - Une réponse publique (
id,title) et une réponse admin (+ authorEmail) ont en revanche besoin de vues différentes :authorEmailest inclus ou exclu selon l'usage.
Le critère est la portée de la règle : globale ou propre à un usage.
| Le fait est… | Il vit… | Exemple |
|---|---|---|
| vrai partout (invariant) | sur le champ — readOnly / writeOnly | un mot de passe ne sort jamais |
| vrai pour un usage (local) | en dérivation — pick / omit | cet endpoint ne renvoie que id + titre |
Une règle boundary est sérialisée dans la carte sous
x-fougere.boundary. Un consommateur dans un autre langage peut donc l'appliquer. Une vue
dérivée en TypeScript n'est pas sérialisée.
Suite : La carte d'identité — la forme portable du schéma.