Hanzo

Models

Define type-safe models with the Model[T] generic mixin.

Models

Every ORM model embeds orm.Model[T] and registers itself in init(). This gives each struct automatic ID generation, kind identification, and all CRUD methods.

Defining a Model

package models

import "github.com/hanzoai/orm"

type Customer struct {
    orm.Model[Customer]
    Name    string `json:"name"`
    Email   string `json:"email"`
    Plan    string `json:"plan"    orm:"default:free"`
    Credits int    `json:"credits" orm:"default:100"`
}

func init() {
    orm.Register[Customer]("customer")
}

What Model[T] Provides

MethodDescription
Id() stringReturns the entity's unique ID
Kind() stringReturns the registered kind name (e.g., "customer")
Key() orm.KeyReturns the entity's datastore key
Create() errorPersists a new entity (calls hooks, applies defaults, serializes)
Update() errorUpdates an existing entity
Delete() errorSoft-deletes the entity
Get() errorReloads the entity from the database by its key
GetById(id string) errorLoads an entity by ID
Clone() *TDeep-copies the entity
Query() *ModelQuery[T]Returns a typed query scoped to this model's kind

Factory Functions

// New[T] creates an instance, sets defaults, and initializes the DB reference
user := orm.New[User](db)

// Get[T] fetches by ID in one call
user, err := orm.Get[User](db, "1234567890")

// TypedQuery[T] starts a query builder
q := orm.TypedQuery[User](db)

// Kind[T] returns the registered kind name
name := orm.Kind[User]() // "user"

Registration Options

Register[T] accepts functional options to customize behavior:

func init() {
    orm.Register[Order]("order",
        orm.WithStringKey[Order](),           // Use string IDs (default is int64-based)
        orm.WithParent[Order](parentKeyFn),   // Set parent key for hierarchical data
        orm.WithInit[Order](initFn),          // Custom initialization logic
        orm.WithDefaults[Order](defaultsFn),  // Programmatic defaults beyond struct tags
        orm.WithCache[Order](cacheConfig),    // Per-model cache configuration
    )
}

WithStringKey

Uses string-based keys instead of the default nanosecond-precision int64 IDs.

orm.Register[APIKey]("api-key", orm.WithStringKey[APIKey]())

WithParent

Sets a parent key function for hierarchical key structures:

orm.Register[LineItem]("line-item",
    orm.WithParent[LineItem](func(li *LineItem) orm.Key {
        return orm.NewKey("order", li.OrderID, 0, nil)
    }),
)

WithInit

Runs custom initialization when New[T] is called:

orm.Register[Session]("session",
    orm.WithInit[Session](func(s *Session) {
        s.Token = generateToken()
        s.ExpiresAt = time.Now().Add(24 * time.Hour)
    }),
)

WithDefaults

Applies programmatic defaults beyond what struct tags can express:

orm.Register[Subscription]("subscription",
    orm.WithDefaults[Subscription](func(s *Subscription) {
        s.BillingAnchor = time.Now()
        s.NextInvoice = time.Now().Add(30 * 24 * time.Hour)
    }),
)

Struct Tags

json Tag

Controls JSON field naming. Required for all stored fields.

type Product struct {
    orm.Model[Product]
    Name     string  `json:"name"`
    Price    int64   `json:"price"`
    Currency string  `json:"currency"`
    Active   bool    `json:"active"`
}

orm Tag

TagDescriptionExample
default:valueSets a default value on creationorm:"default:active"
serializeAuto-serializes complex types to a _ storage fieldorm:"serialize"

datastore Tag (Legacy)

The datastore:"-" tag is detected for backwards compatibility with older model patterns. Fields with datastore:"-" and a corresponding Foo_ sibling are auto-serialized.

ID Generation

IDs are generated using UnixNano + atomic counter, producing globally unique values even in tight loops:

1708905600123456789₀₀₀₁
│                   │
└── nanosecond ts   └── atomic sequence (mod 10000)

This guarantees uniqueness without UUIDs or external sequence generators.

Example: Full Model

package models

import (
    "time"
    "github.com/hanzoai/orm"
)

type Invoice struct {
    orm.Model[Invoice]

    // Business fields
    CustomerID string    `json:"customerId"`
    Number     string    `json:"number"`
    Status     string    `json:"status"   orm:"default:draft"`
    Currency   string    `json:"currency" orm:"default:usd"`
    Total      int64     `json:"total"`
    DueDate    time.Time `json:"dueDate"`

    // Auto-serialized: LineItems stored as JSON in LineItems_ column
    LineItems  []LineItem `json:"lineItems"  orm:"serialize" datastore:"-"`
    LineItems_ string     `json:"-"          datastore:"lineItems"`

    // Metadata
    Notes    string            `json:"notes,omitempty"`
    Metadata map[string]string `json:"metadata,omitempty" orm:"serialize" datastore:"-"`
    Metadata_ string           `json:"-"                  datastore:"metadata"`
}

func init() {
    orm.Register[Invoice]("invoice")
}

How is this guide?

Last updated on

On this page