Skip to content

Commit f3d26cf

Browse files
committed
Support field calls in selection API
1 parent cfe5fb1 commit f3d26cf

File tree

5 files changed

+69
-2
lines changed

5 files changed

+69
-2
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { $args } from "./FieldSelection.js";
12
import type { GadgetConnection } from "./GadgetConnection.js";
23
import type { GadgetTransaction } from "./GadgetTransaction.js";
34
import type { InternalModelManager } from "./InternalModelManager.js";
@@ -18,6 +19,7 @@ export interface AnyClient {
1819
mutate(graphQL: string, variables?: Record<string, any>): Promise<any>;
1920
transaction<T>(callback: (transaction: GadgetTransaction) => Promise<T>): Promise<T>;
2021
internal: InternalModelManagerNamespace;
22+
$args: typeof $args;
2123
apiClientCoreVersion?: string;
2224
[$modelRelationships]?: { [modelName: string]: { [apiIdentifier: string]: { type: string; model: string } } };
2325
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,29 @@
1+
/**
2+
* Symbol key for field arguments in selections.
3+
* Using a symbol avoids conflicts with string index signatures.
4+
*/
5+
export const $args = Symbol.for("gadget/fieldArgs");
6+
7+
/**
8+
* Type for field arguments (e.g., pagination, filtering on a field)
9+
*/
10+
export type FieldArgs = Record<string, any>;
11+
112
/**
213
* Represents a list of fields selected from a GraphQL API call. Allows nesting, conditional selection.
314
* Example: `{ id: true, name: false, richText: { markdown: true, html: false } }`
15+
*
16+
* Supports field arguments using the $args symbol:
17+
* ```
18+
* {
19+
* comments: {
20+
* [$args]: { after: "cursor", first: 10 },
21+
* body: true
22+
* }
23+
* }
24+
* ```
425
**/
526
export interface FieldSelection {
27+
[$args]?: FieldArgs;
628
[key: string]: boolean | null | undefined | FieldSelection;
729
}

packages/core/spec/Select-type.spec.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { AssertTrue, IsExact } from "conditional-type-checks";
22
import type { DeepFilterNever, Select } from "../src/types.js";
3-
import type { TestSchema } from "./TestSchema.js";
3+
import { $args } from "../src/types.js";
4+
import type { TestSchema, TestSchemaWithFieldCalls } from "./TestSchema.js";
45

56
describe("Select<>", () => {
67
type _SelectingProperties = AssertTrue<IsExact<Select<TestSchema, { num: true }>, { num: number }>>;
@@ -75,5 +76,13 @@ describe("Select<>", () => {
7576
>
7677
>;
7778

79+
type _fieldCallSelection = Select<
80+
TestSchemaWithFieldCalls,
81+
{ fieldCall: { [$args]: { arg: [1, 2, 3]; limit: 10 }; nestedField1: true; nestedField2: { nestedField3: true } } }
82+
>;
83+
type _TestSelectingFieldCall = AssertTrue<
84+
IsExact<_fieldCallSelection, { fieldCall: { nestedField1: number; nestedField2: { nestedField3: string } } }>
85+
>;
86+
7887
test("true", () => undefined);
7988
});

packages/core/spec/TestSchema.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,17 @@ export type NestedThing = {
77
nested: NestedThing;
88
};
99

10+
export type TestSchemaWithFieldCalls = {
11+
num: number;
12+
str: string;
13+
fieldCall: {
14+
nestedField1: number;
15+
nestedField2: {
16+
nestedField3: string;
17+
};
18+
};
19+
};
20+
1021
export type TestSchema = {
1122
num: number;
1223
str: string;

packages/core/src/types.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,8 @@ type InnerSelect<Schema, Selection extends FieldSelection | null | undefined> =
115115
: Schema extends null
116116
? InnerSelect<Exclude<Schema, null>, Selection> | null
117117
: {
118-
[Key in keyof Selection & keyof Schema]: Selection[Key] extends true
118+
// Exclude the $args symbol key - it's for field arguments, not nested selections
119+
[Key in Exclude<keyof Selection, typeof $args> & keyof Schema]: Selection[Key] extends true
119120
? Schema[Key]
120121
: Selection[Key] extends FieldSelection
121122
? InnerSelect<Schema[Key], Selection[Key]>
@@ -989,10 +990,32 @@ export type ViewResult<F extends ViewFunction<any, any>> = Awaited<
989990
F extends ViewFunctionWithVariables<any, infer Result> ? Result : F extends ViewFunctionWithoutVariables<infer Result> ? Result : never
990991
>;
991992

993+
/**
994+
* Symbol key for field arguments in selections.
995+
* Using a symbol avoids conflicts with string index signatures.
996+
*/
997+
export const $args = Symbol.for("gadget/fieldArgs");
998+
999+
/**
1000+
* Type for field arguments (e.g., pagination, filtering on a field)
1001+
*/
1002+
export type FieldArgs = Record<string, any>;
1003+
9921004
/**
9931005
* Represents a list of fields selected from a GraphQL API call. Allows nesting, conditional selection.
9941006
* Example: `{ id: true, name: false, richText: { markdown: true, html: false } }`
1007+
*
1008+
* Supports field arguments using the $args symbol:
1009+
* ```
1010+
* {
1011+
* comments: {
1012+
* [$args]: { after: "cursor", first: 10 },
1013+
* body: true
1014+
* }
1015+
* }
1016+
* ```
9951017
**/
9961018
export interface FieldSelection {
1019+
[$args]?: FieldArgs;
9971020
[key: string]: boolean | null | undefined | FieldSelection;
9981021
}

0 commit comments

Comments
 (0)