Skip to content

Commit 737b1d7

Browse files
claude expanding logic to all selection and adding tests
1 parent 10649e0 commit 737b1d7

File tree

2 files changed

+144
-4
lines changed

2 files changed

+144
-4
lines changed

packages/api-client-core/spec/operationBuilders.spec.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1621,4 +1621,120 @@ describe("operation builders", () => {
16211621
`);
16221622
});
16231623
});
1624+
1625+
describe("selection auto-expansion", () => {
1626+
const defaultSelectionWithSpecialFields = {
1627+
__typename: true,
1628+
id: true,
1629+
name: true,
1630+
richText: { markdown: true, truncatedHTML: true },
1631+
fileField: { url: true, mimeType: true, fileName: true },
1632+
roleField: { key: true, name: true },
1633+
};
1634+
1635+
describe("findOneOperation", () => {
1636+
test("auto-expands richText: true to sub-selection from defaultSelection", () => {
1637+
const result = findOneOperation(
1638+
"widget",
1639+
"123",
1640+
defaultSelectionWithSpecialFields,
1641+
"widget",
1642+
{ select: { id: true, richText: true } }
1643+
);
1644+
expect(result.query).toContain("richText");
1645+
expect(result.query).toContain("markdown");
1646+
expect(result.query).toContain("truncatedHTML");
1647+
});
1648+
1649+
test("auto-expands fileField: true to sub-selection from defaultSelection", () => {
1650+
const result = findOneOperation(
1651+
"widget",
1652+
"123",
1653+
defaultSelectionWithSpecialFields,
1654+
"widget",
1655+
{ select: { id: true, fileField: true } }
1656+
);
1657+
expect(result.query).toContain("fileField");
1658+
expect(result.query).toContain("url");
1659+
expect(result.query).toContain("mimeType");
1660+
expect(result.query).toContain("fileName");
1661+
});
1662+
1663+
test("auto-expands roleField: true to sub-selection from defaultSelection", () => {
1664+
const result = findOneOperation(
1665+
"widget",
1666+
"123",
1667+
defaultSelectionWithSpecialFields,
1668+
"widget",
1669+
{ select: { id: true, roleField: true } }
1670+
);
1671+
expect(result.query).toContain("roleField");
1672+
expect(result.query).toContain("key");
1673+
expect(result.query).toContain("name");
1674+
});
1675+
1676+
test("preserves explicit object selections without overwriting", () => {
1677+
const result = findOneOperation(
1678+
"widget",
1679+
"123",
1680+
defaultSelectionWithSpecialFields,
1681+
"widget",
1682+
{ select: { id: true, richText: { markdown: true } } }
1683+
);
1684+
expect(result.query).toContain("markdown");
1685+
expect(result.query).not.toContain("truncatedHTML");
1686+
});
1687+
1688+
test("leaves normal scalar fields untouched", () => {
1689+
const result = findOneOperation(
1690+
"widget",
1691+
"123",
1692+
defaultSelectionWithSpecialFields,
1693+
"widget",
1694+
{ select: { id: true, name: true } }
1695+
);
1696+
expect(result.query).toContain("id");
1697+
expect(result.query).toContain("name");
1698+
expect(result.query).not.toContain("richText");
1699+
});
1700+
});
1701+
1702+
describe("findManyOperation", () => {
1703+
test("auto-expands richText: true in findMany", () => {
1704+
const result = findManyOperation("widgets", defaultSelectionWithSpecialFields, "widget", {
1705+
select: { id: true, richText: true },
1706+
});
1707+
expect(result.query).toContain("richText");
1708+
expect(result.query).toContain("markdown");
1709+
expect(result.query).toContain("truncatedHTML");
1710+
});
1711+
1712+
test("auto-expands fileField: true in findMany", () => {
1713+
const result = findManyOperation("widgets", defaultSelectionWithSpecialFields, "widget", {
1714+
select: { id: true, fileField: true },
1715+
});
1716+
expect(result.query).toContain("fileField");
1717+
expect(result.query).toContain("url");
1718+
expect(result.query).toContain("mimeType");
1719+
});
1720+
});
1721+
1722+
describe("actionOperation", () => {
1723+
test("auto-expands richText: true in action", () => {
1724+
const result = actionOperation(
1725+
"createWidget",
1726+
defaultSelectionWithSpecialFields,
1727+
"widget",
1728+
"widget",
1729+
{
1730+
widget: { type: "CreateWidgetInput", value: { name: "test" } },
1731+
},
1732+
{ select: { id: true, richText: true } }
1733+
);
1734+
expect(result.query).toContain("richText");
1735+
expect(result.query).toContain("markdown");
1736+
expect(result.query).toContain("truncatedHTML");
1737+
});
1738+
});
1739+
});
16241740
});

packages/api-client-core/src/operationBuilders.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,30 @@ const fieldSelectionToQueryCompilerFields = (selection: FieldSelection, includeT
2424

2525
export type FindFirstPaginationOptions = Omit<FindManyOptions, "first" | "last" | "before" | "after">;
2626

27+
/**
28+
* When a user passes `{ field: true }` for a field that requires sub-selections
29+
* (like richText, file, or role fields), auto-expand it using the defaultSelection.
30+
*/
31+
const normalizeSelection = (selection: FieldSelection, defaultSelection: FieldSelection): FieldSelection => {
32+
const result: FieldSelection = {};
33+
for (const [key, value] of Object.entries(selection)) {
34+
const defaultValue = defaultSelection[key];
35+
if (value === true && defaultValue && typeof defaultValue === "object") {
36+
result[key] = defaultValue;
37+
} else if (value && typeof value === "object" && defaultValue && typeof defaultValue === "object") {
38+
result[key] = normalizeSelection(value as FieldSelection, defaultValue as FieldSelection);
39+
} else {
40+
result[key] = value;
41+
}
42+
}
43+
return result;
44+
};
45+
46+
const resolveSelection = (defaultSelection: FieldSelection, userSelection?: FieldSelection | null): FieldSelection => {
47+
if (!userSelection) return defaultSelection;
48+
return normalizeSelection(userSelection, defaultSelection);
49+
};
50+
2751
const directivesForOptions = (options?: BaseFindOptions | null) => {
2852
if (options?.live) return ["@live"];
2953
return undefined;
@@ -41,7 +65,7 @@ export const findOneOperation = (
4165
if (typeof id !== "undefined") variables.id = Var({ type: "GadgetID!", value: id });
4266

4367
let fields = {
44-
[operation]: Call(variables, fieldSelectionToQueryCompilerFields(options?.select || defaultSelection, true)),
68+
[operation]: Call(variables, fieldSelectionToQueryCompilerFields(resolveSelection(defaultSelection, options?.select), true)),
4569
};
4670

4771
fields = namespacify(namespace, fields);
@@ -105,7 +129,7 @@ export const findManyOperation = (
105129
pageInfo: { hasNextPage: true, hasPreviousPage: true, startCursor: true, endCursor: true },
106130
edges: {
107131
cursor: true,
108-
node: fieldSelectionToQueryCompilerFields(options?.select || defaultSelection, true),
132+
node: fieldSelectionToQueryCompilerFields(resolveSelection(defaultSelection, options?.select), true),
109133
},
110134
}
111135
),
@@ -176,7 +200,7 @@ export const actionOperation = (
176200
isBulkAction?: boolean | null,
177201
hasReturnType?: HasReturnType | null
178202
) => {
179-
const selection = options?.select || defaultSelection;
203+
const selection = resolveSelection(defaultSelection!, options?.select);
180204

181205
let fields: BuilderFieldSelection = {
182206
[operation]: Call(
@@ -223,7 +247,7 @@ export const backgroundActionResultOperation = <Action extends AnyActionFunction
223247

224248
switch (backgroundAction.type) {
225249
case "action": {
226-
const selection = options?.select || backgroundAction.defaultSelection;
250+
const selection = resolveSelection(backgroundAction.defaultSelection, options?.select);
227251

228252
fields = {
229253
[`... on ${resultType}`]: actionResultFieldSelection(

0 commit comments

Comments
 (0)