Skip to content

Commit f7302fe

Browse files
committed
Easier context binding with instantiation expressions
1 parent 9178e73 commit f7302fe

File tree

5 files changed

+279
-18
lines changed

5 files changed

+279
-18
lines changed

packages/schema/README.md

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,45 @@ constructing GraphQL Schemas while avoiding type-generation, [declaration mergin
66
and [decorators](https://www.typescriptlang.org/docs/handbook/decorators.html).
77

88
```ts
9-
import { g } from "@graphql-ts/schema";
9+
import { initG } from "@graphql-ts/schema";
1010
import { GraphQLSchema, graphql } from "graphql";
1111

12+
type Context = {
13+
loadPerson: (id: string) => Person;
14+
loadFriends: (id: string) => Person[];
15+
};
16+
const g = initG<Context>();
17+
type g<T> = initG<T>;
18+
19+
type Person = {
20+
id: string;
21+
name: string;
22+
};
23+
24+
const Person: g<typeof g.object<Person>> = g.object<Person>()({
25+
name: "Person",
26+
fields: () => ({
27+
id: g.field({ type: g.ID }),
28+
name: g.field({ type: g.String }),
29+
friends: g.field({
30+
type: g.list(Person),
31+
resolve(source, _, context) {
32+
return context.loadFriends(source.id);
33+
},
34+
}),
35+
}),
36+
});
37+
1238
const Query = g.object()({
1339
name: "Query",
1440
fields: {
15-
hello: g.field({
16-
type: g.String,
17-
resolve() {
18-
return "Hello!";
41+
person: g.field({
42+
type: Person,
43+
args: {
44+
id: g.arg({ type: g.ID }),
45+
},
46+
resolve(_, args, context) {
47+
return context.loadPerson(args.id);
1948
},
2049
}),
2150
},
@@ -25,13 +54,37 @@ const schema = new GraphQLSchema({
2554
query: Query,
2655
});
2756

57+
const people = new Map<string, Person>([
58+
["1", { id: "1", name: "Alice" }],
59+
["2", { id: "2", name: "Bob" }],
60+
]);
61+
const friends = new Map<string, string[]>([
62+
["1", ["2"]],
63+
["2", ["1"]],
64+
]);
65+
2866
graphql({
2967
source: `
30-
query {
31-
hello
32-
}
33-
`,
68+
query {
69+
person(id: "1") {
70+
id
71+
name
72+
friends {
73+
id
74+
name
75+
}
76+
}
77+
}
78+
`,
3479
schema,
80+
context: {
81+
loadPerson: (id) => people.get(id),
82+
loadFriends: (id) => {
83+
return (friends.get(id) ?? [])
84+
.map((id) => people.get(id))
85+
.filter((person) => person !== undefined);
86+
},
87+
},
3588
}).then((result) => {
3689
console.log(result);
3790
});

packages/schema/src/index.ts

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,45 @@
77
* [decorators](https://www.typescriptlang.org/docs/handbook/decorators.html).
88
*
99
* ```ts
10-
* import { g } from "@graphql-ts/schema";
10+
* import { initG } from "@graphql-ts/schema";
1111
* import { GraphQLSchema, graphql } from "graphql";
1212
*
13+
* type Context = {
14+
* loadPerson: (id: string) => Person;
15+
* loadFriends: (id: string) => Person[];
16+
* };
17+
* const g = initG<Context>();
18+
* type g<T> = initG<T>;
19+
*
20+
* type Person = {
21+
* id: string;
22+
* name: string;
23+
* };
24+
*
25+
* const Person: g<typeof g.object<Person>> = g.object<Person>()({
26+
* name: "Person",
27+
* fields: () => ({
28+
* id: g.field({ type: g.ID }),
29+
* name: g.field({ type: g.String }),
30+
* friends: g.field({
31+
* type: g.list(Person),
32+
* resolve(source, _, context) {
33+
* return context.loadFriends(source.id);
34+
* },
35+
* }),
36+
* }),
37+
* });
38+
*
1339
* const Query = g.object()({
1440
* name: "Query",
1541
* fields: {
16-
* hello: g.field({
17-
* type: g.String,
18-
* resolve() {
19-
* return "Hello!";
42+
* person: g.field({
43+
* type: Person,
44+
* args: {
45+
* id: g.arg({ type: g.ID }),
46+
* },
47+
* resolve(_, args, context) {
48+
* return context.loadPerson(args.id);
2049
* },
2150
* }),
2251
* },
@@ -26,13 +55,37 @@
2655
* query: Query,
2756
* });
2857
*
58+
* const people = new Map<string, Person>([
59+
* ["1", { id: "1", name: "Alice" }],
60+
* ["2", { id: "2", name: "Bob" }],
61+
* ]);
62+
* const friends = new Map<string, string[]>([
63+
* ["1", ["2"]],
64+
* ["2", ["1"]],
65+
* ]);
66+
*
2967
* graphql({
3068
* source: `
31-
* query {
32-
* hello
33-
* }
34-
* `,
69+
* query {
70+
* person(id: "1") {
71+
* id
72+
* name
73+
* friends {
74+
* id
75+
* name
76+
* }
77+
* }
78+
* }
79+
* `,
3580
* schema,
81+
* context: {
82+
* loadPerson: (id) => people.get(id),
83+
* loadFriends: (id) => {
84+
* return (friends.get(id) ?? [])
85+
* .map((id) => people.get(id))
86+
* .filter((person) => person !== undefined);
87+
* },
88+
* },
3689
* }).then((result) => {
3790
* console.log(result);
3891
* });
@@ -41,6 +94,7 @@
4194
* @module
4295
*/
4396
export * as g from "./schema-api";
97+
export type g<T> = import("./output").initG<T>;
4498
export { initG, type GWithContext } from "./output";
4599

46100
export type {

packages/schema/src/output.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,12 @@ export function initG<Context>(): typeof withoutContext &
599599
};
600600
}
601601

602+
export type initG<T> = T extends () => (args: any) => infer R
603+
? R
604+
: T extends (args: any) => infer R
605+
? R
606+
: never;
607+
602608
type Flatten<T> = {
603609
[Key in keyof T]: T[Key];
604610
} & {};

test-project/example.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { initG } from "@graphql-ts/schema";
2+
import { GraphQLSchema, graphql } from "graphql";
3+
4+
type Context = {
5+
loadPerson: (id: string) => Person | undefined;
6+
loadFriends: (id: string) => Person[];
7+
};
8+
const g = initG<Context>();
9+
type g<T> = initG<T>;
10+
11+
type Person = {
12+
id: string;
13+
name: string;
14+
};
15+
16+
const Person: g<typeof g.object<Person>> = g.object<Person>()({
17+
name: "Person",
18+
fields: () => ({
19+
id: g.field({ type: g.nonNull(g.ID) }),
20+
name: g.field({ type: g.nonNull(g.String) }),
21+
friends: g.field({
22+
type: g.list(g.nonNull(Person)),
23+
resolve(source, _, context) {
24+
return context.loadFriends(source.id);
25+
},
26+
}),
27+
}),
28+
});
29+
30+
const Query = g.object()({
31+
name: "Query",
32+
fields: {
33+
person: g.field({
34+
type: Person,
35+
args: {
36+
id: g.arg({ type: g.nonNull(g.ID) }),
37+
},
38+
resolve(_, args, context) {
39+
return context.loadPerson(args.id);
40+
},
41+
}),
42+
},
43+
});
44+
45+
const schema = new GraphQLSchema({
46+
query: Query,
47+
});
48+
49+
{
50+
const people = new Map<string, Person>([
51+
["1", { id: "1", name: "Alice" }],
52+
["2", { id: "2", name: "Bob" }],
53+
]);
54+
const friends = new Map<string, string[]>([
55+
["1", ["2"]],
56+
["2", ["1"]],
57+
]);
58+
const contextValue: Context = {
59+
loadPerson: (id) => people.get(id),
60+
loadFriends: (id) => {
61+
return (friends.get(id) ?? [])
62+
.map((id) => people.get(id))
63+
.filter((person) => person !== undefined) as Person[];
64+
},
65+
};
66+
graphql({
67+
source: `
68+
query {
69+
person(id: "1") {
70+
id
71+
name
72+
friends {
73+
id
74+
name
75+
}
76+
}
77+
}
78+
`,
79+
schema,
80+
contextValue,
81+
}).then((result) => {
82+
console.log(result);
83+
});
84+
}

test-project/index.test-d.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2071,3 +2071,67 @@ const someInputFields = {
20712071
Invariant<typeof a>
20722072
>();
20732073
}
2074+
2075+
{
2076+
type Person = {
2077+
name: string;
2078+
};
2079+
const Person: g<typeof g.object<Person>> = g.object<Person>()({
2080+
name: "Person",
2081+
fields: () => ({
2082+
name: g.field({ type: g.String }),
2083+
friends: g.field({
2084+
type: g.list(Person),
2085+
resolve() {
2086+
return [];
2087+
},
2088+
}),
2089+
}),
2090+
});
2091+
}
2092+
2093+
{
2094+
type Context = { loadFriends: (id: string) => Promise<Person[]> };
2095+
const g = initG<Context>();
2096+
type g<T> = initG<T>;
2097+
2098+
type Person = {
2099+
id: string;
2100+
name: string;
2101+
};
2102+
2103+
const Person: g<typeof g.object<Person>> = g.object<Person>()({
2104+
name: "Person",
2105+
fields: () => ({
2106+
id: g.field({ type: g.ID }),
2107+
name: g.field({ type: g.String }),
2108+
friends: g.field({
2109+
type: g.list(Person),
2110+
resolve(source, _, context) {
2111+
return context.loadFriends(source.id);
2112+
},
2113+
}),
2114+
}),
2115+
});
2116+
}
2117+
2118+
{
2119+
type Context = {};
2120+
const g = initG<Context>();
2121+
type g<T> = initG<T>;
2122+
2123+
type PersonFilter = g<
2124+
typeof g.inputObject<{
2125+
name: g<typeof g.arg<typeof g.String>>;
2126+
friends: g<typeof g.arg<PersonFilter>>;
2127+
}>
2128+
>;
2129+
2130+
const PersonFilter: PersonFilter = g.inputObject({
2131+
name: "PersonFilter",
2132+
fields: () => ({
2133+
name: g.arg({ type: g.String }),
2134+
friends: g.arg({ type: PersonFilter }),
2135+
}),
2136+
});
2137+
}

0 commit comments

Comments
 (0)