@uipath/data-fabric-tool 1.1.0 → 1.195.0

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.
@@ -1,14 +1,32 @@
1
1
  import {
2
2
  type CommandExample,
3
3
  catchError,
4
+ createHiddenDeprecatedTenantOption,
4
5
  extractErrorMessage,
5
6
  OutputFormatter,
6
7
  processContext,
7
8
  RESULTS,
8
9
  } from "@uipath/common";
10
+ import type {
11
+ ChoiceSetCreateOptions,
12
+ ChoiceSetServiceModel,
13
+ ChoiceSetUpdateOptions,
14
+ ChoiceSetValueInsertOptions,
15
+ } from "@uipath/uipath-typescript";
9
16
  import type { Command } from "commander";
10
- import { extractCursorValue } from "../utils/pagination";
11
- import { createDataFabricClient } from "../utils/sdk-client";
17
+ import { fail, requireDestructiveConfirmation } from "../utils/output";
18
+ import { connectOrFail } from "../utils/sdk-client";
19
+
20
+ const OUTPUT_CODES = {
21
+ ChoiceSetList: "ChoiceSetList",
22
+ ChoiceSetValues: "ChoiceSetValues",
23
+ ChoiceSetCreated: "ChoiceSetCreated",
24
+ ChoiceSetUpdated: "ChoiceSetUpdated",
25
+ ChoiceSetDeleted: "ChoiceSetDeleted",
26
+ ChoiceSetValueCreated: "ChoiceSetValueCreated",
27
+ ChoiceSetValueUpdated: "ChoiceSetValueUpdated",
28
+ ChoiceSetValuesDeleted: "ChoiceSetValuesDeleted",
29
+ } as const;
12
30
 
13
31
  interface ListOptions {
14
32
  tenant?: string;
@@ -21,24 +39,59 @@ interface GetOptions {
21
39
  cursor?: string;
22
40
  }
23
41
 
42
+ interface CreateOptions {
43
+ tenant?: string;
44
+ displayName?: string;
45
+ description?: string;
46
+ folderKey?: string;
47
+ }
48
+
49
+ interface UpdateOptions {
50
+ tenant?: string;
51
+ displayName?: string;
52
+ description?: string;
53
+ }
54
+
55
+ interface DeleteOptions {
56
+ tenant?: string;
57
+ confirm?: boolean;
58
+ reason?: string;
59
+ }
60
+
61
+ interface ValueCreateOptions {
62
+ tenant?: string;
63
+ displayName?: string;
64
+ }
65
+
66
+ interface ValueUpdateOptions {
67
+ tenant?: string;
68
+ }
69
+
70
+ interface ValueDeleteOptions {
71
+ tenant?: string;
72
+ ids?: string;
73
+ confirm?: boolean;
74
+ reason?: string;
75
+ }
76
+
24
77
  const CHOICE_SETS_LIST_EXAMPLES: CommandExample[] = [
25
78
  {
26
79
  Description:
27
- "List all Data Fabric choice sets. The returned 'ID' is the value to pass as 'choiceSetId' on a CHOICE_SET_SINGLE/CHOICE_SET_MULTIPLE entity field, or to 'df choice-sets get <id>'.",
80
+ "List all Data Fabric choice sets. The returned 'id' is the value to pass as 'choiceSetId' on a CHOICE_SET_SINGLE/CHOICE_SET_MULTIPLE entity field, or to 'df choice-sets list-values <id>'.",
28
81
  Command: "uip df choice-sets list",
29
82
  Output: {
30
- Code: "ChoiceSetList",
83
+ Code: OUTPUT_CODES.ChoiceSetList,
31
84
  Data: [
32
85
  {
33
- ID: "c1d2e3f4-0000-0000-0000-000000000001",
34
- Name: "ExpenseTypes",
35
- DisplayName: "Expense Types",
36
- Description: "Categories of expenses",
37
- FolderId: "f1000000-0000-0000-0000-000000000001",
38
- CreatedBy: "u1000000-0000-0000-0000-000000000001",
39
- UpdatedBy: "u1000000-0000-0000-0000-000000000001",
40
- CreatedTime: "2026-01-01T00:00:00Z",
41
- UpdatedTime: "2026-01-02T00:00:00Z",
86
+ id: "c1d2e3f4-0000-0000-0000-000000000001",
87
+ name: "ExpenseTypes",
88
+ displayName: "Expense Types",
89
+ description: "Categories of expenses",
90
+ folderId: "f1000000-0000-0000-0000-000000000001",
91
+ createdBy: "u1000000-0000-0000-0000-000000000001",
92
+ updatedBy: "u1000000-0000-0000-0000-000000000001",
93
+ createdTime: "2026-01-01T00:00:00Z",
94
+ updatedTime: "2026-01-02T00:00:00Z",
42
95
  },
43
96
  ],
44
97
  },
@@ -48,28 +101,70 @@ const CHOICE_SETS_LIST_EXAMPLES: CommandExample[] = [
48
101
  const CHOICE_SETS_GET_EXAMPLES: CommandExample[] = [
49
102
  {
50
103
  Description:
51
- "Get values for a choice set. The 'NumberId' on each value is the integer to pass when inserting or updating a record into a CHOICE_SET_SINGLE field (or in an array for CHOICE_SET_MULTIPLE).",
104
+ "List the values of a choice set. The 'numberId' on each value is the integer to pass when inserting or updating a record into a CHOICE_SET_SINGLE field (or in an array for CHOICE_SET_MULTIPLE).",
52
105
  Command:
53
- "uip df choice-sets get c1d2e3f4-0000-0000-0000-000000000001 --limit 2",
106
+ "uip df choice-sets list-values c1d2e3f4-0000-0000-0000-000000000001 --limit 2",
54
107
  Output: {
55
- Code: "ChoiceSetValues",
108
+ Code: OUTPUT_CODES.ChoiceSetValues,
56
109
  Data: {
57
- TotalCount: 2,
58
- Values: [
110
+ items: [
59
111
  {
60
- Id: "v1000000-0000-0000-0000-000000000001",
61
- Name: "travel",
62
- DisplayName: "Travel",
63
- NumberId: 1,
112
+ id: "v1000000-0000-0000-0000-000000000001",
113
+ name: "travel",
114
+ displayName: "Travel",
115
+ numberId: 0,
64
116
  },
65
117
  {
66
- Id: "v1000000-0000-0000-0000-000000000002",
67
- Name: "meals",
68
- DisplayName: "Meals",
69
- NumberId: 2,
118
+ id: "v1000000-0000-0000-0000-000000000002",
119
+ name: "meals",
120
+ displayName: "Meals",
121
+ numberId: 1,
70
122
  },
71
123
  ],
72
- HasNextPage: false,
124
+ totalCount: 2,
125
+ hasNextPage: false,
126
+ },
127
+ },
128
+ },
129
+ ];
130
+
131
+ const CHOICE_SETS_CREATE_EXAMPLES: CommandExample[] = [
132
+ {
133
+ Description:
134
+ "Create a choice set. The returned 'ID' is used as 'choiceSetId' on CHOICE_SET_SINGLE/CHOICE_SET_MULTIPLE entity fields. Add values with 'df choice-set-values create'.",
135
+ Command:
136
+ 'uip df choice-sets create ExpenseTypes --display-name "Expense Types" --description "Categories of expenses"',
137
+ Output: {
138
+ Code: OUTPUT_CODES.ChoiceSetCreated,
139
+ Data: { ID: "c1d2e3f4-0000-0000-0000-000000000001" },
140
+ },
141
+ },
142
+ ];
143
+
144
+ const CHOICE_SETS_UPDATE_EXAMPLES: CommandExample[] = [
145
+ {
146
+ Description:
147
+ "Update a choice set's display name and/or description. At least one of --display-name or --description is required.",
148
+ Command:
149
+ 'uip df choice-sets update c1d2e3f4-0000-0000-0000-000000000001 --display-name "Expense Categories"',
150
+ Output: {
151
+ Code: OUTPUT_CODES.ChoiceSetUpdated,
152
+ Data: { ID: "c1d2e3f4-0000-0000-0000-000000000001" },
153
+ },
154
+ },
155
+ ];
156
+
157
+ const CHOICE_SETS_DELETE_EXAMPLES: CommandExample[] = [
158
+ {
159
+ Description:
160
+ "Delete a choice set (irreversible — requires --confirm and --reason).",
161
+ Command:
162
+ 'uip df choice-sets delete c1d2e3f4-0000-0000-0000-000000000001 --confirm --reason "no longer used"',
163
+ Output: {
164
+ Code: OUTPUT_CODES.ChoiceSetDeleted,
165
+ Data: {
166
+ ID: "c1d2e3f4-0000-0000-0000-000000000001",
167
+ Reason: "no longer used",
73
168
  },
74
169
  },
75
170
  },
@@ -79,68 +174,46 @@ export const registerChoiceSetsCommand = (program: Command) => {
79
174
  const choiceSets = program
80
175
  .command("choice-sets")
81
176
  .description(
82
- "Browse Data Fabric choice sets. Read-only: choice sets are authored in the Data Fabric web UI and cannot be created, updated, or deleted from the CLI. " +
83
- "Use 'list' to find a choice set's ID (used as 'choiceSetId' on CHOICE_SET_SINGLE/CHOICE_SET_MULTIPLE entity fields), and 'get' to inspect its values.",
177
+ "Manage Data Fabric choice sets the enumerated value lists referenced by CHOICE_SET_SINGLE/CHOICE_SET_MULTIPLE entity fields. " +
178
+ "Use 'list'/'list-values' to inspect, and 'create'/'update'/'delete' to manage them. Manage individual values with 'df choice-set-values'.",
84
179
  );
85
180
 
86
181
  choiceSets
87
182
  .command("list")
88
183
  .description("List all Data Fabric choice sets")
89
- .option("-t, --tenant <tenant-name>", "Tenant name")
184
+ .addOption(
185
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
186
+ )
90
187
  .examples(CHOICE_SETS_LIST_EXAMPLES)
91
188
  .trackedAction(processContext, async (options: ListOptions) => {
92
- const [clientError, sdk] = await catchError(
93
- createDataFabricClient(options.tenant),
94
- );
95
-
96
- if (clientError) {
97
- OutputFormatter.error({
98
- Result: RESULTS.Failure,
99
- Message: "Error connecting to Data Fabric",
100
- Instructions: await extractErrorMessage(clientError),
101
- });
102
- processContext.exit(1);
103
- return;
104
- }
189
+ const sdk = await connectOrFail(options.tenant);
190
+ if (!sdk) return;
105
191
 
106
192
  const [listError, result] = await catchError(
107
193
  sdk.entities.choicesets.getAll(),
108
194
  );
109
195
 
110
196
  if (listError) {
111
- OutputFormatter.error({
112
- Result: RESULTS.Failure,
113
- Message: "Error listing choice sets",
114
- Instructions: await extractErrorMessage(listError),
115
- });
116
- processContext.exit(1);
117
- return;
197
+ return fail(
198
+ "Error listing choice sets",
199
+ await extractErrorMessage(listError),
200
+ );
118
201
  }
119
202
 
120
- const items = (result ?? []).map((cs) => ({
121
- ID: (cs as { id?: string }).id,
122
- Name: cs.name,
123
- DisplayName: cs.displayName || cs.name,
124
- Description: cs.description || "",
125
- FolderId: cs.folderId,
126
- CreatedBy: cs.createdBy,
127
- UpdatedBy: cs.updatedBy,
128
- CreatedTime: cs.createdTime,
129
- UpdatedTime: cs.updatedTime,
130
- }));
131
-
132
203
  OutputFormatter.success({
133
204
  Result: RESULTS.Success,
134
- Code: "ChoiceSetList",
135
- Data: items,
205
+ Code: OUTPUT_CODES.ChoiceSetList,
206
+ Data: result ?? [],
136
207
  });
137
208
  });
138
209
 
139
210
  choiceSets
140
- .command("get")
141
- .description("Get values for a Data Fabric choice set")
211
+ .command("list-values")
212
+ .description("List the values of a Data Fabric choice set")
142
213
  .argument("<choice-set-id>", "Choice set ID")
143
- .option("-t, --tenant <tenant-name>", "Tenant name")
214
+ .addOption(
215
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
216
+ )
144
217
  .option(
145
218
  "-l, --limit <number>",
146
219
  "Number of values to return per page",
@@ -160,58 +233,36 @@ export const registerChoiceSetsCommand = (program: Command) => {
160
233
  async (choiceSetId: string, options: GetOptions) => {
161
234
  const pageSize = Number(options.limit ?? "50");
162
235
  if (Number.isNaN(pageSize) || pageSize < 1) {
163
- OutputFormatter.error({
164
- Result: RESULTS.Failure,
165
- Message: "Invalid --limit value",
166
- Instructions: "Provide a positive integer for --limit.",
167
- });
168
- processContext.exit(1);
169
- return;
236
+ return fail(
237
+ "Invalid --limit value",
238
+ "Provide a positive integer for --limit.",
239
+ );
170
240
  }
171
241
 
172
242
  if (
173
243
  options.cursor !== undefined &&
174
244
  options.offset !== undefined
175
245
  ) {
176
- OutputFormatter.error({
177
- Result: RESULTS.Failure,
178
- Message: "--offset and --cursor are mutually exclusive",
179
- Instructions:
180
- "Use --offset to jump to a position by record count, or --cursor to continue from a previous response.",
181
- });
182
- processContext.exit(1);
183
- return;
246
+ return fail(
247
+ "--offset and --cursor are mutually exclusive",
248
+ "Use --offset to jump to a position by record count, or --cursor to continue from a previous response.",
249
+ );
184
250
  }
185
251
 
186
252
  let jumpToPage: number | undefined;
187
253
  if (options.offset !== undefined) {
188
254
  const offsetValue = Number(options.offset);
189
255
  if (Number.isNaN(offsetValue) || offsetValue < 0) {
190
- OutputFormatter.error({
191
- Result: RESULTS.Failure,
192
- Message: "Invalid --offset value",
193
- Instructions:
194
- "Provide a non-negative integer for --offset.",
195
- });
196
- processContext.exit(1);
197
- return;
256
+ return fail(
257
+ "Invalid --offset value",
258
+ "Provide a non-negative integer for --offset.",
259
+ );
198
260
  }
199
261
  jumpToPage = Math.floor(offsetValue / pageSize) + 1;
200
262
  }
201
263
 
202
- const [clientError, sdk] = await catchError(
203
- createDataFabricClient(options.tenant),
204
- );
205
-
206
- if (clientError) {
207
- OutputFormatter.error({
208
- Result: RESULTS.Failure,
209
- Message: "Error connecting to Data Fabric",
210
- Instructions: await extractErrorMessage(clientError),
211
- });
212
- processContext.exit(1);
213
- return;
214
- }
264
+ const sdk = await connectOrFail(options.tenant);
265
+ if (!sdk) return;
215
266
 
216
267
  const paginationOptions =
217
268
  options.cursor !== undefined
@@ -228,57 +279,410 @@ export const registerChoiceSetsCommand = (program: Command) => {
228
279
  );
229
280
 
230
281
  if (getError) {
231
- OutputFormatter.error({
232
- Result: RESULTS.Failure,
233
- Message: `Error getting choice set '${choiceSetId}'`,
234
- Instructions: await extractErrorMessage(getError),
235
- });
236
- processContext.exit(1);
237
- return;
282
+ return fail(
283
+ `Error getting choice set '${choiceSetId}'`,
284
+ await extractErrorMessage(getError),
285
+ );
238
286
  }
239
287
 
240
- type ChoiceSetValue = {
241
- id?: string;
242
- name?: string;
243
- displayName?: string;
244
- numberId?: number;
245
- createdTime?: string;
246
- updatedTime?: string;
288
+ OutputFormatter.success({
289
+ Result: RESULTS.Success,
290
+ Code: OUTPUT_CODES.ChoiceSetValues,
291
+ Data: result,
292
+ });
293
+ },
294
+ );
295
+
296
+ choiceSets
297
+ .command("create")
298
+ .description("Create a new Data Fabric choice set")
299
+ .argument(
300
+ "<name>",
301
+ "Choice set name (must start with a letter; letters, numbers, and underscores only)",
302
+ )
303
+ .addOption(
304
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
305
+ )
306
+ .option("--display-name <name>", "Human-readable display name")
307
+ .option("--description <text>", "Choice set description")
308
+ .option(
309
+ "--folder-key <uuid>",
310
+ "UUID of the folder to place the choice set in (defaults to the tenant-level folder)",
311
+ )
312
+ .examples(CHOICE_SETS_CREATE_EXAMPLES)
313
+ .trackedAction(
314
+ processContext,
315
+ async (name: string, options: CreateOptions) => {
316
+ const sdk = await connectOrFail(options.tenant);
317
+ if (!sdk) return;
318
+
319
+ const createOpts: ChoiceSetCreateOptions = {
320
+ ...(options.displayName !== undefined && {
321
+ displayName: options.displayName,
322
+ }),
323
+ ...(options.description !== undefined && {
324
+ description: options.description,
325
+ }),
326
+ ...(options.folderKey !== undefined && {
327
+ folderKey: options.folderKey,
328
+ }),
247
329
  };
248
- const response = result as unknown as {
249
- items?: ChoiceSetValue[];
250
- totalCount?: number;
251
- hasNextPage?: boolean;
252
- nextCursor?: unknown;
253
- currentPage?: number;
254
- totalPages?: number;
330
+
331
+ const choiceSetService: ChoiceSetServiceModel =
332
+ sdk.entities.choicesets;
333
+ const [createError, choiceSetId] = await catchError(
334
+ choiceSetService.create(
335
+ name,
336
+ Object.keys(createOpts).length > 0
337
+ ? createOpts
338
+ : undefined,
339
+ ),
340
+ );
341
+
342
+ if (createError) {
343
+ return fail(
344
+ "Error creating choice set",
345
+ await extractErrorMessage(createError),
346
+ );
347
+ }
348
+
349
+ OutputFormatter.success({
350
+ Result: RESULTS.Success,
351
+ Code: OUTPUT_CODES.ChoiceSetCreated,
352
+ Data: { ID: choiceSetId },
353
+ });
354
+ },
355
+ );
356
+
357
+ choiceSets
358
+ .command("update")
359
+ .description("Update the metadata of a Data Fabric choice set")
360
+ .argument("<choice-set-id>", "Choice set ID")
361
+ .addOption(
362
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
363
+ )
364
+ .option("--display-name <name>", "New display name")
365
+ .option("--description <text>", "New description")
366
+ .examples(CHOICE_SETS_UPDATE_EXAMPLES)
367
+ .trackedAction(
368
+ processContext,
369
+ async (choiceSetId: string, options: UpdateOptions) => {
370
+ if (
371
+ options.displayName === undefined &&
372
+ options.description === undefined
373
+ ) {
374
+ return fail(
375
+ "No update fields provided",
376
+ "Provide at least one of --display-name or --description.",
377
+ );
378
+ }
379
+
380
+ const sdk = await connectOrFail(options.tenant);
381
+ if (!sdk) return;
382
+
383
+ const updateOpts: ChoiceSetUpdateOptions = {
384
+ ...(options.displayName !== undefined && {
385
+ displayName: options.displayName,
386
+ }),
387
+ ...(options.description !== undefined && {
388
+ description: options.description,
389
+ }),
255
390
  };
256
- const values = (response.items ?? []).map((v) => ({
257
- Id: v.id,
258
- Name: v.name,
259
- DisplayName: v.displayName || v.name,
260
- NumberId: v.numberId,
261
- CreatedTime: v.createdTime,
262
- UpdatedTime: v.updatedTime,
263
- }));
264
- const nextCursor = extractCursorValue(response.nextCursor);
391
+
392
+ const choiceSetService: ChoiceSetServiceModel =
393
+ sdk.entities.choicesets;
394
+ const [updateError] = await catchError(
395
+ choiceSetService.updateById(choiceSetId, updateOpts),
396
+ );
397
+
398
+ if (updateError) {
399
+ return fail(
400
+ `Error updating choice set '${choiceSetId}'`,
401
+ await extractErrorMessage(updateError),
402
+ );
403
+ }
404
+
405
+ OutputFormatter.success({
406
+ Result: RESULTS.Success,
407
+ Code: OUTPUT_CODES.ChoiceSetUpdated,
408
+ Data: { ID: choiceSetId },
409
+ });
410
+ },
411
+ );
412
+
413
+ choiceSets
414
+ .command("delete")
415
+ .description("Delete a Data Fabric choice set (irreversible)")
416
+ .argument("<choice-set-id>", "Choice set ID")
417
+ .addOption(
418
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
419
+ )
420
+ .option("--confirm", "Acknowledge this is an irreversible operation")
421
+ .option(
422
+ "--reason <reason>",
423
+ "Reason for the deletion — echoed back in the response so the caller can log it",
424
+ )
425
+ .examples(CHOICE_SETS_DELETE_EXAMPLES)
426
+ .trackedAction(
427
+ processContext,
428
+ async (choiceSetId: string, options: DeleteOptions) => {
429
+ const reason = requireDestructiveConfirmation(
430
+ options,
431
+ 'Pass --reason "<text>" to record why the choice set is being deleted.',
432
+ );
433
+ if (reason === null) return;
434
+
435
+ const sdk = await connectOrFail(options.tenant);
436
+ if (!sdk) return;
437
+
438
+ const choiceSetService: ChoiceSetServiceModel =
439
+ sdk.entities.choicesets;
440
+ const [deleteError] = await catchError(
441
+ choiceSetService.deleteById(choiceSetId),
442
+ );
443
+
444
+ if (deleteError) {
445
+ return fail(
446
+ `Error deleting choice set '${choiceSetId}'`,
447
+ await extractErrorMessage(deleteError),
448
+ );
449
+ }
450
+
451
+ OutputFormatter.success({
452
+ Result: RESULTS.Success,
453
+ Code: OUTPUT_CODES.ChoiceSetDeleted,
454
+ Data: { ID: choiceSetId, Reason: reason },
455
+ });
456
+ },
457
+ );
458
+ };
459
+
460
+ const CHOICE_SET_VALUES_CREATE_EXAMPLES: CommandExample[] = [
461
+ {
462
+ Description:
463
+ "Add a value to a choice set. The returned 'numberId' is the integer to pass when inserting or updating records into a CHOICE_SET_SINGLE field (or in an array for CHOICE_SET_MULTIPLE).",
464
+ Command:
465
+ 'uip df choice-set-values create c1d2e3f4-0000-0000-0000-000000000001 travel --display-name "Travel"',
466
+ Output: {
467
+ Code: OUTPUT_CODES.ChoiceSetValueCreated,
468
+ Data: {
469
+ id: "v1000000-0000-0000-0000-000000000001",
470
+ name: "travel",
471
+ displayName: "Travel",
472
+ numberId: 0,
473
+ },
474
+ },
475
+ },
476
+ ];
477
+
478
+ const CHOICE_SET_VALUES_UPDATE_EXAMPLES: CommandExample[] = [
479
+ {
480
+ Description:
481
+ "Update the display name of an existing choice set value. Find value IDs with 'df choice-sets list-values <choice-set-id>'.",
482
+ Command:
483
+ 'uip df choice-set-values update c1d2e3f4-0000-0000-0000-000000000001 v1000000-0000-0000-0000-000000000001 "Business Travel"',
484
+ Output: {
485
+ Code: OUTPUT_CODES.ChoiceSetValueUpdated,
486
+ Data: {
487
+ id: "v1000000-0000-0000-0000-000000000001",
488
+ name: "travel",
489
+ displayName: "Business Travel",
490
+ numberId: 0,
491
+ },
492
+ },
493
+ },
494
+ ];
495
+
496
+ const CHOICE_SET_VALUES_DELETE_EXAMPLES: CommandExample[] = [
497
+ {
498
+ Description:
499
+ "Delete one or more values from a choice set (irreversible — requires --confirm and --reason). Pass value IDs as a comma-separated list.",
500
+ Command:
501
+ 'uip df choice-set-values delete c1d2e3f4-0000-0000-0000-000000000001 --ids v1000000-0000-0000-0000-000000000001,v1000000-0000-0000-0000-000000000002 --confirm --reason "deprecated categories"',
502
+ Output: {
503
+ Code: OUTPUT_CODES.ChoiceSetValuesDeleted,
504
+ Data: {
505
+ ChoiceSetId: "c1d2e3f4-0000-0000-0000-000000000001",
506
+ DeletedIds: [
507
+ "v1000000-0000-0000-0000-000000000001",
508
+ "v1000000-0000-0000-0000-000000000002",
509
+ ],
510
+ Reason: "deprecated categories",
511
+ },
512
+ },
513
+ },
514
+ ];
515
+
516
+ export const registerChoiceSetValuesCommand = (program: Command) => {
517
+ const values = program
518
+ .command("choice-set-values")
519
+ .description(
520
+ "Manage the individual values of a Data Fabric choice set. " +
521
+ "Use 'df choice-sets list-values <choice-set-id>' to list existing values and their IDs.",
522
+ );
523
+
524
+ values
525
+ .command("create")
526
+ .description("Add a value to a Data Fabric choice set")
527
+ .argument("<choice-set-id>", "Choice set ID")
528
+ .argument(
529
+ "<name>",
530
+ "Value name (must start with a letter; letters, numbers, and underscores only)",
531
+ )
532
+ .addOption(
533
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
534
+ )
535
+ .option("--display-name <name>", "Human-readable display name")
536
+ .examples(CHOICE_SET_VALUES_CREATE_EXAMPLES)
537
+ .trackedAction(
538
+ processContext,
539
+ async (
540
+ choiceSetId: string,
541
+ name: string,
542
+ options: ValueCreateOptions,
543
+ ) => {
544
+ const sdk = await connectOrFail(options.tenant);
545
+ if (!sdk) return;
546
+
547
+ const insertOpts: ChoiceSetValueInsertOptions = {
548
+ ...(options.displayName !== undefined && {
549
+ displayName: options.displayName,
550
+ }),
551
+ };
552
+
553
+ const choiceSetService: ChoiceSetServiceModel =
554
+ sdk.entities.choicesets;
555
+ const [createError, result] = await catchError(
556
+ choiceSetService.insertValueById(
557
+ choiceSetId,
558
+ name,
559
+ Object.keys(insertOpts).length > 0
560
+ ? insertOpts
561
+ : undefined,
562
+ ),
563
+ );
564
+
565
+ if (createError) {
566
+ return fail(
567
+ `Error creating value in choice set '${choiceSetId}'`,
568
+ await extractErrorMessage(createError),
569
+ );
570
+ }
571
+
572
+ OutputFormatter.success({
573
+ Result: RESULTS.Success,
574
+ Code: OUTPUT_CODES.ChoiceSetValueCreated,
575
+ Data: result,
576
+ });
577
+ },
578
+ );
579
+
580
+ values
581
+ .command("update")
582
+ .description("Update the display name of a choice set value")
583
+ .argument("<choice-set-id>", "Choice set ID")
584
+ .argument("<value-id>", "Choice set value ID")
585
+ .argument("<display-name>", "New display name for the value")
586
+ .addOption(
587
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
588
+ )
589
+ .examples(CHOICE_SET_VALUES_UPDATE_EXAMPLES)
590
+ .trackedAction(
591
+ processContext,
592
+ async (
593
+ choiceSetId: string,
594
+ valueId: string,
595
+ displayName: string,
596
+ options: ValueUpdateOptions,
597
+ ) => {
598
+ const sdk = await connectOrFail(options.tenant);
599
+ if (!sdk) return;
600
+
601
+ const choiceSetService: ChoiceSetServiceModel =
602
+ sdk.entities.choicesets;
603
+ const [updateError, result] = await catchError(
604
+ choiceSetService.updateValueById(
605
+ choiceSetId,
606
+ valueId,
607
+ displayName,
608
+ ),
609
+ );
610
+
611
+ if (updateError) {
612
+ return fail(
613
+ `Error updating value '${valueId}' in choice set '${choiceSetId}'`,
614
+ await extractErrorMessage(updateError),
615
+ );
616
+ }
617
+
618
+ OutputFormatter.success({
619
+ Result: RESULTS.Success,
620
+ Code: OUTPUT_CODES.ChoiceSetValueUpdated,
621
+ Data: result,
622
+ });
623
+ },
624
+ );
625
+
626
+ values
627
+ .command("delete")
628
+ .description(
629
+ "Delete values from a Data Fabric choice set (irreversible)",
630
+ )
631
+ .argument("<choice-set-id>", "Choice set ID")
632
+ .addOption(
633
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
634
+ )
635
+ .option("--ids <ids>", "Comma-separated list of value IDs to delete")
636
+ .option("--confirm", "Acknowledge this is an irreversible operation")
637
+ .option(
638
+ "--reason <reason>",
639
+ "Reason for the deletion — echoed back in the response so the caller can log it",
640
+ )
641
+ .examples(CHOICE_SET_VALUES_DELETE_EXAMPLES)
642
+ .trackedAction(
643
+ processContext,
644
+ async (choiceSetId: string, options: ValueDeleteOptions) => {
645
+ const valueIds = (options.ids ?? "")
646
+ .split(",")
647
+ .map((id) => id.trim())
648
+ .filter((id) => id.length > 0);
649
+
650
+ if (valueIds.length === 0) {
651
+ return fail(
652
+ "No value IDs provided",
653
+ "Pass --ids <id,id,...> with at least one value ID to delete.",
654
+ );
655
+ }
656
+
657
+ const reason = requireDestructiveConfirmation(
658
+ options,
659
+ 'Pass --reason "<text>" to record why the values are being deleted.',
660
+ );
661
+ if (reason === null) return;
662
+
663
+ const sdk = await connectOrFail(options.tenant);
664
+ if (!sdk) return;
665
+
666
+ const choiceSetService: ChoiceSetServiceModel =
667
+ sdk.entities.choicesets;
668
+ const [deleteError] = await catchError(
669
+ choiceSetService.deleteValuesById(choiceSetId, valueIds),
670
+ );
671
+
672
+ if (deleteError) {
673
+ return fail(
674
+ `Error deleting values from choice set '${choiceSetId}'`,
675
+ await extractErrorMessage(deleteError),
676
+ );
677
+ }
265
678
 
266
679
  OutputFormatter.success({
267
680
  Result: RESULTS.Success,
268
- Code: "ChoiceSetValues",
681
+ Code: OUTPUT_CODES.ChoiceSetValuesDeleted,
269
682
  Data: {
270
- TotalCount: response.totalCount ?? values.length,
271
- Values: values,
272
- HasNextPage: response.hasNextPage ?? false,
273
- ...(nextCursor !== undefined && {
274
- NextCursor: nextCursor,
275
- }),
276
- ...(response.currentPage !== undefined && {
277
- CurrentPage: response.currentPage,
278
- }),
279
- ...(response.totalPages !== undefined && {
280
- TotalPages: response.totalPages,
281
- }),
683
+ ChoiceSetId: choiceSetId,
684
+ DeletedIds: valueIds,
685
+ Reason: reason,
282
686
  },
283
687
  });
284
688
  },