authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-01-30 17:37:38-08:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-01-30 17:38:12-08:00
log9fe44b404d1cfae5e4751ac3ef020ef8da521b1a
tree8b51c37b5ffc230b053e5a5a9256b9ffab28c7b6
parent30b230b64fca127fd768443d19b768ab5a7f88a2
signaturebadge-check Signed by SSH key SHA256:xbd+BjjhyBfwk7GVoURf9Yx0gzDerHbvYv7SddNWmAs

feat: add runAsHeadlessPromise


4 files changed, 56 insertions(+), 1 deletions(-)

jsr.json+1-1
......@@ -1,6 +1,6 @@
11{
22 "name": "@clo/react-mutation",
3 "version": "1.0.0",
3 "version": "1.1.0",
44 "exports": {
55 ".": "./src/mod.ts",
66 "./tanstack-query.ts": "./src/tanstack-query.ts",
readme.changes.md created+5
......@@ -0,0 +1,5 @@
1# notable changes in React Mutation
2
3## v1.1
4
5- Add `runAsHeadlessPromise`. The function name is intentionally long to avoid using it, please use `runWithOptions` instead.
src/mutation.ts+40
......@@ -256,6 +256,46 @@ export class BlockingMutation<
256256 this.runWithOptions(...args, {});
257257 }
258258
259 /**
260 * Using this in any situation is likely incorrect. Use {@linkcode runWithOptions}
261 * to handle success and error.
262 *
263 * By using this, you must handle the success and error conditions of the
264 * promise, or else the user will never see the result on screen. If the
265 * mutation has snapshots, be aware that cancelled mutations are implemented
266 * with promises that never resolve.
267 */
268 runAsHeadlessPromise(...array: [...Args, RunOptions<Result>]): Promise<Result> {
269 if (!this.#client.enabled) {
270 throw new Error(
271 "MutationClient was passed enabled: false. Are you trying to perform a mutation from SSR?",
272 );
273 }
274
275 const args = array.slice() as Args;
276 const { onSuccess, onSuccessDataOnly, onError, onSettled, onRestore } = args
277 .pop() as RunOptions<Result>;
278 const promise = this.#runWithOptions(args, onRestore, true);
279 return promise.then((result) => {
280 // Call user handlers
281 onSuccess?.(result);
282 onSuccessDataOnly?.(result);
283 onSettled?.({ status: "success", result });
284 return result;
285 }).catch((caught: unknown) => {
286 // Extract error and description if this is a wrapped mutation error
287 const isMutationError = (caught as MutationError)?.__mutationError === true;
288 const error = isMutationError ? (caught as MutationError).error : caught;
289 const description = isMutationError ? (caught as MutationError).description : this.describe(...args);
290
291 // Call user handlers with the unwrapped error
292 onError?.(error);
293 onSettled?.({ status: "error", error });
294
295 throw caught;
296 });
297 }
298
259299 /** Calls the mutation with custom handlers that can suppress global handlers. */
260300 runWithOptions(...array: [...Args, RunOptions<Result>]): void {
261301 if (!this.#client.enabled) {
src/types.ts+10
......@@ -5,6 +5,16 @@ export interface Mutation<Args extends unknown[], Result> {
55 run(...args: Args): void;
66 /** Calls the mutation with custom handlers that can suppress global handlers. */
77 runWithOptions(...args: [...args: Args, options: RunOptions<Result>]): void;
8 /**
9 * Using this in any situation is likely incorrect, even internally.
10 * Use {@linkcode runWithOptions} to handle success and error.
11 *
12 * By using this, you must handle the success and error conditions of the
13 * promise, or else the user will never see the result on screen. If the
14 * mutation has snapshots, be aware that cancelled mutations are implemented
15 * with promises that never resolve.
16 */
17 runAsHeadlessPromise(...array: [...Args, RunOptions<Result>]): Promise<Result>;
818
919 /** Returns the concurrency key used for a given set of arguments */
1020 key(args: Args): string;