@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
@@ -0,0 +1,185 @@
1
+ /**
2
+ Cyberismo
3
+ Copyright © Cyberismo Ltd and contributors 2025
4
+ This program is free software: you can redistribute it and/or modify it under
5
+ the terms of the GNU Affero General Public License version 3 as published by
6
+ the Free Software Foundation.
7
+ This program is distributed in the hope that it will be useful, but WITHOUT
8
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9
+ FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
10
+ details. You should have received a copy of the GNU Affero General Public
11
+ License along with this program. If not, see <https://www.gnu.org/licenses/>.
12
+ */
13
+
14
+ import { dirname, join } from 'node:path';
15
+ import { fileURLToPath } from 'node:url';
16
+ import { Worker } from 'node:worker_threads';
17
+
18
+ import { getChildLogger } from '../utils/log-utils.js';
19
+ import type {
20
+ MigrationContext,
21
+ MigrationStepResult,
22
+ } from '@cyberismo/migrations';
23
+ import type { WorkerMessage, WorkerResponse } from './migration-executor.js';
24
+
25
+ const CANCEL_PERIOD_MS = 100;
26
+ const logger = getChildLogger({ module: 'WorkerExecutor' });
27
+
28
+ /**
29
+ * Execute a migration step in a separate worker thread.
30
+ * The worker loads the migration program dynamically and executes the specified step.
31
+ *
32
+ * @param migrationPath Absolute path to the migration's 'index.js' file
33
+ * @param stepName The migration step to execute
34
+ * @param context Migration context
35
+ * @param timeoutMilliSeconds Timeout in milliseconds to wait for the step to complete
36
+ * @returns Migration step result
37
+ */
38
+ export async function executeStep(
39
+ migrationPath: string,
40
+ stepName: string,
41
+ context: MigrationContext,
42
+ timeoutMilliSeconds: number,
43
+ ): Promise<MigrationStepResult> {
44
+ // Always uses the compiled .js version from the dist directory.
45
+ function _workerPath() {
46
+ const currentFilePath = fileURLToPath(import.meta.url);
47
+ const currentDir = dirname(currentFilePath);
48
+ const srcMigrationsSegment = join('src', 'migrations');
49
+ const distMigrationsSegment = join('dist', 'migrations');
50
+ const distDir = currentDir.replace(
51
+ srcMigrationsSegment,
52
+ distMigrationsSegment,
53
+ );
54
+ return join(distDir, 'migration-worker.js');
55
+ }
56
+
57
+ return new Promise((resolve, reject) => {
58
+ const worker = new Worker(_workerPath(), {
59
+ execArgv: process.execArgv,
60
+ });
61
+ let timeoutId: NodeJS.Timeout | undefined;
62
+ let isResolved = false;
63
+
64
+ const cleanup = () => {
65
+ if (timeoutId) {
66
+ clearTimeout(timeoutId);
67
+ timeoutId = undefined;
68
+ }
69
+ };
70
+
71
+ const terminate = async (sendCancel: boolean = false): Promise<void> => {
72
+ if (sendCancel) {
73
+ try {
74
+ const cancelMessage: WorkerMessage = { type: 'cancel' };
75
+ worker.postMessage(cancelMessage);
76
+ await new Promise((_resolve) =>
77
+ setTimeout(_resolve, CANCEL_PERIOD_MS),
78
+ );
79
+ } catch {
80
+ // Ignore errors when sending cancel message
81
+ }
82
+ }
83
+
84
+ try {
85
+ await worker.terminate();
86
+ } catch (error) {
87
+ logger.debug({ error }, 'Error terminating worker');
88
+ }
89
+ };
90
+
91
+ timeoutId = setTimeout(() => {
92
+ if (!isResolved) {
93
+ isResolved = true;
94
+ cleanup();
95
+
96
+ void (async () => {
97
+ await terminate(true);
98
+ resolve({
99
+ success: false,
100
+ error: new Error(`Migration step '${stepName}' timeout`),
101
+ });
102
+ })();
103
+ }
104
+ }, timeoutMilliSeconds);
105
+
106
+ worker.on('message', (response: WorkerResponse) => {
107
+ if (isResolved) return;
108
+
109
+ isResolved = true;
110
+ cleanup();
111
+
112
+ void (async () => {
113
+ if (response.type === 'error') {
114
+ await terminate(false);
115
+ resolve({
116
+ success: false,
117
+ error: new Error(response.error || 'Unknown worker error'),
118
+ });
119
+ } else if (response.type === 'result' && response.result) {
120
+ await terminate(false);
121
+ resolve(response.result);
122
+ } else {
123
+ await terminate(false);
124
+ resolve({
125
+ success: false,
126
+ error: new Error('Invalid worker response'),
127
+ });
128
+ }
129
+ })();
130
+ });
131
+
132
+ worker.on('error', (error) => {
133
+ if (isResolved) return;
134
+
135
+ isResolved = true;
136
+ cleanup();
137
+
138
+ void (async () => {
139
+ await terminate(false);
140
+ resolve({
141
+ success: false,
142
+ error,
143
+ });
144
+ })();
145
+ });
146
+
147
+ worker.on('exit', (code) => {
148
+ if (isResolved) return;
149
+
150
+ isResolved = true;
151
+ cleanup();
152
+
153
+ if (code !== 0) {
154
+ resolve({
155
+ success: false,
156
+ error: new Error(`Worker exited with code ${code}`),
157
+ });
158
+ }
159
+ });
160
+
161
+ const migrationContext: MigrationContext = {
162
+ cardRootPath: context.cardRootPath,
163
+ cardsConfigPath: context.cardsConfigPath,
164
+ fromVersion: context.fromVersion,
165
+ toVersion: context.toVersion,
166
+ backupDir: context.backupDir,
167
+ };
168
+
169
+ const executeMessage: WorkerMessage = {
170
+ type: 'execute',
171
+ migrationPath,
172
+ stepName,
173
+ context: migrationContext,
174
+ };
175
+
176
+ try {
177
+ worker.postMessage(executeMessage);
178
+ } catch (error) {
179
+ isResolved = true;
180
+ cleanup();
181
+ void terminate(false);
182
+ reject(error);
183
+ }
184
+ });
185
+ }
@@ -35,6 +35,8 @@ export class ProjectConfiguration implements ProjectSettings {
35
35
  schemaVersion?: number;
36
36
  name: string;
37
37
  cardKeyPrefix: string;
38
+ category?: string;
39
+ description: string;
38
40
  modules: ModuleSetting[];
39
41
  hubs: HubSetting[];
40
42
  private logger = getChildLogger({ module: 'Project' });
@@ -45,6 +47,7 @@ export class ProjectConfiguration implements ProjectSettings {
45
47
  this.name = '';
46
48
  this.settingPath = path;
47
49
  this.cardKeyPrefix = '';
50
+ this.description = '';
48
51
  this.modules = [];
49
52
  this.hubs = [];
50
53
  this.autoSave = autoSave;
@@ -79,6 +82,8 @@ export class ProjectConfiguration implements ProjectSettings {
79
82
  this.schemaVersion = settings.schemaVersion;
80
83
  this.cardKeyPrefix = settings.cardKeyPrefix;
81
84
  this.name = settings.name;
85
+ this.category = settings.category;
86
+ this.description = settings.description || '';
82
87
  this.modules = settings.modules || [];
83
88
  this.hubs = settings.hubs || [];
84
89
  } else {
@@ -106,6 +111,8 @@ export class ProjectConfiguration implements ProjectSettings {
106
111
  schemaVersion: this.schemaVersion,
107
112
  cardKeyPrefix: this.cardKeyPrefix,
108
113
  name: this.name,
114
+ category: this.category,
115
+ description: this.description,
109
116
  modules: this.modules,
110
117
  hubs: this.hubs,
111
118
  };
@@ -34,6 +34,11 @@ export class CalculationResource extends FolderResource<
34
34
  CalculationMetadata,
35
35
  CalculationContent
36
36
  > {
37
+ /**
38
+ * Creates instance of CalculationResource
39
+ * @param project Project to use
40
+ * @param name Resource name
41
+ */
37
42
  constructor(project: Project, name: ResourceName) {
38
43
  super(project, name, 'calculations');
39
44
 
@@ -41,9 +46,15 @@ export class CalculationResource extends FolderResource<
41
46
  this.contentSchema = super.contentSchemaContent(this.contentSchemaId);
42
47
  }
43
48
 
44
- // When resource name changes
49
+ /**
50
+ * When resource name changes
51
+ * @param existingName Current resource name
52
+ */
45
53
  protected async onNameChange(existingName: string) {
46
- await super.updateCalculations(existingName, this.content.name);
54
+ await Promise.all([
55
+ super.updateCalculations(existingName, this.content.name),
56
+ super.updateCardContentReferences(existingName, this.content.name),
57
+ ]);
47
58
  await this.write();
48
59
  }
49
60
 
@@ -17,6 +17,7 @@ import { FileResource } from './file-resource.js';
17
17
  import { resourceName, resourceNameToString } from '../utils/resource-utils.js';
18
18
  import { ResourcesFrom } from '../containers/project.js';
19
19
  import { sortCards } from '../utils/card-utils.js';
20
+ import { removeValue } from '../utils/common-utils.js';
20
21
  import { Validate } from '../commands/validate.js';
21
22
 
22
23
  import type {
@@ -39,6 +40,11 @@ import type { ResourceName } from '../utils/resource-utils.js';
39
40
  * Card type resource class.
40
41
  */
41
42
  export class CardTypeResource extends FileResource<CardType> {
43
+ /**
44
+ * Creates instance of CardTypeResource
45
+ * @param project Project to use
46
+ * @param name Resource name
47
+ */
42
48
  constructor(project: Project, name: ResourceName) {
43
49
  super(project, name, 'cardTypes');
44
50
 
@@ -69,7 +75,10 @@ export class CardTypeResource extends FileResource<CardType> {
69
75
  if (op && op.name === 'rank') return;
70
76
 
71
77
  // Collect both project cards and template cards.
72
- const cards = await this.collectCards(this.content.name);
78
+ const cards = await this.collectCards(
79
+ this.content.name,
80
+ (card, cardTypeName) => card.metadata?.cardType === cardTypeName,
81
+ );
73
82
 
74
83
  if (op && op.name === 'change') {
75
84
  const from = (op as ChangeOperation<string>).target;
@@ -102,38 +111,6 @@ export class CardTypeResource extends FileResource<CardType> {
102
111
  }
103
112
  }
104
113
 
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
114
  // When a field is removed, remove it from all affected cards.
138
115
  private async handleRemoveField(cards: Card[], item: CustomField) {
139
116
  for (const card of cards) {
@@ -151,7 +128,10 @@ export class CardTypeResource extends FileResource<CardType> {
151
128
  op: ChangeOperation<Type>,
152
129
  ) {
153
130
  await this.verifyStateMapping(stateMapping, op);
154
- const cards = await this.collectCards(this.content.name);
131
+ const cards = await this.collectCards(
132
+ this.content.name,
133
+ (card, cardTypeName) => card.metadata?.cardType === cardTypeName,
134
+ );
155
135
 
156
136
  const unmappedStates: string[] = [];
157
137
 
@@ -189,15 +169,6 @@ export class CardTypeResource extends FileResource<CardType> {
189
169
  );
190
170
  }
191
171
 
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
172
  // Return link types that use this card type.
202
173
  private relevantLinkTypes(): string[] {
203
174
  const resourceName = resourceNameToString(this.resourceName);
@@ -233,8 +204,8 @@ export class CardTypeResource extends FileResource<CardType> {
233
204
  field = { name: target['name' as keyof Type] };
234
205
  }
235
206
  const fieldName = (field ? field.name : target) as string;
236
- this.removeValue(content.alwaysVisibleFields, fieldName);
237
- this.removeValue(content.optionallyVisibleFields, fieldName);
207
+ removeValue(content.alwaysVisibleFields, fieldName);
208
+ removeValue(content.optionallyVisibleFields, fieldName);
238
209
  }
239
210
 
240
211
  // Sets content container values to be either '[]' or with proper values.
@@ -246,21 +217,8 @@ export class CardTypeResource extends FileResource<CardType> {
246
217
  if (item.isCalculated === undefined) {
247
218
  item.isCalculated = false;
248
219
  }
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;
220
+ if (!item.name) {
221
+ continue;
264
222
  }
265
223
  }
266
224
  } else {
@@ -400,10 +358,48 @@ export class CardTypeResource extends FileResource<CardType> {
400
358
  }
401
359
  }
402
360
 
361
+ /**
362
+ * When resource name changes
363
+ * @param existingName Current resource name
364
+ */
365
+ protected async onNameChange(existingName: string) {
366
+ const current = this.content;
367
+ const prefixes = this.project.projectPrefixes();
368
+ if (current.customFields) {
369
+ current.customFields.map(
370
+ (field) =>
371
+ (field.name = this.updatePrefixInResourceName(field.name, prefixes)),
372
+ );
373
+ }
374
+ if (current.alwaysVisibleFields) {
375
+ current.alwaysVisibleFields = current.alwaysVisibleFields.map((item) =>
376
+ this.updatePrefixInResourceName(item, prefixes),
377
+ );
378
+ }
379
+ if (current.optionallyVisibleFields) {
380
+ current.optionallyVisibleFields = current.optionallyVisibleFields.map(
381
+ (item) => this.updatePrefixInResourceName(item, prefixes),
382
+ );
383
+ }
384
+ current.workflow = this.updatePrefixInResourceName(
385
+ current.workflow,
386
+ prefixes,
387
+ );
388
+ await Promise.all([
389
+ super.updateHandleBars(existingName, this.content.name),
390
+ super.updateCalculations(existingName, this.content.name),
391
+ super.updateCardContentReferences(existingName, this.content.name),
392
+ this.updateLinkTypes(existingName),
393
+ ]);
394
+
395
+ await this.write();
396
+ }
397
+
403
398
  /**
404
399
  * Creates a new card type object. Base class writes the object to disk automatically.
405
400
  * @param workflowName Workflow name that this card type uses.
406
- * @throws when workflow is empty, or does not exist in the project.
401
+ * @throws when workflow is empty, or
402
+ * when workflow does not exist in the project.
407
403
  */
408
404
  public async createCardType(workflowName: string) {
409
405
  if (!workflowName) {
@@ -438,7 +434,7 @@ export class CardTypeResource extends FileResource<CardType> {
438
434
  public async rename(newName: ResourceName) {
439
435
  const existingName = this.content.name;
440
436
  await super.rename(newName);
441
- return this.handleNameChange(existingName);
437
+ return this.onNameChange(existingName);
442
438
  }
443
439
 
444
440
  /**
@@ -451,60 +447,51 @@ export class CardTypeResource extends FileResource<CardType> {
451
447
  op: Operation<Type>,
452
448
  ) {
453
449
  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);
450
+
451
+ if (key === 'name' || key === 'description' || key === 'displayName') {
452
+ await super.update(updateKey, op);
453
+ } else {
454
+ const content = structuredClone(this.content);
455
+ const customFieldsChange = key === 'customFields';
456
+ if (key === 'alwaysVisibleFields') {
457
+ await this.validateFieldType(key, op);
458
+ content.alwaysVisibleFields = super.handleArray(
459
+ op,
460
+ key,
461
+ content.alwaysVisibleFields as Type[],
462
+ ) as string[];
463
+ } else if (key === 'optionallyVisibleFields') {
464
+ await this.validateFieldType(key, op);
465
+ content.optionallyVisibleFields = super.handleArray(
466
+ op,
467
+ key,
468
+ content.optionallyVisibleFields as Type[],
469
+ ) as string[];
470
+ } else if (key === 'workflow') {
471
+ const changeOp = op as ChangeOperation<string>;
472
+ const stateMapping = changeOp.mappingTable?.stateMapping || {};
473
+ content.workflow = super.handleScalar(op) as string;
474
+ if (Object.keys(stateMapping).length > 0) {
475
+ await this.handleWorkflowChange(stateMapping, changeOp);
476
+ }
477
+ } else if (key === 'customFields') {
478
+ await this.validateFieldType(key, op);
479
+ content.customFields = super.handleArray(
480
+ op,
481
+ key,
482
+ content.customFields as Type[],
483
+ ) as CustomField[];
484
+ if (op.name === 'remove') {
485
+ this.removeValueFromOtherArrays(op, content);
486
+ }
487
+ } else {
488
+ throw new Error(`Unknown property '${key}' for CardType`);
482
489
  }
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);
490
+ await super.postUpdate(content, updateKey, op);
491
+
492
+ if (customFieldsChange) {
493
+ return this.handleCustomFieldsChange(op as ChangeOperation<string>);
492
494
  }
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
495
  }
509
496
  }
510
497