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

@neosianexus/super-tebex

v3.3.0

Published

Tebex Headless SDK optimized for Next.js App Router with TanStack Query

Readme

@neosianexus/super-tebex

npm version npm downloads Coverage TypeScript License: MIT

Tebex Headless SDK optimized for Next.js App Router with TanStack Query and Zustand.

Features

  • TanStack Query v5 - Automatic caching, retry, stale-while-revalidate
  • Zustand v5 - Persisted client state (basket, user) in localStorage
  • TypeScript First - Zero any, strict mode, exhaustive types
  • Provider Pattern - Single provider, granular hooks
  • Error Codes - i18n-friendly error handling with TebexErrorCode enum
  • Optimistic Updates - Instant UI feedback on basket mutations

Installation

npm install @neosianexus/super-tebex
# or
yarn add @neosianexus/super-tebex
# or
pnpm add @neosianexus/super-tebex
# or
bun add @neosianexus/super-tebex

Peer Dependencies

npm install @tanstack/react-query zustand tebex_headless

| Dependency | Version | |------------|---------| | react | ^18.3.1 || ^19.0.0 | | react-dom | ^18.3.1 || ^19.0.0 | | next | ^14.0.0 || ^15.0.0 (optional) | | @tanstack/react-query | ^5.0.0 | | zustand | ^5.0.0 | | tebex_headless | ^1.15.0 |

Quick Start

1. Setup Provider

Wrap your app with TebexProvider in your root layout:

// app/layout.tsx
import { TebexProvider } from '@neosianexus/super-tebex';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <TebexProvider
          config={{
            publicKey: process.env.NEXT_PUBLIC_TEBEX_KEY!,
            baseUrl: process.env.NEXT_PUBLIC_BASE_URL!,
            urls: {
              complete: '/shop/complete', // optional, default: /shop/complete
              cancel: '/shop/cancel',     // optional, default: /shop/cancel
            },
            onError: (error) => {
              // Global error handler (optional)
              console.error(`Tebex Error [${error.code}]:`, error.message);
            },
          }}
        >
          {children}
        </TebexProvider>
      </body>
    </html>
  );
}

2. Use Hooks

'use client';

import { useCategories, useBasket, useUser } from '@neosianexus/super-tebex';

export function Shop() {
  const { username, setUsername } = useUser();
  const { categories, isLoading } = useCategories({ includePackages: true });
  const { addPackage, itemCount, isAddingPackage } = useBasket();

  if (isLoading) return <div>Loading...</div>;

  return (
    <div>
      <p>Cart: {itemCount} items</p>
      {categories?.map(category => (
        <div key={category.id}>
          <h2>{category.name}</h2>
          {category.packages?.map(pkg => (
            <button
              key={pkg.id}
              onClick={() => addPackage({ packageId: pkg.id })}
              disabled={!username || isAddingPackage}
            >
              Add {pkg.name} - {pkg.price}
            </button>
          ))}
        </div>
      ))}
    </div>
  );
}

API Reference

Provider & Configuration

| Export | Description | |--------|-------------| | TebexProvider | Main provider component - wrap your app with this | | useTebexContext() | Access QueryClient and config from context | | useTebexConfig() | Access just the Tebex configuration |

TebexConfig

interface TebexConfig {
  publicKey: string;       // Your Tebex public key
  baseUrl: string;         // Your site URL (for checkout redirects)
  urls?: {
    complete?: string;     // Success redirect path (default: '/shop/complete')
    cancel?: string;       // Cancel redirect path (default: '/shop/cancel')
  };
  onError?: (error: TebexError) => void;  // Global error callback
}

Note: React Query DevTools are not included. To add them, install @tanstack/react-query-devtools and render them manually in development.


Hooks

Data Fetching Hooks

| Hook | Description | |------|-------------| | useCategories(options?) | Fetch all categories (with optional packages) | | useCategory(options) | Fetch a single category by ID | | usePackages(options?) | Fetch all packages (optionally by category) | | usePackage(options) | Fetch a single package by ID | | useWebstore() | Fetch webstore info (name, currency, domain) |

Basket Management Hooks

| Hook | Description | |------|-------------| | useBasket() | Full basket management with optimistic updates | | useCheckout(options?) | Launch Tebex.js checkout modal | | useCoupons() | Apply/remove coupon codes | | useGiftCards() | Apply/remove gift cards | | useCreatorCodes() | Apply/remove creator codes | | useGiftPackage() | Gift a package to another user |

User Management Hooks

| Hook | Description | |------|-------------| | useUser() | Username management (persisted in localStorage) |


Hook Details

useBasket

const {
  // Data
  basket,           // Basket | null
  packages,         // BasketPackage[]
  basketIdent,      // string | null
  itemCount,        // number
  total,            // number
  isEmpty,          // boolean

  // Loading states
  isLoading,
  isFetching,
  isAddingPackage,
  isRemovingPackage,
  isUpdatingQuantity,

  // Error handling
  error,            // TebexError | null
  errorCode,        // TebexErrorCode | null

  // Actions
  addPackage,       // (params: AddPackageParams) => Promise<void>
  removePackage,    // (packageId: number) => Promise<void>
  updateQuantity,   // (params: UpdateQuantityParams) => Promise<void>
  clearBasket,      // () => void
  refetch,          // () => Promise<...>
} = useBasket();

// Add package with options
await addPackage({
  packageId: 123,
  quantity: 2,                          // optional, default: 1
  type: 'single',                       // optional: 'single' | 'subscription'
  variableData: { server: 'survival' }, // optional
});

useCategories

const {
  categories,  // Category[] | null
  data,        // same as categories
  isLoading,
  isFetching,
  error,
  errorCode,
  refetch,

  // Helpers
  getByName,   // (name: string) => Category | undefined
  getById,     // (id: number) => Category | undefined
  getBySlug,   // (slug: string) => Category | undefined
} = useCategories({
  includePackages: true,  // default: true
  enabled: true,          // default: true
});

usePackages

const {
  packages,    // Package[] | null
  data,        // same as packages
  isLoading,
  isFetching,
  error,
  errorCode,
  refetch,

  // Helpers
  getById,     // (id: number) => Package | undefined
  getByName,   // (name: string) => Package | undefined
} = usePackages({
  categoryId: 123,  // optional: filter by category
  enabled: true,    // default: true
});

useCheckout

const {
  launch,       // () => Promise<void>
  isLaunching,
  error,
  errorCode,
  canCheckout,  // boolean - true if basket has items
  checkoutUrl,  // string | null - direct checkout URL
} = useCheckout({
  onSuccess: () => console.log('Payment complete!'),
  onError: (error) => console.error(error),
  onClose: () => console.log('Checkout closed'),
});

// IMPORTANT: Requires Tebex.js script in your page
// <script src="https://js.tebex.io/v/1.1.js" async />

useUser

const {
  username,        // string | null
  setUsername,     // (username: string) => void
  clearUsername,   // () => void
  isAuthenticated, // boolean
} = useUser();

useCoupons

const {
  coupons,     // Code[]
  apply,       // (code: string) => Promise<void>
  remove,      // (code: string) => Promise<void>
  isApplying,
  isRemoving,
  error,
  errorCode,
} = useCoupons();

useGiftCards

const {
  giftCards,   // GiftCardCode[]
  apply,       // (code: string) => Promise<void>
  remove,      // (code: string) => Promise<void>
  isApplying,
  isRemoving,
  error,
  errorCode,
} = useGiftCards();

useCreatorCodes

const {
  creatorCode, // string | null
  apply,       // (code: string) => Promise<void>
  remove,      // () => Promise<void>
  isApplying,
  isRemoving,
  error,
  errorCode,
} = useCreatorCodes();

useGiftPackage

const {
  gift,        // (params: GiftPackageParams) => Promise<void>
  isGifting,
  error,
  errorCode,
} = useGiftPackage();

// Gift a package to another player
await gift({
  packageId: 123,
  targetUsername: 'friend_username',
});

useWebstore

const {
  webstore,    // Webstore | null
  name,        // string | null
  currency,    // string | null
  domain,      // string | null
  isLoading,
  isFetching,
  error,
  errorCode,
  refetch,
} = useWebstore();

Error Handling

All hooks expose error (TebexError) and errorCode (TebexErrorCode) for i18n-friendly error handling:

import { TebexErrorCode } from '@neosianexus/super-tebex';

const { error, errorCode } = useBasket();

// Use error codes for translations
const errorMessages: Record<TebexErrorCode, string> = {
  [TebexErrorCode.NOT_AUTHENTICATED]: 'Please log in first',
  [TebexErrorCode.BASKET_NOT_FOUND]: 'Your cart has expired',
  [TebexErrorCode.PACKAGE_OUT_OF_STOCK]: 'Item is out of stock',
  // ...
};

if (errorCode) {
  toast.error(errorMessages[errorCode] ?? 'An error occurred');
}

All Error Codes

| Category | Codes | |----------|-------| | Provider | PROVIDER_NOT_FOUND, INVALID_CONFIG | | Auth | NOT_AUTHENTICATED, INVALID_USERNAME | | Basket | BASKET_NOT_FOUND, BASKET_CREATION_FAILED, BASKET_EXPIRED, BASKET_EMPTY | | Package | PACKAGE_NOT_FOUND, PACKAGE_OUT_OF_STOCK, PACKAGE_ALREADY_OWNED, INVALID_QUANTITY | | Category | CATEGORY_NOT_FOUND | | Coupon | COUPON_INVALID, COUPON_EXPIRED, COUPON_ALREADY_USED | | Gift Card | GIFTCARD_INVALID, GIFTCARD_INSUFFICIENT_BALANCE | | Creator Code | CREATOR_CODE_INVALID | | Checkout | CHECKOUT_FAILED, CHECKOUT_CANCELLED, TEBEX_JS_NOT_LOADED | | Network | NETWORK_ERROR, TIMEOUT, RATE_LIMITED | | Unknown | UNKNOWN |


TypeScript

All types are exported:

import type {
  // Config
  TebexConfig,
  TebexUrls,
  ResolvedTebexConfig,

  // Hook Returns
  UseBasketReturn,
  UseCategoriesReturn,
  UseCategoriesOptions,
  UseCategoryReturn,
  UseCategoryOptions,
  UsePackagesReturn,
  UsePackagesOptions,
  UsePackageReturn,
  UsePackageOptions,
  UseCheckoutReturn,
  UseCheckoutOptions,
  UseCouponsReturn,
  UseGiftCardsReturn,
  UseCreatorCodesReturn,
  UseGiftPackageReturn,
  UseWebstoreReturn,
  UseUserReturn,

  // Params
  AddPackageParams,
  UpdateQuantityParams,
  GiftPackageParams,

  // Base types
  BaseQueryReturn,
  BaseMutationReturn,

  // Tebex API types (re-exported from tebex_headless)
  Basket,
  BasketPackage,
  Category,
  Package,
  PackageType,
  Webstore,
  Code,
  GiftCardCode,

  // Utilities
  Result,
} from '@neosianexus/super-tebex';

// Error handling
import { TebexError, TebexErrorCode } from '@neosianexus/super-tebex';

// Type guards
import {
  isTebexError,  // (error: unknown) => error is TebexError
  isSuccess,     // (result: Result<T,E>) => result is { ok: true, data: T }
  isError,       // (result: Result<T,E>) => result is { ok: false, error: E }
  isDefined,     // <T>(value: T | null | undefined) => value is T
} from '@neosianexus/super-tebex';

// Result utilities
import { ok, err } from '@neosianexus/super-tebex';

Zustand Stores

Direct store access for advanced use cases:

import { useBasketStore, useUserStore } from '@neosianexus/super-tebex';

// Access stores directly (outside of hooks)
const basketIdent = useBasketStore(state => state.basketIdent);
const setBasketIdent = useBasketStore(state => state.setBasketIdent);
const clearBasketIdent = useBasketStore(state => state.clearBasketIdent);

const username = useUserStore(state => state.username);
const setUsername = useUserStore(state => state.setUsername);
const clearUsername = useUserStore(state => state.clearUsername);

Query Keys

For manual cache invalidation:

import { tebexKeys } from '@neosianexus/super-tebex';
import { useQueryClient } from '@tanstack/react-query';

const queryClient = useQueryClient();

// Available keys
tebexKeys.all              // ['tebex']
tebexKeys.categories()     // ['tebex', 'categories']
tebexKeys.categoriesList() // ['tebex', 'categories', 'list']
tebexKeys.category(id)     // ['tebex', 'categories', id]
tebexKeys.packages()       // ['tebex', 'packages']
tebexKeys.packagesList()   // ['tebex', 'packages', 'list']
tebexKeys.package(id)      // ['tebex', 'packages', id]
tebexKeys.baskets()        // ['tebex', 'baskets']
tebexKeys.basket(ident)    // ['tebex', 'baskets', ident]
tebexKeys.webstore()       // ['tebex', 'webstore']

// Invalidate specific queries
queryClient.invalidateQueries({ queryKey: tebexKeys.categories() });
queryClient.invalidateQueries({ queryKey: tebexKeys.basket(basketIdent) });

Advanced Usage

Custom QueryClient

import { QueryClient } from '@tanstack/react-query';
import { TebexProvider } from '@neosianexus/super-tebex';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 30 * 1000, // 30 seconds
    },
  },
});

<TebexProvider config={config} queryClient={queryClient}>
  {children}
</TebexProvider>

Direct API Client Access

For advanced scenarios requiring direct API access:

import { getTebexClient, isTebexClientInitialized } from '@neosianexus/super-tebex';

if (isTebexClientInitialized()) {
  const client = getTebexClient();
  // Use client directly (advanced usage only)
}

Complete Example

'use client';

import { useCategories, useBasket, useUser, useCheckout } from '@neosianexus/super-tebex';
import { useState } from 'react';

export default function ShopPage() {
  const [input, setInput] = useState('');

  // User
  const { username, setUsername, clearUsername } = useUser();

  // Categories
  const { categories, isLoading: categoriesLoading } = useCategories({
    includePackages: true,
  });

  // Basket
  const {
    basket,
    packages,
    addPackage,
    removePackage,
    itemCount,
    total,
    isAddingPackage,
    isEmpty,
  } = useBasket();

  // Checkout
  const { launch, canCheckout, isLaunching } = useCheckout({
    onSuccess: () => alert('Thank you for your purchase!'),
  });

  // Login handler
  const handleLogin = () => {
    if (input.trim()) {
      setUsername(input.trim());
      setInput('');
    }
  };

  if (categoriesLoading) {
    return <div>Loading store...</div>;
  }

  return (
    <div className="container">
      {/* Auth Section */}
      <header>
        {username ? (
          <div>
            <span>Welcome, {username}</span>
            <button onClick={clearUsername}>Logout</button>
          </div>
        ) : (
          <div>
            <input
              value={input}
              onChange={(e) => setInput(e.target.value)}
              placeholder="Enter username"
            />
            <button onClick={handleLogin}>Login</button>
          </div>
        )}
      </header>

      <main>
        {/* Products */}
        <section>
          <h1>Products</h1>
          {categories?.map(category => (
            <div key={category.id}>
              <h2>{category.name}</h2>
              <div className="grid">
                {category.packages?.map(pkg => (
                  <div key={pkg.id} className="card">
                    <h3>{pkg.name}</h3>
                    <p>{pkg.price}</p>
                    <button
                      onClick={() => addPackage({ packageId: pkg.id })}
                      disabled={!username || isAddingPackage}
                    >
                      {isAddingPackage ? 'Adding...' : 'Add to Cart'}
                    </button>
                  </div>
                ))}
              </div>
            </div>
          ))}
        </section>

        {/* Cart */}
        <aside>
          <h2>Cart ({itemCount})</h2>
          {isEmpty ? (
            <p>Your cart is empty</p>
          ) : (
            <>
              <ul>
                {packages.map(pkg => (
                  <li key={pkg.id}>
                    {pkg.name} x{pkg.in_basket.quantity}
                    <button onClick={() => removePackage(pkg.id)}>Remove</button>
                  </li>
                ))}
              </ul>
              <p>Total: {basket?.base_price}</p>
              <button
                onClick={launch}
                disabled={!canCheckout || isLaunching}
              >
                {isLaunching ? 'Loading...' : 'Checkout'}
              </button>
            </>
          )}
        </aside>
      </main>
    </div>
  );
}

Migration from v2

Breaking Changes

| v2 | v3 | Migration | |----|-----|-----------| | initTebex(key) | <TebexProvider config={{...}}> | Wrap app with Provider | | initShopUrls(url) | config.baseUrl + config.urls | Pass in config | | useBasket(username) | useBasket() + useUser() | User is separate | | error.message (FR) | error.code | Translate codes yourself | | sonner peer dep | Removed | Handle toasts yourself | | useShopUserStore | useUserStore | Renamed | | useShopBasketStore | useBasketStore | Renamed |

Migration Example

Before (v2):

// lib/tebex.ts
initTebex(process.env.NEXT_PUBLIC_TEBEX_KEY);
initShopUrls('https://mysite.com');

// Component
const username = useShopUserStore(s => s.username);
const { basket, addPackageToBasket, error } = useBasket(username);

if (error) toast.error(error.message); // French message

After (v3):

// app/layout.tsx
<TebexProvider
  config={{
    publicKey: process.env.NEXT_PUBLIC_TEBEX_KEY!,
    baseUrl: 'https://mysite.com',
    onError: (err) => toast.error(t(`errors.${err.code}`)),
  }}
>
  {children}
</TebexProvider>

// Component
const { username } = useUser();
const { basket, addPackage, errorCode } = useBasket();

// Errors handled by onError callback or manually with errorCode

Quick Reference for AI/LLM

Package Info

  • Name: @neosianexus/super-tebex
  • Purpose: Tebex Headless SDK wrapper for React/Next.js
  • State: TanStack Query v5 (server) + Zustand v5 (client)

Common Patterns

// 1. Setup (app/layout.tsx)
<TebexProvider config={{ publicKey, baseUrl }}>{children}</TebexProvider>

// 2. Get categories with packages
const { categories } = useCategories({ includePackages: true });

// 3. Add to basket
const { addPackage } = useBasket();
await addPackage({ packageId: 123 });

// 4. Checkout
const { launch, canCheckout } = useCheckout();
if (canCheckout) await launch();

// 5. User management
const { username, setUsername } = useUser();
setUsername('player_name');

Hook Signatures

| Hook | Key Params | Key Returns | |------|------------|-------------| | useCategories(opts?) | includePackages | categories, getById, getByName, getBySlug | | useCategory(opts) | id | category | | usePackages(opts?) | categoryId | packages, getById, getByName | | usePackage(opts) | id | package | | useWebstore() | - | webstore, name, currency, domain | | useBasket() | - | basket, addPackage, removePackage, itemCount | | useCheckout(opts?) | onSuccess, onError | launch, canCheckout, isLaunching | | useUser() | - | username, setUsername, isAuthenticated | | useCoupons() | - | coupons, apply, remove | | useGiftCards() | - | giftCards, apply, remove | | useCreatorCodes() | - | creatorCode, apply, remove | | useGiftPackage() | - | gift, isGifting |

Error Handling Pattern

const { errorCode } = useBasket();
if (errorCode === TebexErrorCode.BASKET_NOT_FOUND) {
  // Handle expired basket
}

Requirements

  • Must wrap app with TebexProvider
  • Checkout requires <script src="https://js.tebex.io/v/1.1.js" async />
  • Username required before adding to basket

License

MIT


Links