@rescript-tauri/schema

@rescript-tauri/core 向けの Layer 3 型付き IPC です。エンコーダ・デコーダを 手書きする代わりに、rescript-schema のスキーマ 1 ペアから Tauri コマンドを宣言できます。

注釈

本パッケージは main で機能完備済みです。初回 npm 公開(schema-v0.1.0)は他のパッケージと合わせて予定されています。それまでは、ソースリポジトリ経由かワークスペースリンクで @rescript-tauri/schema を利用してください。

インストール

pnpm add @rescript-tauri/schema rescript-schema

@rescript-tauri/schema@rescript-tauri/corerescript-schema の両方を peerDependencies として宣言します。rescript.json に追加してください:

{
  "dependencies": [
    "@rescript-tauri/core",
    "@rescript-tauri/schema",
    "rescript-schema"
  ]
}

Rust 側の変更は不要です。schema は純粋にフロントエンド側のヘルパーです。

なぜ Layer 3 が必要か

Layer 2(Core.Command.make)はコマンド名を明示的なエンコーダ・デコーダとともに ラップします:

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 では同じコマンドを 1 つの宣言にまとめられます:

open RescriptTauriSchema

let greet = Schema.fromSchemas(
  ~name="greet",
  ~args=Schema.S.object(s => {name: s.field("name", Schema.S.string)}),
  ~result=Schema.S.string,
)

両方の Layer とも Core.Command.t<'args, 'result> を返すため、Core.Command.invoke の呼び出し側はコマンドがどちらの Layer で宣言されたかを 意識する必要はありません。

最小例

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 の失敗は toDecoder 内で捕捉され、既存の Core.Command.invoke のエラー variant に揃えて Error(DecodeError(message)) として表面化されます。

公開 API

エントリポイント

用途

Schema.fromSchemas

S.t<'args>S.t<'result> のペアから型付きコマンドを生成

Schema.channelFromSchema

スキーマでデコードする Core.Channel.t<'message>

Schema.eventFromSchema

スキーマでデコードする Event.t<'payload>

Schema.toDecoder

内部で利用される Core.decoder<_> を返す低レベルヘルパー。手書きの Core.Command.make にスキーマデコーダを差し込みたいときに役立つ

Schema.SRescriptSchema.S を再エクスポートするため、利用者は @rescript-tauri/schema だけをインポートすればスキーマ DSL に 1 経路で アクセスできます。

Channel の例

Tauri の Channel<u32> の受信側は 1 行で書けるようになります:

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
  }
)

Channel を開くコマンドは依然として手書きのエンコーダが必要です。Core.Channel.t は スキーマから見て不透明なためです。完全な手順は examples/ipc-typed-with-schema を参照してください。

rescript-schema DSL の落とし穴

field はトップレベル関数ではなくメソッド

rescript-schema 9.x ではフィールドビルダを Object.s 引数のメソッドとして公開しています。ドット記法で呼び出してください:

// ✅
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 コマンド引数のフラット化

Tauri 2.x の tauri::command は、単一レコード引数を args オブジェクトの トップレベルキーにフラット化します。次のような Rust シグネチャでは:

#[tauri::command]
fn summarize(title: &str, items: Vec<String>) -> Summary { /* … */ }

args の JSON は {"title": ..., "items": ...} を期待し、{"args": {"title": ...}} のようなネストではありません。S.object スキーマは このフラット形状に合わせて記述してください。

互換性

コンポーネント

サポート範囲

上流の rescript-schema

^9.0.0(peer)

@rescript-tauri/core

^0.1.0(peer)

ReScript

>=12.0.0

@rescript/core

>=1.6.0

rescript-struct非対応 です。上流のパッケージは rescript-schema への 置き換え方針で deprecated 済みです(2026-05-09 確認、RFC-0002 §2.1 に記録)。

関連情報