-
Notifications
You must be signed in to change notification settings - Fork 86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: support TypedDocumentString as query argument #609
base: main
Are you sure you want to change the base?
Changes from all commits
eee2a7d
22003e1
59264cb
19ab295
77edf5e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,17 +5,34 @@ import type { | |
RequestParameters, | ||
} from "./types.js"; | ||
import { graphql } from "./graphql.js"; | ||
import type { DocumentTypeDecoration } from "@graphql-typed-document-node/core"; | ||
|
||
export function withDefaults( | ||
request: typeof Request, | ||
newDefaults: RequestParameters, | ||
): ApiInterface { | ||
const newRequest = request.defaults(newDefaults); | ||
const newApi = <ResponseData>( | ||
query: Query | RequestParameters, | ||
query: | ||
| Query | ||
| (String & DocumentTypeDecoration<unknown, unknown>) | ||
| RequestParameters, | ||
options?: RequestParameters, | ||
) => { | ||
return graphql<ResponseData>(newRequest, query, options); | ||
const innerQuery = | ||
typeof query === "string" | ||
? query | ||
: // Allows casting String & DocumentTypeDecoration<unknown, unknown> to | ||
// string. This could be replaced with an instanceof check if we had | ||
// access to a shared TypedDocumentString. Alternatively, we could use | ||
// string & TypedDocumentDecoration<unknown, unknown> as the external | ||
// interface, and push `.toString()` onto the caller, which might not | ||
// be the worst idea. | ||
String.prototype.isPrototypeOf(query) | ||
? query.toString() | ||
: (query as RequestParameters); | ||
Comment on lines
+22
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Left some notes on this at: #609 |
||
|
||
return graphql<ResponseData>(newRequest, innerQuery, options); | ||
}; | ||
|
||
return Object.assign(newApi, { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,9 +5,25 @@ import { getUserAgent } from "universal-user-agent"; | |
import { graphql } from "../src"; | ||
import { VERSION } from "../src/version"; | ||
import type { RequestParameters } from "../src/types"; | ||
import type { DocumentTypeDecoration } from "@graphql-typed-document-node/core"; | ||
|
||
const userAgent = `octokit-graphql.js/${VERSION} ${getUserAgent()}`; | ||
|
||
class TypedDocumentString<TResult, TVariables> | ||
extends String | ||
implements DocumentTypeDecoration<TResult, TVariables> | ||
{ | ||
__apiType?: DocumentTypeDecoration<TResult, TVariables>["__apiType"]; | ||
|
||
constructor(private value: string) { | ||
super(value); | ||
} | ||
|
||
toString(): string & DocumentTypeDecoration<TResult, TVariables> { | ||
return this.value; | ||
} | ||
} | ||
|
||
describe("graphql()", () => { | ||
it("is a function", () => { | ||
expect(graphql).toBeInstanceOf(Function); | ||
|
@@ -77,6 +93,74 @@ describe("graphql()", () => { | |
}); | ||
}); | ||
|
||
it("README TypedDocumentString example", () => { | ||
const mockData = { | ||
repository: { | ||
issues: { | ||
edges: [ | ||
{ | ||
node: { | ||
title: "Foo", | ||
}, | ||
}, | ||
{ | ||
node: { | ||
title: "Bar", | ||
}, | ||
}, | ||
{ | ||
node: { | ||
title: "Baz", | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
}; | ||
|
||
const mock = fetchMock.createInstance().post( | ||
"https://api.github.com/graphql", | ||
{ data: mockData }, | ||
{ | ||
headers: { | ||
accept: "application/vnd.github.v3+json", | ||
authorization: "token secret123", | ||
"user-agent": userAgent, | ||
}, | ||
}, | ||
); | ||
|
||
const RepositoryDocument = new TypedDocumentString< | ||
{ | ||
repository: { issues: { edges: Array<{ node: { title: string } }> } }; | ||
}, | ||
Record<string, never> | ||
>(/* GraphQL */ ` | ||
{ | ||
repository(owner: "octokit", name: "graphql.js") { | ||
issues(last: 3) { | ||
edges { | ||
node { | ||
title | ||
} | ||
} | ||
} | ||
} | ||
} | ||
`); | ||
|
||
return graphql(RepositoryDocument, { | ||
headers: { | ||
authorization: `token secret123`, | ||
}, | ||
request: { | ||
fetch: mock.fetchHandler, | ||
}, | ||
}).then((result) => { | ||
expect(JSON.stringify(result)).toStrictEqual(JSON.stringify(mockData)); | ||
}); | ||
}); | ||
|
||
it("Variables", () => { | ||
const query = `query lastIssues($owner: String!, $repo: String!, $num: Int = 3) { | ||
repository(owner:$owner, name:$repo) { | ||
|
@@ -116,6 +200,54 @@ describe("graphql()", () => { | |
}); | ||
}); | ||
|
||
it("Variables with TypedDocumentString", () => { | ||
const query = new TypedDocumentString< | ||
{ | ||
repository: { issues: { edges: Array<{ node: { title: string } }> } }; | ||
}, | ||
Comment on lines
+205
to
+207
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't like that you have to manually type the query. You might as well do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To clarify: this is a manual annotation that is added as part of the unit tests, in order to test the interface around |
||
{ | ||
owner: string; | ||
repo: string; | ||
num?: number; | ||
} | ||
>(`query lastIssues($owner: String!, $repo: String!, $num: Int = 3) { | ||
repository(owner:$owner, name:$repo) { | ||
issues(last:$num) { | ||
edges { | ||
node { | ||
title | ||
} | ||
} | ||
} | ||
} | ||
}`); | ||
|
||
const mock = fetchMock | ||
.createInstance() | ||
.post("https://api.github.com/graphql", (callHistory) => { | ||
//@ts-ignore mock.fetchHandler is not typed | ||
const body = JSON.parse(mock.callHistory.calls()[0].options.body); | ||
expect(body.query).toEqual(query.toString()); | ||
expect(body.variables).toStrictEqual({ | ||
owner: "octokit", | ||
repo: "graphql.js", | ||
}); | ||
|
||
return { data: {} }; | ||
}); | ||
|
||
return graphql(query, { | ||
headers: { | ||
authorization: `token secret123`, | ||
}, | ||
owner: "octokit", | ||
repo: "graphql.js", | ||
request: { | ||
fetch: mock.fetchHandler, | ||
}, | ||
}); | ||
}); | ||
|
||
it("Pass headers together with variables as 2nd argument", () => { | ||
const query = `query lastIssues($owner: String!, $repo: String!, $num: Int = 3) { | ||
repository(owner:$owner, name:$repo) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm trying to balance giving a complete guide to graphql-codegen and the GitHub GraphQL API, but maybe we can be more descriptive here 💭