npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pyreon/query

v0.12.10

Published

Pyreon adapter for TanStack Query

Readme

@pyreon/query

Pyreon adapter for TanStack Query. Reactive useQuery, useMutation, useInfiniteQuery, and Suspense integration with fine-grained signal updates.

Install

bun add @pyreon/query @tanstack/query-core

Quick Start

import { QueryClient, QueryClientProvider, useQuery } from '@pyreon/query'

const queryClient = new QueryClient()

function UserProfile(props: { id: string }) {
  const query = useQuery(() => ({
    queryKey: ['user', props.id],
    queryFn: () => fetch(`/api/users/${props.id}`).then((r) => r.json()),
  }))

  return () => {
    if (query.isLoading()) return <p>Loading...</p>
    if (query.isError()) return <p>Error</p>
    return <h1>{query.data()?.name}</h1>
  }
}

const App = () => (
  <QueryClientProvider client={queryClient}>
    <UserProfile id="1" />
  </QueryClientProvider>
)

API

QueryClientProvider

Provide a QueryClient to the component tree.

| Parameter | Type | Description | | --------- | ------------- | ------------------------------ | | client | QueryClient | TanStack Query client instance |

useQueryClient()

Access the QueryClient from the nearest QueryClientProvider.

Returns: QueryClient

useQuery(options)

Subscribe to a query with fine-grained reactive signals. Options are passed as a function so reactive values (e.g. signal-based query keys) trigger automatic refetches.

| Parameter | Type | Description | | --------- | ---------------------------- | -------------------------------- | | options | () => QueryObserverOptions | Function returning query options |

Returns: UseQueryResult<TData, TError> with:

| Property | Type | Description | | ------------ | ------------------------------------------- | ----------------------- | | result | Signal<QueryObserverResult> | Full observer result | | data | Signal<TData \| undefined> | Query data | | error | Signal<TError \| null> | Query error | | status | Signal<"pending" \| "error" \| "success"> | Query status | | isPending | Signal<boolean> | No data yet | | isLoading | Signal<boolean> | First fetch in progress | | isFetching | Signal<boolean> | Any fetch in progress | | isError | Signal<boolean> | Query errored | | isSuccess | Signal<boolean> | Query succeeded | | refetch() | () => Promise<QueryObserverResult> | Trigger manual refetch |

const userId = signal(1)
const query = useQuery(() => ({
  queryKey: ['user', userId()],
  queryFn: () => fetchUser(userId()),
}))
// Changing userId triggers automatic refetch

useMutation(options)

Run a mutation with reactive status signals.

| Parameter | Type | Description | | --------- | ------------------------- | --------------- | | options | MutationObserverOptions | Mutation config |

Returns: UseMutationResult<TData, TError, TVariables, TContext> with:

| Property | Type | Description | | -------------------------- | ----------------------------------------------------- | -------------------------- | | data | Signal<TData \| undefined> | Mutation result | | error | Signal<TError \| null> | Mutation error | | status | Signal<"idle" \| "pending" \| "success" \| "error"> | Status | | isPending | Signal<boolean> | Mutation in progress | | isSuccess | Signal<boolean> | Mutation succeeded | | isError | Signal<boolean> | Mutation errored | | isIdle | Signal<boolean> | Not yet fired | | mutate(vars, opts?) | Function | Fire-and-forget mutation | | mutateAsync(vars, opts?) | Function | Promise-returning mutation | | reset() | () => void | Reset to idle state |

const mutation = useMutation({
  mutationFn: (data: { title: string }) =>
    fetch('/api/posts', { method: 'POST', body: JSON.stringify(data) }).then((r) => r.json()),
  onSuccess: () => queryClient.invalidateQueries({ queryKey: ['posts'] }),
})

mutation.mutate({ title: 'New Post' })

useInfiniteQuery(options)

Paginated/infinite query with the same fine-grained signal pattern as useQuery.

| Parameter | Type | Description | | --------- | ------------------------------------ | -------------------------- | | options | () => InfiniteQueryObserverOptions | Function returning options |

Returns: UseInfiniteQueryResult<TData, TError> — same shape as UseQueryResult.

useQueries(options)

Run multiple queries in parallel.

| Parameter | Type | Description | | --------- | ------------------- | ---------------------- | | options | UseQueriesOptions | Array of query configs |

Returns: Array of UseQueryResult objects.

useSuspenseQuery(options) / useSuspenseInfiniteQuery(options)

Suspense-enabled queries. Data is guaranteed non-undefined after the suspense boundary resolves.

| Property | Type | Description | | -------- | --------------- | ------------------------------ | | data | Signal<TData> | Always defined (non-undefined) |

function UserList() {
  const query = useSuspenseQuery({
    queryKey: ['users'],
    queryFn: fetchUsers,
  })
  return () => (
    <ul>
      {query.data().map((u) => (
        <li>{u.name}</li>
      ))}
    </ul>
  )
}

QuerySuspense

Suspense wrapper component with built-in error handling.

| Parameter | Type | Description | | ---------- | ------------ | ---------------- | | fallback | VNodeChild | Loading fallback | | children | VNodeChild | Content |

<QuerySuspense fallback={<p>Loading...</p>}>
  <UserList />
</QuerySuspense>

QueryErrorResetBoundary / useQueryErrorResetBoundary()

Error boundary for resetting query errors on retry.

useIsFetching(filters?) / useIsMutating(filters?)

Global counters of active queries/mutations as reactive signals.

| Parameter | Type | Description | | --------- | ---------------------------------- | -------------------------------- | | filters | QueryFilters / MutationFilters | Optional filters to narrow scope |

Returns: Signal<number>

const fetching = useIsFetching()
// fetching() => number of active queries

Patterns

SSR Dehydration

import { QueryClient, dehydrate, hydrate } from '@pyreon/query'

// Server: prefetch and serialize
const queryClient = new QueryClient()
await queryClient.prefetchQuery({ queryKey: ['users'], queryFn: fetchUsers })
const dehydratedState = dehydrate(queryClient)

// Client: restore cache
hydrate(queryClient, dehydratedState)

Reactive Query Keys

Options are a function, so reading signals inside auto-tracks dependencies.

const filter = signal('active')
const query = useQuery(() => ({
  queryKey: ['todos', filter()],
  queryFn: () => fetchTodos(filter()),
}))
// Changing filter() triggers a new fetch

Re-exports from @tanstack/query-core

Runtime: QueryClient, QueryCache, MutationCache, dehydrate, hydrate, keepPreviousData, hashKey, isCancelledError, CancelledError, defaultShouldDehydrateQuery, defaultShouldDehydrateMutation

Types: QueryKey, QueryFilters, MutationFilters, DehydratedState, FetchQueryOptions, InvalidateQueryFilters, InvalidateOptions, RefetchQueryFilters, RefetchOptions, QueryClientConfig

Gotchas

  • Each field on UseQueryResult is an independent signal. Reading query.data() does not re-run when isFetching changes, and vice versa.
  • useQuery options must be a function () => opts, not a plain object. This is required for reactive option tracking.
  • useMutation options are a plain object (not a function) since mutations are imperative.
  • mutate() swallows errors into the error signal. Use mutateAsync() if you need try/catch.
  • Observer subscriptions are cleaned up automatically on component unmount via onUnmount.