goodm

package module
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Apr 21, 2026 License: MIT Imports: 19 Imported by: 0

README

goodm

CI Go Reference Go Report Card Go Version MongoDB

Documentation | API Reference

A schema-driven ODM for MongoDB in Go. Define your models as Go structs, and goodm handles validation, hooks, indexes, immutability, references, middleware, aggregation, bulk operations, and transactions.

type User struct {
    goodm.Model `bson:",inline"`
    Email       string `bson:"email" goodm:"unique,required"`
    Name        string `bson:"name"  goodm:"required,immutable"`
    Age         int    `bson:"age"   goodm:"min=13,max=120"`
    Role        string `bson:"role"  goodm:"enum=admin|user|mod,default=user"`
}

func init() {
    goodm.Register(&User{}, "users")
}

Why goodm?

Go has a strong MongoDB driver, but no mature ODM that treats Go structs as the schema contract. The ecosystem gap is real:

  • Prisma dropped Go support. If you were looking to Prisma for structured data modeling in Go, that door is closed.
  • The official mongo-driver is low-level. You get bson.D and Collection.InsertOne — everything else (validation, hooks, timestamps, immutability, index management) is left to you.
  • Mongoose proved the pattern. Node.js developers have had schema-as-code with lifecycle hooks, population, and middleware for over a decade. Go deserves the same.

goodm fills that gap. Your Go struct is the schema. Tags declare constraints, indexes, and references. The ODM enforces them on every write — no separate schema files, no code generation step, no runtime surprises.

What you get:

  • One source of truth. The struct definition is the database contract. Add goodm:"unique,required" and the index exists, the validation runs, the error is typed.
  • Full write lifecycle. Create and Update automatically handle ID generation, timestamps, hook execution, validation, and immutable field enforcement. You don't wire any of it.
  • Escape hatches. UpdateOne, DeleteOne, UpdateMany, DeleteMany bypass the ODM and hit MongoDB directly when you need raw performance.
  • Schema-aware CLI. goodm discover reverse-engineers an existing database into Go structs. goodm migrate diffs your structs against a live database and syncs indexes.

How does goodm compare?

Feature goodm mongo-driver mgm mongox
Schema-as-struct tags Yes No Partial No
Lifecycle hooks 6 hooks No 3 hooks No
Validation (required, enum, min/max) Yes No No No
Immutable fields Yes No No No
Optimistic concurrency (versioning) Yes No No No
Population (ref resolution) Yes No No No
Aggregation builder Fluent API Manual No No
Middleware (global + per-model) Yes No No No
CLI: discover (DB → Go structs) Yes No No No
CLI: migrate (sync indexes) Yes No No No
Default values Yes No No No
Escape hatches (raw MongoDB) Yes N/A Yes Yes

Install

go get github.com/dwoolworth/goodm

CLI (optional):

go install github.com/dwoolworth/goodm/cmd/goodm@latest

Quick Start

package main

import (
    "context"
    "log"

    "github.com/dwoolworth/goodm"
    "go.mongodb.org/mongo-driver/v2/bson"
)

type User struct {
    goodm.Model `bson:",inline"`
    Email       string `bson:"email" goodm:"unique,required"`
    Name        string `bson:"name"  goodm:"required,immutable"`
    Age         int    `bson:"age"   goodm:"min=13,max=120"`
}

func init() {
    goodm.Register(&User{}, "users")
}

func main() {
    ctx := context.Background()

    // Connect and enforce indexes
    db, _ := goodm.Connect(ctx, "mongodb://localhost:27017", "myapp")
    goodm.Enforce(ctx, db)

    // Create
    user := &User{Email: "alice@example.com", Name: "Alice", Age: 30}
    goodm.Create(ctx, user)

    // Read
    found := &User{}
    goodm.FindOne(ctx, bson.D{{Key: "email", Value: "alice@example.com"}}, found)

    // Update
    found.Age = 31
    goodm.Update(ctx, found)

    // Delete
    goodm.Delete(ctx, found)
}

Features

Feature Description
Schema Tags unique, index, required, immutable, default, enum, min, max, ref
CRUD Create, FindOne, Find, FindCursor, Update, Delete with full lifecycle
Raw Operations UpdateOne, DeleteOne, UpdateMany, DeleteMany for direct MongoDB access
Hooks BeforeCreate, AfterCreate, BeforeSave, AfterSave, BeforeDelete, AfterDelete
Validation Automatic on Create/Update: required, enum, min/max, immutable enforcement
Middleware Global and per-model middleware chains wrapping all operations
Population Resolve ref= fields by fetching referenced documents
Aggregation Fluent pipeline builder with Match, Group, Sort, Lookup, and more
Bulk CreateMany with hooks/validation, UpdateMany/DeleteMany passthrough
Transactions WithTransaction wraps operations in a MongoDB session transaction
CLI goodm discover (introspect DB), goodm migrate (sync indexes), goodm inspect (view schemas)

Documentation

Detailed guides are in the docs/ directory:

Schema Tags

Tags are added to struct fields via goodm:"...":

type Product struct {
    goodm.Model `bson:",inline"`
    SKU         string        `bson:"sku"      goodm:"unique,required,immutable"`
    Name        string        `bson:"name"     goodm:"required"`
    Price       int           `bson:"price"    goodm:"min=0"`
    Category    string        `bson:"category" goodm:"index,enum=electronics|clothing|food"`
    Stock       int           `bson:"stock"    goodm:"default=0,min=0"`
    BrandID     bson.ObjectID `bson:"brand"    goodm:"ref=brands"`
}
Tag Effect
unique Creates a unique index
index Creates a non-unique index
required Field must be non-zero on Create/Update
immutable Field cannot change after creation
default=X Default value annotation
enum=a|b|c Value must be one of the listed options
min=N Minimum numeric value
max=N Maximum numeric value
ref=collection References a document in another collection

Middleware

// Global middleware — runs on every operation
goodm.Use(func(ctx context.Context, op *goodm.OpInfo, next func(context.Context) error) error {
    start := time.Now()
    err := next(ctx)
    log.Printf("%s %s.%s took %v", op.Operation, op.Collection, op.ModelName, time.Since(start))
    return err
})

// Per-model middleware
goodm.UseFor("User", func(ctx context.Context, op *goodm.OpInfo, next func(context.Context) error) error {
    log.Printf("User operation: %s", op.Operation)
    return next(ctx)
})

Aggregation

var results []bson.M
goodm.NewPipeline(&User{}).
    Match(bson.D{{Key: "age", Value: bson.D{{Key: "$gte", Value: 21}}}}).
    Group(bson.D{
        {Key: "_id", Value: "$role"},
        {Key: "count", Value: bson.D{{Key: "$sum", Value: 1}}},
    }).
    Sort(bson.D{{Key: "count", Value: -1}}).
    Limit(10).
    Execute(ctx, &results)

Transactions

err := goodm.WithTransaction(ctx, func(ctx context.Context) error {
    if err := goodm.Create(ctx, order); err != nil {
        return err
    }
    return goodm.Update(ctx, inventory)
})

Requires a MongoDB replica set. All goodm operations inside the callback participate in the transaction automatically.

Requirements

  • Go 1.19+
  • MongoDB 4.0+ (6.0+ recommended)
  • Replica set required for transactions

Contributing

Contributions are welcome! Here's how to get started:

  1. Fork the repository
  2. Clone your fork: git clone https://github.com/<you>/goodm.git
  3. Create a branch: git checkout -b my-feature
  4. Make your changes and add tests
  5. Run tests: go test ./... (requires a running MongoDB instance)
  6. Commit: git commit -m "Add my feature"
  7. Push: git push origin my-feature
  8. Open a Pull Request against main
Guidelines
  • Follow existing code style and patterns
  • Add tests for new features — integration tests use a local MongoDB
  • Keep PRs focused on a single change
  • Update documentation in docs/ if you add or change behavior
  • Run go vet ./... before submitting
Reporting Issues

Found a bug or have a feature request? Open an issue with a clear description and, if applicable, steps to reproduce.

License

MIT

Documentation

Overview

Example (Hooks)

This example shows how lifecycle hooks work. Implement BeforeCreate, AfterCreate, BeforeSave, AfterSave, BeforeDelete, or AfterDelete on your model struct. goodm detects them automatically — no registration needed.

package main

import (
	"fmt"

	"github.com/dwoolworth/goodm"
)

func main() {
	schema, _ := goodm.Get("AuditableUser")

	fmt.Printf("Model: %s\n", schema.ModelName)
	fmt.Printf("Hooks: %v\n", schema.Hooks)

}
Output:
Model: AuditableUser
Hooks: [BeforeCreate AfterCreate]
Example (Pipeline)

This example shows the fluent aggregation pipeline builder. Pipelines are bound to a model for collection resolution.

package main

import (
	"fmt"

	"github.com/dwoolworth/goodm"
	"go.mongodb.org/mongo-driver/v2/bson"
)

// User is an example model with schema tags for validation, uniqueness, and defaults.
type User struct {
	goodm.Model `bson:",inline"`
	Email       string `bson:"email" goodm:"unique,required"`
	Name        string `bson:"name"  goodm:"required,immutable"`
	Age         int    `bson:"age"   goodm:"min=13,max=120"`
	Role        string `bson:"role"  goodm:"enum=admin|user|mod,default=user"`
}

func main() {
	// Build a pipeline (does not execute without a DB connection)
	p := goodm.NewPipeline(&User{}).
		Match(bson.D{{Key: "age", Value: bson.D{{Key: "$gte", Value: 21}}}}).
		Group(bson.D{
			{Key: "_id", Value: "$role"},
			{Key: "count", Value: bson.D{{Key: "$sum", Value: 1}}},
		}).
		Sort(bson.D{{Key: "count", Value: -1}}).
		Limit(10)

	fmt.Printf("Pipeline stages: %d\n", len(p.Stages()))

}
Output:
Pipeline stages: 4
Example (SchemaDefinition)

This example shows how to register a model and use schema tags to declare constraints. The struct definition is the database contract — tags like unique, required, immutable, min, max, enum, and default are enforced automatically on Create and Update.

package main

import (
	"fmt"
	"strings"

	"github.com/dwoolworth/goodm"
)

func main() {
	schema, ok := goodm.Get("User")
	if !ok {
		fmt.Println("User schema not found")
		return
	}
	fmt.Printf("Model: %s → collection: %s\n", schema.ModelName, schema.Collection)
	fmt.Printf("Fields: %d\n", len(schema.Fields))

	for _, f := range schema.Fields {
		var tags []string
		if f.Required {
			tags = append(tags, "required")
		}
		if f.Unique {
			tags = append(tags, "unique")
		}
		if f.Immutable {
			tags = append(tags, "immutable")
		}
		if f.Enum != nil {
			tags = append(tags, fmt.Sprintf("enum=%s", strings.Join(f.Enum, "|")))
		}
		if f.Default != "" {
			tags = append(tags, fmt.Sprintf("default=%s", f.Default))
		}
		if len(tags) > 0 {
			fmt.Printf("  %s: %s\n", f.BSONName, strings.Join(tags, ", "))
		}
	}

}
Output:
Model: User → collection: users
Fields: 8
  email: required, unique
  name: required, immutable
  role: enum=admin|user|mod, default=user
Example (Validation)

This example shows how validation works. When you Create or Update a document, goodm automatically validates against the schema tags. Validation errors are returned as typed ValidationErrors you can inspect programmatically.

package main

import (
	"fmt"

	"github.com/dwoolworth/goodm"
)

// User is an example model with schema tags for validation, uniqueness, and defaults.
type User struct {
	goodm.Model `bson:",inline"`
	Email       string `bson:"email" goodm:"unique,required"`
	Name        string `bson:"name"  goodm:"required,immutable"`
	Age         int    `bson:"age"   goodm:"min=13,max=120"`
	Role        string `bson:"role"  goodm:"enum=admin|user|mod,default=user"`
}

func main() {
	// A user with age below the minimum (13) and an invalid role
	user := &User{
		Email: "bob@example.com",
		Name:  "Bob",
		Age:   5,
		Role:  "superadmin",
	}

	schema, _ := goodm.Get("User")
	errs := goodm.Validate(user, schema)

	for _, e := range errs {
		fmt.Printf("%s: %s\n", e.Field, e.Message)
	}

}
Output:
age: value 5 is less than minimum 13
role: value "superadmin" is not in enum [admin user mod]

Index

Examples

Constants

View Source
const DefaultDriftSampleSize = 100

DefaultDriftSampleSize is the number of documents sampled for drift detection.

Variables

View Source
var (
	// ErrNotFound is returned when a document is not found.
	ErrNotFound = errors.New("goodm: document not found")

	// ErrNoDatabase is returned when no database connection is available.
	ErrNoDatabase = errors.New("goodm: no database connection (call Connect first)")

	// ErrVersionConflict is returned when an update fails due to a version mismatch
	// (optimistic concurrency control). This means another process modified the
	// document between your read and write.
	ErrVersionConflict = errors.New("goodm: version conflict (document was modified by another process)")
)

Functions

func BatchPopulate added in v0.2.0

func BatchPopulate(ctx context.Context, models interface{}, field string, results interface{}, opts ...PopulateOptions) error

BatchPopulate resolves a single ref field across a slice of models in one query. It collects unique IDs from the ref field and fetches all referenced documents using a single $in query, avoiding N+1 overhead.

models must be a slice or pointer to a slice (e.g. []Post or *[]Post). field is the bson name of the ref field (e.g. "author"). results must be a pointer to a slice of the referenced type (e.g. *[]User).

Example:

var posts []Post
goodm.Find(ctx, bson.D{}, &posts)

var authors []User
err := goodm.BatchPopulate(ctx, posts, "author", &authors)

func ClearMiddleware

func ClearMiddleware()

ClearMiddleware removes all registered middleware. Useful for testing.

func Connect

func Connect(ctx context.Context, uri string, dbName string) (*mongo.Database, error)

Connect establishes a connection to MongoDB and returns the database handle. It also stores the database reference globally for use by Enforce and the CLI.

func Create

func Create(ctx context.Context, model interface{}, opts ...CreateOptions) error

Create inserts a new document. It generates an ID if zero, sets timestamps, runs BeforeCreate/AfterCreate hooks, and validates against the schema.

func CreateMany

func CreateMany(ctx context.Context, models interface{}, opts ...CreateOptions) error

CreateMany inserts multiple documents. It generates IDs, sets timestamps, runs BeforeCreate/AfterCreate hooks, and validates each model before performing a single InsertMany call.

models must be a slice of structs or struct pointers (e.g. []User or []*User).

Performance: hooks and validation run per-model. For large batches where you don't need the ODM lifecycle, use the mongo driver's InsertMany directly.

func DB

func DB() *mongo.Database

DB returns the globally stored database reference. Returns nil if Connect has not been called.

func Delete

func Delete(ctx context.Context, model interface{}, opts ...DeleteOptions) error

Delete removes a document by its ID. Runs BeforeDelete/AfterDelete hooks.

func DeleteOne

func DeleteOne(ctx context.Context, filter interface{}, model interface{}, opts ...DeleteOptions) error

DeleteOne deletes a single document matching filter. The model parameter is used only for schema/collection lookup (e.g. &User{}).

Performance: This is a direct passthrough to MongoDB's DeleteOne. It bypasses hooks entirely. Use Delete for the full ODM lifecycle with BeforeDelete/AfterDelete hooks, or use this when you need raw performance and don't require hook execution.

func Enforce

func Enforce(ctx context.Context, db *mongo.Database, opts ...EnforceOptions) error

Enforce ensures that all registered schemas are reflected in the database. It creates missing indexes and optionally detects schema drift based on the provided options. If no options are provided, drift detection is skipped.

func Find

func Find(ctx context.Context, filter interface{}, results interface{}, opts ...FindOptions) error

Find finds all documents matching filter and decodes them into results. results must be a pointer to a slice (e.g. *[]User).

func FindCursor

func FindCursor(ctx context.Context, filter interface{}, model interface{}, opts ...FindOptions) (*mongo.Cursor, error)

FindCursor returns a raw *mongo.Cursor for streaming large result sets. The model parameter is used only for schema/collection lookup (e.g. &User{}).

func FindOne

func FindOne(ctx context.Context, filter interface{}, result interface{}, opts ...FindOptions) error

FindOne finds a single document matching filter and decodes it into result. Returns ErrNotFound if no document matches.

func GenerateModel

func GenerateModel(coll DiscoveredCollection, opts GenerateOptions) ([]byte, error)

GenerateModel generates Go source code for a discovered collection.

func GetAll

func GetAll() map[string]*Schema

GetAll returns all registered schemas.

func ListExistingIndexes

func ListExistingIndexes(ctx context.Context, coll *mongo.Collection) (map[string]bool, error)

ListExistingIndexes returns a set of index names that exist on the collection.

func ParseBSONTag

func ParseBSONTag(tag string) (name string, omitempty bool)

ParseBSONTag extracts the BSON field name from a `bson:"..."` struct tag. Returns the field name and whether the field should be omitted when empty.

func Populate

func Populate(ctx context.Context, model interface{}, refs Refs, opts ...PopulateOptions) error

Populate resolves ref fields on a loaded model by fetching referenced documents from their respective collections. Each key in refs is a bson field name tagged with goodm:"ref=collection", and the corresponding value is a pointer to a struct where the referenced document will be decoded.

For single refs (bson.ObjectID), the target should be a pointer to a struct:

profile := &Profile{}
err := goodm.Populate(ctx, user, goodm.Refs{"profile": profile})

For array refs ([]bson.ObjectID), the target should be a pointer to a slice:

var tags []Tag
err := goodm.Populate(ctx, post, goodm.Refs{"tags": &tags})

func Register

func Register(model interface{}, collection string) error

Register parses a model struct and registers its schema. The model should be a pointer to a struct that embeds goodm.Model. The collection parameter is the MongoDB collection name.

func Update

func Update(ctx context.Context, model interface{}, opts ...UpdateOptions) error

Update replaces an existing document. It fetches the current document to enforce immutable fields, runs BeforeSave/AfterSave hooks, validates, and sets UpdatedAt.

func UpdateFields added in v0.4.0

func UpdateFields(ctx context.Context, model interface{}, fields bson.M, opts ...UpdateOptions) error

UpdateFields performs a partial $set update on specific fields of a document, identified by the model's ID. It runs middleware, sets UpdatedAt, and increments the version — but does NOT enforce optimistic locking (last-write-wins).

Use this instead of Update when concurrent writers touch disjoint fields and version conflicts are acceptable (e.g. progress tracking, heartbeats).

fields is a map of bson field names to their new values:

err := goodm.UpdateFields(ctx, &task, bson.M{"step": 5, "tokens_used": 12000})

func UpdateOne

func UpdateOne(ctx context.Context, filter interface{}, update interface{}, model interface{}, opts ...UpdateOptions) error

UpdateOne performs a partial update on a single document matching filter. The model parameter is used only for schema/collection lookup (e.g. &User{}). The update parameter should be a MongoDB update document (e.g. bson.D{{"$set", bson.D{...}}}).

Performance: This is a direct passthrough to MongoDB's UpdateOne. It bypasses hooks, validation, and immutable field enforcement. Use Update for the full ODM lifecycle, or use this when you need raw performance and accept responsibility for data integrity.

func Use

func Use(fns ...MiddlewareFunc)

Use registers global middleware applied to all CRUD operations. Middleware executes in the order registered: global first, then per-model.

func UseFor

func UseFor(modelName string, fns ...MiddlewareFunc)

UseFor registers middleware for a specific model name (the Go struct name). Per-model middleware executes after global middleware.

func WithTransaction

func WithTransaction(ctx context.Context, fn func(ctx context.Context) error, opts ...TransactionOptions) error

WithTransaction executes fn within a MongoDB transaction. All goodm CRUD operations called within fn automatically participate in the transaction via the session-aware context.

If fn returns an error, the transaction is aborted. If fn succeeds, the transaction is committed. Transient transaction errors are retried automatically by the driver.

Example:

err := goodm.WithTransaction(ctx, func(ctx context.Context) error {
    if err := goodm.Create(ctx, user); err != nil {
        return err
    }
    if err := goodm.Create(ctx, profile); err != nil {
        return err
    }
    return nil
})

Types

type ActionType

type ActionType int

ActionType describes the kind of migration action.

const (
	ActionCreateIndex ActionType = iota
	ActionDropIndex
	ActionFieldDrift // field in DB not in schema
)

type AfterCreate

type AfterCreate interface {
	AfterCreate(ctx context.Context) error
}

AfterCreate is called after inserting a new document.

type AfterDelete

type AfterDelete interface {
	AfterDelete(ctx context.Context) error
}

AfterDelete is called after deleting a document.

type AfterSave

type AfterSave interface {
	AfterSave(ctx context.Context) error
}

AfterSave is called after updating an existing document.

type BeforeCreate

type BeforeCreate interface {
	BeforeCreate(ctx context.Context) error
}

BeforeCreate is called before inserting a new document.

type BeforeDelete

type BeforeDelete interface {
	BeforeDelete(ctx context.Context) error
}

BeforeDelete is called before deleting a document.

type BeforeSave

type BeforeSave interface {
	BeforeSave(ctx context.Context) error
}

BeforeSave is called before updating an existing document.

type BulkResult

type BulkResult struct {
	InsertedCount int64
	MatchedCount  int64
	ModifiedCount int64
	DeletedCount  int64
}

BulkResult contains the outcome of a bulk operation.

func DeleteMany

func DeleteMany(ctx context.Context, filter interface{}, model interface{}, opts ...DeleteOptions) (*BulkResult, error)

DeleteMany deletes all documents matching filter. The model parameter is used only for schema/collection lookup (e.g. &User{}).

Performance: This is a direct passthrough to MongoDB's DeleteMany. It bypasses hooks entirely. Use Delete for the full ODM lifecycle on individual documents.

func UpdateMany

func UpdateMany(ctx context.Context, filter, update interface{}, model interface{}, opts ...UpdateOptions) (*BulkResult, error)

UpdateMany updates all documents matching filter with the given update document. The model parameter is used only for schema/collection lookup (e.g. &User{}).

Performance: This is a direct passthrough to MongoDB's UpdateMany. It bypasses hooks, validation, and immutable field enforcement. Use Update for the full ODM lifecycle on individual documents.

type CollectionOptions added in v0.3.0

type CollectionOptions struct {
	ReadPreference *readpref.ReadPref
	ReadConcern    *readconcern.ReadConcern
	WriteConcern   *writeconcern.WriteConcern
}

CollectionOptions configures per-schema MongoDB collection behavior. Implement the Configurable interface on your model to set these.

type CompoundIndex

type CompoundIndex struct {
	Fields []string
	Unique bool
}

CompoundIndex represents a multi-field index on a MongoDB collection.

func NewCompoundIndex

func NewCompoundIndex(fields ...string) CompoundIndex

NewCompoundIndex creates a non-unique compound index on the given fields.

func NewUniqueCompoundIndex

func NewUniqueCompoundIndex(fields ...string) CompoundIndex

NewUniqueCompoundIndex creates a unique compound index on the given fields.

type Configurable added in v0.3.0

type Configurable interface {
	CollectionOptions() CollectionOptions
}

Configurable is implemented by models that define per-schema collection options such as read preference, read concern, and write concern.

Example:

func (u *User) CollectionOptions() goodm.CollectionOptions {
    return goodm.CollectionOptions{
        ReadPreference: readpref.SecondaryPreferred(),
        WriteConcern:   writeconcern.Majority(),
    }
}

type CreateOptions

type CreateOptions struct {
	DB *mongo.Database
}

CreateOptions configures the Create operation.

type DeleteOptions

type DeleteOptions struct {
	DB *mongo.Database
}

DeleteOptions configures the Delete operation.

type DiscoverOptions

type DiscoverOptions struct {
	SampleSize  int      // documents to sample per collection (default 500)
	Collections []string // empty = all collections
}

DiscoverOptions controls how database discovery is performed.

type DiscoveredCollection

type DiscoveredCollection struct {
	Name     string
	Fields   []DiscoveredField
	Indexes  []DiscoveredIndex
	DocCount int64
}

DiscoveredCollection holds the discovery results for a single collection.

func Discover

Discover introspects a MongoDB database by sampling documents and reading indexes.

type DiscoveredField

type DiscoveredField struct {
	BSONName   string
	GoType     string // inferred Go type
	IsRequired bool   // appears in every sampled doc
	IsUnique   bool   // has a unique index
	IsIndexed  bool   // has a non-unique index
}

DiscoveredField describes a single field found in a collection's documents.

type DiscoveredIndex

type DiscoveredIndex struct {
	Name   string
	Keys   []string // field names in order
	Unique bool
}

DiscoveredIndex describes an index found on a collection.

type DriftError

type DriftError struct {
	Collection string
	Field      string
	Message    string
}

DriftError indicates a field exists in the database but not in the schema.

func DetectDrift

func DetectDrift(ctx context.Context, db *mongo.Database, schema *Schema, sampleSize int) []DriftError

DetectDrift samples documents from the collection and reports fields that exist in the database but not in the schema. The sampleSize parameter controls how many documents are sampled (use DefaultDriftSampleSize if unsure).

func (*DriftError) Error

func (e *DriftError) Error() string

type DriftPolicy

type DriftPolicy int

DriftPolicy controls how schema drift is handled during enforcement.

const (
	DriftIgnore DriftPolicy = iota // skip drift detection entirely
	DriftWarn                      // detect drift, call OnDriftWarning, continue
	DriftFatal                     // detect drift, return error if any found
)

type EnforceOptions

type EnforceOptions struct {
	DriftPolicy     DriftPolicy
	DriftSampleSize int                // documents to sample for drift detection (default 100)
	OnDriftWarning  func(d DriftError) // called for each drift when policy is DriftWarn
}

EnforceOptions configures the behavior of Enforce.

type EnforcementError

type EnforcementError struct {
	Collection string
	Message    string
}

EnforcementError indicates a schema enforcement failure (e.g., missing index).

func (*EnforcementError) Error

func (e *EnforcementError) Error() string

type FieldSchema

type FieldSchema struct {
	Name      string        // Go field name
	BSONName  string        // bson tag name
	Type      string        // Go type as string
	Required  bool          // field must be non-zero
	Unique    bool          // unique index on this field
	Index     bool          // single-field index
	Default   string        // raw default value
	Enum      []string      // allowed values
	Min       *int          // minimum value/length
	Max       *int          // maximum value/length
	Ref       string        // referenced collection
	Immutable bool          // cannot be changed after creation
	SubFields []FieldSchema // inner fields for struct/[]struct subdocuments
	IsSlice   bool          // true if field is []struct or []*struct
}

FieldSchema describes a single field parsed from struct tags.

func ParseGoodmTag

func ParseGoodmTag(tag string) FieldSchema

ParseGoodmTag parses a `goodm:"..."` struct tag value into FieldSchema attributes. Supported tags: unique, index, required, immutable, default=val, enum=a|b|c, min=N, max=N, ref=collection

type FindOptions

type FindOptions struct {
	DB    *mongo.Database
	Limit int64
	Skip  int64
	Sort  bson.D
}

FindOptions configures Find, FindOne, and FindCursor operations.

type GenerateOptions

type GenerateOptions struct {
	PackageName string // Go package name (default "models")
	OutputDir   string // where to write files
	EmbedModel  bool   // embed goodm.Model (default true)
}

GenerateOptions controls code generation output.

type Indexable

type Indexable interface {
	Indexes() []CompoundIndex
}

Indexable is implemented by models that define compound indexes.

type MergeConflictError added in v0.5.0

type MergeConflictError struct {
	Fields []string
}

MergeConflictError is returned when a retry-with-merge detects that both the caller and another writer modified the same fields. The conflicting field names (bson names) are listed so the caller can decide how to resolve.

func (*MergeConflictError) Error added in v0.5.0

func (e *MergeConflictError) Error() string

type MiddlewareFunc

type MiddlewareFunc func(ctx context.Context, op *OpInfo, next func(context.Context) error) error

MiddlewareFunc is a function that wraps a CRUD operation. Call next(ctx) to continue the middleware chain, or return an error to abort. The context can be modified before passing to next (e.g. for tracing).

type MigrateOptions

type MigrateOptions struct {
	DryRun     bool
	DropExtras bool // drop indexes not in schema
}

MigrateOptions controls migration behavior.

type MigrationAction

type MigrationAction struct {
	Type        ActionType
	Collection  string
	Description string
	IndexName   string
}

MigrationAction describes a single change to apply.

type MigrationPlan

type MigrationPlan struct {
	Actions []MigrationAction
}

MigrationPlan holds all planned actions.

func PlanMigration

func PlanMigration(ctx context.Context, db *mongo.Database, schemas map[string]*Schema) (MigrationPlan, error)

PlanMigration compares registered schemas against the live database and builds a migration plan.

type MigrationResult

type MigrationResult struct {
	Executed int
	Skipped  int
	Warnings []string
	Errors   []error
}

MigrationResult reports what happened during execution.

func ExecuteMigration

func ExecuteMigration(ctx context.Context, db *mongo.Database, plan MigrationPlan, opts MigrateOptions) (MigrationResult, error)

ExecuteMigration applies the planned actions to the database.

func Migrate

Migrate is a convenience function that plans and executes a migration.

type Model

type Model struct {
	ID        bson.ObjectID `bson:"_id,omitempty"`
	CreatedAt time.Time     `bson:"created_at"`
	UpdatedAt time.Time     `bson:"updated_at"`
	Version   int           `bson:"__v"`
}

Model is the base struct that all goodm models should embed. It provides automatic ID generation, timestamp management, and optimistic concurrency control.

type OpInfo

type OpInfo struct {
	Operation  OpType
	Collection string
	ModelName  string
	Model      interface{} // the model being operated on, or nil
	Filter     interface{} // the query filter, if applicable
}

OpInfo provides context about the current operation to middleware.

type OpType

type OpType string

OpType identifies the kind of CRUD operation being performed.

const (
	OpCreate     OpType = "create"
	OpFind       OpType = "find"
	OpUpdate     OpType = "update"
	OpDelete     OpType = "delete"
	OpCreateMany OpType = "create_many"
	OpUpdateMany OpType = "update_many"
	OpDeleteMany OpType = "delete_many"
)

type Pipeline

type Pipeline struct {
	// contains filtered or unexported fields
}

Pipeline is a fluent builder for MongoDB aggregation pipelines. It is bound to a model for collection lookup and supports chaining stages.

Example:

var results []bson.M
err := goodm.NewPipeline(&User{}).
    Match(bson.D{{Key: "age", Value: bson.D{{Key: "$gte", Value: 21}}}}).
    Group(bson.D{{Key: "_id", Value: "$role"}, {Key: "count", Value: bson.D{{Key: "$sum", Value: 1}}}}).
    Sort(bson.D{{Key: "count", Value: -1}}).
    Limit(10).
    Execute(ctx, &results)

func NewPipeline

func NewPipeline(model interface{}, opts ...PipelineOptions) *Pipeline

NewPipeline creates a new aggregation pipeline builder bound to the given model. The model is used for schema/collection lookup (e.g. &User{}).

func (*Pipeline) AddFields

func (p *Pipeline) AddFields(fields interface{}) *Pipeline

AddFields adds a $addFields stage to add computed fields.

func (*Pipeline) Count

func (p *Pipeline) Count(field string) *Pipeline

Count adds a $count stage that outputs a document with the given field containing the count of documents at this stage.

func (*Pipeline) Cursor

func (p *Pipeline) Cursor(ctx context.Context) (*mongo.Cursor, error)

Cursor runs the aggregation pipeline and returns a raw *mongo.Cursor for streaming large result sets. The caller is responsible for closing the cursor.

func (*Pipeline) Execute

func (p *Pipeline) Execute(ctx context.Context, results interface{}) error

Execute runs the aggregation pipeline and decodes all results into the provided slice pointer.

func (*Pipeline) Group

func (p *Pipeline) Group(group interface{}) *Pipeline

Group adds a $group stage for aggregation.

func (*Pipeline) Limit

func (p *Pipeline) Limit(n int64) *Pipeline

Limit adds a $limit stage.

func (*Pipeline) Lookup

func (p *Pipeline) Lookup(from, localField, foreignField, as string) *Pipeline

Lookup adds a $lookup stage for a left outer join.

func (*Pipeline) Match

func (p *Pipeline) Match(filter interface{}) *Pipeline

Match adds a $match stage to filter documents.

func (*Pipeline) Project

func (p *Pipeline) Project(projection interface{}) *Pipeline

Project adds a $project stage to reshape documents.

func (*Pipeline) Skip

func (p *Pipeline) Skip(n int64) *Pipeline

Skip adds a $skip stage.

func (*Pipeline) Sort

func (p *Pipeline) Sort(sort interface{}) *Pipeline

Sort adds a $sort stage.

func (*Pipeline) Stage

func (p *Pipeline) Stage(stage bson.D) *Pipeline

Stage appends a raw aggregation stage for operations not covered by the builder methods.

func (*Pipeline) Stages

func (p *Pipeline) Stages() []bson.D

Stages returns the accumulated pipeline stages. Useful for inspection or testing.

func (*Pipeline) Unwind

func (p *Pipeline) Unwind(field string) *Pipeline

Unwind adds a $unwind stage to deconstruct an array field. The field name is automatically prefixed with "$".

type PipelineOptions

type PipelineOptions struct {
	DB *mongo.Database
}

PipelineOptions configures a Pipeline.

type PopulateOptions

type PopulateOptions struct {
	DB *mongo.Database
}

PopulateOptions configures the Populate operation.

type Refs

type Refs map[string]interface{}

Refs maps bson field names to destination pointers for population. Keys must correspond to fields tagged with goodm:"ref=collection".

type Schema

type Schema struct {
	ModelName       string            // Go struct name
	Collection      string            // MongoDB collection name
	Fields          []FieldSchema     // parsed fields
	CompoundIndexes []CompoundIndex   // compound indexes from Indexes() method
	Hooks           []string          // hook interface names the model implements
	CollOptions     CollectionOptions // per-schema read/write concern and read preference
}

Schema is the parsed representation of a model struct.

func Get

func Get(name string) (*Schema, bool)

Get returns the schema for a given model name, or false if not found.

func (*Schema) GetField

func (s *Schema) GetField(bsonName string) *FieldSchema

GetField returns the FieldSchema for a given BSON name, or nil if not found.

func (*Schema) HasField

func (s *Schema) HasField(bsonName string) bool

HasField returns true if the schema contains a field with the given BSON name.

type TransactionOptions

type TransactionOptions struct {
	DB *mongo.Database
}

TransactionOptions configures the WithTransaction operation.

type UpdateOptions

type UpdateOptions struct {
	DB         *mongo.Database
	Unset      []string // bson field names to remove from the document
	MaxRetries int      // retry with 3-way merge on version conflict (0 = no retry)
}

UpdateOptions configures the Update operation.

func UnsetFields added in v0.4.0

func UnsetFields(fields ...string) UpdateOptions

UnsetFields returns UpdateOptions that will remove the specified fields from the MongoDB document. Field names should be bson names (e.g. "agent_id").

Example:

goodm.Update(ctx, &server, goodm.UnsetFields("agent_id"))

func WithRetry added in v0.5.0

func WithRetry(maxRetries int) UpdateOptions

WithRetry returns UpdateOptions that will automatically retry on version conflict using a 3-way field-level merge. On conflict, the document is re-read from the database, and the caller's changed fields are merged onto the fresh state — but only if no other writer modified the same fields. If both sides changed the same field, a *MergeConflictError is returned.

Example:

goodm.Update(ctx, &task, goodm.WithRetry(3))

type ValidationError

type ValidationError struct {
	Field   string
	Message string
}

ValidationError indicates a field failed validation.

func Validate

func Validate(model interface{}, schema *Schema) []ValidationError

Validate checks a model instance against its schema. Returns a slice of ValidationError for any fields that fail validation.

func (ValidationError) Error

func (e ValidationError) Error() string

type ValidationErrors

type ValidationErrors []ValidationError

ValidationErrors is a slice of ValidationError that implements error.

func (ValidationErrors) Error

func (ve ValidationErrors) Error() string

Directories

Path Synopsis
cmd
goodm command
example
crud command

Jump to

Keyboard shortcuts

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