@uipath/data-fabric-tool 1.0.4 → 1.1.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,6 +1,7 @@
1
1
  import {
2
2
  type CommandExample,
3
3
  catchError,
4
+ createHiddenDeprecatedTenantOption,
4
5
  extractErrorMessage,
5
6
  OutputFormatter,
6
7
  processContext,
@@ -130,6 +131,22 @@ const ENTITIES_DELETE_EXAMPLES: CommandExample[] = [
130
131
  },
131
132
  ];
132
133
 
134
+ const ENTITIES_CREATE_EXAMPLES: CommandExample[] = [
135
+ {
136
+ Description:
137
+ "Create an entity with a choice-set field (single-select and multi-select) and a relationship to another entity. " +
138
+ "CHOICE_SET_SINGLE/CHOICE_SET_MULTIPLE require 'choiceSetId' (from 'df choice-sets list'). " +
139
+ "RELATIONSHIP requires 'referenceEntityName' (target entity) and 'referenceFieldName' (the field used to resolve joins on read; e.g. 'Id' or a unique key like 'Email'). " +
140
+ "Note: regardless of 'referenceFieldName', a relationship column on a record always stores the target record's UUID 'Id' — see 'df records insert' for how to write the value.",
141
+ Command:
142
+ 'uip df entities create Expense --body \'{"displayName":"Expense","fields":[{"fieldName":"category","type":"CHOICE_SET_SINGLE","choiceSetId":"c1d2e3f4-0000-0000-0000-000000000001","isRequired":true},{"fieldName":"tags","type":"CHOICE_SET_MULTIPLE","choiceSetId":"c1d2e3f4-0000-0000-0000-000000000002"},{"fieldName":"submitter","type":"RELATIONSHIP","referenceEntityName":"Employee","referenceFieldName":"Id","isRequired":true}]}\'',
143
+ Output: {
144
+ Code: "EntityCreated",
145
+ Data: { ID: "a1b2c3d4-0000-0000-0000-000000000004" },
146
+ },
147
+ },
148
+ ];
149
+
133
150
  const VALID_FIELD_TYPES = new Set(Object.values(EntityFieldDataType));
134
151
  const VALID_FIELD_TYPES_LIST = [...VALID_FIELD_TYPES].join(", ");
135
152
 
@@ -170,7 +187,9 @@ export const registerEntitiesCommand = (program: Command) => {
170
187
  entities
171
188
  .command("list")
172
189
  .description("List all Data Fabric entities")
173
- .option("-t, --tenant <tenant-name>", "Tenant name")
190
+ .addOption(
191
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
192
+ )
174
193
  .option(
175
194
  "--native-only",
176
195
  "Show only native entities (exclude federated entities with external connections)",
@@ -237,7 +256,9 @@ export const registerEntitiesCommand = (program: Command) => {
237
256
  .command("get")
238
257
  .description("Get schema details of a Data Fabric entity")
239
258
  .argument("<id>", "Entity ID")
240
- .option("-t, --tenant <tenant-name>", "Tenant name")
259
+ .addOption(
260
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
261
+ )
241
262
  .examples(ENTITIES_GET_EXAMPLES)
242
263
  .trackedAction(
243
264
  processContext,
@@ -315,12 +336,15 @@ export const registerEntitiesCommand = (program: Command) => {
315
336
  "<name>",
316
337
  "Entity name (must start with a letter; letters, numbers, and underscores only)",
317
338
  )
318
- .option("-t, --tenant <tenant-name>", "Tenant name")
339
+ .addOption(
340
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
341
+ )
319
342
  .option(
320
343
  "-f, --file <path>",
321
344
  "Path to JSON file with entity definition (fields array required; displayName, description, isRbacEnabled optional)",
322
345
  )
323
346
  .option("--body <json>", "Inline JSON entity definition")
347
+ .examples(ENTITIES_CREATE_EXAMPLES)
324
348
  .trackedAction(
325
349
  processContext,
326
350
  async (name: string, options: CreateOptions) => {
@@ -457,7 +481,9 @@ export const registerEntitiesCommand = (program: Command) => {
457
481
  .command("update")
458
482
  .description("Update schema or metadata of a Data Fabric entity")
459
483
  .argument("<id>", "Entity ID")
460
- .option("-t, --tenant <tenant-name>", "Tenant name")
484
+ .addOption(
485
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
486
+ )
461
487
  .option(
462
488
  "-f, --file <path>",
463
489
  "Path to JSON file with update options (addFields, updateFields, removeFields, displayName, description, isRbacEnabled)",
@@ -739,7 +765,9 @@ export const registerEntitiesCommand = (program: Command) => {
739
765
  .command("delete")
740
766
  .description("Delete a Data Fabric entity (irreversible)")
741
767
  .argument("<id>", "Entity ID")
742
- .option("-t, --tenant <tenant-name>", "Tenant name")
768
+ .addOption(
769
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
770
+ )
743
771
  .option("--confirm", "Acknowledge this is an irreversible operation")
744
772
  .option(
745
773
  "--reason <reason>",
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  type CommandExample,
3
3
  catchError,
4
+ createHiddenDeprecatedTenantOption,
4
5
  extractErrorMessage,
5
6
  OutputFormatter,
6
7
  processContext,
@@ -86,7 +87,9 @@ export const registerFilesCommand = (program: Command) => {
86
87
  .argument("<entity-id>", "Entity ID")
87
88
  .argument("<record-id>", "Record ID")
88
89
  .argument("<field-name>", "Name of the file field (case-sensitive)")
89
- .option("-t, --tenant <tenant-name>", "Tenant name")
90
+ .addOption(
91
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
92
+ )
90
93
  .option("-f, --file <path>", "Path to the file to upload")
91
94
  .examples(FILES_UPLOAD_EXAMPLES)
92
95
  .trackedAction(
@@ -179,7 +182,9 @@ export const registerFilesCommand = (program: Command) => {
179
182
  .argument("<entity-id>", "Entity ID")
180
183
  .argument("<record-id>", "Record ID")
181
184
  .argument("<field-name>", "Name of the file field (case-sensitive)")
182
- .option("-t, --tenant <tenant-name>", "Tenant name")
185
+ .addOption(
186
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
187
+ )
183
188
  .option(
184
189
  "--destination <path>",
185
190
  "Output file path (defaults to <record-id>_<field-name>.bin)",
@@ -275,7 +280,9 @@ export const registerFilesCommand = (program: Command) => {
275
280
  .argument("<entity-id>", "Entity ID")
276
281
  .argument("<record-id>", "Record ID")
277
282
  .argument("<field-name>", "Name of the file field (case-sensitive)")
278
- .option("-t, --tenant <tenant-name>", "Tenant name")
283
+ .addOption(
284
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
285
+ )
279
286
  .examples(FILES_DELETE_EXAMPLES)
280
287
  .trackedAction(
281
288
  processContext,
@@ -701,6 +701,75 @@ describe("records insert", () => {
701
701
  expect(process.exitCode).toBe(1);
702
702
  });
703
703
 
704
+ it("should pass choice-set NumberId integers and a relationship UUID through to the SDK on single insert", async () => {
705
+ const sdk = mockSdk();
706
+ vi.mocked(sdk.entities.insertRecordById).mockResolvedValue({
707
+ Id: "new-rec",
708
+ });
709
+
710
+ const program = buildProgram();
711
+ await program.parseAsync([
712
+ "node",
713
+ "test",
714
+ "records",
715
+ "insert",
716
+ "entity-id",
717
+ "--body",
718
+ '{"title":"Flight to NYC","amount":420.5,"opType":0,"multiOpType":[0,1],"assignee":"AEDE69C3-EFD1-4897-B216-019E1A7E9269","rel1":"B065E2A8-F33B-4721-8228-019DE0C593AF"}',
719
+ ]);
720
+
721
+ expect(sdk.entities.insertRecordById).toHaveBeenCalledWith(
722
+ "entity-id",
723
+ expect.objectContaining({
724
+ opType: 0,
725
+ multiOpType: [0, 1],
726
+ assignee: "AEDE69C3-EFD1-4897-B216-019E1A7E9269",
727
+ rel1: "B065E2A8-F33B-4721-8228-019DE0C593AF",
728
+ }),
729
+ );
730
+ expect(OutputFormatter.success).toHaveBeenCalledWith(
731
+ expect.objectContaining({ Code: "RecordInserted" }),
732
+ );
733
+ });
734
+
735
+ it("should pass choice-set and relationship values through unchanged on batch insert", async () => {
736
+ const sdk = mockSdk();
737
+ vi.mocked(sdk.entities.insertRecordsById).mockResolvedValue({
738
+ successRecords: [{ Id: "rec-a" }, { Id: "rec-b" }],
739
+ failureRecords: [],
740
+ });
741
+
742
+ const program = buildProgram();
743
+ await program.parseAsync([
744
+ "node",
745
+ "test",
746
+ "records",
747
+ "insert",
748
+ "entity-id",
749
+ "--body",
750
+ '[{"opType":0,"multiOpType":[0],"assignee":"AEDE69C3-EFD1-4897-B216-019E1A7E9269"},{"opType":1,"multiOpType":[0,1],"assignee":"2481A9F6-E0F7-4996-8446-019E1A7E926A"}]',
751
+ ]);
752
+
753
+ expect(sdk.entities.insertRecordsById).toHaveBeenCalledWith(
754
+ "entity-id",
755
+ expect.arrayContaining([
756
+ expect.objectContaining({
757
+ opType: 0,
758
+ multiOpType: [0],
759
+ assignee: "AEDE69C3-EFD1-4897-B216-019E1A7E9269",
760
+ }),
761
+ expect.objectContaining({
762
+ opType: 1,
763
+ multiOpType: [0, 1],
764
+ assignee: "2481A9F6-E0F7-4996-8446-019E1A7E926A",
765
+ }),
766
+ ]),
767
+ );
768
+ expect(OutputFormatter.success).toHaveBeenCalledWith(
769
+ expect.objectContaining({ Code: "RecordsBatchInserted" }),
770
+ );
771
+ });
772
+
704
773
  it("should error on partial batch insert failure (FailureCount > 0)", async () => {
705
774
  const sdk = mockSdk();
706
775
  vi.mocked(sdk.entities.insertRecordsById).mockResolvedValue({
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  type CommandExample,
3
3
  catchError,
4
+ createHiddenDeprecatedTenantOption,
4
5
  extractErrorMessage,
5
6
  OutputFormatter,
6
7
  processContext,
@@ -10,6 +11,7 @@ import { getFileSystem } from "@uipath/filesystem";
10
11
  import type { EntityRecord } from "@uipath/uipath-typescript";
11
12
  import type { Command } from "commander";
12
13
  import { readFileBinary, readJsonInput } from "../utils/input";
14
+ import { extractCursorValue } from "../utils/pagination";
13
15
  import { createDataFabricClient } from "../utils/sdk-client";
14
16
 
15
17
  interface ListOptions {
@@ -113,6 +115,25 @@ const RECORDS_INSERT_EXAMPLES: CommandExample[] = [
113
115
  },
114
116
  },
115
117
  },
118
+ {
119
+ Description:
120
+ "Insert a record into an entity that has choice-set and relationship fields. " +
121
+ "CHOICE_SET_SINGLE → pass the choice value's 'NumberId' (integer, NOT the Name string — look it up via 'df choice-sets get <id>'). " +
122
+ "CHOICE_SET_MULTIPLE → pass an array of NumberId integers. " +
123
+ "RELATIONSHIP → always pass the target record's Id (UUID), even if the field was declared with a 'referenceFieldName' other than 'Id' (the referenceFieldName configures the join, not the stored value).",
124
+ Command:
125
+ 'uip df records insert a1b2c3d4-0000-0000-0000-000000000004 --body \'{"category":0,"tags":[1,3],"submitter":"e1f2a3b4-0000-0000-0000-000000000001","amount":250}\'',
126
+ Output: {
127
+ Code: "RecordInserted",
128
+ Data: {
129
+ Id: "b2c3d4e5-0000-0000-0000-000000000020",
130
+ category: 0,
131
+ tags: [1, 3],
132
+ submitter: "e1f2a3b4-0000-0000-0000-000000000001",
133
+ amount: 250,
134
+ },
135
+ },
136
+ },
116
137
  ];
117
138
 
118
139
  const RECORDS_UPDATE_EXAMPLES: CommandExample[] = [
@@ -158,7 +179,9 @@ export const registerRecordsCommand = (program: Command) => {
158
179
  .command("list")
159
180
  .description("List records in a Data Fabric entity")
160
181
  .argument("<id>", "Entity ID")
161
- .option("-t, --tenant <tenant-name>", "Tenant name")
182
+ .addOption(
183
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
184
+ )
162
185
  .option(
163
186
  "-l, --limit <number>",
164
187
  "Number of records to return per page",
@@ -287,7 +310,9 @@ export const registerRecordsCommand = (program: Command) => {
287
310
  .description("Get a single record by ID")
288
311
  .argument("<id>", "Entity ID")
289
312
  .argument("<key>", "Record ID")
290
- .option("-t, --tenant <tenant-name>", "Tenant name")
313
+ .addOption(
314
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
315
+ )
291
316
  .examples(RECORDS_GET_EXAMPLES)
292
317
  .trackedAction(
293
318
  processContext,
@@ -343,7 +368,9 @@ export const registerRecordsCommand = (program: Command) => {
343
368
  .command("insert")
344
369
  .description("Insert records into a Data Fabric entity")
345
370
  .argument("<id>", "Entity ID")
346
- .option("-t, --tenant <tenant-name>", "Tenant name")
371
+ .addOption(
372
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
373
+ )
347
374
  .option(
348
375
  "-f, --file <path>",
349
376
  "Path to JSON file with record data (object or array of objects)",
@@ -449,7 +476,9 @@ export const registerRecordsCommand = (program: Command) => {
449
476
  .command("update")
450
477
  .description("Update records in a Data Fabric entity")
451
478
  .argument("<id>", "Entity ID")
452
- .option("-t, --tenant <tenant-name>", "Tenant name")
479
+ .addOption(
480
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
481
+ )
453
482
  .option(
454
483
  "-f, --file <path>",
455
484
  "Path to JSON file with record data (must include Id field)",
@@ -595,7 +624,9 @@ export const registerRecordsCommand = (program: Command) => {
595
624
  "aggregates (function: COUNT/SUM/AVG/MIN/MAX, field, alias?), groupBy.",
596
625
  )
597
626
  .argument("<id>", "Entity ID")
598
- .option("-t, --tenant <tenant-name>", "Tenant name")
627
+ .addOption(
628
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
629
+ )
599
630
  .option(
600
631
  "-f, --file <path>",
601
632
  "Path to JSON file with query options (filterGroup, selectedFields, sortOptions, aggregates, groupBy)",
@@ -766,7 +797,9 @@ export const registerRecordsCommand = (program: Command) => {
766
797
  .command("import")
767
798
  .description("Import records from a CSV file into a Data Fabric entity")
768
799
  .argument("<id>", "Entity ID")
769
- .option("-t, --tenant <tenant-name>", "Tenant name")
800
+ .addOption(
801
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
802
+ )
770
803
  .option("-f, --file <path>", "Path to the CSV file to import")
771
804
  .trackedAction(
772
805
  processContext,
@@ -855,7 +888,9 @@ export const registerRecordsCommand = (program: Command) => {
855
888
  .description("Delete records from a Data Fabric entity")
856
889
  .argument("<id>", "Entity ID")
857
890
  .argument("<key...>", "Record IDs to delete")
858
- .option("-t, --tenant <tenant-name>", "Tenant name")
891
+ .addOption(
892
+ createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
893
+ )
859
894
  .examples(RECORDS_DELETE_EXAMPLES)
860
895
  .trackedAction(
861
896
  processContext,
@@ -911,17 +946,6 @@ export const registerRecordsCommand = (program: Command) => {
911
946
  );
912
947
  };
913
948
 
914
- /** Normalises the SDK cursor to a plain string for CLI output.
915
- * The SDK returns `PaginationCursor = { value: string }` but we want
916
- * to expose just the string so the user can pass it directly to --cursor. */
917
- function extractCursorValue(cursor: unknown): string | undefined {
918
- if (cursor === undefined || cursor === null) return undefined;
919
- if (typeof cursor === "string") return cursor;
920
- const c = cursor as Record<string, unknown>;
921
- if (typeof c.value === "string") return c.value;
922
- return undefined;
923
- }
924
-
925
949
  const isRecordObject = (value: unknown): value is Record<string, unknown> =>
926
950
  typeof value === "object" && value !== null && !Array.isArray(value);
927
951
 
package/src/index.ts CHANGED
@@ -1,17 +1,12 @@
1
1
  #!/usr/bin/env bun
2
2
  import { Command } from "commander";
3
- import pkg from "../package.json" with { type: "json" };
4
- import { registerEntitiesCommand } from "./commands/entities";
5
- import { registerFilesCommand } from "./commands/files";
6
- import { registerRecordsCommand } from "./commands/records";
3
+ import { metadata, registerCommands } from "./tool.js";
7
4
 
8
5
  const program = new Command();
9
6
  program
10
- .name("data-fabric-tool")
11
- .description("UiPath Data Fabric Tool - Standalone CLI")
12
- .version(pkg.version);
7
+ .name(metadata.name)
8
+ .description(metadata.description)
9
+ .version(metadata.version);
13
10
 
14
- registerEntitiesCommand(program);
15
- registerRecordsCommand(program);
16
- registerFilesCommand(program);
11
+ await registerCommands(program);
17
12
  await program.parseAsync(process.argv);
package/src/tool.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { Command } from "commander";
2
2
  import pkg from "../package.json" with { type: "json" };
3
+ import { registerChoiceSetsCommand } from "./commands/choice-sets";
3
4
  import { registerEntitiesCommand } from "./commands/entities";
4
5
  import { registerFilesCommand } from "./commands/files";
5
6
  import { registerRecordsCommand } from "./commands/records";
@@ -16,4 +17,5 @@ export const registerCommands = async (program: Command): Promise<void> => {
16
17
  registerEntitiesCommand(program);
17
18
  registerRecordsCommand(program);
18
19
  registerFilesCommand(program);
20
+ registerChoiceSetsCommand(program);
19
21
  };
@@ -0,0 +1,10 @@
1
+ /** Normalises an SDK pagination cursor to a plain string for CLI output.
2
+ * The SDK returns `PaginationCursor = { value: string }` but we want
3
+ * to expose just the string so the user can pass it directly to --cursor. */
4
+ export function extractCursorValue(cursor: unknown): string | undefined {
5
+ if (cursor === undefined || cursor === null) return undefined;
6
+ if (typeof cursor === "string") return cursor;
7
+ const c = cursor as Record<string, unknown>;
8
+ if (typeof c.value === "string") return c.value;
9
+ return undefined;
10
+ }