@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.
Files changed (139) hide show
  1. package/README.md +41 -0
  2. package/lib/JavaScriptEvaluator.d.ts +71 -0
  3. package/lib/JavaScriptEvaluator.js +245 -0
  4. package/lib/JavaScriptEvaluator.js.map +1 -0
  5. package/lib/TemplateArchiveProcessor.d.ts +151 -0
  6. package/lib/TemplateArchiveProcessor.js +447 -0
  7. package/lib/TemplateArchiveProcessor.js.map +1 -0
  8. package/lib/TemplateMarkInterpreter.d.ts +34 -0
  9. package/lib/TemplateMarkInterpreter.js +689 -0
  10. package/lib/TemplateMarkInterpreter.js.map +1 -0
  11. package/lib/TemplateMarkNodes.d.ts +19 -0
  12. package/lib/TemplateMarkNodes.js +45 -0
  13. package/lib/TemplateMarkNodes.js.map +1 -0
  14. package/lib/TemplateMarkToJavaScriptCompiler.d.ts +20 -0
  15. package/lib/TemplateMarkToJavaScriptCompiler.js +99 -0
  16. package/lib/TemplateMarkToJavaScriptCompiler.js.map +1 -0
  17. package/lib/TypeScriptCompilationContext.d.ts +42 -0
  18. package/lib/TypeScriptCompilationContext.js +170 -0
  19. package/lib/TypeScriptCompilationContext.js.map +1 -0
  20. package/lib/TypeScriptRuntime.d.ts +17 -0
  21. package/lib/TypeScriptRuntime.js +84 -0
  22. package/lib/TypeScriptRuntime.js.map +1 -0
  23. package/lib/TypeScriptToJavaScriptCompiler.d.ts +11 -0
  24. package/lib/TypeScriptToJavaScriptCompiler.js +169 -0
  25. package/lib/TypeScriptToJavaScriptCompiler.js.map +1 -0
  26. package/lib/drafting/Boolean/index.d.ts +6 -0
  27. package/lib/drafting/Boolean/index.js +30 -0
  28. package/lib/drafting/Boolean/index.js.map +1 -0
  29. package/lib/drafting/DateTime/index.d.ts +8 -0
  30. package/lib/drafting/DateTime/index.js +33 -0
  31. package/lib/drafting/DateTime/index.js.map +1 -0
  32. package/lib/drafting/Double/format.d.ts +14 -0
  33. package/lib/drafting/Double/format.js +62 -0
  34. package/lib/drafting/Double/format.js.map +1 -0
  35. package/lib/drafting/Double/index.d.ts +8 -0
  36. package/lib/drafting/Double/index.js +33 -0
  37. package/lib/drafting/Double/index.js.map +1 -0
  38. package/lib/drafting/DraftFormat.d.ts +5 -0
  39. package/lib/drafting/DraftFormat.js +18 -0
  40. package/lib/drafting/DraftFormat.js.map +1 -0
  41. package/lib/drafting/Duration/index.d.ts +6 -0
  42. package/lib/drafting/Duration/index.js +39 -0
  43. package/lib/drafting/Duration/index.js.map +1 -0
  44. package/lib/drafting/Integer/format.d.ts +14 -0
  45. package/lib/drafting/Integer/format.js +52 -0
  46. package/lib/drafting/Integer/format.js.map +1 -0
  47. package/lib/drafting/Integer/index.d.ts +8 -0
  48. package/lib/drafting/Integer/index.js +33 -0
  49. package/lib/drafting/Integer/index.js.map +1 -0
  50. package/lib/drafting/Long/index.d.ts +8 -0
  51. package/lib/drafting/Long/index.js +33 -0
  52. package/lib/drafting/Long/index.js.map +1 -0
  53. package/lib/drafting/MonetaryAmount/currencycode.d.ts +35 -0
  54. package/lib/drafting/MonetaryAmount/currencycode.js +53 -0
  55. package/lib/drafting/MonetaryAmount/currencycode.js.map +1 -0
  56. package/lib/drafting/MonetaryAmount/formatTokens.d.ts +13 -0
  57. package/lib/drafting/MonetaryAmount/formatTokens.js +44 -0
  58. package/lib/drafting/MonetaryAmount/formatTokens.js.map +1 -0
  59. package/lib/drafting/MonetaryAmount/index.d.ts +13 -0
  60. package/lib/drafting/MonetaryAmount/index.js +51 -0
  61. package/lib/drafting/MonetaryAmount/index.js.map +1 -0
  62. package/lib/drafting/PreciseAmount/index.d.ts +17 -0
  63. package/lib/drafting/PreciseAmount/index.js +113 -0
  64. package/lib/drafting/PreciseAmount/index.js.map +1 -0
  65. package/lib/drafting/String/index.d.ts +6 -0
  66. package/lib/drafting/String/index.js +25 -0
  67. package/lib/drafting/String/index.js.map +1 -0
  68. package/lib/drafting/index.d.ts +2 -0
  69. package/lib/drafting/index.js +63 -0
  70. package/lib/drafting/index.js.map +1 -0
  71. package/lib/index.d.ts +7 -0
  72. package/lib/index.js +42 -0
  73. package/lib/index.js.map +1 -0
  74. package/lib/llm/LLMConfig.d.ts +121 -0
  75. package/lib/llm/LLMConfig.js +30 -0
  76. package/lib/llm/LLMConfig.js.map +1 -0
  77. package/lib/llm/LLMExecutor.d.ts +77 -0
  78. package/lib/llm/LLMExecutor.js +603 -0
  79. package/lib/llm/LLMExecutor.js.map +1 -0
  80. package/lib/llm/ModelManagerSchema.d.ts +14 -0
  81. package/lib/llm/ModelManagerSchema.js +67 -0
  82. package/lib/llm/ModelManagerSchema.js.map +1 -0
  83. package/lib/llm/Reasoners.d.ts +190 -0
  84. package/lib/llm/Reasoners.js +643 -0
  85. package/lib/llm/Reasoners.js.map +1 -0
  86. package/lib/model-gen/concerto.d.ts +13 -0
  87. package/lib/model-gen/concerto.js +4 -0
  88. package/lib/model-gen/concerto.js.map +1 -0
  89. package/lib/model-gen/concerto.metamodel@1.0.0.d.ts +196 -0
  90. package/lib/model-gen/concerto.metamodel@1.0.0.js +4 -0
  91. package/lib/model-gen/concerto.metamodel@1.0.0.js.map +1 -0
  92. package/lib/model-gen/concerto@1.0.0.d.ts +19 -0
  93. package/lib/model-gen/concerto@1.0.0.js +4 -0
  94. package/lib/model-gen/concerto@1.0.0.js.map +1 -0
  95. package/lib/model-gen/org.accordproject.ciceromark@0.6.0.d.ts +49 -0
  96. package/lib/model-gen/org.accordproject.ciceromark@0.6.0.js +4 -0
  97. package/lib/model-gen/org.accordproject.ciceromark@0.6.0.js.map +1 -0
  98. package/lib/model-gen/org.accordproject.commonmark@0.5.0.d.ts +89 -0
  99. package/lib/model-gen/org.accordproject.commonmark@0.5.0.js +4 -0
  100. package/lib/model-gen/org.accordproject.commonmark@0.5.0.js.map +1 -0
  101. package/lib/model-gen/org.accordproject.templatemark@0.5.0.d.ts +76 -0
  102. package/lib/model-gen/org.accordproject.templatemark@0.5.0.js +11 -0
  103. package/lib/model-gen/org.accordproject.templatemark@0.5.0.js.map +1 -0
  104. package/lib/runtime/declarations.d.ts +2 -0
  105. package/lib/runtime/declarations.js +21 -0
  106. package/lib/runtime/declarations.js.map +1 -0
  107. package/lib/slc/model-gen/concerto.d.ts +13 -0
  108. package/lib/slc/model-gen/concerto.decorator@1.0.0.d.ts +6 -0
  109. package/lib/slc/model-gen/concerto.decorator@1.0.0.js +5 -0
  110. package/lib/slc/model-gen/concerto.decorator@1.0.0.js.map +1 -0
  111. package/lib/slc/model-gen/concerto.js +5 -0
  112. package/lib/slc/model-gen/concerto.js.map +1 -0
  113. package/lib/slc/model-gen/concerto@1.0.0.d.ts +24 -0
  114. package/lib/slc/model-gen/concerto@1.0.0.js +6 -0
  115. package/lib/slc/model-gen/concerto@1.0.0.js.map +1 -0
  116. package/lib/slc/model-gen/org.accordproject.contract@0.2.0.d.ts +7 -0
  117. package/lib/slc/model-gen/org.accordproject.contract@0.2.0.js +5 -0
  118. package/lib/slc/model-gen/org.accordproject.contract@0.2.0.js.map +1 -0
  119. package/lib/slc/model-gen/org.accordproject.runtime@0.2.0.d.ts +15 -0
  120. package/lib/slc/model-gen/org.accordproject.runtime@0.2.0.js +4 -0
  121. package/lib/slc/model-gen/org.accordproject.runtime@0.2.0.js.map +1 -0
  122. package/lib/slc/model-gen/org.accordproject.time@0.3.0.d.ts +46 -0
  123. package/lib/slc/model-gen/org.accordproject.time@0.3.0.js +48 -0
  124. package/lib/slc/model-gen/org.accordproject.time@0.3.0.js.map +1 -0
  125. package/lib/utils.d.ts +35 -0
  126. package/lib/utils.js +122 -0
  127. package/lib/utils.js.map +1 -0
  128. package/lib/worker.d.ts +1 -0
  129. package/{dist → lib}/worker.js +12 -10
  130. package/lib/worker.js.map +1 -0
  131. package/package.json +46 -25
  132. package/umd/template-engine.js +13 -0
  133. package/umd/template-engine.js.LICENSE.txt +21 -0
  134. package/dist/index.d.ts +0 -186
  135. package/dist/index.js +0 -1275
  136. package/dist/index.js.map +0 -1
  137. package/dist/index.mjs +0 -1248
  138. package/dist/index.mjs.map +0 -1
  139. 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