roam_codegen/lib.rs
1#![deny(unsafe_code)]
2
3//! Code generation for roam RPC bindings across multiple languages.
4//!
5//! # This Is Where Code Generation Actually Happens
6//!
7//! While [`roam-macros`] parses your service traits and emits metadata, this crate
8//! consumes that metadata and generates actual protocol implementations for:
9//!
10//! - **TypeScript** — Browser and Node.js clients
11//! - **Swift** — iOS/macOS clients
12//! - **Go** — Server and client implementations
13//! - **Java** — Android and server implementations
14//! - **Python** — Client bindings
15//! - **Rust** — Extended codegen beyond what the proc macro provides
16//!
17//! # Usage: In Your build.rs
18//!
19//! ```ignore
20//! // In your service crate's build.rs
21//! use my_service::calculator_service_detail;
22//!
23//! fn main() {
24//! let detail = calculator_service_detail();
25//!
26//! // Generate TypeScript client
27//! let ts_code = roam_codegen::targets::typescript::generate(&detail);
28//! std::fs::write("generated/calculator.ts", ts_code).unwrap();
29//!
30//! // Generate Go server
31//! let go_code = roam_codegen::targets::go::generate(&detail);
32//! std::fs::write("generated/calculator.go", go_code).unwrap();
33//! }
34//! ```
35//!
36//! # The Pipeline
37//!
38//! ```text
39//! #[service] trait → ServiceDescriptor → roam-codegen → .ts, .go, .swift, ...
40//! (your code) (runtime metadata) (build script) (generated code)
41//! ```
42//!
43//! # Why Build Scripts? (The Technical Reason)
44//!
45//! Code generation happens in build scripts (not proc macros) because **proc macros
46//! cannot see into the type system**.
47//!
48//! When a proc macro sees `Tx<String>` in a method signature, it sees tokens — it has
49//! no idea if `Tx` refers to `roam::channel::Tx` or some user-defined type. It cannot
50//! resolve type aliases, follow generic parameters, or inspect nested types.
51//!
52//! But here, with `facet::Shape`, we have **full type introspection**:
53//!
54//! ```ignore
55//! // We can identify roam's Tx vs user-defined types
56//! let shape = <Tx<String> as facet::Facet>::SHAPE;
57//!
58//! // We can traverse nested types like Result<Vec<Tx<T>>, Error>
59//! // and find the Tx buried inside
60//! ```
61//!
62//! This is why validation happens here:
63//! - Is this actually roam's `Tx`/`Rx` channel type?
64//! - Are channel types incorrectly used in error positions?
65//! - What serialization does this nested type require?
66//!
67//! Additional benefits of build scripts:
68//!
69//! 1. **File I/O** — Build scripts can write files; proc macros cannot
70//! 2. **Configuration** — Build scripts can read config files, env vars, etc.
71//! 3. **Flexibility** — Different projects can generate different subsets of bindings
72//!
73//! [`roam-macros`]: https://docs.rs/roam-service-macros
74
75pub mod code_writer;
76mod render;
77pub mod targets;
78
79use roam_types::MethodDescriptor;
80
81pub fn method_id(detail: &MethodDescriptor) -> u64 {
82 detail.id.0
83}