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

@a5it/storefront-sdk

v0.1.4

Published

Type-safe storefront authentication SDK with core, React, and Next.js clients

Readme

@a5it/storefront-sdk

Type-safe client for the A5IT storefront API. The auth, products, categories, brands, orders, and addresses modules work through framework-independent promises, React Query hooks, and a Next.js server client. useCart is a separate, localStorage-backed cart hook exported alongside them. Payment provider integrations (card tokenization, ACH/Plaid, 3-D Secure) are intentionally not part of the SDK; applications call those directly.

Install

pnpm add @a5it/storefront-sdk react @tanstack/react-query

React 18/19 and React Query 5 are peer dependencies. Next.js 14/15 is an optional peer dependency, required when using the /server entry.

Core client

Use the framework-independent entry in scripts, tests, or custom integrations:

import { createStorefrontClient, memoryTokenStore } from "@a5it/storefront-sdk/core";

const tokens = memoryTokenStore();
const client = createStorefrontClient({
  baseUrl: "https://api.example.com/api/v2",
  cookieName: "storefront_session",
  tenantDomain: "store.example.com",
  advanced: { tokenStore: tokens },
});

const challenge = await client.auth.sendCode(
  { email: "[email protected]" },
  { captchaToken: "captcha-response" },
);
await client.auth.confirmEmail(
  { code: "123456", token: challenge.token },
  { captchaToken: "new-captcha-response" },
);
const session = await client.auth.getSession();
await client.auth.logout();

Successful confirmation and registration store the returned session token. Successful logout clears it. Every core endpoint returns a promise. Request options accept captchaToken and an AbortSignal as signal.

React integration

Mount a provider inside your application's client boundary:

"use client";

import { StorefrontProvider, storefront } from "@a5it/storefront-sdk";

function Account() {
  const { data, isLoading, error } = storefront.auth.getSession();
  const { mutate: logout, isPending } = storefront.auth.logout();

  if (isLoading) return <p>Loading account…</p>;
  if (error) return <p>{error.message}</p>;
  return (
    <div>
      <p>{data?.user.email}</p>
      <button disabled={isPending} onClick={() => logout(undefined)}>Sign out</button>
    </div>
  );
}

export function AccountApp() {
  return (
    <StorefrontProvider config={{ baseUrl: "/api/v2", cookieName: "storefront_session" }}>
      <Account />
    </StorefrontProvider>
  );
}

Call storefront.auth.* hooks at the top level of client components or custom hooks. Pass query options after the input; for example, storefront.auth.getSession(undefined, { enabled: signedIn }). Caller options such as staleTime override endpoint defaults. The provider creates its runtime once per mount; remount it to change configuration.

Mutation callbacks receive the original input. Captcha tokens are supplied per mutation call:

const { mutate: confirmEmail } = storefront.auth.confirmEmail();
confirmEmail({ code, token }, { captchaToken, onSuccess: () => router.refresh() });

Cache invalidation

Mutations declare which queries they make stale on the endpoint definition itself, via invalidates, so callers never have to remember to do it. addresses.add, for example:

add: mutation<IAddressWriteInput, IAddress>({
  method: "POST",
  path: ADDRESSES,
  auth: "required",
  invalidates: [queryKey("addresses", "list")],
}),

Every successful call to storefront.addresses.add() (or .update() / .remove()) invalidates addresses.list automatically, refetching it wherever it's mounted — no manual queryClient.invalidateQueries at the call site. Add more keys to invalidates if a mutation should also bust another module's cache (e.g. an order mutation that also changes the customer's default address), or use ALL_QUERIES (as auth.confirmEmail/register/logout do) to clear everything on login/logout: the whole cache is reset, so results fetched under the previous session are dropped and mounted queries refetch.

You are not limited to what a definition declares. Cache helpers use the mounted provider and can run in event handlers or anywhere else in the app:

storefront.auth.getSession.key();
storefront.auth.getSession.getData(undefined);
await storefront.auth.getSession.fetch(undefined);
await storefront.auth.getSession.prefetch(undefined);
await storefront.auth.getSession.cancel();
await storefront.auth.getSession.invalidate();  // one endpoint
await storefront.auth.invalidate();             // every endpoint in a module
await storefront.invalidateAll();               // everything

Query helpers also expose setData(input, valueOrUpdater). Omitting the input to key, invalidate, or cancel targets every cached input for that endpoint. Imperative helpers use a single active provider; use one provider per application. For a one-off case a declared invalidates doesn't cover, useStorefrontContext() returns the real QueryClient, so queryClient.invalidateQueries(...) still works exactly as it would outside the SDK.

Next.js server integration

import { configureStorefront, storefront } from "@a5it/storefront-sdk/server";

configureStorefront({
  baseUrl: process.env.STOREFRONT_API_URL!,
  cookieName: "storefront_session",
});

// Inside a server component, route handler, or server action:
const session = await storefront.auth.getSession();

The server entry uses Next.js's native fetch transport. Every endpoint is a plain promise, and public GET queries accept Next's Data Cache options after the input:

import { cache } from "react";
import { storefront } from "@a5it/storefront-sdk/server";

const getCategory = cache((slug: string) =>
  storefront.categories.getBySlug(
    { slug },
    {
      next: {
        revalidate: 60,
        tags: [`category:${slug}`],
      },
    },
  ),
);

next.revalidate and next.tags persist public responses in Next's Data Cache across requests. cache: "force-cache" is also supported when time-based revalidation is not needed. React's cache() serves a different purpose: it deduplicates calls within one server render, such as a request shared by generateMetadata and the page. Pass server data to a client component as a prop or as initialData on the matching hook.

Server caching is intentionally limited to endpoint definitions with kind: "query", method: "GET", and auth: "none". Authenticated and optionally authenticated queries, along with every mutation, are always sent with cache: "no-store"; attempting to enable caching for one of them throws. This prevents user-specific responses from entering a shared cache.

Configuration is read lazily. Each call reads the current request's host, protocol, and authentication cookie from next/headers, then creates a fresh transport. This makes routes using the server singleton dynamically rendered, even when a public endpoint's response uses the Data Cache. Configure shared deployment settings, not per-user tokens. Server token stores are read-only: login/logout calls do not write response cookies; handle cookie changes in your route handler or server action.

Without explicit configuration, the server reads STOREFRONT_API_URL or NEXT_PUBLIC_BASE_API_ENDPOINT, plus NEXT_PUBLIC_COOKIE_NAME. Relative API paths use NEXT_PUBLIC_BACKEND_URL as the origin, falling back to http://localhost:8000. Outside a Next.js request scope, calls have no request cookie or tenant headers; use the core client for scripts needing explicit tokens.

Authentication endpoints

| Method | Input | Result | | --- | --- | --- | | auth.sendCode | ISendCode | ISendCodeResult | | auth.confirmEmail | IConfirmEmail | IAuthResult | | auth.register | IRegisterUser | IAuthResult | | auth.checkEmailExistence | ICheckEmail | IEmailExistence | | auth.getSession | none | ISession | | auth.logout | none | undefined | | auth.makeOwner | IMakeOwner | undefined |

getSession and makeOwner require a token and reject with NO_TOKEN before sending a request when none is available. Session models preserve the backend's nested customer, organization, permissions, and benefit fields. These are part of the authentication response contract, not separate API modules.

Product endpoints

| Method | Input | Result | | --- | --- | --- | | products.search | IProductSearch | IProductSearchResult | | products.filter | IProductFilter | IProductFilterResult | | products.conditionFacets | IConditionFacetFilter | IConditionFacet[] | | products.related | IRelatedProducts | IRelatedProduct[] | | products.getBySlug | IProductSlug | IProductDetails \| null | | products.getBasicBySlug | IProductSlug | IProductBasic \| null | | products.conditionSiblings | IProductSlug | IProductConditionSibling[] | | pricing.priceDetails | IProductIds | IProductPrice[] | | pricing.livePrices | IProductIds | IProductPrice[] | | pricing.validateCartPrices (mutation) | IProductIds | IProductPrice[] | | products.liveStock (mutation, captcha) | IProductId | IProductLiveStock |

Listing endpoints (search, filter, related) return products without prices; call priceDetails with the listed ids and merge. priceDetails is cached for an hour to match the backend; livePrices bypasses that cache (and is never cached by the hook) and validateCartPrices always returns fresh prices. getBySlug returns the raw catalog record with its brand and category, never a price; getBasicBySlug is the small first-render subset. Both resolve to null and conditionSiblings to [] on 404. These three catalog queries are public, tenant-scoped GETs and may use the server entry's Next.js cache options. Pricing queries remain optionally authenticated because their results can vary by customer organization and cannot use the shared server cache. liveStock is a mutation because each call needs a fresh captcha token.

Category and brand endpoints

| Method | Input | Result | | --- | --- | --- | | categories.list | ICategoryFilter | ICategoryListResult | | categories.minimal | none | ICategoryMenuItem[] | | categories.getBySlug | ICategorySlug | ICategoryDetails \| null | | categories.hierarchy | ICategorySlug | ICategoryHierarchy \| null | | brands.list | IBrandFilter | IBrandListResult |

categories.list returns top-level categories with nested children; minimal returns the featured tree used for navigation. Unknown slugs resolve to null. brands.list paginates only when both page and limit are sent, otherwise it returns every brand.

Order and address endpoints

| Method | Input | Result | | --- | --- | --- | | orders.place (mutation, captcha) | IPlaceOrderInput | IPlaceOrderResult | | orders.placeGuest (mutation, captcha) | IPlaceGuestOrderInput | IPlaceOrderResult | | orders.calculateCharges (mutation) | ICalculateChargesInput | IOrderCharges | | orders.calculateGuestCharges (mutation) | ICalculateGuestChargesInput | IOrderCharges | | orders.applyCoupon (mutation, guest-friendly) | IApplyCouponInput | ICoupon | | addresses.list | none | IAddress[] | | addresses.add (mutation) | IAddressWriteInput | IAddress | | addresses.update (mutation) | IUpdateAddressInput | undefined | | addresses.remove (mutation) | IAddressId | undefined |

orders.place/placeGuest return requires3DS/id/teamId/appId instead of order when Authorize.Net asks for a 3-D Secure challenge; run the challenge and retry with threeDsSessionId set. orders.applyCoupon requires reseller/tenant resolution but not a customer token, so guest checkouts can call it too. addresses.add takes IAddressWriteInput, while .update takes a partial write input — the backend's create/update schemas are strict and reject id/name, so those don't appear on write inputs the way they do on IAddress/IAddressInput (the broader shape used for an already-known or checkout-selected address). .update responds with an empty envelope, not the updated address — re-read addresses.list for fresh data. addresses.add, .update, and .remove all invalidate addresses.list (see Cache invalidation). These modules cover order placement, charge calculation, coupons, and the address book only — card/ACH tokenization, payment credentials, and provider-specific flows (Authorize.Net, Finix, Plaid) stay in application code.

Configuration and errors

baseUrl is required. Optional configuration includes cookieName, timeoutMs (default: 100,000), and advanced: { tokenStore, headers, onUnauthorized, queryClient }. Core clients also accept tenantDomain and getToken.

Token precedence is getToken, then advanced.tokenStore, then browser cookies (or an in-memory store outside the browser). Set cookieName to the backend's customer cookie name. If omitted, the SDK reads NEXT_PUBLIC_COOKIE_NAME and otherwise warns before using a5sync_auth_token.

Errors are normalized to StorefrontError with status, code, message, and messages. Use isStorefrontError to identify them. Only HTTP 401 responses with SESSION_SUPERSEDED, TOKEN_INVALID, ACCOUNT_BLOCKED, or POLICY_REQUIRES_2FA clear the token and call advanced.onUnauthorized. Other client errors do not end the session. React queries do not retry 4xx responses. Applications control how errors are displayed.

Development

Storefront modules live in src/core/modules/, with each module defining its own endpoints (auth.ts, products.ts, categories.ts, brands.ts, orders.ts, addresses.ts). Register new modules in modules/index.ts and add their names to MODULE_NAMES in modules/names.ts. The shared bindings expose these modules through the core client, React hooks, and server client.

Use Node.js 22.15 or newer for the test runner's module mocking hooks.

pnpm install --frozen-lockfile
pnpm typecheck
pnpm test

pnpm test builds the ESM/CommonJS bundles and declarations, then runs the authentication, cache, server-context, and package-surface checks. The root entry has a "use client" directive; /core is framework-independent and /server uses the server-only boundary.