resourcegen

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Sep 26, 2026 License: MIT Imports: 32 Imported by: 0

Documentation

Overview

Package resourcegen implements `gombit make resource`.

It writes a feature-package (model, thin Huma handler, routes; optional service.go / repo.go), React list/form pages that import generated OpenAPI client types, and registers the package from cmd/server/main.go via go/ast. Generators are idempotent and additive; Go source is never patched with regular expressions.

Index

Constants

View Source
const (
	// GeneratedBanner marks files written by this generator. User-owned files
	// without the banner are refused unless --force is set. Identical re-runs
	// rewrite the same bytes (idempotent).
	GeneratedBanner = "Code generated by gombit make resource. DO NOT EDIT."
)
View Source
const ResourceMarkerFile = ".gombit-resource"

ResourceMarkerFile marks a feature package as a model-first CRUD resource. make resource writes it at bootstrap (slice 5b); gombit generate discovers resources by its presence, never by treating every persisted AutoMigrate model as a resource — "AutoMigrate" means "this model is persisted", not "expose a generated API for it" (a join table, an audit-log or token model is persisted but not a resource). It is a durable sentinel, deliberately NOT a generated *.gen.go: a *.gen.go going missing must read as drift, not make the resource itself vanish from discovery.

Variables

This section is empty.

Functions

func AddAutoMigrateModel

func AddAutoMigrateModel(src []byte, importPath, pkgName, typeName string) ([]byte, error)

AddAutoMigrateModel adds `&<pkg>.<type>{}` to the AutoMigrate call in internal/platform and ensures importPath is imported. Idempotent.

func AddImportAndRegister

func AddImportAndRegister(src []byte, importPath, pkgName string) ([]byte, error)

AddImportAndRegister adds `importPath` and `<pkg>.Register(app)` next to an existing `*.Register(app)` call (the scaffold's product.Register) or, if none exists, immediately before `framework.Run(app)` — including the `if err := framework.Run(app); err != nil` form used by the app template. The edit is idempotent: a second call does not duplicate the import or the Register invocation.

func AutoMigrateModels added in v0.2.1

func AutoMigrateModels(workDir string) (models []migrations.Model, ok bool, err error)

AutoMigrateModels returns the models registered in the app's AutoMigrate call (internal/platform/database.go under workDir). ok is false when the app has no such file — a non-scaffolded layout — so callers can skip AutoMigrate-based checks instead of failing. It never mutates the tree.

func CollectAutoMigrateModels

func CollectAutoMigrateModels(src []byte) ([]migrations.Model, error)

CollectAutoMigrateModels returns every `&pkg.Type{}` argument of AutoMigrate calls in src, resolved to import path + type name via the file's imports. Atlas migrate diff treats that slice as the entire desired schema, so callers must pass every registered model — not only the resource being generated.

func CountRegisterCalls

func CountRegisterCalls(src []byte, pkgName string) (int, error)

CountRegisterCalls reports how many `<pkg>.Register(...)` calls appear in src.

func Generate

func Generate(ctx context.Context, opts Options) error

Generate scaffolds a feature-package resource (phase 1 only): the human-owned model, the .gombit-resource marker, the AST-wired registration/AutoMigrate, and the frontend pages. It does NOT produce the generator-owned *.gen.go — gombit generate does, from the committed model. It is the primitive the golden tests and app fixtures use; the user-facing `gombit make resource` (cli/make.go) composes this phase with the Program-Mode phase 2 atomically, via Plan.

func PlatformDBRel added in v0.2.1

func PlatformDBRel() string

PlatformDBRel is the app-relative path of the generated file whose AutoMigrate call is the runtime desired-state source of truth (the models the app persists and migrates on start). Callers surface it in messages about that file.

func ReadModulePath added in v0.2.0

func ReadModulePath(workDir string) (string, error)

ReadModulePath returns the module path from the go.mod in workDir. It is the exported form of the reader make resource already uses, so gombit generate resolves the app's module the same way.

func ValidateAppLayout added in v0.2.0

func ValidateAppLayout(workDir string) error

ValidateAppLayout reports whether workDir looks like a gombit application (go.mod + cmd/server/main.go + internal/platform/database.go), the same check make resource applies before touching an app.

Types

type Field

type Field struct {
	Name     string
	JSONName string
	GoName   string
	Type     FieldType
	GoType   string
	Required bool
	Unique   bool
	Index    bool
	Nullable bool

	// Filterable / Sortable / Searchable opt this field into the generated list
	// handler's declared query surface (issue #260): exact-match ?<field>=,
	// ?ordering=<field> (- prefix for DESC), and ?search= respectively — the same
	// spelling as the admin data plane. belongs_to fields are filterable by
	// default (see isFilterable) so the has_many detail-list case —
	// GET /children?<parent>_id=<id> — works without extra declaration.
	Filterable bool
	Sortable   bool
	Searchable bool

	// Aggregatable opts this numeric field into the generated list handler's
	// server-side aggregate surface (issue #272): ?aggregate=sum:<field>,
	// avg:<field>,min:<field>,max:<field>, computed over the same filtered and
	// searched set as the list, before pagination. Only numeric columns (int,
	// int64, uint, decimal) may opt in (see typeAllowsAggregate).
	Aggregatable bool

	// EnumValues holds the allowed stored values for FieldEnum, in declared
	// order. EnumLabels is the display text for each value. An empty label
	// list means the label is the stored value.
	EnumValues []string
	EnumLabels []string
	// Precision/Scale set the decimal(p,s) column for FieldDecimal.
	Precision int
	Scale     int

	// Declarative constraints (MODEL-3). Empty strings and a zero MaxLength
	// mean unset. Default is the raw token, without SQL quotes.
	Min       string
	Max       string
	MaxLength int
	Pattern   string
	Default   string

	// Target is the related model type (PascalCase) for a relation field, e.g.
	// "Engine". TargetPkg is its feature-package name (snake), e.g. "engine".
	// OnDelete is the GORM OnDelete clause (RESTRICT, CASCADE, SET NULL).
	// Self is a relation whose target is the resource being generated.
	Target    string
	TargetPkg string
	OnDelete  string
	Self      bool
	// FKGoType is the belongs_to / one_to_one foreign-key Go type. Empty
	// means uint. uuid.UUID matches a target whose primary key is a UUID.
	FKGoType string
}

Field is one parsed resource field from the CLI grammar name:type[:required][,unique][,index] (design §27 subset).

type FieldType

type FieldType string

FieldType is the generator's projection of a logical field kind. Scalar values are field.Kind strings. Relation values are field.RelationKind strings. Both sets are defined in package field; these constants exist so call sites can switch on them.

type GeneratedArtifact added in v0.2.0

type GeneratedArtifact struct {
	Path      string    `json:"path"`
	Content   []byte    `json:"content"`
	Ownership Ownership `json:"ownership"`
}

GeneratedArtifact is one file the model-first generator produces for a resource: a path relative to the application root, its rendered bytes, and its ownership class. It is the unit the Program-Mode loader emits (as JSON) and the generate command plans its writes / drift check over. The loader has no write access to the app tree; producing artifacts is all it does.

func GeneratedArtifactPlan added in v0.2.0

func GeneratedArtifactPlan(pkg string) []GeneratedArtifact

GeneratedArtifactPlan is the single source of truth for the files gombit generate produces for a resource package — their paths (internal/<pkg>/…) and ownership, with no content. RenderResource fills these in; make resource's --dry-run lists them, so a preview can never advertise a file the generator does not actually emit.

func RenderResource added in v0.2.0

func RenderResource(model any, pkg string) ([]GeneratedArtifact, error)

RenderResource renders every model-first file for one resource from its parsed model and declared policy: the DTOs + mappers and the CRUD handler (both generator-owned .gen.go), and the default hooks file (seed-once, human-owned). pkg is the feature package the files live in — the generated file paths are internal/<pkg>/…, the gombit feature-package layout. The output is deterministic (slices 3/4), so a regenerate-and-compare drift check is sound.

type Options

type Options struct {
	WorkDir string
	Name    string
	Fields  []string
	// ID is the primary key strategy: uint (default, gorm.Model) or uuid.
	ID       string
	Service  bool
	Repo     bool
	DryRun   bool
	Force    bool
	Stdout   io.Writer
	Stderr   io.Writer
	AtlasBin string
	// SkipMigrations is the explicit opt-in to scaffolding the resource and
	// persisting the loader/registry state without generating the Atlas SQL diff.
	// It makes the committed output independent of whether Atlas is installed, at
	// the cost of a registry-ahead-of-SQL state the developer knowingly accepts
	// (#300).
	SkipMigrations bool
	// contains filtered or unexported fields
}

Options configures resource generation inside an existing application.

type Ownership added in v0.2.0

type Ownership int

Ownership classifies who owns a generated file, which decides how gombit generate treats it.

const (
	// GeneratorOwned files are the derived *.gen.go (DTOs, mappers, handler): the
	// generator rewrites them every run and gombit generate --check compares them.
	// They carry the DO-NOT-EDIT banner.
	GeneratorOwned Ownership = iota
	// SeedOnce files are human-owned (the hooks file): the generator writes one
	// only when it is absent, and never overwrites or drift-checks it afterward —
	// it is the customization surface, so editing it must be safe.
	SeedOnce
)

func (Ownership) String added in v0.2.0

func (o Ownership) String() string

String names the ownership class for diagnostics.

type Pending added in v0.2.0

type Pending struct {
	ImportPath string // module/internal/<pkg>
	TypeName   string // Book
	Pkg        string // book
	// Overlay maps real absolute paths to the source Program Mode compiles instead
	// of what is on disk, so the phase-2 preflight sees the resource exactly as this
	// run will commit it while the real tree stays untouched (nil when the committed
	// package already matches disk). It stages the model when make resource will
	// (re)write it, and — for a --force re-scaffold that changes the model — stubs
	// the package's other .go files so the changed model compiles without the stale
	// generated files this run is about to replace.
	Overlay map[string][]byte
}

Pending describes the resource a make resource run is about to scaffold, so the Program-Mode phase-2 preflight can compile the model through a Go build overlay without the model — or its .gombit-resource marker — existing on disk yet.

type ResourceName

type ResourceName struct {
	Input       string
	TypeName    string
	Package     string
	FileBase    string
	PluralSnake string
	HTTPPath    string
	Tag         string
	Kebab       string
}

ResourceName is the derived identifiers for one generated feature-package.

type ResourcePlan added in v0.2.0

type ResourcePlan struct {
	Pending Pending
	Models  []migrations.Model
	// contains filtered or unexported fields
}

ResourcePlan is a fully computed, not-yet-applied resource scaffold: the phase-1 files, the Pending model the phase-2 overlay preflight compiles, and the AutoMigrate model set the post-commit migration needs. Planning writes nothing; a caller applies the plan (optionally merged with the phase-2 artifacts) as one transaction only after the whole operation has validated.

func Plan added in v0.2.0

func Plan(ctx context.Context, opts Options) (*ResourcePlan, error)

Plan computes a resource scaffold without writing anything. It runs every static check make resource can before touching the tree — app layout, name, HTTP path collision, and the legacy-layout guard — so a caller can preflight the whole operation (including the Program-Mode phase 2) and apply the result atomically.

func (*ResourcePlan) Apply added in v0.2.0

func (p *ResourcePlan) Apply(artifacts []GeneratedArtifact) error

Apply writes the plan (phase-1 files plus the phase-2 artifacts) to the tree as a single transaction: on any filesystem write error it rolls back every file it already wrote, so a failed apply leaves the tree as it found it. It prints each action as it goes.

func (*ResourcePlan) FinalOverlay added in v0.2.0

func (p *ResourcePlan) FinalOverlay(artifacts []GeneratedArtifact) (map[string][]byte, error)

FinalOverlay returns a Go build overlay describing the resource package exactly as this run will commit it: every .go file in internal/<pkg> the plan actually writes (the model when re-scaffolded, the freshly rendered *.gen.go, a seeded hooks.go), staged at its real path. Files the plan keeps (a human-owned hooks.go) and any hand-written files not in the plan are intentionally absent, so a compile over this overlay reads them from disk — the true committed package. generate.ValidateResource compiles that package as the phase-2 preflight's second step, catching preserved customization that no longer matches the regenerated model.

func (*ResourcePlan) Migrate added in v0.2.0

func (p *ResourcePlan) Migrate(ctx context.Context) error

Migrate generates the Atlas migration for the scaffolded model. It is a post-commit step, deliberately not part of the atomic apply: a migration is a separate artifact, and the make resource transaction is about the source tree.

func (*ResourcePlan) ModelSpec added in v0.2.0

func (p *ResourcePlan) ModelSpec() string

ModelSpec is the Atlas loader spec (import path + type) for the scaffolded model.

func (*ResourcePlan) PrintPlan added in v0.2.0

func (p *ResourcePlan) PrintPlan(artifacts []GeneratedArtifact) error

PrintPlan writes the human-readable plan to stdout, changing nothing. With artifacts nil (the phase-1-only Generate path) it appends the files gombit generate would produce, from the generator's own path contract; with the real phase-2 artifacts (the composed make resource path) it prints the exact merged plan the command would apply.

Jump to

Keyboard shortcuts

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