aboutsummaryrefslogtreecommitdiffstats
path: root/src/stores/lib
diff options
context:
space:
mode:
authorLibravatar Stefan Malzner <stefan@adlk.io>2017-10-13 12:29:40 +0200
committerLibravatar Stefan Malzner <stefan@adlk.io>2017-10-13 12:29:40 +0200
commit58cda9cc7fb79ca9df6746de7f9662bc08dc156a (patch)
tree1211600c2a5d3b5f81c435c6896618111a611720 /src/stores/lib
downloadferdium-app-58cda9cc7fb79ca9df6746de7f9662bc08dc156a.tar.gz
ferdium-app-58cda9cc7fb79ca9df6746de7f9662bc08dc156a.tar.zst
ferdium-app-58cda9cc7fb79ca9df6746de7f9662bc08dc156a.zip
initial commit
Diffstat (limited to 'src/stores/lib')
-rw-r--r--src/stores/lib/CachedRequest.js106
-rw-r--r--src/stores/lib/Reaction.js22
-rw-r--r--src/stores/lib/Request.js112
-rw-r--r--src/stores/lib/Store.js44
4 files changed, 284 insertions, 0 deletions
diff --git a/src/stores/lib/CachedRequest.js b/src/stores/lib/CachedRequest.js
new file mode 100644
index 000000000..c0c3d40a1
--- /dev/null
+++ b/src/stores/lib/CachedRequest.js
@@ -0,0 +1,106 @@
1// @flow
2import { action } from 'mobx';
3import { isEqual, remove } from 'lodash';
4import Request from './Request';
5
6export default class CachedRequest extends Request {
7 _apiCalls = [];
8 _isInvalidated = true;
9
10 execute(...callArgs) {
11 // Do not continue if this request is already loading
12 if (this._isWaitingForResponse) return this;
13
14 // Very simple caching strategy -> only continue if the call / args changed
15 // or the request was invalidated manually from outside
16 const existingApiCall = this._findApiCall(callArgs);
17
18 // Invalidate if new or different api call will be done
19 if (existingApiCall && existingApiCall !== this._currentApiCall) {
20 this._isInvalidated = true;
21 this._currentApiCall = existingApiCall;
22 } else if (!existingApiCall) {
23 this._isInvalidated = true;
24 this._currentApiCall = this._addApiCall(callArgs);
25 }
26
27 // Do not continue if this request is not invalidated (see above)
28 if (!this._isInvalidated) return this;
29
30 // This timeout is necessary to avoid warnings from mobx
31 // regarding triggering actions as side-effect of getters
32 setTimeout(action(() => {
33 this.isExecuting = true;
34 // Apply the previous result from this call immediately (cached)
35 if (existingApiCall) {
36 this.result = existingApiCall.result;
37 }
38 }), 0);
39
40 // Issue api call & save it as promise that is handled to update the results of the operation
41 this._promise = new Promise((resolve, reject) => {
42 this._api[this._method](...callArgs)
43 .then((result) => {
44 setTimeout(action(() => {
45 this.result = result;
46 if (this._currentApiCall) this._currentApiCall.result = result;
47 this.isExecuting = false;
48 this.isError = false;
49 this.wasExecuted = true;
50 this._isInvalidated = false;
51 this._isWaitingForResponse = false;
52 this._triggerHooks();
53 resolve(result);
54 }), 1);
55 return result;
56 })
57 .catch(action((error) => {
58 setTimeout(action(() => {
59 this.error = error;
60 this.isExecuting = false;
61 this.isError = true;
62 this.wasExecuted = true;
63 this._isWaitingForResponse = false;
64 this._triggerHooks();
65 reject(error);
66 }), 1);
67 }));
68 });
69
70 this._isWaitingForResponse = true;
71 return this;
72 }
73
74 invalidate(options = { immediately: false }) {
75 this._isInvalidated = true;
76 if (options.immediately && this._currentApiCall) {
77 return this.execute(...this._currentApiCall.args);
78 }
79 return this;
80 }
81
82 patch(modify) {
83 return new Promise((resolve) => {
84 setTimeout(action(() => {
85 const override = modify(this.result);
86 if (override !== undefined) this.result = override;
87 if (this._currentApiCall) this._currentApiCall.result = this.result;
88 resolve(this);
89 }), 0);
90 });
91 }
92
93 removeCacheForCallWith(...args) {
94 remove(this._apiCalls, c => isEqual(c.args, args));
95 }
96
97 _addApiCall(args) {
98 const newCall = { args, result: null };
99 this._apiCalls.push(newCall);
100 return newCall;
101 }
102
103 _findApiCall(args) {
104 return this._apiCalls.find(c => isEqual(c.args, args));
105 }
106}
diff --git a/src/stores/lib/Reaction.js b/src/stores/lib/Reaction.js
new file mode 100644
index 000000000..e9bc26d81
--- /dev/null
+++ b/src/stores/lib/Reaction.js
@@ -0,0 +1,22 @@
1// @flow
2import { autorun } from 'mobx';
3
4export default class Reaction {
5 reaction;
6 hasBeenStarted;
7 dispose;
8
9 constructor(reaction) {
10 this.reaction = reaction;
11 this.hasBeenStarted = false;
12 }
13
14 start() {
15 this.dispose = autorun(() => this.reaction());
16 this.hasBeenStarted = true;
17 }
18
19 stop() {
20 if (this.hasBeenStarted) this.dispose();
21 }
22}
diff --git a/src/stores/lib/Request.js b/src/stores/lib/Request.js
new file mode 100644
index 000000000..4a6925cc5
--- /dev/null
+++ b/src/stores/lib/Request.js
@@ -0,0 +1,112 @@
1import { observable, action, computed } from 'mobx';
2import { isEqual } from 'lodash/fp';
3
4export default class Request {
5 static _hooks = [];
6
7 static registerHook(hook) {
8 Request._hooks.push(hook);
9 }
10
11 @observable result = null;
12 @observable error = null;
13 @observable isExecuting = false;
14 @observable isError = false;
15 @observable wasExecuted = false;
16
17 _promise = Promise;
18 _api = {};
19 _method = '';
20 _isWaitingForResponse = false;
21 _currentApiCall = null;
22
23 constructor(api, method) {
24 this._api = api;
25 this._method = method;
26 }
27
28 execute(...callArgs) {
29 // Do not continue if this request is already loading
30 if (this._isWaitingForResponse) return this;
31
32 if (!this._api[this._method]) {
33 throw new Error(`Missing method <${this._method}> on api object:`, this._api);
34 }
35
36 // This timeout is necessary to avoid warnings from mobx
37 // regarding triggering actions as side-effect of getters
38 setTimeout(action(() => {
39 this.isExecuting = true;
40 }), 0);
41
42 // Issue api call & save it as promise that is handled to update the results of the operation
43 this._promise = new Promise((resolve, reject) => {
44 this._api[this._method](...callArgs)
45 .then((result) => {
46 setTimeout(action(() => {
47 this.result = result;
48 if (this._currentApiCall) this._currentApiCall.result = result;
49 this.isExecuting = false;
50 this.isError = false;
51 this.wasExecuted = true;
52 this._isWaitingForResponse = false;
53 this._triggerHooks();
54 resolve(result);
55 }), 1);
56 return result;
57 })
58 .catch(action((error) => {
59 setTimeout(action(() => {
60 this.error = error;
61 this.isExecuting = false;
62 this.isError = true;
63 this.wasExecuted = true;
64 this._isWaitingForResponse = false;
65 this._triggerHooks();
66 reject(error);
67 }), 1);
68 }));
69 });
70
71 this._isWaitingForResponse = true;
72 this._currentApiCall = { args: callArgs, result: null };
73 return this;
74 }
75
76 reload() {
77 return this.execute(...this._currentApiCall.args);
78 }
79
80 isExecutingWithArgs(...args) {
81 return this.isExecuting && this._currentApiCall && isEqual(this._currentApiCall.args, args);
82 }
83
84 @computed get isExecutingFirstTime() {
85 return !this.wasExecuted && this.isExecuting;
86 }
87
88 then(...args) {
89 if (!this._promise) throw new Error('You have to call Request::execute before you can access it as promise');
90 return this._promise.then(...args);
91 }
92
93 catch(...args) {
94 if (!this._promise) throw new Error('You have to call Request::execute before you can access it as promise');
95 return this._promise.catch(...args);
96 }
97
98 _triggerHooks() {
99 Request._hooks.forEach(hook => hook(this));
100 }
101
102 reset() {
103 this.result = null;
104 this.isExecuting = false;
105 this.isError = false;
106 this.wasExecuted = false;
107 this._isWaitingForResponse = false;
108 this._promise = Promise;
109
110 return this;
111 }
112}
diff --git a/src/stores/lib/Store.js b/src/stores/lib/Store.js
new file mode 100644
index 000000000..873da7b37
--- /dev/null
+++ b/src/stores/lib/Store.js
@@ -0,0 +1,44 @@
1import { computed, observable } from 'mobx';
2import Reaction from './Reaction';
3
4export default class Store {
5 stores = {};
6 api = {};
7 actions = {};
8
9 _reactions = [];
10
11 // status implementation
12 @observable _status = null;
13 @computed get actionStatus() {
14 return this._status || [];
15 }
16 set actionStatus(status) {
17 this._status = status;
18 }
19
20 constructor(stores, api, actions) {
21 this.stores = stores;
22 this.api = api;
23 this.actions = actions;
24 }
25
26 registerReactions(reactions) {
27 reactions.forEach(reaction => this._reactions.push(new Reaction(reaction)));
28 }
29
30 setup() {}
31
32 initialize() {
33 this.setup();
34 this._reactions.forEach(reaction => reaction.start());
35 }
36
37 teardown() {
38 this._reactions.forEach(reaction => reaction.stop());
39 }
40
41 resetStatus() {
42 this._status = null;
43 }
44}