Aller au contenu principal

Exemple pratique

Construisons quelque chose de concret : charger un tableau de bord utilisateur depuis une API, valider les données, les transformer et gérer les erreurs proprement.

Cet exemple combine :

  • Zygos - Opérations asynchrones sûres avec ResultAsync
  • Kanon - Validation de schémas
  • Arkhe - Utilitaires de transformation de données

Le scénario

Vous devez charger les données du tableau de bord d'un utilisateur depuis une API. La réponse peut être malformée, le réseau peut échouer, et vous devez transformer les données brutes avant de les afficher.

Approche traditionnelle : try/catch imbriqués, validation manuelle, croiser les doigts.

Approche Pithos : composable, type-safe, élégante.

Étape 1 : Définir vos schémas

D'abord, définissez à quoi ressemblent des données valides avec Kanon :

// src/lib/schemas.ts
import {
object,
string,
number,
boolean,
array,
optional,
parse,
} from "pithos/kanon";

// Définir la structure attendue de la réponse API
const UserSchema = object({
id: string(),
firstName: string(),
lastName: string(),
email: string().email(),
role: string(),
createdAt: string(),
preferences: optional(
object({
theme: optional(string()),
language: optional(string()),
notifications: optional(boolean()),
})
),
});

const PostSchema = object({
id: string(),
title: string(),
content: string(),
publishedAt: optional(string()),
status: string(),
});

const DashboardSchema = object({
user: UserSchema,
posts: array(PostSchema),
stats: object({
totalViews: number(),
totalLikes: number(),
totalComments: number(),
}),
});
Utiliser le style de l'API Zod

Si vous venez de Zod, le shim compatible Zod offre une syntaxe familière avec moins d'imports, au prix de bundles légèrement plus gros :

import { z } from "pithos/kanon/helpers/as-zod.shim";

const UserSchema = z.object({
id: z.string(),
firstName: z.string(),
email: z.string().email(),
// ... même API Zod
});

Étape 2 : Créer des helpers API sûrs

Encapsulez les appels fetch avec Zygos pour une gestion d'erreurs sûre :

// src/lib/api.ts
import {
ResultAsync,
errAsync,
okAsync,
} from "pithos/zygos/result/result-async";

// Créer un wrapper fetch sûr
const safeFetch = ResultAsync.fromThrowable(
fetch,
(error) => `Erreur réseau : ${error}`
);

// Créer un parser JSON sûr
const safeJson = <T>(response: Response) =>
ResultAsync.fromThrowable(
async () => (await response.json()) as T,
(error) => `Erreur de parsing JSON : ${error}`
)();

Étape 3 : Ajouter la transformation de données

Utilisez les utilitaires Arkhe pour transformer les données validées :

// src/lib/transformers.ts
import { groupBy } from "pithos/arkhe/array/group-by";
import { capitalize } from "pithos/arkhe/string/capitalize";

type User = {
id: string;
firstName: string;
lastName: string;
email: string;
role: string;
createdAt: string;
preferences?: {
theme?: string;
language?: string;
notifications?: boolean;
};
};

type Post = {
id: string;
title: string;
content: string;
publishedAt?: string;
status: string;
};

// Transformer les données utilisateur pour l'affichage
function formatUser(user: User) {
return {
id: user.id,
fullName: `${capitalize(user.firstName)} ${capitalize(user.lastName)}`,
email: user.email,
role: capitalize(user.role),
preferences: user.preferences ?? {
theme: "light",
language: "en",
notifications: true,
},
};
}

// Transformer les articles pour le tableau de bord
function formatPosts(posts: Post[]) {
const grouped = groupBy(posts, (post) => post.status);

return {
published: grouped["published"] ?? [],
draft: grouped["draft"] ?? [],
total: posts.length,
};
}

Étape 4 : Tout composer ensemble

Combinez maintenant toutes les pièces en un seul pipeline composable :

// src/lib/api.ts (suite)
type DashboardData = {
user: ReturnType<typeof formatUser>;
posts: ReturnType<typeof formatPosts>;
stats: {
totalViews: number;
totalLikes: number;
totalComments: number;
};
};

function loadDashboard(userId: string): ResultAsync<DashboardData, string> {
return safeFetch(`/api/dashboard/${userId}`)
.andThen((response) => {
if (!response.ok) {
return errAsync(`Erreur HTTP : ${response.status}`);
}
return okAsync(response);
})
.andThen((response) => safeJson<unknown>(response))
.andThen((data) => {
const result = parse(DashboardSchema, data);

if (!result.success) {
return errAsync(`Données invalides : ${result.error}`);
}

return okAsync(result.data);
})
.map((data) => ({
user: formatUser(data.user),
posts: formatPosts(data.posts),
stats: data.stats,
}));
}

Étape 5 : L'utiliser dans votre app

Avec le pipeline en place, il ne reste qu'à consommer le résultat : on vérifie succès ou erreur, puis on affiche.

// src/components/Dashboard.tsx
async function initDashboard() {
const result = await loadDashboard("user-123");

if (result.isErr()) {
// Gérer l'erreur - afficher un message, réessayer, fallback...
showError(result.error);
return;
}

// TypeScript sait que result.value est DashboardData
const { user, posts, stats } = result.value;

renderHeader(user.fullName, user.role);
renderPostsList(posts.published);
renderDraftsBadge(posts.draft.length);
renderStats(stats);
}

Démo interactive

Code
/**
* Kanon schemas for validating API responses
* Using Pithos/Kanon v3 for schema validation
*/
import {
object,
string,
number,
boolean,
array,
optional,
} from "pithos/kanon/index";

// Define the expected API response structure
export const UserSchema = object({
id: string(),
firstName: string(),
lastName: string(),
email: string().email(),
role: string(),
createdAt: string(),
preferences: optional(
object({
theme: optional(string()),
language: optional(string()),
notifications: optional(boolean()),
})
),
});

export const PostSchema = object({
id: string(),
title: string(),
content: string(),
publishedAt: optional(string()),
status: string(),
});

export const DashboardSchema = object({
user: UserSchema,
posts: array(PostSchema),
stats: object({
totalViews: number(),
totalLikes: number(),
totalComments: number(),
}),
});
Résultat
info

La démo ci-dessus est plus complète que les extraits de code : elle est intégrée dans un projet React et inclut l'interface utilisateur.

Le code source complet est disponible sur GitHub.


Ce que vous venez de construire

Avec un minimum de code, vous avez :

Appels API type-safe - response.json() ne renvoie plus de any

Données validées - Kanon s'assure que la réponse API correspond à votre schéma

Gestion d'erreurs élégante - Chaque erreur est capturée et typée

Transformations propres - les utilitaires Arkhe simplifient la mise en forme

Pipeline composable - Facile d'ajouter du cache, des retries ou du logging


Comparer au code traditionnel

Sans Pithos, cela impliquerait typiquement :

// ❌ L'approche traditionnelle
async function loadDashboard(userId: string) {
try {
const response = await fetch(`/api/dashboard/${userId}`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);

const data = await response.json(); // any 😱

// Validation manuelle...
if (!data.user || !data.posts) {
throw new Error("Données invalides");
}

// Transformation manuelle...
return {
user: {
fullName: data.user.firstName + " " + data.user.lastName,
// ... plus de travail manuel
},
// ... plus de travail manuel
};
} catch (error) {
console.error(error); // Et maintenant ?
return null; // L'appelant doit vérifier null partout
}
}

Prochaines étapes

Maintenant que vous avez vu comment les modules fonctionnent ensemble :