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érivationProduit
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 handlerCrud(Post, { list: PostCard }) nomme la vue d'une opération ; Crud(Post, PostPublic) restreint tout le handler (Handlers).
  • FormulairesuseFormFor(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 writeOnly sur le champ, chaque vue de sortie devrait penser à omettre password. 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 : authorEmail est 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 / writeOnlyun mot de passe ne sort jamais
vrai pour un usage (local)en dérivation — pick / omitcet 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.

Construit avec Fougere — ce site tourne sur le framework qu'il documente.