J3 — La carte du monorepo
Vérifié contre le produit le .
L’objectif du jour : savoir, devant n’importe quelle demande, dans quel dossier ouvrir le premier fichier.
| Durée réaliste | 2 heures de lecture, et une page à rouvrir pendant des mois |
| Prérequis | J1 |
Un workspace Bun piloté par Turborepo : sept applications, sept paquets partagés. Le
README.md racine en décrit encore cinq de chaque — il date d’avant l’atelier de données et ce
site de documentation.
Les sept applications
Section intitulée « Les sept applications »apps/ |
Ce que c’est |
|---|---|
api |
l’API REST NestJS — l’autorité : authentification, permissions, règles métier, OpenAPI servie sur /docs |
web |
le SaaS, en TanStack Start et React — la grille de planification, le référentiel, tous les écrans connectés |
solver |
le microservice d’optimisation, en Kotlin sur JVM. Le seul module non-TypeScript, et le seul sans dépendance au reste |
landing |
le site vitrine, en Astro statique et bilingue — aucun JavaScript expédié au visiteur |
lead-function |
la fonction serverless qui reçoit les formulaires de la vitrine |
seeder |
l’atelier de données de J2 — jamais déployé, purement local |
docs |
ce site — la documentation d’usage, en Astro Starlight |
Les sept paquets
Section intitulée « Les sept paquets »packages/ |
Ce que c’est |
|---|---|
schemas |
les schémas Zod partagés — le contrat entre l’API, le web et le solveur. Le paquet le plus consommé du dépôt |
db |
le schéma Drizzle, les migrations et la source du RLS multi-tenant |
api-client |
le client TypeScript généré depuis l’OpenAPI de l’API. Rien ne s’y écrit à la main |
ui |
les composants partagés shadcn/ui, en Tailwind v4 |
config |
les configurations TypeScript communes. Le lint et le format, eux, sont à la racine (Biome) |
dataset-generator |
le moteur de génération de jeux de données de J2 |
constraints-fixtures |
les scénarios JSON que les deux moteurs de scoring doivent évaluer à l’identique |
Qui dépend de qui
Section intitulée « Qui dépend de qui »Relevé dans les workspace:* de chaque package.json — c’est le graphe réel, pas une intention :
web → api-client, schemas, uiapi → db, schemas, dataset-generatorseeder → dataset-generator, db, schemas, uidocs → schemas, ui, dblanding→ schemas, uilead-function → schemas, config
api-client → config db → configschemas → config ui → configdataset-generator → schemas, configconstraints-fixtures → schemas, config
solver → (rien)Trois choses se lisent dans ce graphe :
schemasest au centre. Presque tout en dépend, donc presque tout casse quand il change — et c’est voulu : un contrat qui se rompt doit se voir à la compilation.webne dépend pas deapi. Il passe parapi-client, qui est généré. C’est le sujet de J5.solverne dépend de rien. Son contrat avec le reste du monde est du JSON, doublé côté Kotlin — voir J6.
Où va mon changement ?
Section intitulée « Où va mon changement ? »La question du jour, pour les cinq modifications les plus courantes.
Un nouveau champ sur une ressource d’API
Section intitulée « Un nouveau champ sur une ressource d’API »Le chemin le plus long du dépôt, et celui qu’il faut connaître par cœur :
packages/schemas— le schéma Zod, source du contrat ;packages/db— la colonne danssrc/schema.ts, puis la migration générée ;apps/api— le module concerné : contrôleur, service, DTO ;- la chaîne de génération —
openapi:emit, puis le client ; apps/web— l’écran qui l’affiche.
Le détail de l’étape 4 est en J5 : c’est là que tout le monde se fait avoir.
Un nouvel écran
Section intitulée « Un nouvel écran »apps/web/src/routes/ — le routage est par fichier, et routeTree.gen.ts est généré, jamais
édité. Un écran connecté vit sous _authenticated. Ajoutez ses textes dans
apps/web/messages/{en,fr}.json — anglais en langue de base, conformément à la règle « le code
en anglais ». Et sa page de référence sur ce site, dans la même pull request.
Une nouvelle contrainte de planification
Section intitulée « Une nouvelle contrainte de planification »Le seul changement qui traverse deux langages :
packages/schemas— la définition et ses paramètres, au catalogue ;apps/api/src/scoring/— l’évaluateur TypeScript ;apps/solver/— la contrainte Timefold équivalente, en Kotlin ;packages/constraints-fixtures— le scénario qui prouve que les deux donnent le même score.
Puis bun run test:contract, que la CI ne joue pas à votre place.
Un composant partagé
Section intitulée « Un composant partagé »packages/ui, et l’ajout passe par l’outil plutôt que par un copier-coller :
bunx shadcn@latest add <composant> --cwd packages/uiIl s’importe ensuite depuis @beemyschool/ui/components/<composant>.
Une migration de base
Section intitulée « Une migration de base »packages/db : on modifie src/schema.ts, puis on génère.
bun run --filter @beemyschool/db db:generateLe fichier SQL produit se relit avant d’être committé — c’est le sujet de J4.
Le piège du jour
Section intitulée « Le piège du jour »Ce qui doit exister à la fin de la journée
Section intitulée « Ce qui doit exister à la fin de la journée »- Vous savez nommer les sept applications et les sept paquets, et dire ce que fait chacun
- Vous savez pourquoi
webne dépend pas deapi - Vous savez où va un nouveau champ d’API, un écran, une contrainte, un composant, une migration
- Vous savez que
routeTree.gen.tsetpackages/api-client/src/generated/ne s’éditent pas - Vous avez lancé un build ciblé avec
--filter
J4 — Le modèle et le RLS : la couche qui explique pourquoi une requête parfaitement écrite peut ne rien renvoyer. C’est le piège n°1 du projet, et il mérite sa journée.