@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.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { ModelManager, ClassDeclaration } from '@accordproject/concerto-core';
2
2
  import { Template } from '@accordproject/cicero-core';
3
+ import { TwoSlashReturn } from '@typescript/twoslash';
3
4
 
4
5
  /*
5
6
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -133,6 +134,7 @@ type InitResponse = {
133
134
  */
134
135
  declare class TemplateArchiveProcessor {
135
136
  template: Template;
137
+ private compiledLogicCache?;
136
138
  /**
137
139
  * Creates a template archive processor
138
140
  * @param {Template} template - the template to be used by the processor
@@ -148,21 +150,31 @@ declare class TemplateArchiveProcessor {
148
150
  */
149
151
  draft(data: any, format: string, options: any, currentTime?: string): Promise<any>;
150
152
  /**
151
- * Trigger the logic of a template
153
+ * Compile the logic of a template
154
+ * @param {boolean} [enableCompiledLogicCache] - whether to cache the compiled logic for future use
155
+ * @returns {Promise<Record<string, TwoSlashReturn>>} the compiled code for each typescript file
156
+ */
157
+ compileLogic(enableCompiledLogicCache?: boolean): Promise<Record<string, TwoSlashReturn>>;
158
+ /**
159
+ * Trigger the logic of a template.
160
+ * @param {object} data - the data for the template
152
161
  * @param {object} request - the request to send to the template logic
153
162
  * @param {object} state - the current state of the template
154
163
  * @param {[string]} currentTime - the current time, defaults to now
155
- * @param {[number]} utcOffset - the UTC offer, defaults to zero
156
- * @returns {Promise} the response and any events
164
+ * @param {[number]} utcOffset - the UTC offset, defaults to zero
165
+ * @param {boolean} [enableCompiledLogicCache] - whether to use the compiled logic cache
166
+ * @returns {Promise<TriggerResponse>} the response and any events
157
167
  */
158
- trigger(data: any, request: any, state?: any, currentTime?: string, utcOffset?: number): Promise<TriggerResponse>;
168
+ trigger(data: any, request: any, state?: any, currentTime?: string, utcOffset?: number, enableCompiledLogicCache?: boolean): Promise<TriggerResponse>;
159
169
  /**
160
- * Init the logic of a template
170
+ * Init the logic of a template.
171
+ * @param {object} data - the data for the template
161
172
  * @param {[string]} currentTime - the current time, defaults to now
162
- * @param {[number]} utcOffset - the UTC offer, defaults to zero
163
- * @returns {Promise} the response and any events
173
+ * @param {[number]} utcOffset - the UTC offset, defaults to zero
174
+ * @param {boolean} [enableCompiledLogicCache] - whether to use the compiled logic cache
175
+ * @returns {Promise<InitResponse>} the new state
164
176
  */
165
- init(data: any, currentTime?: string, utcOffset?: number): Promise<InitResponse>;
177
+ init(data: any, currentTime?: string, utcOffset?: number, enableCompiledLogicCache?: boolean): Promise<InitResponse>;
166
178
  }
167
179
 
168
180
  declare function ensureDirSync(path: string): void;
package/dist/index.js CHANGED
@@ -529,17 +529,31 @@ class JavaScriptEvaluator {
529
529
  const end = (/* @__PURE__ */ new Date()).getTime();
530
530
  resolve({ result, elapsed: end - start });
531
531
  }).catch((err) => {
532
- console.log(err);
533
- reject({
534
- message: err.message
535
- });
532
+ console.error(err);
533
+ if (err instanceof Error) {
534
+ reject({
535
+ message: err.message,
536
+ stack: err.stack
537
+ });
538
+ } else {
539
+ reject({
540
+ message: String(err)
541
+ });
542
+ }
536
543
  });
537
544
  }
538
545
  } catch (err) {
539
- console.log(err);
540
- reject({
541
- message: err.message
542
- });
546
+ console.error(err);
547
+ if (err instanceof Error) {
548
+ reject({
549
+ message: err.message,
550
+ stack: err.stack
551
+ });
552
+ } else {
553
+ reject({
554
+ message: String(err)
555
+ });
556
+ }
543
557
  }
544
558
  });
545
559
  }
@@ -561,6 +575,7 @@ class JavaScriptEvaluator {
561
575
  };
562
576
  if (this.queue.length >= this.options.maxQueueDepth) {
563
577
  reject({ maxQueueDepthExceeded: true, elapsed: 0 });
578
+ return;
564
579
  }
565
580
  this.queue.push(workItem);
566
581
  this.processQueue(options);
@@ -601,7 +616,7 @@ class JavaScriptEvaluator {
601
616
  }
602
617
  const workerPath = this.getWorkerPath();
603
618
  const worker = child_process.fork(workerPath, { timeout: options.timeout, env: {} });
604
- if (!worker.pid) {
619
+ if (worker.pid === void 0 || worker.pid === null) {
605
620
  throw new Error("Failed to fork child process");
606
621
  }
607
622
  this.workers.push(worker);
@@ -610,7 +625,18 @@ class JavaScriptEvaluator {
610
625
  worker.on("error", (err) => {
611
626
  this.workers = this.workers.filter((w) => w.pid !== worker.pid);
612
627
  const end = (/* @__PURE__ */ new Date()).getTime();
613
- reject({ message: err.message, elapsed: end - start });
628
+ if (err instanceof Error) {
629
+ reject({
630
+ message: err.message,
631
+ stack: err.stack,
632
+ elapsed: end - start
633
+ });
634
+ } else {
635
+ reject({
636
+ message: String(err),
637
+ elapsed: end - start
638
+ });
639
+ }
614
640
  });
615
641
  worker.on("message", (msg) => {
616
642
  result = msg;
@@ -714,7 +740,7 @@ function getJsonPath(rootData, currentNode, paths) {
714
740
  }
715
741
  }
716
742
  }
717
- if (currentNode.name !== "this") {
743
+ if (currentNode.name !== "this" && currentNode.name !== "top") {
718
744
  withPath.push(`['${currentNode.name}']`);
719
745
  }
720
746
  return withPath.length > 0 ? `$${withPath.join("")}` : "$";
@@ -793,13 +819,17 @@ async function generateRecursiveBlocks(modelManager, clauseLibrary, templateMark
793
819
  if (!Array.isArray(arrayData)) {
794
820
  throw new Error(`Values found for path '${path}' in data ${data} is not an array: ${arrayData}.`);
795
821
  } else {
822
+ const firstChild = context.nodes[0];
823
+ const hasListWrapper = firstChild && firstChild.$class === `${markdownCommon.CommonMarkModel.NAMESPACE}.List` && Array.isArray(firstChild.nodes) && firstChild.nodes.length > 0;
824
+ const itemTemplate = hasListWrapper ? firstChild.nodes[0] : firstChild;
796
825
  const nodes = [];
797
826
  for (let n2 = 0; n2 < arrayData.length; n2++) {
798
827
  const arrayItem = arrayData[n2];
799
- const subResult = await generateAgreement(modelManager, clauseLibrary, context.nodes[0].nodes[0], arrayItem, options);
828
+ const subResult = await generateAgreement(modelManager, clauseLibrary, itemTemplate, arrayItem, options);
829
+ const childNodes = hasListWrapper ? subResult.nodes ? subResult.nodes : [] : [subResult];
800
830
  nodes.push({
801
831
  $class: childNodeClass,
802
- nodes: subResult.nodes ? subResult.nodes : []
832
+ nodes: childNodes
803
833
  });
804
834
  }
805
835
  result[thisPath.join("/")] = nodes;
@@ -904,8 +934,17 @@ async function generateAgreement(modelManager, clauseLibrary, templateMark, data
904
934
  }
905
935
  } else if (CONDITIONAL_DEFINITION_RE.test(nodeClass)) {
906
936
  if (context.condition) {
907
- const result = userCodeResults[this.path.join("/")];
908
- context.isTrue = !!result;
937
+ const key = this.path.join("/");
938
+ const resultStr = userCodeResults[key];
939
+ if (resultStr === void 0 || resultStr === null) {
940
+ context.isTrue = false;
941
+ } else {
942
+ try {
943
+ context.isTrue = !!JSON.parse(resultStr);
944
+ } catch (err) {
945
+ throw new Error(`Invalid JSON boolean result for condition '${key}': ${String(err)}`);
946
+ }
947
+ }
909
948
  } else {
910
949
  const path = getJsonPath(templateMark, context, this.path);
911
950
  const variableValues = jp.query(data, path, 1);
@@ -926,16 +965,27 @@ async function generateAgreement(modelManager, clauseLibrary, templateMark, data
926
965
  } else if (CLAUSE_DEFINITION_RE.test(nodeClass)) {
927
966
  const path = getJsonPath(templateMark, context, this.path);
928
967
  const variableValues = jp.query(data, path, 1);
929
- if (context.name !== "top" && (variableValues.length === 0 || variableValues[0] === void 0 || variableValues[0] === null)) {
930
- delete context.nodes;
931
- stopHere = true;
932
- } else if (context.condition) {
968
+ if (context.condition) {
933
969
  checkCode(context.condition);
934
- const result = !!userCodeResults[this.path.join("/")];
970
+ const key = this.path.join("/");
971
+ const resultStr = userCodeResults[key];
972
+ let result = false;
973
+ if (resultStr === void 0 || resultStr === null) {
974
+ result = false;
975
+ } else {
976
+ try {
977
+ result = !!JSON.parse(resultStr);
978
+ } catch (err) {
979
+ throw new Error(`Invalid JSON boolean result for condition '${key}': ${String(err)}`);
980
+ }
981
+ }
935
982
  if (!result) {
936
983
  delete context.nodes;
937
984
  stopHere = true;
938
985
  }
986
+ } else if (context.name !== "top" && (variableValues.length === 0 || variableValues[0] === void 0 || variableValues[0] === null)) {
987
+ delete context.nodes;
988
+ stopHere = true;
939
989
  }
940
990
  delete context.condition;
941
991
  delete context.functionName;
@@ -984,14 +1034,14 @@ class TemplateMarkInterpreter {
984
1034
  * @throws {Error} if the templateMark document is invalid
985
1035
  */
986
1036
  checkTypes(templateMark) {
987
- const modelManager = new concertoCore.ModelManager({ strict: true });
1037
+ const modelManager = new concertoCore.ModelManager();
988
1038
  modelManager.addCTOModel(markdownCommon.ConcertoMetaModel.MODEL, "concertometamodel.cto");
989
1039
  modelManager.addCTOModel(markdownCommon.CommonMarkModel.MODEL, "commonmark.cto");
990
1040
  modelManager.addCTOModel(markdownCommon.TemplateMarkModel.MODEL, "templatemark.cto");
991
1041
  const factory = new concertoCore.Factory(modelManager);
992
- const serializer = new concertoCore.Serializer(factory, modelManager);
1042
+ const serializer = new concertoCore.Serializer(factory, modelManager, {});
993
1043
  try {
994
- serializer.fromJSON(templateMark);
1044
+ serializer.fromJSON(templateMark, {});
995
1045
  } catch (err) {
996
1046
  throw new Error(`Generated invalid agreement: ${err}: ${JSON.stringify(templateMark, null, 2)}`);
997
1047
  }
@@ -1063,22 +1113,22 @@ class TemplateMarkInterpreter {
1063
1113
  return compiler.compile(templateMark);
1064
1114
  }
1065
1115
  validateCiceroMark(ciceroMark) {
1066
- const modelManager = new concertoCore.ModelManager({ strict: true });
1116
+ const modelManager = new concertoCore.ModelManager();
1067
1117
  modelManager.addCTOModel(markdownCommon.ConcertoMetaModel.MODEL, "concertometamodel.cto");
1068
1118
  modelManager.addCTOModel(markdownCommon.CommonMarkModel.MODEL, "commonmark.cto");
1069
1119
  modelManager.addCTOModel(markdownCommon.CiceroMarkModel.MODEL, "ciceromark.cto");
1070
1120
  const factory = new concertoCore.Factory(modelManager);
1071
- const serializer = new concertoCore.Serializer(factory, modelManager);
1121
+ const serializer = new concertoCore.Serializer(factory, modelManager, {});
1072
1122
  try {
1073
- return serializer.fromJSON(ciceroMark);
1123
+ return serializer.fromJSON(ciceroMark, {});
1074
1124
  } catch (err) {
1075
1125
  throw new Error(`Generated invalid agreement: ${err}: ${JSON.stringify(ciceroMark, null, 2)}`);
1076
1126
  }
1077
1127
  }
1078
1128
  async generate(templateMark, data, options) {
1079
1129
  const factory = new concertoCore.Factory(this.modelManager);
1080
- const serializer = new concertoCore.Serializer(factory, this.modelManager);
1081
- const templateData = serializer.fromJSON(data);
1130
+ const serializer = new concertoCore.Serializer(factory, this.modelManager, {});
1131
+ const templateData = serializer.fromJSON(data, {});
1082
1132
  if (templateData.getFullyQualifiedType() !== this.templateClass.getFullyQualifiedName()) {
1083
1133
  throw new Error(`Template data must be of type '${this.templateClass.getFullyQualifiedName()}'.`);
1084
1134
  }
@@ -1123,14 +1173,14 @@ class TemplateArchiveProcessor {
1123
1173
  return result;
1124
1174
  }
1125
1175
  /**
1126
- * Trigger the logic of a template
1127
- * @param {object} request - the request to send to the template logic
1128
- * @param {object} state - the current state of the template
1129
- * @param {[string]} currentTime - the current time, defaults to now
1130
- * @param {[number]} utcOffset - the UTC offer, defaults to zero
1131
- * @returns {Promise} the response and any events
1176
+ * Compile the logic of a template
1177
+ * @param {boolean} [enableCompiledLogicCache] - whether to cache the compiled logic for future use
1178
+ * @returns {Promise<Record<string, TwoSlashReturn>>} the compiled code for each typescript file
1132
1179
  */
1133
- async trigger(data, request, state, currentTime, utcOffset) {
1180
+ async compileLogic(enableCompiledLogicCache = false) {
1181
+ if (enableCompiledLogicCache && this.compiledLogicCache) {
1182
+ return this.compiledLogicCache;
1183
+ }
1134
1184
  const logicManager = this.template.getLogicManager();
1135
1185
  if (logicManager.getLanguage() === "typescript") {
1136
1186
  const compiledCode = {};
@@ -1147,65 +1197,70 @@ class TemplateArchiveProcessor {
1147
1197
  const result = compiler.compile(code);
1148
1198
  compiledCode[tsFile.getIdentifier()] = result;
1149
1199
  }
1150
- const evaluator = new JavaScriptEvaluator();
1151
- const evalResponse = await evaluator.evalDangerously({
1152
- templateLogic: true,
1153
- verbose: false,
1154
- functionName: "trigger",
1155
- code: compiledCode["logic/logic.ts"].code,
1156
- // TODO DCS - how to find the code to run?
1157
- argumentNames: ["data", "request", "state"],
1158
- arguments: [data, request, state, currentTime, utcOffset]
1159
- });
1160
- if (evalResponse.result) {
1161
- return evalResponse.result;
1162
- } else {
1163
- throw new Error("Trigger failed with message: " + evalResponse.message);
1200
+ if (enableCompiledLogicCache) {
1201
+ this.compiledLogicCache = compiledCode;
1164
1202
  }
1203
+ return compiledCode;
1165
1204
  } else {
1166
1205
  throw new Error("Only TypeScript is supported at this time");
1167
1206
  }
1168
1207
  }
1169
1208
  /**
1170
- * Init the logic of a template
1209
+ * Trigger the logic of a template.
1210
+ * @param {object} data - the data for the template
1211
+ * @param {object} request - the request to send to the template logic
1212
+ * @param {object} state - the current state of the template
1171
1213
  * @param {[string]} currentTime - the current time, defaults to now
1172
- * @param {[number]} utcOffset - the UTC offer, defaults to zero
1173
- * @returns {Promise} the response and any events
1214
+ * @param {[number]} utcOffset - the UTC offset, defaults to zero
1215
+ * @param {boolean} [enableCompiledLogicCache] - whether to use the compiled logic cache
1216
+ * @returns {Promise<TriggerResponse>} the response and any events
1174
1217
  */
1175
- async init(data, currentTime, utcOffset) {
1176
- const logicManager = this.template.getLogicManager();
1177
- if (logicManager.getLanguage() === "typescript") {
1178
- const compiledCode = {};
1179
- const tsFiles = logicManager.getScriptManager().getScriptsForTarget("typescript");
1180
- for (let n = 0; n < tsFiles.length; n++) {
1181
- const tsFile = tsFiles[n];
1182
- const compiler = new TypeScriptToJavaScriptCompiler(
1183
- this.template.getModelManager(),
1184
- this.template.getTemplateModel().getFullyQualifiedName()
1185
- );
1186
- await compiler.initialize();
1187
- const code = `${Buffer.from(SMART_LEGAL_CONTRACT_BASE64, "base64").toString()}
1188
- ${tsFile.getContents()}`;
1189
- const result = compiler.compile(code);
1190
- compiledCode[tsFile.getIdentifier()] = result;
1191
- }
1192
- const evaluator = new JavaScriptEvaluator();
1193
- const evalResponse = await evaluator.evalDangerously({
1194
- templateLogic: true,
1195
- verbose: false,
1196
- functionName: "init",
1197
- code: compiledCode["logic/logic.ts"].code,
1198
- // TODO DCS - how to find the code to run?
1199
- argumentNames: ["data"],
1200
- arguments: [data, currentTime, utcOffset]
1201
- });
1202
- if (evalResponse.result) {
1203
- return evalResponse.result;
1204
- } else {
1205
- throw new Error("Init failed with message: " + evalResponse.message);
1206
- }
1218
+ async trigger(data, request, state, currentTime, utcOffset, enableCompiledLogicCache) {
1219
+ const compiledCode = await this.compileLogic(enableCompiledLogicCache);
1220
+ const resolvedTime = currentTime ?? (/* @__PURE__ */ new Date()).toISOString();
1221
+ const resolvedOffset = utcOffset ?? 0;
1222
+ const evaluator = new JavaScriptEvaluator();
1223
+ const evalResponse = await evaluator.evalDangerously({
1224
+ templateLogic: true,
1225
+ verbose: false,
1226
+ functionName: "trigger",
1227
+ code: compiledCode["logic/logic.ts"].code,
1228
+ // TODO DCS - how to find the code to run?
1229
+ argumentNames: ["data", "request", "state"],
1230
+ arguments: [data, request, state, resolvedTime, resolvedOffset]
1231
+ });
1232
+ if (evalResponse.result) {
1233
+ return evalResponse.result;
1207
1234
  } else {
1208
- throw new Error("Only TypeScript is supported at this time");
1235
+ throw new Error("Trigger failed with message: " + evalResponse.message);
1236
+ }
1237
+ }
1238
+ /**
1239
+ * Init the logic of a template.
1240
+ * @param {object} data - the data for the template
1241
+ * @param {[string]} currentTime - the current time, defaults to now
1242
+ * @param {[number]} utcOffset - the UTC offset, defaults to zero
1243
+ * @param {boolean} [enableCompiledLogicCache] - whether to use the compiled logic cache
1244
+ * @returns {Promise<InitResponse>} the new state
1245
+ */
1246
+ async init(data, currentTime, utcOffset, enableCompiledLogicCache) {
1247
+ const compiledCode = await this.compileLogic(enableCompiledLogicCache);
1248
+ const resolvedTime = currentTime ?? (/* @__PURE__ */ new Date()).toISOString();
1249
+ const resolvedOffset = utcOffset ?? 0;
1250
+ const evaluator = new JavaScriptEvaluator();
1251
+ const evalResponse = await evaluator.evalDangerously({
1252
+ templateLogic: true,
1253
+ verbose: false,
1254
+ functionName: "init",
1255
+ code: compiledCode["logic/logic.ts"].code,
1256
+ // TODO DCS - how to find the code to run?
1257
+ argumentNames: ["data"],
1258
+ arguments: [data, resolvedTime, resolvedOffset]
1259
+ });
1260
+ if (evalResponse.result) {
1261
+ return evalResponse.result;
1262
+ } else {
1263
+ throw new Error("Init failed with message: " + evalResponse.message);
1209
1264
  }
1210
1265
  }
1211
1266
  }