Ajustes
Hay un puñado de valores que no son contenido pero tampoco código: el nombre del sitio, su URL pública, la dirección desde la que sale el correo. Cambiarlos no debería exigir un despliegue.
Viven en Cloudflare KV, bajo la clave settings:site, y se leen desde Astro.locals.cms.settings
o por la API REST.
Los seis campos
Sección titulada «Los seis campos»| Campo | Por defecto | Quién lo usa |
|---|---|---|
siteName |
email.siteName del config ('Kevin CMS') |
Firma las plantillas de correo |
emailFrom |
email.from del config |
Cabecera From de todo el correo |
emailReplyTo |
email.replyTo del config |
Cabecera Reply-To. Vacío significa sin cabecera |
siteUrl |
'' |
La URL base de better-auth. Vacía significa deducirla de la petición |
favicon |
'' |
El icono del panel. Vacío significa el que trae Kevin CMS |
locale |
'es' |
Nada todavía. Se guarda y se sirve |
El config son los valores por defecto
Sección titulada «El config son los valores por defecto»Esta es la regla que ordena todo lo demás:
cms.config.ts → valores por defecto ⊕ lo guardado en KV = ajustes efectivosLo que escribes en el bloque email de tu config es el valor de arranque. Lo que se guarda en KV lo
pisa. Y lo guardado es disperso: solo las claves que alguien editó de verdad.
Esa última parte importa más de lo que parece. Si guardara el objeto entero, la primera escritura
congelaría dentro de KV los valores que venían del config, y a partir de ahí cambiar siteName en
cms.config.ts dejaría de surtir efecto aunque nadie hubiera tocado ese campo jamás. Siendo disperso,
cada campo sigue al config hasta el día que se edita, y solo desde ese día manda KV.
Leerlos
Sección titulada «Leerlos»---export const prerender = false
const { siteName, siteUrl } = await Astro.locals.cms.settings.get()---
<footer>{siteName}</footer>get() nunca falla. Si la clave no existe, si el JSON está corrupto, si un campo trae el tipo
equivocado o si siteUrl no es una URL válida, ese campo cae a su valor por defecto y el problema se
registra en la consola. Unos ajustes rotos no pueden tumbar el sitio entero.
Escribirlos
Sección titulada «Escribirlos»await Astro.locals.cms.settings.update({ siteName: 'Mi sitio' })Es una fusión superficial: los campos que el patch no trae conservan su valor. Guardar el formulario de ajustes generales no debe tocar campos que ese formulario ni siquiera muestra.
La validación es estricta. Solo se aceptan esos seis campos, todos los valores tienen que ser
cadenas, y un siteUrl no vacío tiene que ser una URL absoluta http(s) sin espacios ni saltos de
línea. favicon no lleva regla de formato: tan pronto es /logo.svg como una URL completa, y exigir
lo segundo impediría lo más común — servirlo desde tu propio public/. Un incumplimiento es un error de validación con todos los problemas a la vez, no solo el
primero — y por REST, un 400 con su array issues.
La API REST
Sección titulada «La API REST»| Petición | Respuesta |
|---|---|
GET /api/cms/settings |
200 con los seis campos efectivos |
PATCH /api/cms/settings |
200 con el resultado de la fusión |
| Cualquiera de las dos sin sesión | 401 |
| Cualquier otro método | 405 |
Las dos exigen sesión, sin excepción: no hay equivalente al access.read: 'public' de las
colecciones, y emailFrom no es algo que dar a quien pregunte sin identificarse.
Por qué KV y no D1
Sección titulada «Por qué KV y no D1»Los ajustes se leen en casi todas las peticiones y no se escriben casi nunca, que es exactamente el perfil para el que sirve KV. Los documentos son al revés, y por eso viven en D1.
El precio es la consistencia eventual: una escritura tarda hasta un minuto en verse en todas partes. Es tolerable para el nombre del sitio e intolerable para un artículo que el autor acaba de guardar.
La caché
Sección titulada «La caché»Una sola, en el ámbito del isolate, con 60 segundos de vida — el mismo minuto que tarda KV en propagar, ni más ni menos. No tendría sentido prometer una frescura que KV no puede cumplir.
Lo que guarda es la promesa y no el valor, así que dos lecturas concurrentes comparten una sola consulta a KV en vez de lanzar dos.
update() sustituye la entrada en el isolate que escribe. Quien guarda ve su cambio de inmediato;
los demás esperan la propagación.
Lo que todavía no hay
Sección titulada «Lo que todavía no hay»| Cosa | Estado |
|---|---|
Credenciales de OAuth en settings:auth-providers |
❌ Llegan con los proveedores sociales |
Que locale haga algo |
❌ Se guarda y se sirve, nada más |
| Más campos que estos seis | ❌ Un modelo de contenido singular de verdad son los globals |