Skip to content
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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

fpapado
Copy link

@fpapado fpapado commented Nov 3, 2024

Resolves #596


Before the change?

  • Prior to this, one could use TypedDocumentString with graphql-codegen, but would have to write their own wrapper around graphql, in order to ensure type-safety. This is not a big deal to be honest, but having support out of the box simplifies ergonomics.

After the change?

  • After this change, one additional override has been added to the public API interface. It allows providing a query as String & DocumentTypeDecoration<ResponseData, QueryVariables>. This the definition of TypedDocumentString from graphql-codegen/ graphql-typed-document-node. DocumentTypeDecoration itself is the lowest common denominator for type-safety in that ecosystem. If the user provides such a query, then we can have type-safety for the query parameters and return value.
  • There are some shortcomings of this approach, which I will list below 🗒️

Pull request checklist

  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been reviewed and added / updated if needed (for bug fixes / features)

Does this introduce a breaking change?

Please see our docs on breaking changes to help!

  • Yes
  • No

This is not a breaking change. The API did not accept plain TypedDocumentStrings previously, whereas now it does. I also took care to with the interface overrides, to preserve RequestParameters as optional, unless the query is a TypedDocumentString with required variables.

Aside: I would be happy to contribute a few type-only tests for the public API interface. The overrides can lead to subtle changes, and I've found type-only tests to be useful to catch regressions in similar projects.


Open remarks

I am not fully happy with the shape here, and I have considered a couple of alternatives, that I could use your input on 👀

Lack of a shared TypedDocumentString type

The first issue is that graphql-codegen does not expose a shared TypedDocumentString type from its core types, but rather inlines it in the codegen output. The type has a runtime representation (as a class), so it is a bit more complicated than the type-only TypedDocumentNode. This is discussed both in the original issue and a follow-up issue in graphql-typed-document-node.

The type String & DocumentTypeDecoration works well-enough, because DocumentTypeDecoration has the actionable types, but that means that the contract between this library and graphql-codegen output is less explicit. Alternative tools do not have access to TypedDocumentString the same way that they have access to TypedDocumentNode.

String & DocumentTypeDecoration vs string & DocumentTypeDecoration

Partly due to the lack of a shared type, and partly because of the runtime class representation, we are currently doing a not-as-nice String.prototype.isPrototypeOf check to duck-type the TypedDocumentString, and call .toString() on it. This could be nicer with a typeof check, but that is only part of my hesitation.

An alternative would be to use string & DocumentTypeDecoration as the interface.

In practice, this means that instead of this:

graphql(someTypedDocumentString, {/* params */})

...the caller would have to do:

graphql(someTypedDocumentString.toString(), {/* params */})

This simplifies the internal implementation and reliance on the runtime representation of TypedDocumentString, at the cost of some developer ergonomics.

I might be overthinking this; I would be happy to implement it/document it whichever way you prefer 😇

Missing one implementation overload

This one is small: I did not implement a TypedDocumentString overload for the query-as-request-parameter API shape. I am happy to implement it, but first I would like feedback on the above points, before going down that rabbit hole 😅

These are all the open discussion points that I can think of. Please let me know if there is any other information that I can provide, and I'll get back to you. And thank you for all your work on octokit; it's been a joy to use over the years!

Copy link

github-actions bot commented Nov 3, 2024

👋 Hi! Thank you for this contribution! Just to let you know, our GitHub SDK team does a round of issue and PR reviews twice a week, every Monday and Friday! We have a process in place for prioritizing and responding to your input. Because you are a part of this community please feel free to comment, add to, or pick up any issues/PRs that are labeled with Status: Up for grabs. You & others like you are the reason all of this works! So thank you & happy coding! 🚀

Comment on lines +265 to +267
If your query is represented as a `String & DocumentTypeDecoration`, for example as [`TypedDocumentString` from graphql-codegen and its client preset](https://the-guild.dev/graphql/codegen/docs/guides/vanilla-typescript), then you can get type-safety for the query's parameters and return values.

Assuming you have configured graphql-codegen, with [GitHub's GraphQL schema](https://docs.github.com/en/graphql/overview/public-schema), and an output under `./graphql`:
Copy link
Author

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 💭

Comment on lines +22 to +33
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);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some notes on this at: #609

Comment on lines 7 to 13
// TODO: This setting is not compatible with the default definition of
// DocumentTypeDecoration from '@graphql-codegen-types/core'. That
// definition includes `{__apiType?: ...}` as an optional, so assigning it
// to an explicit `undefined` is impossible under
// exactOptionalPropertyTypes. We only need to work around this in tests,
// since that is where we use concrete examples of DocumentTypeDecoration.
"exactOptionalPropertyTypes": false
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit cursed, but I do not know of a way to work around this from the call-site. Here is relevant part in graphql-typed-document-node

@fpapado fpapado marked this pull request as ready for review November 3, 2024 14:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: 👀 In review
Development

Successfully merging this pull request may close these issues.

[FEAT]: Support TypedDocumentNode
1 participant