Advertisement
uraniumanchor

Untitled

Apr 10th, 2025
428
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
TypeScript 4.98 KB | Source Code | 0 0
  1. import { Draft } from 'immer';
  2. import {
  3.   DefinitionsFromApi,
  4.   InfiniteData,
  5.   InfiniteQueryArgFrom,
  6.   InfiniteQueryDefinition,
  7.   MutationDefinition,
  8.   PageParamFrom,
  9.   QueryArgFrom,
  10.   QueryDefinition,
  11.   ResultTypeFrom,
  12.   TypedMutationOnQueryStarted,
  13. } from '@reduxjs/toolkit/query';
  14. import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
  15.  
  16. interface Model {
  17.   id: number;
  18.   type: 'model';
  19.   flibbertygibbits: number;
  20. }
  21.  
  22. interface Other {
  23.   id: number;
  24.   type: 'other';
  25.   foobars: number;
  26. }
  27.  
  28. const baseApi = createApi({
  29.   baseQuery: fetchBaseQuery(),
  30.   endpoints: build => ({
  31.     models: build.query<Model[], { id?: number } | void>({
  32.       query: ({ id } = {}) => `models/${id != null ? `${id}/` : ''}`,
  33.     }),
  34.     // allModels: build.infiniteQuery<Model[], { id?: number } | void, number>({
  35.     //   query: ({ queryArg: { id } = {}, pageParam }) => `models/${id != null ? `${id}/` : ''}`,
  36.     //   infiniteQueryOptions: {
  37.     //     initialPageParam: 1,
  38.     //     getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) => lastPageParam + 1,
  39.     //   },
  40.     // }),
  41.     mutateModel: build.mutation<Model, { id: number; flibberygibbits: number }>({
  42.       query: ({ id }) => `models/${id}`,
  43.     }),
  44.     // others: build.query<Other[], { id?: number } | void>({
  45.     //   query: ({ id } = {}) => `others/${id != null ? `${id}/` : ''}`,
  46.     // }),
  47.     // mutateOther: build.mutation<Other, { id: number; foobars: number }>({
  48.     //   query: ({ id }) => `others/${id}`,
  49.     // }),
  50.   }),
  51. });
  52.  
  53. type ApiEndpointDefinitions = DefinitionsFromApi<typeof baseApi>;
  54. type ApiQueryEndpoints = {
  55.   [K in keyof ApiEndpointDefinitions as ApiEndpointDefinitions[K] extends QueryDefinition<any, any, any, any>
  56.     ? K
  57.     : never]: ApiEndpointDefinitions[K];
  58. };
  59. type ApiInfiniteQueryEndpoints = {
  60.   [K in keyof ApiEndpointDefinitions as ApiEndpointDefinitions[K] extends InfiniteQueryDefinition<
  61.     any,
  62.     any,
  63.     any,
  64.     any,
  65.     any
  66.   >
  67.     ? K
  68.     : never]: ApiEndpointDefinitions[K];
  69. };
  70. type ApiMutationEndpoints = {
  71.   [K in keyof ApiEndpointDefinitions as ApiEndpointDefinitions[K] extends MutationDefinition<any, any, any, any>
  72.     ? K
  73.     : never]: ApiEndpointDefinitions[K];
  74. };
  75.  
  76. function optimisticMutation<
  77.   const K extends keyof ApiQueryEndpoints | keyof ApiInfiniteQueryEndpoints,
  78.   const MK extends keyof ApiMutationEndpoints,
  79. >(k: K, mk: MK) {
  80.   type OriginalArgs = K extends keyof ApiQueryEndpoints
  81.     ? QueryArgFrom<ApiEndpointDefinitions[K]>
  82.     : K extends keyof ApiInfiniteQueryEndpoints
  83.       ? InfiniteQueryArgFrom<ApiEndpointDefinitions[K]>
  84.       : never;
  85.   type MutationArgs = QueryArgFrom<ApiEndpointDefinitions[MK]>;
  86.   type QueryData = K extends keyof ApiQueryEndpoints
  87.     ? ResultTypeFrom<ApiEndpointDefinitions[K]>
  88.     : K extends keyof ApiInfiniteQueryEndpoints
  89.       ? InfiniteData<ResultTypeFrom<ApiEndpointDefinitions[K]>, PageParamFrom<ApiEndpointDefinitions[K]>>
  90.       : never;
  91.   type MutationData = ResultTypeFrom<ApiEndpointDefinitions[MK]>;
  92.   type Recipe = (draftData: QueryData | Draft<QueryData>) => void | QueryData | Draft<QueryData>;
  93.   return (
  94.     optimistic: (originalArgs: OriginalArgs, mutationArgs: MutationArgs) => Recipe,
  95.     pessimistic?: (originalArgs: OriginalArgs, mutationArgs: MutationArgs, data: MutationData) => Recipe,
  96.   ): NonNullable<TypedMutationOnQueryStarted<MutationData, MutationArgs, ReturnType<typeof fetchBaseQuery>>> => {
  97.     return async (mutationArgs, api) => {
  98.       const undo = baseApi.util
  99.         .selectCachedArgsForQuery(api.getState(), k)
  100.         .map(
  101.           originalArgs =>
  102.             api.dispatch(baseApi.util.updateQueryData(k, originalArgs, optimistic(originalArgs, mutationArgs))).undo,
  103.         );
  104.       try {
  105.         const { data: result } = await api.queryFulfilled;
  106.         if (pessimistic) {
  107.           baseApi.util
  108.             .selectCachedArgsForQuery(api.getState(), k)
  109.             .forEach(originalArgs =>
  110.               api.dispatch(
  111.                 baseApi.util.updateQueryData(k, originalArgs, pessimistic(originalArgs, mutationArgs, result)),
  112.               ),
  113.             );
  114.         }
  115.       } catch {
  116.         undo.forEach(u => u());
  117.         // nothing
  118.       }
  119.     };
  120.   };
  121. }
  122.  
  123. export const enhancedApi = baseApi.enhanceEndpoints({
  124.   endpoints: {
  125.     mutateModel: {
  126.       onQueryStarted: optimisticMutation('models', 'mutateModel')(
  127.         (originalArgs, mutationArgs) => drafts => {
  128.           const i = drafts.findIndex(d => d.id === mutationArgs.id);
  129.           if (i !== -1) {
  130.             // optimistic update based on client mutation
  131.             drafts[i].flibbertygibbits = mutationArgs.flibberygibbits;
  132.           }
  133.         },
  134.         (originalArgs, mutationArgs, data) => drafts => {
  135.           const i = drafts.findIndex(d => d.id === mutationArgs.id);
  136.           if (i !== -1) {
  137.             // pessimistic update based on server response
  138.             drafts[i] = { ...drafts[i], ...data };
  139.           }
  140.         },
  141.       ),
  142.     },
  143.   },
  144. });
  145.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement