qubit 1.0.0-beta.2

Seamless RPC for Rust & TypeScript
Documentation
use std::io::Write;

use crate::{
    codegen::{Backend, BackendStage, HandlerBackend, HandlerCodegen, QUBIT_HEADER, TypeBackend},
    reflection::{handler::HandlerKind, ty::CodegenType},
};

/// TypeScript implementation of Qubit type generation.
pub struct TypeScript {
    /// Whether to include extra items at beginning of generate types (Qubit imports, header
    /// comment).
    include_preamble: bool,
    /// Name of the router to generate.
    router_name: String,
}

impl TypeScript {
    /// Create a new TypeScript backend, with `QubitServer` as the router name.
    pub fn new() -> Self {
        Self {
            include_preamble: true,
            router_name: "QubitServer".to_string(),
        }
    }

    /// Set a custom router name.
    pub fn with_router_name(mut self, router_name: impl ToString) -> Self {
        self.router_name = router_name.to_string();
        self
    }

    /// Set the inclusion of the preamble (Qubit imports, header comment).
    pub fn without_preamble(mut self) -> Self {
        self.include_preamble = false;
        self
    }
}

impl Default for TypeScript {
    fn default() -> Self {
        Self::new()
    }
}

impl<W: Write> Backend<W> for TypeScript {
    type HandlerBackend = Self;
    type TypeBackend = Self;

    /// Always generate types at the beginning of the file.
    const STAGES: &[BackendStage] = &[BackendStage::Type, BackendStage::Handler];

    fn get_handler_backend(&self) -> &Self::HandlerBackend {
        self
    }

    fn get_type_backend(&self) -> &Self::TypeBackend {
        self
    }

    fn begin(&self, writer: &mut W) -> Result<(), std::io::Error> {
        if self.include_preamble {
            writeln!(writer, "/* eslint-disable */")?;
            writeln!(writer, "// @ts-nocheck")?;

            writeln!(writer, "/*")?;
            writeln!(writer, "{QUBIT_HEADER}")?;
            writeln!(writer, "*/")?;

            writeln!(
                writer,
                r#"import type {{ Query, Mutation, Subscription }} from "@qubit-rs/client";"#
            )?;
        }

        Ok(())
    }
}

impl<W: Write> HandlerBackend<W> for TypeScript {
    fn begin(&self, writer: &mut W) -> std::io::Result<()> {
        write!(writer, "export type {} = ", self.router_name)
    }

    fn end(&self, writer: &mut W) -> std::io::Result<()> {
        writeln!(writer, ";")
    }

    fn write_key(&self, key: &str, writer: &mut W) -> std::io::Result<()> {
        write!(writer, "{key}: ")
    }

    fn write_handler(&self, handler: &HandlerCodegen, writer: &mut W) -> std::io::Result<()> {
        let kind = match handler.kind {
            HandlerKind::Query => "Query",
            HandlerKind::Mutation => "Mutation",
            HandlerKind::Subscription => "Subscription",
        };

        let params = handler
            .params
            .iter()
            .map(|(name, ty)| format!("{name}: {ty}"))
            .collect::<Vec<_>>()
            .join(", ");

        let return_ty = &handler.return_ty;

        write!(writer, "{kind}<[{params}], {return_ty}>, ")
    }

    fn begin_nested(&self, _root: bool, writer: &mut W) -> std::io::Result<()> {
        write!(writer, "{{ ")
    }

    fn end_nested(&self, root: bool, writer: &mut W) -> std::io::Result<()> {
        write!(writer, "}}")?;

        if !root {
            write!(writer, ", ")?;
        }

        Ok(())
    }
}

impl<W: Write> TypeBackend<W> for TypeScript {
    fn write_type(
        &self,
        name: &CodegenType,
        definition: &str,
        writer: &mut W,
    ) -> std::io::Result<()> {
        writeln!(writer, "export type {name} = {definition};")
    }
}