Skip to content
On this page

Deprecation of attachOperation

This ADR is effective starting from v0.12.

TL;DR

attachOperation is a bad abstraction because it is not clear what it does, and it is not clear what it is supposed to do. Furthermore, it can be replaced by a more explicit and more flexible plain JS functions.

ts
 function createOriginalQuery() {
  return createQuery(/*...*/);
}

const originalQuery = createQuery(/*...*/); 
const originalQuery = createOriginalQuery(); 

const copiedQuery = attachOperation(originalQuery); 
const copiedQuery = createOriginalQuery(); 

Issues with attachOperation

Let us take a closer look at problems with attachOperation.

Understanding

attachOperation is a function that takes a Query (or Mutation) and returns a copy of it. However, it is not clear what it does with some additional behavior added to Query. For example, let us say that we have the following code:

ts
import { createQuery, cache, attachOperation } from '@farfetched/core';

const originalQuery = createQuery(/*...*/);

cache(originalQuery);

const copiedQuery = attachOperation(originalQuery);

Should copiedQuery be cached? Should it use the same cache strategy as originalQuery, or should it share cache with originalQuery? The answer is not clear, and it is not clear because attachOperation is not a good abstraction.

However, if we replace attachOperation with a plain JS function, it becomes clear what it does:

ts
import { createQuery, cache } from '@farfetched/core';

function createOriginalQuery() {
  const q = createQuery(/*...*/);

  cache(q);

  return q;
}

const originalQuery = createOriginalQuery();
const copiedQuery = createOriginalQuery();

Now it is crystal clear that copiedQuery should be cached with the same cache strategy as originalQuery, but they should not share cache.

The same applies to other behaviors that can be added to Query or Mutation. For example, the following code example:

ts
import { createQuery, retry, attachOperation } from '@farfetched/core';

const originalQuery = createQuery(/*...*/);

retry(originalQuery, {
  times: 5,
});

const copiedQuery = attachOperation(originalQuery);

Same questions. Should copiedQuery retry? Should it use the same retry strategy as originalQuery, or should it share the whole retry state (how many retries are already over, etc.) with originalQuery? Replacing attachOperation with a plain JS function makes it clear and explicit:

ts
import { createQuery, retry } from '@farfetched/core';

function createOriginalQuery() {
  const q = createQuery(/*...*/);

  retry(q, {
    times: 5,
  });

  return q;
}

const originalQuery = createOriginalQuery();
const copiedQuery = createOriginalQuery();

Since we are trying to make Farfetched as explicit as possible, we should not use attachOperation anymore.

Extensibility

attachOperation is mimic of attach from Effector which has an ability of altering parameters of the original Effect. This ability was copied "as is" to attachOperation, and it works great. However, Query and Mutation are way more complex than Effect, so it is required to extend attachOperation for almost every new feature. A couple of examples:

The solution is to replace attachOperation with a plain JS function does not require any changes because it is just a function. Users can alter it as they want without any limitations.

Future of attachOperation

How to migrate

Let us describe migration paths for all possible use cases of attachOperation.

TIP

You can check an example of migration of SolidJS Real-world Example.

attachOperation(originalQuery)

This overload is used to create a copy of Query or Mutation without any modifications. It can be replaced with a plain JS function:

ts
 function createOriginalQuery() {
  return createQuery(/*...*/);
}

const originalQuery = createQuery(/*...*/); 
const originalQuery = createOriginalQuery(); 

const copiedQuery = attachOperation(originalQuery); 
const copiedQuery = createOriginalQuery(); 

@withease/factories

If your application has SSR you can use @withease/factories to create a factory function with ease:

ts
import { createFactory } from '@withease/factories'; 

const createOriginalQuery = createFactory(() => { 
function createOriginalQuery() { 
  return createQuery(/*...*/);
});

Read more about it in the documentation of @withease/factories.

attachOperation(originalQuery, { mapParams })

This overload allows altering parameters of the original Query or Mutation. It can be replaced with a plain JS function as well:

ts
 function createOriginalQuery({ mapParams } = {}) {
  return createQuery({
    effect(rawParams) {
      const params = mapParams ? mapParams(rawParams) : rawParams;
      return fetch(/*...*/);
    },
  });
}

 const originalQuery = createQuery({
  effect(params) {
    return fetch(/*...*/);
  },
});
const originalQuery = createOriginalQuery(); 

 const copiedQuery = attachOperation(originalQuery, {
  mapParams: p + 1,
});

const copiedQuery = createOriginalQuery({ mapParams: (p) => p + 1 }); 

Deletion schedule

  • attachOperation(originalQuery) will be marked as deprecated in v0.12 with a warning in JSDoc and on TS-level.
  • attachOperation(originalQuery) will start writing deprecation message with console.warning in v0.13.
  • attachOperation(originalQuery) will be deleted in v0.14.

Released under the MIT License.