@cyberismo/data-handler 0.0.17 → 0.0.19

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 (112) hide show
  1. package/dist/command-handler.d.ts +2 -0
  2. package/dist/command-handler.js +26 -2
  3. package/dist/command-handler.js.map +1 -1
  4. package/dist/command-manager.d.ts +2 -0
  5. package/dist/command-manager.js +3 -0
  6. package/dist/command-manager.js.map +1 -1
  7. package/dist/commands/create.d.ts +3 -1
  8. package/dist/commands/create.js +10 -1
  9. package/dist/commands/create.js.map +1 -1
  10. package/dist/commands/migrate.d.ts +33 -0
  11. package/dist/commands/migrate.js +66 -0
  12. package/dist/commands/migrate.js.map +1 -0
  13. package/dist/containers/project/card-cache.js +13 -1
  14. package/dist/containers/project/card-cache.js.map +1 -1
  15. package/dist/containers/project/project-paths.d.ts +1 -0
  16. package/dist/containers/project/project-paths.js +5 -2
  17. package/dist/containers/project/project-paths.js.map +1 -1
  18. package/dist/containers/project.d.ts +10 -0
  19. package/dist/containers/project.js +39 -2
  20. package/dist/containers/project.js.map +1 -1
  21. package/dist/containers/template.js +2 -0
  22. package/dist/containers/template.js.map +1 -1
  23. package/dist/interfaces/command-options.d.ts +6 -1
  24. package/dist/interfaces/project-interfaces.d.ts +4 -0
  25. package/dist/interfaces/project-interfaces.js.map +1 -1
  26. package/dist/migrations/index.d.ts +14 -0
  27. package/dist/migrations/index.js +14 -0
  28. package/dist/migrations/index.js.map +1 -0
  29. package/dist/migrations/migration-executor.d.ts +79 -0
  30. package/dist/migrations/migration-executor.js +312 -0
  31. package/dist/migrations/migration-executor.js.map +1 -0
  32. package/dist/migrations/migration-worker.d.ts +13 -0
  33. package/dist/migrations/migration-worker.js +156 -0
  34. package/dist/migrations/migration-worker.js.map +1 -0
  35. package/dist/migrations/worker-executor.d.ts +24 -0
  36. package/dist/migrations/worker-executor.js +157 -0
  37. package/dist/migrations/worker-executor.js.map +1 -0
  38. package/dist/project-settings.d.ts +2 -0
  39. package/dist/project-settings.js +7 -0
  40. package/dist/project-settings.js.map +1 -1
  41. package/dist/resources/calculation-resource.d.ts +9 -0
  42. package/dist/resources/calculation-resource.js +13 -2
  43. package/dist/resources/calculation-resource.js.map +1 -1
  44. package/dist/resources/card-type-resource.d.ts +12 -3
  45. package/dist/resources/card-type-resource.js +73 -91
  46. package/dist/resources/card-type-resource.js.map +1 -1
  47. package/dist/resources/field-type-resource.d.ts +10 -1
  48. package/dist/resources/field-type-resource.js +62 -61
  49. package/dist/resources/field-type-resource.js.map +1 -1
  50. package/dist/resources/file-resource.d.ts +27 -2
  51. package/dist/resources/file-resource.js +46 -8
  52. package/dist/resources/file-resource.js.map +1 -1
  53. package/dist/resources/graph-model-resource.d.ts +5 -0
  54. package/dist/resources/graph-model-resource.js +6 -0
  55. package/dist/resources/graph-model-resource.js.map +1 -1
  56. package/dist/resources/graph-view-resource.d.ts +5 -0
  57. package/dist/resources/graph-view-resource.js +6 -0
  58. package/dist/resources/graph-view-resource.js.map +1 -1
  59. package/dist/resources/link-type-resource.d.ts +11 -1
  60. package/dist/resources/link-type-resource.js +54 -30
  61. package/dist/resources/link-type-resource.js.map +1 -1
  62. package/dist/resources/report-resource.d.ts +6 -1
  63. package/dist/resources/report-resource.js +7 -1
  64. package/dist/resources/report-resource.js.map +1 -1
  65. package/dist/resources/resource-object.d.ts +22 -7
  66. package/dist/resources/resource-object.js +44 -15
  67. package/dist/resources/resource-object.js.map +1 -1
  68. package/dist/resources/template-resource.d.ts +5 -1
  69. package/dist/resources/template-resource.js +11 -27
  70. package/dist/resources/template-resource.js.map +1 -1
  71. package/dist/resources/workflow-resource.d.ts +7 -3
  72. package/dist/resources/workflow-resource.js +90 -82
  73. package/dist/resources/workflow-resource.js.map +1 -1
  74. package/dist/utils/card-utils.d.ts +1 -1
  75. package/dist/utils/common-utils.d.ts +8 -0
  76. package/dist/utils/common-utils.js +14 -0
  77. package/dist/utils/common-utils.js.map +1 -1
  78. package/dist/utils/file-utils.d.ts +15 -3
  79. package/dist/utils/file-utils.js +48 -9
  80. package/dist/utils/file-utils.js.map +1 -1
  81. package/dist/utils/json.js +2 -2
  82. package/dist/utils/json.js.map +1 -1
  83. package/package.json +5 -3
  84. package/src/command-handler.ts +38 -1
  85. package/src/command-manager.ts +3 -0
  86. package/src/commands/create.ts +11 -0
  87. package/src/commands/migrate.ts +88 -0
  88. package/src/containers/project/card-cache.ts +18 -1
  89. package/src/containers/project/project-paths.ts +6 -2
  90. package/src/containers/project.ts +66 -1
  91. package/src/containers/template.ts +5 -0
  92. package/src/interfaces/command-options.ts +8 -0
  93. package/src/interfaces/project-interfaces.ts +4 -0
  94. package/src/migrations/index.ts +20 -0
  95. package/src/migrations/migration-executor.ts +478 -0
  96. package/src/migrations/migration-worker.ts +190 -0
  97. package/src/migrations/worker-executor.ts +185 -0
  98. package/src/project-settings.ts +7 -0
  99. package/src/resources/calculation-resource.ts +13 -2
  100. package/src/resources/card-type-resource.ts +101 -114
  101. package/src/resources/field-type-resource.ts +78 -71
  102. package/src/resources/file-resource.ts +68 -9
  103. package/src/resources/graph-model-resource.ts +6 -0
  104. package/src/resources/graph-view-resource.ts +6 -0
  105. package/src/resources/link-type-resource.ts +66 -36
  106. package/src/resources/report-resource.ts +7 -1
  107. package/src/resources/resource-object.ts +57 -18
  108. package/src/resources/template-resource.ts +12 -27
  109. package/src/resources/workflow-resource.ts +119 -100
  110. package/src/utils/common-utils.ts +15 -0
  111. package/src/utils/file-utils.ts +56 -12
  112. package/src/utils/json.ts +2 -6
@@ -50,6 +50,11 @@ export class FieldTypeResource extends FileResource<FieldType> {
50
50
  private fromType: DataType = 'integer';
51
51
  private toType: DataType = 'integer';
52
52
 
53
+ /**
54
+ * Creates an instance of FieldTypeResource
55
+ * @param project Project to use
56
+ * @param name Resource name
57
+ */
53
58
  constructor(project: Project, name: ResourceName) {
54
59
  super(project, name, 'fieldTypes');
55
60
 
@@ -206,7 +211,12 @@ export class FieldTypeResource extends FileResource<FieldType> {
206
211
  const removedValue = (op.target as EnumDefinition).enumValue;
207
212
  const cardTypes = this.relevantCardTypes();
208
213
  const allCards = await Promise.all(
209
- cardTypes.map((cardType) => this.collectCards(cardType)),
214
+ cardTypes.map((cardType) =>
215
+ this.collectCards(
216
+ cardType,
217
+ (card, cardTypeName) => card.metadata?.cardType === cardTypeName,
218
+ ),
219
+ ),
210
220
  );
211
221
  const cardsToUpdate = allCards
212
222
  .flat()
@@ -223,15 +233,6 @@ export class FieldTypeResource extends FileResource<FieldType> {
223
233
  );
224
234
  }
225
235
 
226
- // When resource name changes.
227
- private async handleNameChange(existingName: string) {
228
- await Promise.all([
229
- super.updateHandleBars(existingName, this.content.name),
230
- super.updateCalculations(existingName, this.content.name),
231
- ]);
232
- await this.write();
233
- }
234
-
235
236
  // Checks if value 'from' can be converted 'to' value.
236
237
  private isConversionValid(from: DataType, to: DataType) {
237
238
  // Set helpers to avoid dragging 'Operation' object everywhere.
@@ -284,6 +285,20 @@ export class FieldTypeResource extends FileResource<FieldType> {
284
285
  }
285
286
  }
286
287
 
288
+ /**
289
+ * When resource name changes.
290
+ * @param existingName Current resource name.
291
+ */
292
+ protected async onNameChange(existingName: string) {
293
+ await Promise.all([
294
+ super.updateHandleBars(existingName, this.content.name),
295
+ super.updateCalculations(existingName, this.content.name),
296
+ super.updateCardContentReferences(existingName, this.content.name),
297
+ this.updateCardTypes(existingName),
298
+ ]);
299
+ await this.write();
300
+ }
301
+
287
302
  /**
288
303
  * Creates a new field type object. Base class writes the object to disk automatically.
289
304
  * @param dataType Type for the new field type.
@@ -310,16 +325,16 @@ export class FieldTypeResource extends FileResource<FieldType> {
310
325
  */
311
326
  public static fieldDataTypes(): DataType[] {
312
327
  return [
313
- 'shortText',
314
- 'longText',
315
- 'number',
316
- 'integer',
317
328
  'boolean',
318
- 'enum',
319
- 'list',
320
329
  'date',
321
330
  'dateTime',
331
+ 'enum',
332
+ 'integer',
333
+ 'list',
334
+ 'longText',
335
+ 'number',
322
336
  'person',
337
+ 'shortText',
323
338
  ];
324
339
  }
325
340
 
@@ -372,7 +387,7 @@ export class FieldTypeResource extends FileResource<FieldType> {
372
387
  public async rename(newName: ResourceName) {
373
388
  const existingName = this.content.name;
374
389
  await super.rename(newName);
375
- return this.handleNameChange(existingName);
390
+ return this.onNameChange(existingName);
376
391
  }
377
392
 
378
393
  /**
@@ -389,64 +404,56 @@ export class FieldTypeResource extends FileResource<FieldType> {
389
404
  op: Operation<Type>,
390
405
  ) {
391
406
  const { key } = updateKey;
392
- const nameChange = key === 'name';
393
- const typeChange = key === 'dataType';
394
- const enumChange = key === 'enumValues';
395
- const existingName = this.content.name;
396
- const existingType = this.content.dataType;
397
-
398
- await super.update(updateKey, op);
399
407
 
400
- const content = structuredClone(this.content);
401
- if (key === 'name') {
402
- content.name = super.handleScalar(op) as string;
403
- } else if (key === 'dataType') {
404
- const toType = op as ChangeOperation<DataType>;
405
- if (!FieldTypeResource.fieldDataTypes().includes(toType.to)) {
406
- throw new Error(
407
- `Cannot change '${key}' to unknown type '${toType.to}'`,
408
- );
409
- }
410
- if (existingType === toType.to) {
411
- throw new Error(`'${key}' is already '${toType.to}'`);
412
- }
413
- if (!this.isConversionValid(content.dataType, toType.to)) {
414
- throw new Error(
415
- `Cannot change data type from '${content.dataType}' to '${toType.to}'`,
416
- );
417
- }
418
- content.dataType = super.handleScalar(op) as DataType;
419
- } else if (key === 'displayName') {
420
- content.displayName = super.handleScalar(op) as string;
421
- } else if (key === 'enumValues') {
422
- if (op.name === 'add' || op.name === 'change' || op.name === 'remove') {
423
- const existingValue = this.enumValueExists<EnumDefinition>(
424
- op as Operation<EnumDefinition>,
425
- content.enumValues as EnumDefinition[],
426
- ) as Type;
427
- op.target = existingValue ?? op.target;
428
- }
429
- content.enumValues = super.handleArray(
430
- op,
431
- key,
432
- content.enumValues as Type[],
433
- ) as EnumDefinition[];
434
- } else if (key === 'description') {
435
- content.description = super.handleScalar(op) as string;
408
+ if (key === 'name' || key === 'description' || key === 'displayName') {
409
+ await super.update(updateKey, op);
436
410
  } else {
437
- throw new Error(`Unknown property '${key}' for FieldType`);
438
- }
411
+ const content = structuredClone(this.content);
412
+ const typeChange = key === 'dataType';
413
+ const enumChange = key === 'enumValues';
414
+ const existingType = this.content.dataType;
415
+ if (key === 'name') {
416
+ content.name = super.handleScalar(op) as string;
417
+ } else if (key === 'dataType') {
418
+ const toType = op as ChangeOperation<DataType>;
419
+ if (!FieldTypeResource.fieldDataTypes().includes(toType.to)) {
420
+ throw new Error(
421
+ `Cannot change '${key}' to unknown type '${toType.to}'`,
422
+ );
423
+ }
424
+ if (existingType === toType.to) {
425
+ throw new Error(`'${key}' is already '${toType.to}'`);
426
+ }
427
+ if (!this.isConversionValid(content.dataType, toType.to)) {
428
+ throw new Error(
429
+ `Cannot change data type from '${content.dataType}' to '${toType.to}'`,
430
+ );
431
+ }
432
+ content.dataType = super.handleScalar(op) as DataType;
433
+ } else if (key === 'enumValues') {
434
+ if (op.name === 'add' || op.name === 'change' || op.name === 'remove') {
435
+ const existingValue = this.enumValueExists<EnumDefinition>(
436
+ op as Operation<EnumDefinition>,
437
+ content.enumValues as EnumDefinition[],
438
+ ) as Type;
439
+ op.target = existingValue ?? op.target;
440
+ }
441
+ content.enumValues = super.handleArray(
442
+ op,
443
+ key,
444
+ content.enumValues as Type[],
445
+ ) as EnumDefinition[];
446
+ } else {
447
+ throw new Error(`Unknown property '${key}' for FieldType`);
448
+ }
439
449
 
440
- await super.postUpdate(content, updateKey, op);
450
+ await super.postUpdate(content, updateKey, op);
441
451
 
442
- if (nameChange) {
443
- // Renaming this field type causes that references to its name must be updated.
444
- await this.handleNameChange(existingName);
445
- await this.updateCardTypes(existingName);
446
- } else if (typeChange) {
447
- await this.dataTypeChanged();
448
- } else if (enumChange && op.name === 'remove') {
449
- await this.handleEnumValueReplacements(op);
452
+ if (typeChange) {
453
+ await this.dataTypeChanged();
454
+ } else if (enumChange && op.name === 'remove') {
455
+ await this.handleEnumValueReplacements(op);
456
+ }
450
457
  }
451
458
  }
452
459
 
@@ -22,7 +22,8 @@ import type {
22
22
  import type { Project } from '../containers/project.js';
23
23
  import type { ResourceBaseMetadata } from '../interfaces/resource-interfaces.js';
24
24
  import type { ResourceName } from '../utils/resource-utils.js';
25
- import type { ShowReturnType } from './resource-object.js';
25
+ import type { Operation, ShowReturnType } from './resource-object.js';
26
+ import type { UpdateKey } from '../interfaces/resource-interfaces.js';
26
27
 
27
28
  /**
28
29
  * Base class for file based resources (card types, field types, link types, workflows, ...)
@@ -34,23 +35,35 @@ export abstract class FileResource<
34
35
  super(project, name, type);
35
36
  this.initialize();
36
37
  }
37
- // Collects cards that are using the 'cardTypeName'.
38
- protected async collectCards(cardTypeName: string) {
39
- function filteredCards(cardSource: Card[], cardTypeName: string): Card[] {
40
- const cards = cardSource;
41
- return cards.filter((card) => card.metadata?.cardType === cardTypeName);
38
+ /**
39
+ * Collects cards that match the given filter function.
40
+ * @param resourceName The resource name to filter by
41
+ * @param filterFn Function that returns true for cards to include
42
+ * @returns Array of cards that match the filter
43
+ */
44
+ protected async collectCards(
45
+ resourceName: string,
46
+ filterFn: (card: Card, resourceName: string) => boolean,
47
+ ): Promise<Card[]> {
48
+ function filteredCards(
49
+ cardSource: Card[],
50
+ resourceName: string,
51
+ filterFn: (card: Card, resourceName: string) => boolean,
52
+ ): Card[] {
53
+ return cardSource.filter((card) => filterFn(card, resourceName));
42
54
  }
43
55
 
44
56
  // Collect both project cards ...
45
57
  const projectCards = filteredCards(
46
58
  this.project.cards(this.project.paths.cardRootFolder),
47
- cardTypeName,
59
+ resourceName,
60
+ filterFn,
48
61
  );
49
62
  // ... and cards from each template that would be affected.
50
63
  const templates = this.project.resources.templates(ResourcesFrom.localOnly);
51
64
  const templateCards = templates.map((template) => {
52
65
  const templateObject = template.templateObject();
53
- return filteredCards(templateObject.cards(), cardTypeName);
66
+ return filteredCards(templateObject.cards(), resourceName, filterFn);
54
67
  });
55
68
  // Return all affected cards
56
69
  const cards = [projectCards, ...templateCards].reduce(
@@ -59,7 +72,19 @@ export abstract class FileResource<
59
72
  );
60
73
  return cards;
61
74
  }
62
- // Updates resource key to a new prefix
75
+
76
+ /**
77
+ * For handling name changes.
78
+ * @param previousName The previous name before the change
79
+ */
80
+ protected abstract onNameChange?(previousName: string): Promise<void>;
81
+
82
+ /**
83
+ * Updates resource key to a new prefix
84
+ * @param name Resource name
85
+ * @param prefixes list of prefixes in the project
86
+ * @returns updated resource name
87
+ */
63
88
  protected updatePrefixInResourceName(name: string, prefixes: string[]) {
64
89
  const { identifier, prefix, type } = resourceName(name);
65
90
  if (this.moduleResource) {
@@ -70,6 +95,40 @@ export abstract class FileResource<
70
95
  : name;
71
96
  }
72
97
 
98
+ /**
99
+ * Updates resource.
100
+ * @param updateKey Key to modify
101
+ * @param op Operation to perform on 'key'
102
+ * @throws if key is unknown.
103
+ */
104
+ public async update<Type, K extends string>(
105
+ updateKey: UpdateKey<K>,
106
+ op: Operation<Type>,
107
+ ) {
108
+ const { key } = updateKey;
109
+
110
+ const nameChange = key === 'name';
111
+ const existingName = this.content.name;
112
+ await super.update(updateKey, op);
113
+ const content = structuredClone(this.content);
114
+
115
+ if (key === 'name') {
116
+ content.name = super.handleScalar(op) as string;
117
+ } else if (key === 'displayName') {
118
+ content.displayName = super.handleScalar(op) as string;
119
+ } else if (key === 'description') {
120
+ content.description = super.handleScalar(op) as string;
121
+ } else {
122
+ throw new Error(`Unknown property '${key}' for folder resource`);
123
+ }
124
+
125
+ await super.postUpdate(content, updateKey, op);
126
+
127
+ if (nameChange) {
128
+ await this.onNameChange?.(existingName);
129
+ }
130
+ }
131
+
73
132
  /**
74
133
  * Returns the resource metadata content.
75
134
  * @returns metadata content
@@ -38,6 +38,11 @@ export class GraphModelResource extends FolderResource<
38
38
  GraphModelMetadata,
39
39
  GraphModelContent
40
40
  > {
41
+ /**
42
+ * Creates an instance of GraphModelResource
43
+ * @param project Project to use
44
+ * @param name Resource name
45
+ */
41
46
  constructor(project: Project, name: ResourceName) {
42
47
  super(project, name, 'graphModels');
43
48
 
@@ -55,6 +60,7 @@ export class GraphModelResource extends FolderResource<
55
60
  join(this.internalFolder, CONTENT_FILES.model),
56
61
  ]),
57
62
  super.updateCalculations(existingName, this.content.name),
63
+ super.updateCardContentReferences(existingName, this.content.name),
58
64
  ]);
59
65
  // Finally, write updated content.
60
66
  await this.write();
@@ -39,6 +39,11 @@ export class GraphViewResource extends FolderResource<
39
39
  GraphViewMetadata,
40
40
  GraphViewContent
41
41
  > {
42
+ /**
43
+ * Creates instance of GraphViewResource
44
+ * @param project Project to use
45
+ * @param name Resource name
46
+ */
42
47
  constructor(project: Project, name: ResourceName) {
43
48
  super(project, name, 'graphViews');
44
49
 
@@ -56,6 +61,7 @@ export class GraphViewResource extends FolderResource<
56
61
  await this.handleBarFile(),
57
62
  ]),
58
63
  super.updateCalculations(existingName, this.content.name),
64
+ super.updateCardContentReferences(existingName, this.content.name),
59
65
  ]);
60
66
  await this.write();
61
67
  }
@@ -25,6 +25,11 @@ import type { ResourceName } from '../utils/resource-utils.js';
25
25
  * Link Type resource class.
26
26
  */
27
27
  export class LinkTypeResource extends FileResource<LinkType> {
28
+ /**
29
+ * Creates instance of LinkTypeResource
30
+ * @param project Project to use
31
+ * @param name Resource name
32
+ */
28
33
  constructor(project: Project, name: ResourceName) {
29
34
  super(project, name, 'linkTypes');
30
35
 
@@ -32,8 +37,38 @@ export class LinkTypeResource extends FileResource<LinkType> {
32
37
  this.contentSchema = super.contentSchemaContent(this.contentSchemaId);
33
38
  }
34
39
 
35
- // When resource name changes.
36
- private async handleNameChange(existingName: string) {
40
+ // Update card metadata links when link type is renamed
41
+ private async updateCardLinks(from: string, to: string) {
42
+ const cards = await this.collectCards(
43
+ from,
44
+ (card, linkTypeName) =>
45
+ card.metadata?.links?.some((link) => link.linkType === linkTypeName) ??
46
+ false,
47
+ );
48
+ if (cards.length === 0) {
49
+ return;
50
+ }
51
+
52
+ await Promise.all(
53
+ cards.map(async (card) => {
54
+ if (card.metadata?.links) {
55
+ card.metadata.links = card.metadata.links.map((link) => {
56
+ if (link.linkType === from) {
57
+ return { ...link, linkType: to };
58
+ }
59
+ return link;
60
+ });
61
+ await this.project.updateCardMetadata(card, card.metadata);
62
+ }
63
+ }),
64
+ );
65
+ }
66
+
67
+ /**
68
+ * When resource name changes.
69
+ * @param existingName Current resource name.
70
+ */
71
+ protected async onNameChange(existingName: string) {
37
72
  const current = this.content;
38
73
  const prefixes = this.project.projectPrefixes();
39
74
  if (current.sourceCardTypes) {
@@ -49,6 +84,8 @@ export class LinkTypeResource extends FileResource<LinkType> {
49
84
  await Promise.all([
50
85
  super.updateHandleBars(existingName, this.content.name),
51
86
  super.updateCalculations(existingName, this.content.name),
87
+ super.updateCardContentReferences(existingName, this.content.name),
88
+ this.updateCardLinks(existingName, this.content.name),
52
89
  ]);
53
90
  // Finally, write updated content.
54
91
  await this.write();
@@ -76,7 +113,7 @@ export class LinkTypeResource extends FileResource<LinkType> {
76
113
  public async rename(newName: ResourceName) {
77
114
  const existingName = this.content.name;
78
115
  await super.rename(newName);
79
- return this.handleNameChange(existingName);
116
+ return this.onNameChange(existingName);
80
117
  }
81
118
 
82
119
  /**
@@ -89,41 +126,34 @@ export class LinkTypeResource extends FileResource<LinkType> {
89
126
  op: Operation<Type>,
90
127
  ) {
91
128
  const { key } = updateKey;
92
- const nameChange = key === 'name';
93
- const existingName = this.content.name;
94
129
 
95
- await super.update(updateKey, op);
96
-
97
- const content = structuredClone(this.content);
98
- if (key === 'name') {
99
- content.name = super.handleScalar(op) as string;
100
- } else if (key === 'destinationCardTypes') {
101
- content.destinationCardTypes = super.handleArray(
102
- op,
103
- key,
104
- content.destinationCardTypes as Type[],
105
- ) as string[];
106
- } else if (key === 'enableLinkDescription') {
107
- content.enableLinkDescription = super.handleScalar(op) as boolean;
108
- } else if (key === 'inboundDisplayName') {
109
- content.inboundDisplayName = super.handleScalar(op) as string;
110
- } else if (key === 'outboundDisplayName') {
111
- content.outboundDisplayName = super.handleScalar(op) as string;
112
- } else if (key === 'sourceCardTypes') {
113
- content.sourceCardTypes = super.handleArray(
114
- op,
115
- key,
116
- content.sourceCardTypes as Type[],
117
- ) as string[];
130
+ if (key === 'name' || key === 'displayName' || key === 'description') {
131
+ await super.update(updateKey, op);
118
132
  } else {
119
- throw new Error(`Unknown property '${key}' for FieldType`);
120
- }
121
-
122
- await super.postUpdate(content, updateKey, op);
123
-
124
- // Renaming this card type causes that references to its name must be updated.
125
- if (nameChange) {
126
- await this.handleNameChange(existingName);
133
+ const content = structuredClone(this.content);
134
+ if (key === 'destinationCardTypes') {
135
+ content.destinationCardTypes = super.handleArray(
136
+ op,
137
+ key,
138
+ content.destinationCardTypes as Type[],
139
+ ) as string[];
140
+ } else if (key === 'enableLinkDescription') {
141
+ content.enableLinkDescription = super.handleScalar(op) as boolean;
142
+ } else if (key === 'inboundDisplayName') {
143
+ content.inboundDisplayName = super.handleScalar(op) as string;
144
+ } else if (key === 'outboundDisplayName') {
145
+ content.outboundDisplayName = super.handleScalar(op) as string;
146
+ } else if (key === 'sourceCardTypes') {
147
+ content.sourceCardTypes = super.handleArray(
148
+ op,
149
+ key,
150
+ content.sourceCardTypes as Type[],
151
+ ) as string[];
152
+ } else {
153
+ throw new Error(`Unknown property '${key}' for LinkType`);
154
+ }
155
+
156
+ await super.postUpdate(content, updateKey, op);
127
157
  }
128
158
  }
129
159
 
@@ -42,6 +42,11 @@ export class ReportResource extends FolderResource<
42
42
  ReportMetadata,
43
43
  ReportContent
44
44
  > {
45
+ /**
46
+ * Creates instance of ReportResource
47
+ * @param project Project to use
48
+ * @param name Resource name
49
+ */
45
50
  constructor(project: Project, name: ResourceName) {
46
51
  super(project, name, 'reports');
47
52
 
@@ -68,6 +73,7 @@ export class ReportResource extends FolderResource<
68
73
  await this.handleBarFiles(),
69
74
  ),
70
75
  super.updateCalculations(existingName, this.content.name),
76
+ super.updateCardContentReferences(existingName, this.content.name),
71
77
  ]);
72
78
  // Finally, write updated content.
73
79
  await this.write();
@@ -152,9 +158,9 @@ export class ReportResource extends FolderResource<
152
158
 
153
159
  /**
154
160
  * Validates report.
155
- * @throws when there are validation errors.
156
161
  * @param content Content to be validated.
157
162
  * @note If content is not provided, base class validation will use resource's current content.
163
+ * @throws when there are validation errors.
158
164
  */
159
165
  public async validate(content?: object) {
160
166
  const resourceContent = this.contentData();
@@ -416,7 +416,13 @@ export abstract class ResourceObject<
416
416
  }
417
417
  }
418
418
 
419
- // Log details
419
+ /**
420
+ * Log to migration log resource change
421
+ * @param operationType Operation type
422
+ * @param op Details of operation
423
+ * @param key Which property has been changed
424
+ * @throws when operation type is unknown
425
+ */
420
426
  protected async logResourceOperation<Type>(
421
427
  operationType: 'create' | 'delete' | 'update' | 'rename',
422
428
  op?: Operation<Type>,
@@ -596,27 +602,11 @@ export abstract class ResourceObject<
596
602
  }
597
603
  }
598
604
 
599
- /**
600
- * Validates resource identifier to prevent filesystem operations with invalid names
601
- * todo: To Validate?
602
- */
603
- protected validateResourceIdentifier() {
604
- if (!this.moduleResource && this.resourceName.identifier) {
605
- const identifier = this.resourceName.identifier;
606
- if (!/^[a-zA-Z0-9._-]+$/.test(identifier)) {
607
- throw new Error(
608
- `Resource identifier must follow naming rules. Identifier '${identifier}' is invalid`,
609
- );
610
- }
611
- }
612
- }
613
-
614
605
  /**
615
606
  * Update calculation files.
616
607
  * @param from Resource name to update
617
608
  * @param to New name for resource
618
- * @throws if 'from' or 'to' is empty string, or
619
- * if there was error accessing calculation files.
609
+ * @throws if 'from' or 'to' is empty string
620
610
  */
621
611
  protected async updateCalculations(from: string, to: string) {
622
612
  if (!from.trim() || !to.trim()) {
@@ -674,6 +664,39 @@ export abstract class ResourceObject<
674
664
  );
675
665
  }
676
666
 
667
+ /**
668
+ * Update references in card content.
669
+ * Searches through all card content in the cache and replaces references to the old resource name.
670
+ * @param from Resource name to update
671
+ * @param to New name for resource
672
+ * @throws if 'from' or 'to' is empty string
673
+ */
674
+ protected async updateCardContentReferences(from: string, to: string) {
675
+ if (!from.trim() || !to.trim()) {
676
+ throw new Error(
677
+ 'updateCardContentReferences: "from" and "to" parameters must not be empty',
678
+ );
679
+ }
680
+
681
+ const allCards = this.cards();
682
+ const cardsToUpdate = allCards.filter(
683
+ (card) => card.content && card.content.includes(from),
684
+ );
685
+
686
+ if (cardsToUpdate.length === 0) {
687
+ return;
688
+ }
689
+
690
+ await Promise.all(
691
+ cardsToUpdate.map(async (card) => {
692
+ if (card.content) {
693
+ const updatedContent = card.content.replaceAll(from, to);
694
+ await this.project.updateCardContent(card.key, updatedContent);
695
+ }
696
+ }),
697
+ );
698
+ }
699
+
677
700
  /**
678
701
  * Check if there are references to the resource in the card content.
679
702
  * @note that this needs to be async, since inherited classes need to async operations
@@ -695,6 +718,22 @@ export abstract class ResourceObject<
695
718
  .map((card) => card.key);
696
719
  }
697
720
 
721
+ /**
722
+ * Validates resource identifier to prevent filesystem operations with invalid names
723
+ * todo: move to Validate?
724
+ * @throws if identifier is incorrect
725
+ */
726
+ protected validateResourceIdentifier() {
727
+ if (!this.moduleResource && this.resourceName.identifier) {
728
+ const identifier = this.resourceName.identifier;
729
+ if (!/^[a-zA-Z0-9._-]+$/.test(identifier)) {
730
+ throw new Error(
731
+ `Resource identifier must follow naming rules. Identifier '${identifier}' is invalid`,
732
+ );
733
+ }
734
+ }
735
+ }
736
+
698
737
  /**
699
738
  * Checks if resource name is valid.
700
739
  * @param newName New name for resource.