Giter Club home page Giter Club logo

Comments (4)

Drevoed avatar Drevoed commented on June 9, 2024

What do you think about this API?
e.g. overload for pessimistic update:

export type PessimisticUpdateConfig<Data, Response> = {
  mode: 'pessimistic',
  query: Query<any, Data, any>,
  mutation: Mutation<any, Response, any>
} & (Response extends Data ? {
  mapResponse?: (response: Response) => Data
} : {
  mapResponse: (response: Response) => Data
});

export function update<Data, Response>(config: PessimisticUpdateConfig<Data, Response>): void {/*...*/}

from farfetched.

Drevoed avatar Drevoed commented on June 9, 2024

I think it is also probably a good idea to allow 'general' updates. In other words - manual updates.

I am thinking something along the lines of

export type GeneralUpdateConfig<Data, EventPayload> = {
  mode: 'general',
  query: Query<any, Data, any>,
  fn: (payload: EventPayload) => Data
};

export function update<Data, EventPayload>(config: GeneralUpdateConfig<Data, EventPayload>): Event<EventPayload> {/*...*/}

should suffice.

One can always do anything he wants with the received event.

from farfetched.

Drevoed avatar Drevoed commented on June 9, 2024

Moving the gist contents here for further discussion:

RFC: update operator for @farfetched/core

farfetched already knows how to work with mutations.
But they are useless without a way to update any Query based on result of those mutations or on their params (also known as pessimistic and optimistic updates).

I think having an operator that provides this ability will be a perfect and much needed addition for library.

API overview

I propose the next api to update queries via mutations:

import {
  Mutation,
  Query,
  TwoArgsDynamicallySourcedField,
  ThreeArgsDynamicallySourcedField
} from '@farfetched/core';

type ExcludeVoid<T> = Pick<
  T,
  {
    [K in keyof T]: T[K] extends void ? never : K;
  }[keyof T]
>;

type MapperBaseArgs<Data, MutationParams> = {
  queryData: Data;
  mutationParams: MutationParams;
};

type Extends<Specific, Wide> = Specific extends Wide ? true : false;

type Conditional<Condition, T> = Condition extends true
  ? Partial<T>
  : Required<T>;

type BasicUpdateConfig<Data, QueryParams, MutationParams, Response, Error> = {
  query: Query<QueryParams, Data, any>;
  mutation: Mutation<MutationParams, Response, Error>;
  fromError?: (
    args: ExcludeVoid<
      MapperBaseArgs<Data, MutationParams> & {
        error: Error;
        pristineData?: Data;
      }
    >
  ) => Data;
};

export type PessimisticUpdateConfig<
  Data,
  QueryParams,
  MutationParams,
  Response,
  Source,
  Error
> = BasicUpdateConfig<Data, QueryParams, MutationParams, Response, Error> &
  Conditional<
    Extends<Response, Data>,
    {
      map: ThreeArgsDynamicallySourcedField<
        Data,
        { queryParams: QueryParams; mutationParams: MutationParams },
        Response,
        Data,
        Source
      >;
    }
  >;

export type OptimisticUpdateConfig<
  Data,
  QueryParams,
  MutationParams,
  Response,
  Source,
  RefetchSource,
  Error
> = (BasicUpdateConfig<Data, QueryParams, MutationParams, Response, Error> & {
  refetch?: ThreeArgsDynamicallySourcedField<
    Data,
    { queryParams: QueryParams; mutationParams: MutationParams },
    Error,
    { params: QueryParams },
    RefetchSource
  >;
}) &
  Conditional<
    Extends<MutationParams, Data>,
    {
      map: TwoArgsDynamicallySourcedField<Data, MutationParams, Data, Source>;
    }
  >;

export function pessimisticUpdate<
  Data,
  QueryParams,
  MutationParams,
  Response,
  Source,
  Error
>(
  config: PessimisticUpdateConfig<
    Data,
    QueryParams,
    MutationParams,
    Response,
    Source,
    Error
  >
): void {}

export function optimisticUpdate<
  Data,
  QueryParams,
  MutationParams,
  Response,
  Source,
  RefetchSource,
  Error
>(
  config: OptimisticUpdateConfig<
    Data,
    QueryParams,
    MutationParams,
    Response,
    Source,
    RefetchSource,
    Error
  >
): void {}

By having this api it seems like we can cover every possible use case for updates.

Such as:

  • Updating query data immediately after successful mutation
  • Updating query data immediately after beginning of a mutation
  • Updating query data after mutation ended with an error
  • Local updates (developer just needs to add a local mutation)
  • Error handling
  • Updates based on {mutation params, old data, mutation response, error}

Example usage

const query = createQuery({
  handler: async (x: number) => ({
    id: x,
    name: '',
  }),
});

const mutation = createMutation({
  effect: createEffect(async (_: { id: string; name: number }) => ({
    id: 2,
  })),
});

pessimisticUpdate({
  query,
  mutation,
  map: {
    source: createStore('literal' as const),
    fn(queryData, params, response, source) {
      return {
        id: ++response.id,
        name: source,
      };
    },
  },
  fromError({error, pristineData}) {
    console.warn(`mutation ended with an error: ${error.message}`);
    
    function createExpectedData(): NonNullable<typeof pristineData> {
      /* ... */
    }
    
    const origData = pristineData ?? createExpectedData();

    return origData
  }
});

optimisticUpdate({
  query,
  mutation,
  map(queryData, mutationParams) {
    return {
      id: parseInt(mutationParams.id) + 1,
      name: mutationParams.name.toString(),
    };
  },
  refetch: {
    source: createStore(42 as const),
    fn(queryData, { queryParams, mutationParams }, error, source) {
      return '';
    },
    // ^ Type 'string' is not assignable to type '{ params: number; }'.
  },
});

from farfetched.

igorkamyshev avatar igorkamyshev commented on June 9, 2024

I assume optimistic and pessimistic updates are entirely different cases, let's split them to different PR's.

We can release update (just regular pessimistic update) in the next release, however for the optimisitcUpdate we have to solve plenty of different tasks.

update

I love the proposed API, it looks like it could solve all cases that I can imagine. There are couple minor question:

  1. map but fromError looks weird for me, should we keep them consistent, like fromData and fromError? Or maybe it would be better to something like bySuccess and byFailure? It could lead us to read this operator update query originQuery after mutation changeOriginMutation bySuccess (...rule) and byFailure (...ruke).
  2. four arguments of fn in map looks a little bit harsh. Perhaps something like fn(query: { data }, mutation: { params, data }, source) would be more readable?
  3. pristineData in pessimistic update does not have any sense because in case of error Query data would not be updated, so it is just data from Query
  4. I assume error in fromError based on Mutation error, so I purpose to move it to second arg to be consistent with map.
  5. How can we make Query re-start after update? The case is simple, we are logging-in user and response of loginMutation returns only email, but userQuery has optional name that could be retrieved only while re-executing.

optimisticUpdate

All question from the previous section is relevant there too, furthermore I have some other issues with this case:

  1. Race. There is no API for solving such case:
    -> start mutation 1
    -> query updated, get state 1, old state 0 is saved somewhere for rollback
    -> start mutation 2
    -> query updated, get state 2, old state 1 is saved somewhere for rollback
    -> fail mutation 1
    -> what's next?

  2. refetch args have to be consistent with other callbacks. Like (query: { params, data }, mutation: { params, error }, source).

  3. I am not ok with the term pristineData 🤔


However, in general, I love this API 💙

from farfetched.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.