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 2 commits
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
123 changes: 123 additions & 0 deletions packages/rescript-relay/src/RescriptRelay_NetworkUtils.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
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(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 t<'a> = array<'a>

let fromIncrementalResponse: GraphQLIncrementalResponse.t<{..} as 'a> => t<{..} as 'a> = ({
incremental,
hasNext,
}) => {
incremental->Array.mapWithIndex((data, i) => {
let hasNext = i === incremental->Array.length - 1 ? hasNext : true

Object.assign(data, {"hasNext": hasNext, "extensions": {"is_final": !hasNext}})
})
}

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

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

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 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>
}

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