diff --git a/src/api/sdk.ts b/src/api/sdk.ts index b5cd3f8..e84261a 100644 --- a/src/api/sdk.ts +++ b/src/api/sdk.ts @@ -71,9 +71,7 @@ export function mapPurchasedShoppingList(dto: components['schemas']['PurchasedSh if (listRaw) { const base = decodeShoppingList(listRaw) if (base) { - const decoded = Array.isArray(listRaw.items) ? decodeShoppingListItems(listRaw.items) : [] - // start with decoded items and attach refs below; treat as WithRefs variant - const items = decoded as unknown as import('@/domain/types').ShoppingListItemWithRefs[] + const items = Array.isArray(listRaw.items) ? decodeShoppingListItems(listRaw.items) : [] list = { ...base, items } const lookups: ShoppingLookups = { ...(ingredientsLookup && { ingredientsLookup }), @@ -117,9 +115,9 @@ export function mapCurrentShoppingList(dto: components['schemas']['CurrentShoppi } const dtoOut: CurrentShoppingListDTO = { - outstandingItems: (decodeShoppingListItems(outstandingRaw ?? []) as unknown) as ShoppingListItemWithRefs[], - requestedMeals: (decodeShoppingListItems(requestedRaw ?? []) as unknown) as ShoppingListItemWithRefs[], - purchasedItems: (decodeShoppingListItems(purchasedRaw ?? []) as unknown) as ShoppingListItemWithRefs[], + outstandingItems: decodeShoppingListItems(outstandingRaw ?? []), + requestedMeals: decodeShoppingListItems(requestedRaw ?? []), + purchasedItems: decodeShoppingListItems(purchasedRaw ?? []), ...(ingredientsLookup && { ingredientsLookup }), ...(mealsLookup && { mealsLookup }), ...(recipesLookup && { recipesLookup }), diff --git a/src/domain/decoders.ts b/src/domain/decoders.ts index 0fa5050..31afcdd 100644 --- a/src/domain/decoders.ts +++ b/src/domain/decoders.ts @@ -1,4 +1,4 @@ -import type { RecipeOut, Recipe, MealOut, Meal, MealRecipe, Ingredient as DomainIngredient, ShoppingList, ShoppingListItem, MealInput } from './types' +import type { RecipeOut, Recipe, MealOut, Meal, MealRecipe, Ingredient as DomainIngredient, ShoppingList, ShoppingListItem, ShoppingListItemWithRefs, MealInput } from './types' import type { components } from '@/api/types' export function toDate(value: string | Date | null | undefined): Date | null { @@ -9,7 +9,7 @@ export function toDate(value: string | Date | null | undefined): Date | null { // Small helper to decode optional lookup maps without repeating loops everywhere export function decodeLookup( raw: Record | null | undefined, - decode: (v: TIn) => TOut | null + decode: (v: TIn) => TOut ): Record | undefined { if (!raw) return undefined const out: Record = {} @@ -18,13 +18,13 @@ export function decodeLookup( const maybe = raw[key] if (maybe === undefined) continue const decoded = decode(maybe) - if (decoded) out[String(key)] = decoded + out[String(key)] = decoded } return out } -export function decodeRecipe(r: RecipeOut | null | undefined): Recipe | null { - if (!r) return null +export function decodeRecipe(r: RecipeOut | null | undefined): Recipe { + if (!r) throw new Error('Invalid recipe payload') return { ...r, dateCreated: toDate(r.dateCreated), @@ -32,11 +32,10 @@ export function decodeRecipe(r: RecipeOut | null | undefined): Recipe | null { } } - -export function decodeMeal(m: MealOut | null | undefined): Meal | null { - if (!m) return null - const recipes = Array.isArray(m.recipes) - ? m.recipes.map(mr => decodeMealRecipe(mr)).filter((r): r is MealRecipe => !!r) +export function decodeMeal(m: MealOut | null | undefined): Meal { + if (!m) throw new Error('Invalid meal payload') + const recipes = Array.isArray(m.recipes) + ? m.recipes.map((mr) => decodeMealRecipe(mr)) : [] return { @@ -52,40 +51,41 @@ export function decodeMeal(m: MealOut | null | undefined): Meal | null { } } -export function decodeMealRecipe(mr: components['schemas']['MealRecipe-Output'] | null | undefined): MealRecipe | null { - if (!mr) return null +export function decodeMealRecipe(mr: components['schemas']['MealRecipe-Output'] | null | undefined): MealRecipe { + if (!mr) throw new Error('Invalid meal recipe payload') return { ...mr, recipe: mr.recipe ? decodeRecipe(mr.recipe) : null, } } -export function decodeIngredient(i: components['schemas']['Ingredient'] | null | undefined): DomainIngredient | null { - if (!i) return null +export function decodeIngredient(i: components['schemas']['Ingredient'] | null | undefined): DomainIngredient { + if (!i) throw new Error('Invalid ingredient payload') // API guarantees quantity is a number; pass through return { ...i } } export function decodeIngredients(list: components['schemas']['Ingredient'][] | null | undefined): DomainIngredient[] { if (!Array.isArray(list)) return [] - return list.map((i) => decodeIngredient(i)).filter((x): x is DomainIngredient => !!x) + return list.map((i) => decodeIngredient(i)) } -export function decodeShoppingListItem(i: components['schemas']['ShoppingListItem'] | null | undefined): ShoppingListItem | null { - if (!i) return null +export function decodeShoppingListItem(i: components['schemas']['ShoppingListItem'] | null | undefined): ShoppingListItem { + if (!i) throw new Error('Invalid shopping list item payload') return { ...i, createdDate: toDate(i.createdDate), } } -export function decodeShoppingListItems(list: components['schemas']['ShoppingListItem'][] | null | undefined): ShoppingListItem[] { +export function decodeShoppingListItems(list: components['schemas']['ShoppingListItem'][] | null | undefined): ShoppingListItemWithRefs[] { if (!Array.isArray(list)) return [] - return list.map((i) => decodeShoppingListItem(i)).filter((x): x is ShoppingListItem => !!x) + // Build a new array with item clones to allow optional refs to be attached later + return list.map((raw) => ({ ...decodeShoppingListItem(raw) })) } -export function decodeShoppingList(v: components['schemas']['ShoppingList'] | null | undefined): ShoppingList | null { - if (!v) return null +export function decodeShoppingList(v: components['schemas']['ShoppingList'] | null | undefined): ShoppingList { + if (!v) throw new Error('Invalid shopping list payload') const { items: rawItems, ...rest } = v const items = Array.isArray(rawItems) ? decodeShoppingListItems(rawItems) : undefined return {