dodo-bindgen-cli 2.0.0

A CLI tool for generating TypeScript bindings for libraries built with the `dodo_bindgen` crate
# dodo-bindgen

`dodo-bindgen` is an FFI bindings generator that simplifies writing Rust libraries for Dodo.

It works by providing procedural macros that create wrappers around native Rust functions and make it FFI-safe. This makes writing FFI libraries easier as it deals with boilerplate code generation so you don't have to.

It also provides a CLI tool that generates a TypeScript module and tries to follow the semantics of your code wherever possible.

This project aims to empower TypeScript development by bridging access to Rust's powerful memory-safe ecosystem.

## To use

Add the `dodo-bindgen` crate to your library crate's dependencies (note the spelling and dash `-` separator)

```toml
# Cargo.toml
[dependencies]
dodo-bindgen = "2.0"
```

Use the `dodo_bindgen` attribute macro on a function, implement, or struct item in your source code:

```rust
// lib.rs
use dodo_bindgen::dodo_bindgen;

#[dodo_bindgen]
fn test_1() {
    println!("Hello, world!");
}

#[dodo_bindgen]
fn test_2(string: String) -> String {
    format!("{} to Rust!", string)
}
```

Next, to generate the bindings and TypeScript code, you must install the CLI tool with the command below:

```sh
cargo install dodo-bindgen-cli
```

Then, run this command in your project's package folder (not the workspace folder).

```sh
dodo-bindgen
```

This will automatically generate a TypeScript module in `<pkg_root>/lib/<your_module>`, along with another module `rust_type.ts` that contains TypeScript representations of Rust types. Generated files are formatted with `deno fmt`, so your `deno.json` formatting options apply.

Finally, you can write your TypeScript code and import the functions from the Rust code. Function and method names are converted to camelCase (`test_1` becomes `test1`), and a static `fn new() -> Self` becomes the class constructor.

```ts
// hello_world.ts
import { RustString, test1, test2 } from "./lib/libmy_mod.ts";

test1();

Deno.test("test_string", () => {
    // create a `RustString` instance from a JavaScript string
    // by calling the `from()` static method
    const hello_string = test2(RustString.from("Hello from Deno"));

    // turn the RustString into a JavaScript string
    // by calling its `into()` method
    console.log(hello_string.into());
})

```

To run:

```bash
deno run --allow-all --unstable-ffi hello_world.ts
```

This should output:

```text
Hello, world!
Hello from Deno to Rust!
```

For additional code generation options, run `dodo-bindgen --help`

### Primitive types

Numeric types map to `number` (64-bit and pointer-sized integers to `bigint`), `bool` to `boolean` and `char` to a single-character `string`.

### JavaScript strings

`String` values cross the boundary as `RustString` handles. To work with plain JavaScript strings instead, use `JsStr` (borrowed, parameters only) and `JsString` (owned, parameters and return values):

```rust
#[dodo_bindgen]
fn greet(name: JsStr) -> JsString {
    format!("Hello, {name}!").into()
}
```

```ts
greet("Deno"); // "Hello, Deno!"
```

### Typed arrays

JavaScript typed arrays map to `JsSlice<T>` (borrowed, zero-copy), `JsSliceMut<T>` (borrowed mutably, zero-copy, writes are visible to JavaScript) and `JsVec<T>` (owned; copied from parameters, returned as a fresh typed array). `T` is one of `u8`, `i8`, `u16`, `i16`, `u32`, `i32`, `u64`, `i64`, `f32` or `f64`:

```rust
#[dodo_bindgen]
fn scale(mut values: JsSliceMut<f32>, factor: f32) {
    for value in values.iter_mut() {
        *value *= factor;
    }
}

#[dodo_bindgen]
fn range(len: u32) -> JsVec<u32> {
    (0..len).collect()
}
```

```ts
const values = new Float32Array([1, 2, 3]);
scale(values, 2); // values is now [2, 4, 6]
range(3); // Uint32Array [0, 1, 2]
```

Buffers passed to `non_blocking` functions are kept alive until the returned promise settles.

### Extending foreign types

Types from other crates can be exposed as TypeScript classes with a `foreign` impl block. The block is turned into a private extension trait, so the orphan rule does not apply. Concrete instances of generic types work too:

```rust
#[dodo_bindgen(foreign, name = "Bytes")]
impl Vec<u8> {
    fn new() -> Self {
        Vec::new()
    }

    fn len(&self) -> usize {
        self.len()
    }
}
```

Every signature using `Vec<u8>` is then typed as `Bytes` in TypeScript. `RustString` itself is defined this way inside `dodo-bindgen`. Foreign classes that no exported item refers to are left out of the generated module.

### Custom TypeScript

Additional TypeScript class members can be attached to any exported struct, enum or impl block:

```rust
#[dodo_bindgen(ts = "override toString(): string { return this.into(); }")]
#[dodo_bindgen(ts = include_str!("my_class.ts"))]
```

Every generated class also implements `[Symbol.dispose]`, so instances can be released with `using`.

## Examples

Check out the [test directory](example/) to see some examples