Colecciones Y Claves
KVNode organiza los datos en colecciones, cada una contiene pares clave-valor. Este documento cubre la gestión de colecciones y las operaciones con claves en detalle.
Gestión de Colecciones
Crear una Colección
curl -X POST "https://api.hola.cloud/v1/collections" \
-H "apikey: tu-api-key" \
-H "secret: tu-api-secret" \
-d '{"name": "mi-coleccion"}'POST /v1/collections HTTP/1.1
Host: api.hola.cloud
apikey: tu-api-key
secret: tu-api-secret
{"name": "mi-coleccion"}package main
import (
"fmt"
"io"
"net/http"
"encoding/json"
"strings"
)
func main() {
payload := map[string]any{"name": "mi-coleccion"}
bodyBytes, err := json.Marshal(payload)
if err != nil {
panic(err)
}
body := string(bodyBytes)
req, err := http.NewRequest("POST", "https://api.hola.cloud/v1/collections", strings.NewReader(body))
if err != nil {
panic(err)
}
req.Header.Set("apikey", "tu-api-key")
req.Header.Set("secret", "tu-api-secret")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println(string(responseBody))
}
<?php
$payload = ['name' => 'mi-coleccion'];
$body = json_encode($payload);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.hola.cloud/v1/collections',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
'apikey: tu-api-key',
'secret: tu-api-secret',
],
]);
$response = curl_exec($ch);
if ($response === false) {
throw new Exception(curl_error($ch));
}
curl_close($ch);
echo $response;
import requests
import json
headers = {
"apikey": "tu-api-key",
"secret": "tu-api-secret",
}
payload = {"name": "mi-coleccion"}
body = json.dumps(payload)
response = requests.request(
"POST",
"https://api.hola.cloud/v1/collections",
headers=headers,
data=body
)
print(response.text)
const payload = {"name": "mi-coleccion"};
const response = await fetch("https://api.hola.cloud/v1/collections", {
method: "POST",
headers: {
"apikey": "tu-api-key",
"secret": "tu-api-secret"
},
body: JSON.stringify(payload)
});
console.log(await response.text());
const payload = {"name": "mi-coleccion"};
const response = await fetch("https://api.hola.cloud/v1/collections", {
method: "POST",
headers: {
"apikey": "tu-api-key",
"secret": "tu-api-secret"
},
body: JSON.stringify(payload)
});
const text = await response.text();
console.log(text);
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Map;
import java.util.List;
public class Main {
public static void main(String[] args) throws Exception {
var payload = Map.of("name", "mi-coleccion");
var body = new ObjectMapper().writeValueAsString(payload);
var request = HttpRequest.newBuilder()
.uri(URI.create("https://api.hola.cloud/v1/collections"))
.method("POST", HttpRequest.BodyPublishers.ofString(body))
.header("apikey", "tu-api-key")
.header("secret", "tu-api-secret")
.build();
var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}Respuesta:
1{"ok":true,"collection":"mi-coleccion"}
Las colecciones se crean al instante. Si la colección ya existe, la operación es idempotente.
Listar Colecciones
curl "https://api.hola.cloud/v1/collections" \
-H "apikey: tu-api-key" \
-H "secret: tu-api-secret"GET /v1/collections HTTP/1.1
Host: api.hola.cloud
apikey: tu-api-key
secret: tu-api-secret
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, err := http.NewRequest("GET", "https://api.hola.cloud/v1/collections", nil)
if err != nil {
panic(err)
}
req.Header.Set("apikey", "tu-api-key")
req.Header.Set("secret", "tu-api-secret")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println(string(responseBody))
}
<?php
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.hola.cloud/v1/collections',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'apikey: tu-api-key',
'secret: tu-api-secret',
],
]);
$response = curl_exec($ch);
if ($response === false) {
throw new Exception(curl_error($ch));
}
curl_close($ch);
echo $response;
import requests
headers = {
"apikey": "tu-api-key",
"secret": "tu-api-secret",
}
response = requests.request(
"GET",
"https://api.hola.cloud/v1/collections",
headers=headers
)
print(response.text)
const response = await fetch("https://api.hola.cloud/v1/collections", {
method: "GET",
headers: {
"apikey": "tu-api-key",
"secret": "tu-api-secret"
}
});
console.log(await response.text());
const response = await fetch("https://api.hola.cloud/v1/collections", {
method: "GET",
headers: {
"apikey": "tu-api-key",
"secret": "tu-api-secret"
}
});
const text = await response.text();
console.log(text);
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
var request = HttpRequest.newBuilder()
.uri(URI.create("https://api.hola.cloud/v1/collections"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.header("apikey", "tu-api-key")
.header("secret", "tu-api-secret")
.build();
var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}Respuesta:
1{"collections":["mi-coleccion","config","sesiones"]}
Eliminar una Colección
curl -X DELETE "https://api.hola.cloud/v1/collections/mi-coleccion" \
-H "apikey: tu-api-key" \
-H "secret: tu-api-secret"DELETE /v1/collections/mi-coleccion HTTP/1.1
Host: api.hola.cloud
apikey: tu-api-key
secret: tu-api-secret
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, err := http.NewRequest("DELETE", "https://api.hola.cloud/v1/collections/mi-coleccion", nil)
if err != nil {
panic(err)
}
req.Header.Set("apikey", "tu-api-key")
req.Header.Set("secret", "tu-api-secret")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println(string(responseBody))
}
<?php
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.hola.cloud/v1/collections/mi-coleccion',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'apikey: tu-api-key',
'secret: tu-api-secret',
],
]);
$response = curl_exec($ch);
if ($response === false) {
throw new Exception(curl_error($ch));
}
curl_close($ch);
echo $response;
import requests
headers = {
"apikey": "tu-api-key",
"secret": "tu-api-secret",
}
response = requests.request(
"DELETE",
"https://api.hola.cloud/v1/collections/mi-coleccion",
headers=headers
)
print(response.text)
const response = await fetch("https://api.hola.cloud/v1/collections/mi-coleccion", {
method: "DELETE",
headers: {
"apikey": "tu-api-key",
"secret": "tu-api-secret"
}
});
console.log(await response.text());
const response = await fetch("https://api.hola.cloud/v1/collections/mi-coleccion", {
method: "DELETE",
headers: {
"apikey": "tu-api-key",
"secret": "tu-api-secret"
}
});
const text = await response.text();
console.log(text);
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
var request = HttpRequest.newBuilder()
.uri(URI.create("https://api.hola.cloud/v1/collections/mi-coleccion"))
.method("DELETE", HttpRequest.BodyPublishers.noBody())
.header("apikey", "tu-api-key")
.header("secret", "tu-api-secret")
.build();
var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}Respuesta:
1{"ok":true,"collection":"mi-coleccion"}
Eliminar una colección borra todas las claves almacenadas en ella. Esta operación no se puede deshacer.
Operaciones con Claves
Las claves de KVNode son cadenas de texto que pueden contener cualquier carácter UTF-8. Los valores deben ser JSON válido.
Establecer una Clave
curl -X POST "https://api.hola.cloud/v1/collections/mi-coleccion/keys/config:tema" \
-H "apikey: tu-api-key" \
-H "secret: tu-api-secret" \
-d '{"value": {"modo": "oscuro", "tamanoFuente": 14}}'POST /v1/collections/mi-coleccion/keys/config:tema HTTP/1.1
Host: api.hola.cloud
apikey: tu-api-key
secret: tu-api-secret
{"value": {"modo": "oscuro", "tamanoFuente": 14}}package main
import (
"fmt"
"io"
"net/http"
"encoding/json"
"strings"
)
func main() {
payload := map[string]any{"value": map[string]any{"modo": "oscuro", "tamanoFuente": 14}}
bodyBytes, err := json.Marshal(payload)
if err != nil {
panic(err)
}
body := string(bodyBytes)
req, err := http.NewRequest("POST", "https://api.hola.cloud/v1/collections/mi-coleccion/keys/config:tema", strings.NewReader(body))
if err != nil {
panic(err)
}
req.Header.Set("apikey", "tu-api-key")
req.Header.Set("secret", "tu-api-secret")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println(string(responseBody))
}
<?php
$payload = ['value' => ['modo' => 'oscuro', 'tamanoFuente' => 14]];
$body = json_encode($payload);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.hola.cloud/v1/collections/mi-coleccion/keys/config:tema',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
'apikey: tu-api-key',
'secret: tu-api-secret',
],
]);
$response = curl_exec($ch);
if ($response === false) {
throw new Exception(curl_error($ch));
}
curl_close($ch);
echo $response;
import requests
import json
headers = {
"apikey": "tu-api-key",
"secret": "tu-api-secret",
}
payload = {"value": {"modo": "oscuro", "tamanoFuente": 14}}
body = json.dumps(payload)
response = requests.request(
"POST",
"https://api.hola.cloud/v1/collections/mi-coleccion/keys/config:tema",
headers=headers,
data=body
)
print(response.text)
const payload = {"value": {"modo": "oscuro", "tamanoFuente": 14}};
const response = await fetch("https://api.hola.cloud/v1/collections/mi-coleccion/keys/config:tema", {
method: "POST",
headers: {
"apikey": "tu-api-key",
"secret": "tu-api-secret"
},
body: JSON.stringify(payload)
});
console.log(await response.text());
const payload = {"value": {"modo": "oscuro", "tamanoFuente": 14}};
const response = await fetch("https://api.hola.cloud/v1/collections/mi-coleccion/keys/config:tema", {
method: "POST",
headers: {
"apikey": "tu-api-key",
"secret": "tu-api-secret"
},
body: JSON.stringify(payload)
});
const text = await response.text();
console.log(text);
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Map;
import java.util.List;
public class Main {
public static void main(String[] args) throws Exception {
var payload = Map.of("value", Map.of("modo", "oscuro", "tamanoFuente", 14));
var body = new ObjectMapper().writeValueAsString(payload);
var request = HttpRequest.newBuilder()
.uri(URI.create("https://api.hola.cloud/v1/collections/mi-coleccion/keys/config:tema"))
.method("POST", HttpRequest.BodyPublishers.ofString(body))
.header("apikey", "tu-api-key")
.header("secret", "tu-api-secret")
.build();
var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}La respuesta incluye el número de secuencia y la versión:
1{"ok":true,"seq":3,"version":1}
Cada escritura exitosa devuelve un seq (secuencia global) y un version (versión por clave) que aumentan monotónicamente, útiles para control de concurrencia optimista.
Obtener una Clave
curl "https://api.hola.cloud/v1/collections/mi-coleccion/keys/config:tema" \
-H "apikey: tu-api-key" \
-H "secret: tu-api-secret"GET /v1/collections/mi-coleccion/keys/config:tema HTTP/1.1
Host: api.hola.cloud
apikey: tu-api-key
secret: tu-api-secret
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, err := http.NewRequest("GET", "https://api.hola.cloud/v1/collections/mi-coleccion/keys/config:tema", nil)
if err != nil {
panic(err)
}
req.Header.Set("apikey", "tu-api-key")
req.Header.Set("secret", "tu-api-secret")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println(string(responseBody))
}
<?php
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.hola.cloud/v1/collections/mi-coleccion/keys/config:tema',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'apikey: tu-api-key',
'secret: tu-api-secret',
],
]);
$response = curl_exec($ch);
if ($response === false) {
throw new Exception(curl_error($ch));
}
curl_close($ch);
echo $response;
import requests
headers = {
"apikey": "tu-api-key",
"secret": "tu-api-secret",
}
response = requests.request(
"GET",
"https://api.hola.cloud/v1/collections/mi-coleccion/keys/config:tema",
headers=headers
)
print(response.text)
const response = await fetch("https://api.hola.cloud/v1/collections/mi-coleccion/keys/config:tema", {
method: "GET",
headers: {
"apikey": "tu-api-key",
"secret": "tu-api-secret"
}
});
console.log(await response.text());
const response = await fetch("https://api.hola.cloud/v1/collections/mi-coleccion/keys/config:tema", {
method: "GET",
headers: {
"apikey": "tu-api-key",
"secret": "tu-api-secret"
}
});
const text = await response.text();
console.log(text);
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
var request = HttpRequest.newBuilder()
.uri(URI.create("https://api.hola.cloud/v1/collections/mi-coleccion/keys/config:tema"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.header("apikey", "tu-api-key")
.header("secret", "tu-api-secret")
.build();
var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}Respuesta:
1{"key":"config:tema","value":{"modo":"oscuro","tamanoFuente":14},"version":1,"updatedAt":"2025-01-01T00:00:00Z"}
Intentar obtener una clave que no existe devuelve un error 404.
Eliminar una Clave
curl -X DELETE "https://api.hola.cloud/v1/collections/mi-coleccion/keys/config:tema" \
-H "apikey: tu-api-key" \
-H "secret: tu-api-secret"DELETE /v1/collections/mi-coleccion/keys/config:tema HTTP/1.1
Host: api.hola.cloud
apikey: tu-api-key
secret: tu-api-secret
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, err := http.NewRequest("DELETE", "https://api.hola.cloud/v1/collections/mi-coleccion/keys/config:tema", nil)
if err != nil {
panic(err)
}
req.Header.Set("apikey", "tu-api-key")
req.Header.Set("secret", "tu-api-secret")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println(string(responseBody))
}
<?php
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.hola.cloud/v1/collections/mi-coleccion/keys/config:tema',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'apikey: tu-api-key',
'secret: tu-api-secret',
],
]);
$response = curl_exec($ch);
if ($response === false) {
throw new Exception(curl_error($ch));
}
curl_close($ch);
echo $response;
import requests
headers = {
"apikey": "tu-api-key",
"secret": "tu-api-secret",
}
response = requests.request(
"DELETE",
"https://api.hola.cloud/v1/collections/mi-coleccion/keys/config:tema",
headers=headers
)
print(response.text)
const response = await fetch("https://api.hola.cloud/v1/collections/mi-coleccion/keys/config:tema", {
method: "DELETE",
headers: {
"apikey": "tu-api-key",
"secret": "tu-api-secret"
}
});
console.log(await response.text());
const response = await fetch("https://api.hola.cloud/v1/collections/mi-coleccion/keys/config:tema", {
method: "DELETE",
headers: {
"apikey": "tu-api-key",
"secret": "tu-api-secret"
}
});
const text = await response.text();
console.log(text);
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
var request = HttpRequest.newBuilder()
.uri(URI.create("https://api.hola.cloud/v1/collections/mi-coleccion/keys/config:tema"))
.method("DELETE", HttpRequest.BodyPublishers.noBody())
.header("apikey", "tu-api-key")
.header("secret", "tu-api-secret")
.build();
var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}Respuesta:
1{"ok":true,"seq":4,"version":2}
Listar Claves con Prefijo y Límite
curl "https://api.hola.cloud/v1/collections/mi-coleccion/keys?prefix=config:&limit=20" \
-H "apikey: tu-api-key" \
-H "secret: tu-api-secret"GET /v1/collections/mi-coleccion/keys?prefix=config:&limit=20 HTTP/1.1
Host: api.hola.cloud
apikey: tu-api-key
secret: tu-api-secret
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, err := http.NewRequest("GET", "https://api.hola.cloud/v1/collections/mi-coleccion/keys?prefix=config:&limit=20", nil)
if err != nil {
panic(err)
}
req.Header.Set("apikey", "tu-api-key")
req.Header.Set("secret", "tu-api-secret")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println(string(responseBody))
}
<?php
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.hola.cloud/v1/collections/mi-coleccion/keys?prefix=config:&limit=20',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'apikey: tu-api-key',
'secret: tu-api-secret',
],
]);
$response = curl_exec($ch);
if ($response === false) {
throw new Exception(curl_error($ch));
}
curl_close($ch);
echo $response;
import requests
headers = {
"apikey": "tu-api-key",
"secret": "tu-api-secret",
}
response = requests.request(
"GET",
"https://api.hola.cloud/v1/collections/mi-coleccion/keys?prefix=config:&limit=20",
headers=headers
)
print(response.text)
const response = await fetch("https://api.hola.cloud/v1/collections/mi-coleccion/keys?prefix=config:&limit=20", {
method: "GET",
headers: {
"apikey": "tu-api-key",
"secret": "tu-api-secret"
}
});
console.log(await response.text());
const response = await fetch("https://api.hola.cloud/v1/collections/mi-coleccion/keys?prefix=config:&limit=20", {
method: "GET",
headers: {
"apikey": "tu-api-key",
"secret": "tu-api-secret"
}
});
const text = await response.text();
console.log(text);
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
var request = HttpRequest.newBuilder()
.uri(URI.create("https://api.hola.cloud/v1/collections/mi-coleccion/keys?prefix=config:&limit=20"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.header("apikey", "tu-api-key")
.header("secret", "tu-api-secret")
.build();
var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}Tanto prefix como limit son parámetros de consulta opcionales:
prefix— filtra las claves que comienzan con la cadena indicadalimit— limita la cantidad de registros devueltos (por defecto: sin límite)
Respuesta:
1[
2 {"key":"config:tema","value":{"modo":"oscuro","tamanoFuente":14},"version":1,"updatedAt":"2025-01-01T00:00:00Z"},
3 {"key":"config:idioma","value":"es-ES","version":1,"updatedAt":"2025-01-01T00:00:01Z"}
4]
Convenciones para Nombres de Claves
Aunque KVNode acepta cualquier cadena UTF-8 como clave, recomendamos seguir estas convenciones:
- Usa espacios de nombres con dos puntos:
app:modulo:clave(ej.config:basedatos:host) - Evita caracteres especiales que puedan interferir con la codificación URL (
/,?,#) - Mantén las claves legibles — los nombres significativos simplifican la depuración
- Sé consistente — elige un patrón de nomenclatura y aplícalo en todas las colecciones
Tipos de Valor
Todos los valores deben ser JSON válido. KVNode soporta:
- Objetos:
{"usuario": "alice", "rol": "admin"} - Arreglos:
["item1", "item2"] - Cadenas:
"hola mundo" - Números:
42o3.14 - Booleanos:
trueofalse - Null:
null
El tamaño máximo del valor está determinado por la configuración del backend de WAL.
Comentarios