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

@neilthefisher/eden-tanstack-query

v0.3.0

Published

TanStack Query integration for Elysia Eden with SSE (streamed/live) support. Fork of Frank-III/eden-tanstack-query.

Readme

@neilthefisher/eden-tanstack-query

TanStack Query integration for Elysia Eden - type-safe queries and mutations with zero boilerplate.

Fork of Frank-III/eden-tanstack-query with added SSE support (.streamedOptions / .liveOptions).

Highlights:

  • Auto-generated queryKey, queryOptions, mutationOptions, mutation, and cache helpers
  • .streamedOptions and .liveOptions for Elysia SSE / async-generator routes
  • Type-safe data and error inference from your Elysia routes
  • Works with any TanStack Query adapter (React, Svelte, Vue, Solid)

Installation

bun add @neilthefisher/eden-tanstack-query @elysiajs/eden @tanstack/query-core elysia
# or
npm install @neilthefisher/eden-tanstack-query @elysiajs/eden @tanstack/query-core elysia

Usage

import { createEdenTQ } from "@neilthefisher/eden-tanstack-query";
import type { App } from "./server"; // Your Elysia app type

const eden = createEdenTQ<App>("http://localhost:3000");

Route Schema Mode (No Direct Elysia Type Import)

For large codebases, you can avoid pulling full app types into every client file:

import { createEdenTQFromSchema } from "eden-tanstack-query";
import type { App } from "./server";

type Routes = App["~Routes"];
const eden = createEdenTQFromSchema<Routes>("http://localhost:3000");

This keeps the client typed while reducing type-checker pressure compared with importing a full Elysia app type everywhere.

Queries

import { createQuery } from "@tanstack/svelte-query"; // or react-query, vue-query, etc.

// Fully type-safe, auto-generated query key
const query = createQuery(() =>
  eden.users({ id: "123" }).get.queryOptions({
    params: { id: "123" },
  }),
);

// query.data is typed as your Elysia response type!

React example:

import { useQuery } from "@tanstack/react-query";

const query = useQuery(
  eden.users({ id: "123" }).get.queryOptions({
    params: { id: "123" },
  }),
);

Infinite Queries

import { createInfiniteQuery } from "@tanstack/svelte-query";

const infiniteQuery = createInfiniteQuery(() =>
  eden.posts.get.infiniteQueryOptions(
    { query: { limit: "10" } },
    {
      initialPageParam: 0,
      getNextPageParam: (lastPage) => lastPage.nextCursor,
      // cursorKey: 'cursor' // optional, defaults to 'cursor'
    },
  ),
);

Streamed Queries (SSE / async generators)

For Elysia routes that return an async generator (or use sse()), use .streamedOptions. Each yielded chunk is appended to the array stored in data.

import { useQuery } from "@tanstack/react-query";

// server: app.get('/chat', async function* () { yield "hi"; yield "there"; })
const query = useQuery(
  eden.chat.get.streamedOptions(
    { query: { prompt: "hello" } },
    {
      queryFnOptions: { refetchMode: "reset" }, // 'append' | 'reset' | 'replace'
      retry: true,
      // any other QueryObserverOptions...
    },
  ),
);

// query.data: string[] — accumulated chunks

Built on TanStack Query's experimental_streamedQuery. Works with useQuery, useSuspenseQuery, prefetchQuery, etc.

Live Queries

Use .liveOptions when you only care about the latest event. Each chunk replaces the previous value via setQueryData, and the resolved data is the final chunk.

const query = useQuery(eden.counter.get.liveOptions(undefined, { retry: true }));

// query.data: TChunk — the most recent event

The route must yield at least one chunk; otherwise the query rejects. Both helpers honor AbortSignal from TanStack Query.

Mutations

import { createMutation } from "@tanstack/svelte-query";

const mutation = createMutation(
  eden.users.post.mutation({
    onSuccess: (data) => {
      console.log("Created user:", data.id);
    },
  }),
);

// Type-safe variables
mutation.mutate({
  body: { name: "Alice", email: "[email protected]" },
});

Svelte / Solid Inference Note

When using @tanstack/svelte-query or @tanstack/solid-query, TypeScript can sometimes widen mutation TData to undefined if mutationOptions(...) is fully inlined inside createMutation(() => ...) / useMutation(() => ...).

Use one of these stable patterns:

import { createQuery, createMutation } from "@tanstack/svelte-query";

// Query: hoist options first
const userQueryOptions = eden.users({ id: "123" }).get.queryOptions({
  params: { id: "123" },
});
const userQuery = createQuery(() => userQueryOptions);

// Mutation: prefer the built-in accessor helper
const createUserMutation = createMutation(
  eden.users.post.mutation({
    onSuccess: (data) => {
      console.log(data.id);
    },
  }),
);

Solid example:

import { useMutation } from "@tanstack/solid-query";

const createUserMutation = useMutation(
  eden.users.post.mutation({
    onSuccess: (data) => {
      console.log(data.id);
    },
  }),
);

If you need fully inline calls, you can also pin the generic:

type CreateUserResponse = App["~Routes"]["users"]["post"]["response"][200];

const mutation = createMutation(() =>
  eden.users.post.mutationOptions<CreateUserResponse>({
    onSuccess: (data) => {
      console.log(data.id);
    },
  }),
);

Path Params: Inline vs Deferred

For routes like /cases/:id/workflow, you can now choose either pattern:

Inline param (known when building options):

const query = eden.cases({ id: "case-123" }).workflow.get.queryOptions({
  params: { id: "case-123" },
});

const mutation = eden.cases({ id: "case-123" }).workflow.patch.mutationOptions();
await mutation.mutationFn({
  params: { id: "case-123" },
  body: { status: "active" },
});

Deferred param (ID known later at call time):

const query = eden.cases({ id: "" }).workflow.get.queryOptions({
  params: { id: caseId },
});

const mutation = eden.cases({ id: "" }).workflow.patch.mutationOptions();
await mutation.mutationFn({
  params: { id: caseId },
  body: { status: "active" },
});

Recommendation:

  • Use inline params when the route ID is already available.
  • Use deferred params when creating reusable query/mutation configs before the ID is known.

Invalidation

import { useQueryClient } from "@tanstack/svelte-query";

const queryClient = useQueryClient();

// Invalidate specific query
await eden.users({ id: "123" }).get.invalidate(queryClient, {
  params: { id: "123" },
});

// Invalidate all queries for a route
await eden.users({ id: "123" }).get.invalidate(queryClient);

Utils (Bound QueryClient)

For tRPC-like ergonomics, use createEdenTQUtils to bind a QueryClient once:

import { createEdenTQ, createEdenTQUtils } from "eden-tanstack-query";

const eden = createEdenTQ<App>("http://localhost:3000");
const utils = createEdenTQUtils(eden, queryClient);

// No need to pass queryClient every time!
await utils.users({ id: "123" }).get.invalidate({ params: { id: "123" } });
await utils.posts.get.prefetch({ query: { limit: "10" } });
await utils.posts.get.cancel();
await utils.posts.get.refetch();

// Cache manipulation
utils.users({ id: "123" }).get.setData({ params: { id: "123" } }, { id: "123", name: "Updated" });
const cached = utils.users({ id: "123" }).get.getData({ params: { id: "123" } });

Error Handling

queryFn and mutationFn throw when the Eden response has error, so TanStack Query error states are populated automatically:

const options = eden.users({ id: "123" }).get.queryOptions({
  params: { id: "123" },
});

try {
  const data = await options.queryFn();
} catch (error) {
  // error is typed from your Elysia response map
}

If a route has no typed response schema (for example response: never), queryFn/mutationFn data falls back to unknown instead of any.

API

createEdenTQ<App>(domain, config?)

Creates a type-safe Eden client with TanStack Query helpers.

  • domain: Your API URL or Elysia app instance
  • config.queryKeyPrefix: Custom prefix for query keys (default: ['eden'])

createEdenTQFromSchema<Routes>(domain, config?)

Creates the same client from a route schema (App['~Routes']) instead of the full app type.

Use this when your editor/tsserver slows down with very large app types.

createEdenTQUtils<App>(eden, queryClient)

Creates a utils object with a bound QueryClient for tRPC-like ergonomics.

Method Helpers

Each HTTP method (get, post, put, delete, patch) has:

| Method | Description | | ------------------------------------------------ | ------------------------------------------------------------------------------------------- | | .queryOptions(input, overrides?) | Returns { queryKey, queryFn, ...options } for createQuery | | .infiniteQueryOptions(input, opts, overrides?) | Returns options for createInfiniteQuery | | .streamedOptions(input?, overrides?) | Returns options for SSE/generator routes; data is TChunk[] (chunks accumulate) | | .liveOptions(input?, overrides?) | Returns options for SSE/generator routes; data is the latest TChunk (replaces) | | .mutationOptions(overrides?) | Returns { mutationKey, mutationFn, ...options } for createMutation | | .mutation(overrides?) | Returns a stable () => mutationOptions accessor for adapters expecting an options factory | | .queryKey(input?) | Returns the query key | | .mutationKey(input?) | Returns the mutation key | | .invalidate(queryClient, input?, exact?) | Invalidates matching queries | | .prefetch(queryClient, input) | Prefetch a query | | .ensureData(queryClient, input) | Ensure data exists or fetch it | | .setData(queryClient, input, updater) | Manually set cache data | | .getData(queryClient, input) | Read from cache |

Query Key Shape

Query keys are deterministic and include routing information:

[
  ...queryKeyPrefix, // default ['eden']
  method,            // 'get', 'post', ...
  pathTemplate,      // e.g. ['users', ':id']
  params ?? null,
  query ?? null
]

Query Options Overrides

You can pass standard TanStack Query options as overrides:

eden.posts.get.queryOptions(
  { query: { limit: "10" } },
  {
    staleTime: 5000,
    gcTime: 10000,
    enabled: isReady,
    refetchOnMount: false,
    retry: 3,
  },
);

Mutation Options Overrides

eden.users.post.mutationOptions({
  onMutate: (variables) => {
    // Optimistic update
  },
  onSuccess: (data, variables) => {
    // Invalidate related queries
  },
  onError: (error, variables, context) => {
    // Rollback
  },
});

mutationOptions() and mutation() are equivalent in typing. Use mutation() when your adapter usage prefers passing a stable options accessor directly.

Before / After

TypeScript Performance Tips

If your API has many routes:

  • Prefer createEdenTQFromSchema<App['~Routes']>() in frontend/client packages.
  • Create feature-scoped route schema aliases (for example BillingRoutes, CaseRoutes) and instantiate smaller clients per feature.
  • Increase tsserver memory in VS Code:
    • "typescript.tsserver.maxTsServerMemory": 4096

Before (manual boilerplate)

export function createUserQuery(userId: string) {
  return createQuery<User>(() => ({
    queryKey: ["users", userId],
    queryFn: async () => {
      const { data, error } = await api.users({ id: userId }).get();
      if (error) throw error;
      return data as User; // Manual cast!
    },
  }));
}

After (with eden-tanstack-query)

export function createUserQuery(userId: string) {
  return createQuery(() =>
    eden.users({ id: userId }).get.queryOptions({
      params: { id: userId },
    }),
  );
}
// Types are inferred from your Elysia server!

License

MIT