@accordproject/template-engine 3.1.0 → 4.2.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/README.md +41 -0
- package/lib/JavaScriptEvaluator.d.ts +71 -0
- package/lib/JavaScriptEvaluator.js +245 -0
- package/lib/JavaScriptEvaluator.js.map +1 -0
- package/lib/TemplateArchiveProcessor.d.ts +151 -0
- package/lib/TemplateArchiveProcessor.js +447 -0
- package/lib/TemplateArchiveProcessor.js.map +1 -0
- package/lib/TemplateMarkInterpreter.d.ts +34 -0
- package/lib/TemplateMarkInterpreter.js +689 -0
- package/lib/TemplateMarkInterpreter.js.map +1 -0
- package/lib/TemplateMarkNodes.d.ts +19 -0
- package/lib/TemplateMarkNodes.js +45 -0
- package/lib/TemplateMarkNodes.js.map +1 -0
- package/lib/TemplateMarkToJavaScriptCompiler.d.ts +20 -0
- package/lib/TemplateMarkToJavaScriptCompiler.js +99 -0
- package/lib/TemplateMarkToJavaScriptCompiler.js.map +1 -0
- package/lib/TypeScriptCompilationContext.d.ts +42 -0
- package/lib/TypeScriptCompilationContext.js +170 -0
- package/lib/TypeScriptCompilationContext.js.map +1 -0
- package/lib/TypeScriptRuntime.d.ts +17 -0
- package/lib/TypeScriptRuntime.js +84 -0
- package/lib/TypeScriptRuntime.js.map +1 -0
- package/lib/TypeScriptToJavaScriptCompiler.d.ts +11 -0
- package/lib/TypeScriptToJavaScriptCompiler.js +169 -0
- package/lib/TypeScriptToJavaScriptCompiler.js.map +1 -0
- package/lib/drafting/Boolean/index.d.ts +6 -0
- package/lib/drafting/Boolean/index.js +30 -0
- package/lib/drafting/Boolean/index.js.map +1 -0
- package/lib/drafting/DateTime/index.d.ts +8 -0
- package/lib/drafting/DateTime/index.js +33 -0
- package/lib/drafting/DateTime/index.js.map +1 -0
- package/lib/drafting/Double/format.d.ts +14 -0
- package/lib/drafting/Double/format.js +62 -0
- package/lib/drafting/Double/format.js.map +1 -0
- package/lib/drafting/Double/index.d.ts +8 -0
- package/lib/drafting/Double/index.js +33 -0
- package/lib/drafting/Double/index.js.map +1 -0
- package/lib/drafting/DraftFormat.d.ts +5 -0
- package/lib/drafting/DraftFormat.js +18 -0
- package/lib/drafting/DraftFormat.js.map +1 -0
- package/lib/drafting/Duration/index.d.ts +6 -0
- package/lib/drafting/Duration/index.js +39 -0
- package/lib/drafting/Duration/index.js.map +1 -0
- package/lib/drafting/Integer/format.d.ts +14 -0
- package/lib/drafting/Integer/format.js +52 -0
- package/lib/drafting/Integer/format.js.map +1 -0
- package/lib/drafting/Integer/index.d.ts +8 -0
- package/lib/drafting/Integer/index.js +33 -0
- package/lib/drafting/Integer/index.js.map +1 -0
- package/lib/drafting/Long/index.d.ts +8 -0
- package/lib/drafting/Long/index.js +33 -0
- package/lib/drafting/Long/index.js.map +1 -0
- package/lib/drafting/MonetaryAmount/currencycode.d.ts +35 -0
- package/lib/drafting/MonetaryAmount/currencycode.js +53 -0
- package/lib/drafting/MonetaryAmount/currencycode.js.map +1 -0
- package/lib/drafting/MonetaryAmount/formatTokens.d.ts +13 -0
- package/lib/drafting/MonetaryAmount/formatTokens.js +44 -0
- package/lib/drafting/MonetaryAmount/formatTokens.js.map +1 -0
- package/lib/drafting/MonetaryAmount/index.d.ts +13 -0
- package/lib/drafting/MonetaryAmount/index.js +51 -0
- package/lib/drafting/MonetaryAmount/index.js.map +1 -0
- package/lib/drafting/PreciseAmount/index.d.ts +17 -0
- package/lib/drafting/PreciseAmount/index.js +113 -0
- package/lib/drafting/PreciseAmount/index.js.map +1 -0
- package/lib/drafting/String/index.d.ts +6 -0
- package/lib/drafting/String/index.js +25 -0
- package/lib/drafting/String/index.js.map +1 -0
- package/lib/drafting/index.d.ts +2 -0
- package/lib/drafting/index.js +63 -0
- package/lib/drafting/index.js.map +1 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.js +42 -0
- package/lib/index.js.map +1 -0
- package/lib/llm/LLMConfig.d.ts +121 -0
- package/lib/llm/LLMConfig.js +30 -0
- package/lib/llm/LLMConfig.js.map +1 -0
- package/lib/llm/LLMExecutor.d.ts +77 -0
- package/lib/llm/LLMExecutor.js +603 -0
- package/lib/llm/LLMExecutor.js.map +1 -0
- package/lib/llm/ModelManagerSchema.d.ts +14 -0
- package/lib/llm/ModelManagerSchema.js +67 -0
- package/lib/llm/ModelManagerSchema.js.map +1 -0
- package/lib/llm/Reasoners.d.ts +190 -0
- package/lib/llm/Reasoners.js +643 -0
- package/lib/llm/Reasoners.js.map +1 -0
- package/lib/model-gen/concerto.d.ts +13 -0
- package/lib/model-gen/concerto.js +4 -0
- package/lib/model-gen/concerto.js.map +1 -0
- package/lib/model-gen/concerto.metamodel@1.0.0.d.ts +196 -0
- package/lib/model-gen/concerto.metamodel@1.0.0.js +4 -0
- package/lib/model-gen/concerto.metamodel@1.0.0.js.map +1 -0
- package/lib/model-gen/concerto@1.0.0.d.ts +19 -0
- package/lib/model-gen/concerto@1.0.0.js +4 -0
- package/lib/model-gen/concerto@1.0.0.js.map +1 -0
- package/lib/model-gen/org.accordproject.ciceromark@0.6.0.d.ts +49 -0
- package/lib/model-gen/org.accordproject.ciceromark@0.6.0.js +4 -0
- package/lib/model-gen/org.accordproject.ciceromark@0.6.0.js.map +1 -0
- package/lib/model-gen/org.accordproject.commonmark@0.5.0.d.ts +89 -0
- package/lib/model-gen/org.accordproject.commonmark@0.5.0.js +4 -0
- package/lib/model-gen/org.accordproject.commonmark@0.5.0.js.map +1 -0
- package/lib/model-gen/org.accordproject.templatemark@0.5.0.d.ts +76 -0
- package/lib/model-gen/org.accordproject.templatemark@0.5.0.js +11 -0
- package/lib/model-gen/org.accordproject.templatemark@0.5.0.js.map +1 -0
- package/lib/runtime/declarations.d.ts +2 -0
- package/lib/runtime/declarations.js +21 -0
- package/lib/runtime/declarations.js.map +1 -0
- package/lib/slc/model-gen/concerto.d.ts +13 -0
- package/lib/slc/model-gen/concerto.decorator@1.0.0.d.ts +6 -0
- package/lib/slc/model-gen/concerto.decorator@1.0.0.js +5 -0
- package/lib/slc/model-gen/concerto.decorator@1.0.0.js.map +1 -0
- package/lib/slc/model-gen/concerto.js +5 -0
- package/lib/slc/model-gen/concerto.js.map +1 -0
- package/lib/slc/model-gen/concerto@1.0.0.d.ts +24 -0
- package/lib/slc/model-gen/concerto@1.0.0.js +6 -0
- package/lib/slc/model-gen/concerto@1.0.0.js.map +1 -0
- package/lib/slc/model-gen/org.accordproject.contract@0.2.0.d.ts +7 -0
- package/lib/slc/model-gen/org.accordproject.contract@0.2.0.js +5 -0
- package/lib/slc/model-gen/org.accordproject.contract@0.2.0.js.map +1 -0
- package/lib/slc/model-gen/org.accordproject.runtime@0.2.0.d.ts +15 -0
- package/lib/slc/model-gen/org.accordproject.runtime@0.2.0.js +4 -0
- package/lib/slc/model-gen/org.accordproject.runtime@0.2.0.js.map +1 -0
- package/lib/slc/model-gen/org.accordproject.time@0.3.0.d.ts +46 -0
- package/lib/slc/model-gen/org.accordproject.time@0.3.0.js +48 -0
- package/lib/slc/model-gen/org.accordproject.time@0.3.0.js.map +1 -0
- package/lib/utils.d.ts +35 -0
- package/lib/utils.js +122 -0
- package/lib/utils.js.map +1 -0
- package/lib/worker.d.ts +1 -0
- package/{dist → lib}/worker.js +12 -10
- package/lib/worker.js.map +1 -0
- package/package.json +46 -25
- package/umd/template-engine.js +13 -0
- package/umd/template-engine.js.LICENSE.txt +21 -0
- package/dist/index.d.ts +0 -186
- package/dist/index.js +0 -1275
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -1248
- package/dist/index.mjs.map +0 -1
- package/dist/worker.js.map +0 -1
|
@@ -0,0 +1,689 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License.
|
|
5
|
+
* You may obtain a copy of the License at
|
|
6
|
+
*
|
|
7
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
*
|
|
9
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
* See the License for the specific language governing permissions and
|
|
13
|
+
* limitations under the License.
|
|
14
|
+
*/
|
|
15
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
16
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
17
|
+
};
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.TemplateMarkInterpreter = void 0;
|
|
20
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
21
|
+
const jsonpath_1 = __importDefault(require("jsonpath"));
|
|
22
|
+
const traverse_1 = __importDefault(require("traverse"));
|
|
23
|
+
const browser_or_node_1 = require("browser-or-node");
|
|
24
|
+
const os_1 = __importDefault(require("os"));
|
|
25
|
+
const concerto_core_1 = require("@accordproject/concerto-core");
|
|
26
|
+
const drafting_1 = require("./drafting");
|
|
27
|
+
const markdown_common_1 = require("@accordproject/markdown-common");
|
|
28
|
+
const concerto_core_2 = require("@accordproject/concerto-core");
|
|
29
|
+
const TemplateMarkNodes_1 = require("./TemplateMarkNodes");
|
|
30
|
+
const TemplateMarkToJavaScriptCompiler_1 = require("./TemplateMarkToJavaScriptCompiler");
|
|
31
|
+
const org_accordproject_templatemark_0_5_0_1 = require("./model-gen/org.accordproject.templatemark@0.5.0");
|
|
32
|
+
const TypeScriptRuntime_1 = require("./TypeScriptRuntime");
|
|
33
|
+
const utils_1 = require("./utils");
|
|
34
|
+
const JavaScriptEvaluator_1 = require("./JavaScriptEvaluator");
|
|
35
|
+
function checkCode(code) {
|
|
36
|
+
if (code.type !== org_accordproject_templatemark_0_5_0_1.CodeType.ES_2020) {
|
|
37
|
+
throw new Error(`Cannot run ${code.contents} as it is not ES_2020 JavaScript.`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// this is a global because we don't want the user
|
|
41
|
+
// to configure child processes at the TemplateMarkInterpreter instance level
|
|
42
|
+
const javaScriptEvaluator = browser_or_node_1.isBrowser ? new JavaScriptEvaluator_1.JavaScriptEvaluator() : new JavaScriptEvaluator_1.JavaScriptEvaluator({
|
|
43
|
+
maxWorkers: process.env.MAX_WORKERS ? Number.parseInt(process.env.MAX_WORKERS) : os_1.default.availableParallelism(), // how many child processes
|
|
44
|
+
waitInterval: process.env.WAIT_INTERVAL ? Number.parseInt(process.env.WAIT_INTERVAL) : 50, // how long to wait before rescheduling work
|
|
45
|
+
maxQueueDepth: process.env.MAX_QUEUE_DEPTH ? Number.parseInt(process.env.MAX_QUEUE_DEPTH) : 1000 // max requests to queue
|
|
46
|
+
});
|
|
47
|
+
const TEMPLATEMARK_ROOT_NODES = [
|
|
48
|
+
'org.accordproject.templatemark@0.5.0.ClauseDefinition',
|
|
49
|
+
'org.accordproject.templatemark@0.5.0.ContractDefinition'
|
|
50
|
+
];
|
|
51
|
+
const DOCUMENT_ROOT = 'org.accordproject.commonmark@0.5.0.Document';
|
|
52
|
+
/**
|
|
53
|
+
* Evaluates a JS expression
|
|
54
|
+
* @param {*} clauseLibrary the clause library
|
|
55
|
+
* @param {*} data the contract data
|
|
56
|
+
* @param {string} fn the JS function (including header)
|
|
57
|
+
* @param {GenerationOptions} options the generation options
|
|
58
|
+
* @returns {object} the result of evaluating the expression against the data
|
|
59
|
+
*/
|
|
60
|
+
async function evaluateJavaScript(clauseLibrary, data, fn, options) {
|
|
61
|
+
if (options?.disableJavaScriptEvaluation) {
|
|
62
|
+
throw new Error('JavaScript evaluation is disabled.');
|
|
63
|
+
}
|
|
64
|
+
if (!data || !fn) {
|
|
65
|
+
throw new Error(`Cannot evaluate JS ${fn} against ${data}`);
|
|
66
|
+
}
|
|
67
|
+
const functionArgNames = new Array();
|
|
68
|
+
functionArgNames.push('data');
|
|
69
|
+
functionArgNames.push('library');
|
|
70
|
+
functionArgNames.push('options');
|
|
71
|
+
const functionArgValues = new Array();
|
|
72
|
+
functionArgValues.push(data);
|
|
73
|
+
functionArgValues.push(clauseLibrary);
|
|
74
|
+
functionArgValues.push(options);
|
|
75
|
+
// chop the function header and closing
|
|
76
|
+
const expression = fn.substring(fn.indexOf('{') + 1, fn.lastIndexOf('}'));
|
|
77
|
+
if (expression.trim().length === 0) {
|
|
78
|
+
throw new Error('Empty expression');
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const request = { code: expression, argumentNames: functionArgNames, arguments: functionArgValues };
|
|
82
|
+
if (options?.childProcessJavaScriptEvaluation) {
|
|
83
|
+
if (browser_or_node_1.isBrowser) {
|
|
84
|
+
throw new Error('Child process evaluation is not supported inside web browser');
|
|
85
|
+
}
|
|
86
|
+
const evalOptions = options?.timeout ? { timeout: options.timeout } : undefined;
|
|
87
|
+
const r = await javaScriptEvaluator.evalChildProcess(request, evalOptions);
|
|
88
|
+
return r;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
const r = await javaScriptEvaluator.evalDangerously(request);
|
|
92
|
+
return r;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
throw new Error(`Caught error ${JSON.stringify(err)} evaluating ${expression} with arguments ${JSON.stringify(functionArgValues)}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Calculates a JSON path to use to retrieve data, based on a TemplatemMark tree.
|
|
101
|
+
* For example, if we hit a VariableDefinition {{city}} that is nested inside a
|
|
102
|
+
* WithDefinition {{#with address}} then the JSON path returned should be '$.address.city'.
|
|
103
|
+
* Similarly ListBlockDefinition, JoinDefinition and OptionalDefinition also include
|
|
104
|
+
* property names that must apply to their child nodes.
|
|
105
|
+
* @param {*} rootData the root of the JSON document, typically this is a TemplateMark JSON
|
|
106
|
+
* @param {*} currentNode the current TemplateMark node we are processing
|
|
107
|
+
* @param {string[]} paths the traverse path to the current node
|
|
108
|
+
* @returns {string} the JSON path to use to retrieve data
|
|
109
|
+
*/
|
|
110
|
+
function getJsonPath(rootData, currentNode, paths) {
|
|
111
|
+
if (!currentNode) {
|
|
112
|
+
throw new Error('Node must be supplied');
|
|
113
|
+
}
|
|
114
|
+
if (!currentNode.name) {
|
|
115
|
+
throw new Error(`Node must have a name: ${JSON.stringify(currentNode)}`);
|
|
116
|
+
}
|
|
117
|
+
if (currentNode.name.indexOf('.') >= 0) {
|
|
118
|
+
// prevent JSON path injection
|
|
119
|
+
throw new Error(`Invalid name property ${currentNode.name}`);
|
|
120
|
+
}
|
|
121
|
+
if (!paths || !paths.length || paths.length < 1) {
|
|
122
|
+
throw new Error('Paths must be supplied');
|
|
123
|
+
}
|
|
124
|
+
const withPath = [];
|
|
125
|
+
for (let n = 1; n < paths.length; n++) {
|
|
126
|
+
const sub = paths.slice(0, n);
|
|
127
|
+
const obj = traverse_1.default.get(rootData, sub);
|
|
128
|
+
// HACK
|
|
129
|
+
// if(obj===undefined) {
|
|
130
|
+
// throw new Error(`Failed to find data with path ${sub} and data ${JSON.stringify(rootData, null, 2)}`);
|
|
131
|
+
// }
|
|
132
|
+
if (obj && obj.$class) {
|
|
133
|
+
if (TemplateMarkNodes_1.NAVIGATION_NODES.indexOf(obj.$class) >= 0) {
|
|
134
|
+
if (obj.name !== 'top') { // HACK!!
|
|
135
|
+
withPath.push(`['${obj.name}']`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (currentNode.name !== 'this' && currentNode.name !== 'top') {
|
|
141
|
+
withPath.push(`['${currentNode.name}']`);
|
|
142
|
+
}
|
|
143
|
+
return withPath.length > 0 ? `$${withPath.join('')}` : '$';
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Evaluates all the user code in a template mark document
|
|
147
|
+
* @param {*} clauseLibrary - the clause library
|
|
148
|
+
* @param {*} templateMark - the TemplateMark JSON document
|
|
149
|
+
* @param {*} data - the template data JSON
|
|
150
|
+
* @param {[GenerationOptions]} options - the generation options
|
|
151
|
+
* @returns {Promise<UserCodeResult>} a promise to a UserCodeResult
|
|
152
|
+
*/
|
|
153
|
+
async function evaluateUserCode(clauseLibrary, templateMark, data, options) {
|
|
154
|
+
const result = {};
|
|
155
|
+
const paths = (0, traverse_1.default)(templateMark).paths();
|
|
156
|
+
for (let n = 0; n < paths.length; n++) {
|
|
157
|
+
const path = paths[n];
|
|
158
|
+
const context = (0, traverse_1.default)(templateMark).get(path);
|
|
159
|
+
if (typeof context === 'object' && context.$class && typeof context.$class === 'string') {
|
|
160
|
+
const nodeClass = context.$class;
|
|
161
|
+
if (TemplateMarkNodes_1.FORMULA_DEFINITION_RE.test(nodeClass)) {
|
|
162
|
+
if (context.code) {
|
|
163
|
+
checkCode(context.code);
|
|
164
|
+
const evalResponse = await evaluateJavaScript(clauseLibrary, data, context.code.contents, options);
|
|
165
|
+
result[path.join('/')] = JSON.stringify(evalResponse.result);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
throw new Error('Formula node is missing code.');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
else if (TemplateMarkNodes_1.CONDITIONAL_DEFINITION_RE.test(nodeClass) || TemplateMarkNodes_1.CLAUSE_DEFINITION_RE.test(nodeClass)) {
|
|
172
|
+
if (context.condition) {
|
|
173
|
+
checkCode(context.condition);
|
|
174
|
+
const evalResponse = await evaluateJavaScript(clauseLibrary, data, context.condition.contents, options);
|
|
175
|
+
result[path.join('/')] = JSON.stringify(evalResponse.result);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Generates agreementmark for all optional blocks with proper context switching
|
|
184
|
+
* Warning: this is async and recursive
|
|
185
|
+
* @param {ModelManager} modelManager - the template model
|
|
186
|
+
* @param {*} clauseLibrary - the clause library
|
|
187
|
+
* @param {*} templateMark - the TemplateMark JSON document
|
|
188
|
+
* @param {*} data - the template data JSON
|
|
189
|
+
* @param {[GenerationOptions]} options - the generation options
|
|
190
|
+
* @returns {*} the AgreementMark JSON for optional blocks
|
|
191
|
+
*/
|
|
192
|
+
async function generateOptionalBlocks(modelManager, clauseLibrary, templateMark, data, options) {
|
|
193
|
+
const result = {};
|
|
194
|
+
const paths = (0, traverse_1.default)(templateMark).paths();
|
|
195
|
+
for (let n = 0; n < paths.length; n++) {
|
|
196
|
+
const thisPath = paths[n];
|
|
197
|
+
const context = (0, traverse_1.default)(templateMark).get(thisPath);
|
|
198
|
+
if (typeof context === 'object' && context.$class && typeof context.$class === 'string') {
|
|
199
|
+
const nodeClass = context.$class;
|
|
200
|
+
// evaluate optional blocks, processing the whenSome content with the optional property value as context
|
|
201
|
+
if (TemplateMarkNodes_1.OPTIONAL_DEFINITION_RE.test(nodeClass)) {
|
|
202
|
+
const path = getJsonPath(templateMark, context, thisPath);
|
|
203
|
+
const variableValues = jsonpath_1.default.query(data, path, 1);
|
|
204
|
+
if (variableValues.length > 0) {
|
|
205
|
+
// Optional property exists, process whenSome with the property value as context
|
|
206
|
+
const optionalPropertyValue = variableValues[0];
|
|
207
|
+
if (context.whenSome && context.whenSome.length > 0) {
|
|
208
|
+
// Create a paragraph wrapper for the whenSome content
|
|
209
|
+
const whenSomeParagraph = {
|
|
210
|
+
$class: 'org.accordproject.commonmark@0.5.0.Paragraph',
|
|
211
|
+
nodes: context.whenSome
|
|
212
|
+
};
|
|
213
|
+
// Process with the optional property value as the new data context
|
|
214
|
+
const subResult = await generateAgreement(modelManager, clauseLibrary, whenSomeParagraph, optionalPropertyValue, options);
|
|
215
|
+
result[thisPath.join('/')] = subResult.nodes ? subResult.nodes : [];
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
result[thisPath.join('/')] = [];
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// If optional property doesn't exist, we don't add anything to results
|
|
222
|
+
// and the normal processing will handle whenNone
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return result;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Generates agreementmark for all recursive blocks (list, foreach)
|
|
230
|
+
* Warning: this is async and recursive
|
|
231
|
+
* @param {ModelManager} modelManager - the template model
|
|
232
|
+
* @param {*} clauseLibrary - the clause library
|
|
233
|
+
* @param {*} templateMark - the TemplateMark JSON document
|
|
234
|
+
* @param {*} data - the template data JSON
|
|
235
|
+
* @param {RegExp} nodeRegExp - the regex used to match against $class of nodes
|
|
236
|
+
* @param {string} childNodeClass - the $class to use for the node that wraps child items
|
|
237
|
+
* @param {[GenerationOptions]} options - the generation options
|
|
238
|
+
* @returns {*} the AgreementMark JSON for the list block
|
|
239
|
+
*/
|
|
240
|
+
async function generateRecursiveBlocks(modelManager, clauseLibrary, templateMark, data, nodeRegExp, childNodeClass, options) {
|
|
241
|
+
const result = {};
|
|
242
|
+
const paths = (0, traverse_1.default)(templateMark).paths();
|
|
243
|
+
for (let n = 0; n < paths.length; n++) {
|
|
244
|
+
const thisPath = paths[n];
|
|
245
|
+
const context = (0, traverse_1.default)(templateMark).get(thisPath);
|
|
246
|
+
if (typeof context === 'object' && context.$class && typeof context.$class === 'string') {
|
|
247
|
+
const nodeClass = context.$class;
|
|
248
|
+
// evaluate nodes, recursing on each child item
|
|
249
|
+
if (nodeRegExp.test(nodeClass)) {
|
|
250
|
+
const path = getJsonPath(templateMark, context, thisPath);
|
|
251
|
+
const variableValues = jsonpath_1.default.query(data, path, 1);
|
|
252
|
+
if (variableValues.length === 0) {
|
|
253
|
+
throw new Error(`No values found for path '${path}' in data ${JSON.stringify(data)}.`);
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
const arrayData = variableValues[0];
|
|
257
|
+
if (!Array.isArray(arrayData)) {
|
|
258
|
+
throw new Error(`Values found for path '${path}' in data ${data} is not an array: ${arrayData}.`);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
// The shape of the ListBlockDefinition's children depends on whether
|
|
262
|
+
// the markdown parser produced a CommonMark List/Item wrapping. That
|
|
263
|
+
// wrapping is only emitted when the items have leading list markers
|
|
264
|
+
// (e.g. `- ` for ulist, `1. ` for olist). When users put direct
|
|
265
|
+
// variable references inside `{{#ulist}}` / `{{#olist}}` with no
|
|
266
|
+
// leading marker (see #145), the body is parsed as a single Paragraph
|
|
267
|
+
// directly under the ListBlockDefinition.
|
|
268
|
+
const firstChild = context.nodes[0];
|
|
269
|
+
const hasListWrapper = firstChild &&
|
|
270
|
+
firstChild.$class === `${markdown_common_1.CommonMarkModel.NAMESPACE}.List` &&
|
|
271
|
+
Array.isArray(firstChild.nodes) &&
|
|
272
|
+
firstChild.nodes.length > 0;
|
|
273
|
+
// When wrapped, the per-iteration template is the first Item inside
|
|
274
|
+
// the List. Otherwise the ListBlockDefinition's first child IS the
|
|
275
|
+
// per-iteration template (typically a Paragraph).
|
|
276
|
+
const itemTemplate = hasListWrapper ? firstChild.nodes[0] : firstChild;
|
|
277
|
+
const nodes = [];
|
|
278
|
+
for (let n = 0; n < arrayData.length; n++) {
|
|
279
|
+
const arrayItem = arrayData[n];
|
|
280
|
+
// arrayItem is now the data for the nested generation
|
|
281
|
+
const subResult = await generateAgreement(modelManager, clauseLibrary, itemTemplate, arrayItem, options);
|
|
282
|
+
// When the template had a List/Item wrapper, the processed Item's
|
|
283
|
+
// children (a Paragraph) become the children of the new output Item.
|
|
284
|
+
// Otherwise the processed block itself becomes the sole child of
|
|
285
|
+
// the new output node, preserving a valid block hierarchy.
|
|
286
|
+
const childNodes = hasListWrapper
|
|
287
|
+
? (subResult.nodes ? subResult.nodes : [])
|
|
288
|
+
: [subResult];
|
|
289
|
+
nodes.push({
|
|
290
|
+
$class: childNodeClass,
|
|
291
|
+
nodes: childNodes
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
result[thisPath.join('/')] = nodes;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return result;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Generates an AgreementMark JSON document from a template plus data.
|
|
304
|
+
* @param {ModelManager} modelManager - the template model
|
|
305
|
+
* @param {*} clauseLibrary - the clause library
|
|
306
|
+
* @param {*} templateMark - the TemplateMark JSON document
|
|
307
|
+
* @param {*} data - the template data JSON
|
|
308
|
+
* @param {[GenerationOptions]} options - the generation options
|
|
309
|
+
* @returns {*} the AgreementMark JSON
|
|
310
|
+
*/
|
|
311
|
+
async function generateAgreement(modelManager, clauseLibrary, templateMark, data, options) {
|
|
312
|
+
const introspector = new concerto_core_1.Introspector(modelManager);
|
|
313
|
+
// evaluate all the user code (async)
|
|
314
|
+
const userCodeResults = await evaluateUserCode(clauseLibrary, templateMark, data, options);
|
|
315
|
+
// evaluate all recursive blocks (async)
|
|
316
|
+
const listBlockResults = await generateRecursiveBlocks(modelManager, clauseLibrary, templateMark, data, TemplateMarkNodes_1.LISTBLOCK_DEFINITION_RE, `${markdown_common_1.CommonMarkModel.NAMESPACE}.Item`, options);
|
|
317
|
+
const foreachBlockResults = await generateRecursiveBlocks(modelManager, clauseLibrary, templateMark, data, TemplateMarkNodes_1.FOREACH_DEFINITION_RE, `${markdown_common_1.CommonMarkModel.NAMESPACE}.Paragraph`, options);
|
|
318
|
+
// evaluate all optional blocks (async)
|
|
319
|
+
const optionalBlockResults = await generateOptionalBlocks(modelManager, clauseLibrary, templateMark, data, options);
|
|
320
|
+
// traverse the templatemark, creating an output agreementmark tree
|
|
321
|
+
return (0, traverse_1.default)(templateMark).map(function (context) {
|
|
322
|
+
let stopHere = false;
|
|
323
|
+
if (typeof context === 'object' && context.$class && typeof context.$class === 'string') {
|
|
324
|
+
const nodeClass = context.$class;
|
|
325
|
+
// rewrite node types, mapping from TemplateMark namespace to CiceroMark
|
|
326
|
+
const match = nodeClass.match(TemplateMarkNodes_1.TEMPLATEMARK_RE);
|
|
327
|
+
if (match && match.length > 1) {
|
|
328
|
+
context.$class = `${markdown_common_1.CiceroMarkModel.NAMESPACE}.${match[3]}`;
|
|
329
|
+
}
|
|
330
|
+
// convert a contract node to a clause node (HACK)
|
|
331
|
+
if (TemplateMarkNodes_1.CONTRACT_DEFINITION_RE.test(nodeClass)) {
|
|
332
|
+
context.$class = `${markdown_common_1.CommonMarkModel.NAMESPACE}.Paragraph`;
|
|
333
|
+
delete context.name;
|
|
334
|
+
delete context.elementType;
|
|
335
|
+
}
|
|
336
|
+
// convert a WithDefinition to a Paragraph in the output
|
|
337
|
+
// not 100% sure we want to do that ... we may need to process
|
|
338
|
+
// the child variable nodes and reparent them?
|
|
339
|
+
if (TemplateMarkNodes_1.WITH_DEFINITION_RE.test(nodeClass)) {
|
|
340
|
+
context.$class = `${markdown_common_1.CommonMarkModel.NAMESPACE}.Paragraph`;
|
|
341
|
+
delete context.name;
|
|
342
|
+
delete context.elementType;
|
|
343
|
+
}
|
|
344
|
+
// add a 'value' property to FormulaDefinition
|
|
345
|
+
// with the result of evaluating the JS code
|
|
346
|
+
else if (TemplateMarkNodes_1.FORMULA_DEFINITION_RE.test(nodeClass)) {
|
|
347
|
+
if (context.code) {
|
|
348
|
+
const result = userCodeResults[this.path.join('/')];
|
|
349
|
+
if (result === null) {
|
|
350
|
+
context.value = '<null>';
|
|
351
|
+
}
|
|
352
|
+
else if (typeof result === 'string') {
|
|
353
|
+
context.value = result;
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
context.value = JSON.stringify(result);
|
|
357
|
+
}
|
|
358
|
+
delete context.code;
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
throw new Error('Formula node is missing code.');
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
// evaluate lists, recursing on each list item
|
|
365
|
+
else if (TemplateMarkNodes_1.LISTBLOCK_DEFINITION_RE.test(nodeClass)) {
|
|
366
|
+
context.$class = `${markdown_common_1.CommonMarkModel.NAMESPACE}.List`;
|
|
367
|
+
delete context.elementType;
|
|
368
|
+
delete context.name;
|
|
369
|
+
context.nodes = listBlockResults[this.path.join('/')];
|
|
370
|
+
stopHere = true; // do not process child nodes, we've already done it above...
|
|
371
|
+
}
|
|
372
|
+
// map over an array of items, joining them into a Text node
|
|
373
|
+
else if (TemplateMarkNodes_1.JOIN_DEFINITION_RE.test(nodeClass)) {
|
|
374
|
+
const path = getJsonPath(templateMark, context, this.path);
|
|
375
|
+
const variableValues = jsonpath_1.default.query(data, path, 1);
|
|
376
|
+
if (variableValues.length === 0) {
|
|
377
|
+
throw new Error(`No values found for path '${path}' in data ${JSON.stringify(data)}.`);
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
const arrayData = variableValues[0];
|
|
381
|
+
if (!Array.isArray(arrayData)) {
|
|
382
|
+
throw new Error(`Values found for path '${path}' in data ${data} is not an array: ${arrayData}.`);
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
context.$class = `${markdown_common_1.CommonMarkModel.NAMESPACE}.Text`;
|
|
386
|
+
const drafter = (0, drafting_1.getDrafter)(context.elementType);
|
|
387
|
+
context.text = (0, TypeScriptRuntime_1.joinList)(arrayData.map(arrayItem => {
|
|
388
|
+
return drafter ? drafter(arrayItem, context.format) : arrayItem;
|
|
389
|
+
}), context, options);
|
|
390
|
+
delete context.elementType;
|
|
391
|
+
delete context.name;
|
|
392
|
+
delete context.separator;
|
|
393
|
+
delete context.locale;
|
|
394
|
+
delete context.type;
|
|
395
|
+
delete context.style;
|
|
396
|
+
delete context.nodes;
|
|
397
|
+
stopHere = true; // do not process child nodes, we've already done it above...
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
// map over an array of items, joining them into a Text node
|
|
402
|
+
else if (TemplateMarkNodes_1.FOREACH_DEFINITION_RE.test(nodeClass)) {
|
|
403
|
+
context.$class = `${markdown_common_1.CommonMarkModel.NAMESPACE}.Foreach`;
|
|
404
|
+
delete context.elementType;
|
|
405
|
+
delete context.name;
|
|
406
|
+
context.nodes = foreachBlockResults[this.path.join('/')];
|
|
407
|
+
stopHere = true; // do not process child nodes, we've already done it above...
|
|
408
|
+
}
|
|
409
|
+
// add a 'value' property to VariableDefinition
|
|
410
|
+
// with the value of the variable from 'data'
|
|
411
|
+
else if (TemplateMarkNodes_1.VARIABLE_DEFINITION_RE.test(nodeClass) ||
|
|
412
|
+
TemplateMarkNodes_1.ENUM_VARIABLE_DEFINITION_RE.test(nodeClass) ||
|
|
413
|
+
TemplateMarkNodes_1.FORMATTED_VARIABLE_DEFINITION_RE.test(nodeClass)) {
|
|
414
|
+
if (typeof data === 'object') {
|
|
415
|
+
const path = getJsonPath(templateMark, context, this.path);
|
|
416
|
+
const variableValues = jsonpath_1.default.query(data, path, 1);
|
|
417
|
+
if (variableValues.length === 0) {
|
|
418
|
+
throw new Error(`No values found for path '${path}' in data ${JSON.stringify(data)}.`);
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
// convert the value to a string, optionally using the formatter
|
|
422
|
+
const variableValue = variableValues[0];
|
|
423
|
+
const type = concerto_core_2.ModelUtil.isPrimitiveType(context.elementType) ? null : introspector.getClassDeclaration(context.elementType);
|
|
424
|
+
// we want to draft Enums as strings, not objects
|
|
425
|
+
const drafter = (0, drafting_1.getDrafter)(type && type.isEnum() ? 'String' : context.elementType);
|
|
426
|
+
context.value = drafter ? drafter(variableValue, context.format) : JSON.stringify(variableValue);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
// a list of enum values or primitives brings us here
|
|
431
|
+
const variableValue = data;
|
|
432
|
+
const type = concerto_core_2.ModelUtil.isPrimitiveType(context.elementType) ? null : introspector.getClassDeclaration(context.elementType);
|
|
433
|
+
// we want to draft Enums as strings, not objects
|
|
434
|
+
const drafter = (0, drafting_1.getDrafter)(type && type.isEnum() ? 'String' : context.elementType);
|
|
435
|
+
context.value = drafter ? drafter(variableValue, context.format) : JSON.stringify(variableValue);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
// add a 'isTrue' property to ConditionDefinition
|
|
439
|
+
// with the result of evaluating the JS code or a boolean property
|
|
440
|
+
else if (TemplateMarkNodes_1.CONDITIONAL_DEFINITION_RE.test(nodeClass)) {
|
|
441
|
+
if (context.condition) {
|
|
442
|
+
const key = this.path.join('/');
|
|
443
|
+
const resultStr = userCodeResults[key];
|
|
444
|
+
if (resultStr === undefined || resultStr === null) {
|
|
445
|
+
// Treat missing result as false (e.g., condition didn't return a value)
|
|
446
|
+
context.isTrue = false;
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
try {
|
|
450
|
+
// Parse the JSON-stringified result to get the actual boolean value
|
|
451
|
+
context.isTrue = !!JSON.parse(resultStr);
|
|
452
|
+
}
|
|
453
|
+
catch (err) {
|
|
454
|
+
throw new Error(`Invalid JSON boolean result for condition '${key}': ${String(err)}`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
const path = getJsonPath(templateMark, context, this.path);
|
|
460
|
+
const variableValues = jsonpath_1.default.query(data, path, 1);
|
|
461
|
+
if (variableValues && variableValues.length) {
|
|
462
|
+
if (variableValues.length === 1) {
|
|
463
|
+
context.isTrue = !!variableValues[0];
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
throw new Error(`Multiple values found for path '${path}' in data ${data}.`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
context.isTrue = false;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
context.nodes = context.isTrue ? context.whenTrue : context.whenFalse;
|
|
474
|
+
delete context.condition;
|
|
475
|
+
delete context.dependencies;
|
|
476
|
+
delete context.functionName;
|
|
477
|
+
}
|
|
478
|
+
// only include the children of a clause if its condition is true
|
|
479
|
+
else if (TemplateMarkNodes_1.CLAUSE_DEFINITION_RE.test(nodeClass)) {
|
|
480
|
+
const path = getJsonPath(templateMark, context, this.path);
|
|
481
|
+
const variableValues = jsonpath_1.default.query(data, path, 1);
|
|
482
|
+
// If there's an explicit condition, evaluate it first (takes precedence over implicit check).
|
|
483
|
+
// This allows conditions like "return address!==undefined" to work correctly when
|
|
484
|
+
// the optional field is missing - the condition controls whether to render the clause.
|
|
485
|
+
if (context.condition) {
|
|
486
|
+
checkCode(context.condition);
|
|
487
|
+
const key = this.path.join('/');
|
|
488
|
+
const resultStr = userCodeResults[key];
|
|
489
|
+
let result = false;
|
|
490
|
+
if (resultStr === undefined || resultStr === null) {
|
|
491
|
+
// Treat missing result as false (e.g., condition didn't return a value)
|
|
492
|
+
result = false;
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
try {
|
|
496
|
+
// Parse the JSON-stringified result to get the actual boolean value
|
|
497
|
+
result = !!JSON.parse(resultStr);
|
|
498
|
+
}
|
|
499
|
+
catch (err) {
|
|
500
|
+
throw new Error(`Invalid JSON boolean result for condition '${key}': ${String(err)}`);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
if (!result) {
|
|
504
|
+
delete context.nodes;
|
|
505
|
+
stopHere = true;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
// Otherwise, apply implicit undefined check: skip clause block if scoped variable is undefined/null.
|
|
509
|
+
// Skip this check for the root template clause (name === 'top') since its data IS the root object.
|
|
510
|
+
else if (context.name !== 'top' && (variableValues.length === 0 || variableValues[0] === undefined || variableValues[0] === null)) {
|
|
511
|
+
delete context.nodes;
|
|
512
|
+
stopHere = true;
|
|
513
|
+
}
|
|
514
|
+
delete context.condition;
|
|
515
|
+
delete context.functionName;
|
|
516
|
+
}
|
|
517
|
+
// add a 'hasSome' property to OptionalDefinition
|
|
518
|
+
else if (TemplateMarkNodes_1.OPTIONAL_DEFINITION_RE.test(nodeClass)) {
|
|
519
|
+
const path = getJsonPath(templateMark, context, this.path);
|
|
520
|
+
const variableValues = jsonpath_1.default.query(data, path, 1);
|
|
521
|
+
if (variableValues && variableValues.length) {
|
|
522
|
+
if (variableValues.length === 1) {
|
|
523
|
+
context.hasSome = true;
|
|
524
|
+
context.whenNone = [];
|
|
525
|
+
// Check if we have processed optional blocks for this path
|
|
526
|
+
if (optionalBlockResults[this.path.join('/')]) {
|
|
527
|
+
context.nodes = optionalBlockResults[this.path.join('/')];
|
|
528
|
+
// Set whenSome to empty since we've processed it, but keep the field for validation
|
|
529
|
+
context.whenSome = [];
|
|
530
|
+
stopHere = true; // do not process child nodes, we've already done it above...
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
context.nodes = context.whenSome;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
throw new Error(`Multiple values found for path '${path}' in data ${data}.`);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
context.hasSome = false;
|
|
542
|
+
context.whenSome = [];
|
|
543
|
+
context.nodes = context.whenNone;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
this.update(context, stopHere);
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* A template engine: merges the markup and logic of a template with
|
|
552
|
+
* JSON data to produce JSON data.
|
|
553
|
+
*/
|
|
554
|
+
class TemplateMarkInterpreter {
|
|
555
|
+
constructor(modelManager, clauseLibrary, templateConceptFqn) {
|
|
556
|
+
this.modelManager = modelManager;
|
|
557
|
+
this.clauseLibrary = clauseLibrary;
|
|
558
|
+
this.templateClass = (0, utils_1.getTemplateClassDeclaration)(this.modelManager, templateConceptFqn);
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Checks that a TemplateMark JSON document is valid with respect to the
|
|
562
|
+
* TemplateMark model, as well as the template model.
|
|
563
|
+
*
|
|
564
|
+
* Checks:
|
|
565
|
+
* 1. Variable names are valid properties in the template model
|
|
566
|
+
* 2. Optional properties have guards
|
|
567
|
+
* @param {*} templateMark the TemplateMark JSON object
|
|
568
|
+
* @returns {*} TemplateMark JSON that has been typed checked and has type metadata added
|
|
569
|
+
* @throws {Error} if the templateMark document is invalid
|
|
570
|
+
*/
|
|
571
|
+
checkTypes(templateMark) {
|
|
572
|
+
const modelManager = new concerto_core_1.ModelManager();
|
|
573
|
+
modelManager.addCTOModel(markdown_common_1.ConcertoMetaModel.MODEL, 'concertometamodel.cto');
|
|
574
|
+
modelManager.addCTOModel(markdown_common_1.CommonMarkModel.MODEL, 'commonmark.cto');
|
|
575
|
+
modelManager.addCTOModel(markdown_common_1.TemplateMarkModel.MODEL, 'templatemark.cto');
|
|
576
|
+
const factory = new concerto_core_1.Factory(modelManager);
|
|
577
|
+
const serializer = new concerto_core_1.Serializer(factory, modelManager, {});
|
|
578
|
+
try {
|
|
579
|
+
serializer.fromJSON(templateMark, {});
|
|
580
|
+
}
|
|
581
|
+
catch (err) {
|
|
582
|
+
throw new Error(`Generated invalid agreement: ${err}: ${JSON.stringify(templateMark, null, 2)}`);
|
|
583
|
+
}
|
|
584
|
+
const errors = [];
|
|
585
|
+
const templateClass = this.templateClass;
|
|
586
|
+
const guardBlockPaths = new Map();
|
|
587
|
+
(0, traverse_1.default)(templateMark).forEach(function (node) {
|
|
588
|
+
if (!node || typeof node !== 'object' || !node.$class)
|
|
589
|
+
return;
|
|
590
|
+
const currentPath = this.path.join('/');
|
|
591
|
+
if (TemplateMarkNodes_1.OPTIONAL_DEFINITION_RE.test(node.$class) ||
|
|
592
|
+
TemplateMarkNodes_1.CONDITIONAL_DEFINITION_RE.test(node.$class) ||
|
|
593
|
+
TemplateMarkNodes_1.WITH_DEFINITION_RE.test(node.$class)) {
|
|
594
|
+
guardBlockPaths.set(node.name, currentPath);
|
|
595
|
+
}
|
|
596
|
+
if (TemplateMarkNodes_1.VARIABLE_DEFINITION_RE.test(node.$class) ||
|
|
597
|
+
TemplateMarkNodes_1.ENUM_VARIABLE_DEFINITION_RE.test(node.$class) ||
|
|
598
|
+
TemplateMarkNodes_1.FORMATTED_VARIABLE_DEFINITION_RE.test(node.$class)) {
|
|
599
|
+
const propName = node.name;
|
|
600
|
+
if (propName && propName !== 'this') {
|
|
601
|
+
try {
|
|
602
|
+
const property = templateClass.getProperty(propName);
|
|
603
|
+
if (property && property.isOptional()) {
|
|
604
|
+
const guardPath = guardBlockPaths.get(propName);
|
|
605
|
+
const isGuarded = guardPath !== undefined &&
|
|
606
|
+
(currentPath === guardPath || currentPath.startsWith(guardPath + '/'));
|
|
607
|
+
if (!isGuarded) {
|
|
608
|
+
errors.push({
|
|
609
|
+
propertyName: propName,
|
|
610
|
+
message: `Optional property '${propName}' is used without a guard. Wrap it in {{#optional ${propName}}}...{{/optional}} or {{#if ${propName}}}...{{/if}}.`
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
catch {
|
|
616
|
+
// Property not found at root level, might be nested - skip
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
if (errors.length > 0) {
|
|
622
|
+
const errorMessage = `Optional properties used without guards: ${errors.map(e => e.propertyName).join(', ')}`;
|
|
623
|
+
const error = new Error(errorMessage);
|
|
624
|
+
error.errors = errors;
|
|
625
|
+
throw error;
|
|
626
|
+
}
|
|
627
|
+
return templateMark;
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Compiles the code nodes containing TS to code nodes containing JS.
|
|
631
|
+
* @param {*} templateMark the TemplateMark JSON object
|
|
632
|
+
* @returns {*} TemplateMark JSON with JS nodes
|
|
633
|
+
* @throws {Error} if the templateMark document is invalid
|
|
634
|
+
*/
|
|
635
|
+
async compileTypeScriptToJavaScript(templateMark) {
|
|
636
|
+
const clazz = templateMark.$class;
|
|
637
|
+
if (clazz !== DOCUMENT_ROOT) {
|
|
638
|
+
throw new Error(`JSON is not CommonMark. $class is '${clazz}'. ${JSON.stringify(templateMark, null, 2)}`);
|
|
639
|
+
}
|
|
640
|
+
if (!templateMark.nodes || !templateMark.nodes.length || templateMark.nodes.length < 1) {
|
|
641
|
+
throw new Error(`CommonMark does not have nodes: ${JSON.stringify(templateMark, null, 2)}`);
|
|
642
|
+
}
|
|
643
|
+
const firstChild = templateMark.nodes[0];
|
|
644
|
+
const firstChildClazz = firstChild.$class;
|
|
645
|
+
if (!TEMPLATEMARK_ROOT_NODES.includes(firstChildClazz)) {
|
|
646
|
+
throw new Error(`First child is not templatemark. $class is '${firstChildClazz}'. ${JSON.stringify(templateMark, null, 2)}`);
|
|
647
|
+
}
|
|
648
|
+
const templateConcept = firstChild.elementType;
|
|
649
|
+
if (!templateConcept) {
|
|
650
|
+
throw new Error(`First child is not typed: ${JSON.stringify(templateMark, null, 2)}`);
|
|
651
|
+
}
|
|
652
|
+
if (firstChild.name !== 'top') {
|
|
653
|
+
throw new Error('First child is not named "top"!');
|
|
654
|
+
}
|
|
655
|
+
const compiler = new TemplateMarkToJavaScriptCompiler_1.TemplateMarkToJavaScriptCompiler(this.modelManager, templateConcept);
|
|
656
|
+
await compiler.initialize();
|
|
657
|
+
return compiler.compile(templateMark);
|
|
658
|
+
}
|
|
659
|
+
validateCiceroMark(ciceroMark) {
|
|
660
|
+
const modelManager = new concerto_core_1.ModelManager();
|
|
661
|
+
modelManager.addCTOModel(markdown_common_1.ConcertoMetaModel.MODEL, 'concertometamodel.cto');
|
|
662
|
+
modelManager.addCTOModel(markdown_common_1.CommonMarkModel.MODEL, 'commonmark.cto');
|
|
663
|
+
modelManager.addCTOModel(markdown_common_1.CiceroMarkModel.MODEL, 'ciceromark.cto');
|
|
664
|
+
const factory = new concerto_core_1.Factory(modelManager);
|
|
665
|
+
const serializer = new concerto_core_1.Serializer(factory, modelManager, {});
|
|
666
|
+
try {
|
|
667
|
+
return serializer.fromJSON(ciceroMark, {});
|
|
668
|
+
}
|
|
669
|
+
catch (err) {
|
|
670
|
+
throw new Error(`Generated invalid agreement: ${err}: ${JSON.stringify(ciceroMark, null, 2)}`);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
async generate(templateMark, data, options) {
|
|
674
|
+
const factory = new concerto_core_1.Factory(this.modelManager);
|
|
675
|
+
const serializer = new concerto_core_1.Serializer(factory, this.modelManager, {});
|
|
676
|
+
const templateData = serializer.fromJSON(data, {});
|
|
677
|
+
if (templateData.getFullyQualifiedType() !== this.templateClass.getFullyQualifiedName()) {
|
|
678
|
+
throw new Error(`Template data must be of type '${this.templateClass.getFullyQualifiedName()}'.`);
|
|
679
|
+
}
|
|
680
|
+
const typedTemplateMark = this.checkTypes(templateMark);
|
|
681
|
+
const jsTemplateMark = await this.compileTypeScriptToJavaScript(typedTemplateMark);
|
|
682
|
+
// console.log('Compiled JS: ' + JSON.stringify(jsTemplateMark, null, 2));
|
|
683
|
+
const ciceroMark = await generateAgreement(this.modelManager, this.clauseLibrary, jsTemplateMark, data, options);
|
|
684
|
+
// console.log('Generated AgreementMark');
|
|
685
|
+
return this.validateCiceroMark(ciceroMark);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
exports.TemplateMarkInterpreter = TemplateMarkInterpreter;
|
|
689
|
+
//# sourceMappingURL=TemplateMarkInterpreter.js.map
|