@famgia/omnify-core 0.0.6 → 0.0.7
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/dist/index.cjs +758 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +758 -73
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -810,29 +810,544 @@ async function loadSchemas(directoryPath, options = {}) {
|
|
|
810
810
|
return schemas;
|
|
811
811
|
}
|
|
812
812
|
|
|
813
|
-
// src/validation/
|
|
814
|
-
var
|
|
815
|
-
"
|
|
816
|
-
"
|
|
817
|
-
"
|
|
818
|
-
"
|
|
819
|
-
"
|
|
820
|
-
"
|
|
821
|
-
"
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
813
|
+
// src/validation/types/base.ts
|
|
814
|
+
var BASE_PROPERTY_FIELDS = [
|
|
815
|
+
"type",
|
|
816
|
+
"displayName",
|
|
817
|
+
"nullable",
|
|
818
|
+
"default",
|
|
819
|
+
"unique",
|
|
820
|
+
"description",
|
|
821
|
+
"renamedFrom"
|
|
822
|
+
];
|
|
823
|
+
function createValidFields(additionalFields) {
|
|
824
|
+
return [...BASE_PROPERTY_FIELDS, ...additionalFields];
|
|
825
|
+
}
|
|
826
|
+
function fullDbCompatibility() {
|
|
827
|
+
return {
|
|
828
|
+
mysql: "full",
|
|
829
|
+
postgres: "full",
|
|
830
|
+
sqlite: "full",
|
|
831
|
+
sqlserver: "full"
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// src/validation/types/registry.ts
|
|
836
|
+
var TypeRegistry = class {
|
|
837
|
+
types = /* @__PURE__ */ new Map();
|
|
838
|
+
/**
|
|
839
|
+
* Register a type definition.
|
|
840
|
+
*/
|
|
841
|
+
register(type) {
|
|
842
|
+
if (this.types.has(type.name)) {
|
|
843
|
+
throw new Error(`Type '${type.name}' is already registered`);
|
|
844
|
+
}
|
|
845
|
+
this.types.set(type.name, type);
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Register multiple type definitions.
|
|
849
|
+
*/
|
|
850
|
+
registerAll(types) {
|
|
851
|
+
for (const type of types) {
|
|
852
|
+
this.register(type);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Get a type definition by name.
|
|
857
|
+
*/
|
|
858
|
+
get(name) {
|
|
859
|
+
return this.types.get(name);
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Check if a type is registered.
|
|
863
|
+
*/
|
|
864
|
+
has(name) {
|
|
865
|
+
return this.types.has(name);
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Get valid fields for a type.
|
|
869
|
+
* Returns base fields if type not found.
|
|
870
|
+
*/
|
|
871
|
+
getValidFields(name) {
|
|
872
|
+
const type = this.types.get(name);
|
|
873
|
+
return type?.validFields ?? BASE_PROPERTY_FIELDS;
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* Get database compatibility for a type.
|
|
877
|
+
* Returns 'full' if type not found (custom types assumed compatible).
|
|
878
|
+
*/
|
|
879
|
+
getDbCompatibility(name, driver) {
|
|
880
|
+
const type = this.types.get(name);
|
|
881
|
+
return type?.dbCompatibility[driver] ?? "full";
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Get default values for a type.
|
|
885
|
+
*/
|
|
886
|
+
getDefaults(name) {
|
|
887
|
+
return this.types.get(name)?.defaults;
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Validate a property using its type's validator.
|
|
891
|
+
* Returns empty array if type not found (validation handled elsewhere).
|
|
892
|
+
*/
|
|
893
|
+
validateProperty(propertyName, property, filePath) {
|
|
894
|
+
const type = this.types.get(property.type);
|
|
895
|
+
if (!type) {
|
|
896
|
+
return [];
|
|
897
|
+
}
|
|
898
|
+
return type.validate(propertyName, property, filePath);
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Get all registered type names.
|
|
902
|
+
*/
|
|
903
|
+
getAllTypeNames() {
|
|
904
|
+
return [...this.types.keys()];
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Get count of registered types.
|
|
908
|
+
*/
|
|
909
|
+
get size() {
|
|
910
|
+
return this.types.size;
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Clear all registered types.
|
|
914
|
+
* Useful for testing.
|
|
915
|
+
*/
|
|
916
|
+
clear() {
|
|
917
|
+
this.types.clear();
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
var typeRegistry = new TypeRegistry();
|
|
921
|
+
|
|
922
|
+
// src/validation/types/primitives.ts
|
|
923
|
+
var BooleanType = {
|
|
924
|
+
name: "Boolean",
|
|
925
|
+
category: "primitive",
|
|
926
|
+
validFields: createValidFields([]),
|
|
927
|
+
dbCompatibility: fullDbCompatibility(),
|
|
928
|
+
validate() {
|
|
929
|
+
return [];
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
var primitiveTypes = [
|
|
933
|
+
BooleanType
|
|
934
|
+
];
|
|
935
|
+
|
|
936
|
+
// src/validation/types/text.ts
|
|
937
|
+
function buildLocation2(file) {
|
|
938
|
+
return { file };
|
|
939
|
+
}
|
|
940
|
+
function validateLength(propertyName, property, filePath) {
|
|
941
|
+
const { length } = property;
|
|
942
|
+
if (length !== void 0) {
|
|
943
|
+
if (typeof length !== "number" || length < 1 || length > 65535) {
|
|
944
|
+
return [
|
|
945
|
+
validationError(
|
|
946
|
+
`Property '${propertyName}' has invalid length '${length}'`,
|
|
947
|
+
buildLocation2(filePath),
|
|
948
|
+
"length must be a positive number between 1 and 65535"
|
|
949
|
+
)
|
|
950
|
+
];
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
return [];
|
|
954
|
+
}
|
|
955
|
+
var StringType = {
|
|
956
|
+
name: "String",
|
|
957
|
+
category: "text",
|
|
958
|
+
validFields: createValidFields(["length"]),
|
|
959
|
+
dbCompatibility: fullDbCompatibility(),
|
|
960
|
+
defaults: {
|
|
961
|
+
// Default length depends on database, handled by generator
|
|
962
|
+
},
|
|
963
|
+
validate(propertyName, property, filePath) {
|
|
964
|
+
return validateLength(propertyName, property, filePath);
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
var TextType = {
|
|
968
|
+
name: "Text",
|
|
969
|
+
category: "text",
|
|
970
|
+
validFields: createValidFields([]),
|
|
971
|
+
dbCompatibility: fullDbCompatibility(),
|
|
972
|
+
validate() {
|
|
973
|
+
return [];
|
|
974
|
+
}
|
|
975
|
+
};
|
|
976
|
+
var LongTextType = {
|
|
977
|
+
name: "LongText",
|
|
978
|
+
category: "text",
|
|
979
|
+
validFields: createValidFields([]),
|
|
980
|
+
dbCompatibility: {
|
|
981
|
+
mysql: "full",
|
|
982
|
+
postgres: "full",
|
|
983
|
+
sqlite: "full",
|
|
984
|
+
sqlserver: "limited"
|
|
985
|
+
// Uses NVARCHAR(MAX)
|
|
986
|
+
},
|
|
987
|
+
validate() {
|
|
988
|
+
return [];
|
|
989
|
+
}
|
|
990
|
+
};
|
|
991
|
+
var EmailType = {
|
|
992
|
+
name: "Email",
|
|
993
|
+
category: "text",
|
|
994
|
+
validFields: createValidFields(["length"]),
|
|
995
|
+
dbCompatibility: fullDbCompatibility(),
|
|
996
|
+
validate(propertyName, property, filePath) {
|
|
997
|
+
return validateLength(propertyName, property, filePath);
|
|
998
|
+
}
|
|
999
|
+
};
|
|
1000
|
+
var PasswordType = {
|
|
1001
|
+
name: "Password",
|
|
1002
|
+
category: "text",
|
|
1003
|
+
validFields: createValidFields(["length"]),
|
|
1004
|
+
dbCompatibility: fullDbCompatibility(),
|
|
1005
|
+
validate(propertyName, property, filePath) {
|
|
1006
|
+
return validateLength(propertyName, property, filePath);
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
1009
|
+
var textTypes = [
|
|
1010
|
+
StringType,
|
|
1011
|
+
TextType,
|
|
1012
|
+
LongTextType,
|
|
1013
|
+
EmailType,
|
|
1014
|
+
PasswordType
|
|
1015
|
+
];
|
|
1016
|
+
|
|
1017
|
+
// src/validation/types/numeric.ts
|
|
1018
|
+
function buildLocation3(file) {
|
|
1019
|
+
return { file };
|
|
1020
|
+
}
|
|
1021
|
+
var IntType = {
|
|
1022
|
+
name: "Int",
|
|
1023
|
+
category: "numeric",
|
|
1024
|
+
validFields: createValidFields(["unsigned"]),
|
|
1025
|
+
dbCompatibility: fullDbCompatibility(),
|
|
1026
|
+
validate() {
|
|
1027
|
+
return [];
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
var BigIntType = {
|
|
1031
|
+
name: "BigInt",
|
|
1032
|
+
category: "numeric",
|
|
1033
|
+
validFields: createValidFields(["unsigned"]),
|
|
1034
|
+
dbCompatibility: fullDbCompatibility(),
|
|
1035
|
+
validate() {
|
|
1036
|
+
return [];
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
1039
|
+
var FloatType = {
|
|
1040
|
+
name: "Float",
|
|
1041
|
+
category: "numeric",
|
|
1042
|
+
validFields: createValidFields(["unsigned"]),
|
|
1043
|
+
dbCompatibility: fullDbCompatibility(),
|
|
1044
|
+
validate() {
|
|
1045
|
+
return [];
|
|
1046
|
+
}
|
|
1047
|
+
};
|
|
1048
|
+
var DecimalType = {
|
|
1049
|
+
name: "Decimal",
|
|
1050
|
+
category: "numeric",
|
|
1051
|
+
validFields: createValidFields(["precision", "scale", "unsigned"]),
|
|
1052
|
+
dbCompatibility: {
|
|
1053
|
+
mysql: "full",
|
|
1054
|
+
postgres: "full",
|
|
1055
|
+
sqlite: "limited",
|
|
1056
|
+
// Stored as REAL, loses precision
|
|
1057
|
+
sqlserver: "full"
|
|
1058
|
+
},
|
|
1059
|
+
defaults: {
|
|
1060
|
+
// precision: 8, scale: 2 - handled by generator
|
|
1061
|
+
},
|
|
1062
|
+
validate(propertyName, property, filePath) {
|
|
1063
|
+
const errors = [];
|
|
1064
|
+
const { precision, scale } = property;
|
|
1065
|
+
if (precision !== void 0) {
|
|
1066
|
+
if (typeof precision !== "number" || precision < 1 || precision > 65) {
|
|
1067
|
+
errors.push(
|
|
1068
|
+
validationError(
|
|
1069
|
+
`Property '${propertyName}' has invalid precision '${precision}'`,
|
|
1070
|
+
buildLocation3(filePath),
|
|
1071
|
+
"precision must be a number between 1 and 65"
|
|
1072
|
+
)
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
if (scale !== void 0) {
|
|
1077
|
+
if (typeof scale !== "number" || scale < 0 || scale > 30) {
|
|
1078
|
+
errors.push(
|
|
1079
|
+
validationError(
|
|
1080
|
+
`Property '${propertyName}' has invalid scale '${scale}'`,
|
|
1081
|
+
buildLocation3(filePath),
|
|
1082
|
+
"scale must be a number between 0 and 30"
|
|
1083
|
+
)
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1086
|
+
const effectivePrecision = typeof precision === "number" ? precision : 8;
|
|
1087
|
+
if (typeof scale === "number" && scale > effectivePrecision) {
|
|
1088
|
+
errors.push(
|
|
1089
|
+
validationError(
|
|
1090
|
+
`Property '${propertyName}' has scale (${scale}) greater than precision (${effectivePrecision})`,
|
|
1091
|
+
buildLocation3(filePath),
|
|
1092
|
+
"scale cannot be greater than precision"
|
|
1093
|
+
)
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
return errors;
|
|
1098
|
+
}
|
|
1099
|
+
};
|
|
1100
|
+
var numericTypes = [
|
|
1101
|
+
IntType,
|
|
1102
|
+
BigIntType,
|
|
1103
|
+
FloatType,
|
|
1104
|
+
DecimalType
|
|
1105
|
+
];
|
|
1106
|
+
|
|
1107
|
+
// src/validation/types/temporal.ts
|
|
1108
|
+
var DateType = {
|
|
1109
|
+
name: "Date",
|
|
1110
|
+
category: "temporal",
|
|
1111
|
+
validFields: createValidFields([]),
|
|
1112
|
+
dbCompatibility: fullDbCompatibility(),
|
|
1113
|
+
validate() {
|
|
1114
|
+
return [];
|
|
1115
|
+
}
|
|
1116
|
+
};
|
|
1117
|
+
var TimeType = {
|
|
1118
|
+
name: "Time",
|
|
1119
|
+
category: "temporal",
|
|
1120
|
+
validFields: createValidFields([]),
|
|
1121
|
+
dbCompatibility: {
|
|
1122
|
+
mysql: "full",
|
|
1123
|
+
postgres: "full",
|
|
1124
|
+
sqlite: "limited",
|
|
1125
|
+
// Stored as TEXT
|
|
1126
|
+
sqlserver: "full"
|
|
1127
|
+
},
|
|
1128
|
+
validate() {
|
|
1129
|
+
return [];
|
|
1130
|
+
}
|
|
1131
|
+
};
|
|
1132
|
+
var DateTimeType = {
|
|
1133
|
+
name: "DateTime",
|
|
1134
|
+
category: "temporal",
|
|
1135
|
+
validFields: createValidFields([]),
|
|
1136
|
+
dbCompatibility: fullDbCompatibility(),
|
|
1137
|
+
validate() {
|
|
1138
|
+
return [];
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
var TimestampType = {
|
|
1142
|
+
name: "Timestamp",
|
|
1143
|
+
category: "temporal",
|
|
1144
|
+
validFields: createValidFields([]),
|
|
1145
|
+
dbCompatibility: {
|
|
1146
|
+
mysql: "full",
|
|
1147
|
+
postgres: "full",
|
|
1148
|
+
sqlite: "limited",
|
|
1149
|
+
// Stored as INTEGER or TEXT
|
|
1150
|
+
sqlserver: "full"
|
|
1151
|
+
},
|
|
1152
|
+
validate() {
|
|
1153
|
+
return [];
|
|
1154
|
+
}
|
|
1155
|
+
};
|
|
1156
|
+
var temporalTypes = [
|
|
1157
|
+
DateType,
|
|
1158
|
+
TimeType,
|
|
1159
|
+
DateTimeType,
|
|
1160
|
+
TimestampType
|
|
1161
|
+
];
|
|
1162
|
+
|
|
1163
|
+
// src/validation/types/special.ts
|
|
1164
|
+
var JsonType = {
|
|
1165
|
+
name: "Json",
|
|
1166
|
+
category: "special",
|
|
1167
|
+
validFields: createValidFields([]),
|
|
1168
|
+
dbCompatibility: {
|
|
1169
|
+
mysql: "full",
|
|
1170
|
+
// Native JSON type
|
|
1171
|
+
postgres: "full",
|
|
1172
|
+
// Native JSONB type
|
|
1173
|
+
sqlite: "limited",
|
|
1174
|
+
// Stored as TEXT
|
|
1175
|
+
sqlserver: "limited"
|
|
1176
|
+
// Stored as NVARCHAR(MAX)
|
|
1177
|
+
},
|
|
1178
|
+
validate() {
|
|
1179
|
+
return [];
|
|
1180
|
+
}
|
|
1181
|
+
};
|
|
1182
|
+
var UuidType = {
|
|
1183
|
+
name: "Uuid",
|
|
1184
|
+
category: "special",
|
|
1185
|
+
validFields: createValidFields([]),
|
|
1186
|
+
dbCompatibility: fullDbCompatibility(),
|
|
1187
|
+
validate() {
|
|
1188
|
+
return [];
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1191
|
+
var FileType = {
|
|
1192
|
+
name: "File",
|
|
1193
|
+
category: "special",
|
|
1194
|
+
validFields: createValidFields([]),
|
|
1195
|
+
dbCompatibility: fullDbCompatibility(),
|
|
1196
|
+
validate() {
|
|
1197
|
+
return [];
|
|
1198
|
+
}
|
|
1199
|
+
};
|
|
1200
|
+
var MultiFileType = {
|
|
1201
|
+
name: "MultiFile",
|
|
1202
|
+
category: "special",
|
|
1203
|
+
validFields: createValidFields([]),
|
|
1204
|
+
dbCompatibility: fullDbCompatibility(),
|
|
1205
|
+
validate() {
|
|
1206
|
+
return [];
|
|
1207
|
+
}
|
|
1208
|
+
};
|
|
1209
|
+
var specialTypes = [
|
|
1210
|
+
JsonType,
|
|
1211
|
+
UuidType,
|
|
1212
|
+
FileType,
|
|
1213
|
+
MultiFileType
|
|
1214
|
+
];
|
|
1215
|
+
|
|
1216
|
+
// src/validation/types/enum.ts
|
|
1217
|
+
function buildLocation4(file) {
|
|
1218
|
+
return { file };
|
|
1219
|
+
}
|
|
1220
|
+
var EnumType = {
|
|
1221
|
+
name: "Enum",
|
|
1222
|
+
category: "enum",
|
|
1223
|
+
validFields: createValidFields(["enum"]),
|
|
1224
|
+
requiredFields: ["enum"],
|
|
1225
|
+
dbCompatibility: fullDbCompatibility(),
|
|
1226
|
+
validate(propertyName, property, filePath) {
|
|
1227
|
+
const errors = [];
|
|
1228
|
+
const { enum: enumValues } = property;
|
|
1229
|
+
if (enumValues === void 0) {
|
|
1230
|
+
errors.push(
|
|
1231
|
+
validationError(
|
|
1232
|
+
`Property '${propertyName}' of type 'Enum' requires 'enum' field`,
|
|
1233
|
+
buildLocation4(filePath),
|
|
1234
|
+
"Add enum values: enum: [value1, value2]"
|
|
1235
|
+
)
|
|
1236
|
+
);
|
|
1237
|
+
} else if (!Array.isArray(enumValues)) {
|
|
1238
|
+
errors.push(
|
|
1239
|
+
validationError(
|
|
1240
|
+
`Property '${propertyName}' enum field must be an array`,
|
|
1241
|
+
buildLocation4(filePath),
|
|
1242
|
+
"enum: [value1, value2]"
|
|
1243
|
+
)
|
|
1244
|
+
);
|
|
1245
|
+
} else if (enumValues.length === 0) {
|
|
1246
|
+
errors.push(
|
|
1247
|
+
validationError(
|
|
1248
|
+
`Property '${propertyName}' enum field cannot be empty`,
|
|
1249
|
+
buildLocation4(filePath),
|
|
1250
|
+
"Add at least one enum value"
|
|
1251
|
+
)
|
|
1252
|
+
);
|
|
1253
|
+
} else {
|
|
1254
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1255
|
+
for (const value of enumValues) {
|
|
1256
|
+
if (typeof value !== "string") {
|
|
1257
|
+
errors.push(
|
|
1258
|
+
validationError(
|
|
1259
|
+
`Property '${propertyName}' enum values must be strings, got ${typeof value}`,
|
|
1260
|
+
buildLocation4(filePath)
|
|
1261
|
+
)
|
|
1262
|
+
);
|
|
1263
|
+
} else if (seen.has(value)) {
|
|
1264
|
+
errors.push(
|
|
1265
|
+
validationError(
|
|
1266
|
+
`Property '${propertyName}' has duplicate enum value '${value}'`,
|
|
1267
|
+
buildLocation4(filePath)
|
|
1268
|
+
)
|
|
1269
|
+
);
|
|
1270
|
+
} else {
|
|
1271
|
+
seen.add(value);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
return errors;
|
|
1276
|
+
}
|
|
1277
|
+
};
|
|
1278
|
+
var SelectType = {
|
|
1279
|
+
name: "Select",
|
|
1280
|
+
category: "enum",
|
|
1281
|
+
validFields: createValidFields(["options", "source"]),
|
|
1282
|
+
dbCompatibility: fullDbCompatibility(),
|
|
1283
|
+
validate(propertyName, property, filePath) {
|
|
1284
|
+
const errors = [];
|
|
1285
|
+
const { options, source } = property;
|
|
1286
|
+
if (options === void 0 && source === void 0) {
|
|
1287
|
+
errors.push(
|
|
1288
|
+
validationError(
|
|
1289
|
+
`Property '${propertyName}' of type 'Select' requires 'options' or 'source' field`,
|
|
1290
|
+
buildLocation4(filePath),
|
|
1291
|
+
"Add options: [value1, value2] or source: SchemaName"
|
|
1292
|
+
)
|
|
1293
|
+
);
|
|
1294
|
+
}
|
|
1295
|
+
if (options !== void 0 && !Array.isArray(options)) {
|
|
1296
|
+
errors.push(
|
|
1297
|
+
validationError(
|
|
1298
|
+
`Property '${propertyName}' options field must be an array`,
|
|
1299
|
+
buildLocation4(filePath)
|
|
1300
|
+
)
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
|
+
if (source !== void 0 && typeof source !== "string") {
|
|
1304
|
+
errors.push(
|
|
1305
|
+
validationError(
|
|
1306
|
+
`Property '${propertyName}' source field must be a string`,
|
|
1307
|
+
buildLocation4(filePath)
|
|
1308
|
+
)
|
|
1309
|
+
);
|
|
1310
|
+
}
|
|
1311
|
+
return errors;
|
|
1312
|
+
}
|
|
1313
|
+
};
|
|
1314
|
+
var LookupType = {
|
|
1315
|
+
name: "Lookup",
|
|
1316
|
+
category: "enum",
|
|
1317
|
+
validFields: createValidFields(["options", "source"]),
|
|
1318
|
+
dbCompatibility: fullDbCompatibility(),
|
|
1319
|
+
validate(propertyName, property, filePath) {
|
|
1320
|
+
const errors = [];
|
|
1321
|
+
const { source } = property;
|
|
1322
|
+
if (source === void 0) {
|
|
1323
|
+
errors.push(
|
|
1324
|
+
validationError(
|
|
1325
|
+
`Property '${propertyName}' of type 'Lookup' requires 'source' field`,
|
|
1326
|
+
buildLocation4(filePath),
|
|
1327
|
+
"Add source: SchemaName"
|
|
1328
|
+
)
|
|
1329
|
+
);
|
|
1330
|
+
} else if (typeof source !== "string") {
|
|
1331
|
+
errors.push(
|
|
1332
|
+
validationError(
|
|
1333
|
+
`Property '${propertyName}' source field must be a string`,
|
|
1334
|
+
buildLocation4(filePath)
|
|
1335
|
+
)
|
|
1336
|
+
);
|
|
1337
|
+
}
|
|
1338
|
+
return errors;
|
|
1339
|
+
}
|
|
1340
|
+
};
|
|
1341
|
+
var enumTypes = [
|
|
1342
|
+
EnumType,
|
|
1343
|
+
SelectType,
|
|
1344
|
+
LookupType
|
|
835
1345
|
];
|
|
1346
|
+
|
|
1347
|
+
// src/validation/types/relations.ts
|
|
1348
|
+
function buildLocation5(file) {
|
|
1349
|
+
return { file };
|
|
1350
|
+
}
|
|
836
1351
|
var VALID_RELATIONS = [
|
|
837
1352
|
"OneToOne",
|
|
838
1353
|
"OneToMany",
|
|
@@ -846,8 +1361,150 @@ var VALID_REFERENTIAL_ACTIONS = [
|
|
|
846
1361
|
"RESTRICT",
|
|
847
1362
|
"NO ACTION"
|
|
848
1363
|
];
|
|
1364
|
+
var ASSOCIATION_FIELDS = [
|
|
1365
|
+
"relation",
|
|
1366
|
+
"target",
|
|
1367
|
+
"inversedBy",
|
|
1368
|
+
"mappedBy",
|
|
1369
|
+
"onDelete",
|
|
1370
|
+
"onUpdate",
|
|
1371
|
+
"owning",
|
|
1372
|
+
"joinTable"
|
|
1373
|
+
];
|
|
1374
|
+
var AssociationType = {
|
|
1375
|
+
name: "Association",
|
|
1376
|
+
category: "relation",
|
|
1377
|
+
validFields: createValidFields(ASSOCIATION_FIELDS),
|
|
1378
|
+
requiredFields: ["relation", "target"],
|
|
1379
|
+
dbCompatibility: fullDbCompatibility(),
|
|
1380
|
+
validate(propertyName, property, filePath) {
|
|
1381
|
+
const errors = [];
|
|
1382
|
+
const {
|
|
1383
|
+
relation,
|
|
1384
|
+
target,
|
|
1385
|
+
onDelete,
|
|
1386
|
+
onUpdate
|
|
1387
|
+
} = property;
|
|
1388
|
+
if (relation === void 0) {
|
|
1389
|
+
errors.push(
|
|
1390
|
+
validationError(
|
|
1391
|
+
`Property '${propertyName}' of type 'Association' requires 'relation' field`,
|
|
1392
|
+
buildLocation5(filePath),
|
|
1393
|
+
"Add relation: OneToOne, OneToMany, ManyToOne, or ManyToMany"
|
|
1394
|
+
)
|
|
1395
|
+
);
|
|
1396
|
+
} else if (typeof relation !== "string") {
|
|
1397
|
+
errors.push(
|
|
1398
|
+
validationError(
|
|
1399
|
+
`Property '${propertyName}' relation field must be a string`,
|
|
1400
|
+
buildLocation5(filePath)
|
|
1401
|
+
)
|
|
1402
|
+
);
|
|
1403
|
+
} else if (!VALID_RELATIONS.includes(relation)) {
|
|
1404
|
+
errors.push(
|
|
1405
|
+
validationError(
|
|
1406
|
+
`Property '${propertyName}' has invalid relation '${relation}'`,
|
|
1407
|
+
buildLocation5(filePath),
|
|
1408
|
+
`Use one of: ${VALID_RELATIONS.join(", ")}`
|
|
1409
|
+
)
|
|
1410
|
+
);
|
|
1411
|
+
}
|
|
1412
|
+
if (target === void 0) {
|
|
1413
|
+
errors.push(
|
|
1414
|
+
validationError(
|
|
1415
|
+
`Property '${propertyName}' of type 'Association' requires 'target' field`,
|
|
1416
|
+
buildLocation5(filePath),
|
|
1417
|
+
"Add target schema name: target: User"
|
|
1418
|
+
)
|
|
1419
|
+
);
|
|
1420
|
+
} else if (typeof target !== "string") {
|
|
1421
|
+
errors.push(
|
|
1422
|
+
validationError(
|
|
1423
|
+
`Property '${propertyName}' target field must be a string`,
|
|
1424
|
+
buildLocation5(filePath)
|
|
1425
|
+
)
|
|
1426
|
+
);
|
|
1427
|
+
}
|
|
1428
|
+
if (onDelete !== void 0 && typeof onDelete === "string") {
|
|
1429
|
+
if (!VALID_REFERENTIAL_ACTIONS.includes(onDelete)) {
|
|
1430
|
+
errors.push(
|
|
1431
|
+
validationError(
|
|
1432
|
+
`Property '${propertyName}' has invalid onDelete action '${onDelete}'`,
|
|
1433
|
+
buildLocation5(filePath),
|
|
1434
|
+
`Use one of: ${VALID_REFERENTIAL_ACTIONS.join(", ")}`
|
|
1435
|
+
)
|
|
1436
|
+
);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
if (onUpdate !== void 0 && typeof onUpdate === "string") {
|
|
1440
|
+
if (!VALID_REFERENTIAL_ACTIONS.includes(onUpdate)) {
|
|
1441
|
+
errors.push(
|
|
1442
|
+
validationError(
|
|
1443
|
+
`Property '${propertyName}' has invalid onUpdate action '${onUpdate}'`,
|
|
1444
|
+
buildLocation5(filePath),
|
|
1445
|
+
`Use one of: ${VALID_REFERENTIAL_ACTIONS.join(", ")}`
|
|
1446
|
+
)
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
return errors;
|
|
1451
|
+
}
|
|
1452
|
+
};
|
|
1453
|
+
var PolymorphicType = {
|
|
1454
|
+
name: "Polymorphic",
|
|
1455
|
+
category: "relation",
|
|
1456
|
+
validFields: createValidFields(ASSOCIATION_FIELDS),
|
|
1457
|
+
dbCompatibility: fullDbCompatibility(),
|
|
1458
|
+
validate(propertyName, property, filePath) {
|
|
1459
|
+
const errors = [];
|
|
1460
|
+
const { relation, target } = property;
|
|
1461
|
+
if (relation === void 0) {
|
|
1462
|
+
errors.push(
|
|
1463
|
+
validationError(
|
|
1464
|
+
`Property '${propertyName}' of type 'Polymorphic' requires 'relation' field`,
|
|
1465
|
+
buildLocation5(filePath),
|
|
1466
|
+
"Add relation type"
|
|
1467
|
+
)
|
|
1468
|
+
);
|
|
1469
|
+
}
|
|
1470
|
+
if (target !== void 0 && typeof target !== "string") {
|
|
1471
|
+
errors.push(
|
|
1472
|
+
validationError(
|
|
1473
|
+
`Property '${propertyName}' target field must be a string`,
|
|
1474
|
+
buildLocation5(filePath)
|
|
1475
|
+
)
|
|
1476
|
+
);
|
|
1477
|
+
}
|
|
1478
|
+
return errors;
|
|
1479
|
+
}
|
|
1480
|
+
};
|
|
1481
|
+
var relationTypes = [
|
|
1482
|
+
AssociationType,
|
|
1483
|
+
PolymorphicType
|
|
1484
|
+
];
|
|
1485
|
+
|
|
1486
|
+
// src/validation/types/index.ts
|
|
1487
|
+
var allBuiltInTypes = [
|
|
1488
|
+
...primitiveTypes,
|
|
1489
|
+
...textTypes,
|
|
1490
|
+
...numericTypes,
|
|
1491
|
+
...temporalTypes,
|
|
1492
|
+
...specialTypes,
|
|
1493
|
+
...enumTypes,
|
|
1494
|
+
...relationTypes
|
|
1495
|
+
];
|
|
1496
|
+
function registerBuiltInTypes() {
|
|
1497
|
+
for (const type of allBuiltInTypes) {
|
|
1498
|
+
if (!typeRegistry.has(type.name)) {
|
|
1499
|
+
typeRegistry.register(type);
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
registerBuiltInTypes();
|
|
1504
|
+
|
|
1505
|
+
// src/validation/validator.ts
|
|
849
1506
|
var VALID_PRIMARY_KEY_TYPES = ["Int", "BigInt", "Uuid", "String"];
|
|
850
|
-
function
|
|
1507
|
+
function buildLocation6(file, line, column) {
|
|
851
1508
|
const loc = { file };
|
|
852
1509
|
if (line !== void 0) {
|
|
853
1510
|
loc.line = line;
|
|
@@ -857,56 +1514,75 @@ function buildLocation2(file, line, column) {
|
|
|
857
1514
|
}
|
|
858
1515
|
return loc;
|
|
859
1516
|
}
|
|
1517
|
+
function validateDbCompatibility(propertyName, property, filePath, databaseDriver) {
|
|
1518
|
+
const errors = [];
|
|
1519
|
+
const warnings = [];
|
|
1520
|
+
if (!databaseDriver || !property.type) {
|
|
1521
|
+
return { errors, warnings };
|
|
1522
|
+
}
|
|
1523
|
+
const compatibility = typeRegistry.getDbCompatibility(property.type, databaseDriver);
|
|
1524
|
+
if (compatibility === "unsupported") {
|
|
1525
|
+
errors.push(
|
|
1526
|
+
validationError(
|
|
1527
|
+
`Property '${propertyName}' uses type '${property.type}' which is not supported in ${databaseDriver}`,
|
|
1528
|
+
buildLocation6(filePath),
|
|
1529
|
+
`Consider using a different type or database driver`
|
|
1530
|
+
)
|
|
1531
|
+
);
|
|
1532
|
+
} else if (compatibility === "limited") {
|
|
1533
|
+
warnings.push(
|
|
1534
|
+
validationError(
|
|
1535
|
+
`Property '${propertyName}' uses type '${property.type}' which has limited support in ${databaseDriver}`,
|
|
1536
|
+
buildLocation6(filePath),
|
|
1537
|
+
`The type will work but may have precision or feature limitations`
|
|
1538
|
+
)
|
|
1539
|
+
);
|
|
1540
|
+
}
|
|
1541
|
+
return { errors, warnings };
|
|
1542
|
+
}
|
|
1543
|
+
function validateUnknownFields(propertyName, property, filePath) {
|
|
1544
|
+
const warnings = [];
|
|
1545
|
+
if (!property.type) {
|
|
1546
|
+
return warnings;
|
|
1547
|
+
}
|
|
1548
|
+
const validFields = typeRegistry.getValidFields(property.type);
|
|
1549
|
+
const propertyFields = Object.keys(property);
|
|
1550
|
+
for (const field of propertyFields) {
|
|
1551
|
+
if (!validFields.includes(field)) {
|
|
1552
|
+
warnings.push(
|
|
1553
|
+
validationError(
|
|
1554
|
+
`Property '${propertyName}' has unknown field '${field}'`,
|
|
1555
|
+
buildLocation6(filePath),
|
|
1556
|
+
`Valid fields for type '${property.type}': ${validFields.join(", ")}`
|
|
1557
|
+
)
|
|
1558
|
+
);
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
return warnings;
|
|
1562
|
+
}
|
|
860
1563
|
function validatePropertyType(propertyName, property, filePath, customTypes = []) {
|
|
861
1564
|
const errors = [];
|
|
862
|
-
const validTypes = [...BUILT_IN_TYPES, ...customTypes];
|
|
863
1565
|
if (!property.type) {
|
|
864
1566
|
errors.push(
|
|
865
|
-
missingFieldError("type",
|
|
1567
|
+
missingFieldError("type", buildLocation6(filePath))
|
|
866
1568
|
);
|
|
867
1569
|
return errors;
|
|
868
1570
|
}
|
|
869
|
-
|
|
1571
|
+
const isBuiltIn = typeRegistry.has(property.type);
|
|
1572
|
+
const isCustom = customTypes.includes(property.type);
|
|
1573
|
+
if (!isBuiltIn && !isCustom) {
|
|
870
1574
|
errors.push(
|
|
871
1575
|
invalidPropertyTypeError(
|
|
872
1576
|
property.type,
|
|
873
|
-
|
|
874
|
-
|
|
1577
|
+
buildLocation6(filePath),
|
|
1578
|
+
typeRegistry.getAllTypeNames()
|
|
875
1579
|
)
|
|
876
1580
|
);
|
|
1581
|
+
return errors;
|
|
877
1582
|
}
|
|
878
|
-
if (
|
|
879
|
-
const
|
|
880
|
-
|
|
881
|
-
errors.push(
|
|
882
|
-
validationError(
|
|
883
|
-
`Property '${propertyName}' of type 'Enum' requires 'enum' field`,
|
|
884
|
-
buildLocation2(filePath),
|
|
885
|
-
"Add enum values: enum: [value1, value2]"
|
|
886
|
-
)
|
|
887
|
-
);
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
if (property.type === "Association") {
|
|
891
|
-
const assocProp = property;
|
|
892
|
-
if (assocProp.relation === void 0) {
|
|
893
|
-
errors.push(
|
|
894
|
-
validationError(
|
|
895
|
-
`Property '${propertyName}' of type 'Association' requires 'relation' field`,
|
|
896
|
-
buildLocation2(filePath),
|
|
897
|
-
"Add relation: OneToOne, OneToMany, ManyToOne, or ManyToMany"
|
|
898
|
-
)
|
|
899
|
-
);
|
|
900
|
-
}
|
|
901
|
-
if (assocProp.target === void 0) {
|
|
902
|
-
errors.push(
|
|
903
|
-
validationError(
|
|
904
|
-
`Property '${propertyName}' of type 'Association' requires 'target' field`,
|
|
905
|
-
buildLocation2(filePath),
|
|
906
|
-
"Add target schema name: target: User"
|
|
907
|
-
)
|
|
908
|
-
);
|
|
909
|
-
}
|
|
1583
|
+
if (isBuiltIn) {
|
|
1584
|
+
const typeErrors = typeRegistry.validateProperty(propertyName, property, filePath);
|
|
1585
|
+
errors.push(...typeErrors);
|
|
910
1586
|
}
|
|
911
1587
|
return errors;
|
|
912
1588
|
}
|
|
@@ -938,7 +1614,7 @@ function validateAssociations(schema, allSchemas) {
|
|
|
938
1614
|
errors.push(
|
|
939
1615
|
validationError(
|
|
940
1616
|
`Property '${name}' has invalid relation '${assocProp.relation}'`,
|
|
941
|
-
|
|
1617
|
+
buildLocation6(schema.filePath),
|
|
942
1618
|
`Use one of: ${VALID_RELATIONS.join(", ")}`
|
|
943
1619
|
)
|
|
944
1620
|
);
|
|
@@ -947,7 +1623,7 @@ function validateAssociations(schema, allSchemas) {
|
|
|
947
1623
|
errors.push(
|
|
948
1624
|
invalidAssociationTargetError(
|
|
949
1625
|
assocProp.target,
|
|
950
|
-
|
|
1626
|
+
buildLocation6(schema.filePath),
|
|
951
1627
|
availableSchemas
|
|
952
1628
|
)
|
|
953
1629
|
);
|
|
@@ -960,7 +1636,7 @@ function validateAssociations(schema, allSchemas) {
|
|
|
960
1636
|
errors.push(
|
|
961
1637
|
validationError(
|
|
962
1638
|
`Property '${name}' references non-existent inverse property '${assocProp.inversedBy}' on '${assocProp.target}'`,
|
|
963
|
-
|
|
1639
|
+
buildLocation6(schema.filePath),
|
|
964
1640
|
`Add property '${assocProp.inversedBy}' to ${assocProp.target} schema`
|
|
965
1641
|
)
|
|
966
1642
|
);
|
|
@@ -971,7 +1647,7 @@ function validateAssociations(schema, allSchemas) {
|
|
|
971
1647
|
errors.push(
|
|
972
1648
|
validationError(
|
|
973
1649
|
`Property '${name}' has invalid onDelete action '${assocProp.onDelete}'`,
|
|
974
|
-
|
|
1650
|
+
buildLocation6(schema.filePath),
|
|
975
1651
|
`Use one of: ${VALID_REFERENTIAL_ACTIONS.join(", ")}`
|
|
976
1652
|
)
|
|
977
1653
|
);
|
|
@@ -980,7 +1656,7 @@ function validateAssociations(schema, allSchemas) {
|
|
|
980
1656
|
errors.push(
|
|
981
1657
|
validationError(
|
|
982
1658
|
`Property '${name}' has invalid onUpdate action '${assocProp.onUpdate}'`,
|
|
983
|
-
|
|
1659
|
+
buildLocation6(schema.filePath),
|
|
984
1660
|
`Use one of: ${VALID_REFERENTIAL_ACTIONS.join(", ")}`
|
|
985
1661
|
)
|
|
986
1662
|
);
|
|
@@ -999,7 +1675,7 @@ function validateOptions(schema, filePath) {
|
|
|
999
1675
|
errors.push(
|
|
1000
1676
|
validationError(
|
|
1001
1677
|
`Invalid primaryKeyType '${options.primaryKeyType}'`,
|
|
1002
|
-
|
|
1678
|
+
buildLocation6(filePath),
|
|
1003
1679
|
`Use one of: ${VALID_PRIMARY_KEY_TYPES.join(", ")}`
|
|
1004
1680
|
)
|
|
1005
1681
|
);
|
|
@@ -1014,7 +1690,7 @@ function validateOptions(schema, filePath) {
|
|
|
1014
1690
|
errors.push(
|
|
1015
1691
|
validationError(
|
|
1016
1692
|
`Unique constraint references non-existent property '${column}'`,
|
|
1017
|
-
|
|
1693
|
+
buildLocation6(filePath),
|
|
1018
1694
|
`Available properties: ${propertyNames.join(", ")}`
|
|
1019
1695
|
)
|
|
1020
1696
|
);
|
|
@@ -1030,7 +1706,7 @@ function validateOptions(schema, filePath) {
|
|
|
1030
1706
|
errors.push(
|
|
1031
1707
|
validationError(
|
|
1032
1708
|
`Index references non-existent property '${column}'`,
|
|
1033
|
-
|
|
1709
|
+
buildLocation6(filePath),
|
|
1034
1710
|
`Available properties: ${propertyNames.join(", ")}`
|
|
1035
1711
|
)
|
|
1036
1712
|
);
|
|
@@ -1044,7 +1720,7 @@ function validateOptions(schema, filePath) {
|
|
|
1044
1720
|
errors.push(
|
|
1045
1721
|
validationError(
|
|
1046
1722
|
`authenticatableLoginIdField references non-existent property '${options.authenticatableLoginIdField}'`,
|
|
1047
|
-
|
|
1723
|
+
buildLocation6(filePath)
|
|
1048
1724
|
)
|
|
1049
1725
|
);
|
|
1050
1726
|
}
|
|
@@ -1054,7 +1730,7 @@ function validateOptions(schema, filePath) {
|
|
|
1054
1730
|
errors.push(
|
|
1055
1731
|
validationError(
|
|
1056
1732
|
`authenticatablePasswordField references non-existent property '${options.authenticatablePasswordField}'`,
|
|
1057
|
-
|
|
1733
|
+
buildLocation6(filePath)
|
|
1058
1734
|
)
|
|
1059
1735
|
);
|
|
1060
1736
|
}
|
|
@@ -1071,7 +1747,7 @@ function validateEnumSchema(schema, filePath) {
|
|
|
1071
1747
|
errors.push(
|
|
1072
1748
|
validationError(
|
|
1073
1749
|
"Enum schema requires at least one value",
|
|
1074
|
-
|
|
1750
|
+
buildLocation6(filePath),
|
|
1075
1751
|
"Add values: [value1, value2, ...]"
|
|
1076
1752
|
)
|
|
1077
1753
|
);
|
|
@@ -1083,7 +1759,7 @@ function validateEnumSchema(schema, filePath) {
|
|
|
1083
1759
|
errors.push(
|
|
1084
1760
|
validationError(
|
|
1085
1761
|
`Duplicate enum value '${value}'`,
|
|
1086
|
-
|
|
1762
|
+
buildLocation6(filePath)
|
|
1087
1763
|
)
|
|
1088
1764
|
);
|
|
1089
1765
|
}
|
|
@@ -1109,12 +1785,21 @@ function validateSchema(schema, options = {}) {
|
|
|
1109
1785
|
errors.push(
|
|
1110
1786
|
validationError(
|
|
1111
1787
|
`titleIndex references non-existent property '${schema.titleIndex}'`,
|
|
1112
|
-
|
|
1788
|
+
buildLocation6(schema.filePath),
|
|
1113
1789
|
`Available properties: ${Object.keys(schema.properties).join(", ")}`
|
|
1114
1790
|
)
|
|
1115
1791
|
);
|
|
1116
1792
|
}
|
|
1117
1793
|
}
|
|
1794
|
+
if (schema.properties) {
|
|
1795
|
+
for (const [name, property] of Object.entries(schema.properties)) {
|
|
1796
|
+
const unknownFieldWarnings = validateUnknownFields(name, property, schema.filePath);
|
|
1797
|
+
warnings.push(...unknownFieldWarnings);
|
|
1798
|
+
const dbCompat = validateDbCompatibility(name, property, schema.filePath, options.databaseDriver);
|
|
1799
|
+
errors.push(...dbCompat.errors);
|
|
1800
|
+
warnings.push(...dbCompat.warnings);
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1118
1803
|
return {
|
|
1119
1804
|
schemaName: schema.name,
|
|
1120
1805
|
valid: errors.length === 0,
|