@rescript-tauri/schema¶
Layer 3 typed IPC for @rescript-tauri/core. Declare a Tauri
command from a single
rescript-schema
schema pair instead of writing the encoder and decoder by hand.
Note
This package is feature-complete in main. The first npm publish
(schema-v0.1.0) is scheduled alongside the other packages. Until
then, consume @rescript-tauri/schema via the source repository or
a workspace link.
Install¶
pnpm add @rescript-tauri/schema rescript-schema
@rescript-tauri/schema declares both @rescript-tauri/core and
rescript-schema as peerDependencies. Add it to your
rescript.json:
{
"dependencies": [
"@rescript-tauri/core",
"@rescript-tauri/schema",
"rescript-schema"
]
}
No Rust side change is required — schema is purely a frontend-side helper.
Why Layer 3?¶
Layer 2 (Core.Command.make) wraps a single command name with an
explicit encoder and decoder:
let greet = Core.Command.make(
~name="greet",
~encodeArgs=(name: string) => JSON.Encode.object(
Dict.fromArray([("name", JSON.Encode.string(name))])
),
~decodeResult=json =>
switch json->JSON.Decode.string {
| Some(s) => Ok(s)
| None => Error("expected string")
},
)
Layer 3 collapses the same command into a single declaration:
open RescriptTauriSchema
let greet = Schema.fromSchemas(
~name="greet",
~args=Schema.S.object(s => {name: s.field("name", Schema.S.string)}),
~result=Schema.S.string,
)
Both layers return Core.Command.t<'args, 'result>, so callers
of Core.Command.invoke don’t have to care which layer was used
to declare the command.
Minimal example¶
open RescriptTauriCore.Tauri
open RescriptTauriSchema
type greetArgs = {name: string}
let greet = Schema.fromSchemas(
~name="greet",
~args=Schema.S.object(s => {name: s.field("name", Schema.S.string)}),
~result=Schema.S.string,
)
switch await 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)
}
S.parseJsonOrThrow failures are caught inside toDecoder and
surfaced as Error(DecodeError(message)), matching the existing
Core.Command.invoke error variant.
Public API¶
Entry point |
Purpose |
|---|---|
|
Typed command from a |
|
Schema-decoded |
|
Schema-decoded |
|
Lower-level helper that returns the |
Schema.S re-exports RescriptSchema.S, so a caller only
imports @rescript-tauri/schema and reaches the schema DSL
through one path.
Channel example¶
The receive side of a Tauri Channel<u32> becomes a one-liner:
let countChannel = Schema.channelFromSchema(~message=Schema.S.int)
Core.Channel.onMessage(countChannel, result =>
switch result {
| Ok(n) => Console.log2("recv", n)
| Error(_msg) => () // ignore decode failures
}
)
The command that opens the channel still needs a hand-written
encoder because Core.Channel.t is opaque to schemas — see
examples/ipc-typed-with-schema
for the full pattern.
rescript-schema DSL pitfalls¶
field is a method, not a top-level function¶
rescript-schema 9.x exposes the field builder as a method on the
Object.s argument. Use the dot syntax:
// ✅
let s1 = Schema.S.object(s => {
name: s.field("name", Schema.S.string),
})
// ❌ compile error: `field` not in `Schema.S`
let s2 = Schema.S.object(s => {
name: s->Schema.S.field("name", Schema.S.string),
})
Tauri command argument flattening¶
Tauri 2.x’s tauri::command flattens single-record arguments into
the args object’s top-level keys. A Rust signature like
#[tauri::command]
fn summarize(title: &str, items: Vec<String>) -> Summary { /* … */ }
expects an args JSON of {"title": ..., "items": ...}, not a
nested {"args": {"title": ...}}. Author your S.object schema
against the flat shape.
Compatibility¶
Component |
Supported range |
|---|---|
Upstream |
|
|
|
ReScript |
|
|
|
rescript-struct is not supported. The upstream package was
deprecated in favor of rescript-schema (confirmed 2026-05-09 —
recorded in RFC-0002 §2.1).
See also¶
Live demo:
examples/ipc-typed-with-schema— pairs againstexamples/ipc-typed/to show the same commands in both Layer 2 and Layer 3 form.Source:
packages/schemaRFC-0002: Schema integration design