Svelte

Svelte + TanStack Svelte Query adapter with @pyrpc/svelte.

@pyrpc/svelte gives you typed createQuery / createMutation stores for every pyRPC procedure. Works with SvelteKit and plain Svelte.

Installation

npm install @pyrpc/svelte @tanstack/svelte-query

Project structure

my-app/
  src/
    lib/
      pyrpc.ts              ← createSvelteClient, import everywhere
    routes/
      +layout.svelte        ← QueryClientProvider wrapper
      +page.svelte          ← api.greet.createQuery() etc.

1. Create the client

src/lib/pyrpc.ts
import { createSvelteClient, httpBatchLink } from "@pyrpc/svelte"
import type { Types } from "@pyrpc/types"

export const api = createSvelteClient<Types>({
  links: [
    httpBatchLink({
      url: import.meta.env.VITE_API_URL ?? "http://localhost:8000",
    }),
  ],
})

2. Wrap the layout

Svelte uses TanStack Svelte Query's QueryClientProvider directly in the layout, the standard approach:

src/routes/+layout.svelte
<script lang="ts">
  import { QueryClient, QueryClientProvider } from "@tanstack/svelte-query"
  const queryClient = new QueryClient()
</script>

<QueryClientProvider client={queryClient}>
  <slot />
</QueryClientProvider>

3. Call procedures in pages

src/routes/+page.svelte
<script lang="ts">
  import { api } from "$lib/pyrpc"

  let name = ""

  // @rpc.query → createQuery (returns a store)
  const greeting = api.greet.createQuery()

  // pass reactive args as a getter function
  const item = api.read_item.createQuery(
    () => ({ item_id: 42, q: "test" })
  )

  // @rpc.mutation → createMutation (returns a store)
  const createItem = api.create_item.createMutation()

  function handleCreate() {
    if (name.trim()) {
      $createItem.mutate({ name, description: `Item: ${name}` })
      name = ""
    }
  }
</script>

{#if $greeting.isPending}
  <p>Loading…</p>
{:else}
  <pre>{JSON.stringify($greeting.data)}</pre>
{/if}

<pre>{JSON.stringify($item.data)}</pre>

<input bind:value={name} placeholder="Item name" />
<button on:click={handleCreate} disabled={$createItem.isPending}>
  {$createItem.isPending ? "Creating…" : "Create"}
</button>

{#if $createItem.isSuccess}
  <pre>{JSON.stringify($createItem.data)}</pre>
{/if}

Prefix the store with $ to subscribe to its value, this is standard Svelte store syntax.

Reactive args

When query args come from reactive state, use a getter function:

let itemId = 1
const item = api.get_item.createQuery(() => ({ id: itemId }))
// changing itemId triggers a refetch automatically

TypeScript

All parameter and return types are inferred from Python:

api.greet.createQuery()                       // ✓
api.read_item.createQuery(() => ({
  item_id: 42, q: "test"
}))                                           // ✓
api.read_item.createQuery(() => ({
  item_id: "oops"                             // ✗, type error
}))

Client config

OptionTypeDefaultDescription
linksLink[]requiredLink pipeline; exactly one terminating link (httpLink or httpBatchLink)
kindsProcedureKindMapgeneratedOverride generated procedure kinds

Invalidating after a mutation

Queries are cached under the key [QUERY_KEY_PREFIX, procedure, input] (QUERY_KEY_PREFIX is exported from @pyrpc/svelte). To invalidate after a mutation, use the shared TanStack Query client:

src/routes/+page.svelte
<script lang="ts">
  import { useQueryClient } from "@tanstack/svelte-query"
  import { QUERY_KEY_PREFIX } from "@pyrpc/svelte"
  import { api } from "$lib/pyrpc"

  const queryClient = useQueryClient()
  const createItem = api.create_item.createMutation()

  function handleCreate() {
    createItem.mutate(
      { name: "Item" },
      {
        onSuccess: () => {
          queryClient.invalidateQueries({ queryKey: [QUERY_KEY_PREFIX, "list_items"] })
        },
      }
    )
  }
</script>

Vanilla escape hatch

Every procedure also stays callable as a plain promise via api.client, which is the underlying vanilla client:

const greeting = await api.client.greet({ name: "World" })

Full working examples