Skip to content

zoobz-io/atom

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

atom

CI Status codecov Go Report Card CodeQL Go Reference License Go Version Release

Type-segregated struct decomposition for Go.

Break structs into typed maps, work with fields programmatically, reconstruct later — without knowing T.

Normalized Type-Safe Data Without the Types

User code knows its types. Infrastructure code doesn't need to.

// User side: knows T
type User struct {
    ID      string
    Name    string
    Age     int64
    Balance float64
    Active  bool
}

atomizer, _ := atom.Use[User]()
user := &User{ID: "usr-1", Name: "Alice", Age: 30, Balance: 100.50, Active: true}
a := atomizer.Atomize(user)

// Pass atom to any library...
result := storage.Save(a)            // storage never imports User
validated := validator.Check(a)      // validator never imports User
transformed := migrator.Upgrade(a)   // migrator never imports User

// ...get it back
restored, _ := atomizer.Deatomize(result)

The receiving library sees typed maps and metadata — not T:

// Library side: doesn't know T, doesn't need T
func Save(a *atom.Atom) *atom.Atom {
    // Spec describes the struct
    fmt.Println(a.Spec.TypeName) // "User"

    // Typed maps hold the values
    for field, value := range a.Strings {
        db.SetString(field, value)
    }
    for field, value := range a.Ints {
        db.SetInt(field, value)
    }
    for field, value := range a.Floats {
        db.SetFloat(field, value)
    }

    return a
}

Type-safe field access. Zero knowledge of the original struct.

Install

go get github.com/zoobz-io/atom@latest

Requires Go 1.24+.

Quick Start

package main

import (
    "fmt"
    "github.com/zoobz-io/atom"
)

type Order struct {
    ID     string
    Total  float64
    Status string
}

func main() {
    // Register the type once
    atomizer, err := atom.Use[Order]()
    if err != nil {
        panic(err)
    }

    order := &Order{ID: "order-123", Total: 99.99, Status: "pending"}

    // Decompose to atom
    a := atomizer.Atomize(order)

    // Work with typed maps
    fmt.Printf("ID: %s\n", a.Strings["ID"])
    fmt.Printf("Total: %.2f\n", a.Floats["Total"])

    // Modify fields
    a.Strings["Status"] = "confirmed"

    // Reconstruct
    restored, _ := atomizer.Deatomize(a)
    fmt.Printf("Status: %s\n", restored.Status) // "confirmed"
}

Capabilities

Feature Description Docs
Type Segregation Strings, ints, floats, bools, times, bytes in separate typed maps Concepts
Nullable Fields Pointer types (*string, *int64) with explicit nil handling Basic Usage
Slices []string, []int64, etc. preserved as typed slices Basic Usage
Nested Composition Embed atoms within atoms for complex object graphs Nested Structs
Field Introspection Query fields, tables, and type metadata via Spec API Reference
Custom Implementations Atomizable/Deatomizable interfaces bypass reflection Interfaces
Code Generation Generate implementations for zero-reflection paths Code Generation

Why atom?

  • Type-safe without T — Libraries work with typed maps, not any or reflection
  • Field-level control — Read, write, transform individual fields programmatically
  • Decoupled — Infrastructure code never imports user types
  • Zero reflection path — Implement interfaces or use codegen for production performance
  • Sentinel integration — Automatic field discovery and metadata extraction

The Typed Bridge

Atom enables a pattern: user code owns types, infrastructure owns behaviour.

Your application defines structs. Libraries accept atoms. Each side works with what it knows — concrete types on one end, typed maps on the other. No shared type imports. No reflection at runtime (with codegen).

// Your domain package defines types
type User struct { ... }
type Order struct { ... }

// Storage library accepts atoms — never sees User or Order
func (s *Store) Put(a *atom.Atom) error { ... }
func (s *Store) Get(spec atom.Spec, id string) (*atom.Atom, error) { ... }

// Your code bridges the two
a := userAtomizer.Atomize(user)
store.Put(a)

The contract is the Atom structure. The types stay where they belong.

Documentation

Learn

Guides

Cookbook

Reference

Contributing

See CONTRIBUTING.md for guidelines. Run make help for available commands.

License

MIT License — see LICENSE for details.