From 8cfe4346ce299777276385ac6b3d6a47c81d3ff5 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Sat, 4 Jan 2025 22:39:08 +0100 Subject: [PATCH 1/4] implement catch result type --- packages/relay | 2 +- .../__tests__/Test_catch-tests.js | 188 +++++++++++ .../rescript-relay/__tests__/Test_catch.res | 174 ++++++++++ ...TestCatchLoggedInUserPropQuery_graphql.res | 207 ++++++++++++ ...TestCatchMemberPropNestedQuery_graphql.res | 304 ++++++++++++++++++ .../TestCatchMemberPropQuery_graphql.res | 246 ++++++++++++++ .../TestCatchMembersPropQuery_graphql.res | 296 +++++++++++++++++ .../TestCatchUser_user_graphql.res | 65 ++++ packages/rescript-relay/src/RescriptRelay.res | 19 ++ .../rescript-relay/src/RescriptRelay.resi | 18 ++ 10 files changed, 1518 insertions(+), 1 deletion(-) create mode 100644 packages/rescript-relay/__tests__/Test_catch-tests.js create mode 100644 packages/rescript-relay/__tests__/Test_catch.res create mode 100644 packages/rescript-relay/__tests__/__generated__/TestCatchLoggedInUserPropQuery_graphql.res create mode 100644 packages/rescript-relay/__tests__/__generated__/TestCatchMemberPropNestedQuery_graphql.res create mode 100644 packages/rescript-relay/__tests__/__generated__/TestCatchMemberPropQuery_graphql.res create mode 100644 packages/rescript-relay/__tests__/__generated__/TestCatchMembersPropQuery_graphql.res create mode 100644 packages/rescript-relay/__tests__/__generated__/TestCatchUser_user_graphql.res diff --git a/packages/relay b/packages/relay index 00960d5f..4224c9be 160000 --- a/packages/relay +++ b/packages/relay @@ -1 +1 @@ -Subproject commit 00960d5f9e8e67e2dc304554ac5bf75a878972a6 +Subproject commit 4224c9bead46aef53084cc42f45b54bf5079d939 diff --git a/packages/rescript-relay/__tests__/Test_catch-tests.js b/packages/rescript-relay/__tests__/Test_catch-tests.js new file mode 100644 index 00000000..66d2db7c --- /dev/null +++ b/packages/rescript-relay/__tests__/Test_catch-tests.js @@ -0,0 +1,188 @@ +require("@testing-library/jest-dom/extend-expect"); +const t = require("@testing-library/react"); +const React = require("react"); +const queryMock = require("./queryMock"); + +const { test_catch } = require("./Test_catch.bs"); + +const date = new Date("2025-01-01T06:00"); + +describe("Catch", () => { + test("logged in user prop - success", async () => { + queryMock.mockQuery({ + name: "TestCatchLoggedInUserPropQuery", + data: { + loggedInUser: { + id: "user-1", + createdAt: date.toISOString(), + }, + }, + }); + + t.render(test_catch("TestLoggedInUserProp")); + await t.screen.findByText("Got createdAt: 1735707600000"); + }); + + test("logged in user prop - fail", async () => { + queryMock.mockQuery({ + name: "TestCatchLoggedInUserPropQuery", + data: { + loggedInUser: { + id: "user-1", + createdAt: null, + }, + }, + graphqlErrors: [{ path: ["loggedInUser", "createdAt"] }], + }); + + t.render(test_catch("TestLoggedInUserProp")); + await t.screen.findByText("Error!"); + }); + + test("logged in user prop from fragment - success", async () => { + queryMock.mockQuery({ + name: "TestCatchLoggedInUserPropQuery", + data: { + loggedInUser: { + id: "user-1", + createdAt: date.toISOString(), + }, + }, + }); + + t.render(test_catch("TestLoggedInUserPropFragmentData")); + await t.screen.findByText("Got createdAt: 1735707600000"); + }); + + test("logged in user prop from fragment - fail", async () => { + queryMock.mockQuery({ + name: "TestCatchLoggedInUserPropQuery", + data: { + loggedInUser: { + id: "user-1", + createdAt: null, + }, + }, + graphqlErrors: [{ path: ["loggedInUser", "createdAt"] }], + }); + + t.render(test_catch("TestLoggedInUserPropFragmentData")); + await t.screen.findByText("Error!"); + }); + + test("member prop - success", async () => { + queryMock.mockQuery({ + name: "TestCatchMemberPropQuery", + data: { + member: { + __typename: "User", + id: "user-1", + createdAt: date.toISOString(), + }, + }, + }); + + t.render(test_catch("TestMember")); + await t.screen.findByText( + "Got user id: user-1, and createdAt: 1735707600000" + ); + }); + + test("member prop - fail", async () => { + queryMock.mockQuery({ + name: "TestCatchMemberPropQuery", + data: { + member: null, + }, + graphqlErrors: [{ path: ["member"] }], + }); + + t.render(test_catch("TestMember")); + await t.screen.findByText("Error!"); + }); + + test("member prop - success nested", async () => { + queryMock.mockQuery({ + name: "TestCatchMemberPropNestedQuery", + data: { + member: { + __typename: "User", + id: "user-1", + memberOfSingular: { + __typename: "User", + id: "user-2", + createdAt: date.toISOString(), + }, + }, + }, + }); + + t.render(test_catch("TestMemberNested")); + await t.screen.findByText( + "Got user id: user-1, and createdAt: 1735707600000" + ); + }); + + test("member prop - fail nested", async () => { + queryMock.mockQuery({ + name: "TestCatchMemberPropNestedQuery", + data: { + member: { + __typename: "User", + id: "user-1", + memberOfSingular: { + __typename: "User", + id: "user-2", + createdAt: null, + }, + }, + }, + graphqlErrors: [{ path: ["member", "memberOfSingular", "createdAt"] }], + }); + + t.render(test_catch("TestMemberNested")); + await t.screen.findByText("Error nested!"); + }); + + test("members array", async () => { + queryMock.mockQuery({ + name: "TestCatchMembersPropQuery", + data: { + members: { + edges: [ + { + __typename: "UserEdge", + node: { + __typename: "User", + id: "user-1", + createdAt: date.toISOString(), + }, + }, + { + __typename: "UserEdge", + node: { + __typename: "User", + id: "user-2", + createdAt: null, + }, + }, + { + __typename: "UserEdge", + node: { + __typename: "User", + id: "user-3", + createdAt: date.toISOString(), + }, + }, + ], + }, + }, + graphqlErrors: [{ path: ["members", "edges", 1, "node", "createdAt"] }], + }); + + t.render(test_catch("TestMembers")); + await t.screen.findByText( + "User: user-1 - 1735707600000, Error!, User: user-3 - 1735707600000" + ); + }); +}); diff --git a/packages/rescript-relay/__tests__/Test_catch.res b/packages/rescript-relay/__tests__/Test_catch.res new file mode 100644 index 00000000..7019f55c --- /dev/null +++ b/packages/rescript-relay/__tests__/Test_catch.res @@ -0,0 +1,174 @@ +module QueryLoggedInUserProp = %relay(` + query TestCatchLoggedInUserPropQuery { + loggedInUser { + createdAt @catch + ...TestCatchUser_user + } + } +`) + +module LoggedInUserFragment = %relay(` + fragment TestCatchUser_user on User @catch { + createdAt + } +`) + +module TestLoggedInUserProp = { + @react.component + let make = () => { + let query = QueryLoggedInUserProp.use(~variables=()) + + switch query.loggedInUser.createdAt { + | Ok({value: createdAt}) => +
+ {React.string("Got createdAt: " ++ createdAt->Js.Date.getTime->Belt.Float.toString)} +
+ | Error(_) =>
{React.string("Error!")}
+ } + } +} + +module TestLoggedInUserPropFragmentData = { + @react.component + let make = () => { + let query = QueryLoggedInUserProp.use(~variables=()) + let fragmentData = LoggedInUserFragment.use(query.loggedInUser.fragmentRefs) + + switch fragmentData { + | Ok({value: {createdAt}}) => +
+ {React.string("Got createdAt: " ++ createdAt->Js.Date.getTime->Belt.Float.toString)} +
+ | Error(_) =>
{React.string("Error!")}
+ } + } +} + +module QueryMember = %relay(` + query TestCatchMemberPropQuery { + member(id: "123") @catch { + ... on User { + id + createdAt + } + } + } +`) + +module TestMember = { + @react.component + let make = () => { + let query = QueryMember.use(~variables=()) + + switch query.member { + | Ok({value: User({id, createdAt})}) => +
+ {React.string( + "Got user id: " ++ + id ++ + ", and createdAt: " ++ + createdAt->Js.Date.getTime->Belt.Float.toString, + )} +
+ | Error(_) =>
{React.string("Error!")}
+ | _ => React.null + } + } +} + +module QueryMemberNested = %relay(` + query TestCatchMemberPropNestedQuery { + member(id: "123") { + ... on User { + id + memberOfSingular @catch { + ... on User { + id + createdAt + } + } + } + } + } +`) + +module TestMemberNested = { + @react.component + let make = () => { + let query = QueryMemberNested.use(~variables=()) + + switch query.member { + | Some(User({id, memberOfSingular: Ok({value: User({createdAt})})})) => +
+ {React.string( + "Got user id: " ++ + id ++ + ", and createdAt: " ++ + createdAt->Js.Date.getTime->Belt.Float.toString, + )} +
+ | Some(User({memberOfSingular: Error(_)})) =>
{React.string("Error nested!")}
+ | _ => React.null + } + } +} + +module QueryMembers = %relay(` + query TestCatchMembersPropQuery { + members(groupId: "123") { + edges { + node @catch { + ... on User { + id + createdAt + } + } + } + } + } +`) + +module TestMembers = { + @react.component + let make = () => { + let query = QueryMembers.use(~variables=()) + + let members = + query.members + ->Belt.Option.flatMap(v => v.edges) + ->Belt.Option.getWithDefault([]) + ->Belt.Array.keepMap(x => x->Belt.Option.map(r => r.node)) + + members + ->Js.Array2.map(r => + switch r { + | Ok({value: User({id, createdAt})}) => + `User: ${id} - ${createdAt->Js.Date.getTime->Belt.Float.toString}` + | _ => "Error!" + } + ) + ->Js.Array2.joinWith(", ") + ->React.string + } +} + +@live +let test_catch = testName => { + let network = RescriptRelay.Network.makePromiseBased(~fetchFunction=RelayEnv.fetchQuery) + + let environment = RescriptRelay.Environment.make( + ~network, + ~store=RescriptRelay.Store.make(~source=RescriptRelay.RecordSource.make()), + ) + + + {switch testName { + | "TestLoggedInUserProp" => + | "TestLoggedInUserPropFragmentData" => + | "TestMember" => + | "TestMemberNested" => + | "TestMembers" => + | _ => React.null + }} + +} diff --git a/packages/rescript-relay/__tests__/__generated__/TestCatchLoggedInUserPropQuery_graphql.res b/packages/rescript-relay/__tests__/__generated__/TestCatchLoggedInUserPropQuery_graphql.res new file mode 100644 index 00000000..9a322845 --- /dev/null +++ b/packages/rescript-relay/__tests__/__generated__/TestCatchLoggedInUserPropQuery_graphql.res @@ -0,0 +1,207 @@ +/* @sourceLoc Test_catch.res */ +/* @generated */ +%%raw("/* @generated */") +module Types = { + @@warning("-30") + + type rec response_loggedInUser = { + createdAt: RescriptRelay.CatchResult.t, + fragmentRefs: RescriptRelay.fragmentRefs<[ | #TestCatchUser_user]>, + } + type response = { + loggedInUser: response_loggedInUser, + } + @live + type rawResponse = response + @live + type variables = unit + @live + type refetchVariables = unit + @live let makeRefetchVariables = () => () +} + + +type queryRef + +module Internal = { + @live + let variablesConverter: Js.Dict.t>> = %raw( + json`{}` + ) + @live + let variablesConverterMap = () + @live + let convertVariables = v => v->RescriptRelay.convertObj( + variablesConverter, + variablesConverterMap, + Js.undefined + ) + @live + type wrapResponseRaw + @live + let wrapResponseConverter: Js.Dict.t>> = %raw( + json`{"__root":{"loggedInUser_createdAt_value":{"c":"TestsUtils.Datetime"},"loggedInUser":{"f":""}}}` + ) + @live + let wrapResponseConverterMap = { + "TestsUtils.Datetime": TestsUtils.Datetime.serialize, + } + @live + let convertWrapResponse = v => v->RescriptRelay.convertObj( + wrapResponseConverter, + wrapResponseConverterMap, + Js.null + ) + @live + type responseRaw + @live + let responseConverter: Js.Dict.t>> = %raw( + json`{"__root":{"loggedInUser_createdAt_value":{"c":"TestsUtils.Datetime"},"loggedInUser":{"f":""}}}` + ) + @live + let responseConverterMap = { + "TestsUtils.Datetime": TestsUtils.Datetime.parse, + } + @live + let convertResponse = v => v->RescriptRelay.convertObj( + responseConverter, + responseConverterMap, + Js.undefined + ) + type wrapRawResponseRaw = wrapResponseRaw + @live + let convertWrapRawResponse = convertWrapResponse + type rawResponseRaw = responseRaw + @live + let convertRawResponse = convertResponse + type rawPreloadToken<'response> = {source: Js.Nullable.t>} + external tokenToRaw: queryRef => rawPreloadToken = "%identity" +} +module Utils = { + @@warning("-33") + open Types +} + +type relayOperationNode +type operationType = RescriptRelay.queryNode + + +let node: operationType = %raw(json` (function(){ +var v0 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "TestCatchLoggedInUserPropQuery", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "loggedInUser", + "plural": false, + "selections": [ + { + "kind": "CatchField", + "field": (v0/*: any*/), + "to": "RESULT" + }, + { + "args": null, + "kind": "FragmentSpread", + "name": "TestCatchUser_user" + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "TestCatchLoggedInUserPropQuery", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "loggedInUser", + "plural": false, + "selections": [ + (v0/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "3f1f4cf7afa2f93882110139d958809c", + "id": null, + "metadata": {}, + "name": "TestCatchLoggedInUserPropQuery", + "operationKind": "query", + "text": "query TestCatchLoggedInUserPropQuery {\n loggedInUser {\n createdAt\n ...TestCatchUser_user\n id\n }\n}\n\nfragment TestCatchUser_user on User {\n createdAt\n}\n" + } +}; +})() `) + +@live let load: ( + ~environment: RescriptRelay.Environment.t, + ~variables: Types.variables, + ~fetchPolicy: RescriptRelay.fetchPolicy=?, + ~fetchKey: string=?, + ~networkCacheConfig: RescriptRelay.cacheConfig=?, +) => queryRef = ( + ~environment, + ~variables, + ~fetchPolicy=?, + ~fetchKey=?, + ~networkCacheConfig=?, +) => + RescriptRelay.loadQuery( + environment, + node, + variables->Internal.convertVariables, + { + fetchKey, + fetchPolicy, + networkCacheConfig, + }, + ) + +@live +let queryRefToObservable = token => { + let raw = token->Internal.tokenToRaw + raw.source->Js.Nullable.toOption +} + +@live +let queryRefToPromise = token => { + Js.Promise.make((~resolve, ~reject as _) => { + switch token->queryRefToObservable { + | None => resolve(Error()) + | Some(o) => + open RescriptRelay.Observable + let _: subscription = o->subscribe(makeObserver(~complete=() => resolve(Ok()))) + } + }) +} diff --git a/packages/rescript-relay/__tests__/__generated__/TestCatchMemberPropNestedQuery_graphql.res b/packages/rescript-relay/__tests__/__generated__/TestCatchMemberPropNestedQuery_graphql.res new file mode 100644 index 00000000..2ae56de6 --- /dev/null +++ b/packages/rescript-relay/__tests__/__generated__/TestCatchMemberPropNestedQuery_graphql.res @@ -0,0 +1,304 @@ +/* @sourceLoc Test_catch.res */ +/* @generated */ +%%raw("/* @generated */") +module Types = { + @@warning("-30") + + @tag("__typename") type response_member_User_memberOfSingular_value = + | @live User( + { + @live __typename: [ | #User], + createdAt: TestsUtils.Datetime.t, + @live id: string, + } + ) + | @live @as("__unselected") UnselectedUnionMember(string) + + @tag("__typename") type response_member = + | @live User( + { + @live __typename: [ | #User], + @live id: string, + memberOfSingular: RescriptRelay.CatchResult.t, + } + ) + | @live @as("__unselected") UnselectedUnionMember(string) + + type response = { + member: option, + } + @live + type rawResponse = response + @live + type variables = unit + @live + type refetchVariables = unit + @live let makeRefetchVariables = () => () +} + +@live +let unwrap_response_member_User_memberOfSingular_value: Types.response_member_User_memberOfSingular_value => Types.response_member_User_memberOfSingular_value = RescriptRelay_Internal.unwrapUnion(_, ["User"]) +@live +let wrap_response_member_User_memberOfSingular_value: Types.response_member_User_memberOfSingular_value => Types.response_member_User_memberOfSingular_value = RescriptRelay_Internal.wrapUnion +@live +let unwrap_response_member: Types.response_member => Types.response_member = RescriptRelay_Internal.unwrapUnion(_, ["User"]) +@live +let wrap_response_member: Types.response_member => Types.response_member = RescriptRelay_Internal.wrapUnion + +type queryRef + +module Internal = { + @live + let variablesConverter: Js.Dict.t>> = %raw( + json`{}` + ) + @live + let variablesConverterMap = () + @live + let convertVariables = v => v->RescriptRelay.convertObj( + variablesConverter, + variablesConverterMap, + Js.undefined + ) + @live + type wrapResponseRaw + @live + let wrapResponseConverter: Js.Dict.t>> = %raw( + json`{"__root":{"member_User_memberOfSingular_value_User_createdAt":{"c":"TestsUtils.Datetime"},"member_User_memberOfSingular_value":{"u":"response_member_User_memberOfSingular_value"},"member":{"u":"response_member"}}}` + ) + @live + let wrapResponseConverterMap = { + "TestsUtils.Datetime": TestsUtils.Datetime.serialize, + "response_member_User_memberOfSingular_value": wrap_response_member_User_memberOfSingular_value, + "response_member": wrap_response_member, + } + @live + let convertWrapResponse = v => v->RescriptRelay.convertObj( + wrapResponseConverter, + wrapResponseConverterMap, + Js.null + ) + @live + type responseRaw + @live + let responseConverter: Js.Dict.t>> = %raw( + json`{"__root":{"member_User_memberOfSingular_value_User_createdAt":{"c":"TestsUtils.Datetime"},"member_User_memberOfSingular_value":{"u":"response_member_User_memberOfSingular_value"},"member":{"u":"response_member"}}}` + ) + @live + let responseConverterMap = { + "TestsUtils.Datetime": TestsUtils.Datetime.parse, + "response_member_User_memberOfSingular_value": unwrap_response_member_User_memberOfSingular_value, + "response_member": unwrap_response_member, + } + @live + let convertResponse = v => v->RescriptRelay.convertObj( + responseConverter, + responseConverterMap, + Js.undefined + ) + type wrapRawResponseRaw = wrapResponseRaw + @live + let convertWrapRawResponse = convertWrapResponse + type rawResponseRaw = responseRaw + @live + let convertRawResponse = convertResponse + type rawPreloadToken<'response> = {source: Js.Nullable.t>} + external tokenToRaw: queryRef => rawPreloadToken = "%identity" +} +module Utils = { + @@warning("-33") + open Types +} + +type relayOperationNode +type operationType = RescriptRelay.queryNode + + +let node: operationType = %raw(json` (function(){ +var v0 = [ + { + "kind": "Literal", + "name": "id", + "value": "123" + } +], +v1 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null +}, +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v3 = { + "kind": "InlineFragment", + "selections": [ + (v2/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null + } + ], + "type": "User", + "abstractKey": null +}, +v4 = { + "kind": "InlineFragment", + "selections": [ + (v2/*: any*/) + ], + "type": "Node", + "abstractKey": "__isNode" +}; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "TestCatchMemberPropNestedQuery", + "selections": [ + { + "alias": null, + "args": (v0/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "member", + "plural": false, + "selections": [ + (v1/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + (v2/*: any*/), + { + "kind": "CatchField", + "field": { + "alias": null, + "args": null, + "concreteType": null, + "kind": "LinkedField", + "name": "memberOfSingular", + "plural": false, + "selections": [ + (v1/*: any*/), + (v3/*: any*/) + ], + "storageKey": null + }, + "to": "RESULT" + } + ], + "type": "User", + "abstractKey": null + } + ], + "storageKey": "member(id:\"123\")" + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "TestCatchMemberPropNestedQuery", + "selections": [ + { + "alias": null, + "args": (v0/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "member", + "plural": false, + "selections": [ + (v1/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + (v2/*: any*/), + { + "alias": null, + "args": null, + "concreteType": null, + "kind": "LinkedField", + "name": "memberOfSingular", + "plural": false, + "selections": [ + (v1/*: any*/), + (v3/*: any*/), + (v4/*: any*/) + ], + "storageKey": null + } + ], + "type": "User", + "abstractKey": null + }, + (v4/*: any*/) + ], + "storageKey": "member(id:\"123\")" + } + ] + }, + "params": { + "cacheID": "615cea126472d95d81dbe3a878b145a9", + "id": null, + "metadata": {}, + "name": "TestCatchMemberPropNestedQuery", + "operationKind": "query", + "text": "query TestCatchMemberPropNestedQuery {\n member(id: \"123\") {\n __typename\n ... on User {\n id\n memberOfSingular {\n __typename\n ... on User {\n id\n createdAt\n }\n ... on Node {\n __isNode: __typename\n __typename\n id\n }\n }\n }\n ... on Node {\n __isNode: __typename\n __typename\n id\n }\n }\n}\n" + } +}; +})() `) + +@live let load: ( + ~environment: RescriptRelay.Environment.t, + ~variables: Types.variables, + ~fetchPolicy: RescriptRelay.fetchPolicy=?, + ~fetchKey: string=?, + ~networkCacheConfig: RescriptRelay.cacheConfig=?, +) => queryRef = ( + ~environment, + ~variables, + ~fetchPolicy=?, + ~fetchKey=?, + ~networkCacheConfig=?, +) => + RescriptRelay.loadQuery( + environment, + node, + variables->Internal.convertVariables, + { + fetchKey, + fetchPolicy, + networkCacheConfig, + }, + ) + +@live +let queryRefToObservable = token => { + let raw = token->Internal.tokenToRaw + raw.source->Js.Nullable.toOption +} + +@live +let queryRefToPromise = token => { + Js.Promise.make((~resolve, ~reject as _) => { + switch token->queryRefToObservable { + | None => resolve(Error()) + | Some(o) => + open RescriptRelay.Observable + let _: subscription = o->subscribe(makeObserver(~complete=() => resolve(Ok()))) + } + }) +} diff --git a/packages/rescript-relay/__tests__/__generated__/TestCatchMemberPropQuery_graphql.res b/packages/rescript-relay/__tests__/__generated__/TestCatchMemberPropQuery_graphql.res new file mode 100644 index 00000000..b7b79f68 --- /dev/null +++ b/packages/rescript-relay/__tests__/__generated__/TestCatchMemberPropQuery_graphql.res @@ -0,0 +1,246 @@ +/* @sourceLoc Test_catch.res */ +/* @generated */ +%%raw("/* @generated */") +module Types = { + @@warning("-30") + + @tag("__typename") type response_member_value = + | @live User( + { + @live __typename: [ | #User], + createdAt: TestsUtils.Datetime.t, + @live id: string, + } + ) + | @live @as("__unselected") UnselectedUnionMember(string) + + type response = { + member: RescriptRelay.CatchResult.t, + } + @live + type rawResponse = response + @live + type variables = unit + @live + type refetchVariables = unit + @live let makeRefetchVariables = () => () +} + +@live +let unwrap_response_member_value: Types.response_member_value => Types.response_member_value = RescriptRelay_Internal.unwrapUnion(_, ["User"]) +@live +let wrap_response_member_value: Types.response_member_value => Types.response_member_value = RescriptRelay_Internal.wrapUnion + +type queryRef + +module Internal = { + @live + let variablesConverter: Js.Dict.t>> = %raw( + json`{}` + ) + @live + let variablesConverterMap = () + @live + let convertVariables = v => v->RescriptRelay.convertObj( + variablesConverter, + variablesConverterMap, + Js.undefined + ) + @live + type wrapResponseRaw + @live + let wrapResponseConverter: Js.Dict.t>> = %raw( + json`{"__root":{"member_value_User_createdAt":{"c":"TestsUtils.Datetime"},"member_value":{"u":"response_member_value"}}}` + ) + @live + let wrapResponseConverterMap = { + "TestsUtils.Datetime": TestsUtils.Datetime.serialize, + "response_member_value": wrap_response_member_value, + } + @live + let convertWrapResponse = v => v->RescriptRelay.convertObj( + wrapResponseConverter, + wrapResponseConverterMap, + Js.null + ) + @live + type responseRaw + @live + let responseConverter: Js.Dict.t>> = %raw( + json`{"__root":{"member_value_User_createdAt":{"c":"TestsUtils.Datetime"},"member_value":{"u":"response_member_value"}}}` + ) + @live + let responseConverterMap = { + "TestsUtils.Datetime": TestsUtils.Datetime.parse, + "response_member_value": unwrap_response_member_value, + } + @live + let convertResponse = v => v->RescriptRelay.convertObj( + responseConverter, + responseConverterMap, + Js.undefined + ) + type wrapRawResponseRaw = wrapResponseRaw + @live + let convertWrapRawResponse = convertWrapResponse + type rawResponseRaw = responseRaw + @live + let convertRawResponse = convertResponse + type rawPreloadToken<'response> = {source: Js.Nullable.t>} + external tokenToRaw: queryRef => rawPreloadToken = "%identity" +} +module Utils = { + @@warning("-33") + open Types +} + +type relayOperationNode +type operationType = RescriptRelay.queryNode + + +let node: operationType = %raw(json` (function(){ +var v0 = [ + { + "kind": "Literal", + "name": "id", + "value": "123" + } +], +v1 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null +}, +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v3 = { + "kind": "InlineFragment", + "selections": [ + (v2/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null + } + ], + "type": "User", + "abstractKey": null +}; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "TestCatchMemberPropQuery", + "selections": [ + { + "kind": "CatchField", + "field": { + "alias": null, + "args": (v0/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "member", + "plural": false, + "selections": [ + (v1/*: any*/), + (v3/*: any*/) + ], + "storageKey": "member(id:\"123\")" + }, + "to": "RESULT" + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "TestCatchMemberPropQuery", + "selections": [ + { + "alias": null, + "args": (v0/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "member", + "plural": false, + "selections": [ + (v1/*: any*/), + (v3/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + (v2/*: any*/) + ], + "type": "Node", + "abstractKey": "__isNode" + } + ], + "storageKey": "member(id:\"123\")" + } + ] + }, + "params": { + "cacheID": "ac6dc290592743f843d1daec5045034c", + "id": null, + "metadata": {}, + "name": "TestCatchMemberPropQuery", + "operationKind": "query", + "text": "query TestCatchMemberPropQuery {\n member(id: \"123\") {\n __typename\n ... on User {\n id\n createdAt\n }\n ... on Node {\n __isNode: __typename\n __typename\n id\n }\n }\n}\n" + } +}; +})() `) + +@live let load: ( + ~environment: RescriptRelay.Environment.t, + ~variables: Types.variables, + ~fetchPolicy: RescriptRelay.fetchPolicy=?, + ~fetchKey: string=?, + ~networkCacheConfig: RescriptRelay.cacheConfig=?, +) => queryRef = ( + ~environment, + ~variables, + ~fetchPolicy=?, + ~fetchKey=?, + ~networkCacheConfig=?, +) => + RescriptRelay.loadQuery( + environment, + node, + variables->Internal.convertVariables, + { + fetchKey, + fetchPolicy, + networkCacheConfig, + }, + ) + +@live +let queryRefToObservable = token => { + let raw = token->Internal.tokenToRaw + raw.source->Js.Nullable.toOption +} + +@live +let queryRefToPromise = token => { + Js.Promise.make((~resolve, ~reject as _) => { + switch token->queryRefToObservable { + | None => resolve(Error()) + | Some(o) => + open RescriptRelay.Observable + let _: subscription = o->subscribe(makeObserver(~complete=() => resolve(Ok()))) + } + }) +} diff --git a/packages/rescript-relay/__tests__/__generated__/TestCatchMembersPropQuery_graphql.res b/packages/rescript-relay/__tests__/__generated__/TestCatchMembersPropQuery_graphql.res new file mode 100644 index 00000000..bf2afc05 --- /dev/null +++ b/packages/rescript-relay/__tests__/__generated__/TestCatchMembersPropQuery_graphql.res @@ -0,0 +1,296 @@ +/* @sourceLoc Test_catch.res */ +/* @generated */ +%%raw("/* @generated */") +module Types = { + @@warning("-30") + + @tag("__typename") type response_members_edges_node_value = + | @live User( + { + @live __typename: [ | #User], + createdAt: TestsUtils.Datetime.t, + @live id: string, + } + ) + | @live @as("__unselected") UnselectedUnionMember(string) + + type rec response_members_edges = { + node: RescriptRelay.CatchResult.t, + } + and response_members = { + edges: option>>, + } + type response = { + members: option, + } + @live + type rawResponse = response + @live + type variables = unit + @live + type refetchVariables = unit + @live let makeRefetchVariables = () => () +} + +@live +let unwrap_response_members_edges_node_value: Types.response_members_edges_node_value => Types.response_members_edges_node_value = RescriptRelay_Internal.unwrapUnion(_, ["User"]) +@live +let wrap_response_members_edges_node_value: Types.response_members_edges_node_value => Types.response_members_edges_node_value = RescriptRelay_Internal.wrapUnion + +type queryRef + +module Internal = { + @live + let variablesConverter: Js.Dict.t>> = %raw( + json`{}` + ) + @live + let variablesConverterMap = () + @live + let convertVariables = v => v->RescriptRelay.convertObj( + variablesConverter, + variablesConverterMap, + Js.undefined + ) + @live + type wrapResponseRaw + @live + let wrapResponseConverter: Js.Dict.t>> = %raw( + json`{"__root":{"members_edges_node_value_User_createdAt":{"c":"TestsUtils.Datetime"},"members_edges_node_value":{"u":"response_members_edges_node_value"}}}` + ) + @live + let wrapResponseConverterMap = { + "TestsUtils.Datetime": TestsUtils.Datetime.serialize, + "response_members_edges_node_value": wrap_response_members_edges_node_value, + } + @live + let convertWrapResponse = v => v->RescriptRelay.convertObj( + wrapResponseConverter, + wrapResponseConverterMap, + Js.null + ) + @live + type responseRaw + @live + let responseConverter: Js.Dict.t>> = %raw( + json`{"__root":{"members_edges_node_value_User_createdAt":{"c":"TestsUtils.Datetime"},"members_edges_node_value":{"u":"response_members_edges_node_value"}}}` + ) + @live + let responseConverterMap = { + "TestsUtils.Datetime": TestsUtils.Datetime.parse, + "response_members_edges_node_value": unwrap_response_members_edges_node_value, + } + @live + let convertResponse = v => v->RescriptRelay.convertObj( + responseConverter, + responseConverterMap, + Js.undefined + ) + type wrapRawResponseRaw = wrapResponseRaw + @live + let convertWrapRawResponse = convertWrapResponse + type rawResponseRaw = responseRaw + @live + let convertRawResponse = convertResponse + type rawPreloadToken<'response> = {source: Js.Nullable.t>} + external tokenToRaw: queryRef => rawPreloadToken = "%identity" +} +module Utils = { + @@warning("-33") + open Types +} + +type relayOperationNode +type operationType = RescriptRelay.queryNode + + +let node: operationType = %raw(json` (function(){ +var v0 = [ + { + "kind": "Literal", + "name": "groupId", + "value": "123" + } +], +v1 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null +}, +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v3 = { + "kind": "InlineFragment", + "selections": [ + (v2/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null + } + ], + "type": "User", + "abstractKey": null +}; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "TestCatchMembersPropQuery", + "selections": [ + { + "alias": null, + "args": (v0/*: any*/), + "concreteType": "MemberConnection", + "kind": "LinkedField", + "name": "members", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "MemberEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "kind": "CatchField", + "field": { + "alias": null, + "args": null, + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v1/*: any*/), + (v3/*: any*/) + ], + "storageKey": null + }, + "to": "RESULT" + } + ], + "storageKey": null + } + ], + "storageKey": "members(groupId:\"123\")" + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "TestCatchMembersPropQuery", + "selections": [ + { + "alias": null, + "args": (v0/*: any*/), + "concreteType": "MemberConnection", + "kind": "LinkedField", + "name": "members", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "MemberEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v1/*: any*/), + (v3/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + (v2/*: any*/) + ], + "type": "Node", + "abstractKey": "__isNode" + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": "members(groupId:\"123\")" + } + ] + }, + "params": { + "cacheID": "98d239a936e0ac54023a142c978b3102", + "id": null, + "metadata": {}, + "name": "TestCatchMembersPropQuery", + "operationKind": "query", + "text": "query TestCatchMembersPropQuery {\n members(groupId: \"123\") {\n edges {\n node {\n __typename\n ... on User {\n id\n createdAt\n }\n ... on Node {\n __isNode: __typename\n __typename\n id\n }\n }\n }\n }\n}\n" + } +}; +})() `) + +@live let load: ( + ~environment: RescriptRelay.Environment.t, + ~variables: Types.variables, + ~fetchPolicy: RescriptRelay.fetchPolicy=?, + ~fetchKey: string=?, + ~networkCacheConfig: RescriptRelay.cacheConfig=?, +) => queryRef = ( + ~environment, + ~variables, + ~fetchPolicy=?, + ~fetchKey=?, + ~networkCacheConfig=?, +) => + RescriptRelay.loadQuery( + environment, + node, + variables->Internal.convertVariables, + { + fetchKey, + fetchPolicy, + networkCacheConfig, + }, + ) + +@live +let queryRefToObservable = token => { + let raw = token->Internal.tokenToRaw + raw.source->Js.Nullable.toOption +} + +@live +let queryRefToPromise = token => { + Js.Promise.make((~resolve, ~reject as _) => { + switch token->queryRefToObservable { + | None => resolve(Error()) + | Some(o) => + open RescriptRelay.Observable + let _: subscription = o->subscribe(makeObserver(~complete=() => resolve(Ok()))) + } + }) +} diff --git a/packages/rescript-relay/__tests__/__generated__/TestCatchUser_user_graphql.res b/packages/rescript-relay/__tests__/__generated__/TestCatchUser_user_graphql.res new file mode 100644 index 00000000..2e0c411a --- /dev/null +++ b/packages/rescript-relay/__tests__/__generated__/TestCatchUser_user_graphql.res @@ -0,0 +1,65 @@ +/* @sourceLoc Test_catch.res */ +/* @generated */ +%%raw("/* @generated */") +module Types = { + @@warning("-30") + + type fragment_t = { + createdAt: TestsUtils.Datetime.t, + } + type fragment = RescriptRelay.CatchResult.t +} + +module Internal = { + @live + type fragmentRaw + @live + let fragmentConverter: Js.Dict.t>> = %raw( + json`{"__root":{"value_createdAt":{"c":"TestsUtils.Datetime"}}}` + ) + @live + let fragmentConverterMap = { + "TestsUtils.Datetime": TestsUtils.Datetime.parse, + } + @live + let convertFragment = v => v->RescriptRelay.convertObj( + fragmentConverter, + fragmentConverterMap, + Js.undefined + ) +} + +type t +type fragmentRef +external getFragmentRef: + RescriptRelay.fragmentRefs<[> | #TestCatchUser_user]> => fragmentRef = "%identity" + +module Utils = { + @@warning("-33") + open Types +} + +type relayOperationNode +type operationType = RescriptRelay.fragmentNode + + +let node: operationType = %raw(json` { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": { + "catchTo": "RESULT" + }, + "name": "TestCatchUser_user", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null + } + ], + "type": "User", + "abstractKey": null +} `) + diff --git a/packages/rescript-relay/src/RescriptRelay.res b/packages/rescript-relay/src/RescriptRelay.res index 64302d5c..f19fd059 100644 --- a/packages/rescript-relay/src/RescriptRelay.res +++ b/packages/rescript-relay/src/RescriptRelay.res @@ -17,6 +17,25 @@ type dataIdObject = {id: dataId} type recordSourceRecords = Js.Json.t type uploadables +module CatchResult = { + type catchError = Js.Json.t + + @tag("ok") + type t<'value> = | @as(true) Ok({value: 'value}) | @as(false) Error({errors: array}) + + let toOption = (t: t<'value>) => + switch t { + | Ok({value}) => Some(value) + | Error(_) => None + } + + let toResult = (t: t<'value>): result<'value, array> => + switch t { + | Ok({value}) => Ok(value) + | Error({errors}) => Error(errors) + } +} + module SuspenseSentinel = { type t diff --git a/packages/rescript-relay/src/RescriptRelay.resi b/packages/rescript-relay/src/RescriptRelay.resi index 405d250a..a6659985 100644 --- a/packages/rescript-relay/src/RescriptRelay.resi +++ b/packages/rescript-relay/src/RescriptRelay.resi @@ -41,6 +41,24 @@ type dataId type dataIdObject = {id: dataId} +/** A module for results originating from the @catch directive. */ +module CatchResult: { + /** The shape of an error caught via @catch. */ + type catchError = Js.Json.t + + /** The result type for @catch. */ + @tag("ok") + type t<'value> = + | @as(true) Ok({value: 'value}) + | @as(false) Error({errors: array}) + + /** Convert a @catch result to option. */ + let toOption: t<'value> => option<'value> + + /** Convert a @catch result to result. */ + let toResult: t<'value> => result<'value, array> +} + module SuspenseSentinel: { type t From 0aaff9800480e4710f440683c907ff9ca5c7ae9d Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Sat, 4 Jan 2025 22:43:05 +0100 Subject: [PATCH 2/4] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21af31cf..a811e684 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # master +- Add support for the new Relay `@catch` directive. https://github.com/zth/rescript-relay/pull/549 + # 3.1.0 This brings the Relay version to `18.2.0`. From 4439ac23c34c7d57473640d6a71be20d4802ea28 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Sat, 4 Jan 2025 22:47:22 +0100 Subject: [PATCH 3/4] point to correct compiler --- packages/relay | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/relay b/packages/relay index 4224c9be..d123685c 160000 --- a/packages/relay +++ b/packages/relay @@ -1 +1 @@ -Subproject commit 4224c9bead46aef53084cc42f45b54bf5079d939 +Subproject commit d123685cea0e193ae2ff394a081b6871caff0c61 From 367dc9632794e4a2d1b9a23085d8515087de9c25 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Sat, 4 Jan 2025 23:03:32 +0100 Subject: [PATCH 4/4] stable tests --- .../rescript-relay/__tests__/Test_catch-tests.js | 14 +++++--------- packages/rescript-relay/__tests__/Test_catch.res | 14 +++++++++----- packages/rescript-relay/__tests__/TestsUtils.res | 2 ++ 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/rescript-relay/__tests__/Test_catch-tests.js b/packages/rescript-relay/__tests__/Test_catch-tests.js index 66d2db7c..a8741342 100644 --- a/packages/rescript-relay/__tests__/Test_catch-tests.js +++ b/packages/rescript-relay/__tests__/Test_catch-tests.js @@ -20,7 +20,7 @@ describe("Catch", () => { }); t.render(test_catch("TestLoggedInUserProp")); - await t.screen.findByText("Got createdAt: 1735707600000"); + await t.screen.findByText("Got createdAt: 2025-01-01"); }); test("logged in user prop - fail", async () => { @@ -51,7 +51,7 @@ describe("Catch", () => { }); t.render(test_catch("TestLoggedInUserPropFragmentData")); - await t.screen.findByText("Got createdAt: 1735707600000"); + await t.screen.findByText("Got createdAt: 2025-01-01"); }); test("logged in user prop from fragment - fail", async () => { @@ -83,9 +83,7 @@ describe("Catch", () => { }); t.render(test_catch("TestMember")); - await t.screen.findByText( - "Got user id: user-1, and createdAt: 1735707600000" - ); + await t.screen.findByText("Got user id: user-1, and createdAt: 2025-01-01"); }); test("member prop - fail", async () => { @@ -118,9 +116,7 @@ describe("Catch", () => { }); t.render(test_catch("TestMemberNested")); - await t.screen.findByText( - "Got user id: user-1, and createdAt: 1735707600000" - ); + await t.screen.findByText("Got user id: user-1, and createdAt: 2025-01-01"); }); test("member prop - fail nested", async () => { @@ -182,7 +178,7 @@ describe("Catch", () => { t.render(test_catch("TestMembers")); await t.screen.findByText( - "User: user-1 - 1735707600000, Error!, User: user-3 - 1735707600000" + "User: user-1 - 2025-01-01, Error!, User: user-3 - 2025-01-01" ); }); }); diff --git a/packages/rescript-relay/__tests__/Test_catch.res b/packages/rescript-relay/__tests__/Test_catch.res index 7019f55c..d9ac38a2 100644 --- a/packages/rescript-relay/__tests__/Test_catch.res +++ b/packages/rescript-relay/__tests__/Test_catch.res @@ -21,7 +21,9 @@ module TestLoggedInUserProp = { switch query.loggedInUser.createdAt { | Ok({value: createdAt}) =>
- {React.string("Got createdAt: " ++ createdAt->Js.Date.getTime->Belt.Float.toString)} + {React.string( + "Got createdAt: " ++ createdAt->Js.Date.toISOString->Js.String2.slice(~from=0, ~to_=10), + )}
| Error(_) =>
{React.string("Error!")}
} @@ -37,7 +39,9 @@ module TestLoggedInUserPropFragmentData = { switch fragmentData { | Ok({value: {createdAt}}) =>
- {React.string("Got createdAt: " ++ createdAt->Js.Date.getTime->Belt.Float.toString)} + {React.string( + "Got createdAt: " ++ createdAt->Js.Date.toISOString->Js.String2.slice(~from=0, ~to_=10), + )}
| Error(_) =>
{React.string("Error!")}
} @@ -67,7 +71,7 @@ module TestMember = { "Got user id: " ++ id ++ ", and createdAt: " ++ - createdAt->Js.Date.getTime->Belt.Float.toString, + createdAt->Js.Date.toISOString->Js.String2.slice(~from=0, ~to_=10), )} | Error(_) =>
{React.string("Error!")}
@@ -104,7 +108,7 @@ module TestMemberNested = { "Got user id: " ++ id ++ ", and createdAt: " ++ - createdAt->Js.Date.getTime->Belt.Float.toString, + createdAt->Js.Date.toISOString->Js.String2.slice(~from=0, ~to_=10), )} | Some(User({memberOfSingular: Error(_)})) =>
{React.string("Error nested!")}
@@ -143,7 +147,7 @@ module TestMembers = { ->Js.Array2.map(r => switch r { | Ok({value: User({id, createdAt})}) => - `User: ${id} - ${createdAt->Js.Date.getTime->Belt.Float.toString}` + `User: ${id} - ${createdAt->Js.Date.toISOString->Js.String2.slice(~from=0, ~to_=10)}` | _ => "Error!" } ) diff --git a/packages/rescript-relay/__tests__/TestsUtils.res b/packages/rescript-relay/__tests__/TestsUtils.res index 89223175..0eec8a3f 100644 --- a/packages/rescript-relay/__tests__/TestsUtils.res +++ b/packages/rescript-relay/__tests__/TestsUtils.res @@ -2,7 +2,9 @@ exception Malformed_date exception Malformed_number module Datetime = { + @editor.completeFrom(Js.Date) type t = Js.Date.t + let parse = t => switch t->Js.Json.decodeString { | None => raise(Malformed_date)