Skip to content

Commit b263412

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

File tree

6 files changed

+83
-7
lines changed

6 files changed

+83
-7
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export interface AnyClient {
2020
internal: InternalModelManagerNamespace;
2121
apiClientCoreVersion?: string;
2222
[$modelRelationships]?: { [modelName: string]: { [apiIdentifier: string]: { type: string; model: string } } };
23+
/** Symbol for passing field arguments in selections. Use as `{ field: { [api.$args]: { first: 10 }, nestedField: true } }` */
24+
$args?: symbol;
2325
}
2426

2527
/**

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@gadgetinc/core",
3-
"version": "0.14.0",
3+
"version": "0.15.0",
44
"files": [
55
"README.md",
66
"dist/**/*",

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

Lines changed: 37 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, TestSchemaWithNestedFieldCalls } from "./TestSchema.js";
45

56
describe("Select<>", () => {
67
type _SelectingProperties = AssertTrue<IsExact<Select<TestSchema, { num: true }>, { num: number }>>;
@@ -75,5 +76,40 @@ 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+
87+
type _simpleFieldCallSelection = Select<
88+
{ fieldWithArgs: number },
89+
{
90+
fieldWithArgs: {
91+
[$args]: { arg: [1, 2, 3]; limit: 10 };
92+
};
93+
}
94+
>;
95+
type _TestSelectingSimpleFieldCall = AssertTrue<IsExact<_simpleFieldCallSelection, { fieldWithArgs: number }>>;
96+
97+
type _nestedFieldCallSelection = Select<
98+
TestSchemaWithNestedFieldCalls,
99+
{
100+
outerFieldCall: {
101+
[$args]: { outerArg: "value" };
102+
innerFieldCall: {
103+
[$args]: { innerArg: 123 };
104+
deepField: { [$args]: { deepArg: "value" } };
105+
};
106+
regularField: true;
107+
};
108+
}
109+
>;
110+
type _TestSelectingNestedFieldCall = AssertTrue<
111+
IsExact<_nestedFieldCallSelection, { outerFieldCall: { innerFieldCall: { deepField: string }; regularField: number } }>
112+
>;
113+
78114
test("true", () => undefined);
79115
});

packages/core/spec/TestSchema.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,26 @@ 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+
21+
export type TestSchemaWithNestedFieldCalls = {
22+
outerFieldCall: {
23+
innerFieldCall: {
24+
deepField: string;
25+
};
26+
regularField: number;
27+
};
28+
};
29+
1030
export type TestSchema = {
1131
num: number;
1232
str: string;

packages/core/src/AnyClient.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,6 @@ export interface AnyClient {
2222
internal: InternalModelManagerNamespace;
2323
[$modelRelationships]?: { [modelName: string]: { [apiIdentifier: string]: { type: string; model: string } } };
2424
[$coreImplementation]?: AnyCoreImplementation;
25+
/** Symbol for passing field arguments in selections. Use as `{ field: { [api.$args]: { first: 10 }, nestedField: true } }` */
26+
$args?: symbol;
2527
}

packages/core/src/types.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ export type FilterNever<T extends Record<string, unknown>> = NonNeverKeys<T> ext
105105
* >; // { apple: "red" }
106106
* ```
107107
*/
108-
type InnerSelect<Schema, Selection extends FieldSelection | null | undefined> = IfAny<
108+
type InnerSelect<Schema, Selection extends FieldSelection | FieldSelectionWithArgs | null | undefined> = IfAny<
109109
Selection,
110110
never,
111111
Selection extends null | undefined
@@ -115,10 +115,12 @@ 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+
[Key in Exclude<keyof Selection, typeof $args> & keyof Schema]: Selection[Key] extends true
119119
? Schema[Key]
120-
: Selection[Key] extends FieldSelection
121-
? InnerSelect<Schema[Key], Selection[Key]>
120+
: Selection[Key] extends FieldSelection | FieldSelectionWithArgs
121+
? Exclude<keyof Selection[Key], typeof $args> extends never
122+
? Schema[Key]
123+
: InnerSelect<Schema[Key], Selection[Key]>
122124
: never;
123125
}
124126
>;
@@ -148,7 +150,9 @@ export type DeepFilterNever<T> = T extends Record<string, unknown>
148150
* >; // { apple: "red" }
149151
* ```
150152
*/
151-
export type Select<Schema, Selection extends FieldSelection | null | undefined> = DeepFilterNever<InnerSelect<Schema, Selection>>;
153+
export type Select<Schema, Selection extends FieldSelection | FieldSelectionWithArgs | null | undefined> = DeepFilterNever<
154+
InnerSelect<Schema, Selection>
155+
>;
152156

153157
/** Represents an amount of some currency. Specified as a string so user's aren't tempted to do math on the value. */
154158
export type CurrencyAmount = string;
@@ -989,10 +993,22 @@ export type ViewResult<F extends ViewFunction<any, any>> = Awaited<
989993
F extends ViewFunctionWithVariables<any, infer Result> ? Result : F extends ViewFunctionWithoutVariables<infer Result> ? Result : never
990994
>;
991995

996+
/** Symbol key for field arguments in selections */
997+
export const $args: unique symbol = Symbol.for("gadget/fieldArgs");
998+
999+
/** Field arguments (e.g., pagination, filtering) */
1000+
export type FieldArgs = Record<string, any>;
1001+
9921002
/**
9931003
* Represents a list of fields selected from a GraphQL API call. Allows nesting, conditional selection.
9941004
* Example: `{ id: true, name: false, richText: { markdown: true, html: false } }`
9951005
**/
9961006
export interface FieldSelection {
9971007
[key: string]: boolean | null | undefined | FieldSelection;
9981008
}
1009+
1010+
/** A field selection that includes field arguments via the $args symbol key */
1011+
export type FieldSelectionWithArgs = {
1012+
[$args]?: FieldArgs;
1013+
[key: string]: boolean | null | undefined | FieldSelection | FieldSelectionWithArgs;
1014+
};

0 commit comments

Comments
 (0)