teamwalnut / rescript-urql Goto Github PK
View Code? Open in Web Editor NEWReScript bindings for Formidable's Universal React Query Library, urql.
License: MIT License
ReScript bindings for Formidable's Universal React Query Library, urql.
License: MIT License
As the first step to starting work on v1
, we'll need to upgrade the urql
devDependency and peerDependency to ^1.0.0
.
Hello, all!
I have been using reason-urql
since hooks got in, and one of the pain points has been around error handling. Right now, there's no way to inspect the errors that happened during a request, as the error type is simply an UrqlCombinedError.t
with no helper function to extract more information from that type.
What's the plan regarding error handling, and is this error API what's gonna ship with 1.0?
Similar to how urql
has an examples
directory (modeled loosely after Next's), we'll want to have some examples
to demonstrate how to use reason-urql
with ReasonReact. We should use yarn workspaces to manage dependencies across individual examples. Each should therefore have their own package.json
and bsconfig.json
. For the most part, all dependencies can be shared (bs-platform
, webpack
, bs-css
, reason-react
) and we'll take a file:
dependency on reason-urql
.
Now that we have migrated all module
s under the ReasonUrql
module
, we ought to update the docs to reflect this.
As part of the v1
migration, we'll need to update the bindings for Client
. Probably the most significant change will be the addition of bindings for exchanges. This may be simplified by the fact that, under the hood, exchanges are powered by wonka
, whose source is written in Reason. Other than that, our life is greatly simplified b/c we no longer have cache operations to keep track of in the Client
.
cc/ @gugahoa @Schmavery I'm not totally sure how to fix this one, so reaching out for help.
In examples/2-query
in Monster.re
, change L37 from this:
let (result, _) = Hooks.useQuery(~request, ());
to this:
useQuery(~request, ());
and add this at the top:
open Hooks;
When doing this, I get an interesting type error:
We've found a bug for you!
/Users/parkerziegler/Documents/repos/OSS/reason-urql/examples/2-query/src/Monster.re 38:25-46
36 ┆ value passed in from GetAll */
37 ┆ let request = GetPokemon.make(~name=pokemon, ());
38 ┆ let ({response}, _) = useQuery(~request, ());
39 ┆
40 ┆ switch (result.response) {
This has type:
ReasonUrql.Hooks.useQueryResponse({. "pokemon": option({. "classification":
option(string),
"height":
option({. "maximum":
option(
string)}),
"image":
option(string),
"name":
option(string),
"weight":
option({. "maximum":
option(
string)})})})
(defined as
(ReasonUrql.Hooks.useQueryState({. "pokemon": option({. "classification":
option(string),
"height":
option({. "maximum":
option(
string)}),
"image":
option(string),
"name":
option(string),
"weight":
option({. "maximum":
option(
string)})})}),
ReasonUrql.Hooks.partialOperationContextFn))
But somewhere wanted:
(ReasonUrql.Hooks.useSubscriptionResponse('a), 'b)
The incompatible parts:
ReasonUrql.Hooks.useQueryState({. "pokemon": option({. "classification":
option(string),
"height": option(
{. "maximum":
option(
string)}),
"image": option(
string),
"name": option(
string),
"weight": option(
{. "maximum":
option(
string)})})})
(defined as
UrqlUseQuery.useQueryState({. "pokemon": option({. "classification":
option(string),
"height": option(
{. "maximum":
option(
string)}),
"image": option(
string),
"name": option(
string),
"weight": option(
{. "maximum":
option(
string)})})}))
vs
ReasonUrql.Hooks.useSubscriptionResponse('a) (defined as
UrqlUseSubscription.useSubscriptionResponse('a))
I'm not totally sure why this is happening. I'm wondering if Reason is searching for the record type associated with the destrucutred { response }
and it's saying, "Hey, that record is type useSubscriptionResponse
" That's my best guess. If so, I'm thinking we may need to do one-off module UseQuery = UrqlUseQuery;
such that only the definitions from UrqlUseQuery
are brought into scope and the proper record type useQueryResponse
is inferred. Any other thoughts? I also tried adding a top-level type hookReponse('ret)
since our hooks have consistent return structures, but I got an error w/ the useSubscription
GADT b/c type ret
there would escape its scope.
Now that we have bindings for both Query
and Mutation
in place, let's complete the loop with Subscription
! @kiraarghy assigning this to you for now, but feel free to pick it up only when you have time.
I'm pretty excited to try to use this in some projects, but just realized after reading through #64 how the typing works for the hooks api. @gugahoa brought up some really good points (I thought) about how this could potentially be improved.
I think it would be hard for me to use this in real projects if I couldn't rely on the type of the response (though @parkerziegler did provide a clever workaround in that thread!).
I was thinking about this a bit wondering what you thought about a slightly different api.
Taking useQuery
as an example, it seems from one of the examples that this is a pattern for using it:
https://github.com/FormidableLabs/reason-urql/blob/14bb74d1d003b9c9a058b64f65f4b11312f3dfa7/examples/2-query/src/Monster.re#L39-L41
Where we use graphql_ppx to create a query and variables object and then pass them into useQuery. I was wondering if there would be any downsides to simply passing in the entire result from calling GetPokemon.make
(in this example)?
To be clear, the usage would look something like this:
let request = GetPokemon.make(~name=pokemon, ());
let ({response}, _executeQuery) = useQuery(request, ());
It seems like if you did that, the type of useQuery could be:
let useQuery:
({
.
"parse": Js.Json.t => 'response,
"query": string,
"variables": 'vars,
},
~requestPolicy: UrqlTypes.requestPolicy=?,
~pause: bool=?,
unit) =>
useQueryResponse('response);
The takeaway, as far as the types are concerned, would be that the type checker would be able to know what the return type of useQuery is.
Implementation-wise, inside of useQuery you could access the query and variables parameters as before, with the added bonus that you could access the parse function too, if you wanted to do any runtime validation/translation of the returned object (which would have the added benefit of supporting graphql_ppx features like the super-handy @bsDecoders).
I haven't had a chance to check if the same technique could work for mutations/subscriptions yet but wanted to get people's thoughts in case this would be a big enough signature change to warrant trying to squeeze in before v1?
urql
allows users to pass a custom cache
implementation to their client
: https://github.com/FormidableLabs/urql#custom-caches We'll need to figure out a way to support this, likely using type parameters (e.g. generics).
I don't quite remember if it's being used, but if it's not, it would be better to remove it as it causes conflicts when using the new recommended library graphql_ppx_re
Previously this issue was tracked in #26, but it's complex enough that I figured we'd make it a separate issue. We want reason-urql
to add support for exchanges. This shouldn't be too difficult, as urql
already has a set of exchanges that are a part of the core API. We just need to expose those using [@bs.module]
declarations. Adding exchange types will be a bit more complex and should be based off of the urql/types.ts
module.
subscriptionExchange
.[@bs.deriving abstract]
type useQueryArgs = {
query: string,
variables: Js.Json.t,
[@bs.optional]
requestPolicy: UrqlTypes.requestPolicy,
[@bs.optional]
pause: bool,
};
This type doesn't convert nested UrqlTypes.requestPolicy
type when creating.
When doing:
let args =
useQueryArgs(
~query=request##query,
~variables=request##variables,
~requestPolicy?,
~pause?,
(),
);
is should be
let args =
useQueryArgs(
~query=request##query,
~variables=request##variables,
~requestPolicy=requestPolicy->Belt.Option.map(UrqlTypes.requestPolicyToJs),
~pause?,
(),
);
This way string instead of int will be passed int "requestPolicy"
key in urql
options.
Like we have for the 'Mutation' component create an example of how this component can be used with reason-urql V1.
You guys have a module called Types on ReasonUrql
which will/does clash with anyone who defines a module called types in their project. Just an FYI. Please let me know if you need further explanation.
For future reference for readers. You can get around this by add namespace:true
to your bsconfig.json
and referencing your own types module with open MyProjectNamespace.Types;
or MyProjectNamespace.Types.myfunction();
The way to generate a graphql_schema.json
is different on graphql_ppx_re
so the instructions on the README right now will lead to an error when setting up a new project
We should copy the instructions from graphql_ppx_re
and have another section with instructions if the user decides to use graphql_ppx
only
To have a really good developer experience, we should setup hot reloading for the reason-urql
example project. In doing this, let's take stock of our current webpack
setup, removing what feels unnecessary and adding in any niceties. Some thoughts:
webpack-dev-server
.--hot
CLI flag with webpack-dev-server
(adds HMR automatically). See this documentation.url-loader
for loading static assets.I get the following runtime error when trying to update to Reason Urql 2:
The parse
function is generated by the graphql ppx
and will indeed accept an object, not null.
After inverstigating a bit more, I could find that the following function (which I use), https://github.com/FormidableLabs/reason-urql/blob/main/src/Client.re#L221 will pass the parse
function to https://github.com/FormidableLabs/reason-urql/blob/main/src/Client.re#L188, which in turn, will use the Belt.Option.map
function to conditionally call the parse
function.
The issue arises because of the type of response
, which is assumed to be https://github.com/FormidableLabs/reason-urql/blob/main/src/Types.re#L81, where data
is an option(Js.Json.t)
, which is not at runtime, it's actually a Js.Nullable.t(Js.Json.t)
.
But maybe I'm doing something wrong?
Edit: The error attribute is also "wrong" since it's assumed to be an option as well. Also, it seems this function https://github.com/FormidableLabs/reason-urql/blob/main/src/Types.re#L135 has the same issue 😕
Edit 2: With the -lean-parse
option turned off, I get this error instead:
This issue is very similar to #22, and should take a similar approach. Let's keep the code paths as convergent as possible between these components and hooks.
To make things easier when developing subscriptions and testing the subscriptionExchange
, we'll want to add a server similar to the setup over on urql
.
The logic here https://github.com/FormidableLabs/reason-urql/blob/8bb9cf6de62cc3b870bb7b7e59c79f136f04c0ca/src/components/UrqlMutation.re#L59 looks a bit off for dynamic mutations.
By definition, a dynamic mutation will occur after the hook is called, so fetching
will always be false
, and data
and error
will always be None
as long as the mutation hasn't been called.
I propose to introduce an Init
(or something similar) to handle this case in the response type. This will very likely require the use of a state to know whether or not the mutation has been triggered already or not.
We could even consider having a Refetching
constructor if needed.
The Relude library handles this case as well: https://github.com/reazen/relude/blob/master/src/Relude_AsyncData.re#L11
As shown in this example, mutation variables are passed when hook is created. https://github.com/FormidableLabs/reason-urql/blob/master/examples/3-mutation/src/Dog.re#L66
In my use case I want to create mutation hook at top of the component but pass variables later (for example from form submission callback function). Does reason-urql
support it. I cannot find any documentation or example about such case.
let (_, executeSignInMutation) = Hooks.useMutation();
let form =
Form.useForm(~onSubmit=(state, _) =>
executeSignInMutation(
~request=
Mutations.SignIn.make(
~email=state.email,
~password=state.password,
(),
),
)
|> ignore
);
I was just looking at making some graphql queries from a non-react (nodejs) project and realized that it seems like the type of the response from Client.executeQuery
is a Wonka.Types.sourceT('a)
, so it seems like it exhibits the same issues as #80 and #67. Same with executeMutation
and executeSubscription
.
Does that sound right? I might be misreading how to use it.
Now that reason-urql
is published to npm
, we need to add a document to help contributors get started on the project!
I am dropping here some mistakes I got following getting started doc:
Hooks.useMutation(~request, ());
it seems that extra () are causing trouble. It seems that useMutation only accept a single argument https://github.com/FormidableLabs/reason-urql/blob/4b99ef4e11f371516b53bc5b557d2872c7af0a78/src/hooks/UseMutation.rei#L22 so it should be update in all the docsClient.(make(
instead to open Client & make Exchange available)What would this look like in reason-urql
?
const mutate = React.useCallback(() => {
executeMutation({ email, password, name })
.then(({ data }) => {
const token = data && data[isLogin ? 'login' : 'signup'].token
if (token) {
setToken(token)
props.history.push('/')
}
});
}, [executeMutation, props.history, email, password, name, isLogin]);
I can't seem to get the token back and setting it.
I have tried a bunch of things. Here is where I am.
let handleToken = token => {
Js.log2("TOKEN_DATA", token);
Token.setToken(token);
ReasonReactRouter.push("/");
};
let submitLogin = () => {
executeLoginMutation() |> ignore;
Js.Promise.then_(loginState => {
Js.log2("TOKEN_loginState", loginState);
let token = loginState##data##signup##token->Belt.Option.getExn;
handleToken(token)
Js.Promise.resolve();
})
|> ignore;
// ReasonReactRouter.push("/");
};
Here I try to log the response but get nothing:
let submitLogin = () => {
executeLoginMutation()
|> response => {
Js.log2("submitLogin",response)
Js.log(response)
};
ReasonReactRouter.push("/");
};
I'm probably just not familiar enough with the library. Any guidance would be appreciated. Thank you.
Hi, if you're executing a query directly e.g:
ReasonUrql.Client.executeQuery(~client=Client.client, ~request, ())
|> Wonka.subscribe((. {response}: ReasonUrql.Client.ClientTypes.clientResponse('a)) => {
There's no way to unsubscribe(if not being done in a sideeffect).
In order to ensure everyone is using the same version of refmt
when committing their code, we'll want to add husky
and lint-staged
to run on pre-commit.
With [email protected]
out, we'll want to start migrating our in app examples over to this version to ensure compatibility. This will also entail upgrading the root's bs-platform
devDependency
to 6.2.1. Finally, this also means that all examples will need to use @baransu/graphql_ppx_re/ppx6
.
Currently, we allow users to pass anything in their fetchOptions
argument to Client
as long as they provide a type for it. However, urql
uses the RequestInit
interface
for fetch
under the hood to type its fetchOptions
. To support this, and to give users some guard rails when supplying fetchOptions
, we should use the requestInit
type from bs-fetch
: https://github.com/reasonml-community/bs-fetch
We also don't need to type this parameter as a polymorphic variant. Instead, we can just make it a regular variant i.e.:
type fetchOptions =
| FetchObj(requestInit)
| FetchFn(unit => requestInit);
bs-fetch
fetchOptions
typeIt would be nice to start implementing a CI pipeline for reason-urql post V1 to ensure that we don't have build issues, or an Urql update causes our bindings to fail.
@kadikraman seems to be having success using Sail CI for React Native App Auth so I'm up for using that!
Goal: Implement a simple build and verification pipeline for reason-urql with the goal to expand it later on!
urql
v1 adds a specific component for Query
, in addition to a useQuery
hook: https://github.com/FormidableLabs/urql/blob/master/docs/api.md.
For this issue, it'd be ideal to just focus on binding these two pieces of the API. Binding Query
should be as simple as using ReasonReact.wrapJsForReason
and typing the props. We will want to re-use some of the nice utilities we have in place to convert fetching
, data
, error
to a variant (see urqlDataToVariant
). We should keep that function generic so we can 1) unit test it, and 2) use it for Mutation
. Binding the hook should also be fairly easy using standard BuckleScript FFI for functions.
PRs for this issue should target the v1
branch.
In an effort to prevent module
namespace collisions with consuming apps, we'll want to ensure that all individual modules in src
get prefixed with Urql
, i.e. UrqlProvider
and UrqlConnect
. Then, in our top level ReasonUrql
module, we'll want to re-export
(in a sense) those modules by using module
aliasing, i.e. module Provider = UrqlProvider
. This should allow for nice module
references, i.e. ReasonUrql.Query
and ReasonUrql.Provider
that don't conflict with consuming code.
The ppx mentioned in the README is not maintained anymore (announcement). It also may not support BuckleScript 6 (source). Please consider using this one instead.
This is a good and opportune moment to take some time to reflect on a couple of reason-urql
-specific structures and APIs. Specifically, reason-urql
is meant to be idiomatic Reason code, but some parts of the API have been tied to bindings and expose some of the disconnect between JS/TS and Reason.
Because we're preparing to land v2, this may be a good moment to:
We can collect ideas in this umbrella issue and close it as soon as v2
is released.
Currently the project is structured like a JS project with lots of files and some potentially confusing locations.
A lot of the context is typed and directly accessible. It may make sense to extend the hook API to be more idiomatic for the GraphQL bindings, and to integrate more of the context into the hooks' arguments.
request
be its own curried argument?unit
be last in those hooks? We do have the required request
argument.partialOperationContext
could be integrated as a set of labelled optional arguments instead, right into the hook. We could write a small helper for that and pipe all arguments through. requestPolicy
is duplicate for instance, url
can be integrated, etc. Optimally the context itself should just be a JSON type and all relevant context options could be surfaced as labelled arguments.We haven't bound to some crucial exchanges yet and this could be an opportunity to add them?
In the PR #69 by @Schmavery where typed use mutation was introduced, there was also a suggestion on making the interface for useMutation
take makeWithVariables
as parameter to let users pass variables later. In hindsight, it seems this is the expected behavior by users as can be seen here: https://spectrum.chat/urql/reason/usemutation-api~72b6306a-8d92-4d17-9fff-15826dd64070 and here #103
What are the thoughts on creating another useMutation
/useQuery
function where it's possible to pass the variables when calling executeMutation
/executeQuery
?
https://github.com/FormidableLabs/reason-urql/blob/master/src/components/UrqlQuery.re#L71
pause
should be changed to ?pause
69 ┆ variables
70 ┆ requestPolicy={UrqlTypes.requestPolicyToJs(requestPolicy)}
71 ┆ pause>
72 ┆ {result => result |> urqlQueryResponseToReason(parse) |> children}
73 ┆ </QueryJs>;
This has type:
option(bool)
But somewhere wanted:
bool
Hi, first of all, tons of love for making this lib happen! amazing job!
Not sure if i missed something, but couldnt find a way to work with Upload types.
(tried but couldnt make it work)
Hey, seems like urql is at 1.8.2 and specifically it seems like some major critical bugs have landed there.
Anything specific preventing from following up more closely with mainline urql?
urql
v1.1.0 introduced server-side rendering and basic support for Suspense on the server via react-ssr-prepass
. With the new goal being to bind reason-urql
's next version to urql
v1.1.3, this becomes a major priority for us. The responsibility on the binding side is actually quite small (relatively) here:
urql
dependency to v1.1.3. This is the v1.1.x
version that contains several important bug fixes before jumping to 1.2.0, which may require a change to the bindings to work properly.suspense
flag to Client
configuration.react-ssr-prepass
– this should be done in a separate repo at FormidableLabs/bs-react-ssr-prepass
.ssrExchange
.ssrExchange
.Based off on work in #68, #69, and #60, we'll want to ensure our components have the same level of type safety as our hooks now do. This gets trickiest with Subscription
, whose return data type will differ based on whether a handler
is supplied or not. We may consider doing what we did w/ the hooks implementation where we have separate Subscription
and SubscriptionWithHandler
components to handle this polymorphism.
@Schmavery @gugahoa I'm opening this issue here as a place for us to track API ideas for Subscription
and useSubscription
. I checked out the implementation here: https://reasonml.github.io/en/try?rrjsx=true&reason=C4TwDgpgBAThCOBXCBnYAKA5AIwJRQF4AoKKAHygCEB7ADyzwG4ijRIoALAQwDsATADYQYWLgBooOCZgDG+YqQoAJXoIjp01MMACW1HqNzS8hAHySuuAFydVQkVt37Dx6ZZLkoAOWor+Qm25-YXQAfVdJJhY2aEQUCABlRGwUGRgdbT0DTHFI6TlCD3QAPyC1GEC7EJyI2SMoYrgkVGAbJuQ0Blx5c1lmIiFgKAAzampC0nQYqFzp7AlpmQlSqorbYJFc+ag5ZfaWtoQOjDxrHbMoAG8PUkGqOnQYeVgjluZSUhQAdx1gGQ4oOgyvZ8NcPp4-GogatQsMeD11uVYQYfDwIBInjdPD5IUILjAsQBfd5QYkeIijaglYHCAg4qp7V5oAg0egAVm6zEp1NWdN8DIa+2ZrPQACI2aLORSxjyNgRcep0FwZEsoHwuMBLBcwVBvr9-oDlQUdRRUdACOYANrqzUAXQ8FAS1AAtuoBAiBFAAAJQa0arj20iEoiE+qNJnAFkPDm4RhAA and I'll confess I don't 100% understand it, but it seems like an interesting idea. I guess the major discussions to have are:
handler
?handler
that'd behave like (~prevSubscriptions as _, ~subscription) => subscription
?I'd say let's discuss things here and folks can feel free to submit PRs w/ suggested implementations. Thanks to you both so much for all these contributions too, I can really feel the lib getting better and better.
We need to bind the CombinedError
that urql
exposes and that gets passed along to the render props for Query
, Mutation
, and Subscription
. https://github.com/FormidableLabs/urql/blob/master/docs/api.md#combinederror-class
Hello, everyone!
There's this great tool called redoc that could benefit the project.
It generates a good looking documentation website using implementation files and docblocks.
I see two main benefits in adopting it:
What's everyone's opinion?
urql
should be a peer dependency of this package. Because the package has no "JS compilation step" (which it doesn't need), bs.module
declarations like this:
will be transpiled into naked import Urql from "urql"
statements in a JS code base, which causes the bundler (webpack
in my case) to look for urql
in the project's top-level node_modules
, which it won't find because urql
is specified as a direct dependency instead of peer dependency. Specifying urql
as a peer dependency reminds the developer to install urql
alongside this package.
We have five builds in a pending state in Sail, and a build hasn't gone through since Tuesday. Sail just feels too flaky to really be worth it. I'd be open to using Travis or CircleCI if anyone wants to pick this up before I get to it. The project previously was on Travis and the builds always worked pretty well, but Circle could be fun to setup too.
We'll want to add tests and basic CI using Travis for this repo.
The leading platform for testing in Reason for the moment seems to be bs.jest
, so we'll likely want to use that. Tests should be placed in a __tests__
directory at the root.
CI should be setup using Travis. We'll want to add a .travis.yml
file at the root once we add sufficient tests.
Not sure if you have any insight/ideas on this -- It seems like ocaml-graphql-server makes the response a status code 500 when you want to return a graphql error. This is normally fine, except for that our service that uses reason-urql just returns a network error in this case and doesn't seem to attempt to parse any graphql errors once it sees a 500.
I've tracked this down to urql here:
https://github.com/FormidableLabs/urql/blob/f4b11379e43ef14bbc2a5bf563a6aa2cc09b4271/src/utils/error.ts#L3-L19
I'm happy to make a PR to urql to let it aggregate both kinds of error, but not sure what the best policy is here, as I'm not sure what the best practices for graphql are re: status codes. Any thoughts?
Edit: actually I'm not sure if this is right, will investigate further I guess
Currently our API documentation on CombinedError
is non-existent. This is an important part of the urql
API and deserves to be covered in more depth. API documentation should include the following:
CombinedError.combinedError
record.combinedError
on the networkError
and graphQLErrors
fields.message
field added in #105.In addition, we should add a .rei
file for this module
as long as we're here.
We should possibly think about a roadmap for this?
graphql_ppx
is a PPX rewriter for Reason / BuckleScript. It allows us to construct type safe, validated queries at compile time given a GraphQL schema. Users would be able to generate schema for their projects using the built in script helpers yarn send-introspection-query http://my-api.example.com/api
. While this adds some slight overhead for users, it does give the benefit of type checking queries at compile time. We should develop a potential framework for this on a separate branch and test it on the example project.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.