@accordproject/template-engine 2.7.1 → 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 +28 -3
- package/dist/index.js +56 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +56 -2
- package/dist/index.mjs.map +1 -1
- package/dist/worker.js.map +1 -1
- package/package.json +17 -10
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
|

|
|
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
|
|
@@ -125,9 +150,9 @@ TemplateMark JSON is a well-defined file format, meaning that powerful template
|
|
|
125
150
|
|
|
126
151
|
We encourage community and commercial innovation in this area!
|
|
127
152
|
|
|
128
|
-
### 3. Logic
|
|
153
|
+
### 3. Full Logic Support
|
|
129
154
|
|
|
130
|
-
Unlike some templating systems which prohibit, or
|
|
155
|
+
Unlike some templating systems which prohibit, or minimize, logic in templates, Accord Project templates fully embrace templates that may contain sophisticated logic: conditional logic to determine what text to include, or even calculations, for example to calculate the monthly payments for a mortgage based on the term of the mortgage, the amount and the interest rate.
|
|
131
156
|
|
|
132
157
|
### 4. Type-safety
|
|
133
158
|
|
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:
|
|
@@ -910,7 +924,12 @@ async function generateAgreement(modelManager, clauseLibrary, templateMark, data
|
|
|
910
924
|
delete context.dependencies;
|
|
911
925
|
delete context.functionName;
|
|
912
926
|
} else if (CLAUSE_DEFINITION_RE.test(nodeClass)) {
|
|
913
|
-
|
|
927
|
+
const path = getJsonPath(templateMark, context, this.path);
|
|
928
|
+
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) {
|
|
914
933
|
checkCode(context.condition);
|
|
915
934
|
const result = !!userCodeResults[this.path.join("/")];
|
|
916
935
|
if (!result) {
|
|
@@ -973,10 +992,45 @@ class TemplateMarkInterpreter {
|
|
|
973
992
|
const serializer = new concertoCore.Serializer(factory, modelManager);
|
|
974
993
|
try {
|
|
975
994
|
serializer.fromJSON(templateMark);
|
|
976
|
-
return templateMark;
|
|
977
995
|
} catch (err) {
|
|
978
996
|
throw new Error(`Generated invalid agreement: ${err}: ${JSON.stringify(templateMark, null, 2)}`);
|
|
979
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;
|
|
980
1034
|
}
|
|
981
1035
|
/**
|
|
982
1036
|
* Compiles the code nodes containing TS to code nodes containing JS.
|