@routevn/creator-model 1.10.4 → 1.12.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/README.md +38 -0
- package/package.json +1 -1
- package/src/model.js +1311 -126
package/src/model.js
CHANGED
|
@@ -341,7 +341,7 @@ const ANIMATION_EASING_KEYS = [
|
|
|
341
341
|
"easeInOutElastic",
|
|
342
342
|
];
|
|
343
343
|
const VARIABLE_SCOPE_KEYS = ["context", "device", "account"];
|
|
344
|
-
const VARIABLE_TYPE_KEYS = ["string", "number", "boolean"];
|
|
344
|
+
const VARIABLE_TYPE_KEYS = ["string", "number", "boolean", "object"];
|
|
345
345
|
const LAYOUT_TYPE_KEYS = [
|
|
346
346
|
"general",
|
|
347
347
|
"save-load",
|
|
@@ -403,7 +403,7 @@ const SAVE_LOAD_DATE_FORMATS = new Set([
|
|
|
403
403
|
"DD MMM YYYY",
|
|
404
404
|
"YYYY年MM月DD日",
|
|
405
405
|
]);
|
|
406
|
-
export const SCHEMA_VERSION =
|
|
406
|
+
export const SCHEMA_VERSION = 12;
|
|
407
407
|
const LAYOUT_CONTAINER_ELEMENT_TYPES = [
|
|
408
408
|
"folder",
|
|
409
409
|
"container",
|
|
@@ -1680,7 +1680,7 @@ const validateAnimationKeyframes = ({ keyframes, path, errorFactory }) => {
|
|
|
1680
1680
|
{
|
|
1681
1681
|
const result = validateAllowedKeys({
|
|
1682
1682
|
value: keyframe,
|
|
1683
|
-
allowedKeys: ["value", "duration", "easing", "relative"],
|
|
1683
|
+
allowedKeys: ["value", "duration", "delay", "easing", "relative"],
|
|
1684
1684
|
path: keyframePath,
|
|
1685
1685
|
errorFactory,
|
|
1686
1686
|
});
|
|
@@ -1717,6 +1717,16 @@ const validateAnimationKeyframes = ({ keyframes, path, errorFactory }) => {
|
|
|
1717
1717
|
);
|
|
1718
1718
|
}
|
|
1719
1719
|
|
|
1720
|
+
if (
|
|
1721
|
+
keyframe.delay !== undefined &&
|
|
1722
|
+
(!isFiniteNumber(keyframe.delay) || keyframe.delay < 0)
|
|
1723
|
+
) {
|
|
1724
|
+
return invalidFromErrorFactory(
|
|
1725
|
+
errorFactory,
|
|
1726
|
+
`${keyframePath}.delay must be a finite number >= 0 when provided`,
|
|
1727
|
+
);
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1720
1730
|
if (
|
|
1721
1731
|
keyframe.easing !== undefined &&
|
|
1722
1732
|
!ANIMATION_EASING_KEYS.includes(keyframe.easing)
|
|
@@ -2776,138 +2786,1196 @@ const validateParticleItems = ({ items, path, errorFactory }) => {
|
|
|
2776
2786
|
|
|
2777
2787
|
{
|
|
2778
2788
|
const result = validateAllowedKeys({
|
|
2779
|
-
value: item,
|
|
2780
|
-
allowedKeys:
|
|
2781
|
-
item.type === "folder"
|
|
2782
|
-
? ["id", "type", "name", "description"]
|
|
2783
|
-
: [
|
|
2784
|
-
"id",
|
|
2785
|
-
"type",
|
|
2786
|
-
"name",
|
|
2787
|
-
"description",
|
|
2788
|
-
"tagIds",
|
|
2789
|
-
"width",
|
|
2790
|
-
"height",
|
|
2791
|
-
"seed",
|
|
2792
|
-
"modules",
|
|
2793
|
-
"thumbnailFileId",
|
|
2794
|
-
],
|
|
2795
|
-
path: itemPath,
|
|
2789
|
+
value: item,
|
|
2790
|
+
allowedKeys:
|
|
2791
|
+
item.type === "folder"
|
|
2792
|
+
? ["id", "type", "name", "description"]
|
|
2793
|
+
: [
|
|
2794
|
+
"id",
|
|
2795
|
+
"type",
|
|
2796
|
+
"name",
|
|
2797
|
+
"description",
|
|
2798
|
+
"tagIds",
|
|
2799
|
+
"width",
|
|
2800
|
+
"height",
|
|
2801
|
+
"seed",
|
|
2802
|
+
"modules",
|
|
2803
|
+
"thumbnailFileId",
|
|
2804
|
+
],
|
|
2805
|
+
path: itemPath,
|
|
2806
|
+
errorFactory,
|
|
2807
|
+
});
|
|
2808
|
+
if (result?.valid === false) {
|
|
2809
|
+
return result;
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2813
|
+
if (!isNonEmptyString(item.id)) {
|
|
2814
|
+
return invalidFromErrorFactory(
|
|
2815
|
+
errorFactory,
|
|
2816
|
+
`${itemPath}.id must be a non-empty string`,
|
|
2817
|
+
);
|
|
2818
|
+
}
|
|
2819
|
+
|
|
2820
|
+
if (item.id !== itemId) {
|
|
2821
|
+
return invalidFromErrorFactory(
|
|
2822
|
+
errorFactory,
|
|
2823
|
+
`${itemPath}.id must match item key '${itemId}'`,
|
|
2824
|
+
);
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
if (!isNonEmptyString(item.name)) {
|
|
2828
|
+
return invalidFromErrorFactory(
|
|
2829
|
+
errorFactory,
|
|
2830
|
+
`${itemPath}.name must be a non-empty string`,
|
|
2831
|
+
);
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2834
|
+
if (item.description !== undefined && !isString(item.description)) {
|
|
2835
|
+
return invalidFromErrorFactory(
|
|
2836
|
+
errorFactory,
|
|
2837
|
+
`${itemPath}.description must be a string when provided`,
|
|
2838
|
+
);
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
if (item.type !== "particle") {
|
|
2842
|
+
continue;
|
|
2843
|
+
}
|
|
2844
|
+
|
|
2845
|
+
{
|
|
2846
|
+
const result = validateOptionalUniqueIdArray({
|
|
2847
|
+
value: item.tagIds,
|
|
2848
|
+
path: `${itemPath}.tagIds`,
|
|
2849
|
+
errorFactory,
|
|
2850
|
+
allowEmpty: false,
|
|
2851
|
+
});
|
|
2852
|
+
if (result?.valid === false) {
|
|
2853
|
+
return result;
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
|
|
2857
|
+
if (!isFiniteNumber(item.width) || item.width <= 0) {
|
|
2858
|
+
return invalidFromErrorFactory(
|
|
2859
|
+
errorFactory,
|
|
2860
|
+
`${itemPath}.width must be a positive finite number`,
|
|
2861
|
+
);
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2864
|
+
if (!isFiniteNumber(item.height) || item.height <= 0) {
|
|
2865
|
+
return invalidFromErrorFactory(
|
|
2866
|
+
errorFactory,
|
|
2867
|
+
`${itemPath}.height must be a positive finite number`,
|
|
2868
|
+
);
|
|
2869
|
+
}
|
|
2870
|
+
|
|
2871
|
+
if (item.seed !== undefined && !isFiniteNumber(item.seed)) {
|
|
2872
|
+
return invalidFromErrorFactory(
|
|
2873
|
+
errorFactory,
|
|
2874
|
+
`${itemPath}.seed must be a finite number when provided`,
|
|
2875
|
+
);
|
|
2876
|
+
}
|
|
2877
|
+
|
|
2878
|
+
if (
|
|
2879
|
+
item.thumbnailFileId !== undefined &&
|
|
2880
|
+
!isNonEmptyString(item.thumbnailFileId)
|
|
2881
|
+
) {
|
|
2882
|
+
return invalidFromErrorFactory(
|
|
2883
|
+
errorFactory,
|
|
2884
|
+
`${itemPath}.thumbnailFileId must be a non-empty string when provided`,
|
|
2885
|
+
);
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2888
|
+
{
|
|
2889
|
+
const result = validateParticleModules({
|
|
2890
|
+
modules: item.modules,
|
|
2891
|
+
path: `${itemPath}.modules`,
|
|
2892
|
+
errorFactory,
|
|
2893
|
+
});
|
|
2894
|
+
if (result?.valid === false) {
|
|
2895
|
+
return result;
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
};
|
|
2900
|
+
|
|
2901
|
+
const validateVariableTypedValue = ({
|
|
2902
|
+
value,
|
|
2903
|
+
variableType,
|
|
2904
|
+
path,
|
|
2905
|
+
errorFactory,
|
|
2906
|
+
}) => {
|
|
2907
|
+
if (variableType === "string" && !isString(value)) {
|
|
2908
|
+
return invalidFromErrorFactory(errorFactory, `${path} must be a string`);
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2911
|
+
if (variableType === "number" && !isFiniteNumber(value)) {
|
|
2912
|
+
return invalidFromErrorFactory(
|
|
2913
|
+
errorFactory,
|
|
2914
|
+
`${path} must be a finite number`,
|
|
2915
|
+
);
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2918
|
+
if (variableType === "boolean" && typeof value !== "boolean") {
|
|
2919
|
+
return invalidFromErrorFactory(errorFactory, `${path} must be a boolean`);
|
|
2920
|
+
}
|
|
2921
|
+
|
|
2922
|
+
if (
|
|
2923
|
+
variableType === "object" &&
|
|
2924
|
+
(value === null || typeof value !== "object")
|
|
2925
|
+
) {
|
|
2926
|
+
return invalidFromErrorFactory(
|
|
2927
|
+
errorFactory,
|
|
2928
|
+
`${path} must be a non-null object or array`,
|
|
2929
|
+
);
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2932
|
+
if (variableType === "object") {
|
|
2933
|
+
return validateComputedDataValue({
|
|
2934
|
+
value,
|
|
2935
|
+
path,
|
|
2936
|
+
errorFactory,
|
|
2937
|
+
});
|
|
2938
|
+
}
|
|
2939
|
+
};
|
|
2940
|
+
|
|
2941
|
+
const COMPUTED_EXPRESSION_FIXED_OPERAND_COUNTS = Object.freeze({
|
|
2942
|
+
add: 2,
|
|
2943
|
+
sub: 2,
|
|
2944
|
+
mul: 2,
|
|
2945
|
+
div: 2,
|
|
2946
|
+
mod: 2,
|
|
2947
|
+
neg: 1,
|
|
2948
|
+
round: 1,
|
|
2949
|
+
floor: 1,
|
|
2950
|
+
ceil: 1,
|
|
2951
|
+
min: 2,
|
|
2952
|
+
max: 2,
|
|
2953
|
+
clamp: 3,
|
|
2954
|
+
eq: 2,
|
|
2955
|
+
neq: 2,
|
|
2956
|
+
gt: 2,
|
|
2957
|
+
gte: 2,
|
|
2958
|
+
lt: 2,
|
|
2959
|
+
lte: 2,
|
|
2960
|
+
in: 2,
|
|
2961
|
+
not: 1,
|
|
2962
|
+
length: 1,
|
|
2963
|
+
includes: 2,
|
|
2964
|
+
});
|
|
2965
|
+
const COMPUTED_EXPRESSION_VARIADIC_OPERATORS = new Set([
|
|
2966
|
+
"and",
|
|
2967
|
+
"or",
|
|
2968
|
+
"all",
|
|
2969
|
+
"any",
|
|
2970
|
+
]);
|
|
2971
|
+
const COMPUTED_EXPRESSION_NUMERIC_OPERAND_OPERATORS = new Set([
|
|
2972
|
+
"add",
|
|
2973
|
+
"sub",
|
|
2974
|
+
"mul",
|
|
2975
|
+
"div",
|
|
2976
|
+
"mod",
|
|
2977
|
+
"neg",
|
|
2978
|
+
"round",
|
|
2979
|
+
"floor",
|
|
2980
|
+
"ceil",
|
|
2981
|
+
"min",
|
|
2982
|
+
"max",
|
|
2983
|
+
"clamp",
|
|
2984
|
+
]);
|
|
2985
|
+
const COMPUTED_EXPRESSION_NUMERIC_RESULT_OPERATORS = new Set([
|
|
2986
|
+
...COMPUTED_EXPRESSION_NUMERIC_OPERAND_OPERATORS,
|
|
2987
|
+
"length",
|
|
2988
|
+
]);
|
|
2989
|
+
const COMPUTED_EXPRESSION_BOOLEAN_RESULT_OPERATORS = new Set([
|
|
2990
|
+
"eq",
|
|
2991
|
+
"neq",
|
|
2992
|
+
"gt",
|
|
2993
|
+
"gte",
|
|
2994
|
+
"lt",
|
|
2995
|
+
"lte",
|
|
2996
|
+
"in",
|
|
2997
|
+
"and",
|
|
2998
|
+
"or",
|
|
2999
|
+
"all",
|
|
3000
|
+
"any",
|
|
3001
|
+
"not",
|
|
3002
|
+
"includes",
|
|
3003
|
+
]);
|
|
3004
|
+
const COMPUTED_CONDITION_FIXED_OPERAND_COUNTS = Object.freeze({
|
|
3005
|
+
eq: 2,
|
|
3006
|
+
neq: 2,
|
|
3007
|
+
gt: 2,
|
|
3008
|
+
gte: 2,
|
|
3009
|
+
lt: 2,
|
|
3010
|
+
lte: 2,
|
|
3011
|
+
in: 2,
|
|
3012
|
+
add: 2,
|
|
3013
|
+
sub: 2,
|
|
3014
|
+
});
|
|
3015
|
+
const COMPUTED_CONDITION_VARIADIC_OPERATORS = new Set(["all", "any"]);
|
|
3016
|
+
|
|
3017
|
+
const getComputedValueType = (value) => {
|
|
3018
|
+
if (value === null) {
|
|
3019
|
+
return "null";
|
|
3020
|
+
}
|
|
3021
|
+
if (typeof value === "object") {
|
|
3022
|
+
return "object";
|
|
3023
|
+
}
|
|
3024
|
+
return typeof value;
|
|
3025
|
+
};
|
|
3026
|
+
|
|
3027
|
+
const validComputedResult = (valueType) => ({
|
|
3028
|
+
valid: true,
|
|
3029
|
+
valueType,
|
|
3030
|
+
});
|
|
3031
|
+
|
|
3032
|
+
const validateComputedDataValue = ({
|
|
3033
|
+
value,
|
|
3034
|
+
path,
|
|
3035
|
+
errorFactory,
|
|
3036
|
+
ancestors = new Set(),
|
|
3037
|
+
}) => {
|
|
3038
|
+
if (
|
|
3039
|
+
value === null ||
|
|
3040
|
+
typeof value === "string" ||
|
|
3041
|
+
typeof value === "boolean"
|
|
3042
|
+
) {
|
|
3043
|
+
return VALID_RESULT;
|
|
3044
|
+
}
|
|
3045
|
+
|
|
3046
|
+
if (typeof value === "number") {
|
|
3047
|
+
if (Number.isFinite(value)) {
|
|
3048
|
+
return VALID_RESULT;
|
|
3049
|
+
}
|
|
3050
|
+
return invalidFromErrorFactory(
|
|
3051
|
+
errorFactory,
|
|
3052
|
+
`${path} must use finite numeric values`,
|
|
3053
|
+
);
|
|
3054
|
+
}
|
|
3055
|
+
|
|
3056
|
+
if (Array.isArray(value)) {
|
|
3057
|
+
if (ancestors.has(value)) {
|
|
3058
|
+
return invalidFromErrorFactory(
|
|
3059
|
+
errorFactory,
|
|
3060
|
+
`${path} must not contain cyclic data`,
|
|
3061
|
+
);
|
|
3062
|
+
}
|
|
3063
|
+
ancestors.add(value);
|
|
3064
|
+
|
|
3065
|
+
for (const [index, item] of value.entries()) {
|
|
3066
|
+
const result = validateComputedDataValue({
|
|
3067
|
+
value: item,
|
|
3068
|
+
path: `${path}[${index}]`,
|
|
3069
|
+
errorFactory,
|
|
3070
|
+
ancestors,
|
|
3071
|
+
});
|
|
3072
|
+
if (result?.valid === false) {
|
|
3073
|
+
return result;
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
ancestors.delete(value);
|
|
3077
|
+
return VALID_RESULT;
|
|
3078
|
+
}
|
|
3079
|
+
|
|
3080
|
+
if (
|
|
3081
|
+
!isPlainObject(value) ||
|
|
3082
|
+
![Object.prototype, null].includes(Object.getPrototypeOf(value))
|
|
3083
|
+
) {
|
|
3084
|
+
return invalidFromErrorFactory(
|
|
3085
|
+
errorFactory,
|
|
3086
|
+
`${path} must contain JSON-compatible data`,
|
|
3087
|
+
);
|
|
3088
|
+
}
|
|
3089
|
+
|
|
3090
|
+
if (ancestors.has(value)) {
|
|
3091
|
+
return invalidFromErrorFactory(
|
|
3092
|
+
errorFactory,
|
|
3093
|
+
`${path} must not contain cyclic data`,
|
|
3094
|
+
);
|
|
3095
|
+
}
|
|
3096
|
+
ancestors.add(value);
|
|
3097
|
+
|
|
3098
|
+
for (const [key, item] of Object.entries(value)) {
|
|
3099
|
+
const result = validateComputedDataValue({
|
|
3100
|
+
value: item,
|
|
3101
|
+
path: `${path}.${key}`,
|
|
3102
|
+
errorFactory,
|
|
3103
|
+
ancestors,
|
|
3104
|
+
});
|
|
3105
|
+
if (result?.valid === false) {
|
|
3106
|
+
return result;
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
|
|
3110
|
+
ancestors.delete(value);
|
|
3111
|
+
return VALID_RESULT;
|
|
3112
|
+
};
|
|
3113
|
+
|
|
3114
|
+
const invalidComputedReferenceParseResult = Object.freeze({
|
|
3115
|
+
valid: false,
|
|
3116
|
+
});
|
|
3117
|
+
|
|
3118
|
+
const decodeComputedReferenceQuotedPart = (rawValue) => {
|
|
3119
|
+
if (rawValue[0] === '"') {
|
|
3120
|
+
try {
|
|
3121
|
+
return {
|
|
3122
|
+
valid: true,
|
|
3123
|
+
value: JSON.parse(rawValue),
|
|
3124
|
+
};
|
|
3125
|
+
} catch {
|
|
3126
|
+
return invalidComputedReferenceParseResult;
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
|
|
3130
|
+
let value = "";
|
|
3131
|
+
const escapedValues = {
|
|
3132
|
+
"\\": "\\",
|
|
3133
|
+
'"': '"',
|
|
3134
|
+
"'": "'",
|
|
3135
|
+
"/": "/",
|
|
3136
|
+
n: "\n",
|
|
3137
|
+
r: "\r",
|
|
3138
|
+
t: "\t",
|
|
3139
|
+
b: "\b",
|
|
3140
|
+
f: "\f",
|
|
3141
|
+
};
|
|
3142
|
+
|
|
3143
|
+
for (let index = 1; index < rawValue.length - 1; index += 1) {
|
|
3144
|
+
const character = rawValue[index];
|
|
3145
|
+
if (character !== "\\") {
|
|
3146
|
+
if (character.charCodeAt(0) < 0x20) {
|
|
3147
|
+
return invalidComputedReferenceParseResult;
|
|
3148
|
+
}
|
|
3149
|
+
value += character;
|
|
3150
|
+
continue;
|
|
3151
|
+
}
|
|
3152
|
+
|
|
3153
|
+
index += 1;
|
|
3154
|
+
if (index >= rawValue.length - 1) {
|
|
3155
|
+
return invalidComputedReferenceParseResult;
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3158
|
+
const escapedCharacter = rawValue[index];
|
|
3159
|
+
if (escapedCharacter === "u") {
|
|
3160
|
+
const hexValue = rawValue.slice(index + 1, index + 5);
|
|
3161
|
+
if (hexValue.length !== 4 || !/^[0-9a-fA-F]{4}$/.test(hexValue)) {
|
|
3162
|
+
return invalidComputedReferenceParseResult;
|
|
3163
|
+
}
|
|
3164
|
+
value += String.fromCharCode(Number.parseInt(hexValue, 16));
|
|
3165
|
+
index += 4;
|
|
3166
|
+
continue;
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
if (!Object.hasOwn(escapedValues, escapedCharacter)) {
|
|
3170
|
+
return invalidComputedReferenceParseResult;
|
|
3171
|
+
}
|
|
3172
|
+
value += escapedValues[escapedCharacter];
|
|
3173
|
+
}
|
|
3174
|
+
|
|
3175
|
+
return {
|
|
3176
|
+
valid: true,
|
|
3177
|
+
value,
|
|
3178
|
+
};
|
|
3179
|
+
};
|
|
3180
|
+
|
|
3181
|
+
const parseComputedReferencePath = (value) => {
|
|
3182
|
+
if (
|
|
3183
|
+
typeof value !== "string" ||
|
|
3184
|
+
value.length === 0 ||
|
|
3185
|
+
value.trim() !== value
|
|
3186
|
+
) {
|
|
3187
|
+
return invalidComputedReferenceParseResult;
|
|
3188
|
+
}
|
|
3189
|
+
|
|
3190
|
+
const parts = [];
|
|
3191
|
+
let index = 0;
|
|
3192
|
+
|
|
3193
|
+
const readBarePart = () => {
|
|
3194
|
+
const startIndex = index;
|
|
3195
|
+
while (
|
|
3196
|
+
index < value.length &&
|
|
3197
|
+
value[index] !== "." &&
|
|
3198
|
+
value[index] !== "[" &&
|
|
3199
|
+
value[index] !== "]"
|
|
3200
|
+
) {
|
|
3201
|
+
index += 1;
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
const part = value.slice(startIndex, index);
|
|
3205
|
+
if (part.length === 0 || part.trim() !== part || /\s/.test(part)) {
|
|
3206
|
+
return false;
|
|
3207
|
+
}
|
|
3208
|
+
parts.push(part);
|
|
3209
|
+
return true;
|
|
3210
|
+
};
|
|
3211
|
+
|
|
3212
|
+
const readBracketPart = () => {
|
|
3213
|
+
index += 1;
|
|
3214
|
+
while (index < value.length && /\s/.test(value[index])) {
|
|
3215
|
+
index += 1;
|
|
3216
|
+
}
|
|
3217
|
+
|
|
3218
|
+
const firstCharacter = value[index];
|
|
3219
|
+
if (/\d/.test(firstCharacter ?? "")) {
|
|
3220
|
+
const startIndex = index;
|
|
3221
|
+
while (index < value.length && /\d/.test(value[index])) {
|
|
3222
|
+
index += 1;
|
|
3223
|
+
}
|
|
3224
|
+
const rawPart = value.slice(startIndex, index);
|
|
3225
|
+
while (index < value.length && /\s/.test(value[index])) {
|
|
3226
|
+
index += 1;
|
|
3227
|
+
}
|
|
3228
|
+
if (
|
|
3229
|
+
value[index] !== "]" ||
|
|
3230
|
+
(rawPart.length > 1 && rawPart.startsWith("0"))
|
|
3231
|
+
) {
|
|
3232
|
+
return false;
|
|
3233
|
+
}
|
|
3234
|
+
index += 1;
|
|
3235
|
+
parts.push(rawPart);
|
|
3236
|
+
return true;
|
|
3237
|
+
}
|
|
3238
|
+
|
|
3239
|
+
if (firstCharacter !== '"' && firstCharacter !== "'") {
|
|
3240
|
+
return false;
|
|
3241
|
+
}
|
|
3242
|
+
|
|
3243
|
+
const quote = firstCharacter;
|
|
3244
|
+
const startIndex = index;
|
|
3245
|
+
index += 1;
|
|
3246
|
+
let escaped = false;
|
|
3247
|
+
while (index < value.length) {
|
|
3248
|
+
const character = value[index];
|
|
3249
|
+
if (escaped) {
|
|
3250
|
+
escaped = false;
|
|
3251
|
+
} else if (character === "\\") {
|
|
3252
|
+
escaped = true;
|
|
3253
|
+
} else if (character === quote) {
|
|
3254
|
+
const decodedPart = decodeComputedReferenceQuotedPart(
|
|
3255
|
+
value.slice(startIndex, index + 1),
|
|
3256
|
+
);
|
|
3257
|
+
if (!decodedPart.valid) {
|
|
3258
|
+
return false;
|
|
3259
|
+
}
|
|
3260
|
+
|
|
3261
|
+
index += 1;
|
|
3262
|
+
while (index < value.length && /\s/.test(value[index])) {
|
|
3263
|
+
index += 1;
|
|
3264
|
+
}
|
|
3265
|
+
if (value[index] !== "]") {
|
|
3266
|
+
return false;
|
|
3267
|
+
}
|
|
3268
|
+
index += 1;
|
|
3269
|
+
parts.push(decodedPart.value);
|
|
3270
|
+
return true;
|
|
3271
|
+
}
|
|
3272
|
+
index += 1;
|
|
3273
|
+
}
|
|
3274
|
+
|
|
3275
|
+
return false;
|
|
3276
|
+
};
|
|
3277
|
+
|
|
3278
|
+
if (!readBarePart()) {
|
|
3279
|
+
return invalidComputedReferenceParseResult;
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
while (index < value.length) {
|
|
3283
|
+
if (value[index] === ".") {
|
|
3284
|
+
index += 1;
|
|
3285
|
+
if (!readBarePart()) {
|
|
3286
|
+
return invalidComputedReferenceParseResult;
|
|
3287
|
+
}
|
|
3288
|
+
continue;
|
|
3289
|
+
}
|
|
3290
|
+
|
|
3291
|
+
if (value[index] === "[") {
|
|
3292
|
+
if (!readBracketPart()) {
|
|
3293
|
+
return invalidComputedReferenceParseResult;
|
|
3294
|
+
}
|
|
3295
|
+
continue;
|
|
3296
|
+
}
|
|
3297
|
+
|
|
3298
|
+
return invalidComputedReferenceParseResult;
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
return {
|
|
3302
|
+
valid: true,
|
|
3303
|
+
parts,
|
|
3304
|
+
};
|
|
3305
|
+
};
|
|
3306
|
+
|
|
3307
|
+
const validateComputedReferencePath = ({
|
|
3308
|
+
value,
|
|
3309
|
+
path,
|
|
3310
|
+
errorFactory,
|
|
3311
|
+
variables,
|
|
3312
|
+
dependencies,
|
|
3313
|
+
}) => {
|
|
3314
|
+
if (!isNonEmptyString(value)) {
|
|
3315
|
+
return invalidFromErrorFactory(
|
|
3316
|
+
errorFactory,
|
|
3317
|
+
`${path} must be a non-empty string path`,
|
|
3318
|
+
);
|
|
3319
|
+
}
|
|
3320
|
+
|
|
3321
|
+
const parsedPath = parseComputedReferencePath(value);
|
|
3322
|
+
if (!parsedPath.valid) {
|
|
3323
|
+
return invalidFromErrorFactory(
|
|
3324
|
+
errorFactory,
|
|
3325
|
+
`${path} has an invalid reference path`,
|
|
3326
|
+
);
|
|
3327
|
+
}
|
|
3328
|
+
|
|
3329
|
+
const [root, referencedId, ...nestedPath] = parsedPath.parts;
|
|
3330
|
+
if (root !== "variables" && root !== "runtime") {
|
|
3331
|
+
return invalidFromErrorFactory(
|
|
3332
|
+
errorFactory,
|
|
3333
|
+
`${path} must reference a concrete variables.* or runtime.* path`,
|
|
3334
|
+
);
|
|
3335
|
+
}
|
|
3336
|
+
|
|
3337
|
+
if (!isNonEmptyString(referencedId)) {
|
|
3338
|
+
return invalidFromErrorFactory(
|
|
3339
|
+
errorFactory,
|
|
3340
|
+
`${path} must reference a concrete ${root} member`,
|
|
3341
|
+
);
|
|
3342
|
+
}
|
|
3343
|
+
|
|
3344
|
+
if (root === "runtime" || variables === undefined) {
|
|
3345
|
+
return validComputedResult(undefined);
|
|
3346
|
+
}
|
|
3347
|
+
|
|
3348
|
+
const referencedVariable = Object.hasOwn(variables, referencedId)
|
|
3349
|
+
? variables[referencedId]
|
|
3350
|
+
: undefined;
|
|
3351
|
+
if (
|
|
3352
|
+
!isPlainObject(referencedVariable) ||
|
|
3353
|
+
referencedVariable.type !== "variable"
|
|
3354
|
+
) {
|
|
3355
|
+
return invalidFromErrorFactory(
|
|
3356
|
+
errorFactory,
|
|
3357
|
+
`${path} references unknown variable '${referencedId}'`,
|
|
3358
|
+
);
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3361
|
+
if (Object.hasOwn(referencedVariable, "computed")) {
|
|
3362
|
+
dependencies?.add(referencedId);
|
|
3363
|
+
}
|
|
3364
|
+
|
|
3365
|
+
return validComputedResult(
|
|
3366
|
+
nestedPath.length === 0 ? referencedVariable.variableType : undefined,
|
|
3367
|
+
);
|
|
3368
|
+
};
|
|
3369
|
+
|
|
3370
|
+
const validateComputedExpression = ({
|
|
3371
|
+
expression,
|
|
3372
|
+
path,
|
|
3373
|
+
errorFactory,
|
|
3374
|
+
variables,
|
|
3375
|
+
dependencies,
|
|
3376
|
+
ancestors = new Set(),
|
|
3377
|
+
}) => {
|
|
3378
|
+
if (expression === null) {
|
|
3379
|
+
return validComputedResult("null");
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3382
|
+
if (typeof expression !== "object") {
|
|
3383
|
+
if (typeof expression === "number") {
|
|
3384
|
+
if (Number.isFinite(expression)) {
|
|
3385
|
+
return validComputedResult("number");
|
|
3386
|
+
}
|
|
3387
|
+
return invalidFromErrorFactory(
|
|
3388
|
+
errorFactory,
|
|
3389
|
+
`${path} must use finite numeric literals`,
|
|
3390
|
+
);
|
|
3391
|
+
}
|
|
3392
|
+
|
|
3393
|
+
if (typeof expression === "string" || typeof expression === "boolean") {
|
|
3394
|
+
return validComputedResult(typeof expression);
|
|
3395
|
+
}
|
|
3396
|
+
|
|
3397
|
+
return invalidFromErrorFactory(
|
|
3398
|
+
errorFactory,
|
|
3399
|
+
`${path} must use JSON-compatible primitive literals`,
|
|
3400
|
+
);
|
|
3401
|
+
}
|
|
3402
|
+
|
|
3403
|
+
if (Array.isArray(expression)) {
|
|
3404
|
+
return invalidFromErrorFactory(
|
|
3405
|
+
errorFactory,
|
|
3406
|
+
`${path} arrays must be wrapped in a literal operator or authored as value`,
|
|
3407
|
+
);
|
|
3408
|
+
}
|
|
3409
|
+
|
|
3410
|
+
if (ancestors.has(expression)) {
|
|
3411
|
+
return invalidFromErrorFactory(
|
|
3412
|
+
errorFactory,
|
|
3413
|
+
`${path} must not contain cyclic expression data`,
|
|
3414
|
+
);
|
|
3415
|
+
}
|
|
3416
|
+
const nextAncestors = new Set(ancestors);
|
|
3417
|
+
nextAncestors.add(expression);
|
|
3418
|
+
|
|
3419
|
+
const entries = Object.entries(expression);
|
|
3420
|
+
if (entries.length !== 1) {
|
|
3421
|
+
return invalidFromErrorFactory(
|
|
3422
|
+
errorFactory,
|
|
3423
|
+
`${path} must contain exactly one expression operator`,
|
|
3424
|
+
);
|
|
3425
|
+
}
|
|
3426
|
+
|
|
3427
|
+
const [[operator, operands]] = entries;
|
|
3428
|
+
if (operator === "var") {
|
|
3429
|
+
const result = validateComputedReferencePath({
|
|
3430
|
+
value: operands,
|
|
3431
|
+
path: `${path}.var`,
|
|
3432
|
+
errorFactory,
|
|
3433
|
+
variables,
|
|
3434
|
+
dependencies,
|
|
3435
|
+
});
|
|
3436
|
+
return result;
|
|
3437
|
+
}
|
|
3438
|
+
|
|
3439
|
+
if (operator === "literal") {
|
|
3440
|
+
const result = validateComputedDataValue({
|
|
3441
|
+
value: operands,
|
|
3442
|
+
path: `${path}.literal`,
|
|
3443
|
+
errorFactory,
|
|
3444
|
+
});
|
|
3445
|
+
return result?.valid === false
|
|
3446
|
+
? result
|
|
3447
|
+
: validComputedResult(getComputedValueType(operands));
|
|
3448
|
+
}
|
|
3449
|
+
|
|
3450
|
+
const fixedOperandCount = COMPUTED_EXPRESSION_FIXED_OPERAND_COUNTS[operator];
|
|
3451
|
+
const isVariadic = COMPUTED_EXPRESSION_VARIADIC_OPERATORS.has(operator);
|
|
3452
|
+
if (fixedOperandCount === undefined && !isVariadic) {
|
|
3453
|
+
return invalidFromErrorFactory(
|
|
3454
|
+
errorFactory,
|
|
3455
|
+
`${path} contains unsupported expression operator '${operator}'`,
|
|
3456
|
+
);
|
|
3457
|
+
}
|
|
3458
|
+
|
|
3459
|
+
if (!Array.isArray(operands)) {
|
|
3460
|
+
return invalidFromErrorFactory(
|
|
3461
|
+
errorFactory,
|
|
3462
|
+
`${path}.${operator} must be an operand array`,
|
|
3463
|
+
);
|
|
3464
|
+
}
|
|
3465
|
+
|
|
3466
|
+
if (
|
|
3467
|
+
(fixedOperandCount !== undefined &&
|
|
3468
|
+
operands.length !== fixedOperandCount) ||
|
|
3469
|
+
(isVariadic && operands.length === 0)
|
|
3470
|
+
) {
|
|
3471
|
+
const operandRequirement = isVariadic
|
|
3472
|
+
? "at least one operand"
|
|
3473
|
+
: `exactly ${fixedOperandCount} operands`;
|
|
3474
|
+
return invalidFromErrorFactory(
|
|
3475
|
+
errorFactory,
|
|
3476
|
+
`${path}.${operator} requires ${operandRequirement}`,
|
|
3477
|
+
);
|
|
3478
|
+
}
|
|
3479
|
+
|
|
3480
|
+
const operandTypes = [];
|
|
3481
|
+
for (const [index, operand] of operands.entries()) {
|
|
3482
|
+
const result = validateComputedExpression({
|
|
3483
|
+
expression: operand,
|
|
3484
|
+
path: `${path}.${operator}[${index}]`,
|
|
3485
|
+
errorFactory,
|
|
3486
|
+
variables,
|
|
3487
|
+
dependencies,
|
|
3488
|
+
ancestors: nextAncestors,
|
|
3489
|
+
});
|
|
3490
|
+
if (result?.valid === false) {
|
|
3491
|
+
return result;
|
|
3492
|
+
}
|
|
3493
|
+
operandTypes.push(result.valueType);
|
|
3494
|
+
}
|
|
3495
|
+
|
|
3496
|
+
if (
|
|
3497
|
+
COMPUTED_EXPRESSION_NUMERIC_OPERAND_OPERATORS.has(operator) &&
|
|
3498
|
+
operandTypes.some(
|
|
3499
|
+
(operandType) => operandType !== undefined && operandType !== "number",
|
|
3500
|
+
)
|
|
3501
|
+
) {
|
|
3502
|
+
return invalidFromErrorFactory(
|
|
3503
|
+
errorFactory,
|
|
3504
|
+
`${path}.${operator} requires numeric operands`,
|
|
3505
|
+
);
|
|
3506
|
+
}
|
|
3507
|
+
|
|
3508
|
+
if (COMPUTED_EXPRESSION_NUMERIC_RESULT_OPERATORS.has(operator)) {
|
|
3509
|
+
return validComputedResult("number");
|
|
3510
|
+
}
|
|
3511
|
+
if (COMPUTED_EXPRESSION_BOOLEAN_RESULT_OPERATORS.has(operator)) {
|
|
3512
|
+
return validComputedResult("boolean");
|
|
3513
|
+
}
|
|
3514
|
+
return validComputedResult(undefined);
|
|
3515
|
+
};
|
|
3516
|
+
|
|
3517
|
+
const validateComputedCondition = ({
|
|
3518
|
+
condition,
|
|
3519
|
+
path,
|
|
3520
|
+
errorFactory,
|
|
3521
|
+
variables,
|
|
3522
|
+
dependencies,
|
|
3523
|
+
isRoot = true,
|
|
3524
|
+
ancestors = new Set(),
|
|
3525
|
+
}) => {
|
|
3526
|
+
if (isRoot && typeof condition === "string") {
|
|
3527
|
+
return invalidFromErrorFactory(
|
|
3528
|
+
errorFactory,
|
|
3529
|
+
`${path} string conditions are not supported`,
|
|
3530
|
+
);
|
|
3531
|
+
}
|
|
3532
|
+
|
|
3533
|
+
if (condition === null) {
|
|
3534
|
+
return validComputedResult("null");
|
|
3535
|
+
}
|
|
3536
|
+
|
|
3537
|
+
if (typeof condition !== "object") {
|
|
3538
|
+
if (typeof condition === "number") {
|
|
3539
|
+
if (Number.isFinite(condition)) {
|
|
3540
|
+
return validComputedResult("number");
|
|
3541
|
+
}
|
|
3542
|
+
return invalidFromErrorFactory(
|
|
3543
|
+
errorFactory,
|
|
3544
|
+
`${path} must use finite numeric literals`,
|
|
3545
|
+
);
|
|
3546
|
+
}
|
|
3547
|
+
|
|
3548
|
+
if (typeof condition === "string" || typeof condition === "boolean") {
|
|
3549
|
+
return validComputedResult(typeof condition);
|
|
3550
|
+
}
|
|
3551
|
+
|
|
3552
|
+
return invalidFromErrorFactory(
|
|
3553
|
+
errorFactory,
|
|
3554
|
+
`${path} must use JSON-compatible primitive literals`,
|
|
3555
|
+
);
|
|
3556
|
+
}
|
|
3557
|
+
|
|
3558
|
+
if (Array.isArray(condition)) {
|
|
3559
|
+
return invalidFromErrorFactory(
|
|
3560
|
+
errorFactory,
|
|
3561
|
+
`${path} arrays must be wrapped in a condition operator or literal`,
|
|
3562
|
+
);
|
|
3563
|
+
}
|
|
3564
|
+
|
|
3565
|
+
if (ancestors.has(condition)) {
|
|
3566
|
+
return invalidFromErrorFactory(
|
|
3567
|
+
errorFactory,
|
|
3568
|
+
`${path} must not contain cyclic condition data`,
|
|
3569
|
+
);
|
|
3570
|
+
}
|
|
3571
|
+
const nextAncestors = new Set(ancestors);
|
|
3572
|
+
nextAncestors.add(condition);
|
|
3573
|
+
|
|
3574
|
+
const entries = Object.entries(condition);
|
|
3575
|
+
if (entries.length !== 1) {
|
|
3576
|
+
return invalidFromErrorFactory(
|
|
3577
|
+
errorFactory,
|
|
3578
|
+
`${path} must contain exactly one condition operator`,
|
|
3579
|
+
);
|
|
3580
|
+
}
|
|
3581
|
+
|
|
3582
|
+
const [[operator, operands]] = entries;
|
|
3583
|
+
if (operator === "var") {
|
|
3584
|
+
const result = validateComputedReferencePath({
|
|
3585
|
+
value: operands,
|
|
3586
|
+
path: `${path}.var`,
|
|
3587
|
+
errorFactory,
|
|
3588
|
+
variables,
|
|
3589
|
+
dependencies,
|
|
3590
|
+
});
|
|
3591
|
+
return result;
|
|
3592
|
+
}
|
|
3593
|
+
if (operator === "literal") {
|
|
3594
|
+
const result = validateComputedDataValue({
|
|
3595
|
+
value: operands,
|
|
3596
|
+
path: `${path}.literal`,
|
|
3597
|
+
errorFactory,
|
|
3598
|
+
});
|
|
3599
|
+
return result?.valid === false
|
|
3600
|
+
? result
|
|
3601
|
+
: validComputedResult(getComputedValueType(operands));
|
|
3602
|
+
}
|
|
3603
|
+
if (operator === "call") {
|
|
3604
|
+
return invalidFromErrorFactory(
|
|
3605
|
+
errorFactory,
|
|
3606
|
+
`${path} function calls are not supported`,
|
|
3607
|
+
);
|
|
3608
|
+
}
|
|
3609
|
+
if (operator === "not") {
|
|
3610
|
+
const result = validateComputedCondition({
|
|
3611
|
+
condition: operands,
|
|
3612
|
+
path: `${path}.not`,
|
|
3613
|
+
errorFactory,
|
|
3614
|
+
variables,
|
|
3615
|
+
dependencies,
|
|
3616
|
+
isRoot: false,
|
|
3617
|
+
ancestors: nextAncestors,
|
|
3618
|
+
});
|
|
3619
|
+
return result?.valid === false ? result : validComputedResult("boolean");
|
|
3620
|
+
}
|
|
3621
|
+
|
|
3622
|
+
const fixedOperandCount = COMPUTED_CONDITION_FIXED_OPERAND_COUNTS[operator];
|
|
3623
|
+
const isVariadic = COMPUTED_CONDITION_VARIADIC_OPERATORS.has(operator);
|
|
3624
|
+
if (fixedOperandCount === undefined && !isVariadic) {
|
|
3625
|
+
return invalidFromErrorFactory(
|
|
3626
|
+
errorFactory,
|
|
3627
|
+
`${path} contains unsupported condition operator '${operator}'`,
|
|
3628
|
+
);
|
|
3629
|
+
}
|
|
3630
|
+
if (!Array.isArray(operands)) {
|
|
3631
|
+
return invalidFromErrorFactory(
|
|
3632
|
+
errorFactory,
|
|
3633
|
+
`${path}.${operator} must be an operand array`,
|
|
3634
|
+
);
|
|
3635
|
+
}
|
|
3636
|
+
if (
|
|
3637
|
+
(fixedOperandCount !== undefined &&
|
|
3638
|
+
operands.length !== fixedOperandCount) ||
|
|
3639
|
+
(isVariadic && operands.length === 0)
|
|
3640
|
+
) {
|
|
3641
|
+
const operandRequirement = isVariadic
|
|
3642
|
+
? "at least one operand"
|
|
3643
|
+
: `exactly ${fixedOperandCount} operands`;
|
|
3644
|
+
return invalidFromErrorFactory(
|
|
3645
|
+
errorFactory,
|
|
3646
|
+
`${path}.${operator} requires ${operandRequirement}`,
|
|
3647
|
+
);
|
|
3648
|
+
}
|
|
3649
|
+
|
|
3650
|
+
const operandTypes = [];
|
|
3651
|
+
for (const [index, operand] of operands.entries()) {
|
|
3652
|
+
const result = validateComputedCondition({
|
|
3653
|
+
condition: operand,
|
|
3654
|
+
path: `${path}.${operator}[${index}]`,
|
|
3655
|
+
errorFactory,
|
|
3656
|
+
variables,
|
|
3657
|
+
dependencies,
|
|
3658
|
+
isRoot: false,
|
|
3659
|
+
ancestors: nextAncestors,
|
|
3660
|
+
});
|
|
3661
|
+
if (result?.valid === false) {
|
|
3662
|
+
return result;
|
|
3663
|
+
}
|
|
3664
|
+
operandTypes.push(result.valueType);
|
|
3665
|
+
}
|
|
3666
|
+
|
|
3667
|
+
if (
|
|
3668
|
+
(operator === "add" || operator === "sub") &&
|
|
3669
|
+
operandTypes.some(
|
|
3670
|
+
(operandType) => operandType !== undefined && operandType !== "number",
|
|
3671
|
+
)
|
|
3672
|
+
) {
|
|
3673
|
+
return invalidFromErrorFactory(
|
|
3674
|
+
errorFactory,
|
|
3675
|
+
`${path}.${operator} requires numeric operands`,
|
|
3676
|
+
);
|
|
3677
|
+
}
|
|
3678
|
+
|
|
3679
|
+
return validComputedResult(
|
|
3680
|
+
operator === "add" || operator === "sub" ? "number" : "boolean",
|
|
3681
|
+
);
|
|
3682
|
+
};
|
|
3683
|
+
|
|
3684
|
+
const validateComputedResultConfig = ({
|
|
3685
|
+
resultConfig,
|
|
3686
|
+
variableType,
|
|
3687
|
+
path,
|
|
3688
|
+
errorFactory,
|
|
3689
|
+
variables,
|
|
3690
|
+
dependencies,
|
|
3691
|
+
allowedKeys = ["expr", "value"],
|
|
3692
|
+
}) => {
|
|
3693
|
+
if (!isPlainObject(resultConfig)) {
|
|
3694
|
+
return invalidFromErrorFactory(errorFactory, `${path} must be an object`);
|
|
3695
|
+
}
|
|
3696
|
+
|
|
3697
|
+
{
|
|
3698
|
+
const result = validateAllowedKeys({
|
|
3699
|
+
value: resultConfig,
|
|
3700
|
+
allowedKeys,
|
|
3701
|
+
path,
|
|
3702
|
+
errorFactory,
|
|
3703
|
+
});
|
|
3704
|
+
if (result?.valid === false) {
|
|
3705
|
+
return result;
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3708
|
+
|
|
3709
|
+
const hasExpression = Object.hasOwn(resultConfig, "expr");
|
|
3710
|
+
const hasValue = Object.hasOwn(resultConfig, "value");
|
|
3711
|
+
if (hasExpression === hasValue) {
|
|
3712
|
+
return invalidFromErrorFactory(
|
|
3713
|
+
errorFactory,
|
|
3714
|
+
`${path} must contain exactly one of expr or value`,
|
|
3715
|
+
);
|
|
3716
|
+
}
|
|
3717
|
+
|
|
3718
|
+
if (hasValue) {
|
|
3719
|
+
const dataResult = validateComputedDataValue({
|
|
3720
|
+
value: resultConfig.value,
|
|
3721
|
+
path: `${path}.value`,
|
|
3722
|
+
errorFactory,
|
|
3723
|
+
});
|
|
3724
|
+
if (dataResult?.valid === false) {
|
|
3725
|
+
return dataResult;
|
|
3726
|
+
}
|
|
3727
|
+
return validateVariableTypedValue({
|
|
3728
|
+
value: resultConfig.value,
|
|
3729
|
+
variableType,
|
|
3730
|
+
path: `${path}.value`,
|
|
3731
|
+
errorFactory,
|
|
3732
|
+
});
|
|
3733
|
+
}
|
|
3734
|
+
|
|
3735
|
+
const expressionResult = validateComputedExpression({
|
|
3736
|
+
expression: resultConfig.expr,
|
|
3737
|
+
path: `${path}.expr`,
|
|
3738
|
+
errorFactory,
|
|
3739
|
+
variables,
|
|
3740
|
+
dependencies,
|
|
3741
|
+
});
|
|
3742
|
+
if (expressionResult?.valid === false) {
|
|
3743
|
+
return expressionResult;
|
|
3744
|
+
}
|
|
3745
|
+
if (
|
|
3746
|
+
expressionResult.valueType !== undefined &&
|
|
3747
|
+
expressionResult.valueType !== variableType
|
|
3748
|
+
) {
|
|
3749
|
+
return invalidFromErrorFactory(
|
|
3750
|
+
errorFactory,
|
|
3751
|
+
`${path}.expr must resolve to ${variableType}`,
|
|
3752
|
+
);
|
|
3753
|
+
}
|
|
3754
|
+
return VALID_RESULT;
|
|
3755
|
+
};
|
|
3756
|
+
|
|
3757
|
+
const validateVariableComputedConfig = ({
|
|
3758
|
+
computed,
|
|
3759
|
+
variableType,
|
|
3760
|
+
path,
|
|
3761
|
+
errorFactory,
|
|
3762
|
+
variables,
|
|
3763
|
+
dependencies,
|
|
3764
|
+
}) => {
|
|
3765
|
+
if (!isPlainObject(computed)) {
|
|
3766
|
+
return invalidFromErrorFactory(errorFactory, `${path} must be an object`);
|
|
3767
|
+
}
|
|
3768
|
+
|
|
3769
|
+
if (Object.hasOwn(computed, "branches")) {
|
|
3770
|
+
{
|
|
3771
|
+
const result = validateAllowedKeys({
|
|
3772
|
+
value: computed,
|
|
3773
|
+
allowedKeys: ["branches", "default"],
|
|
3774
|
+
path,
|
|
2796
3775
|
errorFactory,
|
|
2797
3776
|
});
|
|
2798
3777
|
if (result?.valid === false) {
|
|
2799
3778
|
return result;
|
|
2800
3779
|
}
|
|
2801
3780
|
}
|
|
2802
|
-
|
|
2803
|
-
if (!isNonEmptyString(item.id)) {
|
|
3781
|
+
if (!Array.isArray(computed.branches) || computed.branches.length === 0) {
|
|
2804
3782
|
return invalidFromErrorFactory(
|
|
2805
3783
|
errorFactory,
|
|
2806
|
-
`${
|
|
3784
|
+
`${path}.branches must be a non-empty array`,
|
|
2807
3785
|
);
|
|
2808
3786
|
}
|
|
2809
|
-
|
|
2810
|
-
if (item.id !== itemId) {
|
|
3787
|
+
if (!isPlainObject(computed.default)) {
|
|
2811
3788
|
return invalidFromErrorFactory(
|
|
2812
3789
|
errorFactory,
|
|
2813
|
-
`${
|
|
3790
|
+
`${path}.default must be an object`,
|
|
2814
3791
|
);
|
|
2815
3792
|
}
|
|
2816
3793
|
|
|
2817
|
-
|
|
2818
|
-
|
|
3794
|
+
for (const [index, branch] of computed.branches.entries()) {
|
|
3795
|
+
const branchPath = `${path}.branches[${index}]`;
|
|
3796
|
+
if (!isPlainObject(branch)) {
|
|
3797
|
+
return invalidFromErrorFactory(
|
|
3798
|
+
errorFactory,
|
|
3799
|
+
`${branchPath} must be an object`,
|
|
3800
|
+
);
|
|
3801
|
+
}
|
|
3802
|
+
if (!Object.hasOwn(branch, "when")) {
|
|
3803
|
+
return invalidFromErrorFactory(
|
|
3804
|
+
errorFactory,
|
|
3805
|
+
`${branchPath}.when is required`,
|
|
3806
|
+
);
|
|
3807
|
+
}
|
|
3808
|
+
const conditionResult = validateComputedCondition({
|
|
3809
|
+
condition: branch.when,
|
|
3810
|
+
path: `${branchPath}.when`,
|
|
2819
3811
|
errorFactory,
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
3812
|
+
variables,
|
|
3813
|
+
dependencies,
|
|
3814
|
+
});
|
|
3815
|
+
if (conditionResult?.valid === false) {
|
|
3816
|
+
return conditionResult;
|
|
3817
|
+
}
|
|
3818
|
+
const branchResult = validateComputedResultConfig({
|
|
3819
|
+
resultConfig: branch,
|
|
3820
|
+
variableType,
|
|
3821
|
+
path: branchPath,
|
|
2826
3822
|
errorFactory,
|
|
2827
|
-
|
|
2828
|
-
|
|
3823
|
+
variables,
|
|
3824
|
+
dependencies,
|
|
3825
|
+
allowedKeys: ["when", "expr", "value"],
|
|
3826
|
+
});
|
|
3827
|
+
if (branchResult?.valid === false) {
|
|
3828
|
+
return branchResult;
|
|
3829
|
+
}
|
|
2829
3830
|
}
|
|
2830
3831
|
|
|
2831
|
-
|
|
3832
|
+
return validateComputedResultConfig({
|
|
3833
|
+
resultConfig: computed.default,
|
|
3834
|
+
variableType,
|
|
3835
|
+
path: `${path}.default`,
|
|
3836
|
+
errorFactory,
|
|
3837
|
+
variables,
|
|
3838
|
+
dependencies,
|
|
3839
|
+
});
|
|
3840
|
+
}
|
|
3841
|
+
|
|
3842
|
+
return validateComputedResultConfig({
|
|
3843
|
+
resultConfig: computed,
|
|
3844
|
+
variableType,
|
|
3845
|
+
path,
|
|
3846
|
+
errorFactory,
|
|
3847
|
+
variables,
|
|
3848
|
+
dependencies,
|
|
3849
|
+
});
|
|
3850
|
+
};
|
|
3851
|
+
|
|
3852
|
+
const validateComputedVariableGraph = ({ items, path, errorFactory }) => {
|
|
3853
|
+
const dependencyGraph = new Map();
|
|
3854
|
+
|
|
3855
|
+
for (const [variableId, variable] of Object.entries(items)) {
|
|
3856
|
+
if (variable?.type !== "variable" || !Object.hasOwn(variable, "computed")) {
|
|
2832
3857
|
continue;
|
|
2833
3858
|
}
|
|
2834
3859
|
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
3860
|
+
const dependencies = new Set();
|
|
3861
|
+
const result = validateVariableComputedConfig({
|
|
3862
|
+
computed: variable.computed,
|
|
3863
|
+
variableType: variable.variableType,
|
|
3864
|
+
path: `${path}.${variableId}.computed`,
|
|
3865
|
+
errorFactory,
|
|
3866
|
+
variables: items,
|
|
3867
|
+
dependencies,
|
|
3868
|
+
});
|
|
3869
|
+
if (result?.valid === false) {
|
|
3870
|
+
return result;
|
|
2845
3871
|
}
|
|
3872
|
+
dependencyGraph.set(variableId, dependencies);
|
|
3873
|
+
}
|
|
2846
3874
|
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
);
|
|
3875
|
+
const visited = new Set();
|
|
3876
|
+
for (const startVariableId of dependencyGraph.keys()) {
|
|
3877
|
+
if (visited.has(startVariableId)) {
|
|
3878
|
+
continue;
|
|
2852
3879
|
}
|
|
2853
3880
|
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
3881
|
+
const frames = [
|
|
3882
|
+
{
|
|
3883
|
+
variableId: startVariableId,
|
|
3884
|
+
dependencies: [...(dependencyGraph.get(startVariableId) ?? [])],
|
|
3885
|
+
nextDependencyIndex: 0,
|
|
3886
|
+
},
|
|
3887
|
+
];
|
|
3888
|
+
const activeIndexes = new Map([[startVariableId, 0]]);
|
|
3889
|
+
|
|
3890
|
+
while (frames.length > 0) {
|
|
3891
|
+
const frame = frames.at(-1);
|
|
3892
|
+
if (frame.nextDependencyIndex >= frame.dependencies.length) {
|
|
3893
|
+
frames.pop();
|
|
3894
|
+
activeIndexes.delete(frame.variableId);
|
|
3895
|
+
visited.add(frame.variableId);
|
|
3896
|
+
continue;
|
|
3897
|
+
}
|
|
2860
3898
|
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
}
|
|
3899
|
+
const dependencyId = frame.dependencies[frame.nextDependencyIndex];
|
|
3900
|
+
frame.nextDependencyIndex += 1;
|
|
3901
|
+
if (visited.has(dependencyId)) {
|
|
3902
|
+
continue;
|
|
3903
|
+
}
|
|
2867
3904
|
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
3905
|
+
const cycleStartIndex = activeIndexes.get(dependencyId);
|
|
3906
|
+
if (cycleStartIndex !== undefined) {
|
|
3907
|
+
const cycle = [
|
|
3908
|
+
...frames.slice(cycleStartIndex).map(({ variableId }) => variableId),
|
|
3909
|
+
dependencyId,
|
|
3910
|
+
].join(" -> ");
|
|
3911
|
+
return invalidFromErrorFactory(
|
|
3912
|
+
errorFactory,
|
|
3913
|
+
`${path} contains computed variable cycle: ${cycle}`,
|
|
3914
|
+
);
|
|
3915
|
+
}
|
|
2877
3916
|
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
path: `${itemPath}.modules`,
|
|
2882
|
-
errorFactory,
|
|
2883
|
-
});
|
|
2884
|
-
if (result?.valid === false) {
|
|
2885
|
-
return result;
|
|
3917
|
+
if (!dependencyGraph.has(dependencyId)) {
|
|
3918
|
+
visited.add(dependencyId);
|
|
3919
|
+
continue;
|
|
2886
3920
|
}
|
|
3921
|
+
|
|
3922
|
+
activeIndexes.set(dependencyId, frames.length);
|
|
3923
|
+
frames.push({
|
|
3924
|
+
variableId: dependencyId,
|
|
3925
|
+
dependencies: [...(dependencyGraph.get(dependencyId) ?? [])],
|
|
3926
|
+
nextDependencyIndex: 0,
|
|
3927
|
+
});
|
|
2887
3928
|
}
|
|
2888
3929
|
}
|
|
3930
|
+
|
|
3931
|
+
return VALID_RESULT;
|
|
2889
3932
|
};
|
|
2890
3933
|
|
|
2891
|
-
const
|
|
2892
|
-
|
|
3934
|
+
const validateVariableStoredOrComputedData = ({
|
|
3935
|
+
data,
|
|
2893
3936
|
variableType,
|
|
2894
3937
|
path,
|
|
2895
3938
|
errorFactory,
|
|
2896
3939
|
}) => {
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
3940
|
+
const isComputed = Object.hasOwn(data, "computed");
|
|
3941
|
+
if (isComputed) {
|
|
3942
|
+
if (Object.hasOwn(data, "default") || Object.hasOwn(data, "value")) {
|
|
3943
|
+
return invalidFromErrorFactory(
|
|
3944
|
+
errorFactory,
|
|
3945
|
+
`${path} computed variables must not contain default or value`,
|
|
3946
|
+
);
|
|
3947
|
+
}
|
|
3948
|
+
if (data.isEnum !== undefined || data.enumValues !== undefined) {
|
|
3949
|
+
return invalidFromErrorFactory(
|
|
3950
|
+
errorFactory,
|
|
3951
|
+
`${path} computed variables must not contain enum metadata`,
|
|
3952
|
+
);
|
|
3953
|
+
}
|
|
3954
|
+
return validateVariableComputedConfig({
|
|
3955
|
+
computed: data.computed,
|
|
3956
|
+
variableType,
|
|
3957
|
+
path: `${path}.computed`,
|
|
2903
3958
|
errorFactory,
|
|
2904
|
-
|
|
2905
|
-
);
|
|
3959
|
+
});
|
|
2906
3960
|
}
|
|
2907
3961
|
|
|
2908
|
-
|
|
2909
|
-
|
|
3962
|
+
{
|
|
3963
|
+
const result = validateVariableTypedValue({
|
|
3964
|
+
value: data.default,
|
|
3965
|
+
variableType,
|
|
3966
|
+
path: `${path}.default`,
|
|
3967
|
+
errorFactory,
|
|
3968
|
+
});
|
|
3969
|
+
if (result?.valid === false) {
|
|
3970
|
+
return result;
|
|
3971
|
+
}
|
|
2910
3972
|
}
|
|
3973
|
+
return validateVariableTypedValue({
|
|
3974
|
+
value: data.value,
|
|
3975
|
+
variableType,
|
|
3976
|
+
path: `${path}.value`,
|
|
3977
|
+
errorFactory,
|
|
3978
|
+
});
|
|
2911
3979
|
};
|
|
2912
3980
|
|
|
2913
3981
|
const normalizeVariableEnumValues = (values = []) => {
|
|
@@ -2995,6 +4063,13 @@ const validateVariableItems = ({ items, path, errorFactory }) => {
|
|
|
2995
4063
|
const itemType = item?.type;
|
|
2996
4064
|
const variableType = item?.variableType;
|
|
2997
4065
|
|
|
4066
|
+
if (itemId === "__proto__") {
|
|
4067
|
+
return invalidFromErrorFactory(
|
|
4068
|
+
errorFactory,
|
|
4069
|
+
`${itemPath} uses reserved variable id '__proto__'`,
|
|
4070
|
+
);
|
|
4071
|
+
}
|
|
4072
|
+
|
|
2998
4073
|
if (itemType !== "folder" && itemType !== "variable") {
|
|
2999
4074
|
return invalidFromErrorFactory(
|
|
3000
4075
|
errorFactory,
|
|
@@ -3020,6 +4095,7 @@ const validateVariableItems = ({ items, path, errorFactory }) => {
|
|
|
3020
4095
|
"value",
|
|
3021
4096
|
"isEnum",
|
|
3022
4097
|
"enumValues",
|
|
4098
|
+
"computed",
|
|
3023
4099
|
],
|
|
3024
4100
|
path: itemPath,
|
|
3025
4101
|
errorFactory,
|
|
@@ -3061,7 +4137,7 @@ const validateVariableItems = ({ items, path, errorFactory }) => {
|
|
|
3061
4137
|
if (!VARIABLE_TYPE_KEYS.includes(variableType)) {
|
|
3062
4138
|
return invalidFromErrorFactory(
|
|
3063
4139
|
errorFactory,
|
|
3064
|
-
`${itemPath}.variableType must be 'string', 'number', or '
|
|
4140
|
+
`${itemPath}.variableType must be 'string', 'number', 'boolean', or 'object'`,
|
|
3065
4141
|
);
|
|
3066
4142
|
}
|
|
3067
4143
|
|
|
@@ -3089,7 +4165,14 @@ const validateVariableItems = ({ items, path, errorFactory }) => {
|
|
|
3089
4165
|
}
|
|
3090
4166
|
}
|
|
3091
4167
|
|
|
3092
|
-
if (
|
|
4168
|
+
if (item.computed !== undefined) {
|
|
4169
|
+
if (item.scope !== undefined) {
|
|
4170
|
+
return invalidFromErrorFactory(
|
|
4171
|
+
errorFactory,
|
|
4172
|
+
`${itemPath}.scope must be omitted for computed variables`,
|
|
4173
|
+
);
|
|
4174
|
+
}
|
|
4175
|
+
} else if (!VARIABLE_SCOPE_KEYS.includes(item.scope)) {
|
|
3093
4176
|
return invalidFromErrorFactory(
|
|
3094
4177
|
errorFactory,
|
|
3095
4178
|
`${itemPath}.scope must be 'context', 'device', or 'account'`,
|
|
@@ -3097,21 +4180,10 @@ const validateVariableItems = ({ items, path, errorFactory }) => {
|
|
|
3097
4180
|
}
|
|
3098
4181
|
|
|
3099
4182
|
{
|
|
3100
|
-
const result =
|
|
3101
|
-
|
|
3102
|
-
variableType,
|
|
3103
|
-
path: `${itemPath}.default`,
|
|
3104
|
-
errorFactory,
|
|
3105
|
-
});
|
|
3106
|
-
if (result?.valid === false) {
|
|
3107
|
-
return result;
|
|
3108
|
-
}
|
|
3109
|
-
}
|
|
3110
|
-
{
|
|
3111
|
-
const result = validateVariableTypedValue({
|
|
3112
|
-
value: item.value,
|
|
4183
|
+
const result = validateVariableStoredOrComputedData({
|
|
4184
|
+
data: item,
|
|
3113
4185
|
variableType,
|
|
3114
|
-
path:
|
|
4186
|
+
path: itemPath,
|
|
3115
4187
|
errorFactory,
|
|
3116
4188
|
});
|
|
3117
4189
|
if (result?.valid === false) {
|
|
@@ -3120,6 +4192,12 @@ const validateVariableItems = ({ items, path, errorFactory }) => {
|
|
|
3120
4192
|
}
|
|
3121
4193
|
}
|
|
3122
4194
|
}
|
|
4195
|
+
|
|
4196
|
+
return validateComputedVariableGraph({
|
|
4197
|
+
items,
|
|
4198
|
+
path,
|
|
4199
|
+
errorFactory,
|
|
4200
|
+
});
|
|
3123
4201
|
};
|
|
3124
4202
|
|
|
3125
4203
|
const validateTextStyleShadow = ({ shadow, path, errorFactory }) => {
|
|
@@ -11070,6 +12148,7 @@ const validateVariableCreateData = ({ data, errorFactory }) => {
|
|
|
11070
12148
|
"value",
|
|
11071
12149
|
"isEnum",
|
|
11072
12150
|
"enumValues",
|
|
12151
|
+
"computed",
|
|
11073
12152
|
],
|
|
11074
12153
|
path: "payload.data",
|
|
11075
12154
|
errorFactory,
|
|
@@ -11097,7 +12176,7 @@ const validateVariableCreateData = ({ data, errorFactory }) => {
|
|
|
11097
12176
|
if (!VARIABLE_TYPE_KEYS.includes(data.variableType)) {
|
|
11098
12177
|
return invalidFromErrorFactory(
|
|
11099
12178
|
errorFactory,
|
|
11100
|
-
"payload.data.variableType must be 'string', 'number', or '
|
|
12179
|
+
"payload.data.variableType must be 'string', 'number', 'boolean', or 'object'",
|
|
11101
12180
|
);
|
|
11102
12181
|
}
|
|
11103
12182
|
|
|
@@ -11124,7 +12203,14 @@ const validateVariableCreateData = ({ data, errorFactory }) => {
|
|
|
11124
12203
|
}
|
|
11125
12204
|
}
|
|
11126
12205
|
|
|
11127
|
-
if (
|
|
12206
|
+
if (data.computed !== undefined) {
|
|
12207
|
+
if (data.scope !== undefined) {
|
|
12208
|
+
return invalidFromErrorFactory(
|
|
12209
|
+
errorFactory,
|
|
12210
|
+
"payload.data.scope must be omitted for computed variables",
|
|
12211
|
+
);
|
|
12212
|
+
}
|
|
12213
|
+
} else if (!VARIABLE_SCOPE_KEYS.includes(data.scope)) {
|
|
11128
12214
|
return invalidFromErrorFactory(
|
|
11129
12215
|
errorFactory,
|
|
11130
12216
|
"payload.data.scope must be 'context', 'device', or 'account'",
|
|
@@ -11132,21 +12218,10 @@ const validateVariableCreateData = ({ data, errorFactory }) => {
|
|
|
11132
12218
|
}
|
|
11133
12219
|
|
|
11134
12220
|
{
|
|
11135
|
-
const result =
|
|
11136
|
-
|
|
11137
|
-
variableType: data.variableType,
|
|
11138
|
-
path: "payload.data.default",
|
|
11139
|
-
errorFactory,
|
|
11140
|
-
});
|
|
11141
|
-
if (result?.valid === false) {
|
|
11142
|
-
return result;
|
|
11143
|
-
}
|
|
11144
|
-
}
|
|
11145
|
-
{
|
|
11146
|
-
const result = validateVariableTypedValue({
|
|
11147
|
-
value: data.value,
|
|
12221
|
+
const result = validateVariableStoredOrComputedData({
|
|
12222
|
+
data,
|
|
11148
12223
|
variableType: data.variableType,
|
|
11149
|
-
path: "payload.data
|
|
12224
|
+
path: "payload.data",
|
|
11150
12225
|
errorFactory,
|
|
11151
12226
|
});
|
|
11152
12227
|
if (result?.valid === false) {
|
|
@@ -11169,6 +12244,7 @@ const validateVariableUpdateData = ({ data, errorFactory }) => {
|
|
|
11169
12244
|
"value",
|
|
11170
12245
|
"isEnum",
|
|
11171
12246
|
"enumValues",
|
|
12247
|
+
"computed",
|
|
11172
12248
|
],
|
|
11173
12249
|
path: "payload.data",
|
|
11174
12250
|
errorFactory,
|
|
@@ -11217,6 +12293,15 @@ const validateVariableUpdateData = ({ data, errorFactory }) => {
|
|
|
11217
12293
|
}
|
|
11218
12294
|
}
|
|
11219
12295
|
|
|
12296
|
+
if (data.computed !== undefined) {
|
|
12297
|
+
if (!isPlainObject(data.computed)) {
|
|
12298
|
+
return invalidFromErrorFactory(
|
|
12299
|
+
errorFactory,
|
|
12300
|
+
"payload.data.computed must be an object when provided",
|
|
12301
|
+
);
|
|
12302
|
+
}
|
|
12303
|
+
}
|
|
12304
|
+
|
|
11220
12305
|
if (data.scope !== undefined && !VARIABLE_SCOPE_KEYS.includes(data.scope)) {
|
|
11221
12306
|
return invalidFromErrorFactory(
|
|
11222
12307
|
errorFactory,
|
|
@@ -13038,6 +14123,7 @@ const createFolderedCollectionCommandDefinitions = ({
|
|
|
13038
14123
|
validateDeleteState = () => {},
|
|
13039
14124
|
afterDelete = () => {},
|
|
13040
14125
|
includeUpdate = true,
|
|
14126
|
+
reservedItemIds = [],
|
|
13041
14127
|
}) => {
|
|
13042
14128
|
const existingMessage = `payload.${idField} must reference an existing ${itemLabel}`;
|
|
13043
14129
|
const duplicateMessage = `payload.${idField} must not already exist`;
|
|
@@ -13078,6 +14164,12 @@ const createFolderedCollectionCommandDefinitions = ({
|
|
|
13078
14164
|
);
|
|
13079
14165
|
}
|
|
13080
14166
|
|
|
14167
|
+
if (reservedItemIds.includes(payload[idField])) {
|
|
14168
|
+
return invalidPayload(
|
|
14169
|
+
`payload.${idField} must not use reserved id '${payload[idField]}'`,
|
|
14170
|
+
);
|
|
14171
|
+
}
|
|
14172
|
+
|
|
13081
14173
|
if (
|
|
13082
14174
|
payload.parentId !== undefined &&
|
|
13083
14175
|
payload.parentId !== null &&
|
|
@@ -18514,6 +19606,7 @@ const COMMAND_DEFINITIONS = [
|
|
|
18514
19606
|
itemLabel: "variable item",
|
|
18515
19607
|
createDataValidator: validateVariableCreateData,
|
|
18516
19608
|
updateDataValidator: validateVariableUpdateData,
|
|
19609
|
+
reservedItemIds: ["__proto__"],
|
|
18517
19610
|
createItem: ({ payload }) => {
|
|
18518
19611
|
const data = structuredClone(payload.data);
|
|
18519
19612
|
if (!Array.isArray(data.tagIds) || data.tagIds.length === 0) {
|
|
@@ -18538,7 +19631,7 @@ const COMMAND_DEFINITIONS = [
|
|
|
18538
19631
|
return;
|
|
18539
19632
|
}
|
|
18540
19633
|
|
|
18541
|
-
|
|
19634
|
+
const tagResult = validateTagIdsAgainstScope({
|
|
18542
19635
|
state,
|
|
18543
19636
|
tagIds: payload.data.tagIds,
|
|
18544
19637
|
scopeKey: "variables",
|
|
@@ -18547,6 +19640,25 @@ const COMMAND_DEFINITIONS = [
|
|
|
18547
19640
|
variableId: payload.variableId,
|
|
18548
19641
|
},
|
|
18549
19642
|
});
|
|
19643
|
+
if (!tagResult.valid) {
|
|
19644
|
+
return tagResult;
|
|
19645
|
+
}
|
|
19646
|
+
|
|
19647
|
+
if (!Object.hasOwn(payload.data, "computed")) {
|
|
19648
|
+
return VALID_RESULT;
|
|
19649
|
+
}
|
|
19650
|
+
|
|
19651
|
+
return validateComputedVariableGraph({
|
|
19652
|
+
items: {
|
|
19653
|
+
...state.variables.items,
|
|
19654
|
+
[payload.variableId]: {
|
|
19655
|
+
id: payload.variableId,
|
|
19656
|
+
...payload.data,
|
|
19657
|
+
},
|
|
19658
|
+
},
|
|
19659
|
+
path: "state.variables.items",
|
|
19660
|
+
errorFactory: createPreconditionValidationError,
|
|
19661
|
+
});
|
|
18550
19662
|
},
|
|
18551
19663
|
validateUpdateState: ({ state, payload, currentItem }) => {
|
|
18552
19664
|
if (
|
|
@@ -18570,6 +19682,36 @@ const COMMAND_DEFINITIONS = [
|
|
|
18570
19682
|
);
|
|
18571
19683
|
}
|
|
18572
19684
|
|
|
19685
|
+
const currentItemIsComputed = Object.hasOwn(currentItem, "computed");
|
|
19686
|
+
if (
|
|
19687
|
+
currentItemIsComputed &&
|
|
19688
|
+
["scope", "default", "value", "isEnum", "enumValues"].some((key) =>
|
|
19689
|
+
Object.hasOwn(payload.data, key),
|
|
19690
|
+
)
|
|
19691
|
+
) {
|
|
19692
|
+
return invalidPrecondition(
|
|
19693
|
+
"computed variables cannot update scope, stored value, or enum fields",
|
|
19694
|
+
);
|
|
19695
|
+
}
|
|
19696
|
+
|
|
19697
|
+
if (!currentItemIsComputed && Object.hasOwn(payload.data, "computed")) {
|
|
19698
|
+
return invalidPrecondition(
|
|
19699
|
+
"stored variables cannot be converted to computed variables",
|
|
19700
|
+
);
|
|
19701
|
+
}
|
|
19702
|
+
|
|
19703
|
+
if (currentItemIsComputed && Object.hasOwn(payload.data, "computed")) {
|
|
19704
|
+
const result = validateVariableComputedConfig({
|
|
19705
|
+
computed: payload.data.computed,
|
|
19706
|
+
variableType: currentItem.variableType,
|
|
19707
|
+
path: "payload.data.computed",
|
|
19708
|
+
errorFactory: createPreconditionValidationError,
|
|
19709
|
+
});
|
|
19710
|
+
if (result?.valid === false) {
|
|
19711
|
+
return result;
|
|
19712
|
+
}
|
|
19713
|
+
}
|
|
19714
|
+
|
|
18573
19715
|
if (currentItem.type !== "folder") {
|
|
18574
19716
|
{
|
|
18575
19717
|
const result = validateTagIdsAgainstScope({
|
|
@@ -18614,6 +19756,49 @@ const COMMAND_DEFINITIONS = [
|
|
|
18614
19756
|
}
|
|
18615
19757
|
}
|
|
18616
19758
|
}
|
|
19759
|
+
|
|
19760
|
+
if (currentItemIsComputed && Object.hasOwn(payload.data, "computed")) {
|
|
19761
|
+
return validateComputedVariableGraph({
|
|
19762
|
+
items: {
|
|
19763
|
+
...state.variables.items,
|
|
19764
|
+
[payload.variableId]: applyVariableUpdate({
|
|
19765
|
+
currentItem,
|
|
19766
|
+
data: payload.data,
|
|
19767
|
+
}),
|
|
19768
|
+
},
|
|
19769
|
+
path: "state.variables.items",
|
|
19770
|
+
errorFactory: createPreconditionValidationError,
|
|
19771
|
+
});
|
|
19772
|
+
}
|
|
19773
|
+
|
|
19774
|
+
return VALID_RESULT;
|
|
19775
|
+
},
|
|
19776
|
+
validateDeleteState: ({ state, payload }) => {
|
|
19777
|
+
const deletedIds = new Set();
|
|
19778
|
+
for (const variableId of payload.variableIds) {
|
|
19779
|
+
const node = findTreeNode({
|
|
19780
|
+
nodes: state.variables.tree,
|
|
19781
|
+
nodeId: variableId,
|
|
19782
|
+
});
|
|
19783
|
+
if (!node) {
|
|
19784
|
+
deletedIds.add(variableId);
|
|
19785
|
+
continue;
|
|
19786
|
+
}
|
|
19787
|
+
for (const descendantId of collectTreeDescendantIds({ node })) {
|
|
19788
|
+
deletedIds.add(descendantId);
|
|
19789
|
+
}
|
|
19790
|
+
}
|
|
19791
|
+
|
|
19792
|
+
const remainingItems = Object.fromEntries(
|
|
19793
|
+
Object.entries(state.variables.items).filter(
|
|
19794
|
+
([variableId]) => !deletedIds.has(variableId),
|
|
19795
|
+
),
|
|
19796
|
+
);
|
|
19797
|
+
return validateComputedVariableGraph({
|
|
19798
|
+
items: remainingItems,
|
|
19799
|
+
path: "state.variables.items",
|
|
19800
|
+
errorFactory: createPreconditionValidationError,
|
|
19801
|
+
});
|
|
18617
19802
|
},
|
|
18618
19803
|
}),
|
|
18619
19804
|
...createFolderedCollectionCommandDefinitions({
|