An Embedded Reactive & Transactional Engine for Content & Metadata.
Loam é uma engine reativa e transacional de documentos embutida, desenhada para aplicações centradas em conteúdo e metadados.
Por padrão, o Loam utiliza o Sistema de Arquivos + Git como banco de dados (.md, .yaml, .json, .csv), oferecendo controle de versão zero-config e legibilidade humana. No entanto, sua arquitetura Core é agnóstica, pronta para escalar para outros backends (S3, SQL) sem alterar o código do aplicativo.
É ideal para toolmakers que constroem:
- Assistentes de PKM (Obsidian, Logseq) - Storage layer apenas.
- Gerenciadores de Configuração (GitOps, Dotfiles).
- Pipelines de Dados Locais (ETL de CSV/JSON).
- Geradores de Sites Estáticos (Hugo, Jekyll).
- 🤔 Por que Loam?
- 📄 Arquivos Suportados
- 🚀 Instalação
- 🛠️ CLI: Uso Básico
- 📦 Library: Uso em Go
- 📂 Exemplos e Receitas
- 📚 Documentação Técnica
Por que não apenas usar os.WriteFile ou SQLite?
- Local-First & Soberania: Seus dados são simples arquivos de texto (
.md,.json). Você tem total controle e não depende do Loam para acessá-los. - GitOps Nativo: Todo
Savegera um histórico auditável. Reverta erros e gerencie estado de configuração com a mesma segurança de infraestrutura. - Automação Segura (ACID): Transações em lote e file-locking garantem que seus scripts de automação nunca corrompam o repositório.
O Adapter padrão (FS) detecta automaticamente o formato do arquivo baseado na extensão do ID, suportando leitura e escrita raw (--raw):
- Markdown (
.md): Padrão. Conteúdo + Frontmatter YAML. - JSON (
.json): Serializa como objeto JSON puro. Campocontenté opcional. - YAML (
.yaml): Serializa como objeto YAML puro. Campocontenté opcional. - CSV (
.csv): Serializa como linha de valores. Suporta coleções com múltiplos documentos.
Smart Retrieval: Na leitura (
Get), se o ID não tiver extensão (ex:dados), o Loam procura automaticamente pordados.md,dados.json, etc., respeitando a existência do arquivo.
go install github.com/aretw0/loam/cmd/loam@latestBaixe os binários pré-compilados na página de Releases.
Para desenvolvedores, utilizamos make para simplificar o processo:
# Build para sua plataforma atual
make build
# Cross-compilation (Linux, Windows, Mac)
make cross-build
# Instalar localmente
make installO Loam CLI funciona como um "Gerenciador de Conteúdo", abstraindo a persistência.
Inicia um cofre Loam. Por padrão usa o adapter de sistema de arquivos (FS + Git).
loam init
# Ou explicitamente:
loam init --adapter fsSalva conteúdo e registra a razão da mudança (Commits no caso do Git).
# Modo Simples (apenas mensagem)
loam write -id daily/2025-12-06 -content "Hoje foi um dia produtivo." -m "log diário"
# Modo Semântico (Type, Scope, Body)
loam write -id feature/nova-ideia -content "..." --type feat --scope ideias -m "adiciona rascunho"
# Modo Imperativo (--set)
# Define metadados individuais sem precisar de JSON
loam write --id docs/readme.md --content "Texto" --set title="Novo Readme" --set status=draft
# Modo Declarativo (--raw)
# Envie o documento inteiro via pipe. O Loam detecta Frontmatter/JSON/CSV.
echo '{"title":"Logs", "content":"..."}' | loam write --id logs/1.json --rawNote
No modo --raw, se o ID não possuir extensão (ex: --id nota), a CLI assumirá .md por padrão para tentar parsear o conteúdo. Se estiver enviando JSON ou CSV sem extensão no ID, o parse falhará.
Sincroniza o cofre com o remoto configurado (se o adapter suportar).
loam sync- Ler:
loam read -id daily/2025-12-06 - Listar:
loam list - Deletar:
loam delete -id daily/2025-12-06
Você pode embutir o Loam em seus próprios projetos Go para gerenciar persistência de dados.
go get github.com/aretw0/loampackage main
import (
"context"
"fmt"
"log/slog"
"os"
"github.com/aretw0/loam/pkg/core"
"github.com/aretw0/loam"
)
func main() {
// 1. Inicializar Serviço (Factory) com Functional Options.
// O primeiro argumento é a URI ou Path do cofre. Para o adapter FS, use o caminho do diretório.
service, err := loam.New("./meus-docs",
loam.WithAdapter("fs"), // Padrão
loam.WithAutoInit(true), // Cria diretório e git init se necessário
loam.WithLogger(slog.New(slog.NewTextHandler(os.Stdout, nil))),
)
if err != nil {
panic(err)
}
ctx := context.Background()
// 2. Escrever (Save)
// Salvamos o conteúdo com uma "razão de mudança" (Commit Message)
// Isso garante que toda mudança tenha um porquê.
ctxMsg := context.WithValue(ctx, core.ChangeReasonKey, "documento inicial")
err = service.SaveDocument(ctxMsg, "daily/hoje", "# Dia Incrível\nComeçamos o projeto.", nil)
if err != nil {
panic(err)
}
fmt.Println("Documento salvo com sucesso!")
// 3. Ler (Read)
doc, err := service.GetDocument(ctx, "daily/hoje")
if err != nil { // Tratamento simplificado
panic(err)
}
fmt.Printf("Conteúdo recuperado:\n%s\n", doc.Content)
// ... (veja exemplos completos em examples/basics/crud)
}Para maior segurança de tipos, você pode usar o wrapper genérico:
type User struct { Name string `json:"name"` }
// Abre um repositório já tipado (leitura/escrita de User)
// O ID do documento é preservado, mas o conteúdo é mapeado para User.
userRepo, err := loam.OpenTypedRepository[User]("./meus-docs")
if err != nil {
panic(err)
}
// Acesso tipado
user, _ := userRepo.Get(ctx, "users/alice")
fmt.Println(user.Data.Name) // Type-safe!Você pode observar mudanças em repositórios tipados para implementar "Hot Reload" de configurações ou interfaces reativas:
// Retorna um canal de core.Event
events, err := userRepo.Watch(ctx, "users/*")
go func() {
for event := range events {
fmt.Printf("Mudança detectada em %s\n", event.ID)
// Recarregue o documento tipado se necessário
newUser, _ := userRepo.Get(ctx, event.ID)
}
}()- Hello World: O exemplo mais básico possível.
- CRUD Básico: Create, Read, Update, Delete.
- formats: Suporte nativo a JSON, YAML, CSV e Markdown.
- Typed API: Exemplo de uso de Generics.
- CLI Scripting: Como converter dados usando Pipes e Shell (Bash/PowerShell).
- ETL & Migration: Migração de dados legados.
Se sua aplicação lida com rajadas massivas de eventos (ex: git checkout em repositórios enormes) e você nota que o watcher "congela", pode ser necessário aumentar o buffer de eventos para evitar bloqueios:
// Aumenta o buffer para 1000 eventos (Padrão: 100)
srv, _ := loam.New("path/to/vault", loam.WithEventBuffer(1000))- Devido a limitações do
inotify, novos diretórios criados após o início do watcher não são monitorados automaticamente (é necessário reiniciar o processo ou recriar o watcher). Em Windows e macOS, isso geralmente funciona nativamente. - Repositórios muito grandes (milhares de diretórios) podem exceder o limite de file descriptors. Aumente o limite via
sysctl fs.inotify.max_user_watchesse necessário.
- O suporte a CSV é otimizado para dados planos (Flat Data). Estruturas aninhadas (
map,struct,array) salvas em CSV sofrem Type Erasure: são convertidas para string (fmt.Sprintf) e não podem ser recuperadas atomicamente como objetos estruturados na leitura. Para dados hierárquicos, prefira JSON ou YAML. - Concorrência: A escrita em coleções (CSV) não possui locking de arquivo (flock). O uso concorrente por múltiplos processos pode resultar em perda de dados (Race Condition no ciclo Read-Modify-Write).
🚧 Alpha.
A API Go (github.com/aretw0/loam) e a CLI são estáveis para uso diário (Unix Compliant). Novas features como suporte a Coleções JSON/YAML estão em desenvolvimento ativo no Adapter FS.