npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

megadbx

v2.3.1

Published

Motor de base de datos embebida, rapida y robusta para Node.js

Readme

MEGADBX

megadbx es un motor de base de datos embebida para Node.js, diseñado para ser rápido y confiable. Combina la flexibilidad de un document store con características avanzadas como integridad de datos, concurrencia segura, compresión, control de versiones, índices optimizados y alta robustez, todo en un paquete ligero y eficiente.

Novedades 2.3.1

  • Se añadio nuevas opciones a MegaDBCloud tanto en la clase como en el panel administrativo.
  • Se añadio la opcion para desactivar el intervalo de tiempo de la creacion de backups.
  • Se arreglaron bugs menores.

Novedades 2.3.0

  • Se añadio la posibilidad de almacenar datos en nube usando MegaDBCloud, todo mediante una interfaz rapida, sencilla y segura.
  • Ya se cuenta con un servidor de discord para hacer consultas, dudas o sugerencias, https://discord.gg/rXngzDpAHf

Resumen rápido de todo lo nuevo:

  • Coordinación multiproceso (multiProcess, BlockManifest, SharedFileLock ya conectado) para cuando varios workers de cluster comparten la misma carpeta db/.
  • Transacciones ACID reales (commit de 2 fases con recuperación automática tras un crash).
  • Cursor de streaming (stream(), entries()) para recorrer colecciones grandes sin cargarlas enteras en RAM.
  • Índice paginado en disco para SkipListIndexManager, CompositeIndexManager y TrieIndexManager y rebuildAllIndexes() para activarlos sobre datos que ya existían sin arriesgar un archivo de índice gigante.
  • Schema rediseñado (required, type, enum, unique) reemplaza al mecanismo viejo.
  • Checksums por documento + recuperación automática (espejo => backup => WAL => cuarentena) si un documento se corrompe.
  • Verificación de backups antes de restaurar (con checksum propio).
  • TTL por documento ({ ttl: ms } en set()).
  • Umbral de compresión (compressMinSize) y carga paralela de bloques en find().
  • AdminPanel reemplaza a MegaDBVisual (ver la sección nueva más abajo).
  • Varios bugs preexistentes corregidos.

Características principales

  • Persistencia optimizada en archivos JSON comprimidos

    • Escritura atómica en disco con compresión opcional.
    • Mirror y Journal para recuperación segura.
  • Integridad de datos avanzada

    • Hash SHA-256 por bloque.
    • CRC32 por páginas de datos.
    • xxHash para detección rápida de corrupción.
    • Checksum por documento (no solo por bloque) si un documento puntual se corrompe, se detecta y se recupera solo, sin invalidar el resto del bloque (ver Checksums por documento y recuperación automática).
    • WAL con checksum por línea detecta corrupción silenciosa (bitrot), no solo cortes a mitad de escritura.
    • Backups verificados: restoreBackup() valida el checksum del backup ANTES de aplicar nada a la base de datos.
  • Compresión selectiva de campos (gzip o lz4), con umbral de tamaño mínimo (compressMinSize) no comprime valores chicos donde comprimir sale más caro que dejarlos tal cual.

  • Búsquedas rápidas

    • Bloom Filters por bloque para aceleración de consultas.
    • LRU Cache para acceso inmediato a datos recientes.
    • Carga paralela de bloques en find() (en vez de secuencial, uno por uno).
  • Automatización

    • flush() periódico configurable.
    • createBackup() automático con intervalos programados.
  • Snapshots & Backups

    • Crear y restaurar snapshots completos.
    • Backups comprimidos con rotación flexible (por contador o timestamp), y verificados por checksum antes de restaurar.
  • Coordinación multiproceso (multiProcess: true) para cuando varios workers de cluster (u otros procesos Node) comparten la misma carpeta de base de datos: invalidación de caché entre procesos + locks por bloque, sin necesidad de un servidor central.

  • Transacciones ACID reales entre múltiples bases de datos commit de 2 fases (prepare + apply) con recuperación automática si el proceso se cae a mitad de un commit.

  • TTL por documento documentos que expiran solos, con expiración perezosa (get()) y un barrido activo periódico.

  • Modo asíncrono y multiproceso (MegaDB, MegaDBSafe, MegaDBFull)

    • WAL (Write-Ahead Log) para consistencia y recuperación.
    • Concurrencia segura con cola FIFO interna.
    • Operaciones totalmente no bloqueantes, ideal para servidores y microservicios.
    • Consultas con filtros avanzados ($gt, $lt, $in, $regex, etc.).
    • Cursor de streaming (stream(), entries()) para recorrer colecciones grandes sin cargarlas enteras en RAM.
  • Extensiones avanzadas (MegaDBFull)

    • Índices secundarios, compuestos, trie (prefijos) y skiplist (rangos) todos con modo paginado en disco opcional, para colecciones grandes.
    • rebuildAllIndexes() reconstruye todos los índices en streaming (memoria acotada), ideal para activarlos sobre datos que ya existían.
    • MVCC múltiples versiones de registros sin bloqueos pesados.
    • Append-only log (AOF) para almacenamiento auditable.
    • Merkle Tree para verificación criptográfica de integridad.
  • Schema opcional (required, type, enum, unique) por colección.

  • Transacciones entre múltiples bases de datos.

  • Panel de administración web (AdminPanel) login, CRUD completo con bloqueo optimista, gestión de índices/backups/transacciones pendientes, auditoría. Reemplaza a la vieja interfaz MegaDBVisual (solo lectura).

  • ...y mucho más.


Que persistencia usa megadbx actualmente al guardar los datos

megadbx implementa un sistema de persistencia diseñado para garantizar integridad de datos, resistencia a fallos y consistencia en disco, incluso en casos de caída del proceso o del sistema operativo.

El ciclo de escritura y persistencia combina varias capas de protección:

1. Escritura atómica

Cada modificación en un bloque se guarda mediante un proceso atomic write:

  • Se escribe primero en un archivo temporal.
  • El archivo temporal se sincroniza en disco.
  • Luego se reemplaza el archivo original, operación atómica en sistemas POSIX.

Esto asegura que nunca exista un archivo a medio escribir, o queda el archivo anterior, o el nuevo completo.

2. Journal de respaldo

Antes de sobrescribir un bloque, megadbx crea un archivo journal (.journal) que contiene la nueva versión.

  • Si ocurre un fallo en mitad de la escritura, el journal queda disponible como copia.
  • Una vez que la escritura finaliza con éxito, el journal se elimina.

Este patrón es similar al Write-Ahead Logging (WAL).

3. Mirror de bloques

Además del archivo principal, cada bloque se replica en un directorio espejo (mirror).

  • El mirror mantiene una segunda copia sincronizada de cada bloque.
  • Si el archivo principal se corrompe (ejemplo: fallo de disco, corte de energía durante flush), megadbx puede recuperarlo automáticamente desde el mirror en el próximo arranque.

4. Checksums e integridad

Cada bloque almacenado en disco incluye:

  • crcPages: sumas CRC32 por páginas, para verificar integridad parcial.
  • xxhash: un hash fuerte del bloque completo.
  • keyCrc: checksums de subdocumentos individuales.
  • docChecksums: un checksum por documento raíz (no solo del bloque entero), ver la sección siguiente.

Con esto se puede detectar y reparar corrupción de datos a varios niveles.

Checksums por documento y recuperación automática

Además del checksum de bloque completo (que ya existía), cada documento raíz tiene su propio checksum guardado junto a él (docChecksums). Esto importa porque un checksum de bloque entero solo te informa que algo cambio dentro de un bloque mas no qué documento, y una corrupción de un solo documento invalidaría la lectura de todos los demás que viven en el mismo bloque si solo tuvieras el checksum global.

Cuando get() detecta que el checksum de un documento no coincide, dispara una cadena de recuperación en este orden, probando cada fuente hasta encontrar una copia íntegra:

  1. Espejo (mirror) la copia espejo del bloque, escrita de forma atómica e independiente. Cubre el caso más común: bitrot de disco en una sola de las dos copias.
  2. Backups del más reciente al más viejo. Se extrae solo ese documento del backup, no se restaura la colección entera.
  3. WAL solo en MegaDBSafe/MegaDBFull. Se reconstruye el último valor conocido de esa clave reaplicando las operaciones registradas para ella, en orden.
  4. Cuarentena si ninguna fuente tiene una copia íntegra, el documento corrupto se mueve a _quarantine/ (se preservan los bytes crudos por si se quiere investigar después) y get() lanza un MegaDBError explícito. Nunca se devuelve un documento corrupto en silencio, y el resto del bloque (los demás documentos) se sigue leyendo con normalidad.
const { MegaDB } = require("megadbx");
const db = new MegaDB("usuarios");

try {
  const doc = await db.get("u1");
  // doc llega integro, o viene de una recuperacion automatica (mirror/backup/WAL)
} catch (err) {
  // err.message: "Documento "u1" corrupto en el bloque block_0001.json y no se
  // pudo recuperar (espejo/backup/WAL agotados). Copia cruda movida a _quarantine/."
}

Nota: esto aplica a documentos raíz completos (db.set('u1', {...})), no a escrituras de rutas anidadas, mismo alcance que el schema (ver Schema).

4.2 WAL con checksum por línea

El WAL (usado por MegaDBSafe/MegaDBFull) guarda cada línea con el formato hash|json (igual que el AOF). Antes, una línea del WAL solo se descartaba si dejaba de parsear como JSON válido (un corte a mitad de escritura); un bit flip que "por casualidad" siguiera pareciendo JSON válido no se detectaba. Ahora, cualquier línea cuyo checksum no coincida se descarta con un warning, en vez de reproducirse como si fuera válida.

5. Backups y snapshots

  • MegaDBX puede generar backups automáticos o bajo demanda.
  • También soporta snapshots comprimidos, que capturan el estado completo de la base de datos en un solo buffer.
  • Los backups se verifican por checksum antes de restaurarse. createBackup() guarda un .sha256 junto al backup; restoreBackup() lo valida antes de tocar la base de datos. Si el backup está corrupto (o ni siquiera descomprime), restoreBackup() lanza un error explícito y no aplica nada:
try {
  await db.restoreBackup("backup_00000");
} catch (err) {
  // err.message: 'El backup "backup_00000" esta corrupto (el checksum no coincide)
  // restauracion cancelada, no se toco la DB.'
}

Como megadbx minimiza el acceso a disco

Una de las optimizaciones más importantes de megadbx es que no vuelve a leer ni parsear los archivos JSON en cada operación.
En su lugar, utiliza varias capas de caché e índices en memoria que reducen drásticamente el acceso a disco.

1. Caché de bloques en memoria (blocksCache)

  • Los datos de la base están divididos en bloques (block_XXXX.json).
  • Cuando un bloque se carga por primera vez, se mantiene en memoria dentro de blocksCache.
  • Las siguientes consultas sobre claves o documentos que pertenecen a ese bloque se atienden directamente desde memoria, sin necesidad de leer el archivo desde el disco.
  • Esto convierte las lecturas repetidas en operaciones de memoria RAM, mucho más rápidas que cualquier acceso a disco.

2. LRU Cache para bloques

  • Para evitar que el consumo de memoria crezca indefinidamente, se implementa una política Least Recently Used (LRU).
  • Esto significa que los bloques más usados recientemente permanecen en memoria, mientras que los menos usados se van expulsando cuando se alcanza el límite configurado.
  • De esta manera, la base se adapta a entornos con memoria limitada sin comprometer demasiado el rendimiento.

3. KeyCache (caché de claves individuales)

  • Además del caché de bloques completos, megadbx mantiene un caché de claves específicas.
  • Esto permite que si se accede varias veces a la misma clave (get, has, etc.), la respuesta se sirva inmediatamente sin necesidad de recorrer el bloque asociado.
  • Este enfoque reduce el tiempo de búsqueda y acelera notablemente las consultas repetitivas.

4. Filtros de Bloom por bloque

  • Cada bloque mantiene un Bloom filter, una estructura probabilística que permite saber rápidamente si una clave puede estar dentro de ese bloque.
  • Antes de intentar leer un bloque, megadbx consulta su filtro de Bloom:
    • Si el filtro indica que la clave no está, se evita por completo la lectura del bloque.
    • Si indica que la clave podría estar, entonces se consulta la caché o, si es necesario, se carga el bloque desde disco.
  • Esto reduce lecturas innecesarias y hace más eficientes las operaciones de búsqueda.

5. Índice en memoria (index) y global opcional (indexes.json)

  • megadbx mantiene en memoria un índice que asocia cada clave con el bloque al que pertenece.
  • Esto permite localizar directamente el bloque correcto sin necesidad de recorrer todos los archivos.
  • Opcionalmente, el índice también se guarda en disco como indexes.json.
    • De esta forma, en un reinicio no es necesario reconstruir el índice recorriendo todos los bloques, lo que acelera la carga inicial.
    • Si indexes.json falta o está dañado, MegaDBX puede reconstruir el índice completo a partir de los datos de los bloques.

En conjunto, estas técnicas permiten:

  • Atomicidad: nunca quedan datos parciales.
  • Durabilidad: los cambios confirmados sobreviven a fallos del proceso o del sistema operativo.
  • Recuperación rápida: con journal y mirror, la base de datos se repara sola si encuentra corrupción.
  • Seguridad extra: snapshots y backups permiten volver atrás en caso de error humano.
  • Evite lecturas repetitivas de JSON en disco.
  • Mantenga la mayoría de las operaciones de consulta en memoria.
  • Ofrezca tiempos de respuesta consistentes incluso en bases con muchos datos.

Y así como estas, se incluye otras optimizaciones internas con el objetivo de ofrecer un equilibrio entre velocidad, durabilidad y facilidad de uso.

Pequeñas actualizaciones:

  • Menores:
    • 1.0.6:
      • Pequeños fix puntuales al readme.
    • 1.0.8:
      • Manejo robusto de escritura en disco.
      • Se implementó un nuevo sistema de escritura atomica con reintentos y backoff exponencial, ahora los modulos que persisten estado consolidado (IndexStore, CompositeIndexManager, etc) usan atomicWriteFile, esto asegura que los archivos no se corrompan incluso si ocurre un error de E/S (EBUSY, EPERM, EACCES, etc.) durante la escritura.
      • El sistema usa backoff exponencial con jitter para reintentar escrituras de forma eficiente, con una tolerancia máxima de hasta ~50 segundos en casos extremos.
    • 2.0.1:
      • Fix a la logica interna del restoreSnapshot y restoreBackup.
  • Mayores:
    • 2.0.0:
      • Migración a asincronía completa
      • Toda la base de datos ha sido reescrita para funcionar de manera totalmente asíncrona.
      • Los metodos ahora devuelven Promesas, por lo que se deben usar con await o .then().
      • Se renombro la clase MegaDBAsync por MegaDBSafe.
      • Esta versión rompe compatibilidad con las versiones 1.x; el código que dependía de la API síncrona deberá actualizarse.
      • Mejora el rendimiento y la escalabilidad al manejar operaciones concurrentes de forma segura.
    • 2.2.0:
      • Ver la sección Novedades 2.2.0 (multiproceso/cluster, transacciones ACID reales, panel de administración web, schema rediseñado, checksums por documento con recuperación automatica, índices paginados en disco, TTL, y varias correcciones de robustez). Se explica todo en detalle ahí, con ejemplos.
    • 2.3.0:
      • Se añadio la opcion para almacenar datos en modo cloud (nube), ver la seccion MegaDBCloud
    • 2.3.1:
      • Se arreglaron errores menores y se añadieron mas opciones en MegaDBCloud.

Instalación

(lz4 es opcional, solo si usas compressAlg: "lz4")


npm install megadbx
npm install xxhashjs

Opcional (si usas compress 'lz4'):
npm install lz4

Opcional (si usas la clase AdminPanel ver sección más abajo):
npm install express express-session bcryptjs

Notas generales sobre parámetros y uso de palabras.

  • En la documentacion se usan palabras como:
    • coleccion: Toda la base de datos (db) => {key1: value1, key2: value2, etc} => {documento1, documento2, etc}
    • documento(s): Lo que se guardó en la base de datos => {key: value}
    • clave/key: La clave con la que se guardo el documento => key
    • value/valor: El valor guardado en el documento => value
    • query: objeto de filtro profesional usando operadores relacionales => { edad: { $gt: 18 } } etc.
    • path: Ruta dentro del documento ("usuario1") o coleccion (si usa wildcard)
    • wildcard: Simbolo especial para acceder a toda la coleccion o sub documentos de un documento. "*"
    • dot notation: Simbolo especial para acceder anidadamente a una ruta dentro de un documento '.'

Operadores relacionales y textuales

  • Existen operadores que nos ayudan a la hora de filtrar o buscar documentos, estos operadores siguen una regla estricta:
    • $gt: Mayor que: valor > $gt
    • $gte: Mayor o igual que: valor >= $gte
    • $lt: Menor que: valor < $lt
    • $lte: Menor o igual que: valor <= $lte
    • $eq: Igualdad estricta: valor === $eq
    • $ne: distinto: valor !== $ne
    • $in: Pertenece a la lista - $in debe ser un array: $in.ncludes(valor)
    • $nin: No pertenece a la lista - $nin debe ser un array: $in.includes(valor) es false
    • $between: Rango inclusivo [min, max] - Esta dentro del rango min y max: valor >= min && valor <= max
    • $prefix: Coincidencia de prefijo de string: (String).startsWith
    • $regex: Expresion regular (string), opcional $flags (los flags son del RegExp): new RegExp($regex, $flags||'').test(String(valor))

Clases principales

(MegaDB) Constructor y metodos:


(MegaDBSafe) Constructor y metodos:


(MegaDBFull) Constructor y metodos:


MegaDBCloud Constructor y metodos:


(Transaction) Constructor y metodos:


(AdminPanel) Constructor y metodos: (reemplaza a MegaDBVisual, ver nota abajo)


Dato importante, leer esto despues de entender como funciona MegaDB/MegaDBSafe/MegaDBFull

1. MegaDB

MegaDB

const { MegaDB } = require("megadbx");
const db = new MegaDB(collectionName, options = {});
  • collectionName (string): nombre de la colección (carpeta en ./db/).
  • options (objeto, opcional):

| Opción | Default | Explicación | | ---------------------- | --------------------------------- | --------------------------------------------------------- | | dir | require.main.filename | Directorio donde se creara la carpeta del db/ | | mirrorDirName | mirror | Carpeta para guardar los 'espejos' del db | | blockSize | 100 | Cantidad de claves por bloque. | | flushInterval | 30000 ms | Cada cuánto guardar datos a disco. | | crcPageSize | 4096 bytes | Tamaño de página para CRC. | | schema | null | Validación (required, type, enum, unique) ver Schema. | | secondaryIndexes | [] | Campos indexados. | | rebuildIndexOnLoad | false | Reconstruye el los campos indexados al inicializar el db. | | compressFields | [] | Campos a comprimir. | | compressMinSize | 256 bytes | No comprime valores por debajo de este tamaño ver Umbral de compresión. | | bloomK | 4 | Hashes en BloomFilter. | | expectedKeysPerBlock | 1000 | Estimación de cuantas claves tendra cada bloque. | | bloomM | expectedKeysPerBlock * 10 | Tamaño (en bits) del Bloom filter por bloque. | | lruCap | 128 | Tamaño de caché LRU. | | integrityOnRead | true | Verificar integridad en lectura. | | integrityCooldownMs | 180000 ms | Tiempo entre verificaciones de integridad. | | backupMode | counter | Nombres de backup (counter o timestamp). | | backupInterval | 3600000 ms | Intervalo entre backups, 0 para desactivar. | | maxBackups | 10 | Backups a conservar. | | compressAlg | gzip | Algoritmo de compresión (gzip o lz4). | | useGlobalIndex | false | Mantiene y persiste un indice global simple.. | | indexFlushInterval | 1800000 ms | Cada cuánto se guarda cuando useGlobalIndex está en true | | keyCacheCap | 1024 | Tamaño de una cache de claves-valor para acelerar get() | | multiProcess | false | Coordinación entre procesos que comparten la misma carpeta db/ (ej. cluster), ver Multiproceso / cluster. | | manifestPollIntervalMs | 3000 ms | Cada cuánto revisa el manifiesto de versiones en modo multiProcess. | | lockTimeoutMs | 30000 ms | Tiempo máximo esperando un lock de bloque en modo multiProcess. | | lockTtlMs | 30000 ms | TTL del lock de escritura (para detectar locks huérfanos si un proceso muere sosteniéndolo). | | ttlSweepIntervalMs | flushInterval | Cada cuánto corre el barrido activo de documentos vencidos, ver TTL por documento. |

(Explicacion detallada)

dir: Directorio donde se creara la base de datos, la carpeta sera [dir]/db/[collectionName], el directorio default es require.main.filename (la ruta donde esta la carpeta de tu proyecto/app), exp:

const { MegaDB } = require("megadbx");
/*
  //Estructura actual de la carpeta global:
  (Estoy usando el archivo db.js)

  [📂] proyecto
    [📂] base de datos
      [📄] handler.js
      [📄] db.js
    [📂] comandos
      [📄] comando1.js
*/

//Crear la db a la altura de la carpeta base de datos/comandos.
let db = new MegaDB('usuarios'); //No se le pasa nada porque ya lo tiene por default.
/*
  //Estructura actual de la carpeta global:

  [📂] proyecto
    [📂] base de datos
      [📄] handler.js
      [📄] db.js
    [📂] comandos
      [📄] comando1.js
    [📂] db
      [📂] usuarios
*/
// o tambien usando dir:
let db = new MegaDB('usuarios', {
  dir: path.join(__dirname + "..") // usando path, tambien puede ser los puntos ..
});
/*
  //Estructura actual de la carpeta global:

  [📂] proyecto
    [📂] base de datos
      [📄] handler.js
      [📄] db.js
    [📂] comandos
      [📄] comando1.js
    [📂] db
      [📂] usuarios
*/

mirrorDirName: Carpeta donde se crearan los respaldos, sirven como espejo para recuperar archivos por si algo falla al escribir (default: 'mirror')

blockSize: Tamaño de bloque en cantidad de claves (default: 100).

  • Divide los datos en varios bloques para no tener un JSON gigante.
  • megadbx no guarda toda la colección en un solo archivo (como haría un usuarios.json enorme).
  • En lugar de eso, divide la colección en varios bloques (archivos pequeños).
  • blockSize define el número aproximado de claves que se guardan en cada bloque.
  • Por defecto es 100, eso significa que cada archivo (block_0.json, block_1.json, etc…) contendrá alrededor de 100 claves.
//Supongamos que guardas 500 usuarios en la colección "usuarios":
for (let i = 1; i <= 500; i++) {
  await db.set(`u${i}`, { nombre: `Persona${i}` });
}
Caso 1 — blockSize: 100 (default)
db/usuarios/
 ├── block_0.json   ← contiene ~100 users
 ├── block_1.json   ← contiene ~100 users
 ├── block_2.json   ← contiene ~100 users
 ├── block_3.json   ← contiene ~100 users
 ├── block_4.json   ← contiene ~100 users
  • 500 usuarios = 5 bloques.
  • Bloques más pequeños → más archivos, más fragmentación.
  • Ideal si quieres minimizar pérdidas (solo se daña un bloque chiquito).

Valores pequeños (ej. 50):

  • ✅ Más seguro (si se daña un bloque, pierdes menos).
  • ❌ Más archivos que manejar, un poco más lento para recorrer todo.

Valores grandes (ej. 500 o 1000):

  • ✅ Menos archivos, búsquedas más rápidas en algunos casos.
  • ❌ Si se corrompe un bloque, pierdes más datos.

Valor por defecto (100):

  • Equilibrio entre seguridad y rendimiento.

No se asusten, hay metodos avanzados de seguridad/respaldo, tanto internas automaticas y otras a su criterio para reducir esto a nulo ^^

flushInterval: Cada cuánto (ms) se guardan los cambios automáticamente en disco (se escriben a disco los cambios pendientes) en segundo plano.

  • Más corto = menos pérdida posible si se cae el proceso, pero más I/O
  • Default: 30000 ms => 30s.
  • Los valores se reflejan en disco al hacer flush, en memoria se actualiza en tiempo real.

crcPageSize: Tamaño de “página” para calcular CRC por fragmentos del archivo, ayuda a detectar corrupción y reparar desde el espejo (mirror) (default: 4096=4KB).

Schema

schema: Validación opcional por colección al hacer set(). Se rediseñó por completo en la 2.2.0 reemplaza al mecanismo viejo (mapa plano campo -> 'tipo', sin required/enum/unique, y con un bug de diseño: matcheaba por el último segmento del path sin importar en qué parte del árbol del documento estuviera ese nombre de campo).

Formato nuevo:

const db = new MegaDB("usuarios", {
  schema: {
    fields: {
      nombre: { type: "string", required: true },
      edad:   { type: "number" },
      email:  { type: "string", required: true, unique: true },
      rol:    { type: "string", enum: ["admin", "user", "guest"] },
    }
  }
});

await db.set("u1", { nombre: "Ana", edad: 30, email: "[email protected]" }); // OK

await db.set("u2", { edad: 20 });
// throws MegaDBError: Documento invalido para "u2": campo requerido
// faltante: "nombre"; campo requerido faltante: "email"

await db.set("u3", { nombre: "Bob", email: "[email protected]" });
// throws MegaDBError: Valor duplicado para campo unico "email" en "u3": "[email protected]"

Reglas soportadas por campo:

  • type: 'string' | 'number' | 'boolean' | 'object' | 'array'.
  • required: si true, el campo no puede faltar.
  • enum: array de valores permitidos.
  • unique: no puede haber dos documentos con el mismo valor en ese campo. El índice de unicidad se reconstruye solo (escaneando lo existente una vez) la primera vez que se necesita, así que también funciona si activas unique sobre datos que ya tenías guardados.

Compatibilidad hacia atrás: si le pasas el formato viejo (mapa plano { campo: 'tipo' }), se normaliza automáticamente al formato nuevo, así que código existente sigue funcionando:

// formato viejo, sigue funcionando (se normaliza solo):
schema: { edad: "number", nombre: "string" }

Alcance (a propósito, para mantenerlo simple y predecible): solo valida cuando se escribe un documento RAÍZ completo (db.set('u1', {...})), no en escrituras de rutas anidadas (db.set('u1.direccion.ciudad', 'Lima')). Validar en escrituras parciales requeriría leer + mergear + revalidar el documento entero en cada escritura anidada, y no se quiso meter ese costo/complejidad sin que fuera explícitamente necesario.

En MegaDBSafe/MegaDBFull, la validación corre antes de tocar el WAL/AOF/MVCC si el documento es inválido, la operación nunca llega a loguearse.

secondaryIndexes: Lista de campos para indexar y acelerar busquedas por igualdad en find/find, usando filtros $eq/$in/$ne/$nin/$gt/$lt/$gte/$lte/$between/$prefix/$regex sobre el valor.

  • Guarda en un diccionario inverso las rutas de las claves asociadas a un valor.
  • Esto evita tener que recorrer todos los datos ya que directamente accede a la ruta.
  • Soporta rutas anidadas usando dot notation que es el punto '.'
  • Muy util cuando tiene miles de datos. Ej:
//Este ejemplo fue testeado con 50,000 usuarios añadidos al db

//usando propiedades planas al igual que dot notation
secondaryIndexes: ["nombre", "perfil.id", "perfil.edad"]

await db.set("u1", { nombre: "mega", email: "[email protected]" });
await db.set("u2", { nombre: "ratsa",   email: "[email protected]" });


await db.set("u3", { perfil: { id: "Ana", edad: 25 } });
await db.set("u4", { perfil: { id: "Luis", edad: 30 } });
await db.set("u5", { perfil: { id: "Ana", edad: 40 } });
//
//

//En memoria al usar MegaDB:
{
  "email": {
    "[email protected]": ["u1"],
    "[email protected]": ["u2"]
  },
  "perfil.id": {
    "Ana": ["u3","u5"],
    "Luis": ["u4"]
  },
  "perfil.edad": {
    "25": ["u3"],
    "30": ["u4"],
    "40": ["u5"]
  }
}

//En disco al usar MegaDBSafe/MegaDBFull => Se crea en el archivo indexes.json
{
  "email": {
    "[email protected]": ["u1"],
    "[email protected]": ["u2"]
  },
  "perfil.id": {
    "Ana": ["u3","u5"],
    "Luis": ["u4"]
  },
  "perfil.edad": {
    "25": ["u3"],
    "30": ["u4"],
    "40": ["u5"]
  }
}

//usamos find o find sin usar el secondaryIndexes:
//  
console.time("find normal");
let data = await db.find("*", {nombre: { $eq: "mega"}});
console.timeEnd("find normal");
console.log(data); 

console.time("find normal2");
let data2 = await db.find("*", { "perfil.id": { $eq: "Ana" } });
console.timeEnd("find normal2");
console.log(data2); 
/*
Imprime:
find normal: 941.061ms => casi 1 segundo
[{ nombre: "mega", email: "[email protected]" }]

find normal2: 981.025ms => casi 1 segundo
[
  { perfil: { id: 'Ana', edad: 25 } },
  { perfil: { id: 'Ana', edad: 40 } }
]
*/

//usamos find o find y el secondaryIndexes:
console.time("find secondaryIndexes");
let data = await db.find("*", {nombre: { $eq: "mega"}});
console.timeEnd("find secondaryIndexes");
console.log(data); 

console.time("find secondaryIndexes2");
let data2 = await db.find("*", { "perfil.id": { $eq: "Ana" } });
console.timeEnd("find secondaryIndexes2");
console.log(data2); 
/*
Imprime:
find secondaryIndexes: 201.005ms => menos de medio segundo
{ nombre: "mega", email: "[email protected]" }

find secondaryIndexes2: 196.032ms => menos de medio segundo
[
  { perfil: { id: 'Ana', edad: 25 } },
  { perfil: { id: 'Ana', edad: 40 } }
]
*/

DATO IMPORTANTE:

En MegaDB:

  • secondaryIndexes al guardar en memoria, solo guarda los datos que se hicieron posterior a la inicializacion del db, esto quiere decir que si tenias datos agregados anteriormente y volviste a inicializar el db solo guardará en memoria los nuevos datos añadidos hasta que el db se apague y asi sucesivamente entre cada inicio y apagado (desventaja por tenerlo en memoria).
  • Existe la opcion de revertir esto, asi al iniciar el db siempre se cargan todos los datos y se reconstruye el secondaryIndexes en memoria en base a tu db actual (ver rebuildIndexOnLoad).

En MegaDBSafe o MegaDBFull

  • secondaryIndexes ahora persiste en disco (se guarda en indexes.json) asi al iniciar el db solo se toman los datos actuales del archivo (evitamos recargar todos los datos) para mejorar la flexibilidad y rapidez al hacer la solicitud, de igual forma se va actualizando de acuerdo a los nuevos datos añadidos.
  • Que sucederia si tenemos 2 usuarios que cumplen el requisito del secondaryIndexes pero el usuario1 fue añadido con el secondaryIndexes y luego el usuario2 a la db sin usar el secondaryIndexes? Al hacer la solicitud de busqueda con find/find los datos del usuario1 seran retornados en cuestion de milisegundos al estar dentro del indexes.json, en cambio el usuario2 al estar solo en la db sus datos seran retornados en cuestion de segundos (tomando de ejemplo los 50,000 usuarios añadidos).
  • Para sincronizar estos datos tomando el ejemplo de arriba, se puede usar la opcion rebuildIndexOnLoad, esto hará que se actualice el indexes.json con los datos del db que cumplen el requisito del secondaryIndexes, pueden usarlo 1 vez para sincronizar y luego deshabilitarlo.

rebuildIndexOnLoad: Reconstruye los campos indexados del secondaryIndexes tomando los datos ya existentes de los bloques (la db), sirve para sincronizar los datos existentes y no solo tomar los nuevos datos añadidos (default: false).

compressFields: Lista de rutas de campos (array de paths, soporta dot notation '.' y wildcards '*') a comprimir con gzip o lz4 al persistir en disco, reduce tamaño en disco al guardarse comprimidos (por campo). Al leer se descomprime solo, util para textos grandes, Ej:

  compressFields: [
    "user1",              // campo top-level, comprime todo el subarbol (objeto)
    "user2.descripcion", // anidado dot notation .
    "*.descripcion",    // usa wildcard * , aplica a todas las claves raiz que tengan el subcampo descripcion (user1.descripcion, user2.descripcion, etc)
    "datos.*.prof"// dot notation/anidado + wildcard, aplica a todas las claves de datos que tengan el subcampo prof (datos.a.prof, datos.b.prof, etc)
  ]
  await db.set('user1', { titulo: 'Ejemplo', descripcion: 'Texto muuuuuuuy largo.....' }); //aplica "user1" y "*.descripcion"
  await db.set('user2', { titulo: 'Ejemplo', descripcion: 'Texto muuuuuuuy largo.....' }); //aplica "user2.descripcion" y "*.descripcion"

  await db.set('datos', {
    mario: {prof: "programador", edad: 20},
    pedro: {prof: "agronomo", edad: 25},
    juan: false,
  }); //aplica "datos.*.prof" a mario y pedro
  • Puedes combinar wildcards y dot notation incluso mas de 1 vez, soporta ya sean separadas o seguidas de otra, data.*.*.obj1, *.*.obj2, etc, a tu criterio.

bloomK: Número de hashes del Bloom filter por bloque (filtro probabilístico para saber si un bloque puede contener una clave, busquedas mas rapidas). Más alto = menos falsos positivos, más CPU.. Normalmente no se tocan (default: 4).

expectedKeysPerBlock: Estimación de cuántas claves tendrá cada bloque, e usa para dimensionar el Bloom filter si no fijas bloomM. Normalmente no se tocan (default: 1000).

bloomM: Tamaño (en bits) del Bloom filter por bloque, si no lo pasas se calcula por defecto como expectedKeysPerBlock * 10 (≈1.25 KB por 1000 claves).

  • Tip rápido: si quieres bajar falsos positivos, sube bloomM o bloomK

integrityOnRead: Activa verificaciones de integridad (CRC por páginas + hash) de forma periódica/al leer los bloques (db). Si detecta corrupción intenta auto-recuperar desde el mirror => espejo, (default: true).

integrityCooldownMs: Tiempo mínimo entre verificaciones de integridad, integrityOnRead y integrityCooldownMs equilibran seguridad vs rendimiento. (default: 180000 ms => 3 minutos para no “castigar” el disco).

backupMode: "counter" o "timestamp". Define cómo nombrar los backups.

backupInterval: Cada cuánto (ms) se crea un backup automático comprimido de todos los bloques(db), esto se rota de acuerdo a maxBackups (default: 1 hora, 0 si quieres desactivarlo).

maxBackups: Cuántos backups guardar antes de borrar los viejos (default: 10).

compressAlg: Define qué algoritmo de compresión se usará para los bloques o campos que indiques en compressFields, al igual que en los snapshots / backups (default: gzip)

| Característica | Gzip | LZ4 | | ------------------------------ | ------------------------------------------------- | ---------------------------------------------------------- | | Compresión | Alta (archivos más pequeños) | Media (archivos más grandes que gzip) | | Velocidad de compresión | Lenta (más CPU) | Muy rápida (optimizada para tiempo real) | | Velocidad de descompresión | Buena, pero más lenta que LZ4 | Extremadamente rápida | | CPU requerida | Mayor | Mucho menor | | Soporte nativo en Node.js | Sí (zlib) | No (se instala lz4) | | instalacion | No requiere nada | Al instalar requiere python / compilador C/C++ |

useGlobalIndex: Mantiene y persiste un índice global simple en index.json (mapea claves raíz → bloque), para acelerar la carga/consultas tras reiniciar, opcional para dbs con miles de datos (default: false).

indexFlushInterval: Cada cuánto se persiste index.json cuando useGlobalIndex está en true, si lo pones en 0 no hay intervalo periódico (default: 1800000 ms => 30 minutos)

lruCap: Capacidad de la caché LRU de bloques en memoria (default: 128), más grande = menos lecturas de disco, más RAM.

keyCacheCap: Define el tamaño máximo de la caché LRU (Least Recently Used) para claves individuales, controla cuántas claves recientes se guardan en memoria para acelerar lecturas repetidas (default: 1024) Esto significa que cuando llamas a get(path), la base de datos guarda en memoria los últimos resultados para que futuras lecturas sean instantáneas sin tener que:

  • Leer el bloque desde disco.
  • Descomprimir el campo (si estaba en compressFields).
  • Pasar por validaciones de integridad.

Si accedes mucho a las mismas claves, súbelo.

const db = new MegaDB("users", {
  keyCacheCap: 2 // solo guardará 2 claves en caché
});

await db.set("user.1", { name: "mega", age: 13 });
await db.set("user.2", { name: "ratsa", age: 14 });
await db.set("user.3", { name: "Charlie", age: 40 });

// Primera lectura (va a disco y guarda en caché)
console.log(await db.get("user.1"));

// Segunda lectura (lee desde caché → mucho más rápida)
console.log(await db.get("user.1"));

// Al llegar a 3 claves, la más antigua se expulsa (user.1 o user.2)
console.log(await db.get("user.3"));

Pequeñas base de datos

  • 1024 claves cacheadas es más que suficiente, el consumo de RAM es bajísimo (kilobytes) y el hit-rate será decente.

Bases medianas (decenas o cientos de miles de claves)

  • 1024 puede quedarse corto → si accedes siempre a distintas claves, la caché se reciclará muy rapido y tendras pocos hits.
  • Subirlo a 8192 (8k) o 16384 (16k) da mejor rendimiento con un costo de memoria aún bajo.

Bases muy grandes (millones de claves)

Lo crítico es medir:

  • si los accesos son locality-friendly (siempre consultas un subconjunto chico de claves), 1024 sigue bastando.
  • si son muy aleatorios, incluso 65536 puede ser razonable.
  • cada entrada en la caché es básicamente {key, value} más overhead → unos cientos de bytes por clave en node, sigues ganando velocidad en lecturas repetidas.

Umbral de compresión

compressMinSize: Con compressFields, no comprime valores cuyo tamaño serializado (JSON) esté por debajo de este umbral (default: 256 bytes).

Comprimir un valor chico es contraproducente: el header de gzip por sí solo son ~18 bytes, y sumale el costo de CPU de comprimir/descomprimir para valores chicos, terminas gastando más de lo que ahorras.

const db = new MegaDB("usuarios", {
  compressFields: ["bio"],
  compressMinSize: 256, // default
});

await db.set("u1", { bio: "hola" });        // ~15 bytes: NO se comprime
await db.set("u2", { bio: "x".repeat(2000) }); // 2000+ bytes: SI se comprime

Multiproceso cluster

multiProcess: Coordina esta instancia con OTROS procesos Node que comparten la misma carpeta db/ el caso típico es el módulo cluster de Node.js, donde repartes carga entre varios núcleos y cada worker es un proceso de sistema operativo separado, con su propia copia en memoria de blocksCache (default: false).

El problema que resuelve: worker A escribe un documento, worker B tiene ese mismo bloque cacheado desde antes en su memoria sin coordinación, worker B seguiría devolviendo el valor viejo indefinidamente (su copia en RAM nunca se entera del cambio en disco).

Cómo lo resuelve:

  1. BlockManifest un archivo chico (_manifest.json) con {blockId: version}. Cada escritura exitosa incrementa la versión de ESE bloque. Los demás procesos hacen polling periódico (manifestPollIntervalMs) + fs.watch como camino rápido, e invalidan solo los bloques que realmente cambiaron, no relee bloques completos en cada poll, solo compara versiones.
  2. SharedFileLock un lock de lectores/escritor por bloque (no global, así dos workers escribiendo bloques distintos no se esperan entre sí), basado en archivos-marcador con heartbeat/TTL en vez de locks nativos del SO. Se adquiere antes de escribir un bloque en disco.
// En cada worker de cluster:
const { MegaDB } = require("megadbx");

const db = new MegaDB("pedidos", {
  dir: "./",
  multiProcess: true,          // activa la coordinacion
  manifestPollIntervalMs: 3000, // default
  lockTimeoutMs: 30000,         // default
  lockTtlMs: 30000,             // default
});

Costo si NO lo necesitas: cero, con multiProcess: false (default) no se instancia ningún manifiesto ni lock, no hay overhead. Solo actívalo si de verdad tienes más de un proceso tocando la misma carpeta db/ al mismo tiempo.

TTL por documento

Documentos que expiran solos, pasado un tiempo. Se define por escritura, no por colección, ver set(path, value, opts) para el parámetro ttl.

const db = new MegaDB("sesiones", {
  ttlSweepIntervalMs: 60000, // default: usa flushInterval (30000ms)
});

await db.set("s1", { userId: "u1" }, { ttl: 60000 }); // expira en 60s
await db.set("s2", { userId: "u2" }); // sin ttl, permanente

await db.get("s1"); // { userId: 'u1' }
// ... pasan 60+ segundos ...
await db.get("s1"); // undefined expiro

Cómo se expira:

  • Perezosa (lazy): si alguien pide una clave vencida vía get(), se borra ahí mismo y devuelve undefined. No hace falta esperar al barrido para que una lectura puntual sea correcta.
  • Barrido activo: cada ttlSweepIntervalMs, se revisan los bloques que están "tibios" en memoria y se borran los documentos vencidos que nadie volvió a leer, así no quedan ocupando espacio indefinidamente solo porque nadie los pidió de nuevo.

Mismo alcance que schema: el TTL aplica a documentos raíz completos. Si haces set() de una ruta anidada, no se le asigna TTL (y si el documento raíz ya tenía uno, un set() posterior sin ttl se lo quita, el documento nuevo reemplaza al viejo, ya no debería expirar solo por herencia del anterior).

set

set(path, value, opts = {})

Crea o actualiza un valor en la ubicación indicada por path.

  • path (string): ruta jerárquica donde almacenar el valor, soporta dot notation '.'
    Ejemplo: "users.1.name" → dentro de users, clave 1, propiedad name.
  • value (any): el valor que quieres guardar (puede ser string, number, boolean, objeto, etc.).
  • opts (objeto, opcional):
    • ttl (number, ms): si se pasa, el documento raíz expira solo después de ese tiempo, ver TTL por documento. Solo aplica cuando path es un documento raíz (sin dot notation).
  • retorna (promesa): true si se guardo correctamente.

Si tienes un schema definido en las opciones del constructor, set lo valida antes de guardar, ver Schema para el detalle completo (required/type/enum/unique).

await db.set("users.1", { name: "mega", age: 13 });
await db.set("users.2", { name: "ratsa", age: 14 });
await db.set("users.1.email", "[email protected]");
/*
{
  "users": {
    "1": {
      "name": "mega",
      "age": 13,
      "email":  "[email protected]"
    },
    "2": {
      "name": "ratsa",
      "age": 14
    }
  }
}
*/

await db.set("counter", { id: "1239858345", count: 48 });
/*
{
  "users": {......},
  "counter": {
    "id": "1239858345",
    "count": 48
  }
}
*/

// con TTL: este documento se borra solo despues de 60 segundos
await db.set("sesiones.s1", { userId: "u1" }, { ttl: 60000 });

has

has(path)

Verificas si existe una clave en la coleccion de la ruta path.

  • path (string): ruta a la clave que quieres verificar, soporta dot notation '.'
  • Retorna (promesa): true si existe o false si no existe.
await db.set("users.1", { name: "mega", age: 13 });
await db.set("users.2", { name: "ratsa", age: 14 }); 

console.log(await db.has("users")); // true
console.log(await db.has("users.1")); // true
console.log(await db.has("users.1.name")); // true
console.log(await db.has("users.2.age")); // true
console.log(await db.has("users.1.status")); //false
console.log(await db.has("users.1.name.data")); //false
console.log(await db.has("docs")); //false

get

get(path)

Obtiene un documento almacenado por su clave.

  • path (string): ruta al documento que quieres obtener, soporta dot notation '.'
  • Retorna (promesa): el valor almacenado o undefined si no existe.
await db.set("users.1", { name: "mega", age: 13 });
await db.set("users.2", { name: "ratsa", age: 14 });
await db.set("docs": []); 

const user1 = await db.get("users.1");
console.log(user1); // { name: "mega", age: 13 }
const age = await db.get("users.2.age");
console.log(age); // 14
const nodata = await db.get("users.3");
console.log(nodata); // undefined
const document = await db.get("docs");
console.log(document); // []

delete

delete(path)

Elimina un documento almacenado por su clave.

  • path (string): ruta del documento a eliminar, soporta dot notation '.'
  • Retorna (promesa): true si se eliminó, false si no existía.
await db.set("users.1", { name: "mega", age: 13 });
await db.set("users.2", { name: "ratsa", age: 14 });
await db.delete("users.2.age"); // elimina el campo "age" del usuario 2
await db.delete("users.1"); // elimina todo el objeto del usuario 1

console.log(await db.get("users.1")); // undefined
console.log(await db.get("users")); //{"2": {"name": "ratsa"}}

all

all()

Obtiene todos los documentos de la coleccion.

  • Retorna (promesa): un objeto con todos los documentos actuales.

await db.set("users.1", { name: "mega", age: 13 });
await db.set("users.2", { name: "ratsa", age: 14 });

console.log(await db.all()); 
/* retorna:
{
  "users":
    "1": {
      "name": "mega",
      "age": 13
    },
    "2": {
      "name": "ratsa",
      "age": 14
    }
}
*/

stream

stream(path, query = {})

Async generator — cursor sobre find(). Para colecciones grandes: en vez de construir un array completo en RAM (como find()/all()), recorre bloque por bloque y va entregando documentos con yield a medida que los encuentra, sin cargar la colección entera en memoria.

  • path (string): igual que en find — ruta o "*".
  • query (objeto, opcional): mismos operadores que find.
  • Retorna: un async generator de valores de documentos (no la clave, si necesitas la clave, usá entries()).
for await (const doc of db.stream("usuarios", { edad: { $gt: 18 } })) {
  // procesa doc de a uno; nunca tiene toda la coleccion en RAM a la vez
}

Nota de memoria: si un bloque no estaba ya "tibio" en blocksCache antes de empezar el recorrido, se descarta de la caché al terminar de procesarlo, así un recorrido completo de la colección no termina dejando todos los bloques calientes en memoria (eso volvería a ser cargar todo en RAM). Si el bloque ya estaba cacheado de antes por otro uso, se respeta tal cual.

entries

entries(path, query = {})

Igual que stream(), pero entrega { key, value } en vez de solo el valor. Hace falta cuando necesitas saber la clave del documento además de su contenido (por ejemplo, para editarlo o borrarlo después), algo que stream()/find() no garantizan, porque el valor no necesariamente contiene su propia clave.

for await (const { key, value } of db.entries("*", {})) {
  console.log(key, value);
}

keys

keys(path)

Devuelve todas las claves de la coleccion.

  • path (string): ruta de las claves a obteber, soporta dot notation '.' y wildcard '*' cuando quieres obtener las claves principales de la coleccion.
  • Retorna (promesa): un array con las claves encontradas.
await db.set("users.1", { name: "mega", age: 13 });
await db.set("users.2", { name: "ratsa", age: 14 });
await db.set("docs": []); 

console.log(await db.keys("users")); // ["1", "2"]
console.log(await db.keys("users.1")); // ["name", "age"]
console.log(await db.keys("*")); // wildcard => ["users", "docs"]
console.log(await db.keys("notfound")); // []
console.log(await db.keys("users.2.data")); // []

values

values(path)

Devuelve todos los valores de la coleccion.

  • path (string): ruta de los valores a obteber, soporta dot notation '.' y wildcard '*' cuando quieres obtener los valores principales de la db.
  • Retorna (promesa): un array con los valores encontradas.
await db.set("users.1", { name: "mega", age: 13 });
await db.set("users.2", { name: "ratsa", age: 14 });
await db.set("docs": []); 
await db.set("status": false);

console.log(await db.values("users")); 
// [{name: "mega", "age": 13}, {name: "ratsa", "age", 14}]
console.log(await db.values("users.1")); //["mega", 13]
console.log(await db.values("users.1.name")); // []
console.log(await db.values("users.3")); // []
console.log(await db.values("*")); 
// wildcard => [{"1": {...}, "2": {...}}, [], false]

count

count(path, query)

Devuelve el numero total de documentos.

  • path (string): ruta donde se hara el conteo de datos, se usa wildcard '*' para escanear toda la coleccion, tambien soporta dot notation '.'
  • query (object) Objeto de filtro, cada clave del objeto es un campo o una ruta de campo.
  • Retorna (promesa): el conteo de elementos que paso el filtro de query.

Soporta 2 tipos de filtros: Un filtro simple => comparacion de igualdad estricta (===)

 { edad: 30 }  // equivale a edad === 30

*Un filtro avanzado => comparacion con operadores, ver aqui

  • Puedes usar incluso mas de un filtro colocando la ,
 { edad: { $gt: 30 }}  // equivale a edad > 30
await db.set("users.1", { name: "mega", edad: 13 });
await db.set("users.2", { name: "megast", edad: 14 });
await db.set("users.3", { name: "pedro", edad: 15 });
await db.set("users.4", { name: "ratsa", edad: 16 });

console.log(await db.count("users", {edad: {$gt: 14}})); // retorna 2 porque solo hay 2 mayores a 14
console.log(await db.count("users", {edad: {$lt: 16}})); // retorna 3 porque solo hay 3 menores a 16
console.log(await db.count("users", {edad: {$gt: 14}, name: {$prefix: 'r'} })); 
// retorna 1 porque se filtro primero por edad mayor a 14 => 2 y tambien se filtro con $prefix dejando de esos 2 resultados solo 1 => name: "ratsa"
console.log(await db.count("*", {etc...})); //puedes usar wildcard '*' para la coleccion entera.
console.log(await db.count("data.dato2", {etc...})); //puedes usar dot notation

find

find(path,query = {})

Busca y devuelve un array con los documentos (o sub-documentos) que cumplen el filtro query dentro de la ruta indicada por path. Está pensado para búsquedas expresivas (filtros por documentos incluyendo sub documentos anidados) y operadores relacionales y textuales, aprovechando índices secundarios (secondaryIndexes) cuando están configurados.

  • path (string, opcional): Ruta del documento donde se aplicará la búsqueda, se usa wildcard '*' para escanear toda la coleccion, tambien soporta dot notation '.'
  • query (objeto, opcional): Objeto de filtro, cada clave del objeto es un campo o una ruta de campo:
  • retorna (promesa): Un array con los documentos que cumplieron el filtro.

Soporta 2 tipos de consulta:

const modo_1 = await db.find(path, query);
const modo_2 = await db.find(query);

modo_1

  • Especificas el path donde se aplicara el filtro, puedes usar wildcard '*' para indicar que es toda la coleccion.
  • path puede recibir dot notation '.' para acceder a un documento o sub documento especifico.
  • query es el objeto con el filtro a usar, puedes usar dot notation para buscar incluso una ruta mas exacta.

modo_2

  • No se especifica path, solo el query, find asume path = '*' (toda la coleccion) y query = objeto/filtro.

Un filtro simple => comparacion de igualdad estricta (===)

 { edad: 30 }  // equivale a edad === 30

Un filtro avanzado => comparacion con operadores, ver aqui

 { edad: { $gt: 30 }}  // equivale a edad > 30
  • Ejemplos (con explicaciones)
await db.set('usuario1', {nombre: "u1", edad: 20});
await db.set('usuario2', {nombre: "u2", edad: 19});
await db.set('usuario3', {nombre: "u3", edad: 18});

console.log( await db.find({nombre: "u1"}) ); //simple, devuelve [{nombre: "u1", "edad": 20}]
console.log( await db.find({edad: {$eq: 20}}) ); //devuelve [{nombre: "u1", edad: 20}]
console.log( await db.find({edad: {$gt: 19}}) ); //devuelve [{nombre: "u1", edad: 20}]
console.log( await db.find({edad: {$lt: 19}}) ); //devuelve [{nombre: "u3", edad: 18}]
console.log( await db.find({nombre: {$in: ["u1", "u3"]}}) ); 
/* devuelve 2, porque el valor de la propiedad "nombre" esta dentro del array ["u1", "u3"]
[
  {nombre: "u1", edad: 20},
  {nombre: "u3", edad: 18}
]
*/
console.log( await db.find({nombre: {$nin: ["u1", "u3"]}}) ); 
/* devuelve 1, porque el valor de la propiedad "nombre" no esta dentro del array ["u1", "u3"]
[
    {nombre: "u2", edad: 19}
]
*/
console.log( await db.find({nombre: {$prefix: "u"}}) ); 
/* devuelve 3, porque el valor de la propiedad "nombre" comienza con "u"
[
  {nombre: "u1", edad: 20},
  {nombre: "u2", edad: 19},
  {nombre: "u3", edad: 18}
]
*/
console.log( await db.find({nombre: {$regex: '^u', $flags: 'i'}}) ); 
/* devuelve 3 porque el valor de la propiedad "nombre" comienza con 'u' o 'U'
[
  {nombre: "u1", edad: 20},
  {nombre: "u2", edad: 19},
  {nombre: "u3", edad: 18}
]
*/
  • Puedes usarlo tambien usando dot notation:
await db.set("usuarios.pablo",{
  id: "u1",
  nombre: "pablo pablon",
  edad: 42,
  rol: "admin",
  direccion: { ciudad: "xxxx", distrito: "aaaaaa" },
  bio: "texto largo..."
});
await db.set("usuarios.pedro",{
  id: "u2",
  nombre: "pedro pedron",
  edad: 17,
  rol: "moderador",
  direccion: { ciudad: "yyyyy", distrito: "eeeeee" },
  bio: "texto largo..."
});
await db.set("usuarios.juan",{
  id: "u3",
  nombre: "juan juanon",
  edad: 25,
  rol: "trusted",
  direccion: { ciudad: "zzzzz", distrito: "iiiiii" },
  bio: "texto largo..."
});
// esto me crea { usuarios: {pablo: {...}, pedro: {...}, {juan: {...}}} }
console.log( await db.find('usuarios', { 'direccion.ciudad': 'yyyyy' }) ); //devuelve el documento de pedro
console.log( await db.find('usuarios', { nombre: { $prefix: 'ju' } }) ); //devuelve el documento de juan
console.log( await db.find('usuarios', { edad: { $between: [20, 40] } }) ); //devuelve el documento de juan porque su edad es mayor o igual a 20 y menor o igual a 40
console.log( await db.find('usuarios', { rol: { $in: ['admin', 'moderador', 'editor'] } }) );
//devuelve el documento de pedro y pablo porque su propiedad "rol" esta dentro del array

//Tambien puedes usar dot notation en el path:
await db.find("dato1.dato2", {propiedad1: { $nin: ["valores", "a", "verificar"] }});
await db.find("dato1.dato2", {"data4.data5": "valor"});
  • En miles de datos esto se agiliza si usas SecondayIndexes

aggregate

aggregate(path, pipeline)

Ejecuta operaciones de agregación sobre los documentos que se encuentran en la ruta indicada por path. Permite tanto:

  • Usar un query simple (similar a find) para filtrar,
  • Como definir un pipeline de etapas ($match, $group, $sort, $limit) para realizar transformaciones y cálculos más complejos.
  • Ideal para consultas analíticas, resumenes estadisticos, o transformación de datos.
  • path (string): Ruta del documento donde se aplicará la búsqueda, se usa wildcard '*' para escanear toda la coleccion, tambien soporta dot notation '.'

  • pipeline (array): Pipeline de etapas, cada elemento del array debe ser un objeto con una sola clave (ej: { $match: {...} }).

  • retorna (promesa): Un array con los resultados de la agregación.

  • Etapas soportadas en el pipeline:

    • $match => Filtra documentos (igual que find, puedes usar operadores relacionales)
    • $group => Agrupa por un campo (_id) y permite acumuladores ($sum, $avg, $min, $max).
      • $ => Si vas a usar un campo especifico para evaluar alguna operacion usando algun acumulador, debes de colocar $ antes del nombre de la propiedad a evaluar, '$rol' => propiedad {'rol': valor}
      • _id => Obligatorio si quieres agrupar por un campo especifico, el nombre del campo a usar debe comenzar con $, por ejemplo $rol => {user1:{rol: "admin"}}
      • $sum => Suma valores numericos de un campo. su uso tipico es contar registros o sumar cantidades.
        • Exp:
        • totalEdad: {$sum: '$edad'}
        • total: {$sum: 1}
      • $avg => Calcula el promedio de un campo numerico.
        • Exp:
        • promedio: {$avg: '$edad'}
      • $min => Devuelve el valor minimo de un campo dentro de cada grupo.
        • Exp:
        • edadMinima: {$min: '$edad'}
      • $max => Devuelve el valor maximo de un campo dentro de cada grupo.
        • Exp:
        • edadMaxima: {$max: '$edad'}
    • $sort => Ordena resultados por uno o varios campos que se usó en $group.
      • El sort se aplica al resultado final de $group
      • Si creaste en $group una propiedad llamada totalEdad y quieres usarlo en el sort, debes colocar esa propiedad.
      • Las direcciones de orden soportadas son:
        • -1 => Orden descendente (mayor a menor / Z - A / 9 - 0)
        • 1 => Orden ascendente (menor a mayor / A - Z / 0 - 9)
      • Puedes ordenar por 1 o mas campos seguidos de una coma ,
        • Exp:
        • {$sort: {_id: 1, totalEdad: -1}} => ordena de forma ascendente el campo _id y en base a lo ordenado ordena tambien pero ahora por totalEdad de forma descendente (sort inteligente multicampo).
    • $limit => Limita la cantidad de resultados, devuelve solo los primeros N numeros del flujo.
      • El limit se aplica al resultado final de $sort
      • Si tienes 5 resultados finales y colocas un limite de 2, te devolvera solo los 2 primeros resultados.

Ejemplos, agregamos primero estos datos a la db.

await db.set("usuarios.pablo",{
  id: "u1",
  nombre: "pablo pablon",
  edad: 42,
  rol: "admin",
  direccion: { ciudad: "xxxx", distrito: "aaaaaa" },
  bio: "texto largo..."
});
await db.set("usuarios.pedro",{
  id: "u2",
  nombre: "pedro pedron",
  edad: 17,
  rol: "moderador",
  direccion: { ciudad: "yyyyy", distrito: "eeeeee" },
  bio: "texto largo..."
});
await db.set("usuarios.juan",{
  id: "u3",
  nombre: "juan juanon",
  edad: 25,
  rol: "trusted",
  direccion: { ciudad: "zzzzz", distrito: "iiiiii" },
  bio: "texto largo..."
});
await db.set("usuarios.patricio",{
  id: "u4",
  nombre: "patricio patrico",
  edad: 26,
  rol: "trusted",
  direccion: { ciudad: "ttttt", distrito: "wwwwww" },
  bio: "texto largo..."
});

En base a esos datos se haran los siguientes ejemplos:

Filtrar con $match y agrupar con $group (queremos contar cuántos usuarios hay por cada rol)
let data = await db.aggregate("usuarios", [
  { $match: { id: { $prefix: "u" } } },
  { $group: { _id: "$rol", total: { $sum: 1 } } }
]);
console.log(data);
/*
[
  { _id: "trusted", total: 2 },
  { _id: "admin", total: 1 },
  { _id: "moderador", total: 1 }
]
*/
Usar acumuladores $sum, $avg, $min, $max (queremos obtener estadísticas de edades por rol)
let data = await db.aggregate("usuarios", [
  { $group: {
      _id: "$rol",
      total: { $sum: 1 },
      edadPromedio: { $avg: "$edad" },
      edadMinima: { $min: "$edad" },
      edadMaxima: { $max: "$edad" }
  }}
]);
console.log(data);
/*
[
  { _id: "trusted", total: 2, edadPromedio: 25.5, edadMinima: 25, edadMaxima: 26 },
  { _id: "admin", total: 1, edadPromedio: 42, edadMinima: 42, edadMaxima: 42 },
  { _id: "moderador", total: 1, edadPromedio: 17, edadMinima: 17, edadMaxima: 17 }
]
*/
Ordenar con $sort, vamos a ordenar los roles por numero de usuarios (descendente) y en caso de empate, por edad promedio (ascendente)
let data = await db.aggregate("usuarios", [
  { $group: {
      _id: "$rol",
      total: { $sum: 1 },
      edadPromedio: { $avg: "$edad" }
  }},
  { $sort: { total: -1, edadPromedio: 1 } }
]);
console.log(data);
/*
[
  { _id: "trusted", total: 2, edadPromedio: 25.5 },
  { _id: "admin", total: 1, edadPromedio: 42 },
  { _id: "moderador", total: 1, edadPromedio: 17 }
]
*/
Limitar resultados con $limit, vamos a mostrar solo el primer rol con más usuarios`
let data = await db.aggregate("usuarios", [
  { $group: { _id: "$rol", total: { $sum: 1 } } },
  { $sort: { total: -1 } },
  { $limit: 1 }
]);
console.log(data);
/*
[
  { _id: "trusted", total: 2 }
]
*/

Y asi como estos puedes hacerlo de acuerdo a lo que necesites, en el caso de wildcard para toda la coleccion => aggregate("*", [...etc])

update

update(path, ops)

Actualiza uno o varios valores dentro