42 lines
1.5 KiB
TypeScript
42 lines
1.5 KiB
TypeScript
|
|
import type { ItemId, StoreItem } from "@/app/types";
|
||
|
|
import { CATALOG } from "./catalog";
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// PriceProvider: the single seam between "where prices come from" and the
|
||
|
|
// rest of the app. The cost math only ever calls `getItem`.
|
||
|
|
//
|
||
|
|
// To use live retailer prices later:
|
||
|
|
// 1. Fetch prices on the SERVER (a Server Action or route handler) so the
|
||
|
|
// API key never reaches the browser.
|
||
|
|
// 2. Build a snapshot `StoreItem[]` from the response.
|
||
|
|
// 3. Pass `new SnapshotPriceProvider("Live grocery prices", snapshot)` into
|
||
|
|
// the grocery/planner functions instead of `averageGroceryProvider`.
|
||
|
|
// The math stays synchronous and testable because it works on a snapshot.
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
export interface PriceProvider {
|
||
|
|
/** Shown in the UI so users know where prices came from. */
|
||
|
|
readonly label: string;
|
||
|
|
getItem(id: ItemId): StoreItem | undefined;
|
||
|
|
}
|
||
|
|
|
||
|
|
export class SnapshotPriceProvider implements PriceProvider {
|
||
|
|
private readonly byId: Map<ItemId, StoreItem>;
|
||
|
|
|
||
|
|
constructor(
|
||
|
|
readonly label: string,
|
||
|
|
items: StoreItem[],
|
||
|
|
) {
|
||
|
|
this.byId = new Map(items.map((item) => [item.id, item]));
|
||
|
|
}
|
||
|
|
|
||
|
|
getItem(id: ItemId): StoreItem | undefined {
|
||
|
|
return this.byId.get(id);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export const averageGroceryProvider: PriceProvider = new SnapshotPriceProvider(
|
||
|
|
"Based on average grocery prices as of October 2026",
|
||
|
|
CATALOG,
|
||
|
|
);
|