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
- func AddAutoMigrateModel(src []byte, importPath, pkgName, typeName string) ([]byte, error)
- func AddImportAndRegister(src []byte, importPath, pkgName string) ([]byte, error)
- func AutoMigrateModels(workDir string) (models []migrations.Model, ok bool, err error)
- func CollectAutoMigrateModels(src []byte) ([]migrations.Model, error)
- func CountRegisterCalls(src []byte, pkgName string) (int, error)
- func Generate(ctx context.Context, opts Options) error
- func PlatformDBRel() string
- func ReadModulePath(workDir string) (string, error)
- func ValidateAppLayout(workDir string) error
- type Field
- type FieldType
- type GeneratedArtifact
- type Options
- type Ownership
- type Pending
- type ResourceName
- type ResourcePlan
- func (p *ResourcePlan) Apply(artifacts []GeneratedArtifact) error
- func (p *ResourcePlan) FinalOverlay(artifacts []GeneratedArtifact) (map[string][]byte, error)
- func (p *ResourcePlan) Migrate(ctx context.Context) error
- func (p *ResourcePlan) ModelSpec() string
- func (p *ResourcePlan) PrintPlan(artifacts []GeneratedArtifact) error
Constants ¶
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." )
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 ¶
AddAutoMigrateModel adds `&<pkg>.<type>{}` to the AutoMigrate call in internal/platform and ensures importPath is imported. Idempotent.
func AddImportAndRegister ¶
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 ¶
CountRegisterCalls reports how many `<pkg>.Register(...)` calls appear in src.
func Generate ¶
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
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
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.
const ( FieldString FieldType = FieldType(logical.String) FieldText FieldType = FieldType(logical.Text) FieldInt FieldType = FieldType(logical.Integer) FieldInt64 FieldType = FieldType(logical.Integer64) FieldBool FieldType = FieldType(logical.Boolean) FieldUint FieldType = FieldType(logical.Unsigned) FieldDecimal FieldType = FieldType(logical.Decimal) FieldTime FieldType = FieldType(logical.DateTime) FieldDate FieldType = FieldType(logical.Date) FieldTimeOfDay FieldType = FieldType(logical.TimeOfDay) FieldDuration FieldType = FieldType(logical.Duration) FieldFloat FieldType = FieldType(logical.Float) FieldUUID FieldType = FieldType(logical.UUID) FieldJSON FieldType = FieldType(logical.JSON) FieldEmail FieldType = FieldType(logical.Email) FieldURL FieldType = FieldType(logical.URL) FieldSlug FieldType = FieldType(logical.Slug) FieldIP FieldType = FieldType(logical.IP) FieldEnum FieldType = FieldType(logical.Enum) FieldBelongsTo FieldType = FieldType(logical.RelBelongsTo) FieldHasMany FieldType = FieldType(logical.RelHasMany) FieldManyToMany FieldType = FieldType(logical.RelManyToMany) FieldOneToOne FieldType = FieldType(logical.RelOneToOne) )
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 )
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.