@cyberismo/data-handler 0.0.14 → 0.0.16

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 (280) hide show
  1. package/dist/card-metadata-updater.js +8 -4
  2. package/dist/card-metadata-updater.js.map +1 -1
  3. package/dist/command-handler.d.ts +4 -0
  4. package/dist/command-handler.js +29 -19
  5. package/dist/command-handler.js.map +1 -1
  6. package/dist/command-manager.d.ts +25 -2
  7. package/dist/command-manager.js +30 -5
  8. package/dist/command-manager.js.map +1 -1
  9. package/dist/commands/create.d.ts +1 -1
  10. package/dist/commands/create.js +45 -93
  11. package/dist/commands/create.js.map +1 -1
  12. package/dist/commands/edit.d.ts +1 -15
  13. package/dist/commands/edit.js +15 -89
  14. package/dist/commands/edit.js.map +1 -1
  15. package/dist/commands/export.d.ts +11 -2
  16. package/dist/commands/export.js +58 -58
  17. package/dist/commands/export.js.map +1 -1
  18. package/dist/commands/import.d.ts +9 -1
  19. package/dist/commands/import.js +17 -11
  20. package/dist/commands/import.js.map +1 -1
  21. package/dist/commands/move.d.ts +1 -2
  22. package/dist/commands/move.js +107 -146
  23. package/dist/commands/move.js.map +1 -1
  24. package/dist/commands/remove.d.ts +8 -1
  25. package/dist/commands/remove.js +17 -48
  26. package/dist/commands/remove.js.map +1 -1
  27. package/dist/commands/rename.d.ts +4 -9
  28. package/dist/commands/rename.js +34 -108
  29. package/dist/commands/rename.js.map +1 -1
  30. package/dist/commands/show.d.ts +22 -34
  31. package/dist/commands/show.js +103 -151
  32. package/dist/commands/show.js.map +1 -1
  33. package/dist/commands/transition.d.ts +9 -2
  34. package/dist/commands/transition.js +49 -44
  35. package/dist/commands/transition.js.map +1 -1
  36. package/dist/commands/update.d.ts +18 -12
  37. package/dist/commands/update.js +34 -18
  38. package/dist/commands/update.js.map +1 -1
  39. package/dist/commands/validate.d.ts +18 -10
  40. package/dist/commands/validate.js +101 -47
  41. package/dist/commands/validate.js.map +1 -1
  42. package/dist/containers/card-container.d.ts +87 -24
  43. package/dist/containers/card-container.js +183 -279
  44. package/dist/containers/card-container.js.map +1 -1
  45. package/dist/containers/project/calculation-engine.d.ts +13 -4
  46. package/dist/containers/project/calculation-engine.js +79 -77
  47. package/dist/containers/project/calculation-engine.js.map +1 -1
  48. package/dist/containers/project/card-cache.d.ts +146 -0
  49. package/dist/containers/project/card-cache.js +411 -0
  50. package/dist/containers/project/card-cache.js.map +1 -0
  51. package/dist/containers/project/project-paths.d.ts +5 -4
  52. package/dist/containers/project/project-paths.js +16 -12
  53. package/dist/containers/project/project-paths.js.map +1 -1
  54. package/dist/containers/project/resource-cache.d.ts +169 -0
  55. package/dist/containers/project/resource-cache.js +507 -0
  56. package/dist/containers/project/resource-cache.js.map +1 -0
  57. package/dist/containers/project/resource-handler.d.ts +129 -0
  58. package/dist/containers/project/resource-handler.js +206 -0
  59. package/dist/containers/project/resource-handler.js.map +1 -0
  60. package/dist/containers/project.d.ts +114 -195
  61. package/dist/containers/project.js +425 -535
  62. package/dist/containers/project.js.map +1 -1
  63. package/dist/containers/template.d.ts +22 -32
  64. package/dist/containers/template.js +113 -115
  65. package/dist/containers/template.js.map +1 -1
  66. package/dist/index.d.ts +1 -0
  67. package/dist/index.js +1 -0
  68. package/dist/index.js.map +1 -1
  69. package/dist/interfaces/folder-content-interfaces.d.ts +7 -4
  70. package/dist/interfaces/folder-content-interfaces.js +3 -3
  71. package/dist/interfaces/folder-content-interfaces.js.map +1 -1
  72. package/dist/interfaces/macros.d.ts +1 -0
  73. package/dist/interfaces/macros.js +1 -1
  74. package/dist/interfaces/macros.js.map +1 -1
  75. package/dist/interfaces/project-interfaces.d.ts +7 -5
  76. package/dist/interfaces/project-interfaces.js.map +1 -1
  77. package/dist/interfaces/resource-interfaces.d.ts +25 -22
  78. package/dist/interfaces/resource-interfaces.js +3 -0
  79. package/dist/interfaces/resource-interfaces.js.map +1 -1
  80. package/dist/macros/common.d.ts +10 -10
  81. package/dist/macros/createCards/index.d.ts +0 -13
  82. package/dist/macros/createCards/index.js.map +1 -1
  83. package/dist/macros/createCards/types.d.ts +44 -0
  84. package/dist/macros/createCards/types.js +15 -0
  85. package/dist/macros/createCards/types.js.map +1 -0
  86. package/dist/macros/graph/index.d.ts +2 -6
  87. package/dist/macros/graph/index.js +14 -28
  88. package/dist/macros/graph/index.js.map +1 -1
  89. package/dist/macros/graph/types.d.ts +23 -0
  90. package/dist/macros/graph/types.js +15 -0
  91. package/dist/macros/graph/types.js.map +1 -0
  92. package/dist/macros/image/index.d.ts +8 -16
  93. package/dist/macros/image/index.js +36 -33
  94. package/dist/macros/image/index.js.map +1 -1
  95. package/dist/macros/image/types.d.ts +38 -0
  96. package/dist/macros/image/types.js +15 -0
  97. package/dist/macros/image/types.js.map +1 -0
  98. package/dist/macros/include/index.d.ts +1 -6
  99. package/dist/macros/include/index.js +4 -7
  100. package/dist/macros/include/index.js.map +1 -1
  101. package/dist/macros/include/types.d.ts +31 -0
  102. package/dist/macros/include/types.js +15 -0
  103. package/dist/macros/include/types.js.map +1 -0
  104. package/dist/macros/index.d.ts +1 -1
  105. package/dist/macros/index.js +2 -2
  106. package/dist/macros/index.js.map +1 -1
  107. package/dist/macros/percentage/index.d.ts +0 -6
  108. package/dist/macros/percentage/index.js.map +1 -1
  109. package/dist/macros/percentage/types.d.ts +31 -0
  110. package/dist/macros/percentage/types.js +15 -0
  111. package/dist/macros/percentage/types.js.map +1 -0
  112. package/dist/macros/report/index.d.ts +0 -3
  113. package/dist/macros/report/index.js +3 -6
  114. package/dist/macros/report/index.js.map +1 -1
  115. package/dist/macros/report/types.d.ts +19 -0
  116. package/dist/macros/report/types.js +15 -0
  117. package/dist/macros/report/types.js.map +1 -0
  118. package/dist/macros/scoreCard/index.d.ts +0 -6
  119. package/dist/macros/scoreCard/index.js.map +1 -1
  120. package/dist/macros/scoreCard/types.d.ts +31 -0
  121. package/dist/macros/scoreCard/types.js +15 -0
  122. package/dist/macros/scoreCard/types.js.map +1 -0
  123. package/dist/macros/types.d.ts +25 -0
  124. package/dist/macros/types.js +2 -0
  125. package/dist/macros/types.js.map +1 -0
  126. package/dist/macros/vega/index.d.ts +0 -4
  127. package/dist/macros/vega/index.js.map +1 -1
  128. package/dist/macros/vega/types.d.ts +20 -0
  129. package/dist/macros/vega/types.js +2 -0
  130. package/dist/macros/vega/types.js.map +1 -0
  131. package/dist/macros/vegalite/index.d.ts +0 -4
  132. package/dist/macros/vegalite/index.js.map +1 -1
  133. package/dist/macros/vegalite/types.d.ts +20 -0
  134. package/dist/macros/vegalite/types.js +15 -0
  135. package/dist/macros/vegalite/types.js.map +1 -0
  136. package/dist/macros/xref/index.d.ts +0 -3
  137. package/dist/macros/xref/index.js +5 -14
  138. package/dist/macros/xref/index.js.map +1 -1
  139. package/dist/macros/xref/types.d.ts +19 -0
  140. package/dist/macros/xref/types.js +15 -0
  141. package/dist/macros/xref/types.js.map +1 -0
  142. package/dist/module-manager.d.ts +16 -3
  143. package/dist/module-manager.js +55 -23
  144. package/dist/module-manager.js.map +1 -1
  145. package/dist/project-settings.d.ts +16 -3
  146. package/dist/project-settings.js +79 -14
  147. package/dist/project-settings.js.map +1 -1
  148. package/dist/resources/calculation-resource.d.ts +6 -33
  149. package/dist/resources/calculation-resource.js +11 -60
  150. package/dist/resources/calculation-resource.js.map +1 -1
  151. package/dist/resources/card-type-resource.d.ts +10 -22
  152. package/dist/resources/card-type-resource.js +46 -66
  153. package/dist/resources/card-type-resource.js.map +1 -1
  154. package/dist/resources/create-defaults.d.ts +3 -2
  155. package/dist/resources/create-defaults.js +3 -2
  156. package/dist/resources/create-defaults.js.map +1 -1
  157. package/dist/resources/field-type-resource.d.ts +8 -22
  158. package/dist/resources/field-type-resource.js +35 -60
  159. package/dist/resources/field-type-resource.js.map +1 -1
  160. package/dist/resources/file-resource.d.ts +14 -35
  161. package/dist/resources/file-resource.js +22 -301
  162. package/dist/resources/file-resource.js.map +1 -1
  163. package/dist/resources/folder-resource.d.ts +44 -66
  164. package/dist/resources/folder-resource.js +102 -149
  165. package/dist/resources/folder-resource.js.map +1 -1
  166. package/dist/resources/graph-model-resource.d.ts +9 -34
  167. package/dist/resources/graph-model-resource.js +18 -64
  168. package/dist/resources/graph-model-resource.js.map +1 -1
  169. package/dist/resources/graph-view-resource.d.ts +9 -29
  170. package/dist/resources/graph-view-resource.js +13 -48
  171. package/dist/resources/graph-view-resource.js.map +1 -1
  172. package/dist/resources/link-type-resource.d.ts +9 -23
  173. package/dist/resources/link-type-resource.js +11 -33
  174. package/dist/resources/link-type-resource.js.map +1 -1
  175. package/dist/resources/report-resource.d.ts +10 -23
  176. package/dist/resources/report-resource.js +20 -67
  177. package/dist/resources/report-resource.js.map +1 -1
  178. package/dist/resources/resource-object.d.ts +143 -23
  179. package/dist/resources/resource-object.js +369 -48
  180. package/dist/resources/resource-object.js.map +1 -1
  181. package/dist/resources/template-resource.d.ts +10 -17
  182. package/dist/resources/template-resource.js +19 -27
  183. package/dist/resources/template-resource.js.map +1 -1
  184. package/dist/resources/workflow-resource.d.ts +9 -25
  185. package/dist/resources/workflow-resource.js +25 -55
  186. package/dist/resources/workflow-resource.js.map +1 -1
  187. package/dist/utils/card-utils.d.ts +69 -19
  188. package/dist/utils/card-utils.js +179 -30
  189. package/dist/utils/card-utils.js.map +1 -1
  190. package/dist/utils/clingo-fact-builder.d.ts +25 -14
  191. package/dist/utils/clingo-fact-builder.js +27 -5
  192. package/dist/utils/clingo-fact-builder.js.map +1 -1
  193. package/dist/utils/clingo-facts.js +14 -7
  194. package/dist/utils/clingo-facts.js.map +1 -1
  195. package/dist/utils/clingo-parser.js +1 -1
  196. package/dist/utils/clingo-parser.js.map +1 -1
  197. package/dist/utils/constants.d.ts +2 -0
  198. package/dist/utils/constants.js +4 -0
  199. package/dist/utils/constants.js.map +1 -1
  200. package/dist/utils/csv.js +1 -1
  201. package/dist/utils/csv.js.map +1 -1
  202. package/dist/utils/resource-utils.d.ts +1 -0
  203. package/dist/utils/resource-utils.js +2 -1
  204. package/dist/utils/resource-utils.js.map +1 -1
  205. package/package.json +11 -11
  206. package/src/card-metadata-updater.ts +9 -7
  207. package/src/command-handler.ts +35 -23
  208. package/src/command-manager.ts +32 -19
  209. package/src/commands/create.ts +59 -160
  210. package/src/commands/edit.ts +16 -132
  211. package/src/commands/export.ts +71 -81
  212. package/src/commands/import.ts +26 -18
  213. package/src/commands/move.ts +143 -179
  214. package/src/commands/remove.ts +20 -59
  215. package/src/commands/rename.ts +45 -156
  216. package/src/commands/show.ts +153 -211
  217. package/src/commands/transition.ts +53 -58
  218. package/src/commands/update.ts +44 -23
  219. package/src/commands/validate.ts +108 -82
  220. package/src/containers/card-container.ts +200 -360
  221. package/src/containers/project/calculation-engine.ts +81 -105
  222. package/src/containers/project/card-cache.ts +497 -0
  223. package/src/containers/project/project-paths.ts +21 -13
  224. package/src/containers/project/resource-cache.ts +648 -0
  225. package/src/containers/project/resource-handler.ts +265 -0
  226. package/src/containers/project.ts +551 -693
  227. package/src/containers/template.ts +129 -142
  228. package/src/index.ts +1 -0
  229. package/src/interfaces/folder-content-interfaces.ts +14 -7
  230. package/src/interfaces/macros.ts +2 -0
  231. package/src/interfaces/project-interfaces.ts +14 -7
  232. package/src/interfaces/resource-interfaces.ts +30 -27
  233. package/src/macros/createCards/index.ts +1 -12
  234. package/src/macros/createCards/types.ts +46 -0
  235. package/src/macros/graph/index.ts +27 -52
  236. package/src/macros/graph/types.ts +24 -0
  237. package/src/macros/image/index.ts +50 -61
  238. package/src/macros/image/types.ts +39 -0
  239. package/src/macros/include/index.ts +6 -15
  240. package/src/macros/include/types.ts +32 -0
  241. package/src/macros/index.ts +2 -2
  242. package/src/macros/percentage/index.ts +1 -7
  243. package/src/macros/percentage/types.ts +32 -0
  244. package/src/macros/report/index.ts +4 -13
  245. package/src/macros/report/types.ts +20 -0
  246. package/src/macros/scoreCard/index.ts +1 -7
  247. package/src/macros/scoreCard/types.ts +32 -0
  248. package/src/macros/types.ts +48 -0
  249. package/src/macros/vega/index.ts +1 -4
  250. package/src/macros/vega/types.ts +21 -0
  251. package/src/macros/vegalite/index.ts +1 -4
  252. package/src/macros/vegalite/types.ts +22 -0
  253. package/src/macros/xref/index.ts +6 -20
  254. package/src/macros/xref/types.ts +20 -0
  255. package/src/module-manager.ts +79 -22
  256. package/src/project-settings.ts +84 -15
  257. package/src/resources/calculation-resource.ts +21 -91
  258. package/src/resources/card-type-resource.ts +74 -109
  259. package/src/resources/create-defaults.ts +3 -2
  260. package/src/resources/field-type-resource.ts +61 -104
  261. package/src/resources/file-resource.ts +33 -441
  262. package/src/resources/folder-resource.ts +130 -207
  263. package/src/resources/graph-model-resource.ts +36 -95
  264. package/src/resources/graph-view-resource.ts +28 -70
  265. package/src/resources/link-type-resource.ts +23 -53
  266. package/src/resources/report-resource.ts +34 -96
  267. package/src/resources/resource-object.ts +511 -66
  268. package/src/resources/template-resource.ts +32 -44
  269. package/src/resources/workflow-resource.ts +42 -85
  270. package/src/utils/card-utils.ts +217 -31
  271. package/src/utils/clingo-fact-builder.ts +28 -16
  272. package/src/utils/clingo-facts.ts +16 -7
  273. package/src/utils/clingo-parser.ts +1 -1
  274. package/src/utils/constants.ts +6 -0
  275. package/src/utils/csv.ts +1 -1
  276. package/src/utils/resource-utils.ts +2 -1
  277. package/dist/containers/project/resource-collector.d.ts +0 -87
  278. package/dist/containers/project/resource-collector.js +0 -337
  279. package/dist/containers/project/resource-collector.js.map +0 -1
  280. package/src/containers/project/resource-collector.ts +0 -396
@@ -12,261 +12,322 @@
12
12
  License along with this program. If not, see <https://www.gnu.org/licenses/>.
13
13
  */
14
14
  // node
15
- import { dirname, join, resolve, sep } from 'node:path';
16
- import { readdir } from 'node:fs/promises';
17
- import { CardContainer } from './card-container.js'; // base class
15
+ import { basename, join, resolve } from 'node:path';
16
+ import { constants as fsConstants, copyFile, mkdir, unlink, writeFile, } from 'node:fs/promises';
17
+ // base class
18
+ import { CardContainer } from './card-container.js';
18
19
  import { CalculationEngine } from './project/calculation-engine.js';
19
20
  import { CardLocation, } from '../interfaces/project-interfaces.js';
20
- import { getFilesSync, pathExists } from '../utils/file-utils.js';
21
+ import { pathExists } from '../utils/file-utils.js';
21
22
  import { generateRandomString } from '../utils/random.js';
22
- import { isTemplateCard } from '../utils/card-utils.js';
23
+ import { cardPathParts, isModulePath, isTemplateCard, } from '../utils/card-utils.js';
23
24
  import { ProjectConfiguration } from '../project-settings.js';
24
25
  import { ProjectPaths } from './project/project-paths.js';
25
26
  import { readJsonFile } from '../utils/json.js';
26
- import { resourceName, resourceNameToString, } from '../utils/resource-utils.js';
27
- import { ResourcesFrom, ResourceCollector, } from './project/resource-collector.js';
27
+ import { ResourcesFrom } from './project/resource-cache.js';
28
+ import { ResourceHandler } from './project/resource-handler.js';
28
29
  import { Validate } from '../commands/validate.js';
29
- import { CalculationResource } from '../resources/calculation-resource.js';
30
- import { CardTypeResource } from '../resources/card-type-resource.js';
31
- import { FieldTypeResource } from '../resources/field-type-resource.js';
32
- import { GraphModelResource } from '../resources/graph-model-resource.js';
33
- import { GraphViewResource } from '../resources/graph-view-resource.js';
34
- import { LinkTypeResource } from '../resources/link-type-resource.js';
35
- import { ReportResource } from '../resources/report-resource.js';
36
- import { TemplateResource } from '../resources/template-resource.js';
37
- import { WorkflowResource } from '../resources/workflow-resource.js';
38
30
  import { ContentWatcher } from './project/project-content-watcher.js';
39
- import { pathToResourceName } from '../utils/resource-utils.js';
31
+ import { getChildLogger } from '../utils/log-utils.js';
32
+ import { ROOT } from '../utils/constants.js';
40
33
  // Re-export this, so that classes that use Project do not need to have separate import.
41
34
  export { ResourcesFrom };
42
35
  /**
43
36
  * Represents project folder.
44
37
  */
45
38
  export class Project extends CardContainer {
46
- watchResourceChanges;
39
+ options;
47
40
  calculationEngine;
48
- resources;
41
+ logger = getChildLogger({ module: 'Project' });
49
42
  projectPaths;
43
+ resourceHandler;
44
+ resourceWatcher;
50
45
  settings;
51
46
  validator;
52
- resourceWatcher;
53
- // Created resources are held in a cache.
54
- // In the cache, key is resource name, and data is resource metadata (as JSON).
55
- createdResources = new Map();
56
- constructor(path, watchResourceChanges) {
57
- super(path, '');
58
- this.watchResourceChanges = watchResourceChanges;
47
+ constructor(path, options = {
48
+ autoSave: true,
49
+ watchResourceChanges: false,
50
+ }) {
51
+ const settings = new ProjectConfiguration(join(path, '.cards', 'local', Project.projectConfigFileName), options.autoSave ?? true);
52
+ super(path, settings.cardKeyPrefix, '');
53
+ this.options = options;
54
+ this.settings = settings;
55
+ this.logger.info({ path }, 'Initializing project');
59
56
  this.calculationEngine = new CalculationEngine(this);
60
- this.settings = new ProjectConfiguration(join(path, '.cards', 'local', Project.projectConfigFileName));
61
57
  this.projectPaths = new ProjectPaths(path);
62
- this.resources = new ResourceCollector(this);
58
+ this.resourceHandler = new ResourceHandler(this);
63
59
  this.containerName = this.settings.name;
64
60
  // todo: implement project validation
65
61
  this.validator = Validate.getInstance();
66
- this.resources.collectLocalResources();
62
+ this.logger.info({ name: this.containerName }, 'Project initialization complete');
67
63
  const ignoreRenameFileChanges = true;
68
64
  // Watch changes in .cards if there are multiple instances of Project being
69
65
  // run concurrently.
70
- if (this.watchResourceChanges) {
66
+ if (this.options.watchResourceChanges) {
71
67
  this.resourceWatcher = new ContentWatcher(ignoreRenameFileChanges, this.paths.resourcesFolder, (fileName) => {
72
68
  void (async () => {
73
- let resource;
74
- try {
75
- resource = pathToResourceName(this, join(this.paths.resourcesFolder, fileName));
76
- if (!resource) {
77
- return;
78
- }
79
- }
80
- catch {
81
- // it wasn't a resource that changed, so ignore the change
82
- return;
83
- }
84
- const resourceName = `${resource.prefix}/${resource.type}/${resource.identifier}`;
85
- await this.replaceCacheValue(resourceName);
86
- this.resources.collectLocalResources();
69
+ this.resources.handleFileSystemChange(join(this.paths.resourcesFolder, fileName));
70
+ this.resources.changed();
87
71
  })();
88
72
  });
89
73
  }
90
74
  }
91
- // Removes current version of resource from cache.
92
- // Then re-creates the resource with current data and caches the value again.
93
- // If the value wasn't in the cache before, it will be added.
94
- async replaceCacheValue(resourceName) {
95
- if (this.createdResources.has(resourceName)) {
96
- // First, remove the old version from cache
97
- this.createdResources.delete(resourceName);
75
+ // Changes a card's parent in the cache and updates all relationships.
76
+ changeParent(updatedCard, previousParent) {
77
+ if (previousParent && previousParent !== ROOT) {
78
+ this.removeCachedChildren(previousParent, updatedCard.key);
98
79
  }
99
- const resourceData = await this.resource(resourceName);
100
- if (!resourceData)
101
- return;
102
- this.createdResources.set(resourceName, resourceData);
80
+ if (updatedCard.parent && updatedCard.parent !== ROOT) {
81
+ this.updateCachedChildren(updatedCard.parent, updatedCard);
82
+ }
83
+ this.cardCache.updateCard(updatedCard.key, updatedCard);
103
84
  }
104
85
  // Finds specific module.
105
86
  async findModule(moduleName) {
106
- return (await this.resources.resources('modules')).find((item) => item.name === moduleName && item.path);
87
+ const moduleExists = this.resources.moduleNames().includes(moduleName);
88
+ if (!moduleExists) {
89
+ return undefined;
90
+ }
91
+ // For modules, we need to construct the local path where the module is stored
92
+ const moduleConfig = this.configuration.modules?.find((module) => module.name === moduleName);
93
+ if (!moduleConfig) {
94
+ return undefined;
95
+ }
96
+ return {
97
+ name: moduleName,
98
+ path: join(this.paths.modulesFolder, moduleConfig.name),
99
+ };
100
+ }
101
+ // Handles attachment changes after filesystem operations.
102
+ async handleAttachmentChange(cardKey, operation, fileName) {
103
+ if (operation === 'added') {
104
+ this.cardCache.addAttachment(cardKey, fileName);
105
+ }
106
+ else if (operation === 'removed') {
107
+ this.cardCache.deleteAttachment(cardKey, fileName);
108
+ }
109
+ else if (operation === 'refresh') {
110
+ const newAttachments = this.cardCache.getCardAttachments(cardKey);
111
+ if (newAttachments) {
112
+ this.cardCache.updateCardAttachments(cardKey, newAttachments);
113
+ }
114
+ }
115
+ }
116
+ // Determines the parent card key from a card's filesystem path.
117
+ // todo: could be moved to card-utils
118
+ parentFromPath(cardPath) {
119
+ return cardPathParts(this.projectPrefix, cardPath).parents.at(-1) || 'root';
120
+ }
121
+ // Remove children from a card in the card cache
122
+ removeCachedChildren(parentKey, childKey) {
123
+ const parentCard = this.cardCache.getCard(parentKey);
124
+ if (parentCard && parentCard.children) {
125
+ parentCard.children = parentCard.children.filter((child) => child !== childKey);
126
+ this.cardCache.updateCard(parentCard.key, parentCard);
127
+ }
128
+ }
129
+ // Updates children in the card cache
130
+ updateCachedChildren(parentKey, newChild) {
131
+ const parentCard = this.cardCache.getCard(parentKey);
132
+ if (parentCard) {
133
+ // Add or update the child in the parent's children array
134
+ const existingChildIndex = parentCard.children?.findIndex((child) => child === newChild.key);
135
+ if (existingChildIndex === -1) {
136
+ parentCard.children.push(newChild.key);
137
+ }
138
+ this.cardCache.updateCard(parentCard.key, parentCard);
139
+ }
140
+ }
141
+ // Validates that card's data is valid.
142
+ async validateCard(card) {
143
+ const invalidCustomData = await this.validator.validateCustomFields(this, card, this.projectPrefixes());
144
+ const invalidWorkFlow = await this.validator.validateWorkflowState(this, card);
145
+ const invalidLabels = this.validator.validateCardLabels(card);
146
+ if (invalidCustomData.length === 0 &&
147
+ invalidWorkFlow.length === 0 &&
148
+ invalidLabels.length === 0) {
149
+ return '';
150
+ }
151
+ const errors = [];
152
+ if (invalidCustomData.length > 0) {
153
+ errors.push(invalidCustomData);
154
+ }
155
+ if (invalidWorkFlow.length > 0) {
156
+ errors.push(invalidWorkFlow);
157
+ }
158
+ if (invalidLabels.length > 0) {
159
+ errors.push(invalidLabels);
160
+ }
161
+ return errors.join('\n');
107
162
  }
108
- // Returns (local or all) resources of a given type.
109
- // @todo: if this would be public, we could remove cardTypes(), fieldTypes(), ... and similar APIs
110
- async resourcesOfType(type, from = ResourcesFrom.localOnly) {
111
- return this.resources.resources(type, from);
163
+ /**
164
+ * Populate template cards into the card cache.
165
+ */
166
+ async populateTemplateCards() {
167
+ try {
168
+ const templateResources = this.resources.templates();
169
+ const prefixes = this.projectPrefixes();
170
+ const loadPromises = templateResources.map(async (template) => {
171
+ try {
172
+ this.validator.validResourceName('templates', template.data?.name || '', prefixes);
173
+ }
174
+ catch (error) {
175
+ this.logger.warn({ templateName: template, error }, `Template name '${template}' does not follow required format, skipping`);
176
+ return;
177
+ }
178
+ const templateObject = template.templateObject();
179
+ const isCreated = templateObject && templateObject.isCreated();
180
+ if (!templateObject || !isCreated) {
181
+ return;
182
+ }
183
+ await this.cardCache.populateFromPath(templateObject.templateCardsFolder(), false);
184
+ });
185
+ await Promise.all(loadPromises);
186
+ // Once all templates have been fetched, build child-parent relationships.
187
+ this.cardCache.populateChildrenRelationships();
188
+ }
189
+ catch (error) {
190
+ this.logger.error({ error }, 'Failed to populate template cards into the card cache');
191
+ }
192
+ }
193
+ /**
194
+ * Populate both the project cards, and all template cards into card cache.
195
+ */
196
+ async populateCardsCache() {
197
+ await this.cardCache.populateFromPath(this.paths.cardRootFolder);
198
+ await this.populateTemplateCards();
112
199
  }
113
200
  /**
114
- * Add a given 'resource' to the local resource arrays.
115
- * @param resource Resource to add.
116
- * @param data JSON data for the resource.
201
+ * Returns all template cards from the project. This includes all module templates' cards.
202
+ * @returns all the template cards from the project
117
203
  */
118
- addResource(resource, data) {
119
- this.resources.add(resource);
120
- this.createdResources.set(resource.name, data);
204
+ allTemplateCards() {
205
+ return this.cardCache.getAllTemplateCards();
121
206
  }
122
207
  /**
123
208
  * Returns an array of all the attachments in the project card's (excluding ones in templates).
124
209
  * @returns all attachments in the project.
125
210
  */
126
- async attachments() {
211
+ attachments() {
127
212
  return super.attachments(this.paths.cardRootFolder);
128
213
  }
129
214
  /**
130
- * Returns an array of all the calculation files (*.lp) in the project.
131
- * @param from Defines where resources are collected from.
132
- * @returns array of all calculation files in the project.
215
+ * Returns attachments from cards at a specific path using the card cache.
216
+ * This method allows templates to access attachments from the shared cache.
217
+ * @param path The path to get attachments from
218
+ * @returns Array of attachments from cards at the specified path
133
219
  */
134
- async calculations(from = ResourcesFrom.all) {
135
- return this.resources.resources('calculations', from);
220
+ attachmentsByPath(path) {
221
+ return super.attachments(path);
136
222
  }
137
223
  /**
138
- * Returns path to card's attachment folder.
224
+ * Returns path to a card's attachment folder.
139
225
  * @param cardKey card key
140
- * @returns path to card's attachment folder.
141
- * @throws if card path cannot be found
226
+ * @returns path to a card's attachment folder.
142
227
  */
143
- async cardAttachmentFolder(cardKey) {
144
- // Check if it is a template card.
145
- if (await this.isTemplateCard(cardKey)) {
146
- const cardPath = await this.cardFolder(cardKey);
147
- return join(cardPath, 'a');
148
- }
149
- const pathToProjectCard = this.pathToCard(cardKey);
150
- if (!pathToProjectCard) {
151
- throw new Error(`Card '${cardKey}' not found`);
152
- }
153
- return join(this.paths.cardRootFolder, pathToProjectCard, 'a');
228
+ cardAttachmentFolder(cardKey) {
229
+ const pathToCard = this.findCard(cardKey).path;
230
+ return join(pathToCard, 'a');
154
231
  }
155
232
  /**
156
- * Returns details (as defined by cardDetails) of a card.
157
- * @param cardKey card key (project prefix and a number, e.g. test_1)
158
- * @param cardDetails which card details are returned.
159
- * @returns Card details, or undefined if the card cannot be found.
233
+ * Creates an attachment for a card.
234
+ * @param cardKey The card to add attachment to
235
+ * @param attachmentName The name for the attachment file
236
+ * @param attachmentData The attachment data (file path or buffer)
237
+ * @throws If trying to add attachment to module card, or if attachment is not found
160
238
  */
161
- async cardDetailsById(cardKey, cardDetails) {
162
- return this.findSpecificCard(cardKey, cardDetails);
239
+ async createCardAttachment(cardKey, attachmentName, attachmentData) {
240
+ const attachmentFolder = this.cardAttachmentFolder(cardKey);
241
+ // Check if this is a module template
242
+ if (isModulePath(attachmentFolder)) {
243
+ throw new Error(`Cannot modify imported module`);
244
+ }
245
+ // Create the attachment folder if it doesn't exist
246
+ await mkdir(attachmentFolder, { recursive: true });
247
+ const attachmentPath = join(attachmentFolder, basename(attachmentName));
248
+ if (Buffer.isBuffer(attachmentData)) {
249
+ await writeFile(attachmentPath, attachmentData, { flag: 'wx' });
250
+ }
251
+ else {
252
+ try {
253
+ await copyFile(attachmentData, attachmentPath, fsConstants.COPYFILE_EXCL);
254
+ }
255
+ catch {
256
+ throw new Error(`Attachment file not found: ${attachmentData}`);
257
+ }
258
+ }
259
+ // Update cache
260
+ await this.handleAttachmentChange(cardKey, 'added', basename(attachmentName));
163
261
  }
164
262
  /**
165
- * Returns path to card's folder.
263
+ * Returns path to a card's folder.
166
264
  * @param cardKey card key
167
- * @returns path to card's folder.
265
+ * @returns path to a card's folder.
168
266
  */
169
267
  async cardFolder(cardKey) {
170
- const found = await super.findCard(this.paths.cardRootFolder, cardKey);
268
+ const found = super.findCard(cardKey);
171
269
  if (found) {
172
270
  return found.path;
173
271
  }
174
- const templates = await this.templates();
175
- const templatePromises = templates.map(async (template) => {
176
- const templateObject = new TemplateResource(this, resourceName(template.name)).templateObject();
272
+ const templates = this.resources.templates();
273
+ const templatePromises = templates.map((template) => {
274
+ const templateObject = template.templateObject();
177
275
  const templateCard = templateObject
178
- ? await templateObject.findSpecificCard(cardKey)
276
+ ? templateObject.findCard(cardKey)
179
277
  : undefined;
180
- return templateCard ? templateCard.path : '';
278
+ const path = templateCard ? templateCard.path : '';
279
+ return path;
181
280
  });
182
281
  const templatePaths = await Promise.all(templatePromises);
183
282
  return templatePaths.find((path) => path !== '') || '';
184
283
  }
185
284
  /**
186
- * Splits card path to parts. Returns the parts.
187
- * Returned parts are: prefix, card key, array of parents and template name. Template name is returned only for template cards.
188
- * @param cardPath path to a card
189
- * @returns card path logical parts
190
- * @throws when called with wrong path, or wrong card owner
191
- * todo: if prefix would be parameter; this could be static, or util method
192
- */
193
- cardPathParts(cardPath) {
194
- const pathParts = cardPath.split(sep);
195
- const cardKey = pathParts.at(pathParts.length - 1);
196
- const parents = [];
197
- let prefix = this.projectPrefix;
198
- let template = '';
199
- let startIndex = -1;
200
- let templatesNameIndex = -1;
201
- const cardRootIndex = pathParts.indexOf('cardRoot');
202
- const projectInternalsIndex = pathParts.indexOf('.cards');
203
- if (projectInternalsIndex === -1 && cardRootIndex >= 0) {
204
- startIndex = projectInternalsIndex;
205
- }
206
- else if (projectInternalsIndex >= 0 && cardRootIndex === -1) {
207
- const templatesIndex = pathParts.indexOf('templates');
208
- startIndex = templatesIndex;
209
- if (templatesIndex === -1) {
210
- throw new Error(`Invalid card path. Template card must have 'templates' in path`);
211
- }
212
- const modulesIndex = pathParts.indexOf('modules');
213
- if (modulesIndex !== -1) {
214
- prefix = pathParts.at(modulesIndex + 1) || '';
215
- }
216
- templatesNameIndex = templatesIndex + 1;
217
- template = `${prefix}/templates/${pathParts.at(templatesNameIndex)}`;
218
- }
219
- else {
220
- throw new Error(`Card must be either project card, or template card`);
221
- }
222
- // Look for parents in the path.
223
- let previousWasParent = false;
224
- for (let index = startIndex; index <= pathParts.length; index++) {
225
- if (previousWasParent) {
226
- previousWasParent = false;
227
- parents.push(pathParts.at(index - 2));
228
- }
229
- const cardsSubFolder = pathParts.at(index) === 'c';
230
- const ignoreOrNotTemplatesParent = index - 1 !== templatesNameIndex || templatesNameIndex === -1;
231
- if (cardsSubFolder && ignoreOrNotTemplatesParent) {
232
- previousWasParent = true;
285
+ * Fetches full Card data for given card keys
286
+ * @param cardIds array of card keys to fetch
287
+ * @returns Card data to the given card keys
288
+ */
289
+ cardKeysToCards(cardIds) {
290
+ const cards = [];
291
+ for (const cardId of cardIds) {
292
+ const card = this.cardCache.getCard(cardId);
293
+ if (card) {
294
+ cards.push(card);
233
295
  }
234
296
  }
235
- return {
236
- cardKey: cardKey,
237
- parents: parents,
238
- prefix: prefix,
239
- template: template,
240
- };
297
+ return cards;
241
298
  }
242
299
  /**
243
- * Returns an array of all the cards in the project. Cards have content and metadata
244
- * @param path Optional path from which to fetch the cards. Generally it is best to fetch from Project root, e.g. Project.cardRootFolder
245
- * @param details Which details to include in the cards; by default only "content" and "metadata" are included.
300
+ * Returns an array of all the cards in the project.
301
+ * @note These are project cards only, by default (unless path dictates otherwise).
302
+ * @param path Path from which to fetch the cards. Generally it is best to fetch from Project root, e.g. Project.cardRootFolder
303
+ * @param details Which details to include in the cards; by default all details are included.
246
304
  * @returns all cards from the given path in the project.
247
305
  */
248
- async cards(path = this.paths.cardRootFolder, details = { content: true, metadata: true }) {
306
+ cards(path = this.paths.cardRootFolder, details) {
249
307
  return super.cards(path, details);
250
308
  }
251
309
  /**
252
- * Returns an array of all the card types in the project.
253
- * @param from Defines where resources are collected from.
254
- * @returns array of all card types in the project.
310
+ * Accessor for cards cache.
311
+ * Used by template container (it needs to access project's cache, not their own instance).
312
+ * @note Should not be used directly (other than Template).
255
313
  */
256
- async cardTypes(from = ResourcesFrom.all) {
257
- return this.resources.resources('cardTypes', from);
314
+ get cardsCache() {
315
+ return this.cardCache;
258
316
  }
259
317
  /**
260
- * Updates all local resources.
318
+ * Returns children of a given card; as Card array
319
+ * @param card Parent card to fetch children from
320
+ * @returns children of a given card; as Card array
261
321
  */
262
- collectLocalResources() {
263
- this.resources.changed();
264
- }
265
- /**
266
- * Updates all imported module resources.
267
- */
268
- async collectModuleResources() {
269
- await this.resources.moduleImported();
322
+ childrenCards(card) {
323
+ const cards = [];
324
+ for (const child of card.children) {
325
+ const card = this.cardCache.getCard(child);
326
+ if (card) {
327
+ cards.push(card);
328
+ }
329
+ }
330
+ return cards;
270
331
  }
271
332
  /**
272
333
  * Returns project configuration.
@@ -284,8 +345,9 @@ export class Project extends CardContainer {
284
345
  if (!card || !card.path || !isTemplateCard(card)) {
285
346
  return undefined;
286
347
  }
287
- const { template } = this.cardPathParts(card.path);
288
- return new TemplateResource(this, resourceName(template)).templateObject();
348
+ const { template } = cardPathParts(this.projectPrefix, card.path);
349
+ const templateResource = this.resources.byType(template, 'templates');
350
+ return templateResource.templateObject();
289
351
  }
290
352
  /**
291
353
  * Cleanups project when it is being closed.
@@ -297,12 +359,13 @@ export class Project extends CardContainer {
297
359
  }
298
360
  }
299
361
  /**
300
- * Returns an array of all the field types in the project.
301
- * @param from Defines where resources are collected from.
302
- * @returns array of all field types in the project.
362
+ * Returns specific card.
363
+ * @param cardToFind Card key to find
364
+ * @param details Defines which card details are included in the return values.
365
+ * @returns specific card details, or undefined if card is not part of the project.
303
366
  */
304
- async fieldTypes(from = ResourcesFrom.all) {
305
- return this.resources.resources('fieldTypes', from);
367
+ findCard(cardToFind, details) {
368
+ return super.findCard(cardToFind, details);
306
369
  }
307
370
  /**
308
371
  * Finds root of a project
@@ -320,128 +383,105 @@ export class Project extends CardContainer {
320
383
  }
321
384
  return Project.findProjectRoot(parentPath);
322
385
  }
323
- /**
324
- * Returns specific card.
325
- * @param cardToFind Card key to find
326
- * @param details Defines which card details are included in the return values.
327
- * @returns specific card details, or undefined if card is not part of the project.
328
- */
329
- async findSpecificCard(cardToFind, details = {}) {
330
- let card;
331
- if (details.location === CardLocation.projectOnly ||
332
- details.location === CardLocation.all ||
333
- !details.location) {
334
- card = await super.findCard(this.paths.cardRootFolder, cardToFind, details);
335
- }
336
- if (!card &&
337
- (details.location === CardLocation.templatesOnly ||
338
- details.location === CardLocation.all ||
339
- !details.location)) {
340
- const templates = await this.templates();
341
- for (const template of templates) {
342
- const templateObject = new TemplateResource(this, resourceName(template.name)).templateObject();
343
- if (!templateObject)
344
- continue;
345
- // optimize: execute each find in template parallel
346
- card = await templateObject.findSpecificCard(cardToFind, details);
347
- if (card) {
348
- break;
349
- }
350
- }
351
- }
352
- return card;
353
- }
354
- /**
355
- * Returns an array of all the graph models in the project.
356
- * @param from Defines where resources are collected from.
357
- * @returns array of all the graph models in the project.
358
- */
359
- async graphModels(from = ResourcesFrom.all) {
360
- return this.resources.resources('graphModels', from);
361
- }
362
- /**
363
- * Returns an array of all the graph views in the project.
364
- * @param from Defines where resources are collected from.
365
- * @returns array of all the graph views in the project.
366
- */
367
- async graphViews(from = ResourcesFrom.all) {
368
- return this.resources.resources('graphViews', from);
369
- }
370
386
  /**
371
387
  * When card changes.
372
388
  * @param changedCard Card that was changed.
373
389
  */
374
390
  async handleCardChanged(changedCard) {
391
+ // Notify the calculation engine about the change
375
392
  return this.calculationEngine.handleCardChanged(changedCard);
376
393
  }
377
394
  /**
378
395
  * When cards are removed.
379
396
  * @param deletedCard Card that is to be removed.
380
397
  */
381
- async handleDeleteCard(deletedCard) {
398
+ async handleCardDeleted(deletedCard) {
399
+ // Delete children from the cache first
400
+ if (deletedCard.children && deletedCard.children.length > 0) {
401
+ for (const child of deletedCard.children) {
402
+ try {
403
+ const childCard = this.findCard(child);
404
+ await this.handleCardDeleted(childCard);
405
+ }
406
+ catch {
407
+ this.logger.warn(`Accessing child '${child}' of '${deletedCard.key}' when deleting cards caused an exception`);
408
+ continue;
409
+ }
410
+ }
411
+ }
412
+ await super.removeCard(deletedCard.key);
382
413
  return this.calculationEngine.handleDeleteCard(deletedCard);
383
414
  }
415
+ /**
416
+ * When card is moved.
417
+ * @param movedCard Card that moved
418
+ * @param newParentCard New parent for the 'movedCard'
419
+ * @param oldParentCard Previous parent of the 'movedCard'
420
+ */
421
+ async handleCardMoved(movedCard, newParentCard, oldParentCard) {
422
+ if (newParentCard) {
423
+ this.cardCache.updateCard(newParentCard.key, newParentCard);
424
+ }
425
+ if (oldParentCard) {
426
+ this.cardCache.updateCard(oldParentCard.key, oldParentCard);
427
+ }
428
+ this.cardCache.updateCard(movedCard.key, movedCard);
429
+ // todo: it would be enough to just update parent, previous parent and changed card
430
+ this.cardCache.populateChildrenRelationships();
431
+ await this.handleCardChanged(movedCard);
432
+ await this.calculationEngine.handleCardMoved();
433
+ }
384
434
  /**
385
435
  * When new cards are added.
386
436
  * @param cards Added cards.
387
437
  */
388
438
  async handleNewCards(cards) {
439
+ // Add new cards to the card cache
440
+ cards.forEach((card) => {
441
+ const cardWithParent = {
442
+ ...card,
443
+ parent: card.parent || this.parentFromPath(card.path),
444
+ };
445
+ this.cardCache.updateCard(cardWithParent.key, cardWithParent);
446
+ // Update the parent's children list in the cache
447
+ if (cardWithParent.parent && cardWithParent.parent !== ROOT) {
448
+ this.updateCachedChildren(cardWithParent.parent, cardWithParent);
449
+ }
450
+ });
389
451
  return this.calculationEngine.handleNewCards(cards);
390
452
  }
391
- /**
392
- * Checks if a given card is part of this project.
393
- * @param cardKey card to check.
394
- * @returns true if a given card is found from project, false otherwise.
395
- */
396
- hasCard(cardKey) {
397
- return super.hasCard(cardKey, this.paths.cardRootFolder);
398
- }
399
453
  /**
400
454
  * Adds a module from project.
401
- * @param module Name of the module
455
+ * @param module Module to add
402
456
  */
403
457
  async importModule(module) {
404
458
  // Add module as a dependency.
405
459
  await this.configuration.addModule(module);
406
- await this.collectModuleResources();
460
+ this.resources.changedModules();
461
+ await this.populateTemplateCards();
462
+ this.logger.info(`Imported module '${module.name}'`);
407
463
  }
408
464
  /**
409
- * Checks if given path is a project.
465
+ * Checks if a given path is a project.
410
466
  * @param path Path to a project
411
467
  * @returns true, if in the given path there is a project; false otherwise
412
468
  */
413
469
  static isCreated(path) {
414
470
  return pathExists(join(path, 'cardRoot'));
415
471
  }
416
- /**
417
- * Returns whether card is a template card or not
418
- * @param cardKey card to check.
419
- * @todo: This is only used from 'remove'. Could it use the static checker?
420
- * @returns true, if card is template card; false otherwise
421
- */
422
- async isTemplateCard(cardKey) {
423
- const templateCards = await this.allTemplateCards();
424
- return templateCards.find((card) => card.key === cardKey) != null;
425
- }
426
- /**
427
- * Returns an array of all the link types in the project.
428
- * @param from Defines where resources are collected from.
429
- * @returns array of all link types in the project.
430
- */
431
- async linkTypes(from = ResourcesFrom.all) {
432
- return this.resources.resources('linkTypes', from);
433
- }
434
472
  /**
435
473
  * Returns an array of cards in the project, in the templates or both.
436
474
  * Cards don't have content and nor metadata.
437
475
  * @param cardsFrom Where to return cards from (project, templates, or both)
438
- * @returns all cards in the project.
476
+ * @returns all cards in the project per container.
439
477
  */
440
478
  async listCards(cardsFrom = CardLocation.all) {
441
479
  const cardListContainer = [];
442
480
  if (cardsFrom === CardLocation.all ||
443
481
  cardsFrom === CardLocation.projectOnly) {
444
- const projectCards = (await super.cards(this.paths.cardRootFolder)).map((item) => item.key);
482
+ const projectCards = super
483
+ .cards(this.paths.cardRootFolder)
484
+ .map((item) => item.key);
445
485
  cardListContainer.push({
446
486
  name: this.projectName,
447
487
  type: 'project',
@@ -450,15 +490,15 @@ export class Project extends CardContainer {
450
490
  }
451
491
  if (cardsFrom === CardLocation.all ||
452
492
  cardsFrom === CardLocation.templatesOnly) {
453
- const templates = await this.templates();
493
+ const templates = this.resources.templates();
454
494
  for (const template of templates) {
455
- const templateObject = new TemplateResource(this, resourceName(template.name)).templateObject();
495
+ const templateObject = template.templateObject();
456
496
  if (templateObject) {
457
497
  // todo: optimization - do all this in parallel
458
- const templateCards = await templateObject.listCards();
459
- if (templateCards) {
498
+ const templateCards = templateObject.listCards();
499
+ if (templateCards.length) {
460
500
  cardListContainer.push({
461
- name: template.name,
501
+ name: template.data?.name || '',
462
502
  type: 'template',
463
503
  cards: templateCards.map((item) => item.key),
464
504
  });
@@ -475,89 +515,43 @@ export class Project extends CardContainer {
475
515
  * @note that cardIDs are not sorted.
476
516
  */
477
517
  async listCardIds(cardsFrom = CardLocation.all) {
478
- const promises = [];
479
- if (cardsFrom === CardLocation.all ||
480
- cardsFrom === CardLocation.projectOnly) {
481
- promises.push(super
482
- .cards(this.paths.cardRootFolder)
483
- .then((cards) => new Set(cards.map((card) => card.key))));
518
+ const cardContainers = await this.listCards(cardsFrom);
519
+ const allCardIDs = new Set();
520
+ for (const container of cardContainers) {
521
+ const cards = container.cards;
522
+ cards.forEach((card) => allCardIDs.add(card));
484
523
  }
485
- if (cardsFrom === CardLocation.all ||
486
- cardsFrom === CardLocation.templatesOnly) {
487
- promises.push((async () => {
488
- const templates = await this.templates();
489
- const templateResources = templates.map((template) => new TemplateResource(this, resourceName(template.name)));
490
- const templateObjectsResults = await Promise.allSettled(templateResources);
491
- const templateObjects = templateObjectsResults
492
- .filter((result) => result.status === 'fulfilled' && result.value !== null)
493
- .map((result) => result.value);
494
- const listCardsResults = await Promise.allSettled(templateObjects.map((obj) => obj.templateObject().listCards()));
495
- const templateCardIds = new Set();
496
- listCardsResults
497
- .filter((result) => result.status === 'fulfilled')
498
- .forEach((result) => {
499
- result.value.forEach((card) => templateCardIds.add(card.key));
500
- });
501
- return templateCardIds;
502
- })());
503
- }
504
- const allCardIdSets = await Promise.all(promises);
505
- return new Set(allCardIdSets.flatMap((set) => [...set]));
524
+ return allCardIDs;
506
525
  }
507
526
  /**
508
527
  * Returns details of a certain module.
509
528
  * @param moduleName Name of the module.
510
- * @returns module details, or undefined if workflow cannot be found.
529
+ * @returns module details, or undefined if module cannot be found.
511
530
  */
512
531
  async module(moduleName) {
513
532
  const module = await this.findModule(moduleName);
514
533
  if (module && module.path) {
515
- const modulePath = join(module.path, module.name);
516
- const moduleConfig = (await readJsonFile(join(modulePath, Project.projectConfigFileName)));
534
+ const modulePath = module.path;
535
+ const moduleConfig = await readJsonFile(join(modulePath, Project.projectConfigFileName));
517
536
  return {
518
537
  name: moduleConfig.name,
519
538
  modules: moduleConfig.modules,
520
539
  hubs: moduleConfig.hubs,
521
540
  path: modulePath,
522
541
  cardKeyPrefix: moduleConfig.cardKeyPrefix,
523
- calculations: [
524
- ...(await this.resources.collectResourcesFromModules('calculations', moduleName)),
525
- ],
526
- cardTypes: [
527
- ...(await this.resources.collectResourcesFromModules('cardTypes', moduleName)),
528
- ],
529
- fieldTypes: [
530
- ...(await this.resources.collectResourcesFromModules('fieldTypes', moduleName)),
531
- ],
532
- graphModels: [
533
- ...(await this.resources.collectResourcesFromModules('graphModels', moduleName)),
534
- ],
535
- graphViews: [
536
- ...(await this.resources.collectResourcesFromModules('graphViews', moduleName)),
537
- ],
538
- linkTypes: [
539
- ...(await this.resources.collectResourcesFromModules('linkTypes', moduleName)),
540
- ],
541
- reports: [
542
- ...(await this.resources.collectResourcesFromModules('reports', moduleName)),
543
- ],
544
- templates: [
545
- ...(await this.resources.collectResourcesFromModules('templates', moduleName)),
546
- ],
547
- workflows: [
548
- ...(await this.resources.collectResourcesFromModules('workflows', moduleName)),
549
- ],
542
+ calculations: this.resources.moduleResourceNames('calculations', moduleName),
543
+ cardTypes: this.resources.moduleResourceNames('cardTypes', moduleName),
544
+ fieldTypes: this.resources.moduleResourceNames('fieldTypes', moduleName),
545
+ graphModels: this.resources.moduleResourceNames('graphModels', moduleName),
546
+ graphViews: this.resources.moduleResourceNames('graphViews', moduleName),
547
+ linkTypes: this.resources.moduleResourceNames('linkTypes', moduleName),
548
+ reports: this.resources.moduleResourceNames('reports', moduleName),
549
+ templates: this.resources.moduleResourceNames('templates', moduleName),
550
+ workflows: this.resources.moduleResourceNames('workflows', moduleName),
550
551
  };
551
552
  }
552
553
  return undefined;
553
554
  }
554
- /**
555
- * Returns list of modules in the project.
556
- * @returns list of modules in the project.
557
- */
558
- async modules() {
559
- return this.resources.resources('modules');
560
- }
561
555
  /**
562
556
  * Returns a new unique card key with project prefix (e.g. test_x649it4x).
563
557
  * Random part of string will be always 8 characters in base-36 (0-9a-z)
@@ -624,15 +618,16 @@ export class Project extends CardContainer {
624
618
  return this.projectPaths;
625
619
  }
626
620
  /**
627
- * Returns full path to a given card.
628
- * @param cardKey card to check path for.
629
- * @returns path to a given card.
621
+ * Populates the card cache, if it has not been populated.
630
622
  */
631
- pathToCard(cardKey) {
632
- const allFiles = getFilesSync(this.paths.cardRootFolder);
633
- const cardIndexJsonFile = join(cardKey, Project.cardMetadataFile);
634
- const foundFile = allFiles.find((file) => file.includes(cardIndexJsonFile));
635
- return foundFile ? dirname(foundFile) : '';
623
+ async populateCaches() {
624
+ if (!this.cardCache.isPopulated) {
625
+ // Only collect modules that are registered in the project configuration
626
+ if (this.configuration.modules && this.configuration.modules.length > 0) {
627
+ this.resources.changedModules();
628
+ }
629
+ await this.populateCardsCache();
630
+ }
636
631
  }
637
632
  /**
638
633
  * Returns project name.
@@ -647,144 +642,60 @@ export class Project extends CardContainer {
647
642
  return this.settings.cardKeyPrefix;
648
643
  }
649
644
  /**
650
- * Collects all prefixes used in the project (project's own plus all from modules).
645
+ * Returns all prefixes used in the project (project's own plus all from imported modules).
651
646
  * @returns all prefixes used in the project.
652
- * @todo - move the module prefix fetch to resource-collector.
653
- * Make it use cached value that is only changed when module is removed/imported.
654
647
  */
655
- async projectPrefixes() {
648
+ projectPrefixes() {
656
649
  const prefixes = [this.projectPrefix];
657
- let files;
658
- try {
659
- files = await readdir(this.paths.modulesFolder, {
660
- withFileTypes: true,
661
- recursive: true,
662
- });
663
- const configurationFiles = files
664
- .filter((dirent) => dirent.isFile())
665
- .filter((dirent) => dirent.name === Project.projectConfigFileName);
666
- const configurationPromises = configurationFiles.map(async (file) => {
667
- const configuration = (await readJsonFile(join(file.parentPath, file.name)));
668
- return configuration.cardKeyPrefix;
669
- });
670
- const configurationPrefixes = await Promise.all(configurationPromises);
671
- prefixes.push(...configurationPrefixes);
672
- }
673
- catch {
674
- // do nothing if readdir throws // TODO: Log it
675
- }
650
+ const moduleNames = this.configuration.modules.map((item) => item.name);
651
+ prefixes.push(...moduleNames);
676
652
  return prefixes;
677
653
  }
678
654
  /**
679
- * Array of reports in the project.
680
- * @param from Defines where resources are collected from.
681
- * @returns array of all reports in the project.
655
+ * Removes an attachment from a card.
656
+ * @param cardKey The card to remove attachment from
657
+ * @param fileName The name of the attachment file to remove
658
+ * @throws if trying to remove module card attachment, or the attachment was not found.
682
659
  */
683
- async reports(from = ResourcesFrom.all) {
684
- return this.resources.resources('reports', from);
685
- }
686
- /**
687
- * Returns handlebar files from reports.
688
- * @param from Defines where report handlebar files are collected from.
689
- * @returns handlebar files from reports.
690
- */
691
- async reportHandlerBarFiles(from = ResourcesFrom.all) {
692
- const reports = await this.reports(from);
693
- const handleBarFiles = [];
694
- for (const reportResourceName of reports) {
695
- const name = resourceName(reportResourceName.name);
696
- const report = new ReportResource(this, name);
697
- handleBarFiles.push(...(await report.handleBarFiles()));
660
+ async removeCardAttachment(cardKey, fileName) {
661
+ const attachmentFolder = this.cardAttachmentFolder(cardKey);
662
+ // Modules cannot be modified.
663
+ if (isModulePath(attachmentFolder)) {
664
+ throw new Error(`Cannot modify imported module`);
698
665
  }
699
- return handleBarFiles;
700
- }
701
- /**
702
- * Removes a resource from Project.
703
- * @param resource Resource to remove.
704
- */
705
- removeResource(resource) {
706
- this.resources.remove(resource);
707
- this.createdResources.delete(resource.name);
708
- }
709
- /**
710
- * Returns metadata from a given resource
711
- * @param name Name of a resource
712
- * @returns Metadata from the resource.
713
- */
714
- async resource(name) {
715
- const resName = resourceName(name);
716
- if (this.createdResources.has(resourceNameToString(resName))) {
717
- const value = this.createdResources.get(resourceNameToString(resName));
718
- return value;
719
- }
720
- let resource = undefined;
666
+ const attachmentPath = join(attachmentFolder, fileName);
721
667
  try {
722
- resource = Project.resourceObject(this, resName);
668
+ await unlink(attachmentPath);
723
669
  }
724
- catch {
725
- return undefined;
670
+ catch (error) {
671
+ this.logger.error({ error }, 'Removing card attachment');
672
+ throw new Error(`Attachment not found: ${fileName}`);
726
673
  }
727
- const data = resource?.data;
728
- if (!data) {
729
- return undefined;
730
- }
731
- return data;
674
+ await this.handleAttachmentChange(cardKey, 'removed', fileName);
732
675
  }
733
676
  /**
734
- * Returns resource cache.
677
+ * Removes a module from the project cache and configuration.
678
+ * @note that ModuleManager removes the actual files.
679
+ * @param moduleName Module name to remove.
735
680
  */
736
- get resourceCache() {
737
- return this.createdResources;
681
+ async removeModule(moduleName) {
682
+ const toBeRemovedTemplates = this.resources.moduleResourceNames('templates', moduleName);
683
+ // First, remove template cards from the cache that are part of removed templates.
684
+ for (const templateName of toBeRemovedTemplates) {
685
+ this.cardCache.deleteCardsFromTemplate(templateName);
686
+ }
687
+ // Then, remove all module resources from cache
688
+ this.resources.removeModule(moduleName);
689
+ // Finally, remove module from project configuration
690
+ await this.configuration.removeModule(moduleName);
691
+ this.logger.info(`Removed module '${moduleName}'`);
738
692
  }
739
693
  /**
740
- * Checks if a given resource exists in the project already.
741
- * @param resourceType Type of resource as a string.
742
- * @param name Valid name of resource.
743
- * @returns boolean, true if resource exists; false otherwise.
694
+ * Accessor for resource handler.
695
+ * @returns Resource handler instance.
744
696
  */
745
- async resourceExists(resourceType, name) {
746
- const resources = await this.resourcesOfType(resourceType, ResourcesFrom.all);
747
- const resource = resources.find((item) => item.name === name);
748
- return resource !== undefined;
749
- }
750
- /**
751
- * Instantiates resource object from project with a resource name.
752
- * @note that this is memory based object only.
753
- * To manipulate the resource (create files, delete files etc), use the resource object's API.
754
- * @param project Project from which resources are created from.
755
- * @param name Resource name
756
- * @throws if called with unsupported resource type.
757
- * @returns Created resource.
758
- */
759
- static resourceObject(project, name) {
760
- if (name.type === 'calculations') {
761
- return new CalculationResource(project, name);
762
- }
763
- else if (name.type === 'cardTypes') {
764
- return new CardTypeResource(project, name);
765
- }
766
- else if (name.type === 'fieldTypes') {
767
- return new FieldTypeResource(project, name);
768
- }
769
- else if (name.type === 'graphModels') {
770
- return new GraphModelResource(project, name);
771
- }
772
- else if (name.type === 'graphViews') {
773
- return new GraphViewResource(project, name);
774
- }
775
- else if (name.type === 'linkTypes') {
776
- return new LinkTypeResource(project, name);
777
- }
778
- else if (name.type === 'reports') {
779
- return new ReportResource(project, name);
780
- }
781
- else if (name.type === 'templates') {
782
- return new TemplateResource(project, name);
783
- }
784
- else if (name.type === 'workflows') {
785
- return new WorkflowResource(project, name);
786
- }
787
- throw new Error(`Unsupported resource type '${resourceNameToString(name)}'`);
697
+ get resources() {
698
+ return this.resourceHandler;
788
699
  }
789
700
  /**
790
701
  * Shows details of a project.
@@ -796,7 +707,7 @@ export class Project extends CardContainer {
796
707
  path: this.basePath,
797
708
  prefix: this.projectPrefix,
798
709
  hubs: this.configuration.hubs,
799
- modules: (await this.modules()).map((item) => item.name),
710
+ modules: this.resources.moduleNames(),
800
711
  numberOfCards: (await this.listCards(CardLocation.projectOnly))[0].cards
801
712
  .length,
802
713
  };
@@ -805,57 +716,37 @@ export class Project extends CardContainer {
805
716
  * Show cards of a project.
806
717
  * @returns an array of all project cards in the project.
807
718
  */
808
- async showProjectCards() {
719
+ showProjectCards() {
809
720
  return this.showCards(this.paths.cardRootFolder);
810
721
  }
811
- /**
812
- * Returns all template cards from the project. This includes all module templates' cards.
813
- * @param cardDetails which details to fetch. Optional.
814
- * @returns all the template cards from the project
815
- */
816
- async allTemplateCards(cardDetails) {
817
- const templates = await this.templates();
818
- const cards = [];
819
- for (const template of templates) {
820
- const templateCards = await this.templateCards(template.name, cardDetails);
821
- if (templateCards)
822
- cards.push(...templateCards);
823
- }
824
- return cards;
825
- }
826
722
  /**
827
723
  * Returns cards from single template.
828
- * @param templateName Name of the template
829
- * @param cardDetails Card information
724
+ * @param templateName Name of the template (supports both full names like 'decision/templates/decision' and short names like 'decision')
830
725
  * @returns List of cards from template.
831
726
  */
832
- async templateCards(templateName, cardDetails) {
833
- const templateObject = new TemplateResource(this, resourceName(templateName)).templateObject();
834
- return await templateObject?.cards('', cardDetails);
835
- }
836
- /**
837
- * Array of templates in the project.
838
- * @param from Defines where resources are collected from.
839
- * @returns array of all templates in the project.
840
- */
841
- async templates(from = ResourcesFrom.all) {
842
- return this.resources.resources('templates', from);
727
+ templateCards(templateName) {
728
+ const templateCards = this.cardCache.getAllTemplateCards();
729
+ return templateCards.filter((cachedCard) => {
730
+ if (cachedCard.location === 'project') {
731
+ return false;
732
+ }
733
+ return cachedCard.location === templateName;
734
+ });
843
735
  }
844
736
  /**
845
- * Update card content.
846
- * @param cardKey card's ID that is updated.
737
+ * Update a card's content.
738
+ * @param cardKey card key to update.
847
739
  * @param content changed content
848
740
  */
849
741
  async updateCardContent(cardKey, content) {
850
- const card = await this.findCard(this.basePath, cardKey, {
851
- metadata: true,
852
- content: true,
853
- });
854
- if (!card) {
855
- throw new Error(`Card '${cardKey}' does not exist in the project`);
856
- }
742
+ const card = this.findCard(cardKey);
857
743
  card.content = content;
744
+ // Update lastUpdated timestamp in metadata
745
+ if (card.metadata) {
746
+ card.metadata.lastUpdated = new Date().toISOString();
747
+ }
858
748
  await this.saveCard(card);
749
+ await this.handleCardChanged(card);
859
750
  }
860
751
  /**
861
752
  * Updates card metadata's single key.
@@ -864,16 +755,13 @@ export class Project extends CardContainer {
864
755
  * @param newValue changed value for the key
865
756
  */
866
757
  async updateCardMetadataKey(cardKey, changedKey, newValue) {
867
- const templateCard = await this.isTemplateCard(cardKey);
868
- const card = await this.findCard(templateCard ? this.paths.templatesFolder : this.paths.cardRootFolder, cardKey, {
869
- metadata: true,
870
- });
871
- if (!card) {
872
- throw new Error(`Card '${cardKey}' does not exist in the project`);
873
- }
758
+ const card = this.findCard(cardKey);
874
759
  if (!card.metadata || card.metadata[changedKey] === newValue) {
875
760
  return;
876
761
  }
762
+ const isRankChange = changedKey === 'rank';
763
+ const previousPath = isRankChange ? card.path : undefined;
764
+ const previousParent = isRankChange ? card.parent : undefined;
877
765
  const cardAsRecord = card.metadata;
878
766
  cardAsRecord[changedKey] = newValue;
879
767
  const invalidCard = isTemplateCard(card)
@@ -882,50 +770,52 @@ export class Project extends CardContainer {
882
770
  if (invalidCard.length !== 0) {
883
771
  throw new Error(invalidCard);
884
772
  }
885
- await this.saveCardMetadata(card);
886
- }
887
- /**
888
- * Updates card metadata.
889
- * @param card affected card
890
- * @param changedMetadata changed content for the card
891
- */
892
- async updateCardMetadata(card, changedMetadata) {
893
- card.metadata = changedMetadata;
894
- return this.saveCardMetadata(card);
773
+ const updated = await this.saveCardMetadata(card);
774
+ if (!updated)
775
+ return;
776
+ // For rank changes, check if path changed (indicating a move)
777
+ if (isRankChange) {
778
+ const updatedCard = this.findCard(cardKey);
779
+ if (updatedCard.path !== previousPath) {
780
+ this.changeParent(updatedCard, previousParent);
781
+ }
782
+ }
895
783
  }
896
784
  /**
897
- * Validates that card's data is valid.
898
- * @param card Card to validate.
899
- * @returns validation errors, if any
785
+ * Updates the entire card in the card cache and handles any path/parent changes.
786
+ * Also persists changes to content and metadata files.
787
+ * @param card The card with updated information (path, parent, metadata, etc.)
900
788
  */
901
- async validateCard(card) {
902
- const invalidCustomData = await this.validator.validateCustomFields(this, card);
903
- const invalidWorkFlow = await this.validator.validateWorkflowState(this, card);
904
- const invalidLabels = await this.validator.validateCardLabels(card);
905
- if (invalidCustomData.length === 0 &&
906
- invalidWorkFlow.length === 0 &&
907
- invalidLabels.length === 0) {
908
- return '';
789
+ async updateCard(card) {
790
+ const cachedCard = this.cardCache.getCard(card.key);
791
+ const pathChange = cachedCard && cachedCard.path !== card.path;
792
+ if (pathChange) {
793
+ this.changeParent(card, cachedCard.parent);
909
794
  }
910
- const errors = [];
911
- if (invalidCustomData.length > 0) {
912
- errors.push(invalidCustomData);
795
+ const metadataChanged = cachedCard &&
796
+ JSON.stringify(cachedCard.metadata) !== JSON.stringify(card.metadata);
797
+ if (metadataChanged) {
798
+ await this.saveCardMetadata(card);
913
799
  }
914
- if (invalidWorkFlow.length > 0) {
915
- errors.push(invalidWorkFlow);
800
+ const contentChanged = cachedCard && cachedCard.content !== card.content;
801
+ if (contentChanged) {
802
+ await this.saveCardContent(card);
916
803
  }
917
- if (invalidLabels.length > 0) {
918
- errors.push(invalidLabels);
804
+ this.cardCache.updateCard(card.key, card);
805
+ if (metadataChanged || contentChanged || pathChange) {
806
+ await this.handleCardChanged(card);
919
807
  }
920
- return errors.join('\n');
921
808
  }
922
809
  /**
923
- * Array of workflows in the project.
924
- * @param from Defines where resources are collected from.
925
- * @returns array of all workflows in the project.
810
+ * Updates a card's metadata.
811
+ * @param card affected card
812
+ * @param changedMetadata changed content for the card
926
813
  */
927
- async workflows(from = ResourcesFrom.all) {
928
- return this.resources.resources('workflows', from);
814
+ async updateCardMetadata(card, changedMetadata) {
815
+ card.metadata = changedMetadata;
816
+ if (await this.saveCardMetadata(card)) {
817
+ await this.handleCardChanged(card);
818
+ }
929
819
  }
930
820
  }
931
821
  //# sourceMappingURL=project.js.map