From 95bd8fab874b720ef696d53605127b80a63592d2 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Sun, 16 Aug 2026 18:21:09 +0200 Subject: [PATCH] refactor: move ErrorHandlerService/HttpError out of express-tools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ErrorHandlerService has zero dependency on Express — it's a plain "map an error to {status, body}" service that works identically behind any HTTP framework. It had no business living in a package named express-tools. Extracted HttpError, ErrorHandlerService, and ErrorHandlingResult into a new packages/error-tools package (same tsc-build-to-dist pattern as shared/express-tools). express-tools now only keeps the actual Express-specific layer: ExpressServer, wrapAsyncHandler, and createErrorMiddleware (which adapts ErrorHandlerService, imported from error-tools, onto Express). - packages/error-tools: new package, depends on shared + zod - packages/express-tools: drops zod dependency, adds error-tools dependency for error-middleware.ts's type import - apps/api: adds error-tools dependency; app.ts, auth.service.ts, require-auth.ts now import HttpError/errorHandlerService from error-tools instead of express-tools - apps/api/Dockerfile: adds COPY for packages/error-tools in the runtime stage - specs/error-handling.md, specs/backend-architecture.md, README.md updated to reflect the new package split Verified: pnpm lint, pnpm build (all packages, correct dependency order), pnpm test (9/9 Mocha), pnpm test:bdd (5/5 Cucumber), full Docker rebuild + compose up (no crash-loop), curl + browser checks of /health, unknown-route 404, signup (201), duplicate-email 409 (code 4001 EMAIL_ALREADY_IN_USE) — all going through the moved ErrorHandlerService/HttpError correctly. Co-Authored-By: Claude Sonnet 5 --- README.md | 28 +++++++---- apps/api/Dockerfile | 1 + apps/api/package.json | 1 + apps/api/src/app.ts | 9 ++-- apps/api/src/middlewares/require-auth.ts | 2 +- apps/api/src/modules/auth/auth.service.ts | 2 +- packages/error-tools/package.json | 27 ++++++++++ .../src/error-handler.service.ts | 10 ++-- .../src/http-error.ts | 2 +- packages/error-tools/src/index.ts | 11 ++++ packages/error-tools/tsconfig.json | 12 +++++ packages/express-tools/package.json | 4 +- .../express-tools/src/error-middleware.ts | 2 +- packages/express-tools/src/index.ts | 12 ++--- pnpm-lock.yaml | 25 ++++++++-- specs/backend-architecture.md | 16 +++--- specs/error-handling.md | 50 +++++++++++++------ 17 files changed, 153 insertions(+), 61 deletions(-) create mode 100644 packages/error-tools/package.json rename packages/{express-tools => error-tools}/src/error-handler.service.ts (86%) rename packages/{express-tools => error-tools}/src/http-error.ts (94%) create mode 100644 packages/error-tools/src/index.ts create mode 100644 packages/error-tools/tsconfig.json diff --git a/README.md b/README.md index 077f274..d5bfbc7 100644 --- a/README.md +++ b/README.md @@ -11,15 +11,20 @@ Monorepo pnpm workspaces : `loginSchema`), types (`SafeUserProfile`), et le contrat d'erreurs (`ErrorCode` numérique, `ApiErrorResponse`, voir [specs/error-handling.md](specs/error-handling.md)) — même règles des deux côtés, pas de risque de dérive entre front et back. +- `packages/error-tools` — gestion des erreurs, **indépendante de tout framework + HTTP** (n'importe pas `express`) : `HttpError`, `ErrorHandlerService`. Séparé + d'`express-tools` précisément parce que rien ici ne dépend d'Express. Détail : + [specs/error-handling.md](specs/error-handling.md). - `packages/express-tools` — outillage Express générique et réutilisable : `ExpressServer` - (init serveur, routes, middlewares), `wrapAsyncHandler`, `HttpError`, - `ErrorHandlerService`, `createErrorMiddleware` — séparé d'`apps/api`, pas de - logique métier. Détail : [specs/backend-architecture.md](specs/backend-architecture.md). + (init serveur, routes, middlewares), `wrapAsyncHandler`, `createErrorMiddleware` + (adapte `ErrorHandlerService` de `error-tools` à Express) — séparé d'`apps/api`, + pas de logique métier. Détail : [specs/backend-architecture.md](specs/backend-architecture.md). -`packages/shared` et `packages/express-tools` ont un vrai build (`tsc` → `dist/`, -voir leur `package.json`) : consommés en JS compilé, pas en TS brut — nécessaire -pour un runtime Node pur (Docker, pas de transpilation à la volée), voir la note -dans [specs/frontend-architecture.md](specs/frontend-architecture.md#note-sur-les-fichiers-dts). +`packages/shared`, `packages/error-tools` et `packages/express-tools` ont un vrai +build (`tsc` → `dist/`, voir leur `package.json`) : consommés en JS compilé, pas en +TS brut — nécessaire pour un runtime Node pur (Docker, pas de transpilation à la +volée), voir la note dans +[specs/frontend-architecture.md](specs/frontend-architecture.md#note-sur-les-fichiers-dts). ## Prérequis @@ -195,10 +200,11 @@ found, `500x` interne — et `ApiErrorResponse`) : l'API renvoie toujours `{ code, message, details? }` (message en anglais, dev-facing — jamais affiché tel quel), et le client traduit `code` en libellé français via **i18next** (`ErrorMessageService`, `apps/web/src/services/error-message.service.ts` → -`apps/web/src/locales/fr/translation.json`). Côté API, `ErrorHandlerService` et -`createErrorMiddleware` (`packages/express-tools`) centralisent la transformation -de toute erreur levée en réponse HTTP conforme — aucune valeur `ErrorCode` codée -en dur nulle part (toujours `ErrorCode.XXX`, y compris dans les mocks Cypress). +`apps/web/src/locales/fr/translation.json`). Côté API, `ErrorHandlerService` +(`packages/error-tools`) et `createErrorMiddleware` (`packages/express-tools`) +centralisent la transformation de toute erreur levée en réponse HTTP conforme — +aucune valeur `ErrorCode` codée en dur nulle part (toujours `ErrorCode.XXX`, y +compris dans les mocks Cypress). Détail complet (schéma, exemples, comment ajouter un nouveau code d'erreur) : [specs/error-handling.md](specs/error-handling.md). diff --git a/apps/api/Dockerfile b/apps/api/Dockerfile index 255ca88..1d5778d 100644 --- a/apps/api/Dockerfile +++ b/apps/api/Dockerfile @@ -24,6 +24,7 @@ COPY --from=build /repo/node_modules ./node_modules COPY --from=build /repo/package.json ./package.json COPY --from=build /repo/pnpm-workspace.yaml ./pnpm-workspace.yaml COPY --from=build /repo/packages/shared ./packages/shared +COPY --from=build /repo/packages/error-tools ./packages/error-tools COPY --from=build /repo/packages/express-tools ./packages/express-tools COPY --from=build /repo/apps/api/node_modules ./apps/api/node_modules COPY --from=build /repo/apps/api/dist ./apps/api/dist diff --git a/apps/api/package.json b/apps/api/package.json index f73a193..c7860cc 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -14,6 +14,7 @@ "postinstall": "prisma generate" }, "dependencies": { + "@batch-cooking/error-tools": "workspace:*", "@batch-cooking/express-tools": "workspace:*", "@batch-cooking/shared": "workspace:*", "@prisma/client": "^5.22.0", diff --git a/apps/api/src/app.ts b/apps/api/src/app.ts index a950827..8e5c08a 100644 --- a/apps/api/src/app.ts +++ b/apps/api/src/app.ts @@ -1,8 +1,5 @@ -import { - ExpressServer, - createErrorMiddleware, - errorHandlerService, -} from "@batch-cooking/express-tools"; +import { errorHandlerService } from "@batch-cooking/error-tools"; +import { ExpressServer, createErrorMiddleware } from "@batch-cooking/express-tools"; import { ErrorCode } from "@batch-cooking/shared"; import type { Express, Request, Response } from "express"; import { env } from "./config/env.js"; @@ -34,7 +31,7 @@ export function createServer(): ExpressServer { // Final error-handling middleware: every thrown/`next(err)`-ed error in // the app ends up here. All the "what status/body does this error map - // to" logic lives in ErrorHandlerService, from @batch-cooking/express-tools + // to" logic lives in ErrorHandlerService, from @batch-cooking/error-tools // — this stays a thin adapter. server.setErrorHandler(createErrorMiddleware(errorHandlerService)); diff --git a/apps/api/src/middlewares/require-auth.ts b/apps/api/src/middlewares/require-auth.ts index 31c33ca..ba32431 100644 --- a/apps/api/src/middlewares/require-auth.ts +++ b/apps/api/src/middlewares/require-auth.ts @@ -1,4 +1,4 @@ -import { HttpError } from "@batch-cooking/express-tools"; +import { HttpError } from "@batch-cooking/error-tools"; import { ErrorCode, type SafeUserProfile } from "@batch-cooking/shared"; import type { NextFunction, Request, Response } from "express"; import { env } from "../config/env.js"; diff --git a/apps/api/src/modules/auth/auth.service.ts b/apps/api/src/modules/auth/auth.service.ts index cfd5b7b..5fb5a3b 100644 --- a/apps/api/src/modules/auth/auth.service.ts +++ b/apps/api/src/modules/auth/auth.service.ts @@ -1,4 +1,4 @@ -import { HttpError } from "@batch-cooking/express-tools"; +import { HttpError } from "@batch-cooking/error-tools"; import { ErrorCode, type LoginInput, type SignupInput } from "@batch-cooking/shared"; import type { UserProfile } from "@prisma/client"; import argon2 from "argon2"; diff --git a/packages/error-tools/package.json b/packages/error-tools/package.json new file mode 100644 index 0000000..ea2f5af --- /dev/null +++ b/packages/error-tools/package.json @@ -0,0 +1,27 @@ +{ + "name": "@batch-cooking/error-tools", + "version": "0.0.0", + "private": true, + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "scripts": { + "test": "echo \"no tests yet\" && exit 0", + "build": "tsc -p tsconfig.json", + "postinstall": "tsc -p tsconfig.json" + }, + "devDependencies": { + "@types/node": "^22.9.0", + "typescript": "^5.7.2" + }, + "dependencies": { + "@batch-cooking/shared": "workspace:*", + "zod": "^3.25.76" + } +} diff --git a/packages/express-tools/src/error-handler.service.ts b/packages/error-tools/src/error-handler.service.ts similarity index 86% rename from packages/express-tools/src/error-handler.service.ts rename to packages/error-tools/src/error-handler.service.ts index 177bb19..a4f0b6e 100644 --- a/packages/express-tools/src/error-handler.service.ts +++ b/packages/error-tools/src/error-handler.service.ts @@ -2,7 +2,7 @@ import { type ApiErrorResponse, ErrorCode } from "@batch-cooking/shared"; import { ZodError } from "zod"; import { HttpError } from "./http-error.js"; -/** Return value of {@link ErrorHandlerService.handle}: everything an Express error middleware needs to send a response. */ +/** Return value of {@link ErrorHandlerService.handle}: everything a caller needs to send an HTTP response. */ export interface ErrorHandlingResult { /** HTTP status code to respond with. */ status: number; @@ -12,9 +12,11 @@ export interface ErrorHandlingResult { /** * Centralizes every "how do we turn a thrown error into an HTTP response" - * decision for an Express app in one place, so route handlers and the - * error middleware never duplicate this logic. Wire it into Express via - * {@link createErrorMiddleware} (see error-middleware.ts). + * decision in one place, so route handlers and framework-specific + * middleware (e.g. `createErrorMiddleware` in `@batch-cooking/express-tools`) + * never duplicate this logic. Framework-agnostic on purpose — it only maps + * an error to `{ status, body }` and has zero dependency on Express or any + * other HTTP framework. * * Recognizes three error shapes today (zod validation failures, our own * `HttpError`, and anything else) and always falls back to a safe, generic diff --git a/packages/express-tools/src/http-error.ts b/packages/error-tools/src/http-error.ts similarity index 94% rename from packages/express-tools/src/http-error.ts rename to packages/error-tools/src/http-error.ts index df4893b..e5f30e2 100644 --- a/packages/express-tools/src/http-error.ts +++ b/packages/error-tools/src/http-error.ts @@ -7,7 +7,7 @@ import type { ErrorCode } from "@batch-cooking/shared"; * Route handlers throw this (or let it bubble from a service call) instead * of manually setting a status/body — {@link ErrorHandlerService} is the * single place that turns it into an actual HTTP response, so every error - * path in an Express app built with these tools is shaped consistently. + * path in an app built with these tools is shaped consistently. */ export class HttpError extends Error { /** HTTP status code to respond with (e.g. 401, 404, 409). */ diff --git a/packages/error-tools/src/index.ts b/packages/error-tools/src/index.ts new file mode 100644 index 0000000..f1faeac --- /dev/null +++ b/packages/error-tools/src/index.ts @@ -0,0 +1,11 @@ +// Public entry point of the framework-agnostic error-handling tooling +// shared across apps in this monorepo. Everything here — HttpError, +// ErrorHandlerService — has zero dependency on Express or any other HTTP +// framework; it only knows how to map an error to a {status, body} pair. +// +// Framework-specific adapters (e.g. Express's `createErrorMiddleware`) live +// in their own package (`@batch-cooking/express-tools`) and consume these +// types instead of duplicating the mapping logic. + +export * from "./error-handler.service.js"; +export * from "./http-error.js"; diff --git a/packages/error-tools/tsconfig.json b/packages/error-tools/tsconfig.json new file mode 100644 index 0000000..3cf7309 --- /dev/null +++ b/packages/error-tools/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "NodeNext", + "moduleResolution": "NodeNext", + "declaration": true, + "outDir": "dist", + "rootDir": "src", + "types": ["node"] + }, + "include": ["src"] +} diff --git a/packages/express-tools/package.json b/packages/express-tools/package.json index 68b90d3..d128000 100644 --- a/packages/express-tools/package.json +++ b/packages/express-tools/package.json @@ -24,10 +24,10 @@ "typescript": "^5.7.2" }, "dependencies": { + "@batch-cooking/error-tools": "workspace:*", "@batch-cooking/shared": "workspace:*", "cookie-parser": "^1.4.7", "cors": "^2.8.6", - "express": "^4.21.1", - "zod": "^3.25.76" + "express": "^4.21.1" } } diff --git a/packages/express-tools/src/error-middleware.ts b/packages/express-tools/src/error-middleware.ts index fcaf9eb..dc303b3 100644 --- a/packages/express-tools/src/error-middleware.ts +++ b/packages/express-tools/src/error-middleware.ts @@ -1,5 +1,5 @@ +import type { ErrorHandlerService } from "@batch-cooking/error-tools"; import type { NextFunction, Request, Response } from "express"; -import type { ErrorHandlerService } from "./error-handler.service.js"; /** Express error-handling middleware signature (the 4-arg form Express detects as an error handler). */ type ExpressErrorMiddleware = ( diff --git a/packages/express-tools/src/index.ts b/packages/express-tools/src/index.ts index cde2fbd..2adf219 100644 --- a/packages/express-tools/src/index.ts +++ b/packages/express-tools/src/index.ts @@ -3,15 +3,11 @@ // infrastructure lives here — domain-specific code (auth, business logic) // stays in the consuming app. // -// Note the split: ErrorHandlerService (error-handler.service.ts) has zero -// dependency on Express — it's a plain "map an error to {status, body}" -// service that would work identically behind Fastify or any other -// framework. ExpressServer and createErrorMiddleware are the actual -// Express-specific layer, adapting framework-agnostic pieces (like -// ErrorHandlerService) onto Express's API. +// Framework-agnostic error-handling pieces (ErrorHandlerService, HttpError) +// live in `@batch-cooking/error-tools` instead, since they have zero +// dependency on Express. `createErrorMiddleware` here is the thin Express +// adapter that wires that service into an Express app. export * from "./async-handler.js"; -export * from "./error-handler.service.js"; export * from "./error-middleware.js"; export * from "./express-server.js"; -export * from "./http-error.js"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9cae0c7..b2efbb3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: apps/api: dependencies: + '@batch-cooking/error-tools': + specifier: workspace:* + version: link:../../packages/error-tools '@batch-cooking/express-tools': specifier: workspace:* version: link:../../packages/express-tools @@ -134,8 +137,27 @@ importers: specifier: ^5.4.11 version: 5.4.21(@types/node@22.20.1)(sass@1.102.0) + packages/error-tools: + dependencies: + '@batch-cooking/shared': + specifier: workspace:* + version: link:../shared + zod: + specifier: ^3.25.76 + version: 3.25.76 + devDependencies: + '@types/node': + specifier: ^22.9.0 + version: 22.20.1 + typescript: + specifier: ^5.7.2 + version: 5.9.3 + packages/express-tools: dependencies: + '@batch-cooking/error-tools': + specifier: workspace:* + version: link:../error-tools '@batch-cooking/shared': specifier: workspace:* version: link:../shared @@ -148,9 +170,6 @@ importers: express: specifier: ^4.21.1 version: 4.22.2 - zod: - specifier: ^3.25.76 - version: 3.25.76 devDependencies: '@types/cookie-parser': specifier: ^1.4.10 diff --git a/specs/backend-architecture.md b/specs/backend-architecture.md index a7f5b4f..a0f39be 100644 --- a/specs/backend-architecture.md +++ b/specs/backend-architecture.md @@ -1,7 +1,7 @@ # Architecture backend — Projet Batch-cooking > Documentation de l'organisation d'`apps/api` et de l'outillage partagé -> (`packages/express-tools`, `packages/shared`). +> (`packages/express-tools`, `packages/error-tools`, `packages/shared`). --- @@ -54,15 +54,17 @@ Sans ça, une exception dans un handler `async` ne remonte jamais tout seule au middleware d'erreur d'Express — chaque route devait faire son propre `try { ... } catch (err) { next(err); }`. `wrapAsyncHandler` l'automatise. -### `HttpError` / `ErrorHandlerService` / `createErrorMiddleware` +### `createErrorMiddleware` — adaptateur Express pour `packages/error-tools` -Voir [error-handling.md](./error-handling.md) pour le détail. À noter : +Voir [error-handling.md](./error-handling.md) pour le détail. `HttpError` et +`ErrorHandlerService` vivent dans **`packages/error-tools`**, pas ici : `ErrorHandlerService` **n'a aucune dépendance à Express** — c'est un service générique `erreur → { status, body }` qui fonctionnerait à l'identique derrière -Fastify ou n'importe quel autre framework. `ExpressServer` et -`createErrorMiddleware` sont la vraie couche Express : elles adaptent des -pièces indépendantes du framework (comme `ErrorHandlerService`) à l'API -d'Express. C'est pour ça que `ErrorHandlerService` n'importe jamais `express`. +Fastify ou n'importe quel autre framework, donc il n'a rien à faire dans un +package *express*-tools. `ExpressServer` et `createErrorMiddleware` (ici) sont +la vraie couche Express : elles adaptent des pièces indépendantes du framework +(`ErrorHandlerService`, importé depuis `@batch-cooking/error-tools`) à l'API +d'Express. --- diff --git a/specs/error-handling.md b/specs/error-handling.md index b4982a4..01f4800 100644 --- a/specs/error-handling.md +++ b/specs/error-handling.md @@ -14,20 +14,29 @@ l'affichage utilisateur, passe par un chemin unique et prévisible : réponse d'erreur de l'API). Ni l'API ni le web ne définissent leur propre liste de codes, et aucune valeur n'est jamais codée en dur ailleurs (toujours `ErrorCode.XXX`, jamais un nombre/une chaîne littérale). +- **`packages/error-tools`** — package séparé, **indépendant de tout framework + HTTP** (n'importe pas `express`) : `HttpError`, `ErrorHandlerService`. Le mapping + « erreur → `{ status, body }` » n'a rien de spécifique à Express, donc il ne vit + pas dans `express-tools`. - **`packages/express-tools`** — package séparé pour l'outillage Express générique (réutilisable par n'importe quel service Express du monorepo, pas seulement - `apps/api`) : `HttpError`, `ErrorHandlerService`, `createErrorMiddleware`. -- **`apps/api`** — consomme `express-tools` : lève des `HttpError`, le middleware - d'erreur final n'est qu'un appel à `createErrorMiddleware(errorHandlerService)`. + `apps/api`) : `createErrorMiddleware` (adapte `ErrorHandlerService` à l'API + Express), `ExpressServer`, `wrapAsyncHandler`. +- **`apps/api`** — consomme les deux : lève des `HttpError` (`error-tools`), le + middleware d'erreur final n'est qu'un appel à + `createErrorMiddleware(errorHandlerService)` (`express-tools`). - **`apps/web` → `ErrorMessageService`** — associe chaque `ErrorCode` à une clé de traduction, résolue via **i18next** (fichiers de locale sous `src/locales/`). Les composants n'écrivent jamais de texte d'erreur en dur. ```mermaid flowchart LR - subgraph TOOLS["packages/express-tools"] + subgraph ERRTOOLS["packages/error-tools"] HTTPERR["HttpError"] EHS["ErrorHandlerService.handle()"] + end + + subgraph TOOLS["packages/express-tools"] MW["createErrorMiddleware()"] end @@ -55,6 +64,7 @@ flowchart LR SHARED -. contrat .-> EMS style SHARED fill:none,stroke:#888,stroke-width:1px + style ERRTOOLS fill:none,stroke:#888,stroke-width:1px style TOOLS fill:none,stroke:#888,stroke-width:1px ``` @@ -97,12 +107,7 @@ Pour ajouter un nouveau cas d'erreur : --- -## `packages/express-tools` — les pièces liées aux erreurs - -`packages/express-tools` contient aussi `ExpressServer` (init serveur, -enregistrement de routes/middlewares) et `wrapAsyncHandler` — voir -[backend-architecture.md](./backend-architecture.md) pour le détail complet du -package. Les pièces qui concernent spécifiquement les erreurs : +## `packages/error-tools` — les pièces liées aux erreurs, indépendantes du framework - **`http-error.ts`** — `HttpError` : erreur typée portant `status` (code HTTP) et `code` (`ErrorCode`). C'est ce que lèvent les routes/services au lieu de @@ -112,24 +117,37 @@ package. Les pièces qui concernent spécifiquement les erreurs : d'autre) en `{ status, body }`. Le cas générique (`INTERNAL_ERROR`, 500) logue l'erreur côté serveur sans jamais exposer de détail interne au client. **N'importe pas `express`** — c'est un service générique, indépendant du - framework HTTP, qui fonctionnerait à l'identique derrière Fastify ou autre. -- **`error-middleware.ts`** — `createErrorMiddleware(service)` : construit le - middleware d'erreur Express (signature à 4 arguments) à partir du service — - c'est LUI la vraie couche Express, `ErrorHandlerService` reste agnostique. + framework HTTP, qui fonctionnerait à l'identique derrière Fastify ou autre. C'est + précisément pour ça qu'il vit dans son propre package plutôt que dans + `express-tools` : rien ici ne dépend d'Express, donc rien ici n'a sa place dans + un package *express*-tools. Build réel (`tsc` → `dist/`, comme `packages/shared`) : consommé en JS compilé, pas en TS brut — voir la note dans [frontend-architecture.md](./frontend-architecture.md#note-sur-les-fichiers-dts) sur pourquoi ça compte pour un runtime Node pur (Docker). +## `packages/express-tools` — l'adaptateur Express + +`packages/express-tools` contient `ExpressServer` (init serveur, enregistrement +de routes/middlewares) et `wrapAsyncHandler` — voir +[backend-architecture.md](./backend-architecture.md) pour le détail complet du +package. La pièce qui concerne spécifiquement les erreurs : + +- **`error-middleware.ts`** — `createErrorMiddleware(service: ErrorHandlerService)` : + construit le middleware d'erreur Express (signature à 4 arguments) à partir + d'un `ErrorHandlerService` importé de `@batch-cooking/error-tools` — c'est LUI + la vraie couche Express, `ErrorHandlerService` reste agnostique. `express-tools` + dépend de `error-tools`, jamais l'inverse. + ## Côté API (`apps/api`) - **`app.ts`** — le middleware d'erreur final est enregistré via `server.setErrorHandler(createErrorMiddleware(errorHandlerService))` (voir [backend-architecture.md](./backend-architecture.md) pour `ExpressServer`) ; - aucune logique de mapping n'y vit directement, tout est dans `express-tools`. + aucune logique de mapping n'y vit directement, tout est dans `error-tools`. - Les modules métier (`modules/auth/auth.service.ts`, `middlewares/require-auth.ts`) - importent `HttpError` depuis `@batch-cooking/express-tools` et `ErrorCode` depuis + importent `HttpError` depuis `@batch-cooking/error-tools` et `ErrorCode` depuis `@batch-cooking/shared`. ## Côté Web (`apps/web`)