zoobzio January 5, 2025 Edit this page

Basic Usage

Common patterns for working with atom.

Registration

Single Type

atomizer, err := atom.Use[User]()
if err != nil {
    log.Fatalf("failed to register User: %v", err)
}

Multiple Types

Register all types at startup for predictable initialization:

func init() {
    types := []func() error{
        func() error { _, err := atom.Use[User](); return err },
        func() error { _, err := atom.Use[Order](); return err },
        func() error { _, err := atom.Use[Product](); return err },
    }

    for _, register := range types {
        if err := register(); err != nil {
            log.Fatalf("type registration failed: %v", err)
        }
    }
}

Handling Errors

Registration fails for unsupported types:

type Invalid struct {
    Data map[string]any // Maps are not supported
}

_, err := atom.Use[Invalid]()
// err: "type Invalid: field "Data": map types are not supported"

Atomization

Basic Atomize

user := &User{Name: "Alice", Age: 30}
atom := atomizer.Atomize(user)

Creating Empty Atoms

Use NewAtom() to create a properly sized empty atom:

atom := atomizer.NewAtom()
atom.Strings["Name"] = "Bob"
atom.Ints["Age"] = 25

Reading Fields

Access fields through type-specific maps:

atom := atomizer.Atomize(user)

name := atom.Strings["Name"]
age := atom.Ints["Age"]
active := atom.Bools["Active"]
created := atom.Times["CreatedAt"]

Checking Field Existence

if name, ok := atom.Strings["Name"]; ok {
    fmt.Println("Name:", name)
}

Deatomization

Basic Deatomize

restored, err := atomizer.Deatomize(atom)
if err != nil {
    log.Printf("deatomize failed: %v", err)
    return
}

Handling Errors

Deatomization can fail for overflow:

type Small struct {
    Value int8
}

atomizer, _ := atom.Use[Small]()
a := &atom.Atom{Ints: map[string]int64{"Value": 200}}

_, err := atomizer.Deatomize(a)
// err: "value 200 overflows int8 (range -128 to 127)"

Missing Fields

Missing fields are left at their zero value:

atom := &atom.Atom{
    Strings: map[string]string{"Name": "Alice"},
    // Age not set
}

user, _ := atomizer.Deatomize(atom)
fmt.Println(user.Name) // "Alice"
fmt.Println(user.Age)  // 0 (zero value)

Field Introspection

List All Fields

fields := atomizer.Fields()
for _, f := range fields {
    fmt.Printf("%s -> %s\n", f.Name, f.Table)
}
// ID -> ints
// Name -> strings
// Email -> strings

Fields by Table

stringFields := atomizer.FieldsIn(atom.TableStrings)
// ["Name", "Email"]

intFields := atomizer.FieldsIn(atom.TableInts)
// ["ID", "Age"]

Get Table for Field

table, ok := atomizer.TableFor("Age")
if ok {
    fmt.Println("Age is stored in:", table) // "ints"
}

Working with Pointers

Pointer Fields

Pointer fields use separate tables and can be nil:

type Config struct {
    Name     string
    MaxRetry *int64
    Timeout  *float64
}

atomizer, _ := atom.Use[Config]()
cfg := &Config{Name: "default", MaxRetry: nil}

atom := atomizer.Atomize(cfg)
// atom.Strings["Name"] = "default"
// atom.IntPtrs["MaxRetry"] = nil

Setting Pointer Values

atom := atomizer.NewAtom()
atom.Strings["Name"] = "custom"

retries := int64(3)
atom.IntPtrs["MaxRetry"] = &retries

cfg, _ := atomizer.Deatomize(atom)
fmt.Println(*cfg.MaxRetry) // 3

Working with Slices

Slice Fields

type User struct {
    Name   string
    Tags   []string
    Scores []int64
}

atomizer, _ := atom.Use[User]()
user := &User{
    Name:   "Alice",
    Tags:   []string{"admin", "verified"},
    Scores: []int64{95, 87, 92},
}

atom := atomizer.Atomize(user)
// atom.Strings["Name"] = "Alice"
// atom.StringSlices["Tags"] = ["admin", "verified"]
// atom.IntSlices["Scores"] = [95, 87, 92]

Empty vs Nil Slices

// Nil slice - no entry in atom
user := &User{Tags: nil}
atom := atomizer.Atomize(user)
_, ok := atom.StringSlices["Tags"] // ok = false

// Empty slice - empty entry in atom
user := &User{Tags: []string{}}
atom := atomizer.Atomize(user)
tags := atom.StringSlices["Tags"] // tags = []

Type Metadata

Access Spec

spec := atomizer.Spec()
fmt.Println(spec.TypeName)    // "User"
fmt.Println(spec.PackageName) // "github.com/example/app"

Atom Spec

Each atom carries its type spec:

atom := atomizer.Atomize(user)
fmt.Println(atom.Spec.TypeName) // "User"

Best Practices

Do: Register Early

Register types at application startup:

func main() {
    // Register all types first
    userAtomizer, _ = atom.Use[User]()
    orderAtomizer, _ = atom.Use[Order]()

    // Then use them
    runApp()
}

Do: Reuse Atomizers

Store atomizers rather than calling Use repeatedly:

// Good
var userAtomizer *atom.Atomizer[User]

func init() {
    userAtomizer, _ = atom.Use[User]()
}

func ProcessUser(u *User) *atom.Atom {
    return userAtomizer.Atomize(u)
}

Don't: Ignore Errors

Always check deatomization errors:

// Bad
user, _ := atomizer.Deatomize(atom)

// Good
user, err := atomizer.Deatomize(atom)
if err != nil {
    return fmt.Errorf("deatomize: %w", err)
}

Next Steps