KV Kit
Store de key-value con soporte para Redis y memoria.
Inicio Rapido
Sección titulada «Inicio Rapido»import { App } from '@iskra-bun/core';import { KVManager } from '@iskra-bun/kv-kit';
const app = new App({ name: 'MiApp', kv: { driver: 'redis', connection: { url: 'redis://localhost:6379' }, },});
const kv = new KVManager();app.register(kv);
await app.start();
// Usar el storeawait kv.set('usuario:123', { name: 'Juan', role: 'admin' });const user = await kv.get('usuario:123');await kv.del('usuario:123');Adapters
Sección titulada «Adapters»Memory (default)
Sección titulada «Memory (default)»Si no configuras nada, usa el adapter de memoria:
const app = new App({ name: 'MiApp' });const kv = new KVManager(); // usa memoria por defectoconnection se pasa directamente al cliente ioredis, por lo que acepta tanto un objeto de opciones (RedisOptions) como una cadena de conexión:
const app = new App({ name: 'MiApp', kv: { driver: 'redis', connection: { url: process.env.REDIS_URL || 'redis://localhost:6379' }, },});
// O una cadena de conexión:const app = new App({ name: 'MiApp', kv: { driver: 'redis', connection: 'redis://localhost:6379' },});app.start() espera a que Redis responda, así que un servidor inalcanzable o una contraseña incorrecta hacen fallar el arranque en vez del primer comando. Una vez conectado, ioredis reintenta si se cae la conexión, y sus errores de conexión van al logger de la app (warn).
// Guardar (con TTL opcional en segundos)await kv.set('clave', valor);await kv.set('clave', valor, 60); // expira en 60 segundos
// Obtener — devuelve undefined si la clave no existeconst data = await kv.get('clave');
// Eliminarawait kv.del('clave');
// Verificar existenciaconst existe = await kv.has('clave');
// Eliminar todas las claves del namespace (ver "Borrar claves")await kv.clear();Cliente nativo de Redis
Sección titulada «Cliente nativo de Redis»Con el driver redis, kv.client es el cliente de ioredis
(despues de app.start()) para comandos que la API KV no cubre (sets, sorted sets,
pipelines). No aplica el namespace ni el codec JSON. Es undefined con el driver
memory. El KVManager tambien queda registrado en el contexto de la app:
const kv = app.context.get('kv'); // el KVManagerawait kv.client?.sadd('tags', 'a', 'b');Valores Genéricos (Tipados)
Sección titulada «Valores Genéricos (Tipados)»get y set aceptan un parámetro de tipo para obtener lecturas tipadas en lugar de any:
interface Usuario { nombre: string; rol: string;}
// Escritura tipadaawait kv.set<Usuario>('usuario:123', { nombre: 'Juan', rol: 'admin' });
// Lectura tipada — resuelve a Usuario | undefinedconst usuario = await kv.get<Usuario>('usuario:123');if (usuario) { console.log(usuario.rol); // string, no any}Una clave inexistente resuelve a undefined (no null). Anteriormente el adaptador Redis podía devolver null para claves inexistentes; ahora está normalizado en todos los adaptadores.
Codec de Valores en Redis
Sección titulada «Codec de Valores en Redis»El adaptador de Redis usa un único codec consistente para cada escritura y lectura: los valores se serializan con JSON.stringify al escribir y se parsean con JSON.parse al leer. Strings, números, booleanos, y objetos y arrays planos de ellos conservan su tipo:
await kv.set('numerica', '123'); // stringtypeof (await kv.get('numerica')); // 'string' — NO se convierte en número 123
await kv.set('jsonish', '{}'); // stringawait kv.get('jsonish'); // '{}' — sigue siendo string, NO un objeto vacío
await kv.set('contador', 42); // numbertypeof (await kv.get('contador')); // 'number'undefined se trata de forma explícita (se almacena como el literal JSON null y se decodifica de vuelta a undefined), por lo que nunca se corrompe en la cadena "undefined". Valores escritos fuera del adaptador que no sean JSON válido se devuelven tal cual (como cadena).
Lo que JSON no puede representar no vuelve igual, a diferencia del adaptador de memoria, que guarda una copia del valor mismo: un Date vuelve como string ISO, un Map o Set como {}, undefined dentro de un array como null, y null como undefined. Esos valores hay que convertirlos a mano (date.toISOString() / new Date(s), Object.fromEntries(map)).
Namespace
Sección titulada «Namespace»Pasa la opción namespace para prefixar automáticamente cada clave y evitar colisiones entre módulos que comparten el mismo store:
const sesiones = new KVManager({ namespace: 'sessions' });const cache = new KVManager({ namespace: 'cache' });
// Escriben en "sessions:token" y "cache:token" — sin colisiónawait sesiones.set('token', datosSesion);await cache.set('token', respuestaCache);El prefijo se aplica de forma transparente; nunca lo incluyes en tus cadenas de clave. La opción tiene como valor predeterminado "" (sin prefijo) para que el código existente no se vea afectado.
Borrar claves
Sección titulada «Borrar claves»kv.clear(prefix?) elimina todas las claves del namespace del manager (solo las que están bajo prefix dentro de él, si se indica), sin tocar la conexión:
- Memoria: se eliminan las claves que coinciden.
- Redis: las claves se buscan con
SCANy se eliminan conDEL, limitadas al namespace (y alkeyPrefixde ioredis, si configuraste uno). Sin namespace,clear()vaciaría toda la base de Redis, que pueden compartir otras apps o servicios, así que lanza un error salvo que lo habilites conflushDb: true, que ejecutaFLUSHDB:
const cache = new KVManager({ namespace: 'cache' });await cache.clear(); // elimina solo cache:*
const propia = new KVManager({ flushDb: true }); // la base es solo de esta appawait propia.clear(); // FLUSHDBEl clear() de cache-kit usa este clear(), así que una Cache sobre un KVManager borra solo sus propias claves.
Sets con expiración
Sección titulada «Sets con expiración»sadd(key, member, ttl?) y sdrain(key) mantienen un conjunto de strings cuyos miembros expiran uno por uno: el conjunto vive lo que su miembro más duradero, y sdrain lo elimina y devuelve sus miembros no expirados en un solo paso (un miembro expirado que aún siga guardado se omite). En Redis es un sorted set ordenado por expiración, actualizado y vaciado por scripts atómicos. cache-kit guarda en ellos su índice de etiquetas. Solo key lleva el namespace; los miembros se guardan tal cual.
Operaciones en Lote
Sección titulada «Operaciones en Lote»KVManager expone tres helpers de lote que ejecutan sus llamadas subyacentes de forma concurrente:
// Leer varias claves a la vez — preserva el orden, undefined para claves inexistentesconst [a, b, c] = await kv.mget<string>(['clave:a', 'clave:b', 'clave:c']);
// Escribir varios pares clave/valor (TTL compartido opcional)await kv.mset({ 'clave:a': 'alfa', 'clave:b': 'beta' });await kv.mset([['clave:c', 'gamma'], ['clave:d', 'delta']], 120); // TTL = 120 s
// Eliminar varias clavesawait kv.mdel(['clave:a', 'clave:b', 'clave:c']);mset acepta tanto un array de tuplas [clave, valor] como un objeto plano.
TTL y el Adaptador de Memoria
Sección titulada «TTL y el Adaptador de Memoria»Un TTL es una cantidad de segundos; 0 (o ninguno) significa sin expiración, y un TTL negativo o no finito se rechaza con un RangeError.
El adaptador en memoria gestiona los temporizadores de expiración sin fugas: sobrescribir una clave con una nueva llamada a set cancela cualquier temporizador previo antes de programar uno nuevo, por lo que un temporizador obsoleto nunca puede eliminar un valor recién escrito. Se admiten TTLs más largos que el límite de setTimeout (unos 24,8 días).
Como Redis, el adaptador en memoria guarda y devuelve copias (structuredClone): modificar un objeto que pasaste a set, o uno que devolvió get, no cambia el valor guardado (antes era el mismo objeto, así que el cambio de una petición aparecía en todas las demás). Un valor que no se puede clonar, como una función, se rechaza; una instancia de clase vuelve como objeto plano.
Variables de Entorno
Sección titulada «Variables de Entorno»REDIS_URL=redis://localhost:6379