@cyberismo/data-handler 0.0.15 → 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 (173) hide show
  1. package/dist/card-metadata-updater.js +7 -1
  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 +19 -3
  5. package/dist/command-handler.js.map +1 -1
  6. package/dist/command-manager.d.ts +24 -1
  7. package/dist/command-manager.js +27 -3
  8. package/dist/command-manager.js.map +1 -1
  9. package/dist/commands/create.d.ts +1 -1
  10. package/dist/commands/create.js +34 -36
  11. package/dist/commands/create.js.map +1 -1
  12. package/dist/commands/export.d.ts +11 -2
  13. package/dist/commands/export.js +54 -41
  14. package/dist/commands/export.js.map +1 -1
  15. package/dist/commands/import.d.ts +9 -1
  16. package/dist/commands/import.js +15 -7
  17. package/dist/commands/import.js.map +1 -1
  18. package/dist/commands/move.js +0 -1
  19. package/dist/commands/move.js.map +1 -1
  20. package/dist/commands/remove.d.ts +8 -1
  21. package/dist/commands/remove.js +8 -4
  22. package/dist/commands/remove.js.map +1 -1
  23. package/dist/commands/rename.d.ts +4 -9
  24. package/dist/commands/rename.js +32 -101
  25. package/dist/commands/rename.js.map +1 -1
  26. package/dist/commands/show.d.ts +16 -10
  27. package/dist/commands/show.js +71 -55
  28. package/dist/commands/show.js.map +1 -1
  29. package/dist/commands/transition.d.ts +9 -2
  30. package/dist/commands/transition.js +25 -17
  31. package/dist/commands/transition.js.map +1 -1
  32. package/dist/commands/update.d.ts +16 -12
  33. package/dist/commands/update.js +19 -17
  34. package/dist/commands/update.js.map +1 -1
  35. package/dist/commands/validate.d.ts +17 -9
  36. package/dist/commands/validate.js +96 -35
  37. package/dist/commands/validate.js.map +1 -1
  38. package/dist/containers/project/calculation-engine.d.ts +7 -4
  39. package/dist/containers/project/calculation-engine.js +61 -66
  40. package/dist/containers/project/calculation-engine.js.map +1 -1
  41. package/dist/containers/project/project-paths.d.ts +5 -4
  42. package/dist/containers/project/project-paths.js +16 -12
  43. package/dist/containers/project/project-paths.js.map +1 -1
  44. package/dist/containers/project/resource-cache.d.ts +169 -0
  45. package/dist/containers/project/resource-cache.js +507 -0
  46. package/dist/containers/project/resource-cache.js.map +1 -0
  47. package/dist/containers/project/resource-handler.d.ts +129 -0
  48. package/dist/containers/project/resource-handler.js +206 -0
  49. package/dist/containers/project/resource-handler.js.map +1 -0
  50. package/dist/containers/project.d.ts +38 -153
  51. package/dist/containers/project.js +129 -405
  52. package/dist/containers/project.js.map +1 -1
  53. package/dist/containers/template.d.ts +8 -2
  54. package/dist/containers/template.js +20 -15
  55. package/dist/containers/template.js.map +1 -1
  56. package/dist/interfaces/folder-content-interfaces.d.ts +5 -3
  57. package/dist/interfaces/folder-content-interfaces.js +3 -3
  58. package/dist/interfaces/folder-content-interfaces.js.map +1 -1
  59. package/dist/interfaces/project-interfaces.d.ts +2 -4
  60. package/dist/interfaces/project-interfaces.js.map +1 -1
  61. package/dist/interfaces/resource-interfaces.d.ts +14 -1
  62. package/dist/interfaces/resource-interfaces.js.map +1 -1
  63. package/dist/macros/graph/index.js +12 -26
  64. package/dist/macros/graph/index.js.map +1 -1
  65. package/dist/macros/index.d.ts +1 -1
  66. package/dist/macros/index.js +2 -2
  67. package/dist/macros/index.js.map +1 -1
  68. package/dist/macros/report/index.js +3 -6
  69. package/dist/macros/report/index.js.map +1 -1
  70. package/dist/module-manager.d.ts +16 -3
  71. package/dist/module-manager.js +51 -19
  72. package/dist/module-manager.js.map +1 -1
  73. package/dist/project-settings.d.ts +16 -3
  74. package/dist/project-settings.js +79 -14
  75. package/dist/project-settings.js.map +1 -1
  76. package/dist/resources/calculation-resource.d.ts +4 -3
  77. package/dist/resources/calculation-resource.js +11 -5
  78. package/dist/resources/calculation-resource.js.map +1 -1
  79. package/dist/resources/card-type-resource.d.ts +6 -1
  80. package/dist/resources/card-type-resource.js +34 -23
  81. package/dist/resources/card-type-resource.js.map +1 -1
  82. package/dist/resources/create-defaults.d.ts +3 -2
  83. package/dist/resources/create-defaults.js +3 -2
  84. package/dist/resources/create-defaults.js.map +1 -1
  85. package/dist/resources/field-type-resource.d.ts +4 -1
  86. package/dist/resources/field-type-resource.js +22 -23
  87. package/dist/resources/field-type-resource.js.map +1 -1
  88. package/dist/resources/file-resource.d.ts +5 -9
  89. package/dist/resources/file-resource.js +6 -11
  90. package/dist/resources/file-resource.js.map +1 -1
  91. package/dist/resources/folder-resource.d.ts +29 -32
  92. package/dist/resources/folder-resource.js +59 -78
  93. package/dist/resources/folder-resource.js.map +1 -1
  94. package/dist/resources/graph-model-resource.d.ts +4 -1
  95. package/dist/resources/graph-model-resource.js +11 -4
  96. package/dist/resources/graph-model-resource.js.map +1 -1
  97. package/dist/resources/graph-view-resource.d.ts +5 -2
  98. package/dist/resources/graph-view-resource.js +7 -3
  99. package/dist/resources/graph-view-resource.js.map +1 -1
  100. package/dist/resources/link-type-resource.d.ts +5 -2
  101. package/dist/resources/link-type-resource.js +5 -2
  102. package/dist/resources/link-type-resource.js.map +1 -1
  103. package/dist/resources/report-resource.d.ts +6 -7
  104. package/dist/resources/report-resource.js +14 -23
  105. package/dist/resources/report-resource.js.map +1 -1
  106. package/dist/resources/resource-object.d.ts +93 -8
  107. package/dist/resources/resource-object.js +162 -110
  108. package/dist/resources/resource-object.js.map +1 -1
  109. package/dist/resources/template-resource.d.ts +7 -3
  110. package/dist/resources/template-resource.js +10 -3
  111. package/dist/resources/template-resource.js.map +1 -1
  112. package/dist/resources/workflow-resource.d.ts +5 -2
  113. package/dist/resources/workflow-resource.js +18 -22
  114. package/dist/resources/workflow-resource.js.map +1 -1
  115. package/dist/utils/card-utils.d.ts +2 -2
  116. package/dist/utils/card-utils.js +1 -1
  117. package/dist/utils/clingo-fact-builder.d.ts +25 -14
  118. package/dist/utils/clingo-fact-builder.js +27 -5
  119. package/dist/utils/clingo-fact-builder.js.map +1 -1
  120. package/dist/utils/clingo-facts.js +3 -4
  121. package/dist/utils/clingo-facts.js.map +1 -1
  122. package/dist/utils/resource-utils.d.ts +1 -0
  123. package/dist/utils/resource-utils.js +2 -1
  124. package/dist/utils/resource-utils.js.map +1 -1
  125. package/package.json +8 -8
  126. package/src/card-metadata-updater.ts +6 -2
  127. package/src/command-handler.ts +24 -5
  128. package/src/command-manager.ts +29 -17
  129. package/src/commands/create.ts +43 -78
  130. package/src/commands/export.ts +63 -52
  131. package/src/commands/import.ts +24 -14
  132. package/src/commands/move.ts +0 -1
  133. package/src/commands/remove.ts +11 -7
  134. package/src/commands/rename.ts +43 -149
  135. package/src/commands/show.ts +113 -78
  136. package/src/commands/transition.ts +26 -28
  137. package/src/commands/update.ts +25 -22
  138. package/src/commands/validate.ts +108 -67
  139. package/src/containers/project/calculation-engine.ts +61 -93
  140. package/src/containers/project/project-paths.ts +21 -13
  141. package/src/containers/project/resource-cache.ts +648 -0
  142. package/src/containers/project/resource-handler.ts +265 -0
  143. package/src/containers/project.ts +178 -522
  144. package/src/containers/template.ts +24 -19
  145. package/src/interfaces/folder-content-interfaces.ts +7 -6
  146. package/src/interfaces/project-interfaces.ts +7 -6
  147. package/src/interfaces/resource-interfaces.ts +18 -3
  148. package/src/macros/graph/index.ts +26 -47
  149. package/src/macros/index.ts +2 -2
  150. package/src/macros/report/index.ts +3 -9
  151. package/src/module-manager.ts +74 -17
  152. package/src/project-settings.ts +83 -14
  153. package/src/resources/calculation-resource.ts +18 -18
  154. package/src/resources/card-type-resource.ts +50 -50
  155. package/src/resources/create-defaults.ts +3 -2
  156. package/src/resources/field-type-resource.ts +41 -55
  157. package/src/resources/file-resource.ts +10 -36
  158. package/src/resources/folder-resource.ts +69 -120
  159. package/src/resources/graph-model-resource.ts +20 -22
  160. package/src/resources/graph-view-resource.ts +15 -17
  161. package/src/resources/link-type-resource.ts +10 -13
  162. package/src/resources/report-resource.ts +21 -43
  163. package/src/resources/resource-object.ts +194 -152
  164. package/src/resources/template-resource.ts +17 -16
  165. package/src/resources/workflow-resource.ts +25 -44
  166. package/src/utils/card-utils.ts +2 -2
  167. package/src/utils/clingo-fact-builder.ts +28 -16
  168. package/src/utils/clingo-facts.ts +3 -4
  169. package/src/utils/resource-utils.ts +2 -1
  170. package/dist/containers/project/resource-collector.d.ts +0 -110
  171. package/dist/containers/project/resource-collector.js +0 -344
  172. package/dist/containers/project/resource-collector.js.map +0 -1
  173. package/src/containers/project/resource-collector.ts +0 -404
@@ -18,12 +18,12 @@ import {
18
18
  constants as fsConstants,
19
19
  copyFile,
20
20
  mkdir,
21
- readdir,
22
21
  unlink,
23
22
  writeFile,
24
23
  } from 'node:fs/promises';
25
24
 
26
- import { CardContainer } from './card-container.js'; // base class
25
+ // base class
26
+ import { CardContainer } from './card-container.js';
27
27
 
28
28
  import { CalculationEngine } from './project/calculation-engine.js';
29
29
  import {
@@ -38,9 +38,6 @@ import {
38
38
  type ModuleSetting,
39
39
  type ProjectFetchCardDetails,
40
40
  type ProjectMetadata,
41
- type ProjectSettings,
42
- type Resource,
43
- type ResourceFolderType,
44
41
  } from '../interfaces/project-interfaces.js';
45
42
  import { pathExists } from '../utils/file-utils.js';
46
43
  import { generateRandomString } from '../utils/random.js';
@@ -52,58 +49,51 @@ import {
52
49
  import { ProjectConfiguration } from '../project-settings.js';
53
50
  import { ProjectPaths } from './project/project-paths.js';
54
51
  import { readJsonFile } from '../utils/json.js';
55
- import {
56
- pathToResourceName,
57
- resourceName,
58
- type ResourceName,
59
- resourceNameToString,
60
- } from '../utils/resource-utils.js';
61
- import {
62
- ResourcesFrom,
63
- ResourceCollector,
64
- } from './project/resource-collector.js';
65
- import type { Template } from './template.js';
52
+ import { ResourcesFrom } from './project/resource-cache.js';
53
+ import { ResourceHandler } from './project/resource-handler.js';
66
54
  import { Validate } from '../commands/validate.js';
67
-
68
- import { CalculationResource } from '../resources/calculation-resource.js';
69
- import { CardTypeResource } from '../resources/card-type-resource.js';
70
- import { FieldTypeResource } from '../resources/field-type-resource.js';
71
- import { GraphModelResource } from '../resources/graph-model-resource.js';
72
- import { GraphViewResource } from '../resources/graph-view-resource.js';
73
- import { LinkTypeResource } from '../resources/link-type-resource.js';
74
- import { ReportResource } from '../resources/report-resource.js';
75
- import { TemplateResource } from '../resources/template-resource.js';
76
- import { WorkflowResource } from '../resources/workflow-resource.js';
77
-
78
55
  import { ContentWatcher } from './project/project-content-watcher.js';
79
56
  import { getChildLogger } from '../utils/log-utils.js';
80
57
 
58
+ import type { Template } from './template.js';
59
+
81
60
  import { ROOT } from '../utils/constants.js';
82
61
 
83
62
  // Re-export this, so that classes that use Project do not need to have separate import.
84
63
  export { ResourcesFrom };
85
64
 
65
+ /**
66
+ * Options for Project initialization.
67
+ * autoSave - If project configuration changes are saved automatically. Default true.
68
+ * watchResourceChanges - If project refresh automatically to filesystem changes. Default false.
69
+ */
70
+ export interface ProjectOptions {
71
+ autoSave?: boolean;
72
+ watchResourceChanges?: boolean;
73
+ }
74
+
86
75
  /**
87
76
  * Represents project folder.
88
77
  */
89
78
  export class Project extends CardContainer {
90
79
  public calculationEngine: CalculationEngine;
91
- // Created resources are held in a cache.
92
- // In the cache, key is resource name, and data is resource metadata (as JSON).
93
- private createdResources = new Map<string, JSON>();
94
80
  private logger = getChildLogger({ module: 'Project' });
95
81
  private projectPaths: ProjectPaths;
96
- private resources: ResourceCollector;
82
+ private resourceHandler: ResourceHandler;
97
83
  private resourceWatcher: ContentWatcher | undefined;
98
84
  private settings: ProjectConfiguration;
99
85
  private validator: Validate;
100
86
 
101
87
  constructor(
102
88
  path: string,
103
- private watchResourceChanges?: boolean,
89
+ private options: ProjectOptions = {
90
+ autoSave: true,
91
+ watchResourceChanges: false,
92
+ },
104
93
  ) {
105
94
  const settings = new ProjectConfiguration(
106
95
  join(path, '.cards', 'local', Project.projectConfigFileName),
96
+ options.autoSave ?? true,
107
97
  );
108
98
  super(path, settings.cardKeyPrefix, '');
109
99
  this.settings = settings;
@@ -112,16 +102,12 @@ export class Project extends CardContainer {
112
102
 
113
103
  this.calculationEngine = new CalculationEngine(this);
114
104
  this.projectPaths = new ProjectPaths(path);
115
- this.resources = new ResourceCollector(this);
105
+ this.resourceHandler = new ResourceHandler(this);
116
106
 
117
107
  this.containerName = this.settings.name;
118
108
  // todo: implement project validation
119
109
  this.validator = Validate.getInstance();
120
- this.logger.info(
121
- { resourcesFolder: this.paths.resourcesFolder },
122
- 'Collecting local resources',
123
- );
124
- this.resources.collectLocalResources();
110
+
125
111
  this.logger.info(
126
112
  { name: this.containerName },
127
113
  'Project initialization complete',
@@ -131,28 +117,16 @@ export class Project extends CardContainer {
131
117
 
132
118
  // Watch changes in .cards if there are multiple instances of Project being
133
119
  // run concurrently.
134
- if (this.watchResourceChanges) {
120
+ if (this.options.watchResourceChanges) {
135
121
  this.resourceWatcher = new ContentWatcher(
136
122
  ignoreRenameFileChanges,
137
123
  this.paths.resourcesFolder,
138
124
  (fileName: string) => {
139
125
  void (async () => {
140
- let resource;
141
- try {
142
- resource = pathToResourceName(
143
- this,
144
- join(this.paths.resourcesFolder, fileName),
145
- );
146
- if (!resource) {
147
- return;
148
- }
149
- } catch {
150
- // it wasn't a resource that changed, so ignore the change
151
- return;
152
- }
153
- const resourceName = `${resource.prefix}/${resource.type}/${resource.identifier}`;
154
- await this.replaceCacheValue(resourceName);
155
- this.resources.collectLocalResources();
126
+ this.resources.handleFileSystemChange(
127
+ join(this.paths.resourcesFolder, fileName),
128
+ );
129
+ this.resources.changed();
156
130
  })();
157
131
  },
158
132
  );
@@ -171,10 +145,26 @@ export class Project extends CardContainer {
171
145
  }
172
146
 
173
147
  // Finds specific module.
174
- private async findModule(moduleName: string): Promise<Resource | undefined> {
175
- return (await this.resources.resources('modules')).find(
176
- (item) => item.name === moduleName && item.path,
148
+ private async findModule(
149
+ moduleName: string,
150
+ ): Promise<{ name: string; path: string } | undefined> {
151
+ const moduleExists = this.resources.moduleNames().includes(moduleName);
152
+ if (!moduleExists) {
153
+ return undefined;
154
+ }
155
+
156
+ // For modules, we need to construct the local path where the module is stored
157
+ const moduleConfig = this.configuration.modules?.find(
158
+ (module) => module.name === moduleName,
177
159
  );
160
+ if (!moduleConfig) {
161
+ return undefined;
162
+ }
163
+
164
+ return {
165
+ name: moduleName,
166
+ path: join(this.paths.modulesFolder, moduleConfig.name),
167
+ };
178
168
  }
179
169
 
180
170
  // Handles attachment changes after filesystem operations.
@@ -196,6 +186,7 @@ export class Project extends CardContainer {
196
186
  }
197
187
 
198
188
  // Determines the parent card key from a card's filesystem path.
189
+ // todo: could be moved to card-utils
199
190
  private parentFromPath(cardPath: string): string {
200
191
  return cardPathParts(this.projectPrefix, cardPath).parents.at(-1) || 'root';
201
192
  }
@@ -211,28 +202,6 @@ export class Project extends CardContainer {
211
202
  }
212
203
  }
213
204
 
214
- // Removes current version of a resource from the resource cache.
215
- // Then re-creates the resource with current data and caches the value again.
216
- // If the value wasn't in the cache before, it will be added.
217
- private async replaceCacheValue(resourceName: string) {
218
- if (this.createdResources.has(resourceName)) {
219
- // First, remove the old version from cache
220
- this.createdResources.delete(resourceName);
221
- }
222
- const resourceData = await this.resource(resourceName);
223
- if (resourceData) {
224
- this.createdResources.set(resourceName, resourceData as JSON);
225
- }
226
- }
227
-
228
- // Returns (local or all) resources of a given type.
229
- private async resourcesOfType(
230
- type: ResourceFolderType,
231
- from: ResourcesFrom = ResourcesFrom.localOnly,
232
- ): Promise<Resource[]> {
233
- return this.resources.resources(type, from);
234
- }
235
-
236
205
  // Updates children in the card cache
237
206
  private updateCachedChildren(parentKey: string, newChild: Card) {
238
207
  const parentCard = this.cardCache.getCard(parentKey);
@@ -248,35 +217,62 @@ export class Project extends CardContainer {
248
217
  }
249
218
  }
250
219
 
220
+ // Validates that card's data is valid.
221
+ private async validateCard(card: Card): Promise<string> {
222
+ const invalidCustomData = await this.validator.validateCustomFields(
223
+ this,
224
+ card,
225
+ this.projectPrefixes(),
226
+ );
227
+ const invalidWorkFlow = await this.validator.validateWorkflowState(
228
+ this,
229
+ card,
230
+ );
231
+
232
+ const invalidLabels = this.validator.validateCardLabels(card);
233
+ if (
234
+ invalidCustomData.length === 0 &&
235
+ invalidWorkFlow.length === 0 &&
236
+ invalidLabels.length === 0
237
+ ) {
238
+ return '';
239
+ }
240
+ const errors: string[] = [];
241
+ if (invalidCustomData.length > 0) {
242
+ errors.push(invalidCustomData);
243
+ }
244
+ if (invalidWorkFlow.length > 0) {
245
+ errors.push(invalidWorkFlow);
246
+ }
247
+ if (invalidLabels.length > 0) {
248
+ errors.push(invalidLabels);
249
+ }
250
+ return errors.join('\n');
251
+ }
252
+
251
253
  /**
252
254
  * Populate template cards into the card cache.
253
255
  */
254
256
  protected async populateTemplateCards(): Promise<void> {
255
257
  try {
256
- // Gets local & module templates
257
- const templateResources = await this.templates();
258
- const prefixes = await this.projectPrefixes();
258
+ const templateResources = this.resources.templates();
259
+ const prefixes = this.projectPrefixes();
259
260
  const loadPromises = templateResources.map(async (template) => {
260
261
  try {
261
262
  this.validator.validResourceName(
262
263
  'templates',
263
- template.name,
264
+ template.data?.name || '',
264
265
  prefixes,
265
266
  );
266
267
  } catch (error) {
267
268
  this.logger.warn(
268
- { templateName: template.name, error },
269
- `Template name '${template.name}' does not follow required format, skipping`,
269
+ { templateName: template, error },
270
+ `Template name '${template}' does not follow required format, skipping`,
270
271
  );
271
272
  return;
272
273
  }
273
274
 
274
- const templateResource = new TemplateResource(
275
- this,
276
- resourceName(template.name),
277
- );
278
-
279
- const templateObject = templateResource.templateObject();
275
+ const templateObject = template.templateObject();
280
276
  const isCreated = templateObject && templateObject.isCreated();
281
277
  if (!templateObject || !isCreated) {
282
278
  return;
@@ -308,16 +304,6 @@ export class Project extends CardContainer {
308
304
  await this.populateTemplateCards();
309
305
  }
310
306
 
311
- /**
312
- * Add a given 'resource' to the local resource arrays.
313
- * @param resource Resource to add.
314
- * @param data JSON data for the resource.
315
- */
316
- public addResource(resource: Resource, data: JSON) {
317
- this.resources.add(resource);
318
- this.createdResources.set(resource.name, data);
319
- }
320
-
321
307
  /**
322
308
  * Returns all template cards from the project. This includes all module templates' cards.
323
309
  * @returns all the template cards from the project
@@ -344,37 +330,6 @@ export class Project extends CardContainer {
344
330
  return super.attachments(path);
345
331
  }
346
332
 
347
- /**
348
- * Returns all the attachments in the template cards.
349
- * @returns all the attachments in the template cards.
350
- */
351
- public async attachmentsFromTemplates() {
352
- const templateAttachments: CardAttachment[] = [];
353
- const templates = await this.templates();
354
- for (const template of templates) {
355
- const templateResource = new TemplateResource(
356
- this,
357
- resourceName(template.name),
358
- );
359
- const templateObject = templateResource.templateObject();
360
- if (templateObject) {
361
- templateAttachments.push(...templateObject.attachments());
362
- }
363
- }
364
- return templateAttachments;
365
- }
366
-
367
- /**
368
- * Returns an array of all the calculation files (*.lp) in the project.
369
- * @param from Defines where resources are collected from.
370
- * @returns array of all calculation files in the project.
371
- */
372
- public async calculations(
373
- from: ResourcesFrom = ResourcesFrom.all,
374
- ): Promise<Resource[]> {
375
- return this.resources.resources('calculations', from);
376
- }
377
-
378
333
  /**
379
334
  * Returns path to a card's attachment folder.
380
335
  * @param cardKey card key
@@ -431,34 +386,6 @@ export class Project extends CardContainer {
431
386
  );
432
387
  }
433
388
 
434
- /**
435
- * Removes an attachment from a card.
436
- * @param cardKey The card to remove attachment from
437
- * @param fileName The name of the attachment file to remove
438
- * @throws if trying to remove module card attachment, or the attachment was not found.
439
- */
440
- public async removeCardAttachment(
441
- cardKey: string,
442
- fileName: string,
443
- ): Promise<void> {
444
- const attachmentFolder = this.cardAttachmentFolder(cardKey);
445
-
446
- // Modules cannot be modified.
447
- if (isModulePath(attachmentFolder)) {
448
- throw new Error(`Cannot modify imported module`);
449
- }
450
-
451
- const attachmentPath = join(attachmentFolder, fileName);
452
-
453
- try {
454
- await unlink(attachmentPath);
455
- } catch (error) {
456
- this.logger.error({ error }, 'Removing card attachment');
457
- throw new Error(`Attachment not found: ${fileName}`);
458
- }
459
- await this.handleAttachmentChange(cardKey, 'removed', fileName);
460
- }
461
-
462
389
  /**
463
390
  * Returns path to a card's folder.
464
391
  * @param cardKey card key
@@ -470,12 +397,9 @@ export class Project extends CardContainer {
470
397
  return found.path;
471
398
  }
472
399
 
473
- const templates = await this.templates();
400
+ const templates = this.resources.templates();
474
401
  const templatePromises = templates.map((template) => {
475
- const templateObject = new TemplateResource(
476
- this,
477
- resourceName(template.name),
478
- ).templateObject();
402
+ const templateObject = template.templateObject();
479
403
  const templateCard = templateObject
480
404
  ? templateObject.findCard(cardKey)
481
405
  : undefined;
@@ -503,15 +427,6 @@ export class Project extends CardContainer {
503
427
  return cards;
504
428
  }
505
429
 
506
- /**
507
- * Accessor for cards cache.
508
- * Used by template container (it needs to access project's cache, not their own instance).
509
- * @note Should not be used directly (other than Template).
510
- */
511
- public get cardsCache() {
512
- return this.cardCache;
513
- }
514
-
515
430
  /**
516
431
  * Returns an array of all the cards in the project.
517
432
  * @note These are project cards only, by default (unless path dictates otherwise).
@@ -527,14 +442,12 @@ export class Project extends CardContainer {
527
442
  }
528
443
 
529
444
  /**
530
- * Returns an array of all the card types in the project.
531
- * @param from Defines where resources are collected from.
532
- * @returns array of all card types in the project.
445
+ * Accessor for cards cache.
446
+ * Used by template container (it needs to access project's cache, not their own instance).
447
+ * @note Should not be used directly (other than Template).
533
448
  */
534
- public async cardTypes(
535
- from: ResourcesFrom = ResourcesFrom.all,
536
- ): Promise<Resource[]> {
537
- return this.resources.resources('cardTypes', from);
449
+ public get cardsCache() {
450
+ return this.cardCache;
538
451
  }
539
452
 
540
453
  /**
@@ -553,20 +466,6 @@ export class Project extends CardContainer {
553
466
  return cards;
554
467
  }
555
468
 
556
- /**
557
- * Updates all local resources.
558
- */
559
- public collectLocalResources() {
560
- this.resources.changed();
561
- }
562
-
563
- /**
564
- * Updates all imported module resources.
565
- */
566
- public collectModuleResources() {
567
- this.resources.moduleImported();
568
- }
569
-
570
469
  /**
571
470
  * Returns project configuration.
572
471
  * @returns project configuration.
@@ -585,7 +484,8 @@ export class Project extends CardContainer {
585
484
  return undefined;
586
485
  }
587
486
  const { template } = cardPathParts(this.projectPrefix, card.path);
588
- return new TemplateResource(this, resourceName(template)).templateObject();
487
+ const templateResource = this.resources.byType(template, 'templates');
488
+ return templateResource.templateObject();
589
489
  }
590
490
 
591
491
  /**
@@ -599,14 +499,13 @@ export class Project extends CardContainer {
599
499
  }
600
500
 
601
501
  /**
602
- * Returns an array of all the field types in the project.
603
- * @param from Defines where resources are collected from.
604
- * @returns array of all field types in the project.
502
+ * Returns specific card.
503
+ * @param cardToFind Card key to find
504
+ * @param details Defines which card details are included in the return values.
505
+ * @returns specific card details, or undefined if card is not part of the project.
605
506
  */
606
- public async fieldTypes(
607
- from: ResourcesFrom = ResourcesFrom.all,
608
- ): Promise<Resource[]> {
609
- return this.resources.resources('fieldTypes', from);
507
+ public findCard(cardToFind: string, details?: ProjectFetchCardDetails): Card {
508
+ return super.findCard(cardToFind, details);
610
509
  }
611
510
 
612
511
  /**
@@ -628,38 +527,6 @@ export class Project extends CardContainer {
628
527
  return Project.findProjectRoot(parentPath);
629
528
  }
630
529
 
631
- /**
632
- * Returns specific card.
633
- * @param cardToFind Card key to find
634
- * @param details Defines which card details are included in the return values.
635
- * @returns specific card details, or undefined if card is not part of the project.
636
- */
637
- public findCard(cardToFind: string, details?: ProjectFetchCardDetails): Card {
638
- return super.findCard(cardToFind, details);
639
- }
640
-
641
- /**
642
- * Returns an array of all the graph models in the project.
643
- * @param from Defines where resources are collected from.
644
- * @returns array of all the graph models in the project.
645
- */
646
- public async graphModels(
647
- from: ResourcesFrom = ResourcesFrom.all,
648
- ): Promise<Resource[]> {
649
- return this.resources.resources('graphModels', from);
650
- }
651
-
652
- /**
653
- * Returns an array of all the graph views in the project.
654
- * @param from Defines where resources are collected from.
655
- * @returns array of all the graph views in the project.
656
- */
657
- public async graphViews(
658
- from: ResourcesFrom = ResourcesFrom.all,
659
- ): Promise<Resource[]> {
660
- return this.resources.resources('graphViews', from);
661
- }
662
-
663
530
  /**
664
531
  * When card changes.
665
532
  * @param changedCard Card that was changed.
@@ -746,7 +613,7 @@ export class Project extends CardContainer {
746
613
  public async importModule(module: ModuleSetting) {
747
614
  // Add module as a dependency.
748
615
  await this.configuration.addModule(module);
749
- this.collectModuleResources();
616
+ this.resources.changedModules();
750
617
  await this.populateTemplateCards();
751
618
  this.logger.info(`Imported module '${module.name}'`);
752
619
  }
@@ -760,17 +627,6 @@ export class Project extends CardContainer {
760
627
  return pathExists(join(path, 'cardRoot'));
761
628
  }
762
629
 
763
- /**
764
- * Returns an array of all the link types in the project.
765
- * @param from Defines where resources are collected from.
766
- * @returns array of all link types in the project.
767
- */
768
- public async linkTypes(
769
- from: ResourcesFrom = ResourcesFrom.all,
770
- ): Promise<Resource[]> {
771
- return this.resources.resources('linkTypes', from);
772
- }
773
-
774
630
  /**
775
631
  * Returns an array of cards in the project, in the templates or both.
776
632
  * Cards don't have content and nor metadata.
@@ -799,18 +655,15 @@ export class Project extends CardContainer {
799
655
  cardsFrom === CardLocation.all ||
800
656
  cardsFrom === CardLocation.templatesOnly
801
657
  ) {
802
- const templates = await this.templates();
658
+ const templates = this.resources.templates();
803
659
  for (const template of templates) {
804
- const templateObject = new TemplateResource(
805
- this,
806
- resourceName(template.name),
807
- ).templateObject();
660
+ const templateObject = template.templateObject();
808
661
  if (templateObject) {
809
662
  // todo: optimization - do all this in parallel
810
663
  const templateCards = templateObject.listCards();
811
664
  if (templateCards.length) {
812
665
  cardListContainer.push({
813
- name: template.name,
666
+ name: template.data?.name || '',
814
667
  type: 'template',
815
668
  cards: templateCards.map((item) => item.key),
816
669
  });
@@ -847,7 +700,7 @@ export class Project extends CardContainer {
847
700
  public async module(moduleName: string): Promise<ModuleContent | undefined> {
848
701
  const module = await this.findModule(moduleName);
849
702
  if (module && module.path) {
850
- const modulePath = join(module.path, module.name);
703
+ const modulePath = module.path;
851
704
  const moduleConfig = await readJsonFile(
852
705
  join(modulePath, Project.projectConfigFileName),
853
706
  );
@@ -857,73 +710,32 @@ export class Project extends CardContainer {
857
710
  hubs: moduleConfig.hubs,
858
711
  path: modulePath,
859
712
  cardKeyPrefix: moduleConfig.cardKeyPrefix,
860
- calculations: [
861
- ...(await this.resources.collectResourcesFromModules(
862
- 'calculations',
863
- moduleName,
864
- )),
865
- ],
866
- cardTypes: [
867
- ...(await this.resources.collectResourcesFromModules(
868
- 'cardTypes',
869
- moduleName,
870
- )),
871
- ],
872
- fieldTypes: [
873
- ...(await this.resources.collectResourcesFromModules(
874
- 'fieldTypes',
875
- moduleName,
876
- )),
877
- ],
878
- graphModels: [
879
- ...(await this.resources.collectResourcesFromModules(
880
- 'graphModels',
881
- moduleName,
882
- )),
883
- ],
884
- graphViews: [
885
- ...(await this.resources.collectResourcesFromModules(
886
- 'graphViews',
887
- moduleName,
888
- )),
889
- ],
890
- linkTypes: [
891
- ...(await this.resources.collectResourcesFromModules(
892
- 'linkTypes',
893
- moduleName,
894
- )),
895
- ],
896
- reports: [
897
- ...(await this.resources.collectResourcesFromModules(
898
- 'reports',
899
- moduleName,
900
- )),
901
- ],
902
- templates: [
903
- ...(await this.resources.collectResourcesFromModules(
904
- 'templates',
905
- moduleName,
906
- )),
907
- ],
908
- workflows: [
909
- ...(await this.resources.collectResourcesFromModules(
910
- 'workflows',
911
- moduleName,
912
- )),
913
- ],
713
+ calculations: this.resources.moduleResourceNames(
714
+ 'calculations',
715
+ moduleName,
716
+ ),
717
+ cardTypes: this.resources.moduleResourceNames('cardTypes', moduleName),
718
+ fieldTypes: this.resources.moduleResourceNames(
719
+ 'fieldTypes',
720
+ moduleName,
721
+ ),
722
+ graphModels: this.resources.moduleResourceNames(
723
+ 'graphModels',
724
+ moduleName,
725
+ ),
726
+ graphViews: this.resources.moduleResourceNames(
727
+ 'graphViews',
728
+ moduleName,
729
+ ),
730
+ linkTypes: this.resources.moduleResourceNames('linkTypes', moduleName),
731
+ reports: this.resources.moduleResourceNames('reports', moduleName),
732
+ templates: this.resources.moduleResourceNames('templates', moduleName),
733
+ workflows: this.resources.moduleResourceNames('workflows', moduleName),
914
734
  };
915
735
  }
916
736
  return undefined;
917
737
  }
918
738
 
919
- /**
920
- * Returns list of modules in the project.
921
- * @returns list of modules in the project.
922
- */
923
- public async modules(): Promise<Resource[]> {
924
- return this.resources.resources('modules');
925
- }
926
-
927
739
  /**
928
740
  * Returns a new unique card key with project prefix (e.g. test_x649it4x).
929
741
  * Random part of string will be always 8 characters in base-36 (0-9a-z)
@@ -996,6 +808,10 @@ export class Project extends CardContainer {
996
808
  */
997
809
  public async populateCaches() {
998
810
  if (!this.cardCache.isPopulated) {
811
+ // Only collect modules that are registered in the project configuration
812
+ if (this.configuration.modules && this.configuration.modules.length > 0) {
813
+ this.resources.changedModules();
814
+ }
999
815
  await this.populateCardsCache();
1000
816
  }
1001
817
  }
@@ -1015,185 +831,76 @@ export class Project extends CardContainer {
1015
831
  }
1016
832
 
1017
833
  /**
1018
- * Collects all prefixes used in the project (project's own plus all from modules).
834
+ * Returns all prefixes used in the project (project's own plus all from imported modules).
1019
835
  * @returns all prefixes used in the project.
1020
- * @todo - move the module prefix fetch to resource-collector.
1021
- * Make it use cached value that is only changed when module is removed/imported.
1022
836
  */
1023
- public async projectPrefixes(): Promise<string[]> {
837
+ public projectPrefixes(): string[] {
1024
838
  const prefixes: string[] = [this.projectPrefix];
1025
- let files;
1026
- try {
1027
- // TODO: Could be optimized so that prefixes are stored once fetched.
1028
- files = await readdir(this.paths.modulesFolder, {
1029
- withFileTypes: true,
1030
- recursive: true,
1031
- });
1032
- const configurationFiles = files
1033
- .filter((dirent) => dirent.isFile())
1034
- .filter((dirent) => dirent.name === Project.projectConfigFileName);
1035
-
1036
- const configurationPromises = configurationFiles.map(async (file) => {
1037
- const configuration = (await readJsonFile(
1038
- join(file.parentPath, file.name),
1039
- )) as ProjectSettings;
1040
- return configuration.cardKeyPrefix;
1041
- });
1042
-
1043
- const configurationPrefixes = await Promise.all(configurationPromises);
1044
- prefixes.push(...configurationPrefixes);
1045
- } catch (error) {
1046
- this.logger.error({ error }, 'Failed to collect prefixes in use');
1047
- }
839
+ const moduleNames = this.configuration.modules.map((item) => item.name);
840
+ prefixes.push(...moduleNames);
1048
841
 
1049
842
  return prefixes;
1050
843
  }
1051
844
 
1052
845
  /**
1053
- * Removes a module from the project
1054
- * @param module Module (name) to remove.
846
+ * Removes an attachment from a card.
847
+ * @param cardKey The card to remove attachment from
848
+ * @param fileName The name of the attachment file to remove
849
+ * @throws if trying to remove module card attachment, or the attachment was not found.
1055
850
  */
1056
- public async removeModule(moduleName: string) {
1057
- const toBeRemovedTemplates = this.resources.moduleResources.resourceArray(
1058
- 'templates',
1059
- moduleName,
1060
- );
1061
- // First, remove cards from the cache
1062
- for (const template of toBeRemovedTemplates) {
1063
- this.cardCache.deleteCardsFromTemplate(template.name);
1064
- }
851
+ public async removeCardAttachment(
852
+ cardKey: string,
853
+ fileName: string,
854
+ ): Promise<void> {
855
+ const attachmentFolder = this.cardAttachmentFolder(cardKey);
1065
856
 
1066
- // Then, remove module from project configuration
1067
- await this.configuration.removeModule(moduleName);
1068
- this.collectModuleResources();
857
+ // Modules cannot be modified.
858
+ if (isModulePath(attachmentFolder)) {
859
+ throw new Error(`Cannot modify imported module`);
860
+ }
1069
861
 
1070
- this.logger.info(`Removed module '${moduleName}'`);
1071
- }
862
+ const attachmentPath = join(attachmentFolder, fileName);
1072
863
 
1073
- /**
1074
- * Removes a resource from Project.
1075
- * @param resource Resource to remove.
1076
- */
1077
- public removeResource(resource: Resource) {
1078
- // Template cards must be removed from the cache when resource is removed.
1079
- if (resource.path.includes('templates')) {
1080
- const templateName = resourceNameToString(resourceName(resource.name));
1081
- this.cardCache.deleteCardsFromTemplate(templateName);
864
+ try {
865
+ await unlink(attachmentPath);
866
+ } catch (error) {
867
+ this.logger.error({ error }, 'Removing card attachment');
868
+ throw new Error(`Attachment not found: ${fileName}`);
1082
869
  }
1083
- this.resources.remove(resource);
1084
- this.createdResources.delete(resource.name);
870
+ await this.handleAttachmentChange(cardKey, 'removed', fileName);
1085
871
  }
1086
872
 
1087
873
  /**
1088
- * Array of reports in the project.
1089
- * @param from Defines where resources are collected from.
1090
- * @returns array of all reports in the project.
874
+ * Removes a module from the project cache and configuration.
875
+ * @note that ModuleManager removes the actual files.
876
+ * @param moduleName Module name to remove.
1091
877
  */
1092
- public async reports(
1093
- from: ResourcesFrom = ResourcesFrom.all,
1094
- ): Promise<Resource[]> {
1095
- return this.resources.resources('reports', from);
1096
- }
878
+ public async removeModule(moduleName: string) {
879
+ const toBeRemovedTemplates = this.resources.moduleResourceNames(
880
+ 'templates',
881
+ moduleName,
882
+ );
1097
883
 
1098
- /**
1099
- * Returns handlebar files from reports.
1100
- * @param from Defines where report handlebar files are collected from.
1101
- * @returns handlebar files from reports.
1102
- */
1103
- public async reportHandlerBarFiles(from: ResourcesFrom = ResourcesFrom.all) {
1104
- const reports = await this.reports(from);
1105
- const handleBarFiles: string[] = [];
1106
- for (const reportResourceName of reports) {
1107
- const name = resourceName(reportResourceName.name);
1108
- const report = new ReportResource(this, name);
1109
- handleBarFiles.push(...(await report.handleBarFiles()));
884
+ // First, remove template cards from the cache that are part of removed templates.
885
+ for (const templateName of toBeRemovedTemplates) {
886
+ this.cardCache.deleteCardsFromTemplate(templateName);
1110
887
  }
1111
- return handleBarFiles;
1112
- }
1113
888
 
1114
- /**
1115
- * Returns metadata from a given resource
1116
- * @param name Name of a resource
1117
- * @returns Metadata from the resource.
1118
- */
1119
- public resource<Type>(name: string): Type | undefined {
1120
- const resName = resourceName(name);
1121
- if (this.createdResources.has(resourceNameToString(resName))) {
1122
- const value = this.createdResources.get(
1123
- resourceNameToString(resName),
1124
- ) as unknown as Type;
1125
- return value;
1126
- }
1127
- let resource = undefined;
1128
- try {
1129
- resource = Project.resourceObject(this, resName);
1130
- } catch {
1131
- return undefined;
1132
- }
1133
- const data = resource?.data as Type;
1134
- if (!data) {
1135
- return undefined;
1136
- }
1137
- return data;
1138
- }
889
+ // Then, remove all module resources from cache
890
+ this.resources.removeModule(moduleName);
1139
891
 
1140
- /**
1141
- * Returns resource cache.
1142
- */
1143
- public get resourceCache(): Map<string, JSON> {
1144
- return this.createdResources;
1145
- }
892
+ // Finally, remove module from project configuration
893
+ await this.configuration.removeModule(moduleName);
1146
894
 
1147
- /**
1148
- * Checks if a given resource exists in the project already.
1149
- * @param resourceType Type of resource as a string.
1150
- * @param name Valid name of resource.
1151
- * @returns boolean, true if resource exists; false otherwise.
1152
- */
1153
- public async resourceExists(
1154
- resourceType: ResourceFolderType,
1155
- name: string,
1156
- ): Promise<boolean> {
1157
- const resources = await this.resourcesOfType(
1158
- resourceType,
1159
- ResourcesFrom.all,
1160
- );
1161
- const resource = resources.find((item) => item.name === name);
1162
- return resource !== undefined;
895
+ this.logger.info(`Removed module '${moduleName}'`);
1163
896
  }
1164
897
 
1165
898
  /**
1166
- * Instantiates resource object from project with a resource name.
1167
- * @note that this is memory based object only.
1168
- * To manipulate the resource (create files, delete files etc), use the resource object's API.
1169
- * @param project Project from which resources are created from.
1170
- * @param name Resource name
1171
- * @throws if called with unsupported resource type.
1172
- * @returns Created resource.
899
+ * Accessor for resource handler.
900
+ * @returns Resource handler instance.
1173
901
  */
1174
- public static resourceObject(project: Project, name: ResourceName) {
1175
- if (name.type === 'calculations') {
1176
- return new CalculationResource(project, name);
1177
- } else if (name.type === 'cardTypes') {
1178
- return new CardTypeResource(project, name);
1179
- } else if (name.type === 'fieldTypes') {
1180
- return new FieldTypeResource(project, name);
1181
- } else if (name.type === 'graphModels') {
1182
- return new GraphModelResource(project, name);
1183
- } else if (name.type === 'graphViews') {
1184
- return new GraphViewResource(project, name);
1185
- } else if (name.type === 'linkTypes') {
1186
- return new LinkTypeResource(project, name);
1187
- } else if (name.type === 'reports') {
1188
- return new ReportResource(project, name);
1189
- } else if (name.type === 'templates') {
1190
- return new TemplateResource(project, name);
1191
- } else if (name.type === 'workflows') {
1192
- return new WorkflowResource(project, name);
1193
- }
1194
- throw new Error(
1195
- `Unsupported resource type '${resourceNameToString(name)}'`,
1196
- );
902
+ public get resources(): ResourceHandler {
903
+ return this.resourceHandler;
1197
904
  }
1198
905
 
1199
906
  /**
@@ -1206,7 +913,7 @@ export class Project extends CardContainer {
1206
913
  path: this.basePath,
1207
914
  prefix: this.projectPrefix,
1208
915
  hubs: this.configuration.hubs,
1209
- modules: (await this.modules()).map((item) => item.name),
916
+ modules: this.resources.moduleNames(),
1210
917
  numberOfCards: (await this.listCards(CardLocation.projectOnly))[0].cards
1211
918
  .length,
1212
919
  };
@@ -1235,17 +942,6 @@ export class Project extends CardContainer {
1235
942
  });
1236
943
  }
1237
944
 
1238
- /**
1239
- * Array of templates in the project.
1240
- * @param from Defines where resources are collected from.
1241
- * @returns array of all templates in the project.
1242
- */
1243
- public async templates(
1244
- from: ResourcesFrom = ResourcesFrom.all,
1245
- ): Promise<Resource[]> {
1246
- return this.resources.resources('templates', from);
1247
- }
1248
-
1249
945
  /**
1250
946
  * Update a card's content.
1251
947
  * @param cardKey card key to update.
@@ -1348,44 +1044,4 @@ export class Project extends CardContainer {
1348
1044
  await this.handleCardChanged(card);
1349
1045
  }
1350
1046
  }
1351
-
1352
- // Validates that card's data is valid.
1353
- private async validateCard(card: Card): Promise<string> {
1354
- const invalidCustomData = await this.validator.validateCustomFields(
1355
- this,
1356
- card,
1357
- );
1358
- const invalidWorkFlow = this.validator.validateWorkflowState(this, card);
1359
-
1360
- const invalidLabels = this.validator.validateCardLabels(card);
1361
- if (
1362
- invalidCustomData.length === 0 &&
1363
- invalidWorkFlow.length === 0 &&
1364
- invalidLabels.length === 0
1365
- ) {
1366
- return '';
1367
- }
1368
- const errors: string[] = [];
1369
- if (invalidCustomData.length > 0) {
1370
- errors.push(invalidCustomData);
1371
- }
1372
- if (invalidWorkFlow.length > 0) {
1373
- errors.push(invalidWorkFlow);
1374
- }
1375
- if (invalidLabels.length > 0) {
1376
- errors.push(invalidLabels);
1377
- }
1378
- return errors.join('\n');
1379
- }
1380
-
1381
- /**
1382
- * Array of workflows in the project.
1383
- * @param from Defines where resources are collected from.
1384
- * @returns array of all workflows in the project.
1385
- */
1386
- public async workflows(
1387
- from: ResourcesFrom = ResourcesFrom.all,
1388
- ): Promise<Resource[]> {
1389
- return this.resources.resources('workflows', from);
1390
- }
1391
1047
  }