mod emit;
mod introspect;
mod signature;
mod type_parser;
mod types;
use std::path::PathBuf;
use anyhow::Result;
use surrealdb::Surreal;
use surrealdb::engine::any::Any;
use time::OffsetDateTime;
use time::format_description::well_known::Rfc3339;
pub use types::{
FieldDef, FieldType, FnArg, FunctionDef, NamedDef, ObjectField, ParamDef, PrimitiveType,
SchemaTypes, TableDef,
};
#[derive(Debug, Clone, Default)]
pub struct TypegenOpts {
pub out: Option<PathBuf>,
pub stdout: bool,
pub pretty: bool,
pub ts_out: Option<PathBuf>,
pub ts_format: Option<String>,
}
pub fn render_typescript(doc: &SchemaTypes) -> Result<String> {
emit::to_typescript(doc)
}
pub fn write_typescript(doc: &SchemaTypes, dir: &std::path::Path) -> Result<PathBuf> {
let ts = emit::to_typescript(doc)?;
std::fs::create_dir_all(dir)?;
let path = dir.join("index.ts");
std::fs::write(&path, ts)?;
Ok(path)
}
pub fn write_typescript_formatted(
doc: &SchemaTypes,
dir: &std::path::Path,
format: Option<&str>,
) -> Result<PathBuf> {
let path = write_typescript(doc, dir)?;
if let Some(cmd) = format {
format_file(cmd, &path);
}
Ok(path)
}
pub fn format_file(command: &str, path: &std::path::Path) {
let mut parts = command.split_whitespace();
let Some(program) = parts.next() else {
return; };
let args: Vec<&str> = parts.collect();
match std::process::Command::new(program).args(&args).arg(path).status() {
Ok(status) if status.success() => {
eprintln!("typegen: formatted {} with `{command}`", path.display());
}
Ok(status) => {
eprintln!("typegen: formatter `{command}` exited with {status}");
}
Err(err) => {
eprintln!("typegen: failed to run formatter `{command}`: {err}");
}
}
}
pub async fn generate(db: &Surreal<Any>) -> Result<SchemaTypes> {
let mut doc = introspect::introspect(db).await?;
doc.generated_at = OffsetDateTime::now_utc().format(&Rfc3339)?;
Ok(doc)
}
#[doc(hidden)]
pub async fn run_typegen(
db: &Surreal<Any>,
folder: &str,
namespace: &str,
database: &str,
opts: TypegenOpts,
) -> Result<()> {
let mut doc = generate(db).await?;
if !namespace.is_empty() {
doc.namespace = Some(namespace.to_string());
}
if !database.is_empty() {
doc.database = Some(database.to_string());
}
let json = emit::to_json(&doc, opts.pretty)?;
if opts.stdout {
println!("{json}");
return Ok(());
}
let path = opts.out.unwrap_or_else(|| crate::constants::typegen_output_path(folder));
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(&path, format!("{json}\n"))?;
eprintln!("typegen: wrote {}", path.display());
if let Some(ts_dir) = &opts.ts_out {
let ts_path = write_typescript_formatted(&doc, ts_dir, opts.ts_format.as_deref())?;
eprintln!("typegen: wrote {}", ts_path.display());
}
Ok(())
}