Quick Start

Note

All @rescript-tauri/core modules are feature-complete in main. The samples below reflect the actual .resi signatures shipped on main. The package is awaiting its first npm publish (v0.1.0); until then, consume @rescript-tauri/core via the source repository or a workspace link.

Prerequisites

  • Completed Installation

  • A Tauri 2.x project with at least one #[tauri::command] defined on the Rust side

Layer 1 — Raw invoke

The lowest layer mirrors @tauri-apps/api/core 1:1. Use it when you want to start as thin as possible or as an escape hatch from the typed layer.

let greeting: string =
  await Tauri.Core.Raw.invoke("greet", ~args={"name": "ReScript"})

Layer 2 — typed Command

Layer 2 wraps a single command name with an explicit encoder and decoder, giving you an end-to-end typed Command.t<'args, 'result>.

module Commands = {
  let greet = Core.Command.make(
    ~name="greet",
    ~encodeArgs=({name}) =>
      JSON.Encode.object([("name", JSON.Encode.string(name))]),
    ~decodeResult=json =>
      switch json->JSON.Decode.string {
      | Some(s) => Ok(s)
      | None => Error("expected string")
      },
  )
}

switch await Commands.greet->Core.Command.invoke({name: "ReScript"}) {
| Ok(message) => Console.log(message)
| Error(DecodeError(msg)) => Console.error("decode failed: " ++ msg)
| Error(RustError(json)) => Console.error2("rust error:", json)
}

Core.Command.invokeExn is provided for callers that prefer exception-based control flow.

Event subscription

Event.make declares a typed event handle once; Event.listen subscribes and returns an unlisten function.

let fileChanged = Event.make(
  ~name="file-changed",
  ~decode=json =>
    switch json->JSON.Decode.string {
    | Some(s) => Ok(s)
    | None => Error("expected string")
    },
)

let unlisten = await fileChanged->Event.listen(result =>
  switch result {
  | Ok(evt) => Console.log(evt.payload)
  | Error(_) => () // ignore decode failures
  }
)
// ... later
unlisten()

Window operations

Window.t is opaque; instance methods are pipe-first.

open Tauri

let win = Window.getCurrent()
await win->Window.setTitle("Hello, ReScript")
await win->Window.maximize

let size = Dpi.LogicalSize.make(~width=1024.0, ~height=768.0)
await win->Window.setSize(size)

A complete buttoned demo is in examples/window-management.

Spawning a WebviewWindow

open Tauri

let secondary = WebviewWindow.make(
  "secondary",
  ~options={
    url: "/",
    title: "Secondary",
    width: 480.0,
    height: 320.0,
  },
)

// Same instance can be viewed as a Window for window-only methods.
await secondary->WebviewWindow.asWindow->Window.setAlwaysOnTop(true)

Streaming with Channel

Core.Channel provides one-way Rust-to-frontend streaming. Each callback receives a result<'message, string> so decoder failures are surfaced explicitly — pattern match on Ok(...) / Error(_) to choose how to handle them.

let counter = Core.Channel.make(~decode=json =>
  switch json->JSON.Decode.float {
  | Some(f) => Ok(Float.toInt(f))
  | None => Error("expected number")
  }
)

counter->Core.Channel.onMessage(result =>
  switch result {
  | Ok(n) => Console.log2("recv", n)
  | Error(_) => () // ignore decode failures
  }
)

let countTo = Core.Command.make(
  ~name="count_to",
  ~encodeArgs=({channel, target}) =>
    JSON.Encode.object(
      Dict.fromArray([
        ("channel", Obj.magic(channel)),
        ("target", JSON.Encode.float(Int.toFloat(target))),
      ]),
    ),
  ~decodeResult=_ => Ok(),
)

let _ = await countTo->Core.Command.invoke({channel: counter, target: 10})

A complete demo (with the matching Rust handler) is in examples/streaming-ipc.

Path / App utilities

let configDir = await Path.appConfigDir()
let appName = await App.getName()
Console.log3("ready:", appName, configDir)

Path is intentionally not re-exported by Tauri, so reach for it explicitly (Path.appConfigDir()) instead of open Tauri’ing it into scope alongside Window / Event.

What’s next?