├── .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 | 18 | 19 | 20 | 21 | {/if} 22 | 23 |

24 | 25 | {#if suffix} 26 |
27 | 32 | 33 | 34 |
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 |
{ 58 | // @ts-expect-error - ?? 59 | const { text } = e.currentTarget.elements; 60 | $createTodo.mutate(text.value); 61 | }} 62 | > 63 | 64 | 65 |
66 | 74 | 79 |
80 | 81 | {#if $createTodo.isError} 82 |
83 | {#each JSON.parse($createTodo.error.message) as error} 84 | 85 | Error: {error.message} 86 | 87 | {/each} 88 |
89 | {/if} 90 |
91 | 92 |
93 | 94 | 95 |
96 | { 102 | if (!(e.target instanceof HTMLInputElement)) return; 103 | $filter = e.target.value || undefined; 104 | }, 500)} 105 | /> 106 | { 112 | if (!(e.target instanceof HTMLInputElement)) return; 113 | $opts.refetchInterval = e.target.value ? +e.target.value : Infinity; 114 | }, 500)} 115 | /> 116 |
117 |
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 |
134 | {#each $todos.data as todo} 135 |
136 | { 142 | $updateTodo.mutate({ id: todo.id, done: !todo.done }); 143 | }} 144 | /> 145 | 146 | 147 | {#if todo.done} 148 | {todo.text} 149 | {:else} 150 | {todo.text} 151 | {/if} 152 | 153 | 154 | 165 |
166 | {/each} 167 |
168 | {/if} 169 | {#if $createTodo.isPending || $deleteTodo.isPending || $updateTodo.isPending} 170 | 171 | {/if} 172 |
173 | 174 |
175 |

Popular Todos (from JSONPlaceholder API)

176 | 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 | 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 |
{ 64 | // @ts-expect-error - ?? 65 | const { text } = e.currentTarget.elements; 66 | $createTodo.mutate(text.value); 67 | }} 68 | > 69 | 70 | 71 |
72 | 80 | 85 |
86 | 87 | {#if $createTodo.isError} 88 |
89 | {#each JSON.parse($createTodo.error.message) as error} 90 | 91 | Error: {error.message} 92 | 93 | {/each} 94 |
95 | {/if} 96 |
97 | 98 |
99 | 100 | 101 |
102 | { 108 | if (!(e.target instanceof HTMLInputElement)) return; 109 | $filter = e.target.value || undefined; 110 | }, 500)} 111 | /> 112 | { 118 | if (!(e.target instanceof HTMLInputElement)) return; 119 | $opts.refetchInterval = e.target.value ? +e.target.value : Infinity; 120 | }, 500)} 121 | /> 122 |
123 |
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 |
140 | {#each $todos.data as todo} 141 |
142 | { 148 | $updateTodo.mutate({ id: todo.id, done: !todo.done }); 149 | }} 150 | /> 151 | 152 | 153 | {#if todo.done} 154 | {todo.text} 155 | {:else} 156 | {todo.text} 157 | {/if} 158 | 159 | 160 | 171 |
172 | {/each} 173 |
174 | {/if} 175 | {#if $createTodo.isPending || $deleteTodo.isPending || $updateTodo.isPending} 176 | 177 | {/if} 178 |
179 | 180 |
181 |

Popular Todos (from JSONPlaceholder API)

182 | 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 | 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 |
{ 53 | // @ts-expect-error - ?? 54 | const { text } = e.currentTarget.elements; 55 | $createTodo.mutate(text.value); 56 | }} 57 | > 58 | 59 | 60 |
61 | 69 | 74 |
75 | 76 | {#if $createTodo.isError} 77 |
78 | {#each JSON.parse($createTodo.error.message) as error} 79 | 80 | Error: {error.message} 81 | 82 | {/each} 83 |
84 | {/if} 85 |
86 | 87 |
88 | 89 | 90 |
91 | { 97 | if (!(e.target instanceof HTMLInputElement)) return; 98 | $filter = e.target.value || undefined; 99 | }, 500)} 100 | /> 101 | { 107 | if (!(e.target instanceof HTMLInputElement)) return; 108 | $opts.refetchInterval = e.target.value ? +e.target.value : Infinity; 109 | }, 500)} 110 | /> 111 |
112 |
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 |
129 | {#each $todos.data as todo} 130 |
131 | { 137 | $updateTodo.mutate({ id: todo.id, done: !todo.done }); 138 | }} 139 | /> 140 | 141 | 142 | {#if todo.done} 143 | {todo.text} 144 | {:else} 145 | {todo.text} 146 | {/if} 147 | 148 | 149 | 160 |
161 | {/each} 162 |
163 | {/if} 164 | {#if $createTodo.isPending || $deleteTodo.isPending || $updateTodo.isPending} 165 | 166 | {/if} 167 |
168 | 169 |
170 |

Popular Todos (from JSONPlaceholder API)

171 | 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 | 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 |
11 |

Examples

12 | 17 |
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 |
{ 315 | const data = new FormData(e.currentTarget).get('name'); 316 | if (typeof data === 'string') $newNames.push(data); 317 | $newNames = $newNames; 318 | }}> 319 | 320 | 321 |
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 | --------------------------------------------------------------------------------