diff --git a/jsr.json b/jsr.json index 2b3b61224f8b597f7aedee0ec7156a02cb895de0..f9696985231dbfda1713548dcc4cd9617186b54a 100644 --- a/jsr.json +++ b/jsr.json @@ -1,6 +1,6 @@ { "name": "@clo/react-mutation", - "version": "1.0.0", + "version": "1.1.0", "exports": { ".": "./src/mod.ts", "./tanstack-query.ts": "./src/tanstack-query.ts", diff --git a/readme.changes.md b/readme.changes.md new file mode 100644 index 0000000000000000000000000000000000000000..0040a81d93071d486d759f82c41565daa7484d12 --- /dev/null +++ b/readme.changes.md @@ -0,0 +1,5 @@ +# notable changes in React Mutation + +## v1.1 + +- Add `runAsHeadlessPromise`. The function name is intentionally long to avoid using it, please use `runWithOptions` instead. diff --git a/src/mutation.ts b/src/mutation.ts index b6e8b6f8bebd9cd2304a773cea9bde8a052ae30a..ff43c18ed7c4a4b758b37fbdf6a1d6c2cbf9b98d 100644 --- a/src/mutation.ts +++ b/src/mutation.ts @@ -256,6 +256,46 @@ export class BlockingMutation< this.runWithOptions(...args, {}); } + /** + * Using this in any situation is likely incorrect. Use {@linkcode runWithOptions} + * to handle success and error. + * + * By using this, you must handle the success and error conditions of the + * promise, or else the user will never see the result on screen. If the + * mutation has snapshots, be aware that cancelled mutations are implemented + * with promises that never resolve. + */ + runAsHeadlessPromise(...array: [...Args, RunOptions]): Promise { + if (!this.#client.enabled) { + throw new Error( + "MutationClient was passed enabled: false. Are you trying to perform a mutation from SSR?", + ); + } + + const args = array.slice() as Args; + const { onSuccess, onSuccessDataOnly, onError, onSettled, onRestore } = args + .pop() as RunOptions; + const promise = this.#runWithOptions(args, onRestore, true); + return promise.then((result) => { + // Call user handlers + onSuccess?.(result); + onSuccessDataOnly?.(result); + onSettled?.({ status: "success", result }); + return result; + }).catch((caught: unknown) => { + // Extract error and description if this is a wrapped mutation error + const isMutationError = (caught as MutationError)?.__mutationError === true; + const error = isMutationError ? (caught as MutationError).error : caught; + const description = isMutationError ? (caught as MutationError).description : this.describe(...args); + + // Call user handlers with the unwrapped error + onError?.(error); + onSettled?.({ status: "error", error }); + + throw caught; + }); + } + /** Calls the mutation with custom handlers that can suppress global handlers. */ runWithOptions(...array: [...Args, RunOptions]): void { if (!this.#client.enabled) { diff --git a/src/types.ts b/src/types.ts index 5126cc54c051569b9b5cdd53132def816d7d400a..d4dc0f2d8e1c916c44f60f5b55727fd43745aa31 100644 --- a/src/types.ts +++ b/src/types.ts @@ -5,6 +5,16 @@ export interface Mutation { run(...args: Args): void; /** Calls the mutation with custom handlers that can suppress global handlers. */ runWithOptions(...args: [...args: Args, options: RunOptions]): void; + /** + * Using this in any situation is likely incorrect, even internally. + * Use {@linkcode runWithOptions} to handle success and error. + * + * By using this, you must handle the success and error conditions of the + * promise, or else the user will never see the result on screen. If the + * mutation has snapshots, be aware that cancelled mutations are implemented + * with promises that never resolve. + */ + runAsHeadlessPromise(...array: [...Args, RunOptions]): Promise; /** Returns the concurrency key used for a given set of arguments */ key(args: Args): string;