Current section
Files
Jump to
Current section
Files
src/libero.gleam
//// Libero: seed-driven typed wire-contract generation for Gleam.
////
//// Frameworks decide which application types cross the boundary. Libero walks
//// those seeded types and generates the ETF, JSON, and decoder artifacts used
//// by Rally.
import gleam/option
import gleam/result
import libero/codegen_atoms
import libero/codegen_decoders
import libero/etf/codegen_erl
import libero/gen_error.{type GenError}
import libero/json/contract
import libero/source
import libero/walker.{type DiscoveredType}
/// Build qualified atom names using the same logic as generated wire modules.
pub fn qualified_atom_name(
module_path module_path: String,
variant_name variant_name: String,
) -> String {
walker.qualified_atom_name(module_path:, variant_name:)
}
/// Walk the type graph from caller-provided seeds using this package's `src/`
/// directory as the source root. Rally runs this from the application root, so
/// its page-local wire types are discovered from the app's `src/`.
pub fn walk(
seeds: List(#(String, String)),
) -> Result(List(DiscoveredType), List(GenError)) {
use files <- result.try(
source.walk_directory("./src")
|> result.map_error(fn(e) { [e] }),
)
walker.walk(seeds, files)
}
/// Generate the Erlang atoms pre-registration file content.
/// Module name uses Gleam's @-separated convention, for example
/// `generated@libero_atoms`.
pub fn generate_atoms(
discovered discovered: List(DiscoveredType),
atoms_module atoms_module: String,
wire_module wire_module: option.Option(String),
) -> String {
codegen_atoms.generate(discovered:, atoms_module:, wire_module:)
}
/// Generate the per-type wire-transformer Erlang module.
pub fn generate_wire_erl(
discovered discovered: List(DiscoveredType),
wire_module wire_module: String,
) -> Result(String, GenError) {
codegen_erl.generate(module_name: wire_module, discovered:)
}
/// Generate the JavaScript typed decoder FFI source.
/// `package` is the Gleam package name that owns the modules.
pub fn generate_decoders_ffi(
discovered discovered: List(DiscoveredType),
package package: String,
dependency_packages dependency_packages: List(String),
) -> String {
codegen_decoders.generate_decoders_ffi(
discovered:,
relpath_prefix: "../../../",
package:,
dependency_packages:,
)
}
/// Generate the Gleam wrapper for the typed decoder FFI.
pub fn generate_decoders_gleam() -> String {
codegen_decoders.generate_decoders_gleam("decoders_ffi.mjs")
}
/// Generate a neutral ETF codec wrapper for generated application code.
pub fn generate_etf_codec_module(
atoms_module atoms_module: String,
decoders_module decoders_module: String,
) -> String {
"//// Code generated by libero. DO NOT EDIT.
import " <> decoders_module <> " as decoders
import libero/error.{type DecodeError}
import libero/etf/wire as etf_wire
pub fn ensure() -> Nil {
let _ = ensure_atoms()
let _ = decoders.ensure_decoders()
Nil
}
pub fn encode(value: a) -> BitArray {
ensure()
etf_wire.encode(value)
}
pub fn decode(bytes: BitArray) -> Result(a, DecodeError) {
ensure()
etf_wire.decode_safe(bytes)
}
@external(erlang, \"" <> atoms_module <> "\", \"ensure\")
fn ensure_atoms() -> Nil {
Nil
}
"
}
/// Generate a deterministic JSON contract artifact from discovered types.
pub fn generate_json_contract(
discovered discovered: List(DiscoveredType),
push_types push_types: List(contract.PushContract),
ssr_models ssr_models: List(contract.SsrModelContract),
) -> String {
contract.generate(discovered:, push_types:, ssr_models:)
}
pub fn generate_json_contract_hash(
discovered discovered: List(DiscoveredType),
push_types push_types: List(contract.PushContract),
ssr_models ssr_models: List(contract.SsrModelContract),
) -> String {
contract.generate_hash(discovered:, push_types:, ssr_models:)
}