rkyv_js_codegen/lib.rs
1//! # rkyv-js-codegen
2//!
3//! TypeScript codec-binding generator for [rkyv](https://rkyv.org) types, targeting the `rkyv-js` runtime.
4//!
5//! The generator parses Rust sources with `syn`,
6//! extracts every type marked with `#[derive(Archive)]` (or a custom marker),
7//! and emits one `export const Archived{Name} = ...` codec per type.
8//!
9//! TypeScript types are always derived from the codecs via `r.Infer<typeof Archived{Name}>`.
10//!
11//! ## build.rs
12//!
13//! ```no_run
14//! use rkyv_js_codegen::CodeGenerator;
15//!
16//! fn main() -> Result<(), rkyv_js_codegen::Error> {
17//! CodeGenerator::new()
18//! .add_source_file("src/lib.rs")?
19//! .write_to_file("generated/bindings.ts")?;
20//!
21//! println!("cargo:rerun-if-changed=src/lib.rs");
22//! Ok(())
23//! }
24//! ```
25//!
26//! ## Source extraction
27//!
28//! [`add_source_file`](CodeGenerator::add_source_file),
29//! [`add_source_dir`](CodeGenerator::add_source_dir) (recursive), and
30//! [`add_source_str`](CodeGenerator::add_source_str) all extract every type carrying the marker derive.
31//! `use` imports are resolved to fully-qualified paths — aliases and globs included —
32//! so registry lookups never depend on local names:
33//!
34//! ```
35//! use rkyv_js_codegen::CodeGenerator;
36//!
37//! let mut generator = CodeGenerator::new();
38//! generator.add_source_str(r#"
39//! use rkyv::Archive;
40//!
41//! #[derive(Archive)]
42//! pub struct Point { pub x: f64, pub y: f64 }
43//! "#)?;
44//!
45//! assert_eq!(generator.archived_name_of("Point").as_deref(), Some("ArchivedPoint"));
46//! # Ok::<(), rkyv_js_codegen::Error>(())
47//! ```
48//!
49//! `#[derive(Archive)]` must be resolvable — through `use rkyv::Archive`, an
50//! alias, a `use rkyv::*` glob, or an extra marker registered with
51//! [`add_marker_path`](CodeGenerator::add_marker_path).
52//!
53//! ## Output options
54//!
55//! | Method | Effect |
56//! |--------|--------|
57//! | [`set_header`](CodeGenerator::set_header) | Replace the generated file's header comment |
58//! | [`set_archived_name`](CodeGenerator::set_archived_name) | Override an export name, matching `#[rkyv(archived = Name)]` |
59//! | [`set_direction`](CodeGenerator::set_direction) | Emit full, decode-only, or encode-only bindings |
60//! | [`set_format`](CodeGenerator::set_format) | Target a non-default rkyv wire format |
61//! | [`allow_typescript_syntax`](CodeGenerator::allow_typescript_syntax) | Drop `export type` lines, emitting plain JavaScript |
62//! | [`on_unknown_type`](CodeGenerator::on_unknown_type) | Fail, or warn and omit, on unmappable types |
63//!
64//! ```
65//! use rkyv_js_codegen::{CodeGenerator, Direction};
66//!
67//! let mut generator = CodeGenerator::new();
68//! generator
69//! .set_direction(Direction::Decode) // imports become `rkyv-js/decode`
70//! .set_format("big", 64, true) // mirrors rkyv's feature flags
71//! .allow_typescript_syntax(false);
72//! ```
73//!
74//! [`Direction`] rewrites only the `rkyv-js` import specifiers.
75//!
76//! Factory names and type exports are unchanged, and modules registered through
77//! [`register_external`](CodeGenerator::register_external) are left alone,
78//! so one schema can produce direction-matched bundles for a browser client and a Rust-facing service.
79//!
80//! Emission is deterministic: dependency-ordered, alphabetical within ties, so generated files diff cleanly.
81//!
82//! ## Expressions instead of format strings
83//!
84//! Codec expressions are a typed tree ([`CodecExpr`]) with builders that mirror the runtime combinators ([`codec`]):
85//!
86//! ```
87//! use rkyv_js_codegen::{CodeGenerator, codec};
88//!
89//! let mut generator = CodeGenerator::new();
90//! generator.add_struct("Person", [
91//! ("name", codec::string()),
92//! ("age", codec::u32()),
93//! ("email", codec::option(codec::string())),
94//! ]);
95//! let code = generator.generate()?;
96//! assert!(code.contains("email: r.option(r.string),"));
97//! # Ok::<(), rkyv_js_codegen::Error>(())
98//! ```
99//!
100//! ## Extending the registry
101//!
102//! External crate types are registered by fully-qualified Rust path:
103//!
104//! ```
105//! use rkyv_js_codegen::{CodeGenerator, CodecExpr, ExternalType, WithWrapper};
106//!
107//! let mut generator = CodeGenerator::new();
108//!
109//! // `my_crate::MyVec<T>` → `myVec(T)` from a custom module.
110//! generator.register_external(
111//! "my_crate::MyVec",
112//! ExternalType::generic1(|t| {
113//! CodecExpr::call(CodecExpr::import_from("my-package/codecs", "myVec"), [t])
114//! }),
115//! );
116//!
117//! // `#[rkyv(with = AsJson)]` fields → a hand-written codec.
118//! generator.register_with(
119//! "AsJson",
120//! WithWrapper::replace(CodecExpr::import_from("./custom.ts", "asJson")),
121//! );
122//! ```
123//!
124//! ## Error handling
125//!
126//! Parse failures surface immediately from `add_source_*`; everything else is validated in [`CodeGenerator::generate`],
127//! which aggregates all [`Diagnostic`]s into a single [`Error::Codegen`].
128//!
129//! Set [`OnUnknown::SkipContainingType`] to emit `cargo:warning`s and omit affected types instead of failing.
130
131mod error;
132mod expr;
133mod extractor;
134mod generator;
135mod registry;
136
137pub use error::{Diagnostic, DiagnosticKind, Error, SourceLocation};
138pub use expr::{CodecExpr, Import, codec, generate_import_block};
139pub use generator::{CodeGenerator, Direction, EnumVariant, OnUnknown};
140pub use registry::{ExternalType, WithWrapper};