Skip to content

Commit 41259e9

Browse files
committed
initial commit
Signed-off-by: sirugh <rugh@adobe.com>
0 parents  commit 41259e9

File tree

3 files changed

+146
-0
lines changed

3 files changed

+146
-0
lines changed

README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# apollo-link-mutation-queue
2+
3+
An Apollo link that enqueues mutations so that they do not fire in parallel.
4+
5+
## Why
6+
7+
I was finding that mutations affecting the same underlying data would often
8+
return incorrect data if fired in parallel. Instead of blocking UI based on the
9+
loading state of the mutations I wrote a link that just enqueues mutations.
10+
11+
## Use
12+
13+
Compose your link chain with the link.
14+
15+
```js
16+
import MutationQueueLink from "apollo-link-mutation-queue";
17+
18+
const link = ApolloLink.from([
19+
new MutationQueueLink()
20+
//... your other links
21+
]);
22+
```
23+
24+
Debug with `debug: true`.
25+
26+
```js
27+
import MutationQueueLink from "apollo-link-mutation-queue";
28+
29+
const link = ApolloLink.from([
30+
new MutationQueueLink({ debug: true })
31+
//... your other links
32+
]);
33+
```
34+
35+
Cut in line with `skipQueue: true`.
36+
37+
```js
38+
const [mutate] = useMutation(MY_MUTATION);
39+
40+
useEffect(() => {
41+
mutate({
42+
context: { skipQueue: true }
43+
});
44+
}, []);
45+
```

package.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "apollo-link-mutation-queue",
3+
"version": "0.0.1",
4+
"description": "An apollo link that queues mutations.",
5+
"main": "src/index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"keywords": [
10+
"apollo",
11+
"link",
12+
"mutation",
13+
"queue"
14+
],
15+
"author": "Stephen Rugh",
16+
"license": "ISC"
17+
}

src/index.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { ApolloLink, Observable } from "apollo-link";
2+
3+
const toRequestKey = operation => {
4+
return operation.operationName;
5+
};
6+
7+
/**
8+
* An Apollo link that enqueues mutations so that they cannot fire in parallel.
9+
*
10+
* To skip the queue pass `{ context: { skipQueue: true } }` to your mutation.
11+
*/
12+
export default class MutationQueueLink extends ApolloLink {
13+
/**
14+
* @param {Boolean} props.debug - set to true to enable logging
15+
*/
16+
constructor({ debug = true } = {}) {
17+
super();
18+
this.opQueue = [];
19+
this.inProcess = false;
20+
this.debug = debug;
21+
}
22+
23+
log(message, ...rest) {
24+
if (this.debug) {
25+
console.log(message, ...rest);
26+
}
27+
}
28+
29+
processOperation(entry) {
30+
const { operation, forward, observer } = entry;
31+
this.inProcess = true;
32+
this.log("[PROCESSING] -", toRequestKey(operation));
33+
forward(operation).subscribe({
34+
next: result => {
35+
this.inProcess = false;
36+
observer.next(result);
37+
this.log("[NEXT] -", toRequestKey(operation));
38+
// If there are more operations, process them.
39+
if (this.opQueue.length) {
40+
this.processOperation(this.opQueue.shift());
41+
}
42+
},
43+
error: error => {
44+
this.inProcess = false;
45+
observer.error(error);
46+
this.log("[ERROR] -", toRequestKey(operation), error);
47+
// If there are more operations, process them.
48+
if (this.opQueue.length) {
49+
this.processOperation(this.opQueue.shift());
50+
}
51+
},
52+
complete: observer.complete.bind(observer)
53+
});
54+
}
55+
56+
request(operation, forward) {
57+
// Enqueue all mutations unless manually skipped.
58+
if (
59+
operation.toKey().includes('"operation":"mutation"') &&
60+
!operation.getContext().skipQueue
61+
) {
62+
return new Observable(observer => {
63+
const operationEntry = { operation, forward, observer };
64+
if (!this.inProcess) {
65+
this.processOperation(operationEntry);
66+
} else {
67+
this.log("[ENQUEUE] -", toRequestKey(operation));
68+
this.opQueue.push(operationEntry);
69+
}
70+
return () => this.cancelOperation(operationEntry);
71+
});
72+
} else {
73+
return forward(operation);
74+
}
75+
}
76+
77+
cancelOperation(entry) {
78+
this.opQueue = this.opQueue.filter(e => e !== entry);
79+
}
80+
81+
enqueue(entry) {
82+
this.opQueue.push(entry);
83+
}
84+
}

0 commit comments

Comments
 (0)