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

ir

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: 8 Imported by: 0

Documentation

Overview

Package ir defines the intermediate representation that rig compiles a Postgres schema and its table configuration into, and that every generator reads.

A Document holds two views of the same system:

  • Schema is the physical truth: tables, columns, SQL types, keys, indexes. It is what the database actually contains, normalized and sorted.
  • API is the projected surface: resources, fields, endpoints, objects, enums. It is what clients see.

The two are cross-referenced. Every API Field backed by storage carries a ColumnRef naming the column behind it, so a generator emitting SQL and a generator emitting JSON cannot disagree about which column a field means. The reference is a denormalized copy of facts that also live in Schema; freezing a document asserts the copies match, which makes drift between the two views a compile error rather than a runtime surprise.

Everything here is plain data. The package has no dependencies outside the standard library, imports nothing from the rest of rig, and performs no I/O. A Document is immutable once frozen: generators read it concurrently and must never write to it.

Index

Constants

View Source
const (
	TypeBool      = "Bool"
	TypeBytes     = "Bytes"
	TypeDate      = "Date"
	TypeDecimal   = "Decimal"
	TypeFloat64   = "Float64"
	TypeInt       = "Int"
	TypeInt64     = "Int64"
	TypeJSON      = "JSON"
	TypeString    = "String"
	TypeTime      = "Time"
	TypeTimestamp = "Timestamp"
	TypeUUID      = "UUID"
)

The primitive type names a Field.Type may take. Anything else must resolve to an enum, object, or resource declared in the same API.

View Source
const (
	ModifierNullable = "Nullable"
	ModifierArray    = "Array"
)

Modifiers a field may carry.

View Source
const (
	OpCreate = "Create"
	OpGet    = "Get"
	OpList   = "List"
	OpSearch = "Search"
	OpUpdate = "Update"
	OpDelete = "Delete"
)

Operations a resource may support.

View Source
const (
	OpListDeleted = "ListDeleted"
	OpRestore     = "Restore"
)

Operations a soft-deletable resource gets on top of the CRUD set.

A deletion that stamps the row rather than removing it has somewhere the row went and a way back from it, and neither is expressible as CRUD. Each follows the operation it is a variety of: listing the trash is a listing, and bringing a row back is a write.

View Source
const (
	OpVersions = "Versions"
	OpRevert   = "Revert"
)

Operations a snapshotable resource gets on top of the CRUD set.

They are not listed in a table's `operations:` — that is the CRUD set, and these follow from the schema rather than from a choice. A table that keeps its previous versions can be asked for them, and one whose rows can be updated can be put back to one. Both are dropped along with the operation they depend on: no Get, no history; no Update, nothing to revert with.

View Source
const (
	FieldOpCreate = "Create"
	FieldOpRead   = "Read"
	FieldOpUpdate = "Update"
)

Operations a field may participate in. Read is implied by any of Get, List, or Search on the owning resource.

View Source
const (
	MediaJSON = "application/json"
	// MediaMultipart is what an upload arrives as, and what a create accepts in
	// addition to JSON on a table with a file column.
	MediaMultipart = "multipart/form-data"
	// MediaOctet is the fallback a download announces when the file's own
	// sniffed type is not known at generation time — which it never is.
	MediaOctet = "application/octet-stream"
)

The media types rig's own endpoints speak.

They live here rather than in the compiler because they are written into the document by one package and read back out of it by every generator that renders a request — an OpenAPI document must not claim application/json for an endpoint whose document says otherwise, and a client must not send JSON to one that takes a form. Until files, every endpoint said JSON, so nothing had ever been asked.

View Source
const (
	ProviderGoogle    = "google"
	ProviderMicrosoft = "microsoft"
	ProviderGitHub    = "github"
)

The provider kinds rig knows how to build. Anything else is a provider the application constructs itself and hands over.

View Source
const CurrentVersion = 1

CurrentVersion is the IR format version. Bump it when a change would make an older generator misread a newer document.

View Source
const ElectricStreamSuffix = "/_stream"

ElectricStreamSuffix marks a route as a live-sync stream.

It is the last segment of every shape's route, so it reads as something done to whatever precedes it — subscribe to this — and a shape's route is the read surface it streams plus one marker. That is why the trash and history shapes are composed by inserting before it rather than appending after it.

View Source
const ScopeParam = "scope"

ScopeParam is the query-string key that widens a read past the caller's own rows.

Here rather than in the compiler because both ends need it and neither should have to know the other exists: the compiler reserves the name and projects the parameter, and a generator has to recognise it to emit the check that authorizes it. It matches github.com/simonjanss/rig/runtime/tenancy.ScopeParam, which is what a client reads.

Variables

This section is empty.

Functions

func FormatDuration

func FormatDuration(d time.Duration) string

FormatDuration renders a duration the way Duration.String does.

func IsPrimitive

func IsPrimitive(name string) bool

IsPrimitive reports whether name is one of the built-in field types.

func Marshal

func Marshal(d *Document) ([]byte, error)

Marshal encodes a document canonically: two-space indent, struct fields in declaration order, no HTML escaping. The same document always produces the same bytes, which is what lets the IR be committed and diffed.

func ParseDuration

func ParseDuration(s string) (time.Duration, error)

ParseDuration reads a duration in Go's syntax, extended with d for days.

The extension is the whole reason this is not time.ParseDuration. A refresh token that lasts a month is configured as 30d by anybody writing it down, and 720h is the same number with the arithmetic already done — which is a thing to get wrong rather than a thing to read.

A day is exactly 24 hours here. Nothing in this package is doing calendar arithmetic, so there is no daylight saving to be wrong about.

Types

type API

type API struct {
	Name        string `json:"name"`
	Version     string `json:"version"`
	Description string `json:"description,omitempty"`
	// BasePath is the prefix every route sits under, for example "/api/v1".
	BasePath string `json:"base_path"`

	// Revision is the date this API surface last changed, as YYYY-MM-DD.
	//
	// It is not a build stamp and not [Version]: Version is the path segment,
	// and a build stamp would move on every regeneration, so two identical
	// clients built a month apart would look a month apart. This moves only
	// when the document's own hash does, which is what makes "how old is the
	// oldest client still calling" a question with an answer.
	//
	// Empty for a document nobody stamped — [Document.Hash] deliberately does
	// not see this field, so stamping one never changes the other.
	Revision string `json:"revision,omitempty"`

	// RevisionHeader is the header Revision travels in, both ways.
	//
	// Configurable because it is a name in somebody else's namespace: a proxy
	// or a gateway may already have claimed it. Empty in a document that
	// predates the setting; a generator reading it falls back to the default.
	RevisionHeader string `json:"revision_header,omitempty"`

	Enums     []Enum     `json:"enums"`
	Objects   []Object   `json:"objects"`
	Resources []Resource `json:"resources"`

	// Auth is the authentication foundation this API is served with, or nil for
	// a project that has none.
	//
	// It is here rather than beside the schema because it is part of the surface:
	// the endpoints it mounts, the permissions a caller has to hold, and how long
	// a token a client is holding stays valid are all things a specification and a
	// client library have to say. A generator reads it instead of asking the
	// project what was configured, which is what keeps the wiring, the document
	// and the client from each carrying their own copy of the answer.
	Auth *Auth `json:"auth,omitempty"`

	// Files is the file handling this API is served with, or nil for a project
	// that accepts no uploads.
	//
	// Here for the reason [API.Auth] is: a byte cap, a sweep interval and the
	// list of types served inline are all part of the surface, and a generator
	// reads them out of the document rather than asking the project what was
	// configured. That is what keeps the wiring, the documentation and a
	// client's expectations from each carrying their own copy of the answer.
	Files *Files `json:"files,omitempty"`

	// Notifications is the inbox this API serves, or nil for a project with
	// none. Here for the reason [API.Auth] and [API.Files] are.
	Notifications *Notifications `json:"notifications,omitempty"`

	// Presence is the resolved `presence:` block, or nil for a project that does
	// not track it.
	Presence *Presence `json:"presence,omitempty"`

	// Throttle is the resolved `throttle:` block, or nil for a project that
	// does not limit API calls.
	//
	// It is part of the surface rather than a deployment detail, which is why it
	// is here and not beside the database settings: the numbers travel to
	// clients in the RateLimit-* headers and into the generated documentation,
	// so a specification that quoted anything else would be quoting a number
	// nobody enforces.
	Throttle *Throttle `json:"throttle,omitempty"`

	// Cache is the resolved `cache:` block, or nil for a project that caches
	// nothing.
	//
	// It is the one part of this document no client can observe, which is why
	// [Document.Hash] clears it: whether a replica answered a request out of
	// memory or out of the database is invisible over HTTP, and a project that
	// spent a revision turning it on would be telling every client it was built
	// against something older than the server.
	Cache *Cache `json:"cache,omitempty"`

	// Servers are the deployments this API is served on, in the order the project
	// named them.
	//
	// Here for the reason [API.Auth] is: the OpenAPI document, the Go client and
	// the TypeScript client each have to say where the API is, and a generator
	// reading it out of the document is what keeps the three from carrying their
	// own copies of the answer. A language added later gets the deployments
	// without growing an option of its own.
	//
	// Empty for a project that has named none, which is the honest state for an
	// API that is not deployed anywhere yet: the document then describes a
	// relative server and the clients ask their caller for a URL.
	//
	// Order is meaningful — a documentation viewer sends its trial request to the
	// first entry — so nothing between here and an emitter sorts it.
	Servers []Server `json:"servers,omitempty"`

	// Web is where this API's browser front end is served, or nil for a project
	// that has no such thing.
	//
	// A deployment fact like [API.Servers], and here for the same reason: two
	// generators read it and neither owns it. The server generator turns it into
	// the origin a provider sign-in redirects to and the origins a preflight is
	// answered for — one fact, two answers that must not disagree.
	//
	// Nil is the switch. No web block means no cross-origin code emitted at all,
	// which is the state every project that serves its front end from the same
	// origin should stay in.
	Web *Web `json:"web,omitempty"`

	// Tracing is the spans this API's generated code opens, or nil for a
	// project that asked for none.
	//
	// Nil is the question every generator asks, and asking it is the whole
	// mechanism: a nil here means no import of rig/observe, no span, and no
	// OpenTelemetry in the application's go.mod. Not a span that calls a no-op
	// — optional, in rig, means absent.
	Tracing *Tracing `json:"tracing,omitempty"`

	// Monitoring is rig's own page over the spans, or nil for a project that
	// asked for none. Never set without Tracing.
	Monitoring *Monitoring `json:"monitoring,omitempty"`

	// OpenAPI is where this API serves the document describing it, or nil for a
	// project that leaves the document a file.
	//
	// Here rather than in the openapi generator's options for the reason
	// [API.Auth] is: two generators have to agree about it. The server mounts the
	// routes and the document has to describe them, and a specification that
	// omitted the routes it is fetched over would be the one omission this
	// generator cannot excuse.
	OpenAPI *OpenAPI `json:"openapi,omitempty"`

	// EmbeddedFoundation says rig's own migrations are carried by the modules
	// that own them rather than vendored into this project's migrations
	// directory.
	//
	// It is here because a generator has to know. The application applies its
	// own schema, so a project whose modules carry theirs needs the code that
	// hands those sets to rig/migrate in the right order — and a project that
	// vendored them must not get it, because then the same DDL would be applied
	// twice under two histories.
	//
	// A bool rather than the mode's spelling: vendored is the default and the
	// absence, so false is what a document written before the key existed means.
	EmbeddedFoundation bool `json:"embedded_foundation,omitempty"`

	// Permissions is every permission this API's endpoints require, computed once
	// at Freeze from the endpoints themselves.
	//
	// Derived once rather than by each consumer, for the same reason
	// [Endpoint.Pattern] is: the handler's check, the catalogue written to the
	// database, an administration screen and the grant an owner receives all have
	// to mean the same thing, and three places deriving it is three places to
	// drift.
	Permissions []Permission `json:"permissions"`
}

API is the projected layer: what clients see.

type AuditColumns

type AuditColumns struct {
	CreatedAt *ColumnRef `json:"created_at,omitempty"`
	CreatedBy *ColumnRef `json:"created_by,omitempty"`
	UpdatedAt *ColumnRef `json:"updated_at,omitempty"`
	UpdatedBy *ColumnRef `json:"updated_by,omitempty"`
	DeletedAt *ColumnRef `json:"deleted_at,omitempty"`
	DeletedBy *ColumnRef `json:"deleted_by,omitempty"`

	// The key columns record which API key a change came through, where the
	// account columns record whose account it was. An integration's service
	// account may be shared between several keys, so the pair together say both
	// "the nightly import did this" and "through this credential".
	CreatedByAPIKey *ColumnRef `json:"created_by_api_key,omitempty"`
	UpdatedByAPIKey *ColumnRef `json:"updated_by_api_key,omitempty"`
	DeletedByAPIKey *ColumnRef `json:"deleted_by_api_key,omitempty"`
}

AuditColumns names the columns rig stamps automatically. Any of them may be nil: a table opts in by having the column.

type Auth

type Auth struct {
	// BasePath is the prefix the endpoints sit under, for example "/auth".
	BasePath string `json:"base_path"`

	Tenant  AuthTenant  `json:"tenant"`
	Session AuthSession `json:"session"`
	// EmailCode is the mailed-code sign-in. It is a value rather than a pointer
	// because Enabled already carries the off state, and a nil would make every
	// consumer nil-check before reading one bool.
	EmailCode AuthEmailCode `json:"email_code"`
	Limits    AuthLimits    `json:"limits"`

	// AllowTenantCreation mounts POST <base>/tenants, where somebody signed in
	// makes one and becomes its owner.
	AllowTenantCreation bool `json:"allow_tenant_creation"`
	// RequireVerifiedEmail refuses a sign-in until the address is verified.
	RequireVerifiedEmail bool `json:"require_verified_email"`

	// AllowedIdentityDomains are the domains a stranger's address must be in to
	// become a person here. Empty, which is the default, restricts nobody.
	//
	// omitempty, so that a project without one leaves the document exactly as it
	// was — this key arriving would otherwise rewrite every golden IR for a
	// setting none of them uses.
	AllowedIdentityDomains []string `json:"allowed_identity_domains,omitempty"`

	// TrustedProxies are the CIDR ranges whose X-Forwarded-For may be believed.
	// Empty means none, and an address is then read from the connection.
	TrustedProxies []string `json:"trusted_proxies,omitempty"`

	// LogRetention is how long an authentication log entry is kept. Zero keeps
	// everything, which is the default because nothing prunes the table unless a
	// project says to.
	//
	// It is never shorter than the longest window in Limits: those limits are
	// counted from this table, so a shorter window would delete the rows a
	// lockout is adding up to. The configuration refuses one rather than clamping
	// it, and the server refuses to start on one assembled by hand.
	LogRetention Duration `json:"log_retention,omitempty"`

	// OAuth is nil when no provider sign-in is configured, which is also when
	// the provider routes do not exist.
	OAuth *AuthOAuth `json:"oauth,omitempty"`
}

Auth is the authentication foundation as this project configured it.

Nil means the project has none: no endpoints are mounted, nothing imports rig/auth, and a document consumer has no authentication to describe.

Every value here is resolved rather than optional. A zero in this struct means somebody wrote a zero, not "use the default" — the defaults were applied while the project configuration was read, so the generated wiring, the emitted specification, and a client library reading the document all see the same numbers instead of each having to know what rig/auth would have picked.

type AuthEmailCode added in v0.6.0

type AuthEmailCode struct {
	// Enabled mounts POST <base>/email-code and POST <base>/email-code/verify.
	Enabled bool `json:"enabled"`
	// Length is how many digits the code has.
	Length int `json:"length"`
	// TTL is how long a code lasts.
	TTL Duration `json:"ttl"`
	// MaxAttempts is how many wrong guesses kill one code. It is a ceiling on
	// the code rather than a rate limit on the address, which is why it is here
	// and not in AuthLimits.
	MaxAttempts int `json:"max_attempts"`
	// AllowProvisioning sends a code to an address rig has never seen, creating
	// the person. Off means a code goes only to an address that already has an
	// identity.
	AllowProvisioning bool `json:"allow_provisioning"`
}

AuthEmailCode is the mailed-code sign-in: a short numeric code sent to an address and typed back.

type AuthLimit

type AuthLimit struct {
	Max    int      `json:"max"`
	Window Duration `json:"window"`
}

AuthLimit is one limit: how many, and over how long.

type AuthLimits

type AuthLimits struct {
	// LoginByEmail locks one account after a handful of wrong codes.
	LoginByEmail AuthLimit `json:"login_by_email"`
	// LoginByIP throttles one source spraying many accounts. It is deliberately
	// looser than the address limit, so an office behind one NAT does not trip it.
	LoginByIP AuthLimit `json:"login_by_ip"`
	// EmailCodeRequest bounds codes mailed per address, and EmailCodeByIP bounds
	// them per source across every address — which is what limits identity
	// creation when AuthEmailCode.AllowProvisioning is set.
	EmailCodeRequest   AuthLimit `json:"email_code_request"`
	EmailCodeByIP      AuthLimit `json:"email_code_ip"`
	VerificationResend AuthLimit `json:"verification_resend"`
	// Refresh bounds one session's rotations.
	Refresh        AuthLimit `json:"refresh"`
	APIKeyFailures AuthLimit `json:"api_key_failures"`
	// InvitationPreview bounds reading an invitation link per source. It is the
	// one unauthenticated endpoint keyed by a secret rather than an address.
	InvitationPreview AuthLimit `json:"invitation_preview"`
}

AuthLimits are the rate limits, counted in the database.

type AuthOAuth

type AuthOAuth struct {
	// BaseURL is this application's own origin, which the callback URL is built
	// from and which the provider has registered exactly.
	BaseURL string `json:"base_url,omitempty"`
	// BaseURLEnv names an environment variable that overrides BaseURL, for the
	// ordinary case where the origin differs per deployment.
	BaseURLEnv string `json:"base_url_env,omitempty"`
	// OriginFromHost derives the origin per request from the Host instead, which
	// is what an application serving a tenant per subdomain needs: the state
	// cookie is set on the host the sign-in started at, and a browser will not
	// send it to a sibling.
	OriginFromHost bool `json:"origin_from_host,omitempty"`

	// SigningKeyEnv names the environment variable holding the key that signs
	// the state parameter. A generated key works for one process and fails
	// across replicas, since a callback may land on a different one.
	SigningKeyEnv string `json:"signing_key_env,omitempty"`

	// StateTTL bounds how long a sign-in round trip may take.
	StateTTL Duration `json:"state_ttl"`

	// AllowProvisioning creates an identity the first time somebody signs in
	// with a provider. Off means a provider sign-in only works for an address
	// that already has one.
	AllowProvisioning bool `json:"allow_provisioning"`
	// AllowJoining creates an account in the tenant a sign-in named, for
	// somebody not in it yet. Resolved: the configuration's key is tri-state
	// and defaults to AllowProvisioning, and that default is applied before it
	// reaches here.
	AllowJoining bool `json:"allow_joining"`
	// AllowedReturnTo bounds where a finished sign-in may land. An open redirect
	// is the classic mistake here.
	AllowedReturnTo []string `json:"allowed_return_to,omitempty"`
	// Insecure allows the state cookie over plain HTTP, for local development
	// where a browser would refuse the __Host- prefixed one.
	Insecure bool `json:"insecure,omitempty"`

	Providers []AuthProvider `json:"providers"`
}

AuthOAuth is provider sign-in.

type AuthProvider

type AuthProvider struct {
	// Name is one of [ProviderGoogle], [ProviderMicrosoft], [ProviderGitHub].
	// It is also the route segment: <base>/oauth/google/start.
	Name string `json:"name"`

	ClientIDEnv     string `json:"client_id_env"`
	ClientSecretEnv string `json:"client_secret_env"`
	// TenantEnv is Microsoft's own idea of a tenant, which has nothing to do
	// with rig's: "common" accepts any account, a directory id restricts sign-in
	// to one organization.
	TenantEnv string `json:"tenant_env,omitempty"`

	// Required fails startup when the credentials are absent. The default skips
	// the provider instead, so one deployment can offer Google and another not,
	// from the same binary and the same configuration.
	Required bool `json:"required,omitempty"`
}

AuthProvider is one identity provider to offer.

Credentials are named rather than carried. A client secret in rig.yaml is a client secret in version control, so the configuration says which environment variable holds it and the generated code reads that.

type AuthSession

type AuthSession struct {
	// AccessTTL is the lifetime of the token that travels on every request.
	AccessTTL Duration `json:"access_ttl"`
	// RefreshTTL is how long an ordinary session lasts.
	RefreshTTL Duration `json:"refresh_ttl"`
	// RememberTTL is how long one lasts when somebody asked to stay signed in.
	RememberTTL Duration `json:"remember_ttl"`
	// RotationLeeway is how long a refresh token stays usable after it has been
	// exchanged, so a dropped response is a retry rather than a logout.
	RotationLeeway Duration `json:"rotation_leeway"`
	// IdentityTTL is the lifetime of the tenant-less credential somebody holds
	// between signing in and picking a tenant.
	IdentityTTL Duration `json:"identity_ttl"`
}

AuthSession is how long the things somebody holds stay good for.

type AuthTenant

type AuthTenant struct {
	// Sources are tried in order, and the first that names a tenant wins. A
	// request that names none is not an error: a sign-in is allowed to arrive
	// without one.
	Sources []AuthTenantSource `json:"sources"`

	// Header is the header read by [TenantFromHeader].
	Header string `json:"header,omitempty"`
	// Query is the parameter read by [TenantFromQuery].
	Query string `json:"query,omitempty"`
	// DefaultSlugEnv names an environment variable holding the slug to fall back
	// to when the host names no tenant. It is the accommodation that lets a
	// host-based deployment be tried on localhost, where there is no subdomain
	// to read.
	DefaultSlugEnv string `json:"default_slug_env,omitempty"`
}

AuthTenant says how a request names the tenant it is for.

It is consulted only where the tenant cannot be known some other way — a sign-in, a password reset, the start of a provider flow. Once there is a session the tenant comes from the token.

func (AuthTenant) Uses

func (t AuthTenant) Uses(source AuthTenantSource) bool

Uses reports whether a source is configured.

type AuthTenantSource

type AuthTenantSource string

AuthTenantSource is one way a request can name the tenant it is for.

const (
	// TenantFromHeader reads a header, [AuthTenant.Header].
	TenantFromHeader AuthTenantSource = "header"
	// TenantFromHost reads the leftmost label of the Host and looks the slug up
	// in the tenant table. This is the one that survives a redirect, which is
	// what a provider sign-in needs.
	TenantFromHost AuthTenantSource = "host"
	// TenantFromQuery reads a query parameter, [AuthTenant.Query]. Useful for a
	// local demonstration and rarely right in a deployment: it travels in links
	// and logs.
	TenantFromQuery AuthTenantSource = "query"
	// TenantFromHook calls the application's own resolver, which is the escape
	// hatch for a deployment none of the others describe.
	TenantFromHook AuthTenantSource = "hook"
)

type Cache

type Cache struct {
	// Enabled says rig caches the reads it owns end to end — session and API key
	// verification. It is what makes server-go write the wiring.
	Enabled bool `json:"enabled"`

	// TTLSeconds is how long an answer may be reused with no invalidation
	// arriving. It is the backstop rather than the guarantee: a revocation is
	// published on the transaction that made it, and reaches every replica when
	// that transaction commits.
	//
	// Fractional for the reason [Throttle.IntervalSeconds] is: this is a number
	// somebody may reasonably want below a second.
	TTLSeconds float64 `json:"ttl_seconds"`

	// Channel is the Postgres NOTIFY channel invalidations travel on. Validated
	// as an identifier when the configuration was read, because it reaches
	// Postgres both quoted in a LISTEN and as a parameter to pg_notify.
	Channel string `json:"channel"`

	// MaxEntries bounds one cache. Past it the whole map is dropped rather than
	// swept — see runtime/cache.
	MaxEntries int `json:"max_entries,omitempty"`
}

Cache is the resolved cache block: whether rig holds its own per-request reads in memory, and the channel that withdraws them.

The three numbers here describe a mechanism rather than a surface. Nothing a client sends or receives changes when they do, which is what makes this the one field Document.Hash clears outright rather than in part.

type Check

type Check struct {
	Name       string `json:"name"`
	Expression string `json:"expression"`
}

Check is a CHECK constraint, carried so generators can surface it in documentation and so rules can recognize the constraints rig itself scaffolds.

type ChildLink struct {
	// Name is the HasMany accessor: HomeFixtures out of fixture.home_team_id.
	Name string `json:"name"`
	// Child is the resource name, Table the physical table behind it.
	Child string `json:"child"`
	Table string `json:"table"`
	// Column is the foreign key on the child's table.
	Column string `json:"column"`
	// Hook is the field on the child's parent-hooks struct this edge fills:
	// HomeTeam, matching the child's own [ParentLink.Name].
	Hook string `json:"hook"`
}

ChildLink is one foreign key pointing at a resource, from the parent's side.

type Column

type Column struct {
	Name          string        `json:"name"`
	Comment       string        `json:"comment,omitempty"`
	CommentSource CommentSource `json:"comment_source"`

	// SQLType is the type as written, for example "text", "timestamptz",
	// "lesson_status" or "text[]". UDTName is the underlying pg_type name,
	// which differs for arrays and domains.
	SQLType string `json:"sql_type"`
	UDTName string `json:"udt_name,omitempty"`

	Nullable   bool   `json:"nullable"`
	HasDefault bool   `json:"has_default,omitempty"`
	Default    string `json:"default,omitempty"`

	// Identity is set for GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY.
	Identity bool `json:"identity,omitempty"`
	// Generated is set for GENERATED ALWAYS AS (expr) STORED. Such columns can
	// never be written, so they are read-only everywhere above this layer.
	Generated bool `json:"generated,omitempty"`

	CharMaxLength int `json:"char_max_length,omitempty"`
	NumericPrec   int `json:"numeric_precision,omitempty"`
	NumericScale  int `json:"numeric_scale,omitempty"`

	// Ordinal is the 1-based position in the table, preserved so generated
	// structs read in the same order as the CREATE TABLE.
	Ordinal int `json:"ordinal"`

	IsPrimaryKey bool   `json:"is_primary_key,omitempty"`
	ForeignKey   *FKRef `json:"foreign_key,omitempty"`

	// EnumType is the Postgres enum type name when this column is an enum.
	EnumType string `json:"enum_type,omitempty"`
	// ArrayElem is the element type name when this column is an array.
	ArrayElem string `json:"array_elem,omitempty"`
}

Column is one column of a Table. Every field here is a fact read out of Postgres; none of it is configurable.

func (Column) Writable

func (c Column) Writable() bool

Writable reports whether a client could ever supply a value for this column.

type ColumnRef

type ColumnRef struct {
	Table    string       `json:"table"`
	Name     string       `json:"name"`
	SQLType  string       `json:"sql_type"`
	Nullable bool         `json:"nullable"`
	Scan     ScanStrategy `json:"scan"`
}

ColumnRef binds an API field to the column that stores it. The fields duplicated from Column are the ones needed on nearly every emitted line; Document.Column reaches the full column when more is needed.

type CommentSource

type CommentSource string

CommentSource records where a comment came from, so a rule that requires documentation can tell an authored sentence from a generated placeholder.

const (
	// CommentSourceNone means the column has no comment at all.
	CommentSourceNone CommentSource = "none"
	// CommentSourceAuto means rig supplied it from a template for a well-known
	// column such as id or created_at.
	CommentSourceAuto CommentSource = "auto"
	// CommentSourceDatabase means it came from a Postgres COMMENT ON.
	CommentSourceDatabase CommentSource = "database"
	// CommentSourceConfig means a human wrote it in the table's YAML.
	CommentSourceConfig CommentSource = "config"
)

func (CommentSource) Authored

func (s CommentSource) Authored() bool

Authored reports whether a human wrote this comment, as opposed to rig or the database supplying one.

type Document

type Document struct {
	IRVersion int    `json:"ir_version"`
	Tool      string `json:"generated_by"`

	API    API    `json:"api"`
	Schema Schema `json:"schema"`

	// Valid is false when the document was written despite validation errors.
	// Generators never run against an invalid document; it is emitted anyway so
	// that a broken project can still be inspected and repaired.
	Valid bool `json:"valid"`
	// contains filtered or unexported fields
}

Document is the complete compile output and the only input a generator reads.

A document is immutable once produced. Unmarshal and the compiler's freeze step both index it eagerly, so concurrent readers are safe; mutating a document after that point is a bug.

func Unmarshal

func Unmarshal(b []byte) (*Document, error)

Unmarshal decodes a document and indexes it.

An unknown field is an error rather than a silent drop: reading a document written by a newer rig should fail loudly, not half-succeed.

func (*Document) Column

func (d *Document) Column(table, column string) *Column

Column returns the named column of the named table, or nil.

func (*Document) Enum

func (d *Document) Enum(name string) *Enum

Enum returns the named API enum, or nil.

func (*Document) Hash

func (d *Document) Hash() (string, error)

Hash returns the SHA-256 of the document's canonical encoding, prefixed with the algorithm. It identifies the document's content, not the run that produced it, so an unchanged schema and configuration always hash the same.

API.Revision is cleared before hashing, and that is not a detail: the revision is derived from this hash, so a hash that saw it would move every time it was set, which would move the revision, which would move the hash. It is cleared here rather than by each caller because there is no caller for whom the other behavior is right.

API.EmbeddedFoundation is cleared for a different reason, and the two are worth telling apart. It is in the document because a generator has to know where rig's own migrations come from — but no client can tell, and the revision is what a client reads to decide whether it is talking to an API it was built against. Moving a project's DDL from its own directory into the modules that own it changes nothing anybody could observe over HTTP, so it must not spend a revision saying otherwise.

Document.Tool is cleared for that reason, and it is the one field here that says nothing about the project at all: it is the build of rig that produced the document. Left in, every release of rig would move every project's revision — upgrading the generator would tell every client the API had changed, on a day the API did not. The version belongs in the document, which is why it is still written; it does not belong in what the document is compared by.

API.Monitoring is cleared for that same reason. rig's own page is mounted beside the API rather than in it: it appears in no specification, no generated client calls it, and a caller cannot tell whether it is there. A project that turned the page on and spent a revision on it would be telling every client it was built against something older than the server, over a change none of them can see.

API.Cache is cleared for that reason too, and it is the clearest case of it: whether a replica answered out of memory or out of the database is not something a client can observe at all. The generated clients are told the backstop lifetime all the same — see rigclient.AuthProfile — because a document consumer should be able to read how long a lost invalidation could go unnoticed. Being told a number is not the same as behaving differently for it, and only the second one is what a revision means.

Resource.Cached is cleared for the same reason as API.Cache, and it has to be: it is the per-table half of the same switch, so leaving it in would let turning a cache on move a revision that turning the block on does not. It is a slice rather than a field, so clearing it copies — the caller's document must not come back with its opt-ins erased.

API.Notifications is cleared in part, and it is the second field here to be — the dispatcher's numbers go, and the three that say what the API looks like stay. Nothing answers a claim_ttl, a send_timeout or any of the retry arithmetic to anybody: no route in notify/notifyhttp carries them, no generated client reads them, and the OpenAPI document mentions notifications only to say its endpoints are not described there. So they are cleared for Monitoring's reason, and it took until they were being changed to notice they had never been — a project that retuned its dispatcher was spending a revision telling every client it was built against something older than the server.

Enabled and Expose stay: the first decides whether the routes exist at all and the second projects a resource. DefaultDigest stays too, and it is the only judgement call in the set. No response carries it, but it decides what an account with no setting actually receives, so a settings page built when the default was Immediate behaves differently against Weekly — which is the thing a revision is for.

Presence is cleared in *part*, and it was the first field here to be. Two of its numbers are answered to the browser on every heartbeat, so a client built when the TTL was a minute behaves differently against twenty seconds — those stay, because that is exactly what a revision is for. The sweeper's interval and its grace period are housekeeping: no response carries them and no caller can tell what they are, so they are cleared for the reason Monitoring is.

Schema.Replication is cleared for Monitoring's reason too, together with the three facts hung on each table for it — Table.Publications, Table.Unlogged and Table.ReplicaIdentity. It is the only thing in the document that nobody wrote: it is read off the server, so it can differ between two machines holding the same files and change on one of them while nobody is looking. The sync service adds a table to its own publication the first time somebody subscribes, so a revision that saw it would move on the day a developer opened the app, and again on the day an unrelated tool published something. What a publication carries decides whether a shape streams, which is why validation reads it — but no response says it, so it is not what a revision is for.

Table.ReplicaIdentity is the same argument reached from the other end. Unlike a publication nothing moves it behind your back — it takes an ALTER TABLE — but it is still read off the server rather than written in a file, and a project whose revision moved because somebody finally set REPLICA IDENTITY FULL would be telling every client it was built against something older, for a change no client can observe.

func (*Document) Object

func (d *Document) Object(name string) *Object

Object returns the named object, or nil.

func (*Document) PgEnum

func (d *Document) PgEnum(name string) *PgEnum

PgEnum returns the named Postgres enum type, or nil.

func (*Document) Reindex

func (d *Document) Reindex()

Reindex rebuilds the lookup tables. Call it after constructing a document by hand; Unmarshal and the compiler do it for you.

func (*Document) Resolve

func (d *Document) Resolve(ref *ColumnRef) *Column

Resolve returns the column a reference points at, or nil. This is the hop from the denormalized ColumnRef carried on a field to the full Column.

func (*Document) Resource

func (d *Document) Resource(name string) *Resource

Resource returns the named resource, or nil.

func (*Document) ResourceForTable

func (d *Document) ResourceForTable(table string) *Resource

ResourceForTable returns the resource projected from the named table, or nil.

func (*Document) SetRevision

func (d *Document) SetRevision(rev string)

SetRevision stamps the API revision.

It is the one field written after the compiler froze the document, and it has to be: the revision is decided from Document.Hash, so the document has to exist before there is a revision to record. It is set once, before any generator runs, and nothing in the index refers to it — so the document is still safe to hand to concurrent readers afterwards.

func (*Document) Table

func (d *Document) Table(name string) *Table

Table returns the named table, or nil.

func (*Document) TypeKindOf

func (d *Document) TypeKindOf(name string) (TypeKind, bool)

TypeKindOf resolves a type name against this document.

type Duration

type Duration time.Duration

Duration is a length of time, written the way a person writes one.

It exists because the document is read by more than Go. A field holding 43200000000000 is a field every consumer has to divide by something, and one holding "12h" reads correctly in the IR JSON, in an OpenAPI description, and in a TypeScript comment. Duration.Duration hands the Go value back to a generator that wants to emit a literal.

func (Duration) Duration

func (d Duration) Duration() time.Duration

Duration returns the value as a time.Duration.

func (Duration) MarshalJSON

func (d Duration) MarshalJSON() ([]byte, error)

MarshalJSON writes the canonical string form.

func (Duration) String

func (d Duration) String() string

String renders the canonical form: the largest units first, and nothing that is zero. A whole number of days is written in days, which is what makes a thirty-day refresh token read as 30d rather than as 720h0m0s.

func (*Duration) UnmarshalJSON

func (d *Duration) UnmarshalJSON(data []byte) error

UnmarshalJSON accepts the string form, and a bare 0 for "none".

type ElectricAuth

type ElectricAuth string

ElectricAuth is the authentication level a live-sync endpoint requires.

const (
	// ElectricAuthTenant requires a session scoped to a tenant, and the shape is
	// filtered to that tenant.
	ElectricAuthTenant ElectricAuth = "tenant"
	// ElectricAuthAdmin requires an administrative session.
	ElectricAuthAdmin ElectricAuth = "admin"
)

type ElectricEndpoint

type ElectricEndpoint struct {
	Auth ElectricAuth `json:"auth"`
	// Path is the live shape's route, for example "/api/v1/lesson/_stream".
	// It is written relative to the API's base path and expanded to the full
	// route when the document is frozen, the same way an endpoint's is.
	Path   string          `json:"path"`
	Params []ElectricParam `json:"params,omitempty"`

	// Fallback is whether this shape answers from the database when the sync
	// service cannot be reached, rather than answering 502.
	//
	// True for a table that asked for a shape at all, because a page whose list
	// *is* a shape renders nothing without its rows. False is for a shape whose
	// value is its freshness rather than its rows: a snapshot of who was looking
	// at this a moment ago, that then stops updating, is worth less than an empty
	// list.
	//
	// No omitempty, deliberately. The interesting value is the false one, and a
	// tag that hid it would leave the shape that opted out looking exactly like
	// one nobody had thought about.
	Fallback bool `json:"fallback"`
}

ElectricEndpoint describes a live-sync shape endpoint for a resource. The tenant and lifecycle predicates are built by rig; the declared params are passed to the application's own scoping function.

func (ElectricEndpoint) DeletedPath

func (e ElectricEndpoint) DeletedPath() string

DeletedPath is the route of the trash shape, and VersionsPath the route of the history one.

Both are the live shape's route with a segment spliced in ahead of the stream marker. They live here rather than in the generator that mounts them so that there is one answer to where the marker goes: a second place composing these by hand is a second place that could put it somewhere else.

func (ElectricEndpoint) VersionsPath

func (e ElectricEndpoint) VersionsPath() string

VersionsPath is the route of the history shape. See DeletedPath.

type ElectricParam

type ElectricParam struct {
	// Name is the query-string key exactly as clients send it.
	Name string `json:"name"`
	// Field is the Go identifier in the generated request struct.
	Field       string `json:"field"`
	Type        string `json:"type"`
	Optional    bool   `json:"optional,omitempty"`
	Description string `json:"description,omitempty"`
}

ElectricParam is one query parameter a live-sync endpoint accepts.

type Endpoint

type Endpoint struct {
	Name        string `json:"name"`
	Title       string `json:"title,omitempty"`
	Summary     string `json:"summary,omitempty"`
	Description string `json:"description,omitempty"`

	Method string `json:"method"`
	// Path is relative to the resource, for example "/{id}" or "".
	Path string `json:"path"`
	// Pattern is the full route in net/http form, for example
	// "GET /api/v1/lessons/{id}". Computed once when the document is frozen so
	// the router, the OpenAPI document, and the TypeScript client cannot
	// disagree about path shape.
	Pattern string `json:"pattern"`
	// AliasPatterns are additional routes dispatching to the same handler. Search
	// uses this to expose a POST fallback alongside the QUERY route, for
	// intermediaries that reject unfamiliar methods.
	AliasPatterns []string `json:"alias_patterns,omitempty"`

	OperationID string `json:"operation_id"`
	// Public answers without a credential.
	//
	// It means the endpoint does not require the claims lookup to succeed, not
	// that the lookup is skipped: an application that resolves a tenant from
	// the host rather than from a token still gets one, and a caller who does
	// present a credential is still identified by it. What changes is that a
	// caller who presents nothing is served instead of refused.
	Public bool `json:"public,omitempty"`

	// Permission is the RBAC key required to call this endpoint. Empty means a
	// valid session is enough.
	Permission string `json:"permission,omitempty"`

	// WidePermission is the key required to pass ?scope=all, on a read of an
	// owner-scoped resource. Empty everywhere else.
	//
	// Separate from Permission because they answer different questions and a
	// caller can hold one without the other. Reading your own notes and reading
	// everybody's are two grants, which is the whole point of the parameter.
	WidePermission string `json:"wide_permission,omitempty"`

	// File is the file column this endpoint acts on, set on the three rig
	// synthesizes per file column and nil on everything else.
	//
	// The upload's own [EndpointRequest.FileParts] says what the form carries;
	// this says which column the bytes end up on, which the download and the
	// delete need just as much and neither of them has a form.
	File *FileColumn `json:"file,omitempty"`

	Request   EndpointRequest    `json:"request"`
	Responses []EndpointResponse `json:"responses"`
	// Errors are the standard failure statuses this endpoint can return. They
	// are listed as bare codes rather than as full responses because every one
	// of them has the same body — the Error object — and spelling that out on
	// every endpoint would triple the size of the document without adding a
	// fact. Generators pair each code with the matching ErrorCode value.
	Errors []int        `json:"errors,omitempty"`
	Impl   EndpointImpl `json:"impl"`
}

Endpoint is one operation on a resource.

type EndpointImpl

type EndpointImpl struct {
	Kind EndpointKind `json:"kind"`
	// RepoMethod is the repository call the default implementation makes. Empty
	// for custom endpoints, which have no default.
	RepoMethod string `json:"repo_method,omitempty"`
	// ServiceMethod is the method name on the generated service interface.
	ServiceMethod string `json:"service_method"`
	// HandlerName is the generated HTTP handler's name.
	HandlerName string `json:"handler_name"`
}

EndpointImpl tells the service and server generators how this endpoint is wired up.

type EndpointKind

type EndpointKind string

EndpointKind distinguishes an endpoint rig synthesized from one declared in configuration.

const (
	EndpointGenerated EndpointKind = "Generated"
	EndpointCustom    EndpointKind = "Custom"
)

Where an endpoint came from.

type EndpointRequest

type EndpointRequest struct {
	// ContentTypes are the media types this endpoint accepts, most preferred
	// first.
	//
	// A list rather than one string because an endpoint can honestly accept two:
	// a create on a table with a file column takes either a JSON body or a
	// multipart form carrying the same JSON in one part and the bytes in
	// another. Everything else has exactly one, and a generator that wants "the"
	// content type takes the first.
	ContentTypes []string `json:"content_types,omitempty"`

	// FileParts are the file parts a multipart request carries, in the order the
	// server reads them. Empty on every endpoint that is JSON only.
	//
	// It is here because [EndpointRequest.ContentTypes] alone is not enough to
	// render a multipart request: it says the endpoint takes a form, and says
	// nothing about what the parts are called. A generator left to work that out
	// would re-derive the <role>_file_id convention from a field's name — which
	// is the convention the compiler exists to have already resolved, and which
	// three generators deriving separately would eventually disagree about.
	FileParts []FilePart `json:"file_parts,omitempty"`

	Headers     []Field `json:"headers,omitempty"`
	PathParams  []Field `json:"path_params,omitempty"`
	QueryParams []Field `json:"query_params,omitempty"`
	BodyParams  []Field `json:"body_params,omitempty"`
	// BodyObject names a whole object as the body, used instead of BodyParams.
	BodyObject string `json:"body_object,omitempty"`
}

EndpointRequest is everything a client sends.

type EndpointResponse

type EndpointResponse struct {
	StatusCode  int    `json:"status_code"`
	Description string `json:"description,omitempty"`
	// ContentTypes are what this response can be. A list for symmetry with the
	// request, and because a download answers with whatever the file turned out
	// to be rather than with one type known at generation time.
	ContentTypes []string `json:"content_types,omitempty"`
	Headers      []Field  `json:"headers,omitempty"`
	BodyFields   []Field  `json:"body_fields,omitempty"`
	BodyObject   string   `json:"body_object,omitempty"`
}

EndpointResponse is one possible outcome. An endpoint always lists every status it can return, so 200, 404, and 409 are all first-class.

type Enum

type Enum struct {
	Name        string      `json:"name"`
	Description string      `json:"description,omitempty"`
	Values      []EnumValue `json:"values"`
	// PgType binds this enum to the Postgres type it came from. Empty for enums
	// that exist only in the API, such as ErrorCode.
	PgType string `json:"pg_type,omitempty"`
}

Enum is a closed set of values exposed to clients.

type EnumValue

type EnumValue struct {
	// Name is the Go and TypeScript identifier, for example "InProgress".
	Name string `json:"name"`
	// Wire is the literal on the wire and in Postgres, for example "in_progress".
	Wire        string `json:"wire"`
	Description string `json:"description,omitempty"`
}

EnumValue separates the identifier from the literal deliberately: Postgres labels are frequently snake_case while Go and TypeScript want PascalCase, and conflating the two makes one of the three wrong.

type FKRef

type FKRef struct {
	Table    string `json:"table"`
	Column   string `json:"column"`
	OnDelete string `json:"on_delete,omitempty"`
	OnUpdate string `json:"on_update,omitempty"`
}

FKRef is the single-column foreign key a column participates in, denormalized onto the column because that is how nearly every generator needs it. Composite foreign keys appear only in Table.ForeignKeys.

type Field

type Field struct {
	// Name is the Go and TypeScript identifier, for example "ManagerEmailAddress".
	Name string `json:"name"`
	// Wire is the JSON key, for example "managerEmailAddress".
	Wire string `json:"wire"`

	// Description is the human-readable text for this field, and the only copy
	// of it.
	//
	// Every generator that can carry a description must emit this one: a Go doc
	// comment, an OpenAPI `description`, a TypeScript doc comment. It is written
	// once — by the compiler, from the column comment or from the shape of the
	// thing — precisely so that a rule explained on a filter field is the same
	// sentence in the struct, in the schema, and in the client. A generator that
	// paraphrases it, or leaves it out, is how three descriptions of one field
	// start disagreeing.
	//
	// internal/gen/gentest.DescriptionsSurvive is the guard, and every generator
	// with a place to put a description calls it.
	Description string `json:"description,omitempty"`

	// Type is a primitive name or the name of an enum, object, or resource.
	Type string `json:"type"`
	// TypeKind resolves Type. Set when the document is frozen.
	TypeKind TypeKind `json:"type_kind"`
	// GoType is the concrete Go type for this field, including a leading * when
	// the field is nullable, for example "*string" or "time.Time".
	GoType string `json:"go_type,omitempty"`

	// Format narrows a primitive for documentation and client-side validation:
	// EmailAddress, URL, PhoneNumber, Color, CountryCode, RichText.
	Format string `json:"format,omitempty"`

	Modifiers []string `json:"modifiers,omitempty"`
	Default   string   `json:"default,omitempty"`
	Example   string   `json:"example,omitempty"`

	// ReadOnly fields never appear in a create or update body.
	ReadOnly bool `json:"read_only,omitempty"`
	// Immutable fields may be set on create but never updated, so they are
	// absent from the update body entirely.
	Immutable bool `json:"immutable,omitempty"`

	// Column binds this field to storage. Nil for computed or virtual fields.
	Column *ColumnRef `json:"column,omitempty"`
}

Field is one member of an object, request, or response.

func (Field) IsArray

func (f Field) IsArray() bool

IsArray reports whether the field carries the Array modifier.

func (Field) IsNullable

func (f Field) IsNullable() bool

IsNullable reports whether the field carries the Nullable modifier.

func (Field) IsTextual

func (f Field) IsTextual() bool

IsTextual reports whether the field is text a pattern can be matched against.

Not every String field is. An inet or cidr column is a string on the wire and is not text in Postgres, so LIKE against one is a type error rather than a filter — and a list of addresses is not a pattern either.

It lives here, on the IR, because the API layer and the persistence layer both have to answer it the same way. The last time they each decided for themselves, one generated a filter field the other had nowhere to put.

type FileColumn

type FileColumn struct {
	// Role is the `<role>` from `<role>_file_id`, in the API's own casing, for
	// example "profileImage".
	Role string `json:"role"`
	// Column is the column itself, for example "profile_image_file_id". It is
	// what the upload writes and the download reads, and it is the one member
	// [FilePart] deliberately does not carry.
	Column string `json:"column"`
	// Field is the Go field on the row, for example "ProfileImageFileID".
	Field string `json:"field"`
	// Part is the multipart part's name, for example "profileImageFile".
	Part string `json:"part"`
	// Segment is the path segment the file endpoints sit under, for example
	// "profile-image-file". It is derived here so the router, the client and a
	// specification cannot spell it three ways.
	Segment string `json:"segment"`
	// Required says the column cannot be null.
	Required bool `json:"required,omitempty"`
}

FileColumn is one `<role>_file_id` column on a resource: a single file attached to each row, in a named role.

The column is the declaration, so everything here is derived from its name and nothing is configurable. It is spelled out rather than left to each generator because five things follow from one column — three endpoints, a form part, two permission keys, a Go field and a path segment — and five derivations of one convention is five places for it to drift.

func (FileColumn) GoName

func (f FileColumn) GoName() string

GoName is the column's Go name without the identifier suffix, for example "ProfileImageFile" out of "ProfileImageFileID".

It is the stem every generated identifier for this column is built from — UploadProfileImageFile, DownloadProfileImageFile, the member on the create's files struct — so that four generators do not each decide where to cut.

type FilePart

type FilePart struct {
	// Name is the part's name on the wire, for example "profileImageFile". It
	// is what the server binds the bytes to, so a client that spells it
	// differently has uploaded a part nobody claimed.
	Name string `json:"name"`
	// Field is the Go field the part is named after, for example
	// "ProfileImageFileID" on a create and "Attachments" on an endpoint that
	// declared the part itself. A generator naming a member after the part
	// rather than after the column would produce a shape that reads nothing
	// like the row it belongs to — which is why a create's part takes its
	// member name from here and not from [FilePart.Name].
	Field string `json:"field"`
	// Role is the <role> from <role>_file_id, for example "profileImage". Empty
	// on a part a configuration declared, which has no column to take a role
	// from: what such a part is for is [FilePart.Description]'s to say.
	Role string `json:"role"`
	// Description is what the part carries, for a part a configuration
	// declared. Empty on a create's, whose role already says it.
	Description string `json:"description,omitempty"`
	// Required says the part has to be present: the column cannot be null, or
	// the configuration did not mark the part optional. It is the whole reason
	// a multipart create exists — a not-null file column is unreachable when
	// the row and its bytes are two requests.
	Required bool `json:"required,omitempty"`
	// Array says the part may repeat, carrying several files under one name.
	//
	// Never true of a create's, where one part is one column and a second copy
	// would be two files for one identifier. It is what an endpoint collecting
	// attachments needs, and it is the difference between an SDK member holding
	// one upload and one holding a list.
	Array bool `json:"array,omitempty"`
}

FilePart is one file a multipart request carries.

type Files

type Files struct {
	// Enabled says this project accepts uploads at all. It is what makes
	// server-go write the wiring.
	Enabled bool `json:"enabled"`
	// Expose says rig_file is projected as a read-only resource, so its url can
	// be read and synced.
	Expose bool `json:"expose,omitempty"`
	// Backend is where the bytes go: memory or s3.
	Backend string `json:"backend"`
	// S3 is the bucket, for a project whose backend is s3, and nil for one
	// whose backend is memory.
	//
	// Nil rather than a zero struct, so that a document for a project keeping
	// its uploads in memory is byte for byte what it was before this field
	// existed — which is what keeps the API revision of every such project from
	// moving because a backend it does not use became possible.
	S3 *FilesS3 `json:"s3,omitempty"`

	// MaxBytes caps one upload. A hard per-file cap and not a quota.
	MaxBytes int64 `json:"max_bytes"`
	// InlineTypes are the sniffed types served without an attachment
	// disposition. Everything else downloads.
	InlineTypes []string `json:"inline_types,omitempty"`

	// AbandonedAfterSeconds and RestoreWindowSeconds are the sweeper's two
	// rules, in seconds because a document is JSON and a duration is not.
	AbandonedAfterSeconds int64 `json:"abandoned_after_seconds"`
	RestoreWindowSeconds  int64 `json:"restore_window_seconds"`

	// CookieDownloads accepts the session cookie on file GET routes, so a
	// stored URL works in an img or a download link.
	CookieDownloads bool `json:"cookie_downloads,omitempty"`
}

Files is the resolved `files:` block: everything about uploads that is a number or a name rather than a function.

Every duration and size here is resolved when the configuration is read, so a zero means somebody wrote a zero rather than meaning "use the default".

type FilesS3 added in v0.3.0

type FilesS3 struct {
	// Bucket is the bucket name, and BucketEnv names an environment variable
	// holding it. Exactly one of them is set — a project that named both would
	// have given two answers to one question, and validation refuses it.
	Bucket    string `json:"bucket,omitempty"`
	BucketEnv string `json:"bucket_env,omitempty"`

	// Region is the bucket's region.
	Region string `json:"region,omitempty"`
	// Endpoint points at something other than AWS — MinIO, R2, a test double.
	// Setting it also selects path-style addressing, because virtual-host style
	// asks DNS to resolve a name almost nothing but AWS answers.
	Endpoint string `json:"endpoint,omitempty"`

	// AccessKeyEnv and SecretKeyEnv name the environment variables the
	// credentials are read from. Both empty at the point of use means the AWS
	// default credential chain, which is what an instance profile or IRSA
	// wants.
	AccessKeyEnv string `json:"access_key_env,omitempty"`
	SecretKeyEnv string `json:"secret_key_env,omitempty"`
}

FilesS3 is the bucket a project on the s3 backend writes to, and how to reach it.

The credentials are the names of environment variables rather than values, the way the OAuth client secrets are: rig.yaml is a file people commit, so the generated code reads the environment and rig never carries the secret.

type ForeignKey

type ForeignKey struct {
	Name           string   `json:"name"`
	Columns        []string `json:"columns"`
	ForeignTable   string   `json:"foreign_table"`
	ForeignColumns []string `json:"foreign_columns"`
	OnDelete       string   `json:"on_delete,omitempty"`
	OnUpdate       string   `json:"on_update,omitempty"`
}

ForeignKey is a full constraint, including composite ones.

type Index

type Index struct {
	Name    string   `json:"name"`
	Columns []string `json:"columns"`
	Unique  bool     `json:"unique,omitempty"`
	Method  string   `json:"method,omitempty"` // btree, gin, gist, hash, brin
	// Partial holds the WHERE predicate of a partial index, empty otherwise.
	Partial string `json:"partial,omitempty"`
}

Index is one index on a table. Column order matters: rules that ask whether a column is "covered by an index leading with it" read the first element.

func (Index) Covers

func (i Index) Covers(name string) bool

Covers reports whether name appears anywhere in the index.

func (Index) LeadsWith

func (i Index) LeadsWith(name string) bool

LeadsWith reports whether the index's first column is name. A partial index does not count: it cannot serve the general lookup the rules care about.

type LinkTable

type LinkTable struct {
	// Table is the join table itself. It is carried here because a relation
	// derived from a join table is otherwise unable to name the thing it
	// travels through, and that name is how configuration refers to it.
	Table string `json:"table"`

	LeftTable   string `json:"left_table"`
	LeftColumn  string `json:"left_column"`
	RightTable  string `json:"right_table"`
	RightColumn string `json:"right_column"`
}

LinkTable describes a pure many-to-many join table.

type Monitoring

type Monitoring struct {
	// Enabled says the generated server serves the page. A document carrying
	// this block always has it set, since a block that is off resolves to no
	// block at all.
	Enabled bool `json:"enabled"`
	// ServiceName is what the page calls this application, taken from the
	// project's own name.
	ServiceName string `json:"service_name"`
	// Addr is where the page listens, as host:port. Always set, because the
	// page has its own listener and rig defaults no part of one.
	Addr string `json:"addr"`
	// BasePath is where the page is mounted on that listener, resolved and
	// absolute.
	BasePath string `json:"base_path"`
	// MaxTraces is how many requests the page lists, newest first.
	MaxTraces int `json:"max_traces"`
	// MaxLogs is how many log lines the page reads, newest first.
	MaxLogs int `json:"max_logs"`
	// PasswordEnv names the variable the page reads its password from.
	PasswordEnv string `json:"password_env"`
	// Password is the literal from rig.yaml, and empty in every project that
	// took the warning.
	Password string `json:"password,omitempty"`
	// Allow is the addresses that may reach the page, as CIDR ranges or single
	// addresses. Empty allows any, and it narrows the password rather than
	// standing in for it.
	Allow []string `json:"allow,omitempty"`
}

Monitoring is the resolved `monitoring:` block: rig's own page over the spans this server wrote and the lines it logged.

A document carries it only when the project asked for the page, and only ever alongside Tracing — the page is a reader over the span file and has nothing to read without it, which the compiler refuses rather than leaves to be discovered as a page that never fills up.

The password is here and the span file is not, which looks inconsistent and is not. Where the spans go is a property of the deployment: the same binary runs on a laptop with no collector and in production with one. Which password guards the page is a property of the deployment too — which is why PasswordEnv is the ordinary way to say it — and Password is the project that decided otherwise, warned about once when rig.yaml is read.

Addr is here on the same terms. It is where the page's own listener binds, so it is as much a deployment fact as the span file is, and $RIG_MONITOR_ADDR overrides it at run time for exactly that reason; what it is doing in the document is being the value that variable defaults to, so that a project which never sets one still has to have made the decision once.

type Notifications

type Notifications struct {
	// Enabled says this project has an inbox. It is what makes server-go write
	// the wiring, and what keeps rig's notification tables in the schema.
	Enabled bool `json:"enabled"`
	// Expose says the inbox is projected as a resource as well, so it gets the
	// filter grammar and a generated client. The hand-written routes exist
	// either way.
	Expose bool `json:"expose,omitempty"`

	// DefaultDigest is what an account with no setting for a channel gets.
	DefaultDigest string `json:"default_digest"`

	// ClaimTTLSeconds is how long a dispatcher's claim is honoured, and the rest
	// of the retry arithmetic beside it. Seconds because a document is JSON and
	// a Go duration in one is either unreadable or has to be parsed by everybody.
	ClaimTTLSeconds int64 `json:"claim_ttl_seconds"`
	// SendTimeoutSeconds bounds one call into a channel, and is necessarily
	// below ClaimTTLSeconds: a send that may outlive its own lease is a send
	// whose row another dispatcher has already taken.
	SendTimeoutSeconds int64 `json:"send_timeout_seconds"`
	MaxAttempts        int   `json:"max_attempts"`
	BackoffBaseSeconds int64 `json:"backoff_base_seconds"`
	// BackoffCapSeconds is the longest one wait may be. It is what makes a long
	// attempt count a schedule rather than a sleep: doubling from a minute
	// fourteen times is five days, and capped at an hour it is eight.
	BackoffCapSeconds int64 `json:"backoff_cap_seconds"`
	// RetentionSeconds is how long a read and deleted inbox line is kept.
	RetentionSeconds int64 `json:"retention_seconds"`
}

Notifications is the resolved notifications block: whether this project has an inbox, and the numbers the engine that fills it runs on.

type Object

type Object struct {
	Name        string  `json:"name"`
	Description string  `json:"description,omitempty"`
	Fields      []Field `json:"fields"`
	Origin      Origin  `json:"origin"`
}

Object is a named shape referenced by fields.

type OpenAPI added in v0.3.0

type OpenAPI struct {
	// JSONPath is where the JSON rendering is served, for example
	// "/api/v1/openapi.json".
	JSONPath string `json:"json_path"`
	// YAMLPath is where the YAML rendering is served.
	YAMLPath string `json:"yaml_path"`

	// Import is the package the document is embedded in: the project's module
	// path joined to the openapi generator's output directory, taken from the
	// module root rather than from the directory holding rig.yaml. Those are the
	// same directory in a flat project and are not in a two-half one, and the
	// nearest go.mod is what says which.
	//
	// Derived rather than configured, because there is exactly one right answer
	// and everything it needs is already written down. A generator reads it
	// instead of asking the project, which is what keeps the package that
	// declares the embed and the router that imports it from being told two
	// different things.
	Import string `json:"import"`

	// Package is the name that package declares, which is [Import]'s last
	// segment.
	//
	// Stored rather than derived twice: the generator that writes the package
	// clause and the generator that qualifies a reference with it have to agree,
	// and a directory name Go could not use as a package name is refused when
	// the configuration is read rather than discovered by a build.
	Package string `json:"package"`
}

OpenAPI is where the generated document is reachable from.

The paths are computed once at freeze, against the same base path every route is, and checked against every other route in the same namespace — so the mux cannot be taken down at startup by a resource whose path segment happens to be `openapi.json`.

What is not here is the document itself. The bytes are the application's to supply: go:embed cannot reach out of the package it is written in, and the document is written to the openapi generator's out_dir rather than beside the router. So a project embeds the file and hands it to the generated router, which is also what makes the routes opt-in twice over — the key makes the field, and the field makes the routes.

type OrderTerm

type OrderTerm struct {
	Column string `json:"column"`
	Desc   bool   `json:"desc,omitempty"`
}

OrderTerm is one clause of a default ordering.

type Origin

type Origin string

Origin records why an object exists, so a generator can treat hand-declared shapes differently from ones rig synthesized.

const (
	// OriginProjected is an object derived from a table's readable columns.
	OriginProjected Origin = "Projected"
	// OriginFilter is one of the generated filter shapes for Search.
	OriginFilter Origin = "Filter"
	// OriginBuiltin is a framework shape such as Error or Pagination.
	OriginBuiltin Origin = "Builtin"
	// OriginConfig is an object declared in a table's YAML.
	OriginConfig Origin = "Config"
)
type ParentLink struct {
	// Name is the accessor, matching the BelongsTo relation: HomeTeam out of
	// home_team_id. It is what the two hook fields are named after.
	Name string `json:"name"`
	// Parent is the resource name, Table the physical table behind it.
	Parent string `json:"parent"`
	Table  string `json:"table"`
	// Column is the foreign key on this resource's own table.
	Column string `json:"column"`
}

ParentLink is one foreign key from a resource to another resource.

Only a foreign key to a table rig generates a service for is here. A column pointing at rig_file or at an audit actor has no service to declare a hook in, and rig should not pretend it can call one.

type Permission

type Permission struct {
	Key         string `json:"key"`
	Name        string `json:"name"`
	Description string `json:"description,omitempty"`

	// Resource is the resource it was derived from, so a rename can be reported
	// against something more useful than a string.
	Resource string `json:"resource,omitempty"`
	// Action is read, write, delete, or the name of a custom endpoint.
	Action string `json:"action,omitempty"`
}

Permission is one thing a caller may be allowed to do.

The key is what code checks and what a role grants. The name and description are for the interface where somebody hands it out — they come from the resource, so nobody writes them twice.

type PgEnum

type PgEnum struct {
	Name    string        `json:"name"`
	Comment string        `json:"comment,omitempty"`
	Values  []PgEnumValue `json:"values"`
}

PgEnum is a Postgres enum type. Values are in enumsortorder, which is the order clients will see them in documentation and generated code.

func (PgEnum) HasValue

func (e PgEnum) HasValue(v string) bool

HasValue reports whether the enum contains the given label.

type PgEnumValue

type PgEnumValue struct {
	Value string `json:"value"`
	// Description comes from the table configuration; Postgres cannot comment
	// on individual enum labels.
	Description string `json:"description,omitempty"`
}

PgEnumValue is one label of a PgEnum.

type Presence

type Presence struct {
	// Enabled says this project tracks presence. It is what makes server-go
	// write the wiring, and what keeps rig_presence in the schema.
	Enabled bool `json:"enabled"`
	// Expose says presence is projected as a read-only resource as well, so it
	// gets the filter grammar and a generated client. The hand-written routes and
	// the live shape exist either way.
	Expose bool `json:"expose,omitempty"`

	// TTLSeconds is how long a session stays present after its last heartbeat.
	// Seconds because a document is JSON and a Go duration in one is either a
	// number nobody can read or a string every consumer has to parse.
	TTLSeconds int64 `json:"ttl_seconds"`
	// HeartbeatSeconds is how often a browser should confirm it is still there.
	// It is carried so the heartbeat route can answer with it, which is what puts
	// the interval on the server's side of the wire.
	HeartbeatSeconds int64 `json:"heartbeat_seconds"`
	// SweepSeconds is how often the in-process sweeper runs. Negative leaves the
	// generated task as the only sweeper.
	SweepSeconds int64 `json:"sweep_seconds"`
	// GraceSeconds is how long past the TTL a row survives, which is what keeps
	// the reader's arithmetic and the sweeper's from disagreeing.
	GraceSeconds int64 `json:"grace_seconds"`
}

Presence is the resolved presence block: whether this project tracks who is here, and the four numbers that decide what that costs.

Two of them are answered to the browser on every heartbeat, which is why they are in the revision hash and the other two are not — see the presence milestone in NEXT.md, and internal/revision for the clearing.

type Publication added in v0.3.0

type Publication struct {
	Name string `json:"name"`
	// AllTables is a publication declared FOR ALL TABLES, which carries every
	// table in the database including ones created after it.
	AllTables bool `json:"all_tables,omitempty"`
	// Owned says this publication belongs to the role rig connected as, which is
	// the role that runs migrations.
	//
	// It is the one fact that tells a publication a migration wrote from one the
	// sync service created for itself, and the two are worth telling apart even
	// when they share a name. The sync service makes
	// `electric_publication_default` on first connection and owns it; a migration
	// that creates the same publication first owns it instead, and that is the
	// arrangement that makes live sync work for a role owning no tables. Only the
	// owner distinguishes them.
	Owned bool `json:"owned,omitempty"`
}

Publication is one Postgres publication.

type Relation

type Relation struct {
	Name   string       `json:"name"`
	Kind   RelationKind `json:"kind"`
	Target string       `json:"target"` // resource name

	LocalColumn   string     `json:"local_column,omitempty"`
	ForeignTable  string     `json:"foreign_table,omitempty"`
	ForeignColumn string     `json:"foreign_column,omitempty"`
	LinkTable     *LinkTable `json:"link_table,omitempty"`

	// Embed includes the related rows in read responses.
	Embed bool `json:"embed,omitempty"`
}

Relation is a link from one resource to another, derived from a foreign key or from a join table.

type RelationKind

type RelationKind string

RelationKind is the cardinality of a Relation.

const (
	RelationBelongsTo  RelationKind = "BelongsTo"
	RelationHasMany    RelationKind = "HasMany"
	RelationManyToMany RelationKind = "ManyToMany"
)

The cardinalities a Relation can have.

type ReplicaIdentity added in v0.3.0

type ReplicaIdentity string

ReplicaIdentity is a table's pg_class.relreplident, spelled out.

Four values rather than a bool, because the three that are not ReplicaIdentityFull fail live sync for three different reasons and a rule worth reading says which: nothing carries no old row at all, index means somebody chose a covering index on purpose, and default — the Postgres default — carries the primary key and nothing else.

const (
	// ReplicaIdentityDefault carries the primary key of the old row, which is
	// what a table has unless somebody changed it.
	ReplicaIdentityDefault ReplicaIdentity = "default"
	// ReplicaIdentityFull carries the whole old row, and is what live sync needs:
	// a subscriber matching an update against the row it replaces has only the
	// old values to match on.
	ReplicaIdentityFull ReplicaIdentity = "full"
	// ReplicaIdentityNothing carries nothing, so an update or a delete reaches a
	// subscriber with no way to say which row it was.
	ReplicaIdentityNothing ReplicaIdentity = "nothing"
	// ReplicaIdentityIndex carries the columns of a nominated unique index.
	ReplicaIdentityIndex ReplicaIdentity = "index"
)

The replica identities Postgres records.

type Replication added in v0.3.0

type Replication struct {
	// WALLevel is the server's wal_level. Logical replication needs "logical",
	// and it cannot be turned on after Postgres has started.
	WALLevel string `json:"wal_level,omitempty"`
	// Publications is every publication in the database, sorted by name,
	// whether or not it carries anything.
	Publications []Publication `json:"publications,omitempty"`
}

Replication is the server-wide half of what live sync needs.

type Resource

type Resource struct {
	Name        string `json:"name"`         // Lesson
	Plural      string `json:"plural"`       // Lessons
	PathSegment string `json:"path_segment"` // lessons
	Description string `json:"description,omitempty"`

	// Unexposed keeps a table out of the API while keeping its model and
	// repository.
	//
	// The authentication foundation is why this exists. Sessions and API keys
	// need generated persistence and must not have generated CRUD endpoints:
	// nothing good comes of a REST interface for the token table. Saying so on
	// the resource rather than deleting it is what lets the data layer stay
	// generated while the API layer stays silent.
	//
	// It is negative so that the zero value is the ordinary case.
	//
	// It is not on its own a reason to generate nothing:
	// [Resource.Unreachable] is that, and it asks about Foundation too.
	Unexposed bool `json:"unexposed,omitempty"`

	// Foundation says rig created this table: it is one of the `rig_` tables a
	// module's migration set carries, and rig ships the configuration that
	// decides how it projects.
	//
	// What reads it is the stub writers. A stub is a file rig creates once and
	// then never touches, for a project to put its own rules in — and for a
	// table the project did not create, has no migration for and cannot change,
	// the rules are usually that there are none. Three of the four such stubs in
	// this repository's own examples were byte-identical no-ops, and two of those
	// were not imported by anything.
	//
	// It also decides, with Unexposed, whether any Go is written for the table
	// at all — see [Resource.Unreachable]. That is a correctness question, and
	// the answer is safe to lean on for the reason the flag is computed the way
	// it is: it comes from the project's migrations or its configuration rather
	// than from the `rig_` prefix, so a project with `auth.own` — which has
	// taken the schema over — has none of these, and gets its stubs and its
	// generated model both.
	Foundation bool `json:"foundation,omitempty"`

	Operations []string `json:"operations"`
	// Public names the operations that answer without a credential, generated
	// and custom alike. An endpoint carries the resolved flag; this is the
	// declaration the endpoints are built from.
	Public    []string        `json:"public,omitempty"`
	Fields    []ResourceField `json:"fields"`
	Endpoints []Endpoint      `json:"endpoints"`

	// Files are this resource's file columns, in the order they appear on the
	// table. Each one yields an upload, a download and a delete endpoint, two
	// permission keys, and a part on the multipart form the create also accepts.
	//
	// Resolved once, where the schema is still in scope: recognizing one means
	// reading the table's foreign keys, and by the time a generator holds a
	// resource the constraints are gone. A generator that re-derived it from a
	// field name would be re-deriving the convention the compiler exists to have
	// already settled.
	Files []FileColumn `json:"files,omitempty"`

	// Notifiable says this table is joined to rig_notification by a link table,
	// so notifications can be about its rows.
	//
	// It is derived from the schema rather than declared: any link table one
	// side of which is rig_notification makes the other side notifiable, so the
	// recommended name — blog_post_notification — is a recommendation and
	// nothing depends on it. There is nothing for a name to say here that the
	// foreign key does not.
	//
	// A notifiable resource's rules interface grows two required methods: when
	// notifications about a row are due, and — at the moment of sending — who
	// should hear about it.
	Notifiable bool `json:"notifiable,omitempty"`

	// Cached says this resource's Get is held in memory between requests, keyed
	// by the row and the scope the read was made under, and withdrawn by a
	// Postgres NOTIFY published on the transaction of every write the generated
	// repository makes to the row.
	//
	// Declared per table rather than derived, because it is a promise about the
	// application as much as a fact about the schema: rig can only publish the
	// withdrawal from writes it makes, so a project that writes this table
	// through Store.Pool or with raw SQL from inside a dbhook is a project where
	// this serves a stale row until the entry expires. Nothing in the schema says
	// whether that is true, so nothing but a person can turn this on.
	//
	// It needs [API.Cache], which is what owns the channel the withdrawals
	// travel on.
	Cached bool `json:"cached,omitempty"`

	// Parents are the resources this one points at, one per foreign key, in the
	// order the columns appear on the table. Each becomes a pair of hook fields
	// the service layer may fill in.
	Parents []ParentLink `json:"parents,omitempty"`

	// Children are the resources that point at this one, in the order they are
	// told about a delete.
	//
	// The order is derived — referencing tables before referenced ones — and
	// resolved here rather than in a generator, because it is a fact about the
	// whole schema and a generator holds one resource at a time.
	Children []ChildLink `json:"children,omitempty"`

	// Storage is nil for a virtual resource with no table behind it.
	Storage *ResourceStorage `json:"storage,omitempty"`
	// Electric is set when the resource exposes a live-sync shape endpoint.
	Electric *ElectricEndpoint `json:"electric,omitempty"`
}

Resource is one table exposed as an API resource.

func (*Resource) Endpoint

func (r *Resource) Endpoint(name string) *Endpoint

Endpoint returns the named endpoint, or nil.

func (*Resource) Field

func (r *Resource) Field(name string) *ResourceField

Field returns the named field, or nil.

func (*Resource) HasEndpoint

func (r *Resource) HasEndpoint(name string) bool

HasEndpoint reports whether the resource already declares the named endpoint. Expansion consults this before synthesizing a CRUD endpoint, so a hand-written endpoint always shadows the generated one of the same name.

func (*Resource) Supports

func (r *Resource) Supports(op string) bool

Supports reports whether the resource has the given operation.

func (*Resource) Unreachable

func (r *Resource) Unreachable() bool

Unreachable reports whether this is one of rig's own tables that the project does not expose: no endpoints, no service layer, and no rows the project's own Go was ever going to touch.

It is the bar for generating anything at all, and it is narrower than Resource.Unexposed on purpose. An unexposed table of the project's own keeps its model and its repository, because a service layer reaching a hidden table is the shape that flag exists for. A table rig created and this project did not ask for is not that shape: the module that owns it does its own SQL, so a generated repository over it is a second way into rows nothing here writes.

Such a table is still projected rather than ignored, and the difference matters. Dropping rig_notification would leave every project's `<subject>_notification` silently unclassified as a link table, and nothing would say why. So it stays a resource, carries its relations, and simply has no Go written for it.

type ResourceField

type ResourceField struct {
	Field `json:",inline"`
	// Operations is some subset of Create, Read, Update.
	Operations []string `json:"operations"`
}

ResourceField is a field of a resource, with the operations it takes part in.

func (ResourceField) In

func (f ResourceField) In(op string) bool

In reports whether the field participates in the given field operation.

type ResourceStorage

type ResourceStorage struct {
	Table      string   `json:"table"`
	PrimaryKey []string `json:"primary_key"`

	// Tenant is the column every generated query is scoped by.
	Tenant *ColumnRef `json:"tenant,omitempty"`

	// Owner is the column a read is narrowed by when the table asked for it —
	// the account that created the row.
	//
	// Set only by `access: { scope: own }`, because it changes what a request
	// answers with and that is not something to infer from a column being
	// present. Plenty of tables record who created a row without meaning that
	// nobody else may read it.
	Owner *ColumnRef `json:"owner,omitempty"`

	Audit      *AuditColumns `json:"audit,omitempty"`
	SoftDelete *SoftDelete   `json:"soft_delete,omitempty"`
	Snapshot   *Snapshot     `json:"snapshot,omitempty"`

	DefaultOrder []OrderTerm `json:"default_order,omitempty"`
	Relations    []Relation  `json:"relations,omitempty"`

	Sortable   []string `json:"sortable,omitempty"`
	Filterable []string `json:"filterable,omitempty"`
}

ResourceStorage is everything the persistence layer needs that the API layer does not care about.

func (*ResourceStorage) IsOwnerScoped

func (s *ResourceStorage) IsOwnerScoped() bool

IsOwnerScoped reports whether a read of this resource defaults to the rows the caller created.

func (*ResourceStorage) IsSnapshotable

func (s *ResourceStorage) IsSnapshotable() bool

IsSnapshotable reports whether updates to this resource keep prior versions.

func (*ResourceStorage) IsSoftDeletable

func (s *ResourceStorage) IsSoftDeletable() bool

IsSoftDeletable reports whether rows of this resource are retired rather than removed.

type ScanStrategy

type ScanStrategy string

ScanStrategy tells the persistence generator how to move a value between Go and pgx. It is derived from the SQL type once, in one place, rather than re-derived by every generator that touches the column.

const (
	ScanDirect      ScanStrategy = "Direct"
	ScanUUID        ScanStrategy = "UUID"
	ScanEnumText    ScanStrategy = "EnumText"
	ScanTimestamptz ScanStrategy = "Timestamptz"
	ScanJSONB       ScanStrategy = "JSONB"
	ScanArray       ScanStrategy = "Array"
	ScanNumeric     ScanStrategy = "Numeric"
)

The strategies a column can be read and written by.

type Schema

type Schema struct {
	// Name is the Postgres schema, almost always "public".
	Name string `json:"name"`

	Tables []Table  `json:"tables"`
	Enums  []PgEnum `json:"enums"`

	// Replication is what the server says about logical replication.
	//
	// Nil means nobody looked: a schema dump written before rig read these
	// facts, or a hand-written fixture. A live introspection always sets it,
	// even when the database has no publication at all, which is what lets a
	// rule tell "looked and found none" from "never looked".
	Replication *Replication `json:"replication,omitempty"`
}

Schema is the physical layer: what Postgres actually contains, normalized.

Tables and enums are sorted by name, columns by ordinal, and every other slice by a stable key, so two runs over the same database produce identical bytes.

type Server added in v0.3.0

type Server struct {
	// Name identifies the deployment, and becomes an identifier in every
	// generated SDK: production is Go's ServerProduction and TypeScript's
	// servers.production. Empty only for the one nameless entry a project still
	// configuring the deprecated per-generator option produces.
	Name string `json:"name,omitempty"`

	// URL is where that deployment answers. The API's own base path is appended
	// to it, so it is not repeated here.
	URL string `json:"url"`

	// Description is one line about what the deployment is for. It reaches the
	// OpenAPI document and the doc comment beside the generated constant.
	Description string `json:"description,omitempty"`

	// Default marks the deployment a caller who names none receives, and exactly
	// one entry in a document carries it. The project resolves "nobody claimed
	// it, so the first one" before this is written, so three generators do not
	// each implement the same rule.
	Default bool `json:"default,omitempty"`
}

Server is one deployment the API is served on.

Named, because a client says which one it wants: production is a constant a caller writes, not a string they copy out of a wiki.

type Snapshot

type Snapshot struct {
	VersionType *ColumnRef `json:"version_type"`
	FromID      *ColumnRef `json:"from_id"`
	FromAt      *ColumnRef `json:"from_at"`
	// IgnoreColumns are not replayed when a snapshot is restored: the live
	// row's values win. They are still copied into the snapshot itself.
	IgnoreColumns []string `json:"ignore_columns,omitempty"`
}

Snapshot marks a table that keeps immutable copies of prior versions.

type SoftDelete

type SoftDelete struct {
	Column *ColumnRef `json:"column"`
	Actor  *ColumnRef `json:"actor,omitempty"`
	// ActorKey is the API key the deletion came through, when the table has a
	// column for it. It is stamped beside Actor rather than instead of it.
	ActorKey *ColumnRef `json:"actor_key,omitempty"`
	// RestoreWindowDays bounds how long a deleted row stays restorable.
	RestoreWindowDays int `json:"restore_window_days"`
}

SoftDelete marks a table whose rows are retired by stamping a timestamp rather than being removed.

type Table

type Table struct {
	Name    string    `json:"name"`
	Comment string    `json:"comment,omitempty"`
	Kind    TableKind `json:"kind"`

	Columns    []Column   `json:"columns"`
	PrimaryKey []string   `json:"primary_key,omitempty"`
	Uniques    [][]string `json:"uniques,omitempty"`

	Indexes     []Index      `json:"indexes,omitempty"`
	ForeignKeys []ForeignKey `json:"foreign_keys,omitempty"`
	Checks      []Check      `json:"checks,omitempty"`

	// Publications names every publication that carries this table, sorted,
	// including one declared FOR ALL TABLES. Empty on a table with live sync
	// means the stream would never emit a row.
	Publications []string `json:"publications,omitempty"`
	// Unlogged is a table whose relpersistence is 'u'. It writes no WAL, so
	// logical replication cannot see it at all.
	Unlogged bool `json:"unlogged,omitempty"`
	// ReplicaIdentity is what an UPDATE or a DELETE on this table puts in the
	// WAL for the row as it was. Live sync needs [ReplicaIdentityFull].
	//
	// Empty means nobody looked — a schema dump written before rig read this, or
	// a hand-written fixture — which is what lets the rule over it be an error
	// rather than a guess. Same convention as [Schema.Replication], one level
	// down.
	ReplicaIdentity ReplicaIdentity `json:"replica_identity,omitempty"`

	// LinkTable is set when this table is a pure many-to-many join: its primary
	// key is exactly two foreign-key columns. Such tables become relations on
	// the resources they join, never resources of their own.
	LinkTable *LinkTable `json:"link_table,omitempty"`
}

Table is one relation in the schema.

func (*Table) Column

func (t *Table) Column(name string) *Column

Column returns the named column, or nil. The lookup is linear; for repeated lookups across a whole document use Document.Column.

func (*Table) HasColumn

func (t *Table) HasColumn(name string) bool

HasColumn reports whether the table has the named column.

func (*Table) IsLinkTable

func (t *Table) IsLinkTable() bool

IsLinkTable reports whether this table is a pure many-to-many join.

func (*Table) SinglePrimaryKey

func (t *Table) SinglePrimaryKey() (string, bool)

SinglePrimaryKey returns the primary key column name when the table has exactly one, and false otherwise.

type TableKind

type TableKind string

TableKind distinguishes real tables from the read-only relations that share their introspection shape.

const (
	TableKindBase             TableKind = "Base"
	TableKindView             TableKind = "View"
	TableKindMaterializedView TableKind = "MaterializedView"
)

The relation kinds rig introspects.

type Throttle

type Throttle struct {
	Enabled bool `json:"enabled"`

	// APIKey, Account and Tenant apply to a caller who said who they were; IP
	// applies only to one who did not. A zero limit is one nobody configured,
	// and the runtime fills it from its own defaults.
	APIKey  ThrottleLimit `json:"api_key"`
	Account ThrottleLimit `json:"account"`
	Tenant  ThrottleLimit `json:"tenant"`
	IP      ThrottleLimit `json:"ip"`

	// IntervalSeconds is how long a replica may count locally before publishing.
	// It is the accuracy of the limit stated as time, so it belongs in the
	// document beside the numbers it qualifies.
	//
	// Fractional, unlike every other duration here: sub-second is a reasonable
	// thing to ask for from something that runs on every request, where a
	// sub-second *window* is not.
	IntervalSeconds float64 `json:"interval_seconds,omitempty"`

	// Routes are extra limits on particular patterns, on top of the per-caller
	// ones.
	Routes []ThrottleRoute `json:"routes,omitempty"`

	// Exempt are patterns nothing applies to.
	Exempt []string `json:"exempt,omitempty"`
}

Throttle is the resolved throttle block: how many calls one caller may make.

Fair-use limiting rather than a defence against a flood. What reaches this is a request that already cost a connection, a handshake and a goroutine, so the arithmetic here bounds abuse and cost rather than volume — see docs/api.md.

type ThrottleLimit

type ThrottleLimit struct {
	Max           int   `json:"max,omitempty"`
	WindowSeconds int64 `json:"window_seconds,omitempty"`
}

ThrottleLimit is how many, and over how long.

type ThrottleRoute

type ThrottleRoute struct {
	// Pattern is the route as the router registers it, matched against what
	// net/http reports rather than against a path.
	Pattern       string `json:"pattern"`
	Max           int    `json:"max"`
	WindowSeconds int64  `json:"window_seconds"`
}

ThrottleRoute is one route pattern's own limit.

type Tracing

type Tracing struct {
	// Enabled says the generated server, repositories and client open spans.
	// A document carrying this block always has it set, since a block that is
	// off resolves to no block at all.
	Enabled bool `json:"enabled"`
	// ServiceName is what this application is called in a collector, taken from
	// the project's own name.
	ServiceName string `json:"service_name"`
}

Tracing is the resolved `tracing:` block: whether the generated code opens spans, and what this service is called when it does.

The service name is here rather than left to the application because the application already said it once, in `project.name`. Nothing about where the spans go is here at all: that is a property of the deployment, not of the generated code, and the same binary runs where there is a collector and where there is not.

type TypeKind

type TypeKind string

TypeKind is the resolution of Field.Type, computed once when the document is frozen so no generator has to resolve names itself.

const (
	TypeKindPrimitive TypeKind = "Primitive"
	TypeKindEnum      TypeKind = "Enum"
	TypeKindObject    TypeKind = "Object"
	TypeKindResource  TypeKind = "Resource"
)

The kinds a Field.Type resolves to.

type Web added in v0.5.1

type Web struct {
	// Origin is where the front end is served, as scheme://host[:port] with
	// nothing after the host. Empty when OriginEnv carries it instead.
	Origin string `json:"origin,omitempty"`

	// OriginEnv is the environment variable the origin comes from, for the
	// deployment that has a different front end per environment. Empty when the
	// origin is written in the file.
	//
	// Both may be set: the file's value is the default and the variable wins,
	// which is how [Auth.OAuth]'s base URL already behaves.
	OriginEnv string `json:"originEnv,omitempty"`

	// CallbackPath is the path on Origin that receives a finished sign-in.
	// Always set; the project fills in the default.
	CallbackPath string `json:"callbackPath"`

	// CORS is what a preflight from that front end is answered with.
	CORS WebCORS `json:"cors"`
}

Web is a browser front end served somewhere other than this API.

It exists because the two are separate deployments in any project worth the name — the API on api.example.com, the application on app.example.com — and three things follow from that one fact: which origin a finished sign-in redirects to, where a relative returnTo resolves, and which origins a preflight is answered for. A project naming the answer once is the point.

type WebCORS added in v0.5.1

type WebCORS struct {
	// AllowedOrigins are origins beyond [Web.Origin] that may call this API — an
	// administrative front end, most often. Each is scheme://host[:port], or one
	// leading *. label for a wildcard.
	AllowedOrigins []string `json:"allowedOrigins,omitempty"`

	// AllowedOriginsEnv is the environment variable that replaces the list
	// above, as a comma-separated string. Always set; the project fills in the
	// default.
	AllowedOriginsEnv string `json:"allowedOriginsEnv"`

	// MaxAgeSeconds is how long a browser may cache a preflight. Seconds rather
	// than a duration because that is what the header carries and what every
	// generator would otherwise convert.
	MaxAgeSeconds int64 `json:"maxAgeSeconds"`
}

WebCORS is the cross-origin policy's project-specific half.

Only the origins and the lifetime are here. What a preflight may ask for — the methods, the request headers, the exposed response headers — is derivable from the rest of this document and is worked out by the generator, because getting it out of a project's own list is how a header the sync cursor needs ends up missing and the subscription stops after one response.

Jump to

Keyboard shortcuts

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