From d6813f98b11e197d9af36d4afbea3889cd78ca15 Mon Sep 17 00:00:00 2001 From: Parker Ziegler Date: Sun, 30 Jun 2019 18:55:21 -0700 Subject: [PATCH] Add proper type inference by way of Subscription and SubscriptionWithHandler. (#82) --- examples/2-query/src/Monster.re | 16 ++--- examples/5-subscription/graphql_schema.json | 50 +++++++++++++ examples/5-subscription/src/app/App.re | 3 +- examples/5-subscription/src/app/Squares.re | 61 ++++++++++++++++ examples/5-subscription/src/server/schema.js | 17 ++++- src/ReasonUrql.re | 17 +++-- src/UrqlTypes.re | 13 +--- src/components/UrqlSubscription.re | 74 ++++++++++++++------ 8 files changed, 199 insertions(+), 52 deletions(-) create mode 100644 examples/5-subscription/src/app/Squares.re diff --git a/examples/2-query/src/Monster.re b/examples/2-query/src/Monster.re index c6baad2c..01c26087 100644 --- a/examples/2-query/src/Monster.re +++ b/examples/2-query/src/Monster.re @@ -16,14 +16,14 @@ module GetPokemon = [%graphql {| query pokemon($name: String!) { pokemon(name: $name) { - name - classification - height { - maximum - } - weight { - maximum - } + name + classification + height { + maximum + } + weight { + maximum + } image } } diff --git a/examples/5-subscription/graphql_schema.json b/examples/5-subscription/graphql_schema.json index 640f172f..f6132984 100644 --- a/examples/5-subscription/graphql_schema.json +++ b/examples/5-subscription/graphql_schema.json @@ -61,6 +61,30 @@ }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "floats", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -141,6 +165,16 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "SCALAR", + "name": "Float", + "description": "The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). ", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, { "kind": "OBJECT", "name": "Subscription", @@ -177,6 +211,22 @@ }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "newFloat", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, diff --git a/examples/5-subscription/src/app/App.re b/examples/5-subscription/src/app/App.re index 3c17cbb0..45efc44a 100644 --- a/examples/5-subscription/src/app/App.re +++ b/examples/5-subscription/src/app/App.re @@ -7,5 +7,6 @@ let make = () => width="1000" style={ReactDOMRe.Style.make(~border="1px solid blue", ())}> + - ; \ No newline at end of file + ; diff --git a/examples/5-subscription/src/app/Squares.re b/examples/5-subscription/src/app/Squares.re new file mode 100644 index 00000000..3f06a92d --- /dev/null +++ b/examples/5-subscription/src/app/Squares.re @@ -0,0 +1,61 @@ +open ReasonUrql; + +module SubscribeRandomFloat = [%graphql + {| + subscription subscribeFloat { + newFloat @bsDecoder(fn: "Js.Float.toString") + } +|} +]; + +let handler = (prevSubscriptions, subscription) => { + switch (prevSubscriptions) { + | Some(subs) => Array.append(subs, [|subscription|]) + | None => [|subscription|] + }; +}; + +[@bs.scope "Math"] [@bs.val] external random: unit => float = "random"; +[@bs.scope "Math"] [@bs.val] external floor: float => int = "floor"; +[@bs.send] external toString: (int, int) => string = "toString"; + +let getRandomInt = (max: int) => { + floor(random() *. float_of_int(max)); +}; + +let getRandomHex = () => { + let encode = random() *. float_of_int(16777215) |> floor; + let hex = encode->toString(16); + {j|#$hex|j}; +}; + +let request = SubscribeRandomFloat.make(); + +[@react.component] +let make = () => { + + ...{({response}) => + switch (response) { + | Fetching => "Loading"->React.string + | Data(d) => + Array.mapi( + (index, datum) => + string_of_int} + width={getRandomInt(30) |> string_of_int} + />, + d, + ) + |> React.array + | Error(_e) => "Error"->React.string + | NotFound => "Not Found"->React.string + } + } + ; +}; diff --git a/examples/5-subscription/src/server/schema.js b/examples/5-subscription/src/server/schema.js index 2af1f8c9..d36997b6 100644 --- a/examples/5-subscription/src/server/schema.js +++ b/examples/5-subscription/src/server/schema.js @@ -5,17 +5,20 @@ const pubsub = new PubSub(); const store = { messages: [], - numbers: [] + numbers: [], + floats: [] }; const typeDefs = ` type Query { messages: [Message!]! numbers: [Int!]! + floats: [Float!]! } type Subscription { newMessage: Message! newNumber: Int! + newFloat: Float! } type Message { id: ID!, @@ -26,7 +29,8 @@ type Message { const resolvers = { Query: { messages: store.messages, - numbers: store.numbers + numbers: store.numbers, + floats: store.floats }, Subscription: { newMessage: { @@ -34,6 +38,9 @@ const resolvers = { }, newNumber: { subscribe: () => pubsub.asyncIterator("newNumber") + }, + newFloat: { + subscribe: () => pubsub.asyncIterator("newFloat") } } }; @@ -71,3 +78,9 @@ setInterval(() => { newNumber: getRandomInt(1000) }); }, 2000); + +setInterval(() => { + pubsub.publish("newFloat", { + newFloat: Math.random() * 1000 + }); +}, 500); diff --git a/src/ReasonUrql.re b/src/ReasonUrql.re index 42cb58ae..81336471 100644 --- a/src/ReasonUrql.re +++ b/src/ReasonUrql.re @@ -8,7 +8,9 @@ module Query = UrqlQuery; module Mutation = UrqlMutation; -module Subscription = UrqlSubscription; +module Subscription = UrqlSubscription.Subscription; + +module SubscriptionWithHandler = UrqlSubscription.SubscriptionWithHandler; module Types = UrqlTypes; @@ -19,12 +21,13 @@ module Error = UrqlCombinedError; module Exchanges = UrqlClient.UrqlExchanges; module Hooks = { - type hookResponse('ret) = Types.hookResponse('ret) = { - fetching: bool, - data: option('ret), - error: option(UrqlCombinedError.t), - response: Types.response('ret) - }; + type hookResponse('ret) = + Types.hookResponse('ret) = { + fetching: bool, + data: option('ret), + error: option(UrqlCombinedError.t), + response: Types.response('ret), + }; include UrqlUseMutation; include UrqlUseQuery; include UrqlUseSubscription; diff --git a/src/UrqlTypes.re b/src/UrqlTypes.re index 6ec01860..43e6d9ce 100644 --- a/src/UrqlTypes.re +++ b/src/UrqlTypes.re @@ -92,18 +92,9 @@ type request('response) = { "variables": Js.Json.t, }; -/* The type of the handler function passed to Subscription and useSubscription. - `handler` corresponds to the type of the handler function _before_ the latest subscription has been parsed. - `parsedHandler` corresponds to the type of the handler function _after_ the latest subscription has been parsed. */ -type handler('acc) = - (~prevSubscriptions: option('acc), ~subscription: Js.Json.t) => 'acc; - -type parsedHandler('acc, 'response) = - (~prevSubscriptions: option('acc), ~subscription: 'response) => 'acc; - type hookResponse('ret) = { fetching: bool, data: option('ret), error: option(UrqlCombinedError.t), - response: response('ret) -} + response: response('ret), +}; diff --git a/src/components/UrqlSubscription.re b/src/components/UrqlSubscription.re index 19e6779f..14ff69aa 100644 --- a/src/components/UrqlSubscription.re +++ b/src/components/UrqlSubscription.re @@ -1,17 +1,19 @@ +open UrqlTypes; + [@bs.deriving abstract] -type subscriptionRenderPropsJs('a) = { +type subscriptionRenderPropsJs('ret) = { fetching: bool, [@bs.optional] - data: 'a, + data: 'ret, [@bs.optional] error: UrqlCombinedError.t, }; -type subscriptionRenderProps('a) = { +type subscriptionRenderProps('ret) = { fetching: bool, - data: option('a), + data: option('ret), error: option(UrqlCombinedError.t), - response: UrqlTypes.response('a), + response: UrqlTypes.response('ret), }; module SubscriptionJs = { @@ -19,22 +21,23 @@ module SubscriptionJs = { external make: ( ~query: string, - ~variables: Js.Json.t=?, - ~handler: UrqlTypes.handler('acc)=?, - ~children: subscriptionRenderPropsJs('a) => React.element + ~variables: Js.Json.t, + ~handler: (option('acc), Js.Json.t) => 'acc=?, + ~children: subscriptionRenderPropsJs('ret) => React.element ) => React.element = "Subscription"; }; -let urqlDataToRecord = (result: subscriptionRenderPropsJs('a)) => { - let data = result->dataGet; +let urqlDataToRecord = (parse, result) => { + let data = result->dataGet->Belt.Option.map(parse); let error = result->errorGet; let fetching = result->fetchingGet; - let response: UrqlTypes.response('a) = + let response = switch (fetching, data, error) { - | (true, _, _) => Fetching + | (true, None, _) => Fetching + | (true, Some(data), _) => Data(data) | (false, Some(data), _) => Data(data) | (false, _, Some(error)) => Error(error) | (false, None, None) => NotFound @@ -43,14 +46,39 @@ let urqlDataToRecord = (result: subscriptionRenderPropsJs('a)) => { {fetching, data, error, response}; }; -[@react.component] -let make = - ( - ~query: string, - ~variables: option(Js.Json.t)=?, - ~handler: option(UrqlTypes.handler('acc))=?, - ~children: subscriptionRenderProps('a) => React.element, - ) => - - {result => result |> urqlDataToRecord |> children} - ; +module Subscription = { + [@react.component] + let make = + ( + ~request: request('response), + ~children: subscriptionRenderProps('response) => React.element, + ) => { + let query = request##query; + let variables = request##variables; + let parse = request##parse; + + + {result => result |> urqlDataToRecord(parse) |> children} + ; + }; +}; + +module SubscriptionWithHandler = { + [@react.component] + let make = + ( + ~request: request('response), + ~handler: (option('acc), 'response) => 'acc, + ~children: subscriptionRenderProps('acc) => React.element, + ) => { + let query = request##query; + let variables = request##variables; + let parse = request##parse; + + let applyParsedResponse = (acc, data) => handler(acc, parse(data)); + + + {result => result |> urqlDataToRecord(x => x) |> children} + ; + }; +};