Caché y revalidación: invalidar por clave
Tras una mutación, los datos que tocó quedan obsoletos y hay que volver a pedirlos, pero solo los justos. Por qué una `action` revalida todas las queries por defecto, cómo acotar con la opción `revalidate` y `json`, la distinción capital entre `query.key` que invalida todas las instancias y `query.keyFor` que invalida una sola, cómo `revalidate` imperativo fuerza el refetch fuera de una acción y espera su promesa, y por qué el alcance de la clave es exactamente el alcance de la invalidación.
Revalidar de más: el defecto seguro
Cuando una action termina con éxito, el router revalida todas las queries activas. La lógica es de seguridad: el framework no puede saber qué datos dependían de lo que mutaste, así que asume que cualquiera pudo quedar obsoleto y los recomputa todos. Es correcto siempre, pero pide de más: recarga queries que la mutación no rozó.
import { action } from "@solidjs/router";// Sin acotar: al publicar una resena, TODAS las queries se revalidanexport const publicarResena = action(async (form: FormData) => { "use server"; await db.resena.create({ data: leer(form) }); // ...sin mas, el router revalida getProducto, getProductos, getResenas...}, "publicarResena");Para una app pequeña esto basta y es lo más simple que funciona. En cuanto la superficie de datos crece, revalidar todo tras cada mutación se nota, y conviene decirle al router exactamente qué recomputar.
Invalidar solo lo necesario: key frente a keyFor
La forma declarativa de acotar es devolver el resultado con json y su opción revalidate, indicando qué claves recomputar. Solo esas queries se refrescan; el resto conserva su valor cacheado. Y aquí aparece la distinción capital entre las dos claves que expone cada query.
import { action, json } from "@solidjs/router";import { getResenas } from "~/lib/productos";export const publicarResena = action(async (form: FormData) => { "use server"; const productoId = form.get("productoId") as string; await db.resena.create({ data: leer(form) }); // recomputa solo las resenas de ESTE producto, nada mas return json({ ok: true }, { revalidate: getResenas.keyFor(productoId) });}, "publicarResena");getResenas.key es el nombre base: revalidarlo recomputa todas las instancias de esa query, sin importar sus argumentos. getResenas.keyFor(id) es la clave de una instancia concreta: revalidarlo recomputa solo las reseñas de ese producto y deja intactas las demás cacheadas. Elegir entre una y otra es elegir el grano de tu invalidación.
revalidate: getResenas.keyFor(id); // solo ese productorevalidate: getResenas.key; // todas las listas de resenasrevalidate: [getProducto.keyFor(id), getResenas.keyFor(id)]; // varias clavesLa misma opción viaja en los otros resultados que una acción puede devolver: reload reejecuta la ruta actual acotando qué recomputar, y redirect navega a otra ruta arrastrando qué claves refrescar en el destino. Así la decisión de qué queda obsoleto acompaña a la acción sea cual sea su desenlace.
revalidate imperativo
No toda revalidación nace de una acción. A veces quieres refrescar tras un evento externo —un websocket que avisa de un cambio, un botón de “actualizar”, un temporizador—. Para eso está la función revalidate, que dispara el refetch de una o varias claves desde cualquier sitio y devuelve una promesa que resuelve cuando el refetch termina.
import { revalidate } from "@solidjs/router";await revalidate(getProducto.keyFor(id)); // refresca un productoawait revalidate(getResenas.key); // refresca toda una queryawait revalidate(); // refresca absolutamente todoComo devuelve una promesa, puedes esperar a que los datos frescos estén listos antes de, por ejemplo, cerrar un modal o mostrar un aviso de “hecho”. Es la misma maquinaria que las acciones usan por dentro, ahora a tu disposición para orquestar la coherencia desde fuera del flujo de formularios.
async function alRecibirWebsocket(id: string) { await revalidate(getProducto.keyFor(id)); // trae la verdad fresca del servidor mostrarAviso("actualizado"); // solo despues de que el dato ya esta al dia}Poder esperar la promesa cambia la calidad de tus interacciones: el aviso de “hecho”, el cierre del modal o la habilitación de un botón dejan de ser optimistas a ciegas y pasan a coincidir con el instante real en que el servidor ya devolvió datos frescos. La revalidación deja de ser un efecto secundario que disparas y olvidas para convertirse en un paso que puedes coordinar dentro de un flujo mayor.
El alcance de la clave es el alcance de la invalidación
Aquí converge todo. La clave con la que query indexa un dato no es solo cómo lo encuentras: es la unidad con la que lo invalidas. Si modelas “todas las reseñas” como una sola query sin argumentos, tu unidad de invalidación es la colección entera: publicar una te obliga a recomputar todas. Si modelas “las reseñas del producto N” como una query parametrizada, tu unidad baja a la instancia: publicar en el 7 revalida solo el 7. La granularidad de tu caché y la de tu invalidación son la misma decisión, tomada al elegir qué es una query y qué son sus argumentos.
Queda un matiz de ergonomía que redondea el patrón. Cuando revalidas una lista leída con createAsync, su valor es atómico: cada refetch reemplaza el objeto entero y un For sobre él remonta todas las filas aunque solo cambiara una. Leerla con createAsyncStore y reconcile resuelve esto: guarda el resultado en un store y cose la respuesta revalidada de grano fino sobre la anterior, preservando la identidad de lo que no cambió.
import { createAsyncStore } from "@solidjs/router";// Cada revalidacion difea contra el valor previo por la clave idconst resenas = createAsyncStore(() => getResenas(id), { reconcile: { key: "id" },});// el For solo remonta las filas que de verdad cambiaronAsí la invalidación por clave y la reconciliación por identidad se complementan: la primera decide qué colección se vuelve a pedir; la segunda, que esa colección aterrice sin remontar lo que ya estaba. Precisión al pedir, suavidad al pintar.