lode/codegen

Schema-first code generation (DESIGN §14).

The hand-authored lode/schema/spec is the single source of truth: generate_from_spec turns it into ready-to-compile modules — record types, Schema(row) values with load/dump codecs, typed FieldRef accessors, association registrations, and preload constructors — with no live database. Enums and embedded schemas declared in the spec are emitted alongside; virtual fields land on the record and in changeset casting but never in dump or the accessors.

Database introspection is retained for two purposes only:

Introspection is engine-aware: information_schema on Postgres, sqlite_master + PRAGMA table_info/foreign_key_list on SQLite (where the schema argument is ignored; pass "main").

let assert Ok(files) = codegen.generate_from_spec(db_spec.tables()) // files : List(#(filename, source)); write them where you like, or: let assert Ok(_) = codegen.generate_from_spec_to_dir(db_spec.tables(), “src/myapp/db”)

Types

pub type ColumnInfo {
  ColumnInfo(
    name: String,
    data_type: String,
    nullable: Bool,
    primary_key: Bool,
    autogenerate: Bool,
  )
}

Constructors

  • ColumnInfo(
      name: String,
      data_type: String,
      nullable: Bool,
      primary_key: Bool,
      autogenerate: Bool,
    )

    Arguments

    data_type

    The database’s reported type, lowercased: the information_schema data_type on Postgres; the declared column type on SQLite.

    autogenerate

    Database generates the value on insert (serial / identity / has default).

A foreign key: child_table.fk_column references parent_table.parent_column.

pub type ForeignKey {
  ForeignKey(
    child_table: String,
    fk_column: String,
    parent_table: String,
    parent_column: String,
  )
}

Constructors

  • ForeignKey(
      child_table: String,
      fk_column: String,
      parent_table: String,
      parent_column: String,
    )
pub type TableInfo {
  TableInfo(name: String, columns: List(ColumnInfo))
}

Constructors

  • TableInfo(name: String, columns: List(ColumnInfo))

Values

pub fn bootstrap_spec(
  repo repo: repo.Repo,
  schema schema_name: String,
) -> Result(String, error.LodeError)

One-shot importer for adopting the schema-first workflow on an existing database: introspects schema_name and renders a spec module source (pub fn tables() -> List(spec.TableSpec)). Best-effort on type intent — the database can’t express enums, embeds, decimal precision, or virtual fields, so refine the emitted spec by hand. From then on the spec is the source of truth: generate_from_spec emits modules and lode/drift verifies the database against it.

Works on Postgres and SQLite (pass schema: "main" on SQLite — the argument is ignored there). On SQLite, type intent is limited to what the storage classes preserve — temporal/uuid/json columns introspect as Text.

pub fn generate(
  repo repo: repo.Repo,
  schema schema_name: String,
) -> Result(List(#(String, String)), error.LodeError)

Generate modules straight from a live database (introspection routed through the spec emitters). This is the bootstrap convenience; the schema-first workflow is bootstrap_spec once, then generate_from_spec.

pub fn generate_from_spec(
  tables tables: List(spec.TableSpec),
) -> Result(List(#(String, String)), error.LodeError)

Generate #(filename, source) modules from a hand-authored spec — no live database. Tables are grouped by the connected components of the association graph: isolated tables get their own module (clean, unprefixed names); any cluster of associated tables shares one module (with record-prefixed names) so the mutually-recursive records and registrations stay intra-module — Gleam forbids circular imports across modules.

manual_schema tables generate nothing (the user hand-writes their record and Schema(row)); they stay in the spec for the drift checker.

pub fn generate_from_spec_to_dir(
  tables tables: List(spec.TableSpec),
  dir dir: String,
) -> Result(List(String), error.LodeError)

generate_from_spec, then write one .gleam file per module into dir.

pub fn generate_module(
  table: TableInfo,
  foreign_keys: List(ForeignKey),
) -> String

Generate the Gleam source for a single table. foreign_keys is the full FK list for the schema; the table’s association names become preload-name constructors, but no association fields or registrations are emitted — register them by piping onto the generated schema() (use generate / generate_from_spec for fully-wired modules).

pub fn generate_to_dir(
  repo repo: repo.Repo,
  schema schema_name: String,
  dir dir: String,
) -> Result(List(String), error.LodeError)

Generate from a live database and write one .gleam file per module into dir. Returns the paths.

pub fn introspect(
  repo repo: repo.Repo,
  schema schema_name: String,
) -> Result(List(TableInfo), error.LodeError)

Introspect all base tables, returning their columns.

Engine-aware: on Postgres this reads information_schema for the base tables in schema_name; on SQLite it reads sqlite_master plus PRAGMA table_info (SQLite has no schemas, so schema_name is ignored — pass "main"). Engine bookkeeping stays invisible on both: pg_catalog never appears in information_schema, and sqlite_* internals (e.g. sqlite_sequence) are excluded here to match.

pub fn introspect_foreign_keys(
  repo repo: repo.Repo,
  schema schema_name: String,
) -> Result(List(ForeignKey), error.LodeError)

Introspect foreign keys. Used to derive associations. Engine-aware like introspect (schema_name is ignored on SQLite).

pub fn render_spec_module(
  tables tables: List(spec.TableSpec),
  schema schema_name: String,
) -> String

Render a spec set back to Gleam source (the inverse of authoring by hand).

pub fn table_specs(
  tables tables: List(TableInfo),
  foreign_keys fks: List(ForeignKey),
) -> List(spec.TableSpec)

Best-effort spec for introspected tables: column types from the database’s reported type (Postgres data_type / SQLite declared type; intent the database can’t express — enums, embeds, virtuals — must be added by hand), belongs_to/has_many associations from the foreign-key graph.

Search Document