QUESTPIE
ClientTanstack query

Configuration

The optional second argument to createQuestpieQueryOptions sets the key prefix, the error mapping and a pinned locale or stage for the whole proxy.

View markdown
OptionTypeDefaultEffect
keyPrefixQueryKey['questpie']Goes on the front of every key
errorMap(error: unknown) => unknowncoerces to ErrorWraps every fetcher and mutator
localestringundefinedPins a locale. See below
stagestringundefinedPins a workflow stage. See below

Pass nothing and you get all four defaults. That is what the starters do.

src/lib/query.ts
export const q = createQuestpieQueryOptions(client, {
	keyPrefix: ["cms"],
	locale: "sk",
});

keyPrefix

Every query and mutation key starts with it. Pass [] and keys start at 'collections' with nothing in front. q.key(parts) uses the same prefix, so invalidation keeps working whatever you set. See Query keys.

errorMap

The default coerces a non-Error throw into an Error. An Error passes through, a string becomes new Error(string), and anything else becomes new Error("Unknown error").

It wraps every fetcher and every mutator on collections, globals, routes and q.custom. The stream behind a live query goes through it too. Whatever it returns is what your onError and your error boundary receive. Replace it once and the whole app agrees on an error shape.

Channels are the exception. subscription() and presence() build their queryFn straight from the channel iterator, so an error there arrives unmapped.

export const q = createQuestpieQueryOptions(client, {
	errorMap: (error) => {
		if (error instanceof MyApiError) return error;
		return new Error("Request failed", { cause: error });
	},
});

A non-retryable realtime error stays non-retryable

Live queries re-mark the mapped error when your map drops the flag, so a rejected topic still fails fast. Return the error you were given and nothing is re-marked.

locale and stage

These two are pinned for the whole proxy. Both always land in the locale and stage slots of every collection and global key. So two proxies with different locales never share a cache entry.

Where they go beyond the key depends on the builder.

BuilderWhat it does with them
collections.find / count / findOnekey only, the call does not receive them
collections.findVersionspassed as the call's second argument
every collection writepassed as the call's second argument
globals.getkey only, the call does not receive them
globals.update / revertToVersion / findVersionsmerged into the call's options
globals.transitionStagelocale merged, stage not
routes and channelsroutes key on locale only, channels on neither

A pinned locale does not translate a plain read

find, count, findOne and globals.get send only the options you pass. Put locale in the read options when you want a localized read. The config value still splits the cache, which is what you want.

The merge on the global writes happens after your own options, so a pinned value wins over a per-call one. If two parts of your UI need different locales, build a second proxy rather than fighting the override.

Custom queries and mutations

q.custom wraps a function of yours with the same prefix and the same error map. Use it for a third-party endpoint or a composed call.

const opts = q.custom.query<DashboardData>({
	key: ["dashboard", "summary"],
	queryFn: () => fetchDashboard(),
});

const mut = q.custom.mutation<{ id: string }, void>({
	key: ["dashboard", "refresh"],
	mutationFn: (vars) => refreshDashboard(vars.id),
});

You give the suffix and the factory prepends keyPrefix. No locale or stage slots are interleaved, so the suffix you write is the whole rest of the key.

Exported types and helpers

import type {
	QuestpieQueryOptionsProxy, // the shape of `q`
	QuestpieQueryOptionsConfig, // this page's argument
	QuestpieQueryErrorMap, // (error: unknown) => unknown
	RealtimeQueryConfig, // the live second argument
} from "@questpie/tanstack-query";

The package re-exports four React Query types: QueryKey, DefaultError, UseQueryOptions and UseMutationOptions. It also re-exports five realtime helpers from questpie/client. Those are buildCollectionTopic, buildGlobalTopic, applyRealtimeFindEvent, applyRealtimeScalarEvent and applyRealtimeSingleEvent, plus the RealtimeAPI and TopicConfig types.

You rarely name QuestpieQueryOptionsProxy yourself. Export typeof q from src/lib/query.ts instead, which is what the starters do.

The proxies do not enumerate

q.collections, q.globals, q.channels and q.routes are Proxy objects with a get trap. Object.keys(), spreading and for…in give you nothing. Reach for a name. q.custom and q.key are plain.

On this page