@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
@@ -14,41 +14,38 @@
14
14
 
15
15
  // node
16
16
  import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
17
- import { basename, join, sep } from 'node:path';
18
-
19
- import { hasCode } from '../utils/error-utils.js';
17
+ import { basename, join } from 'node:path';
20
18
 
21
19
  import { ArrayHandler } from './array-handler.js';
20
+ import { deleteFile, pathExists } from '../utils/file-utils.js';
21
+ import { getChildLogger } from '../utils/log-utils.js';
22
+ import {
23
+ readJsonFile,
24
+ readJsonFileSync,
25
+ writeJsonFile,
26
+ } from '../utils/json.js';
27
+ import {
28
+ resourceName,
29
+ resourceNameToPath,
30
+ resourceNameToString,
31
+ type ResourceName,
32
+ } from '../utils/resource-utils.js';
33
+ import { ResourcesFrom } from '../containers/project.js';
34
+
22
35
  import type {
23
36
  Card,
24
- Resource,
25
37
  ResourceFolderType,
26
38
  } from '../interfaces/project-interfaces.js';
27
39
  import type { Logger } from 'pino';
28
40
  import type { Project } from '../containers/project.js';
29
- import { ResourcesFrom } from '../containers/project/resource-collector.js';
30
41
  import type {
31
42
  ResourceBaseMetadata,
32
43
  UpdateKey,
33
44
  } from '../interfaces/resource-interfaces.js';
34
45
  import type { Validate } from '../commands/validate.js';
35
- import {
36
- resourceName,
37
- resourceNameToPath,
38
- resourceNameToString,
39
- type ResourceName,
40
- } from '../utils/resource-utils.js';
41
- import { getChildLogger } from '../utils/log-utils.js';
42
- import { deleteFile, pathExists } from '../utils/file-utils.js';
43
- import {
44
- readJsonFile,
45
- readJsonFileSync,
46
- writeJsonFile,
47
- } from '../utils/json.js';
48
46
 
49
47
  // Possible operations to perform when doing "update"
50
48
  export type UpdateOperations = 'add' | 'change' | 'rank' | 'remove';
51
- //| 'elementTypeChange';
52
49
 
53
50
  // Base class for update operations.
54
51
  type BaseOperation<T> = {
@@ -117,7 +114,7 @@ export abstract class AbstractResource<
117
114
  protected abstract delete(): Promise<void>; // delete from disk
118
115
  protected abstract read(): Promise<void>; // read content from disk (replaces existing content, if any)
119
116
  protected abstract rename(newName: ResourceName): Promise<void>; // change name of the resource and filename; same as update('name', ...)
120
- protected abstract show(): Promise<ShowReturnType<T, U>>; // return the content as JSON
117
+ protected abstract show(): ShowReturnType<T, U>; // return the content as JSON
121
118
  protected abstract update<Type, K extends string>(
122
119
  updateKey: UpdateKey<K>,
123
120
  operation: Operation<Type>,
@@ -136,8 +133,6 @@ export abstract class ResourceObject<
136
133
  T extends ResourceBaseMetadata,
137
134
  U,
138
135
  > extends AbstractResource<T, U> {
139
- // TODO: Remove when INTDEV-1048 is implemented, since caching is done at object level
140
- private cache: Map<string, JSON>;
141
136
  private static validateInstancePromise?: Promise<ValidateInstance>;
142
137
 
143
138
  protected content: T;
@@ -153,6 +148,12 @@ export abstract class ResourceObject<
153
148
  */
154
149
  public fileName: string = '';
155
150
 
151
+ /**
152
+ * Constructs a ResourceObject instance
153
+ * @param project Project where this resource is
154
+ * @param resourceName Name for the resource
155
+ * @param type Type of resource
156
+ */
156
157
  constructor(
157
158
  protected project: Project,
158
159
  protected resourceName: ResourceName,
@@ -161,12 +162,19 @@ export abstract class ResourceObject<
161
162
  super();
162
163
  this.moduleResource =
163
164
  this.resourceName.prefix !== this.project.projectPrefix;
164
- this.cache = this.project.resourceCache;
165
165
  this.type = type;
166
166
  this.logger = this.getLogger(this.getType);
167
167
  this.content = { name: '' } as T; // not found if name is empty
168
168
  }
169
169
 
170
+ // Check if resource already exists. First checks the cache, then filesystem.
171
+ private exists(): boolean {
172
+ const name = resourceNameToString(this.resourceName);
173
+ const existsInCache = this.project.resources.exists(name);
174
+ return existsInCache || pathExists(this.fileName);
175
+ }
176
+
177
+ // Gets Validate command instance.
170
178
  private static async getValidate(): Promise<ValidateInstance> {
171
179
  // a bit hacky solution to avoid circular dependencies
172
180
  if (!this.validateInstancePromise) {
@@ -177,11 +185,14 @@ export abstract class ResourceObject<
177
185
  return this.validateInstancePromise;
178
186
  }
179
187
 
180
- private resourceObjectToResource(): Resource {
181
- return {
182
- name: this.data ? this.data.name : '',
183
- path: this.fileName.substring(0, this.fileName.lastIndexOf(sep)),
184
- };
188
+ // Gets handlebar files.
189
+ private async reportHandlerBarFiles(from: ResourcesFrom = ResourcesFrom.all) {
190
+ const reports = this.project.resources.reports(from);
191
+ const handleBarFiles: string[] = [];
192
+ for (const report of reports) {
193
+ handleBarFiles.push(...(await report.handleBarFiles()));
194
+ }
195
+ return handleBarFiles;
185
196
  }
186
197
 
187
198
  // Type of resource.
@@ -189,19 +200,13 @@ export abstract class ResourceObject<
189
200
  return this.type;
190
201
  }
191
202
 
192
- private toCache() {
193
- this.cache.set(
194
- resourceNameToString(this.resourceName),
195
- this.content as unknown as JSON,
196
- );
197
- }
198
-
199
203
  /**
200
- * Checks if resource exists
204
+ * Checks if resource exists.
205
+ * This should only throw, if someone creates resources directly; ie. not through the cache
201
206
  * @throws if resource does not exist
202
207
  */
203
208
  protected assertResourceExists() {
204
- if (!pathExists(this.fileName)) {
209
+ if (!this.exists()) {
205
210
  const resourceType = `${this.type[0].toUpperCase()}${this.type.slice(1, this.type.length - 1)}`;
206
211
  const name = resourceNameToString(this.resourceName);
207
212
  throw new Error(
@@ -210,39 +215,30 @@ export abstract class ResourceObject<
210
215
  }
211
216
  }
212
217
 
218
+ /**
219
+ * Calculate; empty implementation.
220
+ */
213
221
  protected async calculate() {}
214
222
 
215
- // Calculations that use this resource.
223
+ /**
224
+ * Calculations that use this resource.
225
+ * @throws if accessing calculations files failed
226
+ */
216
227
  protected async calculations(): Promise<string[]> {
217
228
  const references: string[] = [];
218
229
  const resourceName = resourceNameToString(this.resourceName);
219
- for (const calculation of await this.project.calculations(
220
- ResourcesFrom.all,
221
- )) {
222
- const fileNameWithExtension = calculation.name.endsWith('.lp')
223
- ? calculation.name
224
- : calculation.name + '.lp';
225
- const filename = join(calculation.path, basename(fileNameWithExtension));
226
- try {
227
- const content = await readFile(filename, 'utf-8');
228
- if (content.includes(resourceName)) {
229
- references.push(calculation.name);
230
- }
231
- } catch (error) {
232
- // Skip files that don't exist (they may have been renamed or deleted)
233
- if (hasCode(error) && error.code === 'ENOENT') {
234
- this.logger.warn(`Skipping non-existent file: ${filename}`);
235
- continue;
236
- }
237
- throw new Error(
238
- `Failed to process file ${filename}: ${(error as Error).message}`,
239
- );
230
+ for (const calculation of this.project.resources.calculations()) {
231
+ const content = calculation.contentData();
232
+ if (content.calculation && content.calculation.includes(resourceName)) {
233
+ references.push(calculation.data!.name);
240
234
  }
241
235
  }
242
236
  return references;
243
237
  }
244
238
 
245
- // Cards from project.
239
+ /**
240
+ * Cards from project.
241
+ */
246
242
  protected cards(): Card[] {
247
243
  return [
248
244
  ...this.project.cards(undefined),
@@ -264,9 +260,15 @@ export abstract class ResourceObject<
264
260
  ] as unknown as JSON;
265
261
  }
266
262
 
267
- // Creates resource.
263
+ /**
264
+ * Creates resource.
265
+ * @param newContent Content for resource.
266
+ * @throws when resource already exists in the project.
267
+ */
268
268
  protected async create(newContent?: T) {
269
- if (pathExists(this.fileName)) {
269
+ this.validateResourceIdentifier();
270
+
271
+ if (this.exists()) {
270
272
  throw new Error(
271
273
  `Resource '${this.resourceName.identifier}' already exists in the project`,
272
274
  );
@@ -279,13 +281,17 @@ export abstract class ResourceObject<
279
281
  this.resourceFolder = this.project.paths.resourcePath(
280
282
  this.resourceName.type as ResourceFolderType,
281
283
  );
284
+ this.fileName = join(
285
+ this.resourceFolder,
286
+ this.resourceName.identifier + '.json',
287
+ );
282
288
  }
283
289
 
284
290
  const validator = await ResourceObject.getValidate();
285
291
  const validName = validator.validResourceName(
286
292
  this.resourceType(),
287
293
  resourceNameToString(this.resourceName),
288
- await this.project.projectPrefixes(),
294
+ this.project.projectPrefixes(),
289
295
  );
290
296
 
291
297
  let validContent = {} as T;
@@ -300,19 +306,24 @@ export abstract class ResourceObject<
300
306
  this.content = validContent;
301
307
  await this.write();
302
308
 
303
- // Notify project & collector
304
- this.project.addResource(
305
- this.resourceObjectToResource(),
306
- this.content as unknown as JSON,
307
- );
309
+ const resourceString = resourceNameToString(this.resourceName);
310
+ this.project.resources.add(resourceString, this);
308
311
  }
309
312
 
313
+ /**
314
+ * Gets a logger instance.
315
+ * @param loggerName
316
+ * @returns logger instance
317
+ */
310
318
  protected getLogger(loggerName: string): Logger {
311
319
  return getChildLogger({
312
320
  module: loggerName,
313
321
  });
314
322
  }
315
323
 
324
+ /**
325
+ * Returns type of this resource.
326
+ */
316
327
  protected get getType(): string {
317
328
  return this.type;
318
329
  }
@@ -323,6 +334,7 @@ export abstract class ResourceObject<
323
334
  * @param arrayName Name of the array, for error messages.
324
335
  * @param array Array to be updated.
325
336
  * @returns Changed array after the operation.
337
+ * @throws when operation cannot be done.
326
338
  */
327
339
  protected handleArray<Type>(
328
340
  operation: Operation<Type>,
@@ -347,6 +359,7 @@ export abstract class ResourceObject<
347
359
  * Updates scalar value. The only accepted operation is 'change'
348
360
  * @param operation Operation to perform on scalar.
349
361
  * @returns What the scalar should be changed to.
362
+ * @throws when operation cannot be done
350
363
  */
351
364
  protected handleScalar<Type>(operation: Operation<Type>): Type {
352
365
  if (
@@ -359,7 +372,9 @@ export abstract class ResourceObject<
359
372
  return operation.to;
360
373
  }
361
374
 
362
- // Initialize the resource.
375
+ /**
376
+ * Initialize the resource.
377
+ */
363
378
  protected initialize() {
364
379
  if (this.resourceName.type === '') {
365
380
  this.resourceName.type = this.type;
@@ -379,26 +394,27 @@ export abstract class ResourceObject<
379
394
  : this.project.paths.resourcePath(this.type);
380
395
  this.fileName = resourceNameToPath(this.project, this.resourceName);
381
396
  }
382
- // Read from cache, if entry exists...
383
- if (this.cache.has(resourceNameToString(this.resourceName))) {
384
- this.content = this.cache.get(
385
- resourceNameToString(this.resourceName),
386
- ) as unknown as T;
387
- return;
388
- }
389
- //... otherwise read from disk and add to cache
390
- try {
391
- this.content = readJsonFileSync(this.fileName);
392
- this.toCache();
393
- } catch {
394
- // do nothing, it is possible that file has not been created yet.
395
- this.logger.info(
396
- `Initializing resource '${resourceNameToString(this.resourceName)}' failed: failed to read file '${this.fileName}'`,
397
- );
397
+ // Only load content from disk if resource exists in the cache registry
398
+ if (
399
+ this.project.resources.exists(resourceNameToString(this.resourceName))
400
+ ) {
401
+ try {
402
+ this.content = readJsonFileSync(this.fileName);
403
+ } catch {
404
+ this.logger.debug(
405
+ `Initializing resource '${resourceNameToString(this.resourceName)}' failed: failed to read file '${this.fileName}'`,
406
+ );
407
+ }
398
408
  }
399
409
  }
400
410
 
401
- // Called after inherited class has finished 'update' operation.
411
+ /**
412
+ * Called after inherited class has finished 'update' operation.
413
+ * @param content New content for resource
414
+ * @param updateKey Which property to change
415
+ * @param op What kind of operation is performed to updateKey
416
+ * @throws if validation fails after the update
417
+ */
402
418
  protected async postUpdate<Type, K extends string>(
403
419
  content: T,
404
420
  updateKey: UpdateKey<K>,
@@ -435,36 +451,25 @@ export abstract class ResourceObject<
435
451
  await this.write();
436
452
  }
437
453
 
438
- // Update resource; the base class makes some checks only.
439
- protected async update<Type, K extends string>(
440
- key: UpdateKey<K>,
441
- _op: Operation<Type>,
442
- ): Promise<void> {
443
- const content = this.data;
444
- if (!content) {
445
- throw new Error(
446
- `Resource '${resourceNameToString(this.resourceName)}' does not exist`,
447
- );
448
- }
449
- if (this.moduleResource) {
450
- throw new Error(`Cannot update module resources`);
451
- }
452
- if (key.key === '' || key === undefined) {
453
- throw new Error(`Cannot update empty key`);
454
- }
455
- }
456
-
457
- // Reads content from file to memory.
454
+ /**
455
+ * Reads content from file to memory.
456
+ */
458
457
  protected async read() {
459
458
  this.content = await readJsonFile(this.fileName);
460
459
  }
461
460
 
462
- // Renames resource.
461
+ /**
462
+ * Renames resource.
463
+ * @param newName New name for the resource.
464
+ * @throws if trying to rename module resource, or
465
+ * if resource does not exist,
466
+ * if trying to rename so that type changes
467
+ */
463
468
  protected async rename(newName: ResourceName) {
464
469
  if (this.moduleResource) {
465
470
  throw new Error(`Cannot rename module resources`);
466
471
  }
467
- if (!pathExists(this.fileName)) {
472
+ if (!this.exists()) {
468
473
  throw new Error(
469
474
  `Resource '${this.resourceName.identifier}' does not exist`,
470
475
  );
@@ -479,25 +484,70 @@ export abstract class ResourceObject<
479
484
  validator.validResourceName(
480
485
  this.resourceType(),
481
486
  resourceNameToString(newName),
482
- await this.project.projectPrefixes(),
487
+ this.project.projectPrefixes(),
483
488
  );
484
489
  const newFilename = join(
485
490
  this.project.paths.resourcePath(newName.type as ResourceFolderType),
486
491
  newName.identifier + '.json',
487
492
  );
493
+
494
+ const oldName = resourceNameToString(this.resourceName);
488
495
  await rename(this.fileName, newFilename);
489
496
 
490
- this.cache.delete(resourceNameToString(this.resourceName));
491
497
  this.fileName = newFilename;
492
498
  this.content.name = resourceNameToString(newName);
493
499
  this.resourceName = newName;
494
- this.toCache();
500
+
501
+ this.project.resources.rename(oldName, this.content.name);
502
+ }
503
+
504
+ /**
505
+ * Update resource; the base class makes some checks only.
506
+ * @template type Resource type
507
+ * @template K Resource key
508
+ * @throws if resource does not exist, or
509
+ * if trying to update module content, or
510
+ * if key is empty
511
+ */
512
+ protected async update<Type, K extends string>(
513
+ key: UpdateKey<K>,
514
+ _op: Operation<Type>,
515
+ ): Promise<void> {
516
+ const content = this.data;
517
+ if (!content) {
518
+ throw new Error(
519
+ `Resource '${resourceNameToString(this.resourceName)}' does not exist`,
520
+ );
521
+ }
522
+ if (this.moduleResource) {
523
+ throw new Error(`Cannot update module resources`);
524
+ }
525
+ if (key.key === '' || key === undefined) {
526
+ throw new Error(`Cannot update empty key`);
527
+ }
528
+ }
529
+
530
+ /**
531
+ * Validates resource identifier to prevent filesystem operations with invalid names
532
+ * todo: To Validate?
533
+ */
534
+ protected validateResourceIdentifier() {
535
+ if (!this.moduleResource && this.resourceName.identifier) {
536
+ const identifier = this.resourceName.identifier;
537
+ if (!/^[a-zA-Z0-9._-]+$/.test(identifier)) {
538
+ throw new Error(
539
+ `Resource identifier must follow naming rules. Identifier '${identifier}' is invalid`,
540
+ );
541
+ }
542
+ }
495
543
  }
496
544
 
497
545
  /**
498
546
  * Update calculation files.
499
547
  * @param from Resource name to update
500
548
  * @param to New name for resource
549
+ * @throws if 'from' or 'to' is empty string, or
550
+ * if there was error accessing calculation files.
501
551
  */
502
552
  protected async updateCalculations(from: string, to: string) {
503
553
  if (!from.trim() || !to.trim()) {
@@ -506,41 +556,16 @@ export abstract class ResourceObject<
506
556
  );
507
557
  }
508
558
 
509
- const calculations = await this.project.calculations(
559
+ const calculations = this.project.resources.calculations(
510
560
  ResourcesFrom.localOnly,
511
561
  );
512
562
 
513
563
  await Promise.all(
514
564
  calculations.map(async (calculation) => {
515
- if (!calculation.path) {
516
- throw new Error(
517
- `Calculation file's '${calculation.name}' path is not defined`,
518
- );
519
- }
520
-
521
- const base = basename(calculation.name);
522
- const fileNameWithExtension = base.endsWith('.lp')
523
- ? base
524
- : base + '.lp';
525
- const filename = join(calculation.path, fileNameWithExtension);
526
-
527
- try {
528
- const content = await readFile(filename, 'utf-8');
529
- const updatedContent = content.replaceAll(from, to);
530
- await writeFile(filename, updatedContent);
531
- } catch (error) {
532
- if (hasCode(error) && error.code === 'ENOENT') {
533
- // Skip files that don't exist (they may have been renamed or deleted)
534
- this.getLogger(this.getType).warn(
535
- `Skipping non-existent file: ${filename}`,
536
- );
537
- return;
538
- }
539
- if (error instanceof Error) {
540
- throw new Error(
541
- `Failed to process file while updating calculation ${filename}: ${error.message}`,
542
- );
543
- }
565
+ const content = calculation.contentData();
566
+ if (content.calculation) {
567
+ const updatedContent = content.calculation.replaceAll(from, to);
568
+ await calculation.updateFile('calculation.lp', updatedContent);
544
569
  }
545
570
  }),
546
571
  );
@@ -551,6 +576,7 @@ export abstract class ResourceObject<
551
576
  * @param from Resource name to update
552
577
  * @param to New name for resource
553
578
  * @param handleBarFiles Optional. List of handlebar files. If omitted, affects all handlebar files in the project.
579
+ * @throws if 'from' or 'to' is empty string
554
580
  */
555
581
  protected async updateHandleBars(
556
582
  from: string,
@@ -564,7 +590,7 @@ export abstract class ResourceObject<
564
590
  }
565
591
 
566
592
  if (!handleBarFiles) {
567
- handleBarFiles = await this.project.reportHandlerBarFiles(
593
+ handleBarFiles = await this.reportHandlerBarFiles(
568
594
  ResourcesFrom.localOnly,
569
595
  );
570
596
  }
@@ -579,10 +605,14 @@ export abstract class ResourceObject<
579
605
  );
580
606
  }
581
607
 
582
- // Check if there are references to the resource in the card content.
583
- // @note that this needs to be async, since inherited classes need to async operations
608
+ /**
609
+ * Check if there are references to the resource in the card content.
610
+ * @note that this needs to be async, since inherited classes need to async operations
611
+ * @param cards cards to check
612
+ * @throws if resource does not exist
613
+ */
584
614
  protected async usage(cards?: Card[]): Promise<string[]> {
585
- if (!pathExists(this.fileName)) {
615
+ if (!this.exists()) {
586
616
  throw new Error(
587
617
  `Resource '${this.resourceName.identifier}' does not exist in the project`,
588
618
  );
@@ -596,17 +626,25 @@ export abstract class ResourceObject<
596
626
  .map((card) => card.key);
597
627
  }
598
628
 
629
+ /**
630
+ * Checks if resource name is valid.
631
+ * @param newName New name for resource.
632
+ * @returns valid name
633
+ */
599
634
  protected async validName(newName: ResourceName) {
600
635
  const validator = await ResourceObject.getValidate();
601
636
  const validName = validator.validResourceName(
602
637
  this.resourceType(),
603
638
  resourceNameToString(newName),
604
- await this.project.projectPrefixes(),
639
+ this.project.projectPrefixes(),
605
640
  );
606
641
  return validName;
607
642
  }
608
643
 
609
- // Write the content from memory to disk.
644
+ /**
645
+ * Write the content from memory to disk.
646
+ * @throws if trying to write a module resource.
647
+ */
610
648
  protected async write() {
611
649
  if (this.moduleResource) {
612
650
  throw new Error(`Cannot change module resources`);
@@ -624,15 +662,16 @@ export abstract class ResourceObject<
624
662
  // Check if "name" has changed. Changing "name" means renaming the file.
625
663
  const nameInContent = resourceName(this.content.name).identifier + '.json';
626
664
  const currentFileName = basename(this.fileName);
665
+ const resourceString = resourceNameToString(this.resourceName);
627
666
 
628
667
  if (nameInContent !== currentFileName) {
629
668
  const newFileName = join(this.resourceFolder, nameInContent);
630
669
  await rename(this.fileName, newFileName);
631
670
  this.fileName = newFileName;
671
+ this.resourceName = resourceName(this.content.name);
672
+ this.project.resources.rename(resourceString, this.content.name);
632
673
  }
633
-
634
674
  await writeJsonFile(this.fileName, this.content);
635
- this.toCache();
636
675
  }
637
676
 
638
677
  /**
@@ -646,7 +685,9 @@ export abstract class ResourceObject<
646
685
 
647
686
  /**
648
687
  * Deletes the file and removes the resource from project.
649
- * @throws if resource is a module resource or does not exist or is used by other resources.
688
+ * @throws if resource is a module resource, or
689
+ * if resource does not exist, or
690
+ * if resource is used by other resources.
650
691
  */
651
692
  public async delete() {
652
693
  if (this.moduleResource) {
@@ -657,7 +698,7 @@ export abstract class ResourceObject<
657
698
  if (!this.fileName.endsWith('.json')) {
658
699
  this.fileName += '.json';
659
700
  }
660
- if (!pathExists(this.fileName)) {
701
+ if (!this.exists()) {
661
702
  throw new Error(
662
703
  `Resource '${this.resourceName.identifier}' does not exist in the project`,
663
704
  );
@@ -669,13 +710,14 @@ export abstract class ResourceObject<
669
710
  );
670
711
  }
671
712
  await deleteFile(this.fileName);
672
- this.project.removeResource(this.resourceObjectToResource());
713
+ this.project.resources.remove(resourceNameToString(this.resourceName));
673
714
  this.fileName = '';
674
715
  }
675
716
 
676
717
  /**
677
718
  * Validates the content of the resource.
678
719
  * @param content Content to be validated.
720
+ * @throws if content is invalid.
679
721
  */
680
722
  public async validate(content?: object) {
681
723
  const validator = await ResourceObject.getValidate();
@@ -14,25 +14,22 @@
14
14
  import { dirname, join } from 'node:path';
15
15
  import { mkdir } from 'node:fs/promises';
16
16
 
17
- import type {
18
- Card,
19
- Operation,
20
- Project,
21
- ResourceName,
22
- } from './folder-resource.js';
23
- import {
24
- DefaultContent,
25
- FolderResource,
26
- resourceNameToString,
27
- sortCards,
28
- } from './folder-resource.js';
17
+ import { DefaultContent } from './create-defaults.js';
18
+ import { FolderResource } from './folder-resource.js';
19
+ import { resourceNameToString } from '../utils/resource-utils.js';
20
+ import { sortCards } from '../utils/card-utils.js';
21
+ import { Template } from '../containers/template.js';
22
+ import { writeJsonFile } from '../utils/json.js';
23
+
24
+ import type { Card } from '../interfaces/project-interfaces.js';
25
+ import type { Operation } from './resource-object.js';
26
+ import type { Project } from '../containers/project.js';
27
+ import type { ResourceName } from '../utils/resource-utils.js';
29
28
  import type {
30
29
  TemplateConfiguration,
31
30
  TemplateMetadata,
32
31
  UpdateKey,
33
32
  } from '../interfaces/resource-interfaces.js';
34
- import { Template } from '../containers/template.js';
35
- import { writeJsonFile } from '../utils/json.js';
36
33
 
37
34
  /**
38
35
  * Template resource class.
@@ -51,6 +48,7 @@ export class TemplateResource extends FolderResource<TemplateMetadata, never> {
51
48
  this.cardsFolder = join(this.internalFolder, 'c');
52
49
 
53
50
  // Each template resource contains a template card container (with template cards).
51
+ // todo: Fix Template constructor not to use Resource, but just this filename with path
54
52
  this.cardContainer = new Template(this.project, {
55
53
  name: resourceNameToString(this.resourceName),
56
54
  path: dirname(this.fileName),
@@ -88,8 +86,11 @@ export class TemplateResource extends FolderResource<TemplateMetadata, never> {
88
86
 
89
87
  /**
90
88
  * Deletes file and folder that this resource is based on.
89
+ * Also removes template cards from the project's card cache.
91
90
  */
92
91
  public async delete() {
92
+ const templateName = resourceNameToString(this.resourceName);
93
+ this.project.cardsCache.deleteCardsFromTemplate(templateName);
93
94
  return super.delete();
94
95
  }
95
96
 
@@ -107,8 +108,8 @@ export class TemplateResource extends FolderResource<TemplateMetadata, never> {
107
108
  * Shows metadata of the resource.
108
109
  * @returns template metadata.
109
110
  */
110
- public async show(): Promise<TemplateConfiguration> {
111
- const templateMetadata = await super.show();
111
+ public show(): TemplateConfiguration {
112
+ const templateMetadata = super.show();
112
113
  const container = this.templateObject();
113
114
 
114
115
  return {