Skip to main content

scythe_codegen/backends/
typescript_common.rs

1use std::fmt::Write;
2
3use scythe_core::errors::{ErrorCode, ScytheError};
4
5use crate::backend_trait::ResolvedColumn;
6
7/// Supported TypeScript row type styles for generated code.
8#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
9pub enum TsRowType {
10    #[default]
11    Interface,
12    Zod,
13}
14
15impl TsRowType {
16    /// Parse a row_type option string into a `TsRowType`.
17    pub fn from_option(value: &str) -> Result<Self, ScytheError> {
18        match value {
19            "interface" => Ok(Self::Interface),
20            "zod" => Ok(Self::Zod),
21            _ => Err(ScytheError::new(
22                ErrorCode::InternalError,
23                format!(
24                    "invalid row_type '{}': expected 'interface' or 'zod'",
25                    value
26                ),
27            )),
28        }
29    }
30}
31
32/// Map a neutral type to its Zod v4 schema expression.
33pub fn neutral_to_zod(neutral_type: &str, nullable: bool) -> String {
34    let base = match neutral_type {
35        "int16" | "int32" | "int64" => "z.number()",
36        "float32" | "float64" => "z.number()",
37        "string" | "text" | "inet" | "interval" | "time" | "time_tz" => "z.string()",
38        "bool" => "z.boolean()",
39        "datetime" | "datetime_tz" => "z.date()",
40        "date" => "z.string()",
41        "uuid" => "z.string().uuid()",
42        "json" => "z.unknown()",
43        "decimal" => "z.string()",
44        "bytes" => "z.instanceof(Buffer)",
45        t if t.starts_with("enum::") => "z.string()",
46        _ => "z.unknown()",
47    };
48    if nullable {
49        format!("{base}.nullable()")
50    } else {
51        base.to_string()
52    }
53}
54
55/// Generate a Zod schema and inferred type for a row struct.
56pub fn generate_zod_row_struct(
57    struct_name: &str,
58    query_name: &str,
59    columns: &[ResolvedColumn],
60) -> String {
61    let schema_name = format!("{struct_name}Schema");
62    let mut out = String::new();
63    let _ = writeln!(out, "/** Row type for {} queries. */", query_name);
64    let _ = writeln!(out, "export const {} = z.object({{", schema_name);
65    for col in columns {
66        let zod_type = neutral_to_zod(&col.neutral_type, col.nullable);
67        let _ = writeln!(out, "\t{}: {},", col.field_name, zod_type);
68    }
69    let _ = writeln!(out, "}});");
70    let _ = writeln!(out);
71    let _ = write!(
72        out,
73        "export type {} = z.infer<typeof {}>;",
74        struct_name, schema_name
75    );
76    out
77}
78
79/// Generate a Zod enum schema from enum values.
80pub fn generate_zod_enum(type_name: &str, values: &[String]) -> String {
81    let schema_name = format!("{type_name}Schema");
82    let mut out = String::new();
83    let variants: Vec<String> = values.iter().map(|v| format!("\"{}\"", v)).collect();
84    let _ = writeln!(
85        out,
86        "export const {} = z.enum([{}]);",
87        schema_name,
88        variants.join(", ")
89    );
90    let _ = writeln!(out);
91    let _ = write!(
92        out,
93        "export type {} = z.infer<typeof {}>;",
94        type_name, schema_name
95    );
96    out
97}