@arrai-innovations/reactive-helpers 7.1.1 → 8.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/.vscode/extensions.json +1 -1
- package/package.json +1 -1
- package/tests/unit/crudPromise.js +1 -3
- package/tests/unit/use/listSubscription.spec.js +1 -3
- package/tests/unit/use/objectSubscription.spec.js +2 -6
- package/tests/unit/utils/assignReactiveObject.spec.js +1 -3
- package/use/cancellableIntent.js +0 -4
- package/use/listCalculated.js +8 -19
- package/use/listInstance.js +1 -3
- package/use/listRelated.js +5 -16
- package/use/objectCalculated.js +3 -15
- package/use/objectRelated.js +2 -12
- package/use/watchesRunning.js +1 -6
- package/utils/assignReactiveObject.js +182 -17
- package/utils/debugMessage.js +170 -27
- package/utils/flattenPaths.js +11 -3
- package/utils/keyDiff.js +40 -0
- package/utils/lifecycleDebug.js +68 -5
- package/utils/transformWalk.js +52 -0
package/.vscode/extensions.json
CHANGED
package/package.json
CHANGED
|
@@ -13,9 +13,7 @@ export class CancellableResolvable {
|
|
|
13
13
|
const cancelResolvable = new Resolvable();
|
|
14
14
|
newResolvable.promise.cancel = jest
|
|
15
15
|
.fn()
|
|
16
|
-
.mockImplementationOnce(async () =>
|
|
17
|
-
return cancelResolvable.promise;
|
|
18
|
-
})
|
|
16
|
+
.mockImplementationOnce(async () => cancelResolvable.promise)
|
|
19
17
|
.mockRejectedValue(new Error("cancel already called"));
|
|
20
18
|
Object.assign(this, newResolvable);
|
|
21
19
|
this.cancel = cancelResolvable;
|
|
@@ -24,9 +24,7 @@ describe("use/listSubscription.spec.js", function () {
|
|
|
24
24
|
const listInstanceModule = await import("../../../use/listInstance");
|
|
25
25
|
crudList = jest
|
|
26
26
|
.fn()
|
|
27
|
-
.mockImplementationOnce(() =>
|
|
28
|
-
return crudListResolvable[0].promise;
|
|
29
|
-
})
|
|
27
|
+
.mockImplementationOnce(() => crudListResolvable[0].promise)
|
|
30
28
|
.mockImplementation(() => {
|
|
31
29
|
const newResolvable = new CancellableResolvable();
|
|
32
30
|
crudListResolvable.push(newResolvable);
|
|
@@ -34,12 +34,8 @@ describe("use/objectSubscription.js", function () {
|
|
|
34
34
|
});
|
|
35
35
|
globalUnsubscribe = jest.fn();
|
|
36
36
|
globalSubscribe = jest.fn();
|
|
37
|
-
globalSubscribe.mockImplementation(() =>
|
|
38
|
-
|
|
39
|
-
});
|
|
40
|
-
globalUnsubscribe.mockImplementation(() => {
|
|
41
|
-
return true;
|
|
42
|
-
});
|
|
37
|
+
globalSubscribe.mockImplementation(() => Promise.resolve(globalUnsubscribe));
|
|
38
|
+
globalUnsubscribe.mockImplementation(() => true);
|
|
43
39
|
const imported = await import("../../../use/objectSubscription");
|
|
44
40
|
imported.setObjectSubscriptionCrud({
|
|
45
41
|
subscribe: globalSubscribe,
|
|
@@ -41,9 +41,7 @@ describe("utils/assignReactiveObject", function () {
|
|
|
41
41
|
const tes = effectScope();
|
|
42
42
|
let computedSum;
|
|
43
43
|
tes.run(() => {
|
|
44
|
-
computedSum = computed(() =>
|
|
45
|
-
return target.a + target.b + target.c;
|
|
46
|
-
});
|
|
44
|
+
computedSum = computed(() => target.a + target.b + target.c);
|
|
47
45
|
});
|
|
48
46
|
try {
|
|
49
47
|
expect(computedSum.value).toBe(6);
|
package/use/cancellableIntent.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import { useDebugMessage } from "../utils";
|
|
2
1
|
import identity from "lodash-es/identity";
|
|
3
2
|
import isEqual from "lodash-es/isEqual";
|
|
4
3
|
import { effectScope, nextTick, onScopeDispose, reactive, readonly, watch } from "vue";
|
|
5
4
|
import { deepUnref } from "vue-deepunref";
|
|
6
5
|
|
|
7
|
-
const watchFnDebugMessage = useDebugMessage(["cancellableIntent", "watch"]);
|
|
8
|
-
|
|
9
6
|
/*
|
|
10
7
|
* Calls your awaitable function with the arguments you pass in, when the watch arguments change and are all truthy.
|
|
11
8
|
* Watch arguments should be a reactive object.
|
|
@@ -47,7 +44,6 @@ export function useCancellableIntent({ awaitableWithCancel, watchArguments = {},
|
|
|
47
44
|
}
|
|
48
45
|
|
|
49
46
|
const watchFn = () => {
|
|
50
|
-
watchFnDebugMessage("watchFn called");
|
|
51
47
|
let newWatchValues = deepUnref(Object.values(watchArguments));
|
|
52
48
|
if (isEqual(newWatchValues, previousWatchValues)) {
|
|
53
49
|
return;
|
package/use/listCalculated.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { keyDiff, loadingCombine
|
|
1
|
+
import { keyDiff, loadingCombine } from "../utils";
|
|
2
2
|
import { listInstanceStateKeys } from "./listInstance";
|
|
3
3
|
import { listRelatedStateKeys } from "./listRelated";
|
|
4
4
|
import { listSubscriptionStateKeys } from "./listSubscription";
|
|
@@ -6,9 +6,6 @@ import { useWatchesRunning } from "./watchesRunning";
|
|
|
6
6
|
import isEmpty from "lodash-es/isEmpty";
|
|
7
7
|
import { computed, effectScope, onScopeDispose, reactive, toRef, watch } from "vue";
|
|
8
8
|
|
|
9
|
-
const computedDebugMessage = useDebugMessage(["listCalculated", "computed"]);
|
|
10
|
-
const watchDebugMessage = useDebugMessage(["listCalculated", "watch"]);
|
|
11
|
-
|
|
12
9
|
export const listCalculatedStateKeys = [
|
|
13
10
|
"calculatedObjects",
|
|
14
11
|
"calculatedObjectsParentStateObjectsWatchRunning",
|
|
@@ -38,7 +35,6 @@ export function useListCalculated({ parentState, calculatedObjectsRules }) {
|
|
|
38
35
|
const calculatedObjectsEffectScopes = {};
|
|
39
36
|
|
|
40
37
|
function parentStateObjectsWatch() {
|
|
41
|
-
watchDebugMessage("parentStateObjectsWatch called");
|
|
42
38
|
const { addedKeys, removedKeys } = keyDiff(
|
|
43
39
|
Object.keys(parentState.objects),
|
|
44
40
|
Object.keys(state.calculatedObjects)
|
|
@@ -57,7 +53,6 @@ export function useListCalculated({ parentState, calculatedObjectsRules }) {
|
|
|
57
53
|
}
|
|
58
54
|
|
|
59
55
|
function calculatedObjectsWatch() {
|
|
60
|
-
watchDebugMessage("calculatedObjectsWatch called");
|
|
61
56
|
const calculatedObjectsRulesIsEmpty = !state.calculatedObjectsRules || isEmpty(state.calculatedObjectsRules);
|
|
62
57
|
for (const objectKey of Object.keys(state.calculatedObjects)) {
|
|
63
58
|
if (!calculatedObjectsEffectScopes[objectKey]) {
|
|
@@ -86,9 +81,9 @@ export function useListCalculated({ parentState, calculatedObjectsRules }) {
|
|
|
86
81
|
}
|
|
87
82
|
calculatedObjectsEffectScopes[objectKey].run(() => {
|
|
88
83
|
for (const addedRuleKey of addedRuleKeys) {
|
|
89
|
-
calculatedObjectsObject[addedRuleKey] = computed(() =>
|
|
90
|
-
|
|
91
|
-
|
|
84
|
+
calculatedObjectsObject[addedRuleKey] = computed(() =>
|
|
85
|
+
state.calculatedObjectsRules?.[addedRuleKey]?.(originalObject)
|
|
86
|
+
);
|
|
92
87
|
}
|
|
93
88
|
});
|
|
94
89
|
}
|
|
@@ -125,12 +120,9 @@ export function useListCalculated({ parentState, calculatedObjectsRules }) {
|
|
|
125
120
|
|
|
126
121
|
watchesRunning = useWatchesRunning({
|
|
127
122
|
triggerRefs: [
|
|
128
|
-
computed(() =>
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
? parentState.loading
|
|
132
|
-
: false;
|
|
133
|
-
}),
|
|
123
|
+
computed(() =>
|
|
124
|
+
state.calculatedObjectsRules && !isEmpty(state.calculatedObjectsRules) ? parentState.loading : false
|
|
125
|
+
),
|
|
134
126
|
],
|
|
135
127
|
watchSentinelRefs: [
|
|
136
128
|
toRef(state, "calculatedObjectsParentStateObjectsWatchRunning"),
|
|
@@ -138,10 +130,7 @@ export function useListCalculated({ parentState, calculatedObjectsRules }) {
|
|
|
138
130
|
],
|
|
139
131
|
});
|
|
140
132
|
|
|
141
|
-
state.running = computed(() =>
|
|
142
|
-
computedDebugMessage("listCalculated running");
|
|
143
|
-
return loadingCombine(watchesRunning.state.running, parentState.running);
|
|
144
|
-
});
|
|
133
|
+
state.running = computed(() => loadingCombine(watchesRunning.state.running, parentState.running));
|
|
145
134
|
|
|
146
135
|
onScopeDispose(() => {
|
|
147
136
|
for (const objectKey of Object.keys(calculatedObjectsEffectScopes)) {
|
package/use/listInstance.js
CHANGED
|
@@ -129,9 +129,7 @@ export function useListInstance({ props, functions = {} }) {
|
|
|
129
129
|
return Promise.reject(new ListError("already loading."));
|
|
130
130
|
}
|
|
131
131
|
let returnPromiseResolve;
|
|
132
|
-
const returnPromise = new Promise((resolve) =>
|
|
133
|
-
returnPromiseResolve = resolve;
|
|
134
|
-
});
|
|
132
|
+
const returnPromise = new Promise((resolve) => (returnPromiseResolve = resolve));
|
|
135
133
|
state.loading = true;
|
|
136
134
|
state.errored = false;
|
|
137
135
|
state.error = null;
|
package/use/listRelated.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { keyDiff, loadingCombine
|
|
1
|
+
import { keyDiff, loadingCombine } from "../utils";
|
|
2
2
|
import { listInstanceStateKeys } from "./listInstance";
|
|
3
3
|
import { listSubscriptionStateKeys } from "./listSubscription";
|
|
4
4
|
import { useWatchesRunning } from "./watchesRunning";
|
|
@@ -8,9 +8,6 @@ import isEmpty from "lodash-es/isEmpty";
|
|
|
8
8
|
import isUndefined from "lodash-es/isUndefined";
|
|
9
9
|
import { computed, effectScope, onScopeDispose, reactive, toRef, unref, watch } from "vue";
|
|
10
10
|
|
|
11
|
-
const computedDebugMessage = useDebugMessage(["listRelated", "computed"]);
|
|
12
|
-
const watchDebugMessage = useDebugMessage(["listRelated", "watch"]);
|
|
13
|
-
|
|
14
11
|
export const listRelatedStateKeys = [
|
|
15
12
|
"relatedObjects",
|
|
16
13
|
"relatedObjectsRules",
|
|
@@ -37,7 +34,6 @@ export function useListRelated({ parentState, relatedObjectsRules }) {
|
|
|
37
34
|
const relatedObjectsEffectScopes = {};
|
|
38
35
|
|
|
39
36
|
function parentStateObjectsWatch() {
|
|
40
|
-
watchDebugMessage("parentStateObjectsWatch called");
|
|
41
37
|
const { addedKeys, removedKeys } = keyDiff(Object.keys(parentState.objects), Object.keys(state.relatedObjects));
|
|
42
38
|
for (const removedKey of removedKeys) {
|
|
43
39
|
delete state.relatedObjects[removedKey];
|
|
@@ -53,7 +49,6 @@ export function useListRelated({ parentState, relatedObjectsRules }) {
|
|
|
53
49
|
}
|
|
54
50
|
|
|
55
51
|
function relatedObjectsWatch() {
|
|
56
|
-
watchDebugMessage("relatedObjectsWatch called");
|
|
57
52
|
const relatedObjectsRulesIsEmpty = !state.relatedObjectsRules || isEmpty(state.relatedObjectsRules);
|
|
58
53
|
for (const objectKey of Object.keys(state.relatedObjects)) {
|
|
59
54
|
const relatedObjectsObject = state.relatedObjects[objectKey];
|
|
@@ -125,12 +120,9 @@ export function useListRelated({ parentState, relatedObjectsRules }) {
|
|
|
125
120
|
|
|
126
121
|
watchesRunning = useWatchesRunning({
|
|
127
122
|
triggerRefs: [
|
|
128
|
-
computed(() =>
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
? parentState.loading
|
|
132
|
-
: false;
|
|
133
|
-
}),
|
|
123
|
+
computed(() =>
|
|
124
|
+
state.relatedObjectsRules && !isEmpty(state.relatedObjectsRules) ? parentState.loading : false
|
|
125
|
+
),
|
|
134
126
|
],
|
|
135
127
|
watchSentinelRefs: [
|
|
136
128
|
toRef(state, "relatedObjectsParentStateObjectsWatchRunning"),
|
|
@@ -138,10 +130,7 @@ export function useListRelated({ parentState, relatedObjectsRules }) {
|
|
|
138
130
|
],
|
|
139
131
|
});
|
|
140
132
|
|
|
141
|
-
state.running = computed(() =>
|
|
142
|
-
computedDebugMessage("listRelated running");
|
|
143
|
-
return loadingCombine(watchesRunning.state.running, parentState.running);
|
|
144
|
-
});
|
|
133
|
+
state.running = computed(() => loadingCombine(watchesRunning.state.running, parentState.running));
|
|
145
134
|
|
|
146
135
|
onScopeDispose(() => {
|
|
147
136
|
for (const objectKey of Object.keys(relatedObjectsEffectScopes)) {
|
package/use/objectCalculated.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import { keyDiff, loadingCombine
|
|
1
|
+
import { keyDiff, loadingCombine } from "../utils";
|
|
2
2
|
import { useWatchesRunning } from "./watchesRunning";
|
|
3
3
|
import isEmpty from "lodash-es/isEmpty";
|
|
4
4
|
import { computed, effectScope, onScopeDispose, reactive, toRef, watch } from "vue";
|
|
5
5
|
|
|
6
|
-
const computedDebugMessage = useDebugMessage(["objectCalculated", "computed"]);
|
|
7
|
-
const watchDebugMessage = useDebugMessage(["objectCalculated", "watch"]);
|
|
8
|
-
|
|
9
6
|
export function useObjectCalculateds(instances, args) {
|
|
10
7
|
for (const [key, value] of Object.entries(args)) {
|
|
11
8
|
useObjectCalculated({
|
|
@@ -68,7 +65,6 @@ export function useObjectCalculated({
|
|
|
68
65
|
}
|
|
69
66
|
|
|
70
67
|
watch([() => state.calculatedObjectRules && Object.keys(state.calculatedObjectRules)], () => {
|
|
71
|
-
watchDebugMessage("calculatedObjectRules watch called");
|
|
72
68
|
let addedKeys = [],
|
|
73
69
|
removedKeys = [],
|
|
74
70
|
sameKeys = [];
|
|
@@ -106,12 +102,7 @@ export function useObjectCalculated({
|
|
|
106
102
|
});
|
|
107
103
|
|
|
108
104
|
watchesRunning = useWatchesRunning({
|
|
109
|
-
triggerRefs: [
|
|
110
|
-
computed(() => {
|
|
111
|
-
computedDebugMessage("watchesRunningTriggerRefs computed");
|
|
112
|
-
return !isEmpty(state.calculatedObjectRules) ? parentState.loading : false;
|
|
113
|
-
}),
|
|
114
|
-
],
|
|
105
|
+
triggerRefs: [computed(() => (!isEmpty(state.calculatedObjectRules) ? parentState.loading : false))],
|
|
115
106
|
watchSentinelRefs: [
|
|
116
107
|
toRef(state, "parentStateObjectWatchRunning"),
|
|
117
108
|
toRef(state, "calculatedObjectWatchRunning"),
|
|
@@ -119,10 +110,7 @@ export function useObjectCalculated({
|
|
|
119
110
|
});
|
|
120
111
|
|
|
121
112
|
state.calculatedRunning = toRef(watchesRunning.state, "running");
|
|
122
|
-
state.running = computed(() =>
|
|
123
|
-
computedDebugMessage("running computed");
|
|
124
|
-
return loadingCombine(watchesRunning.state.running, parentState.relatedRunning);
|
|
125
|
-
});
|
|
113
|
+
state.running = computed(() => loadingCombine(watchesRunning.state.running, parentState.relatedRunning));
|
|
126
114
|
|
|
127
115
|
onScopeDispose(() => {
|
|
128
116
|
for (const key in calculatedObjectEffectScopes) {
|
package/use/objectRelated.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { keyDiff
|
|
1
|
+
import { keyDiff } from "../utils";
|
|
2
2
|
import { useWatchesRunning } from "./watchesRunning";
|
|
3
3
|
import get from "lodash-es/get";
|
|
4
4
|
import isArray from "lodash-es/isArray";
|
|
@@ -6,9 +6,6 @@ import isEmpty from "lodash-es/isEmpty";
|
|
|
6
6
|
import isUndefined from "lodash-es/isUndefined";
|
|
7
7
|
import { computed, effectScope, onScopeDispose, reactive, toRef, unref, watch } from "vue";
|
|
8
8
|
|
|
9
|
-
const computedDebugMessage = useDebugMessage(["objectRelated", "computed"]);
|
|
10
|
-
const watchDebugMessage = useDebugMessage(["objectRelated", "watch"]);
|
|
11
|
-
|
|
12
9
|
export function useObjectRelateds(instances, args) {
|
|
13
10
|
for (const [key, value] of Object.entries(args)) {
|
|
14
11
|
useObjectRelated({
|
|
@@ -64,7 +61,6 @@ export function useObjectRelated({
|
|
|
64
61
|
}
|
|
65
62
|
|
|
66
63
|
watch([() => state.relatedObjectRules && Object.keys(state.relatedObjectRules)], () => {
|
|
67
|
-
watchDebugMessage("relatedObjectRules changed");
|
|
68
64
|
let addedRuleKeys = [],
|
|
69
65
|
removedRuleKeys = [];
|
|
70
66
|
if (!state.relatedObjectRules) {
|
|
@@ -87,7 +83,6 @@ export function useObjectRelated({
|
|
|
87
83
|
relatedObjectEffectScopes[addedRuleKey] = effectScope();
|
|
88
84
|
relatedObjectEffectScopes[addedRuleKey].run(() => {
|
|
89
85
|
state.relatedObjectObjects[addedRuleKey] = computed(() => {
|
|
90
|
-
computedDebugMessage("relatedObjectObjects computed");
|
|
91
86
|
// deal with computed objects being passed.
|
|
92
87
|
const ruleObjects = unref(state.relatedObjectRules?.[addedRuleKey]?.objects);
|
|
93
88
|
const rulePkKey = state.relatedObjectRules?.[addedRuleKey]?.pkKey || addedRuleKey;
|
|
@@ -108,12 +103,7 @@ export function useObjectRelated({
|
|
|
108
103
|
});
|
|
109
104
|
|
|
110
105
|
watchesRunning = useWatchesRunning({
|
|
111
|
-
triggerRefs: [
|
|
112
|
-
computed(() => {
|
|
113
|
-
watchDebugMessage("parentStateObjectWatchRunning");
|
|
114
|
-
return !isEmpty(state.relatedObjectRules) ? parentState.loading : false;
|
|
115
|
-
}),
|
|
116
|
-
],
|
|
106
|
+
triggerRefs: [computed(() => (!isEmpty(state.relatedObjectRules) ? parentState.loading : false))],
|
|
117
107
|
watchSentinelRefs: [
|
|
118
108
|
toRef(state, "parentStateObjectWatchRunning"),
|
|
119
109
|
toRef(state, "relatedObjectWatchRunning"),
|
package/use/watchesRunning.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import { loadingCombine
|
|
1
|
+
import { loadingCombine } from "../utils";
|
|
2
2
|
import { computed, effectScope, reactive, unref, watch } from "vue";
|
|
3
3
|
|
|
4
|
-
const computedDebugMessage = useDebugMessage(["watchesRunning", "computed"]);
|
|
5
|
-
const watchDebugMessage = useDebugMessage(["watchesRunning", "watch"]);
|
|
6
|
-
|
|
7
4
|
export function useWatchesRunning({ triggerRefs, watchSentinelRefs }) {
|
|
8
5
|
const state = reactive({});
|
|
9
6
|
|
|
@@ -13,7 +10,6 @@ export function useWatchesRunning({ triggerRefs, watchSentinelRefs }) {
|
|
|
13
10
|
watch(
|
|
14
11
|
triggerRefs,
|
|
15
12
|
(values) => {
|
|
16
|
-
watchDebugMessage("useWatchesRunning triggerRefs");
|
|
17
13
|
if (values.every((value) => unref(value))) {
|
|
18
14
|
watchSentinelRefs.forEach((ref) => {
|
|
19
15
|
ref.value = true;
|
|
@@ -27,7 +23,6 @@ export function useWatchesRunning({ triggerRefs, watchSentinelRefs }) {
|
|
|
27
23
|
);
|
|
28
24
|
state.running = computed(() => {
|
|
29
25
|
const values = watchSentinelRefs.map((ref) => ref.value);
|
|
30
|
-
computedDebugMessage("useWatchesRunning running");
|
|
31
26
|
return loadingCombine(values);
|
|
32
27
|
});
|
|
33
28
|
});
|
|
@@ -11,12 +11,39 @@ export class AssignReactiveObjectError extends Error {
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Ref|object|array} ValidTargetOrSource targets and sources must be refs, objects, or arrays
|
|
16
|
+
* and refs must ultimately resolve to objects or arrays
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Validates that a value is an array or an object, and throws an error if it is not.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} key The key being validated.
|
|
23
|
+
* @param {*} value The value being validated.
|
|
24
|
+
* @throws {AssignReactiveObjectError} If the value is not an array or an object.
|
|
25
|
+
*/
|
|
14
26
|
function isArrayOrObject(key, value) {
|
|
15
27
|
if (!(isArray(value) || isObject(value))) {
|
|
16
28
|
throw new AssignReactiveObjectError(`${key} must be an object or an array, not ${inspect(value)}`);
|
|
17
29
|
}
|
|
18
30
|
}
|
|
19
31
|
|
|
32
|
+
/**
|
|
33
|
+
* @typedef validateTargetAndSourceResult
|
|
34
|
+
* @property {ValidTargetOrSource} target The validated target value.
|
|
35
|
+
* @property {ValidTargetOrSource} source The validated source value.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Validates that the target and source values are arrays or objects, and returns them.
|
|
40
|
+
* If either value is a ref, it is dereferenced before validation.
|
|
41
|
+
*
|
|
42
|
+
* @param {ValidTargetOrSource} target The target value to validate.
|
|
43
|
+
* @param {ValidTargetOrSource} source The source value to validate.
|
|
44
|
+
* @returns {validateTargetAndSourceResult} An object containing the validated target and source values.
|
|
45
|
+
* @throws {AssignReactiveObjectError} If either value is not an array or an object.
|
|
46
|
+
*/
|
|
20
47
|
function validateTargetAndSource(target, source) {
|
|
21
48
|
isArrayOrObject("target", target);
|
|
22
49
|
isArrayOrObject("source", source);
|
|
@@ -33,42 +60,90 @@ function validateTargetAndSource(target, source) {
|
|
|
33
60
|
return { target, source };
|
|
34
61
|
}
|
|
35
62
|
|
|
36
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Replaces keys in a target object or array with reactive refs to the corresponding keys in a
|
|
65
|
+
* source object or array.
|
|
66
|
+
*
|
|
67
|
+
* @param {ValidTargetOrSource} target The object receiving values.
|
|
68
|
+
* @param {ValidTargetOrSource} source The object providing values.
|
|
69
|
+
* @param {array} keys The keys to replace.
|
|
70
|
+
* @param {array} {exclude} Keys to exclude from replacement.
|
|
71
|
+
* @returns {boolean} True if any keys were replaced, false otherwise.
|
|
72
|
+
* @throws {AssignReactiveObjectError} If either target or source are not ultimately objects or arrays.
|
|
73
|
+
*/
|
|
74
|
+
function reactiveReplaceKeys(target, source, keys, exclude = []) {
|
|
37
75
|
const targetIsReactive = isReactive(target);
|
|
38
76
|
const sourceIsReactive = isReactive(source);
|
|
77
|
+
let didAnything = false;
|
|
39
78
|
for (const key of keys) {
|
|
40
79
|
if (!exclude.includes(key)) {
|
|
41
80
|
if (targetIsReactive && sourceIsReactive) {
|
|
42
81
|
target[key] = toRef(source, key);
|
|
82
|
+
didAnything = true;
|
|
43
83
|
} else if (target[key] !== source[key]) {
|
|
44
84
|
target[key] = source[key];
|
|
85
|
+
didAnything = true;
|
|
45
86
|
}
|
|
46
87
|
}
|
|
47
88
|
}
|
|
89
|
+
return didAnything;
|
|
48
90
|
}
|
|
49
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Adds to a target the missing keys from a source. `addedKeys` can be precalculated to avoid recalculation.
|
|
94
|
+
*
|
|
95
|
+
* @param {ValidTargetOrSource} target The object receiving values.
|
|
96
|
+
* @param {ValidTargetOrSource} source The object providing values.
|
|
97
|
+
* @param {array} {exclude} Keys to exclude from the addition.
|
|
98
|
+
* @param {array} {addedKeys} Precaulcated array of keys to add, if available. Otherwise, the
|
|
99
|
+
* keys will be calculated.
|
|
100
|
+
* @returns {boolean} True if any keys were added, false otherwise.
|
|
101
|
+
* @throws {AssignReactiveObjectError} If either target or source are not ultimately objects or arrays.
|
|
102
|
+
*/
|
|
50
103
|
export function addReactiveObject(target, source, exclude = [], addedKeys = null) {
|
|
51
104
|
if (!addedKeys) {
|
|
52
105
|
if (target === source) {
|
|
53
|
-
return;
|
|
106
|
+
return false;
|
|
54
107
|
}
|
|
55
108
|
({ target, source } = validateTargetAndSource(target, source));
|
|
56
109
|
({ addedKeys } = keyDiff(Object.keys(source) || [], Object.keys(target) || []));
|
|
57
110
|
}
|
|
58
|
-
reactiveReplaceKeys(target, source, addedKeys, exclude);
|
|
111
|
+
return reactiveReplaceKeys(target, source, addedKeys, exclude);
|
|
59
112
|
}
|
|
60
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Updates a target with mutually shared keys from a source. `sameKeys` can be precalculated to avoid recalculation.
|
|
116
|
+
*
|
|
117
|
+
* @param {ValidTargetOrSource} target The object receiving values.
|
|
118
|
+
* @param {ValidTargetOrSource} source The object providing values.
|
|
119
|
+
* @param {array} {exclude} Keys to exclude from the update.
|
|
120
|
+
* @param {array} {sameKeys} Precaulcated array of keys to update, if available. Otherwise, the
|
|
121
|
+
* keys will be calculated.
|
|
122
|
+
* @returns {boolean} True if any keys were updated, false otherwise.
|
|
123
|
+
* @throws {AssignReactiveObjectError} If either target or source are not ultimately objects or arrays.
|
|
124
|
+
*/
|
|
61
125
|
export function updateReactiveObject(target, source, exclude = [], sameKeys = null) {
|
|
62
126
|
if (!sameKeys) {
|
|
63
127
|
if (target === source) {
|
|
64
|
-
return;
|
|
128
|
+
return false;
|
|
65
129
|
}
|
|
66
130
|
({ target, source } = validateTargetAndSource(target, source));
|
|
67
131
|
({ sameKeys } = keyDiff(Object.keys(source) || [], Object.keys(target) || []));
|
|
68
132
|
}
|
|
69
|
-
reactiveReplaceKeys(target, source, sameKeys, exclude);
|
|
133
|
+
return reactiveReplaceKeys(target, source, sameKeys, exclude);
|
|
70
134
|
}
|
|
71
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Adds to a target the missing keys from a source, and updates a target with mutually shared keys from a source.
|
|
138
|
+
*
|
|
139
|
+
* @param {ValidTargetOrSource} target The object receiving values.
|
|
140
|
+
* @param {ValidTargetOrSource} source The object providing values.
|
|
141
|
+
* @param {array} {exclude} Keys to exclude from the addition or update.
|
|
142
|
+
* @param {array} {addedKeys} Precaulcated array of keys to add, if available. Otherwise, the
|
|
143
|
+
* keys will be calculated.
|
|
144
|
+
* @param {array} {sameKeys} Precaulcated array of keys to update, if available. Otherwise, the
|
|
145
|
+
* keys will be calculated.
|
|
146
|
+
*/
|
|
72
147
|
export function addOrUpdateReactiveObject(target, source, exclude = [], addedKeys = null, sameKeys = null) {
|
|
73
148
|
if (!addedKeys && !sameKeys) {
|
|
74
149
|
if (target === source) {
|
|
@@ -77,48 +152,91 @@ export function addOrUpdateReactiveObject(target, source, exclude = [], addedKey
|
|
|
77
152
|
({ target, source } = validateTargetAndSource(target, source));
|
|
78
153
|
({ addedKeys, sameKeys } = keyDiff(Object.keys(source) || [], Object.keys(target) || []));
|
|
79
154
|
}
|
|
80
|
-
|
|
81
|
-
|
|
155
|
+
let didAnything = false;
|
|
156
|
+
didAnything |= addReactiveObject(target, source, exclude, addedKeys);
|
|
157
|
+
didAnything |= updateReactiveObject(target, source, exclude, sameKeys);
|
|
158
|
+
return didAnything;
|
|
82
159
|
}
|
|
83
160
|
|
|
84
|
-
|
|
161
|
+
/**
|
|
162
|
+
* Removes keys from a target that are not present in a source.
|
|
163
|
+
*
|
|
164
|
+
* @param {ValidTargetOrSource} target The object receiving trimming.
|
|
165
|
+
* @param {ValidTargetOrSource|null} source The object that provides the allowed set of keys for calculating `removedKeys`.
|
|
166
|
+
* @param {array} {exclude} Keys to exclude from removal.
|
|
167
|
+
* @param {array} {removedKeys} An array to store removed keys.
|
|
168
|
+
* @returns {boolean} True if any keys were removed, false otherwise.
|
|
169
|
+
* @throws {AssignReactiveObjectError} If either target or source are not ultimately objects or arrays.
|
|
170
|
+
*/
|
|
85
171
|
export function trimReactiveObject(target, source, exclude = [], removedKeys = null) {
|
|
86
172
|
if (!removedKeys) {
|
|
87
173
|
if (target === source) {
|
|
88
|
-
return;
|
|
174
|
+
return false;
|
|
89
175
|
}
|
|
90
176
|
({ target, source } = validateTargetAndSource(target, source));
|
|
91
177
|
({ removedKeys } = keyDiff(Object.keys(source) || [], Object.keys(target) || []));
|
|
92
178
|
}
|
|
93
179
|
const targetIsArray = isArray(target);
|
|
180
|
+
let didAnything = false;
|
|
94
181
|
if (targetIsArray) {
|
|
95
182
|
// Remove indices in reverse (descending) order to keep them stable
|
|
96
183
|
for (const removedKey of [...removedKeys].map((key) => parseInt(key, 10)).sort((a, b) => b - a)) {
|
|
97
184
|
if (!exclude.includes(removedKey)) {
|
|
98
185
|
target.splice(removedKey, 1);
|
|
186
|
+
didAnything = true;
|
|
99
187
|
}
|
|
100
188
|
}
|
|
101
189
|
} else {
|
|
102
190
|
for (const removedKey of removedKeys) {
|
|
103
191
|
if (!exclude.includes(removedKey)) {
|
|
104
192
|
delete target[removedKey];
|
|
193
|
+
didAnything = true;
|
|
105
194
|
}
|
|
106
195
|
}
|
|
107
196
|
}
|
|
197
|
+
return didAnything;
|
|
108
198
|
}
|
|
109
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Change a target to match a source, where keys missing from the source are removed from the target,
|
|
202
|
+
* keys present in the source are added to the target, and keys present in both are updated in the target.
|
|
203
|
+
*
|
|
204
|
+
* @param {ValidTargetOrSource} target The target object or array.
|
|
205
|
+
* @param {ValidTargetOrSource} source The reactive object to assign.
|
|
206
|
+
* @param {array} {exclude} Keys to exclude from the assignment.
|
|
207
|
+
* @throws {AssignReactiveObjectError} If either target or source are not ultimately objects or arrays.
|
|
208
|
+
*/
|
|
110
209
|
export function assignReactiveObject(target, source, exclude = []) {
|
|
111
210
|
if (target === source) {
|
|
112
211
|
return;
|
|
113
212
|
}
|
|
114
213
|
({ target, source } = validateTargetAndSource(target, source));
|
|
115
214
|
const { addedKeys, sameKeys, removedKeys } = keyDiff(Object.keys(source) || [], Object.keys(target) || []);
|
|
116
|
-
|
|
117
|
-
|
|
215
|
+
let didAnything = false;
|
|
216
|
+
didAnything |= trimReactiveObject(target, removedKeys, exclude);
|
|
217
|
+
didAnything |= addOrUpdateReactiveObject(target, source, exclude, addedKeys, sameKeys);
|
|
218
|
+
return didAnything;
|
|
118
219
|
}
|
|
119
220
|
|
|
221
|
+
/**
|
|
222
|
+
* Recursively change a target to match a source, where keys missing from the source are removed from the target,
|
|
223
|
+
* keys present in the source are added to the target, and keys present in both are updated in the target.
|
|
224
|
+
*
|
|
225
|
+
* As an internal function, this function does not validate its arguments and has no optional arguments.
|
|
226
|
+
*
|
|
227
|
+
* @param {ValidTargetOrSource} target The object receiving updates.
|
|
228
|
+
* @param {ValidTargetOrSource} source The object providing updates.
|
|
229
|
+
* @param {array} exclude Keys to exclude from the update.
|
|
230
|
+
* @param {array} addedKeys Precaulcated array of keys to add, if available. Otherwise, the
|
|
231
|
+
* keys will be calculated.
|
|
232
|
+
* @param {array} sameKeys Precaulcated array of keys to update, if available. Otherwise, the
|
|
233
|
+
* keys will be calculated.
|
|
234
|
+
* @param {string} path The current path, used to rescope exclude for the next level.
|
|
235
|
+
* @param {function} fn The recursive function to call, likely the calling function itself.
|
|
236
|
+
*/
|
|
120
237
|
function recursiveInner(target, source, exclude, addedKeys, sameKeys, path, fn) {
|
|
121
|
-
|
|
238
|
+
let didAnything = false;
|
|
239
|
+
didAnything |= addReactiveObject(target, source, exclude, addedKeys);
|
|
122
240
|
const keysForRecurse = [];
|
|
123
241
|
const keysForReplace = [];
|
|
124
242
|
for (const key of sameKeys) {
|
|
@@ -130,7 +248,7 @@ function recursiveInner(target, source, exclude, addedKeys, sameKeys, path, fn)
|
|
|
130
248
|
}
|
|
131
249
|
}
|
|
132
250
|
}
|
|
133
|
-
reactiveReplaceKeys(target, source, keysForReplace, exclude);
|
|
251
|
+
didAnything |= reactiveReplaceKeys(target, source, keysForReplace, exclude);
|
|
134
252
|
for (const key of keysForRecurse) {
|
|
135
253
|
// scope exclude for this next level, remove keys that don't start with the current path, trim keys that do to remove the current path
|
|
136
254
|
const nextLevelExclude = exclude
|
|
@@ -139,29 +257,76 @@ function recursiveInner(target, source, exclude, addedKeys, sameKeys, path, fn)
|
|
|
139
257
|
const nextPath = isArray(source[key]) ? `${path}[${key}]` : `${path}.${key}`;
|
|
140
258
|
fn(target[key], source[key], nextLevelExclude, nextPath);
|
|
141
259
|
}
|
|
260
|
+
return didAnything;
|
|
142
261
|
}
|
|
143
262
|
|
|
263
|
+
/**
|
|
264
|
+
* Recursively change a target to match a source, where keys missing from the source are removed from the target,
|
|
265
|
+
* keys present in the source are added to the target, and keys present in both are updated in the target.
|
|
266
|
+
*
|
|
267
|
+
* An internal function to avoid validating arguments repeatedly.
|
|
268
|
+
*
|
|
269
|
+
* @param {ValidTargetOrSource} target The object receiving updates.
|
|
270
|
+
* @param {ValidTargetOrSource} source The object providing updates.
|
|
271
|
+
* @param {array} {exclude} Keys to exclude from the assignment.
|
|
272
|
+
* @param {string} {path} The current path, used to rescope exclude for the next level.
|
|
273
|
+
* @returns {boolean} True if any keys were added, updated, or removed, false otherwise.
|
|
274
|
+
* @throws {AssignReactiveObjectError} If either target or source are not ultimately objects or arrays.
|
|
275
|
+
*/
|
|
144
276
|
function assignReactiveObjectRecursive(target, source, exclude = [], path = "") {
|
|
145
277
|
let { addedKeys, sameKeys, removedKeys } = keyDiff(Object.keys(source) || [], Object.keys(target) || []);
|
|
146
|
-
|
|
147
|
-
|
|
278
|
+
let didAnything = false;
|
|
279
|
+
didAnything |= trimReactiveObject(target, removedKeys, exclude);
|
|
280
|
+
didAnything |= recursiveInner(target, source, exclude, addedKeys, sameKeys, path, assignReactiveObjectRecursive);
|
|
281
|
+
return didAnything;
|
|
148
282
|
}
|
|
149
283
|
|
|
284
|
+
/**
|
|
285
|
+
* Recursively change a target to match a source, where keys missing from the source are removed from the target,
|
|
286
|
+
* keys present in the source are added to the target, and keys present in both are updated in the target.
|
|
287
|
+
*
|
|
288
|
+
* @param {ValidTargetOrSource} target The object receiving updates.
|
|
289
|
+
* @param {ValidTargetOrSource} source The object providing updates.
|
|
290
|
+
* @param {array} {exclude} Keys to exclude from the assignment.
|
|
291
|
+
* @returns {boolean} True if any keys were added, updated, or removed, false otherwise.
|
|
292
|
+
* @throws {AssignReactiveObjectError} If either target or source are not ultimately objects or arrays.
|
|
293
|
+
*/
|
|
150
294
|
export function assignReactiveObjectDeep(target, source, exclude = []) {
|
|
151
295
|
// exclude keys will need to be lodash get strings
|
|
152
296
|
if (target === source) {
|
|
153
297
|
return;
|
|
154
298
|
}
|
|
155
299
|
({ target, source } = validateTargetAndSource(target, source));
|
|
156
|
-
assignReactiveObjectRecursive(target, source, exclude);
|
|
300
|
+
return assignReactiveObjectRecursive(target, source, exclude);
|
|
157
301
|
}
|
|
158
302
|
|
|
303
|
+
/**
|
|
304
|
+
* Recursively change a target to match a source, where keys present in the source are added to the target, and
|
|
305
|
+
* keys present in both are updated in the target. Missing keys are not removed.
|
|
306
|
+
*
|
|
307
|
+
* As an internal function, this function does not validate its argument.
|
|
308
|
+
*
|
|
309
|
+
* @param {ValidTargetOrSource} target The object receiving updates.
|
|
310
|
+
* @param {ValidTargetOrSource} source The object providing updates.
|
|
311
|
+
* @param {array} [exclude] Keys to exclude from the update.
|
|
312
|
+
* @param {string} [path] The current path, used to rescope exclude for the next level.
|
|
313
|
+
*/
|
|
159
314
|
function addOrUpdateReactiveObjectRecursive(target, source, exclude = [], path = "") {
|
|
160
315
|
let addedKeys,
|
|
161
316
|
sameKeys = keyDiff(Object.keys(source) || [], Object.keys(target) || []);
|
|
162
|
-
recursiveInner(target, source, exclude, addedKeys, sameKeys, path, addOrUpdateReactiveObjectRecursive);
|
|
317
|
+
return recursiveInner(target, source, exclude, addedKeys, sameKeys, path, addOrUpdateReactiveObjectRecursive);
|
|
163
318
|
}
|
|
164
319
|
|
|
320
|
+
/**
|
|
321
|
+
* Recursively change a target to match a source, where keys present in the source are added to the target, and
|
|
322
|
+
* keys present in both are updated in the target. Missing keys are not removed.
|
|
323
|
+
*
|
|
324
|
+
* @param {ValidTargetOrSource} target The object receiving updates.
|
|
325
|
+
* @param {ValidTargetOrSource} source The object providing updates.
|
|
326
|
+
* @param {array} [exclude] Keys to exclude from the update.
|
|
327
|
+
* @returns {boolean} True if any keys were added or updated, false otherwise.
|
|
328
|
+
* @throws {AssignReactiveObjectError} If either target or source are not ultimately objects or arrays.
|
|
329
|
+
*/
|
|
165
330
|
export function addOrUpdateReactiveObjectDeep(target, source, exclude = []) {
|
|
166
331
|
// exclude keys will need to be lodash get strings
|
|
167
332
|
if (target === source) {
|
package/utils/debugMessage.js
CHANGED
|
@@ -1,54 +1,197 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { isSuperset } from "./set";
|
|
2
|
+
import { transformWalk } from "./transformWalk";
|
|
3
|
+
import { isSet, partial, union } from "lodash-es";
|
|
4
|
+
import debounce from "lodash-es/debounce";
|
|
2
5
|
import { unref } from "vue";
|
|
3
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Whether debug messages are enabled or not. For deploying to production with debugging but not
|
|
9
|
+
* spamming everyone with debug messages.
|
|
10
|
+
*
|
|
11
|
+
* @type {boolean}
|
|
12
|
+
*/
|
|
4
13
|
window.RH_DEBUG = false;
|
|
5
|
-
window.RH_DEBUG_ENABLED_CATEGORIES = {};
|
|
6
|
-
window.RH_DEBUG_DISABLED_CATEGORIES = {};
|
|
7
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Map of categories to whether they are enabled or not. The order of the entries are important.
|
|
17
|
+
* All categories in the key must be present for the entry to match.
|
|
18
|
+
* The first matching entry with a value opf true will show the message.
|
|
19
|
+
* The first matching entry with a value of false will hide the message.
|
|
20
|
+
* The special category "*" will match all messages.
|
|
21
|
+
* Strings passed as keys will be treated as a single category.
|
|
22
|
+
* Sets can also be passed as keys, which is what the arrays get converted to.
|
|
23
|
+
*
|
|
24
|
+
* @type {Map<string[]|string|Set, boolean>}
|
|
25
|
+
* @example
|
|
26
|
+
* window.RH_DEBUG = true; // turn it on
|
|
27
|
+
* window.RH_DEBUG_CATEGORIES.set(["ofc-form-component", "lifecycle"], true); // show messages matching both categories
|
|
28
|
+
* window.RH_DEBUG_CATEGORIES.set(["lifecycle"], false); // turn off lifecycle messages not matching the above
|
|
29
|
+
* window.RH_DEBUG_CATEGORIES.set(["dropDown"], true); // show dropDown messages except where disabled by the above
|
|
30
|
+
* window.RH_DEBUG_CATEGORIES.set(["*"], false); // turn off all messages not matching the above
|
|
31
|
+
*/
|
|
32
|
+
window.RH_DEBUG_CATEGORIES = new Map();
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Group identical messages together and show a count of how many times they were logged.
|
|
36
|
+
* Messages are only shown on the trailing edge, so logging is delayed and somewhat out of order.
|
|
37
|
+
*
|
|
38
|
+
* @type {boolean}
|
|
39
|
+
*/
|
|
40
|
+
window.RH_DEBOUNCE_DEBUG = false;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Whether to process arguments to be more friendly for console.log, with regard to circular references
|
|
44
|
+
* and not recursing into vue components.
|
|
45
|
+
*
|
|
46
|
+
* @type {boolean}
|
|
47
|
+
*/
|
|
48
|
+
window.RH_TRANSFORM_MESSAGES = true;
|
|
49
|
+
|
|
50
|
+
const DEBOUNCE_WAIT = 50;
|
|
8
51
|
const log = console.log;
|
|
9
|
-
const
|
|
52
|
+
const messageBounceFns = {};
|
|
53
|
+
const counts = {};
|
|
10
54
|
|
|
11
55
|
/**
|
|
12
|
-
* @param {
|
|
13
|
-
* @param {
|
|
56
|
+
* @param {Set} categoriesSet categories for the message log
|
|
57
|
+
* @param {string} categoriesKey key for debouncing
|
|
58
|
+
* @param {*[]} messages
|
|
14
59
|
*/
|
|
15
|
-
const doLog = (
|
|
60
|
+
const doLog = (categoriesSet, categoriesKey, messages) => {
|
|
16
61
|
if (messages.length > 0) {
|
|
17
|
-
|
|
18
|
-
|
|
62
|
+
const key = getKey(categoriesKey, messages);
|
|
63
|
+
const count = counts[key];
|
|
64
|
+
const logArgs = [Array.from(categoriesSet).join(","), ...messages];
|
|
65
|
+
if (count > 1) {
|
|
66
|
+
logArgs.push(`(${count})`);
|
|
19
67
|
}
|
|
20
|
-
log(
|
|
21
|
-
|
|
68
|
+
log(...logArgs);
|
|
69
|
+
delete counts[key];
|
|
70
|
+
delete messageBounceFns[key];
|
|
22
71
|
}
|
|
23
72
|
};
|
|
24
73
|
|
|
25
74
|
/**
|
|
75
|
+
* Process a value for logging, dealing with circular references and
|
|
76
|
+
* not recursing into vue components.
|
|
26
77
|
*
|
|
27
|
-
* @param {
|
|
28
|
-
* @
|
|
78
|
+
* @param {Map} seenObjects for circlular reference detection
|
|
79
|
+
* @param {string} key keys is an unused argument from walk
|
|
80
|
+
* @param {*} value value to process
|
|
81
|
+
* @param {string} path path to the value, used for display in circular references
|
|
82
|
+
* @returns {*} processed value
|
|
83
|
+
*/
|
|
84
|
+
export const inspectWalkFn = (seenObjects, key, value, path) => {
|
|
85
|
+
// return primatives as-is
|
|
86
|
+
if (typeof value !== "object" || value === null) {
|
|
87
|
+
return value;
|
|
88
|
+
}
|
|
89
|
+
if (seenObjects.has(value)) {
|
|
90
|
+
return `「Dupe:${seenObjects.get(value)}」`;
|
|
91
|
+
}
|
|
92
|
+
seenObjects.set(value, path);
|
|
93
|
+
if (value?.type?.__name) {
|
|
94
|
+
// vue component instance
|
|
95
|
+
return `⧼ Component:${value.type.__name} ⧽`;
|
|
96
|
+
}
|
|
97
|
+
return value;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @param {(string|function)[]} messages messages to resolve
|
|
102
|
+
* @returns {*[]} resolved messages
|
|
103
|
+
*/
|
|
104
|
+
const resolveMessages = (messages) => {
|
|
105
|
+
const resolvedMessages = [];
|
|
106
|
+
for (const message of messages) {
|
|
107
|
+
let toPush = unref(typeof message === "function" ? message() : message);
|
|
108
|
+
if (window.RH_TRANSFORM_MESSAGES) {
|
|
109
|
+
const seenObjects = new Map();
|
|
110
|
+
toPush = transformWalk(toPush, partial(inspectWalkFn, seenObjects));
|
|
111
|
+
}
|
|
112
|
+
resolvedMessages.push(toPush);
|
|
113
|
+
}
|
|
114
|
+
return resolvedMessages;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* @param {Set} categoriesSet categories for the message log
|
|
119
|
+
* @param {string} categoriesKey key for debouncing
|
|
120
|
+
* @param {*[]} messages messages to log
|
|
121
|
+
*/
|
|
122
|
+
const doDebouncedLog = (categoriesSet, categoriesKey, messages) => {
|
|
123
|
+
if (!window.RH_DEBOUNCE_DEBUG) {
|
|
124
|
+
return doLog(categoriesSet, categoriesKey, messages);
|
|
125
|
+
}
|
|
126
|
+
const key = getKey(categoriesKey, messages);
|
|
127
|
+
let debouncedLog = messageBounceFns[key];
|
|
128
|
+
if (!debouncedLog) {
|
|
129
|
+
debouncedLog = debounce(doLog, DEBOUNCE_WAIT, { leading: false, trailing: true });
|
|
130
|
+
messageBounceFns[key] = debouncedLog;
|
|
131
|
+
}
|
|
132
|
+
debouncedLog(categoriesSet, categoriesKey, messages);
|
|
133
|
+
counts[key] = (counts[key] || 0) + 1;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @param {string} categoriesKey categories for the message log
|
|
138
|
+
* @param {(string|function)[]} messages messages to log
|
|
139
|
+
* @returns {string} key
|
|
140
|
+
**/
|
|
141
|
+
const getKey = (categoriesKey, messages) => `${categoriesKey}|${messages.join("-")}`;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @typedef {Object} DebugMessageFunction
|
|
145
|
+
* @property {function(...((string|function)[]|string|function)): void} log log a message
|
|
146
|
+
*/
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* @param {string[]} categories categories for the message log
|
|
150
|
+
* @returns {DebugMessageFunction} debug message function
|
|
29
151
|
*/
|
|
30
152
|
export function useDebugMessage(categories) {
|
|
31
|
-
const categoriesSet = new Set(categories);
|
|
32
|
-
const
|
|
33
|
-
|
|
153
|
+
const categoriesSet = isSet(categories) ? categories : new Set(categories);
|
|
154
|
+
const sortedCategories = Array.from(categoriesSet).sort();
|
|
155
|
+
const categoriesKey = categoriesSet.size > 0 ? `[${sortedCategories.join(", ")}]` : "";
|
|
156
|
+
|
|
157
|
+
const debugMessage = (...messages) => {
|
|
34
158
|
if (!window.RH_DEBUG) {
|
|
35
159
|
return;
|
|
36
160
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
161
|
+
for (const [rule, enabledOrDisabled] of window.RH_DEBUG_CATEGORIES.entries()) {
|
|
162
|
+
let ruleSet;
|
|
163
|
+
if (Array.isArray(rule)) {
|
|
164
|
+
ruleSet = new Set(rule);
|
|
165
|
+
} else if (typeof rule === "string") {
|
|
166
|
+
ruleSet = new Set(rule.split(","));
|
|
167
|
+
} else if (rule.toString() === "[object Set]") {
|
|
168
|
+
ruleSet = rule;
|
|
169
|
+
} else if (rule === "*") {
|
|
170
|
+
ruleSet = new Set(["*"]);
|
|
171
|
+
} else {
|
|
172
|
+
throw new Error(`Unexpected rule type: ${typeof rule}, ${rule}`);
|
|
42
173
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
174
|
+
// if categoriesSet is a superset of ruleSet, then apply the rule
|
|
175
|
+
// first applicable rule wins
|
|
176
|
+
if (isSuperset(categoriesSet, ruleSet) || ruleSet.has("*")) {
|
|
177
|
+
if (enabledOrDisabled === true) {
|
|
178
|
+
doDebouncedLog(categoriesSet, categoriesKey, resolveMessages(messages));
|
|
179
|
+
return;
|
|
180
|
+
} else if (enabledOrDisabled === false) {
|
|
46
181
|
return;
|
|
47
182
|
}
|
|
183
|
+
// ignore other values
|
|
48
184
|
}
|
|
49
185
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
186
|
+
doDebouncedLog(categoriesSet, categoriesKey, resolveMessages(messages));
|
|
187
|
+
};
|
|
188
|
+
/**
|
|
189
|
+
* @param {string[]} moreCategories categories to add
|
|
190
|
+
* @returns {DebugMessageFunction} new debug message function
|
|
191
|
+
*/
|
|
192
|
+
debugMessage.more = (moreCategories) => {
|
|
193
|
+
// return a new debugMessage so the added categories are only applied to the new debugMessage
|
|
194
|
+
return useDebugMessage(union(categoriesSet, isSet(moreCategories) ? moreCategories : new Set(moreCategories)));
|
|
53
195
|
};
|
|
196
|
+
return debugMessage;
|
|
54
197
|
}
|
package/utils/flattenPaths.js
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import isArray from "lodash-es/isArray";
|
|
2
2
|
import isObject from "lodash-es/isObject";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Turn an array or object into an array of path strings. Recurses for any found arrays or objects.
|
|
6
|
+
*
|
|
7
|
+
* Array indexes are wrapped in square brackets and object keys are prefixed with a period.
|
|
8
|
+
*
|
|
9
|
+
* @param {Array|Object} arrayOrObject array or object to flatten
|
|
10
|
+
* @param {string} currentPath current path, for recursion or as a starting point
|
|
11
|
+
* @returns {string[]} paths
|
|
12
|
+
*/
|
|
4
13
|
export function flattenPaths(arrayOrObject, currentPath = "") {
|
|
5
14
|
// arrayOrObject keys or indexes values can be objects or arrays.
|
|
6
15
|
// find all paths you could use lodash to "get()" to.
|
|
@@ -10,15 +19,14 @@ export function flattenPaths(arrayOrObject, currentPath = "") {
|
|
|
10
19
|
const keysOrIndexes = isArray(arrayOrObject);
|
|
11
20
|
const dotOrNot = currentPath ? "." : "";
|
|
12
21
|
if (isObject(arrayOrObject)) {
|
|
13
|
-
Object.
|
|
14
|
-
const value = arrayOrObject[key];
|
|
22
|
+
for (const [key, value] of Object.entries(arrayOrObject)) {
|
|
15
23
|
const keyPath = keysOrIndexes ? `[${key}]` : `${dotOrNot}${key}`;
|
|
16
24
|
if (isObject(value) || isArray(value)) {
|
|
17
25
|
paths.push(...flattenPaths(value, `${currentPath}${keyPath}`));
|
|
18
26
|
} else {
|
|
19
27
|
paths.push(`${currentPath}${keyPath}`);
|
|
20
28
|
}
|
|
21
|
-
}
|
|
29
|
+
}
|
|
22
30
|
} else {
|
|
23
31
|
// values
|
|
24
32
|
if (currentPath) {
|
package/utils/keyDiff.js
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
|
+
import { flattenPaths } from "./flattenPaths.js";
|
|
1
2
|
import { difference, intersection } from "./set.js";
|
|
2
3
|
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {object} KeyDiffOptions
|
|
6
|
+
* @property {boolean} [sameKeys=true]
|
|
7
|
+
* @property {boolean} [removedKeys=true]
|
|
8
|
+
* @property {boolean} [addedKeys=true]
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {object} KeyDiffResult
|
|
13
|
+
* @property {string[]} [sameKeys]
|
|
14
|
+
* @property {string[]} [removedKeys]
|
|
15
|
+
* @property {string[]} [addedKeys]
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Calculate the difference between two arrays of keys, in terms of what keys
|
|
20
|
+
* are the same, what keys are removed, and what keys are added.
|
|
21
|
+
*
|
|
22
|
+
* @param {string[]} newKeys
|
|
23
|
+
* @param {string[]} oldKeys
|
|
24
|
+
* @param {KeyDiffOptions} [options]
|
|
25
|
+
* @returns {KeyDiffResult}
|
|
26
|
+
*/
|
|
3
27
|
export function keyDiff(newKeys, oldKeys, { sameKeys = true, removedKeys = true, addedKeys = true } = {}) {
|
|
4
28
|
const newKeysSet = new Set(newKeys);
|
|
5
29
|
const oldKeysSet = new Set(oldKeys);
|
|
@@ -15,3 +39,19 @@ export function keyDiff(newKeys, oldKeys, { sameKeys = true, removedKeys = true,
|
|
|
15
39
|
}
|
|
16
40
|
return returnValue;
|
|
17
41
|
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Calculate the difference between two objects, in terms of what keys are the same,
|
|
45
|
+
* what keys are removed, and what keys are added. Keys are sourced deeply in the objects.
|
|
46
|
+
*
|
|
47
|
+
* @param {object} newObj
|
|
48
|
+
* @param {object} oldObj
|
|
49
|
+
* @param {KeyDiffOptions} [options]
|
|
50
|
+
* @returns {KeyDiffResult}
|
|
51
|
+
*/
|
|
52
|
+
export function keyDiffDeep(newObj, oldObj, options = {}) {
|
|
53
|
+
const newPaths = flattenPaths(newObj);
|
|
54
|
+
const oldPaths = flattenPaths(oldObj);
|
|
55
|
+
const result = keyDiff(newPaths, oldPaths, options);
|
|
56
|
+
return result;
|
|
57
|
+
}
|
package/utils/lifecycleDebug.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { useDebugMessage } from "./debugMessage";
|
|
1
|
+
import { inspecttransformWalkFn, useDebugMessage } from "./debugMessage";
|
|
2
|
+
import { keyDiffDeep } from "./keyDiff";
|
|
3
|
+
import { transformWalk } from "./transformWalk";
|
|
4
|
+
import { isEqual, partial } from "lodash-es";
|
|
2
5
|
import {
|
|
3
6
|
onActivated,
|
|
4
7
|
onBeforeMount,
|
|
@@ -12,8 +15,71 @@ import {
|
|
|
12
15
|
onServerPrefetch,
|
|
13
16
|
onUnmounted,
|
|
14
17
|
onUpdated,
|
|
18
|
+
unref,
|
|
15
19
|
} from "vue";
|
|
16
20
|
|
|
21
|
+
window.RH_DEBUG_SKIP_EMPTY_CHANGE_EFFECTS = true;
|
|
22
|
+
|
|
23
|
+
export const customHandlers = {
|
|
24
|
+
onRenderTriggered: (debugMessage, e) => {
|
|
25
|
+
const newSeenObjects = new Map();
|
|
26
|
+
const newValue = transformWalk(e.newValue, partial(inspecttransformWalkFn, newSeenObjects));
|
|
27
|
+
const oldSeenObjects = new Map();
|
|
28
|
+
const oldValue = transformWalk(e.oldValue, partial(inspecttransformWalkFn, oldSeenObjects));
|
|
29
|
+
let keyDiffResults;
|
|
30
|
+
if (typeof newValue !== "object" || typeof oldValue !== "object") {
|
|
31
|
+
keyDiffResults = keyDiffDeep(newValue, oldValue);
|
|
32
|
+
keyDiffResults.changedKeys = new Set();
|
|
33
|
+
for (const key of keyDiffResults.sameKeys) {
|
|
34
|
+
// since it is a deep diff, object equality is fine to not trigger a change
|
|
35
|
+
if (newValue[key] !== oldValue[key]) {
|
|
36
|
+
keyDiffResults.changedKeys.add(key);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
delete keyDiffResults.sameKeys;
|
|
40
|
+
for (const key in keyDiffResults) {
|
|
41
|
+
if (keyDiffResults[key].size === 0) {
|
|
42
|
+
delete keyDiffResults[key];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (Object.keys(keyDiffResults).length === 0 && window.RH_DEBUG_SKIP_EMPTY_CHANGE_EFFECTS) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
if (isEqual(newValue, oldValue)) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const debugArgs = [
|
|
54
|
+
{
|
|
55
|
+
effect: e.effect,
|
|
56
|
+
fn: e.effect.fn,
|
|
57
|
+
target: unref(e.target),
|
|
58
|
+
type: e.type,
|
|
59
|
+
key: e.key,
|
|
60
|
+
newValue,
|
|
61
|
+
oldValue,
|
|
62
|
+
oldTarget: unref(e.oldTarget),
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
if (keyDiffResults) {
|
|
66
|
+
debugArgs.push(keyDiffResults);
|
|
67
|
+
}
|
|
68
|
+
debugMessage(...debugArgs);
|
|
69
|
+
},
|
|
70
|
+
onRenderTracked: (debugMessage, e) => {
|
|
71
|
+
debugMessage({
|
|
72
|
+
fn: e.effect.fn,
|
|
73
|
+
target: unref(e.target),
|
|
74
|
+
type: e.type,
|
|
75
|
+
key: e.key,
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
const defaultHandler = (debugMessage) => {
|
|
80
|
+
debugMessage();
|
|
81
|
+
};
|
|
82
|
+
|
|
17
83
|
/**
|
|
18
84
|
* @param {string[]} categories
|
|
19
85
|
*/
|
|
@@ -44,10 +110,7 @@ export function useLifecycleDebug(categories, includes = [], excludes = []) {
|
|
|
44
110
|
const myCategories = new Set(categories);
|
|
45
111
|
myCategories.add("lifecycle");
|
|
46
112
|
myCategories.add(key);
|
|
47
|
-
const eventString = `${key} called`;
|
|
48
113
|
const debugMessage = useDebugMessage(myCategories);
|
|
49
|
-
lifeCycleFns[key]((
|
|
50
|
-
debugMessage(eventString);
|
|
51
|
-
});
|
|
114
|
+
lifeCycleFns[key](partial(customHandlers[key] || defaultHandler, debugMessage));
|
|
52
115
|
}
|
|
53
116
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { isSet } from "lodash-es";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Recursively walks through an object's values and applies a transformation function to each value.
|
|
5
|
+
* The value recursed into is the transformed value, not the original value.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
*
|
|
9
|
+
* const obj = {
|
|
10
|
+
* a: 1,
|
|
11
|
+
* b: {
|
|
12
|
+
* c: 2,
|
|
13
|
+
* d: [3, 4, { e: 5 }]
|
|
14
|
+
* }
|
|
15
|
+
* };
|
|
16
|
+
*
|
|
17
|
+
* const transformed = transformWalk(obj, (key, value, path) => {
|
|
18
|
+
* if (key === "e") {
|
|
19
|
+
* return value * 2;
|
|
20
|
+
* }
|
|
21
|
+
* return value;
|
|
22
|
+
* });
|
|
23
|
+
* // transformed = {
|
|
24
|
+
* // a: 1,
|
|
25
|
+
* // b: {
|
|
26
|
+
* // c: 2,
|
|
27
|
+
* // d: [3, 4, { e: 10 }]
|
|
28
|
+
* // }
|
|
29
|
+
* // }
|
|
30
|
+
*
|
|
31
|
+
* @param {*} obj The object to start walking from.
|
|
32
|
+
* @param {function} transformFn The function to transform each value.
|
|
33
|
+
* @param {string} path The path to the current value.
|
|
34
|
+
* @returns {*} The transformed initial value.
|
|
35
|
+
*/
|
|
36
|
+
export const transformWalk = (obj, transformFn, path = "") => {
|
|
37
|
+
if (typeof obj !== "object" || obj === null || isSet(obj)) {
|
|
38
|
+
// base case: obj is not an object or is null
|
|
39
|
+
return obj;
|
|
40
|
+
}
|
|
41
|
+
if (Array.isArray(obj)) {
|
|
42
|
+
// recursive case: obj is an array
|
|
43
|
+
return obj.map((item, index) => transformWalk(item, transformFn, path + `[${index}]`));
|
|
44
|
+
}
|
|
45
|
+
// recursive case: obj is an object
|
|
46
|
+
const result = {};
|
|
47
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
48
|
+
const transformedValue = transformFn(key, value, path + `.${key}`);
|
|
49
|
+
result[key] = transformWalk(transformedValue, transformFn, path + `.${key}`);
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
};
|