svelte-db
frameworksvelteSvelte 5 bindings for TanStack DB. useLiveQuery with Svelte 5 runes ($state, $derived, $effect). Dependency arrays use getter functions (() => value) for reactive props. Direct destructuring breaks reactivity — use dot notation or wrap with $derived. Conditional queries via returning undefined/null. Import from @tanstack/svelte-db (re-exports all of @tanstack/db).
This skill builds on db-core. Read it first for collection setup, query builder, and mutation patterns.
TanStack DB — Svelte 5
Setup
<script lang="ts">
import { useLiveQuery, eq, not } from "@tanstack/svelte-db"
const todosQuery = useLiveQuery((q) =>
q
.from({ todo: todoCollection })
.where(({ todo }) => not(todo.completed))
.orderBy(({ todo }) => todo.created_at, "asc")
)
</script>
{#if todosQuery.isLoading}
<div>Loading...</div>
{:else}
<ul>
{#each todosQuery.data as todo (todo.id)}
<li>{todo.text}</li>
{/each}
</ul>
{/if}
@tanstack/svelte-db re-exports everything from @tanstack/db.
Hook
useLiveQuery
Returns a reactive object with getter properties:
// Query function — access via dot notation
const query = useLiveQuery((q) => q.from({ todo: todoCollection }))
// query.data, query.isLoading, query.status, etc.
// With reactive deps — use GETTER FUNCTIONS
let minPriority = $state(5)
const query = useLiveQuery(
(q) =>
q
.from({ todo: todoCollection })
.where(({ todo }) => gt(todo.priority, minPriority)),
[() => minPriority],
)
// Config object
const query = useLiveQuery({
query: (q) => q.from({ todo: todoCollection }),
gcTime: 60000,
})
// Pre-created collection
const query = useLiveQuery(preloadedCollection)
Svelte-Specific Patterns
Destructuring with $derived
// WRONG — breaks reactivity
const { data, isLoading } = useLiveQuery(...)
// CORRECT — use dot notation
const query = useLiveQuery(...)
// Access: query.data, query.isLoading
// CORRECT — wrap with $derived if you need destructuring
const query = useLiveQuery(...)
const { data, isLoading } = $derived(query)
Props in dependency arrays
<script lang="ts">
let { userId }: { userId: number } = $props()
const todosQuery = useLiveQuery(
(q) => q.from({ todo: todoCollection })
.where(({ todo }) => eq(todo.userId, userId)),
[() => userId]
)
</script>
Includes (Hierarchical Data)
When a query uses includes (subqueries in select), each child field is a live Collection by default. Subscribe to it with useLiveQuery in a subcomponent:
<!-- ProjectList.svelte -->
<script lang="ts">
import { useLiveQuery, eq } from "@tanstack/svelte-db"
import IssueList from "./IssueList.svelte"
const projectsQuery = useLiveQuery((q) =>
q.from({ p: projectsCollection }).select(({ p }) => ({
id: p.id,
name: p.name,
issues: q
.from({ i: issuesCollection })
.where(({ i }) => eq(i.projectId, p.id))
.select(({ i }) => ({ id: i.id, title: i.title })),
}))
)
</script>
{#each projectsQuery.data as project (project.id)}
<div>
{project.name}
<IssueList issuesCollection={project.issues} />
</div>
{/each}
<!-- IssueList.svelte — subscribes to the child Collection -->
<script lang="ts">
import { useLiveQuery } from "@tanstack/svelte-db"
let { issuesCollection } = $props()
const issuesQuery = useLiveQuery(issuesCollection)
</script>
{#each issuesQuery.data as issue (issue.id)}
<li>{issue.title}</li>
{/each}
With toArray(), child results are plain arrays and the parent re-emits on child changes:
import { toArray, eq } from '@tanstack/svelte-db'
const projectsQuery = useLiveQuery((q) =>
q.from({ p: projectsCollection }).select(({ p }) => ({
id: p.id,
name: p.name,
issues: toArray(
q
.from({ i: issuesCollection })
.where(({ i }) => eq(i.projectId, p.id))
.select(({ i }) => ({ id: i.id, title: i.title })),
),
})),
)
// project.issues is a plain array — no subcomponent needed
See db-core/live-queries/SKILL.md for full includes rules (correlation conditions, nested includes, aggregates).
Common Mistakes
CRITICAL Passing values instead of getter functions in deps
Wrong:
let filter = $state('active')
const query = useLiveQuery(
(q) =>
q
.from({ todo: todoCollection })
.where(({ todo }) => eq(todo.status, filter)),
[filter],
)
Correct:
let filter = $state('active')
const query = useLiveQuery(
(q) =>
q
.from({ todo: todoCollection })
.where(({ todo }) => eq(todo.status, filter)),
[() => filter],
)
In Svelte 5, deps must be getter functions. Passing values directly captures them at creation time — changes won't trigger re-execution.
Source: docs/framework/svelte/overview.md
HIGH Direct destructuring breaks reactivity
Wrong:
const { data, isLoading } = useLiveQuery((q) =>
q.from({ todo: todoCollection }),
)
Correct:
const query = useLiveQuery((q) => q.from({ todo: todoCollection }))
// Use query.data, query.isLoading
Direct destructuring captures values at creation time. Use dot notation or wrap with $derived.
Source: packages/svelte-db/src/useLiveQuery.svelte.ts
See also: db-core/live-queries/SKILL.md — for query builder API.
See also: db-core/mutations-optimistic/SKILL.md — for mutation patterns.