@arrai-innovations/reactive-helpers 9.0.2 → 10.0.0
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/.idea/inspectionProfiles/Project_Default.xml +1 -0
- package/README.md +405 -65
- package/config/index.js +2 -0
- package/config/listCrud.js +37 -0
- package/config/objectCrud.js +45 -0
- package/docs.md +169 -14
- package/index.js +1 -0
- package/package.json +1 -1
- package/tests/unit/config/index.spec.js +18 -0
- package/tests/unit/config/listCrud.spec.js +107 -0
- package/tests/unit/config/objectCrud.spec.js +155 -0
- package/tests/unit/use/cancellableIntent.spec.js +1 -1
- package/tests/unit/use/listInstance.spec.js +5 -4
- package/tests/unit/use/listSubscription.spec.js +3 -5
- package/use/list.js +48 -14
- package/use/listCalculated.js +3 -0
- package/use/listFilter.js +40 -0
- package/use/listInstance.js +80 -27
- package/use/listRelated.js +5 -1
- package/use/listSort.js +34 -1
- package/use/listSubscription.js +56 -14
- package/use/object.js +26 -5
- package/use/objectCalculated.js +26 -39
- package/use/objectInstance.js +19 -35
- package/use/objectRelated.js +24 -34
- package/use/objectSubscription.js +25 -28
|
@@ -21,6 +21,7 @@ describe("use/listSubscription.spec.js", function () {
|
|
|
21
21
|
crudSubscribeResolvable = [];
|
|
22
22
|
crudListResolvable.push(new CancellableResolvable());
|
|
23
23
|
crudSubscribeResolvable.push(new CancellableResolvable());
|
|
24
|
+
const listCrudModule = await import("../../../config/listCrud.js");
|
|
24
25
|
const listInstanceModule = await import("../../../use/listInstance");
|
|
25
26
|
crudList = vi
|
|
26
27
|
.fn()
|
|
@@ -30,8 +31,9 @@ describe("use/listSubscription.spec.js", function () {
|
|
|
30
31
|
crudListResolvable.push(newResolvable);
|
|
31
32
|
return newResolvable.promise;
|
|
32
33
|
});
|
|
33
|
-
|
|
34
|
+
listCrudModule.setListCrud({
|
|
34
35
|
list: crudList,
|
|
36
|
+
subscribe: crudSubscribe,
|
|
35
37
|
args: { stream: "test_stream" },
|
|
36
38
|
});
|
|
37
39
|
const listSubscriptionModule = await import("../../../use/listSubscription");
|
|
@@ -49,10 +51,6 @@ describe("use/listSubscription.spec.js", function () {
|
|
|
49
51
|
passedSubscriptionEventCallback = subscriptionEventCallback;
|
|
50
52
|
return newResolvable.promise;
|
|
51
53
|
});
|
|
52
|
-
listSubscriptionModule.setListSubscriptionCrud({
|
|
53
|
-
subscribe: crudSubscribe,
|
|
54
|
-
args: { stream: "test_stream" },
|
|
55
|
-
});
|
|
56
54
|
|
|
57
55
|
useListInstance = listInstanceModule.useListInstance;
|
|
58
56
|
useListInstances = listInstanceModule.useListInstances;
|
package/use/list.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { useListCalculated } from "./listCalculated.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { useListCalculated, listCalculatedFunctions } from "./listCalculated.js";
|
|
2
|
+
import { useListFilter, listFilterFunctions } from "./listFilter.js";
|
|
3
|
+
import { useListInstance, listInstanceFunctions } from "./listInstance.js";
|
|
4
|
+
import { useListRelated, listRelatedFunctions } from "./listRelated.js";
|
|
5
|
+
import { useListSort, listSortFunctions } from "./listSort.js";
|
|
6
|
+
import { useListSubscription, listSubscriptionFunctions } from "./listSubscription.js";
|
|
5
7
|
import { usePagedListInstance } from "./paginatedListInstance.js";
|
|
6
8
|
import { effectScope, reactive, shallowReactive, shallowReadonly, toRef } from "vue";
|
|
7
9
|
|
|
@@ -42,6 +44,7 @@ export const useList = ({ props, functions, paged = false, keepOldPages = false
|
|
|
42
44
|
listInstance: managed.listInstance,
|
|
43
45
|
});
|
|
44
46
|
managed.listSubscription.state.intendToList = toRef(props, "intendToList");
|
|
47
|
+
managed.listSubscription.state.intendToSubscribe = toRef(props, "intendToSubscribe");
|
|
45
48
|
|
|
46
49
|
managed.listRelated = useListRelated({
|
|
47
50
|
parentState: managed.listSubscription.state,
|
|
@@ -52,6 +55,23 @@ export const useList = ({ props, functions, paged = false, keepOldPages = false
|
|
|
52
55
|
parentState: managed.listRelated.state,
|
|
53
56
|
calculatedObjectsRules: toRef(props, "calculatedObjectsRules"),
|
|
54
57
|
});
|
|
58
|
+
|
|
59
|
+
managed.listFilter = useListFilter({
|
|
60
|
+
parentState: managed.listCalculated.state,
|
|
61
|
+
useTextSearch: toRef(props, "useTextSearch"),
|
|
62
|
+
textSearchRules: toRef(props, "textSearchRules"),
|
|
63
|
+
textSearchValue: toRef(props, "textSearchValue"),
|
|
64
|
+
allowedValues: toRef(props, "allowedValues"),
|
|
65
|
+
excludedValues: toRef(props, "excludedValues"),
|
|
66
|
+
allowedFilter: toRef(props, "allowedFilter"),
|
|
67
|
+
excludedFilter: toRef(props, "excludedFilter"),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
managed.listSort = useListSort({
|
|
71
|
+
parentState: managed.listFilter.state,
|
|
72
|
+
orderByRules: toRef(props, "orderByRules"),
|
|
73
|
+
sortThrottleWait: functions.sortThrottleWait,
|
|
74
|
+
});
|
|
55
75
|
});
|
|
56
76
|
|
|
57
77
|
const clearError = (error) => {
|
|
@@ -59,18 +79,32 @@ export const useList = ({ props, functions, paged = false, keepOldPages = false
|
|
|
59
79
|
managed.listInstance.clearError(error);
|
|
60
80
|
};
|
|
61
81
|
|
|
62
|
-
|
|
82
|
+
const returnObject = reactive({
|
|
63
83
|
// we manage the keys on both of these, so hands off the root.
|
|
64
84
|
managed: shallowReadonly(managed),
|
|
65
|
-
state: managed.
|
|
66
|
-
list: managed.listInstance.list,
|
|
67
|
-
addListObject: managed.listInstance.addListObject,
|
|
68
|
-
updateListObject: managed.listInstance.updateListObject,
|
|
69
|
-
deleteListObject: managed.listInstance.deleteListObject,
|
|
70
|
-
clearList: managed.listInstance.clearList,
|
|
71
|
-
clearError,
|
|
72
|
-
getFakeId: managed.listInstance.getFakeId,
|
|
73
|
-
defaultPageCallback: managed.listInstance.defaultPageCallback,
|
|
85
|
+
state: managed.listSort.state,
|
|
74
86
|
effectScope: es,
|
|
75
87
|
});
|
|
88
|
+
const handledDuplicateFunctions = {
|
|
89
|
+
clearError,
|
|
90
|
+
};
|
|
91
|
+
for (const [source, fnNames] of [
|
|
92
|
+
[managed.listInstance, listInstanceFunctions],
|
|
93
|
+
[managed.listSubscription, listSubscriptionFunctions],
|
|
94
|
+
[managed.listRelated, listRelatedFunctions],
|
|
95
|
+
[managed.listCalculated, listCalculatedFunctions],
|
|
96
|
+
[managed.listFilter, listFilterFunctions],
|
|
97
|
+
[managed.listSort, listSortFunctions],
|
|
98
|
+
]) {
|
|
99
|
+
for (const fnName of fnNames) {
|
|
100
|
+
if (handledDuplicateFunctions[fnName]) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
returnObject[fnName] = source[fnName];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
for (const [fnName, fn] of Object.entries(handledDuplicateFunctions)) {
|
|
107
|
+
returnObject[fnName] = fn;
|
|
108
|
+
}
|
|
109
|
+
return returnObject;
|
|
76
110
|
};
|
package/use/listCalculated.js
CHANGED
|
@@ -13,6 +13,8 @@ export const listCalculatedStateKeys = [
|
|
|
13
13
|
"calculatedObjectsWatchRunning",
|
|
14
14
|
];
|
|
15
15
|
|
|
16
|
+
export const listCalculatedFunctions = [];
|
|
17
|
+
|
|
16
18
|
export function useListCalculateds(instances, args) {
|
|
17
19
|
for (const [key, value] of Object.entries(args)) {
|
|
18
20
|
useListCalculated({
|
|
@@ -130,6 +132,7 @@ export function useListCalculated({ parentState, calculatedObjectsRules }) {
|
|
|
130
132
|
],
|
|
131
133
|
});
|
|
132
134
|
|
|
135
|
+
state.calculatedRunning = toRef(watchesRunning.state, "running");
|
|
133
136
|
state.running = computed(() => loadingCombine(watchesRunning.state.running, parentState.running));
|
|
134
137
|
|
|
135
138
|
onScopeDispose(() => {
|
package/use/listFilter.js
CHANGED
|
@@ -1,10 +1,30 @@
|
|
|
1
1
|
import { keyDiff } from "../utils/keyDiff.js";
|
|
2
|
+
import { listCalculatedStateKeys } from "./listCalculated.js";
|
|
3
|
+
import { listInstanceStateKeys } from "./listInstance.js";
|
|
4
|
+
import { listRelatedStateKeys } from "./listRelated.js";
|
|
5
|
+
import { listSubscriptionStateKeys } from "./listSubscription.js";
|
|
2
6
|
import { useSearch } from "./search.js";
|
|
3
7
|
import get from "lodash-es/get.js";
|
|
4
8
|
import identity from "lodash-es/identity.js";
|
|
5
9
|
import isEmpty from "lodash-es/isEmpty.js";
|
|
6
10
|
import { computed, effectScope, onScopeDispose, reactive, toRef, watch, watchEffect } from "vue";
|
|
7
11
|
|
|
12
|
+
export const listFilterStateKeys = [
|
|
13
|
+
"objectIndexes",
|
|
14
|
+
// override but not ours
|
|
15
|
+
// "objects",
|
|
16
|
+
"textSearchRules",
|
|
17
|
+
"textSearchValue",
|
|
18
|
+
"allowedValues",
|
|
19
|
+
"excludedValues",
|
|
20
|
+
"allowedFilter",
|
|
21
|
+
"excludedFilter",
|
|
22
|
+
"searched",
|
|
23
|
+
"searching",
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
export const listFilterFunctions = [];
|
|
27
|
+
|
|
8
28
|
export function useListFilters(listFilterArgs, parentInstances) {
|
|
9
29
|
const filters = {};
|
|
10
30
|
for (const [key, value] of Object.entries(listFilterArgs)) {
|
|
@@ -32,6 +52,8 @@ export function useListFilter({
|
|
|
32
52
|
excludedValues,
|
|
33
53
|
allowedFilter,
|
|
34
54
|
excludedFilter,
|
|
55
|
+
searched: undefined,
|
|
56
|
+
searching: undefined,
|
|
35
57
|
});
|
|
36
58
|
|
|
37
59
|
const es = effectScope();
|
|
@@ -39,6 +61,21 @@ export function useListFilter({
|
|
|
39
61
|
let textSearchIndex;
|
|
40
62
|
|
|
41
63
|
es.run(() => {
|
|
64
|
+
for (const key of listInstanceStateKeys) {
|
|
65
|
+
if (key === "objects") {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
state[key] = toRef(parentState, key);
|
|
69
|
+
}
|
|
70
|
+
for (const key of listSubscriptionStateKeys) {
|
|
71
|
+
state[key] = toRef(parentState, key);
|
|
72
|
+
}
|
|
73
|
+
for (const key of listRelatedStateKeys) {
|
|
74
|
+
state[key] = toRef(parentState, key);
|
|
75
|
+
}
|
|
76
|
+
for (const key of listCalculatedStateKeys) {
|
|
77
|
+
state[key] = toRef(parentState, key);
|
|
78
|
+
}
|
|
42
79
|
if (useTextSearch) {
|
|
43
80
|
textSearchIndex = useSearch();
|
|
44
81
|
textSearchIndex.state.search = toRef(state, "textSearchValue");
|
|
@@ -46,9 +83,11 @@ export function useListFilter({
|
|
|
46
83
|
state.searching = toRef(textSearchIndex.state, "searching");
|
|
47
84
|
}
|
|
48
85
|
|
|
86
|
+
// todo: computed is not the solution here for deep reactions
|
|
49
87
|
state.objectsInOrder = computed(() => parentState.order.map((id) => state.objects[id]).filter(identity));
|
|
50
88
|
state.order = computed(() => state.objectsInOrder.map((object) => `${object.id}`));
|
|
51
89
|
|
|
90
|
+
// todo: this huge watchEffect is fairly gross, but also doesn't watch deep similarly to computed
|
|
52
91
|
watchEffect(() => {
|
|
53
92
|
const allowedValuesEmpty = !state.allowedValues || isEmpty(state.allowedValues);
|
|
54
93
|
const excludedValuesEmpty = !state.excludedValues || isEmpty(state.excludedValues);
|
|
@@ -104,6 +143,7 @@ export function useListFilter({
|
|
|
104
143
|
if (useTextSearch) {
|
|
105
144
|
const stopIndexWatch = {};
|
|
106
145
|
|
|
146
|
+
// todo: this huge watchEffect is fairly gross, but also doesn't watch deep similarly to computed
|
|
107
147
|
watchEffect(() => {
|
|
108
148
|
const { removedKeys, addedKeys } = keyDiff(
|
|
109
149
|
Object.keys(parentState.objects),
|
package/use/listInstance.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getListCrud } from "../config/listCrud.js";
|
|
2
|
+
import { assignReactiveObject } from "../utils/assignReactiveObject.js";
|
|
2
3
|
import { getFakeId } from "../utils/getFakeId.js";
|
|
3
4
|
import inspect from "browser-util-inspect";
|
|
4
|
-
import
|
|
5
|
-
import isFunction from "lodash-es/isFunction.js";
|
|
6
|
-
import { computed, effectScope, reactive, toRef, watchEffect } from "vue";
|
|
5
|
+
import { computed, effectScope, reactive, toRef } from "vue";
|
|
7
6
|
|
|
8
7
|
export class ListError extends Error {
|
|
9
8
|
constructor(message, code) {
|
|
@@ -13,11 +12,6 @@ export class ListError extends Error {
|
|
|
13
12
|
}
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
const defaultCrud = {
|
|
17
|
-
args: {},
|
|
18
|
-
list: undefined,
|
|
19
|
-
};
|
|
20
|
-
|
|
21
15
|
export const listInstanceStateKeys = [
|
|
22
16
|
"crud",
|
|
23
17
|
"retrieveArgs",
|
|
@@ -34,11 +28,39 @@ export const listInstanceStateKeys = [
|
|
|
34
28
|
"perPage",
|
|
35
29
|
];
|
|
36
30
|
|
|
37
|
-
export
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
31
|
+
export const listInstanceFunctions = [
|
|
32
|
+
"list",
|
|
33
|
+
"addListObject",
|
|
34
|
+
"updateListObject",
|
|
35
|
+
"deleteListObject",
|
|
36
|
+
"clearList",
|
|
37
|
+
"clearError",
|
|
38
|
+
"getFakeId",
|
|
39
|
+
"defaultPageCallback",
|
|
40
|
+
"pageCallback",
|
|
41
|
+
];
|
|
41
42
|
|
|
43
|
+
/**
|
|
44
|
+
* The configuration options used to create a list instance.
|
|
45
|
+
* @typedef {object} ListInstanceOptions
|
|
46
|
+
* @property {object} props - props passed to the component
|
|
47
|
+
* @property {object} props.retrieveArgs - the arguments passed to the server
|
|
48
|
+
* @property {object} props.listArgs - the arguments passed to the server
|
|
49
|
+
* @property {object} props.crudArgs - implementation specific arguments
|
|
50
|
+
* @property {object} functions - optional. default implementation are used as set by `setListCrud`.
|
|
51
|
+
* @property {Function} functions.list - provide the implementation for the list function
|
|
52
|
+
* @property {Function} functions.subscribe - provide the implementation for the subscribe function
|
|
53
|
+
* @property {boolean} keepOldPages - if true, pages will not be cleared when defaultPageCallback is called. default is false.
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/* eslint-disable jsdoc/check-types */
|
|
57
|
+
// types valid for jsdoc-to-markdown, which uses the strict jsdoc.app. Object shorthand syntax doesn't work.
|
|
58
|
+
/**
|
|
59
|
+
* A Vue composition function that creates multiple list instances, and returns them as an object.
|
|
60
|
+
* @param {Object.<string, ListInstanceOptions>} listInstanceArgs - each desired list instance options, keyed by an instance name.
|
|
61
|
+
* @returns {Object.<string, ListInstance>} - each list instance, keyed by the instance name.
|
|
62
|
+
*/
|
|
63
|
+
/* eslint-enable jsdoc/check-types */
|
|
42
64
|
export function useListInstances(listInstanceArgs) {
|
|
43
65
|
const instances = {};
|
|
44
66
|
for (const [key, value] of Object.entries(listInstanceArgs)) {
|
|
@@ -47,6 +69,48 @@ export function useListInstances(listInstanceArgs) {
|
|
|
47
69
|
return instances;
|
|
48
70
|
}
|
|
49
71
|
|
|
72
|
+
/**
|
|
73
|
+
* A reactive object that manages a list of objects, as returned by `useListInstance`.
|
|
74
|
+
* @typedef {object} ListInstanceState
|
|
75
|
+
* @property {object} crud - the crud functions
|
|
76
|
+
* @property {object} crud.args - the arguments passed to the crud functions
|
|
77
|
+
* @property {Function} crud.list - the list function
|
|
78
|
+
* @property {Function} crud.subscribe - the subscribe function
|
|
79
|
+
* @property {object} retrieveArgs - the arguments passed to the server
|
|
80
|
+
* @property {object} listArgs - the arguments passed to the server
|
|
81
|
+
* @property {Map} objects - the objects in the list
|
|
82
|
+
* @property {boolean} loading - true if the list is loading
|
|
83
|
+
* @property {boolean} errored - true if the list has errored
|
|
84
|
+
* @property {object} error - the error object
|
|
85
|
+
* @property {Array} objectsInOrder - the objects in the list in order
|
|
86
|
+
* @property {Array} order - the order of the objects in the list
|
|
87
|
+
* @property {number} totalRecords - the total number of records
|
|
88
|
+
* @property {number} totalPages - the total number of pages
|
|
89
|
+
* @property {number} perPage - the number of records per page
|
|
90
|
+
*/
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @typedef {object} ListInstance
|
|
94
|
+
* @property {Function} list - subscribe to updates from the implementation
|
|
95
|
+
* @property {Function} addListObject - add an object to the list
|
|
96
|
+
* @property {Function} updateListObject - update an object in the list
|
|
97
|
+
* @property {Function} deleteListObject - delete an object from the list
|
|
98
|
+
* @property {Function} clearList - clear the list
|
|
99
|
+
* @property {Function} clearError - clear the error
|
|
100
|
+
* @property {Function} getFakeId - get a fake id
|
|
101
|
+
* @property {Function} defaultPageCallback - the default page callback
|
|
102
|
+
* @property {Function} pageCallback - the page callback
|
|
103
|
+
* @property {ListInstanceState} state - the list instance state
|
|
104
|
+
* @property {object} effectScope - a Vue effect scope
|
|
105
|
+
*/
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* `useListInstance` is a Vue composition function that manages a list of objects.
|
|
109
|
+
* It has the ability to retrieve the list from an implementation, or subscribe to updates from an implementation.
|
|
110
|
+
* It tracks the objects in the list, and their added order.
|
|
111
|
+
* @param {ListInstanceOptions} options - the options used to create the list instance
|
|
112
|
+
* @returns {ListInstance} - the list instance
|
|
113
|
+
*/
|
|
50
114
|
export function useListInstance({ props, functions = {}, keepOldPages = false }) {
|
|
51
115
|
if (!props) {
|
|
52
116
|
throw new ListError(`useListInstance requires props`);
|
|
@@ -98,20 +162,8 @@ export function useListInstance({ props, functions = {}, keepOldPages = false })
|
|
|
98
162
|
error: null,
|
|
99
163
|
});
|
|
100
164
|
const es = effectScope();
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
Object.assign(state.crud, cloneDeep(defaultCrud));
|
|
104
|
-
if (props.crudArgs) {
|
|
105
|
-
addOrUpdateReactiveObject(state.crud.args, props.crudArgs);
|
|
106
|
-
}
|
|
107
|
-
for (const [key, value] of Object.entries(functions)) {
|
|
108
|
-
if (isFunction(value) && key in state.crud) {
|
|
109
|
-
state.crud[key] = value;
|
|
110
|
-
} else {
|
|
111
|
-
throw ListError(`Invalid function "${key}" for useListInstance: invalid key or not a function.`);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
});
|
|
165
|
+
|
|
166
|
+
getListCrud(state.crud, { props, functions });
|
|
115
167
|
|
|
116
168
|
const defaultPageCallback = (newObjects) => {
|
|
117
169
|
// with keepOldPages, you are responsible for clearing the list as needed
|
|
@@ -173,6 +225,7 @@ export function useListInstance({ props, functions = {}, keepOldPages = false })
|
|
|
173
225
|
.finally(() => {
|
|
174
226
|
state.loading = false;
|
|
175
227
|
returnPromiseResolve(resolveState);
|
|
228
|
+
promises.list = null;
|
|
176
229
|
});
|
|
177
230
|
return promises.list;
|
|
178
231
|
}
|
package/use/listRelated.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { keyDiff
|
|
1
|
+
import { keyDiff } from "../utils/keyDiff.js";
|
|
2
|
+
import { loadingCombine } from "../utils/loadingCombine.js";
|
|
2
3
|
import { listInstanceStateKeys } from "./listInstance.js";
|
|
3
4
|
import { listSubscriptionStateKeys } from "./listSubscription.js";
|
|
4
5
|
import { useWatchesRunning } from "./watchesRunning.js";
|
|
@@ -15,6 +16,8 @@ export const listRelatedStateKeys = [
|
|
|
15
16
|
"relatedObjectsParentStateObjectsWatchRunning",
|
|
16
17
|
];
|
|
17
18
|
|
|
19
|
+
export const listRelatedFunctions = [];
|
|
20
|
+
|
|
18
21
|
export function useListRelateds(instances, args) {
|
|
19
22
|
for (const [key, value] of Object.entries(args)) {
|
|
20
23
|
useListRelated({
|
|
@@ -130,6 +133,7 @@ export function useListRelated({ parentState, relatedObjectsRules }) {
|
|
|
130
133
|
],
|
|
131
134
|
});
|
|
132
135
|
|
|
136
|
+
state.relatedRunning = toRef(watchesRunning.state, "running");
|
|
133
137
|
state.running = computed(() => loadingCombine(watchesRunning.state.running, parentState.running));
|
|
134
138
|
|
|
135
139
|
onScopeDispose(() => {
|
package/use/listSort.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { assignReactiveObject, keyDiff } from "../utils/index.js";
|
|
2
|
+
import { listCalculatedStateKeys } from "./listCalculated.js";
|
|
3
|
+
import { listFilterStateKeys } from "./listFilter.js";
|
|
4
|
+
import { listInstanceStateKeys } from "./listInstance.js";
|
|
5
|
+
import { listRelatedStateKeys } from "./listRelated.js";
|
|
6
|
+
import { listSubscriptionStateKeys } from "./listSubscription.js";
|
|
2
7
|
import cloneDeep from "lodash-es/cloneDeep.js";
|
|
3
8
|
import get from "lodash-es/get.js";
|
|
4
9
|
import identity from "lodash-es/identity.js";
|
|
@@ -10,6 +15,17 @@ import throttle from "lodash-es/throttle.js";
|
|
|
10
15
|
import zip from "lodash-es/zip.js";
|
|
11
16
|
import { effectScope, onScopeDispose, reactive, toRef, unref, watch } from "vue";
|
|
12
17
|
|
|
18
|
+
export const listSortStateKeys = [
|
|
19
|
+
"orderByRules",
|
|
20
|
+
// "order",
|
|
21
|
+
// "objectsInOrder",
|
|
22
|
+
"sortCriteria",
|
|
23
|
+
"sortCriteriaWatches",
|
|
24
|
+
"orderByDesc",
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export const listSortFunctions = [];
|
|
28
|
+
|
|
13
29
|
const collator = new Intl.Collator(undefined, { numeric: true });
|
|
14
30
|
|
|
15
31
|
const defaultSortThrottleWait = Symbol("defaultSortThrottleWait");
|
|
@@ -161,7 +177,24 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
|
|
|
161
177
|
const throttledSortWatch = throttle(sortWatch, sortThrottleWait);
|
|
162
178
|
|
|
163
179
|
es.run(() => {
|
|
164
|
-
|
|
180
|
+
for (const key of listInstanceStateKeys) {
|
|
181
|
+
if (["order", "objectsInOrder"].includes(key)) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
state[key] = toRef(parentState, key);
|
|
185
|
+
}
|
|
186
|
+
for (const key of listSubscriptionStateKeys) {
|
|
187
|
+
state[key] = toRef(parentState, key);
|
|
188
|
+
}
|
|
189
|
+
for (const key of listRelatedStateKeys) {
|
|
190
|
+
state[key] = toRef(parentState, key);
|
|
191
|
+
}
|
|
192
|
+
for (const key of listCalculatedStateKeys) {
|
|
193
|
+
state[key] = toRef(parentState, key);
|
|
194
|
+
}
|
|
195
|
+
for (const key of listFilterStateKeys) {
|
|
196
|
+
state[key] = toRef(parentState, key);
|
|
197
|
+
}
|
|
165
198
|
// we do not need two immediate watches to the same function.
|
|
166
199
|
watch(() => Object.keys(parentState.objects), sortCriteriaWatch);
|
|
167
200
|
watch(() => cloneDeep(state.orderByRules), sortCriteriaWatch, {
|
package/use/listSubscription.js
CHANGED
|
@@ -14,10 +14,6 @@ export class ListSubscriptionError extends Error {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const defaultCrud = {
|
|
18
|
-
subscribe: undefined,
|
|
19
|
-
};
|
|
20
|
-
|
|
21
17
|
export const listSubscriptionStateKeys = [
|
|
22
18
|
"subscriptionLoading",
|
|
23
19
|
"subscriptionErrored",
|
|
@@ -27,10 +23,25 @@ export const listSubscriptionStateKeys = [
|
|
|
27
23
|
"subscribed",
|
|
28
24
|
];
|
|
29
25
|
|
|
30
|
-
export
|
|
31
|
-
defaultCrud.subscribe = subscribe;
|
|
32
|
-
}
|
|
26
|
+
export const listSubscriptionFunctions = ["subscribe", "unsubscribe", "clearError"];
|
|
33
27
|
|
|
28
|
+
/**
|
|
29
|
+
* The configuration options used to create a list subscription.
|
|
30
|
+
* @typedef {object} ListSubscriptionOptions
|
|
31
|
+
* @property {object} props - passed on to a created list instance if one is not provided
|
|
32
|
+
* @property {object} functions - passed on to a created list instance if one is not provided
|
|
33
|
+
* @property {ListInstance} listInstance - a list instance to use instead of creating one
|
|
34
|
+
* @property {boolean} keepOldPages - if true, pages will not be cleared when defaultPageCallback is called. default is false.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/* eslint-disable jsdoc/check-types */
|
|
38
|
+
// types valid for jsdoc-to-markdown, which uses the strict jsdoc.app. Object shorthand syntax doesn't work.
|
|
39
|
+
/**
|
|
40
|
+
* A Vue composition function that creates multiple list instances, and returns them as an object.
|
|
41
|
+
* @param {Object.<string, ListInstanceOptions>} listInstanceArgs - each desired list instance options, keyed by an instance name.
|
|
42
|
+
* @returns {Object.<string, ListInstance>} - each list instance, keyed by the instance name.
|
|
43
|
+
*/
|
|
44
|
+
/* eslint-enable jsdoc/check-types */
|
|
34
45
|
export function useListSubscriptions(args, listInstances = {}) {
|
|
35
46
|
const subscriptions = {};
|
|
36
47
|
for (const [key, value] of Object.entries(args)) {
|
|
@@ -39,13 +50,43 @@ export function useListSubscriptions(args, listInstances = {}) {
|
|
|
39
50
|
return subscriptions;
|
|
40
51
|
}
|
|
41
52
|
|
|
42
|
-
|
|
53
|
+
/**
|
|
54
|
+
* A reactive object that manages a list of objects, as returned by `useListInstance`.
|
|
55
|
+
* @typedef {object} ListSubscriptionState
|
|
56
|
+
* @augments ListInstanceState
|
|
57
|
+
* @property {boolean} subscriptionLoading - true if the subscription is loading
|
|
58
|
+
* @property {boolean} subscriptionErrored - true if the subscription errored
|
|
59
|
+
* @property {Error} subscriptionError - the error that caused the subscription to error
|
|
60
|
+
* @property {boolean} intendToList - true if the list should be fetched, or refetched when args change
|
|
61
|
+
* @property {boolean} intendToSubscribe - true if the list should subscribe for updates
|
|
62
|
+
* @property {boolean} subscribed - true if the subscription is active
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @typedef {object} ListSubscription
|
|
67
|
+
* @property {ListSubscriptionState} state - the reactive state of the list subscription
|
|
68
|
+
* @property {ListInstance} listInstance - the list instance used by the subscription
|
|
69
|
+
* @property {object} listIntent - the useCancelleableIntent object managing if the list should be (re)fetched
|
|
70
|
+
* @property {object} subscriptionIntent - the useCancelleableIntent object managing if the subscription should be (un)subscribed
|
|
71
|
+
* @property {Function} subscribe - subscribe to the list
|
|
72
|
+
* @property {Function} unsubscribe - unsubscribe from the list
|
|
73
|
+
* @property {Function} clearError - clear the subscription error
|
|
74
|
+
* @property {object} effectScope - a Vue effect scope
|
|
75
|
+
*/
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* `useListSubscription` creates a reactive object that manages a list of objects, as returned by `useListInstance`,
|
|
79
|
+
* causing the list to be re-fetched as needed and listening for updates to the list.
|
|
80
|
+
* @param {ListSubscriptionOptions} options
|
|
81
|
+
* @returns ListSubscription
|
|
82
|
+
*/
|
|
83
|
+
export function useListSubscription({ listInstance, props, functions, keepOldPages = false }) {
|
|
43
84
|
if (!listInstance && !props) {
|
|
44
|
-
throw new ListSubscriptionError("useListSubscription should be passed listInstance or props and
|
|
85
|
+
throw new ListSubscriptionError("useListSubscription should be passed listInstance or props and functions.");
|
|
45
86
|
}
|
|
46
87
|
if (listInstance && props) {
|
|
47
88
|
throw new ListSubscriptionError(
|
|
48
|
-
"useListSubscription should be passed listInstance or props and
|
|
89
|
+
"useListSubscription should be passed listInstance or props and functions, not both."
|
|
49
90
|
);
|
|
50
91
|
}
|
|
51
92
|
if (!listInstance) {
|
|
@@ -55,10 +96,11 @@ export function useListSubscription({ listInstance, props, functions }) {
|
|
|
55
96
|
if (!("retrieveArgs" in props)) {
|
|
56
97
|
console.error("retrieveArgs not set, must be true for intendToList or intendToSubscribe to work.");
|
|
57
98
|
}
|
|
58
|
-
listInstance = useListInstance({ props, functions });
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
99
|
+
listInstance = useListInstance({ props, functions, keepOldPages });
|
|
100
|
+
} else {
|
|
101
|
+
if (functions) {
|
|
102
|
+
console.error("functions passed to useListSubscription, but listInstance was passed. functions ignored.");
|
|
103
|
+
}
|
|
62
104
|
}
|
|
63
105
|
const parentState = listInstance.state;
|
|
64
106
|
|
package/use/object.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { useObjectCalculated } from "./objectCalculated.js";
|
|
2
|
-
import { useObjectInstance } from "./objectInstance.js";
|
|
3
|
-
import { useObjectRelated } from "./objectRelated.js";
|
|
4
|
-
import { useObjectSubscription } from "./objectSubscription.js";
|
|
1
|
+
import { useObjectCalculated, objectCalculatedFunctions } from "./objectCalculated.js";
|
|
2
|
+
import { useObjectInstance, objectInstanceFunctions } from "./objectInstance.js";
|
|
3
|
+
import { useObjectRelated, objectRelatedFunctions } from "./objectRelated.js";
|
|
4
|
+
import { useObjectSubscription, objectSubscriptionFunctions } from "./objectSubscription.js";
|
|
5
5
|
import { effectScope, reactive, shallowReadonly, toRef } from "vue";
|
|
6
6
|
|
|
7
7
|
// Manages a chain of useObject* functions
|
|
@@ -49,7 +49,7 @@ export const useObject = ({ props, functions }) => {
|
|
|
49
49
|
// objectInstance.clear also does objectInstance.clearError
|
|
50
50
|
managed.objectInstance.clear();
|
|
51
51
|
};
|
|
52
|
-
|
|
52
|
+
const returnObject = reactive({
|
|
53
53
|
managed: shallowReadonly(managed),
|
|
54
54
|
state: managed.objectCalculated.state,
|
|
55
55
|
retrieve: managed.objectInstance.retrieve,
|
|
@@ -64,4 +64,25 @@ export const useObject = ({ props, functions }) => {
|
|
|
64
64
|
clear,
|
|
65
65
|
effectScope: es,
|
|
66
66
|
});
|
|
67
|
+
const handledDuplicateFunctions = {
|
|
68
|
+
clearError,
|
|
69
|
+
clear,
|
|
70
|
+
};
|
|
71
|
+
for (const [source, fnNames] of [
|
|
72
|
+
[managed.objectInstance, objectInstanceFunctions],
|
|
73
|
+
[managed.objectSubscription, objectSubscriptionFunctions],
|
|
74
|
+
[managed.objectRelated, objectRelatedFunctions],
|
|
75
|
+
[managed.objectCalculated, objectCalculatedFunctions],
|
|
76
|
+
]) {
|
|
77
|
+
for (const fnName of fnNames) {
|
|
78
|
+
if (handledDuplicateFunctions[fnName]) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
returnObject[fnName] = source[fnName];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
for (const [fnName, fn] of Object.entries(handledDuplicateFunctions)) {
|
|
85
|
+
returnObject[fnName] = fn;
|
|
86
|
+
}
|
|
87
|
+
return returnObject;
|
|
67
88
|
};
|