@accordproject/template-engine 2.8.0 → 2.9.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/README.md CHANGED
@@ -20,6 +20,31 @@ At a high-level the template engine converts a TemplateMark DOM to an AgreementM
20
20
 
21
21
  ![Template Interpreter](./assets/template-interpreter.png)
22
22
 
23
+ ## Execution Pipeline Overview
24
+
25
+ Internally, the Template Engine evaluates templates through a structured execution pipeline:
26
+
27
+ 1. **Type Validation**
28
+ The TemplateMark JSON document and the incoming agreement data are validated against their respective Concerto models to ensure structural and type correctness.
29
+
30
+ 2. **TypeScript Compilation**
31
+ Embedded TypeScript expressions (such as conditionals, clauses, and formulae) are compiled into JavaScript using the `TemplateMarkToJavaScriptCompiler`.
32
+
33
+ 3. **User Code Evaluation**
34
+ During agreement generation, compiled JavaScript expressions are evaluated using the `JavaScriptEvaluator`.
35
+ The evaluator supports two execution strategies:
36
+ - `evalDangerously()` — Executes JavaScript directly within the current process.
37
+ This should only be used with trusted code or in a sandboxed environment (e.g., browser).
38
+ - `evalChildProcess()` — Executes JavaScript in an isolated Node.js child process for improved safety and isolation and is recommended for untrusted template content on the server.
39
+
40
+ 4. **AgreementMark Generation**
41
+ The TemplateMark document is traversed, evaluated values are inserted into the document structure, and an AgreementMark JSON document is produced.
42
+
43
+ 5. **Output Validation**
44
+ The generated AgreementMark document is validated before being returned.
45
+
46
+ This layered architecture ensures type-safety, deterministic execution of template logic, and isolation during runtime evaluation.
47
+
23
48
  ## Hello World Template
24
49
 
25
50
  Let's create the simplest template imaginable, the infamous "hello world"!
@@ -102,7 +127,7 @@ This AgreementMark JSON document can then be passed to the `@accordproject/markd
102
127
 
103
128
  The Hello World example just scratches the surface of what can be accomplished! TemplateMark can define optional sections, conditional sections, TypeScript formulae/calculations and even reference external data.
104
129
 
105
- Refer to the [full](https://github.com/accordproject/template-engine/tree/main/test/templates/full) example for details.
130
+ Refer to the [full](https://github.com/accordproject/template-engine/tree/main/test/templates/good/full) example for details.
106
131
 
107
132
  > More detailed syntax documentation is to come!
108
133
  Read the existing documentation at: https://docs.accordproject.org/docs/markup-templatemark.html
package/dist/index.js CHANGED
@@ -122,6 +122,16 @@ function integerDrafter(value, format) {
122
122
  }
123
123
  }
124
124
 
125
+ function isDuration(value) {
126
+ return value != null && typeof value === "object" && "amount" in value && "unit" in value && typeof value.amount === "number" && typeof value.unit === "string";
127
+ }
128
+ function durationDrafter(value) {
129
+ if (!isDuration(value)) {
130
+ return "0 unknown";
131
+ }
132
+ return `${value.amount} ${value.unit}`;
133
+ }
134
+
125
135
  function longDrafter(value, format) {
126
136
  if (format) {
127
137
  return draftIntegerFormat(value, format);
@@ -210,6 +220,10 @@ function getDrafter(typeName) {
210
220
  return longDrafter;
211
221
  case "org.accordproject.money@0.3.0.MonetaryAmount":
212
222
  return monetaryAmountDrafter;
223
+ case "org.accordproject.time@0.3.0.Duration":
224
+ return durationDrafter;
225
+ case "org.accordproject.time@0.3.0.Period":
226
+ return durationDrafter;
213
227
  case "String":
214
228
  return stringDrafter;
215
229
  default:
@@ -978,10 +992,45 @@ class TemplateMarkInterpreter {
978
992
  const serializer = new concertoCore.Serializer(factory, modelManager);
979
993
  try {
980
994
  serializer.fromJSON(templateMark);
981
- return templateMark;
982
995
  } catch (err) {
983
996
  throw new Error(`Generated invalid agreement: ${err}: ${JSON.stringify(templateMark, null, 2)}`);
984
997
  }
998
+ const errors = [];
999
+ const templateClass = this.templateClass;
1000
+ const guardBlockPaths = /* @__PURE__ */ new Map();
1001
+ traverse(templateMark).forEach(function(node) {
1002
+ if (!node || typeof node !== "object" || !node.$class) return;
1003
+ const currentPath = this.path.join("/");
1004
+ if (OPTIONAL_DEFINITION_RE.test(node.$class) || CONDITIONAL_DEFINITION_RE.test(node.$class) || WITH_DEFINITION_RE.test(node.$class)) {
1005
+ guardBlockPaths.set(node.name, currentPath);
1006
+ }
1007
+ if (VARIABLE_DEFINITION_RE.test(node.$class) || ENUM_VARIABLE_DEFINITION_RE.test(node.$class) || FORMATTED_VARIABLE_DEFINITION_RE.test(node.$class)) {
1008
+ const propName = node.name;
1009
+ if (propName && propName !== "this") {
1010
+ try {
1011
+ const property = templateClass.getProperty(propName);
1012
+ if (property && property.isOptional()) {
1013
+ const guardPath = guardBlockPaths.get(propName);
1014
+ const isGuarded = guardPath !== void 0 && (currentPath === guardPath || currentPath.startsWith(guardPath + "/"));
1015
+ if (!isGuarded) {
1016
+ errors.push({
1017
+ propertyName: propName,
1018
+ message: `Optional property '${propName}' is used without a guard. Wrap it in {{#optional ${propName}}}...{{/optional}} or {{#if ${propName}}}...{{/if}}.`
1019
+ });
1020
+ }
1021
+ }
1022
+ } catch {
1023
+ }
1024
+ }
1025
+ }
1026
+ });
1027
+ if (errors.length > 0) {
1028
+ const errorMessage = `Optional properties used without guards: ${errors.map((e) => e.propertyName).join(", ")}`;
1029
+ const error = new Error(errorMessage);
1030
+ error.errors = errors;
1031
+ throw error;
1032
+ }
1033
+ return templateMark;
985
1034
  }
986
1035
  /**
987
1036
  * Compiles the code nodes containing TS to code nodes containing JS.