ajout patterns

This commit is contained in:
MaksTinyWorkshop
2026-03-12 17:16:05 +01:00
parent 39067b153a
commit 1ac757558b
6 changed files with 559 additions and 478 deletions

View File

@@ -8,7 +8,7 @@ Ce fichier contient **uniquement** des patterns back-end :
Objectif : éviter de réinventer la roue et réduire le temps de debug. Objectif : éviter de réinventer la roue et réduire le temps de debug.
Dernière mise à jour : 10-03-2026 Dernière mise à jour : 12-03-2026
--- ---
@@ -31,6 +31,7 @@ Dernière mise à jour : 10-03-2026
- [Sémantique explicite `Trial` vs `Paid` dans Subscription](#pattern-subscription-trial-vs-paid) - [Sémantique explicite `Trial` vs `Paid` dans Subscription](#pattern-subscription-trial-vs-paid)
- [Restauration dachats Stripe en 3 étapes](#pattern-restauration-achats-stripe) - [Restauration dachats Stripe en 3 étapes](#pattern-restauration-achats-stripe)
- [Mapping explicite de `P2002` Prisma sur update de champ unique](#pattern-prisma-p2002-update-unique) - [Mapping explicite de `P2002` Prisma sur update de champ unique](#pattern-prisma-p2002-update-unique)
- [Autorisation interne minimale sans RBAC complet](#pattern-autorisation-interne-minimale)
--- ---
@@ -88,6 +89,7 @@ Si ce nest pas confirmé comme fonctionnel et utile, **ça na rien à fair
--- ---
<a id="pattern-format-derreur-api-standardise"></a> <a id="pattern-format-derreur-api-standardise"></a>
## Pattern : Format derreur API standardisé ## Pattern : Format derreur API standardisé
- Objectif : fournir des erreurs prévisibles, exploitables et cohérentes pour tous les clients. - Objectif : fournir des erreurs prévisibles, exploitables et cohérentes pour tous les clients.
@@ -125,6 +127,7 @@ Si ce nest pas confirmé comme fonctionnel et utile, **ça na rien à fair
--- ---
<a id="pattern-middleware-correlation-requestid-traceid"></a> <a id="pattern-middleware-correlation-requestid-traceid"></a>
## Pattern : Middleware de corrélation (requestId / traceId) ## Pattern : Middleware de corrélation (requestId / traceId)
- Objectif : relier chaque requête aux logs et erreurs associées. - Objectif : relier chaque requête aux logs et erreurs associées.
@@ -156,6 +159,7 @@ Si ce nest pas confirmé comme fonctionnel et utile, **ça na rien à fair
--- ---
<a id="pattern-idempotency-key-operations-sensibles"></a> <a id="pattern-idempotency-key-operations-sensibles"></a>
## Pattern : Idempotency key pour opérations sensibles ## Pattern : Idempotency key pour opérations sensibles
- Objectif : empêcher les doublons lors de retries ou timeouts. - Objectif : empêcher les doublons lors de retries ou timeouts.
@@ -187,6 +191,7 @@ Si ce nest pas confirmé comme fonctionnel et utile, **ça na rien à fair
--- ---
<a id="pattern-pagination-robuste-cursor-based"></a> <a id="pattern-pagination-robuste-cursor-based"></a>
## Pattern : Pagination robuste (cursor-based) pour les listings ## Pattern : Pagination robuste (cursor-based) pour les listings
- Objectif : fournir des listings stables et performants sans incohérences entre pages. - Objectif : fournir des listings stables et performants sans incohérences entre pages.
@@ -222,6 +227,7 @@ Si ce nest pas confirmé comme fonctionnel et utile, **ça na rien à fair
--- ---
<a id="pattern-execution-asynchrone-taches-longues"></a> <a id="pattern-execution-asynchrone-taches-longues"></a>
## Pattern : Exécution asynchrone des tâches longues (queue + outbox light) ## Pattern : Exécution asynchrone des tâches longues (queue + outbox light)
- Objectif : sortir les opérations longues ou fragiles du chemin request/response. - Objectif : sortir les opérations longues ou fragiles du chemin request/response.
@@ -259,6 +265,7 @@ Si ce nest pas confirmé comme fonctionnel et utile, **ça na rien à fair
--- ---
<a id="pattern-soft-delete-archivage-explicite"></a> <a id="pattern-soft-delete-archivage-explicite"></a>
## Pattern : Soft delete et archivage explicite ## Pattern : Soft delete et archivage explicite
- Objectif : permettre la suppression logique sans perte immédiate de données. - Objectif : permettre la suppression logique sans perte immédiate de données.
@@ -295,6 +302,7 @@ Si ce nest pas confirmé comme fonctionnel et utile, **ça na rien à fair
--- ---
<a id="pattern-webhooks-sortants-robustes-idempotents"></a> <a id="pattern-webhooks-sortants-robustes-idempotents"></a>
## Pattern : Webhooks sortants robustes et idempotents ## Pattern : Webhooks sortants robustes et idempotents
- Objectif : garantir des intégrations fiables avec des systèmes externes. - Objectif : garantir des intégrations fiables avec des systèmes externes.
@@ -332,6 +340,7 @@ Si ce nest pas confirmé comme fonctionnel et utile, **ça na rien à fair
--- ---
<a id="pattern-contracts-first-zod-infer-no-dto"></a> <a id="pattern-contracts-first-zod-infer-no-dto"></a>
## Pattern : Contracts-First / Zod-Infer / No-DTO (monorepo TypeScript fullstack) ## Pattern : Contracts-First / Zod-Infer / No-DTO (monorepo TypeScript fullstack)
- Objectif : avoir une seule source de vérité pour les contrats dinterface entre API et client, sans redéfinition manuelle de types. - Objectif : avoir une seule source de vérité pour les contrats dinterface entre API et client, sans redéfinition manuelle de types.
@@ -407,6 +416,7 @@ packages/contracts/src/
--- ---
<a id="pattern-guard-global-nestjs"></a> <a id="pattern-guard-global-nestjs"></a>
## Pattern : Guard global NestJS — ordre denregistrement et décorateurs de bypass ## Pattern : Guard global NestJS — ordre denregistrement et décorateurs de bypass
- Objectif : protéger tous les endpoints par défaut, avec un mécanisme explicite pour les exceptions. - Objectif : protéger tous les endpoints par défaut, avec un mécanisme explicite pour les exceptions.
@@ -455,6 +465,7 @@ if (skip) return true;
--- ---
<a id="pattern-provider-strategy-integrations-tierces"></a> <a id="pattern-provider-strategy-integrations-tierces"></a>
## Pattern : Provider-Strategy pour intégrations tierces — périmètre complet ## Pattern : Provider-Strategy pour intégrations tierces — périmètre complet
- Objectif : isoler intégralement la logique propre à un prestataire (Stripe, Brevo, Firebase…) derrière une interface stable, pour éviter la contamination du domaine par le SDK tiers. - Objectif : isoler intégralement la logique propre à un prestataire (Stripe, Brevo, Firebase…) derrière une interface stable, pour éviter la contamination du domaine par le SDK tiers.
@@ -502,6 +513,7 @@ async handleWebhook(rawBody: Buffer, signature: string): Promise<void> {
--- ---
<a id=”pattern-stripe-subscription-metadata”></a> <a id=”pattern-stripe-subscription-metadata”></a>
## Pattern : Stripe — metadata sur `subscription_data`, pas sur la Session ## Pattern : Stripe — metadata sur `subscription_data`, pas sur la Session
- Objectif : garantir que `userId` (ou tout identifiant métier) soit accessible dans les events `customer.subscription.*`, pas seulement dans `checkout.session.completed`. - Objectif : garantir que `userId` (ou tout identifiant métier) soit accessible dans les events `customer.subscription.*`, pas seulement dans `checkout.session.completed`.
@@ -523,6 +535,7 @@ stripe.checkout.sessions.create({
--- ---
<a id=”pattern-webhook-parsing-unique”></a> <a id=”pattern-webhook-parsing-unique”></a>
## Pattern : Webhooks entrants — parsing unique (single `constructWebhookEvent`) ## Pattern : Webhooks entrants — parsing unique (single `constructWebhookEvent`)
- Objectif : appeler `constructWebhookEvent` une seule fois par requête, puis router vers des extracteurs purs. - Objectif : appeler `constructWebhookEvent` une seule fois par requête, puis router vers des extracteurs purs.
@@ -545,6 +558,7 @@ handlePackWebhookEvent(event): PackWebhookResult | null
--- ---
<a id=”pattern-contracts-error-codes”></a> <a id=”pattern-contracts-error-codes”></a>
## Pattern : Contracts-First — error codes comme contrat obligatoire ## Pattern : Contracts-First — error codes comme contrat obligatoire
- Objectif : maintenir les codes derreur API dans `packages/contracts` pour éviter les clients stringly-typed. - Objectif : maintenir les codes derreur API dans `packages/contracts` pour éviter les clients stringly-typed.
@@ -563,6 +577,7 @@ handlePackWebhookEvent(event): PackWebhookResult | null
--- ---
<a id="pattern-redis-health-cache-court"></a> <a id="pattern-redis-health-cache-court"></a>
## Pattern : RedisHealthService avec cache interne court ## Pattern : RedisHealthService avec cache interne court
- Objectif : exposer un état Redis exploitable par les guards globaux sans ping Redis à chaque requête. - Objectif : exposer un état Redis exploitable par les guards globaux sans ping Redis à chaque requête.
@@ -596,6 +611,7 @@ handlePackWebhookEvent(event): PackWebhookResult | null
--- ---
<a id="pattern-subscription-trial-vs-paid"></a> <a id="pattern-subscription-trial-vs-paid"></a>
## Pattern : Sémantique explicite `Trial` vs `Paid` dans Subscription ## Pattern : Sémantique explicite `Trial` vs `Paid` dans Subscription
- Objectif : aligner le modèle métier, les guards et les jeux de tests sur une définition unique de labonnement payant actif. - Objectif : aligner le modèle métier, les guards et les jeux de tests sur une définition unique de labonnement payant actif.
@@ -627,6 +643,7 @@ handlePackWebhookEvent(event): PackWebhookResult | null
--- ---
<a id="pattern-restauration-achats-stripe"></a> <a id="pattern-restauration-achats-stripe"></a>
## Pattern : restauration dachats Stripe en 3 étapes ## Pattern : restauration dachats Stripe en 3 étapes
- Objectif : reconstruire un état local cohérent à partir de Stripe sans dépendre dune hypothèse fragile. - Objectif : reconstruire un état local cohérent à partir de Stripe sans dépendre dune hypothèse fragile.
@@ -658,6 +675,7 @@ handlePackWebhookEvent(event): PackWebhookResult | null
--- ---
<a id="pattern-prisma-p2002-update-unique"></a> <a id="pattern-prisma-p2002-update-unique"></a>
## Pattern : mapping explicite de `P2002` Prisma sur update de champ unique ## Pattern : mapping explicite de `P2002` Prisma sur update de champ unique
- Objectif : transformer un conflit dunicité prévisible en erreur métier exploitable plutôt quen 500 opaque. - Objectif : transformer un conflit dunicité prévisible en erreur métier exploitable plutôt quen 500 opaque.
@@ -688,6 +706,43 @@ handlePackWebhookEvent(event): PackWebhookResult | null
--- ---
<a id="pattern-autorisation-interne-minimale"></a>
## Pattern : Autorisation interne minimale sans RBAC complet
- Objectif : sécuriser une capacité interne sensible sans ouvrir trop tôt un chantier RBAC complet.
- Contexte : application avec peu de rôles, besoin ponctuel dune capacité admin ou opérateur clairement identifiée.
- Quand lutiliser : quand une story métier demande un pouvoir interne limité mais réel.
- Quand léviter : si les permissions deviennent nombreuses, hiérarchiques ou contextuelles.
- Avantage :
- sécurisation rapide et lisible dune capacité sensible
- source de vérité backend explicite
- chemin dévolution propre vers un RBAC plus complet
- Limites / vigilance :
- ne pas laisser proliférer des rôles ad hoc non gouvernés
- ne remplace pas un vrai modèle de permissions si le domaine grossit
- Validé le : 10-03-2026
- Contexte technique : NestJS / auth par session ou JWT / API métier interne
### Implémentation (exemple minimal)
```txt
- introduire un enum de rôle minimal côté backend (ex. USER | ADMIN)
- propager ce rôle dans la session ou le token dauth
- créer un décorateur + guard dédiés pour la capacité sensible
- interdire les booléens front, emails hardcodés ou `if` dispersés dans les contrôleurs
```
### Checklist
- Le rôle vit dans la source de vérité backend
- Le rôle est propagé dans le mécanisme dauth existant
- Les endpoints sensibles passent par un guard dédié
- Aucun contrôle daccès critique nest piloté par le front
- Le passage à RBAC reste possible sans casser le contrat existant
---
### Notes importantes ### Notes importantes
- On préfère 5 patterns solides à 50 “bons conseils”. - On préfère 5 patterns solides à 50 “bons conseils”.

View File

@@ -12,7 +12,7 @@ Il sert de **mémoire durable** pour éviter :
- de redélibérer éternellement sur des sujets déjà tranchés, - de redélibérer éternellement sur des sujets déjà tranchés,
- de propager des “bonnes pratiques” théoriques non éprouvées. - de propager des “bonnes pratiques” théoriques non éprouvées.
Dernière mise à jour : 09-03-2026 Dernière mise à jour : 12-03-2026
--- ---
@@ -22,6 +22,7 @@ Dernière mise à jour : 09-03-2026
- [Séparation claire server state / client state](#pattern-separation-server-state-client-state) - [Séparation claire server state / client state](#pattern-separation-server-state-client-state)
- [Formulaire robuste avec validation et erreurs explicites](#pattern-formulaire-robuste) - [Formulaire robuste avec validation et erreurs explicites](#pattern-formulaire-robuste)
- [Navigation réactive post-action async (React / Expo Router)](#pattern-navigation-reactive-post-action-async) - [Navigation réactive post-action async (React / Expo Router)](#pattern-navigation-reactive-post-action-async)
- [Refresh idempotent sur store de liste paginée](#pattern-refresh-idempotent-liste-paginee)
--- ---
@@ -290,6 +291,51 @@ const handleOAuth = async () => {
--- ---
<a id="pattern-refresh-idempotent-liste-paginee"></a>
## Pattern : Refresh idempotent sur store de liste paginée
### Synthèse
- **Objectif** : garantir quun pull-to-refresh recharge une liste paginée sans doublons, sans courses réseau et sans état intermédiaire incohérent.
- **Contexte** : app mobile ou SPA avec store de domaine (ex. Zustand) et pagination incrémentale.
- **Quand lutiliser** : dès quune même liste supporte à la fois `loadMore` et `refresh`.
- **Quand léviter** : listes purement statiques ou données entièrement remplacées sans pagination.
### Analyse
- **Avantages** :
- évite les doublons lors des refresh concurrents
- garde une transition atomique entre ancien et nouvel état
- rend le comportement async testable côté store
- **Limites / vigilance** :
- impose une discipline claire entre `refresh` et `loadMore`
- demande une clé didentité stable pour dédupliquer les items
### Validation
- Validé le : 10-03-2026
- Contexte technique : React Native / Expo / Zustand / listes paginées
### Implémentation (exemple minimal)
```txt
- conserver une promesse de refresh partagée tant quun refresh est en vol
- refuser ou réutiliser tout refresh concurrent au lieu den lancer un second
- remplacer atomiquement la liste à la fin du refresh
- dédupliquer les items par identifiant au merge des pages suivantes
- empêcher `loadMore` de fusionner sur un snapshot devenu obsolète
```
### Checklist
- [ ] Une seule promesse de refresh en vol à la fois
- [ ] `refresh` et `loadMore` ont des garde-fous explicites
- [ ] La liste est remplacée atomiquement après refresh
- [ ] Les pages suivantes sont dédupliquées par identifiant stable
- [ ] Tests sur refresh concurrent + refresh suivi de pagination
---
### Principes transverses ### Principes transverses
- Un pattern = une responsabilité claire - Un pattern = une responsabilité claire

View File

@@ -7,7 +7,7 @@ Ce fichier recense des risques front-end susceptibles de provoquer :
- dette technique rapide, - dette technique rapide,
- régressions UX/perf/a11y. - régressions UX/perf/a11y.
Dernière mise à jour : 10-03-2026 Dernière mise à jour : 12-03-2026
--- ---
@@ -32,6 +32,7 @@ Dernière mise à jour : 10-03-2026
- [Catch silencieux — erreur inconnue sans feedback utilisateur](#risque-catch-silencieux) - [Catch silencieux — erreur inconnue sans feedback utilisateur](#risque-catch-silencieux)
- [Auto-reset dun état dégradé sur toute réponse 2xx](#risque-auto-reset-etat-degrade) - [Auto-reset dun état dégradé sur toute réponse 2xx](#risque-auto-reset-etat-degrade)
- [Refresh store en fire-and-forget après mutation](#risque-refresh-store-fire-and-forget) - [Refresh store en fire-and-forget après mutation](#risque-refresh-store-fire-and-forget)
- [Loading infini sur écran gated par droits distants](#risque-loading-infini-ecran-gated)
--- ---
@@ -255,3 +256,28 @@ Dernière mise à jour : 10-03-2026
- Gestion derreur dédiée sur la phase de resynchronisation - Gestion derreur dédiée sur la phase de resynchronisation
- Nutiliser le fire-and-forget que pour un effet secondaire réellement non bloquant - Nutiliser le fire-and-forget que pour un effet secondaire réellement non bloquant
- Contexte technique : React Native / Expo — 10-03-2026 - Contexte technique : React Native / Expo — 10-03-2026
---
<a id="risque-loading-infini-ecran-gated"></a>
## Loading infini sur écran gated par droits distants
### Risques
- Un écran protégé reste bloqué dans un faux `loading` après une erreur de chargement des entitlements
- Un effet relance automatiquement la récupération en boucle sans action utilisateur
- Lutilisateur ne voit ni état derreur ni issue de sortie claire
### Symptômes
- Spinner infini sur un écran soumis à permissions distantes
- `entitlements` ou autorisations laissés à `null` après erreur
- `useEffect` ou logique dentrée qui retrigger le fetch à chaque rendu
### Bonnes pratiques / mitigations
- Distinguer explicitement `loading`, `error`, `ready`
- Ne pas réutiliser `null` comme état ambigu "pas encore chargé" et "chargement en erreur"
- Bloquer les retries automatiques en boucle après erreur
- Réautoriser un retry seulement via action utilisateur explicite ou nouvelle condition dentrée
- Contexte technique : React Native / Expo / store dentitlements — 10-03-2026

View File

@@ -8,27 +8,380 @@ Objectifs :
- éviter de reposer les mêmes questions - éviter de reposer les mêmes questions
- assumer les compromis - assumer les compromis
Dernière mise à jour : 2026-03-10 Dernière mise à jour : 2026-03-12
--- ---
## Index ## Pré-index par section
1. [Global](#section-global)
2. [Infra](#section-infra)
3. [Backend](#section-backend)
4. [n8n](#section-n8n)
## Index détaillé
### 1. Global
<a id="section-global"></a>
- [Story sizing — foundations vs qualité non bloquante](#decision-story-sizing-foundations) - [Story sizing — foundations vs qualité non bloquante](#decision-story-sizing-foundations)
- [Code review adversariale — contexte frais](#decision-code-review-adversariale) - [Code review adversariale — contexte frais](#decision-code-review-adversariale)
- [Workflows n8n complexes = mini-systèmes](#decision-n8n-mini-systemes)
- [Le front-end est un logiciel en production](#decision-frontend-production) - [Le front-end est un logiciel en production](#decision-frontend-production)
- [Le back-end est un logiciel en production](#decision-backend-production)
- [Contrats dAPI explicites et versionnés](#decision-contrats-api)
- [Single source of truth des contrats — schémas runtime partagés (Zod) + z.infer (No-DTO)](#decision-contrats-sso-zod) - [Single source of truth des contrats — schémas runtime partagés (Zod) + z.infer (No-DTO)](#decision-contrats-sso-zod)
- [User views — User public par défaut + MeUser explicite](#decision-user-views) - [User views — User public par défaut + MeUser explicite](#decision-user-views)
### 2. Infra
<a id="section-infra"></a>
- [Structure Docker et données persistantes](#decision-structure-docker)
- [Accès réseau des VM de développement via Tailscale](#decision-reseau-tailscale-vm-dev)
### 3. Backend
<a id="section-backend"></a>
- [Le back-end est un logiciel en production](#decision-backend-production)
- [Contrats dAPI explicites et versionnés](#decision-contrats-api)
- [Gestion standard des erreurs et des statuts HTTP](#decision-erreurs-http) - [Gestion standard des erreurs et des statuts HTTP](#decision-erreurs-http)
- [Migrations et évolution de schéma maîtrisées](#decision-migrations) - [Migrations et évolution de schéma maîtrisées](#decision-migrations)
- [Observabilité minimale obligatoire](#decision-observabilite) - [Observabilité minimale obligatoire](#decision-observabilite)
- [Authentification et autorisation centrales](#decision-auth-central) - [Authentification et autorisation centrales](#decision-auth-central)
- [Idempotence et gestion des retries](#decision-idempotence-retries) - [Idempotence et gestion des retries](#decision-idempotence-retries)
- [Structure Docker et données persistantes](#decision-structure-docker)
- [Accès réseau des VM de développement via Tailscale](#decision-reseau-tailscale-vm-dev) ### 4. n8n
<a id="section-n8n"></a>
- [Workflows n8n complexes = mini-systèmes](#decision-n8n-mini-systemes)
---
## _Format standard dune décision_
## <Titre>
- Date : YYYY-MM-DD
- Statut : Proposed | Accepted | Deprecated
- Périmètre : n8n | backend | infra | global
### Contexte
### Options envisagées
### Décision
### Justification
### Conséquences
---
## 1. Global
<a id="decision-story-sizing-foundations"></a>
## Story sizing — foundations bloquantes vs qualité non bloquante (CI mobile)
- Date : 2026-03-09
- Statut : Accepted
- Périmètre : global
### Contexte
Des items “infra” ont été mis dans des stories foundations depic alors quaucune story métier suivante nen dépendait (ex : CI Maestro mobile iOS/Android).
Résultat observé : story foundations “never-done”, friction et coût de contexte, sans bénéfice sur le throughput métier.
### Options envisagées
- Mettre toutes les améliorations de qualité dans foundations “par principe”
- Séparer les prérequis réellement bloquants du reste (qualité non bloquante)
### Décision
On distingue explicitement :
- **Prérequis bloquants** : à inclure dans foundations (les stories suivantes en dépendent)
- **Qualité non bloquante** : story indépendante, en parallèle ou après, sans bloquer le métier
### Justification
- Un epic doit pouvoir avancer sur le métier dès que les dépendances techniques minimales sont là
- Les chantiers “qualité” (CI mobile, perf, audits…) ont souvent une inertie qui ne doit pas geler lepic
### Conséquences
- Pour chaque AC “infra” en foundations : poser la question “la story X+1 est-elle bloquée si ce nest pas fait ?”
- Si la réponse est non : sortir lAC en story dédiée (tag qualité / infra), et la planifier à part
---
<a id="decision-code-review-adversariale"></a>
## Code review adversariale — passe dédiée en contexte frais
- Date : 2026-03-09
- Statut : Accepted
- Périmètre : global
### Contexte
Certaines issues CRITICAL sont invisibles dans le contexte dimplémentation (biais de confirmation : “je sais comment cest censé marcher”).
Elles émergent uniquement en lecture froide : fondations manquantes, usages dépréciés, invariants non respectés (ex : sessions sans TTL).
### Options envisagées
- Review “au fil de leau” dans le même contexte que limplémentation
- Review dédiée, séparée, en contexte frais (nouvelle session / nouveau modèle / reviewer différent)
### Décision
La code review doit être une passe **séparée** de limplémentation, en **contexte frais**, avec un objectif explicite : chercher des CRITICAL sans concession.
### Justification
- Les incohérences systémiques (sécurité, idempotence, TTL, drift de contracts) se détectent mieux en lecture froide
- Réduit fortement le coût de debug tardif (prod/staging) et les refactors “de fondations”
### Conséquences
- Process recommandé :
1. Dev termine limplémentation, marque la story `review`
2. Nouvelle session (contexte frais) : charger uniquement story + diff
3. Review adversariale : lister CRITICAL + mitigations
4. Corriger avant `done`
---
<a id="decision-frontend-production"></a>
## Le front-end est un logiciel en production
- Date : 2026-01-25
- Statut : Accepted
- Périmètre : global
### Contexte
Les applications front-end modernes (SPA, webapps) portent une part significative
de la logique applicative : state, validations, permissions, erreurs, performance,
sécurité côté client, expérience utilisateur.
Les traiter comme une simple “couche UI” conduit régulièrement à :
- une dette technique rapide,
- des bugs difficiles à diagnostiquer,
- une expérience utilisateur incohérente,
- une maintenance coûteuse dans le temps.
### Options envisagées
- Traiter le front-end comme une couche de rendu légère, peu structurée
- Traiter le front-end comme un logiciel à part entière, avec des exigences similaires au backend
### Décision
Le front-end est traité comme un **logiciel en production**.
Il est soumis aux mêmes principes que le backend :
- architecture explicite,
- gestion des erreurs,
- conventions de code,
- tests au bon niveau,
- attention portée à la maintenabilité et à lévolution.
### Justification
- Le front concentre une logique métier et technique réelle
- Les bugs front ont un impact direct sur les utilisateurs
- La complexité augmente mécaniquement avec le produit
- Les choix initiaux conditionnent fortement la maintenabilité future
### Conséquences
- Mise en place de patterns front validés et documentés
- Gestion explicite des états UI, erreurs et formulaires
- Attention portée à la performance et à laccessibilité
- Acceptation dun léger surcoût initial pour réduire la dette long terme
---
<a id="decision-contrats-sso-zod"></a>
## Single source of truth des contrats — schémas runtime partagés (Zod) + z.infer (No-DTO)
- Date : 2026-03-10
- Statut : Proposed
- Périmètre : global
### Contexte
TypeScript ne valide pas les payloads HTTP au runtime (les types disparaissent à lexécution).
Quand on maintient à la fois des DTO/back (ex. Nest + decorators) et des types/contracts côté clients, on obtient une double source de vérité → drift, régressions, temps de debug.
### Options envisagées
- DTO NestJS + `class-validator` (validation runtime OK côté API, mais non partageable tel quel côté clients → duplication ou génération)
- Schémas runtime partagés dans un package `contracts` + types dérivés (validation + types alignés)
- OpenAPI/JSON Schema “first” + codegen (artefact contractuel unique, mais pipeline/outillage à maintenir)
### Décision
La source de vérité des contrats API↔clients est un package `contracts` contenant des **schémas runtime** (Zod), et les types TypeScript sont **dérivés** via `z.infer` (No-DTO / pas de redéfinition locale).
Principe opérationnel :
- validation concentrée aux frontières (ex. pipe/guard de validation côté API),
- le reste du code consomme des **types** (et non des classes DTO redondantes),
- les contrats restent “wire-level” (pas de métier, pas de stores, pas de classes Nest).
### Justification
- Un seul artefact sert à la fois de validation runtime et de typage compile-time
- Propagation des changements plus fiable (compile + tests) → réduction du temps de debug
### Conséquences
- Dépendance assumée à Zod dans `contracts`
- Les clients consomment les types; la validation côté client reste optionnelle (utile surtout sur entrées non-API : storage, deeplinks, etc.)
- En contexte non-mobile (Nest/React), OpenAPI-first + codegen reste une alternative valide si multi-clients/externe ou besoin fort de contrat public/documenté
---
<a id="decision-user-views"></a>
## User views — User public par défaut + MeUser explicite
- Date : 2026-03-10
- Statut : Proposed
- Périmètre : global
### Contexte
Un “User” complet est rarement un bon contrat universel : il peut contenir des champs sensibles (email, adresse, téléphone, flags internes…).
Les dérivations TypeScript seules (`Omit<User, "password">`) ne protègent pas au runtime et favorisent les fuites accidentelles.
### Options envisagées
- Un type `User` “god object” + dérivations TS (Omit/Pick) au cas par cas
- Des “views” explicites par contexte (public, self, admin…), dérivées depuis les schémas runtime
- Un modèle unique côté DB + sérialisation implicite (risque élevé de fuite)
### Décision
`User` (dans les contracts) est **public par défaut** et minimal (safe-by-default).
La vue self/compte est un contrat séparé `MeUser`.
Toute vue plus riche est créée explicitement et nommée (ex. `AdminUser`, `UserDirectoryEntry`).
Règles associées :
- les vues sont dérivées depuis les schémas runtime (extend/pick/omit) + `z.infer`
- `password` (et assimilés) nexiste que dans des requêtes dauth (login/register/reset/change), jamais dans un `User*`
### Justification
- Réduit le risque de fuite de données et clarifie les permissions/UX
- Évite les champs optionnels ambigus et les contrats “implicites”
### Conséquences
- Chaque endpoint choisit explicitement la vue renvoyée (et côté DB, un `select` explicite par vue)
- Les clients typent “public” vs “mon compte” distinctement
- Des tests “no secret keys” sur réponses user/auth deviennent simples et efficaces
---
## 2. Infra
<a id="decision-structure-docker"></a>
## Convention de structure pour les projets Docker et les données persistantes
- Date : 2026-03-06
- Statut : Accepted
- Périmètre : infra
### Contexte
Sur un serveur de développement (NUC / VM docker-dev), Docker mélange facilement
code applicatif, données persistantes, volumes et backups si aucune convention
nest définie.
Avec le temps cela rend :
- les sauvegardes ambiguës
- le nettoyage risqué
- la compréhension de linfrastructure difficile
- la reconstruction dun environnement compliquée
Une structure simple et explicite permet déviter ces problèmes.
### Options envisagées
- Laisser Docker gérer implicitement les volumes (`/var/lib/docker/volumes`)
- Laisser chaque projet organiser librement ses dossiers
- Définir une convention globale claire séparant code, données et sauvegardes
### Décision
La structure standard suivante est adoptée sur les machines dinfrastructure
(NUC / serveurs de développement) :
```
/srv
├ projects
├ docker-data
└ backups
```
Principes :
- `/srv/projects`
contient les projets applicatifs (code, `docker-compose.yml`, `.env`, scripts).
- `/srv/docker-data`
contient les données persistantes des conteneurs (bases de données, uploads,
état applicatif).
- `/srv/backups`
contient les dumps, archives et exports destinés à la sauvegarde.
### Justification
- séparation claire **code / données / sauvegardes**
- sauvegardes plus simples et plus fiables
- nettoyage dun projet possible sans risque pour les autres
- lisibilité immédiate de linfrastructure
- reproductibilité de lenvironnement
### Conséquences
Les conteneurs utilisent en priorité des **bind mounts explicites**, par exemple :
```txt
/srv/docker-data/monapp-postgres:/var/lib/postgresql/data
```
Les volumes Docker implicites (`/var/lib/docker/volumes`) sont évités quand la
lisibilité et la maintenabilité priment.
Les données critiques à sauvegarder se trouvent principalement dans :
```
/srv/projects
/srv/docker-data
```
Convention de nommage recommandée pour les dossiers de données :
```
ex :
rl799-postgres
monapp-redis
n8n-postgres
```
--- ---
@@ -129,190 +482,7 @@ Cette convention est recommandée pour toutes les nouvelles VM du NUC, notamment
--- ---
<a id="decision-story-sizing-foundations"></a> ## 3. Backend
## Story sizing — foundations bloquantes vs qualité non bloquante (CI mobile)
- Date : 2026-03-09
- Statut : Accepted
- Périmètre : global
### Contexte
Des items “infra” ont été mis dans des stories foundations depic alors quaucune story métier suivante nen dépendait (ex : CI Maestro mobile iOS/Android).
Résultat observé : story foundations “never-done”, friction et coût de contexte, sans bénéfice sur le throughput métier.
### Options envisagées
- Mettre toutes les améliorations de qualité dans foundations “par principe”
- Séparer les prérequis réellement bloquants du reste (qualité non bloquante)
### Décision
On distingue explicitement :
- **Prérequis bloquants** : à inclure dans foundations (les stories suivantes en dépendent)
- **Qualité non bloquante** : story indépendante, en parallèle ou après, sans bloquer le métier
### Justification
- Un epic doit pouvoir avancer sur le métier dès que les dépendances techniques minimales sont là
- Les chantiers “qualité” (CI mobile, perf, audits…) ont souvent une inertie qui ne doit pas geler lepic
### Conséquences
- Pour chaque AC “infra” en foundations : poser la question “la story X+1 est-elle bloquée si ce nest pas fait ?”
- Si la réponse est non : sortir lAC en story dédiée (tag qualité / infra), et la planifier à part
---
<a id="decision-code-review-adversariale"></a>
## Code review adversariale — passe dédiée en contexte frais
- Date : 2026-03-09
- Statut : Accepted
- Périmètre : global
### Contexte
Certaines issues CRITICAL sont invisibles dans le contexte dimplémentation (biais de confirmation : “je sais comment cest censé marcher”).
Elles émergent uniquement en lecture froide : fondations manquantes, usages dépréciés, invariants non respectés (ex : sessions sans TTL).
### Options envisagées
- Review “au fil de leau” dans le même contexte que limplémentation
- Review dédiée, séparée, en contexte frais (nouvelle session / nouveau modèle / reviewer différent)
### Décision
La code review doit être une passe **séparée** de limplémentation, en **contexte frais**, avec un objectif explicite : chercher des CRITICAL sans concession.
### Justification
- Les incohérences systémiques (sécurité, idempotence, TTL, drift de contracts) se détectent mieux en lecture froide
- Réduit fortement le coût de debug tardif (prod/staging) et les refactors “de fondations”
### Conséquences
- Process recommandé :
1. Dev termine limplémentation, marque la story `review`
2. Nouvelle session (contexte frais) : charger uniquement story + diff
3. Review adversariale : lister CRITICAL + mitigations
4. Corriger avant `done`
---
## Format standard dune décision
## <Titre>
- Date : YYYY-MM-DD
- Statut : Proposed | Accepted | Deprecated
- Périmètre : n8n | backend | infra | global
### Contexte
### Options envisagées
### Décision
### Justification
### Conséquences
---
<a id="decision-n8n-mini-systemes"></a>
## Workflows n8n complexes = mini-systèmes
- Date : 2025-12-19
- Statut : Accepted
- Périmètre : n8n
### Contexte
Certains workflows n8n dépassent le simple enchaînement de nodes et deviennent
de véritables systèmes applicatifs (orchestration, état, branches, retries, intégrations multiples).
### Options envisagées
- Traiter ces workflows comme de simples automatisations “low-code”
- Les considérer comme du code à part entière (discipline, patterns, doc, prudence sur upgrades)
### Décision
Les considérer comme du code.
### Justification
- Complexité réelle (logique, état, orchestration)
- Sensibilité aux versions n8n (upgrade-risk)
- Besoin de maintenabilité et de capitalisation
### Conséquences
- Prudence accrue lors des upgrades
- Documentation minimale mais ciblée
- Patterns explicitement identifiés et partagés
- On accepte dutiliser du Code (JS) quand nécessaire
---
<a id="decision-frontend-production"></a>
## Le front-end est un logiciel en production
- Date : 2026-01-25
- Statut : Accepted
- Périmètre : global
### Contexte
Les applications front-end modernes (SPA, webapps) portent une part significative
de la logique applicative : state, validations, permissions, erreurs, performance,
sécurité côté client, expérience utilisateur.
Les traiter comme une simple “couche UI” conduit régulièrement à :
- une dette technique rapide,
- des bugs difficiles à diagnostiquer,
- une expérience utilisateur incohérente,
- une maintenance coûteuse dans le temps.
### Options envisagées
- Traiter le front-end comme une couche de rendu légère, peu structurée
- Traiter le front-end comme un logiciel à part entière, avec des exigences similaires au backend
### Décision
Le front-end est traité comme un **logiciel en production**.
Il est soumis aux mêmes principes que le backend :
- architecture explicite,
- gestion des erreurs,
- conventions de code,
- tests au bon niveau,
- attention portée à la maintenabilité et à lévolution.
### Justification
- Le front concentre une logique métier et technique réelle
- Les bugs front ont un impact direct sur les utilisateurs
- La complexité augmente mécaniquement avec le produit
- Les choix initiaux conditionnent fortement la maintenabilité future
### Conséquences
- Mise en place de patterns front validés et documentés
- Gestion explicite des états UI, erreurs et formulaires
- Attention portée à la performance et à laccessibilité
- Acceptation dun léger surcoût initial pour réduire la dette long terme
---
<a id="decision-backend-production"></a> <a id="decision-backend-production"></a>
@@ -407,91 +577,6 @@ Minimum attendu :
--- ---
<a id="decision-contrats-sso-zod"></a>
## Single source of truth des contrats — schémas runtime partagés (Zod) + z.infer (No-DTO)
- Date : 2026-03-10
- Statut : Proposed
- Périmètre : global
### Contexte
TypeScript ne valide pas les payloads HTTP au runtime (les types disparaissent à lexécution).
Quand on maintient à la fois des DTO/back (ex. Nest + decorators) et des types/contracts côté clients, on obtient une double source de vérité → drift, régressions, temps de debug.
### Options envisagées
- DTO NestJS + `class-validator` (validation runtime OK côté API, mais non partageable tel quel côté clients → duplication ou génération)
- Schémas runtime partagés dans un package `contracts` + types dérivés (validation + types alignés)
- OpenAPI/JSON Schema “first” + codegen (artefact contractuel unique, mais pipeline/outillage à maintenir)
### Décision
La source de vérité des contrats API↔clients est un package `contracts` contenant des **schémas runtime** (Zod), et les types TypeScript sont **dérivés** via `z.infer` (No-DTO / pas de redéfinition locale).
Principe opérationnel :
- validation concentrée aux frontières (ex. pipe/guard de validation côté API),
- le reste du code consomme des **types** (et non des classes DTO redondantes),
- les contrats restent “wire-level” (pas de métier, pas de stores, pas de classes Nest).
### Justification
- Un seul artefact sert à la fois de validation runtime et de typage compile-time
- Propagation des changements plus fiable (compile + tests) → réduction du temps de debug
### Conséquences
- Dépendance assumée à Zod dans `contracts`
- Les clients consomment les types; la validation côté client reste optionnelle (utile surtout sur entrées non-API : storage, deeplinks, etc.)
- En contexte non-mobile (Nest/React), OpenAPI-first + codegen reste une alternative valide si multi-clients/externe ou besoin fort de contrat public/documenté
---
<a id="decision-user-views"></a>
## User views — User public par défaut + MeUser explicite
- Date : 2026-03-10
- Statut : Proposed
- Périmètre : global
### Contexte
Un “User” complet est rarement un bon contrat universel : il peut contenir des champs sensibles (email, adresse, téléphone, flags internes…).
Les dérivations TypeScript seules (`Omit<User, "password">`) ne protègent pas au runtime et favorisent les fuites accidentelles.
### Options envisagées
- Un type `User` “god object” + dérivations TS (Omit/Pick) au cas par cas
- Des “views” explicites par contexte (public, self, admin…), dérivées depuis les schémas runtime
- Un modèle unique côté DB + sérialisation implicite (risque élevé de fuite)
### Décision
`User` (dans les contracts) est **public par défaut** et minimal (safe-by-default).
La vue self/compte est un contrat séparé `MeUser`.
Toute vue plus riche est créée explicitement et nommée (ex. `AdminUser`, `UserDirectoryEntry`).
Règles associées :
- les vues sont dérivées depuis les schémas runtime (extend/pick/omit) + `z.infer`
- `password` (et assimilés) nexiste que dans des requêtes dauth (login/register/reset/change), jamais dans un `User*`
### Justification
- Réduit le risque de fuite de données et clarifie les permissions/UX
- Évite les champs optionnels ambigus et les contrats “implicites”
### Conséquences
- Chaque endpoint choisit explicitement la vue renvoyée (et côté DB, un `select` explicite par vue)
- Les clients typent “public” vs “mon compte” distinctement
- Des tests “no secret keys” sur réponses user/auth deviennent simples et efficaces
---
<a id="decision-erreurs-http"></a> <a id="decision-erreurs-http"></a>
## Gestion standard des erreurs et des statuts HTTP ## Gestion standard des erreurs et des statuts HTTP
@@ -703,92 +788,41 @@ Principes :
--- ---
<a id="decision-structure-docker"></a> ## 4. n8n
## Convention de structure pour les projets Docker et les données persistantes <a id="decision-n8n-mini-systemes"></a>
- Date : 2026-03-06 ## Workflows n8n complexes = mini-systèmes
- Date : 2025-12-19
- Statut : Accepted - Statut : Accepted
- Périmètre : infra - Périmètre : n8n
### Contexte ### Contexte
Sur un serveur de développement (NUC / VM docker-dev), Docker mélange facilement Certains workflows n8n dépassent le simple enchaînement de nodes et deviennent
code applicatif, données persistantes, volumes et backups si aucune convention de véritables systèmes applicatifs (orchestration, état, branches, retries, intégrations multiples).
nest définie.
Avec le temps cela rend :
- les sauvegardes ambiguës
- le nettoyage risqué
- la compréhension de linfrastructure difficile
- la reconstruction dun environnement compliquée
Une structure simple et explicite permet déviter ces problèmes.
### Options envisagées ### Options envisagées
- Laisser Docker gérer implicitement les volumes (`/var/lib/docker/volumes`) - Traiter ces workflows comme de simples automatisations “low-code”
- Laisser chaque projet organiser librement ses dossiers - Les considérer comme du code à part entière (discipline, patterns, doc, prudence sur upgrades)
- Définir une convention globale claire séparant code, données et sauvegardes
### Décision ### Décision
La structure standard suivante est adoptée sur les machines dinfrastructure Les considérer comme du code.
(NUC / serveurs de développement) :
```
/srv
├ projects
├ docker-data
└ backups
```
Principes :
- `/srv/projects`
contient les projets applicatifs (code, `docker-compose.yml`, `.env`, scripts).
- `/srv/docker-data`
contient les données persistantes des conteneurs (bases de données, uploads,
état applicatif).
- `/srv/backups`
contient les dumps, archives et exports destinés à la sauvegarde.
### Justification ### Justification
- séparation claire **code / données / sauvegardes** - Complexité réelle (logique, état, orchestration)
- sauvegardes plus simples et plus fiables - Sensibilité aux versions n8n (upgrade-risk)
- nettoyage dun projet possible sans risque pour les autres - Besoin de maintenabilité et de capitalisation
- lisibilité immédiate de linfrastructure
- reproductibilité de lenvironnement
### Conséquences ### Conséquences
Les conteneurs utilisent en priorité des **bind mounts explicites**, par exemple : - Prudence accrue lors des upgrades
- Documentation minimale mais ciblée
```txt - Patterns explicitement identifiés et partagés
/srv/docker-data/monapp-postgres:/var/lib/postgresql/data - On accepte dutiliser du Code (JS) quand nécessaire
```
Les volumes Docker implicites (`/var/lib/docker/volumes`) sont évités quand la
lisibilité et la maintenabilité priment.
Les données critiques à sauvegarder se trouvent principalement dans :
```
/srv/projects
/srv/docker-data
```
Convention de nommage recommandée pour les dossiers de données :
```
ex :
rl799-postgres
monapp-redis
n8n-postgres
```
--- ---

View File

@@ -93,3 +93,48 @@ Deux processus ont réécrit le même fichier sans coordination, le second
- éviter deux passes décriture concurrentes sur le même fichier - éviter deux passes décriture concurrentes sur le même fichier
- relire le diff immédiatement après toute passe automatique - relire le diff immédiatement après toute passe automatique
- privilégier une séquence stricte : édition, puis lint/format, puis vérification - privilégier une séquence stricte : édition, puis lint/format, puis vérification
---
## tsx + NestJS : injection par type cassée silencieusement
### Contexte
Projet `app-alexandrie`, Epic 3, le 10-03-2026.
Le backend NestJS tournait avec `tsx watch` dans un contexte ESM (`module: nodenext`), notamment pour rester compatible avec Prisma v7.
### Symptômes
- `TypeError: Cannot read properties of undefined (reading 'get')` dans le constructeur dun service
- `ConfigService` injecté par type mais `undefined` au runtime
- `@Injectable()` et `ConfigModule` correctement configurés, sans erreur de compilation
### Cause probable
`tsx` repose sur esbuild pour transpiler TypeScript.
Dans ce contexte, `emitDecoratorMetadata` est ignoré même sil est activé dans `tsconfig.json`.
NestJS ne peut donc plus résoudre correctement certaines injections par type, notamment `constructor(private readonly config: ConfigService)`.
### Correctif / règle à retenir
- ne pas supposer que `emitDecoratorMetadata` fonctionne avec `tsx`
- dans ce contexte, éviter linjection par type de `ConfigService` pour les services dinfra
- lire explicitement les variables via `process.env`, après chargement amont de `ConfigModule.forRoot()`
Exemple :
```typescript
// AVANT
constructor(private readonly config: ConfigService) {
const host = this.config.get('REDIS_HOST');
}
// APRES
constructor() {
const host = process.env['REDIS_HOST'] ?? 'localhost';
}
```
### Alternative écartée
`nest start --watch` a été testé mais a introduit des conflits ESM/CJS dans ce contexte (`exports is not defined`).

View File

@@ -55,17 +55,13 @@ FILE_UPDATE_PROPOSAL
Fichier cible : 10_backend_patterns_valides.md Fichier cible : 10_backend_patterns_valides.md
Pourquoi : Pourquoi :
Ordre d'enregistrement des Guards NestJS causant un bug Pattern réutilisable validé sur un projet réel.
`request.user undefined` observé à plusieurs reprises.
Proposition : Proposition :
## Ordre des Guards NestJS ## Nom du pattern
Toujours enregistrer `AuthGuard` avant les Guards dépendants Description courte, factuelle, orientée réutilisation.
(`EmailVerifiedGuard`, `RoleGuard`, etc.).
Sinon `request.user` peut être undefined dans les guards suivants.
``` ```
--- ---
@@ -104,129 +100,8 @@ Pour une feature de progression minimum viable :
FILE_UPDATE_PROPOSAL FILE_UPDATE_PROPOSAL
Fichier cible : 10_frontend_patterns_valides.md Fichier cible : 10_frontend_patterns_valides.md
Pourquoi :
Pattern frontend/mobile utile pour brancher un écran récapitulatif serveur sans dupliquer la logique réseau dans l'écran et sans navigation impérative via `store.getState()`.
Proposition :
## Écran récapitulatif branché sur store de domaine existant
Quand une vue récapitulative dépend d'un domaine déjà présent :
- étendre le `service` et le `store` existants au lieu de créer un nouveau domaine artificiel
- laisser l'écran purement déclaratif avec états `loading / error / empty / nominal`
- déclencher le chargement via `useEffect` et actions du store
- réserver `refresh` à une action idempotente du store
- ajouter le point d'entrée depuis l'écran parent naturel (`library`, `profile`, etc.) au lieu de refondre la navigation globale
Réduit les régressions et garde le comportement async testable.
---
2026-03-10 — app-alexandrie
FILE_UPDATE_PROPOSAL
Fichier cible : 10_frontend_patterns_valides.md
Pourquoi :
Le pull-to-refresh mobile sur listes paginées a besoin dune vraie idempotence côté store, sinon on crée des doublons et des courses réseau invisibles. Le pattern a été validé ici avec Zustand sans introduire de lib de cache serveur.
Proposition :
Pattern "Refresh idempotent sur store de liste paginée" : conserver une promesse de refresh en vol partagée, refuser les refresh concurrents, remplacer atomiquement la liste à la fin du refresh, et dédupliquer les items par identifiant au merge des pages suivantes.
---
2026-03-10 — app-alexandrie
FILE_UPDATE_PROPOSAL
Fichier cible : 10_frontend_risques_et_vigilance.md
Pourquoi :
Un écran gated par des droits distants peut tomber dans un faux état `loading` infini si lerreur de chargement des entitlements laisse les données à `null` et relance automatiquement leffet. Le défaut a été détecté en review sur la Bibliothèque mobile.
Proposition :
Risque "Loading infini sur écran gated" : si un écran dépend dun store dentitlements ou dautorisations, distinguer explicitement `loading`, `error`, `ready`, et bloquer les retries automatiques en boucle après erreur tant quun retry utilisateur ou une nouvelle condition dentrée na pas eu lieu.
# Rôle dans l'architecture
```
Projet
Proposition
95_a_capitaliser.md
Validation humaine
Lead_tech
```
Ce mécanisme permet :
- d'éviter la pollution de la base de connaissance
- de capitaliser progressivement l'expérience des projets
- de garder `Lead_tech` cohérent et fiable.
---
2026-03-10 — app-alexandrie
FILE_UPDATE_PROPOSAL
Fichier cible : 10_backend_patterns_valides.md
Pourquoi :
Quand une story métier demande un pouvoir interne limité, ajouter un rôle minimal persistant + un guard dédié permet de sécuriser lauthz sans ouvrir prématurément un RBAC complet. Le pattern a été validé ici pour la publication admin de contenus.
Proposition :
Pattern "Autorisation interne minimale sans RBAC complet" : introduire un enum de rôle simple côté source de vérité backend (ex. `USER | ADMIN`), le propager dans la session/auth, puis créer un décorateur + guard dédiés à la capacité sensible. Éviter les booléens front, les emails hardcodés et les `if` dispersés dans les contrôleurs.
---
2026-03-10 — app-alexandrie
FILE_UPDATE_PROPOSAL
Fichier cible : 10_frontend_patterns_valides.md
Pourquoi : Pourquoi :
Pour une capacité admin mince sur mobile, une route dédiée légère branchée sur le domaine existant et un refresh explicite du store après mutation permettent de rester testable et robuste sans lancer un back-office séparé. Pour une capacité admin mince sur mobile, une route dédiée légère branchée sur le domaine existant et un refresh explicite du store après mutation permettent de rester testable et robuste sans lancer un back-office séparé.
Proposition : Proposition :
Pattern "UI admin légère sur domaine existant" : pour une action interne simple (publication, activation, modération légère), ajouter une route dédiée minimale qui réutilise le service/store métier existant, afficher le statut courant, bloquer les actions concurrentes avec un flag `isUpdating*`, et déclencher un refresh explicite des vues impactées après succès au lieu dun `fire-and-forget`. Pattern "UI admin légère sur domaine existant" : pour une action interne simple (publication, activation, modération légère), ajouter une route dédiée minimale qui réutilise le service/store métier existant, afficher le statut courant, bloquer les actions concurrentes avec un flag `isUpdating*`, et déclencher un refresh explicite des vues impactées après succès au lieu dun `fire-and-forget`.
---
2026-03-10 — app-alexandrie
FILE_UPDATE_PROPOSAL
Fichier cible : 90_debug_et_postmortem.md
Pourquoi :
`tsx` (esbuild) ignore `emitDecoratorMetadata` — l'injection par type NestJS est silencieusement `undefined` au runtime, même avec `emitDecoratorMetadata: true` dans tsconfig.json. Bug difficile à diagnostiquer car aucune erreur de compilation.
Proposition :
## tsx + NestJS : injection par type cassée silencieusement
**Contexte :** app-alexandrie, Epic 3, 2026-03-10
**Symptôme :** `TypeError: Cannot read properties of undefined (reading 'get')` dans le constructeur d'un service NestJS. `this.config` (de type `ConfigService`) est `undefined` malgré `@Injectable()` et `ConfigModule` correctement configuré.
**Cause racine :** `tsx` utilise esbuild pour transpiler TypeScript. esbuild ignore `emitDecoratorMetadata` même si activé dans tsconfig. NestJS ne peut donc pas résoudre les types pour l'injection de dépendances par type (`constructor(private config: ConfigService)`).
**Conditions :** runner = `tsx watch`, `module: nodenext` dans tsconfig (requis pour Prisma v7 ESM), `emitDecoratorMetadata: true` présent mais ignoré.
**Fix :** Remplacer l'injection `ConfigService` par `process.env['VAR']` directement dans le constructeur. `ConfigModule.forRoot` avec `isGlobal: true` charge dotenv en amont → `process.env` est peuplé avant l'instanciation des services.
```typescript
// AVANT (cassé avec tsx)
constructor(private readonly config: ConfigService) {
const host = this.config.get('REDIS_HOST');
}
// APRÈS (compatible tsx)
constructor() {
const host = process.env['REDIS_HOST'] ?? 'localhost';
}
```
**Alternative non retenue :** `nest start --watch` → conflits ESM/CJS avec Prisma v7 + `module: nodenext` (exports is not defined).
**Règle :** Dans un projet NestJS avec `tsx` comme runner, ne jamais injecter `ConfigService` par type. Toujours utiliser `process.env` directement dans les services d'infra (Redis, DB pools, etc.).