@routevn/creator-model 1.10.3 → 1.11.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 +67 -0
- package/package.json +1 -1
- package/src/model.js +1425 -88
package/src/model.js
CHANGED
|
@@ -42,6 +42,7 @@ const LINE_UPDATE_ACTIONS_PRESERVE_PATHS_SET = new Set(
|
|
|
42
42
|
LINE_UPDATE_ACTIONS_PRESERVE_PATHS,
|
|
43
43
|
);
|
|
44
44
|
const CURRENT_LAYOUT_SCHEMA_VERSION = 2;
|
|
45
|
+
const FONT_WEIGHT_KEYS = ["minWeight", "defaultWeight", "maxWeight"];
|
|
45
46
|
const isPositiveFiniteNumber = (value) => isFiniteNumber(value) && value > 0;
|
|
46
47
|
const normalizeLayoutSchemaVersion = (value) =>
|
|
47
48
|
Number.isInteger(value) && value >= 1 ? value : 1;
|
|
@@ -340,7 +341,7 @@ const ANIMATION_EASING_KEYS = [
|
|
|
340
341
|
"easeInOutElastic",
|
|
341
342
|
];
|
|
342
343
|
const VARIABLE_SCOPE_KEYS = ["context", "device", "account"];
|
|
343
|
-
const VARIABLE_TYPE_KEYS = ["string", "number", "boolean"];
|
|
344
|
+
const VARIABLE_TYPE_KEYS = ["string", "number", "boolean", "object"];
|
|
344
345
|
const LAYOUT_TYPE_KEYS = [
|
|
345
346
|
"general",
|
|
346
347
|
"save-load",
|
|
@@ -402,7 +403,7 @@ const SAVE_LOAD_DATE_FORMATS = new Set([
|
|
|
402
403
|
"DD MMM YYYY",
|
|
403
404
|
"YYYY年MM月DD日",
|
|
404
405
|
]);
|
|
405
|
-
export const SCHEMA_VERSION =
|
|
406
|
+
export const SCHEMA_VERSION = 11;
|
|
406
407
|
const LAYOUT_CONTAINER_ELEMENT_TYPES = [
|
|
407
408
|
"folder",
|
|
408
409
|
"container",
|
|
@@ -2388,6 +2389,41 @@ const validateAnimationItems = ({ items, path, errorFactory }) => {
|
|
|
2388
2389
|
}
|
|
2389
2390
|
};
|
|
2390
2391
|
|
|
2392
|
+
const validateFontWeightFields = ({ value, path, errorFactory }) => {
|
|
2393
|
+
const presentKeys = FONT_WEIGHT_KEYS.filter((key) =>
|
|
2394
|
+
Object.hasOwn(value, key),
|
|
2395
|
+
);
|
|
2396
|
+
if (presentKeys.length === 0) {
|
|
2397
|
+
return;
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
if (presentKeys.length !== FONT_WEIGHT_KEYS.length) {
|
|
2401
|
+
return invalidFromErrorFactory(
|
|
2402
|
+
errorFactory,
|
|
2403
|
+
`${path} must include minWeight, defaultWeight, and maxWeight together`,
|
|
2404
|
+
);
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
for (const key of FONT_WEIGHT_KEYS) {
|
|
2408
|
+
if (!isFiniteNumber(value[key]) || value[key] < 1 || value[key] > 1000) {
|
|
2409
|
+
return invalidFromErrorFactory(
|
|
2410
|
+
errorFactory,
|
|
2411
|
+
`${path}.${key} must be a finite number between 1 and 1000`,
|
|
2412
|
+
);
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
if (
|
|
2417
|
+
value.minWeight > value.defaultWeight ||
|
|
2418
|
+
value.defaultWeight > value.maxWeight
|
|
2419
|
+
) {
|
|
2420
|
+
return invalidFromErrorFactory(
|
|
2421
|
+
errorFactory,
|
|
2422
|
+
`${path} must satisfy minWeight <= defaultWeight <= maxWeight`,
|
|
2423
|
+
);
|
|
2424
|
+
}
|
|
2425
|
+
};
|
|
2426
|
+
|
|
2391
2427
|
const validateFontItems = ({ items, path, errorFactory }) => {
|
|
2392
2428
|
for (const [itemId, item] of Object.entries(items)) {
|
|
2393
2429
|
const itemPath = `${path}.${itemId}`;
|
|
@@ -2413,6 +2449,7 @@ const validateFontItems = ({ items, path, errorFactory }) => {
|
|
|
2413
2449
|
"tagIds",
|
|
2414
2450
|
"fileId",
|
|
2415
2451
|
"fontFamily",
|
|
2452
|
+
...FONT_WEIGHT_KEYS,
|
|
2416
2453
|
],
|
|
2417
2454
|
path: itemPath,
|
|
2418
2455
|
errorFactory,
|
|
@@ -2476,6 +2513,17 @@ const validateFontItems = ({ items, path, errorFactory }) => {
|
|
|
2476
2513
|
`${itemPath}.fontFamily must be a non-empty string`,
|
|
2477
2514
|
);
|
|
2478
2515
|
}
|
|
2516
|
+
|
|
2517
|
+
{
|
|
2518
|
+
const result = validateFontWeightFields({
|
|
2519
|
+
value: item,
|
|
2520
|
+
path: itemPath,
|
|
2521
|
+
errorFactory,
|
|
2522
|
+
});
|
|
2523
|
+
if (result?.valid === false) {
|
|
2524
|
+
return result;
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2479
2527
|
}
|
|
2480
2528
|
}
|
|
2481
2529
|
};
|
|
@@ -2817,49 +2865,1122 @@ const validateParticleItems = ({ items, path, errorFactory }) => {
|
|
|
2817
2865
|
);
|
|
2818
2866
|
}
|
|
2819
2867
|
|
|
2820
|
-
if (
|
|
2821
|
-
item.thumbnailFileId !== undefined &&
|
|
2822
|
-
!isNonEmptyString(item.thumbnailFileId)
|
|
2823
|
-
) {
|
|
2824
|
-
return invalidFromErrorFactory(
|
|
2825
|
-
errorFactory,
|
|
2826
|
-
`${itemPath}.thumbnailFileId must be a non-empty string when provided`,
|
|
2827
|
-
);
|
|
2868
|
+
if (
|
|
2869
|
+
item.thumbnailFileId !== undefined &&
|
|
2870
|
+
!isNonEmptyString(item.thumbnailFileId)
|
|
2871
|
+
) {
|
|
2872
|
+
return invalidFromErrorFactory(
|
|
2873
|
+
errorFactory,
|
|
2874
|
+
`${itemPath}.thumbnailFileId must be a non-empty string when provided`,
|
|
2875
|
+
);
|
|
2876
|
+
}
|
|
2877
|
+
|
|
2878
|
+
{
|
|
2879
|
+
const result = validateParticleModules({
|
|
2880
|
+
modules: item.modules,
|
|
2881
|
+
path: `${itemPath}.modules`,
|
|
2882
|
+
errorFactory,
|
|
2883
|
+
});
|
|
2884
|
+
if (result?.valid === false) {
|
|
2885
|
+
return result;
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
};
|
|
2890
|
+
|
|
2891
|
+
const validateVariableTypedValue = ({
|
|
2892
|
+
value,
|
|
2893
|
+
variableType,
|
|
2894
|
+
path,
|
|
2895
|
+
errorFactory,
|
|
2896
|
+
}) => {
|
|
2897
|
+
if (variableType === "string" && !isString(value)) {
|
|
2898
|
+
return invalidFromErrorFactory(errorFactory, `${path} must be a string`);
|
|
2899
|
+
}
|
|
2900
|
+
|
|
2901
|
+
if (variableType === "number" && !isFiniteNumber(value)) {
|
|
2902
|
+
return invalidFromErrorFactory(
|
|
2903
|
+
errorFactory,
|
|
2904
|
+
`${path} must be a finite number`,
|
|
2905
|
+
);
|
|
2906
|
+
}
|
|
2907
|
+
|
|
2908
|
+
if (variableType === "boolean" && typeof value !== "boolean") {
|
|
2909
|
+
return invalidFromErrorFactory(errorFactory, `${path} must be a boolean`);
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2912
|
+
if (
|
|
2913
|
+
variableType === "object" &&
|
|
2914
|
+
(value === null || typeof value !== "object")
|
|
2915
|
+
) {
|
|
2916
|
+
return invalidFromErrorFactory(
|
|
2917
|
+
errorFactory,
|
|
2918
|
+
`${path} must be a non-null object or array`,
|
|
2919
|
+
);
|
|
2920
|
+
}
|
|
2921
|
+
|
|
2922
|
+
if (variableType === "object") {
|
|
2923
|
+
return validateComputedDataValue({
|
|
2924
|
+
value,
|
|
2925
|
+
path,
|
|
2926
|
+
errorFactory,
|
|
2927
|
+
});
|
|
2928
|
+
}
|
|
2929
|
+
};
|
|
2930
|
+
|
|
2931
|
+
const COMPUTED_EXPRESSION_FIXED_OPERAND_COUNTS = Object.freeze({
|
|
2932
|
+
add: 2,
|
|
2933
|
+
sub: 2,
|
|
2934
|
+
mul: 2,
|
|
2935
|
+
div: 2,
|
|
2936
|
+
mod: 2,
|
|
2937
|
+
neg: 1,
|
|
2938
|
+
round: 1,
|
|
2939
|
+
floor: 1,
|
|
2940
|
+
ceil: 1,
|
|
2941
|
+
min: 2,
|
|
2942
|
+
max: 2,
|
|
2943
|
+
clamp: 3,
|
|
2944
|
+
eq: 2,
|
|
2945
|
+
neq: 2,
|
|
2946
|
+
gt: 2,
|
|
2947
|
+
gte: 2,
|
|
2948
|
+
lt: 2,
|
|
2949
|
+
lte: 2,
|
|
2950
|
+
in: 2,
|
|
2951
|
+
not: 1,
|
|
2952
|
+
length: 1,
|
|
2953
|
+
includes: 2,
|
|
2954
|
+
});
|
|
2955
|
+
const COMPUTED_EXPRESSION_VARIADIC_OPERATORS = new Set([
|
|
2956
|
+
"and",
|
|
2957
|
+
"or",
|
|
2958
|
+
"all",
|
|
2959
|
+
"any",
|
|
2960
|
+
]);
|
|
2961
|
+
const COMPUTED_EXPRESSION_NUMERIC_OPERAND_OPERATORS = new Set([
|
|
2962
|
+
"add",
|
|
2963
|
+
"sub",
|
|
2964
|
+
"mul",
|
|
2965
|
+
"div",
|
|
2966
|
+
"mod",
|
|
2967
|
+
"neg",
|
|
2968
|
+
"round",
|
|
2969
|
+
"floor",
|
|
2970
|
+
"ceil",
|
|
2971
|
+
"min",
|
|
2972
|
+
"max",
|
|
2973
|
+
"clamp",
|
|
2974
|
+
]);
|
|
2975
|
+
const COMPUTED_EXPRESSION_NUMERIC_RESULT_OPERATORS = new Set([
|
|
2976
|
+
...COMPUTED_EXPRESSION_NUMERIC_OPERAND_OPERATORS,
|
|
2977
|
+
"length",
|
|
2978
|
+
]);
|
|
2979
|
+
const COMPUTED_EXPRESSION_BOOLEAN_RESULT_OPERATORS = new Set([
|
|
2980
|
+
"eq",
|
|
2981
|
+
"neq",
|
|
2982
|
+
"gt",
|
|
2983
|
+
"gte",
|
|
2984
|
+
"lt",
|
|
2985
|
+
"lte",
|
|
2986
|
+
"in",
|
|
2987
|
+
"and",
|
|
2988
|
+
"or",
|
|
2989
|
+
"all",
|
|
2990
|
+
"any",
|
|
2991
|
+
"not",
|
|
2992
|
+
"includes",
|
|
2993
|
+
]);
|
|
2994
|
+
const COMPUTED_CONDITION_FIXED_OPERAND_COUNTS = Object.freeze({
|
|
2995
|
+
eq: 2,
|
|
2996
|
+
neq: 2,
|
|
2997
|
+
gt: 2,
|
|
2998
|
+
gte: 2,
|
|
2999
|
+
lt: 2,
|
|
3000
|
+
lte: 2,
|
|
3001
|
+
in: 2,
|
|
3002
|
+
add: 2,
|
|
3003
|
+
sub: 2,
|
|
3004
|
+
});
|
|
3005
|
+
const COMPUTED_CONDITION_VARIADIC_OPERATORS = new Set(["all", "any"]);
|
|
3006
|
+
|
|
3007
|
+
const getComputedValueType = (value) => {
|
|
3008
|
+
if (value === null) {
|
|
3009
|
+
return "null";
|
|
3010
|
+
}
|
|
3011
|
+
if (typeof value === "object") {
|
|
3012
|
+
return "object";
|
|
3013
|
+
}
|
|
3014
|
+
return typeof value;
|
|
3015
|
+
};
|
|
3016
|
+
|
|
3017
|
+
const validComputedResult = (valueType) => ({
|
|
3018
|
+
valid: true,
|
|
3019
|
+
valueType,
|
|
3020
|
+
});
|
|
3021
|
+
|
|
3022
|
+
const validateComputedDataValue = ({
|
|
3023
|
+
value,
|
|
3024
|
+
path,
|
|
3025
|
+
errorFactory,
|
|
3026
|
+
ancestors = new Set(),
|
|
3027
|
+
}) => {
|
|
3028
|
+
if (
|
|
3029
|
+
value === null ||
|
|
3030
|
+
typeof value === "string" ||
|
|
3031
|
+
typeof value === "boolean"
|
|
3032
|
+
) {
|
|
3033
|
+
return VALID_RESULT;
|
|
3034
|
+
}
|
|
3035
|
+
|
|
3036
|
+
if (typeof value === "number") {
|
|
3037
|
+
if (Number.isFinite(value)) {
|
|
3038
|
+
return VALID_RESULT;
|
|
3039
|
+
}
|
|
3040
|
+
return invalidFromErrorFactory(
|
|
3041
|
+
errorFactory,
|
|
3042
|
+
`${path} must use finite numeric values`,
|
|
3043
|
+
);
|
|
3044
|
+
}
|
|
3045
|
+
|
|
3046
|
+
if (Array.isArray(value)) {
|
|
3047
|
+
if (ancestors.has(value)) {
|
|
3048
|
+
return invalidFromErrorFactory(
|
|
3049
|
+
errorFactory,
|
|
3050
|
+
`${path} must not contain cyclic data`,
|
|
3051
|
+
);
|
|
3052
|
+
}
|
|
3053
|
+
ancestors.add(value);
|
|
3054
|
+
|
|
3055
|
+
for (const [index, item] of value.entries()) {
|
|
3056
|
+
const result = validateComputedDataValue({
|
|
3057
|
+
value: item,
|
|
3058
|
+
path: `${path}[${index}]`,
|
|
3059
|
+
errorFactory,
|
|
3060
|
+
ancestors,
|
|
3061
|
+
});
|
|
3062
|
+
if (result?.valid === false) {
|
|
3063
|
+
return result;
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
ancestors.delete(value);
|
|
3067
|
+
return VALID_RESULT;
|
|
3068
|
+
}
|
|
3069
|
+
|
|
3070
|
+
if (
|
|
3071
|
+
!isPlainObject(value) ||
|
|
3072
|
+
![Object.prototype, null].includes(Object.getPrototypeOf(value))
|
|
3073
|
+
) {
|
|
3074
|
+
return invalidFromErrorFactory(
|
|
3075
|
+
errorFactory,
|
|
3076
|
+
`${path} must contain JSON-compatible data`,
|
|
3077
|
+
);
|
|
3078
|
+
}
|
|
3079
|
+
|
|
3080
|
+
if (ancestors.has(value)) {
|
|
3081
|
+
return invalidFromErrorFactory(
|
|
3082
|
+
errorFactory,
|
|
3083
|
+
`${path} must not contain cyclic data`,
|
|
3084
|
+
);
|
|
3085
|
+
}
|
|
3086
|
+
ancestors.add(value);
|
|
3087
|
+
|
|
3088
|
+
for (const [key, item] of Object.entries(value)) {
|
|
3089
|
+
const result = validateComputedDataValue({
|
|
3090
|
+
value: item,
|
|
3091
|
+
path: `${path}.${key}`,
|
|
3092
|
+
errorFactory,
|
|
3093
|
+
ancestors,
|
|
3094
|
+
});
|
|
3095
|
+
if (result?.valid === false) {
|
|
3096
|
+
return result;
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
|
|
3100
|
+
ancestors.delete(value);
|
|
3101
|
+
return VALID_RESULT;
|
|
3102
|
+
};
|
|
3103
|
+
|
|
3104
|
+
const invalidComputedReferenceParseResult = Object.freeze({
|
|
3105
|
+
valid: false,
|
|
3106
|
+
});
|
|
3107
|
+
|
|
3108
|
+
const decodeComputedReferenceQuotedPart = (rawValue) => {
|
|
3109
|
+
if (rawValue[0] === '"') {
|
|
3110
|
+
try {
|
|
3111
|
+
return {
|
|
3112
|
+
valid: true,
|
|
3113
|
+
value: JSON.parse(rawValue),
|
|
3114
|
+
};
|
|
3115
|
+
} catch {
|
|
3116
|
+
return invalidComputedReferenceParseResult;
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
|
|
3120
|
+
let value = "";
|
|
3121
|
+
const escapedValues = {
|
|
3122
|
+
"\\": "\\",
|
|
3123
|
+
'"': '"',
|
|
3124
|
+
"'": "'",
|
|
3125
|
+
"/": "/",
|
|
3126
|
+
n: "\n",
|
|
3127
|
+
r: "\r",
|
|
3128
|
+
t: "\t",
|
|
3129
|
+
b: "\b",
|
|
3130
|
+
f: "\f",
|
|
3131
|
+
};
|
|
3132
|
+
|
|
3133
|
+
for (let index = 1; index < rawValue.length - 1; index += 1) {
|
|
3134
|
+
const character = rawValue[index];
|
|
3135
|
+
if (character !== "\\") {
|
|
3136
|
+
if (character.charCodeAt(0) < 0x20) {
|
|
3137
|
+
return invalidComputedReferenceParseResult;
|
|
3138
|
+
}
|
|
3139
|
+
value += character;
|
|
3140
|
+
continue;
|
|
3141
|
+
}
|
|
3142
|
+
|
|
3143
|
+
index += 1;
|
|
3144
|
+
if (index >= rawValue.length - 1) {
|
|
3145
|
+
return invalidComputedReferenceParseResult;
|
|
3146
|
+
}
|
|
3147
|
+
|
|
3148
|
+
const escapedCharacter = rawValue[index];
|
|
3149
|
+
if (escapedCharacter === "u") {
|
|
3150
|
+
const hexValue = rawValue.slice(index + 1, index + 5);
|
|
3151
|
+
if (hexValue.length !== 4 || !/^[0-9a-fA-F]{4}$/.test(hexValue)) {
|
|
3152
|
+
return invalidComputedReferenceParseResult;
|
|
3153
|
+
}
|
|
3154
|
+
value += String.fromCharCode(Number.parseInt(hexValue, 16));
|
|
3155
|
+
index += 4;
|
|
3156
|
+
continue;
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
if (!Object.hasOwn(escapedValues, escapedCharacter)) {
|
|
3160
|
+
return invalidComputedReferenceParseResult;
|
|
3161
|
+
}
|
|
3162
|
+
value += escapedValues[escapedCharacter];
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3165
|
+
return {
|
|
3166
|
+
valid: true,
|
|
3167
|
+
value,
|
|
3168
|
+
};
|
|
3169
|
+
};
|
|
3170
|
+
|
|
3171
|
+
const parseComputedReferencePath = (value) => {
|
|
3172
|
+
if (
|
|
3173
|
+
typeof value !== "string" ||
|
|
3174
|
+
value.length === 0 ||
|
|
3175
|
+
value.trim() !== value
|
|
3176
|
+
) {
|
|
3177
|
+
return invalidComputedReferenceParseResult;
|
|
3178
|
+
}
|
|
3179
|
+
|
|
3180
|
+
const parts = [];
|
|
3181
|
+
let index = 0;
|
|
3182
|
+
|
|
3183
|
+
const readBarePart = () => {
|
|
3184
|
+
const startIndex = index;
|
|
3185
|
+
while (
|
|
3186
|
+
index < value.length &&
|
|
3187
|
+
value[index] !== "." &&
|
|
3188
|
+
value[index] !== "[" &&
|
|
3189
|
+
value[index] !== "]"
|
|
3190
|
+
) {
|
|
3191
|
+
index += 1;
|
|
3192
|
+
}
|
|
3193
|
+
|
|
3194
|
+
const part = value.slice(startIndex, index);
|
|
3195
|
+
if (part.length === 0 || part.trim() !== part || /\s/.test(part)) {
|
|
3196
|
+
return false;
|
|
3197
|
+
}
|
|
3198
|
+
parts.push(part);
|
|
3199
|
+
return true;
|
|
3200
|
+
};
|
|
3201
|
+
|
|
3202
|
+
const readBracketPart = () => {
|
|
3203
|
+
index += 1;
|
|
3204
|
+
while (index < value.length && /\s/.test(value[index])) {
|
|
3205
|
+
index += 1;
|
|
3206
|
+
}
|
|
3207
|
+
|
|
3208
|
+
const firstCharacter = value[index];
|
|
3209
|
+
if (/\d/.test(firstCharacter ?? "")) {
|
|
3210
|
+
const startIndex = index;
|
|
3211
|
+
while (index < value.length && /\d/.test(value[index])) {
|
|
3212
|
+
index += 1;
|
|
3213
|
+
}
|
|
3214
|
+
const rawPart = value.slice(startIndex, index);
|
|
3215
|
+
while (index < value.length && /\s/.test(value[index])) {
|
|
3216
|
+
index += 1;
|
|
3217
|
+
}
|
|
3218
|
+
if (
|
|
3219
|
+
value[index] !== "]" ||
|
|
3220
|
+
(rawPart.length > 1 && rawPart.startsWith("0"))
|
|
3221
|
+
) {
|
|
3222
|
+
return false;
|
|
3223
|
+
}
|
|
3224
|
+
index += 1;
|
|
3225
|
+
parts.push(rawPart);
|
|
3226
|
+
return true;
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
if (firstCharacter !== '"' && firstCharacter !== "'") {
|
|
3230
|
+
return false;
|
|
3231
|
+
}
|
|
3232
|
+
|
|
3233
|
+
const quote = firstCharacter;
|
|
3234
|
+
const startIndex = index;
|
|
3235
|
+
index += 1;
|
|
3236
|
+
let escaped = false;
|
|
3237
|
+
while (index < value.length) {
|
|
3238
|
+
const character = value[index];
|
|
3239
|
+
if (escaped) {
|
|
3240
|
+
escaped = false;
|
|
3241
|
+
} else if (character === "\\") {
|
|
3242
|
+
escaped = true;
|
|
3243
|
+
} else if (character === quote) {
|
|
3244
|
+
const decodedPart = decodeComputedReferenceQuotedPart(
|
|
3245
|
+
value.slice(startIndex, index + 1),
|
|
3246
|
+
);
|
|
3247
|
+
if (!decodedPart.valid) {
|
|
3248
|
+
return false;
|
|
3249
|
+
}
|
|
3250
|
+
|
|
3251
|
+
index += 1;
|
|
3252
|
+
while (index < value.length && /\s/.test(value[index])) {
|
|
3253
|
+
index += 1;
|
|
3254
|
+
}
|
|
3255
|
+
if (value[index] !== "]") {
|
|
3256
|
+
return false;
|
|
3257
|
+
}
|
|
3258
|
+
index += 1;
|
|
3259
|
+
parts.push(decodedPart.value);
|
|
3260
|
+
return true;
|
|
3261
|
+
}
|
|
3262
|
+
index += 1;
|
|
3263
|
+
}
|
|
3264
|
+
|
|
3265
|
+
return false;
|
|
3266
|
+
};
|
|
3267
|
+
|
|
3268
|
+
if (!readBarePart()) {
|
|
3269
|
+
return invalidComputedReferenceParseResult;
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3272
|
+
while (index < value.length) {
|
|
3273
|
+
if (value[index] === ".") {
|
|
3274
|
+
index += 1;
|
|
3275
|
+
if (!readBarePart()) {
|
|
3276
|
+
return invalidComputedReferenceParseResult;
|
|
3277
|
+
}
|
|
3278
|
+
continue;
|
|
3279
|
+
}
|
|
3280
|
+
|
|
3281
|
+
if (value[index] === "[") {
|
|
3282
|
+
if (!readBracketPart()) {
|
|
3283
|
+
return invalidComputedReferenceParseResult;
|
|
3284
|
+
}
|
|
3285
|
+
continue;
|
|
3286
|
+
}
|
|
3287
|
+
|
|
3288
|
+
return invalidComputedReferenceParseResult;
|
|
3289
|
+
}
|
|
3290
|
+
|
|
3291
|
+
return {
|
|
3292
|
+
valid: true,
|
|
3293
|
+
parts,
|
|
3294
|
+
};
|
|
3295
|
+
};
|
|
3296
|
+
|
|
3297
|
+
const validateComputedReferencePath = ({
|
|
3298
|
+
value,
|
|
3299
|
+
path,
|
|
3300
|
+
errorFactory,
|
|
3301
|
+
variables,
|
|
3302
|
+
dependencies,
|
|
3303
|
+
}) => {
|
|
3304
|
+
if (!isNonEmptyString(value)) {
|
|
3305
|
+
return invalidFromErrorFactory(
|
|
3306
|
+
errorFactory,
|
|
3307
|
+
`${path} must be a non-empty string path`,
|
|
3308
|
+
);
|
|
3309
|
+
}
|
|
3310
|
+
|
|
3311
|
+
const parsedPath = parseComputedReferencePath(value);
|
|
3312
|
+
if (!parsedPath.valid) {
|
|
3313
|
+
return invalidFromErrorFactory(
|
|
3314
|
+
errorFactory,
|
|
3315
|
+
`${path} has an invalid reference path`,
|
|
3316
|
+
);
|
|
3317
|
+
}
|
|
3318
|
+
|
|
3319
|
+
const [root, referencedId, ...nestedPath] = parsedPath.parts;
|
|
3320
|
+
if (root !== "variables" && root !== "runtime") {
|
|
3321
|
+
return invalidFromErrorFactory(
|
|
3322
|
+
errorFactory,
|
|
3323
|
+
`${path} must reference a concrete variables.* or runtime.* path`,
|
|
3324
|
+
);
|
|
3325
|
+
}
|
|
3326
|
+
|
|
3327
|
+
if (!isNonEmptyString(referencedId)) {
|
|
3328
|
+
return invalidFromErrorFactory(
|
|
3329
|
+
errorFactory,
|
|
3330
|
+
`${path} must reference a concrete ${root} member`,
|
|
3331
|
+
);
|
|
3332
|
+
}
|
|
3333
|
+
|
|
3334
|
+
if (root === "runtime" || variables === undefined) {
|
|
3335
|
+
return validComputedResult(undefined);
|
|
3336
|
+
}
|
|
3337
|
+
|
|
3338
|
+
const referencedVariable = Object.hasOwn(variables, referencedId)
|
|
3339
|
+
? variables[referencedId]
|
|
3340
|
+
: undefined;
|
|
3341
|
+
if (
|
|
3342
|
+
!isPlainObject(referencedVariable) ||
|
|
3343
|
+
referencedVariable.type !== "variable"
|
|
3344
|
+
) {
|
|
3345
|
+
return invalidFromErrorFactory(
|
|
3346
|
+
errorFactory,
|
|
3347
|
+
`${path} references unknown variable '${referencedId}'`,
|
|
3348
|
+
);
|
|
3349
|
+
}
|
|
3350
|
+
|
|
3351
|
+
if (Object.hasOwn(referencedVariable, "computed")) {
|
|
3352
|
+
dependencies?.add(referencedId);
|
|
3353
|
+
}
|
|
3354
|
+
|
|
3355
|
+
return validComputedResult(
|
|
3356
|
+
nestedPath.length === 0 ? referencedVariable.variableType : undefined,
|
|
3357
|
+
);
|
|
3358
|
+
};
|
|
3359
|
+
|
|
3360
|
+
const validateComputedExpression = ({
|
|
3361
|
+
expression,
|
|
3362
|
+
path,
|
|
3363
|
+
errorFactory,
|
|
3364
|
+
variables,
|
|
3365
|
+
dependencies,
|
|
3366
|
+
ancestors = new Set(),
|
|
3367
|
+
}) => {
|
|
3368
|
+
if (expression === null) {
|
|
3369
|
+
return validComputedResult("null");
|
|
3370
|
+
}
|
|
3371
|
+
|
|
3372
|
+
if (typeof expression !== "object") {
|
|
3373
|
+
if (typeof expression === "number") {
|
|
3374
|
+
if (Number.isFinite(expression)) {
|
|
3375
|
+
return validComputedResult("number");
|
|
3376
|
+
}
|
|
3377
|
+
return invalidFromErrorFactory(
|
|
3378
|
+
errorFactory,
|
|
3379
|
+
`${path} must use finite numeric literals`,
|
|
3380
|
+
);
|
|
3381
|
+
}
|
|
3382
|
+
|
|
3383
|
+
if (
|
|
3384
|
+
typeof expression === "string" ||
|
|
3385
|
+
typeof expression === "boolean"
|
|
3386
|
+
) {
|
|
3387
|
+
return validComputedResult(typeof expression);
|
|
3388
|
+
}
|
|
3389
|
+
|
|
3390
|
+
return invalidFromErrorFactory(
|
|
3391
|
+
errorFactory,
|
|
3392
|
+
`${path} must use JSON-compatible primitive literals`,
|
|
3393
|
+
);
|
|
3394
|
+
}
|
|
3395
|
+
|
|
3396
|
+
if (Array.isArray(expression)) {
|
|
3397
|
+
return invalidFromErrorFactory(
|
|
3398
|
+
errorFactory,
|
|
3399
|
+
`${path} arrays must be wrapped in a literal operator or authored as value`,
|
|
3400
|
+
);
|
|
3401
|
+
}
|
|
3402
|
+
|
|
3403
|
+
if (ancestors.has(expression)) {
|
|
3404
|
+
return invalidFromErrorFactory(
|
|
3405
|
+
errorFactory,
|
|
3406
|
+
`${path} must not contain cyclic expression data`,
|
|
3407
|
+
);
|
|
3408
|
+
}
|
|
3409
|
+
const nextAncestors = new Set(ancestors);
|
|
3410
|
+
nextAncestors.add(expression);
|
|
3411
|
+
|
|
3412
|
+
const entries = Object.entries(expression);
|
|
3413
|
+
if (entries.length !== 1) {
|
|
3414
|
+
return invalidFromErrorFactory(
|
|
3415
|
+
errorFactory,
|
|
3416
|
+
`${path} must contain exactly one expression operator`,
|
|
3417
|
+
);
|
|
3418
|
+
}
|
|
3419
|
+
|
|
3420
|
+
const [[operator, operands]] = entries;
|
|
3421
|
+
if (operator === "var") {
|
|
3422
|
+
const result = validateComputedReferencePath({
|
|
3423
|
+
value: operands,
|
|
3424
|
+
path: `${path}.var`,
|
|
3425
|
+
errorFactory,
|
|
3426
|
+
variables,
|
|
3427
|
+
dependencies,
|
|
3428
|
+
});
|
|
3429
|
+
return result;
|
|
3430
|
+
}
|
|
3431
|
+
|
|
3432
|
+
if (operator === "literal") {
|
|
3433
|
+
const result = validateComputedDataValue({
|
|
3434
|
+
value: operands,
|
|
3435
|
+
path: `${path}.literal`,
|
|
3436
|
+
errorFactory,
|
|
3437
|
+
});
|
|
3438
|
+
return result?.valid === false
|
|
3439
|
+
? result
|
|
3440
|
+
: validComputedResult(getComputedValueType(operands));
|
|
3441
|
+
}
|
|
3442
|
+
|
|
3443
|
+
const fixedOperandCount = COMPUTED_EXPRESSION_FIXED_OPERAND_COUNTS[operator];
|
|
3444
|
+
const isVariadic = COMPUTED_EXPRESSION_VARIADIC_OPERATORS.has(operator);
|
|
3445
|
+
if (fixedOperandCount === undefined && !isVariadic) {
|
|
3446
|
+
return invalidFromErrorFactory(
|
|
3447
|
+
errorFactory,
|
|
3448
|
+
`${path} contains unsupported expression operator '${operator}'`,
|
|
3449
|
+
);
|
|
3450
|
+
}
|
|
3451
|
+
|
|
3452
|
+
if (!Array.isArray(operands)) {
|
|
3453
|
+
return invalidFromErrorFactory(
|
|
3454
|
+
errorFactory,
|
|
3455
|
+
`${path}.${operator} must be an operand array`,
|
|
3456
|
+
);
|
|
3457
|
+
}
|
|
3458
|
+
|
|
3459
|
+
if (
|
|
3460
|
+
(fixedOperandCount !== undefined &&
|
|
3461
|
+
operands.length !== fixedOperandCount) ||
|
|
3462
|
+
(isVariadic && operands.length === 0)
|
|
3463
|
+
) {
|
|
3464
|
+
const operandRequirement = isVariadic
|
|
3465
|
+
? "at least one operand"
|
|
3466
|
+
: `exactly ${fixedOperandCount} operands`;
|
|
3467
|
+
return invalidFromErrorFactory(
|
|
3468
|
+
errorFactory,
|
|
3469
|
+
`${path}.${operator} requires ${operandRequirement}`,
|
|
3470
|
+
);
|
|
3471
|
+
}
|
|
3472
|
+
|
|
3473
|
+
const operandTypes = [];
|
|
3474
|
+
for (const [index, operand] of operands.entries()) {
|
|
3475
|
+
const result = validateComputedExpression({
|
|
3476
|
+
expression: operand,
|
|
3477
|
+
path: `${path}.${operator}[${index}]`,
|
|
3478
|
+
errorFactory,
|
|
3479
|
+
variables,
|
|
3480
|
+
dependencies,
|
|
3481
|
+
ancestors: nextAncestors,
|
|
3482
|
+
});
|
|
3483
|
+
if (result?.valid === false) {
|
|
3484
|
+
return result;
|
|
3485
|
+
}
|
|
3486
|
+
operandTypes.push(result.valueType);
|
|
3487
|
+
}
|
|
3488
|
+
|
|
3489
|
+
if (
|
|
3490
|
+
COMPUTED_EXPRESSION_NUMERIC_OPERAND_OPERATORS.has(operator) &&
|
|
3491
|
+
operandTypes.some(
|
|
3492
|
+
(operandType) => operandType !== undefined && operandType !== "number",
|
|
3493
|
+
)
|
|
3494
|
+
) {
|
|
3495
|
+
return invalidFromErrorFactory(
|
|
3496
|
+
errorFactory,
|
|
3497
|
+
`${path}.${operator} requires numeric operands`,
|
|
3498
|
+
);
|
|
3499
|
+
}
|
|
3500
|
+
|
|
3501
|
+
if (COMPUTED_EXPRESSION_NUMERIC_RESULT_OPERATORS.has(operator)) {
|
|
3502
|
+
return validComputedResult("number");
|
|
3503
|
+
}
|
|
3504
|
+
if (COMPUTED_EXPRESSION_BOOLEAN_RESULT_OPERATORS.has(operator)) {
|
|
3505
|
+
return validComputedResult("boolean");
|
|
3506
|
+
}
|
|
3507
|
+
return validComputedResult(undefined);
|
|
3508
|
+
};
|
|
3509
|
+
|
|
3510
|
+
const validateComputedCondition = ({
|
|
3511
|
+
condition,
|
|
3512
|
+
path,
|
|
3513
|
+
errorFactory,
|
|
3514
|
+
variables,
|
|
3515
|
+
dependencies,
|
|
3516
|
+
isRoot = true,
|
|
3517
|
+
ancestors = new Set(),
|
|
3518
|
+
}) => {
|
|
3519
|
+
if (isRoot && typeof condition === "string") {
|
|
3520
|
+
return invalidFromErrorFactory(
|
|
3521
|
+
errorFactory,
|
|
3522
|
+
`${path} string conditions are not supported`,
|
|
3523
|
+
);
|
|
3524
|
+
}
|
|
3525
|
+
|
|
3526
|
+
if (condition === null) {
|
|
3527
|
+
return validComputedResult("null");
|
|
3528
|
+
}
|
|
3529
|
+
|
|
3530
|
+
if (typeof condition !== "object") {
|
|
3531
|
+
if (typeof condition === "number") {
|
|
3532
|
+
if (Number.isFinite(condition)) {
|
|
3533
|
+
return validComputedResult("number");
|
|
3534
|
+
}
|
|
3535
|
+
return invalidFromErrorFactory(
|
|
3536
|
+
errorFactory,
|
|
3537
|
+
`${path} must use finite numeric literals`,
|
|
3538
|
+
);
|
|
3539
|
+
}
|
|
3540
|
+
|
|
3541
|
+
if (
|
|
3542
|
+
typeof condition === "string" ||
|
|
3543
|
+
typeof condition === "boolean"
|
|
3544
|
+
) {
|
|
3545
|
+
return validComputedResult(typeof condition);
|
|
3546
|
+
}
|
|
3547
|
+
|
|
3548
|
+
return invalidFromErrorFactory(
|
|
3549
|
+
errorFactory,
|
|
3550
|
+
`${path} must use JSON-compatible primitive literals`,
|
|
3551
|
+
);
|
|
3552
|
+
}
|
|
3553
|
+
|
|
3554
|
+
if (Array.isArray(condition)) {
|
|
3555
|
+
return invalidFromErrorFactory(
|
|
3556
|
+
errorFactory,
|
|
3557
|
+
`${path} arrays must be wrapped in a condition operator or literal`,
|
|
3558
|
+
);
|
|
3559
|
+
}
|
|
3560
|
+
|
|
3561
|
+
if (ancestors.has(condition)) {
|
|
3562
|
+
return invalidFromErrorFactory(
|
|
3563
|
+
errorFactory,
|
|
3564
|
+
`${path} must not contain cyclic condition data`,
|
|
3565
|
+
);
|
|
3566
|
+
}
|
|
3567
|
+
const nextAncestors = new Set(ancestors);
|
|
3568
|
+
nextAncestors.add(condition);
|
|
3569
|
+
|
|
3570
|
+
const entries = Object.entries(condition);
|
|
3571
|
+
if (entries.length !== 1) {
|
|
3572
|
+
return invalidFromErrorFactory(
|
|
3573
|
+
errorFactory,
|
|
3574
|
+
`${path} must contain exactly one condition operator`,
|
|
3575
|
+
);
|
|
3576
|
+
}
|
|
3577
|
+
|
|
3578
|
+
const [[operator, operands]] = entries;
|
|
3579
|
+
if (operator === "var") {
|
|
3580
|
+
const result = validateComputedReferencePath({
|
|
3581
|
+
value: operands,
|
|
3582
|
+
path: `${path}.var`,
|
|
3583
|
+
errorFactory,
|
|
3584
|
+
variables,
|
|
3585
|
+
dependencies,
|
|
3586
|
+
});
|
|
3587
|
+
return result;
|
|
3588
|
+
}
|
|
3589
|
+
if (operator === "literal") {
|
|
3590
|
+
const result = validateComputedDataValue({
|
|
3591
|
+
value: operands,
|
|
3592
|
+
path: `${path}.literal`,
|
|
3593
|
+
errorFactory,
|
|
3594
|
+
});
|
|
3595
|
+
return result?.valid === false
|
|
3596
|
+
? result
|
|
3597
|
+
: validComputedResult(getComputedValueType(operands));
|
|
3598
|
+
}
|
|
3599
|
+
if (operator === "call") {
|
|
3600
|
+
return invalidFromErrorFactory(
|
|
3601
|
+
errorFactory,
|
|
3602
|
+
`${path} function calls are not supported`,
|
|
3603
|
+
);
|
|
3604
|
+
}
|
|
3605
|
+
if (operator === "not") {
|
|
3606
|
+
const result = validateComputedCondition({
|
|
3607
|
+
condition: operands,
|
|
3608
|
+
path: `${path}.not`,
|
|
3609
|
+
errorFactory,
|
|
3610
|
+
variables,
|
|
3611
|
+
dependencies,
|
|
3612
|
+
isRoot: false,
|
|
3613
|
+
ancestors: nextAncestors,
|
|
3614
|
+
});
|
|
3615
|
+
return result?.valid === false ? result : validComputedResult("boolean");
|
|
3616
|
+
}
|
|
3617
|
+
|
|
3618
|
+
const fixedOperandCount = COMPUTED_CONDITION_FIXED_OPERAND_COUNTS[operator];
|
|
3619
|
+
const isVariadic = COMPUTED_CONDITION_VARIADIC_OPERATORS.has(operator);
|
|
3620
|
+
if (fixedOperandCount === undefined && !isVariadic) {
|
|
3621
|
+
return invalidFromErrorFactory(
|
|
3622
|
+
errorFactory,
|
|
3623
|
+
`${path} contains unsupported condition operator '${operator}'`,
|
|
3624
|
+
);
|
|
3625
|
+
}
|
|
3626
|
+
if (!Array.isArray(operands)) {
|
|
3627
|
+
return invalidFromErrorFactory(
|
|
3628
|
+
errorFactory,
|
|
3629
|
+
`${path}.${operator} must be an operand array`,
|
|
3630
|
+
);
|
|
3631
|
+
}
|
|
3632
|
+
if (
|
|
3633
|
+
(fixedOperandCount !== undefined &&
|
|
3634
|
+
operands.length !== fixedOperandCount) ||
|
|
3635
|
+
(isVariadic && operands.length === 0)
|
|
3636
|
+
) {
|
|
3637
|
+
const operandRequirement = isVariadic
|
|
3638
|
+
? "at least one operand"
|
|
3639
|
+
: `exactly ${fixedOperandCount} operands`;
|
|
3640
|
+
return invalidFromErrorFactory(
|
|
3641
|
+
errorFactory,
|
|
3642
|
+
`${path}.${operator} requires ${operandRequirement}`,
|
|
3643
|
+
);
|
|
3644
|
+
}
|
|
3645
|
+
|
|
3646
|
+
const operandTypes = [];
|
|
3647
|
+
for (const [index, operand] of operands.entries()) {
|
|
3648
|
+
const result = validateComputedCondition({
|
|
3649
|
+
condition: operand,
|
|
3650
|
+
path: `${path}.${operator}[${index}]`,
|
|
3651
|
+
errorFactory,
|
|
3652
|
+
variables,
|
|
3653
|
+
dependencies,
|
|
3654
|
+
isRoot: false,
|
|
3655
|
+
ancestors: nextAncestors,
|
|
3656
|
+
});
|
|
3657
|
+
if (result?.valid === false) {
|
|
3658
|
+
return result;
|
|
3659
|
+
}
|
|
3660
|
+
operandTypes.push(result.valueType);
|
|
3661
|
+
}
|
|
3662
|
+
|
|
3663
|
+
if (
|
|
3664
|
+
(operator === "add" || operator === "sub") &&
|
|
3665
|
+
operandTypes.some(
|
|
3666
|
+
(operandType) => operandType !== undefined && operandType !== "number",
|
|
3667
|
+
)
|
|
3668
|
+
) {
|
|
3669
|
+
return invalidFromErrorFactory(
|
|
3670
|
+
errorFactory,
|
|
3671
|
+
`${path}.${operator} requires numeric operands`,
|
|
3672
|
+
);
|
|
3673
|
+
}
|
|
3674
|
+
|
|
3675
|
+
return validComputedResult(
|
|
3676
|
+
operator === "add" || operator === "sub" ? "number" : "boolean",
|
|
3677
|
+
);
|
|
3678
|
+
};
|
|
3679
|
+
|
|
3680
|
+
const validateComputedResultConfig = ({
|
|
3681
|
+
resultConfig,
|
|
3682
|
+
variableType,
|
|
3683
|
+
path,
|
|
3684
|
+
errorFactory,
|
|
3685
|
+
variables,
|
|
3686
|
+
dependencies,
|
|
3687
|
+
allowedKeys = ["expr", "value"],
|
|
3688
|
+
}) => {
|
|
3689
|
+
if (!isPlainObject(resultConfig)) {
|
|
3690
|
+
return invalidFromErrorFactory(errorFactory, `${path} must be an object`);
|
|
3691
|
+
}
|
|
3692
|
+
|
|
3693
|
+
{
|
|
3694
|
+
const result = validateAllowedKeys({
|
|
3695
|
+
value: resultConfig,
|
|
3696
|
+
allowedKeys,
|
|
3697
|
+
path,
|
|
3698
|
+
errorFactory,
|
|
3699
|
+
});
|
|
3700
|
+
if (result?.valid === false) {
|
|
3701
|
+
return result;
|
|
3702
|
+
}
|
|
3703
|
+
}
|
|
3704
|
+
|
|
3705
|
+
const hasExpression = Object.hasOwn(resultConfig, "expr");
|
|
3706
|
+
const hasValue = Object.hasOwn(resultConfig, "value");
|
|
3707
|
+
if (hasExpression === hasValue) {
|
|
3708
|
+
return invalidFromErrorFactory(
|
|
3709
|
+
errorFactory,
|
|
3710
|
+
`${path} must contain exactly one of expr or value`,
|
|
3711
|
+
);
|
|
3712
|
+
}
|
|
3713
|
+
|
|
3714
|
+
if (hasValue) {
|
|
3715
|
+
const dataResult = validateComputedDataValue({
|
|
3716
|
+
value: resultConfig.value,
|
|
3717
|
+
path: `${path}.value`,
|
|
3718
|
+
errorFactory,
|
|
3719
|
+
});
|
|
3720
|
+
if (dataResult?.valid === false) {
|
|
3721
|
+
return dataResult;
|
|
3722
|
+
}
|
|
3723
|
+
return validateVariableTypedValue({
|
|
3724
|
+
value: resultConfig.value,
|
|
3725
|
+
variableType,
|
|
3726
|
+
path: `${path}.value`,
|
|
3727
|
+
errorFactory,
|
|
3728
|
+
});
|
|
3729
|
+
}
|
|
3730
|
+
|
|
3731
|
+
const expressionResult = validateComputedExpression({
|
|
3732
|
+
expression: resultConfig.expr,
|
|
3733
|
+
path: `${path}.expr`,
|
|
3734
|
+
errorFactory,
|
|
3735
|
+
variables,
|
|
3736
|
+
dependencies,
|
|
3737
|
+
});
|
|
3738
|
+
if (expressionResult?.valid === false) {
|
|
3739
|
+
return expressionResult;
|
|
3740
|
+
}
|
|
3741
|
+
if (
|
|
3742
|
+
expressionResult.valueType !== undefined &&
|
|
3743
|
+
expressionResult.valueType !== variableType
|
|
3744
|
+
) {
|
|
3745
|
+
return invalidFromErrorFactory(
|
|
3746
|
+
errorFactory,
|
|
3747
|
+
`${path}.expr must resolve to ${variableType}`,
|
|
3748
|
+
);
|
|
3749
|
+
}
|
|
3750
|
+
return VALID_RESULT;
|
|
3751
|
+
};
|
|
3752
|
+
|
|
3753
|
+
const validateVariableComputedConfig = ({
|
|
3754
|
+
computed,
|
|
3755
|
+
variableType,
|
|
3756
|
+
path,
|
|
3757
|
+
errorFactory,
|
|
3758
|
+
variables,
|
|
3759
|
+
dependencies,
|
|
3760
|
+
}) => {
|
|
3761
|
+
if (!isPlainObject(computed)) {
|
|
3762
|
+
return invalidFromErrorFactory(errorFactory, `${path} must be an object`);
|
|
3763
|
+
}
|
|
3764
|
+
|
|
3765
|
+
if (Object.hasOwn(computed, "branches")) {
|
|
3766
|
+
{
|
|
3767
|
+
const result = validateAllowedKeys({
|
|
3768
|
+
value: computed,
|
|
3769
|
+
allowedKeys: ["branches", "default"],
|
|
3770
|
+
path,
|
|
3771
|
+
errorFactory,
|
|
3772
|
+
});
|
|
3773
|
+
if (result?.valid === false) {
|
|
3774
|
+
return result;
|
|
3775
|
+
}
|
|
3776
|
+
}
|
|
3777
|
+
if (!Array.isArray(computed.branches) || computed.branches.length === 0) {
|
|
3778
|
+
return invalidFromErrorFactory(
|
|
3779
|
+
errorFactory,
|
|
3780
|
+
`${path}.branches must be a non-empty array`,
|
|
3781
|
+
);
|
|
3782
|
+
}
|
|
3783
|
+
if (!isPlainObject(computed.default)) {
|
|
3784
|
+
return invalidFromErrorFactory(
|
|
3785
|
+
errorFactory,
|
|
3786
|
+
`${path}.default must be an object`,
|
|
3787
|
+
);
|
|
3788
|
+
}
|
|
3789
|
+
|
|
3790
|
+
for (const [index, branch] of computed.branches.entries()) {
|
|
3791
|
+
const branchPath = `${path}.branches[${index}]`;
|
|
3792
|
+
if (!isPlainObject(branch)) {
|
|
3793
|
+
return invalidFromErrorFactory(
|
|
3794
|
+
errorFactory,
|
|
3795
|
+
`${branchPath} must be an object`,
|
|
3796
|
+
);
|
|
3797
|
+
}
|
|
3798
|
+
if (!Object.hasOwn(branch, "when")) {
|
|
3799
|
+
return invalidFromErrorFactory(
|
|
3800
|
+
errorFactory,
|
|
3801
|
+
`${branchPath}.when is required`,
|
|
3802
|
+
);
|
|
3803
|
+
}
|
|
3804
|
+
const conditionResult = validateComputedCondition({
|
|
3805
|
+
condition: branch.when,
|
|
3806
|
+
path: `${branchPath}.when`,
|
|
3807
|
+
errorFactory,
|
|
3808
|
+
variables,
|
|
3809
|
+
dependencies,
|
|
3810
|
+
});
|
|
3811
|
+
if (conditionResult?.valid === false) {
|
|
3812
|
+
return conditionResult;
|
|
3813
|
+
}
|
|
3814
|
+
const branchResult = validateComputedResultConfig({
|
|
3815
|
+
resultConfig: branch,
|
|
3816
|
+
variableType,
|
|
3817
|
+
path: branchPath,
|
|
3818
|
+
errorFactory,
|
|
3819
|
+
variables,
|
|
3820
|
+
dependencies,
|
|
3821
|
+
allowedKeys: ["when", "expr", "value"],
|
|
3822
|
+
});
|
|
3823
|
+
if (branchResult?.valid === false) {
|
|
3824
|
+
return branchResult;
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
3827
|
+
|
|
3828
|
+
return validateComputedResultConfig({
|
|
3829
|
+
resultConfig: computed.default,
|
|
3830
|
+
variableType,
|
|
3831
|
+
path: `${path}.default`,
|
|
3832
|
+
errorFactory,
|
|
3833
|
+
variables,
|
|
3834
|
+
dependencies,
|
|
3835
|
+
});
|
|
3836
|
+
}
|
|
3837
|
+
|
|
3838
|
+
return validateComputedResultConfig({
|
|
3839
|
+
resultConfig: computed,
|
|
3840
|
+
variableType,
|
|
3841
|
+
path,
|
|
3842
|
+
errorFactory,
|
|
3843
|
+
variables,
|
|
3844
|
+
dependencies,
|
|
3845
|
+
});
|
|
3846
|
+
};
|
|
3847
|
+
|
|
3848
|
+
const validateComputedVariableGraph = ({
|
|
3849
|
+
items,
|
|
3850
|
+
path,
|
|
3851
|
+
errorFactory,
|
|
3852
|
+
}) => {
|
|
3853
|
+
const dependencyGraph = new Map();
|
|
3854
|
+
|
|
3855
|
+
for (const [variableId, variable] of Object.entries(items)) {
|
|
3856
|
+
if (
|
|
3857
|
+
variable?.type !== "variable" ||
|
|
3858
|
+
!Object.hasOwn(variable, "computed")
|
|
3859
|
+
) {
|
|
3860
|
+
continue;
|
|
3861
|
+
}
|
|
3862
|
+
|
|
3863
|
+
const dependencies = new Set();
|
|
3864
|
+
const result = validateVariableComputedConfig({
|
|
3865
|
+
computed: variable.computed,
|
|
3866
|
+
variableType: variable.variableType,
|
|
3867
|
+
path: `${path}.${variableId}.computed`,
|
|
3868
|
+
errorFactory,
|
|
3869
|
+
variables: items,
|
|
3870
|
+
dependencies,
|
|
3871
|
+
});
|
|
3872
|
+
if (result?.valid === false) {
|
|
3873
|
+
return result;
|
|
3874
|
+
}
|
|
3875
|
+
dependencyGraph.set(variableId, dependencies);
|
|
3876
|
+
}
|
|
3877
|
+
|
|
3878
|
+
const visited = new Set();
|
|
3879
|
+
for (const startVariableId of dependencyGraph.keys()) {
|
|
3880
|
+
if (visited.has(startVariableId)) {
|
|
3881
|
+
continue;
|
|
2828
3882
|
}
|
|
2829
3883
|
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
}
|
|
2836
|
-
|
|
2837
|
-
|
|
3884
|
+
const frames = [
|
|
3885
|
+
{
|
|
3886
|
+
variableId: startVariableId,
|
|
3887
|
+
dependencies: [...(dependencyGraph.get(startVariableId) ?? [])],
|
|
3888
|
+
nextDependencyIndex: 0,
|
|
3889
|
+
},
|
|
3890
|
+
];
|
|
3891
|
+
const activeIndexes = new Map([[startVariableId, 0]]);
|
|
3892
|
+
|
|
3893
|
+
while (frames.length > 0) {
|
|
3894
|
+
const frame = frames.at(-1);
|
|
3895
|
+
if (frame.nextDependencyIndex >= frame.dependencies.length) {
|
|
3896
|
+
frames.pop();
|
|
3897
|
+
activeIndexes.delete(frame.variableId);
|
|
3898
|
+
visited.add(frame.variableId);
|
|
3899
|
+
continue;
|
|
3900
|
+
}
|
|
3901
|
+
|
|
3902
|
+
const dependencyId = frame.dependencies[frame.nextDependencyIndex];
|
|
3903
|
+
frame.nextDependencyIndex += 1;
|
|
3904
|
+
if (visited.has(dependencyId)) {
|
|
3905
|
+
continue;
|
|
3906
|
+
}
|
|
3907
|
+
|
|
3908
|
+
const cycleStartIndex = activeIndexes.get(dependencyId);
|
|
3909
|
+
if (cycleStartIndex !== undefined) {
|
|
3910
|
+
const cycle = [
|
|
3911
|
+
...frames
|
|
3912
|
+
.slice(cycleStartIndex)
|
|
3913
|
+
.map(({ variableId }) => variableId),
|
|
3914
|
+
dependencyId,
|
|
3915
|
+
].join(" -> ");
|
|
3916
|
+
return invalidFromErrorFactory(
|
|
3917
|
+
errorFactory,
|
|
3918
|
+
`${path} contains computed variable cycle: ${cycle}`,
|
|
3919
|
+
);
|
|
3920
|
+
}
|
|
3921
|
+
|
|
3922
|
+
if (!dependencyGraph.has(dependencyId)) {
|
|
3923
|
+
visited.add(dependencyId);
|
|
3924
|
+
continue;
|
|
2838
3925
|
}
|
|
3926
|
+
|
|
3927
|
+
activeIndexes.set(dependencyId, frames.length);
|
|
3928
|
+
frames.push({
|
|
3929
|
+
variableId: dependencyId,
|
|
3930
|
+
dependencies: [...(dependencyGraph.get(dependencyId) ?? [])],
|
|
3931
|
+
nextDependencyIndex: 0,
|
|
3932
|
+
});
|
|
2839
3933
|
}
|
|
2840
3934
|
}
|
|
3935
|
+
|
|
3936
|
+
return VALID_RESULT;
|
|
2841
3937
|
};
|
|
2842
3938
|
|
|
2843
|
-
const
|
|
2844
|
-
|
|
3939
|
+
const validateVariableStoredOrComputedData = ({
|
|
3940
|
+
data,
|
|
2845
3941
|
variableType,
|
|
2846
3942
|
path,
|
|
2847
3943
|
errorFactory,
|
|
2848
3944
|
}) => {
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
3945
|
+
const isComputed = Object.hasOwn(data, "computed");
|
|
3946
|
+
if (isComputed) {
|
|
3947
|
+
if (Object.hasOwn(data, "default") || Object.hasOwn(data, "value")) {
|
|
3948
|
+
return invalidFromErrorFactory(
|
|
3949
|
+
errorFactory,
|
|
3950
|
+
`${path} computed variables must not contain default or value`,
|
|
3951
|
+
);
|
|
3952
|
+
}
|
|
3953
|
+
if (data.isEnum !== undefined || data.enumValues !== undefined) {
|
|
3954
|
+
return invalidFromErrorFactory(
|
|
3955
|
+
errorFactory,
|
|
3956
|
+
`${path} computed variables must not contain enum metadata`,
|
|
3957
|
+
);
|
|
3958
|
+
}
|
|
3959
|
+
return validateVariableComputedConfig({
|
|
3960
|
+
computed: data.computed,
|
|
3961
|
+
variableType,
|
|
3962
|
+
path: `${path}.computed`,
|
|
2855
3963
|
errorFactory,
|
|
2856
|
-
|
|
2857
|
-
);
|
|
3964
|
+
});
|
|
2858
3965
|
}
|
|
2859
3966
|
|
|
2860
|
-
|
|
2861
|
-
|
|
3967
|
+
{
|
|
3968
|
+
const result = validateVariableTypedValue({
|
|
3969
|
+
value: data.default,
|
|
3970
|
+
variableType,
|
|
3971
|
+
path: `${path}.default`,
|
|
3972
|
+
errorFactory,
|
|
3973
|
+
});
|
|
3974
|
+
if (result?.valid === false) {
|
|
3975
|
+
return result;
|
|
3976
|
+
}
|
|
2862
3977
|
}
|
|
3978
|
+
return validateVariableTypedValue({
|
|
3979
|
+
value: data.value,
|
|
3980
|
+
variableType,
|
|
3981
|
+
path: `${path}.value`,
|
|
3982
|
+
errorFactory,
|
|
3983
|
+
});
|
|
2863
3984
|
};
|
|
2864
3985
|
|
|
2865
3986
|
const normalizeVariableEnumValues = (values = []) => {
|
|
@@ -2947,6 +4068,13 @@ const validateVariableItems = ({ items, path, errorFactory }) => {
|
|
|
2947
4068
|
const itemType = item?.type;
|
|
2948
4069
|
const variableType = item?.variableType;
|
|
2949
4070
|
|
|
4071
|
+
if (itemId === "__proto__") {
|
|
4072
|
+
return invalidFromErrorFactory(
|
|
4073
|
+
errorFactory,
|
|
4074
|
+
`${itemPath} uses reserved variable id '__proto__'`,
|
|
4075
|
+
);
|
|
4076
|
+
}
|
|
4077
|
+
|
|
2950
4078
|
if (itemType !== "folder" && itemType !== "variable") {
|
|
2951
4079
|
return invalidFromErrorFactory(
|
|
2952
4080
|
errorFactory,
|
|
@@ -2972,6 +4100,7 @@ const validateVariableItems = ({ items, path, errorFactory }) => {
|
|
|
2972
4100
|
"value",
|
|
2973
4101
|
"isEnum",
|
|
2974
4102
|
"enumValues",
|
|
4103
|
+
"computed",
|
|
2975
4104
|
],
|
|
2976
4105
|
path: itemPath,
|
|
2977
4106
|
errorFactory,
|
|
@@ -3013,7 +4142,7 @@ const validateVariableItems = ({ items, path, errorFactory }) => {
|
|
|
3013
4142
|
if (!VARIABLE_TYPE_KEYS.includes(variableType)) {
|
|
3014
4143
|
return invalidFromErrorFactory(
|
|
3015
4144
|
errorFactory,
|
|
3016
|
-
`${itemPath}.variableType must be 'string', 'number', or '
|
|
4145
|
+
`${itemPath}.variableType must be 'string', 'number', 'boolean', or 'object'`,
|
|
3017
4146
|
);
|
|
3018
4147
|
}
|
|
3019
4148
|
|
|
@@ -3041,7 +4170,14 @@ const validateVariableItems = ({ items, path, errorFactory }) => {
|
|
|
3041
4170
|
}
|
|
3042
4171
|
}
|
|
3043
4172
|
|
|
3044
|
-
if (
|
|
4173
|
+
if (item.computed !== undefined) {
|
|
4174
|
+
if (item.scope !== undefined) {
|
|
4175
|
+
return invalidFromErrorFactory(
|
|
4176
|
+
errorFactory,
|
|
4177
|
+
`${itemPath}.scope must be omitted for computed variables`,
|
|
4178
|
+
);
|
|
4179
|
+
}
|
|
4180
|
+
} else if (!VARIABLE_SCOPE_KEYS.includes(item.scope)) {
|
|
3045
4181
|
return invalidFromErrorFactory(
|
|
3046
4182
|
errorFactory,
|
|
3047
4183
|
`${itemPath}.scope must be 'context', 'device', or 'account'`,
|
|
@@ -3049,21 +4185,10 @@ const validateVariableItems = ({ items, path, errorFactory }) => {
|
|
|
3049
4185
|
}
|
|
3050
4186
|
|
|
3051
4187
|
{
|
|
3052
|
-
const result =
|
|
3053
|
-
|
|
3054
|
-
variableType,
|
|
3055
|
-
path: `${itemPath}.default`,
|
|
3056
|
-
errorFactory,
|
|
3057
|
-
});
|
|
3058
|
-
if (result?.valid === false) {
|
|
3059
|
-
return result;
|
|
3060
|
-
}
|
|
3061
|
-
}
|
|
3062
|
-
{
|
|
3063
|
-
const result = validateVariableTypedValue({
|
|
3064
|
-
value: item.value,
|
|
4188
|
+
const result = validateVariableStoredOrComputedData({
|
|
4189
|
+
data: item,
|
|
3065
4190
|
variableType,
|
|
3066
|
-
path:
|
|
4191
|
+
path: itemPath,
|
|
3067
4192
|
errorFactory,
|
|
3068
4193
|
});
|
|
3069
4194
|
if (result?.valid === false) {
|
|
@@ -3072,6 +4197,12 @@ const validateVariableItems = ({ items, path, errorFactory }) => {
|
|
|
3072
4197
|
}
|
|
3073
4198
|
}
|
|
3074
4199
|
}
|
|
4200
|
+
|
|
4201
|
+
return validateComputedVariableGraph({
|
|
4202
|
+
items,
|
|
4203
|
+
path,
|
|
4204
|
+
errorFactory,
|
|
4205
|
+
});
|
|
3075
4206
|
};
|
|
3076
4207
|
|
|
3077
4208
|
const validateTextStyleShadow = ({ shadow, path, errorFactory }) => {
|
|
@@ -3216,11 +4347,15 @@ const validateTextStyleItems = ({ items, path, errorFactory }) => {
|
|
|
3216
4347
|
}
|
|
3217
4348
|
}
|
|
3218
4349
|
|
|
3219
|
-
|
|
3220
|
-
|
|
4350
|
+
{
|
|
4351
|
+
const result = validateRequiredStringOrUniqueIdArray({
|
|
4352
|
+
value: item.fontId,
|
|
4353
|
+
path: `${itemPath}.fontId`,
|
|
3221
4354
|
errorFactory,
|
|
3222
|
-
|
|
3223
|
-
)
|
|
4355
|
+
});
|
|
4356
|
+
if (result?.valid === false) {
|
|
4357
|
+
return result;
|
|
4358
|
+
}
|
|
3224
4359
|
}
|
|
3225
4360
|
|
|
3226
4361
|
if (!isNonEmptyString(item.colorId)) {
|
|
@@ -7192,15 +8327,17 @@ export const assertInvariants = ({ state }) => {
|
|
|
7192
8327
|
continue;
|
|
7193
8328
|
}
|
|
7194
8329
|
|
|
7195
|
-
const
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7203
|
-
|
|
8330
|
+
for (const fontId of toIdArray(textStyle.fontId)) {
|
|
8331
|
+
const font = state.fonts.items[fontId];
|
|
8332
|
+
if (!isPlainObject(font) || font.type === "folder") {
|
|
8333
|
+
return invalidInvariant(
|
|
8334
|
+
"textStyle.fontId must reference an existing non-folder font",
|
|
8335
|
+
{
|
|
8336
|
+
textStyleId,
|
|
8337
|
+
fontId,
|
|
8338
|
+
},
|
|
8339
|
+
);
|
|
8340
|
+
}
|
|
7204
8341
|
}
|
|
7205
8342
|
|
|
7206
8343
|
const color = state.colors.items[textStyle.colorId];
|
|
@@ -8689,6 +9826,27 @@ const validateRequiredUniqueIdArray = ({ value, path, errorFactory }) => {
|
|
|
8689
9826
|
}
|
|
8690
9827
|
};
|
|
8691
9828
|
|
|
9829
|
+
const validateRequiredStringOrUniqueIdArray = ({
|
|
9830
|
+
value,
|
|
9831
|
+
path,
|
|
9832
|
+
errorFactory,
|
|
9833
|
+
}) => {
|
|
9834
|
+
if (isNonEmptyString(value)) {
|
|
9835
|
+
return VALID_RESULT;
|
|
9836
|
+
}
|
|
9837
|
+
|
|
9838
|
+
if (!Array.isArray(value)) {
|
|
9839
|
+
return invalidFromErrorFactory(
|
|
9840
|
+
errorFactory,
|
|
9841
|
+
`${path} must be a non-empty string or a non-empty array of strings`,
|
|
9842
|
+
);
|
|
9843
|
+
}
|
|
9844
|
+
|
|
9845
|
+
return validateRequiredUniqueIdArray({ value, path, errorFactory });
|
|
9846
|
+
};
|
|
9847
|
+
|
|
9848
|
+
const toIdArray = (value) => (Array.isArray(value) ? value : [value]);
|
|
9849
|
+
|
|
8692
9850
|
const validateOptionalUniqueIdArray = ({
|
|
8693
9851
|
value,
|
|
8694
9852
|
path,
|
|
@@ -9978,7 +11136,15 @@ const validateFontCreateData = ({ data, errorFactory }) => {
|
|
|
9978
11136
|
allowedKeys:
|
|
9979
11137
|
data.type === "folder"
|
|
9980
11138
|
? ["type", "name", "description"]
|
|
9981
|
-
: [
|
|
11139
|
+
: [
|
|
11140
|
+
"type",
|
|
11141
|
+
"name",
|
|
11142
|
+
"description",
|
|
11143
|
+
"tagIds",
|
|
11144
|
+
"fileId",
|
|
11145
|
+
"fontFamily",
|
|
11146
|
+
...FONT_WEIGHT_KEYS,
|
|
11147
|
+
],
|
|
9982
11148
|
path: "payload.data",
|
|
9983
11149
|
errorFactory,
|
|
9984
11150
|
});
|
|
@@ -10026,6 +11192,17 @@ const validateFontCreateData = ({ data, errorFactory }) => {
|
|
|
10026
11192
|
"payload.data.fontFamily must be a non-empty string",
|
|
10027
11193
|
);
|
|
10028
11194
|
}
|
|
11195
|
+
|
|
11196
|
+
{
|
|
11197
|
+
const result = validateFontWeightFields({
|
|
11198
|
+
value: data,
|
|
11199
|
+
path: "payload.data",
|
|
11200
|
+
errorFactory,
|
|
11201
|
+
});
|
|
11202
|
+
if (result?.valid === false) {
|
|
11203
|
+
return result;
|
|
11204
|
+
}
|
|
11205
|
+
}
|
|
10029
11206
|
}
|
|
10030
11207
|
};
|
|
10031
11208
|
|
|
@@ -10033,7 +11210,14 @@ const validateFontUpdateData = ({ data, errorFactory }) => {
|
|
|
10033
11210
|
{
|
|
10034
11211
|
const result = validateAllowedKeys({
|
|
10035
11212
|
value: data,
|
|
10036
|
-
allowedKeys: [
|
|
11213
|
+
allowedKeys: [
|
|
11214
|
+
"name",
|
|
11215
|
+
"description",
|
|
11216
|
+
"tagIds",
|
|
11217
|
+
"fileId",
|
|
11218
|
+
"fontFamily",
|
|
11219
|
+
...FONT_WEIGHT_KEYS,
|
|
11220
|
+
],
|
|
10037
11221
|
path: "payload.data",
|
|
10038
11222
|
errorFactory,
|
|
10039
11223
|
});
|
|
@@ -10087,6 +11271,17 @@ const validateFontUpdateData = ({ data, errorFactory }) => {
|
|
|
10087
11271
|
"payload.data.fontFamily must be a non-empty string when provided",
|
|
10088
11272
|
);
|
|
10089
11273
|
}
|
|
11274
|
+
|
|
11275
|
+
{
|
|
11276
|
+
const result = validateFontWeightFields({
|
|
11277
|
+
value: data,
|
|
11278
|
+
path: "payload.data",
|
|
11279
|
+
errorFactory,
|
|
11280
|
+
});
|
|
11281
|
+
if (result?.valid === false) {
|
|
11282
|
+
return result;
|
|
11283
|
+
}
|
|
11284
|
+
}
|
|
10090
11285
|
};
|
|
10091
11286
|
|
|
10092
11287
|
const validateFileCreateData = ({ data, errorFactory }) => {
|
|
@@ -10958,6 +12153,7 @@ const validateVariableCreateData = ({ data, errorFactory }) => {
|
|
|
10958
12153
|
"value",
|
|
10959
12154
|
"isEnum",
|
|
10960
12155
|
"enumValues",
|
|
12156
|
+
"computed",
|
|
10961
12157
|
],
|
|
10962
12158
|
path: "payload.data",
|
|
10963
12159
|
errorFactory,
|
|
@@ -10985,7 +12181,7 @@ const validateVariableCreateData = ({ data, errorFactory }) => {
|
|
|
10985
12181
|
if (!VARIABLE_TYPE_KEYS.includes(data.variableType)) {
|
|
10986
12182
|
return invalidFromErrorFactory(
|
|
10987
12183
|
errorFactory,
|
|
10988
|
-
"payload.data.variableType must be 'string', 'number', or '
|
|
12184
|
+
"payload.data.variableType must be 'string', 'number', 'boolean', or 'object'",
|
|
10989
12185
|
);
|
|
10990
12186
|
}
|
|
10991
12187
|
|
|
@@ -11012,7 +12208,14 @@ const validateVariableCreateData = ({ data, errorFactory }) => {
|
|
|
11012
12208
|
}
|
|
11013
12209
|
}
|
|
11014
12210
|
|
|
11015
|
-
if (
|
|
12211
|
+
if (data.computed !== undefined) {
|
|
12212
|
+
if (data.scope !== undefined) {
|
|
12213
|
+
return invalidFromErrorFactory(
|
|
12214
|
+
errorFactory,
|
|
12215
|
+
"payload.data.scope must be omitted for computed variables",
|
|
12216
|
+
);
|
|
12217
|
+
}
|
|
12218
|
+
} else if (!VARIABLE_SCOPE_KEYS.includes(data.scope)) {
|
|
11016
12219
|
return invalidFromErrorFactory(
|
|
11017
12220
|
errorFactory,
|
|
11018
12221
|
"payload.data.scope must be 'context', 'device', or 'account'",
|
|
@@ -11020,21 +12223,10 @@ const validateVariableCreateData = ({ data, errorFactory }) => {
|
|
|
11020
12223
|
}
|
|
11021
12224
|
|
|
11022
12225
|
{
|
|
11023
|
-
const result =
|
|
11024
|
-
|
|
11025
|
-
variableType: data.variableType,
|
|
11026
|
-
path: "payload.data.default",
|
|
11027
|
-
errorFactory,
|
|
11028
|
-
});
|
|
11029
|
-
if (result?.valid === false) {
|
|
11030
|
-
return result;
|
|
11031
|
-
}
|
|
11032
|
-
}
|
|
11033
|
-
{
|
|
11034
|
-
const result = validateVariableTypedValue({
|
|
11035
|
-
value: data.value,
|
|
12226
|
+
const result = validateVariableStoredOrComputedData({
|
|
12227
|
+
data,
|
|
11036
12228
|
variableType: data.variableType,
|
|
11037
|
-
path: "payload.data
|
|
12229
|
+
path: "payload.data",
|
|
11038
12230
|
errorFactory,
|
|
11039
12231
|
});
|
|
11040
12232
|
if (result?.valid === false) {
|
|
@@ -11057,6 +12249,7 @@ const validateVariableUpdateData = ({ data, errorFactory }) => {
|
|
|
11057
12249
|
"value",
|
|
11058
12250
|
"isEnum",
|
|
11059
12251
|
"enumValues",
|
|
12252
|
+
"computed",
|
|
11060
12253
|
],
|
|
11061
12254
|
path: "payload.data",
|
|
11062
12255
|
errorFactory,
|
|
@@ -11105,6 +12298,15 @@ const validateVariableUpdateData = ({ data, errorFactory }) => {
|
|
|
11105
12298
|
}
|
|
11106
12299
|
}
|
|
11107
12300
|
|
|
12301
|
+
if (data.computed !== undefined) {
|
|
12302
|
+
if (!isPlainObject(data.computed)) {
|
|
12303
|
+
return invalidFromErrorFactory(
|
|
12304
|
+
errorFactory,
|
|
12305
|
+
"payload.data.computed must be an object when provided",
|
|
12306
|
+
);
|
|
12307
|
+
}
|
|
12308
|
+
}
|
|
12309
|
+
|
|
11108
12310
|
if (data.scope !== undefined && !VARIABLE_SCOPE_KEYS.includes(data.scope)) {
|
|
11109
12311
|
return invalidFromErrorFactory(
|
|
11110
12312
|
errorFactory,
|
|
@@ -11283,7 +12485,18 @@ const validateTextStyleUpdateData = ({ data, errorFactory }) => {
|
|
|
11283
12485
|
}
|
|
11284
12486
|
}
|
|
11285
12487
|
|
|
11286
|
-
|
|
12488
|
+
if (data.fontId !== undefined) {
|
|
12489
|
+
const result = validateRequiredStringOrUniqueIdArray({
|
|
12490
|
+
value: data.fontId,
|
|
12491
|
+
path: "payload.data.fontId",
|
|
12492
|
+
errorFactory,
|
|
12493
|
+
});
|
|
12494
|
+
if (result?.valid === false) {
|
|
12495
|
+
return result;
|
|
12496
|
+
}
|
|
12497
|
+
}
|
|
12498
|
+
|
|
12499
|
+
for (const key of ["colorId", "strokeColorId"]) {
|
|
11287
12500
|
if (data[key] !== undefined && !isNonEmptyString(data[key])) {
|
|
11288
12501
|
return invalidFromErrorFactory(
|
|
11289
12502
|
errorFactory,
|
|
@@ -12915,6 +14128,7 @@ const createFolderedCollectionCommandDefinitions = ({
|
|
|
12915
14128
|
validateDeleteState = () => {},
|
|
12916
14129
|
afterDelete = () => {},
|
|
12917
14130
|
includeUpdate = true,
|
|
14131
|
+
reservedItemIds = [],
|
|
12918
14132
|
}) => {
|
|
12919
14133
|
const existingMessage = `payload.${idField} must reference an existing ${itemLabel}`;
|
|
12920
14134
|
const duplicateMessage = `payload.${idField} must not already exist`;
|
|
@@ -12955,6 +14169,12 @@ const createFolderedCollectionCommandDefinitions = ({
|
|
|
12955
14169
|
);
|
|
12956
14170
|
}
|
|
12957
14171
|
|
|
14172
|
+
if (reservedItemIds.includes(payload[idField])) {
|
|
14173
|
+
return invalidPayload(
|
|
14174
|
+
`payload.${idField} must not use reserved id '${payload[idField]}'`,
|
|
14175
|
+
);
|
|
14176
|
+
}
|
|
14177
|
+
|
|
12958
14178
|
if (
|
|
12959
14179
|
payload.parentId !== undefined &&
|
|
12960
14180
|
payload.parentId !== null &&
|
|
@@ -17421,6 +18641,11 @@ const COMMAND_DEFINITIONS = [
|
|
|
17421
18641
|
});
|
|
17422
18642
|
nextFont.fileId = payload.data.fileId;
|
|
17423
18643
|
nextFont.fontFamily = payload.data.fontFamily;
|
|
18644
|
+
for (const key of FONT_WEIGHT_KEYS) {
|
|
18645
|
+
if (payload.data[key] !== undefined) {
|
|
18646
|
+
nextFont[key] = payload.data[key];
|
|
18647
|
+
}
|
|
18648
|
+
}
|
|
17424
18649
|
}
|
|
17425
18650
|
|
|
17426
18651
|
state.fonts.items[payload.fontId] = nextFont;
|
|
@@ -17481,7 +18706,8 @@ const COMMAND_DEFINITIONS = [
|
|
|
17481
18706
|
currentFont.type === "folder" &&
|
|
17482
18707
|
(payload.data.tagIds !== undefined ||
|
|
17483
18708
|
payload.data.fileId !== undefined ||
|
|
17484
|
-
payload.data.fontFamily !== undefined
|
|
18709
|
+
payload.data.fontFamily !== undefined ||
|
|
18710
|
+
FONT_WEIGHT_KEYS.some((key) => payload.data[key] !== undefined))
|
|
17485
18711
|
) {
|
|
17486
18712
|
return invalidPrecondition(
|
|
17487
18713
|
"folder font items cannot update font fields",
|
|
@@ -18385,6 +19611,7 @@ const COMMAND_DEFINITIONS = [
|
|
|
18385
19611
|
itemLabel: "variable item",
|
|
18386
19612
|
createDataValidator: validateVariableCreateData,
|
|
18387
19613
|
updateDataValidator: validateVariableUpdateData,
|
|
19614
|
+
reservedItemIds: ["__proto__"],
|
|
18388
19615
|
createItem: ({ payload }) => {
|
|
18389
19616
|
const data = structuredClone(payload.data);
|
|
18390
19617
|
if (!Array.isArray(data.tagIds) || data.tagIds.length === 0) {
|
|
@@ -18409,7 +19636,7 @@ const COMMAND_DEFINITIONS = [
|
|
|
18409
19636
|
return;
|
|
18410
19637
|
}
|
|
18411
19638
|
|
|
18412
|
-
|
|
19639
|
+
const tagResult = validateTagIdsAgainstScope({
|
|
18413
19640
|
state,
|
|
18414
19641
|
tagIds: payload.data.tagIds,
|
|
18415
19642
|
scopeKey: "variables",
|
|
@@ -18418,6 +19645,25 @@ const COMMAND_DEFINITIONS = [
|
|
|
18418
19645
|
variableId: payload.variableId,
|
|
18419
19646
|
},
|
|
18420
19647
|
});
|
|
19648
|
+
if (!tagResult.valid) {
|
|
19649
|
+
return tagResult;
|
|
19650
|
+
}
|
|
19651
|
+
|
|
19652
|
+
if (!Object.hasOwn(payload.data, "computed")) {
|
|
19653
|
+
return VALID_RESULT;
|
|
19654
|
+
}
|
|
19655
|
+
|
|
19656
|
+
return validateComputedVariableGraph({
|
|
19657
|
+
items: {
|
|
19658
|
+
...state.variables.items,
|
|
19659
|
+
[payload.variableId]: {
|
|
19660
|
+
id: payload.variableId,
|
|
19661
|
+
...payload.data,
|
|
19662
|
+
},
|
|
19663
|
+
},
|
|
19664
|
+
path: "state.variables.items",
|
|
19665
|
+
errorFactory: createPreconditionValidationError,
|
|
19666
|
+
});
|
|
18421
19667
|
},
|
|
18422
19668
|
validateUpdateState: ({ state, payload, currentItem }) => {
|
|
18423
19669
|
if (
|
|
@@ -18441,6 +19687,36 @@ const COMMAND_DEFINITIONS = [
|
|
|
18441
19687
|
);
|
|
18442
19688
|
}
|
|
18443
19689
|
|
|
19690
|
+
const currentItemIsComputed = Object.hasOwn(currentItem, "computed");
|
|
19691
|
+
if (
|
|
19692
|
+
currentItemIsComputed &&
|
|
19693
|
+
["scope", "default", "value", "isEnum", "enumValues"].some((key) =>
|
|
19694
|
+
Object.hasOwn(payload.data, key),
|
|
19695
|
+
)
|
|
19696
|
+
) {
|
|
19697
|
+
return invalidPrecondition(
|
|
19698
|
+
"computed variables cannot update scope, stored value, or enum fields",
|
|
19699
|
+
);
|
|
19700
|
+
}
|
|
19701
|
+
|
|
19702
|
+
if (!currentItemIsComputed && Object.hasOwn(payload.data, "computed")) {
|
|
19703
|
+
return invalidPrecondition(
|
|
19704
|
+
"stored variables cannot be converted to computed variables",
|
|
19705
|
+
);
|
|
19706
|
+
}
|
|
19707
|
+
|
|
19708
|
+
if (currentItemIsComputed && Object.hasOwn(payload.data, "computed")) {
|
|
19709
|
+
const result = validateVariableComputedConfig({
|
|
19710
|
+
computed: payload.data.computed,
|
|
19711
|
+
variableType: currentItem.variableType,
|
|
19712
|
+
path: "payload.data.computed",
|
|
19713
|
+
errorFactory: createPreconditionValidationError,
|
|
19714
|
+
});
|
|
19715
|
+
if (result?.valid === false) {
|
|
19716
|
+
return result;
|
|
19717
|
+
}
|
|
19718
|
+
}
|
|
19719
|
+
|
|
18444
19720
|
if (currentItem.type !== "folder") {
|
|
18445
19721
|
{
|
|
18446
19722
|
const result = validateTagIdsAgainstScope({
|
|
@@ -18485,6 +19761,49 @@ const COMMAND_DEFINITIONS = [
|
|
|
18485
19761
|
}
|
|
18486
19762
|
}
|
|
18487
19763
|
}
|
|
19764
|
+
|
|
19765
|
+
if (currentItemIsComputed && Object.hasOwn(payload.data, "computed")) {
|
|
19766
|
+
return validateComputedVariableGraph({
|
|
19767
|
+
items: {
|
|
19768
|
+
...state.variables.items,
|
|
19769
|
+
[payload.variableId]: applyVariableUpdate({
|
|
19770
|
+
currentItem,
|
|
19771
|
+
data: payload.data,
|
|
19772
|
+
}),
|
|
19773
|
+
},
|
|
19774
|
+
path: "state.variables.items",
|
|
19775
|
+
errorFactory: createPreconditionValidationError,
|
|
19776
|
+
});
|
|
19777
|
+
}
|
|
19778
|
+
|
|
19779
|
+
return VALID_RESULT;
|
|
19780
|
+
},
|
|
19781
|
+
validateDeleteState: ({ state, payload }) => {
|
|
19782
|
+
const deletedIds = new Set();
|
|
19783
|
+
for (const variableId of payload.variableIds) {
|
|
19784
|
+
const node = findTreeNode({
|
|
19785
|
+
nodes: state.variables.tree,
|
|
19786
|
+
nodeId: variableId,
|
|
19787
|
+
});
|
|
19788
|
+
if (!node) {
|
|
19789
|
+
deletedIds.add(variableId);
|
|
19790
|
+
continue;
|
|
19791
|
+
}
|
|
19792
|
+
for (const descendantId of collectTreeDescendantIds({ node })) {
|
|
19793
|
+
deletedIds.add(descendantId);
|
|
19794
|
+
}
|
|
19795
|
+
}
|
|
19796
|
+
|
|
19797
|
+
const remainingItems = Object.fromEntries(
|
|
19798
|
+
Object.entries(state.variables.items).filter(
|
|
19799
|
+
([variableId]) => !deletedIds.has(variableId),
|
|
19800
|
+
),
|
|
19801
|
+
);
|
|
19802
|
+
return validateComputedVariableGraph({
|
|
19803
|
+
items: remainingItems,
|
|
19804
|
+
path: "state.variables.items",
|
|
19805
|
+
errorFactory: createPreconditionValidationError,
|
|
19806
|
+
});
|
|
18488
19807
|
},
|
|
18489
19808
|
}),
|
|
18490
19809
|
...createFolderedCollectionCommandDefinitions({
|
|
@@ -18531,16 +19850,24 @@ const COMMAND_DEFINITIONS = [
|
|
|
18531
19850
|
}
|
|
18532
19851
|
}
|
|
18533
19852
|
|
|
18534
|
-
for (const
|
|
19853
|
+
for (const fontId of toIdArray(data.fontId)) {
|
|
19854
|
+
const item = state.fonts.items[fontId];
|
|
19855
|
+
if (!isPlainObject(item) || item.type === "folder") {
|
|
19856
|
+
return invalidPrecondition(
|
|
19857
|
+
"payload.data.fontId must reference an existing non-folder font",
|
|
19858
|
+
);
|
|
19859
|
+
}
|
|
19860
|
+
}
|
|
19861
|
+
|
|
19862
|
+
for (const field of ["colorId", "strokeColorId"]) {
|
|
18535
19863
|
if (data[field] === undefined) {
|
|
18536
19864
|
continue;
|
|
18537
19865
|
}
|
|
18538
19866
|
|
|
18539
|
-
const
|
|
18540
|
-
const item = state[collectionKey].items[data[field]];
|
|
19867
|
+
const item = state.colors.items[data[field]];
|
|
18541
19868
|
if (!isPlainObject(item) || item.type === "folder") {
|
|
18542
19869
|
return invalidPrecondition(
|
|
18543
|
-
`payload.data.${field} must reference an existing non-folder
|
|
19870
|
+
`payload.data.${field} must reference an existing non-folder color`,
|
|
18544
19871
|
);
|
|
18545
19872
|
}
|
|
18546
19873
|
}
|
|
@@ -18583,16 +19910,26 @@ const COMMAND_DEFINITIONS = [
|
|
|
18583
19910
|
}
|
|
18584
19911
|
}
|
|
18585
19912
|
|
|
18586
|
-
|
|
19913
|
+
if (payload.data.fontId !== undefined) {
|
|
19914
|
+
for (const fontId of toIdArray(payload.data.fontId)) {
|
|
19915
|
+
const item = state.fonts.items[fontId];
|
|
19916
|
+
if (!isPlainObject(item) || item.type === "folder") {
|
|
19917
|
+
return invalidPrecondition(
|
|
19918
|
+
"payload.data.fontId must reference an existing non-folder font",
|
|
19919
|
+
);
|
|
19920
|
+
}
|
|
19921
|
+
}
|
|
19922
|
+
}
|
|
19923
|
+
|
|
19924
|
+
for (const field of ["colorId", "strokeColorId"]) {
|
|
18587
19925
|
if (payload.data[field] === undefined) {
|
|
18588
19926
|
continue;
|
|
18589
19927
|
}
|
|
18590
19928
|
|
|
18591
|
-
const
|
|
18592
|
-
const item = state[collectionKey].items[payload.data[field]];
|
|
19929
|
+
const item = state.colors.items[payload.data[field]];
|
|
18593
19930
|
if (!isPlainObject(item) || item.type === "folder") {
|
|
18594
19931
|
return invalidPrecondition(
|
|
18595
|
-
`payload.data.${field} must reference an existing non-folder
|
|
19932
|
+
`payload.data.${field} must reference an existing non-folder color`,
|
|
18596
19933
|
);
|
|
18597
19934
|
}
|
|
18598
19935
|
}
|