diff --git a/dprint.jsonc b/dprint.jsonc
new file mode 100644
index 0000000000000000000000000000000000000000..38cbd41ba9bbee4341adcef11088b7d05940d62d
--- /dev/null
+++ b/dprint.jsonc
@@ -0,0 +1,13 @@
+{
+ "excludes": [
+ "**/node_modules",
+ "**/*-lock.json",
+ ],
+ "plugins": [
+ "https://plugins.dprint.dev/typescript-0.95.13.wasm",
+ "https://plugins.dprint.dev/json-0.21.1.wasm",
+ "https://plugins.dprint.dev/markdown-0.20.0.wasm",
+ "https://plugins.dprint.dev/g-plane/malva-v0.15.2.wasm",
+ "https://plugins.dprint.dev/g-plane/markup_fmt-v0.25.3.wasm",
+ ],
+}
diff --git a/example/src/App.tsx b/example/src/App.tsx
index fe1b0ded221f42d302f84168ea70719870cc3038..8103ab197a7d227fa1e1bc2f5b63db26fe5208a6 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -1,10 +1,5 @@
+import { createMutationButton, MutationClient, queryClientOptimisticHelpers, useMutate } from "@clo/react-mutation";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
-import {
- createMutationButton,
- MutationClient,
- queryClientOptimisticHelpers,
- useMutate,
-} from "@clo/react-mutation";
import { queryOptions as queryOptions } from "@tanstack/react-query";
import { useSuspenseQuery } from "@tanstack/react-query";
import { QueryKeyAndFn } from "../../src/tanstack-query.ts";
diff --git a/jsr.json b/jsr.json
index 1b4e5420fc5a0cd60bbf78e870097d1eb6a22ca7..1b9ddcaf6e684aaa2f420e292d895f2c049ffb62 100644
--- a/jsr.json
+++ b/jsr.json
@@ -1,6 +1,6 @@
{
"name": "@clo/react-mutation",
- "version": "1.0.0-beta.10",
+ "version": "1.0.0-beta.11",
"exports": {
".": "./src/mod.ts",
"./tanstack-query.ts": "./src/tanstack-query.ts",
diff --git a/readme.md b/readme.md
index acb7ca8fc939ea5c74aff3b3081fbfea18446941..f5a9618023d53cd3e5dd5214e04b38c86886a2fe 100644
--- a/readme.md
+++ b/readme.md
@@ -10,7 +10,7 @@ their mutation story falls apart, is confusing, and misses a few obvious
features. Additionally, coworkers using AI agents continue to propagate bad
patterns and verbose code that is hard to review.
-The primary gains React Mutation provides are
+The primary gains React Mutation provides are:
- **Automatic result handling**. If a `useMutate` hook does not observe
`isError`, unhandled errors will be propagated to a global handler, which can
@@ -19,17 +19,20 @@ The primary gains React Mutation provides are
UI without worrying about bugged error states. The
[built in helpers for React Query](#react-query-optimistic-helpers) shows
this power in more detail.
-- Easy debouncing and batching utilities.
+- Extra treats such as debouncing (toggle button spam) and no-op filters (auto-save text inputs).
## Setup
React Mutation starts with a `MutationClient`, which shares global state for an application.
```ts
-import { QueryClient } from "@tanstack/react-query";
-import { MutationClient } from "@clo/react-mutation";
-import { queryClientOptimisticHelpers, boundQueryClientGet } from "@clo/react-mutation";
import { showToastUI } from "...";
+import { MutationClient } from "@clo/react-mutation";
+import {
+ boundQueryClientGet,
+ queryClientOptimisticHelpers,
+} from "@clo/react-mutation";
+import { QueryClient } from "@tanstack/react-query";
const queryClient = new QueryClient();
export const mutations = new MutationClient({
@@ -40,12 +43,12 @@ export const mutations = new MutationClient({
// (btw, the correctly typed version of `get` is exported as `boundQueryClientGet(client)`)
get: (k: QueryKey) => client.getQueryData(k),
},
-
+
// Optimistic helpers are a second type of context, only available within
// optimistic update functions. These functions are bound to each mutation,
// which means they can handle automatic rollbacks and query invalidation.
getOptimisticHelpers: queryClientOptimisticHelpers(queryClient),
-
+
// When call sites do not opt into handling errors, or a pending
// mutation hook is unmounted, errors are sent to this function.
// An example is to bind this to global a UI toast.
@@ -53,7 +56,7 @@ export const mutations = new MutationClient({
showToastUI("error", userFriendlyErrorMessage);
console.error(error); // or send to telemetry
},
-
+
// Similarly, when call sites do opt into handling success.
reportSuccess(userFriendlySuccessMessage: string) {
showToastUI("success", userFriendlyErrorMessage);
@@ -234,19 +237,18 @@ anything, `snapshot` can be used to detect no-op mutations.
```tsx
const mutUpdateField = mutations.define({
- async mutate(id: string, value: string) { /* mutation */ },
-
+ async mutate(id: string, value: string) {/* mutation */},
+
optimistic({ args: [id, value], helpers }) {
helpers.objSet(queryItem(id), ["value"], value);
},
-
+
// called once before `optimistic` and once after. if the values are equal,
// then the mutation is cancelled (won't call `onSuccess`, but will `onSettled`)
// (defaulting to a json-based deep equal check, customize in MutationClient)
snapshot({ args: [id], get }) {
return get(queryItem(id))?.value;
- }
-
+ },
// (...describe and optionally debounce stuff...)
});
```
@@ -328,56 +330,19 @@ It can now be used for easy mutations:
```tsx
<>
{/* Static Arguments */}
- Follow
+
+ Follow
+
{/* Dynamic Arguments */}
- {
- if (Math.random() < 0.5) e.preventDefault(); // prevent the submit
- return [userId, messageContent];
- }}>Send Message
->
-```
-
-
-
-## Batched Mutations
-
-This is an advanced feature. Complete Documentation is pending. It is not recommended to use this.
-
-Each call to the mutation applies new optimistic state on top of the previous,
-and after a debounce / throttle, the new optimistic state is committed to the
-API. UI never shows a pending state for these. This works great for toggle buttons
-and any other state where you'd like to define an optimistic state
-
-In many places, similar behavior can be achieved with standard mutations and its
-`debounceMs` field.
-
-```tsx
-const mutToggleFollow = mutations.defineBatched({
- // Think of your mutator in terms of how it applies optimistic state.
- optimistic({ helpers }, userId: string) {
- helpers.objToggle(queryUser(userId), ["following"]);
- },
- // A value is snapshotted *before* calling `optimistic`, and then again after
- // the timer. If the snapshots differ, then `commit` function is called.
- getValue: ({ get }) => get(queryUser(id))?.following,
-
- // Split different `id`s into their own batches.
- key: ({ args: [id] }) => id,
-
- // Commit the result to the backend.
- // Here, you can observe the two snapshotted values and form an API request.
- async commit({ initial, current, args: [id] }) {
- const response = await fetch(`/items/${id}`, {
- method: "patch",
- body: JSON.stringify({ title: current }),
- });
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
- },
-
- describe: ({ get, args: [id] }) =>
- `Rename '${get(queryItem())?.title ?? 'Unknown Item'}'`,
- describeResult: ({ get, args: [id] }) =>
- `Renamed '${get(queryItem(id))?.title ?? 'Unknown Item'}'`,
-});
+ {
+ if (Math.random() < 0.5) e.preventDefault(); // prevent the submit
+ return [userId, messageContent];
+ }}
+ >
+ Send Message
+
+>;
```
diff --git a/src/blocking.ts b/src/blocking.ts
index 0ca7e409325dcfe7cb8d408d1360e4cb68856f76..a125e8a6b6e1108d89691b03726a8155d3066eec 100644
--- a/src/blocking.ts
+++ b/src/blocking.ts
@@ -1,7 +1,7 @@
+import { message as errMessage } from "@clo/lib/error.ts";
import type { MutationClient, MutationClientFromConfig } from "./client.ts";
import type { MutationClientConfig } from "./client.ts";
import type { Mutation, MutationEvent, RunOptions } from "./types.ts";
-import { message as errMessage } from "@clo/lib/error.ts";
/**
* Argument to `defineBlocking`.
@@ -138,8 +138,8 @@ export class BlockingMutation<
}
key(args: Args) {
- const k = this.#options.key?.({ ...this.#client.context, args }) ??
- "shared";
+ const k = this.#options.key?.({ ...this.#client.context, args })
+ ?? "shared";
return JSON.stringify(k);
}
@@ -263,9 +263,7 @@ export class BlockingMutation<
// Call global handler unless suppressed
if (!suppressGlobalError) {
- const message = `Failed to ${this.describe(...args)}: ${
- errMessage(error)
- }`;
+ const message = `Failed to ${this.describe(...args)}: ${errMessage(error)}`;
this.#client.reportError(message, error);
}
});
@@ -354,8 +352,7 @@ export class BlockingMutation<
expired = true;
let next;
while (
- next =
- channel.rollbacks.splice(channel.rollbacks.length - rollbacks, 1)[0]
+ next = channel.rollbacks.splice(channel.rollbacks.length - rollbacks, 1)[0]
) {
next();
}
@@ -408,9 +405,7 @@ export class BlockingMutation<
// Report any errors from refetch or callbacks
results.forEach((result) => {
if (result.status === "rejected") {
- const message = `Failed to refetch after ${
- this.describe(...args)
- }: ${errMessage(result.reason)}`;
+ const message = `Failed to refetch after ${this.describe(...args)}: ${errMessage(result.reason)}`;
this.#client.reportError(message, result.reason);
}
});
@@ -447,9 +442,7 @@ export class BlockingMutation<
// Report any errors from refetch or callbacks
results.forEach((result) => {
if (result.status === "rejected") {
- const message = `Failed to refetch after ${
- this.describe(...args)
- }: ${errMessage(result.reason)}`;
+ const message = `Failed to refetch after ${this.describe(...args)}: ${errMessage(result.reason)}`;
this.#client.reportError(message, result.reason);
}
});
@@ -533,8 +526,7 @@ export class BlockingMutation<
// Roll back the rollbacks we just added
let next;
while (
- next =
- channel.rollbacks.splice(channel.rollbacks.length - rollbacks, 1)[0]
+ next = channel.rollbacks.splice(channel.rollbacks.length - rollbacks, 1)[0]
) {
next();
}
@@ -611,13 +603,12 @@ export class BlockingMutation<
return;
}
- const { args, rollbackCount, pending, onSuccess } =
- channel.pendingDebounced;
+ const { args, rollbackCount, pending, onSuccess } = channel.pendingDebounced;
channel.pendingDebounced = null;
// Check if there are any listeners at time of enqueue
const hasListeners = channel.listeners.size > 0;
-
+
// Create wrapper resolve/reject that resolves ALL pending promises
const {
promise: wrapperPromise,
@@ -630,7 +621,7 @@ export class BlockingMutation<
(result) => {
// Resolve all pending promises
pending.forEach((p) => p.resolve(result));
-
+
// Check if there are any listeners at execution time
const hasListeners = channel.listeners.size > 0;
if (!hasListeners) {
@@ -643,13 +634,11 @@ export class BlockingMutation<
(error) => {
// Reject all pending promises
pending.forEach((p) => p.reject(error));
-
+
// Check if there are any listeners at execution time
const hasListeners = channel.listeners.size > 0;
if (!hasListeners) {
- const message = `Failed to ${this.describe(...args)}: ${
- errMessage(error)
- }`;
+ const message = `Failed to ${this.describe(...args)}: ${errMessage(error)}`;
this.#client.reportError(message, error);
}
},
diff --git a/src/client.ts b/src/client.ts
index 48a6dbb1a099bb3833da6784c8d328f1c54fadee..968f7a26909956b60736fdd0666b79bd9607b60d 100644
--- a/src/client.ts
+++ b/src/client.ts
@@ -1,8 +1,5 @@
-import {
- DebouncedMutation,
- type DebouncedMutationOptions,
-} from "./debounced.ts";
import { BlockingMutation, type MutationOptions } from "./blocking.ts";
+import { DebouncedMutation, type DebouncedMutationOptions } from "./debounced.ts";
import type { Mutation } from "./types.ts";
export interface MutationClientConfig {
@@ -10,11 +7,12 @@ export interface MutationClientConfig {
optimisticHelpers: {};
}
-export type MutationClientFromConfig =
- MutationClient;
+export type MutationClientFromConfig = MutationClient<
+ Config["context"],
+ Config["optimisticHelpers"]
+>;
-const defaultDeepEquals = (a: unknown, b: unknown): boolean =>
- JSON.stringify(a) === JSON.stringify(b);
+const defaultDeepEquals = (a: unknown, b: unknown): boolean => JSON.stringify(a) === JSON.stringify(b);
export interface MutationClientOptions<
Context extends object,
diff --git a/src/debounced.ts b/src/debounced.ts
index 22f3e1ecdc048683c795798f338edda11ae4a3b4..e2dfd4fa493d646607afe7bbf2934ef76740773d 100644
--- a/src/debounced.ts
+++ b/src/debounced.ts
@@ -1,7 +1,7 @@
+import { message as errMessage } from "@clo/lib/error.ts";
import type { MutationClient, MutationClientFromConfig } from "./client.ts";
import type { MutationClientConfig } from "./client.ts";
import type { Mutation, MutationEvent, RunOptions } from "./types.ts";
-import { message as errMessage } from "@clo/lib/error.ts";
export interface DebouncedMutationOptions<
Args extends unknown[],
@@ -288,9 +288,7 @@ export class DebouncedMutation<
);
}
this.#runAndReturn(args, true, undefined).catch((error) => {
- const message = `Failed to ${this.describe(...args)}: ${
- errMessage(error)
- }`;
+ const message = `Failed to ${this.describe(...args)}: ${errMessage(error)}`;
this.#client.reportError(message, error);
});
}
@@ -321,9 +319,7 @@ export class DebouncedMutation<
// Call global error handler unless suppressed
if (!suppressGlobalError) {
- const message = `Failed to ${this.describe(...args)}: ${
- errMessage(error)
- }`;
+ const message = `Failed to ${this.describe(...args)}: ${errMessage(error)}`;
this.#client.reportError(message, error);
}
});
@@ -525,9 +521,7 @@ export class DebouncedMutation<
pendingItems.forEach(({ resolve }) => resolve(result));
// Report success globally if any of the pending items requested it
- const shouldReportSuccess = pendingItems.some((item) =>
- item.reportSuccessGlobally
- );
+ const shouldReportSuccess = pendingItems.some((item) => item.reportSuccessGlobally);
if (shouldReportSuccess) {
const message = this.#describeResult(
firstArgs,
@@ -557,9 +551,7 @@ export class DebouncedMutation<
// Report any errors from refetch or callbacks
results.forEach((result) => {
if (result.status === "rejected") {
- const message = `Failed to refetch after ${
- this.describe(...firstArgs)
- }: ${errMessage(result.reason)}`;
+ const message = `Failed to refetch after ${this.describe(...firstArgs)}: ${errMessage(result.reason)}`;
this.#client.reportError(message, result.reason);
}
});
@@ -596,9 +588,7 @@ export class DebouncedMutation<
// Report any errors from refetch or callbacks
results.forEach((result) => {
if (result.status === "rejected") {
- const message = `Failed to refetch after ${
- this.describe(...firstArgs)
- }: ${errMessage(result.reason)}`;
+ const message = `Failed to refetch after ${this.describe(...firstArgs)}: ${errMessage(result.reason)}`;
this.#client.reportError(message, result.reason);
}
});
diff --git a/src/mod.ts b/src/mod.ts
index 4a58684c55e252c62e57d3c11cec66e3c1530e7d..8ab5314ace755c914048145be1b57cbab8cb8f2a 100644
--- a/src/mod.ts
+++ b/src/mod.ts
@@ -1,16 +1,11 @@
export type { MutationOptions, OptimisticContext } from "./blocking.ts";
-export type {
- DebouncedCommitContext,
- DebouncedMutationOptions,
- DebouncedOptimisticContext,
-} from "./debounced.ts";
export {
MutationClient,
type MutationClientConfig,
type MutationClientFromConfig,
type MutationClientOptions,
} from "./client.ts";
-export type { Mutation, MutationEvent } from "./types.ts";
+export type { DebouncedCommitContext, DebouncedMutationOptions, DebouncedOptimisticContext } from "./debounced.ts";
export {
createMutationButton,
type MutationButtonComponent,
@@ -22,3 +17,4 @@ export {
type UseMutateResultBase,
type UseMutateSuccess,
} from "./react.ts";
+export type { Mutation, MutationEvent } from "./types.ts";
diff --git a/src/object-path.ts b/src/object-path.ts
index 436308f37559394d375551d394ca0d79ef3ccd9d..addfc355733c84962998c4cda3204d18912ba7a8 100644
--- a/src/object-path.ts
+++ b/src/object-path.ts
@@ -1,13 +1,12 @@
-export type AllObjectPaths = T extends
- ReadonlyArray ? [] | [number, ...AllObjectPaths]
+export type AllObjectPaths = T extends ReadonlyArray ? [] | [number, ...AllObjectPaths]
: T extends object ?
| {
[K in keyof T]-?: [K, ...AllObjectPaths];
}[keyof T]
| []
: [];
-export type GetObjectPath = P extends
- [infer K extends keyof T, ...infer Rest] ? GetObjectPath
+export type GetObjectPath = P extends [infer K extends keyof T, ...infer Rest]
+ ? GetObjectPath
: T;
/** Get an object path property */
diff --git a/src/react.ts b/src/react.ts
index aaf73159b18f7e394c43ea52bd7467449c190c33..fa7e10bf3f78745978d687e9008f4162d118a9fb 100644
--- a/src/react.ts
+++ b/src/react.ts
@@ -1,3 +1,4 @@
+import { message as errMessage } from "@clo/lib/error.ts";
import {
type FC,
type MouseEvent,
@@ -7,9 +8,8 @@ import {
useEffect,
useState,
} from "react";
-import { message as errMessage } from "@clo/lib/error.ts";
-import type { Mutation, RunOptions } from "./types.ts";
import { jsx } from "react/jsx-runtime";
+import type { Mutation, RunOptions } from "./types.ts";
/**
* Subscribe to a mutation's status, as well as accessing a local `run` method.
@@ -170,9 +170,7 @@ class Observer {
if (!error) return undefined;
const mutation = this.mutation;
if (!mutation || !this.state.args) return errMessage(error);
- return `Failed to ${mutation.describe(...this.state.args)}: ${
- errMessage(error)
- }`;
+ return `Failed to ${mutation.describe(...this.state.args)}: ${errMessage(error)}`;
}
run(...args: Args) {
@@ -212,8 +210,8 @@ class Observer {
isPending: status === "mutating" || status === "refetching",
isSuccess: hasResult && !hasError,
isError: hasError,
- isOptimisticData: status === "waiting" || status === "mutating" ||
- status === "refetching",
+ isOptimisticData: status === "waiting" || status === "mutating"
+ || status === "refetching",
args: hasError || hasResult ? undefined : this.state.args,
});
},
@@ -222,10 +220,10 @@ class Observer {
// Use global error/success handling if this usage of the hook doesn't check for
// errors or success. This makes it act pretty awesome in terms of defaults.
// You don't have to worry about result UI, they'll surface exactly once.
- const watchesError = this.watched.has("isError") ||
- this.watched.has("error") || this.watched.has("errorMessage");
- const watchesSuccess = this.watched.has("isSuccess") ||
- this.watched.has("result");
+ const watchesError = this.watched.has("isError")
+ || this.watched.has("error") || this.watched.has("errorMessage");
+ const watchesSuccess = this.watched.has("isSuccess")
+ || this.watched.has("result");
const promise = mutation.runAsPromise(...args)
.then((result) => {
if (!watchesSuccess && mutation.describeResult) {
@@ -237,9 +235,7 @@ class Observer {
});
promise.catch((err) => {
if (!watchesError) {
- const message = `Failed to ${mutation.describe(...args)}: ${
- errMessage(err)
- }`;
+ const message = `Failed to ${mutation.describe(...args)}: ${errMessage(err)}`;
mutation.client.reportError(message, err);
}
});
@@ -289,8 +285,8 @@ class Observer {
isPending: status === "mutating" || status === "refetching",
isSuccess: hasResult && !hasError,
isError: hasError,
- isOptimisticData: status === "waiting" || status === "mutating" ||
- status === "refetching",
+ isOptimisticData: status === "waiting" || status === "mutating"
+ || status === "refetching",
args: hasError || hasResult ? undefined : this.state.args,
});
},
@@ -429,9 +425,7 @@ export function createMutationButton(
// back into unspecified generics.
.bind(null, Component) as MutationButtonComponent;
// react devtools loves display names
- bound.displayName = `MutationButton[${
- Component.displayName ?? Component.name
- }]`;
+ bound.displayName = `MutationButton[${Component.displayName ?? Component.name}]`;
return bound;
}
diff --git a/src/tanstack-query.ts b/src/tanstack-query.ts
index 1f7dfb4610661d1a316a5348c90b7b188dd760a3..953fe4a23db3c6a3162e6a1b4f1b46478270de47 100644
--- a/src/tanstack-query.ts
+++ b/src/tanstack-query.ts
@@ -1,16 +1,6 @@
-import {
- QueryClient,
- type QueryFunction,
- type QueryKey,
- type Updater,
-} from "@tanstack/react-query";
-import {
- type AllObjectPaths,
- type GetObjectPath,
- getPath,
- setPath,
-} from "./object-path.ts";
+import { QueryClient, type QueryFunction, type QueryKey, type Updater } from "@tanstack/react-query";
import type { OptimisticEvents } from "./client.ts";
+import { type AllObjectPaths, type GetObjectPath, getPath, setPath } from "./object-path.ts";
export type QueryKeyAndFn = {
queryKey: Key;
@@ -336,9 +326,7 @@ class TanstackQueryOptimisticHelpers {
const { value: original, exists } = getPath(prev, path);
if (!exists || !Array.isArray(original)) return;
- const newArray = original.filter((item, index) =>
- !removeFilter(item, index)
- );
+ const newArray = original.filter((item, index) => !removeFilter(item, index));
this.#set(
queryKey,
(obj) => obj ? setPath(obj, path, newArray as any) : obj,
@@ -405,9 +393,7 @@ class TanstackQueryOptimisticHelpers {
const { value: original, exists } = getPath(prev, path);
if (!exists || !Array.isArray(original)) return;
- const newArray = original.map((item, index) =>
- filter(item, index) ? update(item) : item
- );
+ const newArray = original.map((item, index) => filter(item, index) ? update(item) : item);
this.#set(
queryKey,
(obj) => obj ? setPath(obj, path, newArray as any) : obj,
@@ -549,9 +535,7 @@ class TanstackQueryOptimisticHelpers {
const prev = this.#get(queryKey);
if (!prev || !Array.isArray(prev)) return;
- const newArray = prev.map((item, index) =>
- (filter ? filter(item, index) : true) ? update(item) : item
- );
+ const newArray = prev.map((item, index) => (filter ? filter(item, index) : true) ? update(item) : item);
this.#set(queryKey, newArray);
this.#onRestore(() => {
// TODO: splice items back in case original changed
diff --git a/test/blocking-debounce-edge-cases.test.ts b/test/blocking-debounce-edge-cases.test.ts
index a96180342efaa6c473b20297b6de94fded74b0c5..9a43bff674fee7f84e104e4c4bc844a4cdee5ad8 100644
--- a/test/blocking-debounce-edge-cases.test.ts
+++ b/test/blocking-debounce-edge-cases.test.ts
@@ -1,7 +1,7 @@
import { assertEquals, assertRejects } from "@std/assert";
+import { test } from "vitest";
import { MutationClient } from "../src/client.ts";
import type { MutationEvent } from "../src/types.ts";
-import { test } from "vitest";
// Helper to create a test mutation client
function createTestClient() {
@@ -116,7 +116,7 @@ test("BlockingMutation - debounce: mutation still executes after all listeners u
// Mutation should have been called despite no listeners
assertEquals(mutateCallCount, 1);
-
+
// Global success handler should be called since no local listeners
assertEquals(successes.length, 1);
assertEquals(successes[0], "Successfully processed test");
diff --git a/test/blocking.test.ts b/test/blocking.test.ts
index 1ecd03f51d187513dc92504333a48bff16fd8d0d..ed57358ae7b242daa46573a3f2cac042e870fe23 100644
--- a/test/blocking.test.ts
+++ b/test/blocking.test.ts
@@ -1,7 +1,7 @@
import { assertEquals, assertRejects } from "@std/assert";
+import { test } from "vitest";
import { MutationClient } from "../src/client.ts";
import type { MutationEvent } from "../src/types.ts";
-import { test } from "vitest";
// Helper to create a test mutation client
function createTestClient() {
@@ -731,9 +731,7 @@ test("BlockingMutation - notifies error on mutation failure", async () => {
await assertRejects(() => mutation.runAsPromise("test"));
// Should have error event
- const errorEvents = tracker.events.filter((e) =>
- e.status === "mutating" && e.error
- );
+ const errorEvents = tracker.events.filter((e) => e.status === "mutating" && e.error);
assertEquals(errorEvents.length > 0, true);
assertEquals((errorEvents[0]?.error as Error).message, "mutation failed");
});
@@ -819,9 +817,7 @@ test("BlockingMutation - result is passed to notification on success", async ()
await delay(20);
// Should have refetching event with result
- const refetchingEvents = tracker.events.filter((e) =>
- e.status === "refetching"
- );
+ const refetchingEvents = tracker.events.filter((e) => e.status === "refetching");
assertEquals(refetchingEvents.length > 0, true);
assertEquals(refetchingEvents[0]?.result, "result-test");
});
diff --git a/test/debounced.test.ts b/test/debounced.test.ts
index 51789eedce8ed6d736b05a5a3a2a24afd4a6fe3e..c020d77833fc5b08917d2f97a7947dbfcdcfee0d 100644
--- a/test/debounced.test.ts
+++ b/test/debounced.test.ts
@@ -1,7 +1,7 @@
import { assertEquals, assertRejects } from "@std/assert";
+import { test } from "vitest";
import { MutationClient } from "../src/client.ts";
import type { MutationEvent } from "../src/types.ts";
-import { test } from "vitest";
// Shared test store for optimistic updates
const testStore = new Map();
diff --git a/test/object-path.test.ts b/test/object-path.test.ts
index e2bd46ddcd4eefbff795d499cc2afa969279d3a5..8fba1b09cf0f83214569b96ed87ed52d6e7ac90b 100644
--- a/test/object-path.test.ts
+++ b/test/object-path.test.ts
@@ -282,11 +282,11 @@ test("set - should share references for unchanged branches (structural sharing)"
};
const result = setPath(obj, ["address", "city"], "LA");
-
+
// Changed path should have new references
assertEquals(result === obj, false); // Root is new
assertEquals(result.address === obj.address, false); // Address is new
-
+
// Unchanged branches should share references
assertEquals(result.hobbies === obj.hobbies, true); // Same reference
assertEquals(result.nested === obj.nested, true); // Same reference
diff --git a/test/object-path.types.ts b/test/object-path.types.ts
index e6fa6989ee5a8615949999f224bf996ded32061e..0d272bf3834cce445a0abf151cd7c5e1d3bf8d66 100644
--- a/test/object-path.types.ts
+++ b/test/object-path.types.ts
@@ -7,9 +7,8 @@ import type { AllObjectPaths, GetObjectPath } from "../src/object-path.ts";
// Type testing utilities
type Expect = T;
-type Equal = (() => T extends X ? 1 : 2) extends
- () => T extends Y ? 1
- : 2 ? true
+type Equal = (() => T extends X ? 1 : 2) extends () => T extends Y ? 1
+ : 2 ? true
: false;
type NotEqual = Equal extends true ? false : true;
type IsAny = 0 extends 1 & T ? true : false;
@@ -325,8 +324,7 @@ objSetCount(["count"], (n) => n + 1);
type ArrayPushSignature<
Data extends object,
Path extends AllObjectPaths,
-> = GetObjectPath extends readonly (infer T)[]
- ? (path: Path, ...items: T[]) => void
+> = GetObjectPath extends readonly (infer T)[] ? (path: Path, ...items: T[]) => void
: never;
// This should accept individual items, not arrays
@@ -359,8 +357,7 @@ arrayRemoveTags(["tags"], (tag) => tag === "alpha");
type IncrementSignature<
Data extends object,
Path extends AllObjectPaths,
-> = GetObjectPath extends number
- ? (path: Path, amount?: number) => void
+> = GetObjectPath extends number ? (path: Path, amount?: number) => void
: never;
declare const increment: IncrementSignature;
@@ -398,10 +395,4 @@ type NoAnyTest4 = Expect<
NotAny>
>;
-export type {
- ArrayPushSignature,
- ArrayRemoveSignature,
- IncrementSignature,
- ObjSetSignature,
- ToggleSignature,
-};
+export type { ArrayPushSignature, ArrayRemoveSignature, IncrementSignature, ObjSetSignature, ToggleSignature };
diff --git a/test/react-button.test.tsx b/test/react-button.test.tsx
index c0bfa02aab25b0f533e17044a253e458d5d76af6..2db95cfdf819fe4be3dbda542c42c0627037c325 100644
--- a/test/react-button.test.tsx
+++ b/test/react-button.test.tsx
@@ -1,9 +1,9 @@
import { render, screen, waitFor } from "@testing-library/react";
import { userEvent } from "@testing-library/user-event";
-import { describe, test, expect, vi } from "vitest";
-import { MutationClient } from "../src/client.ts";
-import { useMutate, createMutationButton } from "../src/react.ts";
import type { FC } from "react";
+import { describe, expect, test, vi } from "vitest";
+import { MutationClient } from "../src/client.ts";
+import { createMutationButton, useMutate } from "../src/react.ts";
// Helper to create a test mutation client
function createTestClient() {
@@ -632,7 +632,7 @@ describe("createMutationButton - Edge Cases", () => {
expect(completedCount).toBeGreaterThan(0);
expect(screen.getByTestId("pending-status").textContent).toBe("idle");
},
- { timeout: 200 }
+ { timeout: 200 },
);
// All clicks should be processed
diff --git a/test/react.test.tsx b/test/react.test.tsx
index 20b8a34118d6a44c061e0fcd1d4e419a1a5d3088..5ef485eaa1082da09389dcb247eb008ed95e6f0d 100644
--- a/test/react.test.tsx
+++ b/test/react.test.tsx
@@ -1,11 +1,11 @@
+import { assertEquals } from "@std/assert";
import { render, screen, waitFor } from "@testing-library/react";
import { userEvent } from "@testing-library/user-event";
-import { assertEquals } from "@std/assert";
-import { describe, test, expect, beforeEach, vi } from "vitest";
+import { useState } from "react";
+import { beforeEach, describe, expect, test, vi } from "vitest";
import { MutationClient } from "../src/client.ts";
-import { useMutate, createMutationButton } from "../src/react.ts";
+import { createMutationButton, useMutate } from "../src/react.ts";
import type { Mutation } from "../src/types.ts";
-import { useState } from "react";
// Helper to create a test mutation client
function createTestClient() {
diff --git a/test/tanstack-query-helpers.test.ts b/test/tanstack-query-helpers.test.ts
index 5599a99295c9f7f584380056c2a17772418cec1b..e4bff6b103aca0388fafd58b2c9bc4dba7a66312 100644
--- a/test/tanstack-query-helpers.test.ts
+++ b/test/tanstack-query-helpers.test.ts
@@ -1,6 +1,6 @@
import { assertEquals } from "@std/assert";
-import { test } from "vitest";
import { QueryClient, queryOptions } from "@tanstack/react-query";
+import { test } from "vitest";
import { queryClientOptimisticHelpers } from "../src/tanstack-query.ts";
interface TestData {