@uipath/data-fabric-tool 0.9.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.
@@ -0,0 +1,875 @@
1
+ import {
2
+ type CommandExample,
3
+ catchError,
4
+ extractErrorMessage,
5
+ OutputFormatter,
6
+ processContext,
7
+ RESULTS,
8
+ } from "@uipath/common";
9
+ import { getFileSystem } from "@uipath/filesystem";
10
+ import type { EntityRecord } from "@uipath/uipath-typescript";
11
+ import type { Command } from "commander";
12
+ import { readFileBinary, readJsonInput } from "../utils/input";
13
+ import { createDataFabricClient } from "../utils/sdk-client";
14
+
15
+ interface ListOptions {
16
+ tenant?: string;
17
+ limit: string;
18
+ cursor?: string;
19
+ }
20
+
21
+ interface GetOptions {
22
+ tenant?: string;
23
+ }
24
+
25
+ interface InsertOptions {
26
+ tenant?: string;
27
+ file?: string;
28
+ body?: string;
29
+ }
30
+
31
+ interface UpdateOptions {
32
+ tenant?: string;
33
+ file?: string;
34
+ body?: string;
35
+ }
36
+
37
+ interface DeleteOptions {
38
+ tenant?: string;
39
+ }
40
+
41
+ interface QueryOptions {
42
+ tenant?: string;
43
+ file?: string;
44
+ body?: string;
45
+ limit: string;
46
+ cursor?: string;
47
+ }
48
+
49
+ interface ImportOptions {
50
+ tenant?: string;
51
+ file?: string;
52
+ }
53
+
54
+ interface BatchResult {
55
+ successRecords?: unknown[];
56
+ failureRecords?: unknown[];
57
+ }
58
+
59
+ const RECORDS_LIST_EXAMPLES: CommandExample[] = [
60
+ {
61
+ Description: "List records in an entity",
62
+ Command:
63
+ "uip df records list a1b2c3d4-0000-0000-0000-000000000001 --limit 2",
64
+ Output: {
65
+ Code: "RecordList",
66
+ Data: {
67
+ TotalCount: 2,
68
+ Records: [
69
+ {
70
+ Id: "b2c3d4e5-0000-0000-0000-000000000001",
71
+ amount: 1500,
72
+ },
73
+ {
74
+ Id: "b2c3d4e5-0000-0000-0000-000000000002",
75
+ amount: 2750,
76
+ },
77
+ ],
78
+ HasNextPage: false,
79
+ },
80
+ },
81
+ },
82
+ ];
83
+
84
+ const RECORDS_GET_EXAMPLES: CommandExample[] = [
85
+ {
86
+ Description: "Get a record by ID",
87
+ Command:
88
+ "uip df records get a1b2c3d4-0000-0000-0000-000000000001 b2c3d4e5-0000-0000-0000-000000000001",
89
+ Output: {
90
+ Code: "RecordDetails",
91
+ Data: {
92
+ Id: "b2c3d4e5-0000-0000-0000-000000000001",
93
+ amount: 1500,
94
+ status: "Paid",
95
+ },
96
+ },
97
+ },
98
+ ];
99
+
100
+ const RECORDS_INSERT_EXAMPLES: CommandExample[] = [
101
+ {
102
+ Description: "Insert a record",
103
+ Command:
104
+ 'uip df records insert a1b2c3d4-0000-0000-0000-000000000001 --body \'{"amount":1500,"status":"New"}\'',
105
+ Output: {
106
+ Code: "RecordInserted",
107
+ Data: {
108
+ Id: "b2c3d4e5-0000-0000-0000-000000000010",
109
+ amount: 1500,
110
+ status: "New",
111
+ },
112
+ },
113
+ },
114
+ ];
115
+
116
+ const RECORDS_UPDATE_EXAMPLES: CommandExample[] = [
117
+ {
118
+ Description: "Update a record",
119
+ Command:
120
+ 'uip df records update a1b2c3d4-0000-0000-0000-000000000001 --body \'{"Id":"b2c3d4e5-0000-0000-0000-000000000001","status":"Paid"}\'',
121
+ Output: {
122
+ Code: "RecordUpdated",
123
+ Data: {
124
+ Id: "b2c3d4e5-0000-0000-0000-000000000001",
125
+ status: "Paid",
126
+ },
127
+ },
128
+ },
129
+ ];
130
+
131
+ const RECORDS_DELETE_EXAMPLES: CommandExample[] = [
132
+ {
133
+ Description: "Delete records by ID",
134
+ Command:
135
+ "uip df records delete a1b2c3d4-0000-0000-0000-000000000001 b2c3d4e5-0000-0000-0000-000000000001",
136
+ Output: {
137
+ Code: "RecordsDeleted",
138
+ Data: {
139
+ SuccessCount: 1,
140
+ FailureCount: 0,
141
+ SuccessRecords: [
142
+ { Id: "b2c3d4e5-0000-0000-0000-000000000001" },
143
+ ],
144
+ FailureRecords: [],
145
+ },
146
+ },
147
+ },
148
+ ];
149
+
150
+ export const registerRecordsCommand = (program: Command) => {
151
+ const records = program
152
+ .command("records")
153
+ .description("Manage Data Fabric entity records");
154
+
155
+ records
156
+ .command("list")
157
+ .description("List records in a Data Fabric entity")
158
+ .argument("<id>", "Entity ID")
159
+ .option("-t, --tenant <tenant-name>", "Tenant name")
160
+ .option(
161
+ "-l, --limit <number>",
162
+ "Number of records to return per page",
163
+ "50",
164
+ )
165
+ .option(
166
+ "--cursor <cursor>",
167
+ "Pagination cursor from a previous response to fetch the next page",
168
+ )
169
+ .examples(RECORDS_LIST_EXAMPLES)
170
+ .trackedAction(
171
+ processContext,
172
+ async (entityId: string, options: ListOptions) => {
173
+ const pageSize = parseInt(options.limit, 10);
174
+ if (Number.isNaN(pageSize) || pageSize < 1) {
175
+ OutputFormatter.error({
176
+ Result: RESULTS.Failure,
177
+ Message: "Invalid --limit value",
178
+ Instructions: "Provide a positive integer for --limit.",
179
+ });
180
+ processContext.exit(1);
181
+ return;
182
+ }
183
+
184
+ const [clientError, sdk] = await catchError(
185
+ createDataFabricClient(options.tenant),
186
+ );
187
+
188
+ if (clientError) {
189
+ OutputFormatter.error({
190
+ Result: RESULTS.Failure,
191
+ Message: "Error connecting to Data Fabric",
192
+ Instructions: await extractErrorMessage(clientError),
193
+ });
194
+ processContext.exit(1);
195
+ return;
196
+ }
197
+
198
+ const [listError, result] = await catchError(
199
+ sdk.entities.getAllRecords(entityId, {
200
+ pageSize,
201
+ ...(options.cursor !== undefined && {
202
+ cursor: { value: options.cursor },
203
+ }),
204
+ }),
205
+ );
206
+
207
+ if (listError) {
208
+ OutputFormatter.error({
209
+ Result: RESULTS.Failure,
210
+ Message: "Error listing records",
211
+ Instructions: await extractErrorMessage(listError),
212
+ });
213
+ processContext.exit(1);
214
+ return;
215
+ }
216
+
217
+ const r = result as {
218
+ items: unknown[];
219
+ totalCount?: number;
220
+ hasNextPage?: boolean;
221
+ nextCursor?: unknown;
222
+ currentPage?: number;
223
+ totalPages?: number;
224
+ };
225
+ const nextCursor = extractCursorValue(r.nextCursor);
226
+
227
+ OutputFormatter.success({
228
+ Result: RESULTS.Success,
229
+ Code: "RecordList",
230
+ Data: {
231
+ TotalCount: r.totalCount ?? r.items.length,
232
+ Records: r.items,
233
+ HasNextPage: r.hasNextPage ?? false,
234
+ ...(nextCursor !== undefined && {
235
+ NextCursor: nextCursor,
236
+ }),
237
+ ...(r.currentPage !== undefined && {
238
+ CurrentPage: r.currentPage,
239
+ }),
240
+ ...(r.totalPages !== undefined && {
241
+ TotalPages: r.totalPages,
242
+ }),
243
+ },
244
+ });
245
+ },
246
+ );
247
+
248
+ records
249
+ .command("get")
250
+ .description("Get a single record by ID")
251
+ .argument("<id>", "Entity ID")
252
+ .argument("<key>", "Record ID")
253
+ .option("-t, --tenant <tenant-name>", "Tenant name")
254
+ .examples(RECORDS_GET_EXAMPLES)
255
+ .trackedAction(
256
+ processContext,
257
+ async (entityId: string, recordId: string, options: GetOptions) => {
258
+ const [clientError, sdk] = await catchError(
259
+ createDataFabricClient(options.tenant),
260
+ );
261
+
262
+ if (clientError) {
263
+ OutputFormatter.error({
264
+ Result: RESULTS.Failure,
265
+ Message: "Error connecting to Data Fabric",
266
+ Instructions: await extractErrorMessage(clientError),
267
+ });
268
+ processContext.exit(1);
269
+ return;
270
+ }
271
+
272
+ const [getError, record] = await catchError(
273
+ sdk.entities.getRecordById(entityId, recordId),
274
+ );
275
+
276
+ if (getError) {
277
+ OutputFormatter.error({
278
+ Result: RESULTS.Failure,
279
+ Message: `Error getting record '${recordId}'`,
280
+ Instructions: await extractErrorMessage(getError),
281
+ });
282
+ processContext.exit(1);
283
+ return;
284
+ }
285
+
286
+ if (!record) {
287
+ OutputFormatter.error({
288
+ Result: RESULTS.Failure,
289
+ Message: `Record '${recordId}' not found in entity '${entityId}'`,
290
+ Instructions:
291
+ "Verify the record ID exists. Use 'df records list' to see available records.",
292
+ });
293
+ processContext.exit(1);
294
+ return;
295
+ }
296
+
297
+ OutputFormatter.success({
298
+ Result: RESULTS.Success,
299
+ Code: "RecordDetails",
300
+ Data: record as Record<string, unknown>,
301
+ });
302
+ },
303
+ );
304
+
305
+ records
306
+ .command("insert")
307
+ .description("Insert records into a Data Fabric entity")
308
+ .argument("<id>", "Entity ID")
309
+ .option("-t, --tenant <tenant-name>", "Tenant name")
310
+ .option(
311
+ "-f, --file <path>",
312
+ "Path to JSON file with record data (object or array of objects)",
313
+ )
314
+ .option(
315
+ "--body <json>",
316
+ "Inline JSON record data (object or array of objects)",
317
+ )
318
+ .examples(RECORDS_INSERT_EXAMPLES)
319
+ .trackedAction(
320
+ processContext,
321
+ async (entityId: string, options: InsertOptions) => {
322
+ const [parseError, rawData] = await catchError(
323
+ resolveJsonInput(options.file, options.body),
324
+ );
325
+
326
+ if (parseError) {
327
+ OutputFormatter.error({
328
+ Result: RESULTS.Failure,
329
+ Message: "Error parsing input data",
330
+ Instructions: parseError.message,
331
+ });
332
+ processContext.exit(1);
333
+ return;
334
+ }
335
+
336
+ const [clientError, sdk] = await catchError(
337
+ createDataFabricClient(options.tenant),
338
+ );
339
+
340
+ if (clientError) {
341
+ OutputFormatter.error({
342
+ Result: RESULTS.Failure,
343
+ Message: "Error connecting to Data Fabric",
344
+ Instructions: await extractErrorMessage(clientError),
345
+ });
346
+ processContext.exit(1);
347
+ return;
348
+ }
349
+
350
+ const data = rawData as
351
+ | Record<string, unknown>
352
+ | Record<string, unknown>[];
353
+ const recordsList = Array.isArray(data) ? data : [data];
354
+
355
+ if (recordsList.length === 1) {
356
+ const [insertError, result] = await catchError(
357
+ sdk.entities.insertRecordById(entityId, recordsList[0]),
358
+ );
359
+
360
+ if (insertError) {
361
+ OutputFormatter.error({
362
+ Result: RESULTS.Failure,
363
+ Message: "Error inserting record",
364
+ Instructions:
365
+ await extractErrorMessage(insertError),
366
+ });
367
+ processContext.exit(1);
368
+ return;
369
+ }
370
+
371
+ OutputFormatter.success({
372
+ Result: RESULTS.Success,
373
+ Code: "RecordInserted",
374
+ Data: result as Record<string, unknown>,
375
+ });
376
+ } else {
377
+ const [insertError, result] = await catchError(
378
+ sdk.entities.insertRecordsById(entityId, recordsList),
379
+ );
380
+
381
+ if (insertError) {
382
+ OutputFormatter.error({
383
+ Result: RESULTS.Failure,
384
+ Message: "Error inserting records",
385
+ Instructions:
386
+ await extractErrorMessage(insertError),
387
+ });
388
+ processContext.exit(1);
389
+ return;
390
+ }
391
+
392
+ const r = result as BatchResult;
393
+ const failureCount = r.failureRecords?.length ?? 0;
394
+ OutputFormatter.success({
395
+ Result: RESULTS.Success,
396
+ Code: "RecordsBatchInserted",
397
+ Data: {
398
+ SuccessCount: r.successRecords?.length ?? 0,
399
+ FailureCount: failureCount,
400
+ SuccessRecords: r.successRecords ?? [],
401
+ FailureRecords: r.failureRecords ?? [],
402
+ },
403
+ });
404
+ if (failureCount > 0) {
405
+ processContext.exit(1);
406
+ }
407
+ }
408
+ },
409
+ );
410
+
411
+ records
412
+ .command("update")
413
+ .description("Update records in a Data Fabric entity")
414
+ .argument("<id>", "Entity ID")
415
+ .option("-t, --tenant <tenant-name>", "Tenant name")
416
+ .option(
417
+ "-f, --file <path>",
418
+ "Path to JSON file with record data (must include Id field)",
419
+ )
420
+ .option(
421
+ "--body <json>",
422
+ "Inline JSON record data (must include Id field)",
423
+ )
424
+ .examples(RECORDS_UPDATE_EXAMPLES)
425
+ .trackedAction(
426
+ processContext,
427
+ async (entityId: string, options: UpdateOptions) => {
428
+ const [parseError, rawData] = await catchError(
429
+ resolveJsonInput(options.file, options.body),
430
+ );
431
+
432
+ if (parseError) {
433
+ OutputFormatter.error({
434
+ Result: RESULTS.Failure,
435
+ Message: "Error parsing input data",
436
+ Instructions: parseError.message,
437
+ });
438
+ processContext.exit(1);
439
+ return;
440
+ }
441
+
442
+ const [clientError, sdk] = await catchError(
443
+ createDataFabricClient(options.tenant),
444
+ );
445
+
446
+ if (clientError) {
447
+ OutputFormatter.error({
448
+ Result: RESULTS.Failure,
449
+ Message: "Error connecting to Data Fabric",
450
+ Instructions: await extractErrorMessage(clientError),
451
+ });
452
+ processContext.exit(1);
453
+ return;
454
+ }
455
+
456
+ const data = rawData as
457
+ | Record<string, unknown>
458
+ | Record<string, unknown>[];
459
+ const recordsList = Array.isArray(data) ? data : [data];
460
+
461
+ if (recordsList.length === 1) {
462
+ const record = recordsList[0];
463
+ const recordId =
464
+ record.Id !== undefined ? record.Id : record.id;
465
+ if (recordId === undefined || recordId === null) {
466
+ OutputFormatter.error({
467
+ Result: RESULTS.Failure,
468
+ Message: "Record must include an 'Id' field",
469
+ Instructions:
470
+ "Provide the record ID in the JSON data.",
471
+ });
472
+ processContext.exit(1);
473
+ return;
474
+ }
475
+
476
+ const [updateError, result] = await catchError(
477
+ sdk.entities.updateRecordById(
478
+ entityId,
479
+ String(recordId),
480
+ record,
481
+ ),
482
+ );
483
+
484
+ if (updateError) {
485
+ OutputFormatter.error({
486
+ Result: RESULTS.Failure,
487
+ Message: "Error updating record",
488
+ Instructions:
489
+ await extractErrorMessage(updateError),
490
+ });
491
+ processContext.exit(1);
492
+ return;
493
+ }
494
+
495
+ OutputFormatter.success({
496
+ Result: RESULTS.Success,
497
+ Code: "RecordUpdated",
498
+ Data: result as Record<string, unknown>,
499
+ });
500
+ } else {
501
+ const missingId = recordsList.find(
502
+ (r) => r.Id == null && r.id == null,
503
+ );
504
+ if (missingId) {
505
+ OutputFormatter.error({
506
+ Result: RESULTS.Failure,
507
+ Message: "All records must include an 'Id' field",
508
+ Instructions:
509
+ "Provide the record ID in each JSON object.",
510
+ });
511
+ processContext.exit(1);
512
+ return;
513
+ }
514
+
515
+ const [updateError, result] = await catchError(
516
+ sdk.entities.updateRecordsById(
517
+ entityId,
518
+ recordsList as EntityRecord[],
519
+ ),
520
+ );
521
+
522
+ if (updateError) {
523
+ OutputFormatter.error({
524
+ Result: RESULTS.Failure,
525
+ Message: "Error updating records",
526
+ Instructions:
527
+ await extractErrorMessage(updateError),
528
+ });
529
+ processContext.exit(1);
530
+ return;
531
+ }
532
+
533
+ const r = result as BatchResult;
534
+ const failureCount = r.failureRecords?.length ?? 0;
535
+ OutputFormatter.success({
536
+ Result: RESULTS.Success,
537
+ Code: "RecordsBatchUpdated",
538
+ Data: {
539
+ SuccessCount: r.successRecords?.length ?? 0,
540
+ FailureCount: failureCount,
541
+ SuccessRecords: r.successRecords ?? [],
542
+ FailureRecords: r.failureRecords ?? [],
543
+ },
544
+ });
545
+ if (failureCount > 0) {
546
+ processContext.exit(1);
547
+ }
548
+ }
549
+ },
550
+ );
551
+
552
+ records
553
+ .command("query")
554
+ .description(
555
+ "Query records in a Data Fabric entity with filters and sorting. " +
556
+ "Provide a JSON object via --body or --file with optional keys: " +
557
+ "filterGroup, sortOptions (use isDescending: true/false), selectedFields.",
558
+ )
559
+ .argument("<id>", "Entity ID")
560
+ .option("-t, --tenant <tenant-name>", "Tenant name")
561
+ .option(
562
+ "-f, --file <path>",
563
+ "Path to JSON file with query options (filterGroup, selectedFields, sortOptions)",
564
+ )
565
+ .option(
566
+ "--body <json>",
567
+ "Inline JSON query options (filterGroup, selectedFields, sortOptions)",
568
+ )
569
+ .option(
570
+ "-l, --limit <number>",
571
+ "Number of records to return per page",
572
+ "50",
573
+ )
574
+ .option(
575
+ "--cursor <cursor>",
576
+ "Pagination cursor from a previous response to fetch the next page",
577
+ )
578
+ .trackedAction(
579
+ processContext,
580
+ async (entityId: string, options: QueryOptions) => {
581
+ const pageSize = parseInt(options.limit, 10);
582
+ if (Number.isNaN(pageSize) || pageSize < 1) {
583
+ OutputFormatter.error({
584
+ Result: RESULTS.Failure,
585
+ Message: "Invalid --limit value",
586
+ Instructions: "Provide a positive integer for --limit.",
587
+ });
588
+ processContext.exit(1);
589
+ return;
590
+ }
591
+
592
+ let queryBody: Record<string, unknown> | null = null;
593
+ if (options.file !== undefined || options.body !== undefined) {
594
+ const [parseError, parsed] = await catchError(
595
+ readJsonInput(
596
+ options.file,
597
+ options.body,
598
+ "Provide a JSON object with query options via --file or --body.",
599
+ ),
600
+ );
601
+ if (parseError) {
602
+ OutputFormatter.error({
603
+ Result: RESULTS.Failure,
604
+ Message: "Error parsing query options",
605
+ Instructions: parseError.message,
606
+ });
607
+ processContext.exit(1);
608
+ return;
609
+ }
610
+ if (
611
+ typeof parsed !== "object" ||
612
+ parsed === null ||
613
+ Array.isArray(parsed)
614
+ ) {
615
+ OutputFormatter.error({
616
+ Result: RESULTS.Failure,
617
+ Message: "Query options must be a JSON object",
618
+ Instructions:
619
+ "Provide a JSON object (not an array) with filterGroup, selectedFields, or sortOptions.",
620
+ });
621
+ processContext.exit(1);
622
+ return;
623
+ }
624
+ queryBody = parsed as Record<string, unknown>;
625
+ }
626
+
627
+ const [clientError, sdk] = await catchError(
628
+ createDataFabricClient(options.tenant),
629
+ );
630
+
631
+ if (clientError) {
632
+ OutputFormatter.error({
633
+ Result: RESULTS.Failure,
634
+ Message: "Error connecting to Data Fabric",
635
+ Instructions: await extractErrorMessage(clientError),
636
+ });
637
+ processContext.exit(1);
638
+ return;
639
+ }
640
+
641
+ const [queryError, result] = await catchError(
642
+ sdk.entities.queryRecordsById(entityId, {
643
+ ...(queryBody !== null && queryBody),
644
+ pageSize,
645
+ ...(options.cursor !== undefined && {
646
+ cursor: { value: options.cursor },
647
+ }),
648
+ }),
649
+ );
650
+
651
+ if (queryError) {
652
+ OutputFormatter.error({
653
+ Result: RESULTS.Failure,
654
+ Message: "Error querying records",
655
+ Instructions: await extractErrorMessage(queryError),
656
+ });
657
+ processContext.exit(1);
658
+ return;
659
+ }
660
+
661
+ const r = result as {
662
+ items: unknown[];
663
+ totalCount?: number;
664
+ hasNextPage?: boolean;
665
+ nextCursor?: unknown;
666
+ currentPage?: number;
667
+ totalPages?: number;
668
+ };
669
+ const nextCursor = extractCursorValue(r.nextCursor);
670
+
671
+ OutputFormatter.success({
672
+ Result: RESULTS.Success,
673
+ Code: "RecordQuery",
674
+ Data: {
675
+ TotalCount: r.totalCount ?? r.items.length,
676
+ Records: r.items,
677
+ HasNextPage: r.hasNextPage ?? false,
678
+ ...(nextCursor !== undefined && {
679
+ NextCursor: nextCursor,
680
+ }),
681
+ ...(r.currentPage !== undefined && {
682
+ CurrentPage: r.currentPage,
683
+ }),
684
+ ...(r.totalPages !== undefined && {
685
+ TotalPages: r.totalPages,
686
+ }),
687
+ },
688
+ });
689
+ },
690
+ );
691
+
692
+ records
693
+ .command("import")
694
+ .description("Import records from a CSV file into a Data Fabric entity")
695
+ .argument("<id>", "Entity ID")
696
+ .option("-t, --tenant <tenant-name>", "Tenant name")
697
+ .option("-f, --file <path>", "Path to the CSV file to import")
698
+ .trackedAction(
699
+ processContext,
700
+ async (entityId: string, options: ImportOptions) => {
701
+ if (options.file === undefined) {
702
+ OutputFormatter.error({
703
+ Result: RESULTS.Failure,
704
+ Message: "A CSV file path is required",
705
+ Instructions: "Provide a CSV file path via --file.",
706
+ });
707
+ processContext.exit(1);
708
+ return;
709
+ }
710
+
711
+ const [readError, fileContent] = await catchError(
712
+ readFileBinary(options.file),
713
+ );
714
+
715
+ if (readError) {
716
+ OutputFormatter.error({
717
+ Result: RESULTS.Failure,
718
+ Message: "Error reading CSV file",
719
+ Instructions: readError.message,
720
+ });
721
+ processContext.exit(1);
722
+ return;
723
+ }
724
+
725
+ const [clientError, sdk] = await catchError(
726
+ createDataFabricClient(options.tenant),
727
+ );
728
+
729
+ if (clientError) {
730
+ OutputFormatter.error({
731
+ Result: RESULTS.Failure,
732
+ Message: "Error connecting to Data Fabric",
733
+ Instructions: await extractErrorMessage(clientError),
734
+ });
735
+ processContext.exit(1);
736
+ return;
737
+ }
738
+
739
+ const fs = getFileSystem();
740
+ const fileName = fs.path.basename(options.file) || "import.csv";
741
+ const csvFile = new File(
742
+ [fileContent as Uint8Array<ArrayBuffer>],
743
+ fileName,
744
+ { type: "text/csv" },
745
+ );
746
+ const [importError, result] = await catchError(
747
+ sdk.entities.importRecordsById(entityId, csvFile),
748
+ );
749
+
750
+ if (importError) {
751
+ OutputFormatter.error({
752
+ Result: RESULTS.Failure,
753
+ Message: "Error importing records",
754
+ Instructions: await extractErrorMessage(importError),
755
+ });
756
+ processContext.exit(1);
757
+ return;
758
+ }
759
+
760
+ const r = result as {
761
+ insertedRecords?: number;
762
+ totalRecords?: number;
763
+ errorFileLink?: string;
764
+ };
765
+
766
+ OutputFormatter.success({
767
+ Result: RESULTS.Success,
768
+ Code: "RecordsImported",
769
+ Data: {
770
+ InsertedRecords: r.insertedRecords ?? 0,
771
+ TotalRecords: r.totalRecords ?? 0,
772
+ ...(r.errorFileLink !== undefined && {
773
+ ErrorFileLink: r.errorFileLink,
774
+ }),
775
+ },
776
+ });
777
+ },
778
+ );
779
+
780
+ records
781
+ .command("delete")
782
+ .description("Delete records from a Data Fabric entity")
783
+ .argument("<id>", "Entity ID")
784
+ .argument("<key...>", "Record IDs to delete")
785
+ .option("-t, --tenant <tenant-name>", "Tenant name")
786
+ .examples(RECORDS_DELETE_EXAMPLES)
787
+ .trackedAction(
788
+ processContext,
789
+ async (
790
+ entityId: string,
791
+ recordIds: string[],
792
+ options: DeleteOptions,
793
+ ) => {
794
+ const [clientError, sdk] = await catchError(
795
+ createDataFabricClient(options.tenant),
796
+ );
797
+
798
+ if (clientError) {
799
+ OutputFormatter.error({
800
+ Result: RESULTS.Failure,
801
+ Message: "Error connecting to Data Fabric",
802
+ Instructions: await extractErrorMessage(clientError),
803
+ });
804
+ processContext.exit(1);
805
+ return;
806
+ }
807
+
808
+ const [deleteError, result] = await catchError(
809
+ sdk.entities.deleteRecordsById(entityId, recordIds),
810
+ );
811
+
812
+ if (deleteError) {
813
+ OutputFormatter.error({
814
+ Result: RESULTS.Failure,
815
+ Message: "Error deleting records",
816
+ Instructions: await extractErrorMessage(deleteError),
817
+ });
818
+ processContext.exit(1);
819
+ return;
820
+ }
821
+
822
+ const r = result as BatchResult;
823
+ const failureCount = r.failureRecords?.length ?? 0;
824
+ OutputFormatter.success({
825
+ Result: RESULTS.Success,
826
+ Code: "RecordsDeleted",
827
+ Data: {
828
+ SuccessCount: r.successRecords?.length ?? 0,
829
+ FailureCount: failureCount,
830
+ SuccessRecords: r.successRecords ?? [],
831
+ FailureRecords: r.failureRecords ?? [],
832
+ },
833
+ });
834
+ if (failureCount > 0) {
835
+ processContext.exit(1);
836
+ }
837
+ },
838
+ );
839
+ };
840
+
841
+ /** Normalises the SDK cursor to a plain string for CLI output.
842
+ * The SDK returns `PaginationCursor = { value: string }` but we want
843
+ * to expose just the string so the user can pass it directly to --cursor. */
844
+ function extractCursorValue(cursor: unknown): string | undefined {
845
+ if (cursor === undefined || cursor === null) return undefined;
846
+ if (typeof cursor === "string") return cursor;
847
+ const c = cursor as Record<string, unknown>;
848
+ if (typeof c.value === "string") return c.value;
849
+ return undefined;
850
+ }
851
+
852
+ const isRecordObject = (value: unknown): value is Record<string, unknown> =>
853
+ typeof value === "object" && value !== null && !Array.isArray(value);
854
+
855
+ async function resolveJsonInput(
856
+ filePath?: string,
857
+ inlineBody?: string,
858
+ ): Promise<Record<string, unknown> | Record<string, unknown>[]> {
859
+ const parsed = await readJsonInput(
860
+ filePath,
861
+ inlineBody,
862
+ "Provide either --file <path> or --body <json> with record data.",
863
+ );
864
+ if (
865
+ (!Array.isArray(parsed) && !isRecordObject(parsed)) ||
866
+ (Array.isArray(parsed) &&
867
+ parsed.some((item: unknown) => !isRecordObject(item)))
868
+ ) {
869
+ throw new Error(
870
+ "Input must be a JSON object or an array of JSON objects.",
871
+ );
872
+ }
873
+
874
+ return parsed as Record<string, unknown> | Record<string, unknown>[];
875
+ }