#generate-typescript #schema #idl #codegen

bin+lib fluorite_codegen

Generate rust/typescript codes from schemas specified by Yaml/JSON

12 releases (4 breaking)

new 0.6.0 Feb 12, 2026
0.5.0 Feb 8, 2026
0.4.0 Feb 7, 2026
0.2.0 Feb 2, 2026
0.1.7 Dec 27, 2023

#2753 in Database interfaces

MIT/Apache

200KB
4.5K SLoC

Fluorite

Crates.io docs.rs CI

Fluorite generates Rust, TypeScript, and Swift code from a shared schema language. Define your types once in .fl files, then generate type-safe, serialization-ready code for all three languages.

All generated code uses camelCase as the JSON serialization format, ensuring consistent cross-language interoperability without any configuration.

Quick Start

1. Install

# via Cargo
cargo install fluorite_codegen

# or via npm
npm install -D @zhxiaogg/fluorite-cli

2. Write a Schema

Create schema.fl:

package myapp;

struct User {
    id: String,
    name: String,
    email: Option<String>,
    active: bool,
}

enum Role {
    Admin,
    Member,
    Guest,
}

3. Generate Code

# Rust
fluorite rust --inputs schema.fl --output ./src/generated

# TypeScript
fluorite ts --inputs schema.fl --output ./src/generated

# Swift
fluorite swift --inputs schema.fl --output ./Sources/Generated

That's it. You now have type-safe structs (Rust), interfaces (TypeScript), and Codable types (Swift) with full serialization support.


The Fluorite IDL

Fluorite uses .fl files with a Rust-like syntax. Here's what you can express:

Structs

/// A customer order
struct Order {
    order_id: String,
    total: f64,
    shipped: bool,
    notes: Option<String>,
}

Fields are automatically serialized as camelCase in JSON across all languages. No annotation needed — order_id becomes "orderId" in JSON.

Rust output — a #[derive(Serialize, Deserialize)] struct with #[serde(rename_all = "camelCase")].

TypeScript output — an exported interface with camelCase field names.

Swift output — a Codable struct with camelCase properties and CodingKeys.

Enums

enum OrderStatus {
    Pending,
    Confirmed,
    Shipped,
    Delivered,
    Cancelled,
}

Rust — a standard enum with serde derives. TypeScript — a string literal union type. Swift — a String-backed enum with Codable conformance.

Tagged Unions

Fluorite uses adjacently tagged unions, producing consistent JSON across all languages:

#[type_tag = "type"]
#[content_tag = "value"]
union OrderEvent {
    Created(Order),
    StatusChanged(StatusChange),
    Cancelled,
}

This serializes as:

{"type": "Created", "value": {"orderId": "...", "total": 42.0, ...}}
{"type": "Cancelled"}

Rust output:

#[serde(tag = "type", content = "value")]
pub enum OrderEvent {
    Created(Order),
    StatusChanged(StatusChange),
    Cancelled,
}

TypeScript output:

export type OrderEvent =
  | { type: "Created"; value: Order }
  | { type: "StatusChanged"; value: StatusChange }
  | { type: "Cancelled" };

Swift output:

public enum OrderEvent: Codable, Equatable, Sendable {
    case created(Order)
    case statusChanged(StatusChange)
    case cancelled
    // Custom Codable implementation for adjacently tagged format
}

Type Aliases

type OrderList = Vec<Order>;
type OrderMap = Map<String, Order>;

Packages and Imports

Split schemas across files with dotted package names:

// common.fl
package myapp.common;

struct Address {
    street: String,
    city: String,
    country: String,
}
// users.fl
package myapp.users;

use myapp.common.Address;

struct User {
    name: String,
    home_address: Address,
}

Doc Comments

Lines starting with /// become doc comments in Rust, JSDoc comments in TypeScript, and documentation comments in Swift:

/// A user in the system.
/// Created during registration.
struct User {
    /// Unique identifier
    id: String,
}

Attributes

Attribute Applies to Effect
#[rename = "name"] fields, variants Rename in JSON
#[alias = "alt"] fields Accept alternate name during deserialization
#[default] fields Use Default::default() if missing
#[skip_if_none] fields Omit if None
#[skip_if_default] fields Omit if default value
#[flatten] fields Flatten nested struct into parent
#[deprecated] types, fields Mark as deprecated
#[type_tag = "..."] unions Tag field name (default: "type")
#[content_tag = "..."] unions Content field name (default: "value")

Note: All fields are serialized as camelCase by default. Use #[rename = "..."] to override individual fields when needed.

Type Reference

Fluorite Type Rust TypeScript Swift
String String string String
bool bool boolean Bool
i32, i64 i32, i64 number Int32, Int64
u32, u64 u32, u64 number UInt32, UInt64
f32, f64 f32, f64 number Float, Double
Uuid uuid::Uuid string UUID
Decimal rust_decimal::Decimal string Decimal
Bytes Vec<u8> string Data
Url url::Url string URL
DateTime, DateTimeUtc, DateTimeTz chrono types string Date
Date, Time, Duration chrono types string String
Timestamp, TimestampMillis i64 number Date
Any fluorite::Any unknown AnyCodable
Option<T> Option<T> T | undefined (optional field) T?
Vec<T> Vec<T> T[] [T]
Map<K, V> HashMap<K, V> Record<K, V> [K: V]

Using Fluorite in a Rust Project

For Rust projects, the recommended approach is build.rs integration so types are generated at compile time.

See the examples/demo project for a complete working example.

1. Add Dependencies

[dependencies]
serde = { version = "1.0", features = ["serde_derive"] }
fluorite = "0.2"
derive-new = "0.7"

[build-dependencies]
fluorite_codegen = "0.2"

2. Create build.rs

use fluorite_codegen::code_gen::rust::RustOptions;

fn main() {
    let out_dir = std::env::var("OUT_DIR").unwrap();
    let options = RustOptions::new(&out_dir)
        .with_any_type("serde_json::Value")
        .with_single_file(true);
    fluorite_codegen::compile_with_options(options, &["schemas/myapp.fl"]).unwrap();
}

3. Include Generated Code

mod myapp {
    include!(concat!(env!("OUT_DIR"), "/myapp/mod.rs"));
}

use myapp::User;

Rust Options

RustOptions::new(output_dir)
    .with_single_file(true)              // All types in one mod.rs (default: true)
    .with_any_type("serde_json::Value")  // Map `Any` to this type
    .with_derives(vec!["Debug", "Clone"]) // Replace default derives
    .with_additional_derives(vec!["Hash"]) // Add extra derives
    .with_generate_new(true)             // Add derive_new::new (default: true)
    .with_visibility(Visibility::Public) // Type visibility (default: public)

Using Fluorite in a TypeScript Project

Via npm

npm install -D @zhxiaogg/fluorite-cli

Add to package.json:

{
  "scripts": {
    "generate": "fluorite ts --inputs ./schemas/*.fl --output ./src/generated",
    "build": "npm run generate && tsc"
  }
}

See the examples/demo-ts project for a complete working example.

Via Rust API

use fluorite_codegen::code_gen::ts::TypeScriptOptions;

let options = TypeScriptOptions::new("./src/generated")
    .with_single_file(true)
    .with_readonly(true);

fluorite_codegen::compile_ts_with_options(options, &["schemas/users.fl"]).unwrap();

TypeScript Options

TypeScriptOptions::new(output_dir)
    .with_single_file(true)        // All types in index.ts (default: false)
    .with_any_type("any")          // Map `Any` to this type (default: "unknown")
    .with_readonly(true)           // Generate readonly properties (default: false)
    .with_package_name("custom")   // Override output directory name

Using Fluorite for Swift

Generate Swift Codable types for iOS/macOS projects.

Via CLI

fluorite swift --inputs schemas/users.fl --output ./Sources/Generated

Via Rust API

use fluorite_codegen::code_gen::swift::SwiftOptions;

let options = SwiftOptions::new("./Sources/Generated")
    .with_single_file(false)
    .with_visibility(SwiftVisibility::Public);

fluorite_codegen::compile_swift_with_options(options, &["schemas/users.fl"]).unwrap();

Swift Options

SwiftOptions::new(output_dir)
    .with_single_file(false)       // Separate file per type (default: false)
    .with_any_type("AnyCodable")   // Map `Any` to this type (default: "AnyCodable")
    .with_visibility(SwiftVisibility::Public)  // public, internal, or package

Generated Swift types conform to Codable, Equatable, and Sendable. Unions include a custom Codable implementation for adjacently tagged JSON format.


CLI Reference

fluorite <COMMAND>

Commands:
  rust    Generate Rust code
  ts      Generate TypeScript code
  swift   Generate Swift code

fluorite rust

Flag Default Description
--inputs required Input .fl files
--output required Output directory
--single-file true Put all types in one mod.rs
--any-type fluorite::Any Rust type for Any
--derives Custom derives (replaces defaults)
--extra-derives Additional derives
--generate-new true Generate derive_new::new
--visibility public Type visibility

fluorite ts

Flag Default Description
--inputs required Input .fl files
--output required Output directory
--single-file false Put all types in one index.ts
--any-type unknown TypeScript type for Any
--readonly false Generate readonly properties
--package-name Override output directory name

fluorite swift

Flag Default Description
--inputs required Input .fl files
--output required Output directory
--single-file false Separate file per type or all in one
--any-type AnyCodable Swift type for Any
--visibility public Access level: public, internal, package

Examples

The examples/ directory contains complete projects:

  • examples/demo — Rust project with build.rs integration, multi-package schemas, and cross-package imports
  • examples/demo-ts — TypeScript project using generated types from the same schemas

The demo schemas in examples/demo/fluorite/ show real-world patterns:

File What it demonstrates
common.fl Shared types, skip_if_none, Any type
users.fl Cross-package imports, tagged unions, type aliases
orders.fl Multiple imports, enums, complex structs
notifications.fl Unions with primitive variants (PlainText(String))

Development

# Build
cargo build

# Run all tests
cargo test

# Run all CI checks (format, lint, test)
make all

# Run Rust <-> TypeScript interop tests
make interop-test
Make target Description
make all Format check + lint + test
make test Run all tests
make fmt Format code
make lint Run clippy
make interop-test Rust/TypeScript round-trip tests

License

MIT

Dependencies

~8MB
~142K SLoC