Skip to content

Generate TypeScript Client for IPC

Info

This is an experimental feature. You may also encounter some bugsβ€”please report them on GitHub issues!


Since v0.7, pytauri natively supports generating a TypeScript client for Commands. This makes calling pytauri commands in TypeScript very simple and type-safe.

Install Dependencies

pytauri uses pydantic to generate jsonschema, then calls json-schema-to-typescript to convert it into TypeScript type definitions.

Install the dependencies with the following commands:

pnpm add json-schema-to-typescript --save-dev

pnpm json2ts --help  # check if installed correctly

Usage Example

Commands.experimental_gen_ts_background

src-tauri/python/tauri_app/__init__.py
from os import getenv
from pathlib import Path

from anyio.from_thread import start_blocking_portal
from pydantic import BaseModel, ConfigDict
from pydantic.alias_generators import to_camel
from pytauri import (
    Commands,
    builder_factory,
    context_factory,
)

# ⭐ You should only enable this feature in development (not production)
PYTAURI_GEN_TS = getenv("PYTAURI_GEN_TS") != "0"

# ⭐ Enable this feature first
commands = Commands(experimental_gen_ts=PYTAURI_GEN_TS)


class _BaseModel(BaseModel):
    model_config = ConfigDict(
        # Accepts camelCase js ipc arguments for snake_case python fields.
        #
        # See: <https://docs.pydantic.dev/2.10/concepts/alias/#using-an-aliasgenerator>
        alias_generator=to_camel,
        # By default, pydantic allows unknown fields,
        # which results in TypeScript types having `[key: string]: unknown`.
        #
        # See: <https://docs.pydantic.dev/2.10/concepts/models/#extra-data>
        extra="forbid",
    )


class Person(_BaseModel):
    """A simple model representing a person.

    @property name - The name of the person.
    """

    # πŸ‘† This pydoc will be converted to tsdoc
    name: str


# ⭐ Just use `commands` as usual
@commands.command()
async def greet_to_person(body: Person) -> str:
    """A simple command that returns a greeting message.

    @param body - The person to greet.
    """
    # πŸ‘† This pydoc will be converted to tsdoc
    return f"Hello, {body.name}!"


def main() -> int:
    with start_blocking_portal("asyncio") as portal:
        if PYTAURI_GEN_TS:
            # ⭐ Generate TypeScript Client to your frontend `src/client` directory
            output_dir = Path(__file__).parent.parent.parent.parent / "src" / "client"
            # ⭐ The CLI to run `json-schema-to-typescript`,
            # `--format=false` is optional to improve performance
            json2ts_cmd = "pnpm json2ts --format=false"

            # ⭐ Start the background task to generate TypeScript types
            portal.start_task_soon(
                lambda: commands.experimental_gen_ts_background(
                    output_dir, json2ts_cmd, cmd_alias=to_camel
                )
            )

        app = builder_factory().build(
            context=context_factory(),
            invoke_handler=commands.generate_handler(portal),
        )
        exit_code = app.run_return()
        return exit_code

When you run your app (pnpm tauri dev), the TypeScript client code will be generated in the src/client directory:

Tip

Every time you modify a command, the TypeScript client code will automatically update after hot reload. Awesome πŸŽ‰!

src/client/apiClient.ts
/* eslint-disable */
/**
 * This file was automatically generated by pytauri-gen-ts.
 * DO NOT MODIFY IT BY HAND. Instead, modify the source commands API,
 * and run pytauri-gen-ts to regenerate this file.
 */

import { pyInvoke } from "tauri-plugin-pytauri-api";
import { InvokeOptions } from "@tauri-apps/api/core";

import type { Commands } from "./_apiTypes.d.ts";

/**
 * A simple command that returns a greeting message.
 *
 * @param body - The person to greet.
 */
export async function greetToPerson(
    body: Commands["greet_to_person"]["input"],
    options?: InvokeOptions
): Promise<Commands["greet_to_person"]["output"]> {
    return await pyInvoke("greet_to_person", body, options);
}

FAQ

JS Channel and PY JavaScriptChannelId Type Mismatch

Argument of type 'Channel<unknown>' is not assignable to parameter of type 'string'.ts(2345)

JavaScriptChannelId is serialized and deserialized as a string between the frontend and backend, so the generated ts type is string.

You just need to pass Channel.toJSON() (i.e., JSON.stringify(Channel)):

import { Channel } from "@tauri-apps/api/core";

const channel = new Channel();
await fooCmd(channel.toJSON()); // pass the channel as a string

This is a known limitation, but we don't know how to solve it yet. If you have a good suggestion, please open a GitHub issue πŸ˜‡.

How to Generate TS Types for Event and Channel?

Not supported yet. We haven't figured out an ergonomic API. If you have a good suggestion, please open a GitHub issue πŸ˜‡.