Saltearse al contenido

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. La app llama al proxy KVS en https://kvs.sureva.com; Sureva controla autorización, aislamiento, límites de tasa y medición antes de que una petición llegue al almacenamiento.

  1. Abre la app en el frontend de Sureva y revisa Servicios → Bases de datos.
  2. Confirma que KVS gestionado respaldado por DynamoDB esté disponible para el tipo de app: api, web-ssr o sse.
  3. Creá una tabla KVS desde el frontend (por ejemplo sessions). Crear la primera tabla activa el servicio — no hay un paso separado de “habilitar”.
  4. Lee SUREVA_KVS_URL y el token de la tabla SUREVA_KVS_TOKEN_<NAME> desde el entorno runtime de Lambda.
  5. Llama a /v1/put, /v1/get y /v1/delete desde la Lambda de la app con AWS SigV4 y X-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.

AspectoComportamiento
Alcance de almacenamientoLos valores quedan aislados por organización, app y entorno.
Exposición públicaLa 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 appEl código usa SUREVA_KVS_URL y un token de tabla SUREVA_KVS_TOKEN_<NAME>; no recibe permisos de DynamoDB.
Límite de tasaUn bucket RPM fijo por app-entorno protege el servicio para todos los métodos KVS.
Respuesta de cuotaCuando se agota el bucket, la API responde 429 con Retry-After.

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.

Para tipos de app basados en Lambda, Sureva deja disponibles estas variables runtime cuando es posible:

VariableSignificado
SUREVA_KVS_URLURL de datos de KVS gestionado, normalmente https://kvs.sureva.com.
SUREVA_KVS_REGIONRegión AWS para la firma SigV4 — la región del gateway KVS (us-east-2). 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.

Las peticiones de producción necesitan dos capas de autorización:

  1. 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.
  2. 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. El gateway KVS vive en us-east-2, pero tu app puede correr en otra región (por ejemplo us-east-1); 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.

POST /v1/put HTTP/1.1
Host: kvs.sureva.com
Content-Type: application/json
X-KVS-Authorization: Bearer <kvs-token>
Authorization: AWS4-HMAC-SHA256 ...
{
"key": "settings/theme",
"value": {
"mode": "dark"
},
"ttlSeconds": 3600
}

Respuesta:

{
"ok": true
}
{
"key": "settings/theme"
}

Respuesta encontrada:

{
"found": true,
"value": {
"mode": "dark"
},
"expiresAt": "2026-06-21T01:00:00Z"
}

Respuesta ausente o expirada:

{
"found": false,
"expiresAt": null
}
{
"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.

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';
const baseUrl = process.env.SUREVA_KVS_URL ?? 'https://kvs.sureva.com';
const region = process.env.SUREVA_KVS_REGION ?? 'us-east-2';
// 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 });
}

Una app puede tener hasta 2 tablas KVS con nombre. Cada tabla tiene su propio espacio de claves aislado, token y cuota de tasa independiente. 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>;
}
// Uso
const 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.

ReglaValor
Cuerpo de petición128 KiB
Clave512 bytes
Valor JSON64 KiB
TTL mínimo60 segundos
TTL máximo365 días
Límite de tasaBucket RPM fijo por app-entorno

Los errores del servicio usan esta forma:

{
"error": "invalid_key"
}
EstadoSignificado
400JSON, clave, valor o TTL inválido.
401Token bearer KVS ausente o inválido.
403Binding inactivo, tipo de app no soportado o rechazo del caller por API Gateway.
413El cuerpo de la petición es demasiado grande.
429Límite de tasa excedido. Lee Retry-After antes de reintentar.
500 / 503Falla del lado del servicio en storage, auth, cuota o dependencias.
  • El tipo de app es api, web-ssr o sse.
  • Se creó al menos una tabla KVS para la app (la primera tabla activa el servicio).
  • El código lee SUREVA_KVS_URL y un token de tabla SUREVA_KVS_TOKEN_<NAME> desde variables runtime.
  • Las peticiones están firmadas con SigV4 para execute-api usando SUREVA_KVS_REGION (la región del gateway, no el AWS_REGION de la app).
  • El token KVS se envía con X-KVS-Authorization, no con el header Authorization de producción.
  • Las respuestas 429 respetan Retry-After antes de reintentar.