lite-nosql
v1.0.1
Published
Embedded NoSQL document database for Node.js — no binaries, file-based, WAL + snapshot durability
Maintainers
Readme
lite-nosql
Embedded NoSQL document database for Node.js
Sem binários nativos · apenas filesystem · funciona em shared hosting
Características
| Característica | Detalhe |
|---|---|
| Zero binários nativos | Usa apenas fs, path, crypto |
| Durabilidade WAL + Snapshot | Inspirado em SQLite; crash-safe |
| CJS + ESM | Funciona com require() e import |
| TypeScript | Declarações .d.ts incluídas |
| Índices em memória | Lookups rápidos em campos indexados |
| Concorrência | Fila de escrita serializada + file lock |
| Filtros ricos | $gt, $gte, $lt, $lte, $ne, $in, $nin, $regex |
| Sem dependências | dependencies: {} — zero pacotes externos |
Instalação
npm install lite-nosqlExemplo Completo
import { abrirBanco } from "lite-nosql"; // ESM
// const { abrirBanco } = require("lite-nosql"); // CommonJS
const db = await abrirBanco({
pasta: "./dados",
modoDuravel: true, // fsync em cada escrita (recomendado em produção)
debug: false,
});
const users = db.colecao("users", { indices: ["email", "createdAt"] });
// Inserir
const id = await users.inserir({ nome: "Ana", email: "[email protected]" });
// Buscar por _id
const ana = await users.buscarUm({ _id: id });
console.log(ana); // { _id: "...", nome: "Ana", email: "[email protected]", createdAt: "...", updatedAt: "..." }
// Actualizar
await users.actualizarUm({ _id: id }, { $set: { nome: "Ana Maria" } });
await users.actualizarUm({ _id: id }, { $inc: { loginCount: 1 } });
await users.actualizarUm({ _id: id }, { $unset: ["campoObsoleto"] });
// Buscar com filtro, ordenação e paginação
const lista = await users.buscar(
{ email: "[email protected]" },
{ limite: 50, saltar: 0, ordenarPor: "createdAt", ordem: "desc" }
);
// Buscar com operadores
const recentes = await users.buscar({ createdAt: { $gt: "2024-01-01T00:00:00.000Z" } });
const especificos = await users.buscar({ email: { $in: ["[email protected]", "[email protected]"] } });
// Contar
const total = await users.contar();
const activos = await users.contar({ status: "activo" });
// Remover
await users.removerUm({ _id: id });
// Compactar manualmente (opcional — ocorre automaticamente)
await users.compactar();
// Fechar o banco (aguarda escritas pendentes)
await db.fechar();API
abrirBanco(opcoes) → Promise<Banco>
Abre (ou cria) um banco de dados no directório especificado.
| Opção | Tipo | Padrão | Descrição |
|---|---|---|---|
| pasta | string | obrigatório | Directório de armazenamento |
| modoDuravel | boolean | true | Se true, faz fsync em cada escrita |
| limiteWAL | number | 5242880 (5 MB) | Bytes de WAL antes de compactar |
| debug | boolean | false | Activar logs detalhados |
| serializar | object | null | Serializer personalizado (ver abaixo) |
Banco
banco.colecao(nome, opcoes?) → Colecao
Obtém ou cria uma colecção. Múltiplas chamadas com o mesmo nome retornam a mesma instância.
| Opção | Tipo | Descrição |
|---|---|---|
| indices | string[] | Campos a indexar para lookups eficientes |
banco.fechar() → Promise<void>
Aguarda escritas pendentes e liberta recursos.
Colecao
inserir(doc) → Promise<string>
Insere um documento. Gera _id automaticamente se ausente. Adiciona createdAt e updatedAt.
const id = await col.inserir({ nome: "Alice", idade: 30 });buscar(filtro?, opcoes?) → Promise<Documento[]>
Retorna documentos que correspondem ao filtro.
// Sem filtro — todos os documentos
await col.buscar();
// Igualdade simples
await col.buscar({ status: "activo" });
// Operadores
await col.buscar({ idade: { $gte: 18, $lt: 65 } });
await col.buscar({ nome: { $regex: /^Ana/i } });
await col.buscar({ role: { $in: ["admin", "editor"] } });
// Com opções
await col.buscar({}, { limite: 20, saltar: 40, ordenarPor: "nome", ordem: "asc" });Operadores de filtro suportados:
| Operador | Significado |
|---|---|
| $gt | maior que |
| $gte | maior ou igual |
| $lt | menor que |
| $lte | menor ou igual |
| $ne | diferente de |
| $in | valor está na lista |
| $nin | valor não está na lista |
| $regex | correspondência de regex |
buscarUm(filtro?) → Promise<Documento \| null>
Retorna o primeiro documento correspondente, ou null.
actualizarUm(filtro, update) → Promise<boolean>
Actualiza o primeiro documento correspondente. Retorna true se encontrado.
Operadores de update:
| Operador | Exemplo | Efeito |
|---|---|---|
| $set | { $set: { nome: "Bob" } } | Define campos |
| $unset | { $unset: ["campo"] } | Remove campos |
| $inc | { $inc: { visitas: 1 } } | Incrementa campo numérico |
removerUm(filtro) → Promise<boolean>
Remove o primeiro documento correspondente. Retorna true se encontrado.
contar(filtro?) → Promise<number>
Conta documentos. Sem filtro, retorna o total da colecção.
compactar() → Promise<void>
Força a compactação imediata: escreve snapshot e zera o WAL.
Armazenamento e Durabilidade
Ficheiros por colecção
dados/
users.snapshot.json ← estado compacto (JSON)
users.wal.log ← journal append-only
users.lock ← lock temporário de escritaFormato do WAL
Cada linha é um objecto JSON:
{"op":"insert","ts":1700000000000,"id":"abc123","doc":{"_id":"abc123","nome":"Ana",...}}
{"op":"update","ts":1700000001000,"id":"abc123","set":{"nome":"Ana Maria"},"unset":[],"inc":{}}
{"op":"delete","ts":1700000002000,"id":"abc123"}Ciclo de vida
Escrita → append WAL → [fsync] → actualizar memória
Leitura → sempre da memória (cache quente)
Startup → carregar snapshot → reaplicar WAL
Compactação → snapshot atómico (rename) → zerar WALCompactação
Ocorre automaticamente quando o WAL excede limiteWAL. Processo seguro:
- Escrever snapshot temporário (
.tmp) fsyncrenameatómico- Zerar WAL
Concorrência
Dentro do mesmo processo
A fila de escrita serializada (_filaEscrita) garante que inserts/updates/deletes paralelos são processados em sequência. Leituras são sempre servidas da memória.
Entre múltiplos processos
O lock de ficheiro (colecao.lock) usa O_EXCL para exclusividade. Cada processo tenta adquirir o lock com backoff exponencial antes de compactar.
Atenção: Para múltiplos processos simultâneos a fazer escritas normais (não compactação), a arquitectura WAL append-only já é segura no mesmo filesystem POSIX — escritas ao nível de
write()syscall são atómicas para blocos pequenos. Para workloads multi-processo intensivos, considere uma arquitectura cliente-servidor.
Índices
_idé sempre indexado (viaMapdirecta, O(1))- Índices opcionais por campo são
Map<valor, Set<_id>>em memória - Filtros em campos indexados usam o índice automaticamente
- Filtros em campos não indexados fazem varredura completa (com aviso em modo
debug) - Os índices são reconstruídos na abertura do banco
const col = db.colecao("posts", { indices: ["autorId", "createdAt", "status"] });Serializer Personalizado
const db = await abrirBanco({
pasta: "./dados",
serializar: {
serializar: (valor) => JSON.stringify(valor), // snapshot → string
deserializar: (raw) => JSON.parse(raw), // string → snapshot
}
});Útil para compressão, encriptação, ou substituição de JSON por MessagePack.
Limitações
| Limitação | Notas |
|---|---|
| Tudo em memória | Todos os documentos são mantidos em RAM. Não adequado para colecções com centenas de MB de dados |
| Sem transações | Não há atomicidade entre múltiplas colecções |
| Sem joins/agregações | Processamento ad-hoc deve ser feito na aplicação |
| Concorrência multi-processo | Escrita simultânea de múltiplos processos Node.js é segura ao nível do WAL; compactação usa file lock |
| Sem query planner | Apenas índices de igualdade; filtros compostos fazem varredura parcial |
| Node.js ≥18 | Usa node:test, crypto.randomBytes |
Sugestões de Nomes npm (alternativas)
Caso lite-nosql esteja ocupado:
filenosqlnodefiledbwaldbsnapdb-nodeembeddedoc
Licença
MIT
