Mutations
Every write builder on the query-options proxy, the variables its mutate() call takes, and what it resolves to.
Each builder takes no arguments. It returns mutationOptions(), and the
variables below go to mutate().
Collections
| Builder | mutate(variables) | Resolves to |
|---|---|---|
create() | the create input | the new row |
update() | { id, data } | the updated row |
delete() | { id } | { success, data } |
restore() | { id } | the restored row |
purgeById() | { id } | { success: true } |
updateMany() | { where, data } | the rows it wrote |
updateBatch() | { updates: [{ id, data }] } | the rows it wrote |
deleteMany() | { where } | { success, count } |
revertToVersion() | { id, version } or { id, versionId } | the reverted row |
transitionStage() | { id, stage, scheduledAt? } | the row at that stage |
const create = useMutation(q.collections.posts.create());
create.mutate({ title: "Hello", slug: "hello" });
const update = useMutation(q.collections.posts.update());
update.mutate({ id: postId, data: { published: true } });
const remove = useMutation(q.collections.posts.delete());
remove.mutate({ id: postId });The create input is your insert shape, nested relation writes included. The
data in update() is the patch, not the whole row.
By id, or by filter
update() and delete() are the single-record calls. They take an id and
nothing else identifies the row. To write by filter, reach for the bulk pair.
const publishAll = useMutation(q.collections.posts.updateMany());
publishAll.mutate({ where: { published: false }, data: { published: true } });where is the same typed filter find() takes, so a column typo does not
compile. updateMany() resolves to the array of rows it wrote, and
deleteMany() resolves to { success, count }.
updateBatch() is the one that writes different data per row. It takes
{ updates: [{ id, data }] } and returns the written rows.
Soft delete
restore() undoes a soft delete and gives you the row back. purgeById()
removes an already-deleted row for good, and it is a separate builder because
the server authorizes it separately. It rejects rows that are still active.
`purgeById` exists only on soft-delete collections
The builder is on the type when the collection sets softDelete: true. On a
hard-delete collection it is not there, and reaching for it does not compile.
Versions and stages
revertToVersion() takes { id, version } or { id, versionId } and writes
that past state back. transitionStage() moves one row to another workflow
stage without touching its data. A future scheduledAt schedules the move
instead of running it now.
Both need the feature turned on in .options(). See
Options.
Globals
The global builders forward both client arguments, so the variables object carries the payload and the call options side by side. That means one more level of nesting than the collection builders have.
| Builder | mutate(variables) | Resolves to |
|---|---|---|
update() | { data, options? } | the updated global |
revertToVersion() | { params, options? } | the reverted global |
transitionStage() | { params, options? } | the global at that stage |
const save = useMutation(q.globals.siteSettings.update());
save.mutate({ data: { siteName: "QUESTPIE" } });
const revert = useMutation(q.globals.siteSettings.revertToVersion());
revert.mutate({ params: { version: 3 } });The outer key is the envelope, not a field
mutate({ siteName: "X" }) does not work. The fields go under data, and
the version or stage arguments go under params. The optional options is
the client's second argument, so { locale } and { with } belong there.
Optimistic concurrency
Turn on optimisticConcurrency and the row version stops being optional. The
type demands it, so a forgotten expectedRevision fails to compile.
| Builder | Where the version goes |
|---|---|
update, delete, restore | expectedRevision beside id |
purgeById | expectedRevision beside id |
revertToVersion, transitionStage | expectedRevision beside id |
updateMany, deleteMany | expectedRevisions: [{ id, expectedRevision }] |
updateBatch | expectedRevision on each entry |
a global update | { data: { data: fields, expectedRevision } } |
a global revertToVersion or transitionStage | expectedRevision inside params |
update.mutate({ id: postId, expectedRevision: 4, data: { title: "New" } });Every collection row is flat. The version sits beside id, in the same object
you pass to mutate().
The two global rows nest instead. The outer key is the mutation variable, and
the inner one is the client's own payload. That is why a global update shows
data twice, and it is not a typo. See
Optimistic concurrency.
Keys and invalidation
Mutation keys carry the operation but never the variables, so
['questpie', 'collections', 'posts', 'create', locale, stage] is the whole
key. Invalidate the reads yourself in onSuccess. See
Query keys.
const create = useMutation({
...q.collections.posts.create(),
onSuccess: () =>
queryClient.invalidateQueries({
queryKey: q.key(["collections", "posts"]),
}),
});Query keys
Every builder writes its own key. Knowing the four shapes is what lets you invalidate a whole collection, or exactly one route call, without guessing.
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.