Conventions de nommage¶
Source de vérité du nommage pour projects/api. But : sémantique déterministe — pour un couple (couche + intention + type) il existe un seul nom correct, et un seul terme par concept (zéro synonyme). Vaut pour les agents comme pour l'équipe.
S'applique au nouveau code (src/__new/). Conforme à la règle absolue : ne jamais renommer l'existant sans demande explicite.
DRY — ne pas redéclarer, lier. Déjà réglé ailleurs :
- Fichiers (*.repository.ts…), service objects (PascalCase sans Factory), namespace Model, IDs brandés, codes d'erreur, préfixe ky_* → api-conventions.mdc
- Branded types, Err('code' as const), zod (nouveau) vs idtlt (existant) → .cursor/rules/generic-monorepo-rules.mdc
- Sémantique des entités métier → domain-map.mdc
Lookup rapide (verbe par intention)¶
| Intention | Couche | Nom |
|---|---|---|
| Lire 1 entité, présence attendue | repository | getOne / getXById |
| Lire 1 entité, peut être absente | repository | findOneByX → T \| undefined |
| Lire 1 entité + verrou (FOR UPDATE) | repository | getAndLock… |
| Lire N entités / collection | repository | getMany / getAll |
| Compter | repository | countX |
| Existence (booléen) | repository | hasX / existsX |
| Insérer | repository | insertOne / insertMany |
| Modifier | repository | updateOne / updateMany |
| Supprimer (soft préféré) | repository | deleteOne |
| Orchestrer un workflow async / transaction | service | processX |
| Tirer un état externe → local | service | syncX |
| Créer une entité / un graphe | service | createX |
| Calcul pur | business | computeX |
| Sélection d'une règle / config | business | resolveX |
| Application d'un jeu de règles → décision | business | evaluateX |
| Prédicat (partout) | toutes | isX / hasX / canX |
1. Verbes par couche¶
Repository — lecture¶
get*par défaut : présence attendue (assert/throw si absent).getOne,getMany,getAll,getXById.find*uniquement nullable-by-design : la valeur peut légitimement ne pas exister → retourT | undefined(findOneByEmail). Le verbe porte l'information « peut être absent ».getAndLock*seul pour les lectures avecFOR UPDATE(pasfindOneAndLock).count*pour les agrégats scalaires ;has*/exists*pour les booléens.
Repository — écriture¶
insertOne/insertMany(plierinsertBatchdansinsertManysauf payload réellement distinct).updateOne/updateMany.deleteOne: hard delete. Soft-delete préféré (statut / timestampdeletedAt) — c'est déjà le pattern de facto.- Pas de
create/save/markau niveau repository : ce sont des verbes service.
Service (un gagnant par intention)¶
get (lecture), create (nouvelle entité/graphe), update (merge de champs sur un existant), set (flag / champ unique), compute (dérivation pure — déléguée à business/), generate (artefact / fichier), save (persiste un agrégat de haut niveau), sync (PULL d'un état externe → local), process (workflow async / transaction — gagnant face à handle/execute/run/perform), transfer (mouvement de fonds), confirm (commit d'une action utilisateur), send (message sortant), schedule (job différé), trigger (fire-and-forget). Prédicats : can (éligibilité), is (état), has (existence).
Business (fonctions pures)¶
compute (arithmétique pure), resolve (lookup / sélection d'une config ou règle), evaluate (application d'un jeu de règles → objet décision) — distincts, ne pas fusionner. Prédicats : canX (→ Result / booléen), assert (throw sur précondition). Transition d'état du domaine = verbe métier court sur l'entité (PendingX.succeed(), .retry()).
Controller¶
Handler aligné sur le verbe HTTP : get() / list() pour @Get, create() pour @Post, update() pour @Patch, delete() pour @Delete. Transition d'état via @Put :id/<action> (ex. /complete). Upload en deux temps : request-upload → confirm-upload. Éviter POST /create-* : un @Post sur la ressource implique déjà la création.
2. Suffixes de type¶
- Entrée (create/update) →
Payload:XCreatePayload,XUpdatePayload.Inputréservé aux données de template / formulaire / PDF.Dtobanni (legacy).Bodyautorisé uniquement en stack Zod (*BodySchema) — marqueur de stack assumé. - Sortie API →
Response, namespacée dans le modèle :
export namespace InvestorPortfolio {
export type Response = { invested: Cents; brickCount: number }
}
View réservé aux fonctions de projection qui construisent une Response. Ne jamais renvoyer Model.T brut depuis un controller dès qu'on ajoute des champs calculés/agrégés.
- Alias d'entité → type T = typeof validator.T dans le namespace Model (idiomatique). Pas de suffixe Entity / Row / Model sur l'alias. Alias PascalCase nommés seulement pour les variantes d'une discriminated union.
3. Booléens¶
Préfixe obligatoire is / has / can / should sur tout champ booléen et tout prédicat. Pas d'adjectif nu : isEnabled (pas enabled), isBlocked, isAvailable, isHighlighted, isFlagged.
4. Variables¶
- Collections → nom pluriel exclusivement (
documents,properties,brickIds). Bannir les suffixes-List/-Array/-Collection. - Map / lookup → fonction
getXById/findXById; une map pré-construite en mémoire =xById(propertyNamesById) ; suffixe*Mapseulement si la clé n'est pas un id (countryCodesMap). - Compteurs / quantités →
*Count(compteur d'items :brickCount),*Total(somme agrégée :totalFundedAmount),*Amount(montant monétaire :repaymentAmount). Bannirnb*en TS (alias SQL uniquement → mapper enxCountau repository). - Génériques bannis → voir api-conventions.mdc (
data/info/value/details) ; idemresult/tmp/objhors d'un scope local trivial.
5. Suffixes de champ¶
Timestamps → *At (createdAt, deletedAt, attemptedAt). Dates sans heure → *Date.
Montants : tout est en centimes en DB → s'appuyer sur le brand Cents (ex. invested: Cents, brickPrice: Cents), pas de suffixe *Cents qui ré-encode l'unité déjà portée par le type. Sur un champ de contrat JSON le brand ne survit pas à la sérialisation → ajouter .describe('… en centimes').
6. Lexique des statuts¶
Un seul terme canonique par concept. Champ discriminant = status (jamais state ni kind).
| Concept | Canonique | Bannir (synonymes constatés) |
|---|---|---|
| Réussi / confirmé | confirmed |
succeeded*, successful, completed, ok, validated |
| Payout réglé | paid |
|
| En attente, pas commencé | pending |
processing, queued |
| Attente bloquante (événement externe) | waiting |
waiting_for_* dans la valeur (cf. ci-dessous) |
| Traitement actif | in_progress |
processing, ongoing |
| Échec système | failed |
error (réservé aux codes), ko |
| Rejet métier | declined |
rejected, refused |
| Remboursé / contre-passation | refunded |
reversed |
| Bloqué (fraude / conformité) | blocked |
|
| Annulé | canceled |
cancelled, aborted, voided |
| Expiré | expired |
|
| Planifié | scheduled |
notScheduled |
| Affecté / alloué | assigned |
|
| Stocké | stored |
|
| Supprimé (soft) | deleted |
|
| Acheté | purchased |
|
| Réservé | reserved |
|
| Approuvé | approved |
* succeeded : exception runtime tolérée pour lemonway-p2p (enum d'un provider déjà déployé). Aligner le fichier (successful-lemonway-p2p.ts) et le type (SucceededLemonwayP2P) sur la valeur succeeded, et documenter la traduction succeeded ↔ confirmed au bord wallet/p2p. Règle : traduire au bord, pas migrer un enum de paiement live.
Interdits transverses
- Négations : notScheduled / noFees / notExecuted → état positif + flag séparé (hasFees).
- Surcharge : un même littéral pour succès et échec (executed) → séparer en deux valeurs.
- Casing mixte dans une même entité.
Casing
- Valeurs de statut → snake_case (waiting_for_assignation).
- Codes d'erreur → kebab-case + dot-namespacing (déjà réglé, ne pas redéclarer).
- Sous-états fins (waiting_for_card_confirmation…) : OK pour une machine à états interne, mais pour une surface API pousser la spécificité dans un champ « raison » séparé plutôt que dans la valeur de status.
7. Vocabulaire de domaine¶
Un seul terme par concept — tout synonyme est banni. Deux règles transverses :
- Legacy figé : les bannis marqués (legacy : …) restent en place (tables, FK, modules — règle absolue → api-conventions.mdc) mais ne nomment jamais du code neuf.
- Métier FR jamais traduit : un concept réglementaire/contractuel français garde son nom FR, même absent de cette table ; les valeurs de
statusrestent EN —mensualiteStatus: 'paid', jamais'payee'. - Enforcement : le sous-ensemble machine-vérifiable est linté sur les lignes ajoutées uniquement (declarations, pas les références aux noms figés) par
scripts/lint-conventions/lint-domain-vocabulary.ts(pnpm lint+ pre-commit). Source de vérité de ce sous-ensemble :scripts/lint-conventions/domain-vocabulary.json— nouvelle paire à enforcer = l'ajouter au JSON, pas ici.
| Concept | Canonique | Bannir |
|---|---|---|
| Unité de propriété | brick |
share, unit |
| Investisseur | investor |
user ; customer (legacy : table customers — aucune table investors —, modules customer-*) |
| Projet immobilier (véhicule de financement / investissement) | project |
property (legacy : tables properties / property_*, FK propertyId, modules property-*) |
| Porteur de projet | ProjectOwner |
operator, manager |
| Espace financement porteur | project-financing-request |
espace-fi (legacy abandonné) |
| Société emprunteuse (espace financement) | ProjectOwnerCompany (project_owner_company) |
SPV / EspaceFiSpv (legacy EF — distinct du SPV investisseur special_purpose_vehicule) |
| Transfert investisseur ↔ investisseur | p2p |
|
| Conteneur de registre | transaction |
|
| Capital de développement | funding |
collecte, collect |
| Entrée initiale investisseur | money-in |
deposit (nouveau code) |
| Opération de retrait | withdraw (forme verbale) |
withdrawal (legacy : table investor_withdraw_requests, WT kind withdrawal_legacy) |
| Versement sortant | payout |
versement, reversement |
| Frais | fee / fees |
frais (hors littéraux fiscaux/facturation) |
| Mandat SEPA | Mandate (SddMandate) |
mandat |
| Rendement / retours | investment (réservé à ce contexte) |
|
| Calendrier de remboursement | echeancier (echeancierConfig, EcheancierViewService) |
schedule, ScheduleConfig (legacy : module property-payment-schedule) |
| Ligne d'échéance | echeance (Echeance, EcheanceId, EcheanceStatus) |
installment |
| Mensualité | mensualite (mensualiteStatus) |
monthlyPayment |
| Tirage sur budget travaux | tirage (terme FR, comme echeancier) |
drawdown, draw |
| Budget travaux (enveloppe travaux) | constructionBudget (totalConstructionBudget) |
« séquestre travaux » — jamais « séquestre » pour les travaux |
| Séquestre d'intérêts (échéancier) | sequestre (interestSequestre) |
escrow (legacy : colonne DB totalFundEscrow) ; « séquestre » nu (toujours « séquestre d'intérêts ») |
| Obligation (titre) | obligation |
bond |
| Fiducie | fiducie (fiducieFees) |
trust |
| Caution | caution (cautionPersonnelleEtSolidaire) |
guarantee, surety |
| Hypothèque | hypotheque (hypothequeRang) |
mortgage |
| Fiscalité FR | prelevements (prelevementsSociaux), suffixes Ht / Ttc |
8. Noms de modules¶
prefix-entity-variant en kebab-case, singulier par défaut. Familles de préfixes : investor-, property-, project-, primary-, lemonway- (déjà réglé). espace-fi- est abandonné. Pluriel uniquement pour les modules registre/collection (wallet-transactions, referrals, property-charts).
Hors périmètre (à trancher plus tard)¶
Non couvert par ce doc — à traiter dans une passe dédiée :
- Forme des réponses & pagination : enveloppe (records / data / total), page / limit / offset / cursor. (data est banni comme identifiant mais présent dans des littéraux de réponse.)
- Mécanisme d'enum : discriminatedUnion (idtlt) vs createEnum (space-lift) vs union TS brute — quel choix quand.
- Tests (*.test.ts) : application ou non des génériques bannis (result / value).
- Enforcement lint : quelles règles automatiser (préfixes booléens, suffixes bannis, casing des statuts).