loam

package module
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 8, 2025 License: AGPL-3.0 Imports: 4 Imported by: 2

README

Loam 🌱

A Transactional Storage Engine for Content & Metadata.

Go Report Card Go Doc

Loam é uma engine de armazenamento transacional de documentos (Headless CMS), focada em conteúdo textual e metadados. Embora a implementação padrão utilize Arquivos (Markdown, JSON, YAML, etc) sobre Git (FS Adapter), a arquitetura é agnóstica e permite outros backends (S3, SQL, etc).

Ele oferece operações de CRUD atômicas e seguras, garantindo que suas automações não corrompam seus dados. É ideal para toolmakers que constroem:

  • Assistentes de PKM (Obsidian, Logseq).
  • Gerenciadores de Configuração (GitOps, Dotfiles).
  • Pipelines de Dados Locais (ETL de CSV/JSON).
  • Geradores de Sites Estáticos (Hugo, Jekyll).

📄 Arquivos Suportados (Smart Persistence)

O Adapter padrão (FS) detecta automaticamente o formato do arquivo baseado na extensão do ID:

  • Markdown (.md): Padrão. Conteúdo + Frontmatter YAML.
  • JSON (.json): Serializa como objeto JSON puro. Campo content é opcional.
  • YAML (.yaml): Serializa como objeto YAML puro. Campo content é opcional.
  • CSV (.csv): Serializa como linha de valores. Coluna content é o corpo (opcional), demais são metadados.

🚀 Instalação

go install github.com/aretw0/loam/cmd/loam@latest

🛠️ CLI: Uso Básico

O Loam CLI funciona como um "Gerenciador de Conteúdo", abstraindo a persistência.

Inicializar

Inicia um cofre Loam. Por padrão usa o adapter de sistema de arquivos (FS + Git).

loam init
# Ou explicitamente:
loam init --adapter fs
Criar/Editar Documento

Salva 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"
Sincronizar (Sync)

Sincroniza o cofre com o remoto configurado (se o adapter suportar).

loam sync
Outros Comandos
  • Ler: loam read -id daily/2025-12-06
  • Listar: loam list
  • Deletar: loam delete -id daily/2025-12-06

📦 Library: Uso em Go

Você pode embutir o Loam em seus próprios projetos Go para gerenciar persistência de dados.

go get github.com/aretw0/loam
Exemplo
package 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("./minhas-notas",
  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)
}

131: 132: ### Typed Retrieval (Generics) 133: 134: Para maior segurança de tipos, você pode usar o wrapper genérico: 135: 136: go 137: type User struct { Name string `json:"name"` } 138: 139: // Wraps o repositório base 140: userRepo := loam.NewTyped[User](baseRepo) 141: 142: // Acesso tipado 143: user, _ := userRepo.Get(ctx, "users/alice") 144: fmt.Println(user.Data.Name) 145: 146:

📚 Documentação Técnica

Status

🚧 Alpha. A API interna pkg/loam está se estabilizando, mas mudanças podem ocorrer. A CLI é estável para uso diário.

Documentation

Overview

Package loam is the Composition Root for the Loam application.

It connects the core business logic (Domain Layer) with the infrastructure adapters (Persistence Layer) using the Hexagonal Architecture pattern.

Philosophy:

Loam is a "Headless CMS" for toolmakers. It treats a collection of documents as a transactional database, abstracting the underlying storage mechanism. While the default implementation uses the File System and Git, Loam's core is agnostic, allowing for future adapters (e.g., S3, SQLite).

Features:

  • **Hexagonal Architecture**: Core domain is isolated from persistence details.
  • **Transactional Safe**: Atomic operations regardless of the underlying storage.
  • **Metadata First**: Native support for structured metadata indexing (Frontmatter, JSON fields, etc).
  • **Typed Retrieval**: Generic wrapper (`NewTyped[T]`) for type-safe document access.
  • **Default Adapter (FS + Git)**: Out-of-the-box support for local Markdown files with Git versioning.
  • **Extensible**: Designed to support other backends (SQL, S3, NoSQL) via `core.Repository`.

Usage:

// Initialize service with functional options
svc, err := loam.New("./vault",
	loam.WithAutoInit(true),
	loam.WithLogger(logger),
)

// Save a note
err := svc.SaveNote(ctx, "my-note", "content", nil)
Example (Basic)

Example_basic demonstrates how to initialize a Vault, save a note, and read it back.

package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/aretw0/loam"
	"github.com/aretw0/loam/pkg/core"
)

func main() {
	// Create a temporary directory for the example
	tmpDir, err := os.MkdirTemp("", "loam-example-*")
	if err != nil {
		log.Fatal(err)
	}
	defer os.RemoveAll(tmpDir)

	// Initialize the Loam service (Vault) targeting the temporary directory.
	// WithAutoInit(true) ensures the underlying storage (git repo) is initialized.
	vault, err := loam.New(tmpDir, loam.WithAutoInit(true))
	if err != nil {
		log.Fatal(err)
	}

	ctx := context.Background()

	// 1. Save a Document
	err = vault.SaveDocument(ctx, "hello-world", "This is my first note in Loam.", core.Metadata{
		"tags":   []string{"example"},
		"author": "Gopher",
	})
	if err != nil {
		log.Fatal(err)
	}

	// 2. Read it back
	doc, err := vault.GetDocument(ctx, "hello-world")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Found document: %s\n", doc.ID)
}
Output:
Found document: hello-world

Index

Examples

Constants

View Source
const (
	CommitTypeFeat     = platform.CommitTypeFeat
	CommitTypeFix      = platform.CommitTypeFix
	CommitTypeDocs     = platform.CommitTypeDocs
	CommitTypeStyle    = platform.CommitTypeStyle
	CommitTypeRefactor = platform.CommitTypeRefactor
	CommitTypePerf     = platform.CommitTypePerf
	CommitTypeTest     = platform.CommitTypeTest
	CommitTypeChore    = platform.CommitTypeChore
)

Variables

View Source
var Version string

Functions

func AppendFooter added in v0.5.1

func AppendFooter(msg string) string

AppendFooter appends the Loam footer to an arbitrary message.

func FormatChangeReason added in v0.5.1

func FormatChangeReason(ctype, scope, subject, body string) string

FormatChangeReason builds a Conventional Commit message.

func Init added in v0.5.1

func Init(path string, opts ...Option) (core.Repository, error)

Init initializes a repository explicitly.

func IsDevRun added in v0.5.1

func IsDevRun() bool

IsDevRun checks if the current process is running via `go run` or `go test`.

func New added in v0.5.1

func New(path string, opts ...Option) (*core.Service, error)

New creates a new Loam Service.

func ResolveVaultPath added in v0.5.1

func ResolveVaultPath(userPath string, forceTemp bool) string

ResolveVaultPath determines the actual path for the vault based on safety rules.

func Sync added in v0.5.1

func Sync(path string, opts ...Option) error

Sync performs a synchronization (pull/push) of the vault.

Types

type DocumentModel added in v0.6.0

type DocumentModel[T any] = platform.DocumentModel[T]

DocumentModel wraps the raw core.Document with a typed Metadata field. It is the generic equivalent of core.Document.

type Option added in v0.5.1

type Option = platform.Option

Option defines a functional option for configuring Loam.

func WithAdapter added in v0.5.1

func WithAdapter(name string) Option

WithAdapter allows specifying the storage adapter to use by name.

func WithAutoInit added in v0.5.1

func WithAutoInit(auto bool) Option

WithAutoInit enables automatic initialization of the vault (git init).

func WithForceTemp added in v0.5.1

func WithForceTemp(force bool) Option

WithForceTemp forces the use of a temporary directory (useful for testing).

func WithLogger added in v0.5.1

func WithLogger(logger *slog.Logger) Option

WithLogger sets the logger for the service.

func WithMustExist added in v0.5.1

func WithMustExist(must bool) Option

WithMustExist ensures the vault directory must already exist.

func WithRepository added in v0.5.1

func WithRepository(repo core.Repository) Option

WithRepository allows injecting a custom storage adapter.

func WithSystemDir added in v0.6.0

func WithSystemDir(name string) Option

WithSystemDir allows specifying the hidden directory name (e.g. ".loam").

func WithVersioning added in v0.5.1

func WithVersioning(enabled bool) Option

WithVersioning enables or disables version control (e.g. Git).

type TypedRepository added in v0.6.0

type TypedRepository[T any] = platform.TypedRepository[T]

TypedRepository wraps a core.Repository to provide type-safe access. It acts as an Application Layer adapter, converting between raw maps and typed structs.

func NewTyped added in v0.6.0

func NewTyped[T any](repo core.Repository) *TypedRepository[T]

NewTyped creates a new type-safe repository wrapper. T is the type of the struct you want to store in the document metadata.

Example

Example_typed demonstrates how to use the Generic Typed Wrapper for type safety.

package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"path/filepath"

	"github.com/aretw0/loam"
)

func main() {
	// Setup: Temporary repository
	tmpDir, err := os.MkdirTemp("", "loam-typed-example-*")
	if err != nil {
		log.Fatal(err)
	}
	defer os.RemoveAll(tmpDir)

	// Use loam.Init to get the Repository directly
	repo, err := loam.Init(filepath.Join(tmpDir, "vault"), loam.WithAutoInit(true))
	if err != nil {
		log.Fatal(err)
	}

	// Define your Domain Model
	type User struct {
		Name  string `json:"name"`
		Email string `json:"email"`
	}

	// Wrap the repository
	userRepo := loam.NewTyped[User](repo)
	ctx := context.Background()

	// Save a typed document
	err = userRepo.Save(ctx, &loam.DocumentModel[User]{
		ID:      "users/alice",
		Content: "Alice's Profile",
		Data: User{
			Name:  "Alice",
			Email: "alice@example.com",
		},
	})
	if err != nil {
		log.Fatal(err)
	}

	// Retrieve it back
	doc, err := userRepo.Get(ctx, "users/alice")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("User Name: %s\n", doc.Data.Name)
}
Output:
User Name: Alice

Directories

Path Synopsis
cmd
loam command
examples
demos/formats command
demos/typed command
internal
pkg
core
Document is the central entity of the domain.
Document is the central entity of the domain.
git

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL