├── .github
└── workflows
│ └── npm-publish.yml
├── .gitignore
├── @example
├── .gitignore
├── .npmrc
├── .prettierignore
├── README.md
├── drizzle.config.ts
├── drizzle
│ ├── 0000_premium_pestilence.sql
│ └── meta
│ │ ├── 0000_snapshot.json
│ │ └── _journal.json
├── eslint.config.js
├── package.json
├── prettier.config.js
├── src
│ ├── app.d.ts
│ ├── app.html
│ ├── hooks.server.ts
│ ├── lib
│ │ ├── components
│ │ │ └── Heading.svelte
│ │ ├── index.ts
│ │ ├── server
│ │ │ └── db
│ │ │ │ ├── index.ts
│ │ │ │ └── schema.ts
│ │ ├── trpc
│ │ │ ├── client.ts
│ │ │ ├── context.ts
│ │ │ └── router.ts
│ │ └── utils.ts
│ └── routes
│ │ ├── (app)
│ │ ├── client-only
│ │ │ └── +page.svelte
│ │ ├── ssr-with-streaming
│ │ │ ├── +page.server.ts
│ │ │ └── +page.svelte
│ │ └── ssr
│ │ │ ├── +page.svelte
│ │ │ └── +page.ts
│ │ ├── +layout.svelte
│ │ ├── +layout.ts
│ │ └── +page.svelte
├── static
│ └── favicon.png
├── svelte.config.js
├── tsconfig.json
└── vite.config.ts
├── @lib
├── .npmignore
├── LICENSE
├── README.md
├── eslint.config.js
├── package.json
├── prettier.config.js
├── src
│ └── index.ts
└── tsconfig.json
├── @shared
├── eslint
│ ├── index.d.ts
│ ├── index.d.ts.map
│ └── index.js
├── package.json
└── prettier
│ ├── index.d.ts
│ ├── index.d.ts.map
│ └── index.js
├── README.md
├── package.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
└── tsconfig.base.json
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish Package to npmjs
2 |
3 | on:
4 | push:
5 | branches: main
6 |
7 | jobs:
8 | publish:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v4
12 | - uses: actions/setup-node@v3
13 | with:
14 | node-version: "20"
15 | - uses: JS-DevTools/npm-publish@v3
16 | with:
17 | token: ${{ secrets.NPM_TOKEN }}
18 | package: "./@lib/package.json"
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 | .pnpm-debug.log*
9 |
10 | # Diagnostic reports (https://nodejs.org/api/report.html)
11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 | *.lcov
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # Snowpack dependency directory (https://snowpack.dev/)
46 | web_modules/
47 |
48 | # TypeScript cache
49 | *.tsbuildinfo
50 |
51 | # Optional npm cache directory
52 | .npm
53 |
54 | # Optional eslint cache
55 | .eslintcache
56 |
57 | # Optional stylelint cache
58 | .stylelintcache
59 |
60 | # Microbundle cache
61 | .rpt2_cache/
62 | .rts2_cache_cjs/
63 | .rts2_cache_es/
64 | .rts2_cache_umd/
65 |
66 | # Optional REPL history
67 | .node_repl_history
68 |
69 | # Output of 'npm pack'
70 | *.tgz
71 |
72 | # Yarn Integrity file
73 | .yarn-integrity
74 |
75 | # dotenv environment variable files
76 | .env
77 | .env.development.local
78 | .env.test.local
79 | .env.production.local
80 | .env.local
81 |
82 | # parcel-bundler cache (https://parceljs.org/)
83 | .cache
84 | .parcel-cache
85 |
86 | # Next.js build output
87 | .next
88 | out
89 |
90 | # Nuxt.js build / generate output
91 | .nuxt
92 | dist
93 |
94 | # Gatsby files
95 | .cache/
96 | # Comment in the public line in if your project uses Gatsby and not Next.js
97 | # https://nextjs.org/blog/next-9-1#public-directory-support
98 | # public
99 |
100 | # vuepress build output
101 | .vuepress/dist
102 |
103 | # vuepress v2.x temp and cache directory
104 | .temp
105 | .cache
106 |
107 | # Docusaurus cache and generated files
108 | .docusaurus
109 |
110 | # Serverless directories
111 | .serverless/
112 |
113 | # FuseBox cache
114 | .fusebox/
115 |
116 | # DynamoDB Local files
117 | .dynamodb/
118 |
119 | # TernJS port file
120 | .tern-port
121 |
122 | # Stores VSCode versions used for testing VSCode extensions
123 | .vscode-test
124 |
125 | # yarn v2
126 | .yarn/cache
127 | .yarn/unplugged
128 | .yarn/build-state.yml
129 | .yarn/install-state.gz
130 | .pnp.*
131 |
132 | core.*
133 |
134 | *.db
135 |
--------------------------------------------------------------------------------
/@example/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | # Output
4 | .output
5 | .vercel
6 | /.svelte-kit
7 | /build
8 |
9 | # OS
10 | .DS_Store
11 | Thumbs.db
12 |
13 | # Env
14 | .env
15 | .env.*
16 | !.env.example
17 | !.env.test
18 |
19 | # Vite
20 | vite.config.js.timestamp-*
21 | vite.config.ts.timestamp-*
22 |
--------------------------------------------------------------------------------
/@example/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/@example/.prettierignore:
--------------------------------------------------------------------------------
1 | # Package Managers
2 | package-lock.json
3 | pnpm-lock.yaml
4 | yarn.lock
5 |
--------------------------------------------------------------------------------
/@example/README.md:
--------------------------------------------------------------------------------
1 | # create-svelte
2 |
3 | Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte).
4 |
5 | ## Creating a project
6 |
7 | If you're seeing this, you've probably already done this step. Congrats!
8 |
9 | ```bash
10 | # create a new project in the current directory
11 | npm create svelte@latest
12 |
13 | # create a new project in my-app
14 | npm create svelte@latest my-app
15 | ```
16 |
17 | ## Developing
18 |
19 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
20 |
21 | ```bash
22 | npm run dev
23 |
24 | # or start the server and open the app in a new browser tab
25 | npm run dev -- --open
26 | ```
27 |
28 | ## Building
29 |
30 | To create a production version of your app:
31 |
32 | ```bash
33 | npm run build
34 | ```
35 |
36 | You can preview the production build with `npm run preview`.
37 |
38 | > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
39 |
--------------------------------------------------------------------------------
/@example/drizzle.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'drizzle-kit';
2 |
3 | export default defineConfig({
4 | dialect: 'sqlite', // 'mysql' | 'sqlite' | 'postgresql'
5 | schema: './src/lib/server/db/schema.ts',
6 | out: './drizzle',
7 | dbCredentials: {
8 | url: './sqlite.db',
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/@example/drizzle/0000_premium_pestilence.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE `todo` (
2 | `id` integer PRIMARY KEY NOT NULL,
3 | `text` text NOT NULL,
4 | `done` integer
5 | );
6 |
--------------------------------------------------------------------------------
/@example/drizzle/meta/0000_snapshot.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "6",
3 | "dialect": "sqlite",
4 | "id": "ed5f3dde-bc69-4a62-940f-d13706e158c0",
5 | "prevId": "00000000-0000-0000-0000-000000000000",
6 | "tables": {
7 | "todo": {
8 | "name": "todo",
9 | "columns": {
10 | "id": {
11 | "name": "id",
12 | "type": "integer",
13 | "primaryKey": true,
14 | "notNull": true,
15 | "autoincrement": false
16 | },
17 | "text": {
18 | "name": "text",
19 | "type": "text",
20 | "primaryKey": false,
21 | "notNull": true,
22 | "autoincrement": false
23 | },
24 | "done": {
25 | "name": "done",
26 | "type": "integer",
27 | "primaryKey": false,
28 | "notNull": false,
29 | "autoincrement": false
30 | }
31 | },
32 | "indexes": {},
33 | "foreignKeys": {},
34 | "compositePrimaryKeys": {},
35 | "uniqueConstraints": {}
36 | }
37 | },
38 | "enums": {},
39 | "_meta": {
40 | "schemas": {},
41 | "tables": {},
42 | "columns": {}
43 | },
44 | "internal": {
45 | "indexes": {}
46 | }
47 | }
--------------------------------------------------------------------------------
/@example/drizzle/meta/_journal.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "7",
3 | "dialect": "sqlite",
4 | "entries": [
5 | {
6 | "idx": 0,
7 | "version": "6",
8 | "when": 1721139719463,
9 | "tag": "0000_premium_pestilence",
10 | "breakpoints": true
11 | }
12 | ]
13 | }
--------------------------------------------------------------------------------
/@example/eslint.config.js:
--------------------------------------------------------------------------------
1 | import ts from 'typescript-eslint';
2 | import svelte from 'eslint-plugin-svelte';
3 | import shared from 'shared/eslint';
4 |
5 | /** @type {import('eslint').Linter.FlatConfig[]} */
6 | export default [
7 | ...shared,
8 | ...svelte.configs['flat/recommended'],
9 | ...svelte.configs['flat/prettier'],
10 | {
11 | files: ['**/*.svelte'],
12 | languageOptions: {
13 | parserOptions: {
14 | parser: ts.parser,
15 | },
16 | },
17 | },
18 | {
19 | ignores: ['build/', '.svelte-kit/', 'dist/'],
20 | },
21 | ];
22 |
--------------------------------------------------------------------------------
/@example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "version": "0.0.1",
4 | "private": true,
5 | "description": "SvelteKit app for testing trpc-svelte-query-adapter",
6 | "scripts": {
7 | "dev": "vite dev",
8 | "build": "vite build",
9 | "preview": "vite preview",
10 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
11 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
12 | "lint": "prettier --check . && eslint .",
13 | "format": "prettier --write .",
14 | "db:generate": "drizzle-kit generate",
15 | "db:migrate": "drizzle-kit migrate",
16 | "db:studio": "drizzle-kit studio"
17 | },
18 | "devDependencies": {
19 | "@fontsource-variable/inter": "^5.0.19",
20 | "@sveltejs/adapter-auto": "^3.0.0",
21 | "@sveltejs/kit": "^2.0.0",
22 | "@sveltejs/vite-plugin-svelte": "^3.0.0",
23 | "@types/better-sqlite3": "^7.6.11",
24 | "@types/eslint": "^8.56.7",
25 | "drizzle-kit": "^0.23.0",
26 | "eslint": "^9.0.0",
27 | "eslint-plugin-svelte": "^2.36.0",
28 | "globals": "^15.0.0",
29 | "phosphor-svelte": "^2.0.1",
30 | "prettier": "^3.1.1",
31 | "prettier-plugin-svelte": "^3.1.2",
32 | "svelte": "^4.2.7",
33 | "svelte-check": "^3.6.0",
34 | "tslib": "^2.4.1",
35 | "typescript": "^5.0.0",
36 | "typescript-eslint": "^8.0.0-alpha.20",
37 | "typescript-svelte-plugin": "^0.3.39",
38 | "vite": "^5.0.3"
39 | },
40 | "type": "module",
41 | "dependencies": {
42 | "@picocss/pico": "^2.0.6",
43 | "@tanstack/svelte-query": "^5.50.3",
44 | "@trpc/client": "^10.45.2",
45 | "@trpc/server": "^10.45.2",
46 | "better-sqlite3": "^11.1.2",
47 | "drizzle-orm": "^0.32.0",
48 | "trpc-svelte-query-adapter": "workspace:^",
49 | "trpc-sveltekit": "^3.6.2",
50 | "zod": "^3.23.8"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/@example/prettier.config.js:
--------------------------------------------------------------------------------
1 | import shared from 'shared/prettier';
2 |
3 | /** @type {import('prettier').Config} */
4 | export default {
5 | ...shared,
6 | plugins: ['prettier-plugin-svelte'],
7 | overrides: [{ files: '*.svelte', options: { parser: 'svelte' } }],
8 | };
9 |
--------------------------------------------------------------------------------
/@example/src/app.d.ts:
--------------------------------------------------------------------------------
1 | // See https://kit.svelte.dev/docs/types#app
2 | // for information about these interfaces
3 | declare global {
4 | namespace App {
5 | // interface Error {}
6 | // interface Locals {}
7 | // interface PageData {}
8 | // interface PageState {}
9 | // interface Platform {}
10 | }
11 | }
12 |
13 | export {};
14 |
--------------------------------------------------------------------------------
/@example/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | %sveltekit.head%
8 |
9 |
10 | %sveltekit.body%
11 |
12 |
13 |
--------------------------------------------------------------------------------
/@example/src/hooks.server.ts:
--------------------------------------------------------------------------------
1 | import { createContext } from '$lib/trpc/context';
2 | import { router } from '$lib/trpc/router';
3 | import type { Handle } from '@sveltejs/kit';
4 | import { createTRPCHandle } from 'trpc-sveltekit';
5 |
6 | export const handle: Handle = createTRPCHandle({ router, createContext });
7 |
--------------------------------------------------------------------------------
/@example/src/lib/components/Heading.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 | {#if prefix}
10 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | {/if}
22 |
23 |
24 |
25 | {#if suffix}
26 |
35 | {/if}
36 |
37 |
--------------------------------------------------------------------------------
/@example/src/lib/index.ts:
--------------------------------------------------------------------------------
1 | // place files you want to import through the `$lib` alias in this folder.
2 |
--------------------------------------------------------------------------------
/@example/src/lib/server/db/index.ts:
--------------------------------------------------------------------------------
1 | import { drizzle } from 'drizzle-orm/better-sqlite3';
2 | import * as schema from './schema';
3 | import Database from 'better-sqlite3';
4 |
5 | const client = new Database('sqlite.db');
6 | export const db = drizzle(client, { schema });
7 |
--------------------------------------------------------------------------------
/@example/src/lib/server/db/schema.ts:
--------------------------------------------------------------------------------
1 | import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core';
2 |
3 | export const todo = sqliteTable('todo', {
4 | id: integer('id').primaryKey(),
5 | text: text('text').notNull(),
6 | done: integer('done', { mode: 'boolean' }),
7 | });
8 |
--------------------------------------------------------------------------------
/@example/src/lib/trpc/client.ts:
--------------------------------------------------------------------------------
1 | import type { Router } from '$lib/trpc/router';
2 | import { createTRPCClient, type TRPCClientInit } from 'trpc-sveltekit';
3 | import { svelteQueryWrapper } from 'trpc-svelte-query-adapter';
4 | import type { QueryClient } from '@tanstack/svelte-query';
5 |
6 | let browserClient: ReturnType>;
7 |
8 | export function trpc(init?: TRPCClientInit, queryClient?: QueryClient) {
9 | const isBrowser = typeof window !== 'undefined';
10 | if (isBrowser && browserClient) return browserClient;
11 | const client = svelteQueryWrapper({
12 | client: createTRPCClient({ init }),
13 | queryClient,
14 | });
15 | if (isBrowser) browserClient = client;
16 | return client;
17 | }
18 |
--------------------------------------------------------------------------------
/@example/src/lib/trpc/context.ts:
--------------------------------------------------------------------------------
1 | import { db } from '$lib/server/db';
2 | import type { RequestEvent } from '@sveltejs/kit';
3 |
4 | export async function createContext(event: RequestEvent) {
5 | return {
6 | event, // 👈 `event` is now available in your context
7 | db,
8 | };
9 | }
10 |
11 | export type Context = Awaited>;
12 |
--------------------------------------------------------------------------------
/@example/src/lib/trpc/router.ts:
--------------------------------------------------------------------------------
1 | import { createContext, type Context } from '$lib/trpc/context';
2 | import { initTRPC } from '@trpc/server';
3 | import { z } from 'zod';
4 |
5 | import { todo } from '$lib/server/db/schema';
6 | import { eq } from 'drizzle-orm';
7 | import type { RequestEvent } from '../../routes/(app)/ssr2/$types';
8 |
9 | export const t = initTRPC.context().create();
10 |
11 | export const router = t.router({
12 | todos: t.router({
13 | create: t.procedure
14 | .input(z.string().min(1, 'Todo text cannot be empty'))
15 | .mutation(async ({ input: text, ctx: { db } }) => {
16 | await new Promise((r) => setTimeout(r, 2000));
17 | return db
18 | .insert(todo)
19 | .values({ text })
20 | .returning()
21 | .then((r) => r[0]);
22 | }),
23 |
24 | get: t.procedure
25 | .input(z.string().optional())
26 | .query(({ input: filter, ctx: { db } }) =>
27 | db.query.todo.findMany({
28 | where: filter
29 | ? (todo, { like }) => like(todo.text, `%${filter}%`)
30 | : undefined,
31 | })
32 | ),
33 |
34 | getPopular: t.procedure
35 | .input(
36 | z.object({
37 | cursor: z.number().optional(),
38 | limit: z.number().optional(),
39 | })
40 | )
41 | .query(async ({ input: { cursor: start = 0, limit = 10 } }) => {
42 | const res = await fetch(
43 | `https://jsonplaceholder.typicode.com/todos?_start=${start}&_limit=${limit}`
44 | );
45 | const todos = (await res.json()) as {
46 | userId: number;
47 | id: number;
48 | title: string;
49 | completed: boolean;
50 | }[];
51 |
52 | return { todos, nextCursor: start + limit };
53 | }),
54 |
55 | update: t.procedure
56 | .input(
57 | z.object({
58 | id: z.number(),
59 | text: z.string().min(1).optional(),
60 | done: z.boolean().optional(),
61 | })
62 | )
63 | .mutation(({ input: { id, ...newTodo }, ctx: { db } }) =>
64 | db.update(todo).set(newTodo).where(eq(todo.id, id))
65 | ),
66 |
67 | delete: t.procedure
68 | .input(z.number())
69 | .mutation(({ input: id, ctx: { db } }) =>
70 | db
71 | .delete(todo)
72 | .where(eq(todo.id, id))
73 | .returning()
74 | .then((r) => r?.[0])
75 | ),
76 | }),
77 | });
78 |
79 | const factory = t.createCallerFactory(router);
80 | export const createCaller = async (event: RequestEvent) => {
81 | return factory(await createContext(event));
82 | };
83 |
84 | export type Router = typeof router;
85 |
--------------------------------------------------------------------------------
/@example/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | export function debounce(cb: (v: T) => void, durationMs: number) {
2 | let timer: ReturnType;
3 | return (v: T) => {
4 | clearTimeout(timer);
5 | timer = setTimeout(() => cb(v), durationMs);
6 | };
7 | }
8 |
--------------------------------------------------------------------------------
/@example/src/routes/(app)/client-only/+page.svelte:
--------------------------------------------------------------------------------
1 |
48 |
49 | Client-only
50 |
51 |
52 |
53 |
Todos
54 |
55 |
91 |
92 |
118 |
119 |
120 |
121 | {#if $todos.isPending}
122 |
123 |
124 | Loading todos...
125 |
126 | {:else if $todos.isError}
127 |
128 | Error loading todos: {$todos.error}
129 |
130 | {:else if $todos.data.length <= 0}
131 |
Create a new Todo!
132 | {:else}
133 |
168 | {/if}
169 | {#if $createTodo.isPending || $deleteTodo.isPending || $updateTodo.isPending}
170 |
171 | {/if}
172 |
173 |
174 |
175 |
Popular Todos (from JSONPlaceholder API)
176 |
$popularTodos.fetchNextPage()}
178 | class="outline"
179 | style="display:block;margin-left:auto"
180 | >
181 | Fetch more
182 |
183 |
184 |
185 | {#if $popularTodos.isPending || $popularTodos.isFetching}
186 |
187 |
188 | Loading popular todos...
189 |
190 | {:else if $popularTodos.isError}
191 |
192 | Error loading todos: {$popularTodos.error}
193 |
194 | {:else if $popularTodos.data}
195 |
196 | {#each $popularTodos.data?.pages.flatMap((page) => page.todos) as todo}
197 |
200 |
201 | {todo.id}: {todo.title}
202 |
203 |
204 | {
206 | $createTodo.mutate(todo.title);
207 | }}
208 | title="Add Todo"
209 | disabled={$popularTodos.isPending || $createTodo.isPending}
210 | class="outline contrast pico-color-green-450"
211 | style="margin-left:auto;padding:0.1rem;line-height:1;border-color:var(--pico-color-green-450);display:grid;place-items:center;"
212 | >
213 |
214 |
215 |
216 | {/each}
217 |
218 | {/if}
219 |
220 |
221 |
222 |
235 |
--------------------------------------------------------------------------------
/@example/src/routes/(app)/ssr-with-streaming/+page.server.ts:
--------------------------------------------------------------------------------
1 | import { createCaller } from '$lib/trpc/router';
2 |
3 | export async function load(event) {
4 | const api = await createCaller(event);
5 | return {
6 | popularTodos: api.todos.getPopular({}),
7 | todos: await api.todos.get(),
8 | };
9 | }
10 |
--------------------------------------------------------------------------------
/@example/src/routes/(app)/ssr-with-streaming/+page.svelte:
--------------------------------------------------------------------------------
1 |
54 |
55 | SSR
56 |
57 |
58 |
59 |
Todos
60 |
61 |
97 |
98 |
124 |
125 |
126 |
127 | {#if $todos.isPending}
128 |
129 |
130 | Loading todos...
131 |
132 | {:else if $todos.isError}
133 |
134 | Error loading todos: {$todos.error}
135 |
136 | {:else if $todos.data.length <= 0}
137 |
Create a new Todo!
138 | {:else}
139 |
174 | {/if}
175 | {#if $createTodo.isPending || $deleteTodo.isPending || $updateTodo.isPending}
176 |
177 | {/if}
178 |
179 |
180 |
181 |
Popular Todos (from JSONPlaceholder API)
182 |
$popularTodos.fetchNextPage()}
184 | class="outline"
185 | style="display:block;margin-left:auto"
186 | >
187 | Fetch more
188 |
189 |
190 |
191 | {#await resolvePopularTodos(data.popularTodos)}
192 |
193 |
194 | Streaming popular todos...
195 |
196 | {:then}
197 | {#if $popularTodos.isPending || $popularTodos.isFetching}
198 |
199 |
200 | Loading popular todos...
201 |
202 | {:else if $popularTodos.isError}
203 |
204 | Error loading todos: {$popularTodos.error}
205 |
206 | {:else if $popularTodos.data}
207 |
208 | {#each $popularTodos.data?.pages.flatMap((page) => page.todos) as todo}
209 |
212 |
213 | {todo.id}: {todo.title}
214 |
215 |
216 | {
218 | $createTodo.mutate(todo.title);
219 | }}
220 | title="Add Todo"
221 | disabled={$popularTodos.isPending || $createTodo.isPending}
222 | class="outline contrast pico-color-green-450"
223 | style="margin-left:auto;padding:0.1rem;line-height:1;border-color:var(--pico-color-green-450);display:grid;place-items:center;"
224 | >
225 |
226 |
227 |
228 | {/each}
229 |
230 | {/if}
231 | {/await}
232 |
233 |
234 |
235 |
248 |
--------------------------------------------------------------------------------
/@example/src/routes/(app)/ssr/+page.svelte:
--------------------------------------------------------------------------------
1 |
43 |
44 | SSR
45 |
46 |
47 |
48 |
Todos
49 |
50 |
86 |
87 |
113 |
114 |
115 |
116 | {#if $todos.isPending}
117 |
118 |
119 | Loading todos...
120 |
121 | {:else if $todos.isError}
122 |
123 | Error loading todos: {$todos.error}
124 |
125 | {:else if $todos.data.length <= 0}
126 |
Create a new Todo!
127 | {:else}
128 |
163 | {/if}
164 | {#if $createTodo.isPending || $deleteTodo.isPending || $updateTodo.isPending}
165 |
166 | {/if}
167 |
168 |
169 |
170 |
Popular Todos (from JSONPlaceholder API)
171 |
$popularTodos.fetchNextPage()}
173 | class="outline"
174 | style="display:block;margin-left:auto"
175 | >
176 | Fetch more
177 |
178 |
179 |
180 | {#if $popularTodos.isPending || $popularTodos.isFetching}
181 |
182 |
183 | Loading popular todos...
184 |
185 | {:else if $popularTodos.isError}
186 |
187 | Error loading todos: {$popularTodos.error}
188 |
189 | {:else if $popularTodos.data}
190 |
191 | {#each $popularTodos.data?.pages.flatMap((page) => page.todos) as todo}
192 |
195 |
196 | {todo.id}: {todo.title}
197 |
198 |
199 | {
201 | $createTodo.mutate(todo.title);
202 | }}
203 | title="Add Todo"
204 | disabled={$popularTodos.isPending || $createTodo.isPending}
205 | class="outline contrast pico-color-green-450"
206 | style="margin-left:auto;padding:0.1rem;line-height:1;border-color:var(--pico-color-green-450);display:grid;place-items:center;"
207 | >
208 |
209 |
210 |
211 | {/each}
212 |
213 | {/if}
214 |
215 |
216 |
217 |
230 |
--------------------------------------------------------------------------------
/@example/src/routes/(app)/ssr/+page.ts:
--------------------------------------------------------------------------------
1 | import { trpc } from '$lib/trpc/client.js';
2 |
3 | export async function load(event) {
4 | const { queryClient } = await event.parent();
5 | const api = trpc(event, queryClient);
6 |
7 | return {
8 | api,
9 | todos: await api.todos.get.createServerQuery(),
10 |
11 | popularTodos: await api.todos.getPopular.createServerInfiniteQuery(
12 | {},
13 | {
14 | getNextPageParam: (lastPage) => lastPage.nextCursor,
15 | }
16 | ),
17 | };
18 | }
19 |
--------------------------------------------------------------------------------
/@example/src/routes/+layout.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
40 |
--------------------------------------------------------------------------------
/@example/src/routes/+layout.ts:
--------------------------------------------------------------------------------
1 | import { browser } from '$app/environment';
2 | import { QueryClient } from '@tanstack/svelte-query';
3 |
4 | export async function load() {
5 | const queryClient = new QueryClient({
6 | defaultOptions: {
7 | queries: {
8 | enabled: browser,
9 | refetchOnWindowFocus: false,
10 | staleTime: 10 * 60 * 1000,
11 | },
12 | },
13 | });
14 |
15 | return { queryClient };
16 | }
17 |
--------------------------------------------------------------------------------
/@example/src/routes/+page.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 | tRPC - Svelte-Query Adapter Demo
8 |
9 |
10 |
18 |
19 |
--------------------------------------------------------------------------------
/@example/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vishalbalaji/trpc-svelte-query-adapter/54726423b69ba0785c6a9ccfd5ac5c0e6311a55f/@example/static/favicon.png
--------------------------------------------------------------------------------
/@example/svelte.config.js:
--------------------------------------------------------------------------------
1 | import adapter from '@sveltejs/adapter-auto';
2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
3 |
4 | /** @type {import('@sveltejs/kit').Config} */
5 | const config = {
6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors
7 | // for more information about preprocessors
8 | preprocess: vitePreprocess(),
9 |
10 | kit: {
11 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
12 | // If your environment is not supported, or you settled on a specific environment, switch out the adapter.
13 | // See https://kit.svelte.dev/docs/adapters for more information about adapters.
14 | adapter: adapter()
15 | }
16 | };
17 |
18 | export default config;
19 |
--------------------------------------------------------------------------------
/@example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "../tsconfig.base.json",
4 | "./.svelte-kit/tsconfig.json"
5 | ],
6 | "compilerOptions": {
7 | "allowJs": true,
8 | "checkJs": true,
9 | "esModuleInterop": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "resolveJsonModule": true,
12 | "skipLibCheck": true,
13 | "sourceMap": true,
14 | "strict": true,
15 | "moduleResolution": "bundler",
16 | "plugins": [
17 | {
18 | "name": "typescript-svelte-plugin",
19 | // the following options can be set additionally; they are optional; their default values are listed here
20 | "enabled": true, // enables this plugin
21 | "assumeIsSvelteProject": false // if true, skip detection and always assume it's a Svelte project
22 | }
23 | ]
24 | }
25 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
26 | // except $lib which is handled by https://kit.svelte.dev/docs/configuration#files
27 | //
28 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
29 | // from the referenced tsconfig.json - TypeScript does not merge them in
30 | }
31 |
--------------------------------------------------------------------------------
/@example/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { sveltekit } from '@sveltejs/kit/vite';
2 | import { defineConfig } from 'vite';
3 |
4 | export default defineConfig({
5 | plugins: [sveltekit()]
6 | });
7 |
--------------------------------------------------------------------------------
/@lib/.npmignore:
--------------------------------------------------------------------------------
1 | **/*
2 | !/src/*
3 | !/package.json
4 | !/README.md
5 | !/LICENSE
6 |
--------------------------------------------------------------------------------
/@lib/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2024 Vishal Balaji D
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/@lib/README.md:
--------------------------------------------------------------------------------
1 | # `tRPC` - `svelte-query` Adapter
2 |
3 | [![NPM version][npm-image]][npm-url]
4 | [![License][license-image]][license-url]
5 | [![Last commit][last-commit-image]][repo-url]
6 |
7 | > **See [#67](https://github.com/vishalbalaji/trpc-svelte-query-adapter/issues/67) for the latest updates on this project**
8 |
9 | > [!NOTE]
10 | > The README on [npmjs](https://npmjs.com/trpc-svelte-query-adapter) might not be fully up to date. Please refer to
11 | > the [README on the Github Repo](https://github.com/vishalbalaji/trpc-svelte-query-adapter/#readme) for the latest setup instructions.
12 |
13 | An adapter to call `tRPC` procedures wrapped with [@tanstack/svelte-query](https://tanstack.com/query/latest/docs/svelte/overview)
, similar to [@trpc/react-query](https://trpc.io/docs/react-query)
. This is made possible using [proxy-deep](https://www.npmjs.com/package/proxy-deep)
.
14 |
15 | ## Installation
16 |
17 | ```sh
18 | # npm
19 | npm install trpc-svelte-query-adapter @trpc/client @trpc/server @tanstack/svelte-query
20 |
21 | # yarn
22 | yarn add trpc-svelte-query-adapter @trpc/client @trpc/server @tanstack/svelte-query
23 |
24 | # pnpm
25 | pnpm add trpc-svelte-query-adapter @trpc/client @trpc/server @tanstack/svelte-query
26 | ```
27 |
28 | If you are using client-side Svelte, you would need to install `@trpc/server` as a `devDependency` using `--save-dev`.
29 |
30 | ## Available Functions
31 |
32 | The following functions from `@trpc/react-query` are ported over:
33 |
34 | - `useQuery` -> `createQuery`
35 | - `useInfiniteQuery` -> `createInfiniteQuery`
36 | - `useMutation` -> `createMutation`
37 | - `useSubscription` -> `createSubscription`
38 | - `useQueries` -> `createQueries`
39 | - `useUtils` -> `createUtils`
40 | - `getQueryKey`
41 |
42 | You can refer to [tanstack-query docs](https://tanstack.com/query/latest/docs/react/overview)
and [@trpc/react-query docs](https://trpc.io/docs/react-query)
for documentation on how to use them.
43 |
44 | There are also some new procedures that are only relevant for SvelteKit:
45 |
46 | - `createServerQuery`
47 | - `createServerInfiniteQuery`
48 | - `createServerQueries`
49 |
50 | As for these procedures, you can refer to the [Server-Side Query Pre-Fetching](#server-side-query-pre-fetching) section.
51 |
52 | ## Usage
53 |
54 | The following instructions assume the `tRPC` router to have the following procedures:
55 |
56 | ```typescript
57 | export const router = t.router({
58 | greeting: t.procedure
59 | .input((name: unknown) => {
60 | if (typeof name === 'string') return name;
61 |
62 | throw new Error(`Invalid input: ${typeof name}`);
63 | })
64 | .query(async ({ input }) => {
65 | return `Hello, ${input} from tRPC v10 @ ${new Date().toLocaleTimeString()}`;
66 | }),
67 | });
68 |
69 | export type Router = typeof router;
70 | ```
71 |
72 | ### Client-Only Svelte
73 |
74 | 1. Setup `@tanstack/svelte-query` as per [svelte-query docs](https://tanstack.com/query/latest/docs/svelte/overview).
75 | 2. Setup [@trpc/client](https://trpc.io/docs/client)
and export the `tRPC` client.
76 | 3. Wrap the exported `tRPC` client with `svelteQueryWrapper` from `trpc-svelte-query-adapter`, as demonstrated in the example below:
77 |
78 | ```typescript
79 | // src/lib/trpc.ts
80 | import type { Router } from '/path/to/trpc/router';
81 | import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
82 |
83 | import { svelteQueryWrapper } from 'trpc-svelte-query-adapter';
84 |
85 | const client = createTRPCProxyClient({
86 | links: [
87 | httpBatchLink({
88 | // Replace this URL with that of your tRPC server
89 | url: 'http://localhost:5000/api/v1/trpc/',
90 | }),
91 | ],
92 | });
93 |
94 | export const trpc = svelteQueryWrapper({ client });
95 | ```
96 |
97 | 4. The exported `tRPC` client can then be used in `svelte` components as follows:
98 |
99 | ```svelte
100 |
105 |
106 | {#if $foo.isPending}
107 | Loading...
108 | {:else if $foo.isError}
109 | Error: {$foo.error.message}
110 | {:else if $foo.data}
111 | {$foo.data.message}
112 | {/if}
113 | ```
114 |
115 | ### SvelteKit and SSR
116 |
117 | 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).
118 |
119 | 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`.
120 |
121 | Here is an example of what that might look like:
122 |
123 | ```typescript
124 | import type { QueryClient } from '@tanstack/svelte-query';
125 |
126 | const client = createTRPCProxyClient({
127 | links: [
128 | httpBatchLink({
129 | // Replace this URL with that of your tRPC server
130 | url: 'http://localhost:5000/api/v1/trpc/',
131 | }),
132 | ],
133 | });
134 |
135 | export function trpc(queryClient?: QueryClient) {
136 | return svelteQueryWrapper({
137 | client,
138 | queryClient,
139 | });
140 | }
141 | ```
142 |
143 | Which can then be used in a component as such:
144 |
145 | ```svelte
146 |
147 |
153 |
154 |
155 | {#if $foo.isPending}
156 | Loading...
157 | {:else if $foo.isError}
158 | Error: {$foo.error.message}
159 | {:else}
160 | {$foo.data}
161 | {/if}
162 |
163 | ```
164 |
165 | 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 [trpc-sveltekit by icflorescu](https://icflorescu.github.io/trpc-sveltekit)
, all you would need to do after setting it up would be to change the client initialization function from something like this:
166 |
167 | ```typescript
168 | let browserClient: ReturnType>;
169 |
170 | export function trpc(init?: TRPCClientInit) {
171 | const isBrowser = typeof window !== 'undefined';
172 | if (isBrowser && browserClient) return browserClient;
173 | const client = createTRPCClient({ init });
174 | if (isBrowser) browserClient = client;
175 | return client;
176 | }
177 | ```
178 |
179 | to this:
180 |
181 | ```typescript
182 | import { svelteQueryWrapper } from 'trpc-svelte-query-adapter';
183 | import type { QueryClient } from '@tanstack/svelte-query';
184 |
185 | let browserClient: ReturnType>;
186 |
187 | export function trpc(init?: TRPCClientInit, queryClient?: QueryClient) {
188 | const isBrowser = typeof window !== 'undefined';
189 | if (isBrowser && browserClient) return browserClient;
190 | const client = svelteQueryWrapper({
191 | client: createTRPCClient({ init }),
192 | queryClient,
193 | });
194 | if (isBrowser) browserClient = client;
195 | return client;
196 | }
197 | ```
198 |
199 | 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).
200 |
201 | #### Server-Side Query Pre-Fetching
202 |
203 | 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.
204 |
205 | 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`.
206 |
207 | These procedures can be used as such:
208 |
209 | > [!NOTE]
210 | > [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).
211 |
212 | ```typescript
213 | // +page.ts
214 | // tRPC is setup using `trpc-sveltekit` for this example.
215 | import { trpc } from '$lib/trpc/client';
216 | import type { PageLoad } from './$types';
217 |
218 | export const load = (async (event) => {
219 | const { queryClient } = await event.parent();
220 | const client = trpc(event, queryClient);
221 |
222 | return {
223 | foo: await client.greeting.createServerQuery('foo'),
224 | queries: await client.createServerQueries(
225 | (t) =>
226 | ['bar', 'baz'].map((name) => t.greeting(name, { ssr: name !== 'baz' })) // pre-fetching disabled for the `baz` query.
227 | ),
228 | };
229 | }) satisfies PageLoad;
230 | ```
231 |
232 | Then, in the component:
233 |
234 | ```svelte
235 |
236 |
245 |
246 | {#if $foo.isPending}
247 | Loading...
248 | {:else if $foo.isError}
249 | {$foo.error}
250 | {:else if $foo.data}
251 | {$foo.data}
252 | {/if}
253 |
254 |
255 | {#each $queries as query}
256 | {#if query.isPending}
257 | Loading...
258 | {:else if query.isError}
259 | {query.error.message}
260 | {:else if query.data}
261 | {query.data}
262 | {/if}
263 |
264 | {/each}
265 | ```
266 |
267 | 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:
268 |
269 | ```svelte
270 |
288 |
289 |
290 | {#if $foo.isPending}
291 | Loading...
292 | {:else if $foo.isError}
293 | {$foo.error}
294 | {:else if $foo.data}
295 | {$foo.data}
296 | {/if}
297 |
298 |
299 |
300 |
301 |
302 |
303 | {#each $queries as query}
304 | {#if query.isPending}
305 | Loading...
306 | {:else if query.isError}
307 | {query.error.message}
308 | {:else if query.data}
309 | {query.data}
310 | {/if}
311 |
312 | {/each}
313 |
314 |
322 |
323 | ```
324 |
325 | For more usage examples, you can refer to the [example app provided in the repo](/@example).
326 |
327 | [npm-url]: https://npmjs.org/package/trpc-svelte-query-adapter
328 | [npm-image]: https://img.shields.io/npm/v/trpc-svelte-query-adapter.svg
329 | [license-url]: LICENSE
330 | [license-image]: http://img.shields.io/npm/l/trpc-svelte-query-adapter.svg
331 | [repo-url]: https://github.com/vishalbalaji/trpc-svelte-query-adapter
332 | [last-commit-image]: https://img.shields.io/github/last-commit/vishalbalaji/trpc-svelte-query-adapter
333 |
--------------------------------------------------------------------------------
/@lib/eslint.config.js:
--------------------------------------------------------------------------------
1 | export { default } from 'shared/eslint';
2 |
--------------------------------------------------------------------------------
/@lib/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "trpc-svelte-query-adapter",
3 | "version": "2.3.16",
4 | "description": "A simple adapter to use `@tanstack/svelte-query` with trpc, similar to `@trpc/react-query`.",
5 | "keywords": [
6 | "trpc",
7 | "svelte",
8 | "sveltekit",
9 | "kit",
10 | "adapter",
11 | "handle",
12 | "typescript",
13 | "rpc",
14 | "svelte-query",
15 | "react-query",
16 | "tanstack-query",
17 | "query"
18 | ],
19 | "author": {
20 | "name": "Vishal Balaji D",
21 | "email": "vishalbalaji@gmail.com",
22 | "url": "https://github.com/vishalbalaji"
23 | },
24 | "repository": "vishalbalaji/trpc-svelte-query-adapter",
25 | "bugs": {
26 | "url": "https://github.com/vishalbalaji/trpc-svelte-query-adapter/issues"
27 | },
28 | "license": "MIT",
29 | "type": "module",
30 | "types": "./src/index.ts",
31 | "module": "./src/index.ts",
32 | "exports": {
33 | "types": "./index.d.ts",
34 | "import": "./src/index.ts"
35 | },
36 | "dependencies": {
37 | "proxy-deep": "^4.0.1"
38 | },
39 | "peerDependencies": {
40 | "@tanstack/svelte-query": "^5.8.2",
41 | "@trpc/client": "^10.43.3",
42 | "@trpc/server": "^10.43.3",
43 | "@sveltejs/vite-plugin-svelte": ">=4.0.0 <6",
44 | "svelte": "^5",
45 | "typescript": "^5.2.2"
46 | },
47 | "devDependencies": {
48 | "@types/node": "^20.14.10"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/@lib/prettier.config.js:
--------------------------------------------------------------------------------
1 | export { default } from 'shared/prettier';
2 |
--------------------------------------------------------------------------------
/@lib/src/index.ts:
--------------------------------------------------------------------------------
1 | import DeepProxy from 'proxy-deep';
2 |
3 | import type {
4 | TRPCClientErrorLike,
5 | CreateTRPCProxyClient,
6 | TRPCUntypedClient,
7 | } from '@trpc/client';
8 | import type { AnyRouter, DeepPartial } from '@trpc/server';
9 |
10 | import {
11 | useQueryClient,
12 | createQuery,
13 | createMutation,
14 | createInfiniteQuery,
15 | createQueries,
16 | skipToken,
17 | hashKey,
18 | type CreateQueryOptions,
19 | type CreateMutationOptions,
20 | type CreateInfiniteQueryOptions,
21 | type InvalidateQueryFilters,
22 | type FetchQueryOptions,
23 | type FetchInfiniteQueryOptions,
24 | type InfiniteData,
25 | type RefetchQueryFilters,
26 | type RefetchOptions,
27 | type ResetOptions,
28 | type CancelOptions,
29 | type Updater,
30 | type Query,
31 | type SetDataOptions,
32 | type QueryClient,
33 | type InvalidateOptions,
34 | type CreateQueryResult,
35 | type CreateInfiniteQueryResult,
36 | type CreateMutationResult,
37 | type StoreOrVal as _StoreOrVal,
38 | type QueryObserverResult,
39 | type QueryObserverOptions,
40 | type DefaultError,
41 | type OmitKeyof,
42 | type QueriesPlaceholderDataFunction,
43 | } from '@tanstack/svelte-query';
44 |
45 | import { afterUpdate, onDestroy, onMount } from 'svelte';
46 | import {
47 | derived,
48 | get,
49 | writable,
50 | type Readable,
51 | type Writable,
52 | } from 'svelte/store';
53 |
54 | /**
55 | * Omits the key without removing a potential union
56 | * @internal
57 | */
58 | type DistributiveOmit = TObj extends any
59 | ? Omit
60 | : never;
61 |
62 | type ValueOf = T[keyof T];
63 |
64 | type ExhaustiveRecord<
65 | TKey extends PropertyKey,
66 | TValue = any,
67 | U extends
68 | | (
69 | { [K in TKey]: TValue } &
70 | { [K in keyof U]: K extends TKey ? TValue : never; }
71 | )
72 | | undefined
73 | = undefined,
74 | > = U extends undefined ? { [K in TKey]: TValue }
75 | : U extends { [K in TKey]: TValue } ? U
76 | : never; // prettier-ignore
77 |
78 | type StoreOrVal = _StoreOrVal | Writable;
79 |
80 | // CREDIT: https://stackoverflow.com/a/63448246
81 | type WithNevers = {
82 | [K in keyof T]: Exclude extends V
83 | ? never
84 | : T[K] extends Record
85 | ? Without
86 | : T[K];
87 | };
88 |
89 | type Without> = Pick<
90 | I,
91 | { [K in keyof I]: I[K] extends never ? never : K }[keyof I]
92 | >;
93 |
94 | type HasQuery = { query: (...args: any[]) => any };
95 | type HasMutate = { mutate: (...args: any[]) => any };
96 | type HasSubscribe = { subscribe: (...args: any[]) => any };
97 | type OnlyQueries = Without;
98 |
99 | function isSvelteStore(
100 | obj: StoreOrVal
101 | ): obj is Readable {
102 | return (
103 | typeof obj === 'object' &&
104 | 'subscribe' in obj &&
105 | typeof obj.subscribe === 'function'
106 | );
107 | }
108 |
109 | /**
110 | * Check that value is object
111 | * @internal
112 | */
113 | function isObject(value: unknown): value is Record {
114 | return !!value && !Array.isArray(value) && typeof value === 'object';
115 | }
116 |
117 | const blank = Symbol('blank');
118 | const isBlank = (val: unknown): val is typeof blank => val === blank;
119 | const blankStore: Readable = {
120 | subscribe(run) {
121 | run(blank);
122 | return () => {};
123 | },
124 | };
125 |
126 | function hasOwn(obj: T, prop: PropertyKey): prop is keyof T {
127 | return typeof obj === 'object' && Object.hasOwn(obj as any, prop);
128 | }
129 |
130 | const Procedure = {
131 | query: 'createQuery',
132 | serverQuery: 'createServerQuery',
133 | infiniteQuery: 'createInfiniteQuery',
134 | serverInfiniteQuery: 'createServerInfiniteQuery',
135 | mutate: 'createMutation',
136 | subscribe: 'createSubscription',
137 | queryKey: 'getQueryKey',
138 | context: 'createContext',
139 | utils: 'createUtils',
140 | queries: 'createQueries',
141 | serverQueries: 'createServerQueries',
142 | } as const;
143 |
144 | const Util = {
145 | Query: {
146 | client: 'client',
147 | fetch: 'fetch',
148 | prefetch: 'prefetch',
149 | fetchInfinite: 'fetchInfinite',
150 | prefetchInfinite: 'prefetchInfinite',
151 | ensureData: 'ensureData',
152 | invalidate: 'invalidate',
153 | refetch: 'refetch',
154 | reset: 'reset',
155 | cancel: 'cancel',
156 | setData: 'setData',
157 | getData: 'getData',
158 | setInfiniteData: 'setInfiniteData',
159 | getInfiniteData: 'getInfiniteData',
160 | },
161 |
162 | Mutation: {
163 | setMutationDefaults: 'setMutationDefaults',
164 | getMutationDefaults: 'getMutationDefaults',
165 | isMutating: 'isMutating',
166 | },
167 | } as const;
168 |
169 | // getQueryKey
170 | type GetInfiniteQueryInput<
171 | TProcedureInput,
172 | TInputWithoutCursorAndDirection = Omit<
173 | TProcedureInput,
174 | 'cursor' | 'direction'
175 | >,
176 | > = keyof TInputWithoutCursorAndDirection extends never
177 | ? undefined
178 | : DeepPartial | undefined;
179 |
180 | type GetQueryProcedureInput = TProcedureInput extends {
181 | cursor?: any;
182 | }
183 | ? GetInfiniteQueryInput
184 | : DeepPartial | undefined;
185 |
186 | type QueryType = 'query' | 'infinite' | 'any';
187 |
188 | export type TRPCQueryKey = [
189 | readonly string[],
190 | { input?: unknown; type?: Exclude }?,
191 | ];
192 |
193 | export type TRPCMutationKey = [readonly string[]]; // = [TRPCQueryKey[0]]
194 |
195 | type QueryKeyKnown> = [
196 | string[],
197 | { input?: GetQueryProcedureInput; type: TType }?,
198 | ];
199 |
200 | function getQueryKeyInternal(
201 | path: readonly string[],
202 | input: unknown,
203 | type: QueryType
204 | ): TRPCQueryKey {
205 | // Construct a query key that is easy to destructure and flexible for
206 | // partial selecting etc.
207 | // https://github.com/trpc/trpc/issues/3128
208 |
209 | // some parts of the path may be dot-separated, split them up
210 | const splitPath = path.flatMap((part) => part.split('.'));
211 |
212 | if (!input && (!type || type === 'any')) {
213 | // this matches also all mutations (see `getMutationKeyInternal`)
214 |
215 | // for `utils.invalidate()` to match all queries (including vanilla react-query)
216 | // we don't want nested array if path is empty, i.e. `[]` instead of `[[]]`
217 | return splitPath.length ? [splitPath] : ([] as unknown as TRPCQueryKey);
218 | }
219 |
220 | if (
221 | type === 'infinite' &&
222 | isObject(input) &&
223 | ('direction' in input || 'cursor' in input)
224 | ) {
225 | const {
226 | cursor: _,
227 | direction: __,
228 | ...inputWithoutCursorAndDirection
229 | } = input;
230 | return [
231 | splitPath,
232 | {
233 | input: inputWithoutCursorAndDirection,
234 | type: 'infinite',
235 | },
236 | ];
237 | }
238 | return [
239 | splitPath,
240 | {
241 | ...(typeof input !== 'undefined' &&
242 | input !== skipToken && { input: input }),
243 | ...(type && type !== 'any' && { type: type }),
244 | },
245 | ];
246 | }
247 |
248 | function getMutationKeyInternal(path: readonly string[]) {
249 | return getQueryKeyInternal(path, undefined, 'any') as TRPCMutationKey;
250 | }
251 |
252 | type GetQueryKey = [TInput] extends [undefined | void]
253 | ? {
254 | /**
255 | * @deprecated import `getQueryKey` from `trpc-svelte-query-adapter` instead
256 | */
257 | [Procedure.queryKey]: () => TRPCQueryKey;
258 | }
259 | : {
260 | /**
261 | * @deprecated import `getQueryKey` from `trpc-svelte-query-adapter` instead
262 | *
263 | * Method to extract the query key for a procedure
264 | * @param type - defaults to `any`
265 | */
266 | [Procedure.queryKey]: (input?: TInput, type?: QueryType) => TRPCQueryKey;
267 | } & {};
268 |
269 | function getClientArgs(
270 | queryKey: TRPCQueryKey,
271 | opts: TOptions,
272 | infiniteParams?: {
273 | pageParam: any;
274 | direction: 'forward' | 'backward';
275 | }
276 | ): [path: string, input: unknown, opts: any] {
277 | const path = queryKey[0];
278 | let input = queryKey[1]?.input;
279 | if (infiniteParams) {
280 | input = {
281 | ...(input ?? {}),
282 | ...(infiniteParams.pageParam ? { cursor: infiniteParams.pageParam } : {}),
283 | direction: infiniteParams.direction,
284 | };
285 | }
286 | return [path.join('.'), input, (opts as any)?.trpc] as const;
287 | }
288 |
289 | // createUtils
290 | type TRPCFetchQueryOptions = DistributiveOmit<
291 | FetchQueryOptions,
292 | 'queryKey'
293 | >;
294 |
295 | type TRPCFetchInfiniteQueryOptions = DistributiveOmit<
296 | FetchInfiniteQueryOptions,
297 | 'queryKey' | 'initialPageParam'
298 | >;
299 |
300 | type QueryUtils<
301 | TInput = undefined,
302 | TOutput = undefined,
303 | TError = undefined
304 | > = ExhaustiveRecord, 'client'>, any, {
305 | /**
306 | * @link https://tanstack.com/query/v5/docs/reference/QueryClient#queryclientfetchquery
307 | */
308 | [Util.Query.fetch](
309 | input: TInput,
310 | opts?: TRPCFetchQueryOptions
311 | ): Promise;
312 |
313 | /**
314 | * @link https://tanstack.com/query/v5/docs/reference/QueryClient#queryclientfetchinfinitequery
315 | */
316 | [Util.Query.fetchInfinite](
317 | input: TInput,
318 | opts?: TRPCFetchInfiniteQueryOptions
319 | ): Promise<
320 | InfiniteData> | null>
321 | >;
322 |
323 | /**
324 | * @link https://tanstack.com/query/v5/docs/reference/QueryClient#queryclientprefetchquery
325 | */
326 | [Util.Query.prefetch](
327 | input: TInput,
328 | opts?: TRPCFetchQueryOptions
329 | ): Promise;
330 |
331 | /**
332 | * @link https://tanstack.com/query/v5/docs/reference/QueryClient#queryclientprefetchinfinitequery
333 | */
334 | [Util.Query.prefetchInfinite](
335 | input: TInput,
336 | opts?: TRPCFetchInfiniteQueryOptions
337 | ): Promise;
338 |
339 | /**
340 | * @link https://tanstack.com/query/v5/docs/reference/QueryClient#queryclientensurequerydata
341 | */
342 | [Util.Query.ensureData](
343 | input: TInput,
344 | opts?: TRPCFetchQueryOptions
345 | ): Promise;
346 |
347 | /**
348 | * @link https://tanstack.com/query/v5/docs/reference/QueryClient#queryclientinvalidatequeries
349 | */
350 | [Util.Query.invalidate](
351 | input?: DeepPartial,
352 | filters?: Omit & {
353 | predicate?: (
354 | query: Query<
355 | TInput,
356 | TError,
357 | TInput,
358 | QueryKeyKnown<
359 | TInput,
360 | TInput extends { cursor?: any } | void ? 'infinite' : 'query'
361 | >
362 | >
363 | ) => boolean;
364 | },
365 | options?: InvalidateOptions
366 | ): Promise;
367 |
368 | /**
369 | * @link https://tanstack.com/query/v5/docs/reference/QueryClient#queryclientrefetchqueries
370 | */
371 | [Util.Query.refetch](
372 | input?: TInput,
373 | filters?: RefetchQueryFilters,
374 | options?: RefetchOptions
375 | ): Promise;
376 |
377 | /**
378 | * @link https://tanstack.com/query/v5/docs/reference/QueryClient#queryclientcancelqueries
379 | */
380 | [Util.Query.cancel](input?: TInput, options?: CancelOptions): Promise;
381 |
382 | /**
383 | * @link https://tanstack.com/query/v5/docs/reference/QueryClient#queryclientresetqueries
384 | */
385 | [Util.Query.reset](input?: TInput, options?: ResetOptions): Promise;
386 |
387 | /**
388 | * @link https://tanstack.com/query/v5/docs/reference/QueryClient#queryclientsetquerydata
389 | */
390 | [Util.Query.setData](
391 | /**
392 | * The input of the procedure
393 | */
394 | input: TInput,
395 | updater: Updater,
396 | options?: SetDataOptions
397 | ): void;
398 |
399 | /**
400 | * @link https://tanstack.com/query/v5/docs/reference/QueryClient#queryclientsetquerydata
401 | */
402 | [Util.Query.setInfiniteData](
403 | input: TInput,
404 | updater: Updater<
405 | | InfiniteData> | null>
406 | | undefined,
407 | | InfiniteData> | null>
408 | | undefined
409 | >,
410 | options?: SetDataOptions
411 | ): void;
412 |
413 | /**
414 | * @link https://tanstack.com/query/v5/docs/reference/QueryClient#queryclientgetquerydata
415 | */
416 | [Util.Query.getData](input?: TInput): TOutput | undefined;
417 |
418 | /**
419 | * @link https://tanstack.com/query/v5/docs/reference/QueryClient#queryclientgetquerydata
420 | */
421 | [Util.Query.getInfiniteData](
422 | input?: TInput
423 | ):
424 | | InfiniteData> | null>
425 | | undefined;
426 | }>; // prettier-ignore
427 |
428 | type MutationUtils<
429 | TInput = undefined,
430 | TOutput = undefined,
431 | TError = undefined,
432 | > = ExhaustiveRecord, any, {
433 | [Util.Mutation.setMutationDefaults](
434 | opts:
435 | | CreateMutationOptions
436 | | ((args: {
437 | canonicalMutationFn: NonNullable<
438 | CreateMutationOptions['mutationFn']
439 | >;
440 | }) => CreateMutationOptions)
441 | ): void;
442 |
443 | [Util.Mutation.getMutationDefaults]():
444 | | CreateMutationOptions
445 | | undefined;
446 |
447 | [Util.Mutation.isMutating](): number;
448 | }>; // prettier-ignore
449 |
450 | type AddUtilsPropTypes = {
451 | [K in keyof TClient]:
452 | TClient[K] extends HasQuery ? QueryUtils<
453 | Parameters[0],
454 | Awaited>,
455 | TError
456 | >
457 | : TClient[K] extends HasMutate ? MutationUtils<
458 | Parameters[0],
459 | Awaited>,
460 | TError
461 | >
462 | : AddUtilsPropTypes &
463 | Pick;
464 | }; // prettier-ignore
465 |
466 | type CreateUtilsProcedure = {
467 | /**
468 | * @see https://trpc.io/docs/client/react/useUtils
469 | */
470 | [Procedure.utils](): AddUtilsPropTypes &
471 | Pick & {
472 | [Util.Query.client]: TClient;
473 | };
474 |
475 | /**
476 | * @deprecated renamed to `createUtils` and will be removed in a future tRPC version
477 | *
478 | * @see https://trpc.io/docs/client/react/useUtils
479 | */
480 | [Procedure.context](): AddUtilsPropTypes &
481 | Pick & {
482 | [Util.Query.client]: TClient;
483 | };
484 | } & {};
485 |
486 | const utilProcedures: Record<
487 | | Exclude, 'client'>
488 | | ValueOf, // prettier-ignore
489 | (ctx: SvelteQueryWrapperContext) => any
490 | > = {
491 | // QueryUtils
492 | [Util.Query.fetch]: ({ path, queryClient, client, key }) => {
493 | return (input: any, opts?: any) => {
494 | const queryKey = getQueryKeyInternal(
495 | path,
496 | input,
497 | getQueryType(key as any)
498 | );
499 | return queryClient.fetchQuery({
500 | ...opts,
501 | queryKey,
502 | queryFn: () => client.query(...getClientArgs(queryKey, opts)),
503 | });
504 | };
505 | },
506 | [Util.Query.fetchInfinite]: ({ path, queryClient, client, key }) => {
507 | return (input: any, opts?: any) => {
508 | const queryKey = getQueryKeyInternal(
509 | path,
510 | input,
511 | getQueryType(key as any)
512 | );
513 | return queryClient.fetchInfiniteQuery({
514 | ...opts,
515 | queryKey,
516 | queryFn: ({ pageParam, direction }) => {
517 | return client.query(
518 | ...getClientArgs(queryKey, opts, { pageParam, direction })
519 | );
520 | },
521 | initialPageParam: opts?.initialCursor ?? null,
522 | });
523 | };
524 | },
525 | [Util.Query.prefetch]: ({ path, queryClient, client, key }) => {
526 | return (input: any, opts?: any) => {
527 | const queryKey = getQueryKeyInternal(
528 | path,
529 | input,
530 | getQueryType(key as any)
531 | );
532 | return queryClient.prefetchQuery({
533 | ...opts,
534 | queryKey,
535 | queryFn: () => client.query(...getClientArgs(queryKey, opts)),
536 | });
537 | };
538 | },
539 | [Util.Query.prefetchInfinite]: ({ path, queryClient, client, key }) => {
540 | return (input: any, opts?: any) => {
541 | const queryKey = getQueryKeyInternal(
542 | path,
543 | input,
544 | getQueryType(key as any)
545 | );
546 | return queryClient.prefetchInfiniteQuery({
547 | ...opts,
548 | queryKey,
549 | queryFn: ({ pageParam, direction }) => {
550 | return client.query(
551 | ...getClientArgs(queryKey, opts, { pageParam, direction })
552 | );
553 | },
554 | initialPageParam: opts?.initialCursor ?? null,
555 | });
556 | };
557 | },
558 | [Util.Query.ensureData]: ({ path, queryClient, client, key }) => {
559 | return (input: any, opts?: any) => {
560 | const queryKey = getQueryKeyInternal(
561 | path,
562 | input,
563 | getQueryType(key as any)
564 | );
565 | return queryClient.ensureQueryData({
566 | ...opts,
567 | queryKey,
568 | queryFn: () => client.query(...getClientArgs(queryKey, opts)),
569 | });
570 | };
571 | },
572 | [Util.Query.invalidate]: ({ path, queryClient, key }) => {
573 | return (input?: any, filters?: any, options?: any) => {
574 | console.log(path, input, getQueryType(key as any));
575 | const queryKey = getQueryKeyInternal(
576 | path,
577 | input,
578 | getQueryType(key as any)
579 | );
580 | return queryClient.invalidateQueries(
581 | {
582 | ...filters,
583 | queryKey,
584 | },
585 | options
586 | );
587 | };
588 | },
589 | [Util.Query.reset]: ({ queryClient, path, key }) => {
590 | return (input?: any, filters?: any, options?: any) => {
591 | const queryKey = getQueryKeyInternal(
592 | path,
593 | input,
594 | getQueryType(key as any)
595 | );
596 | return queryClient.resetQueries(
597 | {
598 | ...filters,
599 | queryKey,
600 | },
601 | options
602 | );
603 | };
604 | },
605 | [Util.Query.refetch]: ({ path, queryClient, key }) => {
606 | return (input?: any, filters?: any, options?: any) => {
607 | const queryKey = getQueryKeyInternal(
608 | path,
609 | input,
610 | getQueryType(key as any)
611 | );
612 | return queryClient.refetchQueries(
613 | {
614 | ...filters,
615 | queryKey,
616 | },
617 | options
618 | );
619 | };
620 | },
621 | [Util.Query.cancel]: ({ path, queryClient, key }) => {
622 | return (input?: any, options?: any) => {
623 | const queryKey = getQueryKeyInternal(
624 | path,
625 | input,
626 | getQueryType(key as any)
627 | );
628 | return queryClient.cancelQueries(
629 | {
630 | queryKey,
631 | },
632 | options
633 | );
634 | };
635 | },
636 | [Util.Query.setData]: ({ queryClient, path, key }) => {
637 | return (input: any, updater: any, options?: any) => {
638 | const queryKey = getQueryKeyInternal(
639 | path,
640 | input,
641 | getQueryType(key as any)
642 | );
643 | return queryClient.setQueryData(queryKey, updater as any, options);
644 | };
645 | },
646 | [Util.Query.setInfiniteData]: ({ queryClient, path, key }) => {
647 | return (input: any, updater: any, options?: any) => {
648 | const queryKey = getQueryKeyInternal(
649 | path,
650 | input,
651 | getQueryType(key as any)
652 | );
653 | return queryClient.setQueryData(queryKey, updater as any, options);
654 | };
655 | },
656 | [Util.Query.getData]: ({ queryClient, path, key }) => {
657 | return (input?: any) => {
658 | const queryKey = getQueryKeyInternal(
659 | path,
660 | input,
661 | getQueryType(key as any)
662 | );
663 | return queryClient.getQueryData(queryKey);
664 | };
665 | },
666 | [Util.Query.getInfiniteData]: ({ queryClient, path, key }) => {
667 | return (input?: any) => {
668 | const queryKey = getQueryKeyInternal(
669 | path,
670 | input,
671 | getQueryType(key as any)
672 | );
673 | return queryClient.getQueryData(queryKey);
674 | };
675 | },
676 |
677 | // MutationUtils
678 | [Util.Mutation.setMutationDefaults]: ({
679 | queryClient,
680 | path: _path,
681 | client,
682 | }) => {
683 | return (options: any) => {
684 | const mutationKey = getMutationKeyInternal(_path);
685 | const path = mutationKey[0];
686 | const canonicalMutationFn = (input: unknown) => {
687 | return client.mutation(...getClientArgs([path, { input }], {}));
688 | };
689 | return queryClient.setMutationDefaults(
690 | mutationKey,
691 | typeof options === 'function'
692 | ? options({ canonicalMutationFn })
693 | : options
694 | );
695 | };
696 | },
697 | [Util.Mutation.getMutationDefaults]: ({ queryClient, path }) => {
698 | return () => {
699 | return queryClient.getMutationDefaults(getMutationKeyInternal(path));
700 | };
701 | },
702 | [Util.Mutation.isMutating]: ({ queryClient, path }) => {
703 | return () => {
704 | return queryClient.isMutating({
705 | mutationKey: getMutationKeyInternal(path),
706 | exact: true,
707 | });
708 | };
709 | },
710 | };
711 |
712 | function createUtilsProxy(ctx: SvelteQueryWrapperContext) {
713 | return new DeepProxy(
714 | {},
715 | {
716 | get(_target, key, _receiver) {
717 | if (key === Util.Query.client) return ctx.baseClient;
718 |
719 | if (hasOwn(utilProcedures, key)) {
720 | return utilProcedures[key](
721 | Object.assign(ctx, { key, path: this.path })
722 | );
723 | }
724 |
725 | return this.nest(() => {});
726 | },
727 | }
728 | );
729 | }
730 |
731 | // createQueries
732 | // REFER: https://github.com/trpc/trpc/blob/936db6dd2598337758e29c843ff66984ed54faaf/packages/react-query/src/internals/useQueries.ts#L33
733 | type QueriesResults<
734 | TQueriesOptions extends CreateQueryOptionsForCreateQueries<
735 | any,
736 | any,
737 | any,
738 | any
739 | >[],
740 | > = {
741 | [TKey in keyof TQueriesOptions]: TQueriesOptions[TKey] extends CreateQueryOptionsForCreateQueries<
742 | infer TQueryFnData,
743 | infer TError,
744 | infer TData,
745 | any
746 | >
747 | ? QueryObserverResult
748 | : never;
749 | };
750 |
751 | type QueryObserverOptionsForCreateQueries<
752 | TQueryFnData = unknown,
753 | TError = DefaultError,
754 | TData = TQueryFnData,
755 | TQueryKey extends TRPCQueryKey = TRPCQueryKey,
756 | > = OmitKeyof<
757 | QueryObserverOptions,
758 | 'placeholderData'
759 | > & {
760 | placeholderData?: TQueryFnData | QueriesPlaceholderDataFunction;
761 | };
762 |
763 | type CreateQueryOptionsForCreateQueries<
764 | TOutput = unknown,
765 | TError = unknown,
766 | TData = unknown,
767 | TQueryKey extends TRPCQueryKey = TRPCQueryKey,
768 | > = Omit<
769 | QueryObserverOptionsForCreateQueries,
770 | 'context' | 'queryKey' | 'queryFn'
771 | > &
772 | TRPCQueryOpts;
773 |
774 | type CreateQueriesRecord = {
775 | [K in keyof TClient]: TClient[K] extends HasQuery
776 | ? >, TData = TOutput>(
777 | input: Parameters[0],
778 | opts?: CreateQueryOptionsForCreateQueries
779 | ) => CreateQueryOptionsForCreateQueries
780 | : CreateQueriesRecord;
781 | };
782 |
783 | type CreateQueriesOpts<
784 | TOpts extends CreateQueryOptionsForCreateQueries[],
785 | TCombinedResult,
786 | > = {
787 | combine?: (result: QueriesResults) => TCombinedResult;
788 | };
789 |
790 | // createServerQueries
791 | type CreateQueryOptionsForCreateServerQueries<
792 | TOutput = unknown,
793 | TError = unknown,
794 | TData = unknown,
795 | TQueryKey extends TRPCQueryKey = TRPCQueryKey,
796 | > = CreateQueryOptionsForCreateQueries & {
797 | ssr?: boolean;
798 | };
799 |
800 | type CreateServerQueriesRecord = {
801 | [K in keyof TClient]: TClient[K] extends HasQuery
802 | ? >, TData = TOutput>(
803 | input: Parameters[0],
804 | opts?: CreateQueryOptionsForCreateServerQueries
805 | ) => CreateQueryOptionsForCreateServerQueries
806 | : CreateServerQueriesRecord;
807 | };
808 |
809 | type CreateQueriesProcedure = {
810 | [Procedure.queries]: <
811 | TOpts extends CreateQueryOptionsForCreateQueries[],
812 | TCombinedResult = QueriesResults,
813 | >(
814 | queriesCallback: (
815 | t: CreateQueriesRecord, TError>
816 | ) => StoreOrVal,
817 | opts?: CreateQueriesOpts
818 | ) => Readable;
819 |
820 | [Procedure.serverQueries]: <
821 | TOpts extends CreateQueryOptionsForCreateServerQueries<
822 | any,
823 | any,
824 | any,
825 | any
826 | >[],
827 | TCombinedResult = QueriesResults,
828 | >(
829 | queriesCallback: (
830 | t: CreateServerQueriesRecord, TError>
831 | ) => readonly [...TOpts],
832 | opts?: CreateQueriesOpts
833 | ) => Promise<
834 | (
835 | queriesCallback?: (
836 | t: CreateServerQueriesRecord, TError>,
837 | old: readonly [...TOpts]
838 | ) => StoreOrVal,
839 | opts?: CreateQueriesOpts
840 | ) => Readable
841 | >;
842 | } & {};
843 |
844 | // Procedures
845 | type TRPCQueryOpts = {
846 | trpc?: {
847 | abortOnUnmount?: boolean;
848 | };
849 | };
850 |
851 | type CreateTRPCQueryOptions<
852 | TOutput,
853 | TError,
854 | TData,
855 | TEnv extends 'client' | 'server' = 'client'
856 | > = Omit, 'queryKey' | 'queryFn'>
857 | & (TEnv extends 'server' ? { ssr?: boolean } : {})
858 | & TRPCQueryOpts
859 | ; // prettier-ignore
860 |
861 | type CreateQueryProcedure = {
862 | [Procedure.query]: {
863 | (
864 | input: StoreOrVal,
865 | opts?: StoreOrVal<
866 | CreateTRPCQueryOptions & { lazy?: TLazy }
867 | >
868 | ): TLazy extends true
869 | ? [
870 | CreateQueryResult,
871 | (data?: Promise) => Promise,
872 | ]
873 | : CreateQueryResult;
874 |
875 | opts: (
876 | opts: CreateTRPCQueryOptions & { lazy?: TLazy }
877 | ) => CreateTRPCQueryOptions & { lazy?: TLazy }; // prettier-ignore
878 | };
879 |
880 | [Procedure.serverQuery]: (
881 | input: TInput,
882 | opts?: CreateTRPCQueryOptions
883 | ) => Promise<
884 | (
885 | input?: StoreOrVal | ((old: TInput) => StoreOrVal),
886 | opts?: StoreOrVal>
887 | ) => CreateQueryResult
888 | >;
889 | } & {};
890 |
891 | type ExtractCursorType = TInput extends { cursor?: any }
892 | ? TInput['cursor']
893 | : unknown;
894 |
895 | type CreateTRPCInfiniteQueryOptions<
896 | TInput,
897 | TOutput,
898 | TError,
899 | TData,
900 | TEnv extends 'client' | 'server' = 'client'
901 | > = Omit<
902 | CreateInfiniteQueryOptions>,
903 | 'queryKey' | 'queryFn' | 'initialPageParam'
904 | >
905 | & { initialCursor?: ExtractCursorType }
906 | & (TEnv extends 'server' ? { ssr?: boolean } : {})
907 | & TRPCQueryOpts
908 | ; // prettier-ignore
909 |
910 | type CreateInfiniteQueryProcedure = {
911 | [Procedure.infiniteQuery]: {
912 | (
913 | input: StoreOrVal>,
914 | opts: StoreOrVal<
915 | CreateTRPCInfiniteQueryOptions & {
916 | lazy?: TLazy;
917 | }
918 | >
919 | ): TLazy extends true
920 | ? [
921 | CreateInfiniteQueryResult<
922 | InfiniteData> | null>,
923 | TError
924 | >,
925 | (data?: Promise) => Promise,
926 | ]
927 | : CreateInfiniteQueryResult<
928 | InfiniteData> | null>,
929 | TError
930 | >;
931 |
932 | opts: (
933 | opts: CreateTRPCInfiniteQueryOptions
934 | ) => CreateTRPCInfiniteQueryOptions; // prettier-ignore
935 | };
936 |
937 | [Procedure.serverInfiniteQuery]: (
938 | input: Omit,
939 | opts: CreateTRPCInfiniteQueryOptions<
940 | TInput,
941 | TOutput,
942 | TError,
943 | TData,
944 | 'server'
945 | >
946 | ) => Promise<
947 | (
948 | input?:
949 | | StoreOrVal>
950 | | ((old: Omit) => StoreOrVal>),
951 | opts?: StoreOrVal<
952 | CreateTRPCInfiniteQueryOptions
953 | >
954 | ) => CreateInfiniteQueryResult<
955 | InfiniteData> | null>,
956 | TError
957 | >
958 | >;
959 | };
960 |
961 | type QueryProcedures =
962 | CreateQueryProcedure
963 | & (TInput extends { cursor?: any }
964 | ? CreateInfiniteQueryProcedure
965 | : {})
966 | & GetQueryKey
967 | ; // prettier-ignore
968 |
969 | type CreateMutationProcedure<
970 | TInput = any,
971 | TOutput = any,
972 | TError = any,
973 | TContext = unknown,
974 | > = {
975 | [Procedure.mutate]: {
976 | (
977 | opts?: CreateMutationOptions
978 | ): CreateMutationResult;
979 |
980 | opts: (
981 | opts: CreateMutationOptions
982 | ) => CreateMutationOptions; // prettier-ignore
983 | };
984 | } & {};
985 |
986 | type CreateSubscriptionOptions = {
987 | enabled?: boolean;
988 | onStarted?: () => void;
989 | onData: (data: TOutput) => void;
990 | onError?: (err: TError) => void;
991 | };
992 |
993 | type GetSubscriptionOutput = TOpts extends unknown & Partial
994 | ? A extends { onData: any }
995 | ? Parameters[0]
996 | : never
997 | : never;
998 |
999 | type CreateSubscriptionProcedure = {
1000 | [Procedure.subscribe]: {
1001 | (input: TInput, opts?: CreateSubscriptionOptions): void;
1002 |
1003 | opts: (
1004 | opts: CreateSubscriptionOptions
1005 | ) => CreateSubscriptionOptions; // prettier-ignore
1006 | };
1007 | } & {};
1008 |
1009 | type AddQueryPropTypes = {
1010 | [K in keyof TClient]: TClient[K] extends HasQuery
1011 | ? QueryProcedures<
1012 | Parameters[0],
1013 | Awaited>,
1014 | TError
1015 | > & {}
1016 | : TClient[K] extends HasMutate
1017 | ? CreateMutationProcedure<
1018 | Parameters[0],
1019 | Awaited>,
1020 | TError
1021 | >
1022 | : TClient[K] extends HasSubscribe
1023 | ? CreateSubscriptionProcedure<
1024 | Parameters[0],
1025 | GetSubscriptionOutput[1]>,
1026 | TError
1027 | >
1028 | : AddQueryPropTypes & GetQueryKey;
1029 | };
1030 |
1031 | type UntypedClient = TRPCUntypedClient;
1032 |
1033 | interface SvelteQueryWrapperContext {
1034 | baseClient: CreateTRPCProxyClient;
1035 | client: UntypedClient;
1036 | queryClient: QueryClient;
1037 | path: string[];
1038 | key: string;
1039 | abortOnUnmount?: boolean;
1040 | }
1041 |
1042 | function getQueryType(
1043 | utilName:
1044 | | Exclude
1045 | | keyof typeof Util.Mutation
1046 | ): QueryType {
1047 | switch (utilName) {
1048 | case 'fetch':
1049 | case 'ensureData':
1050 | case 'prefetch':
1051 | case 'getData':
1052 | case 'setData':
1053 | // case 'setQueriesData':
1054 | return 'query';
1055 |
1056 | case 'fetchInfinite':
1057 | case 'prefetchInfinite':
1058 | case 'getInfiniteData':
1059 | case 'setInfiniteData':
1060 | return 'infinite';
1061 |
1062 | case 'setMutationDefaults':
1063 | case 'getMutationDefaults':
1064 | case 'isMutating':
1065 | case 'cancel':
1066 | case 'invalidate':
1067 | case 'refetch':
1068 | case 'reset':
1069 | return 'any';
1070 | }
1071 | }
1072 |
1073 | function createQueriesProxy({ client, abortOnUnmount }: SvelteQueryWrapperContext) {
1074 | return new DeepProxy(
1075 | {},
1076 | {
1077 | get() {
1078 | return this.nest(() => {});
1079 | },
1080 | apply(_target, _thisArg, argList) {
1081 | const [input, opts] = argList;
1082 |
1083 | const shouldAbortOnUnmount =
1084 | opts?.trpc?.abortOnUnmount ?? abortOnUnmount;
1085 |
1086 | const queryKey = getQueryKeyInternal(this.path, input, 'query');
1087 |
1088 | return {
1089 | ...opts,
1090 | queryKey,
1091 | queryFn: ({ signal }) =>
1092 | client.query(
1093 | ...getClientArgs(queryKey, {
1094 | trpc: {
1095 | ...opts?.trpc,
1096 | ...(shouldAbortOnUnmount && { signal }),
1097 | },
1098 | })
1099 | ),
1100 | } satisfies CreateQueryOptions;
1101 | },
1102 | }
1103 | );
1104 | }
1105 |
1106 | // CREDIT: https://svelte.dev/repl/300c16ee38af49e98261eef02a9b04a8?version=3.38.2
1107 | function effect(
1108 | cb: () => T | void,
1109 | deps: () => U[]
1110 | ) {
1111 | let cleanup: T | void;
1112 |
1113 | function apply() {
1114 | if (cleanup) cleanup();
1115 | cleanup = cb();
1116 | }
1117 |
1118 | if (deps) {
1119 | let values: U[] = [];
1120 | afterUpdate(() => {
1121 | const new_values = deps();
1122 | if (new_values.some((value, i) => value !== values[i])) {
1123 | apply();
1124 | values = new_values;
1125 | }
1126 | });
1127 | } else {
1128 | // no deps = always run
1129 | afterUpdate(apply);
1130 | }
1131 |
1132 | onDestroy(() => {
1133 | if (cleanup) cleanup();
1134 | });
1135 | }
1136 |
1137 | const procedures: Record<
1138 | ValueOf,
1139 | (ctx: SvelteQueryWrapperContext) => any
1140 | > = {
1141 | [Procedure.queryKey]: ({ path }) => {
1142 | return (input?: any, opts?: any) => getQueryKeyInternal(path, input, opts);
1143 | },
1144 | [Procedure.query]: ({ path, client, abortOnUnmount, queryClient }) => {
1145 | return (input: any, opts?: any) => {
1146 | const isOptsStore = isSvelteStore(opts);
1147 | const isInputStore = isSvelteStore(input);
1148 | const currentOpts = isOptsStore ? get(opts) : opts;
1149 |
1150 | const queryKey = getQueryKeyInternal(path, input, 'query');
1151 |
1152 | if (!isInputStore && !isOptsStore && !currentOpts?.lazy) {
1153 | const shouldAbortOnUnmount =
1154 | opts?.trpc?.abortOnUnmount ?? abortOnUnmount;
1155 |
1156 | return createQuery({
1157 | ...opts,
1158 | queryKey,
1159 | queryFn: ({ signal }) =>
1160 | client.query(
1161 | ...getClientArgs(queryKey, {
1162 | trpc: {
1163 | ...opts?.trpc,
1164 | ...(shouldAbortOnUnmount && { signal }),
1165 | },
1166 | })
1167 | ),
1168 | });
1169 | }
1170 |
1171 | const shouldAbortOnUnmount =
1172 | currentOpts?.trpc?.abortOnUnmount ?? abortOnUnmount;
1173 | const enabled = currentOpts?.lazy ? writable(false) : blankStore;
1174 |
1175 | const query = createQuery(
1176 | derived(
1177 | [
1178 | isInputStore ? input : blankStore,
1179 | isOptsStore ? opts : blankStore,
1180 | enabled,
1181 | ],
1182 | ([$input, $opts, $enabled]) => {
1183 | const newInput = !isBlank($input) ? $input : input;
1184 | const newOpts = !isBlank($opts) ? $opts : opts;
1185 |
1186 | const queryKey = getQueryKeyInternal(path, newInput, 'query');
1187 |
1188 | return {
1189 | ...newOpts,
1190 | queryKey,
1191 | queryFn: ({ signal }) =>
1192 | client.query(
1193 | ...getClientArgs(queryKey, {
1194 | trpc: {
1195 | ...newOpts?.trpc,
1196 | ...(shouldAbortOnUnmount && { signal }),
1197 | },
1198 | })
1199 | ),
1200 | ...(!isBlank($enabled) && {
1201 | enabled: $enabled && (newOpts?.enabled ?? true),
1202 | }),
1203 | } satisfies CreateQueryOptions;
1204 | }
1205 | )
1206 | );
1207 |
1208 | return currentOpts?.lazy
1209 | ? [
1210 | query,
1211 | async (data?: any) => {
1212 | if (data) {
1213 | queryClient.setQueryData(queryKey, await data);
1214 | }
1215 | (enabled as Writable).set(true);
1216 | },
1217 | ]
1218 | : query;
1219 | };
1220 | },
1221 | [Procedure.serverQuery]: ({ path, client, queryClient, abortOnUnmount }) => {
1222 | return async (_input: any, _opts?: any) => {
1223 | let input = _input;
1224 | let opts = _opts;
1225 | let shouldAbortOnUnmount = opts?.trpc?.abortOnUnmount ?? abortOnUnmount;
1226 |
1227 | const queryKey = getQueryKeyInternal(path, input, 'query');
1228 |
1229 | const query: FetchQueryOptions = {
1230 | queryKey,
1231 | queryFn: ({ signal }) =>
1232 | client.query(
1233 | ...getClientArgs(queryKey, {
1234 | trpc: {
1235 | ...opts?.trpc,
1236 | ...(shouldAbortOnUnmount && { signal }),
1237 | },
1238 | })
1239 | ),
1240 | };
1241 |
1242 | const cache = queryClient
1243 | .getQueryCache()
1244 | .find({ queryKey: query.queryKey });
1245 | const cacheNotFound = !cache?.state?.data;
1246 | if (opts?.ssr !== false && cacheNotFound) {
1247 | await queryClient.prefetchQuery(query);
1248 | }
1249 |
1250 | return (...args: any[]) => {
1251 | if (args.length > 0) input = args.shift();
1252 | if (args.length > 0) opts = args.shift();
1253 |
1254 | const isOptsStore = isSvelteStore(opts);
1255 | const currentOpts = isOptsStore ? get(opts) : opts;
1256 | shouldAbortOnUnmount =
1257 | currentOpts?.trpc?.abortOnUnmount ?? abortOnUnmount;
1258 |
1259 | const staleTime = writable(Infinity);
1260 | onMount(() => { staleTime.set(null); }); // prettier-ignore
1261 |
1262 | return createQuery(
1263 | derived(
1264 | [
1265 | isSvelteStore(input) ? input : blankStore,
1266 | isOptsStore ? opts : blankStore,
1267 | staleTime,
1268 | ],
1269 | ([$input, $opts, $staleTime]) => {
1270 | const newInput = !isBlank($input) ? $input : input;
1271 | const newOpts = !isBlank($opts) ? $opts : opts;
1272 | const queryKey = getQueryKeyInternal(path, newInput, 'query');
1273 | return {
1274 | ...newOpts,
1275 | queryKey,
1276 | queryFn: ({ signal }) =>
1277 | client.query(
1278 | ...getClientArgs(queryKey, {
1279 | trpc: {
1280 | ...newOpts?.trpc,
1281 | ...(shouldAbortOnUnmount && { signal }),
1282 | },
1283 | })
1284 | ),
1285 | ...($staleTime && { staleTime: $staleTime }),
1286 | } satisfies CreateQueryOptions;
1287 | }
1288 | )
1289 | );
1290 | };
1291 | };
1292 | },
1293 | [Procedure.infiniteQuery]: ({
1294 | path,
1295 | client,
1296 | abortOnUnmount,
1297 | queryClient,
1298 | }) => {
1299 | return (input: any, opts?: any) => {
1300 | const isOptsStore = isSvelteStore(opts);
1301 | const isInputStore = isSvelteStore(input);
1302 | const currentOpts = isOptsStore ? get(opts) : opts;
1303 |
1304 | const queryKey = getQueryKeyInternal(path, input, 'infinite');
1305 |
1306 | if (!isInputStore && !isOptsStore && !currentOpts?.lazy) {
1307 | const shouldAbortOnUnmount =
1308 | opts?.trpc?.abortOnUnmount ?? abortOnUnmount;
1309 |
1310 | return createInfiniteQuery({
1311 | ...opts,
1312 | initialPageParam: opts?.initialCursor ?? null,
1313 | queryKey,
1314 | queryFn: ({ pageParam, signal, direction }) =>
1315 | client.query(
1316 | ...getClientArgs(
1317 | queryKey,
1318 | {
1319 | trpc: {
1320 | ...opts?.trpc,
1321 | ...(shouldAbortOnUnmount && { signal }),
1322 | },
1323 | },
1324 | {
1325 | pageParam: pageParam ?? opts.initialCursor,
1326 | direction,
1327 | }
1328 | )
1329 | ),
1330 | } satisfies CreateInfiniteQueryOptions);
1331 | }
1332 |
1333 | const shouldAbortOnUnmount =
1334 | currentOpts?.trpc?.abortOnUnmount ?? abortOnUnmount;
1335 | const enabled = currentOpts?.lazy ? writable(false) : blankStore;
1336 |
1337 | const query = createInfiniteQuery(
1338 | derived(
1339 | [
1340 | isInputStore ? input : blankStore,
1341 | isOptsStore ? opts : blankStore,
1342 | enabled,
1343 | ],
1344 | ([$input, $opts, $enabled]) => {
1345 | const newInput = !isBlank($input) ? $input : input;
1346 | const newOpts = !isBlank($opts) ? $opts : opts;
1347 | const queryKey = getQueryKeyInternal(path, newInput, 'infinite');
1348 |
1349 | return {
1350 | ...newOpts,
1351 | queryKey,
1352 | queryFn: ({ pageParam, signal, direction }) =>
1353 | client.query(
1354 | ...getClientArgs(
1355 | queryKey,
1356 | {
1357 | trpc: {
1358 | ...newOpts?.trpc,
1359 | ...(shouldAbortOnUnmount && { signal }),
1360 | },
1361 | },
1362 | {
1363 | pageParam: pageParam ?? newOpts.initialCursor,
1364 | direction,
1365 | }
1366 | )
1367 | ),
1368 | ...(!isBlank($enabled) && {
1369 | enabled: $enabled && (newOpts?.enabled ?? true),
1370 | }),
1371 | } satisfies CreateInfiniteQueryOptions;
1372 | }
1373 | )
1374 | );
1375 |
1376 | return currentOpts?.lazy
1377 | ? [
1378 | query,
1379 | async (data?: any) => {
1380 | if (data) {
1381 | queryClient.setQueryData(queryKey, {
1382 | pages: [await data],
1383 | pageParams: [currentOpts?.initialCursor ?? null],
1384 | });
1385 | }
1386 | (enabled as Writable).set(true);
1387 | },
1388 | ]
1389 | : query;
1390 | };
1391 | },
1392 | [Procedure.serverInfiniteQuery]: ({
1393 | path,
1394 | client,
1395 | queryClient,
1396 | abortOnUnmount,
1397 | }) => {
1398 | return async (_input: any, _opts?: any) => {
1399 | let input = _input;
1400 | let opts = _opts;
1401 | let shouldAbortOnUnmount = opts?.trpc?.abortOnUnmount ?? abortOnUnmount;
1402 |
1403 | const queryKey = getQueryKeyInternal(path, input, 'infinite');
1404 |
1405 | const query: Omit = {
1406 | queryKey,
1407 | queryFn: ({ pageParam, signal, direction }) =>
1408 | client.query(
1409 | ...getClientArgs(
1410 | queryKey,
1411 | {
1412 | trpc: {
1413 | ...opts?.trpc,
1414 | ...(shouldAbortOnUnmount && { signal }),
1415 | },
1416 | },
1417 | {
1418 | pageParam: pageParam ?? opts.initialCursor,
1419 | direction,
1420 | }
1421 | )
1422 | ),
1423 | };
1424 |
1425 | const cache = queryClient
1426 | .getQueryCache()
1427 | .find({ queryKey: query.queryKey });
1428 | const cacheNotFound = !cache?.state?.data;
1429 | if (opts?.ssr !== false && cacheNotFound) {
1430 | await queryClient.prefetchInfiniteQuery(query as any);
1431 | }
1432 |
1433 | return (...args: any[]) => {
1434 | if (args.length > 0) input = args.shift();
1435 | if (args.length > 0) opts = args.shift();
1436 |
1437 | const isOptsStore = isSvelteStore(opts);
1438 | const currentOpts = isOptsStore ? get(opts) : opts;
1439 | shouldAbortOnUnmount =
1440 | currentOpts?.trpc?.abortOnUnmount ?? abortOnUnmount;
1441 |
1442 | const staleTime = writable(Infinity);
1443 | onMount(() => { staleTime.set(null); }); // prettier-ignore
1444 |
1445 | return createInfiniteQuery(
1446 | derived(
1447 | [
1448 | isSvelteStore(input) ? input : blankStore,
1449 | isOptsStore ? opts : blankStore,
1450 | staleTime,
1451 | ],
1452 | ([$input, $opts, $staleTime]) => {
1453 | const newInput = !isBlank($input) ? $input : input;
1454 | const newOpts = !isBlank($opts) ? $opts : opts;
1455 | const queryKey = getQueryKeyInternal(path, newInput, 'infinite');
1456 |
1457 | return {
1458 | ...newOpts,
1459 | initialPageParam: newOpts?.initialCursor,
1460 | queryKey,
1461 | queryFn: ({ pageParam, signal, direction }) =>
1462 | client.query(
1463 | ...getClientArgs(
1464 | queryKey,
1465 | {
1466 | trpc: {
1467 | ...newOpts?.trpc,
1468 | ...(shouldAbortOnUnmount && { signal }),
1469 | },
1470 | },
1471 | {
1472 | pageParam: pageParam ?? newOpts.initialCursor,
1473 | direction,
1474 | }
1475 | )
1476 | ),
1477 | ...($staleTime && { staleTime: $staleTime }),
1478 | } satisfies CreateInfiniteQueryOptions;
1479 | }
1480 | )
1481 | );
1482 | };
1483 | };
1484 | },
1485 | [Procedure.mutate]: ({ path, client, queryClient }) => {
1486 | return (opts?: any) => {
1487 | const mutationKey = getMutationKeyInternal(path);
1488 | const defaultOpts = queryClient.defaultMutationOptions(
1489 | queryClient.getMutationDefaults(mutationKey)
1490 | );
1491 |
1492 | // TODO: Add useMutation override to `svelteQueryWrapper`
1493 | const mutationSuccessOverride = (options: any) => options.originalFn();
1494 |
1495 | return createMutation({
1496 | ...opts,
1497 | mutationKey,
1498 | mutationFn: (input) =>
1499 | client.mutation(...getClientArgs([path, { input }], opts)),
1500 | onSuccess(...args) {
1501 | const originalFn = () =>
1502 | opts?.onSuccess?.(...args) ?? defaultOpts?.onSuccess?.(...args);
1503 |
1504 | return mutationSuccessOverride({
1505 | originalFn,
1506 | queryClient,
1507 | meta: opts?.meta ?? defaultOpts?.meta ?? {},
1508 | });
1509 | },
1510 | });
1511 | };
1512 | },
1513 | [Procedure.subscribe]: ({ path, client }) => {
1514 | return (input: any, opts?: any) => {
1515 | const enabled = opts?.enabled ?? true;
1516 | const queryKey = hashKey(getQueryKeyInternal(path, input, 'any'));
1517 |
1518 | effect(
1519 | () => {
1520 | if (!enabled) return;
1521 | let isStopped = false;
1522 | const subscription = client.subscription(
1523 | path.join('.'),
1524 | input ?? undefined,
1525 | {
1526 | onStarted: () => {
1527 | if (!isStopped) opts?.onStarted?.();
1528 | },
1529 | onData: (data: any) => {
1530 | if (!isStopped) opts?.onData?.(data);
1531 | },
1532 | onError: (err: any) => {
1533 | if (!isStopped) opts?.onError?.(err);
1534 | },
1535 | }
1536 | );
1537 | return () => {
1538 | isStopped = true;
1539 | subscription.unsubscribe();
1540 | };
1541 | },
1542 | () => [queryKey, enabled]
1543 | );
1544 | };
1545 | },
1546 | [Procedure.queries]: (ctx) => {
1547 | if (ctx.path.length !== 0) return;
1548 | return (input: (...args: any[]) => any, opts?: any) => {
1549 | return createQueries({
1550 | ...opts,
1551 | queries: input(createQueriesProxy(ctx)),
1552 | });
1553 | };
1554 | },
1555 | [Procedure.serverQueries]: (ctx) => {
1556 | const { path, queryClient } = ctx;
1557 | if (path.length !== 0) return;
1558 | const proxy = createQueriesProxy(ctx);
1559 |
1560 | return async (
1561 | input: (...args: any[]) => QueryObserverOptionsForCreateQueries[],
1562 | _opts?: any
1563 | ) => {
1564 | let opts = _opts;
1565 |
1566 | let queries = input(proxy);
1567 | await Promise.all(
1568 | queries.map(async (query: any) => {
1569 | const cache = queryClient
1570 | .getQueryCache()
1571 | .find({ queryKey: query.queryKey });
1572 | const cacheNotFound = !cache?.state?.data;
1573 |
1574 | if (query.ssr !== false && cacheNotFound) {
1575 | await queryClient.prefetchQuery(query);
1576 | }
1577 | })
1578 | );
1579 |
1580 | return (...args: any[]) => {
1581 | if (args.length > 0) queries = args.shift()!(proxy, queries);
1582 | if (args.length > 0) opts = args.shift();
1583 |
1584 | const staleTime = writable(Infinity);
1585 | onMount(() => { staleTime.set(null); }); // prettier-ignore
1586 |
1587 | return createQueries({
1588 | ...opts,
1589 | queries: derived(
1590 | [isSvelteStore(queries) ? queries : blankStore, staleTime],
1591 | ([$queries, $staleTime]) => {
1592 | const newQueries = !isBlank($queries) ? $queries : queries;
1593 | if (!staleTime) return newQueries;
1594 | return newQueries.map((query) => ({
1595 | ...query,
1596 | ...($staleTime && { staleTime: $staleTime }),
1597 | }));
1598 | }
1599 | ),
1600 | });
1601 | };
1602 | };
1603 | },
1604 | [Procedure.utils]: (ctx) => {
1605 | if (ctx.path.length !== 0) return;
1606 | return () => createUtilsProxy(ctx);
1607 | },
1608 | [Procedure.context]: (ctx) => {
1609 | if (ctx.path.length !== 0) return;
1610 | return () => createUtilsProxy(ctx);
1611 | },
1612 | };
1613 |
1614 | const procedureExts = {
1615 | [Procedure.query]: {
1616 | opts: (opts: unknown) => opts,
1617 | },
1618 | [Procedure.infiniteQuery]: {
1619 | opts: (opts: unknown) => opts,
1620 | },
1621 | [Procedure.mutate]: {
1622 | opts: (opts: unknown) => opts,
1623 | },
1624 | [Procedure.subscribe]: {
1625 | opts: (opts: unknown) => opts,
1626 | },
1627 | };
1628 |
1629 | type ProcedureOrRouter =
1630 | | CreateMutationProcedure
1631 | | CreateQueryProcedure
1632 | | AddQueryPropTypes;
1633 |
1634 | type GetParams =
1635 | TProcedureOrRouter extends CreateQueryProcedure
1636 | ? [input?: GetQueryProcedureInput, type?: QueryType]
1637 | : [];
1638 |
1639 | /**
1640 | * Method to extract the query key for a procedure
1641 | * @param procedureOrRouter - procedure or any router
1642 | * @param input - input to procedureOrRouter
1643 | * @param type - defaults to `any`
1644 | * @link https://trpc.io/docs/v11/getQueryKey
1645 | */
1646 | export function getQueryKey(
1647 | procedureOrRouter: TProcedureOrRouter,
1648 | ..._params: GetParams
1649 | ) {
1650 | const [input, type] = _params;
1651 |
1652 | // @ts-expect-error - we don't expose _def on the type layer
1653 | const path = procedureOrRouter._def().path;
1654 | const queryKey = getQueryKeyInternal(path, input, type ?? 'any');
1655 | return queryKey;
1656 | }
1657 |
1658 | interface SvelteQueryWrapperOptions {
1659 | client: CreateTRPCProxyClient;
1660 | queryClient?: QueryClient;
1661 | abortOnUnmount?: boolean;
1662 | }
1663 |
1664 | export function svelteQueryWrapper({
1665 | client,
1666 | queryClient: _queryClient,
1667 | abortOnUnmount,
1668 | }: SvelteQueryWrapperOptions) {
1669 | type Client = typeof client;
1670 | type RouterError = TRPCClientErrorLike;
1671 | type ClientWithQuery =
1672 | Client extends Record
1673 | ? AddQueryPropTypes
1674 | : Client;
1675 |
1676 | const queryClient = _queryClient ?? useQueryClient();
1677 |
1678 | // REFER: https://github.com/trpc/trpc/blob/c6e46bbd493f0ea32367eaa33c3cabe19a2614a0/packages/client/src/createTRPCClient.ts#L143
1679 | const innerClient = client.__untypedClient as UntypedClient;
1680 |
1681 | return new DeepProxy(
1682 | {} as ClientWithQuery &
1683 | (ClientWithQuery extends Record
1684 | ? CreateUtilsProcedure &
1685 | CreateQueriesProcedure
1686 | : {}),
1687 | {
1688 | get() {
1689 | return this.nest(() => {});
1690 | },
1691 | apply(_target, _thisArg, argList: [any]) {
1692 | const key = this.path.pop() ?? '';
1693 |
1694 | if (key === '_def') return { path: this.path };
1695 |
1696 | if (hasOwn(procedures, key)) {
1697 | return procedures[key]({
1698 | baseClient: client as any,
1699 | client: innerClient,
1700 | path: this.path,
1701 | queryClient,
1702 | abortOnUnmount,
1703 | key,
1704 | })(...argList);
1705 | }
1706 |
1707 | const proc = this.path.pop() ?? '';
1708 | if (hasOwn(procedureExts, proc) && hasOwn(procedureExts[proc], key)) {
1709 | return procedureExts[proc][key](...argList);
1710 | }
1711 | },
1712 | }
1713 | );
1714 | }
1715 |
--------------------------------------------------------------------------------
/@lib/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.base.json",
3 | "include": [
4 | "src/**/*"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/@shared/eslint/index.d.ts:
--------------------------------------------------------------------------------
1 | declare const _default: import("eslint").Linter.FlatConfig[];
2 | export default _default;
3 | //# sourceMappingURL=index.d.ts.map
--------------------------------------------------------------------------------
/@shared/eslint/index.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.js"],"names":[],"mappings":"wBAKU,OAAO,QAAQ,EAAE,MAAM,CAAC,UAAU,EAAE"}
--------------------------------------------------------------------------------
/@shared/eslint/index.js:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js';
2 | import ts from 'typescript-eslint';
3 | import prettier from 'eslint-config-prettier';
4 | import globals from 'globals';
5 |
6 | /** @type import('eslint').Linter.FlatConfig[] **/
7 | export default [
8 | js.configs.recommended,
9 | ...ts.configs.recommended,
10 | prettier,
11 | {
12 | languageOptions: {
13 | globals: {
14 | ...globals.browser,
15 | ...globals.node,
16 | },
17 | },
18 | },
19 | {
20 | rules: {
21 | '@typescript-eslint/no-explicit-any': 'off',
22 | '@typescript-eslint/ban-types': 'off',
23 | '@typescript-eslint/no-empty-function': 'off',
24 | '@typescript-eslint/no-unused-vars': 'off',
25 | '@typescript-eslint/no-empty-object-type': 'off',
26 |
27 | quotes: ['warn', 'single'],
28 | semi: ['warn', 'always'],
29 |
30 | 'comma-dangle': [
31 | 'warn',
32 | {
33 | arrays: 'always-multiline',
34 | exports: 'always-multiline',
35 | functions: 'never',
36 | imports: 'always-multiline',
37 | objects: 'always-multiline',
38 | },
39 | ],
40 | },
41 | },
42 | ];
43 |
--------------------------------------------------------------------------------
/@shared/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "shared",
3 | "version": "1.0.0",
4 | "description": "Shared utils for this monorepo",
5 | "type": "module",
6 | "exports": {
7 | "./eslint": "./eslint/index.js",
8 | "./prettier": "./prettier/index.js"
9 | },
10 | "devDependencies": {
11 | "eslint": "^9.0.0",
12 | "eslint-config-prettier": "^9.1.0",
13 | "globals": "^15.0.0",
14 | "prettier": "^3.1.1",
15 | "typescript-eslint": "^8.0.0-alpha.20"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/@shared/prettier/index.d.ts:
--------------------------------------------------------------------------------
1 | declare const _default: import("prettier").Config;
2 | export default _default;
3 | //# sourceMappingURL=index.d.ts.map
--------------------------------------------------------------------------------
/@shared/prettier/index.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.js"],"names":[],"mappings":"wBAKU,OAAO,UAAU,EAAE,MAAM"}
--------------------------------------------------------------------------------
/@shared/prettier/index.js:
--------------------------------------------------------------------------------
1 |
2 | // prettier.config.js, .prettierrc.js, prettier.config.cjs, or .prettierrc.cjs
3 |
4 | /**
5 | * @see https://prettier.io/docs/en/configuration.html
6 | * @type {import("prettier").Config}
7 | */
8 | export default {
9 | singleQuote: true,
10 | semi: true,
11 | useTabs: true,
12 | tabWidth: 2,
13 | trailingComma: "es5",
14 | };
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | @lib/README.md
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "root",
3 | "private": true,
4 | "scripts": {
5 | "lib": "pnpm --filter=trpc-svelte-query-adapter",
6 | "app": "pnpm --filter=example",
7 | "list-pkgs": "pnpm m ls --json --depth=-1 | node -e \"const path = require('path'); console.log(JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf-8')).map((m) => path.relative(__dirname, m.path)).filter(Boolean))\""
8 | },
9 | "devDependencies": {
10 | "typescript": "^5.0.0"
11 | },
12 | "dependencies": {
13 | "shared": "workspace:^"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - './@*'
3 |
--------------------------------------------------------------------------------
/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "module": "ESNext",
6 | "lib": ["ESNext", "DOM", "DOM.Iterable"],
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "Bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 |
16 | /* Linting */
17 | "strict": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "noFallthroughCasesInSwitch": true
21 | }
22 | }
23 |
24 |
--------------------------------------------------------------------------------