src/state.ts — Readonly state
In the code column — src/state.ts. It's lesson 9's pub/sub, now typed — with one crucial guard.
getState() returns Readonly<AppState>:
export function getState(): Readonly<AppState> {
return state;
}
Readonly<T> makes every field read-only. A view can read getState().pictures, but:
getState().pictures = []; // ❌ Cannot assign to 'pictures' because it is read-only
doesn't compile. This is the language enforcing lesson 9's convention: state can only be changed through actions (setPictures, setStatus, setSort) that end in notify(). In the vanilla version that was just discipline; now it's a compiler rule.
catch (error) is unknown — the third idea, in errors.ts:
export function toMessage(error: unknown): string { // unknown, NOT any
if (error instanceof AppError) return error.message;
if (error instanceof Error) return error.message;
return 'Something went wrong.';
}
Why unknown? Because JavaScript lets you throw anything: throw 42, throw 'oops', throw null. So in a catch block TypeScript types the error as unknown and forces you to narrow (instanceof) before using it. With any you could write error.message on a number and crash at runtime.
Verify.
getState().searchQuery = 'x'; // ❌ read-only
setSearchQuery('x'); // ✅ through an action
// state.ts getFilteredSaved: switch (state.sortKey) with no `default` —
// add a fourth SortKey value, and the switch stops compiling.
Tip.
Readonly<AppState>'s read-only-ness is shallow (top-level fields only). For deeply nested objects there areDeepReadonlypatterns, but shallow is usually enough: it catches the most common mistake — a view trying to overwrite state directly instead of using an action.