@famgia/omnify-core 0.0.10 → 0.0.12
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 +851 -161
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +67 -2
- package/dist/index.d.ts +67 -2
- package/dist/index.js +847 -164
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.cjs
CHANGED
|
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
FILE_SCHEMA_NAME: () => FILE_SCHEMA_NAME,
|
|
33
34
|
Omnify: () => Omnify,
|
|
34
35
|
OmnifyError: () => OmnifyError,
|
|
35
36
|
PluginManager: () => PluginManager,
|
|
@@ -39,10 +40,13 @@ __export(index_exports, {
|
|
|
39
40
|
circularReferenceError: () => circularReferenceError,
|
|
40
41
|
configError: () => configError,
|
|
41
42
|
configNotFoundError: () => configNotFoundError,
|
|
43
|
+
createFileLoadedSchema: () => createFileLoadedSchema,
|
|
44
|
+
createFileSchemaDefinition: () => createFileSchemaDefinition,
|
|
42
45
|
createOmnify: () => createOmnify,
|
|
43
46
|
createPluginManager: () => createPluginManager,
|
|
44
47
|
createVersionStore: () => createVersionStore,
|
|
45
48
|
duplicateSchemaError: () => duplicateSchemaError,
|
|
49
|
+
ensureFileSchema: () => ensureFileSchema,
|
|
46
50
|
err: () => import_omnify_types.err,
|
|
47
51
|
expandProperty: () => expandProperty,
|
|
48
52
|
expandSchema: () => expandSchema,
|
|
@@ -54,6 +58,7 @@ __export(index_exports, {
|
|
|
54
58
|
formatError: () => formatError,
|
|
55
59
|
formatErrorPlain: () => formatErrorPlain,
|
|
56
60
|
formatErrorSummary: () => formatErrorSummary,
|
|
61
|
+
generateFileSchemaYaml: () => generateFileSchemaYaml,
|
|
57
62
|
generationError: () => generationError,
|
|
58
63
|
getAssociationMetadata: () => getAssociationMetadata,
|
|
59
64
|
getCustomTypeNames: () => getCustomTypeNames,
|
|
@@ -92,7 +97,9 @@ __export(index_exports, {
|
|
|
92
97
|
pluginTypeConflictError: () => pluginTypeConflictError,
|
|
93
98
|
schemaNotFoundError: () => schemaNotFoundError,
|
|
94
99
|
schemaParseError: () => schemaParseError,
|
|
100
|
+
schemasHaveFileProperties: () => schemasHaveFileProperties,
|
|
95
101
|
validateAssociations: () => validateAssociations,
|
|
102
|
+
validateDefaultValue: () => validateDefaultValue,
|
|
96
103
|
validateEnumSchema: () => validateEnumSchema,
|
|
97
104
|
validateOptions: () => validateOptions,
|
|
98
105
|
validateProperties: () => validateProperties,
|
|
@@ -595,8 +602,40 @@ function parseJsonSchema(content, filePath) {
|
|
|
595
602
|
throw error;
|
|
596
603
|
}
|
|
597
604
|
}
|
|
605
|
+
var VALID_SCHEMA_FIELDS = /* @__PURE__ */ new Set([
|
|
606
|
+
"kind",
|
|
607
|
+
"displayName",
|
|
608
|
+
"titleIndex",
|
|
609
|
+
"group",
|
|
610
|
+
"options",
|
|
611
|
+
"properties",
|
|
612
|
+
"values"
|
|
613
|
+
]);
|
|
614
|
+
var VALID_SCHEMA_OPTIONS_FIELDS = /* @__PURE__ */ new Set([
|
|
615
|
+
"id",
|
|
616
|
+
"idType",
|
|
617
|
+
"timestamps",
|
|
618
|
+
"softDelete",
|
|
619
|
+
"unique",
|
|
620
|
+
"indexes",
|
|
621
|
+
"translations",
|
|
622
|
+
"tableName",
|
|
623
|
+
"authenticatable",
|
|
624
|
+
"authenticatableLoginIdField",
|
|
625
|
+
"authenticatablePasswordField",
|
|
626
|
+
"authenticatableGuardName"
|
|
627
|
+
]);
|
|
598
628
|
function buildSchemaDefinition(data) {
|
|
599
629
|
const schema = {};
|
|
630
|
+
const unknownFields = [];
|
|
631
|
+
for (const key of Object.keys(data)) {
|
|
632
|
+
if (!VALID_SCHEMA_FIELDS.has(key)) {
|
|
633
|
+
unknownFields.push(key);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
if (unknownFields.length > 0) {
|
|
637
|
+
schema._unknownFields = unknownFields;
|
|
638
|
+
}
|
|
600
639
|
if (data.kind !== void 0) {
|
|
601
640
|
schema.kind = data.kind;
|
|
602
641
|
}
|
|
@@ -610,7 +649,17 @@ function buildSchemaDefinition(data) {
|
|
|
610
649
|
schema.group = data.group;
|
|
611
650
|
}
|
|
612
651
|
if (data.options !== void 0 && typeof data.options === "object") {
|
|
613
|
-
const
|
|
652
|
+
const optionsData = data.options;
|
|
653
|
+
const unknownOptionsFields = [];
|
|
654
|
+
for (const key of Object.keys(optionsData)) {
|
|
655
|
+
if (!VALID_SCHEMA_OPTIONS_FIELDS.has(key)) {
|
|
656
|
+
unknownOptionsFields.push(key);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
if (unknownOptionsFields.length > 0) {
|
|
660
|
+
schema._unknownOptionsFields = unknownOptionsFields;
|
|
661
|
+
}
|
|
662
|
+
const options = buildSchemaOptions(optionsData);
|
|
614
663
|
if (Object.keys(options).length > 0) {
|
|
615
664
|
schema.options = options;
|
|
616
665
|
}
|
|
@@ -675,10 +724,53 @@ function buildProperties(data) {
|
|
|
675
724
|
}
|
|
676
725
|
return properties;
|
|
677
726
|
}
|
|
727
|
+
var VALID_PROPERTY_FIELDS = /* @__PURE__ */ new Set([
|
|
728
|
+
"type",
|
|
729
|
+
"displayName",
|
|
730
|
+
"nullable",
|
|
731
|
+
"default",
|
|
732
|
+
"unique",
|
|
733
|
+
"description",
|
|
734
|
+
"renamedFrom",
|
|
735
|
+
// String properties
|
|
736
|
+
"length",
|
|
737
|
+
// Numeric properties
|
|
738
|
+
"unsigned",
|
|
739
|
+
"precision",
|
|
740
|
+
"scale",
|
|
741
|
+
// Enum properties
|
|
742
|
+
"enum",
|
|
743
|
+
// Association properties
|
|
744
|
+
"relation",
|
|
745
|
+
"target",
|
|
746
|
+
"targets",
|
|
747
|
+
"morphName",
|
|
748
|
+
"inversedBy",
|
|
749
|
+
"mappedBy",
|
|
750
|
+
"onDelete",
|
|
751
|
+
"onUpdate",
|
|
752
|
+
"owning",
|
|
753
|
+
"joinTable",
|
|
754
|
+
"pivotFields",
|
|
755
|
+
// File properties
|
|
756
|
+
"multiple",
|
|
757
|
+
"maxFiles",
|
|
758
|
+
"accept",
|
|
759
|
+
"maxSize"
|
|
760
|
+
]);
|
|
678
761
|
function buildPropertyDefinition(data) {
|
|
679
762
|
const prop = {
|
|
680
763
|
type: data.type || "String"
|
|
681
764
|
};
|
|
765
|
+
const unknownFields = [];
|
|
766
|
+
for (const key of Object.keys(data)) {
|
|
767
|
+
if (!VALID_PROPERTY_FIELDS.has(key)) {
|
|
768
|
+
unknownFields.push(key);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
if (unknownFields.length > 0) {
|
|
772
|
+
prop._unknownFields = unknownFields;
|
|
773
|
+
}
|
|
682
774
|
if (data.displayName !== void 0 && typeof data.displayName === "string") {
|
|
683
775
|
prop.displayName = data.displayName;
|
|
684
776
|
}
|
|
@@ -712,12 +804,6 @@ function buildPropertyDefinition(data) {
|
|
|
712
804
|
if (data.enum !== void 0) {
|
|
713
805
|
prop.enum = data.enum;
|
|
714
806
|
}
|
|
715
|
-
if (data.options !== void 0 && Array.isArray(data.options)) {
|
|
716
|
-
prop.options = data.options;
|
|
717
|
-
}
|
|
718
|
-
if (data.source !== void 0 && typeof data.source === "string") {
|
|
719
|
-
prop.source = data.source;
|
|
720
|
-
}
|
|
721
807
|
if (data.relation !== void 0 && typeof data.relation === "string") {
|
|
722
808
|
prop.relation = data.relation;
|
|
723
809
|
}
|
|
@@ -742,6 +828,27 @@ function buildPropertyDefinition(data) {
|
|
|
742
828
|
if (data.joinTable !== void 0 && typeof data.joinTable === "string") {
|
|
743
829
|
prop.joinTable = data.joinTable;
|
|
744
830
|
}
|
|
831
|
+
if (data.targets !== void 0 && Array.isArray(data.targets)) {
|
|
832
|
+
prop.targets = data.targets;
|
|
833
|
+
}
|
|
834
|
+
if (data.morphName !== void 0 && typeof data.morphName === "string") {
|
|
835
|
+
prop.morphName = data.morphName;
|
|
836
|
+
}
|
|
837
|
+
if (data.pivotFields !== void 0 && typeof data.pivotFields === "object") {
|
|
838
|
+
prop.pivotFields = data.pivotFields;
|
|
839
|
+
}
|
|
840
|
+
if (data.multiple !== void 0 && typeof data.multiple === "boolean") {
|
|
841
|
+
prop.multiple = data.multiple;
|
|
842
|
+
}
|
|
843
|
+
if (data.maxFiles !== void 0 && typeof data.maxFiles === "number") {
|
|
844
|
+
prop.maxFiles = data.maxFiles;
|
|
845
|
+
}
|
|
846
|
+
if (data.accept !== void 0 && Array.isArray(data.accept)) {
|
|
847
|
+
prop.accept = data.accept;
|
|
848
|
+
}
|
|
849
|
+
if (data.maxSize !== void 0 && typeof data.maxSize === "number") {
|
|
850
|
+
prop.maxSize = data.maxSize;
|
|
851
|
+
}
|
|
745
852
|
return prop;
|
|
746
853
|
}
|
|
747
854
|
async function loadSchema(filePath, options = {}) {
|
|
@@ -817,6 +924,141 @@ async function loadSchemas(directoryPath, options = {}) {
|
|
|
817
924
|
}
|
|
818
925
|
return schemas;
|
|
819
926
|
}
|
|
927
|
+
var FILE_SCHEMA_NAME = "File";
|
|
928
|
+
function schemasHaveFileProperties(schemas) {
|
|
929
|
+
for (const schema of Object.values(schemas)) {
|
|
930
|
+
if (!schema.properties) continue;
|
|
931
|
+
for (const prop of Object.values(schema.properties)) {
|
|
932
|
+
if (prop.type === "File") {
|
|
933
|
+
return true;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
return false;
|
|
938
|
+
}
|
|
939
|
+
function createFileSchemaDefinition() {
|
|
940
|
+
return {
|
|
941
|
+
displayName: "File",
|
|
942
|
+
options: {
|
|
943
|
+
timestamps: true,
|
|
944
|
+
tableName: "files",
|
|
945
|
+
indexes: [
|
|
946
|
+
{
|
|
947
|
+
columns: ["fileableType", "fileableId", "fileableField"],
|
|
948
|
+
name: "files_fileable_index"
|
|
949
|
+
}
|
|
950
|
+
]
|
|
951
|
+
},
|
|
952
|
+
properties: {
|
|
953
|
+
path: {
|
|
954
|
+
type: "String",
|
|
955
|
+
displayName: "Storage Path"
|
|
956
|
+
},
|
|
957
|
+
disk: {
|
|
958
|
+
type: "String",
|
|
959
|
+
displayName: "Storage Disk",
|
|
960
|
+
default: "local"
|
|
961
|
+
},
|
|
962
|
+
size: {
|
|
963
|
+
type: "BigInt",
|
|
964
|
+
displayName: "File Size (bytes)",
|
|
965
|
+
unsigned: true
|
|
966
|
+
},
|
|
967
|
+
mimeType: {
|
|
968
|
+
type: "String",
|
|
969
|
+
displayName: "MIME Type"
|
|
970
|
+
},
|
|
971
|
+
fileableType: {
|
|
972
|
+
type: "String",
|
|
973
|
+
displayName: "Fileable Type"
|
|
974
|
+
},
|
|
975
|
+
fileableId: {
|
|
976
|
+
type: "BigInt",
|
|
977
|
+
displayName: "Fileable ID",
|
|
978
|
+
unsigned: true
|
|
979
|
+
},
|
|
980
|
+
fileableField: {
|
|
981
|
+
type: "String",
|
|
982
|
+
displayName: "Fileable Field"
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
function createFileLoadedSchema(schemasDir) {
|
|
988
|
+
const definition = createFileSchemaDefinition();
|
|
989
|
+
const filePath = path.join(schemasDir, "File.yaml");
|
|
990
|
+
return {
|
|
991
|
+
...definition,
|
|
992
|
+
name: FILE_SCHEMA_NAME,
|
|
993
|
+
filePath,
|
|
994
|
+
relativePath: "File.yaml"
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
function generateFileSchemaYaml() {
|
|
998
|
+
return `# File Schema - Polymorphic file storage
|
|
999
|
+
# Auto-generated by Omnify when File type is detected
|
|
1000
|
+
# You can customize this schema as needed
|
|
1001
|
+
|
|
1002
|
+
displayName: File
|
|
1003
|
+
|
|
1004
|
+
options:
|
|
1005
|
+
timestamps: true
|
|
1006
|
+
tableName: files
|
|
1007
|
+
indexes:
|
|
1008
|
+
- columns: [fileableType, fileableId, fileableField]
|
|
1009
|
+
name: files_fileable_index
|
|
1010
|
+
|
|
1011
|
+
properties:
|
|
1012
|
+
path:
|
|
1013
|
+
type: String
|
|
1014
|
+
displayName: Storage Path
|
|
1015
|
+
|
|
1016
|
+
disk:
|
|
1017
|
+
type: String
|
|
1018
|
+
displayName: Storage Disk
|
|
1019
|
+
default: local
|
|
1020
|
+
|
|
1021
|
+
size:
|
|
1022
|
+
type: BigInt
|
|
1023
|
+
displayName: File Size (bytes)
|
|
1024
|
+
unsigned: true
|
|
1025
|
+
|
|
1026
|
+
mimeType:
|
|
1027
|
+
type: String
|
|
1028
|
+
displayName: MIME Type
|
|
1029
|
+
|
|
1030
|
+
fileableType:
|
|
1031
|
+
type: String
|
|
1032
|
+
displayName: Fileable Type
|
|
1033
|
+
|
|
1034
|
+
fileableId:
|
|
1035
|
+
type: BigInt
|
|
1036
|
+
displayName: Fileable ID
|
|
1037
|
+
unsigned: true
|
|
1038
|
+
|
|
1039
|
+
fileableField:
|
|
1040
|
+
type: String
|
|
1041
|
+
displayName: Fileable Field
|
|
1042
|
+
`;
|
|
1043
|
+
}
|
|
1044
|
+
async function ensureFileSchema(schemas, schemasDir, autoCreate = false) {
|
|
1045
|
+
if (!schemasHaveFileProperties(schemas)) {
|
|
1046
|
+
return schemas;
|
|
1047
|
+
}
|
|
1048
|
+
if (schemas[FILE_SCHEMA_NAME]) {
|
|
1049
|
+
return schemas;
|
|
1050
|
+
}
|
|
1051
|
+
const fileSchema = createFileLoadedSchema(schemasDir);
|
|
1052
|
+
if (autoCreate) {
|
|
1053
|
+
const filePath = path.join(schemasDir, "File.yaml");
|
|
1054
|
+
const yamlContent = generateFileSchemaYaml();
|
|
1055
|
+
await fs.writeFile(filePath, yamlContent, "utf-8");
|
|
1056
|
+
}
|
|
1057
|
+
return {
|
|
1058
|
+
[FILE_SCHEMA_NAME]: fileSchema,
|
|
1059
|
+
...schemas
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
820
1062
|
|
|
821
1063
|
// src/validation/types/base.ts
|
|
822
1064
|
var BASE_PROPERTY_FIELDS = [
|
|
@@ -928,6 +1170,16 @@ var TypeRegistry = class {
|
|
|
928
1170
|
var typeRegistry = new TypeRegistry();
|
|
929
1171
|
|
|
930
1172
|
// src/validation/types/primitives.ts
|
|
1173
|
+
function validateBooleanDefault(value) {
|
|
1174
|
+
if (value === void 0 || value === null || value === "") {
|
|
1175
|
+
return { valid: true };
|
|
1176
|
+
}
|
|
1177
|
+
const strValue = String(value).toLowerCase();
|
|
1178
|
+
if (!["true", "false", "1", "0"].includes(strValue)) {
|
|
1179
|
+
return { valid: false, error: "Must be true or false" };
|
|
1180
|
+
}
|
|
1181
|
+
return { valid: true };
|
|
1182
|
+
}
|
|
931
1183
|
var BooleanType = {
|
|
932
1184
|
name: "Boolean",
|
|
933
1185
|
category: "primitive",
|
|
@@ -935,13 +1187,27 @@ var BooleanType = {
|
|
|
935
1187
|
dbCompatibility: fullDbCompatibility(),
|
|
936
1188
|
validate() {
|
|
937
1189
|
return [];
|
|
938
|
-
}
|
|
1190
|
+
},
|
|
1191
|
+
validateDefaultValue: validateBooleanDefault
|
|
939
1192
|
};
|
|
940
1193
|
var primitiveTypes = [
|
|
941
1194
|
BooleanType
|
|
942
1195
|
];
|
|
943
1196
|
|
|
944
1197
|
// src/validation/types/text.ts
|
|
1198
|
+
function validateTextDefault() {
|
|
1199
|
+
return { valid: true };
|
|
1200
|
+
}
|
|
1201
|
+
function validateEmailDefault(value) {
|
|
1202
|
+
if (value === void 0 || value === null || value === "") {
|
|
1203
|
+
return { valid: true };
|
|
1204
|
+
}
|
|
1205
|
+
const strValue = String(value);
|
|
1206
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(strValue)) {
|
|
1207
|
+
return { valid: false, error: "Must be a valid email address" };
|
|
1208
|
+
}
|
|
1209
|
+
return { valid: true };
|
|
1210
|
+
}
|
|
945
1211
|
function buildLocation2(file) {
|
|
946
1212
|
return { file };
|
|
947
1213
|
}
|
|
@@ -970,7 +1236,8 @@ var StringType = {
|
|
|
970
1236
|
},
|
|
971
1237
|
validate(propertyName, property, filePath) {
|
|
972
1238
|
return validateLength(propertyName, property, filePath);
|
|
973
|
-
}
|
|
1239
|
+
},
|
|
1240
|
+
validateDefaultValue: validateTextDefault
|
|
974
1241
|
};
|
|
975
1242
|
var TextType = {
|
|
976
1243
|
name: "Text",
|
|
@@ -979,7 +1246,8 @@ var TextType = {
|
|
|
979
1246
|
dbCompatibility: fullDbCompatibility(),
|
|
980
1247
|
validate() {
|
|
981
1248
|
return [];
|
|
982
|
-
}
|
|
1249
|
+
},
|
|
1250
|
+
validateDefaultValue: validateTextDefault
|
|
983
1251
|
};
|
|
984
1252
|
var LongTextType = {
|
|
985
1253
|
name: "LongText",
|
|
@@ -994,7 +1262,8 @@ var LongTextType = {
|
|
|
994
1262
|
},
|
|
995
1263
|
validate() {
|
|
996
1264
|
return [];
|
|
997
|
-
}
|
|
1265
|
+
},
|
|
1266
|
+
validateDefaultValue: validateTextDefault
|
|
998
1267
|
};
|
|
999
1268
|
var EmailType = {
|
|
1000
1269
|
name: "Email",
|
|
@@ -1003,7 +1272,8 @@ var EmailType = {
|
|
|
1003
1272
|
dbCompatibility: fullDbCompatibility(),
|
|
1004
1273
|
validate(propertyName, property, filePath) {
|
|
1005
1274
|
return validateLength(propertyName, property, filePath);
|
|
1006
|
-
}
|
|
1275
|
+
},
|
|
1276
|
+
validateDefaultValue: validateEmailDefault
|
|
1007
1277
|
};
|
|
1008
1278
|
var PasswordType = {
|
|
1009
1279
|
name: "Password",
|
|
@@ -1012,7 +1282,8 @@ var PasswordType = {
|
|
|
1012
1282
|
dbCompatibility: fullDbCompatibility(),
|
|
1013
1283
|
validate(propertyName, property, filePath) {
|
|
1014
1284
|
return validateLength(propertyName, property, filePath);
|
|
1015
|
-
}
|
|
1285
|
+
},
|
|
1286
|
+
validateDefaultValue: validateTextDefault
|
|
1016
1287
|
};
|
|
1017
1288
|
var textTypes = [
|
|
1018
1289
|
StringType,
|
|
@@ -1023,6 +1294,26 @@ var textTypes = [
|
|
|
1023
1294
|
];
|
|
1024
1295
|
|
|
1025
1296
|
// src/validation/types/numeric.ts
|
|
1297
|
+
function validateIntegerDefault(value) {
|
|
1298
|
+
if (value === void 0 || value === null || value === "") {
|
|
1299
|
+
return { valid: true };
|
|
1300
|
+
}
|
|
1301
|
+
const strValue = String(value);
|
|
1302
|
+
if (!/^-?\d+$/.test(strValue)) {
|
|
1303
|
+
return { valid: false, error: "Must be an integer" };
|
|
1304
|
+
}
|
|
1305
|
+
return { valid: true };
|
|
1306
|
+
}
|
|
1307
|
+
function validateNumberDefault(value) {
|
|
1308
|
+
if (value === void 0 || value === null || value === "") {
|
|
1309
|
+
return { valid: true };
|
|
1310
|
+
}
|
|
1311
|
+
const strValue = String(value);
|
|
1312
|
+
if (!/^-?\d+(\.\d+)?$/.test(strValue)) {
|
|
1313
|
+
return { valid: false, error: "Must be a number" };
|
|
1314
|
+
}
|
|
1315
|
+
return { valid: true };
|
|
1316
|
+
}
|
|
1026
1317
|
function buildLocation3(file) {
|
|
1027
1318
|
return { file };
|
|
1028
1319
|
}
|
|
@@ -1033,7 +1324,8 @@ var IntType = {
|
|
|
1033
1324
|
dbCompatibility: fullDbCompatibility(),
|
|
1034
1325
|
validate() {
|
|
1035
1326
|
return [];
|
|
1036
|
-
}
|
|
1327
|
+
},
|
|
1328
|
+
validateDefaultValue: validateIntegerDefault
|
|
1037
1329
|
};
|
|
1038
1330
|
var BigIntType = {
|
|
1039
1331
|
name: "BigInt",
|
|
@@ -1042,7 +1334,8 @@ var BigIntType = {
|
|
|
1042
1334
|
dbCompatibility: fullDbCompatibility(),
|
|
1043
1335
|
validate() {
|
|
1044
1336
|
return [];
|
|
1045
|
-
}
|
|
1337
|
+
},
|
|
1338
|
+
validateDefaultValue: validateIntegerDefault
|
|
1046
1339
|
};
|
|
1047
1340
|
var FloatType = {
|
|
1048
1341
|
name: "Float",
|
|
@@ -1051,7 +1344,8 @@ var FloatType = {
|
|
|
1051
1344
|
dbCompatibility: fullDbCompatibility(),
|
|
1052
1345
|
validate() {
|
|
1053
1346
|
return [];
|
|
1054
|
-
}
|
|
1347
|
+
},
|
|
1348
|
+
validateDefaultValue: validateNumberDefault
|
|
1055
1349
|
};
|
|
1056
1350
|
var DecimalType = {
|
|
1057
1351
|
name: "Decimal",
|
|
@@ -1103,7 +1397,8 @@ var DecimalType = {
|
|
|
1103
1397
|
}
|
|
1104
1398
|
}
|
|
1105
1399
|
return errors;
|
|
1106
|
-
}
|
|
1400
|
+
},
|
|
1401
|
+
validateDefaultValue: validateNumberDefault
|
|
1107
1402
|
};
|
|
1108
1403
|
var numericTypes = [
|
|
1109
1404
|
IntType,
|
|
@@ -1113,6 +1408,36 @@ var numericTypes = [
|
|
|
1113
1408
|
];
|
|
1114
1409
|
|
|
1115
1410
|
// src/validation/types/temporal.ts
|
|
1411
|
+
function validateDateDefault(value) {
|
|
1412
|
+
if (value === void 0 || value === null || value === "") {
|
|
1413
|
+
return { valid: true };
|
|
1414
|
+
}
|
|
1415
|
+
const strValue = String(value);
|
|
1416
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(strValue)) {
|
|
1417
|
+
return { valid: false, error: "Must be in YYYY-MM-DD format" };
|
|
1418
|
+
}
|
|
1419
|
+
return { valid: true };
|
|
1420
|
+
}
|
|
1421
|
+
function validateTimeDefault(value) {
|
|
1422
|
+
if (value === void 0 || value === null || value === "") {
|
|
1423
|
+
return { valid: true };
|
|
1424
|
+
}
|
|
1425
|
+
const strValue = String(value);
|
|
1426
|
+
if (!/^\d{2}:\d{2}(:\d{2})?$/.test(strValue)) {
|
|
1427
|
+
return { valid: false, error: "Must be in HH:MM or HH:MM:SS format" };
|
|
1428
|
+
}
|
|
1429
|
+
return { valid: true };
|
|
1430
|
+
}
|
|
1431
|
+
function validateTimestampDefault(value) {
|
|
1432
|
+
if (value === void 0 || value === null || value === "") {
|
|
1433
|
+
return { valid: true };
|
|
1434
|
+
}
|
|
1435
|
+
const strValue = String(value);
|
|
1436
|
+
if (!/^\d{4}-\d{2}-\d{2}(T|\s)\d{2}:\d{2}(:\d{2})?/.test(strValue)) {
|
|
1437
|
+
return { valid: false, error: "Must be in YYYY-MM-DD HH:MM:SS format" };
|
|
1438
|
+
}
|
|
1439
|
+
return { valid: true };
|
|
1440
|
+
}
|
|
1116
1441
|
var DateType = {
|
|
1117
1442
|
name: "Date",
|
|
1118
1443
|
category: "temporal",
|
|
@@ -1120,7 +1445,8 @@ var DateType = {
|
|
|
1120
1445
|
dbCompatibility: fullDbCompatibility(),
|
|
1121
1446
|
validate() {
|
|
1122
1447
|
return [];
|
|
1123
|
-
}
|
|
1448
|
+
},
|
|
1449
|
+
validateDefaultValue: validateDateDefault
|
|
1124
1450
|
};
|
|
1125
1451
|
var TimeType = {
|
|
1126
1452
|
name: "Time",
|
|
@@ -1135,7 +1461,8 @@ var TimeType = {
|
|
|
1135
1461
|
},
|
|
1136
1462
|
validate() {
|
|
1137
1463
|
return [];
|
|
1138
|
-
}
|
|
1464
|
+
},
|
|
1465
|
+
validateDefaultValue: validateTimeDefault
|
|
1139
1466
|
};
|
|
1140
1467
|
var DateTimeType = {
|
|
1141
1468
|
name: "DateTime",
|
|
@@ -1144,7 +1471,8 @@ var DateTimeType = {
|
|
|
1144
1471
|
dbCompatibility: fullDbCompatibility(),
|
|
1145
1472
|
validate() {
|
|
1146
1473
|
return [];
|
|
1147
|
-
}
|
|
1474
|
+
},
|
|
1475
|
+
validateDefaultValue: validateTimestampDefault
|
|
1148
1476
|
};
|
|
1149
1477
|
var TimestampType = {
|
|
1150
1478
|
name: "Timestamp",
|
|
@@ -1159,7 +1487,8 @@ var TimestampType = {
|
|
|
1159
1487
|
},
|
|
1160
1488
|
validate() {
|
|
1161
1489
|
return [];
|
|
1162
|
-
}
|
|
1490
|
+
},
|
|
1491
|
+
validateDefaultValue: validateTimestampDefault
|
|
1163
1492
|
};
|
|
1164
1493
|
var temporalTypes = [
|
|
1165
1494
|
DateType,
|
|
@@ -1169,6 +1498,35 @@ var temporalTypes = [
|
|
|
1169
1498
|
];
|
|
1170
1499
|
|
|
1171
1500
|
// src/validation/types/special.ts
|
|
1501
|
+
var FILE_VALID_FIELDS = ["multiple", "maxFiles", "accept", "maxSize"];
|
|
1502
|
+
function validateJsonDefault(value) {
|
|
1503
|
+
if (value === void 0 || value === null || value === "") {
|
|
1504
|
+
return { valid: true };
|
|
1505
|
+
}
|
|
1506
|
+
const strValue = String(value);
|
|
1507
|
+
try {
|
|
1508
|
+
JSON.parse(strValue);
|
|
1509
|
+
return { valid: true };
|
|
1510
|
+
} catch {
|
|
1511
|
+
return { valid: false, error: "Must be valid JSON" };
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
function validateUuidDefault(value) {
|
|
1515
|
+
if (value === void 0 || value === null || value === "") {
|
|
1516
|
+
return { valid: true };
|
|
1517
|
+
}
|
|
1518
|
+
const strValue = String(value);
|
|
1519
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(strValue)) {
|
|
1520
|
+
return { valid: false, error: "Must be a valid UUID" };
|
|
1521
|
+
}
|
|
1522
|
+
return { valid: true };
|
|
1523
|
+
}
|
|
1524
|
+
function validateFileDefault() {
|
|
1525
|
+
return { valid: true };
|
|
1526
|
+
}
|
|
1527
|
+
function buildLocation4(file) {
|
|
1528
|
+
return { file };
|
|
1529
|
+
}
|
|
1172
1530
|
var JsonType = {
|
|
1173
1531
|
name: "Json",
|
|
1174
1532
|
category: "special",
|
|
@@ -1185,7 +1543,8 @@ var JsonType = {
|
|
|
1185
1543
|
},
|
|
1186
1544
|
validate() {
|
|
1187
1545
|
return [];
|
|
1188
|
-
}
|
|
1546
|
+
},
|
|
1547
|
+
validateDefaultValue: validateJsonDefault
|
|
1189
1548
|
};
|
|
1190
1549
|
var UuidType = {
|
|
1191
1550
|
name: "Uuid",
|
|
@@ -1194,35 +1553,110 @@ var UuidType = {
|
|
|
1194
1553
|
dbCompatibility: fullDbCompatibility(),
|
|
1195
1554
|
validate() {
|
|
1196
1555
|
return [];
|
|
1197
|
-
}
|
|
1556
|
+
},
|
|
1557
|
+
validateDefaultValue: validateUuidDefault
|
|
1198
1558
|
};
|
|
1199
1559
|
var FileType = {
|
|
1200
1560
|
name: "File",
|
|
1201
1561
|
category: "special",
|
|
1202
|
-
validFields: createValidFields(
|
|
1562
|
+
validFields: createValidFields(FILE_VALID_FIELDS),
|
|
1203
1563
|
dbCompatibility: fullDbCompatibility(),
|
|
1204
|
-
validate() {
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1564
|
+
validate(propertyName, property, filePath) {
|
|
1565
|
+
const errors = [];
|
|
1566
|
+
const {
|
|
1567
|
+
multiple,
|
|
1568
|
+
maxFiles,
|
|
1569
|
+
accept,
|
|
1570
|
+
maxSize
|
|
1571
|
+
} = property;
|
|
1572
|
+
if (multiple !== void 0 && typeof multiple !== "boolean") {
|
|
1573
|
+
errors.push(
|
|
1574
|
+
validationError(
|
|
1575
|
+
`Property '${propertyName}' multiple must be a boolean`,
|
|
1576
|
+
buildLocation4(filePath)
|
|
1577
|
+
)
|
|
1578
|
+
);
|
|
1579
|
+
}
|
|
1580
|
+
if (maxFiles !== void 0) {
|
|
1581
|
+
if (!multiple) {
|
|
1582
|
+
errors.push(
|
|
1583
|
+
validationError(
|
|
1584
|
+
`Property '${propertyName}' maxFiles is only valid when multiple=true`,
|
|
1585
|
+
buildLocation4(filePath),
|
|
1586
|
+
"Add multiple: true or remove maxFiles"
|
|
1587
|
+
)
|
|
1588
|
+
);
|
|
1589
|
+
} else if (typeof maxFiles !== "number" || maxFiles < 1 || !Number.isInteger(maxFiles)) {
|
|
1590
|
+
errors.push(
|
|
1591
|
+
validationError(
|
|
1592
|
+
`Property '${propertyName}' maxFiles must be a positive integer`,
|
|
1593
|
+
buildLocation4(filePath)
|
|
1594
|
+
)
|
|
1595
|
+
);
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
if (accept !== void 0) {
|
|
1599
|
+
if (!Array.isArray(accept)) {
|
|
1600
|
+
errors.push(
|
|
1601
|
+
validationError(
|
|
1602
|
+
`Property '${propertyName}' accept must be an array of file extensions`,
|
|
1603
|
+
buildLocation4(filePath),
|
|
1604
|
+
"Example: accept: [jpg, png, pdf]"
|
|
1605
|
+
)
|
|
1606
|
+
);
|
|
1607
|
+
} else {
|
|
1608
|
+
for (const ext of accept) {
|
|
1609
|
+
if (typeof ext !== "string") {
|
|
1610
|
+
errors.push(
|
|
1611
|
+
validationError(
|
|
1612
|
+
`Property '${propertyName}' accept array contains non-string value`,
|
|
1613
|
+
buildLocation4(filePath)
|
|
1614
|
+
)
|
|
1615
|
+
);
|
|
1616
|
+
break;
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
if (maxSize !== void 0) {
|
|
1622
|
+
if (typeof maxSize !== "number" || maxSize < 1 || !Number.isInteger(maxSize)) {
|
|
1623
|
+
errors.push(
|
|
1624
|
+
validationError(
|
|
1625
|
+
`Property '${propertyName}' maxSize must be a positive integer (KB)`,
|
|
1626
|
+
buildLocation4(filePath)
|
|
1627
|
+
)
|
|
1628
|
+
);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
return errors;
|
|
1632
|
+
},
|
|
1633
|
+
validateDefaultValue: validateFileDefault
|
|
1216
1634
|
};
|
|
1217
1635
|
var specialTypes = [
|
|
1218
1636
|
JsonType,
|
|
1219
1637
|
UuidType,
|
|
1220
|
-
FileType
|
|
1221
|
-
MultiFileType
|
|
1638
|
+
FileType
|
|
1222
1639
|
];
|
|
1223
1640
|
|
|
1224
1641
|
// src/validation/types/enum.ts
|
|
1225
|
-
function
|
|
1642
|
+
function validateEnumDefault(value, property) {
|
|
1643
|
+
if (value === void 0 || value === null || value === "") {
|
|
1644
|
+
return { valid: true };
|
|
1645
|
+
}
|
|
1646
|
+
const enumValues = property?.enum;
|
|
1647
|
+
if (!enumValues || !Array.isArray(enumValues)) {
|
|
1648
|
+
return { valid: true };
|
|
1649
|
+
}
|
|
1650
|
+
const strValue = String(value);
|
|
1651
|
+
if (!enumValues.includes(strValue)) {
|
|
1652
|
+
return {
|
|
1653
|
+
valid: false,
|
|
1654
|
+
error: `Must be one of: ${enumValues.join(", ")}`
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
return { valid: true };
|
|
1658
|
+
}
|
|
1659
|
+
function buildLocation5(file) {
|
|
1226
1660
|
return { file };
|
|
1227
1661
|
}
|
|
1228
1662
|
var EnumType = {
|
|
@@ -1238,7 +1672,7 @@ var EnumType = {
|
|
|
1238
1672
|
errors.push(
|
|
1239
1673
|
validationError(
|
|
1240
1674
|
`Property '${propertyName}' of type 'Enum' requires 'enum' field`,
|
|
1241
|
-
|
|
1675
|
+
buildLocation5(filePath),
|
|
1242
1676
|
"Add enum values: enum: [value1, value2]"
|
|
1243
1677
|
)
|
|
1244
1678
|
);
|
|
@@ -1246,7 +1680,7 @@ var EnumType = {
|
|
|
1246
1680
|
errors.push(
|
|
1247
1681
|
validationError(
|
|
1248
1682
|
`Property '${propertyName}' enum field must be an array`,
|
|
1249
|
-
|
|
1683
|
+
buildLocation5(filePath),
|
|
1250
1684
|
"enum: [value1, value2]"
|
|
1251
1685
|
)
|
|
1252
1686
|
);
|
|
@@ -1254,7 +1688,7 @@ var EnumType = {
|
|
|
1254
1688
|
errors.push(
|
|
1255
1689
|
validationError(
|
|
1256
1690
|
`Property '${propertyName}' enum field cannot be empty`,
|
|
1257
|
-
|
|
1691
|
+
buildLocation5(filePath),
|
|
1258
1692
|
"Add at least one enum value"
|
|
1259
1693
|
)
|
|
1260
1694
|
);
|
|
@@ -1265,14 +1699,14 @@ var EnumType = {
|
|
|
1265
1699
|
errors.push(
|
|
1266
1700
|
validationError(
|
|
1267
1701
|
`Property '${propertyName}' enum values must be strings, got ${typeof value}`,
|
|
1268
|
-
|
|
1702
|
+
buildLocation5(filePath)
|
|
1269
1703
|
)
|
|
1270
1704
|
);
|
|
1271
1705
|
} else if (seen.has(value)) {
|
|
1272
1706
|
errors.push(
|
|
1273
1707
|
validationError(
|
|
1274
1708
|
`Property '${propertyName}' has duplicate enum value '${value}'`,
|
|
1275
|
-
|
|
1709
|
+
buildLocation5(filePath)
|
|
1276
1710
|
)
|
|
1277
1711
|
);
|
|
1278
1712
|
} else {
|
|
@@ -1281,79 +1715,56 @@ var EnumType = {
|
|
|
1281
1715
|
}
|
|
1282
1716
|
}
|
|
1283
1717
|
return errors;
|
|
1284
|
-
}
|
|
1718
|
+
},
|
|
1719
|
+
validateDefaultValue: validateEnumDefault
|
|
1285
1720
|
};
|
|
1286
|
-
var
|
|
1287
|
-
name: "
|
|
1721
|
+
var EnumRefType = {
|
|
1722
|
+
name: "EnumRef",
|
|
1288
1723
|
category: "enum",
|
|
1289
|
-
validFields: createValidFields(["
|
|
1724
|
+
validFields: createValidFields(["enum"]),
|
|
1725
|
+
requiredFields: ["enum"],
|
|
1290
1726
|
dbCompatibility: fullDbCompatibility(),
|
|
1291
1727
|
validate(propertyName, property, filePath) {
|
|
1292
1728
|
const errors = [];
|
|
1293
|
-
const {
|
|
1294
|
-
if (
|
|
1729
|
+
const { enum: enumRef } = property;
|
|
1730
|
+
if (enumRef === void 0) {
|
|
1295
1731
|
errors.push(
|
|
1296
1732
|
validationError(
|
|
1297
|
-
`Property '${propertyName}' of type '
|
|
1298
|
-
|
|
1299
|
-
"Add
|
|
1300
|
-
)
|
|
1301
|
-
);
|
|
1302
|
-
}
|
|
1303
|
-
if (options !== void 0 && !Array.isArray(options)) {
|
|
1304
|
-
errors.push(
|
|
1305
|
-
validationError(
|
|
1306
|
-
`Property '${propertyName}' options field must be an array`,
|
|
1307
|
-
buildLocation4(filePath)
|
|
1733
|
+
`Property '${propertyName}' of type 'EnumRef' requires 'enum' field`,
|
|
1734
|
+
buildLocation5(filePath),
|
|
1735
|
+
"Add enum schema reference: enum: EnumSchemaName"
|
|
1308
1736
|
)
|
|
1309
1737
|
);
|
|
1310
|
-
}
|
|
1311
|
-
if (source !== void 0 && typeof source !== "string") {
|
|
1738
|
+
} else if (typeof enumRef !== "string") {
|
|
1312
1739
|
errors.push(
|
|
1313
1740
|
validationError(
|
|
1314
|
-
`Property '${propertyName}'
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
);
|
|
1318
|
-
}
|
|
1319
|
-
return errors;
|
|
1320
|
-
}
|
|
1321
|
-
};
|
|
1322
|
-
var LookupType = {
|
|
1323
|
-
name: "Lookup",
|
|
1324
|
-
category: "enum",
|
|
1325
|
-
validFields: createValidFields(["options", "source"]),
|
|
1326
|
-
dbCompatibility: fullDbCompatibility(),
|
|
1327
|
-
validate(propertyName, property, filePath) {
|
|
1328
|
-
const errors = [];
|
|
1329
|
-
const { source } = property;
|
|
1330
|
-
if (source === void 0) {
|
|
1331
|
-
errors.push(
|
|
1332
|
-
validationError(
|
|
1333
|
-
`Property '${propertyName}' of type 'Lookup' requires 'source' field`,
|
|
1334
|
-
buildLocation4(filePath),
|
|
1335
|
-
"Add source: SchemaName"
|
|
1741
|
+
`Property '${propertyName}' enum field must be a string (enum schema name)`,
|
|
1742
|
+
buildLocation5(filePath),
|
|
1743
|
+
"enum: EnumSchemaName"
|
|
1336
1744
|
)
|
|
1337
1745
|
);
|
|
1338
|
-
} else if (
|
|
1746
|
+
} else if (enumRef.trim() === "") {
|
|
1339
1747
|
errors.push(
|
|
1340
1748
|
validationError(
|
|
1341
|
-
`Property '${propertyName}'
|
|
1342
|
-
|
|
1749
|
+
`Property '${propertyName}' enum field cannot be empty`,
|
|
1750
|
+
buildLocation5(filePath),
|
|
1751
|
+
"Add enum schema name"
|
|
1343
1752
|
)
|
|
1344
1753
|
);
|
|
1345
1754
|
}
|
|
1346
1755
|
return errors;
|
|
1347
|
-
}
|
|
1756
|
+
},
|
|
1757
|
+
// EnumRef default value validation requires schema context
|
|
1758
|
+
// This is handled at a higher level (validator.ts) where schemas are available
|
|
1759
|
+
validateDefaultValue: () => ({ valid: true })
|
|
1348
1760
|
};
|
|
1349
1761
|
var enumTypes = [
|
|
1350
1762
|
EnumType,
|
|
1351
|
-
|
|
1352
|
-
LookupType
|
|
1763
|
+
EnumRefType
|
|
1353
1764
|
];
|
|
1354
1765
|
|
|
1355
1766
|
// src/validation/types/relations.ts
|
|
1356
|
-
function
|
|
1767
|
+
function buildLocation6(file) {
|
|
1357
1768
|
return { file };
|
|
1358
1769
|
}
|
|
1359
1770
|
var VALID_RELATIONS = [
|
|
@@ -1380,6 +1791,23 @@ var VALID_REFERENTIAL_ACTIONS = [
|
|
|
1380
1791
|
"RESTRICT",
|
|
1381
1792
|
"NO ACTION"
|
|
1382
1793
|
];
|
|
1794
|
+
var PIVOT_TABLE_RELATIONS = [
|
|
1795
|
+
"ManyToMany",
|
|
1796
|
+
"MorphToMany"
|
|
1797
|
+
];
|
|
1798
|
+
var VALID_PIVOT_FIELD_TYPES = [
|
|
1799
|
+
"String",
|
|
1800
|
+
"Int",
|
|
1801
|
+
"BigInt",
|
|
1802
|
+
"Float",
|
|
1803
|
+
"Decimal",
|
|
1804
|
+
"Boolean",
|
|
1805
|
+
"Text",
|
|
1806
|
+
"Date",
|
|
1807
|
+
"Time",
|
|
1808
|
+
"Timestamp",
|
|
1809
|
+
"Json"
|
|
1810
|
+
];
|
|
1383
1811
|
var ASSOCIATION_FIELDS = [
|
|
1384
1812
|
"relation",
|
|
1385
1813
|
"target",
|
|
@@ -1392,7 +1820,9 @@ var ASSOCIATION_FIELDS = [
|
|
|
1392
1820
|
"onDelete",
|
|
1393
1821
|
"onUpdate",
|
|
1394
1822
|
"owning",
|
|
1395
|
-
"joinTable"
|
|
1823
|
+
"joinTable",
|
|
1824
|
+
"pivotFields"
|
|
1825
|
+
// For ManyToMany/MorphToMany: additional fields on pivot table
|
|
1396
1826
|
];
|
|
1397
1827
|
var AssociationType = {
|
|
1398
1828
|
name: "Association",
|
|
@@ -1414,7 +1844,7 @@ var AssociationType = {
|
|
|
1414
1844
|
errors.push(
|
|
1415
1845
|
validationError(
|
|
1416
1846
|
`Property '${propertyName}' of type 'Association' requires 'relation' field`,
|
|
1417
|
-
|
|
1847
|
+
buildLocation6(filePath),
|
|
1418
1848
|
"Add relation: OneToOne, OneToMany, ManyToOne, ManyToMany, MorphTo, MorphOne, MorphMany, MorphToMany, or MorphedByMany"
|
|
1419
1849
|
)
|
|
1420
1850
|
);
|
|
@@ -1424,7 +1854,7 @@ var AssociationType = {
|
|
|
1424
1854
|
errors.push(
|
|
1425
1855
|
validationError(
|
|
1426
1856
|
`Property '${propertyName}' relation field must be a string`,
|
|
1427
|
-
|
|
1857
|
+
buildLocation6(filePath)
|
|
1428
1858
|
)
|
|
1429
1859
|
);
|
|
1430
1860
|
return errors;
|
|
@@ -1433,7 +1863,7 @@ var AssociationType = {
|
|
|
1433
1863
|
errors.push(
|
|
1434
1864
|
validationError(
|
|
1435
1865
|
`Property '${propertyName}' has invalid relation '${relation}'`,
|
|
1436
|
-
|
|
1866
|
+
buildLocation6(filePath),
|
|
1437
1867
|
`Use one of: ${VALID_RELATIONS.join(", ")}`
|
|
1438
1868
|
)
|
|
1439
1869
|
);
|
|
@@ -1445,7 +1875,7 @@ var AssociationType = {
|
|
|
1445
1875
|
errors.push(
|
|
1446
1876
|
validationError(
|
|
1447
1877
|
`Property '${propertyName}' with relation 'MorphTo' requires 'targets' field`,
|
|
1448
|
-
|
|
1878
|
+
buildLocation6(filePath),
|
|
1449
1879
|
"Add targets: [Post, Video, Image]"
|
|
1450
1880
|
)
|
|
1451
1881
|
);
|
|
@@ -1453,7 +1883,7 @@ var AssociationType = {
|
|
|
1453
1883
|
errors.push(
|
|
1454
1884
|
validationError(
|
|
1455
1885
|
`Property '${propertyName}' targets field must be an array of schema names`,
|
|
1456
|
-
|
|
1886
|
+
buildLocation6(filePath),
|
|
1457
1887
|
"Example: targets: [Post, Video, Image]"
|
|
1458
1888
|
)
|
|
1459
1889
|
);
|
|
@@ -1461,7 +1891,7 @@ var AssociationType = {
|
|
|
1461
1891
|
errors.push(
|
|
1462
1892
|
validationError(
|
|
1463
1893
|
`Property '${propertyName}' targets array cannot be empty`,
|
|
1464
|
-
|
|
1894
|
+
buildLocation6(filePath),
|
|
1465
1895
|
"Add at least one target schema"
|
|
1466
1896
|
)
|
|
1467
1897
|
);
|
|
@@ -1471,7 +1901,7 @@ var AssociationType = {
|
|
|
1471
1901
|
errors.push(
|
|
1472
1902
|
validationError(
|
|
1473
1903
|
`Property '${propertyName}' targets array contains non-string value`,
|
|
1474
|
-
|
|
1904
|
+
buildLocation6(filePath)
|
|
1475
1905
|
)
|
|
1476
1906
|
);
|
|
1477
1907
|
}
|
|
@@ -1481,7 +1911,7 @@ var AssociationType = {
|
|
|
1481
1911
|
errors.push(
|
|
1482
1912
|
validationError(
|
|
1483
1913
|
`Property '${propertyName}' with relation 'MorphTo' should use 'targets' (plural) not 'target'`,
|
|
1484
|
-
|
|
1914
|
+
buildLocation6(filePath),
|
|
1485
1915
|
"Change target to targets array"
|
|
1486
1916
|
)
|
|
1487
1917
|
);
|
|
@@ -1491,7 +1921,7 @@ var AssociationType = {
|
|
|
1491
1921
|
errors.push(
|
|
1492
1922
|
validationError(
|
|
1493
1923
|
`Property '${propertyName}' with relation '${relation}' requires 'target' field`,
|
|
1494
|
-
|
|
1924
|
+
buildLocation6(filePath),
|
|
1495
1925
|
"Add target schema name: target: Comment"
|
|
1496
1926
|
)
|
|
1497
1927
|
);
|
|
@@ -1499,7 +1929,7 @@ var AssociationType = {
|
|
|
1499
1929
|
errors.push(
|
|
1500
1930
|
validationError(
|
|
1501
1931
|
`Property '${propertyName}' target field must be a string`,
|
|
1502
|
-
|
|
1932
|
+
buildLocation6(filePath)
|
|
1503
1933
|
)
|
|
1504
1934
|
);
|
|
1505
1935
|
}
|
|
@@ -1507,7 +1937,7 @@ var AssociationType = {
|
|
|
1507
1937
|
errors.push(
|
|
1508
1938
|
validationError(
|
|
1509
1939
|
`Property '${propertyName}' with relation '${relation}' requires 'morphName' field`,
|
|
1510
|
-
|
|
1940
|
+
buildLocation6(filePath),
|
|
1511
1941
|
"Add morphName to match the MorphTo property name (e.g., morphName: commentable)"
|
|
1512
1942
|
)
|
|
1513
1943
|
);
|
|
@@ -1515,7 +1945,7 @@ var AssociationType = {
|
|
|
1515
1945
|
errors.push(
|
|
1516
1946
|
validationError(
|
|
1517
1947
|
`Property '${propertyName}' morphName field must be a string`,
|
|
1518
|
-
|
|
1948
|
+
buildLocation6(filePath)
|
|
1519
1949
|
)
|
|
1520
1950
|
);
|
|
1521
1951
|
}
|
|
@@ -1524,7 +1954,7 @@ var AssociationType = {
|
|
|
1524
1954
|
errors.push(
|
|
1525
1955
|
validationError(
|
|
1526
1956
|
`Property '${propertyName}' with relation 'MorphToMany' requires 'target' field`,
|
|
1527
|
-
|
|
1957
|
+
buildLocation6(filePath),
|
|
1528
1958
|
"Add target schema name: target: Tag"
|
|
1529
1959
|
)
|
|
1530
1960
|
);
|
|
@@ -1532,7 +1962,7 @@ var AssociationType = {
|
|
|
1532
1962
|
errors.push(
|
|
1533
1963
|
validationError(
|
|
1534
1964
|
`Property '${propertyName}' target field must be a string`,
|
|
1535
|
-
|
|
1965
|
+
buildLocation6(filePath)
|
|
1536
1966
|
)
|
|
1537
1967
|
);
|
|
1538
1968
|
}
|
|
@@ -1541,7 +1971,7 @@ var AssociationType = {
|
|
|
1541
1971
|
errors.push(
|
|
1542
1972
|
validationError(
|
|
1543
1973
|
`Property '${propertyName}' with relation 'MorphedByMany' requires 'target' field`,
|
|
1544
|
-
|
|
1974
|
+
buildLocation6(filePath),
|
|
1545
1975
|
"Add target schema name: target: Post"
|
|
1546
1976
|
)
|
|
1547
1977
|
);
|
|
@@ -1549,7 +1979,7 @@ var AssociationType = {
|
|
|
1549
1979
|
errors.push(
|
|
1550
1980
|
validationError(
|
|
1551
1981
|
`Property '${propertyName}' target field must be a string`,
|
|
1552
|
-
|
|
1982
|
+
buildLocation6(filePath)
|
|
1553
1983
|
)
|
|
1554
1984
|
);
|
|
1555
1985
|
}
|
|
@@ -1557,7 +1987,7 @@ var AssociationType = {
|
|
|
1557
1987
|
errors.push(
|
|
1558
1988
|
validationError(
|
|
1559
1989
|
`Property '${propertyName}' morphName field must be a string`,
|
|
1560
|
-
|
|
1990
|
+
buildLocation6(filePath)
|
|
1561
1991
|
)
|
|
1562
1992
|
);
|
|
1563
1993
|
}
|
|
@@ -1566,7 +1996,7 @@ var AssociationType = {
|
|
|
1566
1996
|
errors.push(
|
|
1567
1997
|
validationError(
|
|
1568
1998
|
`Property '${propertyName}' of type 'Association' requires 'target' field`,
|
|
1569
|
-
|
|
1999
|
+
buildLocation6(filePath),
|
|
1570
2000
|
"Add target schema name: target: User"
|
|
1571
2001
|
)
|
|
1572
2002
|
);
|
|
@@ -1574,7 +2004,7 @@ var AssociationType = {
|
|
|
1574
2004
|
errors.push(
|
|
1575
2005
|
validationError(
|
|
1576
2006
|
`Property '${propertyName}' target field must be a string`,
|
|
1577
|
-
|
|
2007
|
+
buildLocation6(filePath)
|
|
1578
2008
|
)
|
|
1579
2009
|
);
|
|
1580
2010
|
}
|
|
@@ -1584,7 +2014,7 @@ var AssociationType = {
|
|
|
1584
2014
|
errors.push(
|
|
1585
2015
|
validationError(
|
|
1586
2016
|
`Property '${propertyName}' has invalid onDelete action '${onDelete}'`,
|
|
1587
|
-
|
|
2017
|
+
buildLocation6(filePath),
|
|
1588
2018
|
`Use one of: ${VALID_REFERENTIAL_ACTIONS.join(", ")}`
|
|
1589
2019
|
)
|
|
1590
2020
|
);
|
|
@@ -1595,12 +2025,77 @@ var AssociationType = {
|
|
|
1595
2025
|
errors.push(
|
|
1596
2026
|
validationError(
|
|
1597
2027
|
`Property '${propertyName}' has invalid onUpdate action '${onUpdate}'`,
|
|
1598
|
-
|
|
2028
|
+
buildLocation6(filePath),
|
|
1599
2029
|
`Use one of: ${VALID_REFERENTIAL_ACTIONS.join(", ")}`
|
|
1600
2030
|
)
|
|
1601
2031
|
);
|
|
1602
2032
|
}
|
|
1603
2033
|
}
|
|
2034
|
+
const pivotFields = property.pivotFields;
|
|
2035
|
+
if (pivotFields !== void 0) {
|
|
2036
|
+
if (!PIVOT_TABLE_RELATIONS.includes(relationTyped)) {
|
|
2037
|
+
errors.push(
|
|
2038
|
+
validationError(
|
|
2039
|
+
`Property '${propertyName}' with relation '${relation}' cannot have pivotFields`,
|
|
2040
|
+
buildLocation6(filePath),
|
|
2041
|
+
"pivotFields is only allowed for ManyToMany and MorphToMany relations"
|
|
2042
|
+
)
|
|
2043
|
+
);
|
|
2044
|
+
} else if (typeof pivotFields !== "object" || pivotFields === null || Array.isArray(pivotFields)) {
|
|
2045
|
+
errors.push(
|
|
2046
|
+
validationError(
|
|
2047
|
+
`Property '${propertyName}' pivotFields must be an object`,
|
|
2048
|
+
buildLocation6(filePath),
|
|
2049
|
+
'Example: pivotFields: { assigned_at: { type: "Timestamp" } }'
|
|
2050
|
+
)
|
|
2051
|
+
);
|
|
2052
|
+
} else {
|
|
2053
|
+
for (const [fieldName, fieldDef] of Object.entries(pivotFields)) {
|
|
2054
|
+
if (typeof fieldDef !== "object" || fieldDef === null) {
|
|
2055
|
+
errors.push(
|
|
2056
|
+
validationError(
|
|
2057
|
+
`Pivot field '${fieldName}' in '${propertyName}' must be an object`,
|
|
2058
|
+
buildLocation6(filePath)
|
|
2059
|
+
)
|
|
2060
|
+
);
|
|
2061
|
+
continue;
|
|
2062
|
+
}
|
|
2063
|
+
const { type: fieldType } = fieldDef;
|
|
2064
|
+
if (fieldType === void 0) {
|
|
2065
|
+
errors.push(
|
|
2066
|
+
validationError(
|
|
2067
|
+
`Pivot field '${fieldName}' in '${propertyName}' requires 'type' field`,
|
|
2068
|
+
buildLocation6(filePath),
|
|
2069
|
+
`Example: ${fieldName}: { type: "String" }`
|
|
2070
|
+
)
|
|
2071
|
+
);
|
|
2072
|
+
} else if (typeof fieldType !== "string") {
|
|
2073
|
+
errors.push(
|
|
2074
|
+
validationError(
|
|
2075
|
+
`Pivot field '${fieldName}' in '${propertyName}' type must be a string`,
|
|
2076
|
+
buildLocation6(filePath)
|
|
2077
|
+
)
|
|
2078
|
+
);
|
|
2079
|
+
} else if (fieldType === "Association") {
|
|
2080
|
+
errors.push(
|
|
2081
|
+
validationError(
|
|
2082
|
+
`Pivot field '${fieldName}' in '${propertyName}' cannot be of type 'Association'`,
|
|
2083
|
+
buildLocation6(filePath),
|
|
2084
|
+
"Pivot fields only support basic types like String, Int, Boolean, Timestamp, etc."
|
|
2085
|
+
)
|
|
2086
|
+
);
|
|
2087
|
+
} else if (!VALID_PIVOT_FIELD_TYPES.includes(fieldType)) {
|
|
2088
|
+
errors.push(
|
|
2089
|
+
validationError(
|
|
2090
|
+
`Pivot field '${fieldName}' in '${propertyName}' has unsupported type '${fieldType}'`,
|
|
2091
|
+
buildLocation6(filePath),
|
|
2092
|
+
`Supported types: ${VALID_PIVOT_FIELD_TYPES.join(", ")}`
|
|
2093
|
+
)
|
|
2094
|
+
);
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
1604
2099
|
return errors;
|
|
1605
2100
|
}
|
|
1606
2101
|
};
|
|
@@ -1626,10 +2121,20 @@ function registerBuiltInTypes() {
|
|
|
1626
2121
|
}
|
|
1627
2122
|
}
|
|
1628
2123
|
registerBuiltInTypes();
|
|
2124
|
+
function validateDefaultValue(typeName, value, property) {
|
|
2125
|
+
const typeDef = typeRegistry.get(typeName);
|
|
2126
|
+
if (!typeDef) {
|
|
2127
|
+
return { valid: true };
|
|
2128
|
+
}
|
|
2129
|
+
if (typeDef.validateDefaultValue) {
|
|
2130
|
+
return typeDef.validateDefaultValue(value, property);
|
|
2131
|
+
}
|
|
2132
|
+
return { valid: true };
|
|
2133
|
+
}
|
|
1629
2134
|
|
|
1630
2135
|
// src/validation/validator.ts
|
|
1631
2136
|
var VALID_ID_TYPES = ["Int", "BigInt", "Uuid", "String"];
|
|
1632
|
-
function
|
|
2137
|
+
function buildLocation7(file, line, column) {
|
|
1633
2138
|
const loc = { file };
|
|
1634
2139
|
if (line !== void 0) {
|
|
1635
2140
|
loc.line = line;
|
|
@@ -1639,6 +2144,68 @@ function buildLocation6(file, line, column) {
|
|
|
1639
2144
|
}
|
|
1640
2145
|
return loc;
|
|
1641
2146
|
}
|
|
2147
|
+
function validateUnknownSchemaFields(schema, filePath) {
|
|
2148
|
+
const errors = [];
|
|
2149
|
+
const unknownFields = schema._unknownFields;
|
|
2150
|
+
if (!unknownFields || unknownFields.length === 0) {
|
|
2151
|
+
return errors;
|
|
2152
|
+
}
|
|
2153
|
+
for (const field of unknownFields) {
|
|
2154
|
+
let suggestion = `Valid schema fields: kind, displayName, titleIndex, group, options, properties, values`;
|
|
2155
|
+
if (field === "associations") {
|
|
2156
|
+
suggestion = `Associations should be defined inside 'properties' with type: Association.
|
|
2157
|
+
Example:
|
|
2158
|
+
properties:
|
|
2159
|
+
author:
|
|
2160
|
+
type: Association
|
|
2161
|
+
relation: ManyToOne
|
|
2162
|
+
target: User
|
|
2163
|
+
onDelete: CASCADE`;
|
|
2164
|
+
} else if (field === "indexes") {
|
|
2165
|
+
suggestion = `Indexes should be inside 'options.indexes'.
|
|
2166
|
+
Example:
|
|
2167
|
+
options:
|
|
2168
|
+
indexes:
|
|
2169
|
+
- columns: [email]
|
|
2170
|
+
unique: true`;
|
|
2171
|
+
} else if (field === "required") {
|
|
2172
|
+
suggestion = `'required' is not a valid field. Use 'nullable: false' on properties (default is not nullable).
|
|
2173
|
+
Example:
|
|
2174
|
+
properties:
|
|
2175
|
+
email:
|
|
2176
|
+
type: String
|
|
2177
|
+
# not nullable by default, explicitly use nullable: true to make optional`;
|
|
2178
|
+
} else if (field === "hidden") {
|
|
2179
|
+
suggestion = `'hidden' is not a schema-level field. Hidden fields should be handled by your framework.
|
|
2180
|
+
For Laravel, the model generator can handle this, but you need to use a custom property.`;
|
|
2181
|
+
}
|
|
2182
|
+
errors.push(
|
|
2183
|
+
validationError(
|
|
2184
|
+
`Unknown schema field '${field}'`,
|
|
2185
|
+
buildLocation7(filePath),
|
|
2186
|
+
suggestion
|
|
2187
|
+
)
|
|
2188
|
+
);
|
|
2189
|
+
}
|
|
2190
|
+
return errors;
|
|
2191
|
+
}
|
|
2192
|
+
function validateUnknownOptionsFields(schema, filePath) {
|
|
2193
|
+
const errors = [];
|
|
2194
|
+
const unknownOptionsFields = schema._unknownOptionsFields;
|
|
2195
|
+
if (!unknownOptionsFields || unknownOptionsFields.length === 0) {
|
|
2196
|
+
return errors;
|
|
2197
|
+
}
|
|
2198
|
+
for (const field of unknownOptionsFields) {
|
|
2199
|
+
errors.push(
|
|
2200
|
+
validationError(
|
|
2201
|
+
`Unknown option field '${field}'`,
|
|
2202
|
+
buildLocation7(filePath),
|
|
2203
|
+
`Valid option fields: id, idType, timestamps, softDelete, unique, indexes, translations, tableName, authenticatable, authenticatableLoginIdField, authenticatablePasswordField, authenticatableGuardName`
|
|
2204
|
+
)
|
|
2205
|
+
);
|
|
2206
|
+
}
|
|
2207
|
+
return errors;
|
|
2208
|
+
}
|
|
1642
2209
|
function validateDbCompatibility(propertyName, property, filePath, databaseDriver) {
|
|
1643
2210
|
const errors = [];
|
|
1644
2211
|
const warnings = [];
|
|
@@ -1650,7 +2217,7 @@ function validateDbCompatibility(propertyName, property, filePath, databaseDrive
|
|
|
1650
2217
|
errors.push(
|
|
1651
2218
|
validationError(
|
|
1652
2219
|
`Property '${propertyName}' uses type '${property.type}' which is not supported in ${databaseDriver}`,
|
|
1653
|
-
|
|
2220
|
+
buildLocation7(filePath),
|
|
1654
2221
|
`Consider using a different type or database driver`
|
|
1655
2222
|
)
|
|
1656
2223
|
);
|
|
@@ -1658,7 +2225,7 @@ function validateDbCompatibility(propertyName, property, filePath, databaseDrive
|
|
|
1658
2225
|
warnings.push(
|
|
1659
2226
|
validationError(
|
|
1660
2227
|
`Property '${propertyName}' uses type '${property.type}' which has limited support in ${databaseDriver}`,
|
|
1661
|
-
|
|
2228
|
+
buildLocation7(filePath),
|
|
1662
2229
|
`The type will work but may have precision or feature limitations`
|
|
1663
2230
|
)
|
|
1664
2231
|
);
|
|
@@ -1666,30 +2233,51 @@ function validateDbCompatibility(propertyName, property, filePath, databaseDrive
|
|
|
1666
2233
|
return { errors, warnings };
|
|
1667
2234
|
}
|
|
1668
2235
|
function validateUnknownFields(propertyName, property, filePath) {
|
|
1669
|
-
const
|
|
1670
|
-
|
|
1671
|
-
|
|
2236
|
+
const errors = [];
|
|
2237
|
+
const unknownFields = property._unknownFields;
|
|
2238
|
+
if (!unknownFields || unknownFields.length === 0) {
|
|
2239
|
+
return errors;
|
|
1672
2240
|
}
|
|
1673
|
-
const
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
2241
|
+
for (const field of unknownFields) {
|
|
2242
|
+
let suggestion = `Valid property fields: type, displayName, nullable, default, unique, description, length, unsigned, precision, scale, enum, relation, target, onDelete, onUpdate, joinTable`;
|
|
2243
|
+
if (field === "required") {
|
|
2244
|
+
suggestion = `'required' is not a valid field. Properties are required by default.
|
|
2245
|
+
Use 'nullable: true' to make a field optional.
|
|
2246
|
+
Example:
|
|
2247
|
+
email:
|
|
2248
|
+
type: String
|
|
2249
|
+
# required by default
|
|
2250
|
+
|
|
2251
|
+
phone:
|
|
2252
|
+
type: String
|
|
2253
|
+
nullable: true # optional field`;
|
|
2254
|
+
} else if (field === "hidden") {
|
|
2255
|
+
suggestion = `'hidden' is not a standard field. For Laravel, hidden fields are configured in the Model class.`;
|
|
2256
|
+
} else if (field === "foreignKey") {
|
|
2257
|
+
suggestion = `'foreignKey' is not needed. Omnify auto-generates foreign keys from Association properties.
|
|
2258
|
+
The foreign key name is derived from the property name + '_id'.
|
|
2259
|
+
Example:
|
|
2260
|
+
author:
|
|
2261
|
+
type: Association
|
|
2262
|
+
relation: ManyToOne
|
|
2263
|
+
target: User
|
|
2264
|
+
# Creates author_id column automatically`;
|
|
1684
2265
|
}
|
|
2266
|
+
errors.push(
|
|
2267
|
+
validationError(
|
|
2268
|
+
`Property '${propertyName}' has unknown field '${field}'`,
|
|
2269
|
+
buildLocation7(filePath),
|
|
2270
|
+
suggestion
|
|
2271
|
+
)
|
|
2272
|
+
);
|
|
1685
2273
|
}
|
|
1686
|
-
return
|
|
2274
|
+
return errors;
|
|
1687
2275
|
}
|
|
1688
2276
|
function validatePropertyType(propertyName, property, filePath, customTypes = []) {
|
|
1689
2277
|
const errors = [];
|
|
1690
2278
|
if (!property.type) {
|
|
1691
2279
|
errors.push(
|
|
1692
|
-
missingFieldError("type",
|
|
2280
|
+
missingFieldError("type", buildLocation7(filePath))
|
|
1693
2281
|
);
|
|
1694
2282
|
return errors;
|
|
1695
2283
|
}
|
|
@@ -1699,7 +2287,7 @@ function validatePropertyType(propertyName, property, filePath, customTypes = []
|
|
|
1699
2287
|
errors.push(
|
|
1700
2288
|
invalidPropertyTypeError(
|
|
1701
2289
|
property.type,
|
|
1702
|
-
|
|
2290
|
+
buildLocation7(filePath),
|
|
1703
2291
|
typeRegistry.getAllTypeNames()
|
|
1704
2292
|
)
|
|
1705
2293
|
);
|
|
@@ -1741,7 +2329,7 @@ function validateAssociations(schema, allSchemas) {
|
|
|
1741
2329
|
errors.push(
|
|
1742
2330
|
validationError(
|
|
1743
2331
|
`Property '${name}' has invalid relation '${assocProp.relation}'`,
|
|
1744
|
-
|
|
2332
|
+
buildLocation7(schema.filePath),
|
|
1745
2333
|
`Use one of: ${VALID_RELATIONS.join(", ")}`
|
|
1746
2334
|
)
|
|
1747
2335
|
);
|
|
@@ -1754,7 +2342,7 @@ function validateAssociations(schema, allSchemas) {
|
|
|
1754
2342
|
errors.push(
|
|
1755
2343
|
invalidAssociationTargetError(
|
|
1756
2344
|
target,
|
|
1757
|
-
|
|
2345
|
+
buildLocation7(schema.filePath),
|
|
1758
2346
|
availableSchemas
|
|
1759
2347
|
)
|
|
1760
2348
|
);
|
|
@@ -1765,7 +2353,7 @@ function validateAssociations(schema, allSchemas) {
|
|
|
1765
2353
|
errors.push(
|
|
1766
2354
|
invalidAssociationTargetError(
|
|
1767
2355
|
assocProp.target,
|
|
1768
|
-
|
|
2356
|
+
buildLocation7(schema.filePath),
|
|
1769
2357
|
availableSchemas
|
|
1770
2358
|
)
|
|
1771
2359
|
);
|
|
@@ -1779,7 +2367,7 @@ function validateAssociations(schema, allSchemas) {
|
|
|
1779
2367
|
errors.push(
|
|
1780
2368
|
validationError(
|
|
1781
2369
|
`Property '${name}' references non-existent morphName '${assocProp.morphName}' on '${assocProp.target}'`,
|
|
1782
|
-
|
|
2370
|
+
buildLocation7(schema.filePath),
|
|
1783
2371
|
`Add a MorphTo property '${assocProp.morphName}' to ${assocProp.target} schema`
|
|
1784
2372
|
)
|
|
1785
2373
|
);
|
|
@@ -1789,7 +2377,7 @@ function validateAssociations(schema, allSchemas) {
|
|
|
1789
2377
|
errors.push(
|
|
1790
2378
|
validationError(
|
|
1791
2379
|
`Property '${name}' morphName '${assocProp.morphName}' on '${assocProp.target}' must be a MorphTo relation`,
|
|
1792
|
-
|
|
2380
|
+
buildLocation7(schema.filePath),
|
|
1793
2381
|
`Change ${assocProp.target}.${assocProp.morphName} relation to MorphTo`
|
|
1794
2382
|
)
|
|
1795
2383
|
);
|
|
@@ -1806,7 +2394,7 @@ function validateAssociations(schema, allSchemas) {
|
|
|
1806
2394
|
errors.push(
|
|
1807
2395
|
validationError(
|
|
1808
2396
|
`Property '${name}' references non-existent inverse property '${assocProp.inversedBy}' on '${assocProp.target}'`,
|
|
1809
|
-
|
|
2397
|
+
buildLocation7(schema.filePath),
|
|
1810
2398
|
`Add property '${assocProp.inversedBy}' to ${assocProp.target} schema`
|
|
1811
2399
|
)
|
|
1812
2400
|
);
|
|
@@ -1817,7 +2405,7 @@ function validateAssociations(schema, allSchemas) {
|
|
|
1817
2405
|
errors.push(
|
|
1818
2406
|
validationError(
|
|
1819
2407
|
`Property '${name}' has invalid onDelete action '${assocProp.onDelete}'`,
|
|
1820
|
-
|
|
2408
|
+
buildLocation7(schema.filePath),
|
|
1821
2409
|
`Use one of: ${VALID_REFERENTIAL_ACTIONS.join(", ")}`
|
|
1822
2410
|
)
|
|
1823
2411
|
);
|
|
@@ -1826,7 +2414,7 @@ function validateAssociations(schema, allSchemas) {
|
|
|
1826
2414
|
errors.push(
|
|
1827
2415
|
validationError(
|
|
1828
2416
|
`Property '${name}' has invalid onUpdate action '${assocProp.onUpdate}'`,
|
|
1829
|
-
|
|
2417
|
+
buildLocation7(schema.filePath),
|
|
1830
2418
|
`Use one of: ${VALID_REFERENTIAL_ACTIONS.join(", ")}`
|
|
1831
2419
|
)
|
|
1832
2420
|
);
|
|
@@ -1845,7 +2433,7 @@ function validateOptions(schema, filePath) {
|
|
|
1845
2433
|
errors.push(
|
|
1846
2434
|
validationError(
|
|
1847
2435
|
`Invalid idType '${options.idType}'`,
|
|
1848
|
-
|
|
2436
|
+
buildLocation7(filePath),
|
|
1849
2437
|
`Use one of: ${VALID_ID_TYPES.join(", ")}`
|
|
1850
2438
|
)
|
|
1851
2439
|
);
|
|
@@ -1860,7 +2448,7 @@ function validateOptions(schema, filePath) {
|
|
|
1860
2448
|
errors.push(
|
|
1861
2449
|
validationError(
|
|
1862
2450
|
`Unique constraint references non-existent property '${column}'`,
|
|
1863
|
-
|
|
2451
|
+
buildLocation7(filePath),
|
|
1864
2452
|
`Available properties: ${propertyNames.join(", ")}`
|
|
1865
2453
|
)
|
|
1866
2454
|
);
|
|
@@ -1871,12 +2459,13 @@ function validateOptions(schema, filePath) {
|
|
|
1871
2459
|
if (options.indexes && schema.properties) {
|
|
1872
2460
|
const propertyNames = Object.keys(schema.properties);
|
|
1873
2461
|
for (const index of options.indexes) {
|
|
1874
|
-
|
|
2462
|
+
const columns = typeof index === "string" ? [index] : index.columns;
|
|
2463
|
+
for (const column of columns) {
|
|
1875
2464
|
if (!propertyNames.includes(column)) {
|
|
1876
2465
|
errors.push(
|
|
1877
2466
|
validationError(
|
|
1878
2467
|
`Index references non-existent property '${column}'`,
|
|
1879
|
-
|
|
2468
|
+
buildLocation7(filePath),
|
|
1880
2469
|
`Available properties: ${propertyNames.join(", ")}`
|
|
1881
2470
|
)
|
|
1882
2471
|
);
|
|
@@ -1890,7 +2479,7 @@ function validateOptions(schema, filePath) {
|
|
|
1890
2479
|
errors.push(
|
|
1891
2480
|
validationError(
|
|
1892
2481
|
`authenticatableLoginIdField references non-existent property '${options.authenticatableLoginIdField}'`,
|
|
1893
|
-
|
|
2482
|
+
buildLocation7(filePath)
|
|
1894
2483
|
)
|
|
1895
2484
|
);
|
|
1896
2485
|
}
|
|
@@ -1900,7 +2489,7 @@ function validateOptions(schema, filePath) {
|
|
|
1900
2489
|
errors.push(
|
|
1901
2490
|
validationError(
|
|
1902
2491
|
`authenticatablePasswordField references non-existent property '${options.authenticatablePasswordField}'`,
|
|
1903
|
-
|
|
2492
|
+
buildLocation7(filePath)
|
|
1904
2493
|
)
|
|
1905
2494
|
);
|
|
1906
2495
|
}
|
|
@@ -1917,7 +2506,7 @@ function validateEnumSchema(schema, filePath) {
|
|
|
1917
2506
|
errors.push(
|
|
1918
2507
|
validationError(
|
|
1919
2508
|
"Enum schema requires at least one value",
|
|
1920
|
-
|
|
2509
|
+
buildLocation7(filePath),
|
|
1921
2510
|
"Add values: [value1, value2, ...]"
|
|
1922
2511
|
)
|
|
1923
2512
|
);
|
|
@@ -1929,7 +2518,7 @@ function validateEnumSchema(schema, filePath) {
|
|
|
1929
2518
|
errors.push(
|
|
1930
2519
|
validationError(
|
|
1931
2520
|
`Duplicate enum value '${value}'`,
|
|
1932
|
-
|
|
2521
|
+
buildLocation7(filePath)
|
|
1933
2522
|
)
|
|
1934
2523
|
);
|
|
1935
2524
|
}
|
|
@@ -1942,6 +2531,10 @@ function validateSchema(schema, options = {}) {
|
|
|
1942
2531
|
const errors = [];
|
|
1943
2532
|
const warnings = [];
|
|
1944
2533
|
const customTypes = options.customTypes ?? [];
|
|
2534
|
+
const unknownSchemaFieldErrors = validateUnknownSchemaFields(schema, schema.filePath);
|
|
2535
|
+
errors.push(...unknownSchemaFieldErrors);
|
|
2536
|
+
const unknownOptionsErrors = validateUnknownOptionsFields(schema, schema.filePath);
|
|
2537
|
+
errors.push(...unknownOptionsErrors);
|
|
1945
2538
|
const propErrors = validateProperties(schema, schema.filePath, customTypes);
|
|
1946
2539
|
errors.push(...propErrors);
|
|
1947
2540
|
const optErrors = validateOptions(schema, schema.filePath);
|
|
@@ -1955,7 +2548,7 @@ function validateSchema(schema, options = {}) {
|
|
|
1955
2548
|
errors.push(
|
|
1956
2549
|
validationError(
|
|
1957
2550
|
`titleIndex references non-existent property '${schema.titleIndex}'`,
|
|
1958
|
-
|
|
2551
|
+
buildLocation7(schema.filePath),
|
|
1959
2552
|
`Available properties: ${Object.keys(schema.properties).join(", ")}`
|
|
1960
2553
|
)
|
|
1961
2554
|
);
|
|
@@ -1963,8 +2556,8 @@ function validateSchema(schema, options = {}) {
|
|
|
1963
2556
|
}
|
|
1964
2557
|
if (schema.properties) {
|
|
1965
2558
|
for (const [name, property] of Object.entries(schema.properties)) {
|
|
1966
|
-
const
|
|
1967
|
-
|
|
2559
|
+
const unknownFieldErrors = validateUnknownFields(name, property, schema.filePath);
|
|
2560
|
+
errors.push(...unknownFieldErrors);
|
|
1968
2561
|
const dbCompat = validateDbCompatibility(name, property, schema.filePath, options.databaseDriver);
|
|
1969
2562
|
errors.push(...dbCompat.errors);
|
|
1970
2563
|
warnings.push(...dbCompat.warnings);
|
|
@@ -3054,6 +3647,31 @@ var import_path = require("path");
|
|
|
3054
3647
|
var import_js_yaml2 = __toESM(require("js-yaml"), 1);
|
|
3055
3648
|
var OMNIFY_DIR = ".omnify";
|
|
3056
3649
|
var VERSIONS_DIR = "versions";
|
|
3650
|
+
function deepEqual(a, b) {
|
|
3651
|
+
if (a === b) return true;
|
|
3652
|
+
if (a === null || b === null) return a === b;
|
|
3653
|
+
if (typeof a !== typeof b) return false;
|
|
3654
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
3655
|
+
if (a.length !== b.length) return false;
|
|
3656
|
+
for (let i = 0; i < a.length; i++) {
|
|
3657
|
+
if (!deepEqual(a[i], b[i])) return false;
|
|
3658
|
+
}
|
|
3659
|
+
return true;
|
|
3660
|
+
}
|
|
3661
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
3662
|
+
const aObj = a;
|
|
3663
|
+
const bObj = b;
|
|
3664
|
+
const aKeys = Object.keys(aObj);
|
|
3665
|
+
const bKeys = Object.keys(bObj);
|
|
3666
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
3667
|
+
for (const key of aKeys) {
|
|
3668
|
+
if (!Object.prototype.hasOwnProperty.call(bObj, key)) return false;
|
|
3669
|
+
if (!deepEqual(aObj[key], bObj[key])) return false;
|
|
3670
|
+
}
|
|
3671
|
+
return true;
|
|
3672
|
+
}
|
|
3673
|
+
return false;
|
|
3674
|
+
}
|
|
3057
3675
|
var CURRENT_FILE = "current.lock";
|
|
3058
3676
|
var VERSION_FILE_PATTERN = /^(\d{4})(?:_(.+))?\.lock$/;
|
|
3059
3677
|
function formatVersionNumber(version) {
|
|
@@ -3312,7 +3930,7 @@ var VersionStore = class {
|
|
|
3312
3930
|
}
|
|
3313
3931
|
for (const prop of fromPropNames) {
|
|
3314
3932
|
if (!toPropNames.has(prop)) continue;
|
|
3315
|
-
if (
|
|
3933
|
+
if (!deepEqual(fromProps[prop], toProps[prop])) {
|
|
3316
3934
|
changes.push({
|
|
3317
3935
|
action: "property_modified",
|
|
3318
3936
|
schema: name,
|
|
@@ -3322,13 +3940,78 @@ var VersionStore = class {
|
|
|
3322
3940
|
});
|
|
3323
3941
|
}
|
|
3324
3942
|
}
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3943
|
+
const fromOpts = fromSchema.options ?? {};
|
|
3944
|
+
const toOpts = toSchema.options ?? {};
|
|
3945
|
+
const optionDefaults = {
|
|
3946
|
+
id: true,
|
|
3947
|
+
idType: "BigInt",
|
|
3948
|
+
timestamps: true,
|
|
3949
|
+
softDelete: false,
|
|
3950
|
+
tableName: void 0,
|
|
3951
|
+
// no default
|
|
3952
|
+
translations: false,
|
|
3953
|
+
authenticatable: false
|
|
3954
|
+
};
|
|
3955
|
+
const getNormalizedOption = (opts, key) => {
|
|
3956
|
+
const value = opts[key];
|
|
3957
|
+
if (value === void 0) {
|
|
3958
|
+
return optionDefaults[key];
|
|
3959
|
+
}
|
|
3960
|
+
return value;
|
|
3961
|
+
};
|
|
3962
|
+
const simpleOptions = ["timestamps", "softDelete", "id", "idType", "tableName", "translations", "authenticatable"];
|
|
3963
|
+
for (const opt of simpleOptions) {
|
|
3964
|
+
const fromVal = getNormalizedOption(fromOpts, opt);
|
|
3965
|
+
const toVal = getNormalizedOption(toOpts, opt);
|
|
3966
|
+
if (!deepEqual(fromVal, toVal)) {
|
|
3967
|
+
changes.push({
|
|
3968
|
+
action: "option_changed",
|
|
3969
|
+
schema: name,
|
|
3970
|
+
property: opt,
|
|
3971
|
+
from: fromOpts[opt],
|
|
3972
|
+
// Keep original value for display
|
|
3973
|
+
to: toOpts[opt]
|
|
3974
|
+
});
|
|
3975
|
+
}
|
|
3976
|
+
}
|
|
3977
|
+
const fromIndexes = fromOpts.indexes ?? [];
|
|
3978
|
+
const toIndexes = toOpts.indexes ?? [];
|
|
3979
|
+
const columnsMatch = (a, b) => {
|
|
3980
|
+
if (a.length !== b.length) return false;
|
|
3981
|
+
return a.every((col, i) => col === b[i]);
|
|
3982
|
+
};
|
|
3983
|
+
const matchedFromIndexes = /* @__PURE__ */ new Set();
|
|
3984
|
+
for (const toIdx of toIndexes) {
|
|
3985
|
+
const fromIndex = fromIndexes.findIndex(
|
|
3986
|
+
(fromIdx, i) => !matchedFromIndexes.has(i) && columnsMatch(fromIdx.columns, toIdx.columns)
|
|
3987
|
+
);
|
|
3988
|
+
if (fromIndex === -1) {
|
|
3989
|
+
changes.push({
|
|
3990
|
+
action: "index_added",
|
|
3991
|
+
schema: name,
|
|
3992
|
+
to: toIdx
|
|
3993
|
+
});
|
|
3994
|
+
} else {
|
|
3995
|
+
matchedFromIndexes.add(fromIndex);
|
|
3996
|
+
const fromIdx = fromIndexes[fromIndex];
|
|
3997
|
+
if (!deepEqual(fromIdx, toIdx)) {
|
|
3998
|
+
changes.push({
|
|
3999
|
+
action: "index_modified",
|
|
4000
|
+
schema: name,
|
|
4001
|
+
from: fromIdx,
|
|
4002
|
+
to: toIdx
|
|
4003
|
+
});
|
|
4004
|
+
}
|
|
4005
|
+
}
|
|
4006
|
+
}
|
|
4007
|
+
for (let i = 0; i < fromIndexes.length; i++) {
|
|
4008
|
+
if (!matchedFromIndexes.has(i)) {
|
|
4009
|
+
changes.push({
|
|
4010
|
+
action: "index_removed",
|
|
4011
|
+
schema: name,
|
|
4012
|
+
from: fromIndexes[i]
|
|
4013
|
+
});
|
|
4014
|
+
}
|
|
3332
4015
|
}
|
|
3333
4016
|
}
|
|
3334
4017
|
return changes;
|
|
@@ -3346,6 +4029,7 @@ function createVersionStore(config) {
|
|
|
3346
4029
|
}
|
|
3347
4030
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3348
4031
|
0 && (module.exports = {
|
|
4032
|
+
FILE_SCHEMA_NAME,
|
|
3349
4033
|
Omnify,
|
|
3350
4034
|
OmnifyError,
|
|
3351
4035
|
PluginManager,
|
|
@@ -3355,10 +4039,13 @@ function createVersionStore(config) {
|
|
|
3355
4039
|
circularReferenceError,
|
|
3356
4040
|
configError,
|
|
3357
4041
|
configNotFoundError,
|
|
4042
|
+
createFileLoadedSchema,
|
|
4043
|
+
createFileSchemaDefinition,
|
|
3358
4044
|
createOmnify,
|
|
3359
4045
|
createPluginManager,
|
|
3360
4046
|
createVersionStore,
|
|
3361
4047
|
duplicateSchemaError,
|
|
4048
|
+
ensureFileSchema,
|
|
3362
4049
|
err,
|
|
3363
4050
|
expandProperty,
|
|
3364
4051
|
expandSchema,
|
|
@@ -3370,6 +4057,7 @@ function createVersionStore(config) {
|
|
|
3370
4057
|
formatError,
|
|
3371
4058
|
formatErrorPlain,
|
|
3372
4059
|
formatErrorSummary,
|
|
4060
|
+
generateFileSchemaYaml,
|
|
3373
4061
|
generationError,
|
|
3374
4062
|
getAssociationMetadata,
|
|
3375
4063
|
getCustomTypeNames,
|
|
@@ -3408,7 +4096,9 @@ function createVersionStore(config) {
|
|
|
3408
4096
|
pluginTypeConflictError,
|
|
3409
4097
|
schemaNotFoundError,
|
|
3410
4098
|
schemaParseError,
|
|
4099
|
+
schemasHaveFileProperties,
|
|
3411
4100
|
validateAssociations,
|
|
4101
|
+
validateDefaultValue,
|
|
3412
4102
|
validateEnumSchema,
|
|
3413
4103
|
validateOptions,
|
|
3414
4104
|
validateProperties,
|