@arrai-innovations/reactive-helpers 1.2.3 → 2.0.1
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/.lintstagedrc +3 -0
- package/README.md +21 -16
- package/package.json +1 -1
- package/tests/unit/crudPromise.js +23 -0
- package/tests/unit/poll.js +50 -0
- package/tests/unit/use/listInstance.spec.js +47 -89
- package/tests/unit/use/listSort.spec.js +1 -1
- package/tests/unit/use/listSubscription.spec.js +184 -104
- package/tests/unit/use/objectInstance.spec.js +156 -147
- package/tests/unit/use/objectSubscription.spec.js +144 -120
- package/tests/unit/use/watches.spec.js +2 -2
- package/use/listInstance.js +39 -28
- package/use/listSubscription.js +83 -34
- package/use/objectInstance.js +34 -43
- package/use/objectSubscription.js +66 -71
- package/use/paginatedListInstance.js +2 -2
- package/utils/cancellableIntent.js +93 -0
- package/utils/index.js +1 -0
package/use/listInstance.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { cloneDeep
|
|
2
|
-
import { computed, effectScope, reactive
|
|
1
|
+
import { cloneDeep } from "lodash";
|
|
2
|
+
import { computed, effectScope, reactive } from "vue";
|
|
3
3
|
import { assignReactiveObject } from "../utils/assignReactiveObject";
|
|
4
4
|
import { getFakeId } from "../utils/getFakeId";
|
|
5
5
|
import inspect from "browser-util-inspect";
|
|
@@ -30,23 +30,24 @@ export function useListInstances(listInstanceArgs) {
|
|
|
30
30
|
return instances;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
export function useListInstance({ crudArgs,
|
|
33
|
+
export function useListInstance({ crudArgs, listArgs = {}, retrieveArgs = {} }) {
|
|
34
34
|
const state = reactive({
|
|
35
|
-
|
|
35
|
+
crud: {
|
|
36
36
|
args: {},
|
|
37
37
|
list: undefined,
|
|
38
38
|
},
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
retrieveArgs,
|
|
40
|
+
listArgs,
|
|
41
41
|
objects: {},
|
|
42
42
|
loading: undefined,
|
|
43
43
|
errored: false,
|
|
44
44
|
error: null,
|
|
45
45
|
order: [],
|
|
46
46
|
});
|
|
47
|
-
|
|
47
|
+
// prevent linking of all instances to the same default .args object
|
|
48
|
+
Object.assign(state.crud, cloneDeep(defaultCrud));
|
|
48
49
|
if (crudArgs) {
|
|
49
|
-
assignReactiveObject(state.
|
|
50
|
+
assignReactiveObject(state.crud.args, crudArgs);
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
const defaultPageCallback = (newObjects) => {
|
|
@@ -59,37 +60,47 @@ export function useListInstance({ crudArgs, defaultListArgs = {}, defaultRetriev
|
|
|
59
60
|
});
|
|
60
61
|
};
|
|
61
62
|
|
|
62
|
-
|
|
63
|
+
function list() {
|
|
64
|
+
// this function cannot be async, or the resulting promise will lose its .cancel() method
|
|
63
65
|
if (state.loading) {
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
if (isEmpty(retrieveArgs) && !isEmpty(unref(state.defaultRetrieveArgs))) {
|
|
67
|
-
retrieveArgs = unref(state.defaultRetrieveArgs);
|
|
68
|
-
}
|
|
69
|
-
if (isEmpty(listArgs) && !isEmpty(unref(state.defaultListArgs))) {
|
|
70
|
-
listArgs = unref(state.defaultListArgs);
|
|
66
|
+
return Promise.reject(new ListError("already loading."));
|
|
71
67
|
}
|
|
68
|
+
let returnPromiseResolve;
|
|
69
|
+
const returnPromise = new Promise((resolve) => {
|
|
70
|
+
returnPromiseResolve = resolve;
|
|
71
|
+
});
|
|
72
72
|
state.loading = true;
|
|
73
73
|
state.errored = false;
|
|
74
74
|
state.error = null;
|
|
75
|
-
|
|
76
|
-
.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
75
|
+
const listPromise = state.crud.list({
|
|
76
|
+
crudArgs: state.crud.args,
|
|
77
|
+
retrieveArgs: state.retrieveArgs,
|
|
78
|
+
listArgs: state.listArgs,
|
|
79
|
+
pageCallback: returnedObject.pageCallback,
|
|
80
|
+
});
|
|
81
|
+
if (listPromise.cancel) {
|
|
82
|
+
returnPromise.cancel = async () => {
|
|
83
|
+
let promise = listPromise.cancel();
|
|
84
|
+
state.loading = false;
|
|
85
|
+
returnPromiseResolve(false);
|
|
86
|
+
if (promise) {
|
|
87
|
+
await promise;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
// the indirection of promises here is to allow us to do additional work on listPromise's cancel
|
|
92
|
+
listPromise
|
|
82
93
|
.then(() => {
|
|
83
|
-
|
|
94
|
+
state.loading = false;
|
|
95
|
+
returnPromiseResolve(true);
|
|
84
96
|
})
|
|
85
97
|
.catch((error) => {
|
|
98
|
+
state.loading = false;
|
|
86
99
|
state.errored = true;
|
|
87
100
|
state.error = error;
|
|
88
|
-
|
|
89
|
-
})
|
|
90
|
-
.finally(() => {
|
|
91
|
-
state.loading = false;
|
|
101
|
+
returnPromiseResolve(false);
|
|
92
102
|
});
|
|
103
|
+
return returnPromise;
|
|
93
104
|
}
|
|
94
105
|
|
|
95
106
|
function addListObject(object) {
|
package/use/listSubscription.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { computed, effectScope, reactive, toRef } from "vue";
|
|
2
2
|
import { useListInstance } from "./listInstance";
|
|
3
3
|
import { cloneDeep, isEmpty, isObject } from "lodash";
|
|
4
4
|
import { assignReactiveObject } from "../utils/assignReactiveObject";
|
|
5
5
|
import inspect from "browser-util-inspect";
|
|
6
|
+
import { useCancellableIntent } from "../utils/cancellableIntent";
|
|
6
7
|
|
|
7
8
|
export class ListSubscriptionError extends Error {
|
|
8
9
|
constructor(message) {
|
|
@@ -12,11 +13,13 @@ export class ListSubscriptionError extends Error {
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
const defaultCrud = {
|
|
16
|
+
args: {},
|
|
15
17
|
subscribe: undefined,
|
|
16
18
|
};
|
|
17
19
|
|
|
18
|
-
export function setListSubscriptionCrud({ subscribe }) {
|
|
20
|
+
export function setListSubscriptionCrud({ subscribe, args = {} }) {
|
|
19
21
|
defaultCrud.subscribe = subscribe;
|
|
22
|
+
Object.assign(defaultCrud.args, args);
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
export function useListSubscriptions(args, listInstances = {}) {
|
|
@@ -27,35 +30,41 @@ export function useListSubscriptions(args, listInstances = {}) {
|
|
|
27
30
|
return subscriptions;
|
|
28
31
|
}
|
|
29
32
|
|
|
30
|
-
export function useListSubscription({ listInstance, crudArgs,
|
|
33
|
+
export function useListSubscription({ listInstance, crudArgs, listArgs, retrieveArgs }) {
|
|
31
34
|
if (!listInstance) {
|
|
32
|
-
listInstance = useListInstance({ crudArgs,
|
|
35
|
+
listInstance = useListInstance({ crudArgs, listArgs, retrieveArgs });
|
|
33
36
|
}
|
|
34
|
-
let
|
|
37
|
+
let subscribeIntent, listIntent;
|
|
35
38
|
const state = reactive({
|
|
36
|
-
|
|
39
|
+
crud: {
|
|
40
|
+
args: {},
|
|
41
|
+
subscribe: undefined,
|
|
42
|
+
},
|
|
43
|
+
subscriptionLoading: undefined,
|
|
44
|
+
subscriptionErrored: false,
|
|
45
|
+
subscriptionError: null,
|
|
46
|
+
intendToList: false,
|
|
37
47
|
intendToSubscribe: false,
|
|
38
48
|
});
|
|
39
|
-
|
|
49
|
+
// prevent linking of all instances to the same default .args object
|
|
50
|
+
Object.assign(state.crud, cloneDeep(defaultCrud));
|
|
51
|
+
if (crudArgs) {
|
|
52
|
+
assignReactiveObject(state.crud.args, crudArgs);
|
|
53
|
+
}
|
|
40
54
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
retrieveArgs = cloneDeep(listInstance.state.defaultRetrieveArgs);
|
|
55
|
+
function publicSubscribe({ list = true } = {}) {
|
|
56
|
+
let didSubscribe = false;
|
|
57
|
+
if (!state.intendToSubscribe) {
|
|
58
|
+
state.intendToSubscribe = true;
|
|
59
|
+
didSubscribe = true;
|
|
47
60
|
}
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
subscriptionEventCallback,
|
|
54
|
-
});
|
|
55
|
-
state.subscribed = true;
|
|
56
|
-
return true;
|
|
61
|
+
if (list) {
|
|
62
|
+
if (!state.intendToList) {
|
|
63
|
+
state.intendToList = true;
|
|
64
|
+
didSubscribe = true;
|
|
65
|
+
}
|
|
57
66
|
}
|
|
58
|
-
return
|
|
67
|
+
return didSubscribe;
|
|
59
68
|
}
|
|
60
69
|
|
|
61
70
|
function subscriptionEventCallback(data, action) {
|
|
@@ -72,14 +81,17 @@ export function useListSubscription({ listInstance, crudArgs, defaultListArgs, d
|
|
|
72
81
|
}
|
|
73
82
|
}
|
|
74
83
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
state.
|
|
79
|
-
|
|
80
|
-
return returnValue;
|
|
84
|
+
function publicUnsubscribe() {
|
|
85
|
+
let didUnsubscribe = false;
|
|
86
|
+
if (state.intendToSubscribe) {
|
|
87
|
+
state.intendToSubscribe = false;
|
|
88
|
+
didUnsubscribe = true;
|
|
81
89
|
}
|
|
82
|
-
|
|
90
|
+
if (state.intendToList) {
|
|
91
|
+
state.intendToList = false;
|
|
92
|
+
didUnsubscribe = true;
|
|
93
|
+
}
|
|
94
|
+
return didUnsubscribe;
|
|
83
95
|
}
|
|
84
96
|
|
|
85
97
|
function addFromSubscription(data) {
|
|
@@ -128,19 +140,56 @@ export function useListSubscription({ listInstance, crudArgs, defaultListArgs, d
|
|
|
128
140
|
const es = effectScope();
|
|
129
141
|
|
|
130
142
|
es.run(() => {
|
|
143
|
+
state.loading = computed(() => listInstance.state.loading || state.subscriptionLoading);
|
|
144
|
+
state.errored = computed(() => listInstance.state.errored || state.subscriptionErrored);
|
|
145
|
+
state.error = computed(() => listInstance.state.error || state.subscriptionError);
|
|
146
|
+
|
|
147
|
+
state.retrieveArgs = computed(() => listInstance.state.retrieveArgs);
|
|
148
|
+
state.listArgs = computed(() => listInstance.state.listArgs);
|
|
131
149
|
state.objects = toRef(listInstance.state, "objects");
|
|
132
150
|
state.order = toRef(listInstance.state, "order");
|
|
133
151
|
state.objectsInOrder = toRef(listInstance.state, "objectsInOrder");
|
|
134
|
-
|
|
135
|
-
|
|
152
|
+
|
|
153
|
+
subscribeIntent = useCancellableIntent({
|
|
154
|
+
awaitableWithCancel: () => {
|
|
155
|
+
// this function cannot be async, or the resulting promise will lose its .cancel() method
|
|
156
|
+
const subscribePromise = state.crud.subscribe({
|
|
157
|
+
crudArgs: cloneDeep(state.crud.args),
|
|
158
|
+
listArgs: cloneDeep(listInstance.state.listArgs),
|
|
159
|
+
retrieveArgs: cloneDeep(listInstance.state.retrieveArgs),
|
|
160
|
+
subscriptionEventCallback,
|
|
161
|
+
});
|
|
162
|
+
// catching makes a new promise, we need to make sure the cancel method lives on.
|
|
163
|
+
const catchPromise = subscribePromise.catch((err) => {
|
|
164
|
+
console.error(err);
|
|
165
|
+
state.subscriptionErrored = true;
|
|
166
|
+
state.subscriptionError = err;
|
|
167
|
+
});
|
|
168
|
+
catchPromise.cancel = subscribePromise.cancel;
|
|
169
|
+
return catchPromise;
|
|
170
|
+
},
|
|
171
|
+
watchArguments: [toRef(state, "intendToSubscribe"), toRef(state, "listArgs"), toRef(state, "retrieveArgs")],
|
|
172
|
+
clearActiveOnResolved: false,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
state.subscribed = toRef(subscribeIntent.state, "active");
|
|
176
|
+
|
|
177
|
+
listIntent = useCancellableIntent({
|
|
178
|
+
awaitableWithCancel: () => {
|
|
179
|
+
listInstance.clearList();
|
|
180
|
+
return listInstance.list();
|
|
181
|
+
},
|
|
182
|
+
watchArguments: [toRef(state, "intendToList"), toRef(state, "listArgs"), toRef(state, "retrieveArgs")],
|
|
136
183
|
});
|
|
137
184
|
});
|
|
138
185
|
|
|
139
186
|
return {
|
|
140
187
|
state,
|
|
141
188
|
listInstance,
|
|
142
|
-
|
|
143
|
-
|
|
189
|
+
listIntent,
|
|
190
|
+
subscribeIntent,
|
|
191
|
+
subscribe: publicSubscribe,
|
|
192
|
+
unsubscribe: publicUnsubscribe,
|
|
144
193
|
effectScope: es,
|
|
145
194
|
};
|
|
146
195
|
}
|
package/use/objectInstance.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { cloneDeep
|
|
2
|
-
import { effectScope, reactive
|
|
1
|
+
import { cloneDeep } from "lodash";
|
|
2
|
+
import { effectScope, reactive } from "vue";
|
|
3
3
|
import { assignReactiveObject } from "../utils/assignReactiveObject";
|
|
4
4
|
|
|
5
5
|
export class ObjectError extends Error {
|
|
@@ -24,7 +24,7 @@ export function setObjectInstanceCrud({ retrieve, create, update, patch, delete:
|
|
|
24
24
|
defaultCrud.update = update;
|
|
25
25
|
defaultCrud.patch = patch;
|
|
26
26
|
defaultCrud.delete = deleteFn;
|
|
27
|
-
|
|
27
|
+
Object.assign(defaultCrud.args, args);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export function useObjectInstances(instanceArgs) {
|
|
@@ -35,9 +35,9 @@ export function useObjectInstances(instanceArgs) {
|
|
|
35
35
|
return instances;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
export function useObjectInstance({ crudArgs, retrieveArgs }) {
|
|
38
|
+
export function useObjectInstance({ crudArgs, id, retrieveArgs }) {
|
|
39
39
|
const state = reactive({
|
|
40
|
-
|
|
40
|
+
crud: {
|
|
41
41
|
args: {},
|
|
42
42
|
retrieve: undefined,
|
|
43
43
|
create: undefined,
|
|
@@ -46,32 +46,32 @@ export function useObjectInstance({ crudArgs, retrieveArgs }) {
|
|
|
46
46
|
delete: undefined,
|
|
47
47
|
},
|
|
48
48
|
object: {},
|
|
49
|
-
|
|
49
|
+
id,
|
|
50
|
+
retrieveArgs,
|
|
50
51
|
loading: undefined,
|
|
51
52
|
errored: false,
|
|
52
53
|
error: null,
|
|
53
54
|
deleted: false,
|
|
54
55
|
});
|
|
55
|
-
|
|
56
|
+
// prevent linking of all instances to the same default .args object
|
|
57
|
+
Object.assign(state.crud, cloneDeep(defaultCrud));
|
|
56
58
|
if (crudArgs) {
|
|
57
|
-
assignReactiveObject(state.
|
|
59
|
+
assignReactiveObject(state.crud.args, crudArgs);
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
|
|
62
|
+
function retrieve() {
|
|
63
|
+
// this function cannot be async, or the resulting promise will lose its .cancel() method
|
|
61
64
|
if (state.loading) {
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
if (isEmpty(retrieveArgs) && !isEmpty(unref(state.defaultRetrieveArgs))) {
|
|
65
|
-
retrieveArgs = unref(state.defaultRetrieveArgs);
|
|
65
|
+
return Promise.reject(new ObjectError("already loading."));
|
|
66
66
|
}
|
|
67
67
|
state.loading = true;
|
|
68
68
|
state.errored = false;
|
|
69
69
|
state.error = null;
|
|
70
|
-
return state.
|
|
70
|
+
return state.crud
|
|
71
71
|
.retrieve({
|
|
72
|
-
crudArgs: state.
|
|
73
|
-
id,
|
|
74
|
-
retrieveArgs,
|
|
72
|
+
crudArgs: state.crud.args,
|
|
73
|
+
id: state.id,
|
|
74
|
+
retrieveArgs: state.retrieveArgs,
|
|
75
75
|
})
|
|
76
76
|
.then((object) => {
|
|
77
77
|
assignReactiveObject(state.object, object);
|
|
@@ -87,21 +87,18 @@ export function useObjectInstance({ crudArgs, retrieveArgs }) {
|
|
|
87
87
|
});
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
async function create({ object
|
|
90
|
+
async function create({ object }) {
|
|
91
91
|
if (state.loading) {
|
|
92
92
|
throw new ObjectError("already loading.");
|
|
93
93
|
}
|
|
94
|
-
if (isEmpty(retrieveArgs) && !isEmpty(unref(state.defaultRetrieveArgs))) {
|
|
95
|
-
retrieveArgs = unref(state.defaultRetrieveArgs);
|
|
96
|
-
}
|
|
97
94
|
state.loading = true;
|
|
98
95
|
state.errored = false;
|
|
99
96
|
state.error = null;
|
|
100
|
-
return state.
|
|
97
|
+
return state.crud
|
|
101
98
|
.create({
|
|
102
|
-
crudArgs: state.
|
|
99
|
+
crudArgs: state.crud.args,
|
|
103
100
|
object,
|
|
104
|
-
retrieveArgs,
|
|
101
|
+
retrieveArgs: state.retrieveArgs,
|
|
105
102
|
})
|
|
106
103
|
.then((object) => {
|
|
107
104
|
assignReactiveObject(state.object, object);
|
|
@@ -117,21 +114,18 @@ export function useObjectInstance({ crudArgs, retrieveArgs }) {
|
|
|
117
114
|
});
|
|
118
115
|
}
|
|
119
116
|
|
|
120
|
-
async function update({ object
|
|
117
|
+
async function update({ object }) {
|
|
121
118
|
if (state.loading) {
|
|
122
119
|
throw new ObjectError("already loading.");
|
|
123
120
|
}
|
|
124
|
-
if (isEmpty(retrieveArgs) && !isEmpty(unref(state.defaultRetrieveArgs))) {
|
|
125
|
-
retrieveArgs = unref(state.defaultRetrieveArgs);
|
|
126
|
-
}
|
|
127
121
|
state.loading = true;
|
|
128
122
|
state.errored = false;
|
|
129
123
|
state.error = null;
|
|
130
|
-
return state.
|
|
124
|
+
return state.crud
|
|
131
125
|
.update({
|
|
132
|
-
crudArgs: state.
|
|
126
|
+
crudArgs: state.crud.args,
|
|
133
127
|
object,
|
|
134
|
-
retrieveArgs,
|
|
128
|
+
retrieveArgs: state.retrieveArgs,
|
|
135
129
|
})
|
|
136
130
|
.then((object) => {
|
|
137
131
|
assignReactiveObject(state.object, object);
|
|
@@ -147,22 +141,19 @@ export function useObjectInstance({ crudArgs, retrieveArgs }) {
|
|
|
147
141
|
});
|
|
148
142
|
}
|
|
149
143
|
|
|
150
|
-
async function patch({
|
|
144
|
+
async function patch({ partialObject }) {
|
|
151
145
|
if (state.loading) {
|
|
152
146
|
throw new ObjectError("already loading.");
|
|
153
147
|
}
|
|
154
|
-
if (isEmpty(retrieveArgs) && !isEmpty(unref(state.defaultRetrieveArgs))) {
|
|
155
|
-
retrieveArgs = unref(state.defaultRetrieveArgs);
|
|
156
|
-
}
|
|
157
148
|
state.loading = true;
|
|
158
149
|
state.errored = false;
|
|
159
150
|
state.error = null;
|
|
160
|
-
return state.
|
|
151
|
+
return state.crud
|
|
161
152
|
.patch({
|
|
162
|
-
crudArgs: state.
|
|
163
|
-
id,
|
|
153
|
+
crudArgs: state.crud.args,
|
|
154
|
+
id: state.id,
|
|
164
155
|
partialObject,
|
|
165
|
-
retrieveArgs,
|
|
156
|
+
retrieveArgs: state.retrieveArgs,
|
|
166
157
|
})
|
|
167
158
|
.then((object) => {
|
|
168
159
|
assignReactiveObject(state.object, object);
|
|
@@ -178,17 +169,17 @@ export function useObjectInstance({ crudArgs, retrieveArgs }) {
|
|
|
178
169
|
});
|
|
179
170
|
}
|
|
180
171
|
|
|
181
|
-
async function deleteFn(
|
|
172
|
+
async function deleteFn() {
|
|
182
173
|
if (state.loading) {
|
|
183
174
|
throw new ObjectError("already loading.");
|
|
184
175
|
}
|
|
185
176
|
state.loading = true;
|
|
186
177
|
state.errored = false;
|
|
187
178
|
state.error = null;
|
|
188
|
-
return state.
|
|
179
|
+
return state.crud
|
|
189
180
|
.delete({
|
|
190
|
-
crudArgs: state.
|
|
191
|
-
id,
|
|
181
|
+
crudArgs: state.crud.args,
|
|
182
|
+
id: state.id,
|
|
192
183
|
})
|
|
193
184
|
.then(() => {
|
|
194
185
|
state.deleted = true;
|