Contratto dati
La regola
Sezione intitolata “La regola”Tutto ciò che arriva da WordPress è unknown finché non passa da Zod. Nessun any,
nessun accesso diretto a response.meta.campo senza validazione.
REST JSON → schema Zod → mapper → modello di dominio → componente- schema: descrive esattamente ciò che WordPress restituisce (meta compresi).
- mapper: traduce i nomi (
sort_date→sortDate) e risolve i media. - modello di dominio: ciò che serve alla UI, indipendente dal CMS.
Un esempio completo
Sezione intitolata “Un esempio completo”src/cms/wordpress/wordpress-evento.schema.ts:
const WordPressEventoMetaSchema = z.object({ date: z.string().default(""), sort_date: z.string().default(""), location: z.string().default(""), cat: z.string(), // ...});
export const WordPressEventiSchema = z.array( WordPressPostSchema.extend({ meta: WordPressEventoMetaSchema }),);cat non ha default: se il CPT non è configurato o il meta manca, lo schema fallisce
subito. È una scelta: meglio un errore esplicito che una categoria vuota silenziosa.
Mapper:
export function mapWordPressEventoToEvento( wp: WordPressEvento, media: Map<string, string>,): Evento { return { id: wp.id, slug: wp.slug, title: wp.title.rendered, sortDate: wp.meta.sort_date, image: media.get(wp.meta.image) ?? "", // ... };}Loader (src/cms/wordpress/wordpress-loader.ts):
store.clear();GETdell’endpoint conper_page=100e_fieldsridotti;schema.parse(json);- risoluzione media: una sola chiamata a
/wp/v2/media?include=id1,id2,...; mapper ogni item estore.set({ id: slug, data }).
Il flag ENABLE_HTML_FALLBACK
Sezione intitolata “Il flag ENABLE_HTML_FALLBACK”| Valore | Comportamento |
|---|---|
false (CI, staging, produzione) |
qualsiasi errore di contratto fa fallire la build |
true (solo sviluppo locale) |
logga un warning e lascia la collezione vuota |
Con fallback attivo il sito si vede, ma le sezioni mancano. Serve per lavorare senza WordPress, non per “aggiustare” una build rossa in produzione.
Immagini
Sezione intitolata “Immagini”Il campo in admin contiene un ID media. Il loader raccoglie tutti gli ID della collezione e fa una richiesta:
/wp-json/wp/v2/media?per_page=100&include=42,43,44&_fields=id,source_urlSenza questo batch servirebbe una richiesta per immagine. Con 60 eventi e 2 immagini ciascuno sarebbero 120 richieste invece di 1.
tests/wordpress-schema.test.ts valida fixture JSON reali contro gli schema e i mapper.
tests/wordpress-loader.test.ts simula fetch e verifica i tre casi:
- contratto valido → entry nello store;
- contratto rotto + fallback
false→ errore; - contratto rotto + fallback
true→ warning, store vuoto.
Quando cambi un campo del mu-plugin:
- aggiorna lo schema Zod;
- aggiorna il mapper e il modello;
- aggiorna la fixture in
tests/fixtures/; - esegui
npm run test.
Errori tipici
Sezione intitolata “Errori tipici”| Sintomo | Causa |
|---|---|
Invalid input: expected string, received undefined |
un meta non è registrato nel mu-plugin, o il JSON salvato è malformato |
meta: {} |
il CPT non ha custom-fields tra i supports, oppure i permalink non sono stati rigenerati |
| Immagine vuota | campo compilato con URL invece che ID media |
| Tutte le collezioni vuote | WordPress non raggiungibile o WORDPRESS_API_BASE_URL sbagliata |
| Build rossa dopo una rinomina | schema e mu-plugin non sono allineati |
Limiti noti
Sezione intitolata “Limiti noti”per_page=100, senza paginazione: oltre 100 elementi per CPT il loader ne legge solo 100. Per superare il limite si aggiunge un ciclo supagenel loader (while (items.length === 100) { page++; ... }).- Nessuna cache tra build: ogni build rifà le stesse richieste. Sono ~14 richieste per build, quindi va bene.
- Nessun preview: il contenuto si vede dopo il rebuild.
- Nessun contenuto HTML arbitrario: i campi sono testo, non HTML dell’editor (salvo scelta esplicita e sanitizzazione).
Questi limiti sono deliberati. Se un progetto li supera, la soluzione va progettata, non improvvisata nel componente.