# `tRPC` - `svelte-query` Adapter

[![NPM version][npm-image]][npm-url]
[![License][license-image]][license-url]
[![Last commit][last-commit-image]][repo-url]

> [!NOTE]
> The README on [npmjs](https://npmjs.com/trpc-svelte-query-adapter) might not be fully up to date. Please refer to
> the [README on the Github Repo](https://github.com/vishalbalaji/trpc-svelte-query-adapter/#readme) for the latest setup instructions.

An adapter to call `tRPC` procedures wrapped with <code>[@tanstack/svelte-query](https://tanstack.com/query/latest/docs/svelte/overview)</code>, similar to <code>[@trpc/react-query](https://trpc.io/docs/react-query)</code>. This is made possible using <code>[proxy-deep](https://www.npmjs.com/package/proxy-deep)</code>.

## Installation

```sh
# npm
npm install trpc-svelte-query-adapter @trpc/client @trpc/server @tanstack/svelte-query

# yarn
yarn add trpc-svelte-query-adapter @trpc/client @trpc/server @tanstack/svelte-query

# pnpm
pnpm add trpc-svelte-query-adapter @trpc/client @trpc/server @tanstack/svelte-query
```

If you are using client-side Svelte, you would need to install `@trpc/server` as a `devDependency` using `--save-dev`.

## Available Functions

The following functions from `@trpc/react-query` are ported over:

- `useQuery` -> `createQuery`
- `useInfiniteQuery` -> `createInfiniteQuery`
- `useMutation` -> `createMutation`
- `useSubscription` -> `createSubscription`
- `useQueries` -> `createQueries`
- `useUtils` -> `createUtils`
- `getQueryKey`

You can refer to <code>[tanstack-query docs](https://tanstack.com/query/latest/docs/react/overview)</code> and <code>[@trpc/react-query docs](https://trpc.io/docs/react-query)</code> for documentation on how to use them.

There are also some new procedures that are only relevant for SvelteKit:

- `createServerQuery`
- `createServerInfiniteQuery`
- `createServerQueries`

As for these procedures, you can refer to the [Server-Side Query Pre-Fetching](#server-side-query-pre-fetching) section.

## Usage

The following instructions assume the `tRPC` router to have the following procedures:

```typescript
export const router = t.router({
  greeting: t.procedure
    .input((name: unknown) => {
      if (typeof name === 'string') return name;

      throw new Error(`Invalid input: ${typeof name}`);
    })
    .query(async ({ input }) => {
      return `Hello, ${input} from tRPC v10 @ ${new Date().toLocaleTimeString()}`;
    }),
});

export type Router = typeof router;
```

### Client-Only Svelte

1. Setup `@tanstack/svelte-query` as per [svelte-query docs](https://tanstack.com/query/latest/docs/svelte/overview).
2. Setup <code>[@trpc/client](https://trpc.io/docs/client)</code> and export the `tRPC` client.
3. Wrap the exported `tRPC` client with `svelteQueryWrapper` from `trpc-svelte-query-adapter`, as demonstrated in the example below:

```typescript
// src/lib/trpc.ts
import type { Router } from '/path/to/trpc/router';
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';

import { svelteQueryWrapper } from 'trpc-svelte-query-adapter';

const client = createTRPCProxyClient<Router>({
  links: [
    httpBatchLink({
      // Replace this URL with that of your tRPC server
      url: 'http://localhost:5000/api/v1/trpc/',
    }),
  ],
});

export const trpc = svelteQueryWrapper<Router>({ client });
```

4. The exported `tRPC` client can then be used in `svelte` components as follows:

```svelte
<script lang="ts">
  import { trpc } from "/path/to/lib/trpc";

  const foo = trpc.greeting.createQuery('foo', { retry: false });
</script>

{#if $foo.isPending}
  Loading...
{:else if $foo.isError}
  Error: {$foo.error.message}
{:else if $foo.data}
  {$foo.data.message}
{/if}
```

### SvelteKit and SSR

For SvelteKit, the process is pretty much the same as for client-only svelte. However, if you intend to call queries from the server in a `load` function, you would need to setup `@tanstack/svelte-query` according to the [the ssr example in the svelte-query docs](https://tanstack.com/query/latest/docs/svelte/ssr#using-prefetchquery).

Upon doing that, you would also need to pass in the `queryClient` to `svelteQueryWrapper` when initializing on the server, which you can get by calling the `event.parent` method in the `load` function. You can see an example of this in the [Server-Side Query Pre-Fetching](#server-side-query-pre-fetching) section. For this purpose, you might also want to export your client wrapped in a function that optionally takes in `queryClient` and passes it onto `svelteQueryWrapper`.

Here is an example of what that might look like:

```typescript
import type { QueryClient } from '@tanstack/svelte-query';

const client = createTRPCProxyClient<Router>({
  links: [
    httpBatchLink({
      // Replace this URL with that of your tRPC server
      url: 'http://localhost:5000/api/v1/trpc/',
    }),
  ],
});

export function trpc(queryClient?: QueryClient) {
  return svelteQueryWrapper<Router>({
    client,
    queryClient,
  });
}
```

Which can then be used in a component as such:

```svelte
<!-- routes/+page.svelte -->
<script lang="ts">
  import { trpc } from "$lib/trpc/client";

  const client = trpc();
  const foo = client.greeting.createQuery("foo", { retry: false });
</script>

<p>
  {#if $foo.isPending}
    Loading...
  {:else if $foo.isError}
    Error: {$foo.error.message}
  {:else}
    {$foo.data}
  {/if}
</p>
```

The main thing that needs to passed in to `svelteQueryWrapper` is the `tRPC` client itself. So, this adapter should support different implementations of `tRPC` for Svelte and SvelteKit. For example, if you are using <code>[trpc-sveltekit by icflorescu](https://icflorescu.github.io/trpc-sveltekit)</code>, all you would need to do after setting it up would be to change the client initialization function from something like this:

```typescript
let browserClient: ReturnType<typeof createTRPCClient<Router>>;

export function trpc(init?: TRPCClientInit) {
  const isBrowser = typeof window !== 'undefined';
  if (isBrowser && browserClient) return browserClient;
  const client = createTRPCClient<Router>({ init });
  if (isBrowser) browserClient = client;
  return client;
}
```

to this:

```typescript
import { svelteQueryWrapper } from 'trpc-svelte-query-adapter';
import type { QueryClient } from '@tanstack/svelte-query';

let browserClient: ReturnType<typeof svelteQueryWrapper<Router>>;

export function trpc(init?: TRPCClientInit, queryClient?: QueryClient) {
  const isBrowser = typeof window !== 'undefined';
  if (isBrowser && browserClient) return browserClient;
  const client = svelteQueryWrapper<Router>({
    client: createTRPCClient<Router>({ init }),
    queryClient,
  });
  if (isBrowser) browserClient = client;
  return client;
}
```

Which can then be initialized and used in the way that it is described [in its docs](https://icflorescu.github.io/trpc-sveltekit/getting-started).

#### Server-Side Query Pre-Fetching

This adapter provides 3 additional procedures: `createServerQuery`, `createServerInfiniteQuery` and `createServerQueries`, which can be used to call their counterpart procedures in the `load` function in either a `+page.ts` or `+layout.ts`. These procedures return a `promise` and therefore can only really be called on the server.

By default, these 3 procedures will pre-fetch the data required to pre-render the page on the server. However, if you wish to disable this behaviour on certain queries, you can do so by setting the `ssr` option to `false`.

These procedures can be used as such:

> [!NOTE] 
> [Gotta await top-level promises to pre-fetch data from SvelteKit v2](https://kit.svelte.dev/docs/migrating-to-sveltekit-2#top-level-promises-are-no-longer-awaited).

```typescript
// +page.ts
// tRPC is setup using `trpc-sveltekit` for this example.
import { trpc } from '$lib/trpc/client';
import type { PageLoad } from './$types';

export const load = (async (event) => {
  const { queryClient } = await event.parent();
  const client = trpc(event, queryClient);

  return {
    foo: await client.greeting.createServerQuery('foo'),
    queries: await client.createServerQueries(
      (t) =>
        ['bar', 'baz'].map((name) => t.greeting(name, { ssr: name !== 'baz' })) // pre-fetching disabled for the `baz` query.
    ),
  };
}) satisfies PageLoad;
```

Then, in the component:

```svelte
<!-- +page.svelte -->
<script lang="ts">
  import { page } from "$app/stores";
  import type { PageData } from "./$types";

  export let data: PageData;

  const foo = data.foo();
  const queries = data.queries();
</script>

{#if $foo.isPending}
  Loading...
{:else if $foo.isError}
  {$foo.error}
{:else if $foo.data}
  {$foo.data}
{/if}
<br /><br />

{#each $queries as query}
  {#if query.isPending}
    Loading...
  {:else if query.isError}
    {query.error.message}
  {:else if query.data}
    {query.data}
  {/if}
  <br />
{/each}
```

You can also optionally pass new inputs to the queries and infinite queries from the client side(see [#34](/../../issues/34), [#47]( /../../issues/47 )) like so:

```svelte
<script lang="ts">
  import { page } from "$app/stores";
  import type { PageData } from "./$types";

  import { derived, writable } from '@svelte/store';

  export let data: PageData;

  const name = writable('foo');
  const newNames = writable<string[]>([]);

  const foo = data.foo($name);

  // You can also access the default input if you pass in a callback as the new input:
  // const foo = data.foo((old) => derived(name, ($name) => old + name));

  const queries = data.queries((t, old) => derived(newNames, ($newNames) => [...old, ...$newNames.map((name) => t.greeting(name))]));
</script>

<div>
  {#if $foo.isPending}
    Loading...
  {:else if $foo.isError}
    {$foo.error}
    {:else if $foo.data}
    {$foo.data}
  {/if}
  <input bind:value={$name} />
</div>

<br />

<div>
  {#each $queries as query}
    {#if query.isPending}
      Loading...
    {:else if query.isError}
      {query.error.message}
      {:else if query.data}
      {query.data}
    {/if}
    <br />
  {/each}

  <form on:submit|preventDefault={(e) => {
    const data = new FormData(e.currentTarget).get('name');
    if (typeof data === 'string') $newNames.push(data);
    $newNames = $newNames;
  }}>
    <input name="name" />
    <button type="submit">Submit</button>
  </form>
</div>
```

For more usage examples, you can refer to the [example app provided in the repo](/@example).

[npm-url]: https://npmjs.org/package/trpc-svelte-query-adapter
[npm-image]: https://img.shields.io/npm/v/trpc-svelte-query-adapter.svg
[license-url]: LICENSE
[license-image]: http://img.shields.io/npm/l/trpc-svelte-query-adapter.svg
[repo-url]: https://github.com/vishalbalaji/trpc-svelte-query-adapter
[last-commit-image]: https://img.shields.io/github/last-commit/vishalbalaji/trpc-svelte-query-adapter
