orm
A typed, fluent communications bridge over backend Media. Callers build
intent; a Medium transports it. The same Schema declaration
lands against DuckDB today, Postgres tomorrow, a Borg DataNode next week
— the bridge is stateless, the backend’s capability is what changes.
Built on core/go — every query returns a Result.
Install
Section titled “Install”go get dappco.re/go/orm@latestImport
Section titled “Import”import "dappco.re/go/orm"Declare a Schema
Section titled “Declare a Schema”orm.Define collects field declarations into a Schema value. Field
builders are chainable so constraints stay close to the column they
describe:
type User struct { ID int64 Name string Email string}
func (User) Schema() orm.Schema { return orm.Define(func(b *orm.Builder) { b.Name("users") b.PK("id") b.String("name").NotNull() b.String("email").Unique() })}
type Post struct { ID int64 UserID int64 Title string}
func (Post) Schema() orm.Schema { return orm.Define(func(b *orm.Builder) { b.Name("posts") b.PK("id") b.Int64("user_id") b.String("title") })}Schemas are values — pass them around, serialise them to JSON via
SchemaFromJSON, ship them across the polyglot boundary (per RFC §12 the
same Schema() declaration ports across Go, PHP, and TS implementations
producing identical JSON shape).
Mount a Medium
Section titled “Mount a Medium”A Medium is what carries intent to a real backend. Today: in-memory
(NewMemium), DuckDB (via go/store), more to follow.
Mount once on a Core and every Bridge call routes through:
c := core.New()mem := orm.NewMemium()orm.Mount(c, "default", mem)Query — typed Bridge
Section titled “Query — typed Bridge”Bridge[T] is the typed query builder. Construct one from a Schema +
Core, chain predicates, terminate with a fetch verb that returns
core.Result:
bridge := orm.From[User](c)
r := bridge. Where("email", "=", "a@b.com"). First()if !r.OK { return r }user := r.Value.(*User)
// Multi-row + ordering + limitr := orm.From[Post](c). Where("user_id", "=", user.ID). OrderBy("id", "desc"). Limit(10). Get()posts := r.Value.([]*Post)
// Aggregatesr := orm.From[Post](c).Where("user_id", "=", user.ID).Count()n := r.Value.(int)The fluent surface mirrors Eloquent’s reading rhythm without dragging in Eloquent’s machinery. Every predicate, every order, every join is just intent in a Go value until the Medium turns it into a backend call.
Write — Insert, Update, Delete
Section titled “Write — Insert, Update, Delete”Writes go through the same Bridge or the package-level helpers:
// Through the Bridgebridge.Insert(&User{Name: "alice", Email: "a@b.com"})bridge.Where("id", "=", 42).Update(map[string]any{"name": "renamed"})bridge.Where("id", "=", 42).Delete(&User{})
// Package-level — multi-row insertorm.Insert(c, &User{Name: "bob"}, &User{Name: "carol"})orm.Delete(c, &User{ID: 7})Every write returns core.Result — failures surface the bridge intent
that didn’t transport plus the underlying Medium error, so the call site
sees what was attempted.
Joins + relations
Section titled “Joins + relations”Bridge.From(...) aliases a related Schema for cross-table predicates;
Bridge.With(...) declares eager-load relations the Medium should
pre-fetch:
users := orm.From[User](c). From(orm.A{"posts": Post{}.Schema()}). Where("posts.title", "like", "%golang%"). With("posts"). Get()Predicate grouping
Section titled “Predicate grouping”Compound (A AND (B OR C)) predicates with WhereGroup:
bridge. Where("status", "=", "active"). WhereGroup(func(g *orm.Group) { g.Where("role", "=", "admin"). OrWhere("role", "=", "owner") }). Get()Sibling packages
Section titled “Sibling packages”go/store— the persistence layer most orm Mediums route togo/io— Medium transport contract orm extendsgo/api— the polyglot boundary orm Schemas cross at runtime
Source
Section titled “Source”github.com/dAppCore/orm — full source,
RFC, and the IMPLEMENTATION_PLAN that sequences the Go v1 build.