@arrai-innovations/reactive-helpers 8.0.1 → 8.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arrai-innovations/reactive-helpers",
3
- "version": "8.0.1",
3
+ "version": "8.0.3",
4
4
  "description": "VueJS 3 utility composition functions to help manipulate objects and lists.",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -1,6 +1,7 @@
1
+ import { isEmpty } from "lodash-es";
1
2
  import identity from "lodash-es/identity";
2
3
  import isEqual from "lodash-es/isEqual";
3
- import { effectScope, nextTick, onScopeDispose, reactive, readonly, watch } from "vue";
4
+ import { computed, effectScope, nextTick, onScopeDispose, reactive, readonly, watch } from "vue";
4
5
  import { deepUnref } from "vue-deepunref";
5
6
 
6
7
  /*
@@ -8,7 +9,12 @@ import { deepUnref } from "vue-deepunref";
8
9
  * Watch arguments should be a reactive object.
9
10
  * If the promise is not resolved before the watch arguments change again, the previous promise is cancelled.
10
11
  */
11
- export function useCancellableIntent({ awaitableWithCancel, watchArguments = {}, clearActiveOnResolved = true }) {
12
+ export function useCancellableIntent({
13
+ awaitableWithCancel,
14
+ watchArguments = {},
15
+ guardArguments = {},
16
+ clearActiveOnResolved = true,
17
+ }) {
12
18
  if (!awaitableWithCancel) {
13
19
  throw new Error("awaitableWithCancel is required");
14
20
  }
@@ -16,7 +22,9 @@ export function useCancellableIntent({ awaitableWithCancel, watchArguments = {},
16
22
  throw new Error("awaitableWithCancel must be a function");
17
23
  }
18
24
  const state = reactive({
25
+ activeCount: undefined, // the active count doesn't mean much when not using clearActiveOnResolved
19
26
  active: undefined,
27
+ resolvingCount: undefined,
20
28
  resolving: undefined,
21
29
  errored: false,
22
30
  error: null,
@@ -34,8 +42,6 @@ export function useCancellableIntent({ awaitableWithCancel, watchArguments = {},
34
42
 
35
43
  async function cancel() {
36
44
  if (cancelFunction) {
37
- state.active = false;
38
- state.resolving = false;
39
45
  const cancelPromise = cancelFunction().catch(console.error);
40
46
  cancelFunction = null;
41
47
  return cancelPromise;
@@ -43,52 +49,95 @@ export function useCancellableIntent({ awaitableWithCancel, watchArguments = {},
43
49
  return false;
44
50
  }
45
51
 
46
- const watchFn = () => {
52
+ const doIntentWatch = async () => {
53
+ state.errored = false;
54
+ state.error = null;
55
+ if (state.activeCount === undefined) {
56
+ state.activeCount = 0;
57
+ }
58
+ state.activeCount += 1;
59
+ if (state.resolvingCount === undefined) {
60
+ state.resolvingCount = 0;
61
+ }
62
+ state.resolvingCount += 1;
63
+ let awaitablePromise = awaitableWithCancel();
64
+
65
+ if (awaitablePromise.cancel) {
66
+ cancelFunction = awaitablePromise.cancel;
67
+ }
68
+ // we don't want to await this, because we want to be able to cancel it
69
+ awaitablePromise
70
+ .catch(async (err) => {
71
+ await cancel();
72
+ console.error(err);
73
+ state.errored = true;
74
+ state.error = err;
75
+ throw err;
76
+ })
77
+ .finally(() => {
78
+ state.resolvingCount--;
79
+ if (state.clearActiveOnResolved) {
80
+ cancelFunction = null;
81
+ state.activeCount--;
82
+ }
83
+ });
84
+ };
85
+
86
+ let delayedWatch = null;
87
+
88
+ const intentWatch = async () => {
47
89
  let newWatchValues = deepUnref(Object.values(watchArguments));
48
90
  if (isEqual(newWatchValues, previousWatchValues)) {
49
91
  return;
50
92
  }
51
93
  previousWatchValues = newWatchValues;
52
- cancel().catch(console.error);
94
+ await cancel();
53
95
  if (Object.values(previousWatchValues).every(identity)) {
54
- state.errored = false;
55
- state.error = null;
56
- let awaitablePromise = awaitableWithCancel();
57
- state.active = true;
58
- state.resolving = true;
59
- if (awaitablePromise.cancel) {
60
- cancelFunction = async () => {
61
- state.active = false;
62
- state.resolving = false;
63
- cancelFunction = null;
64
- return awaitablePromise.cancel();
65
- };
96
+ // if any guards are true, delay the watch.
97
+ if (guardArguments && !isEmpty(guardArguments) && Object.values(guardArguments).some(identity)) {
98
+ delayedWatch = doIntentWatch;
99
+ return;
100
+ }
101
+ doIntentWatch();
102
+ }
103
+ };
104
+
105
+ const guardWatch = async () => {
106
+ if (delayedWatch) {
107
+ // if all guards are false, run the watch
108
+ if (Object.values(guardArguments).every((x) => !x)) {
109
+ const myDelayedWatch = delayedWatch;
110
+ delayedWatch = null;
111
+ await myDelayedWatch();
66
112
  }
67
- awaitablePromise
68
- .then(() => {
69
- state.resolving = false;
70
- if (state.clearActiveOnResolved) {
71
- cancelFunction = null;
72
- state.active = false;
73
- }
74
- })
75
- .catch(async (err) => {
76
- await cancel();
77
- console.error(err);
78
- state.errored = true;
79
- state.error = err;
80
- throw err;
81
- });
82
113
  }
83
114
  };
84
115
 
85
116
  es.run(() => {
86
- watch(() => Object.values(watchArguments), watchFn, {
117
+ state.active = computed(() => {
118
+ if (state.activeCount === undefined) {
119
+ return undefined;
120
+ }
121
+ return state.activeCount > 0;
122
+ });
123
+ state.resolving = computed(() => {
124
+ if (state.resolvingCount === undefined) {
125
+ return undefined;
126
+ }
127
+ return state.resolvingCount > 0;
128
+ });
129
+
130
+ watch(() => Object.values(watchArguments), intentWatch, {
87
131
  // this can't be immediate because subscribe wants to look at our state, which won't exist yet.
88
132
  deep: true,
89
133
  });
90
134
 
91
- nextTick().then(watchFn);
135
+ watch(() => Object.values(guardArguments), guardWatch, {
136
+ // we can't possibly have a delayed watch immediately
137
+ deep: true,
138
+ });
139
+
140
+ nextTick().then(intentWatch);
92
141
 
93
142
  onScopeDispose(cancel);
94
143
  });
@@ -114,6 +114,7 @@ export function useListInstance({ props, functions = {} }) {
114
114
  });
115
115
 
116
116
  const defaultPageCallback = (newObjects) => {
117
+ clearList();
117
118
  newObjects.forEach((newObject) => {
118
119
  if (newObject.id in state.objects) {
119
120
  updateListObject(newObject);
@@ -139,11 +140,10 @@ export function useListInstance({ props, functions = {} }) {
139
140
  listArgs: state.listArgs,
140
141
  pageCallback: returnedObject.pageCallback,
141
142
  });
143
+ let resolveState = false;
142
144
  if (listPromise.cancel) {
143
145
  returnPromise.cancel = async () => {
144
146
  let promise = listPromise.cancel();
145
- state.loading = false;
146
- returnPromiseResolve(false);
147
147
  if (promise) {
148
148
  await promise;
149
149
  }
@@ -152,14 +152,15 @@ export function useListInstance({ props, functions = {} }) {
152
152
  // the indirection of promises here is to allow us to do additional work on listPromise's cancel
153
153
  listPromise
154
154
  .then(() => {
155
- state.loading = false;
156
- returnPromiseResolve(true);
155
+ resolveState = true;
157
156
  })
158
157
  .catch((error) => {
159
- state.loading = false;
160
158
  state.errored = true;
161
159
  state.error = error;
162
- returnPromiseResolve(false);
160
+ })
161
+ .finally(() => {
162
+ state.loading = false;
163
+ returnPromiseResolve(resolveState);
163
164
  });
164
165
  return returnPromise;
165
166
  }
@@ -196,15 +196,17 @@ export function useListSubscription({ listInstance, props, functions }) {
196
196
  state.subscribed = toRef(subscribeIntent.state, "active");
197
197
 
198
198
  listIntent = useCancellableIntent({
199
- awaitableWithCancel: () => {
200
- listInstance.clearList();
201
- return listInstance.list();
202
- },
199
+ awaitableWithCancel: listInstance.list,
203
200
  watchArguments: reactive({
204
201
  intendToList: toRef(state, "intendToList"),
205
202
  listArgs: toRef(parentState, "listArgs"),
206
203
  retrieveArgs: toRef(parentState, "retrieveArgs"),
207
204
  }),
205
+ // delay triggering a list until the last list has finished/cancelled
206
+ // cancel can still be triggered
207
+ guardArguments: reactive({
208
+ loading: toRef(parentState, "loading"),
209
+ }),
208
210
  });
209
211
  });
210
212
 
@@ -7,14 +7,27 @@ export function usePagedListInstance({ keepOldPages = false, ...useListInstanceA
7
7
  listInstance.state.totalPages = 0;
8
8
  listInstance.state.perPage = 0;
9
9
 
10
+ const superClearList = listInstance.clearList;
11
+ listInstance.clearList = () => {
12
+ superClearList();
13
+ listInstance.state.totalRecords = 0;
14
+ listInstance.state.totalPages = 0;
15
+ listInstance.state.perPage = 0;
16
+ };
17
+
10
18
  listInstance.pageCallback = (newObjects, { totalRecords, totalPages, perPage }) => {
11
19
  // with keepOldPages, you are responsible for clearing the list as needed
12
20
  if (!keepOldPages) {
13
21
  // display one page at a time, clear the list
14
22
  listInstance.clearList();
15
23
  }
16
-
17
- listInstance.defaultPageCallback(newObjects);
24
+ newObjects.forEach((newObject) => {
25
+ if (newObject.id in listInstance.state.objects) {
26
+ listInstance.updateListObject(newObject);
27
+ } else {
28
+ listInstance.addListObject(newObject);
29
+ }
30
+ });
18
31
  if (totalRecords !== undefined) {
19
32
  listInstance.state.totalRecords = totalRecords;
20
33
  }