@accordproject/template-engine 2.9.0 → 3.1.0

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
@@ -508,17 +508,31 @@ class JavaScriptEvaluator {
508
508
  const end = (/* @__PURE__ */ new Date()).getTime();
509
509
  resolve({ result, elapsed: end - start });
510
510
  }).catch((err) => {
511
- console.log(err);
512
- reject({
513
- message: err.message
514
- });
511
+ console.error(err);
512
+ if (err instanceof Error) {
513
+ reject({
514
+ message: err.message,
515
+ stack: err.stack
516
+ });
517
+ } else {
518
+ reject({
519
+ message: String(err)
520
+ });
521
+ }
515
522
  });
516
523
  }
517
524
  } catch (err) {
518
- console.log(err);
519
- reject({
520
- message: err.message
521
- });
525
+ console.error(err);
526
+ if (err instanceof Error) {
527
+ reject({
528
+ message: err.message,
529
+ stack: err.stack
530
+ });
531
+ } else {
532
+ reject({
533
+ message: String(err)
534
+ });
535
+ }
522
536
  }
523
537
  });
524
538
  }
@@ -540,6 +554,7 @@ class JavaScriptEvaluator {
540
554
  };
541
555
  if (this.queue.length >= this.options.maxQueueDepth) {
542
556
  reject({ maxQueueDepthExceeded: true, elapsed: 0 });
557
+ return;
543
558
  }
544
559
  this.queue.push(workItem);
545
560
  this.processQueue(options);
@@ -580,7 +595,7 @@ class JavaScriptEvaluator {
580
595
  }
581
596
  const workerPath = this.getWorkerPath();
582
597
  const worker = child_process.fork(workerPath, { timeout: options.timeout, env: {} });
583
- if (!worker.pid) {
598
+ if (worker.pid === void 0 || worker.pid === null) {
584
599
  throw new Error("Failed to fork child process");
585
600
  }
586
601
  this.workers.push(worker);
@@ -589,7 +604,18 @@ class JavaScriptEvaluator {
589
604
  worker.on("error", (err) => {
590
605
  this.workers = this.workers.filter((w) => w.pid !== worker.pid);
591
606
  const end = (/* @__PURE__ */ new Date()).getTime();
592
- reject({ message: err.message, elapsed: end - start });
607
+ if (err instanceof Error) {
608
+ reject({
609
+ message: err.message,
610
+ stack: err.stack,
611
+ elapsed: end - start
612
+ });
613
+ } else {
614
+ reject({
615
+ message: String(err),
616
+ elapsed: end - start
617
+ });
618
+ }
593
619
  });
594
620
  worker.on("message", (msg) => {
595
621
  result = msg;
@@ -693,7 +719,7 @@ function getJsonPath(rootData, currentNode, paths) {
693
719
  }
694
720
  }
695
721
  }
696
- if (currentNode.name !== "this") {
722
+ if (currentNode.name !== "this" && currentNode.name !== "top") {
697
723
  withPath.push(`['${currentNode.name}']`);
698
724
  }
699
725
  return withPath.length > 0 ? `$${withPath.join("")}` : "$";
@@ -772,13 +798,17 @@ async function generateRecursiveBlocks(modelManager, clauseLibrary, templateMark
772
798
  if (!Array.isArray(arrayData)) {
773
799
  throw new Error(`Values found for path '${path}' in data ${data} is not an array: ${arrayData}.`);
774
800
  } else {
801
+ const firstChild = context.nodes[0];
802
+ const hasListWrapper = firstChild && firstChild.$class === `${CommonMarkModel.NAMESPACE}.List` && Array.isArray(firstChild.nodes) && firstChild.nodes.length > 0;
803
+ const itemTemplate = hasListWrapper ? firstChild.nodes[0] : firstChild;
775
804
  const nodes = [];
776
805
  for (let n2 = 0; n2 < arrayData.length; n2++) {
777
806
  const arrayItem = arrayData[n2];
778
- const subResult = await generateAgreement(modelManager, clauseLibrary, context.nodes[0].nodes[0], arrayItem, options);
807
+ const subResult = await generateAgreement(modelManager, clauseLibrary, itemTemplate, arrayItem, options);
808
+ const childNodes = hasListWrapper ? subResult.nodes ? subResult.nodes : [] : [subResult];
779
809
  nodes.push({
780
810
  $class: childNodeClass,
781
- nodes: subResult.nodes ? subResult.nodes : []
811
+ nodes: childNodes
782
812
  });
783
813
  }
784
814
  result[thisPath.join("/")] = nodes;
@@ -883,8 +913,17 @@ async function generateAgreement(modelManager, clauseLibrary, templateMark, data
883
913
  }
884
914
  } else if (CONDITIONAL_DEFINITION_RE.test(nodeClass)) {
885
915
  if (context.condition) {
886
- const result = userCodeResults[this.path.join("/")];
887
- context.isTrue = !!result;
916
+ const key = this.path.join("/");
917
+ const resultStr = userCodeResults[key];
918
+ if (resultStr === void 0 || resultStr === null) {
919
+ context.isTrue = false;
920
+ } else {
921
+ try {
922
+ context.isTrue = !!JSON.parse(resultStr);
923
+ } catch (err) {
924
+ throw new Error(`Invalid JSON boolean result for condition '${key}': ${String(err)}`);
925
+ }
926
+ }
888
927
  } else {
889
928
  const path = getJsonPath(templateMark, context, this.path);
890
929
  const variableValues = jp.query(data, path, 1);
@@ -905,16 +944,27 @@ async function generateAgreement(modelManager, clauseLibrary, templateMark, data
905
944
  } else if (CLAUSE_DEFINITION_RE.test(nodeClass)) {
906
945
  const path = getJsonPath(templateMark, context, this.path);
907
946
  const variableValues = jp.query(data, path, 1);
908
- if (context.name !== "top" && (variableValues.length === 0 || variableValues[0] === void 0 || variableValues[0] === null)) {
909
- delete context.nodes;
910
- stopHere = true;
911
- } else if (context.condition) {
947
+ if (context.condition) {
912
948
  checkCode(context.condition);
913
- const result = !!userCodeResults[this.path.join("/")];
949
+ const key = this.path.join("/");
950
+ const resultStr = userCodeResults[key];
951
+ let result = false;
952
+ if (resultStr === void 0 || resultStr === null) {
953
+ result = false;
954
+ } else {
955
+ try {
956
+ result = !!JSON.parse(resultStr);
957
+ } catch (err) {
958
+ throw new Error(`Invalid JSON boolean result for condition '${key}': ${String(err)}`);
959
+ }
960
+ }
914
961
  if (!result) {
915
962
  delete context.nodes;
916
963
  stopHere = true;
917
964
  }
965
+ } else if (context.name !== "top" && (variableValues.length === 0 || variableValues[0] === void 0 || variableValues[0] === null)) {
966
+ delete context.nodes;
967
+ stopHere = true;
918
968
  }
919
969
  delete context.condition;
920
970
  delete context.functionName;
@@ -963,14 +1013,14 @@ class TemplateMarkInterpreter {
963
1013
  * @throws {Error} if the templateMark document is invalid
964
1014
  */
965
1015
  checkTypes(templateMark) {
966
- const modelManager = new ModelManager({ strict: true });
1016
+ const modelManager = new ModelManager();
967
1017
  modelManager.addCTOModel(ConcertoMetaModel.MODEL, "concertometamodel.cto");
968
1018
  modelManager.addCTOModel(CommonMarkModel.MODEL, "commonmark.cto");
969
1019
  modelManager.addCTOModel(TemplateMarkModel.MODEL, "templatemark.cto");
970
1020
  const factory = new Factory(modelManager);
971
- const serializer = new Serializer(factory, modelManager);
1021
+ const serializer = new Serializer(factory, modelManager, {});
972
1022
  try {
973
- serializer.fromJSON(templateMark);
1023
+ serializer.fromJSON(templateMark, {});
974
1024
  } catch (err) {
975
1025
  throw new Error(`Generated invalid agreement: ${err}: ${JSON.stringify(templateMark, null, 2)}`);
976
1026
  }
@@ -1042,22 +1092,22 @@ class TemplateMarkInterpreter {
1042
1092
  return compiler.compile(templateMark);
1043
1093
  }
1044
1094
  validateCiceroMark(ciceroMark) {
1045
- const modelManager = new ModelManager({ strict: true });
1095
+ const modelManager = new ModelManager();
1046
1096
  modelManager.addCTOModel(ConcertoMetaModel.MODEL, "concertometamodel.cto");
1047
1097
  modelManager.addCTOModel(CommonMarkModel.MODEL, "commonmark.cto");
1048
1098
  modelManager.addCTOModel(CiceroMarkModel.MODEL, "ciceromark.cto");
1049
1099
  const factory = new Factory(modelManager);
1050
- const serializer = new Serializer(factory, modelManager);
1100
+ const serializer = new Serializer(factory, modelManager, {});
1051
1101
  try {
1052
- return serializer.fromJSON(ciceroMark);
1102
+ return serializer.fromJSON(ciceroMark, {});
1053
1103
  } catch (err) {
1054
1104
  throw new Error(`Generated invalid agreement: ${err}: ${JSON.stringify(ciceroMark, null, 2)}`);
1055
1105
  }
1056
1106
  }
1057
1107
  async generate(templateMark, data, options) {
1058
1108
  const factory = new Factory(this.modelManager);
1059
- const serializer = new Serializer(factory, this.modelManager);
1060
- const templateData = serializer.fromJSON(data);
1109
+ const serializer = new Serializer(factory, this.modelManager, {});
1110
+ const templateData = serializer.fromJSON(data, {});
1061
1111
  if (templateData.getFullyQualifiedType() !== this.templateClass.getFullyQualifiedName()) {
1062
1112
  throw new Error(`Template data must be of type '${this.templateClass.getFullyQualifiedName()}'.`);
1063
1113
  }
@@ -1102,14 +1152,14 @@ class TemplateArchiveProcessor {
1102
1152
  return result;
1103
1153
  }
1104
1154
  /**
1105
- * Trigger the logic of a template
1106
- * @param {object} request - the request to send to the template logic
1107
- * @param {object} state - the current state of the template
1108
- * @param {[string]} currentTime - the current time, defaults to now
1109
- * @param {[number]} utcOffset - the UTC offer, defaults to zero
1110
- * @returns {Promise} the response and any events
1155
+ * Compile the logic of a template
1156
+ * @param {boolean} [enableCompiledLogicCache] - whether to cache the compiled logic for future use
1157
+ * @returns {Promise<Record<string, TwoSlashReturn>>} the compiled code for each typescript file
1111
1158
  */
1112
- async trigger(data, request, state, currentTime, utcOffset) {
1159
+ async compileLogic(enableCompiledLogicCache = false) {
1160
+ if (enableCompiledLogicCache && this.compiledLogicCache) {
1161
+ return this.compiledLogicCache;
1162
+ }
1113
1163
  const logicManager = this.template.getLogicManager();
1114
1164
  if (logicManager.getLanguage() === "typescript") {
1115
1165
  const compiledCode = {};
@@ -1126,65 +1176,70 @@ class TemplateArchiveProcessor {
1126
1176
  const result = compiler.compile(code);
1127
1177
  compiledCode[tsFile.getIdentifier()] = result;
1128
1178
  }
1129
- const evaluator = new JavaScriptEvaluator();
1130
- const evalResponse = await evaluator.evalDangerously({
1131
- templateLogic: true,
1132
- verbose: false,
1133
- functionName: "trigger",
1134
- code: compiledCode["logic/logic.ts"].code,
1135
- // TODO DCS - how to find the code to run?
1136
- argumentNames: ["data", "request", "state"],
1137
- arguments: [data, request, state, currentTime, utcOffset]
1138
- });
1139
- if (evalResponse.result) {
1140
- return evalResponse.result;
1141
- } else {
1142
- throw new Error("Trigger failed with message: " + evalResponse.message);
1179
+ if (enableCompiledLogicCache) {
1180
+ this.compiledLogicCache = compiledCode;
1143
1181
  }
1182
+ return compiledCode;
1144
1183
  } else {
1145
1184
  throw new Error("Only TypeScript is supported at this time");
1146
1185
  }
1147
1186
  }
1148
1187
  /**
1149
- * Init the logic of a template
1188
+ * Trigger the logic of a template.
1189
+ * @param {object} data - the data for the template
1190
+ * @param {object} request - the request to send to the template logic
1191
+ * @param {object} state - the current state of the template
1150
1192
  * @param {[string]} currentTime - the current time, defaults to now
1151
- * @param {[number]} utcOffset - the UTC offer, defaults to zero
1152
- * @returns {Promise} the response and any events
1193
+ * @param {[number]} utcOffset - the UTC offset, defaults to zero
1194
+ * @param {boolean} [enableCompiledLogicCache] - whether to use the compiled logic cache
1195
+ * @returns {Promise<TriggerResponse>} the response and any events
1153
1196
  */
1154
- async init(data, currentTime, utcOffset) {
1155
- const logicManager = this.template.getLogicManager();
1156
- if (logicManager.getLanguage() === "typescript") {
1157
- const compiledCode = {};
1158
- const tsFiles = logicManager.getScriptManager().getScriptsForTarget("typescript");
1159
- for (let n = 0; n < tsFiles.length; n++) {
1160
- const tsFile = tsFiles[n];
1161
- const compiler = new TypeScriptToJavaScriptCompiler(
1162
- this.template.getModelManager(),
1163
- this.template.getTemplateModel().getFullyQualifiedName()
1164
- );
1165
- await compiler.initialize();
1166
- const code = `${Buffer.from(SMART_LEGAL_CONTRACT_BASE64, "base64").toString()}
1167
- ${tsFile.getContents()}`;
1168
- const result = compiler.compile(code);
1169
- compiledCode[tsFile.getIdentifier()] = result;
1170
- }
1171
- const evaluator = new JavaScriptEvaluator();
1172
- const evalResponse = await evaluator.evalDangerously({
1173
- templateLogic: true,
1174
- verbose: false,
1175
- functionName: "init",
1176
- code: compiledCode["logic/logic.ts"].code,
1177
- // TODO DCS - how to find the code to run?
1178
- argumentNames: ["data"],
1179
- arguments: [data, currentTime, utcOffset]
1180
- });
1181
- if (evalResponse.result) {
1182
- return evalResponse.result;
1183
- } else {
1184
- throw new Error("Init failed with message: " + evalResponse.message);
1185
- }
1197
+ async trigger(data, request, state, currentTime, utcOffset, enableCompiledLogicCache) {
1198
+ const compiledCode = await this.compileLogic(enableCompiledLogicCache);
1199
+ const resolvedTime = currentTime ?? (/* @__PURE__ */ new Date()).toISOString();
1200
+ const resolvedOffset = utcOffset ?? 0;
1201
+ const evaluator = new JavaScriptEvaluator();
1202
+ const evalResponse = await evaluator.evalDangerously({
1203
+ templateLogic: true,
1204
+ verbose: false,
1205
+ functionName: "trigger",
1206
+ code: compiledCode["logic/logic.ts"].code,
1207
+ // TODO DCS - how to find the code to run?
1208
+ argumentNames: ["data", "request", "state"],
1209
+ arguments: [data, request, state, resolvedTime, resolvedOffset]
1210
+ });
1211
+ if (evalResponse.result) {
1212
+ return evalResponse.result;
1186
1213
  } else {
1187
- throw new Error("Only TypeScript is supported at this time");
1214
+ throw new Error("Trigger failed with message: " + evalResponse.message);
1215
+ }
1216
+ }
1217
+ /**
1218
+ * Init the logic of a template.
1219
+ * @param {object} data - the data for the template
1220
+ * @param {[string]} currentTime - the current time, defaults to now
1221
+ * @param {[number]} utcOffset - the UTC offset, defaults to zero
1222
+ * @param {boolean} [enableCompiledLogicCache] - whether to use the compiled logic cache
1223
+ * @returns {Promise<InitResponse>} the new state
1224
+ */
1225
+ async init(data, currentTime, utcOffset, enableCompiledLogicCache) {
1226
+ const compiledCode = await this.compileLogic(enableCompiledLogicCache);
1227
+ const resolvedTime = currentTime ?? (/* @__PURE__ */ new Date()).toISOString();
1228
+ const resolvedOffset = utcOffset ?? 0;
1229
+ const evaluator = new JavaScriptEvaluator();
1230
+ const evalResponse = await evaluator.evalDangerously({
1231
+ templateLogic: true,
1232
+ verbose: false,
1233
+ functionName: "init",
1234
+ code: compiledCode["logic/logic.ts"].code,
1235
+ // TODO DCS - how to find the code to run?
1236
+ argumentNames: ["data"],
1237
+ arguments: [data, resolvedTime, resolvedOffset]
1238
+ });
1239
+ if (evalResponse.result) {
1240
+ return evalResponse.result;
1241
+ } else {
1242
+ throw new Error("Init failed with message: " + evalResponse.message);
1188
1243
  }
1189
1244
  }
1190
1245
  }