Type-safe ReScript bindings for Tauri 2.0 - Build native desktop and mobile apps with ReScript.
rescript-tauri provides comprehensive ReScript bindings for the Tauri 2.0 JavaScript API, enabling type-safe development of native desktop and mobile applications.
-
Type-safe command bridge - Typed request/response pattern with error handling
-
Full event system - Listen, emit, and create typed event channels
-
Window management - Complete window API with properties and events
-
Plugin bindings - Filesystem, dialogs, and more
-
TEA integration - Examples using rescript-tea architecture
-
Zero npm option - Works with Deno imports
open Tauri
// Call a Tauri command
let result = await invoke("greet", ~args={"name": "World"})
Console.log(result) // "Hello, World!"The command bridge pattern provides compile-time type safety:
open Tauri
// Define a typed command
let greetCommand = Command.defineCommand(
~name="greet",
~encode=name => {"name": name},
~decode=json => {
switch JSON.Decode.string(json) {
| Some(s) => Ok(s)
| None => Error("Expected string")
}
},
)
// Execute with type safety
switch await Command.execute(greetCommand, "World") {
| Ok(greeting) => Console.log(greeting)
| Error(err) => Console.error(Command.CommandError.toString(err))
}// Execute with retry logic
let result = await Command.executeWithRetry(
greetCommand,
"World",
~maxRetries=3,
~delayMs=1000,
)
// Or use the fluent builder
let result = await Command.Builder.make(greetCommand)
->Command.Builder.withTimeout(5000)
->Command.Builder.withRetries(3)
->Command.Builder.run("World")open Tauri
// Listen for events
let unlisten = await Event.listen("my-event", event => {
Console.log(`Received: ${event.payload}`)
})
// Emit events
await Event.emit("my-event", ~payload="Hello from ReScript!")
// Clean up
unlisten()open Tauri
// Get current window
let window = Window.getCurrentWindow()
// Window operations
await window->Window.setTitle("My App")
await window->Window.center()
await window->Window.maximize()
// Or use the convenience module
await Window.Current.setTitle("My App")
await Window.Current.center()open Tauri
// Pick a file
let path = await Dialog.openSingle(
~options={
title: "Select a file",
filters: [Dialog.Filters.json, Dialog.Filters.all],
},
)
// Or use the fluent builder
let path = await Dialog.OpenDialog.make()
->Dialog.OpenDialog.title("Select a file")
->Dialog.OpenDialog.filter(Dialog.Filters.json)
->Dialog.OpenDialog.pickSingle()open Tauri
// Read a file
let content = await Fs.readTextFile("config.json", ~options={baseDir: AppConfig})
// Write a file
await Fs.writeTextFile("output.txt", "Hello!", ~options={baseDir: AppData})
// JSON helpers
switch await Fs.readJsonFile("data.json") {
| Ok(json) => Console.log(json)
| Error(err) => Console.error(err)
}| Module | Description |
|---|---|
|
Core invoke API, channels, resources, app info |
|
Event listeners, emitters, typed event channels |
|
Window management, properties, events |
|
Type-safe command bridge pattern |
|
Filesystem operations (requires @tauri-apps/plugin-fs) |
|
Native file/message dialogs (requires @tauri-apps/plugin-dialog) |
Some modules require Tauri plugins to be installed:
# Filesystem
npm install @tauri-apps/plugin-fs
# Dialogs
npm install @tauri-apps/plugin-dialogAnd add to tauri.conf.json:
{
"plugins": {
"fs": {
"scope": ["$APPDATA/**", "$RESOURCE/**"]
},
"dialog": {}
}
}rescript-tauri works seamlessly with rescript-tea:
type msg =
| LoadData
| DataLoaded(result<data, Tauri.Command.commandError>)
let update = (model, msg) => {
switch msg {
| LoadData => (
{...model, loading: true},
Cmd.call(dispatch => {
let _ = Tauri.Command.execute(loadCommand, ())
->Promise.thenResolve(result => dispatch(DataLoaded(result)))
}),
)
| DataLoaded(Ok(data)) => ({...model, data, loading: false}, Cmd.none)
| DataLoaded(Error(err)) => ({...model, error: Some(err), loading: false}, Cmd.none)
}
}This library is part of the rescript-full-stack ecosystem, providing the P4 (Mobile & Desktop) layer.