@revisium/schema-toolkit 0.11.1 → 0.12.1

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.
@@ -2,6 +2,8 @@
2
2
 
3
3
  var chunkEGC32GPY_cjs = require('./chunk-EGC32GPY.cjs');
4
4
  var chunkTMWPWGUN_cjs = require('./chunk-TMWPWGUN.cjs');
5
+ var formula = require('@revisium/formula');
6
+ var spec = require('@revisium/formula/spec');
5
7
 
6
8
  // src/lib/createJsonSchemaStore.ts
7
9
  var createJsonSchemaStore = (schema, refs = {}) => {
@@ -571,6 +573,497 @@ function createChange(path, oldValue, newValue, changeType) {
571
573
  return { path, oldValue, newValue, changeType };
572
574
  }
573
575
 
576
+ // src/lib/extract-schema-formulas.ts
577
+ function extractSchemaFormulas(schema) {
578
+ const formulas = [];
579
+ extractFormulasRecursive(schema, "", formulas);
580
+ return formulas;
581
+ }
582
+ function extractFormulasRecursive(schema, pathPrefix, formulas) {
583
+ if (schema.type === "array" && schema.items) {
584
+ extractFormulasRecursive(schema.items, `${pathPrefix}[]`, formulas);
585
+ return;
586
+ }
587
+ const properties = schema.properties ?? {};
588
+ for (const [fieldName, fieldSchema] of Object.entries(properties)) {
589
+ const fullPath = pathPrefix ? `${pathPrefix}.${fieldName}` : fieldName;
590
+ const xFormula = fieldSchema["x-formula"];
591
+ if (xFormula) {
592
+ formulas.push({
593
+ fieldName: fullPath,
594
+ expression: xFormula.expression,
595
+ fieldType: fieldSchema.type ?? "string"
596
+ });
597
+ }
598
+ if (fieldSchema.type === "object" && fieldSchema.properties) {
599
+ extractFormulasRecursive(fieldSchema, fullPath, formulas);
600
+ }
601
+ if (fieldSchema.type === "array" && fieldSchema.items) {
602
+ extractFormulasRecursive(fieldSchema.items, `${fullPath}[]`, formulas);
603
+ }
604
+ }
605
+ }
606
+ function validateSchemaFormulas(schema) {
607
+ const errors = [];
608
+ const formulas = extractSchemaFormulas(schema);
609
+ for (const formula of formulas) {
610
+ const error = validateFormulaAgainstSchema(
611
+ formula.expression,
612
+ formula.fieldName,
613
+ schema
614
+ );
615
+ if (error) {
616
+ errors.push(error);
617
+ }
618
+ }
619
+ if (errors.length > 0) {
620
+ return { isValid: false, errors };
621
+ }
622
+ const dependencies = {};
623
+ for (const formula$1 of formulas) {
624
+ const parseResult = formula.parseExpression(formula$1.expression);
625
+ const parentPath = getParentPath(formula$1.fieldName);
626
+ const prefix = parentPath ? `${parentPath}.` : "";
627
+ dependencies[formula$1.fieldName] = parseResult.dependencies.map((dep) => {
628
+ if (dep.startsWith("/")) {
629
+ return extractFieldRoot(dep.slice(1));
630
+ }
631
+ if (dep.startsWith("../")) {
632
+ const fieldWithoutPrefix = dep.replace(/^(\.\.\/)+/, "");
633
+ return extractFieldRoot(fieldWithoutPrefix);
634
+ }
635
+ const rootField = extractFieldRoot(dep);
636
+ return `${prefix}${rootField}`;
637
+ });
638
+ }
639
+ const graph = formula.buildDependencyGraph(dependencies);
640
+ const circularCheck = formula.detectCircularDependencies(graph);
641
+ const cycle = circularCheck.cycle;
642
+ if (circularCheck.hasCircular && cycle && cycle.length > 0) {
643
+ const firstField = cycle[0];
644
+ if (firstField) {
645
+ errors.push({
646
+ field: firstField,
647
+ error: `Circular dependency: ${cycle.join(" \u2192 ")}`
648
+ });
649
+ return { isValid: false, errors };
650
+ }
651
+ }
652
+ return { isValid: true, errors: [] };
653
+ }
654
+ function validateFormulaAgainstSchema(expression, fieldName, schema) {
655
+ return validateFormulaInContext(expression, fieldName, schema);
656
+ }
657
+ function validateFormulaInContext(expression, fieldPath, rootSchema) {
658
+ const syntaxResult = formula.validateFormulaSyntax(expression);
659
+ if (!syntaxResult.isValid) {
660
+ return {
661
+ field: fieldPath,
662
+ error: syntaxResult.error,
663
+ position: syntaxResult.position
664
+ };
665
+ }
666
+ const parentPath = getParentPath(fieldPath);
667
+ const localFieldName = getFieldName(fieldPath);
668
+ const contextSchema = resolveSubSchema(rootSchema, parentPath);
669
+ if (!contextSchema) {
670
+ return {
671
+ field: fieldPath,
672
+ error: `Cannot resolve schema context for path '${parentPath}'`
673
+ };
674
+ }
675
+ const parseResult = formula.parseExpression(expression);
676
+ const localSchemaFields = getSchemaFields(contextSchema);
677
+ const rootSchemaFields = getSchemaFields(rootSchema);
678
+ for (const dep of parseResult.dependencies) {
679
+ if (dep.startsWith("/")) {
680
+ const rootField = extractFieldRoot(dep.slice(1));
681
+ if (!rootSchemaFields.has(rootField)) {
682
+ return {
683
+ field: fieldPath,
684
+ error: `Unknown root field '${rootField}' in formula`
685
+ };
686
+ }
687
+ } else if (dep.startsWith("../")) {
688
+ const fieldWithoutPrefix = dep.replace(/^(\.\.\/)+/, "");
689
+ const rootField = extractFieldRoot(fieldWithoutPrefix);
690
+ if (!rootSchemaFields.has(rootField)) {
691
+ return {
692
+ field: fieldPath,
693
+ error: `Unknown root field '${rootField}' in formula`
694
+ };
695
+ }
696
+ } else {
697
+ const rootField = extractFieldRoot(dep);
698
+ if (!localSchemaFields.has(rootField)) {
699
+ return {
700
+ field: fieldPath,
701
+ error: `Unknown field '${rootField}' in formula`
702
+ };
703
+ }
704
+ }
705
+ }
706
+ if (parseResult.dependencies.some((d) => extractFieldRoot(d) === localFieldName)) {
707
+ return {
708
+ field: fieldPath,
709
+ error: `Formula cannot reference itself`
710
+ };
711
+ }
712
+ const fieldSchema = contextSchema.properties?.[localFieldName];
713
+ const expectedType = schemaTypeToInferred(fieldSchema?.type);
714
+ const fieldTypes = getSchemaFieldTypes(contextSchema);
715
+ const inferredType = formula.inferFormulaType(expression, fieldTypes);
716
+ if (!isTypeCompatible(inferredType, expectedType)) {
717
+ return {
718
+ field: fieldPath,
719
+ error: `Type mismatch: formula returns '${inferredType}' but field expects '${expectedType}'`
720
+ };
721
+ }
722
+ return null;
723
+ }
724
+ function resolveSubSchema(schema, fieldPath) {
725
+ if (!fieldPath) {
726
+ return schema;
727
+ }
728
+ const segments = parsePathSegments2(fieldPath);
729
+ let current = schema;
730
+ for (const segment of segments) {
731
+ if (segment === "[]") {
732
+ if (current.type === "array" && current.items) {
733
+ current = current.items;
734
+ } else {
735
+ return null;
736
+ }
737
+ } else if (current.properties?.[segment]) {
738
+ current = current.properties[segment];
739
+ } else {
740
+ return null;
741
+ }
742
+ }
743
+ return current;
744
+ }
745
+ function parsePathSegments2(path) {
746
+ const segments = [];
747
+ let current = "";
748
+ let inBracket = false;
749
+ for (const char of path) {
750
+ if (char === "[") {
751
+ if (current) {
752
+ segments.push(current);
753
+ current = "";
754
+ }
755
+ inBracket = true;
756
+ } else if (char === "]") {
757
+ inBracket = false;
758
+ segments.push("[]");
759
+ } else if (char === "." && !inBracket) {
760
+ if (current) {
761
+ segments.push(current);
762
+ current = "";
763
+ }
764
+ } else if (!inBracket) {
765
+ current += char;
766
+ }
767
+ }
768
+ if (current) {
769
+ segments.push(current);
770
+ }
771
+ return segments;
772
+ }
773
+ function getParentPath(fieldPath) {
774
+ const lastDotIndex = fieldPath.lastIndexOf(".");
775
+ const lastBracketIndex = fieldPath.lastIndexOf("[");
776
+ const splitIndex = Math.max(lastDotIndex, lastBracketIndex);
777
+ if (splitIndex <= 0) {
778
+ return "";
779
+ }
780
+ return fieldPath.substring(0, splitIndex);
781
+ }
782
+ function getFieldName(fieldPath) {
783
+ const lastDotIndex = fieldPath.lastIndexOf(".");
784
+ const lastBracketIndex = fieldPath.lastIndexOf("]");
785
+ const splitIndex = Math.max(lastDotIndex, lastBracketIndex);
786
+ if (splitIndex === -1) {
787
+ return fieldPath;
788
+ }
789
+ return fieldPath.substring(splitIndex + 1);
790
+ }
791
+ function getSchemaFields(schema) {
792
+ const fields = /* @__PURE__ */ new Set();
793
+ const properties = schema.properties ?? {};
794
+ for (const fieldName of Object.keys(properties)) {
795
+ fields.add(fieldName);
796
+ }
797
+ return fields;
798
+ }
799
+ function getSchemaFieldTypes(schema) {
800
+ const fieldTypes = {};
801
+ const properties = schema.properties ?? {};
802
+ for (const [fieldName, fieldSchema] of Object.entries(properties)) {
803
+ const schemaType = fieldSchema.type;
804
+ if (schemaType === "integer") {
805
+ fieldTypes[fieldName] = "number";
806
+ } else if (schemaType === "number" || schemaType === "string" || schemaType === "boolean" || schemaType === "object" || schemaType === "array") {
807
+ fieldTypes[fieldName] = schemaType;
808
+ }
809
+ }
810
+ return fieldTypes;
811
+ }
812
+ function schemaTypeToInferred(schemaType) {
813
+ if (schemaType === "number" || schemaType === "integer") return "number";
814
+ if (schemaType === "string") return "string";
815
+ if (schemaType === "boolean") return "boolean";
816
+ return null;
817
+ }
818
+ function isTypeCompatible(inferredType, expectedType) {
819
+ if (expectedType === null) return true;
820
+ if (inferredType === "unknown") return true;
821
+ return inferredType === expectedType;
822
+ }
823
+ function extractFieldRoot(dependency) {
824
+ const root = dependency.split(".")[0]?.split("[")[0];
825
+ return root || dependency;
826
+ }
827
+
828
+ // src/lib/formula.ts
829
+ function prepareFormulas(schema) {
830
+ const formulas = extractSchemaFormulas(schema);
831
+ if (formulas.length === 0) {
832
+ return [];
833
+ }
834
+ const formulasWithMeta = formulas.map((f) => enrichFormula(f));
835
+ if (formulas.length <= 1) {
836
+ return formulasWithMeta;
837
+ }
838
+ return orderByDependencies(formulasWithMeta);
839
+ }
840
+ function evaluateFormulas(formulas, data, options = {}) {
841
+ const values = {};
842
+ const errors = [];
843
+ const failedFields = /* @__PURE__ */ new Set();
844
+ for (const formula of formulas) {
845
+ const hasDependencyFailure = formula.dependencies.some(
846
+ (dep) => failedFields.has(dep)
847
+ );
848
+ if (hasDependencyFailure) {
849
+ failedFields.add(formula.fieldName);
850
+ if (options.useDefaults) {
851
+ const defaultValue = getDefaultValue(formula, options.defaults);
852
+ setValueByPath2(values, formula.fieldName, defaultValue);
853
+ setValueByPath2(data, formula.fieldName, defaultValue);
854
+ }
855
+ errors.push(
856
+ createError(formula, "Dependency formula failed", options.useDefaults ?? false)
857
+ );
858
+ continue;
859
+ }
860
+ const formulaErrors = evaluateFormula(formula, data, values, options);
861
+ if (formulaErrors.length > 0) {
862
+ errors.push(...formulaErrors);
863
+ failedFields.add(formula.fieldName);
864
+ }
865
+ }
866
+ return { values, errors };
867
+ }
868
+ function enrichFormula(formula) {
869
+ const dependencies = parseDependencies(formula.expression);
870
+ const pathInfo = parseArrayItemPath(formula.fieldName);
871
+ return { ...formula, dependencies, ...pathInfo };
872
+ }
873
+ function parseDependencies(expression) {
874
+ try {
875
+ return formula.parseFormula(expression).dependencies;
876
+ } catch {
877
+ return [];
878
+ }
879
+ }
880
+ function parseArrayItemPath(fieldName) {
881
+ const bracketIndex = fieldName.indexOf("[]");
882
+ if (bracketIndex === -1) {
883
+ return { isArrayItem: false, arrayPath: null, localFieldPath: fieldName };
884
+ }
885
+ const arrayPath = fieldName.slice(0, bracketIndex);
886
+ const afterBrackets = fieldName.slice(bracketIndex + 2);
887
+ const localFieldPath = afterBrackets.startsWith(".") ? afterBrackets.slice(1) : afterBrackets;
888
+ return { isArrayItem: true, arrayPath, localFieldPath };
889
+ }
890
+ function orderByDependencies(formulas) {
891
+ const dependencies = Object.fromEntries(
892
+ formulas.map((f) => [f.fieldName, f.dependencies])
893
+ );
894
+ const result = formula.getTopologicalOrder(formula.buildDependencyGraph(dependencies));
895
+ if (!result.success) {
896
+ throw new Error(
897
+ `Cyclic dependency detected in formulas: ${result.error ?? "unknown error"}`
898
+ );
899
+ }
900
+ const formulaMap = new Map(formulas.map((f) => [f.fieldName, f]));
901
+ return result.order.map((name) => formulaMap.get(name)).filter((f) => f !== void 0);
902
+ }
903
+ function evaluateFormula(formula, data, values, options) {
904
+ if (formula.isArrayItem && formula.arrayPath) {
905
+ return evaluateArrayFormula(formula, data, values, options);
906
+ }
907
+ return evaluateSingleFormula(formula, data, values, options);
908
+ }
909
+ function evaluateSingleFormula(formula$1, data, values, options) {
910
+ const parentPath = getParentPath2(formula$1.fieldName);
911
+ const itemData = parentPath ? getValueByPath2(data, parentPath) : void 0;
912
+ const context = {
913
+ rootData: data,
914
+ ...itemData && { itemData, currentPath: parentPath }
915
+ };
916
+ try {
917
+ const result = formula.evaluateWithContext(formula$1.expression, context);
918
+ if (result === void 0) {
919
+ if (options.useDefaults) {
920
+ const defaultValue = getDefaultValue(formula$1, options.defaults);
921
+ setValueByPath2(values, formula$1.fieldName, defaultValue);
922
+ setValueByPath2(data, formula$1.fieldName, defaultValue);
923
+ }
924
+ return [
925
+ createError(
926
+ formula$1,
927
+ "Formula returned undefined",
928
+ options.useDefaults ?? false
929
+ )
930
+ ];
931
+ }
932
+ setValueByPath2(values, formula$1.fieldName, result);
933
+ setValueByPath2(data, formula$1.fieldName, result);
934
+ return [];
935
+ } catch (error) {
936
+ if (options.useDefaults) {
937
+ const defaultValue = getDefaultValue(formula$1, options.defaults);
938
+ setValueByPath2(values, formula$1.fieldName, defaultValue);
939
+ setValueByPath2(data, formula$1.fieldName, defaultValue);
940
+ }
941
+ return [
942
+ createError(
943
+ formula$1,
944
+ error instanceof Error ? error.message : String(error),
945
+ options.useDefaults ?? false
946
+ )
947
+ ];
948
+ }
949
+ }
950
+ function evaluateArrayFormula(formula$1, data, values, options) {
951
+ const errors = [];
952
+ const arrayPath = formula$1.arrayPath;
953
+ const array = getValueByPath2(data, arrayPath);
954
+ if (!Array.isArray(array)) {
955
+ return errors;
956
+ }
957
+ for (let i = 0; i < array.length; i++) {
958
+ const item = array[i];
959
+ if (typeof item !== "object" || item === null) {
960
+ continue;
961
+ }
962
+ const itemData = item;
963
+ const fieldPath = `${arrayPath}[${i}].${formula$1.localFieldPath}`;
964
+ const context = {
965
+ rootData: data,
966
+ itemData,
967
+ currentPath: `${arrayPath}[${i}]`
968
+ };
969
+ try {
970
+ const result = formula.evaluateWithContext(formula$1.expression, context);
971
+ if (result === void 0) {
972
+ if (options.useDefaults) {
973
+ const defaultValue = getDefaultValue(formula$1, options.defaults);
974
+ setValueByPath2(values, fieldPath, defaultValue);
975
+ setValueByPath2(itemData, formula$1.localFieldPath, defaultValue);
976
+ }
977
+ errors.push({
978
+ field: fieldPath,
979
+ expression: formula$1.expression,
980
+ error: "Formula returned undefined",
981
+ defaultUsed: options.useDefaults ?? false
982
+ });
983
+ continue;
984
+ }
985
+ setValueByPath2(values, fieldPath, result);
986
+ setValueByPath2(itemData, formula$1.localFieldPath, result);
987
+ } catch (error) {
988
+ if (options.useDefaults) {
989
+ const defaultValue = getDefaultValue(formula$1, options.defaults);
990
+ setValueByPath2(values, fieldPath, defaultValue);
991
+ setValueByPath2(itemData, formula$1.localFieldPath, defaultValue);
992
+ }
993
+ errors.push({
994
+ field: fieldPath,
995
+ expression: formula$1.expression,
996
+ error: error instanceof Error ? error.message : String(error),
997
+ defaultUsed: options.useDefaults ?? false
998
+ });
999
+ }
1000
+ }
1001
+ return errors;
1002
+ }
1003
+ function getParentPath2(fieldPath) {
1004
+ const lastDotIndex = fieldPath.lastIndexOf(".");
1005
+ if (lastDotIndex === -1) {
1006
+ return "";
1007
+ }
1008
+ return fieldPath.substring(0, lastDotIndex);
1009
+ }
1010
+ function getValueByPath2(obj, path) {
1011
+ const segments = path.split(".");
1012
+ let current = obj;
1013
+ for (const segment of segments) {
1014
+ if (current === null || current === void 0) {
1015
+ return void 0;
1016
+ }
1017
+ current = current[segment];
1018
+ }
1019
+ return current;
1020
+ }
1021
+ function isSafeKey(key) {
1022
+ return key !== "__proto__";
1023
+ }
1024
+ function setValueByPath2(obj, path, value) {
1025
+ const segments = path.split(".");
1026
+ let current = obj;
1027
+ for (let i = 0; i < segments.length - 1; i++) {
1028
+ const segment = segments[i];
1029
+ if (!isSafeKey(segment)) {
1030
+ return;
1031
+ }
1032
+ if (!(segment in current)) {
1033
+ current[segment] = {};
1034
+ }
1035
+ current = current[segment];
1036
+ }
1037
+ const lastSegment = segments.at(-1);
1038
+ if (!isSafeKey(lastSegment)) {
1039
+ return;
1040
+ }
1041
+ current[lastSegment] = value;
1042
+ }
1043
+ function getDefaultValue(formula, defaults) {
1044
+ if (defaults && formula.fieldName in defaults) {
1045
+ return defaults[formula.fieldName];
1046
+ }
1047
+ switch (formula.fieldType) {
1048
+ case "number":
1049
+ return 0;
1050
+ case "string":
1051
+ return "";
1052
+ case "boolean":
1053
+ return false;
1054
+ default:
1055
+ return null;
1056
+ }
1057
+ }
1058
+ function createError(formula, error, defaultUsed) {
1059
+ return {
1060
+ field: formula.fieldName,
1061
+ expression: formula.expression,
1062
+ error,
1063
+ defaultUsed
1064
+ };
1065
+ }
1066
+
574
1067
  // src/lib/getDBJsonPathByJsonSchemaStore.ts
575
1068
  var getDBJsonPathByJsonSchemaStore = (store) => {
576
1069
  let node = store;
@@ -824,6 +1317,10 @@ var SchemaTable = class {
824
1317
  }
825
1318
  };
826
1319
 
1320
+ Object.defineProperty(exports, "formulaSpec", {
1321
+ enumerable: true,
1322
+ get: function () { return spec.formulaSpec; }
1323
+ });
827
1324
  exports.SchemaTable = SchemaTable;
828
1325
  exports.VALIDATE_JSON_FIELD_NAME_ERROR_MESSAGE = VALIDATE_JSON_FIELD_NAME_ERROR_MESSAGE;
829
1326
  exports.applyAddPatch = applyAddPatch;
@@ -837,6 +1334,8 @@ exports.createJsonObjectSchemaStore = createJsonObjectSchemaStore;
837
1334
  exports.createJsonSchemaStore = createJsonSchemaStore;
838
1335
  exports.createPrimitiveStoreBySchema = createPrimitiveStoreBySchema;
839
1336
  exports.deepEqual = deepEqual;
1337
+ exports.evaluateFormulas = evaluateFormulas;
1338
+ exports.extractSchemaFormulas = extractSchemaFormulas;
840
1339
  exports.getDBJsonPathByJsonSchemaStore = getDBJsonPathByJsonSchemaStore;
841
1340
  exports.getForeignKeyPatchesFromSchema = getForeignKeyPatchesFromSchema;
842
1341
  exports.getForeignKeysFromSchema = getForeignKeysFromSchema;
@@ -850,12 +1349,15 @@ exports.getValueByPath = getValueByPath;
850
1349
  exports.hasPath = hasPath;
851
1350
  exports.parsePath = parsePath;
852
1351
  exports.pluginRefs = pluginRefs;
1352
+ exports.prepareFormulas = prepareFormulas;
853
1353
  exports.replaceForeignKeyValue = replaceForeignKeyValue;
854
1354
  exports.resolveRefs = resolveRefs;
855
1355
  exports.saveSharedFields = saveSharedFields;
856
1356
  exports.setValueByPath = setValueByPath;
857
1357
  exports.traverseStore = traverseStore;
858
1358
  exports.traverseValue = traverseValue;
1359
+ exports.validateFormulaAgainstSchema = validateFormulaAgainstSchema;
859
1360
  exports.validateJsonFieldName = validateJsonFieldName;
860
- //# sourceMappingURL=chunk-UJBE43WI.cjs.map
861
- //# sourceMappingURL=chunk-UJBE43WI.cjs.map
1361
+ exports.validateSchemaFormulas = validateSchemaFormulas;
1362
+ //# sourceMappingURL=chunk-M6IMV3AN.cjs.map
1363
+ //# sourceMappingURL=chunk-M6IMV3AN.cjs.map