From fd7e4484d26ac5e095cb39bfb2bfc17fe6527d43 Mon Sep 17 00:00:00 2001 From: clover caruso Date: Fri, 23 Jan 2026 17:48:03 -0800 Subject: [PATCH] feat: the library mostly exists now --- .gitignore | 1 + .npmrc | 1 + example/index.html | 12 + example/package.json | 19 + example/src/App.tsx | 137 ++++ example/src/index.css | 198 +++++ example/src/main.tsx | 10 + example/tsconfig.json | 25 + example/vite.config.ts | 13 + package-lock.json | 1058 ++++++++++++++++++--------- package.json | 36 +- readme.md | 107 +++ src/batch.ts | 438 ++++++++++- src/client.ts | 87 ++- src/index.ts | 17 + src/object-path.ts | 77 ++ src/queued.ts | 251 +++++-- src/react.tsx | 323 +++++--- src/register.ts | 9 - src/tanstack-query.ts | 403 ++++++++++ src/types.ts | 9 +- test/batch.test.ts | 1018 ++++++++++++++++++++++++++ test/example.test.tsx | 96 --- test/object-path-types.test.ts | 411 +++++++++++ test/object-path.test.ts | 339 +++++++++ test/queued.test.ts | 967 ++++++++++++++++++++++++ test/tanstack-query-helpers.test.ts | 809 ++++++++++++++++++++ tsconfig.json | 30 +- vitest.config.ts | 11 +- 29 files changed, 6275 insertions(+), 637 deletions(-) create mode 100644 .npmrc create mode 100644 example/index.html create mode 100644 example/package.json create mode 100644 example/src/App.tsx create mode 100644 example/src/index.css create mode 100644 example/src/main.tsx create mode 100644 example/tsconfig.json create mode 100644 example/vite.config.ts create mode 100644 readme.md create mode 100644 src/index.ts create mode 100644 src/object-path.ts delete mode 100644 src/register.ts create mode 100644 src/tanstack-query.ts create mode 100644 test/batch.test.ts delete mode 100644 test/example.test.tsx create mode 100644 test/object-path-types.test.ts create mode 100644 test/object-path.test.ts create mode 100644 test/queued.test.ts create mode 100644 test/tanstack-query-helpers.test.ts diff --git a/.gitignore b/.gitignore index ba2a97b57acafe13673b76a624a59ebceadb2849..85b73bbee851c61404b06f32a09def6bf0521725 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules coverage +*.tsbuildinfo diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000000000000000000000000000000000000..41583e36ca8d2eaf61c855a0500988bb163adc2a --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +@jsr:registry=https://npm.jsr.io diff --git a/example/index.html b/example/index.html new file mode 100644 index 0000000000000000000000000000000000000000..593e1a2c12d1829ed17215e75f627c2406f3dcd5 --- /dev/null +++ b/example/index.html @@ -0,0 +1,12 @@ + + + + + + React Mutation Demo + + +
+ + + diff --git a/example/package.json b/example/package.json new file mode 100644 index 0000000000000000000000000000000000000000..abe5c67893ac5a9a0cfb0ed2777534c9c6296029 --- /dev/null +++ b/example/package.json @@ -0,0 +1,19 @@ +{ + "private": true, + "type": "module", + "scripts": { + "dev": "vite" + }, + "dependencies": { + "@tanstack/react-query": "^5.90.20", + "react": "^19.2.4", + "react-dom": "^19.2.4" + }, + "devDependencies": { + "@types/node": "^24.10.1", + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "vite": "^7.2.4" + } +} diff --git a/example/src/App.tsx b/example/src/App.tsx new file mode 100644 index 0000000000000000000000000000000000000000..cf05c93031fb5e99848aa732c90c14134355eee4 --- /dev/null +++ b/example/src/App.tsx @@ -0,0 +1,137 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { + createMutationButton, + MutationClient, + queryClientOptimisticHelpers, + useMutation, +} 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"; + +const queryClient = new QueryClient(); +const mutationClient = new MutationClient({ + context: { + client: queryClient, + get(q: QueryKeyAndFn): T | undefined { + return queryClient.getQueryData(q.queryKey); + }, + }, + getOptimisticHelpers: queryClientOptimisticHelpers(queryClient), + reportError(error: unknown) { + console.error("Mutation error:", error); + }, +}); + +// React query stuff +let backendValue = 0; +const queryCounter = queryOptions({ + queryKey: ["demo"], + queryFn: () => ({ count: backendValue }), + staleTime: Infinity, +}); + +// Define a simple mutation +// const mutIncrement = mutationClient.queuedMutation({ +// async mutate(amount: number) { +// // Simulate API call +// await new Promise((resolve) => setTimeout(resolve, 500)); +// if (Math.random() < 0.1) throw new Error("Random error occurred!"); +// backendValue += amount; + +// return amount; +// }, +// describe: "Increment counter", +// optimistic({ helpers, args: [amount] }) { +// helpers.objSet(queryCounter, ["count"], (n) => n + amount); +// }, +// async refetch({ client }) { +// await client.invalidateQueries(queryCounter); +// }, +// }); +const mutIncrement = mutationClient.defineBatched({ + mode: "debounce", + time: 200, + + optimistic({ helpers }, amount: number) { + helpers.objIncrement(queryCounter, ["count"], amount); + }, + key: () => "counter", + getValue: ({ get }) => get(queryCounter)?.count ?? 0, + + async commit({ initial, current }) { + const delta = current - initial; + // Simulate API call + await new Promise((resolve) => setTimeout(resolve, 500)); + if (Math.random() < 0.1) throw new Error("Random error occurred!"); + backendValue += delta; + return delta; + }, + + describe: "update counter", +}); + +function CustomButton( + { onClick, isPending, ...rest }: { + onClick: (e: React.MouseEvent) => void; + isPending: boolean; + children: React.ReactNode; + className?: string; + }, +) { + return ( +