authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-02-12 16:07:36-08:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-02-12 16:11:07-08:00
log2f39b6a4fe105ba99110ed4a2b9b63124cccbfcc
tree9665344e6da0a3a798e96f2a10f3ee51534dce9c
parente3d6a4b906d60a10abef5cb9611947b52c979f1a
signaturelock-open Commit is signed but in an unrecognized format.

fix: pass context to optimistic handlers

honestly really sus that ts didnt catch this bug. but i consider it a success that we had so many mutations at work that didn't requires directly accessing context in an optimistic updater -- it means the helpers pattern is too goated.

5 files changed, 56 insertions(+), 3 deletions(-)

jsr.json+1-1
......@@ -1,6 +1,6 @@
11{
22 "name": "@clo/react-mutation",
3 "version": "2.0.0",
3 "version": "2.0.1",
44 "exports": {
55 ".": "./src/mod.ts",
66 "./tanstack-query.ts": "./src/tanstack-query.ts",
readme.changes.md+6
......@@ -1,5 +1,11 @@
11# notable changes in React Mutation
22
3## v2.0.1
4
5### bugfixes
6
7- typescript violation not passing client context to optimistic handlers
8
39## v2
410
511### breaking
src/mutation.ts+2
......@@ -413,6 +413,7 @@ export class BlockingMutation<
413413
414414 try {
415415 this.#options.optimistic({
416 ...this.#client.context,
416417 args,
417418 helpers: channel.helpers,
418419 onRestore,
......@@ -623,6 +624,7 @@ export class BlockingMutation<
623624
624625 try {
625626 this.#options.optimistic({
627 ...this.client.context,
626628 args,
627629 helpers: channel.helpers,
628630 onRestore,
test/optimistic.ts created+45
......@@ -0,0 +1,45 @@
1import { delay } from "@clo/lib/async.ts";
2import { assertEquals } from "@std/assert";
3import { test, vi } from "vitest";
4import { createTestMutationClient } from "./share.ts";
5
6test("passes context value to functions", async () => {
7 vi.useFakeTimers();
8 const { client, successMessages } = createTestMutationClient();
9 const calls: string[] = [];
10
11 let values: number[] = [];
12 const mutTest = client.define({
13 async mutate() {
14 values.push(this.contextValue);
15 calls.push("mutate");
16 await delay(100);
17 },
18 describe: ({ contextValue }) => {
19 values.push(contextValue);
20 calls.push("describe");
21 return "test action";
22 },
23 describeResult: ({ contextValue }) => {
24 values.push(contextValue);
25 calls.push("describe");
26 return "tested action";
27 },
28 optimistic: ({ contextValue, onRefetch, onRestore }) => {
29 values.push(contextValue);
30 onRefetch(async () => void calls.push("refetch"));
31 onRestore(async () => void calls.push("restore"));
32 calls.push("optimistic");
33 },
34 refetchOnSuccess: false,
35 });
36
37 mutTest.run();
38 await vi.advanceTimersByTimeAsync(200);
39
40 // Even with refetchOnSuccess=false, errors should still trigger refetch
41 assertEquals(calls.includes("refetch"), true, "refetch should be called on error");
42 assertEquals(calls.includes("restore"), true, "restore should be called on error");
43 assertEquals(successMessages.length, 1);
44 assertEquals(values, [42, 42, 42]);
45});
test/share.ts+2-2
......@@ -2,7 +2,7 @@
22import { MutationClient } from "../src/client.ts";
33
44export interface TestMutationClient {
5 client: MutationClient<{}, {}>;
5 client: MutationClient<{ contextValue: number }, {}>;
66 errorMessages: Array<{ message: string; error: unknown }>;
77 successMessages: string[];
88}
......@@ -12,7 +12,7 @@ export function createTestMutationClient(): TestMutationClient {
1212 const successMessages: string[] = [];
1313
1414 const client = new MutationClient({
15 context: {},
15 context: { contextValue: 42 },
1616 getOptimisticHelpers: () => ({}),
1717 reportError: (message: string, error: unknown) => {
1818 errorMessages.push({ message, error });