@accordproject/template-engine 2.8.0 → 3.0.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.
package/dist/index.mjs CHANGED
@@ -101,6 +101,16 @@ function integerDrafter(value, format) {
101
101
  }
102
102
  }
103
103
 
104
+ function isDuration(value) {
105
+ return value != null && typeof value === "object" && "amount" in value && "unit" in value && typeof value.amount === "number" && typeof value.unit === "string";
106
+ }
107
+ function durationDrafter(value) {
108
+ if (!isDuration(value)) {
109
+ return "0 unknown";
110
+ }
111
+ return `${value.amount} ${value.unit}`;
112
+ }
113
+
104
114
  function longDrafter(value, format) {
105
115
  if (format) {
106
116
  return draftIntegerFormat(value, format);
@@ -189,6 +199,10 @@ function getDrafter(typeName) {
189
199
  return longDrafter;
190
200
  case "org.accordproject.money@0.3.0.MonetaryAmount":
191
201
  return monetaryAmountDrafter;
202
+ case "org.accordproject.time@0.3.0.Duration":
203
+ return durationDrafter;
204
+ case "org.accordproject.time@0.3.0.Period":
205
+ return durationDrafter;
192
206
  case "String":
193
207
  return stringDrafter;
194
208
  default:
@@ -679,7 +693,7 @@ function getJsonPath(rootData, currentNode, paths) {
679
693
  }
680
694
  }
681
695
  }
682
- if (currentNode.name !== "this") {
696
+ if (currentNode.name !== "this" && currentNode.name !== "top") {
683
697
  withPath.push(`['${currentNode.name}']`);
684
698
  }
685
699
  return withPath.length > 0 ? `$${withPath.join("")}` : "$";
@@ -869,8 +883,17 @@ async function generateAgreement(modelManager, clauseLibrary, templateMark, data
869
883
  }
870
884
  } else if (CONDITIONAL_DEFINITION_RE.test(nodeClass)) {
871
885
  if (context.condition) {
872
- const result = userCodeResults[this.path.join("/")];
873
- context.isTrue = !!result;
886
+ const key = this.path.join("/");
887
+ const resultStr = userCodeResults[key];
888
+ if (resultStr === void 0 || resultStr === null) {
889
+ context.isTrue = false;
890
+ } else {
891
+ try {
892
+ context.isTrue = !!JSON.parse(resultStr);
893
+ } catch (err) {
894
+ throw new Error(`Invalid JSON boolean result for condition '${key}': ${String(err)}`);
895
+ }
896
+ }
874
897
  } else {
875
898
  const path = getJsonPath(templateMark, context, this.path);
876
899
  const variableValues = jp.query(data, path, 1);
@@ -891,16 +914,27 @@ async function generateAgreement(modelManager, clauseLibrary, templateMark, data
891
914
  } else if (CLAUSE_DEFINITION_RE.test(nodeClass)) {
892
915
  const path = getJsonPath(templateMark, context, this.path);
893
916
  const variableValues = jp.query(data, path, 1);
894
- if (context.name !== "top" && (variableValues.length === 0 || variableValues[0] === void 0 || variableValues[0] === null)) {
895
- delete context.nodes;
896
- stopHere = true;
897
- } else if (context.condition) {
917
+ if (context.condition) {
898
918
  checkCode(context.condition);
899
- const result = !!userCodeResults[this.path.join("/")];
919
+ const key = this.path.join("/");
920
+ const resultStr = userCodeResults[key];
921
+ let result = false;
922
+ if (resultStr === void 0 || resultStr === null) {
923
+ result = false;
924
+ } else {
925
+ try {
926
+ result = !!JSON.parse(resultStr);
927
+ } catch (err) {
928
+ throw new Error(`Invalid JSON boolean result for condition '${key}': ${String(err)}`);
929
+ }
930
+ }
900
931
  if (!result) {
901
932
  delete context.nodes;
902
933
  stopHere = true;
903
934
  }
935
+ } else if (context.name !== "top" && (variableValues.length === 0 || variableValues[0] === void 0 || variableValues[0] === null)) {
936
+ delete context.nodes;
937
+ stopHere = true;
904
938
  }
905
939
  delete context.condition;
906
940
  delete context.functionName;
@@ -949,18 +983,53 @@ class TemplateMarkInterpreter {
949
983
  * @throws {Error} if the templateMark document is invalid
950
984
  */
951
985
  checkTypes(templateMark) {
952
- const modelManager = new ModelManager({ strict: true });
986
+ const modelManager = new ModelManager();
953
987
  modelManager.addCTOModel(ConcertoMetaModel.MODEL, "concertometamodel.cto");
954
988
  modelManager.addCTOModel(CommonMarkModel.MODEL, "commonmark.cto");
955
989
  modelManager.addCTOModel(TemplateMarkModel.MODEL, "templatemark.cto");
956
990
  const factory = new Factory(modelManager);
957
- const serializer = new Serializer(factory, modelManager);
991
+ const serializer = new Serializer(factory, modelManager, {});
958
992
  try {
959
- serializer.fromJSON(templateMark);
960
- return templateMark;
993
+ serializer.fromJSON(templateMark, {});
961
994
  } catch (err) {
962
995
  throw new Error(`Generated invalid agreement: ${err}: ${JSON.stringify(templateMark, null, 2)}`);
963
996
  }
997
+ const errors = [];
998
+ const templateClass = this.templateClass;
999
+ const guardBlockPaths = /* @__PURE__ */ new Map();
1000
+ traverse(templateMark).forEach(function(node) {
1001
+ if (!node || typeof node !== "object" || !node.$class) return;
1002
+ const currentPath = this.path.join("/");
1003
+ if (OPTIONAL_DEFINITION_RE.test(node.$class) || CONDITIONAL_DEFINITION_RE.test(node.$class) || WITH_DEFINITION_RE.test(node.$class)) {
1004
+ guardBlockPaths.set(node.name, currentPath);
1005
+ }
1006
+ if (VARIABLE_DEFINITION_RE.test(node.$class) || ENUM_VARIABLE_DEFINITION_RE.test(node.$class) || FORMATTED_VARIABLE_DEFINITION_RE.test(node.$class)) {
1007
+ const propName = node.name;
1008
+ if (propName && propName !== "this") {
1009
+ try {
1010
+ const property = templateClass.getProperty(propName);
1011
+ if (property && property.isOptional()) {
1012
+ const guardPath = guardBlockPaths.get(propName);
1013
+ const isGuarded = guardPath !== void 0 && (currentPath === guardPath || currentPath.startsWith(guardPath + "/"));
1014
+ if (!isGuarded) {
1015
+ errors.push({
1016
+ propertyName: propName,
1017
+ message: `Optional property '${propName}' is used without a guard. Wrap it in {{#optional ${propName}}}...{{/optional}} or {{#if ${propName}}}...{{/if}}.`
1018
+ });
1019
+ }
1020
+ }
1021
+ } catch {
1022
+ }
1023
+ }
1024
+ }
1025
+ });
1026
+ if (errors.length > 0) {
1027
+ const errorMessage = `Optional properties used without guards: ${errors.map((e) => e.propertyName).join(", ")}`;
1028
+ const error = new Error(errorMessage);
1029
+ error.errors = errors;
1030
+ throw error;
1031
+ }
1032
+ return templateMark;
964
1033
  }
965
1034
  /**
966
1035
  * Compiles the code nodes containing TS to code nodes containing JS.
@@ -993,22 +1062,22 @@ class TemplateMarkInterpreter {
993
1062
  return compiler.compile(templateMark);
994
1063
  }
995
1064
  validateCiceroMark(ciceroMark) {
996
- const modelManager = new ModelManager({ strict: true });
1065
+ const modelManager = new ModelManager();
997
1066
  modelManager.addCTOModel(ConcertoMetaModel.MODEL, "concertometamodel.cto");
998
1067
  modelManager.addCTOModel(CommonMarkModel.MODEL, "commonmark.cto");
999
1068
  modelManager.addCTOModel(CiceroMarkModel.MODEL, "ciceromark.cto");
1000
1069
  const factory = new Factory(modelManager);
1001
- const serializer = new Serializer(factory, modelManager);
1070
+ const serializer = new Serializer(factory, modelManager, {});
1002
1071
  try {
1003
- return serializer.fromJSON(ciceroMark);
1072
+ return serializer.fromJSON(ciceroMark, {});
1004
1073
  } catch (err) {
1005
1074
  throw new Error(`Generated invalid agreement: ${err}: ${JSON.stringify(ciceroMark, null, 2)}`);
1006
1075
  }
1007
1076
  }
1008
1077
  async generate(templateMark, data, options) {
1009
1078
  const factory = new Factory(this.modelManager);
1010
- const serializer = new Serializer(factory, this.modelManager);
1011
- const templateData = serializer.fromJSON(data);
1079
+ const serializer = new Serializer(factory, this.modelManager, {});
1080
+ const templateData = serializer.fromJSON(data, {});
1012
1081
  if (templateData.getFullyQualifiedType() !== this.templateClass.getFullyQualifiedName()) {
1013
1082
  throw new Error(`Template data must be of type '${this.templateClass.getFullyQualifiedName()}'.`);
1014
1083
  }
@@ -1057,7 +1126,7 @@ class TemplateArchiveProcessor {
1057
1126
  * @param {object} request - the request to send to the template logic
1058
1127
  * @param {object} state - the current state of the template
1059
1128
  * @param {[string]} currentTime - the current time, defaults to now
1060
- * @param {[number]} utcOffset - the UTC offer, defaults to zero
1129
+ * @param {[number]} utcOffset - the UTC offset, defaults to zero
1061
1130
  * @returns {Promise} the response and any events
1062
1131
  */
1063
1132
  async trigger(data, request, state, currentTime, utcOffset) {
@@ -1077,6 +1146,8 @@ class TemplateArchiveProcessor {
1077
1146
  const result = compiler.compile(code);
1078
1147
  compiledCode[tsFile.getIdentifier()] = result;
1079
1148
  }
1149
+ const resolvedTime = currentTime ?? (/* @__PURE__ */ new Date()).toISOString();
1150
+ const resolvedOffset = utcOffset ?? 0;
1080
1151
  const evaluator = new JavaScriptEvaluator();
1081
1152
  const evalResponse = await evaluator.evalDangerously({
1082
1153
  templateLogic: true,
@@ -1085,7 +1156,7 @@ class TemplateArchiveProcessor {
1085
1156
  code: compiledCode["logic/logic.ts"].code,
1086
1157
  // TODO DCS - how to find the code to run?
1087
1158
  argumentNames: ["data", "request", "state"],
1088
- arguments: [data, request, state, currentTime, utcOffset]
1159
+ arguments: [data, request, state, resolvedTime, resolvedOffset]
1089
1160
  });
1090
1161
  if (evalResponse.result) {
1091
1162
  return evalResponse.result;
@@ -1099,8 +1170,8 @@ class TemplateArchiveProcessor {
1099
1170
  /**
1100
1171
  * Init the logic of a template
1101
1172
  * @param {[string]} currentTime - the current time, defaults to now
1102
- * @param {[number]} utcOffset - the UTC offer, defaults to zero
1103
- * @returns {Promise} the response and any events
1173
+ * @param {[number]} utcOffset - the UTC offset, defaults to zero
1174
+ * @returns {Promise<InitResponse>} the new state
1104
1175
  */
1105
1176
  async init(data, currentTime, utcOffset) {
1106
1177
  const logicManager = this.template.getLogicManager();
@@ -1119,6 +1190,8 @@ class TemplateArchiveProcessor {
1119
1190
  const result = compiler.compile(code);
1120
1191
  compiledCode[tsFile.getIdentifier()] = result;
1121
1192
  }
1193
+ const resolvedTime = currentTime ?? (/* @__PURE__ */ new Date()).toISOString();
1194
+ const resolvedOffset = utcOffset ?? 0;
1122
1195
  const evaluator = new JavaScriptEvaluator();
1123
1196
  const evalResponse = await evaluator.evalDangerously({
1124
1197
  templateLogic: true,
@@ -1127,7 +1200,7 @@ class TemplateArchiveProcessor {
1127
1200
  code: compiledCode["logic/logic.ts"].code,
1128
1201
  // TODO DCS - how to find the code to run?
1129
1202
  argumentNames: ["data"],
1130
- arguments: [data, currentTime, utcOffset]
1203
+ arguments: [data, resolvedTime, resolvedOffset]
1131
1204
  });
1132
1205
  if (evalResponse.result) {
1133
1206
  return evalResponse.result;