@cyberismo/data-handler 0.0.8 → 0.0.10

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 (258) hide show
  1. package/dist/command-handler.d.ts +11 -2
  2. package/dist/command-handler.js +61 -18
  3. package/dist/command-handler.js.map +1 -1
  4. package/dist/command-manager.js +8 -8
  5. package/dist/command-manager.js.map +1 -1
  6. package/dist/commands/calculate.d.ts +7 -44
  7. package/dist/commands/calculate.js +8 -389
  8. package/dist/commands/calculate.js.map +1 -1
  9. package/dist/commands/create.d.ts +6 -3
  10. package/dist/commands/create.js +27 -5
  11. package/dist/commands/create.js.map +1 -1
  12. package/dist/commands/edit.d.ts +15 -3
  13. package/dist/commands/edit.js +52 -9
  14. package/dist/commands/edit.js.map +1 -1
  15. package/dist/commands/export.d.ts +11 -9
  16. package/dist/commands/export.js +80 -28
  17. package/dist/commands/export.js.map +1 -1
  18. package/dist/commands/import.d.ts +7 -0
  19. package/dist/commands/import.js +13 -0
  20. package/dist/commands/import.js.map +1 -1
  21. package/dist/commands/move.d.ts +11 -12
  22. package/dist/commands/move.js +12 -13
  23. package/dist/commands/move.js.map +1 -1
  24. package/dist/commands/remove.d.ts +1 -3
  25. package/dist/commands/remove.js +4 -6
  26. package/dist/commands/remove.js.map +1 -1
  27. package/dist/commands/rename.d.ts +1 -3
  28. package/dist/commands/rename.js +3 -6
  29. package/dist/commands/rename.js.map +1 -1
  30. package/dist/commands/show.d.ts +37 -5
  31. package/dist/commands/show.js +89 -8
  32. package/dist/commands/show.js.map +1 -1
  33. package/dist/commands/transition.d.ts +1 -3
  34. package/dist/commands/transition.js +3 -5
  35. package/dist/commands/transition.js.map +1 -1
  36. package/dist/commands/update.d.ts +5 -1
  37. package/dist/commands/update.js +7 -1
  38. package/dist/commands/update.js.map +1 -1
  39. package/dist/commands/validate.d.ts +7 -0
  40. package/dist/commands/validate.js +31 -4
  41. package/dist/commands/validate.js.map +1 -1
  42. package/dist/containers/card-container.d.ts +6 -0
  43. package/dist/containers/card-container.js +61 -0
  44. package/dist/containers/card-container.js.map +1 -1
  45. package/dist/containers/project/calculation-engine.d.ts +90 -0
  46. package/dist/containers/project/calculation-engine.js +402 -0
  47. package/dist/containers/project/calculation-engine.js.map +1 -0
  48. package/dist/containers/project/resource-collector.js +9 -13
  49. package/dist/containers/project/resource-collector.js.map +1 -1
  50. package/dist/containers/project.d.ts +18 -1
  51. package/dist/containers/project.js +28 -55
  52. package/dist/containers/project.js.map +1 -1
  53. package/dist/containers/template.d.ts +5 -0
  54. package/dist/containers/template.js +9 -0
  55. package/dist/containers/template.js.map +1 -1
  56. package/dist/index.d.ts +5 -2
  57. package/dist/index.js +5 -1
  58. package/dist/index.js.map +1 -1
  59. package/dist/interfaces/macros.d.ts +4 -2
  60. package/dist/interfaces/project-interfaces.d.ts +21 -0
  61. package/dist/interfaces/project-interfaces.js +4 -0
  62. package/dist/interfaces/project-interfaces.js.map +1 -1
  63. package/dist/interfaces/resource-interfaces.d.ts +4 -2
  64. package/dist/interfaces/resource-interfaces.js.map +1 -1
  65. package/dist/macros/base-macro.d.ts +2 -1
  66. package/dist/macros/base-macro.js +14 -2
  67. package/dist/macros/base-macro.js.map +1 -1
  68. package/dist/macros/common.d.ts +16 -9
  69. package/dist/macros/common.js +22 -9
  70. package/dist/macros/common.js.map +1 -1
  71. package/dist/macros/createCards/index.d.ts +2 -2
  72. package/dist/macros/createCards/index.js +4 -0
  73. package/dist/macros/createCards/index.js.map +1 -1
  74. package/dist/macros/graph/index.d.ts +1 -3
  75. package/dist/macros/graph/index.js +1 -6
  76. package/dist/macros/graph/index.js.map +1 -1
  77. package/dist/macros/image/index.d.ts +39 -0
  78. package/dist/macros/image/index.js +82 -0
  79. package/dist/macros/image/index.js.map +1 -0
  80. package/dist/macros/image/metadata.d.ts +18 -0
  81. package/dist/macros/image/metadata.js +22 -0
  82. package/dist/macros/image/metadata.js.map +1 -0
  83. package/dist/macros/include/index.d.ts +31 -0
  84. package/dist/macros/include/index.js +94 -0
  85. package/dist/macros/include/index.js.map +1 -0
  86. package/dist/macros/include/metadata.d.ts +15 -0
  87. package/dist/macros/include/metadata.js +19 -0
  88. package/dist/macros/include/metadata.js.map +1 -0
  89. package/dist/macros/index.d.ts +33 -31
  90. package/dist/macros/index.js +142 -71
  91. package/dist/macros/index.js.map +1 -1
  92. package/dist/macros/percentage/index.d.ts +28 -0
  93. package/dist/macros/percentage/index.js +33 -0
  94. package/dist/macros/percentage/index.js.map +1 -0
  95. package/dist/macros/percentage/metadata.d.ts +15 -0
  96. package/dist/macros/percentage/metadata.js +19 -0
  97. package/dist/macros/percentage/metadata.js.map +1 -0
  98. package/dist/macros/report/index.d.ts +2 -6
  99. package/dist/macros/report/index.js +3 -7
  100. package/dist/macros/report/index.js.map +1 -1
  101. package/dist/macros/scoreCard/index.d.ts +14 -15
  102. package/dist/macros/scoreCard/index.js +14 -18
  103. package/dist/macros/scoreCard/index.js.map +1 -1
  104. package/dist/macros/vega/index.d.ts +28 -0
  105. package/dist/macros/vega/index.js +27 -0
  106. package/dist/macros/vega/index.js.map +1 -0
  107. package/dist/macros/vega/metadata.d.ts +15 -0
  108. package/dist/macros/vega/metadata.js +7 -0
  109. package/dist/macros/vega/metadata.js.map +1 -0
  110. package/dist/macros/vegalite/index.d.ts +26 -0
  111. package/dist/macros/vegalite/index.js +24 -0
  112. package/dist/macros/vegalite/index.js.map +1 -0
  113. package/dist/macros/vegalite/metadata.d.ts +15 -0
  114. package/dist/macros/vegalite/metadata.js +7 -0
  115. package/dist/macros/vegalite/metadata.js.map +1 -0
  116. package/dist/macros/xref/index.d.ts +26 -0
  117. package/dist/macros/xref/index.js +53 -0
  118. package/dist/macros/xref/index.js.map +1 -0
  119. package/dist/macros/xref/metadata.d.ts +15 -0
  120. package/dist/macros/xref/metadata.js +19 -0
  121. package/dist/macros/xref/metadata.js.map +1 -0
  122. package/dist/module-manager.d.ts +1 -0
  123. package/dist/module-manager.js +14 -4
  124. package/dist/module-manager.js.map +1 -1
  125. package/dist/permissions/action-guard.d.ts +2 -2
  126. package/dist/permissions/action-guard.js +1 -1
  127. package/dist/permissions/action-guard.js.map +1 -1
  128. package/dist/resources/card-type-resource.d.ts +2 -0
  129. package/dist/resources/card-type-resource.js +63 -0
  130. package/dist/resources/card-type-resource.js.map +1 -1
  131. package/dist/resources/file-resource.d.ts +2 -0
  132. package/dist/resources/file-resource.js +12 -4
  133. package/dist/resources/file-resource.js.map +1 -1
  134. package/dist/resources/folder-resource.d.ts +19 -0
  135. package/dist/resources/folder-resource.js +51 -2
  136. package/dist/resources/folder-resource.js.map +1 -1
  137. package/dist/resources/graph-model-resource.js +1 -1
  138. package/dist/resources/graph-model-resource.js.map +1 -1
  139. package/dist/resources/graph-view-resource.js +1 -1
  140. package/dist/resources/graph-view-resource.js.map +1 -1
  141. package/dist/resources/report-resource.js +1 -1
  142. package/dist/resources/report-resource.js.map +1 -1
  143. package/dist/resources/resource-object.d.ts +8 -0
  144. package/dist/resources/resource-object.js +10 -1
  145. package/dist/resources/resource-object.js.map +1 -1
  146. package/dist/resources/template-resource.js +1 -1
  147. package/dist/resources/template-resource.js.map +1 -1
  148. package/dist/resources/workflow-resource.d.ts +2 -0
  149. package/dist/resources/workflow-resource.js +53 -9
  150. package/dist/resources/workflow-resource.js.map +1 -1
  151. package/dist/svg/index.d.ts +15 -0
  152. package/dist/svg/index.js +16 -0
  153. package/dist/svg/index.js.map +1 -0
  154. package/dist/svg/lib.d.ts +9 -0
  155. package/dist/svg/lib.js +26 -0
  156. package/dist/svg/lib.js.map +1 -0
  157. package/dist/svg/percentage.d.ts +25 -0
  158. package/dist/svg/percentage.js +90 -0
  159. package/dist/svg/percentage.js.map +1 -0
  160. package/dist/svg/scoreCard.d.ts +19 -0
  161. package/dist/svg/scoreCard.js +55 -0
  162. package/dist/svg/scoreCard.js.map +1 -0
  163. package/dist/types/queries.d.ts +8 -7
  164. package/dist/types/queries.js.map +1 -1
  165. package/dist/utils/card-utils.d.ts +6 -0
  166. package/dist/utils/card-utils.js +12 -0
  167. package/dist/utils/card-utils.js.map +1 -1
  168. package/dist/utils/clingo-facts.d.ts +2 -1
  169. package/dist/utils/clingo-facts.js +19 -2
  170. package/dist/utils/clingo-facts.js.map +1 -1
  171. package/dist/utils/clingo-parser.d.ts +1 -0
  172. package/dist/utils/clingo-parser.js +22 -100
  173. package/dist/utils/clingo-parser.js.map +1 -1
  174. package/dist/utils/constants.d.ts +7 -0
  175. package/dist/utils/constants.js +14 -0
  176. package/dist/utils/constants.js.map +1 -1
  177. package/dist/utils/csv.js.map +1 -1
  178. package/dist/utils/report.d.ts +17 -3
  179. package/dist/utils/report.js +38 -2
  180. package/dist/utils/report.js.map +1 -1
  181. package/dist/utils/resource-utils.d.ts +2 -1
  182. package/dist/utils/resource-utils.js +12 -3
  183. package/dist/utils/resource-utils.js.map +1 -1
  184. package/dist/utils/user-preferences.d.ts +1 -11
  185. package/dist/utils/user-preferences.js +30 -13
  186. package/dist/utils/user-preferences.js.map +1 -1
  187. package/dist/utils/validate.d.ts +2 -3
  188. package/dist/utils/validate.js +2 -2
  189. package/dist/utils/validate.js.map +1 -1
  190. package/package.json +8 -6
  191. package/src/command-handler.ts +96 -17
  192. package/src/command-manager.ts +8 -8
  193. package/src/commands/calculate.ts +11 -525
  194. package/src/commands/create.ts +35 -6
  195. package/src/commands/edit.ts +94 -11
  196. package/src/commands/export.ts +104 -31
  197. package/src/commands/import.ts +16 -0
  198. package/src/commands/move.ts +12 -15
  199. package/src/commands/remove.ts +4 -8
  200. package/src/commands/rename.ts +3 -12
  201. package/src/commands/show.ts +126 -9
  202. package/src/commands/transition.ts +3 -7
  203. package/src/commands/update.ts +6 -0
  204. package/src/commands/validate.ts +41 -13
  205. package/src/containers/card-container.ts +74 -0
  206. package/src/containers/project/calculation-engine.ts +535 -0
  207. package/src/containers/project/resource-collector.ts +13 -15
  208. package/src/containers/project.ts +30 -66
  209. package/src/containers/template.ts +16 -0
  210. package/src/index.ts +13 -2
  211. package/src/interfaces/macros.ts +4 -1
  212. package/src/interfaces/project-interfaces.ts +27 -0
  213. package/src/interfaces/resource-interfaces.ts +5 -2
  214. package/src/macros/base-macro.ts +19 -4
  215. package/src/macros/common.ts +22 -9
  216. package/src/macros/createCards/index.ts +6 -2
  217. package/src/macros/graph/index.ts +6 -10
  218. package/src/macros/image/index.ts +128 -0
  219. package/src/macros/image/metadata.ts +25 -0
  220. package/src/macros/include/index.ts +143 -0
  221. package/src/macros/include/metadata.ts +22 -0
  222. package/src/macros/index.ts +150 -98
  223. package/src/macros/percentage/index.ts +50 -0
  224. package/src/macros/percentage/metadata.ts +22 -0
  225. package/src/macros/report/index.ts +4 -12
  226. package/src/macros/scoreCard/index.ts +21 -25
  227. package/src/macros/vega/index.ts +55 -0
  228. package/src/macros/vega/metadata.ts +21 -0
  229. package/src/macros/vegalite/index.ts +46 -0
  230. package/src/macros/vegalite/metadata.ts +21 -0
  231. package/src/macros/xref/index.ts +73 -0
  232. package/src/macros/xref/metadata.ts +22 -0
  233. package/src/module-manager.ts +15 -5
  234. package/src/permissions/action-guard.ts +3 -3
  235. package/src/resources/card-type-resource.ts +100 -0
  236. package/src/resources/file-resource.ts +16 -4
  237. package/src/resources/folder-resource.ts +59 -2
  238. package/src/resources/graph-model-resource.ts +1 -1
  239. package/src/resources/graph-view-resource.ts +1 -1
  240. package/src/resources/report-resource.ts +1 -1
  241. package/src/resources/resource-object.ts +19 -1
  242. package/src/resources/template-resource.ts +1 -1
  243. package/src/resources/workflow-resource.ts +68 -13
  244. package/src/svg/index.ts +15 -0
  245. package/src/svg/lib.ts +31 -0
  246. package/src/svg/percentage.ts +97 -0
  247. package/src/svg/scoreCard.ts +88 -0
  248. package/src/types/queries.ts +8 -7
  249. package/src/types/string-pixel-width.d.ts +23 -0
  250. package/src/utils/card-utils.ts +13 -0
  251. package/src/utils/clingo-facts.ts +65 -3
  252. package/src/utils/clingo-parser.ts +31 -144
  253. package/src/utils/constants.ts +16 -0
  254. package/src/utils/csv.ts +1 -1
  255. package/src/utils/report.ts +45 -4
  256. package/src/utils/resource-utils.ts +12 -2
  257. package/src/utils/user-preferences.ts +32 -14
  258. package/src/utils/validate.ts +3 -3
@@ -0,0 +1,535 @@
1
+ /**
2
+ Cyberismo
3
+ Copyright © Cyberismo Ltd and contributors 2025
4
+ This program is free software: you can redistribute it and/or modify it under
5
+ the terms of the GNU Affero General Public License version 3 as published by
6
+ the Free Software Foundation. This program is distributed in the hope that it
7
+ will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
8
+ of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
9
+ See the GNU Affero General Public License for more details.
10
+ You should have received a copy of the GNU Affero General Public
11
+ License along with this program. If not, see <https://www.gnu.org/licenses/>.
12
+ */
13
+
14
+ // node
15
+ import { basename, join, resolve } from 'node:path';
16
+ import { readFile } from 'node:fs/promises';
17
+
18
+ import { sanitizeSvgBase64 } from '../../utils/sanitize-svg.js';
19
+ import { instance } from '@viz-js/viz';
20
+
21
+ import type {
22
+ BaseResult,
23
+ ParseResult,
24
+ QueryName,
25
+ QueryResult,
26
+ } from '../../types/queries.js';
27
+ import type { Card, Context } from '../../interfaces/project-interfaces.js';
28
+ import ClingoParser from '../../utils/clingo-parser.js';
29
+ import { pathExists } from '../../utils/file-utils.js';
30
+ import { Mutex } from 'async-mutex';
31
+ import Handlebars from 'handlebars';
32
+ import { type Project, ResourcesFrom } from '../../containers/project.js';
33
+ import { getChildLogger } from '../../utils/log-utils.js';
34
+ import {
35
+ createCardFacts,
36
+ createCardTypeFacts,
37
+ createContextFacts,
38
+ createFieldTypeFacts,
39
+ createLinkTypeFacts,
40
+ createModuleFacts,
41
+ createProjectFacts,
42
+ createReportFacts,
43
+ createTemplateFacts,
44
+ createWorkflowFacts,
45
+ } from '../../utils/clingo-facts.js';
46
+ import { CardMetadataUpdater } from '../../card-metadata-updater.js';
47
+ import type {
48
+ CardType,
49
+ FieldType,
50
+ LinkType,
51
+ ReportMetadata,
52
+ TemplateMetadata,
53
+ Workflow,
54
+ } from '../../interfaces/resource-interfaces.js';
55
+ import {
56
+ removeAllPrograms,
57
+ solve,
58
+ setProgram,
59
+ removeProgram,
60
+ } from '@cyberismo/node-clingo';
61
+ import { generateReportContent } from '../../utils/report.js';
62
+ import { lpFiles, graphvizReport } from '@cyberismo/assets';
63
+ import {
64
+ type ResourceName,
65
+ resourceNameToString,
66
+ } from '../../utils/resource-utils.js';
67
+
68
+ // Define the all category that will be used for all programs
69
+ const ALL_CATEGORY = 'all';
70
+
71
+ export class CalculationEngine {
72
+ constructor(private project: Project) {}
73
+
74
+ private static mutex = new Mutex();
75
+
76
+ private get logger() {
77
+ return getChildLogger({
78
+ module: 'calculate',
79
+ });
80
+ }
81
+
82
+ // Storage for in-memory program content
83
+ private modules: string = '';
84
+
85
+ private modulesInitialized = false;
86
+
87
+ // Initialize modules during construction
88
+ private async initializeModules() {
89
+ try {
90
+ // Collect all available calculations at initialization time
91
+ this.modules = await this.generateModules();
92
+ this.modulesInitialized = true;
93
+ } catch (error) {
94
+ this.logger.error(error, 'Failed to initialize modules');
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Gets the logic program content for a specific card
100
+ * @param cardKey The key of the card
101
+ * @returns The logic program content for the card
102
+ */
103
+ public async cardLogicProgram(cardKey: string): Promise<string> {
104
+ const card = await this.project.findSpecificCard(cardKey, {
105
+ metadata: true,
106
+ });
107
+ if (!card) {
108
+ throw new Error(`Card '${cardKey}' does not exist in the project`);
109
+ }
110
+ return createCardFacts(card, this.project);
111
+ }
112
+
113
+ // // Wrapper to run onCreation query.
114
+ private async creationQuery(cardKeys: string[], context: Context) {
115
+ if (!cardKeys) return undefined;
116
+ return this.runQuery('onCreation', context, {
117
+ cardKeys,
118
+ });
119
+ }
120
+
121
+ // Generate card tree content
122
+ private async setCardTreeContent() {
123
+ const cards = await this.getCards(undefined);
124
+
125
+ for (const card of cards) {
126
+ await this.setCardContent(card);
127
+ }
128
+ }
129
+
130
+ private async setCardContent(card: Card) {
131
+ const cardContent = await createCardFacts(card, this.project);
132
+ setProgram(card.key, cardContent, [ALL_CATEGORY]);
133
+ }
134
+
135
+ // Generates logic programs related to modules (and project itself).
136
+ private async generateModules() {
137
+ const modules = await this.project.modules();
138
+ let content = '';
139
+ for (const module of await Promise.all(
140
+ modules.map((mod) => this.project.module(mod.name)),
141
+ )) {
142
+ if (!module) continue;
143
+ const moduleContent = createModuleFacts(module);
144
+ content = content.concat(moduleContent);
145
+ }
146
+ const projectContent = createProjectFacts(this.project.projectPrefix);
147
+ content = content.concat(projectContent);
148
+ return content;
149
+ }
150
+
151
+ // Sets individual CardType programs
152
+ private async setCardTypesPrograms() {
153
+ const cardTypes = await this.project.cardTypes();
154
+
155
+ for (const cardType of await Promise.all(
156
+ cardTypes.map((c) => this.project.resource<CardType>(c.name)),
157
+ )) {
158
+ if (!cardType) continue;
159
+
160
+ const cardTypeContent = createCardTypeFacts(cardType);
161
+ setProgram(cardType.name, cardTypeContent, [ALL_CATEGORY]);
162
+ }
163
+ }
164
+
165
+ // Sets individual FieldType programs
166
+ private async setFieldTypesPrograms() {
167
+ const fieldTypes = await this.project.fieldTypes();
168
+
169
+ for (const fieldType of await Promise.all(
170
+ fieldTypes.map((m) => this.project.resource<FieldType>(m.name)),
171
+ )) {
172
+ if (!fieldType) continue;
173
+
174
+ const fieldTypeContent = createFieldTypeFacts(fieldType);
175
+ setProgram(fieldType.name, fieldTypeContent, [ALL_CATEGORY]);
176
+ }
177
+ }
178
+
179
+ // Sets individual LinkType programs
180
+ private async setLinkTypesPrograms() {
181
+ const linkTypes = await this.project.linkTypes();
182
+
183
+ for (const linkType of await Promise.all(
184
+ linkTypes.map((c) => this.project.resource<LinkType>(c.name)),
185
+ )) {
186
+ if (!linkType) continue;
187
+
188
+ const linkTypeContent = createLinkTypeFacts(linkType);
189
+ setProgram(linkType.name, linkTypeContent, [ALL_CATEGORY]);
190
+ }
191
+ }
192
+
193
+ // Sets individual Workflow programs
194
+ private async setWorkflowsPrograms() {
195
+ const workflows = await this.project.workflows();
196
+
197
+ for (const workflow of await Promise.all(
198
+ workflows.map((m) => this.project.resource<Workflow>(m.name)),
199
+ )) {
200
+ if (!workflow) continue;
201
+
202
+ const workflowContent = createWorkflowFacts(workflow);
203
+ setProgram(workflow.name, workflowContent, [ALL_CATEGORY]);
204
+ }
205
+ }
206
+
207
+ // Sets individual Report programs
208
+ private async setReportsPrograms() {
209
+ const reports = await this.project.reports();
210
+
211
+ for (const report of await Promise.all(
212
+ reports.map((r) => this.project.resource<ReportMetadata>(r.name)),
213
+ )) {
214
+ if (!report) continue;
215
+
216
+ const reportContent = createReportFacts(report);
217
+ setProgram(report.name, reportContent, [ALL_CATEGORY]);
218
+ }
219
+ }
220
+
221
+ // Sets individual Template programs
222
+ private async setTemplatesPrograms() {
223
+ const templates = await this.project.templates();
224
+
225
+ for (const template of await Promise.all(
226
+ templates.map((r) => this.project.resource<TemplateMetadata>(r.name)),
227
+ )) {
228
+ if (!template) continue;
229
+
230
+ const templateContent = createTemplateFacts(template);
231
+ const cards = await this.getCards(template.name);
232
+ for (const card of cards) {
233
+ const cardContent = await createCardFacts(card, this.project);
234
+ setProgram(card.key, cardContent, [ALL_CATEGORY]);
235
+ }
236
+ setProgram(template.name, templateContent, [ALL_CATEGORY]);
237
+ }
238
+ }
239
+
240
+ // Sets individual Calculation programs
241
+ private async setCalculationsPrograms() {
242
+ const calculations = await this.project.calculations(ResourcesFrom.all);
243
+
244
+ for (const calculationFile of calculations) {
245
+ if (calculationFile.path) {
246
+ const moduleLogicFile = resolve(
247
+ join(calculationFile.path, basename(calculationFile.name)),
248
+ );
249
+
250
+ const filePath = moduleLogicFile.endsWith('.lp')
251
+ ? moduleLogicFile
252
+ : moduleLogicFile + '.lp';
253
+
254
+ if (pathExists(filePath)) {
255
+ try {
256
+ const moduleContent = await readFile(filePath, 'utf-8');
257
+ setProgram(calculationFile.name, moduleContent, [ALL_CATEGORY]);
258
+ } catch (error) {
259
+ this.logger.warn(
260
+ error,
261
+ `Failed to read calculation ${calculationFile.name}`,
262
+ );
263
+ }
264
+ }
265
+ }
266
+ }
267
+ }
268
+
269
+ // Gets either all the cards (no parent), or a subtree.
270
+ private async getCards(templateName?: string): Promise<Card[]> {
271
+ return templateName
272
+ ? this.project.templateCards(templateName)
273
+ : this.project.cards();
274
+ }
275
+
276
+ // Checks that Clingo successfully returned result.
277
+ private async parseClingoResult(
278
+ data: string[],
279
+ ): Promise<ParseResult<BaseResult>> {
280
+ const parser = new ClingoParser();
281
+ return parser.parseInput(data.join('\n'));
282
+ }
283
+
284
+ private async run(query: string, context: Context): Promise<string[]> {
285
+ try {
286
+ const res = await CalculationEngine.mutex.runExclusive(async () => {
287
+ // Use the main category to include all programs
288
+ const basePrograms = [ALL_CATEGORY];
289
+
290
+ this.logger.trace(
291
+ {
292
+ clingo: true,
293
+ },
294
+ 'Solving',
295
+ );
296
+
297
+ const contextFacts = createContextFacts(context);
298
+ setProgram('context', contextFacts, [ALL_CATEGORY]);
299
+ // Then solve with the program - need to pass the program as parameter
300
+ return solve(query, basePrograms);
301
+ });
302
+
303
+ if (res && res.answers && res.answers.length > 0) {
304
+ return res.answers;
305
+ }
306
+ throw new Error('Failed to run Clingo solve. No answers returned.');
307
+ } catch (error) {
308
+ this.logger.error(
309
+ {
310
+ error,
311
+ query,
312
+ },
313
+ 'Clingo solve failed',
314
+ );
315
+ throw error;
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Generates a logic program.
321
+ */
322
+ public async generate() {
323
+ await CalculationEngine.mutex.runExclusive(async () => {
324
+ this.logger.trace(
325
+ {
326
+ clingo: true,
327
+ },
328
+ 'Generating logic program',
329
+ );
330
+ removeAllPrograms();
331
+
332
+ if (!this.modulesInitialized) {
333
+ await this.initializeModules();
334
+ }
335
+
336
+ // Set base common programs with main category
337
+ setProgram('base', lpFiles.common.base, [ALL_CATEGORY]);
338
+ setProgram('queryLanguage', lpFiles.common.queryLanguage, [ALL_CATEGORY]);
339
+ setProgram('utils', lpFiles.common.utils, [ALL_CATEGORY]);
340
+ setProgram('modules', this.modules, [ALL_CATEGORY]);
341
+
342
+ // Set individual resource type programs
343
+ await this.setCardTreeContent();
344
+ await this.setCardTypesPrograms();
345
+ await this.setFieldTypesPrograms();
346
+ await this.setLinkTypesPrograms();
347
+ await this.setWorkflowsPrograms();
348
+ await this.setReportsPrograms();
349
+ await this.setTemplatesPrograms();
350
+ await this.setCalculationsPrograms();
351
+
352
+ this.logger.trace(
353
+ {
354
+ clingo: true,
355
+ },
356
+ 'Logic program set',
357
+ );
358
+ });
359
+ }
360
+
361
+ /**
362
+ * When card changes, update the card specific calculations.
363
+ * @param changedCard Card that was changed.
364
+ */
365
+ public async handleCardChanged(changedCard: Card) {
366
+ await CalculationEngine.mutex.runExclusive(async () => {
367
+ await this.setCardContent(changedCard);
368
+ });
369
+ }
370
+
371
+ /**
372
+ * When cards are removed, automatically remove card-specific calculations.
373
+ * @param deletedCard Card that is to be removed.
374
+ */
375
+ public async handleDeleteCard(deletedCard: Card) {
376
+ if (!deletedCard) {
377
+ return;
378
+ }
379
+ try {
380
+ await CalculationEngine.mutex.runExclusive(async () => {
381
+ if (!removeProgram(deletedCard.key)) {
382
+ this.logger.warn(
383
+ {
384
+ cardKey: deletedCard.key,
385
+ },
386
+ 'Tried to remove card program that does not exist',
387
+ );
388
+ }
389
+ });
390
+ } catch {
391
+ this.logger.warn('Removing program failed');
392
+ }
393
+ }
394
+
395
+ /**
396
+ * When new cards are added, automatically calculate card-specific values.
397
+ * @param cards Added cards.
398
+ */
399
+ public async handleNewCards(cards: Card[]) {
400
+ if (!cards) {
401
+ return;
402
+ }
403
+ await CalculationEngine.mutex.runExclusive(async () => {
404
+ for (const card of cards) {
405
+ await this.setCardContent(card);
406
+ }
407
+ });
408
+ const cardKeys = cards.map((item) => item.key);
409
+ const queryResult = await this.creationQuery(cardKeys, 'localApp');
410
+ if (
411
+ !queryResult ||
412
+ queryResult.at(0) === undefined ||
413
+ queryResult.at(0)?.updateFields === undefined
414
+ ) {
415
+ return;
416
+ }
417
+ const fieldsToUpdate = queryResult.at(0)!.updateFields;
418
+ await CardMetadataUpdater.apply(this.project, fieldsToUpdate);
419
+ }
420
+
421
+ /**
422
+ * Gets the logic program content for a specific resource
423
+ * @param resourceName The name of the resource
424
+ * @returns The logic program content for the resource
425
+ */
426
+ public async resourceLogicProgram(
427
+ resourceName: ResourceName,
428
+ ): Promise<string> {
429
+ const resource = await this.project.resource(
430
+ resourceNameToString(resourceName),
431
+ );
432
+ if (!resource) {
433
+ throw new Error(
434
+ `Resource '${resourceNameToString(resourceName)}' does not exist in the project`,
435
+ );
436
+ }
437
+
438
+ switch (resourceName.type) {
439
+ case 'cardTypes':
440
+ return createCardTypeFacts(resource as CardType);
441
+ case 'fieldTypes':
442
+ return createFieldTypeFacts(resource as FieldType);
443
+ case 'linkTypes':
444
+ return createLinkTypeFacts(resource as LinkType);
445
+ case 'workflows':
446
+ return createWorkflowFacts(resource as Workflow);
447
+ case 'reports':
448
+ return createReportFacts(resource as ReportMetadata);
449
+ case 'templates':
450
+ return createTemplateFacts(resource as TemplateMetadata);
451
+ default:
452
+ throw new Error(
453
+ `Resource ${resourceNameToString(resourceName)} does not have a logic program`,
454
+ );
455
+ }
456
+ }
457
+
458
+ /**
459
+ * Runs given logic program and creates a graph using clingraph
460
+ * @param data Provide a query or/and a file which can be given to clingraph
461
+ * @param timeout Maximum amount of milliseconds clingraph is allowed to run
462
+ * @returns a base64 encoded image as a string
463
+ */
464
+ public async runGraph(model: string, view: string, context: Context) {
465
+ this.logger.trace(
466
+ {
467
+ model,
468
+ view,
469
+ },
470
+ 'Running graph',
471
+ );
472
+
473
+ const result = await generateReportContent({
474
+ calculate: this,
475
+ contentTemplate: graphvizReport.content,
476
+ queryTemplate: graphvizReport.query,
477
+ options: {
478
+ model: model,
479
+ view: view,
480
+ },
481
+ graph: true,
482
+ context,
483
+ });
484
+ let graph = (await instance()).renderString(result, {
485
+ format: 'svg',
486
+ });
487
+
488
+ // asciidoctor-pdf will error on the a elements with xtitle attribute
489
+ // because of the unescaped <font> tags.
490
+ if (context === 'exportedDocument') {
491
+ graph = graph.replace(/xlink:title="[^"]*"/g, '');
492
+ }
493
+ return sanitizeSvgBase64(graph);
494
+ }
495
+
496
+ /**
497
+ * Runs a logic program using clingo.
498
+ * @param query Logic program to be run
499
+ * @returns parsed program output
500
+ */
501
+ public async runLogicProgram(query: string, context: Context = 'localApp') {
502
+ const clingoOutput = await this.run(query, context);
503
+ return this.parseClingoResult(clingoOutput);
504
+ }
505
+
506
+ /**
507
+ * Runs a pre-defined query.
508
+ * @param queryName Name of the query file without extension
509
+ * @param options Any object that contains state for handlebars
510
+ * @returns parsed program output
511
+ */
512
+ public async runQuery<T extends QueryName>(
513
+ queryName: T,
514
+ context: Context = 'localApp',
515
+ options?: unknown,
516
+ ): Promise<QueryResult<T>[]> {
517
+ let content = lpFiles.queries[queryName];
518
+ const handlebars = Handlebars.create();
519
+ const compiled = handlebars.compile(content);
520
+ content = compiled(options || {});
521
+ if (!content) {
522
+ throw new Error(`Query file ${queryName} not found`);
523
+ }
524
+
525
+ this.logger.trace({ queryName }, 'Running query');
526
+ const clingoOutput = await this.run(content, context);
527
+
528
+ const result = await this.parseClingoResult(clingoOutput);
529
+
530
+ if (result.error) {
531
+ throw new Error(result.error);
532
+ }
533
+ return result.results as QueryResult<T>[];
534
+ }
535
+ }
@@ -14,9 +14,8 @@
14
14
 
15
15
  import { type Dirent, readdirSync } from 'node:fs';
16
16
  import { readdir } from 'node:fs/promises';
17
- import { join } from 'node:path';
17
+ import { extname, join } from 'node:path';
18
18
 
19
- import { CardContainer } from '../card-container.js';
20
19
  import type { Project } from '../project.js';
21
20
  import type { ProjectPaths } from './project-paths.js';
22
21
  import type {
@@ -38,6 +37,9 @@ export enum ResourcesFrom {
38
37
  localOnly = 'local',
39
38
  }
40
39
 
40
+ // This class collects resources that have these types of files.
41
+ const allowedExtensions = ['.lp', '.json'];
42
+
41
43
  // Helper class to contain collected resources.
42
44
  class ResourceCollection {
43
45
  public calculations: Resource[] = [];
@@ -109,9 +111,7 @@ export class ResourceCollector {
109
111
  }
110
112
 
111
113
  const isValidFile = (item: Dirent): boolean =>
112
- item.isFile() &&
113
- item.name !== CardContainer.schemaContentFile &&
114
- item.name !== '.gitkeep';
114
+ item.isFile() && allowedExtensions.includes(extname(item.name));
115
115
 
116
116
  const processResource = async (resource: Dirent): Promise<Resource[]> => {
117
117
  const resourcePath = join(
@@ -231,17 +231,12 @@ export class ResourceCollector {
231
231
  }
232
232
  resources.push(
233
233
  ...entries
234
- .filter((entry) => {
235
- return (
236
- entry.isFile() &&
237
- entry.name !== '.gitkeep' &&
238
- entry.name !== CardContainer.schemaContentFile
239
- );
240
- })
234
+ .filter(
235
+ (entry) =>
236
+ entry.isFile() && allowedExtensions.includes(extname(entry.name)),
237
+ )
241
238
  .map((entry) => {
242
- if (entry.name.endsWith('.json')) {
243
- entry.name = stripExtension(entry.name);
244
- }
239
+ entry.name = stripExtension(entry.name);
245
240
  return {
246
241
  name: `${this.project.projectPrefix}/${type}/${entry.name}`,
247
242
  path: entry.parentPath,
@@ -303,6 +298,9 @@ export class ResourceCollector {
303
298
 
304
299
  const { type } = resourceName(resource.name);
305
300
  switch (type) {
301
+ case 'calculations':
302
+ addItem(this.local.calculations, resource);
303
+ break;
306
304
  case 'cardTypes':
307
305
  addItem(this.local.cardTypes, resource);
308
306
  break;