Configuration
The optional second argument to createQuestpieQueryOptions sets the key prefix, the error mapping and a pinned locale or stage for the whole proxy.
| Option | Type | Default | Effect |
|---|---|---|---|
keyPrefix | QueryKey | ['questpie'] | Goes on the front of every key |
errorMap | (error: unknown) => unknown | coerces to Error | Wraps every fetcher and mutator |
locale | string | undefined | Pins a locale. See below |
stage | string | undefined | Pins a workflow stage. See below |
Pass nothing and you get all four defaults. That is what the starters do.
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.
| Builder | What it does with them |
|---|---|
collections.find / count / findOne | key only, the call does not receive them |
collections.findVersions | passed as the call's second argument |
| every collection write | passed as the call's second argument |
globals.get | key only, the call does not receive them |
globals.update / revertToVersion / findVersions | merged into the call's options |
globals.transitionStage | locale merged, stage not |
| routes and channels | routes 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.
Live queries
Three reads take a second argument. Set realtime to true there and the same builder streams server snapshots into the same useQuery, instead of fetching once.
Row deltas
A snapshot resends the whole page on every change. A delta sends the one row that moved. Most apps never need to ask for it, and this page says why.