@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/dist/browser-strategy-yccf8mtd.js +99 -0
- package/dist/getMachineId-bsd-wfkxpxjv.js +38 -0
- package/dist/getMachineId-darwin-9asn4zy6.js +38 -0
- package/dist/getMachineId-linux-62t6m027.js +31 -0
- package/dist/getMachineId-unsupported-dfrzeqc3.js +21 -0
- package/dist/getMachineId-win-zgvrp5q3.js +40 -0
- package/dist/index-2fj08e7n.js +2058 -0
- package/dist/index-w03qc9m8.js +634 -0
- package/dist/index.js +14 -2148
- package/dist/js-yaml-4ypbq2tt.js +3145 -0
- package/dist/node-strategy-12qfy0nv.js +348 -0
- package/dist/tool-0v6na3yp.js +37 -0
- package/dist/tool-5arsyj36.js +11 -0
- package/dist/tool-7eva0peq.js +265 -0
- package/dist/tool-9qecd4wb.js +14 -0
- package/dist/tool-cyfkvxhw.js +23350 -0
- package/dist/tool-h1k9mjs9.js +17 -0
- package/dist/tool-r6rhp5ep.js +1487 -0
- package/dist/tool.js +10 -48431
- package/package.json +2 -2
- package/src/commands/choice-sets.spec.ts +44 -0
- package/src/commands/choice-sets.ts +39 -2
- package/src/commands/entities.spec.ts +70 -8
- package/src/commands/entities.ts +40 -17
- package/src/commands/records.spec.ts +29 -0
- package/src/commands/records.ts +12 -1
- package/src/utils/validate.spec.ts +279 -12
- package/src/utils/validate.ts +694 -40
- package/tests/entities-lifecycle.e2e.test.ts +9 -8
- package/tests/performance.e2e.test.ts +32 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uipath/data-fabric-tool",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "1.
|
|
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": "
|
|
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 {
|
|
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
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
1659
|
-
removeFields: [{ fieldName: "
|
|
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":"
|
|
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 '
|
|
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
|
|
1773
|
+
Message: "Duplicate field name 'dup' (case-insensitive)",
|
|
1712
1774
|
}),
|
|
1713
1775
|
);
|
|
1714
1776
|
expect(process.exitCode).toBe(1);
|
package/src/commands/entities.ts
CHANGED
|
@@ -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
|
|
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":"
|
|
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(
|
|
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
|
-
() =>
|
|
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":"
|
|
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":"
|
|
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
|
-
|
|
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", () => {
|
package/src/commands/records.ts
CHANGED
|
@@ -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 {
|
|
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);
|