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>
37 lines
1.8 KiB
TypeScript
37 lines
1.8 KiB
TypeScript
/**
|
|
* Error vocabulary a {@link RecipeSourceAdapter} (recipe-source-adapter.ts)
|
|
* implementation throws when talking to its source fails — kept separate
|
|
* from `@batch-cooking/error-tools`'s `HttpError`/`ErrorCode` (used for
|
|
* *this API's* HTTP responses) since no route drives this module yet. A
|
|
* future import route would catch these and translate them into an
|
|
* `HttpError` with a dedicated `ErrorCode` the same way any other service
|
|
* error is; this module only needs a consistent shape to throw in the
|
|
* meantime, not that translation.
|
|
*/
|
|
|
|
/** Base class for every error a {@link RecipeSourceAdapter} can throw — lets a caller `catch (err) { if (err instanceof RecipeSourceError) ... }` regardless of which stage failed. */
|
|
export class RecipeSourceError extends Error {
|
|
/** The failing adapter's `key` (recipe-source-adapter.ts's `RecipeSourceAdapter.key`) — which source this error came from. */
|
|
readonly sourceKey: string;
|
|
|
|
constructor(sourceKey: string, message: string, options?: { cause?: unknown }) {
|
|
super(message, options);
|
|
this.sourceKey = sourceKey;
|
|
}
|
|
}
|
|
|
|
/** The source's `list`/`fetchDetail` failed — network error, non-2xx response, source unreachable, etc. */
|
|
export class RecipeSourceFetchError extends RecipeSourceError {
|
|
constructor(sourceKey: string, message: string, options?: { cause?: unknown }) {
|
|
super(sourceKey, message, options);
|
|
this.name = "RecipeSourceFetchError";
|
|
}
|
|
}
|
|
|
|
/** The source responded, but `parse` couldn't make sense of the raw payload (unexpected shape, missing required field, …). */
|
|
export class RecipeSourceParseError extends RecipeSourceError {
|
|
constructor(sourceKey: string, message: string, options?: { cause?: unknown }) {
|
|
super(sourceKey, message, options);
|
|
this.name = "RecipeSourceParseError";
|
|
}
|
|
}
|