@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
@@ -0,0 +1,648 @@
1
+ /**
2
+ Cyberismo
3
+ Copyright © Cyberismo Ltd and contributors 2025
4
+
5
+ This program is free software: you can redistribute it and/or modify it under
6
+ the terms of the GNU Affero General Public License version 3 as published by
7
+ the Free Software Foundation. This program is distributed in the hope that it
8
+ will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9
+ of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
+ See the GNU Affero General Public License for more details.
11
+ You should have received a copy of the GNU Affero General Public
12
+ License along with this program. If not, see <https://www.gnu.org/licenses/>.
13
+ */
14
+
15
+ import { dirname, extname, join } from 'node:path';
16
+ import { type Dirent, readdirSync, readFileSync } from 'node:fs';
17
+
18
+ import { getChildLogger } from '../../utils/log-utils.js';
19
+ import {
20
+ pathToResourceName,
21
+ resourceName,
22
+ resourceNameToString,
23
+ } from '../../utils/resource-utils.js';
24
+ import { stripExtension } from '../../utils/file-utils.js';
25
+ import { VALID_FOLDER_RESOURCE_FILES } from '../../utils/constants.js';
26
+
27
+ import { CalculationResource } from '../../resources/calculation-resource.js';
28
+ import { CardTypeResource } from '../../resources/card-type-resource.js';
29
+ import { FieldTypeResource } from '../../resources/field-type-resource.js';
30
+ import { GraphModelResource } from '../../resources/graph-model-resource.js';
31
+ import { GraphViewResource } from '../../resources/graph-view-resource.js';
32
+ import { LinkTypeResource } from '../../resources/link-type-resource.js';
33
+ import { ReportResource } from '../../resources/report-resource.js';
34
+ import { TemplateResource } from '../../resources/template-resource.js';
35
+ import { WorkflowResource } from '../../resources/workflow-resource.js';
36
+
37
+ import type { Project } from '../project.js';
38
+ import type { ResourceFolderType } from '../../interfaces/project-interfaces.js';
39
+ import type { ResourceName } from '../../utils/resource-utils.js';
40
+
41
+ // Project resource, such as workflow, template or card type as file system object.
42
+ // @todo: Once template constructor has been fixed, no need to export this.
43
+ export interface Resource {
44
+ name: string;
45
+ path: string;
46
+ }
47
+
48
+ // Resource type mappings
49
+ export type ResourceMap = {
50
+ calculations: CalculationResource;
51
+ cardTypes: CardTypeResource;
52
+ fieldTypes: FieldTypeResource;
53
+ graphViews: GraphViewResource;
54
+ graphModels: GraphModelResource;
55
+ linkTypes: LinkTypeResource;
56
+ reports: ReportResource;
57
+ templates: TemplateResource;
58
+ workflows: WorkflowResource;
59
+ };
60
+
61
+ // Helper for SafeExtract.
62
+ export type ExtractResourceType<T extends string> =
63
+ T extends `${string}/${infer R}/${string}` ? R : never;
64
+
65
+ // If type is correct, this always infers type correctly.
66
+ export type SafeExtract<T extends string> =
67
+ ExtractResourceType<T> extends keyof ResourceMap
68
+ ? ExtractResourceType<T>
69
+ : never;
70
+
71
+ // Defines where resources are collected from.
72
+ export enum ResourcesFrom {
73
+ all = 'all',
74
+ importedOnly = 'imported',
75
+ localOnly = 'local',
76
+ }
77
+
78
+ // Resource as stored in the instance cache.
79
+ interface ResourceMetadata {
80
+ name: string;
81
+ type: ResourceFolderType;
82
+ path: string;
83
+ source: 'local' | 'module';
84
+ moduleName?: string;
85
+ contentFiles?: Map<string, string>;
86
+ }
87
+
88
+ // Allowed files in resource instance data.
89
+ const allowedExtensions = ['.lp', '.json'];
90
+
91
+ // Resource types that have internal folders with content files
92
+ const FOLDER_RESOURCE_TYPES: ResourceFolderType[] = [
93
+ 'calculations',
94
+ 'graphModels',
95
+ 'graphViews',
96
+ 'reports',
97
+ 'templates',
98
+ ];
99
+
100
+ /**
101
+ * ResourceCache handles all resource collecting, caching, and management.
102
+ * Uses a two-layered approach:
103
+ * 1. lightweight registry for collecting items that exist on disk
104
+ * 2. more complex instance cache that contains full instance data of a resource.
105
+ *
106
+ * Resource populates the first layer automatically when created.
107
+ * When new instance of a resource is created by an access function (e.g. resourceByType() or resourceByName()),
108
+ * instance of resource is saved to cache.
109
+ *
110
+ */
111
+ export class ResourceCache {
112
+ private resourceRegistry = new Map<string, ResourceMetadata>();
113
+ private instanceCache = new Map<string, unknown>();
114
+
115
+ private project: Project;
116
+
117
+ // Private constructor - use ResourceCache.create() instead.
118
+ private constructor(project: Project) {
119
+ this.project = project;
120
+ }
121
+
122
+ // Initialize the cache by collecting all resources.
123
+ private initialize(): void {
124
+ this.collectAllResources();
125
+ }
126
+
127
+ // Build a full resource name from partial name and type.
128
+ private buildResourceName(name: string, type?: string): ResourceName {
129
+ if (type && name && name.split('/').length === 1) {
130
+ name = `${this.project.projectPrefix}/${type}/${name}`;
131
+ }
132
+ return resourceName(name);
133
+ }
134
+
135
+ // Create a resource object instance
136
+ private createResourceObject(resourceName: ResourceName): unknown {
137
+ const key = resourceNameToString(resourceName);
138
+ const metadata = this.resourceRegistry.get(key);
139
+ let resource: unknown;
140
+
141
+ if (resourceName.type === 'calculations') {
142
+ resource = new CalculationResource(this.project, resourceName);
143
+ } else if (resourceName.type === 'cardTypes') {
144
+ resource = new CardTypeResource(this.project, resourceName);
145
+ } else if (resourceName.type === 'fieldTypes') {
146
+ resource = new FieldTypeResource(this.project, resourceName);
147
+ } else if (resourceName.type === 'graphModels') {
148
+ resource = new GraphModelResource(this.project, resourceName);
149
+ } else if (resourceName.type === 'graphViews') {
150
+ resource = new GraphViewResource(this.project, resourceName);
151
+ } else if (resourceName.type === 'linkTypes') {
152
+ resource = new LinkTypeResource(this.project, resourceName);
153
+ } else if (resourceName.type === 'reports') {
154
+ resource = new ReportResource(this.project, resourceName);
155
+ } else if (resourceName.type === 'templates') {
156
+ resource = new TemplateResource(this.project, resourceName);
157
+ } else if (resourceName.type === 'workflows') {
158
+ resource = new WorkflowResource(this.project, resourceName);
159
+ } else {
160
+ throw new Error(`Unsupported resource type '${resourceName.type}'`);
161
+ }
162
+
163
+ // Populate content files into folder resources
164
+ if (metadata?.contentFiles && this.hasSetContentFiles(resource)) {
165
+ resource.setContentFiles(metadata.contentFiles);
166
+ }
167
+
168
+ return resource;
169
+ }
170
+
171
+ // Collects all resources; both local and modules.
172
+ private collectAllResources() {
173
+ this.collectLocalResources();
174
+ this.collectModuleResources();
175
+ }
176
+
177
+ // Collect all local resources from the filesystem
178
+ private collectLocalResources() {
179
+ const resourceTypes: ResourceFolderType[] = [
180
+ 'calculations',
181
+ 'cardTypes',
182
+ 'fieldTypes',
183
+ 'graphModels',
184
+ 'graphViews',
185
+ 'linkTypes',
186
+ 'reports',
187
+ 'templates',
188
+ 'workflows',
189
+ ];
190
+
191
+ for (const type of resourceTypes) {
192
+ this.collectResourcesOfType(type, 'local');
193
+ }
194
+ }
195
+
196
+ // Collect all module resources from the filesystem
197
+ // Only collects modules that are registered in the project configuration
198
+ // todo: For future:
199
+ // Should it also try to collect what is in .local/modules and then log for disparities?
200
+ private collectModuleResources() {
201
+ try {
202
+ const registeredModules = this.project.configuration.modules.map(
203
+ (m) => m.name,
204
+ );
205
+ if (registeredModules.length === 0) {
206
+ return;
207
+ }
208
+
209
+ const resourceTypes: ResourceFolderType[] = [
210
+ 'calculations',
211
+ 'cardTypes',
212
+ 'fieldTypes',
213
+ 'graphModels',
214
+ 'graphViews',
215
+ 'linkTypes',
216
+ 'reports',
217
+ 'templates',
218
+ 'workflows',
219
+ ];
220
+
221
+ for (const moduleName of registeredModules) {
222
+ for (const type of resourceTypes) {
223
+ this.collectResourcesOfType(type, 'module', moduleName);
224
+ }
225
+ }
226
+ } catch {
227
+ ResourceCache.logger.warn(`.cards/modules folder is missing`);
228
+ }
229
+ }
230
+
231
+ // Collects one folder resource's internal folder content.
232
+ private collectResourceContentFiles(type: ResourceFolderType, entry: Dirent) {
233
+ const identifier = stripExtension(entry.name);
234
+ let contentFiles: Map<string, string> | undefined = undefined;
235
+
236
+ // Set content files for folder resources
237
+ if (FOLDER_RESOURCE_TYPES.includes(type)) {
238
+ const internalFolder = join(entry.parentPath, identifier);
239
+ try {
240
+ const contentEntries = readdirSync(internalFolder, {
241
+ withFileTypes: true,
242
+ });
243
+ const files = new Map<string, string>();
244
+
245
+ for (const contentEntry of contentEntries) {
246
+ if (
247
+ contentEntry.isFile() &&
248
+ VALID_FOLDER_RESOURCE_FILES.includes(contentEntry.name)
249
+ ) {
250
+ try {
251
+ const filePath = join(internalFolder, contentEntry.name);
252
+ const content = readFileSync(filePath, 'utf8');
253
+ files.set(contentEntry.name, content);
254
+ } catch {
255
+ ResourceCache.logger.warn(
256
+ `Failed to read content file '${contentEntry.name}' for resource '${name}'`,
257
+ );
258
+ }
259
+ }
260
+ }
261
+
262
+ contentFiles = files.size > 0 ? files : undefined;
263
+ } catch {
264
+ // Internal folder doesn't exist - this is okay
265
+ }
266
+ }
267
+ return contentFiles;
268
+ }
269
+
270
+ // Collect resources of a specific type
271
+ private collectResourcesOfType(
272
+ type: ResourceFolderType,
273
+ source: 'local' | 'module',
274
+ moduleName?: string,
275
+ ) {
276
+ const resourceFolder =
277
+ source === 'local'
278
+ ? this.project.paths.resourcePath(type)
279
+ : join(this.project.paths.modulesFolder, moduleName!, type);
280
+
281
+ try {
282
+ const entries = readdirSync(resourceFolder, { withFileTypes: true });
283
+
284
+ for (const entry of entries) {
285
+ if (entry.isFile() && allowedExtensions.includes(extname(entry.name))) {
286
+ const name =
287
+ source === 'local'
288
+ ? `${this.project.projectPrefix}/${type}/${stripExtension(entry.name)}`
289
+ : `${moduleName}/${type}/${stripExtension(entry.name)}`;
290
+
291
+ this.resourceRegistry.set(name, {
292
+ name: name,
293
+ type: type,
294
+ path: entry.parentPath,
295
+ source: source,
296
+ moduleName: source === 'module' ? moduleName : undefined,
297
+ contentFiles: this.collectResourceContentFiles(type, entry),
298
+ });
299
+ }
300
+ }
301
+ } catch {
302
+ ResourceCache.logger.warn(
303
+ `Resource folder '${resourceFolder}' is missing`,
304
+ );
305
+ }
306
+ }
307
+
308
+ // Removes a key from cache layers.
309
+ private deleteKey(key: string) {
310
+ this.resourceRegistry.delete(key);
311
+ this.instanceCache.delete(key);
312
+ }
313
+
314
+ // Type guard to check if resource has setContentFiles method
315
+ private hasSetContentFiles(
316
+ resource: unknown,
317
+ ): resource is { setContentFiles: (files: Map<string, string>) => void } {
318
+ return (
319
+ typeof (resource as { setContentFiles?: unknown }).setContentFiles ===
320
+ 'function'
321
+ );
322
+ }
323
+
324
+ // Returns instance of logger.
325
+ private static get logger() {
326
+ return getChildLogger({
327
+ module: 'resourceCache',
328
+ });
329
+ }
330
+
331
+ // Normalize resource name (string or ResourceName) to a consistent string format.
332
+ private normalizeResourceName(name: string | ResourceName): string {
333
+ if (typeof name === 'string') {
334
+ const resName = resourceName(name);
335
+ return resourceNameToString(resName);
336
+ }
337
+ return resourceNameToString(name);
338
+ }
339
+
340
+ /**
341
+ * Add a resource instance to cache. If using
342
+ * @param name Name of the resource to update
343
+ * @param instance New data for the resource.
344
+ */
345
+ public addResource(name: string | ResourceName, instance: unknown) {
346
+ const key = this.normalizeResourceName(name);
347
+ if (!this.resourceRegistry.has(key)) {
348
+ const resName = typeof name === 'string' ? resourceName(name) : name;
349
+ const isModule = resName.prefix !== this.project.projectPrefix;
350
+ const resourcePath = isModule
351
+ ? this.project.paths.moduleResourcePath(
352
+ resName.prefix,
353
+ resName.type as ResourceFolderType,
354
+ )
355
+ : this.project.paths.resourcePath(resName.type as ResourceFolderType);
356
+
357
+ this.resourceRegistry.set(key, {
358
+ name: key,
359
+ type: resName.type as ResourceFolderType,
360
+ path: resourcePath,
361
+ source: isModule ? 'module' : 'local',
362
+ moduleName: isModule ? resName.prefix : undefined,
363
+ contentFiles: undefined, // resources will set this as-needed
364
+ });
365
+
366
+ this.instanceCache.set(key, instance);
367
+ }
368
+ }
369
+
370
+ /**
371
+ * Refresh local resources in the cache.
372
+ */
373
+ public changed() {
374
+ for (const [key, metadata] of this.resourceRegistry) {
375
+ if (metadata.source === 'local') {
376
+ this.resourceRegistry.delete(key);
377
+ }
378
+ }
379
+ this.collectLocalResources();
380
+ }
381
+
382
+ /**
383
+ * Refresh module resources in the cache.
384
+ * @param moduleName Name of the module. If given, will only update this modules resources.
385
+ */
386
+ public changedModules(moduleName?: string) {
387
+ for (const [key, metadata] of this.resourceRegistry) {
388
+ if (
389
+ metadata.source === 'module' &&
390
+ (metadata.moduleName === moduleName || !moduleName)
391
+ ) {
392
+ this.resourceRegistry.delete(key);
393
+ }
394
+ }
395
+ this.collectModuleResources();
396
+ }
397
+
398
+ /**
399
+ * Change resource name in cache, but keep instance information.
400
+ * Cache has to create cache key for new and move the existing instance to it.
401
+ * @param oldName Old name of the resource
402
+ * @param newName New name of the resource
403
+ */
404
+ public changeResourceName(oldName: string, newName: string) {
405
+ const oldKey = this.normalizeResourceName(oldName);
406
+ const newKey = this.normalizeResourceName(newName);
407
+
408
+ // Move instance from old key to new key if it exists
409
+ if (this.instanceCache.has(oldKey)) {
410
+ const resource = this.instanceCache.get(oldKey);
411
+ this.instanceCache.delete(oldKey);
412
+ this.instanceCache.set(newKey, resource);
413
+ }
414
+
415
+ // Update registry
416
+ const metadata = this.resourceRegistry.get(oldKey);
417
+ if (metadata) {
418
+ this.resourceRegistry.delete(oldKey);
419
+ this.resourceRegistry.set(newKey, {
420
+ ...metadata,
421
+ name: newKey,
422
+ });
423
+ }
424
+ }
425
+
426
+ /**
427
+ * Creates and initializes a ResourceCache.
428
+ * This performs filesystem I/O to collect all resources.
429
+ * @param project Project to use
430
+ * @returns Initialized ResourceCache
431
+ */
432
+ public static create(project: Project): ResourceCache {
433
+ const cache = new ResourceCache(project);
434
+ cache.initialize();
435
+ return cache;
436
+ }
437
+
438
+ /**
439
+ * Handle file system changes
440
+ * This is used by the Watcher in the Project class.
441
+ * @param fileName A changed file in the file system.
442
+ */
443
+ public handleFileSystemChange(fileName: string) {
444
+ try {
445
+ const resource = pathToResourceName(this.project, fileName);
446
+ if (!resource) {
447
+ return;
448
+ }
449
+
450
+ const name = resourceNameToString(resource);
451
+
452
+ // Update registry with new path
453
+ const isModule = resource.prefix !== this.project.projectPrefix;
454
+ this.resourceRegistry.set(name, {
455
+ name: name,
456
+ type: resource.type as ResourceFolderType,
457
+ path: dirname(fileName),
458
+ source: isModule ? 'module' : 'local',
459
+ moduleName: isModule ? resource.prefix : undefined,
460
+ contentFiles: undefined,
461
+ });
462
+
463
+ // Invalidate cached instance
464
+ this.invalidateResource(name);
465
+ } catch {
466
+ ResourceCache.logger.warn(`Not a resource file: ${fileName}`);
467
+ }
468
+ }
469
+
470
+ /**
471
+ * Invalidate a resource instance.
472
+ * This forces reload on next access.
473
+ * @param name Name of the resource to invalidate.
474
+ */
475
+ public invalidateResource(name: string | ResourceName) {
476
+ const key = this.normalizeResourceName(name);
477
+
478
+ // Remove from instance cache, but keep in registry
479
+ this.instanceCache.delete(key);
480
+ }
481
+
482
+ /**
483
+ * Get module names.
484
+ * @returns Module names.
485
+ */
486
+ public moduleNames(): string[] {
487
+ const names = new Set<string>();
488
+
489
+ for (const [, metadata] of this.resourceRegistry) {
490
+ if (metadata.source === 'module' && metadata.moduleName) {
491
+ names.add(metadata.moduleName);
492
+ }
493
+ }
494
+
495
+ return Array.from(names);
496
+ }
497
+
498
+ /**
499
+ * Get certain types of resources from a specific module.
500
+ * @param type Type of resource to fetch
501
+ * @param moduleName Name of the module
502
+ * @returns resources names from a specific module.
503
+ */
504
+ public moduleResourceNames(
505
+ type: ResourceFolderType,
506
+ moduleName: string,
507
+ ): string[] {
508
+ const names: string[] = [];
509
+
510
+ for (const [key, metadata] of this.resourceRegistry) {
511
+ if (
512
+ metadata.type === type &&
513
+ metadata.source === 'module' &&
514
+ metadata.moduleName === moduleName
515
+ ) {
516
+ names.push(key);
517
+ }
518
+ }
519
+
520
+ return names;
521
+ }
522
+
523
+ /**
524
+ * Invalidate all resources of a specific module.
525
+ * @param moduleName Name of the module.
526
+ */
527
+ public removeModule(moduleName: string) {
528
+ for (const [key, metadata] of this.resourceRegistry) {
529
+ if (
530
+ metadata &&
531
+ metadata.source === 'module' &&
532
+ metadata.moduleName === moduleName
533
+ ) {
534
+ this.removeResource(key);
535
+ }
536
+ }
537
+ }
538
+
539
+ /**
540
+ * Remove a resource from cache. This includes both registry and instance cache.
541
+ * @param name Resource to remove.
542
+ */
543
+ public removeResource(name: string | ResourceName) {
544
+ const key = this.normalizeResourceName(name);
545
+ if (!this.resourceRegistry.get(key)) {
546
+ return;
547
+ }
548
+ this.deleteKey(key);
549
+ }
550
+
551
+ /**
552
+ * Get resource with explicit type parameter
553
+ * @param name Name of the resource
554
+ * @param type Type of the resource
555
+ * @template T Resource type
556
+ * @throws If resource creation fails.
557
+ * @returns Typed resource that matches name and type.
558
+ */
559
+ public resourceByType<T extends keyof ResourceMap>(
560
+ name: string,
561
+ type: T,
562
+ ): ResourceMap[T] {
563
+ const builtName = this.buildResourceName(name, type);
564
+ const key = resourceNameToString(builtName);
565
+
566
+ if (this.instanceCache.has(key)) {
567
+ return this.instanceCache.get(key) as ResourceMap[T];
568
+ }
569
+
570
+ const resource = this.createResourceObject(builtName);
571
+ if (!resource) {
572
+ throw new Error(`Failed to create resource '${key}'`);
573
+ }
574
+
575
+ if (this.resourceRegistry.has(key)) {
576
+ this.instanceCache.set(key, resource);
577
+ }
578
+
579
+ return resource as ResourceMap[T];
580
+ }
581
+
582
+ /**
583
+ * Get resource by ResourceName object
584
+ * @param name Resource name.
585
+ * @throws If resource creation fails.
586
+ * @returns Typed resource that matches the name.
587
+ */
588
+ public resourceByName<T extends keyof ResourceMap>(
589
+ name: ResourceName,
590
+ ): ResourceMap[T] {
591
+ const key = resourceNameToString(name);
592
+
593
+ if (this.instanceCache.has(key)) {
594
+ return this.instanceCache.get(key) as ResourceMap[T];
595
+ }
596
+
597
+ const resource = this.createResourceObject(name);
598
+ if (!resource) {
599
+ throw new Error(`Failed to create resource '${key}'`);
600
+ }
601
+
602
+ if (this.resourceRegistry.has(key)) {
603
+ this.instanceCache.set(key, resource);
604
+ }
605
+
606
+ return resource as ResourceMap[T];
607
+ }
608
+
609
+ /**
610
+ * Check if a resource exists.
611
+ * @param name Resource name to check.
612
+ * @returns true, if resource is in the cache; false otherwise.
613
+ */
614
+ public has(name: string | ResourceName): boolean {
615
+ const key = this.normalizeResourceName(name);
616
+ return this.resourceRegistry.has(key);
617
+ }
618
+
619
+ /**
620
+ * Get resources with full metadata for a specific type
621
+ * @param type Type of resources to get.
622
+ * @param from Where to return resources from (all, local, imported modules)
623
+ * @template T Resource type
624
+ * @returns Array of resources with metadata.
625
+ */
626
+ public resources<T extends keyof ResourceMap>(
627
+ type: T,
628
+ from: ResourcesFrom = ResourcesFrom.all,
629
+ ): Array<ResourceMap[T]> {
630
+ const resources: ResourceMap[T][] = [];
631
+
632
+ for (const [key, metadata] of this.resourceRegistry) {
633
+ if (metadata.type !== type) continue;
634
+
635
+ if (from === ResourcesFrom.localOnly && metadata.source !== 'local')
636
+ continue;
637
+ if (from === ResourcesFrom.importedOnly && metadata.source !== 'module')
638
+ continue;
639
+
640
+ // Get or create the actual resource instance
641
+ const resName = resourceName(key);
642
+ const resource = this.resourceByName<T>(resName);
643
+ resources.push(resource);
644
+ }
645
+
646
+ return resources;
647
+ }
648
+ }