Skip to content

About

Type-safe Elysiajs TanStack Query - automatic queryOptions and mutationOptions inference from your API routes

Topics

Resources

Contributing

Stars

3 stars

Watchers

0 watching

Forks

Repository files navigation

treaty-tanstack-query

treaty-tanstack-query

Type-safe TanStack Query integration for Eden Treaty (Elysia)

npm version npm downloads license CI status docs

Documentation Β· Report Bug


Why?

Using Eden Treaty with TanStack Query manually requires repetitive boilerplate:

// Without treaty-tanstack-query 😫
const fetchTasks = async (filters: TaskFilters) => {
  const response = await client.api.tasks.get({ query: filters });
  if (response.error) throw response.error;
  return response.data;
};

const { data } = useQuery({
  queryKey: ['api', 'tasks', filters],  // Easy to typo!
  queryFn: () => fetchTasks(filters),
});

// Mutation with manual invalidation
const createTask = useMutation({
  mutationFn: async (input: CreateTaskInput) => {
    const response = await client.api.tasks.post(input);
    if (response.error) throw response.error;
    return response.data;
  },
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['api', 'tasks'] }); // Hope you remembered the key!
  },
});

With this library, it becomes:

// With treaty-tanstack-query πŸŽ‰
const treaty = useTreaty();
const queryClient = useQueryClient();

const { data } = useQuery(treaty.api.tasks.queryOptions({ query: filters }));

const createTask = useMutation(
  treaty.api.tasks.mutationOptions({
    onSuccess: () => queryClient.invalidateQueries(treaty.api.tasks.pathFilter()),
  })
);

Features

  • Full type safety β€” Automatic inference from Elysia routes to queryOptions and mutationOptions
  • First-class TanStack Query v5 β€” Returns standard options objects that work with useQuery, useMutation, useSuspenseQuery, etc.
  • Smart query keys β€” Hierarchical keys auto-generated from your API paths. Invalidate by path with pathFilter()
  • Infinite queries β€” Automatic support when your input contains a cursor field
  • Real-time β€” WebSocket subscriptions via subscriptionOptions() + useSubscription()
  • SSR & Suspense β€” Full support for React Suspense, streaming SSR, and hydration

Install

npm install treaty-tanstack-react-query @tanstack/react-query @elysiajs/eden
Other package managers
pnpm add treaty-tanstack-react-query @tanstack/react-query @elysiajs/eden
bun add treaty-tanstack-react-query @tanstack/react-query @elysiajs/eden

Quick Start

import { treaty } from "@elysiajs/eden";
import { QueryClient, QueryClientProvider, useQuery } from "@tanstack/react-query";
import { createTreatyContext } from "treaty-tanstack-react-query";
import type { App } from "./server"; // Your Elysia app type

// 1. Create the Eden client
const client = treaty<App>("http://localhost:3000");

// 2. Create the treaty context
const { TreatyProvider, useTreaty } = createTreatyContext<typeof client>();

// 3. Setup providers
const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <TreatyProvider client={client} queryClient={queryClient}>
        <Tasks />
      </TreatyProvider>
    </QueryClientProvider>
  );
}

// 4. Use in components with full type safety!
function Tasks() {
  const treaty = useTreaty();

  // βœ… Fully typed - IDE autocomplete shows available endpoints
  const { data: tasks } = useQuery(
    treaty.api.tasks.queryOptions({ query: { status: "todo" } })
  );

  return (
    <ul>
      {tasks?.map(task => <li key={task.id}>{task.title}</li>)}
    </ul>
  );
}

Examples

Queries with Filters

const { data } = useQuery(
  treaty.api.tasks.queryOptions({
    query: { status: "in-progress", priority: "high" }
  })
);

Mutations

const treaty = useTreaty();
const queryClient = useQueryClient();

const updateTask = useMutation(
  treaty.api.tasks({ id: taskId }).mutationOptions('patch', {
    onSuccess: () => {
      // Invalidate all task queries
      queryClient.invalidateQueries(treaty.api.tasks.pathFilter());
    },
  })
);

updateTask.mutate({ status: "done" });

Infinite Queries

// Automatically detected when your endpoint has a `cursor` parameter
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery(
  treaty.api.tasks.infinite.infiniteQueryOptions({
    query: { limit: 10 }
  })
);

Real-time Subscriptions

const treaty = useTreaty();
const queryClient = useQueryClient();

const { lastMessage } = useSubscription(
  treaty.ws.tasks.subscriptionOptions()
);

useEffect(() => {
  if (lastMessage?.type === "task-updated") {
    queryClient.invalidateQueries(treaty.api.tasks.pathFilter());
  }
}, [lastMessage]);

Documentation

Full documentation is available at abanoubnassem.github.io/treaty-tanstack-query

Repository Structure

β”œβ”€β”€ packages/
β”‚   └── treaty-tanstack-react-query/   # The library
β”œβ”€β”€ apps/
β”‚   └── react-fullstack-example/       # Demo app (Task Manager)
└── docs/                              # VitePress documentation

Development

# Install dependencies
bun install

# Run the example app
bun run dev

# Run tests
bun run test

# Build
bun run build

Contributing

Contributions are welcome! Please read our Contributing Guide before submitting a PR.

License

Apache-2.0 Β© Abanoub Nassem


Built with ❀️ for the Elysia + TanStack Query community

About

Type-safe Elysiajs TanStack Query - automatic queryOptions and mutationOptions inference from your API routes

Topics

Resources

Contributing

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages