scaffold

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 30, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package scaffold writes the files a new rig project starts from.

Everything here is generated once and then owned by the developer. Nothing in this package overwrites a file that already exists: a scaffold that clobbers your work on the second run is worse than no scaffold at all.

Index

Constants

View Source
const (
	PartTenancy  = "tenancy"
	PartSessions = "sessions"
	PartAPIKeys  = "apikeys"
	PartOAuth    = "oauth"
	// PartVerificationDelivery is the mail queue for the links tenancy mints. Its
	// own part because it is its own migration, and it arrives with auth for the
	// reason the table it queues for does: a project that mints links can always
	// choose to queue them, and the choice is a Go one rather than a rig.yaml one.
	PartVerificationDelivery = "verification_delivery"
	PartFiles                = "files"
	PartNotifications        = "notifications"
	PartPresence             = "presence"
	PartIdempotency          = "idempotency"
	PartThrottle             = "throttle"
)

Foundation parts. Each is one migration and its table configuration, so skipping one leaves a project that still applies and still validates.

A part's name is the name of the migration that creates it, inside the module that owns it — see Sets. That is what makes a vendored `00009_rig_tenancy.sql` recognisable as auth's `tenancy` however it was numbered on the way in, and it is why Managed can read a project's migrations directory and say which parts arrived with the foundation.

View Source
const TablePrefix = "rig_"

TablePrefix is on every table rig creates, and rig keeps it.

A project's own `rig_…` table would be indistinguishable in psql from one `setup-project` wrote, which is the whole reason the prefix exists — and the next part rig adds could create a table by that name outright, turning somebody's schema into a migration that will not apply. Reserving the prefix costs a project one naming choice and buys rig the freedom to grow the foundation without breaking anybody.

Variables

This section is empty.

Functions

func AppliedParts

func AppliedParts(existing []string) []string

AppliedParts are the parts a project's own migration files show it has, in apply order.

The evidence is the filenames: a part rig wrote is named for itself, so a project that vendored the foundation says which parts it has in its own migrations directory. That matters twice over. It decides whether rig generates a model and a repository for a table, and it is what tells a `rig_account` rig created from one somebody wrote by hand — which is refused rather than generated for, now that TablePrefix is reserved.

A name alone cannot answer either question, which is why this reads the directory. Wanted.Parts is the other reading, for a project whose modules carry their own schema and which therefore has no files here to read.

func BookkeepingTables

func BookkeepingTables() []string

BookkeepingTables are the tables the foundation's sets record themselves in.

They carry the `rig_` prefix like everything else rig creates, and introspection returns them like any other table — but they arrive through a different door: goose writes them, no migration creates them. So two rules have to know about them. The reserved prefix would otherwise refuse them as tables under rig's prefix that no migration created, and the ignore list would otherwise let rig project a CRUD resource over its own migration history.

The project's own bookkeeping table is not here. It is named in rig.yaml and github.com/simonjanss/rig/internal/compile reads it from there.

func ImportPath

func ImportPath(s dbschema.Set) string

ImportPath is where a set's Go package lives, for generated code that has to name it in an import.

Derived from the module rather than listed, because the layout is a convention this repository keeps: `rig/auth`'s set is `rig/auth/foundation`. Deriving it means a fourth module carrying a set needs nothing here.

func Managed

func Managed(existing []string) []string

Managed are the foundation tables a project has actually applied, read from its own migration files. It is AppliedParts in terms of tables.

`auth.own` is the exception and it turns the whole thing off: a project that has taken the schema over owns those tables and their names.

func Migration

func Migration(opt MigrationOptions) string

Migration renders a migration file.

func MigrationFilename

func MigrationFilename(dir string, number int, name string) string

MigrationFilename builds the path for a new migration.

func NextMigrationNumber

func NextMigrationNumber(dir string) (int, error)

NextMigrationNumber reads the directory and returns the number after the highest one present.

Numbering by sequence rather than by timestamp keeps the directory readable and the order obvious. It does mean two people adding a migration on the same day will collide — which is the point: a merge conflict on the filename is a far better outcome than two migrations silently interleaving.

func PartOf

func PartOf(table string) string

PartOf names the part that creates a table, or "" for a table the foundation does not create.

The inverse of PartTables, and it exists for a diagnostic. A project whose rig_account arrived through a migration Managed cannot recognise has to be told which name that migration needs, and the part is that name: `Managed` matches on the `_rig_<part>.sql` suffix and nothing else.

func PartTables

func PartTables(part string) []string

PartTables are the tables a part creates.

Every name carries the `rig_` prefix the migrations create them under, so a project can tell at a glance which tables arrived with the foundation. Both halves of that name are rig's and rig refuses a project either: the prefix itself (TablePrefix) and the API name the prefix strips to (ReservedResources), so no project has an `account` or a `tenant` of its own and exposing this one later is never a rename.

The names come from the owning module's manifest, where they are written out rather than parsed from the SQL — so that adding a table to the foundation is a decision somebody makes there as well. A heuristic would quietly start or stop treating one as the module's, and which side of that line a table falls on decides whether rig generates code for it.

func Parts

func Parts() []string

Parts in the order they must be applied.

Derived from Sets rather than listed, so a migration added to one of those modules is a part here without anybody having to say so twice. The consts above name the parts that exist today; this is the list, and it is the modules that decide what is on it.

func Requires

func Requires(part string) []string

Requires reports the parts a part depends on, so a skip list that would leave a dangling reference can be refused rather than discovered by psql.

func ReservedResources

func ReservedResources() map[string]string

ReservedResources maps every API name rig's own tables project to, to the table that takes it: Account to rig_account, File to rig_file, and so on.

They are reserved whether or not a project exposes rig's tables. The name a project cannot have is the price of `auth.expose` staying one line in rig.yaml: without the reservation, a project that called its own table `account` would find out on the day it exposed rig_account, and the fix then is a migration plus a rename in every client that reads the resource.

Derived from the table configurations `setup-project` writes rather than listed, so a part added to the foundation reserves its names by existing. The planned entries are the one exception, and they say so.

func SetOf

func SetOf(part string) (dbschema.Set, bool)

SetOf names the set that carries a part, and whether any does.

Callers that have to apply a part, or write down which module a project depends on for it, ask here rather than matching on the part name — the mapping is the sets' to state and this is the one place that reads it.

func Sets

func Sets() []dbschema.Set

Sets are the foundation's migration sets, in the order they apply.

One per module that writes SQL against the schema, and each records how far it has been applied in a table of its own. The private table is what lets those modules be tagged separately: a shared one would mean a shared numbering sequence, and two modules adding a migration in the same release would collide on a version number.

The order is the dependency order, and each set says why in its own package documentation. auth first because everything references its tenancy migration; files next because rig_file references nothing at all, deliberately, so that uploads work in a project with no authentication; notify after that because an inbox line names an account, which auth creates.

runtime and presence are last, and the rule is the same one: **a set is appended, never inserted.** Neither had to be here rather than earlier — rig_idempotency references nothing, and rig_presence references only what auth already created — so both went at the end because that is where a new set belongs. A project that vendored the foundation before one existed then finds it as a migration to add rather than as a renumbering of the ones it has already applied.

func SetsFor

func SetsFor(parts []string) []dbschema.Set

SetsFor are the sets a list of parts needs, in apply order.

A set appears once however many of its parts are named, because a migration set is applied whole: goose reads the directory, so asking for `sessions` and not `oauth` is not something a set can express. That is a real limit of leaving the schema to the modules, and the honest place to state it — a project that wants a subset of auth's tables vendors the foundation and skips the parts it does not want.

func TableConfig

func TableConfig(table string) (yaml []byte, ok bool)

TableConfig is rig's own configuration for one of its own tables, as YAML.

It is the same text `rig setup-project --expose` writes to disk, returned as bytes so the compiler can read it without a file. That is the whole point: how rig's tables project — the API name the `rig_` prefix strips to, which operations exist, which columns are read-only, what each enum value means — is rig's answer and not a thing every project should have to write out. A project that wants to differ runs `--expose` and edits the file it gets, and a file on disk wins over this outright.

ok is false for a table rig does not own, and for one it owns and has nothing to say about.

func Tables

func Tables() []string

Tables are every table the foundation creates.

func TablesFor

func TablesFor(parts []string) []string

TablesFor are the tables a list of parts creates, in apply order.

func Write

func Write(files []File) (written, skipped []string, err error)

Write creates files that do not already exist and reports which ones it made. Existing files are left alone and reported as skipped.

Types

type File

type File struct {
	Path    string
	Content string
}

File is one file to write.

func Foundation

func Foundation(opt FoundationOptions) []File

Foundation returns the files `rig setup-project` writes.

Migrations, and — only for a table named in FoundationOptions.Expose — a table configuration.

The tables are ordinary rig: real Postgres tables following the same column conventions any other table follows, so `rig sync` can read them and every rule applies to them. What is deliberately not ordinary is that rig generates no code for them. The Go types, the stores and the endpoints all live in the rig/auth module, and a second copy projected into the project would be a few thousand lines nothing calls. The migrations stay here because they are the project's own schema history: a library that silently owns a dozen tables is harder to reason about than a dozen tables you can read.

func Project

func Project(opt ProjectOptions) []File

Project returns the files a new project starts with.

type FoundationOptions

type FoundationOptions struct {
	// Skip names parts to leave out.
	Skip []string
	// Expose names foundation tables to write a table configuration for.
	//
	// Empty is the ordinary case and writes none. The foundation's tables belong
	// to the rig/auth module: their Go types, their stores and their endpoints
	// are imported from there, so generating a model and a repository for each
	// of them would be a few thousand lines of code in the project that nothing
	// in the project calls.
	//
	// A table listed here gets its configuration back, and with it everything a
	// configuration asks for. It is for the case where an application genuinely
	// wants CRUD over one — an administration screen listing the people in a
	// tenant, most often.
	Expose []string
	// FirstNumber is the migration number to start from.
	FirstNumber int
	// MigrationsDir and ConfigPath place the files.
	MigrationsDir string
	ConfigPath    func(table string) string
	// Existing is the migration files already present, by base name.
	//
	// A part whose migration is already there is skipped entirely. Without
	// this, a second run would write the same tables again under fresh
	// numbers, and `rig db up` would fail on the first CREATE TABLE — which is
	// a poor way to learn that the command was not idempotent.
	//
	// It is also the upgrade path. The sets are append-only, so a module that has
	// gained a migration since this project was set up has a part nothing here
	// matches, and it is written at the next free number — leaving what is already
	// applied exactly as it was.
	Existing []string
	// ConfigsOnly leaves the migrations out and writes only the table
	// configurations for whatever [FoundationOptions.Expose] names.
	//
	// It is `migrations.foundation: embedded`: the modules carry the schema, so
	// there is no migration for this project to keep. Exposing a table is still a
	// question for the project, because it is about rig.yaml rather than about
	// DDL — a project can leave the tables to the modules and still want a model
	// and a repository for one of them.
	//
	// False is vendored, which is the default, so the zero value writes the
	// migrations as it always has.
	ConfigsOnly bool
}

FoundationOptions describe what to scaffold.

type MigrationOptions

type MigrationOptions struct {
	// Name is the descriptive half of the filename.
	Name string
	// Table, when set, scaffolds a CREATE TABLE with the conventional columns.
	Table string
	// SoftDelete adds the columns that make the table soft-deletable.
	SoftDelete bool
	// Snapshot adds the snapshot triple and its CHECK constraint.
	Snapshot bool
}

MigrationOptions describe a new migration.

type ProjectOptions

type ProjectOptions struct {
	Name   string
	Module string
	Image  string
}

ProjectOptions describe a new project.

type Wanted

type Wanted struct {
	// Auth is an `auth:` block, which needs identities, tokens and keys.
	Auth bool
	// OAuth is a provider configured under `auth.oauth`.
	//
	// It brings no part [Wanted.Auth] does not: oauth is a migration in auth's
	// set, and a set is applied whole. Read rather than assumed all the same,
	// because it is what the configuration says.
	OAuth bool
	// Files is `files.enabled`.
	Files bool
	// Notifications is `notifications.enabled`.
	Notifications bool
	// Presence is `presence.enabled`.
	Presence bool
}

Wanted says which of the foundation's features a project's configuration turns on. It is the input to Wanted.Parts.

func (Wanted) Parts

func (w Wanted) Parts() []string

Parts names the parts these features bring, in apply order, with Requires expanded and every named set taken whole.

This is the other answer to the question Managed answers. A project that vendored the foundation says which parts it has by having their migrations; a project that left them to the modules has only its configuration to say so, and this is that reading. `rig setup-project` decides what to write the same way, which is what keeps the two from disagreeing about a project it set up.

**Brings, not asks for, and the difference is the whole of it.** A set is applied whole — goose reads a directory, so `sessions but not oauth` is not something a set can express — so an `auth:` block with no provider configured still ends up with rig_identity_oauth in the database, and a project with an inbox and no authentication ends up with all of auth's. Answering with the narrower list would have rig report RIG2005 on a table it created itself one command earlier, and then, with the table missing from the ignore list, project a resource over it. So the widening happens here rather than in each caller: there is one answer to "which of rig's tables does this project have", and no way to reach for the wrong one.

Jump to

Keyboard shortcuts

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