@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 CHANGED
@@ -810,29 +810,544 @@ async function loadSchemas(directoryPath, options = {}) {
810
810
  return schemas;
811
811
  }
812
812
 
813
- // src/validation/validator.ts
814
- var BUILT_IN_TYPES = [
815
- "String",
816
- "Int",
817
- "BigInt",
818
- "Float",
819
- "Boolean",
820
- "Text",
821
- "LongText",
822
- "Date",
823
- "Time",
824
- "Timestamp",
825
- "Json",
826
- "Email",
827
- "Password",
828
- "File",
829
- "MultiFile",
830
- "Enum",
831
- "Select",
832
- "Lookup",
833
- "Association",
834
- "Polymorphic"
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 buildLocation2(file, line, column) {
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", buildLocation2(filePath))
1567
+ missingFieldError("type", buildLocation6(filePath))
866
1568
  );
867
1569
  return errors;
868
1570
  }
869
- if (!validTypes.includes(property.type)) {
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
- buildLocation2(filePath),
874
- BUILT_IN_TYPES
1577
+ buildLocation6(filePath),
1578
+ typeRegistry.getAllTypeNames()
875
1579
  )
876
1580
  );
1581
+ return errors;
877
1582
  }
878
- if (property.type === "Enum") {
879
- const enumProp = property;
880
- if (enumProp.enum === void 0) {
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
- buildLocation2(schema.filePath),
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
- buildLocation2(schema.filePath),
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
- buildLocation2(schema.filePath),
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
- buildLocation2(schema.filePath),
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
- buildLocation2(schema.filePath),
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
- buildLocation2(filePath),
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
- buildLocation2(filePath),
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
- buildLocation2(filePath),
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
- buildLocation2(filePath)
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
- buildLocation2(filePath)
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
- buildLocation2(filePath),
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
- buildLocation2(filePath)
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
- buildLocation2(schema.filePath),
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,