Pratique

Architecture et contrats d’erreur

Comprendre les couches imposées de Sushi SaaS, le flux de données navigateur, l’autorisation et les erreurs traduites sans fuite.

Synchronisé avec le commit 7d452a6 du starter.

Un seul sens côté serveur

src/app/**       routes et pages : entrée/sortie HTTP

src/services/**  règles métier, orchestration et invariants

src/models/**    persistance typée ; seule couche autorisée à appeler db()

src/db/**        schéma, migrations et connexion

Les routes authentifient, valident et traduisent HTTP. Toute écriture passe par un service. Les modèles possèdent les requêtes, le filtrage tenant et les transactions. tests/unit/architecture.test.ts refuse les imports qui franchissent ces frontières.

Ne créez pas src/features/ : chaque domaine se répartit horizontalement entre modèles, services, configuration et composants.

Flux de données du navigateur

Server Component → service direct
Client Component → src/api/** → client API partagé → /api/**

Un Server Component n’appelle pas l’API de sa propre application. Un Client Component n’utilise pas fetch directement ; il passe par un module de domaine sous src/api/.

Deux questions d’autorisation

Le rôle d’organisation et le plan d’abonnement restent indépendants :

const ctx = await getOrgContext(request);
if (!ctx || !can(ctx, "file:delete", file)) return respForbidden();
await requireEntitlement(ctx.orgUuid, "storage.upload");

can() décide si le rôle autorise l’action. Les entitlements décident si le plan effectif de l’organisation inclut la capacité ou la limite.

Contrat d’erreur sans fuite

Le serveur lève AppError avec un code stable du catalogue. Chaque route termine ses exceptions dans respError, qui journalise le détail interne et renvoie un message sûr et traduit.

L’interface branche sur error_code et résout le texte avec resolveErrorMessage ou resolveAuthError ; elle n’affiche jamais error.message. Les traductions sont dans src/lib/errors/i18n/locales/ et chaque code doit exister dans les cinq langues.

Ajouter un domaine proprement

  1. Définir les constantes dans src/config/.
  2. Ajouter le CRUD typé dans src/models/.
  3. Placer invariants, autorisation, idempotence et effets dans src/services/.
  4. Garder les routes minces et utiliser le catalogue d’erreurs.
  5. Ajouter les tests du niveau adapté.
  6. Exécuter pnpm lint, pnpm test:run et pnpm build.

Les contrats complets restent versionnés dans docs/errors.md, docs/frontend.md et AGENTS.md du starter.

Architecture et contrats d’erreur · Sushi SaaS