KVS gestionado respaldado por DynamoDB
KVS gestionado es el servicio de base de datos respaldado por DynamoDB para apps backend de Sureva. Permite guardar datos clave-valor acotados a la app sin acceso directo a DynamoDB. KVS gestionado es regional: el KVS de cada app vive en la región propia de la app, y tu app lo alcanza en la URL específica de región que está en SUREVA_KVS_URL (por ejemplo https://kvs.sureva.com en eu-central-2 o https://kvs.eu-central-1.sureva.com en eu-central-1). Sureva controla autorización, aislamiento, límites de tasa y medición antes de que una petición llegue al almacenamiento.
Ruta rápida
Sección titulada «Ruta rápida»- Abre la app en el frontend de Sureva y revisa Servicios → Bases de datos.
- Confirma que KVS gestionado respaldado por DynamoDB esté disponible para el tipo de app:
api,web-ssrosse. - Creá una tabla KVS desde el frontend (por ejemplo
sessions). Crear la primera tabla activa el servicio — no hay un paso separado de “habilitar”. - Lee
SUREVA_KVS_URLy el token de la tablaSUREVA_KVS_TOKEN_<NAME>desde el entorno runtime de Lambda. - Llama a
/v1/put,/v1/gety/v1/deletedesde la Lambda de la app con AWS SigV4 yX-KVS-Authorization: Bearer <table-token>.
Las apps estáticas web no pueden usar KVS gestionado porque entregar tokens al navegador expondría credenciales de almacenamiento backend.
Qué proporciona KVS gestionado
Sección titulada «Qué proporciona KVS gestionado»| Aspecto | Comportamiento |
|---|---|
| Alcance de almacenamiento | Los valores quedan aislados por organización, app y entorno. |
| Exposición pública | La API de datos no es una API pública para navegador. En producción, las llamadas deben venir de Lambdas de apps gestionadas por Sureva. |
| Credenciales de app | El código usa SUREVA_KVS_URL y un token de tabla SUREVA_KVS_TOKEN_<NAME>; no recibe permisos de DynamoDB. |
| Límite de tasa | Un bucket RPM fijo por app-entorno (por defecto 600 peticiones/minuto) protege el servicio para todos los métodos KVS. |
| Respuesta de cuota | Cuando se agota el bucket, la API responde 429 con Retry-After. |
Cómo los clientes descubren disponibilidad
Sección titulada «Cómo los clientes descubren disponibilidad»Los clientes descubren las bases de datos disponibles en el frontend de Sureva, dentro de la sección Servicios → Bases de datos de la app. Hoy, el servicio backend disponible es KVS gestionado respaldado por DynamoDB.
El frontend es dueño del provisioning y de la entrega del token. El código de aplicación no debe llamar directamente endpoints de creación del plano de control; solo consume el contrato runtime después de crear una tabla. Las tablas también se pueden gestionar desde la CLI sureva (sureva services kvs tables create, rotate, delete), pero ese camino todavía no está del todo probado — por ahora, preferí el frontend.
Para tipos de app basados en Lambda, Sureva deja disponibles estas variables runtime cuando es posible:
| Variable | Significado |
|---|---|
SUREVA_KVS_URL | URL de datos de KVS gestionado para la región de tu app — por ejemplo https://kvs.sureva.com (eu-central-2) o https://kvs.eu-central-1.sureva.com (eu-central-1). Leé este valor; no hardcodees el host. |
SUREVA_KVS_REGION | Región AWS para la firma SigV4 — la región del gateway KVS de tu app (coincide con la región de tu app, por ejemplo eu-central-2 o eu-central-1). Puede diferir del AWS_REGION propio de tu app, así que firmá siempre con este valor. |
SUREVA_KVS_TOKEN_<NAME> | Token bearer para una tabla KVS. El nombre de la variable es el nombre de la tabla en mayúsculas — por ejemplo, SUREVA_KVS_TOKEN_SESSIONS para una tabla llamada sessions. Solo está presente cuando esa tabla existe. KVS es solo-tablas: no hay token por defecto. |
Llamar al plano de datos
Sección titulada «Llamar al plano de datos»Las peticiones de producción necesitan dos capas de autorización:
- AWS SigV4 en el header estándar
Authorization. API Gateway lo usa para verificar que el caller sea una Lambda autorizada de una app gestionada por Sureva. - Token bearer KVS en
X-KVS-Authorization. El proxy usa este token para vincular la petición a la organización, app y entorno correctos.
No pongas el token KVS en el header estándar Authorization en producción. SigV4 usa ese header.
Firmá con la región de SUREVA_KVS_REGION, no con el AWS_REGION propio de tu app. KVS gestionado es regional: el gateway de tu app vive en la región propia de la app (dada por SUREVA_KVS_REGION, por ejemplo eu-central-2 o eu-central-1), y tu app puede correr en una región distinta; una credencial firmada para una región distinta se rechaza con 403 (Credential should be scoped to a valid region). El dominio personalizado no lleva pista de región, así que el signer no puede inferirla — tomá siempre la región de SUREVA_KVS_REGION.
Guardar un valor
Sección titulada «Guardar un valor»POST /v1/put HTTP/1.1Host: kvs.sureva.comContent-Type: application/jsonX-KVS-Authorization: Bearer <kvs-token>Authorization: AWS4-HMAC-SHA256 ...{ "key": "settings/theme", "value": { "mode": "dark" }, "ttlSeconds": 3600}Respuesta:
{ "ok": true}Leer un valor
Sección titulada «Leer un valor»{ "key": "settings/theme"}Respuesta encontrada:
{ "found": true, "value": { "mode": "dark" }, "expiresAt": "2026-06-21T01:00:00Z"}Respuesta ausente o expirada:
{ "found": false, "expiresAt": null}Eliminar un valor
Sección titulada «Eliminar un valor»{ "key": "settings/theme"}Respuesta:
{ "deleted": true}deleted: true significa que la operación terminó correctamente. No indica que la clave existiera antes de la petición.
Ejemplo TypeScript
Sección titulada «Ejemplo TypeScript»Este helper firma cada petición para API Gateway y envía el token KVS en X-KVS-Authorization. Si tu app no usa TypeScript, usa el signer SigV4 equivalente para tu runtime.
import { Sha256 } from '@aws-crypto/sha256-js';import { defaultProvider } from '@aws-sdk/credential-provider-node';import { HttpRequest } from '@smithy/protocol-http';import { SignatureV4 } from '@smithy/signature-v4';
// Leé la URL específica de región y la región de firma que inyecta Sureva.// No las hardcodees — difieren según la región de la app (por ejemplo eu-central-1 vs eu-central-2).const baseUrl = process.env.SUREVA_KVS_URL;const region = process.env.SUREVA_KVS_REGION;if (!baseUrl || !region) { throw new Error('SUREVA_KVS_URL / SUREVA_KVS_REGION are not set — is managed KVS enabled for this app?');}
// KVS es solo-tablas: leé el token de la tabla que creaste.const tableName = 'sessions';const token = process.env[`SUREVA_KVS_TOKEN_${tableName.toUpperCase()}`];
const signer = new SignatureV4({ credentials: defaultProvider(), region, service: 'execute-api', sha256: Sha256,});
async function kvsRequest<T>(path: string, body: Record<string, unknown>): Promise<T> { if (!token) throw new Error(`KVS table "${tableName}" is not enabled for this app`);
const url = new URL(path, baseUrl); const payload = JSON.stringify(body);
const request = new HttpRequest({ protocol: url.protocol, hostname: url.hostname, path: url.pathname, method: 'POST', headers: { host: url.hostname, 'content-type': 'application/json', 'x-kvs-authorization': `Bearer ${token}`, }, body: payload, });
const signed = await signer.sign(request); const response = await fetch(url, { method: 'POST', headers: signed.headers, body: payload, });
if (response.status === 429) { const retryAfter = response.headers.get('retry-after'); throw new Error(`KVS rate limited; retry after ${retryAfter ?? 'a short delay'}`); }
if (!response.ok) { throw new Error(`KVS request failed: ${response.status} ${await response.text()}`); }
return response.json() as Promise<T>;}
export function putKVS(key: string, value: unknown, ttlSeconds?: number) { return kvsRequest<{ ok: true }>('/v1/put', { key, value, ttlSeconds });}
export function getKVS<T>(key: string) { return kvsRequest<{ found: boolean; value?: T; expiresAt: string | null }>('/v1/get', { key });}
export function deleteKVS(key: string) { return kvsRequest<{ deleted: true }>('/v1/delete', { key });}Namespaces de tabla con nombre
Sección titulada «Namespaces de tabla con nombre»Una app puede tener hasta 3 tablas KVS con nombre (el límite por defecto). Cada tabla tiene su propio espacio de claves aislado y token. Usá tablas con nombre para separar datos por propósito — por ejemplo, sessions y cache — sin interferencia entre ellas.
Cuando se habilita una tabla con nombre, Sureva inyecta SUREVA_KVS_TOKEN_<UPPER(name)> en el entorno runtime de Lambda automáticamente. Cuando se elimina la tabla, la variable se remueve.
Para llamar al plano de datos de una tabla, usá el token de esa tabla en X-KVS-Authorization. La URL del plano de datos (SUREVA_KVS_URL) es la misma para todas las tablas.
function createTableClient(tableName: string) { const varName = `SUREVA_KVS_TOKEN_${tableName.toUpperCase()}`; const tableToken = process.env[varName]; if (!tableToken) throw new Error(`La tabla KVS '${tableName}' no está habilitada para esta app`);
return { put: (key: string, value: unknown, ttlSeconds?: number) => kvsRequestWithToken(tableToken, '/v1/put', { key, value, ttlSeconds }), get: <T>(key: string) => kvsRequestWithToken<{ found: boolean; value?: T; expiresAt: string | null }>(tableToken, '/v1/get', { key }), delete: (key: string) => kvsRequestWithToken<{ deleted: true }>(tableToken, '/v1/delete', { key }), };}
async function kvsRequestWithToken<T>(token: string, path: string, body: Record<string, unknown>): Promise<T> { const url = new URL(path, baseUrl); const payload = JSON.stringify(body); const request = new HttpRequest({ protocol: url.protocol, hostname: url.hostname, path: url.pathname, method: 'POST', headers: { host: url.hostname, 'content-type': 'application/json', 'x-kvs-authorization': `Bearer ${token}` }, body: payload, }); const signed = await signer.sign(request); const response = await fetch(url, { method: 'POST', headers: signed.headers, body: payload }); if (response.status === 429) throw new Error(`KVS con límite de tasa; reintentá después de ${response.headers.get('retry-after') ?? 'un momento'}`); if (!response.ok) throw new Error(`Petición KVS fallida: ${response.status} ${await response.text()}`); return response.json() as Promise<T>;}
// Usoconst sessions = createTableClient('sessions');await sessions.put('user:123', { role: 'admin' }, 3600);Las claves están aisladas por tabla. user:123 en sessions no interfiere con user:123 en otra tabla.
Límites y errores
Sección titulada «Límites y errores»| Regla | Valor |
|---|---|
| Cuerpo de petición | 128 KiB |
| Clave | 512 bytes |
| Valor JSON | 64 KiB |
| TTL mínimo | 60 segundos |
| TTL máximo | 365 días |
| Límite de tasa | 600 peticiones/minuto por app-entorno (por defecto) |
Los errores del servicio usan esta forma:
{ "error": "invalid_key"}| Estado | Significado |
|---|---|
400 | JSON, clave, valor o TTL inválido. |
401 | Token bearer KVS ausente o inválido. |
403 | Binding inactivo, tipo de app no soportado o rechazo del caller por API Gateway. |
413 | El cuerpo de la petición es demasiado grande. |
429 | Límite de tasa excedido. Lee Retry-After antes de reintentar. |
500 / 503 | Falla del lado del servicio en storage, auth, cuota o dependencias. |
Checklist
Sección titulada «Checklist»- El tipo de app es
api,web-ssrosse. - Se creó al menos una tabla KVS para la app (la primera tabla activa el servicio).
- El código lee
SUREVA_KVS_URLy un token de tablaSUREVA_KVS_TOKEN_<NAME>desde variables runtime. - Las peticiones están firmadas con SigV4 para
execute-apiusandoSUREVA_KVS_REGION(la región del gateway, no elAWS_REGIONde la app). - El token KVS se envía con
X-KVS-Authorization, no con el headerAuthorizationde producción. - Las respuestas
429respetanRetry-Afterantes de reintentar.