Skip to content

Recipes ​

Short answers to common tasks. Each recipe is a complete example: the code on the page is type-checked, and, except for the React form, runs in CI against Postgres, with the values in its // => comments checked.

Each recipe shows its schemas, tables and starting rows in a folded block. Run each one in an empty database of its own: they create their own tables, and some use the same table names with other columns.

Reading ​

RecipeIn short
Paginationlimit and offset with a total, or keyset pagination with a tuple cursor.
Top N per parentA preload with a per-parent limit, or a window function.
Filters from a query stringValidate ?q=…&page=… with a schemaless changeset, then add clauses conditionally.
Full-text searchA generated tsvector column, and fragment for @@ and ts_rank.

Writing ​

RecipeIn short
UpsertsonConflict on insert and insertAll.
Find or createInsert with onConflict: "nothing", then read the row that won.
Optimistic lockingoptimisticLock("lockVersion"), and a conflict reported on the form.
Soft deletesA deletedAt field and a reusable scope.
AuditingA batch that writes a row and its audit entry together.

Modelling ​

RecipeIn short
Enumst.enum for text and integer columns, with a check constraint.
MoneyA custom type that stores cents in a numeric column.
Multi-tenancy with database schemasA database schema per tenant, chosen per call or per repo.

Forms and messages ​

RecipeIn short
Showing form errors (React)A server action that returns errorMessages(), and fields that show them.
Translating error messagestraverseErrors with a message catalogue and plural rules.
Errors as HTTP responses422 for an invalid changeset, 404, 409 and 500 for the rest, typed on the client with Json and ErrorMessages.