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

Add utility function for incremental response. #462

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 104 additions & 23 deletions packages/rescript-relay/src/RescriptRelay_NetworkUtils.res
Original file line number Diff line number Diff line change
@@ -1,26 +1,95 @@
let unsafeMergeJson: (Js.Json.t, Js.Json.t) => Js.Json.t = %raw("function (a, b) {
zth marked this conversation as resolved.
Show resolved Hide resolved
return { ...a, ...b};
}")


module GraphQLIncrementalResponse = {
type t<'a> = {incremental: array<'a>, hasNext: bool}
}

module GraphQLResponse = {
type data = {.}
type t<'a> = {incremental: array<{..} as 'a>, hasNext: bool}

let mapWithDefault: (
Js.Json.t,
'a => array<'d>,
'c => array<'d>,
) => array<Js.Json.t> = %raw(`function(response, f, b) {
if (response.incremental)
return f(response);
else return b(response);

}`)
external fromJson: Js.Json.t => t<'a> = "%identity"
type t<'a> = Incremental(GraphQLIncrementalResponse.t<'a>) | Response('a)

let mapIncrementalWithDefault: (
t<'a>,
GraphQLIncrementalResponse.t<'a> => array<'b>,
'a => array<'b>,
) => array<'b> = (t, withIncremental, default) => {
switch t {
| Incremental(incremental) => withIncremental(incremental)
| Response(json) => default(json)
}
}
let fromIncremental = data => Incremental(data)
let makeResponse = data => Response(data)

// Use parser to parse fully type-safe response
let parse: type a. (Js.Json.t, Js.Json.t => option<a>) => option<t<a>> = (json, parseFn) =>
switch json->Js.Json.decodeObject {
| Some(dict) =>
switch dict->Js.Dict.get("incremental") {
| Some(data) =>
switch data->Js.Json.decodeArray {
| Some(arrayData) =>
Some(
Incremental({
incremental: arrayData->Array.map(parseFn)->Array.filterMap(x => x),
hasNext: dict
->Js.Dict.get("hasNext")
->Option.mapWithDefault(false, v =>
v->Js.Json.decodeBoolean->Option.mapWithDefault(false, v => v)
),
}),
)
| None => {

let data = parseFn(json)
switch data {
| Some(data) => Some(Response(data))
| None => None
}
}
}
| None => {
let data = parseFn(json)
switch data {
| Some(data) => Some(Response(data))
| None => None
}
}
}
| None => None
}

// Partially parse response
let fromJson: Js.Json.t => t<'a> = json =>
switch json->Js.Json.decodeObject {
| Some(dict) =>
switch dict->Js.Dict.get("incremental") {
| Some(data) =>
switch data->Js.Json.decodeArray {
| Some(arrayData) =>
Incremental({
incremental: arrayData,
hasNext: dict
->Js.Dict.get("hasNext")
->Option.mapWithDefault(false, v =>
v->Js.Json.decodeBoolean->Option.mapWithDefault(false, v => v)
),
})
| None => Response(json)
}
| None => Response(json)
}
| None => Response(json)
}
}

module RelayDeferResponse = {
type extension = {is_final: bool}
type t<'a> = {.."hasNext": bool, "extensions": extension} as 'a
type t<'a> = array<'a>

let fromIncrementalResponse: GraphQLIncrementalResponse.t<'a> => array<t<'a>> = ({
let fromIncrementalResponse: GraphQLIncrementalResponse.t<{..} as 'a> => t<{..} as 'a> = ({
incremental,
hasNext,
}) => {
Expand All @@ -30,13 +99,25 @@ module RelayDeferResponse = {
Object.assign(data, {"hasNext": hasNext, "extensions": {"is_final": !hasNext}})
})
}
external toJson: t<'a> => Js.Json.t = "%identity"

external toJson: 'a => Js.Json.t = "%identity"

let fromJsonIncrementalResponse: GraphQLIncrementalResponse.t<Js.Json.t> => array<Js.Json.t> = ({
incremental,
hasNext,
}) => {
incremental->Array.mapWithIndex((data, i) => {
let hasNext = i === incremental->Array.length - 1 ? hasNext : true

unsafeMergeJson(data, {"hasNext": hasNext, "extensions": {"is_final": !hasNext}}->toJson)
})
}
}
let adaptIncrementalResponseToRelay = part =>
part->GraphQLIncrementalResponse.mapWithDefault(
json => {
open RelayDeferResponse
json->GraphQLIncrementalResponse.fromJson->fromIncrementalResponse->Array.map(toJson)
},

let adaptJsonIncrementalResponseToRelay: Js.Json.t => array<Js.Json.t> = part =>
part
->GraphQLResponse.fromJson
->GraphQLResponse.mapIncrementalWithDefault(
RelayDeferResponse.fromJsonIncrementalResponse,
part => [part],
)
57 changes: 33 additions & 24 deletions packages/rescript-relay/src/RescriptRelay_NetworkUtils.resi
Original file line number Diff line number Diff line change
@@ -1,31 +1,40 @@
let unsafeMergeJson: (. Js.Json.t, Js.Json.t) => Js.Json.t

module GraphQLIncrementalResponse: {
type data = {.}
type t<'a> = {incremental: array<'a>, hasNext: bool}
constraint 'a = {..}
let mapWithDefault: (.
Js.Json.t,
(. 'a) => array<'d>,
(. 'c) => array<'d>,
) => array<Js.Json.t>
let fromJson: (. Js.Json.t) => t<{..}>
}

module GraphQLResponse: {
type data = {.}
type t<'a> =
| Incremental(GraphQLIncrementalResponse.t<'a>)
| Response('a)
let mapIncrementalWithDefault: (.
t<'a>,
(. GraphQLIncrementalResponse.t<'a>) => array<'b>,
(. 'a) => array<'b>,
) => array<'b>
let fromIncremental: (. GraphQLIncrementalResponse.t<'a>) => t<'a>
let makeResponse: (. 'a) => t<'a>
let parse: 'a. (. Js.Json.t, (. Js.Json.t) => option<'a>) => option<
t<'a>,
>
let fromJson: (. Js.Json.t) => t<Js.Json.t>
}

module RelayDeferResponse: {
type extension = {is_final: bool}
type t<'a> = 'a
constraint 'a = {..
"extensions": extension,
"hasNext": bool,
}
let fromIncrementalResponse: (.
GraphQLIncrementalResponse.t<
{.."extensions": extension, "hasNext": bool},
>,
) => array<t<{.."extensions": extension, "hasNext": bool}>>
let toJson: (.
t<{.."extensions": extension, "hasNext": bool}>,
) => Js.Json.t
type t<'a> = array<'a>
// Type safe conversion from a GraphQL spec response
let fromIncrementalResponse: (. GraphQLIncrementalResponse.t<{..}>) => t<{..}>

let toJson: (. 'a) => Js.Json.t

// Not type safe conversion due to use of Json.t and object merging
let fromJsonIncrementalResponse: (.
GraphQLIncrementalResponse.t<Js.Json.t>,
) => array<Js.Json.t>
}

let adaptIncrementalResponseToRelay:
(. Js.Json.t) => array<Js.Json.t>
// Not type safe conversion of GraphQL spec defer response to Relay-compatible
// version
let adaptJsonIncrementalResponseToRelay: (. Js.Json.t) => array<Js.Json.t>