Help us shape the future of Go! Take the 2026 Go Developer Survey

scaffold

package
v0.6.7 Latest Latest
Warning

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

Go to latest
Published: Oct 3, 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"
	// PartLastTenant is the index behind "sign in and land back where you were".
	// Its own part because it is its own migration, and it arrives with auth the
	// way [PartVerificationDelivery] does: a set is applied whole, so having
	// sessions is having this. Which is the point — a project with sessions and
	// without it has a sign-in that works and a query that sorts the whole token
	// table to answer where somebody was.
	PartLastTenant    = "last_tenant"
	PartFiles         = "files"
	PartNotifications = "notifications"
	PartPresence      = "presence"
	PartIdempotency   = "idempotency"
	PartThrottle      = "throttle"
	// PartElectricRole is the Postgres role the sync service connects as. One of
	// the two parts that create no table — the other is [PartLastTenant], which
	// creates an index — and the only one that creates no schema object at all:
	// what it creates is a role and its grants. It arrives only for a project
	// that streams — see [Wanted.Electric].
	PartElectricRole = "electric_role"
)

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 DefaultPublication = "electric_publication_default"

DefaultPublication is the publication rig writes streaming tables into.

The sync service's own name, and that is the whole point rather than a collision: **the sync service reads only its own publication.** A publication under a name of the project's own is never consulted, so a table published there and nowhere else streams nothing for a database role that owns no tables — which is every deployment worth granting least privilege.

So the migration creates this one, before the service ever connects, and owns it. The service then finds its publication already correct and verifies it instead of building it, which is what ELECTRIC_MANUAL_TABLE_PUBLISHING=true asks for and what removes the table ownership it would otherwise need.

The suffix is the replication stream id, which is "default" unless a deployment sets ELECTRIC_REPLICATION_STREAM_ID. A deployment that sets one has to rename this in the migration to match, and there is nothing rig can read to know.

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.

The highest is read with migcheck.Version rather than by rig's own naming rule, so a file rig would not have written still counts. 25_by_hand.sql is version 25 to goose whatever rig thinks of the name, and numbering the next migration 1 because the rule did not recognize it would produce the one thing this function exists to avoid.

func OptionalParts added in v0.3.0

func OptionalParts() []string

OptionalParts are the parts Foundation writes only when asked for by name.

Everything else in the foundation creates tables, and a table a project never reads costs it nothing — which is why `--skip` is the only gate the rest need: the default is "write it", and skipping is the unusual answer.

This one creates a Postgres **role**. A role is cluster-scoped: it outlives the database, it may already exist because something else on the same instance made it, and it cannot be dropped while the sync service holds a slot open through it. So it is the first part where scaffolded-and-unused is not free, and the first where the default has to be the other way round.

Wanted is the same decision read off a project's configuration. This is the list Foundation checks, because Foundation is handed FoundationOptions rather than a Wanted — it writes what a project *may* have, and the parts a project asks for are FoundationOptions.Want.

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 Publish added in v0.3.0

func Publish(opt PublishOptions) string

Publish renders the migration that publishes a project's streaming tables and gives them a replica identity live sync can use.

Both halves in one file because Postgres gates both on owning the table, so they are one job: a migration that publishes and stops has moved half of what a least-privilege deployment cannot do for itself.

It is a migration rather than something rig applies because the answer has to be the same in every environment. The sync service will publish a table itself on the first subscription — which is why this is easy never to think about on a laptop, where its role owns everything — and cannot where it does not own the table, which is every deployment that grants least privilege.

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.

electric is last, and here that is more than convention. It creates no table — it grants the sync service's role SELECT on the schema, with ALTER DEFAULT PRIVILEGES for whatever is created next — so where it sits decides what those two statements cover. Last among rig's sets and before the project's own is exactly the seam: ON ALL TABLES takes every table rig has already created, and the default privileges take every one the project adds after.

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
	// Want names the parts in [OptionalParts] this project asks for.
	//
	// Only those. Every other part is written unless [FoundationOptions.Skip]
	// names it, and listing one here is neither required nor an error — the two
	// fields answer different questions, and conflating them would make `--skip`
	// mean "skip, except the ones that are off by default anyway".
	Want []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 PublishOptions added in v0.3.0

type PublishOptions struct {
	// Publication is the publication to write into. Empty means
	// [DefaultPublication].
	Publication string
	// Create says the publication does not exist yet, so this migration is the
	// one that makes it — which is also what decides whether Down may drop it.
	Create bool
	// Add are the tables to add to the publication, in the order they should be
	// named.
	Add []string
	// Identity are the tables whose replica identity has to become FULL.
	//
	// Usually the same list as [PublishOptions.Add] and not always: a table
	// published by an earlier migration that never had its identity set is in
	// this one and not that one, which is exactly the state RIG5093 reports and
	// RIG5090 does not.
	Identity []string
	// Already are the tables the publication carries already, named in a comment
	// so the file says what it left alone.
	Already []string
}

PublishOptions describe the migration that makes a table streamable.

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
	// Electric is a project that streams: a table with live sync, or a sync
	// service configured for one.
	//
	// The only feature here whose part creates no table. What it brings is the
	// Postgres role the sync service connects as, and it is conditional for the
	// reason the others are not: a role holding SELECT on every table is not
	// something to create in the database of a project that will never sync from
	// it. See [github.com/simonjanss/rig/runtime/electric/foundation].
	Electric 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