@jskit-ai/http-web 0.1.92 → 0.1.93
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -6
- package/patterns/crud-screen-set/PATTERN.md +11 -1
- package/src/client/bulkActions.js +13 -40
- package/src/client/components/CrudAddEditScreen.vue +1 -9
- package/src/client/components/CrudListBulkActionSurface.vue +2 -5
- package/src/client/components/CrudListFilterControls.vue +175 -0
- package/src/client/components/CrudListFilterSurface.vue +5 -248
- package/src/client/components/CrudListScreen.vue +1 -28
- package/src/client/components/CrudViewScreen.vue +1 -9
- package/src/client/composables/crud/crudJsonApiTransportSupport.js +20 -42
- package/src/client/composables/crud/crudLookupFieldLabelSupport.js +2 -13
- package/src/client/composables/crud/crudLookupFieldRuntime.js +2 -27
- package/src/client/composables/crud/crudSchemaFormHelpers.js +1 -7
- package/src/client/composables/internal/crudListFilterLookupSupport.js +2 -1
- package/src/client/composables/internal/useOperationScope.js +5 -7
- package/src/client/composables/records/useAddEdit.js +12 -11
- package/src/client/composables/records/useCrudAddEdit.js +74 -22
- package/src/client/composables/records/useCrudList.js +0 -6
- package/src/client/composables/records/useCrudView.js +0 -6
- package/src/client/composables/records/useList.js +2 -8
- package/src/client/composables/records/useView.js +3 -8
- package/src/client/composables/runtime/addEditUiRuntime.js +12 -60
- package/src/client/composables/runtime/listUiRuntime.js +11 -36
- package/src/client/composables/runtime/useFieldErrorBag.js +3 -3
- package/src/client/composables/runtime/viewUiRuntime.js +14 -62
- package/src/client/composables/support/errorMessageHelpers.js +6 -48
- package/src/client/composables/support/resourceLoadStateHelpers.js +67 -156
- package/src/client/composables/support/routeTemplateHelpers.js +57 -0
- package/src/client/composables/useAccess.js +12 -17
- package/src/client/composables/useCommand.js +0 -1
- package/src/client/composables/useCrudDeleteAction.js +7 -5
- package/src/client/composables/useCrudListBulkActions.js +9 -21
- package/src/client/composables/useCrudListFilterLookups.js +1 -3
- package/src/client/composables/useCrudListParentTitle.js +0 -2
- package/src/client/composables/useCrudListRowActions.js +8 -24
- package/src/client/composables/useCrudListScreen.js +4 -5
- package/src/client/composables/useCrudViewScreen.js +2 -3
- package/src/client/composables/useRealtimeQueryInvalidation.js +3 -10
- package/src/client/composables/useScopeRuntime.js +2 -5
- package/src/client/rowActions.js +13 -40
- package/src/client/support/crudListActionSupport.js +54 -0
- package/test/crudAddEditActions.test.js +59 -19
- package/test/crudListFilterSurface.test.js +7 -4
- package/test/listUiRuntime.test.js +3 -1
- package/test/requestTransportOptions.test.js +24 -0
- package/test/resourceLoadStateHelpers.test.js +32 -0
- package/test/useAccess.test.js +62 -0
- package/test/useCrudDeleteAction.test.js +57 -2
- package/test/useCrudListScreen.test.js +45 -0
- package/test/useListRouteInitialization.test.js +5 -44
- package/src/client/composables/runtime/operationAdapters.js +0 -34
|
@@ -1,24 +1,11 @@
|
|
|
1
|
-
import { computed
|
|
1
|
+
import { computed } from "vue";
|
|
2
2
|
import { asPlainObject } from "../support/scopeHelpers.js";
|
|
3
3
|
import {
|
|
4
|
+
createRouteTemplateResolver,
|
|
4
5
|
normalizeRouteParamName,
|
|
5
|
-
resolveRouteParamsSource,
|
|
6
|
-
resolveScopedRoutePathname,
|
|
7
|
-
resolveRouteTemplateLocation,
|
|
8
6
|
toRouteParamValue
|
|
9
7
|
} from "../support/routeTemplateHelpers.js";
|
|
10
8
|
|
|
11
|
-
function toResolvedRecordId({ routeParams, recordIdParam, routeRecordId }) {
|
|
12
|
-
const explicitRecordId = toRouteParamValue(
|
|
13
|
-
typeof routeRecordId === "function" ? routeRecordId() : unref(routeRecordId)
|
|
14
|
-
);
|
|
15
|
-
if (explicitRecordId) {
|
|
16
|
-
return explicitRecordId;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return toRouteParamValue(routeParams[recordIdParam]);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
9
|
function resolveSavedRecordId(payload, saveRecordIdSelector) {
|
|
23
10
|
const sourcePayload = asPlainObject(payload);
|
|
24
11
|
if (typeof saveRecordIdSelector === "function") {
|
|
@@ -46,56 +33,21 @@ function createAddEditUiRuntime({
|
|
|
46
33
|
const normalizedViewUrlTemplate = String(viewUrlTemplate || "").trim();
|
|
47
34
|
const normalizedListUrlTemplate = String(listUrlTemplate || "").trim();
|
|
48
35
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
...currentRouteParams,
|
|
58
|
-
...asPlainObject(extraParams)
|
|
59
|
-
};
|
|
60
|
-
const currentRouteRecordId = toResolvedRecordId({
|
|
61
|
-
routeParams: currentRouteParams,
|
|
62
|
-
recordIdParam: normalizedRecordIdParam,
|
|
63
|
-
routeRecordId
|
|
64
|
-
});
|
|
65
|
-
const resolvedRecordId = toRouteParamValue(sourceParams[normalizedRecordIdParam]) ||
|
|
66
|
-
currentRouteRecordId;
|
|
67
|
-
sourceParams[normalizedRecordIdParam] = resolvedRecordId;
|
|
68
|
-
const currentPathname = resolveScopedRoutePathname({
|
|
69
|
-
currentPathname: routePath,
|
|
70
|
-
params: currentRouteParams,
|
|
71
|
-
orderedParamNames: routeParamNames,
|
|
72
|
-
anchorParamName: normalizedRecordIdParam,
|
|
73
|
-
anchorParamValue: currentRouteRecordId,
|
|
74
|
-
anchorMode: "after"
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
return resolveRouteTemplateLocation(normalizedTemplate, {
|
|
78
|
-
params: sourceParams,
|
|
79
|
-
currentPathname
|
|
80
|
-
});
|
|
81
|
-
}
|
|
36
|
+
const { resolveRecordId, resolveTemplatePath } = createRouteTemplateResolver({
|
|
37
|
+
recordIdParam: normalizedRecordIdParam,
|
|
38
|
+
routeParams,
|
|
39
|
+
routeParamNames,
|
|
40
|
+
routePath,
|
|
41
|
+
routeRecordId,
|
|
42
|
+
anchorMode: "after"
|
|
43
|
+
});
|
|
82
44
|
|
|
83
|
-
const recordId = computed(() =>
|
|
84
|
-
toResolvedRecordId({
|
|
85
|
-
routeParams: resolveRouteParamsSource(routeParams),
|
|
86
|
-
recordIdParam: normalizedRecordIdParam,
|
|
87
|
-
routeRecordId
|
|
88
|
-
})
|
|
89
|
-
);
|
|
45
|
+
const recordId = computed(() => resolveRecordId());
|
|
90
46
|
|
|
91
47
|
const apiSuffix = computed(() => resolveTemplatePath(normalizedApiUrlTemplate));
|
|
92
48
|
const listUrl = computed(() => resolveTemplatePath(normalizedListUrlTemplate));
|
|
93
49
|
const cancelUrl = computed(() => resolveTemplatePath(normalizedViewUrlTemplate) || listUrl.value);
|
|
94
50
|
|
|
95
|
-
function resolveParams(urlTemplate = "", extraParams = {}) {
|
|
96
|
-
return resolveTemplatePath(urlTemplate, extraParams);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
51
|
function resolveViewUrl(recordIdLike = "") {
|
|
100
52
|
const targetRecordId = toRouteParamValue(recordIdLike);
|
|
101
53
|
if (!targetRecordId) {
|
|
@@ -121,7 +73,7 @@ function createAddEditUiRuntime({
|
|
|
121
73
|
apiSuffix,
|
|
122
74
|
listUrl,
|
|
123
75
|
cancelUrl,
|
|
124
|
-
resolveParams,
|
|
76
|
+
resolveParams: resolveTemplatePath,
|
|
125
77
|
resolveViewUrl,
|
|
126
78
|
resolveSavedViewUrl
|
|
127
79
|
});
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { computed } from "vue";
|
|
2
2
|
import { asPlainObject } from "../support/scopeHelpers.js";
|
|
3
3
|
import {
|
|
4
|
+
createRouteTemplateResolver,
|
|
4
5
|
normalizeRouteParamName,
|
|
5
|
-
toRouteParamValue
|
|
6
|
-
resolveRouteParamsSource,
|
|
7
|
-
resolveScopedRoutePathname,
|
|
8
|
-
resolveRouteTemplateLocation
|
|
6
|
+
toRouteParamValue
|
|
9
7
|
} from "../support/routeTemplateHelpers.js";
|
|
10
8
|
|
|
11
9
|
function resolveRecordId(record, recordIdSelector) {
|
|
@@ -35,39 +33,17 @@ function createListUiRuntime({
|
|
|
35
33
|
const normalizedEditUrlTemplate = String(editUrlTemplate || "").trim();
|
|
36
34
|
const hasViewUrl = Boolean(normalizedViewUrlTemplate);
|
|
37
35
|
const hasEditUrl = Boolean(normalizedEditUrlTemplate);
|
|
38
|
-
const actionColumnCount = (hasViewUrl ? 1 : 0) + (hasEditUrl ? 1 : 0);
|
|
39
36
|
const normalizedItems = computed(() => (Array.isArray(items?.value) ? items.value : []));
|
|
40
37
|
const showListSkeleton = computed(() => Boolean(isInitialLoading?.value && normalizedItems.value.length < 1));
|
|
41
38
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
...currentRouteParams,
|
|
51
|
-
...asPlainObject(extraParams)
|
|
52
|
-
};
|
|
53
|
-
const currentPathname = resolveScopedRoutePathname({
|
|
54
|
-
currentPathname: routePath,
|
|
55
|
-
params: currentRouteParams,
|
|
56
|
-
orderedParamNames: routeParamNames,
|
|
57
|
-
anchorParamName: normalizedRecordIdParam,
|
|
58
|
-
anchorParamValue: currentRouteParams[normalizedRecordIdParam],
|
|
59
|
-
anchorMode: "before"
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
return resolveRouteTemplateLocation(normalizedTemplate, {
|
|
63
|
-
params: sourceParams,
|
|
64
|
-
currentPathname
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function resolveParams(urlTemplate = "", extraParams = {}) {
|
|
69
|
-
return resolveTemplatePath(urlTemplate, extraParams);
|
|
70
|
-
}
|
|
39
|
+
const { resolveTemplatePath } = createRouteTemplateResolver({
|
|
40
|
+
recordIdParam: normalizedRecordIdParam,
|
|
41
|
+
routeParams,
|
|
42
|
+
routeParamNames,
|
|
43
|
+
routePath,
|
|
44
|
+
anchorMode: "before",
|
|
45
|
+
fillRecordId: false
|
|
46
|
+
});
|
|
71
47
|
|
|
72
48
|
function resolveRowKey(record, index) {
|
|
73
49
|
const recordId = resolveRecordId(record, recordIdSelector);
|
|
@@ -111,9 +87,8 @@ function createListUiRuntime({
|
|
|
111
87
|
return Object.freeze({
|
|
112
88
|
hasViewUrl,
|
|
113
89
|
hasEditUrl,
|
|
114
|
-
actionColumnCount,
|
|
115
90
|
showListSkeleton,
|
|
116
|
-
resolveParams,
|
|
91
|
+
resolveParams: resolveTemplatePath,
|
|
117
92
|
resolveRowKey,
|
|
118
93
|
resolveViewUrl,
|
|
119
94
|
resolveEditUrl
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { reactive } from "vue";
|
|
2
2
|
|
|
3
|
-
function
|
|
3
|
+
function normalizeFieldErrorKeys(keys) {
|
|
4
4
|
return Array.isArray(keys)
|
|
5
5
|
? keys.map((entry) => String(entry || "").trim()).filter(Boolean)
|
|
6
6
|
: [];
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
function useFieldErrorBag(keys = []) {
|
|
10
|
-
const normalizedKeys =
|
|
10
|
+
const normalizedKeys = normalizeFieldErrorKeys(keys);
|
|
11
11
|
const hasFixedKeys = normalizedKeys.length > 0;
|
|
12
12
|
const errors = reactive(
|
|
13
13
|
Object.fromEntries(normalizedKeys.map((key) => [key, ""]))
|
|
@@ -58,4 +58,4 @@ function useFieldErrorBag(keys = []) {
|
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
export { useFieldErrorBag };
|
|
61
|
+
export { normalizeFieldErrorKeys, useFieldErrorBag };
|
|
@@ -1,24 +1,9 @@
|
|
|
1
|
-
import { computed
|
|
2
|
-
import { asPlainObject } from "../support/scopeHelpers.js";
|
|
1
|
+
import { computed } from "vue";
|
|
3
2
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
resolveScopedRoutePathname,
|
|
7
|
-
resolveRouteTemplateLocation,
|
|
8
|
-
toRouteParamValue
|
|
3
|
+
createRouteTemplateResolver,
|
|
4
|
+
normalizeRouteParamName
|
|
9
5
|
} from "../support/routeTemplateHelpers.js";
|
|
10
6
|
|
|
11
|
-
function resolveRecordId({ routeParams, recordIdParam, routeRecordId }) {
|
|
12
|
-
const explicitRecordId = toRouteParamValue(
|
|
13
|
-
typeof routeRecordId === "function" ? routeRecordId() : unref(routeRecordId)
|
|
14
|
-
);
|
|
15
|
-
if (explicitRecordId) {
|
|
16
|
-
return explicitRecordId;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return toRouteParamValue(routeParams[recordIdParam]);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
7
|
function createViewUiRuntime({
|
|
23
8
|
recordIdParam = "recordId",
|
|
24
9
|
routeParams = null,
|
|
@@ -36,60 +21,27 @@ function createViewUiRuntime({
|
|
|
36
21
|
const normalizedListUrlTemplate = String(listUrlTemplate || "").trim();
|
|
37
22
|
const normalizedEditUrlTemplate = String(editUrlTemplate || "").trim();
|
|
38
23
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
...asPlainObject(extraParams)
|
|
49
|
-
};
|
|
50
|
-
const resolvedRecordId = toRouteParamValue(sourceParams[normalizedRecordIdParam]) ||
|
|
51
|
-
resolveRecordId({
|
|
52
|
-
routeParams: currentRouteParams,
|
|
53
|
-
recordIdParam: normalizedRecordIdParam,
|
|
54
|
-
routeRecordId
|
|
55
|
-
});
|
|
56
|
-
sourceParams[normalizedRecordIdParam] = resolvedRecordId;
|
|
57
|
-
const currentPathname = resolveScopedRoutePathname({
|
|
58
|
-
currentPathname: routePath,
|
|
59
|
-
params: currentRouteParams,
|
|
60
|
-
orderedParamNames: routeParamNames,
|
|
61
|
-
anchorParamName: normalizedRecordIdParam,
|
|
62
|
-
anchorParamValue: resolvedRecordId,
|
|
63
|
-
anchorMode: "at"
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
return resolveRouteTemplateLocation(normalizedTemplate, {
|
|
67
|
-
params: sourceParams,
|
|
68
|
-
currentPathname
|
|
69
|
-
});
|
|
70
|
-
}
|
|
24
|
+
const { resolveRecordId, resolveTemplatePath } = createRouteTemplateResolver({
|
|
25
|
+
recordIdParam: normalizedRecordIdParam,
|
|
26
|
+
routeParams,
|
|
27
|
+
routeParamNames,
|
|
28
|
+
routePath,
|
|
29
|
+
routeRecordId,
|
|
30
|
+
anchorMode: "at",
|
|
31
|
+
anchorOnTargetRecord: true
|
|
32
|
+
});
|
|
71
33
|
|
|
72
|
-
const recordId = computed(() =>
|
|
73
|
-
resolveRecordId({
|
|
74
|
-
routeParams: resolveRouteParamsSource(routeParams),
|
|
75
|
-
recordIdParam: normalizedRecordIdParam,
|
|
76
|
-
routeRecordId
|
|
77
|
-
})
|
|
78
|
-
);
|
|
34
|
+
const recordId = computed(() => resolveRecordId());
|
|
79
35
|
const apiSuffix = computed(() => resolveTemplatePath(normalizedApiUrlTemplate));
|
|
80
36
|
const listUrl = computed(() => resolveTemplatePath(normalizedListUrlTemplate));
|
|
81
37
|
const editUrl = computed(() => resolveTemplatePath(normalizedEditUrlTemplate));
|
|
82
38
|
|
|
83
|
-
function resolveParams(urlTemplate = "", extraParams = {}) {
|
|
84
|
-
return resolveTemplatePath(urlTemplate, extraParams);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
39
|
return Object.freeze({
|
|
88
40
|
recordId,
|
|
89
41
|
apiSuffix,
|
|
90
42
|
listUrl,
|
|
91
43
|
editUrl,
|
|
92
|
-
resolveParams
|
|
44
|
+
resolveParams: resolveTemplatePath
|
|
93
45
|
});
|
|
94
46
|
}
|
|
95
47
|
|
|
@@ -1,55 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
if (error == null || typeof error !== "object") {
|
|
3
|
-
return null;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
const hasStatus = Object.prototype.hasOwnProperty.call(error, "status");
|
|
7
|
-
const hasStatusCode = Object.prototype.hasOwnProperty.call(error, "statusCode");
|
|
8
|
-
if (!hasStatus && !hasStatusCode) {
|
|
9
|
-
return null;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const status = Number(hasStatus ? error.status : error.statusCode);
|
|
13
|
-
return Number.isInteger(status) ? status : null;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function isGenericTransportMessage(error) {
|
|
17
|
-
const normalizedMessage = String(error?.message || "").trim();
|
|
18
|
-
if (!normalizedMessage) {
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const status = normalizeErrorStatus(error);
|
|
23
|
-
if (status === 0) {
|
|
24
|
-
return true;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return status != null && status >= 400 && normalizedMessage === `Request failed with status ${status}.`;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function toQueryErrorMessage(error, fallbackMessage = "", defaultMessage = "Request failed.") {
|
|
31
|
-
if (!error) {
|
|
32
|
-
return "";
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const normalizedFallback = String(fallbackMessage || "").trim();
|
|
36
|
-
const normalizedMessage = String(error?.message || "").trim();
|
|
37
|
-
if (normalizedMessage && !isGenericTransportMessage(error)) {
|
|
38
|
-
return normalizedMessage;
|
|
39
|
-
}
|
|
40
|
-
if (normalizedFallback) {
|
|
41
|
-
return normalizedFallback;
|
|
42
|
-
}
|
|
43
|
-
if (normalizedMessage) {
|
|
44
|
-
return normalizedMessage;
|
|
45
|
-
}
|
|
46
|
-
return String(defaultMessage || "Request failed.").trim();
|
|
47
|
-
}
|
|
1
|
+
import { isGenericHttpErrorMessage } from "@jskit-ai/http-runtime/client";
|
|
48
2
|
|
|
49
3
|
function toUiErrorMessage(error, fallbackMessage = "", defaultMessage = "Request failed.") {
|
|
50
4
|
const normalizedFallback = String(fallbackMessage || "").trim();
|
|
51
5
|
const normalizedMessage = String(error?.message || "").trim();
|
|
52
|
-
if (normalizedMessage && !
|
|
6
|
+
if (normalizedMessage && !isGenericHttpErrorMessage(error)) {
|
|
53
7
|
return normalizedMessage;
|
|
54
8
|
}
|
|
55
9
|
if (normalizedFallback) {
|
|
@@ -62,6 +16,10 @@ function toUiErrorMessage(error, fallbackMessage = "", defaultMessage = "Request
|
|
|
62
16
|
return String(defaultMessage || "Request failed.").trim();
|
|
63
17
|
}
|
|
64
18
|
|
|
19
|
+
function toQueryErrorMessage(error, fallbackMessage = "", defaultMessage = "Request failed.") {
|
|
20
|
+
return error ? toUiErrorMessage(error, fallbackMessage, defaultMessage) : "";
|
|
21
|
+
}
|
|
22
|
+
|
|
65
23
|
export {
|
|
66
24
|
toQueryErrorMessage,
|
|
67
25
|
toUiErrorMessage
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { unref } from "vue";
|
|
2
|
+
import { hasValue, isRecord, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
3
|
|
|
3
4
|
function hasResolvedQueryData({ query = null, data = null } = {}) {
|
|
4
5
|
const querySucceeded = Boolean(unref(query?.isSuccess));
|
|
@@ -8,20 +9,8 @@ function hasResolvedQueryData({ query = null, data = null } = {}) {
|
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
function mergeQueryMeta(queryOptions = null, meta = {}) {
|
|
11
|
-
const sourceOptions =
|
|
12
|
-
|
|
13
|
-
const sourceMeta =
|
|
14
|
-
sourceOptions.meta && typeof sourceOptions.meta === "object" && !Array.isArray(sourceOptions.meta)
|
|
15
|
-
? sourceOptions.meta
|
|
16
|
-
: {};
|
|
17
|
-
const sourceJskitMeta =
|
|
18
|
-
sourceMeta.jskit && typeof sourceMeta.jskit === "object" && !Array.isArray(sourceMeta.jskit)
|
|
19
|
-
? sourceMeta.jskit
|
|
20
|
-
: {};
|
|
21
|
-
const nextJskitMeta =
|
|
22
|
-
meta.jskit && typeof meta.jskit === "object" && !Array.isArray(meta.jskit)
|
|
23
|
-
? meta.jskit
|
|
24
|
-
: {};
|
|
12
|
+
const sourceOptions = isRecord(queryOptions) ? queryOptions : {};
|
|
13
|
+
const sourceMeta = isRecord(sourceOptions.meta) ? sourceOptions.meta : {};
|
|
25
14
|
|
|
26
15
|
return {
|
|
27
16
|
...sourceOptions,
|
|
@@ -29,175 +18,97 @@ function mergeQueryMeta(queryOptions = null, meta = {}) {
|
|
|
29
18
|
...sourceMeta,
|
|
30
19
|
...meta,
|
|
31
20
|
jskit: {
|
|
32
|
-
...
|
|
33
|
-
...
|
|
21
|
+
...(isRecord(sourceMeta.jskit) ? sourceMeta.jskit : {}),
|
|
22
|
+
...(isRecord(meta.jskit) ? meta.jskit : {})
|
|
34
23
|
}
|
|
35
24
|
}
|
|
36
25
|
};
|
|
37
26
|
}
|
|
38
27
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
28
|
+
// Each requestRecovery option and the query meta key shell-web's request
|
|
29
|
+
// recovery reads for it.
|
|
30
|
+
const REQUEST_RECOVERY_META_KEYS = Object.freeze({
|
|
31
|
+
label: "requestRecoveryLabel",
|
|
32
|
+
source: "requestRecoverySource",
|
|
33
|
+
dedupeKey: "requestRecoveryDedupeKey",
|
|
34
|
+
method: "requestRecoveryMethod",
|
|
35
|
+
dedupeWindowMs: "requestRecoveryDedupeWindowMs"
|
|
36
|
+
});
|
|
37
|
+
const DISABLED_REQUEST_RECOVERY_META = Object.freeze({
|
|
38
|
+
jskit: Object.freeze({ requestRecovery: false })
|
|
39
|
+
});
|
|
42
40
|
|
|
43
|
-
function
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (normalized) {
|
|
47
|
-
return normalized;
|
|
48
|
-
}
|
|
41
|
+
function normalizeRequestRecoveryInput(requestRecovery = null) {
|
|
42
|
+
if (typeof requestRecovery === "string") {
|
|
43
|
+
return { label: requestRecovery };
|
|
49
44
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (Number.isFinite(numberValue)) {
|
|
57
|
-
return Math.max(0, numberValue);
|
|
45
|
+
const source = isRecord(requestRecovery) ? requestRecovery : {};
|
|
46
|
+
for (const key of Object.keys(source)) {
|
|
47
|
+
if (key !== "enabled" && !Object.hasOwn(REQUEST_RECOVERY_META_KEYS, key)) {
|
|
48
|
+
throw new TypeError(
|
|
49
|
+
`requestRecovery.${key} is not supported; use ${Object.keys(REQUEST_RECOVERY_META_KEYS).join(", ")} or enabled.`
|
|
50
|
+
);
|
|
58
51
|
}
|
|
59
52
|
}
|
|
60
|
-
return
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function normalizePlainObject(value = null) {
|
|
64
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
53
|
+
return source;
|
|
65
54
|
}
|
|
66
55
|
|
|
67
|
-
function
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
};
|
|
56
|
+
function normalizeRequestRecoveryValue(name, value) {
|
|
57
|
+
if (name === "dedupeWindowMs") {
|
|
58
|
+
const numberValue = value == null || value === "" ? Number.NaN : Number(value);
|
|
59
|
+
return Number.isFinite(numberValue) ? Math.max(0, numberValue) : null;
|
|
72
60
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function isRequestRecoveryMetaDisabled(sourceJskitMeta = {}) {
|
|
81
|
-
return sourceJskitMeta.requestRecovery === false;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function hasUsableMetaValue(source = {}, key = "") {
|
|
85
|
-
return Object.hasOwn(source, key) && source[key] !== undefined && source[key] !== null && source[key] !== "";
|
|
61
|
+
const text = normalizeText(value);
|
|
62
|
+
if (!text) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return name === "method" ? text.toUpperCase() : text;
|
|
86
66
|
}
|
|
87
67
|
|
|
68
|
+
// requestRecovery is `false`, a label string, or an object of the short option
|
|
69
|
+
// names above (plus `enabled: false`); missing values come from `defaults`.
|
|
88
70
|
function buildRequestRecoveryMeta(requestRecovery = null, defaults = {}) {
|
|
89
71
|
if (requestRecovery === false) {
|
|
90
|
-
return
|
|
91
|
-
jskit: {
|
|
92
|
-
requestRecovery: false
|
|
93
|
-
}
|
|
94
|
-
};
|
|
72
|
+
return DISABLED_REQUEST_RECOVERY_META;
|
|
95
73
|
}
|
|
96
|
-
|
|
97
|
-
const fallback = normalizePlainObject(defaults);
|
|
98
74
|
const source = normalizeRequestRecoveryInput(requestRecovery);
|
|
99
|
-
if (source.
|
|
100
|
-
return
|
|
101
|
-
jskit: {
|
|
102
|
-
requestRecovery: false
|
|
103
|
-
}
|
|
104
|
-
};
|
|
75
|
+
if (source.enabled === false) {
|
|
76
|
+
return DISABLED_REQUEST_RECOVERY_META;
|
|
105
77
|
}
|
|
106
78
|
|
|
107
|
-
const
|
|
108
|
-
source.requestRecoveryLabel,
|
|
109
|
-
source.label,
|
|
110
|
-
fallback.requestRecoveryLabel,
|
|
111
|
-
fallback.label
|
|
112
|
-
);
|
|
113
|
-
const recoverySource = firstNormalizedText(
|
|
114
|
-
source.requestRecoverySource,
|
|
115
|
-
source.source,
|
|
116
|
-
fallback.requestRecoverySource,
|
|
117
|
-
fallback.source
|
|
118
|
-
);
|
|
119
|
-
const dedupeKey = firstNormalizedText(
|
|
120
|
-
source.requestRecoveryDedupeKey,
|
|
121
|
-
source.dedupeKey,
|
|
122
|
-
fallback.requestRecoveryDedupeKey,
|
|
123
|
-
fallback.dedupeKey
|
|
124
|
-
);
|
|
125
|
-
const method = normalizeRequestRecoveryMethod(
|
|
126
|
-
firstNormalizedText(
|
|
127
|
-
source.requestRecoveryMethod,
|
|
128
|
-
source.method,
|
|
129
|
-
fallback.requestRecoveryMethod,
|
|
130
|
-
fallback.method
|
|
131
|
-
)
|
|
132
|
-
);
|
|
133
|
-
const dedupeWindowMs = firstNonNegativeFiniteNumber(
|
|
134
|
-
source.requestRecoveryDedupeWindowMs,
|
|
135
|
-
source.dedupeWindowMs,
|
|
136
|
-
fallback.requestRecoveryDedupeWindowMs,
|
|
137
|
-
fallback.dedupeWindowMs
|
|
138
|
-
);
|
|
139
|
-
|
|
79
|
+
const fallback = isRecord(defaults) ? defaults : {};
|
|
140
80
|
const jskit = {};
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (dedupeKey) {
|
|
148
|
-
jskit.requestRecoveryDedupeKey = dedupeKey;
|
|
149
|
-
}
|
|
150
|
-
if (method) {
|
|
151
|
-
jskit.requestRecoveryMethod = method;
|
|
152
|
-
}
|
|
153
|
-
if (dedupeWindowMs !== null) {
|
|
154
|
-
jskit.requestRecoveryDedupeWindowMs = dedupeWindowMs;
|
|
81
|
+
for (const [name, metaKey] of Object.entries(REQUEST_RECOVERY_META_KEYS)) {
|
|
82
|
+
const value = normalizeRequestRecoveryValue(name, source[name]) ??
|
|
83
|
+
normalizeRequestRecoveryValue(name, fallback[name]);
|
|
84
|
+
if (value !== null) {
|
|
85
|
+
jskit[metaKey] = value;
|
|
86
|
+
}
|
|
155
87
|
}
|
|
156
|
-
|
|
157
88
|
return Object.keys(jskit).length > 0 ? { jskit } : {};
|
|
158
89
|
}
|
|
159
90
|
|
|
160
|
-
function resolveRequestRecoveryDefaults(queryOptions = null, defaults = {}) {
|
|
161
|
-
const sourceOptions = normalizePlainObject(queryOptions);
|
|
162
|
-
const sourceMeta = normalizePlainObject(sourceOptions.meta);
|
|
163
|
-
const sourceJskitMeta = normalizePlainObject(sourceMeta.jskit);
|
|
164
|
-
const fallback = normalizePlainObject(defaults);
|
|
165
|
-
if (isRequestRecoveryMetaDisabled(sourceJskitMeta)) {
|
|
166
|
-
return {};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const hasExplicitLabel = hasUsableMetaValue(sourceJskitMeta, "requestRecoveryLabel");
|
|
170
|
-
const hasExplicitSource = hasUsableMetaValue(sourceJskitMeta, "requestRecoverySource");
|
|
171
|
-
const hasExplicitDedupeKey = hasUsableMetaValue(sourceJskitMeta, "requestRecoveryDedupeKey");
|
|
172
|
-
const hasExplicitDedupeWindowMs = hasUsableMetaValue(sourceJskitMeta, "requestRecoveryDedupeWindowMs");
|
|
173
|
-
const hasExplicitMethod = hasUsableMetaValue(sourceJskitMeta, "requestRecoveryMethod");
|
|
174
|
-
|
|
175
|
-
return {
|
|
176
|
-
...fallback,
|
|
177
|
-
requestRecoveryLabel: hasExplicitLabel ? "" : fallback.requestRecoveryLabel,
|
|
178
|
-
label: hasExplicitLabel ? "" : fallback.label,
|
|
179
|
-
requestRecoverySource: hasExplicitSource ? "" : fallback.requestRecoverySource,
|
|
180
|
-
source: hasExplicitSource ? "" : fallback.source,
|
|
181
|
-
requestRecoveryDedupeKey: hasExplicitDedupeKey ? "" : fallback.requestRecoveryDedupeKey,
|
|
182
|
-
dedupeKey: hasExplicitDedupeKey ? "" : fallback.dedupeKey,
|
|
183
|
-
requestRecoveryDedupeWindowMs: hasExplicitDedupeWindowMs ? "" : fallback.requestRecoveryDedupeWindowMs,
|
|
184
|
-
dedupeWindowMs: hasExplicitDedupeWindowMs ? "" : fallback.dedupeWindowMs,
|
|
185
|
-
requestRecoveryMethod: hasExplicitMethod ? "" : fallback.requestRecoveryMethod,
|
|
186
|
-
method: hasExplicitMethod ? "" : fallback.method
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
|
|
190
91
|
function mergeRequestRecoveryQueryMeta(queryOptions = null, requestRecovery = null, defaults = {}) {
|
|
191
|
-
const sourceOptions =
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
92
|
+
const sourceOptions = isRecord(queryOptions) ? queryOptions : {};
|
|
93
|
+
let meta;
|
|
94
|
+
if (requestRecovery == null) {
|
|
95
|
+
const presetMeta = isRecord(sourceOptions.meta?.jskit) ? sourceOptions.meta.jskit : {};
|
|
96
|
+
if (presetMeta.requestRecovery === false) {
|
|
97
|
+
return sourceOptions;
|
|
98
|
+
}
|
|
99
|
+
// Composable defaults never replace a hint the query options already set.
|
|
100
|
+
const fallback = isRecord(defaults) ? defaults : {};
|
|
101
|
+
const unsetDefaults = {};
|
|
102
|
+
for (const [name, metaKey] of Object.entries(REQUEST_RECOVERY_META_KEYS)) {
|
|
103
|
+
if (!hasValue(presetMeta[metaKey])) {
|
|
104
|
+
unsetDefaults[name] = fallback[name];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
meta = buildRequestRecoveryMeta(null, unsetDefaults);
|
|
108
|
+
} else {
|
|
109
|
+
meta = buildRequestRecoveryMeta(requestRecovery, defaults);
|
|
199
110
|
}
|
|
200
|
-
return mergeQueryMeta(sourceOptions, meta);
|
|
111
|
+
return Object.keys(meta).length > 0 ? mergeQueryMeta(sourceOptions, meta) : sourceOptions;
|
|
201
112
|
}
|
|
202
113
|
|
|
203
114
|
export {
|