@cyberismo/data-handler 0.0.17 → 0.0.18

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.
@@ -102,38 +102,6 @@ export class CardTypeResource extends FileResource<CardType> {
102
102
  }
103
103
  }
104
104
 
105
- // When resource name changes.
106
- private async handleNameChange(existingName: string) {
107
- const current = this.content;
108
- const prefixes = this.project.projectPrefixes();
109
- if (current.customFields) {
110
- current.customFields.map(
111
- (field) =>
112
- (field.name = this.updatePrefixInResourceName(field.name, prefixes)),
113
- );
114
- }
115
- if (current.alwaysVisibleFields) {
116
- current.alwaysVisibleFields = current.alwaysVisibleFields.map((item) =>
117
- this.updatePrefixInResourceName(item, prefixes),
118
- );
119
- }
120
- if (current.optionallyVisibleFields) {
121
- current.optionallyVisibleFields = current.optionallyVisibleFields.map(
122
- (item) => this.updatePrefixInResourceName(item, prefixes),
123
- );
124
- }
125
- current.workflow = this.updatePrefixInResourceName(
126
- current.workflow,
127
- prefixes,
128
- );
129
- await Promise.all([
130
- super.updateHandleBars(existingName, this.content.name),
131
- super.updateCalculations(existingName, this.content.name),
132
- ]);
133
- // Finally, write updated content.
134
- await this.write();
135
- }
136
-
137
105
  // When a field is removed, remove it from all affected cards.
138
106
  private async handleRemoveField(cards: Card[], item: CustomField) {
139
107
  for (const card of cards) {
@@ -189,15 +157,6 @@ export class CardTypeResource extends FileResource<CardType> {
189
157
  );
190
158
  }
191
159
 
192
- // Remove value from array.
193
- // todo: make it as generic and move to utils
194
- private removeValue(array: string[], value: string) {
195
- const index = array.findIndex((element) => element === value);
196
- if (index !== -1) {
197
- array.splice(index, 1);
198
- }
199
- }
200
-
201
160
  // Return link types that use this card type.
202
161
  private relevantLinkTypes(): string[] {
203
162
  const resourceName = resourceNameToString(this.resourceName);
@@ -220,6 +179,15 @@ export class CardTypeResource extends FileResource<CardType> {
220
179
  return references;
221
180
  }
222
181
 
182
+ // Remove value from array.
183
+ // todo: make it as generic and move to utils
184
+ private removeValue(array: string[], value: string) {
185
+ const index = array.findIndex((element) => element === value);
186
+ if (index !== -1) {
187
+ array.splice(index, 1);
188
+ }
189
+ }
190
+
223
191
  // If value from 'customFields' is removed, remove it also from 'optionallyVisible' and 'alwaysVisible' arrays.
224
192
  private removeValueFromOtherArrays<Type>(
225
193
  op: Operation<Type>,
@@ -246,21 +214,8 @@ export class CardTypeResource extends FileResource<CardType> {
246
214
  if (item.isCalculated === undefined) {
247
215
  item.isCalculated = false;
248
216
  }
249
- // Fetch "displayName" from field type if it is missing.
250
- if (item.name && item.displayName === undefined) {
251
- const fieldType = this.project.resources.byType(
252
- item.name,
253
- 'fieldTypes',
254
- );
255
- const fieldTypeContent = fieldType.data;
256
- if (fieldTypeContent) {
257
- item.displayName = fieldTypeContent.displayName;
258
- }
259
- } else if (!item.name) {
260
- console.error(
261
- `Custom field '${item.name}' is missing mandatory 'name' in card type '${content.name}'`,
262
- );
263
- return undefined;
217
+ if (!item.name) {
218
+ continue;
264
219
  }
265
220
  }
266
221
  } else {
@@ -400,6 +355,42 @@ export class CardTypeResource extends FileResource<CardType> {
400
355
  }
401
356
  }
402
357
 
358
+ /**
359
+ * When resource name changes
360
+ * @param existingName Current resource name
361
+ */
362
+ protected async onNameChange(existingName: string) {
363
+ const current = this.content;
364
+ const prefixes = this.project.projectPrefixes();
365
+ if (current.customFields) {
366
+ current.customFields.map(
367
+ (field) =>
368
+ (field.name = this.updatePrefixInResourceName(field.name, prefixes)),
369
+ );
370
+ }
371
+ if (current.alwaysVisibleFields) {
372
+ current.alwaysVisibleFields = current.alwaysVisibleFields.map((item) =>
373
+ this.updatePrefixInResourceName(item, prefixes),
374
+ );
375
+ }
376
+ if (current.optionallyVisibleFields) {
377
+ current.optionallyVisibleFields = current.optionallyVisibleFields.map(
378
+ (item) => this.updatePrefixInResourceName(item, prefixes),
379
+ );
380
+ }
381
+ current.workflow = this.updatePrefixInResourceName(
382
+ current.workflow,
383
+ prefixes,
384
+ );
385
+ await Promise.all([
386
+ super.updateHandleBars(existingName, this.content.name),
387
+ super.updateCalculations(existingName, this.content.name),
388
+ this.updateLinkTypes(existingName),
389
+ ]);
390
+
391
+ await this.write();
392
+ }
393
+
403
394
  /**
404
395
  * Creates a new card type object. Base class writes the object to disk automatically.
405
396
  * @param workflowName Workflow name that this card type uses.
@@ -438,7 +429,7 @@ export class CardTypeResource extends FileResource<CardType> {
438
429
  public async rename(newName: ResourceName) {
439
430
  const existingName = this.content.name;
440
431
  await super.rename(newName);
441
- return this.handleNameChange(existingName);
432
+ return this.onNameChange(existingName);
442
433
  }
443
434
 
444
435
  /**
@@ -451,60 +442,51 @@ export class CardTypeResource extends FileResource<CardType> {
451
442
  op: Operation<Type>,
452
443
  ) {
453
444
  const { key } = updateKey;
454
- const nameChange = key === 'name';
455
- const customFieldsChange = key === 'customFields';
456
- const existingName = this.content.name;
457
- await super.update(updateKey, op);
458
-
459
- const content = structuredClone(this.content);
460
- if (key === 'name') {
461
- content.name = super.handleScalar(op) as string;
462
- } else if (key === 'alwaysVisibleFields') {
463
- await this.validateFieldType(key, op);
464
- content.alwaysVisibleFields = super.handleArray(
465
- op,
466
- key,
467
- content.alwaysVisibleFields as Type[],
468
- ) as string[];
469
- } else if (key === 'optionallyVisibleFields') {
470
- await this.validateFieldType(key, op);
471
- content.optionallyVisibleFields = super.handleArray(
472
- op,
473
- key,
474
- content.optionallyVisibleFields as Type[],
475
- ) as string[];
476
- } else if (key === 'workflow') {
477
- const changeOp = op as ChangeOperation<string>;
478
- const stateMapping = changeOp.mappingTable?.stateMapping || {};
479
- content.workflow = super.handleScalar(op) as string;
480
- if (Object.keys(stateMapping).length > 0) {
481
- await this.handleWorkflowChange(stateMapping, changeOp);
445
+
446
+ if (key === 'name' || key === 'description' || key === 'displayName') {
447
+ await super.update(updateKey, op);
448
+ } else {
449
+ const content = structuredClone(this.content);
450
+ const customFieldsChange = key === 'customFields';
451
+ if (key === 'alwaysVisibleFields') {
452
+ await this.validateFieldType(key, op);
453
+ content.alwaysVisibleFields = super.handleArray(
454
+ op,
455
+ key,
456
+ content.alwaysVisibleFields as Type[],
457
+ ) as string[];
458
+ } else if (key === 'optionallyVisibleFields') {
459
+ await this.validateFieldType(key, op);
460
+ content.optionallyVisibleFields = super.handleArray(
461
+ op,
462
+ key,
463
+ content.optionallyVisibleFields as Type[],
464
+ ) as string[];
465
+ } else if (key === 'workflow') {
466
+ const changeOp = op as ChangeOperation<string>;
467
+ const stateMapping = changeOp.mappingTable?.stateMapping || {};
468
+ content.workflow = super.handleScalar(op) as string;
469
+ if (Object.keys(stateMapping).length > 0) {
470
+ await this.handleWorkflowChange(stateMapping, changeOp);
471
+ }
472
+ } else if (key === 'customFields') {
473
+ await this.validateFieldType(key, op);
474
+ content.customFields = super.handleArray(
475
+ op,
476
+ key,
477
+ content.customFields as Type[],
478
+ ) as CustomField[];
479
+ if (op.name === 'remove') {
480
+ this.removeValueFromOtherArrays(op, content);
481
+ }
482
+ } else {
483
+ throw new Error(`Unknown property '${key}' for CardType`);
482
484
  }
483
- } else if (key === 'customFields') {
484
- await this.validateFieldType(key, op);
485
- content.customFields = super.handleArray(
486
- op,
487
- key,
488
- content.customFields as Type[],
489
- ) as CustomField[];
490
- if (op.name === 'remove') {
491
- this.removeValueFromOtherArrays(op, content);
485
+ await super.postUpdate(content, updateKey, op);
486
+
487
+ if (customFieldsChange) {
488
+ return this.handleCustomFieldsChange(op as ChangeOperation<string>);
492
489
  }
493
- } else if (key === 'description') {
494
- content.description = super.handleScalar(op) as string;
495
- } else if (key === 'displayName') {
496
- content.displayName = super.handleScalar(op) as string;
497
- } else {
498
- throw new Error(`Unknown property '${key}' for CardType`);
499
- }
500
- await super.postUpdate(content, updateKey, op);
501
-
502
- // Renaming this card type causes that references to its name must be updated.
503
- if (nameChange) {
504
- await this.handleNameChange(existingName);
505
- await this.updateLinkTypes(existingName);
506
- } else if (customFieldsChange) {
507
- return this.handleCustomFieldsChange(op as ChangeOperation<string>);
508
490
  }
509
491
  }
510
492
 
@@ -223,15 +223,6 @@ export class FieldTypeResource extends FileResource<FieldType> {
223
223
  );
224
224
  }
225
225
 
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
226
  // Checks if value 'from' can be converted 'to' value.
236
227
  private isConversionValid(from: DataType, to: DataType) {
237
228
  // Set helpers to avoid dragging 'Operation' object everywhere.
@@ -284,6 +275,19 @@ export class FieldTypeResource extends FileResource<FieldType> {
284
275
  }
285
276
  }
286
277
 
278
+ /**
279
+ * When resource name changes.
280
+ * @param existingName Current resource name.
281
+ */
282
+ protected async onNameChange(existingName: string) {
283
+ await Promise.all([
284
+ super.updateHandleBars(existingName, this.content.name),
285
+ super.updateCalculations(existingName, this.content.name),
286
+ this.updateCardTypes(existingName),
287
+ ]);
288
+ await this.write();
289
+ }
290
+
287
291
  /**
288
292
  * Creates a new field type object. Base class writes the object to disk automatically.
289
293
  * @param dataType Type for the new field type.
@@ -372,7 +376,7 @@ export class FieldTypeResource extends FileResource<FieldType> {
372
376
  public async rename(newName: ResourceName) {
373
377
  const existingName = this.content.name;
374
378
  await super.rename(newName);
375
- return this.handleNameChange(existingName);
379
+ return this.onNameChange(existingName);
376
380
  }
377
381
 
378
382
  /**
@@ -389,64 +393,56 @@ export class FieldTypeResource extends FileResource<FieldType> {
389
393
  op: Operation<Type>,
390
394
  ) {
391
395
  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
396
 
398
- await super.update(updateKey, op);
399
-
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;
397
+ if (key === 'name' || key === 'description' || key === 'displayName') {
398
+ await super.update(updateKey, op);
436
399
  } else {
437
- throw new Error(`Unknown property '${key}' for FieldType`);
438
- }
400
+ const content = structuredClone(this.content);
401
+ const typeChange = key === 'dataType';
402
+ const enumChange = key === 'enumValues';
403
+ const existingType = this.content.dataType;
404
+ if (key === 'name') {
405
+ content.name = super.handleScalar(op) as string;
406
+ } else if (key === 'dataType') {
407
+ const toType = op as ChangeOperation<DataType>;
408
+ if (!FieldTypeResource.fieldDataTypes().includes(toType.to)) {
409
+ throw new Error(
410
+ `Cannot change '${key}' to unknown type '${toType.to}'`,
411
+ );
412
+ }
413
+ if (existingType === toType.to) {
414
+ throw new Error(`'${key}' is already '${toType.to}'`);
415
+ }
416
+ if (!this.isConversionValid(content.dataType, toType.to)) {
417
+ throw new Error(
418
+ `Cannot change data type from '${content.dataType}' to '${toType.to}'`,
419
+ );
420
+ }
421
+ content.dataType = super.handleScalar(op) as DataType;
422
+ } else if (key === 'enumValues') {
423
+ if (op.name === 'add' || op.name === 'change' || op.name === 'remove') {
424
+ const existingValue = this.enumValueExists<EnumDefinition>(
425
+ op as Operation<EnumDefinition>,
426
+ content.enumValues as EnumDefinition[],
427
+ ) as Type;
428
+ op.target = existingValue ?? op.target;
429
+ }
430
+ content.enumValues = super.handleArray(
431
+ op,
432
+ key,
433
+ content.enumValues as Type[],
434
+ ) as EnumDefinition[];
435
+ } else {
436
+ throw new Error(`Unknown property '${key}' for FieldType`);
437
+ }
439
438
 
440
- await super.postUpdate(content, updateKey, op);
439
+ await super.postUpdate(content, updateKey, op);
441
440
 
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);
441
+ if (typeChange) {
442
+ await this.dataTypeChanged();
443
+ } else if (enumChange && op.name === 'remove') {
444
+ await this.handleEnumValueReplacements(op);
445
+ }
450
446
  }
451
447
  }
452
448
 
@@ -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, ...)
@@ -59,6 +60,13 @@ export abstract class FileResource<
59
60
  );
60
61
  return cards;
61
62
  }
63
+
64
+ /**
65
+ * For handling name changes.
66
+ * @param previousName The previous name before the change
67
+ */
68
+ protected abstract onNameChange?(previousName: string): Promise<void>;
69
+
62
70
  // Updates resource key to a new prefix
63
71
  protected updatePrefixInResourceName(name: string, prefixes: string[]) {
64
72
  const { identifier, prefix, type } = resourceName(name);
@@ -70,6 +78,40 @@ export abstract class FileResource<
70
78
  : name;
71
79
  }
72
80
 
81
+ /**
82
+ * Updates resource.
83
+ * @param updateKey Key to modify
84
+ * @param op Operation to perform on 'key'
85
+ * @throws if key is unknown.
86
+ */
87
+ public async update<Type, K extends string>(
88
+ updateKey: UpdateKey<K>,
89
+ op: Operation<Type>,
90
+ ) {
91
+ const { key } = updateKey;
92
+
93
+ const nameChange = key === 'name';
94
+ const existingName = this.content.name;
95
+ await super.update(updateKey, op);
96
+ const content = structuredClone(this.content);
97
+
98
+ if (key === 'name') {
99
+ content.name = super.handleScalar(op) as string;
100
+ } else if (key === 'displayName') {
101
+ content.displayName = super.handleScalar(op) as string;
102
+ } else if (key === 'description') {
103
+ content.description = super.handleScalar(op) as string;
104
+ } else {
105
+ throw new Error(`Unknown property '${key}' for folder resource`);
106
+ }
107
+
108
+ await super.postUpdate(content, updateKey, op);
109
+
110
+ if (nameChange) {
111
+ await this.onNameChange?.(existingName);
112
+ }
113
+ }
114
+
73
115
  /**
74
116
  * Returns the resource metadata content.
75
117
  * @returns metadata content
@@ -32,8 +32,11 @@ export class LinkTypeResource extends FileResource<LinkType> {
32
32
  this.contentSchema = super.contentSchemaContent(this.contentSchemaId);
33
33
  }
34
34
 
35
- // When resource name changes.
36
- private async handleNameChange(existingName: string) {
35
+ /**
36
+ * When resource name changes.
37
+ * @param existingName Current resource name.
38
+ */
39
+ protected async onNameChange(existingName: string) {
37
40
  const current = this.content;
38
41
  const prefixes = this.project.projectPrefixes();
39
42
  if (current.sourceCardTypes) {
@@ -76,7 +79,7 @@ export class LinkTypeResource extends FileResource<LinkType> {
76
79
  public async rename(newName: ResourceName) {
77
80
  const existingName = this.content.name;
78
81
  await super.rename(newName);
79
- return this.handleNameChange(existingName);
82
+ return this.onNameChange(existingName);
80
83
  }
81
84
 
82
85
  /**
@@ -89,41 +92,34 @@ export class LinkTypeResource extends FileResource<LinkType> {
89
92
  op: Operation<Type>,
90
93
  ) {
91
94
  const { key } = updateKey;
92
- const nameChange = key === 'name';
93
- const existingName = this.content.name;
94
95
 
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[];
96
+ if (key === 'name' || key === 'displayName' || key === 'description') {
97
+ await super.update(updateKey, op);
118
98
  } 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);
99
+ const content = structuredClone(this.content);
100
+ 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[];
118
+ } else {
119
+ throw new Error(`Unknown property '${key}' for LinkType`);
120
+ }
121
+
122
+ await super.postUpdate(content, updateKey, op);
127
123
  }
128
124
  }
129
125
 
@@ -140,35 +140,15 @@ export class TemplateResource extends FolderResource<TemplateMetadata, never> {
140
140
  updateKey: UpdateKey<K>,
141
141
  op: Operation<Type>,
142
142
  ) {
143
- const { key } = updateKey;
144
- const nameChange = key === 'name';
145
- const existingName = this.content.name;
146
-
147
- // Only call super.update for keys that base class supports
148
- if (key === 'name' || key === 'displayName' || key === 'description') {
149
- await super.update(updateKey, op);
150
- }
151
-
152
- const content = structuredClone(this.content);
153
-
154
- if (key === 'name') {
155
- content.name = super.handleScalar(op) as string;
156
- } else if (key === 'displayName') {
157
- content.displayName = super.handleScalar(op) as string;
158
- } else if (key === 'description') {
159
- content.description = super.handleScalar(op) as string;
160
- } else if (key === 'category') {
143
+ if (updateKey.key === 'category') {
144
+ const content = structuredClone(this.content);
161
145
  content.category = super.handleScalar(op) as string;
162
- } else {
163
- throw new Error(`Unknown property '${key}' for Template`);
164
- }
165
146
 
166
- await super.postUpdate(content, updateKey, op);
167
-
168
- // Renaming this template causes that references to its name must be updated.
169
- if (nameChange) {
170
- await this.onNameChange(existingName);
147
+ await super.postUpdate(content, updateKey, op);
148
+ return;
171
149
  }
150
+
151
+ await super.update(updateKey, op);
172
152
  }
173
153
 
174
154
  /**