Documentation
¶
Overview ¶
Package schema parses schema.lidza, the single source of truth for an application's data shapes, and generates Go structs with validation, the Postgres DDL and migrations, Rust serde structs and TypeScript types from it.
The file format:
enum Status { draft published }
model Post @table("posts") {
id uuid @id @default(uuid())
title string @min(1) @max(200)
body text?
status Status @default(draft)
authorId uuid @ref(User)
topicId int? @ref(Topic, setnull)
tags string[]
price decimal(12, 2)? @min(0)
createdAt time @default(now())
@@index(status, createdAt)
}
type CreatePost {
title string @min(1) @max(200)
body text?
}
A model becomes a table and a struct; a type becomes a struct only. "?" marks an optional (nullable) field, "[]" an array. Scalar types: string, text, int, bigint, float, decimal(p, s), bool, time, date, uuid, json, bytes. Field attributes: @id, @unique, @index, @default(v) (uuid(), now(), autoincrement() on an int or bigint, or a literal), @ref(Model), @min(n), @max(n), @email, @url, @pattern("re"). Block attributes: @@index(a, b), @@unique(a, b); model attributes @table("name"), @public and @shared. A @ref field has the type of the referenced model's id: uuid, int, bigint or string.
A model is owned when a field ties each row to a user (Schema.Owner): `lidza gen resource` scopes its queries to the signed-in user and `lidza check` (L018) flags a query that does not. @public marks a model whose rows anyone may read and write: not owned, and its generated routes are not behind sign-in. @shared marks one whose rows every signed-in user shares: routes behind sign-in, queries not scoped, even with an owner-like field (a team's projects with a createdBy user).
Index ¶
- Constants
- Variables
- func GenerateGo(s *Schema) string
- func GenerateRust(s *Schema) string
- func GenerateSQL(s *Schema) string
- func GenerateTS(s *Schema) string
- func GoName(name string) string
- func Gofmt(src string) string
- func JSONSchema(s *Schema) map[string]any
- func MergeLocks(base, ours, theirs []byte) (merged []byte, conflicts []string, err error)
- func Migrations(root string) []string
- func NextSeq(dir string) int
- func NextStamp(dir string, now time.Time) string
- func SQLCName(name string) string
- func SQLCNames(s *Schema) (initialisms []string, rename map[string]string)
- func SQLCRenames(names []string) map[string]string
- func SQLCRowType(table string) string
- func SaveLock(root string, s *Schema) error
- type Enum
- type Field
- type Index
- type Migration
- type Model
- type Rename
- type Result
- type Schema
Constants ¶
const ( MigrationsDir = "db/migrations" LockFile = "db/schema.lock.json" )
Migration files, relative to the project root. The lock file is the schema as of the last generated migration and is committed; the next `lidza gen` diffs the current schema against it.
const DecimalPattern = `^[+-]?[0-9]+(\.[0-9]+)?$`
DecimalPattern is the JSON Schema pattern of a decimal field: plain notation, an optional sign and fraction.
const FileName = "schema.lidza"
FileName is the schema source, at the project root.
const GoFile = "schema/schema.go"
GoFile is the generated Go source, relative to the project root.
const GoPackage = "schema"
GoPackage is the package name of the generated Go file.
const LocalTimePattern = `^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2})?$`
LocalTimePattern is the JSON Schema pattern of a localtime field: what an HTML datetime-local input sends, with optional seconds.
const NoTransaction = "-- lidza:no-transaction"
NoTransaction is the first line of a migration the db pack runs statement by statement outside a transaction (db.NoTransaction).
const RustFile = "src/schema.rs"
RustFile is the generated Rust module, relative to the crate directory. The crate needs `serde = { version = "1", features = ["derive"] }` and `mod schema;` in lib.rs.
const SQLFile = "db/schema.sql"
SQLFile is the full DDL of the current schema, relative to the project root. It is regenerated on every `lidza gen`; sqlc reads it.
const StampLayout = "20060102150405"
StampLayout names a migration by the UTC time it was written: 20261006140512_create_post. Two branches that each add one no longer pick the same number, and the existing numbered migrations (0001_...) sort before every timestamped one.
Variables ¶
var Clock = time.Now
Clock is the time new migrations are named by; tests set it.
var Initialisms = []string{"id", "url", "api", "http", "json", "uuid", "sql", "ip", "html"}
Initialisms are the name parts Go spells in capitals: authorId is AuthorID, url is URL. Their plurals keep a lowercase s: tagIds is TagIDs, imageUrls is ImageURLs. The schema package and sqlc's db/queries/gen follow the same rule: `lidza gen` writes this list and the plurals into sqlc.yaml (SQLCNames). Model and enum names are the ones schema.lidza gives; sqlc derives its struct and enum names from the table and the Postgres type (SQLCName).
var Scalars = map[string]bool{ "string": true, "text": true, "int": true, "bigint": true, "float": true, "decimal": true, "bool": true, "time": true, "date": true, "uuid": true, "json": true, "bytes": true, "localtime": true, }
Scalars are the built-in types.
var UserModels = []string{"User", "AuthUser"}
UserModels are the model names Owner treats as the app's users: an app's own User and the auth pack's AuthUser (auth.Mount's accounts).
var WorkspaceModels = []string{"Workspace", "AuthWorkspace"}
WorkspaceModels are the model names Workspace treats as the app's workspaces: the auth pack's AuthWorkspace (auth.Teams) and an app's own Workspace.
Functions ¶
func GenerateGo ¶
GenerateGo renders the Go package: enums as string types with constants, models and types as structs with json and db tags, and a Validate method per struct that applies the field rules.
func GenerateRust ¶
GenerateRust renders serde structs and enums. Time, date, uuid and decimal fields are strings (a decimal as "12.50", exact), so the crate needs no chrono, uuid or decimal dependency; packs that want typed values convert at the boundary. Bytes fields are Vec<u8> carried as base64 (the JSON form Go uses), through the b64 helpers the file defines when it needs them (crate dependency: base64).
func GenerateSQL ¶
GenerateSQL renders the Postgres DDL for every enum and model.
func GenerateTS ¶
GenerateTS renders the TypeScript types: enums as string literal unions with a values array, models and types as interfaces. Optional fields are `T | null` because the Go side encodes a nil pointer as null. A decimal is a string ("12.50"), exact as the server sent it.
func GoName ¶
GoName is the exported Go name of a schema name, camelCase or snake_case: authorId -> AuthorID, image_urls -> ImageURLs.
func Gofmt ¶
Gofmt formats generated Go source. On a syntax error the source is returned as is, so the compiler reports it.
func JSONSchema ¶
JSONSchema renders the JSON Schema (2020-12) definitions for every enum, model and type, keyed by name, for OpenAPI's components.schemas.
func MergeLocks ¶
MergeLocks merges two branches' lock files with their common ancestor, model by model and enum by enum: an entry one branch changed (or added, or removed) and the other left as it was takes the change; the same change on both is taken once. Line numbers are ignored: an edit higher up in schema.lidza moves them in every entry below it. Conflicts name the entries both branches changed differently; the result then keeps ours for them, and the developer merges schema.lidza and runs lidza gen.
func Migrations ¶
Migrations lists the migration names in root, in order.
func NextSeq ¶
NextSeq returns the next migration number from the files in dir: the numbering before migrations were named by time.
func NextStamp ¶
NextStamp is the name prefix of a new migration in dir: now in UTC, moved past the newest migration there (a clock behind another machine's must not run a migration before one it builds on) and past any name taken.
func SQLCName ¶
SQLCName is the Go name sqlc gives a Postgres name (a column, a table in the singular, an enum type, or an enum type and value joined by _) once sqlc.yaml carries SQLCNames: api_key is APIKey, tag_ids is TagIDs, status_draft is StatusDraft.
func SQLCNames ¶
SQLCNames returns what sqlc.yaml needs so that db/queries/gen follows the naming of the schema package: the initialisms, and a rename for each name sqlc cannot spell from them alone (a plural: tag_ids is TagIDs). A column's name is then its field's name in schema/.
func SQLCRenames ¶
SQLCRenames returns, for snake_case names sqlc turns into Go names outside the schema (a query's sqlc.arg('ids') or @ids parameter), the ones its initialisms alone spell differently from SQLCName.
func SQLCRowType ¶
SQLCRowType is the Go type sqlc generates for a table's row: a model with @table("store_products") reads rows of queries.StoreProduct.
Types ¶
type Field ¶
type Field struct {
Name string
// Type is a scalar name, an enum name or (for types) another type name.
Type string
Optional bool
Array bool
ID bool
Unique bool
Index bool
// Default is the literal as written: 42, true, "x", draft, uuid(),
// now(), autoincrement().
Default string
// Ref is the model the field references (the field has the type of
// that model's id); OnDelete is what happens
// to the row when the referenced one is deleted: "cascade" (delete
// it), "setnull" (clear the field; it must be optional) or "" (the
// delete fails while the row exists).
Ref string
OnDelete string
Min *float64
Max *float64
// MinText and MaxText are @min and @max as written, for a decimal
// field, whose rules compare exactly.
MinText string `json:",omitempty"`
MaxText string `json:",omitempty"`
// Precision and Scale are the arguments of decimal(p, s).
Precision int `json:",omitempty"`
Scale int `json:",omitempty"`
Email bool
// Search is @search: the field is part of the model's full-text
// search, with SearchWeight (A to D; by position when empty).
Search bool `json:",omitempty"`
SearchWeight string `json:",omitempty"`
URL bool
// Timezone requires an IANA zone name ("Europe/Paris").
Timezone bool
Pattern string
Line int
}
Field is one line of a block.
func (*Field) Autoincrement ¶
Autoincrement reports whether the field is an identity column, @default(autoincrement()): the database numbers the rows and inserts leave it out.
type Migration ¶
type Migration struct {
// Name is "0003_add_post"; the files are Name+".up.sql" and ".down.sql".
Name string
Up []string
Down []string
// After holds what would lock a table that already has rows if it
// ran in Up: indexes built CONCURRENTLY, constraints validated. It
// becomes a second migration, marked NoTransaction, named after Up's
// with the next stamp; AfterDown reverts it.
After []string
AfterDown []string
}
Migration is one generated pair of up and down scripts, and the statements that must run outside a transaction after it.
func Diff ¶
Diff computes the migration from prev (nil for a fresh project) to cur. It returns nil when nothing changed. Handled: enums created, dropped or given new values; tables created or dropped; columns added, dropped, or changed in type, nullability or default; indexes created or dropped. A dropped or narrowed column loses data; the statement is generated and marked with a comment so it is reviewed before it runs.
func (*Migration) AfterFiles ¶ added in v0.1.101
AfterFiles renders the After scripts, named name (the migration that follows this one): NoTransaction first, then a statement per line.
type Model ¶
type Model struct {
Name string
// Table is the Postgres table name; empty for types.
Table string
Fields []*Field
Indexes []Index
Line int
// Persisted is true for "model", false for "type".
Persisted bool
// Public is the model attribute @public: the rows are not owned and
// the generated routes are open to visitors.
Public bool `json:",omitempty"`
// the generated routes are behind sign-in.
Shared bool `json:",omitempty"`
// SearchConfig is @@search("english"): the text search configuration
// of the model's @search fields; "simple" when empty.
SearchConfig string `json:",omitempty"`
}
Model is a model or type block.
func (*Model) SearchDocument ¶
SearchDocument is the plain text the highlights come from: the @search fields joined.
func (*Model) SearchFields ¶
SearchFields are the model's @search fields, in order.
func (*Model) SearchLanguage ¶
SearchLanguage is the model's text search configuration: @@search's, else "simple" (no stemming: right for names, codes and mixed languages).
func (*Model) SearchVector ¶
SearchVector is the expression of the model's search document: each @search field weighted (its @search(A) weight, else A, B, C, then D by position) and joined. The GIN index and every search query use this exact text, so the planner matches them; "" without @search fields.
type Rename ¶
type Rename struct {
// Where is "schema.Post" or "queries (column post.tag_ids)".
Where string
Old, New string
}
Rename is a Go name that changed with a Līdza release.
func QueryRenames ¶
QueryRenames lists the db/queries/gen names that change when sqlc.yaml first gets the names SQLCNames gives: before, sqlc knew only the initialism id. Fields of the query rows and parameters change with the columns they come from.
type Result ¶
type Result struct {
// Files are the paths written, relative to the project root.
Files []string
// Migration is the new migration's name, empty when nothing changed.
Migration string
// Renamed are the Go names in the schema package that changed from
// the previous schema/schema.go with this release's naming (plurals
// of initialisms as IDs, URLs): the app's code must follow.
Renamed []Rename
}
Result says what Generate wrote.
type Schema ¶
type Schema struct {
Enums []*Enum
Models []*Model // persisted: table and struct
Types []*Model // API shapes: struct only
}
Schema is a parsed schema.lidza.
func Load ¶
Load parses dir/schema.lidza. It returns nil, nil when the file does not exist: a project without a schema is valid.
func (*Schema) Owner ¶
Owner returns the field that ties a row of m to one user, or nil when m is not owned. The first match wins:
- a field named ownerId;
- the first field with @ref(User) or @ref(AuthUser), such as userId uuid @ref(User) or authorId uuid @ref(User).
A userId without the @ref is not the owner: in a join table (memberships: userId, projectId) the rows belong to the project. The id and arrays are never the owner. A model marked @public or @shared, and a type, have none. The owner's value is the signed-in user's id, auth.CurrentUser(ctx).ID.
func (*Schema) Workspace ¶
Workspace returns the field that ties a row of m to a workspace (auth.Teams), or nil: a field named workspaceId, else the first with @ref(Workspace) or @ref(AuthWorkspace). Its value is the request's workspace, auth.WorkspaceID(ctx), which auth.RequireWorkspace checks the signed-in user belongs to. It takes precedence over Owner: the rows belong to the workspace, not to whoever wrote them. A model marked @public or @shared, and a type, have none.