snapshot of https://github.com/TanStack/query.git (main, 2026-09-16; blobs > 131072B omitted)
Some checks failed
autofix.ci / autofix (push) Failing after 4s
Release / Release (push) Has been skipped
GitHub Actions Security Analysis / Run zizmor (push) Failing after 2s

This commit is contained in:
upstream-mirror
2026-09-16 01:13:08 +05:00
commit 3a013ccdfe
3570 changed files with 490194 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
!lib/

View File

@@ -0,0 +1,38 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm create svelte@latest
# create a new project in my-app
npm create svelte@latest my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn` or `bun install`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.

View File

@@ -0,0 +1,23 @@
{
"name": "@tanstack/query-example-svelte-auto-refetching",
"private": true,
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@tanstack/svelte-query": "^6.1.48",
"@tanstack/svelte-query-devtools": "^6.1.48"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^6.1.0",
"@sveltejs/kit": "^2.57.1",
"@sveltejs/vite-plugin-svelte": "^5.1.1",
"svelte": "^5.39.3",
"svelte-check": "^4.3.1",
"typescript": "5.8.3",
"vite": "^6.4.1"
}
}

View File

@@ -0,0 +1,81 @@
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
.card {
padding: 2em;
}
main {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

View File

@@ -0,0 +1,12 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface Platform {}
}
}
export {}

View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@@ -0,0 +1,23 @@
<script lang="ts">
import '../app.css'
import { browser } from '$app/environment'
import { QueryClientProvider, QueryClient } from '@tanstack/svelte-query'
import { SvelteQueryDevtools } from '@tanstack/svelte-query-devtools'
const { children } = $props()
const queryClient = new QueryClient({
defaultOptions: {
queries: {
enabled: browser,
},
},
})
</script>
<QueryClientProvider client={queryClient}>
<main>
{@render children()}
</main>
<SvelteQueryDevtools />
</QueryClientProvider>

View File

@@ -0,0 +1,109 @@
<script lang="ts">
import {
useQueryClient,
createQuery,
createMutation,
} from '@tanstack/svelte-query'
let intervalMs = $state(1000)
let value = $state<string>('')
const client = useQueryClient()
const endpoint = '/api/data'
const todos = createQuery<{ items: string[] }>(() => ({
queryKey: ['refetch'],
queryFn: async () => await fetch(endpoint).then((r) => r.json()),
// Refetch the data every second
refetchInterval: intervalMs,
}))
const addMutation = createMutation(() => ({
mutationFn: (value: string) =>
fetch(`${endpoint}?add=${encodeURIComponent(value)}`).then((r) =>
r.json(),
),
onSuccess: () => client.invalidateQueries({ queryKey: ['refetch'] }),
}))
const clearMutation = createMutation(() => ({
mutationFn: () => fetch(`${endpoint}?clear=1`).then((r) => r.json()),
onSuccess: () => client.invalidateQueries({ queryKey: ['refetch'] }),
}))
</script>
<h1>Auto Refetch with stale-time set to {intervalMs}ms</h1>
<p>
This example is best experienced on your own machine, where you can open
multiple tabs to the same localhost server and see your changes propagate
between the two.
</p>
<label>
Query Interval speed (ms):{' '}
<div class="flex">
<input bind:value={intervalMs} type="number" step="100" />{' '}
<span
style="display:inline-block;
margin-left:.5rem;
width:.75rem;
height:.75rem;
background: {todos.isFetching ? 'green' : 'transparent'};
transition: {!todos.isFetching ? 'all .3s ease' : 'none'};
border-radius: 100%;
transform: scale(1.5)"
></span>
</div>
</label>
<h2>Todo List</h2>
<form
onsubmit={(e) => {
e.preventDefault()
e.stopPropagation()
addMutation.mutate(value, {
onSuccess: () => (value = ''),
})
}}
>
<input placeholder="enter something" bind:value />
</form>
{#if todos.isPending}
Loading...
{/if}
{#if todos.error}
An error has occurred:
{todos.error.message}
{/if}
{#if todos.isSuccess}
<ul>
{#each todos.data.items as item}
<li>{item}</li>
{/each}
</ul>
<div>
<button onclick={() => clearMutation.mutate(undefined)}> Clear All </button>
</div>
{/if}
<pre
class={['updating-text', todos.isFetching && 'on']}
style="font-weight:700">Background Updating...</pre>
<style>
li {
text-align: left;
}
.updating-text {
color: transparent;
transition: all 0.3s ease;
}
.updating-text.on {
color: green;
transition: none;
}
</style>

View File

@@ -0,0 +1,20 @@
import { json } from '@sveltejs/kit'
import type { RequestHandler } from '@sveltejs/kit'
const list = { items: ['Item 1', 'Item 2', 'Item 3'] }
/** @type {import('./$types').RequestHandler} */
export const GET: RequestHandler = async ({ url }) => {
const add = url.searchParams.get('add')
const clear = url.searchParams.get('clear')
if (add) {
if (!list.items.includes(add)) {
list.items.push(add)
}
} else if (clear) {
list.items = []
}
await new Promise((r) => setTimeout(r, 200))
return json(list, { status: 200 })
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,15 @@
import adapter from '@sveltejs/adapter-auto'
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
adapter: adapter(),
},
compilerOptions: {
runes: true,
},
}
export default config

View File

@@ -0,0 +1,17 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

View File

@@ -0,0 +1,6 @@
import { sveltekit } from '@sveltejs/kit/vite'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [sveltekit()],
})

9
examples/svelte/basic/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
!lib/

View File

@@ -0,0 +1,38 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm create svelte@latest
# create a new project in my-app
npm create svelte@latest my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn` or `bun install`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.

View File

@@ -0,0 +1,25 @@
{
"name": "@tanstack/query-example-svelte-basic",
"private": true,
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@tanstack/query-async-storage-persister": "^5.102.8",
"@tanstack/svelte-query": "^6.1.48",
"@tanstack/svelte-query-devtools": "^6.1.48",
"@tanstack/svelte-query-persist-client": "^6.1.48"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^6.1.0",
"@sveltejs/kit": "^2.57.1",
"@sveltejs/vite-plugin-svelte": "^5.1.1",
"svelte": "^5.39.3",
"svelte-check": "^4.3.1",
"typescript": "5.8.3",
"vite": "^6.4.1"
}
}

View File

@@ -0,0 +1,81 @@
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
.card {
padding: 2em;
}
main {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
.button:hover {
border-color: #646cff;
}
.button:focus,
.button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
.button {
background-color: #f9f9f9;
}
}

12
examples/svelte/basic/src/app.d.ts vendored Normal file
View File

@@ -0,0 +1,12 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface Platform {}
}
}
export {}

View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@@ -0,0 +1,31 @@
<script lang="ts">
import { createQuery } from '@tanstack/svelte-query'
import { getPostById } from './data'
import type { Post } from './types'
const { postId }: { postId: number } = $props()
const post = createQuery<Post>(() => ({
queryKey: ['post', postId],
queryFn: () => getPostById(postId),
}))
</script>
<div>
<div>
<a class="button" href="/"> Back </a>
</div>
{#if !postId || post.isPending}
<span>Loading...</span>
{/if}
{#if post.error}
<span>Error: {post.error.message}</span>
{/if}
{#if post.isSuccess}
<h1>{post.data.title}</h1>
<div>
<p>{post.data.body}</p>
</div>
<div>{post.isFetching ? 'Background Updating...' : ' '}</div>
{/if}
</div>

View File

@@ -0,0 +1,66 @@
<script lang="ts">
import { useQueryClient, createQuery } from '@tanstack/svelte-query'
import { getPosts } from './data'
const client = useQueryClient()
let limit = 10
const posts = createQuery<
{ id: number; title: string; body: string }[],
Error
>(() => ({
queryKey: ['posts', limit],
queryFn: () => getPosts(limit),
}))
</script>
<div>
<div>
{#if posts.status === 'pending'}
<span>Loading...</span>
{:else if posts.status === 'error'}
<span>Error: {posts.error.message}</span>
{:else}
<ul>
{#each posts.data as post}
<article>
<a
href={`/${post.id}`}
style={// We can use the queryCache here to show bold links for
// ones that are cached
client.getQueryData(['post', post.id])
? 'font-weight: bold; color: indianred'
: 'cursor: pointer'}
>
{post.title}
</a>
</article>
{/each}
</ul>
<pre
class={['updating-text', posts.isFetching && 'on']}
style="font-weight:700">Background Updating...</pre>
{/if}
</div>
</div>
<style>
article {
text-align: left;
}
a {
display: block;
font-size: 1.5rem;
margin-bottom: 1rem;
}
.updating-text {
color: transparent;
transition: all 0.3s ease;
}
.updating-text.on {
color: green;
transition: none;
}
</style>

View File

@@ -0,0 +1,15 @@
import type { Post } from './types'
export const getPosts = async (limit: number) => {
const response = await fetch('https://jsonplaceholder.typicode.com/posts')
const data = (await response.json()) as Array<Post>
return data.filter((x) => x.id <= limit)
}
export const getPostById = async (id: number): Promise<Post> => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts/${id}`,
)
const data = (await response.json()) as Post
return data
}

View File

@@ -0,0 +1,5 @@
export type Post = {
id: number
title: string
body: string
}

View File

@@ -0,0 +1,29 @@
<script lang="ts">
import '../app.css'
import { browser } from '$app/environment'
import { QueryClient } from '@tanstack/svelte-query'
import { SvelteQueryDevtools } from '@tanstack/svelte-query-devtools'
import { PersistQueryClientProvider } from '@tanstack/svelte-query-persist-client'
import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister'
const { children } = $props()
const queryClient = new QueryClient({
defaultOptions: {
queries: {
enabled: browser,
},
},
})
const persister = createAsyncStoragePersister({
storage: browser ? window.localStorage : null,
})
</script>
<PersistQueryClientProvider client={queryClient} persistOptions={{ persister }}>
<main>
{@render children()}
</main>
<SvelteQueryDevtools />
</PersistQueryClientProvider>

View File

@@ -0,0 +1,6 @@
<script lang="ts">
import Posts from '$lib/Posts.svelte'
</script>
<h1>Basic Query</h1>
<Posts />

View File

@@ -0,0 +1,7 @@
<script lang="ts">
import Post from '$lib/Post.svelte'
const { data } = $props()
</script>
<Post postId={data.postId} />

View File

@@ -0,0 +1,6 @@
import type { PageLoad } from './$types'
export const load: PageLoad = async ({ params }) => {
const postId = parseInt(params.postId)
return { postId }
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,15 @@
import adapter from '@sveltejs/adapter-auto'
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
adapter: adapter(),
},
compilerOptions: {
runes: true,
},
}
export default config

View File

@@ -0,0 +1,17 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

View File

@@ -0,0 +1,6 @@
import { sveltekit } from '@sveltejs/kit/vite'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [sveltekit()],
})

View File

@@ -0,0 +1,10 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

View File

@@ -0,0 +1,38 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm create svelte@latest
# create a new project in my-app
npm create svelte@latest my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn` or `bun install`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.

View File

@@ -0,0 +1,23 @@
{
"name": "@tanstack/query-example-svelte-load-more-infinite-scroll",
"private": true,
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@tanstack/svelte-query": "^6.1.48",
"@tanstack/svelte-query-devtools": "^6.1.48"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^6.1.0",
"@sveltejs/kit": "^2.57.1",
"@sveltejs/vite-plugin-svelte": "^5.1.1",
"svelte": "^5.39.3",
"svelte-check": "^4.3.1",
"typescript": "5.8.3",
"vite": "^6.4.1"
}
}

View File

@@ -0,0 +1,81 @@
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
.card {
padding: 2em;
}
main {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
.button:hover {
border-color: #646cff;
}
.button:focus,
.button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
.button {
background-color: #f9f9f9;
}
}

View File

@@ -0,0 +1,12 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface Platform {}
}
}
export {}

View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@@ -0,0 +1,65 @@
<script lang="ts">
import { createInfiniteQuery } from '@tanstack/svelte-query'
const endPoint = 'https://swapi.dev/api'
const fetchPlanets = async ({ pageParam = 1 }) =>
await fetch(`${endPoint}/planets/?page=${pageParam}`).then((r) => r.json())
const query = createInfiniteQuery(() => ({
queryKey: ['planets'],
queryFn: ({ pageParam }) => fetchPlanets({ pageParam }),
initialPageParam: 1,
getNextPageParam: (lastPage) => {
if (lastPage.next) {
const nextUrl = new URLSearchParams(new URL(lastPage.next).search)
const nextCursor = nextUrl.get('page')
if (nextCursor) {
return +nextCursor
}
}
return undefined
},
}))
</script>
{#if query.isPending}
Loading...
{/if}
{#if query.error}
<span>Error: {query.error.message}</span>
{/if}
{#if query.isSuccess}
<div>
{#each query.data.pages as { results }}
{#each results as planet}
<div class="card">
<div class="card-body">
<h2 class="card-title">Planet Name: {planet.name}</h2>
<p>Population: {planet.population}</p>
</div>
</div>
{/each}
{/each}
</div>
<div>
<button
onclick={() => query.fetchNextPage()}
disabled={!query.hasNextPage || query.isFetchingNextPage}
>
{#if query.isFetching}
Loading more...
{:else if query.hasNextPage}
Load More
{:else}Nothing more to load{/if}
</button>
</div>
{/if}
<style>
.card {
background-color: #111;
margin-bottom: 1rem;
color: rgba(255, 255, 255, 0.87);
}
</style>

View File

@@ -0,0 +1,23 @@
<script lang="ts">
import '../app.css'
import { browser } from '$app/environment'
import { QueryClientProvider, QueryClient } from '@tanstack/svelte-query'
import { SvelteQueryDevtools } from '@tanstack/svelte-query-devtools'
const { children } = $props()
const queryClient = new QueryClient({
defaultOptions: {
queries: {
enabled: browser,
},
},
})
</script>
<QueryClientProvider client={queryClient}>
<main>
{@render children()}
</main>
<SvelteQueryDevtools />
</QueryClientProvider>

View File

@@ -0,0 +1,6 @@
<script lang="ts">
import LoadMore from '../lib/LoadMore.svelte'
</script>
<h1>Infinite Load More</h1>
<LoadMore />

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,15 @@
import adapter from '@sveltejs/adapter-auto'
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
adapter: adapter(),
},
compilerOptions: {
runes: true,
},
}
export default config

View File

@@ -0,0 +1,17 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

View File

@@ -0,0 +1,6 @@
import { sveltekit } from '@sveltejs/kit/vite'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [sveltekit()],
})

View File

@@ -0,0 +1,9 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
!lib/

View File

@@ -0,0 +1,38 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm create svelte@latest
# create a new project in my-app
npm create svelte@latest my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn` or `bun install`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.

View File

@@ -0,0 +1,23 @@
{
"name": "@tanstack/query-example-svelte-optimistic-updates",
"private": true,
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@tanstack/svelte-query": "^6.1.48",
"@tanstack/svelte-query-devtools": "^6.1.48"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^6.1.0",
"@sveltejs/kit": "^2.57.1",
"@sveltejs/vite-plugin-svelte": "^5.1.1",
"svelte": "^5.39.3",
"svelte-check": "^4.3.1",
"typescript": "5.8.3",
"vite": "^6.4.1"
}
}

View File

@@ -0,0 +1,81 @@
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
.card {
padding: 2em;
}
main {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

View File

@@ -0,0 +1,12 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface Platform {}
}
}
export {}

View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@@ -0,0 +1,23 @@
<script lang="ts">
import '../app.css'
import { browser } from '$app/environment'
import { QueryClientProvider, QueryClient } from '@tanstack/svelte-query'
import { SvelteQueryDevtools } from '@tanstack/svelte-query-devtools'
const { children } = $props()
const queryClient = new QueryClient({
defaultOptions: {
queries: {
enabled: browser,
},
},
})
</script>
<QueryClientProvider client={queryClient}>
<main>
{@render children()}
</main>
<SvelteQueryDevtools />
</QueryClientProvider>

View File

@@ -0,0 +1,129 @@
<script lang="ts">
import '../app.css'
import {
useQueryClient,
createQuery,
createMutation,
} from '@tanstack/svelte-query'
type Todo = {
id: string
text: string
}
type Todos = {
items: readonly Todo[]
ts: number
}
let text = $state<string>('')
const client = useQueryClient()
const endpoint = '/api/data'
const fetchTodos = async (): Promise<Todos> =>
await fetch(endpoint).then((r) => r.json())
const createTodo = async (text: string): Promise<Todo> =>
await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
text,
}),
}).then((res) => res.json())
const todos = createQuery<Todos>(() => ({
queryKey: ['optimistic'],
queryFn: fetchTodos,
}))
const addTodoMutation = createMutation(() => ({
mutationFn: createTodo,
onMutate: async (newTodo: string) => {
text = ''
// Cancel any outgoing refetches (so they don't overwrite our optimistic update)
await client.cancelQueries({ queryKey: ['optimistic'] })
// Snapshot the previous value
const previousTodos = client.getQueryData<Todos>(['optimistic'])
// Optimistically update to the new value
if (previousTodos) {
client.setQueryData<Todos>(['optimistic'], {
...previousTodos,
items: [
...previousTodos.items,
{ id: Math.random().toString(), text: newTodo },
],
})
}
return { previousTodos }
},
// If the mutation fails, use the context returned from onMutate to roll back
onError: (err: any, variables: any, context: any) => {
if (context?.previousTodos) {
client.setQueryData<Todos>(['optimistic'], context.previousTodos)
}
},
// Always refetch after error or success:
onSettled: () => {
client.invalidateQueries({ queryKey: ['optimistic'] })
},
}))
</script>
<h1>Optimistic Updates</h1>
<p>
In this example, new items can be created using a mutation. The new item will
be optimistically added to the list in hopes that the server accepts the item.
If it does, the list is refetched with the true items from the list. Every now
and then, the mutation may fail though. When that happens, the previous list
of items is restored and the list is again refetched from the server.
</p>
<form
onsubmit={(e) => {
e.preventDefault()
e.stopPropagation()
addTodoMutation.mutate(text)
}}
>
<div>
<input type="text" bind:value={text} />
<button disabled={addTodoMutation.isPending}>Create</button>
</div>
</form>
{#if todos.isPending}
Loading...
{/if}
{#if todos.error}
An error has occurred:
{todos.error.message}
{/if}
{#if todos.isSuccess}
<div class="mb-4">
Updated At: {new Date(todos.data.ts).toLocaleTimeString()}
</div>
<ul>
{#each todos.data.items as todo}
<li>{todo.text}</li>
{/each}
</ul>
{/if}
{#if todos.isFetching}
<div style="color:darkgreen; font-weight:700">
'Background Updating...' : ' '
</div>
{/if}
<style>
li {
text-align: left;
}
</style>

View File

@@ -0,0 +1,31 @@
import { json } from '@sveltejs/kit'
import type { RequestHandler } from '@sveltejs/kit'
type Todo = {
id: string
text: string
}
const items: Array<Todo> = []
/** @type {import('./$types').RequestHandler} */
export const GET: RequestHandler = async (req) => {
await new Promise((r) => setTimeout(r, 1000))
return json({ ts: Date.now(), items }, { status: 200 })
}
/** @type {import('./$types').RequestHandler} */
export const POST: RequestHandler = async ({ request }) => {
const { text } = await request.json()
if (Math.random() > 0.7) {
return json({ message: 'Could not add item!' }, { status: 500 })
}
const newTodo = {
id: Math.random().toString(),
text: text.toUpperCase() as string,
}
items.push(newTodo)
return json(newTodo, { status: 200 })
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,15 @@
import adapter from '@sveltejs/adapter-auto'
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
adapter: adapter(),
},
compilerOptions: {
runes: true,
},
}
export default config

View File

@@ -0,0 +1,17 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

View File

@@ -0,0 +1,6 @@
import { sveltekit } from '@sveltejs/kit/vite'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [sveltekit()],
})

9
examples/svelte/playground/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
!lib/

View File

@@ -0,0 +1,8 @@
# Example
This example is a rewrite of the React Query playground example.
To run this example:
- `npm install`
- `npm run dev`

View File

@@ -0,0 +1,23 @@
{
"name": "@tanstack/query-example-svelte-playground",
"private": true,
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@tanstack/svelte-query": "^6.1.48",
"@tanstack/svelte-query-devtools": "^6.1.48"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^6.1.0",
"@sveltejs/kit": "^2.57.1",
"@sveltejs/vite-plugin-svelte": "^5.1.1",
"svelte": "^5.39.3",
"svelte-check": "^4.3.1",
"typescript": "5.8.3",
"vite": "^6.4.1"
}
}

View File

@@ -0,0 +1,11 @@
body {
margin: 0;
padding: 1rem;
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: white;
background: #0b1521;
}

12
examples/svelte/playground/src/app.d.ts vendored Normal file
View File

@@ -0,0 +1,12 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface Platform {}
}
}
export {}

View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@@ -0,0 +1,37 @@
export function ref<T>(initial: T) {
let value = $state(initial)
return {
get value() {
return value
},
set value(newValue) {
value = newValue
},
}
}
export const staleTime = ref(1000)
export const gcTime = ref(3000)
export const errorRate = ref(0.05)
export const queryTimeMin = ref(1000)
export const queryTimeMax = ref(2000)
export const editingIndex = ref<number | null>(null)
export const views = ref(['', 'fruit', 'grape'])
let initialId = 0
const initialList = [
'apple',
'banana',
'pineapple',
'grapefruit',
'dragonfruit',
'grapes',
].map((d) => ({ id: initialId++, name: d, notes: 'These are some notes' }))
export const list = ref(initialList)
export const id = ref(initialId)
export type Todos = typeof initialList
export type Todo = Todos[0]

View File

@@ -0,0 +1,27 @@
<script lang="ts">
import '../app.css'
import { browser } from '$app/environment'
import { QueryClientProvider, QueryClient } from '@tanstack/svelte-query'
import { SvelteQueryDevtools } from '@tanstack/svelte-query-devtools'
const { children } = $props()
const queryClient = new QueryClient({
defaultOptions: {
queries: {
enabled: browser,
},
},
})
</script>
<svelte:head>
<title>Svelte Query Playground Example</title>
</svelte:head>
<QueryClientProvider client={queryClient}>
<div id="app">
{@render children()}
</div>
<SvelteQueryDevtools />
</QueryClientProvider>

View File

@@ -0,0 +1,81 @@
<script lang="ts">
import { useQueryClient } from '@tanstack/svelte-query'
import {
staleTime,
gcTime,
errorRate,
queryTimeMin,
queryTimeMax,
} from '../lib/stores.svelte'
import App from './App.svelte'
const queryClient = useQueryClient()
queryClient.setDefaultOptions({
queries: {
staleTime: staleTime.value,
gcTime: gcTime.value,
},
})
</script>
<p>
The "staleTime" and "gcTime" durations have been altered in this example to
show how query stale-ness and query caching work on a granular level
</p>
<div>
Stale Time:{' '}
<input
type="number"
min="0"
step="1000"
bind:value={staleTime.value}
style="width: 100px"
/>
</div>
<div>
GcTime:{' '}
<input
type="number"
min="0"
step="1000"
bind:value={gcTime.value}
style="width: 100px"
/>
</div>
<br />
<div>
Error Rate:{' '}
<input
type="number"
min="0"
max="1"
step=".05"
bind:value={errorRate.value}
style="width: 100px"
/>
</div>
<div>
Fetch Time Min:{' '}
<input
type="number"
min="1"
step="500"
bind:value={queryTimeMin.value}
style="width: 100px"
/>{' '}
</div>
<div>
Fetch Time Max:{' '}
<input
type="number"
min="1"
step="500"
bind:value={queryTimeMax.value}
style="width: 100px"
/>
</div>
<br />
<App />
<br />

View File

@@ -0,0 +1,67 @@
<script lang="ts">
import { createMutation, useQueryClient } from '@tanstack/svelte-query'
import {
errorRate,
queryTimeMin,
queryTimeMax,
list,
id,
} from '$lib/stores.svelte'
import type { Todo } from '$lib/stores.svelte'
const queryClient = useQueryClient()
let name = $state('')
const postTodo = async ({ name, notes }: Omit<Todo, 'id'>) => {
return new Promise((resolve, reject) => {
setTimeout(
() => {
if (Math.random() < errorRate.value) {
return reject(
new Error(
JSON.stringify(
{ postTodo: { name: $state.snapshot(name), notes } },
null,
2,
),
),
)
}
const todo = { name, notes, id: id.value }
id.value = id.value + 1
list.value.push(todo)
resolve(todo)
},
queryTimeMin.value +
Math.random() * (queryTimeMax.value - queryTimeMin.value),
)
})
}
const addMutation = createMutation(() => ({
mutationFn: postTodo,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] })
},
}))
</script>
<div>
<input bind:value={name} disabled={addMutation.status === 'pending'} />
<button
onclick={() => addMutation.mutate({ name, notes: name })}
disabled={addMutation.status === 'pending' || !name}
>
Add Todo
</button>
<div>
{addMutation.status === 'pending'
? 'Saving...'
: addMutation.status === 'error'
? addMutation.error.message
: 'Saved!'}
</div>
</div>

View File

@@ -0,0 +1,42 @@
<script lang="ts">
import { useQueryClient } from '@tanstack/svelte-query'
import TodosList from './TodosList.svelte'
import EditTodo from './EditTodo.svelte'
import AddTodo from './AddTodo.svelte'
import { views, editingIndex } from '../lib/stores.svelte'
const queryClient = useQueryClient()
</script>
<div>
<div>
<button onclick={() => queryClient.invalidateQueries()}>
Force Refetch All
</button>
</div>
<br />
<hr />
{#each views.value as view}
<div>
<TodosList initialFilter={view} />
<br />
</div>
{/each}
<button
onclick={() => {
views.value.push('')
}}
>
Add Filter List
</button>
<hr />
{#if editingIndex.value !== null}
<EditTodo />
<hr />
{/if}
<AddTodo />
</div>

View File

@@ -0,0 +1,138 @@
<script lang="ts">
import {
useQueryClient,
createQuery,
createMutation,
} from '@tanstack/svelte-query'
import {
errorRate,
queryTimeMin,
queryTimeMax,
list,
editingIndex,
} from '$lib/stores.svelte'
import type { Todo } from '$lib/stores.svelte'
const queryClient = useQueryClient()
const fetchTodoById = async ({ id }: { id: number }): Promise<Todo> => {
return new Promise((resolve, reject) => {
setTimeout(
() => {
if (Math.random() < errorRate.value) {
return reject(
new Error(JSON.stringify({ fetchTodoById: { id } }, null, 2)),
)
}
const todo = $state.snapshot(list.value.find((d) => d.id === id))
if (!todo) {
return reject(
new Error(JSON.stringify({ fetchTodoById: { id } }, null, 2)),
)
}
resolve(todo)
},
queryTimeMin.value +
Math.random() * (queryTimeMax.value - queryTimeMin.value),
)
})
}
function patchTodo(todo?: Todo): Promise<Todo> {
console.info('patchTodo', todo)
return new Promise((resolve, reject) => {
setTimeout(
() => {
if (Math.random() < errorRate.value) {
return reject(
new Error(JSON.stringify({ patchTodo: todo }, null, 2)),
)
}
if (!todo) {
return reject(
new Error(JSON.stringify({ patchTodo: todo }, null, 2)),
)
}
list.value = list.value.map((d) => {
if (d.id === todo.id) {
return $state.snapshot(todo)
}
return d
})
resolve(todo)
},
queryTimeMin.value +
Math.random() * (queryTimeMax.value - queryTimeMin.value),
)
})
}
const query = createQuery(() => ({
queryKey: ['todo', { id: editingIndex.value }],
queryFn: () => fetchTodoById({ id: editingIndex.value || 0 }),
enabled: editingIndex.value !== null,
}))
const saveMutation = createMutation(() => ({
mutationFn: patchTodo,
onSuccess: (data) => {
// Update `todos` and the individual todo queries when this mutation succeeds
queryClient.invalidateQueries({ queryKey: ['todos'] })
queryClient.setQueryData(['todo', { id: editingIndex }], data)
},
}))
const todo = $derived(query.data)
const onSave = () => {
saveMutation.mutate(todo)
}
const disableEditSave = $derived(
query.status === 'pending' || saveMutation.status === 'pending',
)
</script>
<div>
<div>
{#if query.data}
<button onclick={() => (editingIndex.value = null)}>Back</button> Editing
Todo "{query.data.name}" (#{editingIndex.value})
{/if}
</div>
{#if query.status === 'pending'}
<span>Loading... (Attempt: {query.failureCount + 1})</span>
{:else if query.error}
<span>
Error! <button onclick={() => query.refetch()}>Retry</button>
</span>
{:else if todo}
<label>
Name:{' '}
<input bind:value={todo.name} disabled={disableEditSave} />
</label>
<label>
Notes:{' '}
<input bind:value={todo.notes} disabled={disableEditSave} />
</label>
<div>
<button onclick={onSave} disabled={disableEditSave}> Save </button>
</div>
<div>
{saveMutation.status === 'pending'
? 'Saving...'
: saveMutation.status === 'error'
? saveMutation.error.message
: 'Saved!'}
</div>
<div>
{#if query.isFetching}
<span>
Background Refreshing... (Attempt: {query.failureCount + 1})
</span>
{:else}
<span>&nbsp;</span>
{/if}
</div>
{/if}
</div>

View File

@@ -0,0 +1,74 @@
<script lang="ts">
import { createQuery } from '@tanstack/svelte-query'
import {
errorRate,
queryTimeMin,
queryTimeMax,
list,
editingIndex,
} from '$lib/stores.svelte'
import type { Todos } from '$lib/stores.svelte'
let { initialFilter }: { initialFilter: string } = $props()
let filter = $state(initialFilter)
const fetchTodos = async ({ filter }: { filter: string }): Promise<Todos> => {
return new Promise((resolve, reject) => {
setTimeout(
() => {
if (Math.random() < errorRate.value) {
return reject(
new Error(JSON.stringify({ fetchTodos: { filter } }, null, 2)),
)
}
resolve(list.value.filter((d) => d.name.includes(filter)))
},
queryTimeMin.value +
Math.random() * (queryTimeMax.value - queryTimeMin.value),
)
})
}
const query = createQuery(() => ({
queryKey: ['todos', { filter: filter }],
queryFn: () => fetchTodos({ filter: filter }),
}))
</script>
<div>
<label>
Filter:{' '}
<input bind:value={filter} />
</label>
</div>
{#if query.status === 'pending'}
<span>Loading... (Attempt: {query.failureCount + 1})</span>
{:else if query.status === 'error'}
<span>
Error: {query.error.message}
<br />
<button onclick={() => query.refetch()}>Retry</button>
</span>
{:else}
<ul>
{#if query.data}
{#each query.data as todo}
<li>
{todo.name}{' '}
<button onclick={() => (editingIndex.value = todo.id)}> Edit </button>
</li>
{/each}
{/if}
</ul>
<div>
{#if query.isFetching}
<span>
Background Refreshing... (Attempt: {query.failureCount + 1})
</span>
{:else}
<span>&nbsp;</span>
{/if}
</div>
{/if}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,15 @@
import adapter from '@sveltejs/adapter-auto'
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
adapter: adapter(),
},
compilerOptions: {
runes: true,
},
}
export default config

View File

@@ -0,0 +1,13 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true
}
}

View File

@@ -0,0 +1,6 @@
import { sveltekit } from '@sveltejs/kit/vite'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [sveltekit()],
})

24
examples/svelte/simple/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
!lib/
# Editor directories and files
.vscode
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -0,0 +1,48 @@
# Svelte + TS + Vite
This template should help get you started developing with Svelte and TypeScript in Vite.
## Recommended IDE Setup
[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
## Need an official Svelte framework?
Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more.
## Technical considerations
**Why use this over SvelteKit?**
- It brings its own routing solution which might not be preferable for some users.
- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
`vite dev` and `vite build` wouldn't work in a SvelteKit environment, for example.
This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project.
Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate.
**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information.
**Why include `.vscode/extensions.json`?**
Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project.
**Why enable `allowJs` in the TS template?**
While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant.
**Why is HMR not preserving my local component state?**
HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr).
If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR.
```ts
// store.ts
// An extremely simple external store
import { writable } from 'svelte/store'
export default writable(0)
```

View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>Vite + Svelte + TS</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@@ -0,0 +1,22 @@
{
"name": "@tanstack/query-example-svelte-simple",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@tanstack/svelte-query": "^6.1.48",
"@tanstack/svelte-query-devtools": "^6.1.48"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^5.1.1",
"@tsconfig/svelte": "^5.0.4",
"svelte": "^5.39.3",
"svelte-check": "^4.3.1",
"typescript": "5.8.3",
"vite": "^6.4.1"
}
}

View File

@@ -0,0 +1,14 @@
<script lang="ts">
import { QueryClientProvider, QueryClient } from '@tanstack/svelte-query'
import { SvelteQueryDevtools } from '@tanstack/svelte-query-devtools'
import Simple from './lib/Simple.svelte'
const queryClient = new QueryClient()
</script>
<QueryClientProvider client={queryClient}>
<main>
<Simple />
</main>
<SvelteQueryDevtools />
</QueryClientProvider>

View File

@@ -0,0 +1,81 @@
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
.card {
padding: 2em;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="26.6" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 308"><path fill="#FF3E00" d="M239.682 40.707C211.113-.182 154.69-12.301 113.895 13.69L42.247 59.356a82.198 82.198 0 0 0-37.135 55.056a86.566 86.566 0 0 0 8.536 55.576a82.425 82.425 0 0 0-12.296 30.719a87.596 87.596 0 0 0 14.964 66.244c28.574 40.893 84.997 53.007 125.787 27.016l71.648-45.664a82.182 82.182 0 0 0 37.135-55.057a86.601 86.601 0 0 0-8.53-55.577a82.409 82.409 0 0 0 12.29-30.718a87.573 87.573 0 0 0-14.963-66.244"></path><path fill="#FFF" d="M106.889 270.841c-23.102 6.007-47.497-3.036-61.103-22.648a52.685 52.685 0 0 1-9.003-39.85a49.978 49.978 0 0 1 1.713-6.693l1.35-4.115l3.671 2.697a92.447 92.447 0 0 0 28.036 14.007l2.663.808l-.245 2.659a16.067 16.067 0 0 0 2.89 10.656a17.143 17.143 0 0 0 18.397 6.828a15.786 15.786 0 0 0 4.403-1.935l71.67-45.672a14.922 14.922 0 0 0 6.734-9.977a15.923 15.923 0 0 0-2.713-12.011a17.156 17.156 0 0 0-18.404-6.832a15.78 15.78 0 0 0-4.396 1.933l-27.35 17.434a52.298 52.298 0 0 1-14.553 6.391c-23.101 6.007-47.497-3.036-61.101-22.649a52.681 52.681 0 0 1-9.004-39.849a49.428 49.428 0 0 1 22.34-33.114l71.664-45.677a52.218 52.218 0 0 1 14.563-6.398c23.101-6.007 47.497 3.036 61.101 22.648a52.685 52.685 0 0 1 9.004 39.85a50.559 50.559 0 0 1-1.713 6.692l-1.35 4.116l-3.67-2.693a92.373 92.373 0 0 0-28.037-14.013l-2.664-.809l.246-2.658a16.099 16.099 0 0 0-2.89-10.656a17.143 17.143 0 0 0-18.398-6.828a15.786 15.786 0 0 0-4.402 1.935l-71.67 45.674a14.898 14.898 0 0 0-6.73 9.975a15.9 15.9 0 0 0 2.709 12.012a17.156 17.156 0 0 0 18.404 6.832a15.841 15.841 0 0 0 4.402-1.935l27.345-17.427a52.147 52.147 0 0 1 14.552-6.397c23.101-6.006 47.497 3.037 61.102 22.65a52.681 52.681 0 0 1 9.003 39.848a49.453 49.453 0 0 1-22.34 33.12l-71.664 45.673a52.218 52.218 0 0 1-14.563 6.398"></path></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,41 @@
<script lang="ts">
import { createQuery } from '@tanstack/svelte-query'
type Repo = {
full_name: string
description: string
subscribers_count: number
stargazers_count: number
forks_count: number
}
const query = createQuery<Repo>(() => ({
queryKey: ['repoData'],
queryFn: async () =>
await fetch('https://api.github.com/repos/TanStack/query').then((r) =>
r.json(),
),
}))
</script>
<h1>Simple</h1>
<div class="my-4">
<div>
{#if query.isPending}
Loading...
{/if}
{#if query.error}
An error has occurred:
{query.error.message}
{/if}
{#if query.isSuccess}
<div>
<h1>{query.data.full_name}</h1>
<p>{query.data.description}</p>
<strong>👀 {query.data.subscribers_count}</strong>{' '}
<strong>✨ {query.data.stargazers_count}</strong>{' '}
<strong>🍴 {query.data.forks_count}</strong>
</div>
{/if}
</div>
</div>

View File

@@ -0,0 +1,10 @@
import { mount } from 'svelte'
import './app.css'
import App from './App.svelte'
const target = document.querySelector('#app')
if (!target) throw new Error('Missing #app element')
const app = mount(App, { target })
export default app

View File

@@ -0,0 +1,2 @@
/// <reference types="svelte" />
/// <reference types="vite/client" />

View File

@@ -0,0 +1,8 @@
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
export default {
preprocess: vitePreprocess(),
compilerOptions: {
runes: true,
},
}

View File

@@ -0,0 +1,25 @@
{
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"resolveJsonModule": true,
/**
* Typecheck JS in `.svelte` and `.js` files by default.
* Disable checkJs if you'd like to use dynamic types in JS.
* Note that setting allowJs false does not prevent the use
* of JS in `.svelte` files.
*/
"allowJs": true,
"checkJs": true,
"isolatedModules": true
},
"include": [
"src/**/*.d.ts",
"src/**/*.ts",
"src/**/*.js",
"src/**/*.svelte",
"vite.config.ts"
]
}

View File

@@ -0,0 +1,6 @@
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
export default defineConfig({
plugins: [svelte()],
})

9
examples/svelte/ssr/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
!lib/

View File

@@ -0,0 +1,38 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm create svelte@latest
# create a new project in my-app
npm create svelte@latest my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn` or `bun install`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.

View File

@@ -0,0 +1,23 @@
{
"name": "@tanstack/query-example-svelte-ssr",
"private": true,
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@tanstack/svelte-query": "^6.1.48",
"@tanstack/svelte-query-devtools": "^6.1.48"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^6.1.0",
"@sveltejs/kit": "^2.57.1",
"@sveltejs/vite-plugin-svelte": "^5.1.1",
"svelte": "^5.39.3",
"svelte-check": "^4.3.1",
"typescript": "5.8.3",
"vite": "^6.4.1"
}
}

View File

@@ -0,0 +1,81 @@
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
.card {
padding: 2em;
}
main {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
.button:hover {
border-color: #646cff;
}
.button:focus,
.button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
.button {
background-color: #f9f9f9;
}
}

12
examples/svelte/ssr/src/app.d.ts vendored Normal file
View File

@@ -0,0 +1,12 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface Platform {}
}
}
export {}

View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@@ -0,0 +1,31 @@
<script lang="ts">
import { createQuery } from '@tanstack/svelte-query'
import { api } from './api'
import type { Post } from './types'
const { postId }: { postId: number } = $props()
const post = createQuery<Post>(() => ({
queryKey: ['post', postId],
queryFn: () => api().getPostById(postId),
}))
</script>
<div>
<div>
<a class="button" href="/"> Back </a>
</div>
{#if !postId || post.isPending}
<span>Loading...</span>
{/if}
{#if post.error}
<span>Error: {post.error.message}</span>
{/if}
{#if post.isSuccess}
<h1>{post.data.title}</h1>
<div>
<p>{post.data.body}</p>
</div>
<div>{post.isFetching ? 'Background Updating...' : ' '}</div>
{/if}
</div>

View File

@@ -0,0 +1,65 @@
<script lang="ts">
import { useQueryClient, createQuery } from '@tanstack/svelte-query'
import { api } from './api'
const client = useQueryClient()
const limit = 10
const posts = createQuery<
{ id: number; title: string; body: string }[],
Error
>(() => ({
queryKey: ['posts', limit],
queryFn: () => api().getPosts(limit),
}))
</script>
<div>
<div>
{#if posts.status === 'pending'}
<span>Loading...</span>
{:else if posts.status === 'error'}
<span>Error: {posts.error.message}</span>
{:else}
<ul>
{#each posts.data as post}
<article>
<a
href={`/${post.id}`}
style={// We can use the queryCache here to show bold links for
// ones that are cached
client.getQueryData(['post', post.id])
? 'font-weight: bold; color: indianred'
: 'cursor: pointer'}
>
{post.title}
</a>
</article>
{/each}
</ul>
<pre
class={['updating-text', posts.isFetching && 'on']}
style="font-weight:700">Background Updating...</pre>
{/if}
</div>
</div>
<style>
article {
text-align: left;
}
a {
display: block;
font-size: 1.5rem;
margin-bottom: 1rem;
}
.updating-text {
color: transparent;
transition: all 0.3s ease;
}
.updating-text.on {
color: green;
transition: none;
}
</style>

View File

@@ -0,0 +1,18 @@
import type { Post } from './types'
export const api = (customFetch = fetch) => ({
getPosts: async (limit: number) => {
const response = await customFetch(
'https://jsonplaceholder.typicode.com/posts',
)
const data = (await response.json()) as Array<Post>
return data.filter((x) => x.id <= limit)
},
getPostById: async (id: number): Promise<Post> => {
const response = await customFetch(
`https://jsonplaceholder.typicode.com/posts/${id}`,
)
const data = (await response.json()) as Post
return data
},
})

View File

@@ -0,0 +1,5 @@
export type Post = {
id: number
title: string
body: string
}

View File

@@ -0,0 +1,14 @@
<script lang="ts">
import '../app.css'
import { QueryClientProvider } from '@tanstack/svelte-query'
import { SvelteQueryDevtools } from '@tanstack/svelte-query-devtools'
const { data, children } = $props()
</script>
<QueryClientProvider client={data.queryClient}>
<main>
{@render children()}
</main>
<SvelteQueryDevtools />
</QueryClientProvider>

View File

@@ -0,0 +1,16 @@
import { QueryClient } from '@tanstack/svelte-query'
import type { LayoutLoad } from './$types'
import { browser } from '$app/environment'
export const load: LayoutLoad = () => {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
enabled: browser,
staleTime: 60 * 1000,
},
},
})
return { queryClient }
}

View File

@@ -0,0 +1,6 @@
<script lang="ts">
import Posts from '$lib/Posts.svelte'
</script>
<h1>Basic Query with SSR</h1>
<Posts />

Some files were not shown because too many files have changed in this diff Show More