@uipath/data-fabric-tool 1.199.0 → 1.201.0-preview.115

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@uipath/data-fabric-tool",
3
3
  "license": "MIT",
4
- "version": "1.199.0",
4
+ "version": "1.201.0-preview.115",
5
5
  "description": "Manage Data Fabric entities and records.",
6
6
  "type": "module",
7
7
  "main": "./dist/tool.js",
@@ -14,5 +14,5 @@
14
14
  "publishConfig": {
15
15
  "registry": "https://registry.npmjs.org/"
16
16
  },
17
- "gitHead": "723e6801b77b5926ba75e75b6a756cc38b1b7adc"
17
+ "gitHead": "f1086b73654d7728cb71f280588b3e0c77d535fc"
18
18
  }
@@ -815,6 +815,50 @@ describe("choice-set-values", () => {
815
815
  expect(process.exitCode).toBe(1);
816
816
  });
817
817
 
818
+ it("should reject underscores in a choice-set value name", async () => {
819
+ const sdk = mockSdk();
820
+ const program = buildValuesProgram();
821
+ await program.parseAsync([
822
+ "node",
823
+ "test",
824
+ "choice-set-values",
825
+ "create",
826
+ "cs-1",
827
+ "business_travel",
828
+ ]);
829
+ expect(sdk.entities.choicesets.insertValueById).not.toHaveBeenCalled();
830
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
831
+ expect.objectContaining({
832
+ Result: "Failure",
833
+ Message: "Invalid choice-set value name 'business_travel'",
834
+ }),
835
+ );
836
+ expect(process.exitCode).toBe(1);
837
+ });
838
+
839
+ it("should reject an overlong choice-set value display name", async () => {
840
+ const sdk = mockSdk();
841
+ const program = buildValuesProgram();
842
+ await program.parseAsync([
843
+ "node",
844
+ "test",
845
+ "choice-set-values",
846
+ "create",
847
+ "cs-1",
848
+ "travel",
849
+ "--display-name",
850
+ "x".repeat(501),
851
+ ]);
852
+ expect(sdk.entities.choicesets.insertValueById).not.toHaveBeenCalled();
853
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
854
+ expect.objectContaining({
855
+ Result: "Failure",
856
+ Message: "Choice-set value display name exceeds 500 characters",
857
+ }),
858
+ );
859
+ expect(process.exitCode).toBe(1);
860
+ });
861
+
818
862
  it("should update a value's display name", async () => {
819
863
  const sdk = mockSdk();
820
864
  const program = buildValuesProgram();
@@ -21,7 +21,12 @@ import type {
21
21
  import { type Command, Option } from "commander";
22
22
  import { fail, requireDestructiveConfirmation } from "../utils/output";
23
23
  import { connectOrFail } from "../utils/sdk-client";
24
- import { validateChoiceValueName, validateName } from "../utils/validate";
24
+ import {
25
+ validateChoiceValueDisplayName,
26
+ validateChoiceValueName,
27
+ validateName,
28
+ validateSchemaMetadata,
29
+ } from "../utils/validate";
25
30
 
26
31
  const OUTPUT_CODES = {
27
32
  ChoiceSetList: "ChoiceSetList",
@@ -368,6 +373,15 @@ export const registerChoiceSetsCommand = (program: Command) => {
368
373
  async (name: string, options: CreateOptions) => {
369
374
  const nameErr = validateName(name, "choice set");
370
375
  if (nameErr) return fail(nameErr.message, nameErr.instructions);
376
+ const metadataErr = validateSchemaMetadata(
377
+ {
378
+ displayName: options.displayName,
379
+ description: options.description,
380
+ },
381
+ "Choice set",
382
+ );
383
+ if (metadataErr)
384
+ return fail(metadataErr.message, metadataErr.instructions);
371
385
 
372
386
  const sdk = await connectOrFail(options.tenant);
373
387
  if (!sdk) return;
@@ -441,6 +455,15 @@ export const registerChoiceSetsCommand = (program: Command) => {
441
455
  "The server rejects a partial update with 'DisplayName is required.' or Internal Server Error. To change one field, run 'uip df choice-sets list' first to read the current value of the other, then re-send both with --display-name and --description.",
442
456
  );
443
457
  }
458
+ const metadataErr = validateSchemaMetadata(
459
+ {
460
+ displayName: options.displayName,
461
+ description: options.description,
462
+ },
463
+ "Choice set",
464
+ );
465
+ if (metadataErr)
466
+ return fail(metadataErr.message, metadataErr.instructions);
444
467
 
445
468
  const sdk = await connectOrFail(options.tenant);
446
469
  if (!sdk) return;
@@ -614,7 +637,7 @@ export const registerChoiceSetValuesCommand = (program: Command) => {
614
637
  .argument("<choice-set-id>", "Choice set ID")
615
638
  .argument(
616
639
  "<name>",
617
- "Value name (must start with a letter; letters, numbers, and underscores only)",
640
+ "Value name (must start with a letter; letters and numbers only)",
618
641
  )
619
642
  .addOption(
620
643
  createHiddenDeprecatedTenantOption("-t, --tenant <tenant-name>"),
@@ -634,6 +657,16 @@ export const registerChoiceSetValuesCommand = (program: Command) => {
634
657
  ) => {
635
658
  const nameErr = validateChoiceValueName(name);
636
659
  if (nameErr) return fail(nameErr.message, nameErr.instructions);
660
+ if (options.displayName !== undefined) {
661
+ const displayErr = validateChoiceValueDisplayName(
662
+ options.displayName,
663
+ );
664
+ if (displayErr)
665
+ return fail(
666
+ displayErr.message,
667
+ displayErr.instructions,
668
+ );
669
+ }
637
670
 
638
671
  const sdk = await connectOrFail(options.tenant);
639
672
  if (!sdk) return;
@@ -696,6 +729,10 @@ export const registerChoiceSetValuesCommand = (program: Command) => {
696
729
  displayName: string,
697
730
  options: ValueUpdateOptions,
698
731
  ) => {
732
+ const displayErr = validateChoiceValueDisplayName(displayName);
733
+ if (displayErr)
734
+ return fail(displayErr.message, displayErr.instructions);
735
+
699
736
  const sdk = await connectOrFail(options.tenant);
700
737
  if (!sdk) return;
701
738
 
@@ -561,7 +561,7 @@ describe("entities create", () => {
561
561
  vi.mocked(sdk.entities.create).mockResolvedValue("entity-xfolder-rel");
562
562
  const fields = [
563
563
  {
564
- fieldName: "order",
564
+ fieldName: "purchaseOrder",
565
565
  type: "RELATIONSHIP",
566
566
  referenceEntityId: "a1b2c3d4-0000-0000-0000-000000000010",
567
567
  referenceFieldId: "f1000000-0000-0000-0000-000000000100",
@@ -587,7 +587,7 @@ describe("entities create", () => {
587
587
  "OrderLine",
588
588
  expect.arrayContaining([
589
589
  expect.objectContaining({
590
- fieldName: "order",
590
+ fieldName: "purchaseOrder",
591
591
  type: "RELATIONSHIP",
592
592
  referenceFolderKey: "f1000000-0000-0000-0000-000000000060",
593
593
  }),
@@ -826,6 +826,33 @@ describe("entities create", () => {
826
826
  expect(process.exitCode).toBe(1);
827
827
  });
828
828
 
829
+ it("should reject an entity name that is a confirmed SQL keyword", async () => {
830
+ const sdk = mockSdk();
831
+ vi.mocked(readJsonInput).mockResolvedValue({
832
+ fields: [{ fieldName: "title", type: "STRING" }],
833
+ });
834
+
835
+ const program = buildProgram();
836
+ await program.parseAsync([
837
+ "node",
838
+ "test",
839
+ "entities",
840
+ "create",
841
+ "Order",
842
+ "--body",
843
+ '{"fields":[{"fieldName":"title","type":"STRING"}]}',
844
+ ]);
845
+
846
+ expect(sdk.entities.create).not.toHaveBeenCalled();
847
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
848
+ expect.objectContaining({
849
+ Result: "Failure",
850
+ Message: expect.stringContaining("reserved SQL keyword"),
851
+ }),
852
+ );
853
+ expect(process.exitCode).toBe(1);
854
+ });
855
+
829
856
  it("should reject a fieldName that is a reserved system field", async () => {
830
857
  const sdk = mockSdk();
831
858
  vi.mocked(readJsonInput).mockResolvedValue({
@@ -853,6 +880,41 @@ describe("entities create", () => {
853
880
  expect(process.exitCode).toBe(1);
854
881
  });
855
882
 
883
+ it("should reject reference metadata on a FILE field", async () => {
884
+ const sdk = mockSdk();
885
+ vi.mocked(readJsonInput).mockResolvedValue({
886
+ fields: [
887
+ {
888
+ fieldName: "receipt",
889
+ type: "FILE",
890
+ referenceFolderKey: "folder-id",
891
+ },
892
+ ],
893
+ });
894
+
895
+ const program = buildProgram();
896
+ await program.parseAsync([
897
+ "node",
898
+ "test",
899
+ "entities",
900
+ "create",
901
+ "MyEntity",
902
+ "--body",
903
+ '{"fields":[{"fieldName":"receipt","type":"FILE","referenceFolderKey":"folder-id"}]}',
904
+ ]);
905
+
906
+ expect(sdk.entities.create).not.toHaveBeenCalled();
907
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
908
+ expect.objectContaining({
909
+ Result: "Failure",
910
+ Message: expect.stringContaining(
911
+ "FILE field 'receipt' must not pass reference fields",
912
+ ),
913
+ }),
914
+ );
915
+ expect(process.exitCode).toBe(1);
916
+ });
917
+
856
918
  it("should reject a UI-broken field type", async () => {
857
919
  const sdk = mockSdk();
858
920
  vi.mocked(readJsonInput).mockResolvedValue({
@@ -958,7 +1020,7 @@ describe("entities update", () => {
958
1020
  const sdk = mockSdk();
959
1021
  const addFields = [
960
1022
  {
961
- fieldName: "order",
1023
+ fieldName: "purchaseOrder",
962
1024
  type: "RELATIONSHIP",
963
1025
  referenceEntityId: "a1b2c3d4-0000-0000-0000-000000000010",
964
1026
  referenceFieldId: "f1000000-0000-0000-0000-000000000100",
@@ -1655,8 +1717,8 @@ describe("entities update", () => {
1655
1717
  it("should reject when same fieldName appears in addFields and removeFields", async () => {
1656
1718
  const sdk = mockSdk();
1657
1719
  vi.mocked(readJsonInput).mockResolvedValue({
1658
- addFields: [{ fieldName: "shared", type: "STRING" }],
1659
- removeFields: [{ fieldName: "shared" }],
1720
+ addFields: [{ fieldName: "overlap", type: "STRING" }],
1721
+ removeFields: [{ fieldName: "overlap" }],
1660
1722
  });
1661
1723
 
1662
1724
  const program = buildProgram();
@@ -1667,7 +1729,7 @@ describe("entities update", () => {
1667
1729
  "update",
1668
1730
  "entity-id",
1669
1731
  "--body",
1670
- '{"addFields":[{"fieldName":"shared","type":"STRING"}],"removeFields":[{"fieldName":"shared"}]}',
1732
+ '{"addFields":[{"fieldName":"overlap","type":"STRING"}],"removeFields":[{"fieldName":"overlap"}]}',
1671
1733
  "--confirm",
1672
1734
  "--reason",
1673
1735
  "recreate",
@@ -1678,7 +1740,7 @@ describe("entities update", () => {
1678
1740
  expect.objectContaining({
1679
1741
  Result: "Failure",
1680
1742
  Message:
1681
- "Field 'shared' appears in both addFields and removeFields",
1743
+ "Field 'overlap' appears in both addFields and removeFields",
1682
1744
  }),
1683
1745
  );
1684
1746
  expect(process.exitCode).toBe(1);
@@ -1708,7 +1770,7 @@ describe("entities update", () => {
1708
1770
  expect(OutputFormatter.error).toHaveBeenCalledWith(
1709
1771
  expect.objectContaining({
1710
1772
  Result: "Failure",
1711
- Message: "Duplicate fieldName 'dup' in addFields",
1773
+ Message: "Duplicate field name 'dup' (case-insensitive)",
1712
1774
  }),
1713
1775
  );
1714
1776
  expect(process.exitCode).toBe(1);
@@ -24,12 +24,16 @@ import { fail, requireDestructiveConfirmation } from "../utils/output";
24
24
  import { connectOrFail } from "../utils/sdk-client";
25
25
  import {
26
26
  findInvalidFieldConstraints,
27
+ findInvalidFieldMetadata,
27
28
  findInvalidFieldName,
28
29
  findIsUniqueInUpdateFields,
29
30
  findMissingComplexFieldExtras,
31
+ findReferenceFieldsOnFile,
30
32
  findReferenceFolderKeyOnChoiceSet,
33
+ findTooManyMultilineMaxFields,
31
34
  findUiBrokenFieldType,
32
35
  validateName,
36
+ validateSchemaMetadata,
33
37
  } from "../utils/validate";
34
38
 
35
39
  interface ListOptions {
@@ -173,10 +177,11 @@ const ENTITIES_CREATE_EXAMPLES: CommandExample[] = [
173
177
  Description:
174
178
  "Create a folder-scoped entity with cross-folder references. " +
175
179
  "Pass '--folder-key' to place the new entity in a folder. " +
176
- "Per-field 'referenceFolderKey' points a RELATIONSHIP or CHOICE_SET_* field at a target that lives in a different folder (or at the tenant level when omitted). " +
180
+ "Per-field 'referenceFolderKey' points a RELATIONSHIP field at a target that lives in a different folder (or at the tenant level when omitted). " +
181
+ "CHOICE_SET_* fields resolve scope from 'choiceSetId' and must not pass 'referenceFolderKey'. " +
177
182
  "Get target entity/field IDs from 'df entities list --folder-key <key>' and 'df entities get <id> --folder-key <key>'; get choice-set IDs from 'df choice-sets list --folder-key <key>' (omit '--folder-key' for tenant-level choice sets).",
178
183
  Command:
179
- 'uip df entities create OrderLine --folder-key f1000000-0000-0000-0000-000000000050 --body \'{"displayName":"Order Line","fields":[{"fieldName":"order","type":"RELATIONSHIP","referenceEntityId":"a1b2c3d4-0000-0000-0000-000000000010","referenceFieldId":"f1000000-0000-0000-0000-000000000100","referenceFolderKey":"f1000000-0000-0000-0000-000000000060","isRequired":true},{"fieldName":"userType","type":"CHOICE_SET_SINGLE","choiceSetId":"c1d2e3f4-0000-0000-0000-000000000077"}]}\'',
184
+ 'uip df entities create OrderLine --folder-key f1000000-0000-0000-0000-000000000050 --body \'{"displayName":"Order Line","fields":[{"fieldName":"purchaseOrder","type":"RELATIONSHIP","referenceEntityId":"a1b2c3d4-0000-0000-0000-000000000010","referenceFieldId":"f1000000-0000-0000-0000-000000000100","referenceFolderKey":"f1000000-0000-0000-0000-000000000060","isRequired":true},{"fieldName":"userType","type":"CHOICE_SET_SINGLE","choiceSetId":"c1d2e3f4-0000-0000-0000-000000000077"}]}\'',
180
185
  Output: {
181
186
  Code: "EntityCreated",
182
187
  Data: { ID: "a1b2c3d4-0000-0000-0000-000000000005" },
@@ -220,13 +225,20 @@ function pickKnownUpdateKeys(
220
225
  // name rules and UI-broken-type substitution. The SDK-enum type check
221
226
  // stays inline at the call site because it needs `hasInvalidFieldType`
222
227
  // (bound to the SDK enum, not portable to @uipath/common).
223
- function validateFieldsPayload(fields: unknown[]): ValidationError | null {
228
+ function validateFieldsPayload(
229
+ fields: unknown[],
230
+ entityName?: string,
231
+ isNewEntity = false,
232
+ ): ValidationError | null {
224
233
  return runChecks([
225
234
  () => findInvalidFieldName(fields),
235
+ () => findInvalidFieldMetadata(fields, entityName),
226
236
  () => findUiBrokenFieldType(fields),
237
+ () => findReferenceFieldsOnFile(fields),
227
238
  () => findReferenceFolderKeyOnChoiceSet(fields),
228
239
  () => findMissingComplexFieldExtras(fields),
229
240
  () => findInvalidFieldConstraints(fields),
241
+ () => (isNewEntity ? findTooManyMultilineMaxFields(fields) : null),
230
242
  ]);
231
243
  }
232
244
 
@@ -436,7 +448,13 @@ export const registerEntitiesCommand = (program: Command) => {
436
448
 
437
449
  const err = runChecks([
438
450
  () => validateName(name, "entity"),
439
- () => validateFieldsPayload(definition.fields as unknown[]),
451
+ () => validateSchemaMetadata(definition, "Entity"),
452
+ () =>
453
+ validateFieldsPayload(
454
+ definition.fields as unknown[],
455
+ name,
456
+ true,
457
+ ),
440
458
  ]);
441
459
  if (err) return fail(err.message, err.instructions);
442
460
 
@@ -544,6 +562,9 @@ export const registerEntitiesCommand = (program: Command) => {
544
562
  }
545
563
 
546
564
  const input = parsed as Record<string, unknown>;
565
+ const metadataErr = validateSchemaMetadata(input, "Entity");
566
+ if (metadataErr)
567
+ return fail(metadataErr.message, metadataErr.instructions);
547
568
 
548
569
  if (
549
570
  input.addFields !== undefined &&
@@ -571,7 +592,7 @@ export const registerEntitiesCommand = (program: Command) => {
571
592
  ) {
572
593
  return fail(
573
594
  "'removeFields' must be an array",
574
- 'Example: {"removeFields":[{"fieldName":"old_field"}]}',
595
+ 'Example: {"removeFields":[{"fieldName":"oldField"}]}',
575
596
  );
576
597
  }
577
598
 
@@ -591,7 +612,7 @@ export const registerEntitiesCommand = (program: Command) => {
591
612
  if (hasInvalidRemove) {
592
613
  return fail(
593
614
  "Each field in removeFields must include a non-empty 'fieldName' string",
594
- 'Example: {"removeFields":[{"fieldName":"old_field"}]}',
615
+ 'Example: {"removeFields":[{"fieldName":"oldField"}]}',
595
616
  );
596
617
  }
597
618
 
@@ -640,19 +661,12 @@ export const registerEntitiesCommand = (program: Command) => {
640
661
  const addNames = (
641
662
  input.addFields as { fieldName: string }[]
642
663
  ).map((f) => f.fieldName);
643
- const duplicateAdd = addNames.find(
644
- (name, i) => addNames.indexOf(name) !== i,
645
- );
646
- if (duplicateAdd !== undefined) {
647
- return fail(
648
- `Duplicate fieldName '${duplicateAdd}' in addFields`,
649
- "Each entry in addFields must have a unique fieldName.",
650
- );
651
- }
652
-
653
664
  if (removedFieldNames.length > 0) {
665
+ const removedNamesLower = new Set(
666
+ removedFieldNames.map((name) => name.toLowerCase()),
667
+ );
654
668
  const conflict = addNames.find((name) =>
655
- removedFieldNames.includes(name),
669
+ removedNamesLower.has(name.toLowerCase()),
656
670
  );
657
671
  if (conflict !== undefined) {
658
672
  return fail(
@@ -683,6 +697,15 @@ export const registerEntitiesCommand = (program: Command) => {
683
697
  );
684
698
  if (updateErr)
685
699
  return fail(updateErr.message, updateErr.instructions);
700
+
701
+ const updateMetadataErr = findInvalidFieldMetadata(
702
+ input.updateFields as unknown[],
703
+ );
704
+ if (updateMetadataErr)
705
+ return fail(
706
+ updateMetadataErr.message,
707
+ updateMetadataErr.instructions,
708
+ );
686
709
  }
687
710
 
688
711
  const sdk = await connectOrFail(options.tenant);
@@ -2067,6 +2067,35 @@ describe("records query — filter validation", () => {
2067
2067
  );
2068
2068
  expect(process.exitCode).toBe(1);
2069
2069
  });
2070
+
2071
+ it("rejects more than five aggregates before connecting", async () => {
2072
+ const sdk = mockSdk();
2073
+ const program = buildProgram();
2074
+ await program.parseAsync([
2075
+ "node",
2076
+ "test",
2077
+ "records",
2078
+ "query",
2079
+ "entity-id",
2080
+ "--body",
2081
+ JSON.stringify({
2082
+ aggregates: Array.from({ length: 6 }, (_, index) => ({
2083
+ function: "COUNT",
2084
+ field: "Id",
2085
+ alias: `count${index}`,
2086
+ })),
2087
+ }),
2088
+ ]);
2089
+
2090
+ expect(sdk.entities.queryRecordsById).not.toHaveBeenCalled();
2091
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
2092
+ expect.objectContaining({
2093
+ Result: "Failure",
2094
+ Message: "A query supports at most 5 aggregates",
2095
+ }),
2096
+ );
2097
+ expect(process.exitCode).toBe(1);
2098
+ });
2070
2099
  });
2071
2100
 
2072
2101
  describe("records query — pagination", () => {
@@ -13,7 +13,11 @@ import { type Command, Option } from "commander";
13
13
  import { readFileBinary, readJsonInput } from "../utils/input";
14
14
  import { fail, requireDestructiveConfirmation } from "../utils/output";
15
15
  import { connectOrFail } from "../utils/sdk-client";
16
- import { findFilePayloadKeys, validateQueryFilter } from "../utils/validate";
16
+ import {
17
+ findFilePayloadKeys,
18
+ validateQueryAggregates,
19
+ validateQueryFilter,
20
+ } from "../utils/validate";
17
21
 
18
22
  // Fetch the entity schema and return the set of FILE-typed field names.
19
23
  // One RTT — worth it: without this, `records insert/update` silently
@@ -766,6 +770,13 @@ export const registerRecordsCommand = (program: Command) => {
766
770
  );
767
771
  if (filterErr)
768
772
  return fail(filterErr.message, filterErr.instructions);
773
+
774
+ const aggregateErr = validateQueryAggregates(queryBody);
775
+ if (aggregateErr)
776
+ return fail(
777
+ aggregateErr.message,
778
+ aggregateErr.instructions,
779
+ );
769
780
  }
770
781
 
771
782
  const sdk = await connectOrFail(options.tenant);