src/lib/api.ts — a type guard at the boundary
In the code column — src/lib/api.ts. Two decisions do all the work.
1. request() returns Promise<unknown>, not Promise<any>. The difference is fundamental:
any— "don't worry about types, do whatever". A hole.unknown— "you have something, but you don't know what yet; you must check before using it".
With unknown the compiler won't let you use the result as Apod[] straight away — it forces you through a guard.
2. The type guard isApod:
function isApod(value: unknown): value is Apod {
if (typeof value !== 'object' || value === null) return false;
const v = value as Record<string, unknown>;
return (
typeof v['date'] === 'string' &&
typeof v['url'] === 'string' &&
(v['media_type'] === 'image' || v['media_type'] === 'video')
);
}
value is Apod is a type predicate. If the function returns true, the compiler treats value as an Apod from there on. The real check happens at runtime (it inspects the fields), and its result unlocks the type at compile time.
And fetchPictures uses it at the boundary:
return data.filter(isApod).reverse(); // drop anything malformed, don't crash
.filter(isApod) is clever: it both validates and filters out bad entries. If NASA returns one malformed picture, it simply disappears and the gallery keeps working. The returned type is a clean Apod[].
Tip. We write the guard once, in one place — at the network boundary. After that, the whole app works with a trusted
Apodand no extra checks. This is "pushany/unknownto the edges, keep the core strictly typed". The boundary is where the untrusted world meets your types.