Skip to content

Commit cb81e8e

Browse files
committed
Document derived context
1 parent f9b80ae commit cb81e8e

19 files changed

+609
-68
lines changed

src/Errors.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,3 +607,19 @@ export function noTypesDefined() {
607607
export function tsConfigNotFound(cwd: string) {
608608
return `Grats: Could not find \`tsconfig.json\` searching in ${cwd}.\n\nSee https://www.typescriptlang.org/download/ for instructors on how to add TypeScript to your project. Then run \`npx tsc --init\` to create a \`tsconfig.json\` file.`;
609609
}
610+
611+
export function cyclicDerivedContext() {
612+
return `Cyclic dependency detected in derived context. This derived context value depends upon itself.`;
613+
}
614+
615+
export function invalidDerivedContextArgType() {
616+
return "Invalid type for derived context function argument. Derived context functions may only accept other `@gqlContext` types as arguments.";
617+
}
618+
619+
export function missingReturnTypeForDerivedResolver() {
620+
return 'Expected derived resolver to have an explicit return type. This is needed to allow Grats to "see" which type to treat as a derived context type.';
621+
}
622+
623+
export function derivedResolverInvalidReturnType() {
624+
return "Expected derived resolver function's return type to be a type reference. Grats uses this type reference to determine which type to treat as a derived context type.";
625+
}

src/Extractor.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,6 @@ class Extractor {
139139
name: NameNode,
140140
kind: NameDefinition["kind"],
141141
): void {
142-
// @ts-ignore FIXME
143142
this.nameDefinitions.set(node, { name, kind });
144143
}
145144

@@ -341,10 +340,10 @@ class Extractor {
341340
recordDerivedContext(node: ts.FunctionDeclaration, tag: ts.JSDocTag) {
342341
const returnType = node.type;
343342
if (returnType == null) {
344-
throw new Error("Function declaration must have a return type");
343+
return this.report(node, E.missingReturnTypeForDerivedResolver());
345344
}
346345
if (!ts.isTypeReferenceNode(returnType)) {
347-
throw new Error("Function declaration must return an explicit type");
346+
return this.report(returnType, E.missingReturnTypeForDerivedResolver());
348347
}
349348

350349
const funcName = this.namedFunctionExportName(node);

src/TypeContext.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,14 @@ export class TypeContext {
8787
const existing = self._declarationToName.get(declaration);
8888
if (existing != null) {
8989
errors.push(
90-
// TODO: Better error messages here
91-
tsErr(declaration, "Duplicate derived contexts for given type", [
92-
tsRelated(reference, "One was defined here"),
93-
gqlRelated(existing.name, "Other here"),
94-
]),
90+
tsErr(
91+
declaration,
92+
"Multiple derived contexts defined for given type",
93+
[
94+
gqlRelated(definition.name, "One was defined here"),
95+
gqlRelated(existing.name, "Another here"),
96+
],
97+
),
9598
);
9699
continue;
97100
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/** @gqlContext */
2+
type RootContext = {
3+
userName: string;
4+
};
5+
6+
type DerivedContext = {
7+
greeting: string;
8+
};
9+
10+
/** @gqlContext */
11+
export function createDerivedContext(
12+
ctx: RootContext,
13+
oops: DerivedContext,
14+
): DerivedContext {
15+
return { greeting: `Hello, ${ctx.userName}!` };
16+
}
17+
18+
/** @gqlType */
19+
type Query = unknown;
20+
21+
/** @gqlField */
22+
export function greeting(_: Query, ctx: DerivedContext): string {
23+
return ctx.greeting;
24+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
-----------------
2+
INPUT
3+
-----------------
4+
/** @gqlContext */
5+
type RootContext = {
6+
userName: string;
7+
};
8+
9+
type DerivedContext = {
10+
greeting: string;
11+
};
12+
13+
/** @gqlContext */
14+
export function createDerivedContext(
15+
ctx: RootContext,
16+
oops: DerivedContext,
17+
): DerivedContext {
18+
return { greeting: `Hello, ${ctx.userName}!` };
19+
}
20+
21+
/** @gqlType */
22+
type Query = unknown;
23+
24+
/** @gqlField */
25+
export function greeting(_: Query, ctx: DerivedContext): string {
26+
return ctx.greeting;
27+
}
28+
29+
-----------------
30+
OUTPUT
31+
-----------------
32+
src/tests/fixtures/derived_context/cyclicContextDependency.invalid.ts:10:5 - error: Cyclic dependency detected in derived context. This derived context value depends upon itself.
33+
34+
10 /** @gqlContext */
35+
~~~~~~~~~~~~
36+
37+
src/tests/fixtures/derived_context/cyclicContextDependency.invalid.ts:13:3
38+
13 oops: DerivedContext,
39+
~~~~~~~~~~~~~~~~~~~~
40+
This derived context depends on itself
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/** @gqlContext */
2+
type RootContext = {
3+
userName: string;
4+
};
5+
6+
type A = {
7+
greeting: string;
8+
};
9+
10+
/** @gqlContext */
11+
export function a(ctx: RootContext, b: B): A {
12+
return { greeting: `Hello, ${ctx.userName}!` };
13+
}
14+
15+
type B = {
16+
greeting: string;
17+
};
18+
19+
/** @gqlContext */
20+
export function b(ctx: RootContext, c: C): B {
21+
return { greeting: `Hello, ${ctx.userName}!` };
22+
}
23+
24+
type C = {
25+
greeting: string;
26+
};
27+
28+
/** @gqlContext */
29+
export function c(ctx: RootContext, a: A): C {
30+
return { greeting: `Hello, ${ctx.userName}!` };
31+
}
32+
33+
/** @gqlType */
34+
type Query = unknown;
35+
36+
/** @gqlField */
37+
export function greeting(_: Query, ctx: A): string {
38+
return ctx.greeting;
39+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
-----------------
2+
INPUT
3+
-----------------
4+
/** @gqlContext */
5+
type RootContext = {
6+
userName: string;
7+
};
8+
9+
type A = {
10+
greeting: string;
11+
};
12+
13+
/** @gqlContext */
14+
export function a(ctx: RootContext, b: B): A {
15+
return { greeting: `Hello, ${ctx.userName}!` };
16+
}
17+
18+
type B = {
19+
greeting: string;
20+
};
21+
22+
/** @gqlContext */
23+
export function b(ctx: RootContext, c: C): B {
24+
return { greeting: `Hello, ${ctx.userName}!` };
25+
}
26+
27+
type C = {
28+
greeting: string;
29+
};
30+
31+
/** @gqlContext */
32+
export function c(ctx: RootContext, a: A): C {
33+
return { greeting: `Hello, ${ctx.userName}!` };
34+
}
35+
36+
/** @gqlType */
37+
type Query = unknown;
38+
39+
/** @gqlField */
40+
export function greeting(_: Query, ctx: A): string {
41+
return ctx.greeting;
42+
}
43+
44+
-----------------
45+
OUTPUT
46+
-----------------
47+
src/tests/fixtures/derived_context/cyclicContextDependencyWithChain.invalid.ts:10:5 - error: Cyclic dependency detected in derived context. This derived context value depends upon itself.
48+
49+
10 /** @gqlContext */
50+
~~~~~~~~~~~~
51+
52+
src/tests/fixtures/derived_context/cyclicContextDependencyWithChain.invalid.ts:11:37
53+
11 export function a(ctx: RootContext, b: B): A {
54+
~~~~
55+
This derived context depends on
56+
src/tests/fixtures/derived_context/cyclicContextDependencyWithChain.invalid.ts:20:37
57+
20 export function b(ctx: RootContext, c: C): B {
58+
~~~~
59+
Which in turn depends on
60+
src/tests/fixtures/derived_context/cyclicContextDependencyWithChain.invalid.ts:29:37
61+
29 export function c(ctx: RootContext, a: A): C {
62+
~~~~
63+
Which ultimately creates a cycle back to the initial derived context

src/tests/fixtures/derived_context/derivedContextChain.ts.expected

Lines changed: 34 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -51,38 +51,37 @@ export function consumingMultipleContexts(
5151
-----------------
5252
OUTPUT
5353
-----------------
54-
-- SDL --
55-
type Query {
56-
consumingMultipleContexts: String
57-
greeting: String
58-
}
59-
-- TypeScript --
60-
import { consumingMultipleContexts as queryConsumingMultipleContextsResolver, createDerivedContextA as createDerivedContextA, createDerivedContextB as createDerivedContextB, allTheContexts as allTheContexts, greeting as queryGreetingResolver } from "./derivedContextChain";
61-
import { GraphQLSchema, GraphQLObjectType, GraphQLString } from "graphql";
62-
export function getSchema(): GraphQLSchema {
63-
const QueryType: GraphQLObjectType = new GraphQLObjectType({
64-
name: "Query",
65-
fields() {
66-
return {
67-
consumingMultipleContexts: {
68-
name: "consumingMultipleContexts",
69-
type: GraphQLString,
70-
resolve(source, _args, context) {
71-
return queryConsumingMultipleContextsResolver(source, context, createDerivedContextA(context), createDerivedContextB(createDerivedContextA(context)), allTheContexts(context, createDerivedContextA(context), createDerivedContextB(createDerivedContextA(context))));
72-
}
73-
},
74-
greeting: {
75-
name: "greeting",
76-
type: GraphQLString,
77-
resolve(source) {
78-
return queryGreetingResolver(source, allTheContexts(context, createDerivedContextA(context), createDerivedContextB(createDerivedContextA(context))));
79-
}
80-
}
81-
};
82-
}
83-
});
84-
return new GraphQLSchema({
85-
query: QueryType,
86-
types: [QueryType]
87-
});
88-
}
54+
src/tests/fixtures/derived_context/derivedContextChain.ts:6:5 - error: Cyclic dependency detected in derived context. This derived context value depends upon itself.
55+
56+
6 /** @gqlContext */
57+
~~~~~~~~~~~~
58+
59+
src/tests/fixtures/derived_context/derivedContextChain.ts:23:3
60+
23 a: DerivedContextA,
61+
~~~~~~~~~~~~~~~~~~
62+
This derived context depends on
63+
src/tests/fixtures/derived_context/derivedContextChain.ts:24:3
64+
24 b: DerivedContextB,
65+
~~~~~~~~~~~~~~~~~~
66+
Which in turn depends on
67+
src/tests/fixtures/derived_context/derivedContextChain.ts:14:39
68+
14 export function createDerivedContextB(ctx: DerivedContextA): DerivedContextB {
69+
~~~~~~~~~~~~~~~~~~~~
70+
Which ultimately creates a cycle back to the initial derived context
71+
src/tests/fixtures/derived_context/derivedContextChain.ts:6:5 - error: Cyclic dependency detected in derived context. This derived context value depends upon itself.
72+
73+
6 /** @gqlContext */
74+
~~~~~~~~~~~~
75+
76+
src/tests/fixtures/derived_context/derivedContextChain.ts:23:3
77+
23 a: DerivedContextA,
78+
~~~~~~~~~~~~~~~~~~
79+
This derived context depends on
80+
src/tests/fixtures/derived_context/derivedContextChain.ts:24:3
81+
24 b: DerivedContextB,
82+
~~~~~~~~~~~~~~~~~~
83+
Which in turn depends on
84+
src/tests/fixtures/derived_context/derivedContextChain.ts:14:39
85+
14 export function createDerivedContextB(ctx: DerivedContextA): DerivedContextB {
86+
~~~~~~~~~~~~~~~~~~~~
87+
Which ultimately creates a cycle back to the initial derived context
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/** @gqlContext */
2+
type RootContext = {
3+
userName: string;
4+
};
5+
6+
type DerivedContext = {
7+
greeting: string;
8+
};
9+
10+
/** @gqlContext */
11+
export function createDerivedContext(ctx: RootContext) {
12+
return { greeting: `Hello, ${ctx.userName}!` };
13+
}
14+
15+
/** @gqlType */
16+
type Query = unknown;
17+
18+
/** @gqlField */
19+
export function greeting(_: Query, ctx: DerivedContext): string {
20+
return ctx.greeting;
21+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
-----------------
2+
INPUT
3+
-----------------
4+
/** @gqlContext */
5+
type RootContext = {
6+
userName: string;
7+
};
8+
9+
type DerivedContext = {
10+
greeting: string;
11+
};
12+
13+
/** @gqlContext */
14+
export function createDerivedContext(ctx: RootContext) {
15+
return { greeting: `Hello, ${ctx.userName}!` };
16+
}
17+
18+
/** @gqlType */
19+
type Query = unknown;
20+
21+
/** @gqlField */
22+
export function greeting(_: Query, ctx: DerivedContext): string {
23+
return ctx.greeting;
24+
}
25+
26+
-----------------
27+
OUTPUT
28+
-----------------
29+
src/tests/fixtures/derived_context/derivedContextNoReturnType.invalid.ts:11:1 - error: Expected derived resolver to have an explicit return type. This is needed to allow Grats to "see" which type to treat as a derived context type.
30+
31+
11 export function createDerivedContext(ctx: RootContext) {
32+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
33+
12 return { greeting: `Hello, ${ctx.userName}!` };
34+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
35+
13 }
36+
~

0 commit comments

Comments
 (0)