QUESTPIE
ClientTanstack query

Mutations

Every write builder on the query-options proxy, the variables its mutate() call takes, and what it resolves to.

View markdown

Each builder takes no arguments. It returns mutationOptions(), and the variables below go to mutate().

Collections

Buildermutate(variables)Resolves to
create()the create inputthe 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.

Buildermutate(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.

BuilderWhere the version goes
update, delete, restoreexpectedRevision beside id
purgeByIdexpectedRevision beside id
revertToVersion, transitionStageexpectedRevision beside id
updateMany, deleteManyexpectedRevisions: [{ id, expectedRevision }]
updateBatchexpectedRevision on each entry
a global update{ data: { data: fields, expectedRevision } }
a global revertToVersion or transitionStageexpectedRevision 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"]),
		}),
});

On this page