diff --git a/src/api/sdk.ts b/src/api/sdk.ts index a7264a7..3baba8f 100644 --- a/src/api/sdk.ts +++ b/src/api/sdk.ts @@ -154,19 +154,7 @@ export function mapCurrentShoppingList(dto: components['schemas']['CurrentShoppi return dtoOut } -function hasProp(o: unknown, k: T): o is Record { - return o !== null && typeof o === 'object' && k in o -} - -function isListIngredientItem(v: unknown): v is components['schemas']['ListIngredientItem'] { - return ( - hasProp(v, 'kind') && typeof v.kind === 'string' && v.kind === 'ingredient' && - hasProp(v, 'id') && typeof v.id === 'number' && - hasProp(v, 'ingredientId') && typeof v.ingredientId === 'number' && - hasProp(v, 'personId') && typeof v.personId === 'number' && - hasProp(v, 'createdDate') && typeof v.createdDate === 'string' - ) -} +// With stricter OpenAPI types, we can rely on the typed responses and decoders. export async function listRecipes(params?: { q?: string | null; cursor?: string | null; limit?: number }): Promise> { const query: Record = {} @@ -191,10 +179,10 @@ export async function getRecipe(householdSlug: string, id: number | string): Pro return mapped } -export async function saveRecipe(recipe: components['schemas']['RecipeCreate']): Promise { +export async function saveRecipe(recipe: components['schemas']['RecipeCreate-Input']): Promise { const householdSlug = requireSlug() // Map "Recipe-Input" to "RecipeCreate" - const body: components['schemas']['RecipeCreate'] = { + const body: components['schemas']['RecipeCreate-Input'] = { name: recipe.name, link: recipe.link, serves: recipe.serves, @@ -219,25 +207,28 @@ export async function parseRecipe(url: string): Promise { body: { url }, }) if (!response.ok) throw httpError(response, error) - // Validate unknown payload before decoding to maintain strict boundary - const isRecipeLike = (v: unknown): v is components['schemas']['RecipeOut'] => ( - v !== null && typeof v === 'object' && - hasProp(v, 'id') && typeof v.id === 'number' && - hasProp(v, 'name') && typeof v.name === 'string' && - hasProp(v, 'link') && typeof v.link === 'string' && - hasProp(v, 'serves') && typeof v.serves === 'number' && - hasProp(v, 'imageUrls') && Array.isArray(v.imageUrls) && - hasProp(v, 'ingredients') && Array.isArray(v.ingredients) && - hasProp(v, 'createdById') && typeof v.createdById === 'number' - ) - if (!isRecipeLike(data)) throw new Error('Invalid parse response payload') - return decodeRecipe(data) + // Response is RecipeCreate-Output; map to domain Recipe via decoder by augmenting required ids + const created: components['schemas']['RecipeCreate-Output'] | undefined = data + if (!created) return null + const out: components['schemas']['RecipeOut'] = { + id: -1, + name: created.name, + link: created.link, + serves: created.serves, + imageUrls: created.imageUrls, + ingredients: created.ingredients, + createdById: -1, + } + return decodeRecipe(out) } -export async function parseIngredients(_lines: string[]): Promise { - // Removed in v2 API; replaced by full recipe parsing endpoint - // Reference _lines to satisfy lint rules without changing behavior - throw new Error(`parseIngredients is not available in v2 API (got ${_lines.length} lines)`) +export async function parseIngredients(lines: string[]): Promise { + const householdSlug = requireSlug() + const { data, error, response } = await api.GET('/api/v1/households/{householdSlug}/ingredients/parse', { + params: { path: { householdSlug }, query: { lines } }, + }) + if (!response.ok) throw httpError(response, error) + return Array.isArray(data) ? data.map(decodeIngredient) : [] } export async function parseProduct(): Promise { @@ -388,7 +379,7 @@ export async function requestIngredient(ingredientId: number): Promise) -function toRecipeCreate(r: DomainRecipe): components['schemas']['RecipeCreate'] { +function toRecipeCreate(r: DomainRecipe): components['schemas']['RecipeCreate-Input'] { return { name: r.name, link: r.link,