Skip to content

Commit

Permalink
Add proper type inference by way of Subscription and SubscriptionWith…
Browse files Browse the repository at this point in the history
…Handler. (#82)
  • Loading branch information
parkerziegler authored Jul 1, 2019
1 parent d434fbd commit d6813f9
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 52 deletions.
16 changes: 8 additions & 8 deletions examples/2-query/src/Monster.re
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down
50 changes: 50 additions & 0 deletions examples/5-subscription/graphql_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion examples/5-subscription/src/app/App.re
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ let make = () =>
width="1000"
style={ReactDOMRe.Style.make(~border="1px solid blue", ())}>
<Circles />
<Squares />
</svg>
</div>;
</div>;
61 changes: 61 additions & 0 deletions examples/5-subscription/src/app/Squares.re
Original file line number Diff line number Diff line change
@@ -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 = () => {
<SubscriptionWithHandler request handler>
...{({response}) =>
switch (response) {
| Fetching => <text> "Loading"->React.string </text>
| Data(d) =>
Array.mapi(
(index, datum) =>
<rect
x={
datum##newFloat;
}
y={index === 0 ? datum##newFloat : d[index - 1]##newFloat}
stroke={getRandomHex()}
fill="none"
height={getRandomInt(30) |> string_of_int}
width={getRandomInt(30) |> string_of_int}
/>,
d,
)
|> React.array
| Error(_e) => <text> "Error"->React.string </text>
| NotFound => <text> "Not Found"->React.string </text>
}
}
</SubscriptionWithHandler>;
};
17 changes: 15 additions & 2 deletions examples/5-subscription/src/server/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -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!,
Expand All @@ -26,14 +29,18 @@ type Message {
const resolvers = {
Query: {
messages: store.messages,
numbers: store.numbers
numbers: store.numbers,
floats: store.floats
},
Subscription: {
newMessage: {
subscribe: () => pubsub.asyncIterator("newMessage")
},
newNumber: {
subscribe: () => pubsub.asyncIterator("newNumber")
},
newFloat: {
subscribe: () => pubsub.asyncIterator("newFloat")
}
}
};
Expand Down Expand Up @@ -71,3 +78,9 @@ setInterval(() => {
newNumber: getRandomInt(1000)
});
}, 2000);

setInterval(() => {
pubsub.publish("newFloat", {
newFloat: Math.random() * 1000
});
}, 500);
17 changes: 10 additions & 7 deletions src/ReasonUrql.re
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ module Query = UrqlQuery;

module Mutation = UrqlMutation;

module Subscription = UrqlSubscription;
module Subscription = UrqlSubscription.Subscription;

module SubscriptionWithHandler = UrqlSubscription.SubscriptionWithHandler;

module Types = UrqlTypes;

Expand All @@ -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;
Expand Down
13 changes: 2 additions & 11 deletions src/UrqlTypes.re
Original file line number Diff line number Diff line change
Expand Up @@ -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),
};
74 changes: 51 additions & 23 deletions src/components/UrqlSubscription.re
Original file line number Diff line number Diff line change
@@ -1,40 +1,43 @@
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 = {
[@bs.module "urql"] [@react.component]
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
Expand All @@ -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,
) =>
<SubscriptionJs query ?variables ?handler>
{result => result |> urqlDataToRecord |> children}
</SubscriptionJs>;
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;

<SubscriptionJs query variables>
{result => result |> urqlDataToRecord(parse) |> children}
</SubscriptionJs>;
};
};

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));

<SubscriptionJs query variables handler=applyParsedResponse>
{result => result |> urqlDataToRecord(x => x) |> children}
</SubscriptionJs>;
};
};

0 comments on commit d6813f9

Please sign in to comment.