9 fichiers à plat -> recipe-sources/ (recipe-source-adapter, recipe-source- errors, recipe-source-registry) et recipe-matching/ (recipe-translation, ingredient-matcher, tech-step-matcher). jwt.ts, safe-profile.ts et logger.service.ts restent à la racine de lib/ (pas de sous-domaine partagé avec les autres). Chemins relatifs corrigés dans les fichiers déplacés (profondeur +1 vers db/) et chez tous leurs importeurs (modules/sources, modules/recipe, sources/*, db/recipe-source-sync.ts, 12 fichiers de test), doc mise à jour (specs/backend-architecture.md, specs/batch-cooking-architecture.md). Vérifié : tsc --noEmit, biome check, build complet, 303 tests API. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
56 lines
2.6 KiB
TypeScript
56 lines
2.6 KiB
TypeScript
import type { RecipeSourceAdapter } from "./recipe-source-adapter.js";
|
|
|
|
/**
|
|
* In-memory registry of every {@link RecipeSourceAdapter} (recipe-source-adapter.ts)
|
|
* this process knows about, keyed by `adapter.key`. Deliberately not
|
|
* DB-backed — an adapter *is* code (a website's fetch/parse logic can't
|
|
* live in a database row) — but the `Source` table (schema.prisma) is kept
|
|
* in sync with it (see `syncRecipeSources`, recipe-source-sync.ts) so a
|
|
* saved `Recipe.sourceId` has a row to point at. Actually saving an
|
|
* imported recipe (setting `Recipe.sourceId`/`externalId`) is still future
|
|
* work for whichever module ends up driving the import pipeline — this
|
|
* registry only answers "which sources can we import from right now".
|
|
*
|
|
* No adapter is registered here yet — this file only provides the
|
|
* mechanism; `registerRecipeSource` is meant to be called once per adapter
|
|
* module, at whatever point a concrete source is added.
|
|
*/
|
|
const adapters = new Map<string, RecipeSourceAdapter>();
|
|
|
|
/**
|
|
* Registers `adapter` under its own `key`. Throws if that key is already
|
|
* taken — two adapters silently overwriting each other would be a bug (a
|
|
* caller reaching for "marmiton" should never get a different adapter than
|
|
* the one it registered), not a case to swallow.
|
|
*/
|
|
export function registerRecipeSource<TRawDetail>(adapter: RecipeSourceAdapter<TRawDetail>): void {
|
|
if (adapters.has(adapter.key)) {
|
|
throw new Error(`Recipe source "${adapter.key}" is already registered`);
|
|
}
|
|
// `TRawDetail` only matters within one adapter's own list/fetchDetail/parse
|
|
// trio — once stored, callers look adapters up by key and drive the same
|
|
// three methods generically, so the registry itself doesn't need to know
|
|
// each adapter's raw type. This cast is the standard way to store a
|
|
// heterogeneous collection of otherwise-identically-shaped generics.
|
|
adapters.set(adapter.key, adapter as RecipeSourceAdapter);
|
|
}
|
|
|
|
/** The adapter registered under `key`, or `undefined` if none is. */
|
|
export function getRecipeSource(key: string): RecipeSourceAdapter | undefined {
|
|
return adapters.get(key);
|
|
}
|
|
|
|
/** Every registered adapter — e.g. to offer a source picker. */
|
|
export function listRecipeSources(): RecipeSourceAdapter[] {
|
|
return [...adapters.values()];
|
|
}
|
|
|
|
/**
|
|
* Empties the registry. Not meant for application code — `apps/api/src`
|
|
* never calls this — only for test isolation, the same role
|
|
* `test-support/reset-db.ts` plays for the database: without it, adapters
|
|
* registered by one test file would leak into the next.
|
|
*/
|
|
export function clearRecipeSources(): void {
|
|
adapters.clear();
|
|
}
|