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

@graphql-suite/query

v0.9.1

Published

React Query hooks for the Drizzle GraphQL client with caching and automatic invalidation

Downloads

395

Readme

@graphql-suite/query

Part of graphql-suite. See also: schema | client

TanStack React Query hooks for @graphql-suite/client — type-safe data fetching with caching, pagination, and mutations.

Installation

bun add @graphql-suite/query
npm install @graphql-suite/query

Or install the full suite:

bun add graphql-suite
npm install graphql-suite

Setup

Provider

Wrap your app with <GraphQLProvider> inside a <QueryClientProvider>:

import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { GraphQLProvider } from '@graphql-suite/query'
import { client } from './graphql-client'

const queryClient = new QueryClient()

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <GraphQLProvider client={client}>
        {/* your app */}
      </GraphQLProvider>
    </QueryClientProvider>
  )
}

useGraphQLClient()

Access the raw GraphQLClient instance from context:

import { useGraphQLClient } from '@graphql-suite/query'

function MyComponent() {
  const client = useGraphQLClient()
  // client.entity('user'), client.execute(query, variables), etc.
}

useEntity(entityName)

Get a typed EntityClient for use with query and mutation hooks:

import { useEntity } from '@graphql-suite/query'

function UserList() {
  const user = useEntity('user')
  // Pass `user` to useEntityList, useEntityQuery, etc.
}

Query Hooks

useEntityList(entity, params, options?)

Fetch a list of records. Returns UseQueryResult<T[]>.

Params: select, where, limit, offset, orderBy

import { useEntity, useEntityList } from '@graphql-suite/query'

function UserList() {
  const user = useEntity('user')
  const { data, isLoading, error } = useEntityList(user, {
    select: { id: true, name: true, email: true },
    where: { role: { eq: 'admin' } },
    orderBy: { name: { direction: 'asc', priority: 1 } },
    limit: 20,
  })

  if (isLoading) return <div>Loading...</div>
  if (error) return <div>Error: {error.message}</div>

  return (
    <ul>
      {data?.map((u) => <li key={u.id}>{u.name} ({u.email})</li>)}
    </ul>
  )
}

useEntityQuery(entity, params, options?)

Fetch a single record. Returns UseQueryResult<T | null>.

Params: select, where, offset, orderBy

function UserDetail({ userId }: { userId: string }) {
  const user = useEntity('user')
  const { data } = useEntityQuery(user, {
    select: { id: true, name: true, email: true, role: true },
    where: { id: { eq: userId } },
  })

  if (!data) return null
  return <div>{data.name} — {data.role}</div>
}

useEntityInfiniteQuery(entity, params, options?)

Infinite scrolling with cursor-based pagination. Returns UseInfiniteQueryResult with pages containing { items: T[], count: number }.

Params: select, where, pageSize, orderBy

function InfiniteUserList() {
  const user = useEntity('user')
  const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
    useEntityInfiniteQuery(user, {
      select: { id: true, name: true },
      pageSize: 20,
      orderBy: { name: { direction: 'asc', priority: 1 } },
    })

  const allUsers = data?.pages.flatMap((page) => page.items) ?? []

  return (
    <div>
      <ul>
        {allUsers.map((u) => <li key={u.id}>{u.name}</li>)}
      </ul>
      {hasNextPage && (
        <button onClick={() => fetchNextPage()} disabled={isFetchingNextPage}>
          {isFetchingNextPage ? 'Loading...' : 'Load more'}
        </button>
      )}
    </div>
  )
}

Query Options

All query hooks accept an optional options parameter:

| Option | Type | Description | |--------|------|-------------| | enabled | boolean | Disable automatic fetching | | gcTime | number | Garbage collection time (ms) | | staleTime | number | Time until data is considered stale (ms) | | refetchOnWindowFocus | boolean | Refetch when window regains focus | | queryKey | unknown[] | Override the auto-generated query key |

Mutation Hooks

useEntityInsert(entity, returning?, options?)

Insert records. Call .mutate({ values }) to execute.

function CreateUser() {
  const user = useEntity('user')
  const { mutate, isPending } = useEntityInsert(
    user,
    { id: true, name: true },
    { onSuccess: (data) => console.log('Created:', data) },
  )

  return (
    <button
      disabled={isPending}
      onClick={() => mutate({
        values: [{ name: 'Alice', email: '[email protected]' }],
      })}
    >
      Create User
    </button>
  )
}

useEntityUpdate(entity, returning?, options?)

Update records. Call .mutate({ set, where }) to execute.

function UpdateRole({ userId }: { userId: string }) {
  const user = useEntity('user')
  const { mutate } = useEntityUpdate(user, { id: true, role: true })

  return (
    <button onClick={() => mutate({
      set: { role: 'admin' },
      where: { id: { eq: userId } },
    })}>
      Make Admin
    </button>
  )
}

useEntityDelete(entity, returning?, options?)

Delete records. Call .mutate({ where }) to execute.

function DeleteUser({ userId }: { userId: string }) {
  const user = useEntity('user')
  const { mutate } = useEntityDelete(user, { id: true })

  return (
    <button onClick={() => mutate({
      where: { id: { eq: userId } },
    })}>
      Delete
    </button>
  )
}

Mutation Options

All mutation hooks accept an optional options parameter:

| Option | Type | Description | |--------|------|-------------| | invalidate | boolean | Invalidate queries after mutation (default: true) | | invalidateKey | unknown[] | Custom query key prefix to invalidate | | onSuccess | (data) => void | Callback after successful mutation | | onError | (error) => void | Callback after failed mutation |

Cache Invalidation

By default, all mutations invalidate queries with the ['gql'] key prefix. Since all query hooks use keys starting with ['gql', ...], this means every mutation refreshes all GraphQL queries.

Custom Invalidation Key

Narrow invalidation to specific queries:

const { mutate } = useEntityUpdate(user, { id: true }, {
  invalidateKey: ['gql', 'list'], // only invalidate list queries
})

Disable Invalidation

const { mutate } = useEntityInsert(user, undefined, {
  invalidate: false,
})

Query Key Override

Override the auto-generated key on query hooks for fine-grained cache control:

const { data } = useEntityList(user, params, {
  queryKey: ['users', 'admin-list'],
})

Type Inference Flow

Types flow end-to-end from your Drizzle schema to hook return types:

Drizzle Schema (tables + relations)
  ↓ InferEntityDefs
EntityDefs (fields, filters, inputs, orderBy per table)
  ↓ createDrizzleClient
GraphQLClient<SchemaDescriptor, EntityDefs>
  ↓ useEntity / client.entity()
EntityClient<EntityDefs, EntityDef>
  ↓ useEntityList / useEntityQuery (select param)
InferResult<EntityDefs, EntityDef, Select>
  ↓
Fully typed data: only selected fields, relations resolve to T[] or T | null