Skip to content

Commit

Permalink
feat: generation for react working
Browse files Browse the repository at this point in the history
feat: added nextjs-plugin package for quick next.js config setup
  • Loading branch information
seawatts committed Jan 30, 2025
1 parent 6c3df5a commit c75e1a0
Show file tree
Hide file tree
Showing 138 changed files with 58,783 additions and 813 deletions.
6 changes: 5 additions & 1 deletion engine/baml-lib/baml-types/src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ pub enum GeneratorOutputType {
#[strum(serialize = "typescript")]
Typescript,

#[strum(serialize = "typescript/react")]
TypescriptReact,

#[strum(serialize = "ruby/sorbet")]
RubySorbet,
}
Expand All @@ -38,6 +41,7 @@ impl GeneratorOutputType {
// DO NOT CHANGE THIS DEFAULT EVER OR YOU WILL BREAK EXISTING USERS
Self::PythonPydantic => GeneratorDefaultClientMode::Async,
Self::Typescript => GeneratorDefaultClientMode::Async,
Self::TypescriptReact => GeneratorDefaultClientMode::Async,
Self::RubySorbet => GeneratorDefaultClientMode::Sync,
}
}
Expand All @@ -48,6 +52,7 @@ impl GeneratorOutputType {
Self::OpenApi => GeneratorDefaultClientMode::Sync,
Self::PythonPydantic => GeneratorDefaultClientMode::Sync,
Self::Typescript => GeneratorDefaultClientMode::Async,
Self::TypescriptReact => GeneratorDefaultClientMode::Async,
Self::RubySorbet => GeneratorDefaultClientMode::Sync,
}
}
Expand All @@ -56,7 +61,6 @@ impl GeneratorOutputType {
impl clap::ValueEnum for GeneratorOutputType {
fn value_variants<'a>() -> &'a [Self] {
use strum::VariantArray;

Self::VARIANTS
}

Expand Down
4 changes: 4 additions & 0 deletions engine/baml-runtime/src/cli/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ impl GenerateArgs {
// this has no meaning
GeneratorDefaultClientMode::Sync
}
internal_baml_core::configuration::GeneratorOutputType::TypescriptReact => {
GeneratorDefaultClientMode::Async
}
};
// Normally `baml_client` is added via the generator, but since we're not running the generator, we need to add it manually.
let output_dir_relative_to_baml_src = PathBuf::from("..");
Expand All @@ -77,6 +80,7 @@ impl GenerateArgs {
default_client_mode,
// TODO: this should be set if user is asking for openapi
vec![],
None,
)
.context("Failed while resolving .baml paths in baml_src/")?,
)
Expand Down
4 changes: 3 additions & 1 deletion engine/baml-runtime/src/cli/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ impl InitArgs {
Some(s) => format!("{} clients via OpenAPI", s),
None => "REST clients".to_string(),
},
GeneratorOutputType::TypescriptReact => "TypeScript React clients".to_string(),
}
);
log::info!(
Expand All @@ -100,6 +101,7 @@ impl InitArgs {
GeneratorOutputType::Typescript => "typescript",
GeneratorOutputType::RubySorbet => "ruby",
GeneratorOutputType::OpenApi => "openapi",
GeneratorOutputType::TypescriptReact => "typescript-react",
}
);

Expand All @@ -114,7 +116,7 @@ fn generate_main_baml_content(
) -> String {
let default_client_mode = match output_type {
GeneratorOutputType::OpenApi | GeneratorOutputType::RubySorbet => "".to_string(),
GeneratorOutputType::PythonPydantic | GeneratorOutputType::Typescript => format!(
GeneratorOutputType::PythonPydantic | GeneratorOutputType::Typescript | GeneratorOutputType::TypescriptReact => format!(
r#"
// Valid values: "sync", "async"
// This controls what `b.FunctionName()` will be (sync or async).
Expand Down
5 changes: 3 additions & 2 deletions engine/baml-runtime/src/cli/serve/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use axum_extra::{
headers::{self, authorization::Basic, Authorization, Header},
TypedHeader,
};
use baml_types::{BamlValue, GeneratorDefaultClientMode};
use baml_types::{BamlValue, GeneratorDefaultClientMode, GeneratorOutputType};
use core::pin::Pin;
use futures::Stream;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -67,7 +67,7 @@ impl ServeArgs {
if !self.preview {
log::warn!(
r#"BAML-over-HTTP API is a preview feature.
Please run with --preview, like so:
{} serve --preview
Expand Down Expand Up @@ -623,6 +623,7 @@ Tip: test that the server is up using `curl http://localhost:{}/_debug/ping`
true,
GeneratorDefaultClientMode::Sync,
Vec::new(),
None,
)
.map_err(|_| BamlError::InternalError {
message: "Failed to make placeholder generator".to_string(),
Expand Down
1 change: 1 addition & 0 deletions engine/baml-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@ impl BamlRuntime {
no_version_check,
generator.default_client_mode(),
generator.on_generate.clone(),
Some(generator.output_type),
)?,
))
})
Expand Down
6 changes: 6 additions & 0 deletions engine/language_client_codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ pub struct GeneratorArgs {
// Default call mode for functions
default_client_mode: GeneratorDefaultClientMode,
on_generate: Vec<String>,

// The type of client to generate
client_type: Option<GeneratorOutputType>,
}

fn relative_path_to_baml_src(path: &Path, baml_src: &Path) -> Result<PathBuf> {
Expand All @@ -54,6 +57,7 @@ impl GeneratorArgs {
no_version_check: bool,
default_client_mode: GeneratorDefaultClientMode,
on_generate: Vec<String>,
client_type: Option<GeneratorOutputType>,
) -> Result<Self> {
let baml_src = baml_src_dir.into();
let input_file_map: BTreeMap<PathBuf, String> = input_files
Expand All @@ -70,6 +74,7 @@ impl GeneratorArgs {
no_version_check,
default_client_mode,
on_generate,
client_type,
})
}

Expand Down Expand Up @@ -185,6 +190,7 @@ impl GenerateClient for GeneratorOutputType {
GeneratorOutputType::PythonPydantic => python::generate(ir, gen),
GeneratorOutputType::RubySorbet => ruby::generate(ir, gen),
GeneratorOutputType::Typescript => typescript::generate(ir, gen),
GeneratorOutputType::TypescriptReact => typescript::generate(ir, gen),
}?;

#[cfg(not(target_arch = "wasm32"))]
Expand Down
105 changes: 104 additions & 1 deletion engine/language_client_codegen/src/typescript/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,55 @@ use baml_types::LiteralValue;
use generate_types::{render_docstring, type_name_for_checks};
use indexmap::IndexMap;
use internal_baml_core::{
configuration::GeneratorDefaultClientMode,
configuration::{GeneratorDefaultClientMode, GeneratorOutputType},
ir::{repr::IntermediateRepr, FieldType, IRHelper},
};

use self::typescript_language_features::{ToTypescript, TypescriptLanguageFeatures};
use crate::{dir_writer::FileCollector, field_type_attributes};

mod framework {
use internal_baml_core::configuration::GeneratorOutputType;

#[derive(Debug, Clone, Copy)]
pub enum TypescriptFramework {
None,
React
}

impl TypescriptFramework {
pub fn from_generator_type(output_type: Option<GeneratorOutputType>) -> Self {
match output_type {
Some(GeneratorOutputType::TypescriptReact) => Self::React,
Some(GeneratorOutputType::Typescript) | None => Self::None,
Some(_) => panic!("Invalid generator type for TypeScript framework"),
}
}
}
}

use framework::TypescriptFramework;

mod filters {
pub fn length<T>(v: &Vec<T>) -> Result<usize, askama::Error> {
Ok(v.len())
}
}

#[derive(askama::Template)]
#[template(path = "react/server.ts.j2", escape = "none")]
struct ReactServerActions {
funcs: Vec<TypescriptFunction>,
types: Vec<String>,
}

#[derive(askama::Template)]
#[template(path = "react/client.tsx.j2", escape = "none")]
struct ReactClientHooks {
funcs: Vec<TypescriptFunction>,
types: Vec<String>,
}

#[derive(askama::Template)]
#[template(path = "async_client.ts.j2", escape = "none")]
struct AsyncTypescriptClient {
Expand Down Expand Up @@ -52,6 +94,24 @@ impl From<TypescriptClient> for SyncTypescriptClient {
}
}

impl From<TypescriptClient> for ReactServerActions {
fn from(value: TypescriptClient) -> Self {
Self {
funcs: value.funcs,
types: value.types,
}
}
}

impl From<TypescriptClient> for ReactClientHooks {
fn from(value: TypescriptClient) -> Self {
Self {
funcs: value.funcs,
types: value.types,
}
}
}

#[derive(Debug)]
struct TypescriptFunction {
name: String,
Expand Down Expand Up @@ -82,11 +142,18 @@ struct InlinedBaml {
#[template(path = "tracing.ts.j2", escape = "none")]
struct TypescriptTracing {}

#[derive(askama::Template)]
#[template(path = "react/types.ts.j2", escape = "none")]
struct ReactTypes {}

pub(crate) fn generate(
ir: &IntermediateRepr,
generator: &crate::GeneratorArgs,
) -> Result<IndexMap<PathBuf, String>> {
let framework = TypescriptFramework::from_generator_type(generator.client_type);
let mut collector = FileCollector::<TypescriptLanguageFeatures>::new();

// Add base TypeScript files
collector.add_template::<generate_types::TypescriptTypes>("types.ts", (ir, generator))?;
collector.add_template::<generate_types::TypescriptStreamTypes>(
"partial_types.ts",
Expand All @@ -100,6 +167,16 @@ pub(crate) fn generate(
collector.add_template::<TypescriptInit>("index.ts", (ir, generator))?;
collector.add_template::<InlinedBaml>("inlinedbaml.ts", (ir, generator))?;

// Add framework-specific files
match framework {
TypescriptFramework::React => {
collector.add_template::<ReactTypes>("react/types.ts", (ir, generator))?;
collector.add_template::<ReactServerActions>("react/server.ts", (ir, generator))?;
collector.add_template::<ReactClientHooks>("react/client.tsx", (ir, generator))?;
}
TypescriptFramework::None => {}
}

collector.commit(&generator.output_dir())
}

Expand Down Expand Up @@ -206,6 +283,32 @@ impl TryFrom<(&'_ IntermediateRepr, &'_ crate::GeneratorArgs)> for TypescriptIni
}
}

impl TryFrom<(&'_ IntermediateRepr, &'_ crate::GeneratorArgs)> for ReactServerActions {
type Error = anyhow::Error;

fn try_from(params: (&'_ IntermediateRepr, &'_ crate::GeneratorArgs)) -> Result<Self> {
let typscript_client = TypescriptClient::try_from(params)?;
Ok(typscript_client.into())
}
}

impl TryFrom<(&'_ IntermediateRepr, &'_ crate::GeneratorArgs)> for ReactClientHooks {
type Error = anyhow::Error;

fn try_from(params: (&'_ IntermediateRepr, &'_ crate::GeneratorArgs)) -> Result<Self> {
let typscript_client = TypescriptClient::try_from(params)?;
Ok(typscript_client.into())
}
}

impl TryFrom<(&'_ IntermediateRepr, &'_ crate::GeneratorArgs)> for ReactTypes {
type Error = anyhow::Error;

fn try_from(_: (&IntermediateRepr, &crate::GeneratorArgs)) -> Result<Self> {
Ok(ReactTypes {})
}
}

trait ToTypeReferenceInClientDefinition {
fn to_type_ref(&self, ir: &IntermediateRepr, use_module_prefix: bool) -> String;
/// The string representation of a field type, and whether the field is optional.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ import {
import TypeBuilder from "./type_builder"
import { DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_CTX, DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_RUNTIME } from "./globals"

/**
* @deprecated Use RecursivePartialNull from 'baml_client/types' instead.
*/
export type RecursivePartialNull<T> = T extends object
? {
[P in keyof T]?: RecursivePartialNull<T[P]>;
}
: T | null;

export class BamlAsyncClient {
private runtime: BamlRuntime
private ctx_manager: BamlCtxManager
Expand Down
Loading

0 comments on commit c75e1a0

Please sign in to comment.