1. Introducción

Qué es NyxKV

NyxKV es un almacén key-value compatible con Redis, construido enteramente en Nyx. El daemon compila a un único binario nativo de ~290KB sin dependencias de runtime externas, y soporta 52 comandos en strings, listas, sets, hashes y Pub/Sub.

Arquitectura

Cuándo usar NyxKV

NyxKV es una buena opción cuando querés un store compatible con Redis sin tener que correr y mantener tu propio Redis. Funciona bien para:

2. Primeros pasos

El servicio público de NyxKV requiere TLS y un token. Ambos vienen del dashboard.

Requisitos

Conectarse

$ export TOKEN=<pegá tu token aquí>
$ redis-cli -h nyxkv.com -p 6380 --tls -a $TOKEN PING
PONG

Si recibís Connection reset by peer, te olvidaste de --tls. Si recibís NOAUTH, te olvidaste de -a $TOKEN.

Tus primeros comandos

$ redis-cli -h nyxkv.com -p 6380 --tls -a $TOKEN
nyxkv.com:6380> SET hola "mundo"
OK
nyxkv.com:6380> GET hola
"mundo"

Entendiendo las respuestas

NyxKV habla RESP. Los tipos de respuesta que vas a ver:

3. Conectar desde el dashboard

El servicio público de NyxKV es multi-tenant: no hay acceso anónimo. Cada conexión se autentica con un token emitido por el dashboard.

1. Creá una cuenta

Andá a dashboard.nyxkv.com/signup. Pone un email y una contraseña. No hay tarjeta de crédito ni plan que elegir — el tier gratis es el producto.

2. Generá un token

Abrí dashboard.nyxkv.com/app/keys y hacé clic en Crear token. El valor del token se muestra una sola vez; copialo a un password manager o a una variable de entorno. Podés crear todos los tokens que quieras y revocar cualquiera desde la misma página.

3. Conectarte

Pasá el token como password a cualquier cliente Redis, y activá TLS:

$ redis-cli -h nyxkv.com -p 6380 --tls -a $TOKEN
nyxkv.com:6380> SET hola "mundo"
OK

Errores comunes

Desarrollo local: si corrés el daemon vos mismo con ./nyxkv (sin --public ni --tls), todo este libro aplica también — sacá --tls -a $TOKEN de los ejemplos y el prompt pasa a ser 127.0.0.1:6380>.

4. Strings

Los strings son el tipo de dato más simple. Toda key empieza como string a menos que uses comandos de lista, set o hash.

SET key value

Guarda un string. Sobreescribe cualquier valor existente y limpia el TTL.

> SET usuario:1 "Alicia"
OK

GET key

Devuelve el string. Devuelve (nil) si la key no existe.

> GET usuario:1
"Alicia"
> GET inexistente
(nil)

SETNX key value

Setea el valor solo si la key no existe. Devuelve 1 si seteó, 0 si no.

> SETNX lock:job42 "worker-1"
(integer) 1
> SETNX lock:job42 "worker-2"
(integer) 0
# worker-1 tiene el lock

SETEX key seconds value

Setea un valor con TTL (en segundos), atómicamente.

> SETEX session:abc 3600 "user:1"
OK
# Expira en 1 hora

GETSET key value

Setea un valor nuevo y devuelve el viejo, atómicamente.

> SET counter 10
OK
> GETSET counter 0
"10"
# Devolvió el viejo, ahora counter es 0

INCR key

Incrementa un entero en 1. Crea la key con valor 0 si no existe.

> SET visitas 0
OK
> INCR visitas
(integer) 1
> INCR visitas
(integer) 2

DECR key

Decrementa un entero en 1. Crea la key con valor 0 si no existe.

> SET stock 50
OK
> DECR stock
(integer) 49

APPEND key value

Concatena el valor al final del string. Crea la key si no existe. Devuelve el largo nuevo.

> SET saludo "Hola"
OK
> APPEND saludo " Mundo"
(integer) 10
> GET saludo
"Hola Mundo"

STRLEN key

Devuelve el largo del string en bytes. Devuelve 0 si la key no existe.

> STRLEN saludo
(integer) 10

MSET key value [key value ...]

Setea múltiples pares key-value en un solo comando.

> MSET nombre "Alicia" edad "30" ciudad "BA"
OK

MGET key [key ...]

Obtiene múltiples valores en un solo comando. Devuelve (nil) para keys faltantes.

> MGET nombre edad ciudad falta
1) "Alicia"
2) "30"
3) "BA"
4) (nil)

Patrón: contador atómico

# Contador de page views — sin race conditions
> INCR page:/docs/intro
(integer) 1
> INCR page:/docs/intro
(integer) 2
> GET page:/docs/intro
"2"

5. Listas

Las listas son secuencias ordenadas de strings. Soportan push/pop desde ambos extremos, ideal para colas y stacks.

LPUSH key value [value ...]

Pushea uno o más valores a la cabeza (izquierda). Crea la lista si no existe.

> LPUSH tareas "Deploy v2"
(integer) 1
> LPUSH tareas "Corregir bug #42"
(integer) 2

RPUSH key value [value ...]

Pushea uno o más valores a la cola (derecha).

> RPUSH logs "2024-01-01 started"
(integer) 1
> RPUSH logs "2024-01-01 completed"
(integer) 2

LPOP key

Quita y devuelve el primer elemento (cabeza).

> LPOP tareas
"Corregir bug #42"

RPOP key

Quita y devuelve el último elemento (cola).

> RPOP tareas
"Deploy v2"

LLEN key

Devuelve el número de elementos.

> LPUSH items "a" "b" "c"
(integer) 3
> LLEN items
(integer) 3

LRANGE key start stop

Devuelve elementos de start a stop (inclusive). Soporta índices negativos: -1 es el último.

> RPUSH colores "rojo" "verde" "azul" "amarillo"
(integer) 4
> LRANGE colores 0 -1
1) "rojo"
2) "verde"
3) "azul"
4) "amarillo"
> LRANGE colores 1 2
1) "verde"
2) "azul"

LINDEX key index

Devuelve el elemento en el índice indicado. Soporta negativos.

> LINDEX colores 0
"rojo"
> LINDEX colores -1
"amarillo"

Patrón: cola de jobs

# Producer: pushea jobs a la izquierda
> LPUSH jobs '{"type":"email","to":"alicia@ejemplo.com"}'
(integer) 1
# Consumer: popea desde la derecha (FIFO)
> RPOP jobs
"{\"type\":\"email\",\"to\":\"alicia@ejemplo.com\"}"

6. Sets

Los sets son colecciones desordenadas de strings únicos. Los duplicados se ignoran automáticamente.

SADD key member [member ...]

Agrega uno o más miembros. Devuelve la cantidad de miembros nuevos (ignora duplicados).

> SADD tags "nyx" "rápido" "ligero"
(integer) 3
> SADD tags "nyx"
(integer) 0
# "nyx" ya estaba en el set

SREM key member [member ...]

Quita uno o más miembros. Devuelve la cantidad eliminada.

> SREM tags "ligero"
(integer) 1

SISMEMBER key member

Verifica si un miembro existe. Devuelve 1 o 0.

> SISMEMBER tags "nyx"
(integer) 1
> SISMEMBER tags "lento"
(integer) 0

SMEMBERS key

Devuelve todos los miembros del set.

> SMEMBERS tags
1) "nyx"
2) "rápido"

SCARD key

Devuelve la cardinalidad del set.

> SCARD tags
(integer) 2

Patrón: visitantes únicos

# Trackea visitantes únicos por página
> SADD visitantes:/home "user:1" "user:2" "user:1"
(integer) 2
> SCARD visitantes:/home
(integer) 2
# user:1 contado solo una vez

7. Hashes

Los hashes guardan pares campo-valor bajo una sola key. Pensalos como objetos o diccionarios.

HSET key field value [field value ...]

Setea uno o más pares campo-valor. Devuelve la cantidad de campos nuevos.

> HSET usuario:1 nombre "Alicia" email "alicia@ejemplo.com" edad "30"
(integer) 3

HGET key field

Obtiene el valor de un campo.

> HGET usuario:1 nombre
"Alicia"
> HGET usuario:1 telefono
(nil)

HDEL key field [field ...]

Borra uno o más campos. Devuelve la cantidad eliminada.

> HDEL usuario:1 edad
(integer) 1

HGETALL key

Devuelve todos los campos y valores como array intercalado.

> HGETALL usuario:1
1) "nombre"
2) "Alicia"
3) "email"
4) "alicia@ejemplo.com"

HKEYS key

Devuelve los nombres de campo.

> HKEYS usuario:1
1) "nombre"
2) "email"

HVALS key

Devuelve los valores.

> HVALS usuario:1
1) "Alicia"
2) "alicia@ejemplo.com"

HEXISTS key field

Verifica si un campo existe. Devuelve 1 o 0.

> HEXISTS usuario:1 email
(integer) 1

HLEN key

Devuelve la cantidad de campos.

> HLEN usuario:1
(integer) 2

Patrón: perfil de usuario

# Guardá datos estructurados como hash
> HSET producto:42 nombre "Widget" precio "19.99" stock "150"
(integer) 3
# Leé un solo campo
> HGET producto:42 precio
"19.99"
# Actualizá un campo sin tocar los otros
> HSET producto:42 stock "149"
(integer) 0

8. Gestión de keys

DEL key [key ...]

Borra una o más keys de cualquier tipo. Devuelve la cantidad borrada.

> SET temp "data"
OK
> DEL temp
(integer) 1
> DEL temp
(integer) 0

EXISTS key

Verifica si una key existe (cualquier tipo). Devuelve 1 o 0.

> EXISTS usuario:1
(integer) 1
> EXISTS inexistente
(integer) 0

TYPE key

Devuelve el tipo del valor: string, list, set, hash o none.

> SET nombre "Alicia"
OK
> LPUSH items "a"
(integer) 1
> TYPE nombre
string
> TYPE items
list

KEYS pattern

Lista keys que matchean el pattern. Usá * para todas.

> KEYS *
1) "usuario:1"
2) "nombre"
3) "items"
> KEYS usuario:*
1) "usuario:1"
Nota: en el servicio público, KEYS solo devuelve keys de tu namespace, con un máximo de 100 resultados.

DBSIZE

Devuelve la cantidad de keys en tu namespace.

> DBSIZE
(integer) 3

9. Expiración y TTL

Cualquier key puede tener un TTL. Cuando expira, se borra automáticamente al próximo acceso (lazy deletion).

EXPIRE key seconds

Setea un timeout en una key. Después de seconds segundos, la key se borra. Devuelve 1 si se seteó, 0 si la key no existe.

> SET session "active"
OK
> EXPIRE session 3600
(integer) 1
# La sesión expira en 1 hora

TTL key

Devuelve el tiempo restante en segundos.

> TTL session
(integer) 3598
> TTL usuario:1
(integer) -1
> TTL inexistente
(integer) -2

PERSIST key

Quita la expiración de una key. Devuelve 1 si había TTL, 0 si no.

> PERSIST session
(integer) 1
> TTL session
(integer) -1
Tier gratis: en el servicio público, toda key tiene un TTL fijo de 24 horas. Los EXPIRE con valores mayores a 86.400 segundos se truncan a 86.400. PERSIST no puede sacar el cap.

Patrón: sesión con auto-expiración

# Sesión que expira en 30 minutos
> SETEX sess:abc123 1800 '{"user":"alicia","role":"admin"}'
OK
# Chequear tiempo restante
> TTL sess:abc123
(integer) 1798
# Extender en cada actividad
> EXPIRE sess:abc123 1800
(integer) 1

10. Pub/Sub

Pub/Sub habilita mensajería en tiempo real entre clientes. Los publishers mandan mensajes a canales; todos los subscribers de ese canal los reciben al toque.

SUBSCRIBE channel

Se suscribe a un canal. La conexión entra en modo subscriber y bloquea, recibiendo mensajes a medida que llegan.

# Terminal 1: subscriber
> SUBSCRIBE notificaciones
1) "subscribe"
2) "notificaciones"
3) (integer) 1
# Esperando mensajes...

PUBLISH channel message

Manda un mensaje a todos los subscribers del canal. Devuelve cuántos lo recibieron.

# Terminal 2: publisher
> PUBLISH notificaciones "Nuevo pedido #1234"
(integer) 1

El subscriber en la Terminal 1 recibe:

1) "message"
2) "notificaciones"
3) "Nuevo pedido #1234"

UNSUBSCRIBE channel

Se desuscribe de un canal y sale del modo subscriber.

Limitación: en modo subscriber, solo se aceptan SUBSCRIBE, UNSUBSCRIBE y PING. Pub/Sub no está disponible sobre conexiones TLS, así que los subscribers tienen que usar el daemon local ./nyxkv durante el desarrollo — el endpoint público con TLS acepta todos los demás comandos.

Patrón: notificaciones en tiempo real

# Sala de chat
> SUBSCRIBE chat:general
# Otro cliente:
> PUBLISH chat:general "alicia: ¡Hola a todos!"
# Todos los subscribers reciben el mensaje

11. Persistencia

NyxKV escribe los datos a disco como snapshots binarios en un archivo .ndb con checksum CRC32.

Cómo funciona

Comportamiento de SIGTERM: el handler de SIGTERM que antes flusheaba al cerrar está intencionalmente desactivado en el build actual — corría en un contexto async-signal-unsafe y corrompía snapshots. El bg-saver cubre los reinicios normales; el peor caso (kill -9) pierde ≤1 s.

SAVE

Dispara un snapshot sincrónico a dump.ndb. Bloquea hasta terminar.

> SAVE
OK

BGSAVE

Dispara un snapshot en background. Devuelve al toque.

> BGSAVE
Background saving started

LASTSAVE

Devuelve el timestamp Unix del último save exitoso.

> LASTSAVE
(integer) 1711987200
En el servicio público: SAVE y BGSAVE son comandos de admin y no están expuestos a las conexiones de usuario. El bg-saver corre incondicionalmente para cada cuenta.

Formato .ndb

Snapshot binario con header, entries y checksum CRC32:

Si el CRC32 no matchea al cargar, NyxKV reporta corrupción y rehúsa arrancar, en vez de perder datos silenciosamente.

12. Autenticación y multi-tenant

El servicio público de NyxKV es multi-tenant. Cada cuenta autenticada tiene su propio namespace, y los tokens se emiten y revocan desde el dashboard, no desde el protocolo.

Conectarse con un token

AUTH token

Autentica la conexión. Todos los comandos posteriores operan en el namespace de tu cuenta. La mayoría de los clientes lo llaman por vos cuando pasás un password.

> AUTH a1b2c3d4e5f6...
OK
> SET mikey "midata"
OK
# Internamente guardado como: alicia::mikey

WHOAMI

Devuelve el estado actual de autenticación.

> WHOAMI
"alicia:free"

Cómo funciona el aislamiento de namespaces

Cuando estás autenticado, todas las keys se prefijean transparentemente con el id de tu cuenta. Esto significa:

Los tokens se manejan desde el dashboard

Para crear, listar o revocar tokens, entrá a dashboard.nyxkv.com/app/keys:

No hay comandos TOKEN_CREATE/TOKEN_REVOKE/TOKEN_LIST en el servicio público. Esos existen en el daemon pero están restringidos a conexiones de admin, así que el dashboard es la única fuente de verdad sobre quién tiene acceso.

Patrón: tokens por entorno

Emití tokens separados para staging y producción. Si un token se filtra, revocá solo ese sin afectar el otro entorno:

13. Librerías cliente

NyxKV habla RESP, así que cualquier librería cliente de Redis funciona. El servicio público requiere TLS y un token — los snippets de abajo muestran el connect mínimo viable en lenguajes populares.

Python (redis-py)

import os, redis

r = redis.Redis(
    host="nyxkv.com",
    port=6380,
    ssl=True,
    password=os.environ["NYXKV_TOKEN"],
)

r.set("key", "value")
val = r.get("key")         # b"value"
r.incr("counter")
r.lpush("queue", "job1")
r.hset("user:1", mapping={"name": "Alicia", "age": "30"})

Node.js (ioredis)

import Redis from "ioredis";

const r = new Redis({
  host: "nyxkv.com",
  port: 6380,
  tls: {},                     // activa TLS
  password: process.env.NYXKV_TOKEN,
});

await r.set("key", "value");
const val = await r.get("key");  // "value"
await r.lpush("queue", "job1");
await r.hset("user:1", "name", "Alicia");

Go (go-redis)

import (
    "crypto/tls"
    "os"
    "github.com/redis/go-redis/v9"
)

rdb := redis.NewClient(&redis.Options{
    Addr:      "nyxkv.com:6380",
    Password:  os.Getenv("NYXKV_TOKEN"),
    TLSConfig: &tls.Config{},
})

rdb.Set(ctx, "key", "value", 0)
val, _ := rdb.Get(ctx, "key").Result()
rdb.LPush(ctx, "queue", "job1")
rdb.HSet(ctx, "user:1", "name", "Alicia")

Rust (redis-rs)

let token = std::env::var("NYXKV_TOKEN")?;
let url = format!("rediss://:{}@nyxkv.com:6380", token);

let client = redis::Client::open(url)?;
let mut con = client.get_connection()?;

redis::cmd("SET").arg("key").arg("value").execute(&mut con);
let val: String = redis::cmd("GET").arg("key").query(&mut con)?;
Desarrollo local: si estás corriendo ./nyxkv en tu laptop sin --public ni --tls, sacá la config de TLS y el password de estos snippets, y apuntá a localhost:6380.

14. Patrones y recetas

Session store

Usá SETEX para guardar sesiones con expiración automática.

# Crear sesión (TTL de 30 min)
> SETEX sess:abc123 1800 '{"user":"alicia","role":"admin"}'
OK
# Leer sesión
> GET sess:abc123
"{\"user\":\"alicia\",\"role\":\"admin\"}"
# Extender en cada actividad
> EXPIRE sess:abc123 1800
(integer) 1
# Logout
> DEL sess:abc123
(integer) 1

Rate limiter

Usá INCR con EXPIRE para implementar un rate limiter por key.

# Trackear requests por usuario por minuto
> INCR rate:user:42
(integer) 1
> EXPIRE rate:user:42 60
(integer) 1
# En cada request: INCR, chequear si supera el umbral, rechazar
> GET rate:user:42
"47"

Cache con TTL

Cacheá cómputos caros con invalidación automática.

# Cachear respuesta de API por 5 minutos
> SETEX cache:api:/users 300 '[{"id":1,"name":"Alicia"}]'
OK
# Chequear cache antes de llamar a la API
> GET cache:api:/users
"[{\"id\":1,\"name\":\"Alicia\"}]"
# Cache miss → llamar API → SETEX de nuevo

Cola de jobs (FIFO)

Usá LPUSH + RPOP para una cola first-in-first-out.

# Encolar jobs
> LPUSH jobs '{"task":"send_email","to":"alicia@ejemplo.com"}'
(integer) 1
> LPUSH jobs '{"task":"resize_image","id":42}'
(integer) 2
# Desencolar (más viejo primero)
> RPOP jobs
"{\"task\":\"send_email\",\"to\":\"alicia@ejemplo.com\"}"
# Largo de la cola
> LLEN jobs
(integer) 1

Configuración

Usá hashes para guardar configuración con updates por campo.

> HSET config:app max_retries "3" timeout "30" debug "false"
(integer) 3
# Actualizar un setting sin tocar los otros
> HSET config:app debug "true"
(integer) 0
# Leer toda la config
> HGETALL config:app
1) "max_retries"
2) "3"
3) "timeout"
4) "30"
5) "debug"
6) "true"

Leaderboard

Usá INCR para tracking simple de scores.

> INCR score:alicia
(integer) 1
> INCR score:alicia
(integer) 2
> INCR score:bob
(integer) 1
> MGET score:alicia score:bob
1) "2"
2) "1"

Feature flags

Usá sets para gestionar feature flags por grupo de usuarios.

> SADD feature:dark-mode "user:1" "user:3" "user:7"
(integer) 3
# Chequear si el usuario tiene la feature
> SISMEMBER feature:dark-mode "user:3"
(integer) 1
> SISMEMBER feature:dark-mode "user:5"
(integer) 0

15. Límites

Hay un solo tier en NyxKV: free. No hay plan Pro. No hay plan Enterprise. El tier gratis es el producto.

Lo que recibís

LímiteValor
Requests por segundo1000
Keys máximas10.000
Tamaño máximo de valor1 MB
TTL en cada key24 horas
TLSobligatorio
Aislamiento multi-tenantincluido
Pub/Subincluido (sobre TCP plano, ver capítulo 10)

Detalle del tier gratis

Cómo funciona el rate limiting

Por cuenta, ventana deslizante: cada conexión cuenta contra el budget de tu cuenta. Cuando se supera el límite, el servidor responde:

-ERR rate limit exceeded

Precio

$0. Para siempre. Sin tarjeta de crédito. Sin prompts de upgrade. Si te queda chico el tier gratis, te quedó chico el servicio — corré el daemon open source en tu propio host.

Comandos de servidor

PING [message]

Devuelve PONG, o hace eco del mensaje. Útil para health checks y mediciones de latencia.

INFO

Devuelve estadísticas del servidor en formato Redis INFO: versión, total de comandos procesados, info del keyspace.

CONFIG GET save

Stub para compatibilidad con redis-benchmark. Deshabilitado en el servicio público.

FLUSHDB / FLUSHALL

Solo admin en el daemon. No están disponibles en el servicio público.

16. Memoria de agente (beta)

Qué es

Agent Memory es memoria privada y persistente para agentes AI y las apps que se construyen alrededor de ellos — sobre NyxKV mismo, no un servicio aparte. Resuelve dos cosas que un agente de código no te da solo: memoria que sobrevive al cierre de una sesión, y memoria que te sigue entre máquinas.

Por debajo es simplemente claves. Las entradas viven en mem:<colección>:<clave> dentro de tu namespace normal de token, aisladas exactamente igual que cualquier otra clave (ver capítulo 12). No hay cuenta separada, ni token separado, ni protocolo separado — cualquier cosa que hable RESP puede leer y escribir memoria de agente.

Primeros pasos

Cuatro pasos: token, SDK, un round-trip, y el plugin de Claude Code.

1. Token

El mismo token que usás en el resto de NyxKV. Creá uno en dashboard.nyxkv.com/app/keys y guardalo donde el SDK lo busca por defecto:

$ umask 077 && printf '%s' 'TU_TOKEN' > ~/.nyxkv-token

2. Compilar el SDK

nyx-kv-sdk es un CLI chico en Nyx que envuelve el modelo de colección/clave de abajo. Cloná nyx-kv y compilalo con el toolchain de Nyx:

$ cd sdk && nyx build
$ ./nyx-kv-sdk help

3. Guardar y leer una entrada

$ ./nyx-kv-sdk put notas readme "primera entrada"
OK
$ ./nyx-kv-sdk get notas readme
primera entrada

4. Instalar el plugin de Claude Code

sdk/claude/ es un plugin drop-in con dos skills centrales, memory-pull y memory-push (más friction-share / friction-review opcionales, para dejar notas entre instancias de agente). Copialo a donde Claude Code carga tus plugins y después simplemente pedíselo en lenguaje natural: “restaurá mi memoria” al empezar una sesión, “guardá mi memoria” antes de cerrarla. Las dos llaman al SDK por debajo.

5. Sincronizar entre máquinas

El plugin envuelve exactamente esto — corré el SDK directo si querés sincronizar un directorio de archivos markdown sin un agente en el medio:

# máquina A
$ ./nyx-kv-sdk sync-push miproyecto ./notas
sync-push: 3 archivo(s) de ./notas → colección miproyecto
# máquina B, mismo token
$ ./nyx-kv-sdk sync-pull miproyecto ./notas
sync-pull: 3 archivo(s) de colección miproyecto → ./notas

El modelo de claves mem:*

Todo vive bajo el prefijo mem:, que lo distingue visualmente de claves de cache o sesión y le permite al dashboard enumerarlo sin escanear todo tu namespace:

Una “colección” es simplemente un namespace dentro del keyspace de tu token — un proyecto, un tema, cualquier agrupación que te sirva. El plugin de Claude Code usa por defecto una colección por proyecto (agentmem-<nombre del directorio del proyecto>), configurable con la variable de entorno NYXKV_MEMORY_COLLECTION.

Los valores de las entradas viajan codificados en base64 con prefijo b64:, para que el markdown multilínea sobreviva intacto a RESP. El SDK y el plugin codifican/decodifican esto de forma transparente — si inspeccionás una entrada directo con redis-cli GET, vas a ver la forma cruda b64:..., no texto plano.

Ver tu memoria

El dashboard tiene un visor integrado en dashboard.nyxkv.com/app/memory. Lista todas las colecciones y claves bajo el namespace mem:* de tu token, te deja abrir una entrada para leer el contenido ya decodificado, y borrar entradas individuales. Reusa tu sesión del dashboard — no hay un login separado para memoria.

Límites (beta)

Agent Memory es beta. Las restricciones honestas, para que no haya sorpresas:

Libro NyxKV — nyxkv.com — construido con Nyx