Ajoute markAlreadyImported(items, importedExternalIds) et le type
BrowsableRecipeItem à recipe-source-adapter.ts : quand on parcourt le
catalogue d'une source (list()), on peut désormais annoter chaque
item pour savoir s'il correspond à une recette déjà intégrée dans
notre base ou non.
Reste une fonction pure, volontairement séparée de list() : un
adaptateur ne connaît que sa source, jamais notre base — même
séparation I/O/pur que tech-step-matcher.ts. La constitution du set
d'externalId déjà importés (où/comment on persiste ce lien) est
laissée à une future couche, pas encore décidée.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Pose les bases du pipeline d'import décrit dans
specs/batch-cooking-architecture.md (Import depuis source → Traduction
en étapes → Sauvegarde), en commençant par le premier maillon :
récupérer et parser des recettes brutes depuis une source externe,
indépendamment du site/API concerné.
- RecipeSourceAdapter<TRawDetail> (recipe-source-adapter.ts) : contrat
générique par source — list() pour parcourir un catalogue de façon
paginée (l'utilisateur "browse" les recettes disponibles), puis
fetchDetail(externalId) une fois une recette sélectionnée, puis
parse(raw) pour la transformer en ParsedRecipe. parse() est pure et
synchrone (même séparation I/O vs pur que tech-step-matcher.ts), ce
qui la rend testable sans réseau.
- ParsedRecipe est volontairement distinct de CreateRecipeInput : les
ingrédients restent en texte libre (pas d'ingredientId/unitId) — la
résolution vers nos catalogues Ingredient/Unit est un sujet séparé,
pas encore construit.
- recipe-source-registry.ts : registre en mémoire des adaptateurs,
identifiés par une clé stable (même convention que Diet.key/
Unit.key/TechStep.key), distinct de la table Source (schema.prisma)
qui documente la provenance d'une recette déjà sauvegardée.
- recipe-source-errors.ts : RecipeSourceFetchError/RecipeSourceParseError,
vocabulaire d'erreur dédié en attendant qu'une route les traduise en
HttpError/ErrorCode.
Pas de route HTTP, pas d'écriture en base, pas d'implémentation
concrète pour l'instant — uniquement le module générique, validé par
un adaptateur factice dans les tests. Le câblage (endpoint, sourceId,
un vrai parseur) sera une PR suivante.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>