Augur

Recipes

Everyday patterns on the augur packages: the proxy, SSR prefetch, cart changes, category and search lists, errors and upgrades.

Recipes

Patterns for a site already set up as in Getting started.

The proxy

Create it once and use q everywhere. Its types come from augur-api, so autocomplete shows every SDK method.

// lib/augur-query.ts
"use client";
import { createServerQueryProxy } from "@simpleapps-com/augur-server/hooks";

export const q = createServerQueryProxy();
import { useMutation, useQuery } from "@tanstack/react-query";
import { q } from "@/lib/augur-query";

const { data: item } = useQuery(q.items.invMast.doc.get(invMastUid));
const { data: price } = useQuery(q.pricing.priceEngine.list({ itemId: "SKU-1", customerId }));
const addLine = useMutation(q.commerce.cartLine.add.create());

Per-call options go after the spread: useQuery({ ...q.items.invMast.doc.get(invMastUid), enabled: invMastUid > 0, select }). Debounce the input (useDebounce) before passing it in.

SSR prefetch

Prefetch with createAugurOptions from @simpleapps-com/augur-hooks/server and wrap the page in HydrationBoundary; Getting started has the full page. The server and the browser MUST build the query through a proxy so the keys match.

Cart changes

useCartActions updates the cart store at once and rolls back if your server action fails:

import { useCartActions } from "@simpleapps-com/augur-hooks";
import { addToCart, deleteItemsFromCart, updateCartLines } from "@/lib/actions/commerce";

const cart = useCartActions({ addToCart, updateCartLines, deleteItemsFromCart, toast: { info: toast.info, error: toast.error } });

await cart.addToCart({ invMastUid: 123, quantity: 1, unitOfMeasure: "EA" });
await cart.updateQuantity(123, 3);
await cart.removeFromCart(123);

It also returns clearCart, isInCart, getItemQuantity, cartLines, cartHdrUid and invalidateCartCache. For other writes, use the proxy's mutation options.

Category items with filters

serializeCategoryFilters and paginateOffset (augur-utils) turn TItemsFilters into the SDK's params: JSON-encoded filters, stock included, and sortByPrefix applied when both it and sortBy are set.

import { useInfiniteQuery } from "@tanstack/react-query";
import { paginateOffset, serializeCategoryFilters } from "@simpleapps-com/augur-utils";
import { queryAction } from "@simpleapps-com/augur-server/query-action";
import { siteConfig } from "@/lib/augur-config";
import { q } from "@/lib/augur-query";

const page = (pageParam: number) =>
  serializeCategoryFilters(paginateOffset(filters, pageParam), { sortByPrefix: siteConfig.items?.sortByPrefix });

const { data, fetchNextPage } = useInfiniteQuery({
  queryKey: q.items.categories.items.list(itemCategoryUid, page(0)).queryKey,
  queryFn: ({ pageParam }) =>
    queryAction("items.categories.items.list", itemCategoryUid, page(pageParam)) as Promise<{ total: number }>,
  initialPageParam: 0,
  getNextPageParam: (last, _all, lastPage) =>
    (lastPage + 1) * filters.limit < last.total ? lastPage + 1 : undefined,
});

An infinite query fetches a different page per pageParam, so it takes the proxy's key and its own queryFn calling queryAction, the server action behind the proxy (which resolves to the response's data).

Search with filters

serializeSearchParams maps the same filters to the search endpoint's names: limit to size, offset to from, searchType: "query", a categoryUid to classId5List, and sourceFieldsList (default display_desc):

const params = serializeSearchParams(paginateOffset(filters, pageParam), {
  categoryUid,
  sourceFields: siteConfig.search?.sourceFields,
});
useQuery(q.openSearch.itemSearch.list(params));

Errors

  • Reads: let React Query handle them (retries, isError, error). Don't wrap reads in try/catch or safeAction.
  • Writes in server actions: wrap them in safeAction, which gives { ok: true, data } or { ok: false, error } instead of throwing.
"use server";
import { getSiteActions, safeAction } from "@simpleapps-com/augur-server";

type CartItem = { invMastUid: number; quantity: number; unitOfMeasure: string };

export async function addToCart(cartHdrUid: number, items: CartItem[]) {
  return safeAction(() => getSiteActions().commerce.addToCart(cartHdrUid, items));
}

Which hooks need what

Hook Needs
useAugurOptions AugurHooksProvider (or AugurSiteProvider)
useCartPricing The provider, and a customer from auth or site.defaults.customerId
useCartActions A QueryClientProvider; its server actions come in as callbacks
useFormatPrice The provider; reads site.formatting when given
useStock The provider

Upgrading

Each augur package releases on its own, so upgrade any of them on its own:

  1. Set the new version in package.json by hand.
  2. Run pnpm install. Don't use pnpm update or pnpm add -L: they re-resolve shared dependencies such as React and can leave two copies.
  3. Check pnpm ls react shows one copy.
  4. Run pnpm augur-doctor . and your quality gate.

Shared helpers

A helper every site needs goes into a package. A transform only some sites need lands in augur-utils once at least two sites want it; until then it stays in the site.