diff --git a/CHANGELOG.md b/CHANGELOG.md index bf40ebf5..cc500a5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # master +## Improvements + +- Experimental support for allowing `null` in operation variables. Add `@rescriptRelayNullableVariables` to your operation (query, mutation, subscription) and you'll be allowed to pass `Js.Nullable.null` to your server. https://github.com/zth/rescript-relay/pull/426 + # 1.0.4 ## Improvements diff --git a/packages/relay b/packages/relay index f2439ac5..b02b4640 160000 --- a/packages/relay +++ b/packages/relay @@ -1 +1 @@ -Subproject commit f2439ac59923ca9e3cdbffbc5263362deb5b2e24 +Subproject commit b02b46402a7bc1aa9165449ae8bf11e03952d642 diff --git a/packages/rescript-relay/__tests__/Test_nullableVariables-tests.js b/packages/rescript-relay/__tests__/Test_nullableVariables-tests.js new file mode 100644 index 00000000..d0615fff --- /dev/null +++ b/packages/rescript-relay/__tests__/Test_nullableVariables-tests.js @@ -0,0 +1,49 @@ +require("@testing-library/jest-dom/extend-expect"); +const t = require("@testing-library/react"); +const React = require("react"); +const queryMock = require("./queryMock"); +const ReactTestUtils = require("react-dom/test-utils"); + +const { test_nullableVariables } = require("./Test_nullableVariables.bs"); + +describe("Mutation", () => { + test("variables can be null", async () => { + queryMock.mockQuery({ + name: "TestNullableVariablesQuery", + data: { + loggedInUser: { + id: "user-1", + avatarUrl: "avatar-url-1", + }, + }, + }); + + t.render(test_nullableVariables()); + await t.screen.findByText("Avatar url is avatar-url-1"); + + queryMock.mockQuery({ + name: "TestNullableVariablesMutation", + variables: { + avatarUrl: null, + someInput: { + int: null, + }, + }, + data: { + updateUserAvatar: { + user: { + id: "user-1", + avatarUrl: null, + someRandomArgField: "test", + }, + }, + }, + }); + + ReactTestUtils.act(() => { + t.fireEvent.click(t.screen.getByText("Change avatar URL")); + }); + + await t.screen.findByText("Avatar url is -"); + }); +}); diff --git a/packages/rescript-relay/__tests__/Test_nullableVariables.res b/packages/rescript-relay/__tests__/Test_nullableVariables.res new file mode 100644 index 00000000..d11db455 --- /dev/null +++ b/packages/rescript-relay/__tests__/Test_nullableVariables.res @@ -0,0 +1,62 @@ +module Query = %relay(` + query TestNullableVariablesQuery { + loggedInUser { + avatarUrl + } + } +`) + +module Mutation = %relay(` + mutation TestNullableVariablesMutation($avatarUrl: String, $someInput: SomeInput) @rescriptRelayNullableVariables { + updateUserAvatar(avatarUrl: $avatarUrl) { + user { + avatarUrl + someRandomArgField(someInput: $someInput) + } + } + } +`) + +module Test = { + @react.component + let make = () => { + let environment = RescriptRelay.useEnvironmentFromContext() + let query = Query.use(~variables=(), ()) + let data = query.loggedInUser + +
+ {React.string("Avatar url is " ++ data.avatarUrl->Belt.Option.getWithDefault("-"))} + +
+ } +} + +@live +let test_nullableVariables = () => { + let network = RescriptRelay.Network.makePromiseBased(~fetchFunction=RelayEnv.fetchQuery, ()) + + let environment = RescriptRelay.Environment.make( + ~network, + ~store=RescriptRelay.Store.make(~source=RescriptRelay.RecordSource.make(), ()), + (), + ) + () + + + + +} diff --git a/packages/rescript-relay/__tests__/__generated__/RelaySchemaAssets_graphql.res b/packages/rescript-relay/__tests__/__generated__/RelaySchemaAssets_graphql.res index d73a187a..e82a8b5b 100644 --- a/packages/rescript-relay/__tests__/__generated__/RelaySchemaAssets_graphql.res +++ b/packages/rescript-relay/__tests__/__generated__/RelaySchemaAssets_graphql.res @@ -36,6 +36,13 @@ type rec input_InputA = { usingB: option, } +@live +and input_InputA_nullable = { + time: TestsUtils.Datetime.t, + recursiveA?: Js.Nullable.t, + usingB?: Js.Nullable.t, +} + @live and input_InputB = { time: option, @@ -43,12 +50,25 @@ and input_InputB = { @as("constraint") constraint_: option, } +@live +and input_InputB_nullable = { + time?: Js.Nullable.t, + usingA?: Js.Nullable.t, + @as("constraint") constraint_?: Js.Nullable.t, +} + @live and input_InputC = { intStr: TestsUtils.IntString.t, recursiveC: option, } +@live +and input_InputC_nullable = { + intStr: TestsUtils.IntString.t, + recursiveC?: Js.Nullable.t, +} + @live and input_SomeInput = { str: option, @@ -60,12 +80,29 @@ and input_SomeInput = { @as("private") private_: option, } +@live +and input_SomeInput_nullable = { + str?: Js.Nullable.t, + bool?: Js.Nullable.t, + float?: Js.Nullable.t, + int?: Js.Nullable.t, + datetime?: Js.Nullable.t, + recursive?: Js.Nullable.t, + @as("private") private_?: Js.Nullable.t, +} + @live and input_RecursiveSetOnlineStatusInput = { someValue: TestsUtils.IntString.t, setOnlineStatus: option, } +@live +and input_RecursiveSetOnlineStatusInput_nullable = { + someValue: TestsUtils.IntString.t, + setOnlineStatus?: Js.Nullable.t, +} + @live and input_SetOnlineStatusInput = { onlineStatus: [#Online | #Idle | #Offline], @@ -73,6 +110,13 @@ and input_SetOnlineStatusInput = { recursed: option, } +@live +and input_SetOnlineStatusInput_nullable = { + onlineStatus: [#Online | #Idle | #Offline], + someJsonValue: Js.Json.t, + recursed?: Js.Nullable.t, +} + @live and input_SearchInput = { names: option>>, @@ -80,6 +124,13 @@ and input_SearchInput = { someOtherId: option, } +@live +and input_SearchInput_nullable = { + names?: Js.Nullable.t>>, + id: int, + someOtherId?: Js.Nullable.t, +} + @live and input_PesticideListSearchInput = { companyName: option>, @@ -87,6 +138,14 @@ and input_PesticideListSearchInput = { skip: int, take: int, } + +@live +and input_PesticideListSearchInput_nullable = { + companyName?: Js.Nullable.t>, + pesticideIds?: Js.Nullable.t>, + skip: int, + take: int, +} @live @obj external make_InputA: ( ~time: TestsUtils.Datetime.t, diff --git a/packages/rescript-relay/__tests__/__generated__/TestNullableVariablesMutation_graphql.res b/packages/rescript-relay/__tests__/__generated__/TestNullableVariablesMutation_graphql.res new file mode 100644 index 00000000..776aa2a2 --- /dev/null +++ b/packages/rescript-relay/__tests__/__generated__/TestNullableVariablesMutation_graphql.res @@ -0,0 +1,233 @@ +/* @sourceLoc Test_nullableVariables.res */ +/* @generated */ +%%raw("/* @generated */") +module Types = { + @@ocaml.warning("-30") + + @live type someInput = RelaySchemaAssets_graphql.input_SomeInput_nullable + @live + type rec response_updateUserAvatar_user = { + avatarUrl: option, + someRandomArgField: option, + } + @live + and response_updateUserAvatar = { + user: option, + } + @live + type response = { + updateUserAvatar: option, + } + @live + type rawResponse = response + @live + type variables = { + avatarUrl?: Js.Nullable.t, + someInput?: Js.Nullable.t, + } +} + +module Internal = { + @live + let variablesConverter: Js.Dict.t>> = %raw( + json`{"someInput":{"recursive":{"r":"someInput"},"datetime":{"c":"TestsUtils.Datetime"}},"__root":{"someInput":{"r":"someInput"}}}` + ) + @live + let variablesConverterMap = { + "TestsUtils.Datetime": TestsUtils.Datetime.serialize, + } + @live + let convertVariables = v => v->RescriptRelay.convertObj( + variablesConverter, + variablesConverterMap, + Js.null + ) + @live + type wrapResponseRaw + @live + let wrapResponseConverter: Js.Dict.t>> = %raw( + json`{}` + ) + @live + let wrapResponseConverterMap = () + @live + let convertWrapResponse = v => v->RescriptRelay.convertObj( + wrapResponseConverter, + wrapResponseConverterMap, + Js.null + ) + @live + type responseRaw + @live + let responseConverter: Js.Dict.t>> = %raw( + json`{}` + ) + @live + let responseConverterMap = () + @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 +} +module Utils = { + @@ocaml.warning("-33") + open Types + @live @obj external make_someInput: ( + ~bool: bool=?, + ~datetime: TestsUtils.Datetime.t=?, + ~float: float=?, + ~int: int=?, + ~_private: bool=?, + ~recursive: someInput=?, + ~str: string=?, + unit + ) => someInput = "" + + + @live @obj external makeVariables: ( + ~avatarUrl: string=?, + ~someInput: someInput=?, + unit + ) => variables = "" + + +} + +type relayOperationNode +type operationType = RescriptRelay.mutationNode + + +let node: operationType = %raw(json` (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "avatarUrl" + }, + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "someInput" + } +], +v1 = [ + { + "kind": "Variable", + "name": "avatarUrl", + "variableName": "avatarUrl" + } +], +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "avatarUrl", + "storageKey": null +}, +v3 = { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "someInput", + "variableName": "someInput" + } + ], + "kind": "ScalarField", + "name": "someRandomArgField", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "TestNullableVariablesMutation", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": "UpdateUserAvatarPayload", + "kind": "LinkedField", + "name": "updateUserAvatar", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "user", + "plural": false, + "selections": [ + (v2/*: any*/), + (v3/*: any*/) + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "TestNullableVariablesMutation", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": "UpdateUserAvatarPayload", + "kind": "LinkedField", + "name": "updateUserAvatar", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "user", + "plural": false, + "selections": [ + (v2/*: any*/), + (v3/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "9a78edc9246d901f0f8b29ef34b53df7", + "id": null, + "metadata": {}, + "name": "TestNullableVariablesMutation", + "operationKind": "mutation", + "text": "mutation TestNullableVariablesMutation(\n $avatarUrl: String\n $someInput: SomeInput\n) {\n updateUserAvatar(avatarUrl: $avatarUrl) {\n user {\n avatarUrl\n someRandomArgField(someInput: $someInput)\n id\n }\n }\n}\n" + } +}; +})() `) + + diff --git a/packages/rescript-relay/__tests__/__generated__/TestNullableVariablesQuery_graphql.res b/packages/rescript-relay/__tests__/__generated__/TestNullableVariablesQuery_graphql.res new file mode 100644 index 00000000..66c343e8 --- /dev/null +++ b/packages/rescript-relay/__tests__/__generated__/TestNullableVariablesQuery_graphql.res @@ -0,0 +1,159 @@ +/* @sourceLoc Test_nullableVariables.res */ +/* @generated */ +%%raw("/* @generated */") +module Types = { + @@ocaml.warning("-30") + + type rec response_loggedInUser = { + avatarUrl: option, + } + type response = { + loggedInUser: response_loggedInUser, + } + @live + type rawResponse = response + @live + type variables = unit + @live + type refetchVariables = unit + @live let makeRefetchVariables = () => () +} + +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`{}` + ) + @live + let wrapResponseConverterMap = () + @live + let convertWrapResponse = v => v->RescriptRelay.convertObj( + wrapResponseConverter, + wrapResponseConverterMap, + Js.null + ) + @live + type responseRaw + @live + let responseConverter: Js.Dict.t>> = %raw( + json`{}` + ) + @live + let responseConverterMap = () + @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 queryRef + +module Utils = { + @@ocaml.warning("-33") + open Types + @live @obj external makeVariables: unit => unit = "" +} + +type relayOperationNode +type operationType = RescriptRelay.queryNode + + +let node: operationType = %raw(json` (function(){ +var v0 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "avatarUrl", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "TestNullableVariablesQuery", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "loggedInUser", + "plural": false, + "selections": [ + (v0/*: any*/) + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "TestNullableVariablesQuery", + "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": "d9322739175937b5325307d7d2c9d354", + "id": null, + "metadata": {}, + "name": "TestNullableVariablesQuery", + "operationKind": "query", + "text": "query TestNullableVariablesQuery {\n loggedInUser {\n avatarUrl\n id\n }\n}\n" + } +}; +})() `) + +include RescriptRelay.MakeLoadQuery({ + type variables = Types.variables + type loadedQueryRef = queryRef + type response = Types.response + type node = relayOperationNode + let query = node + let convertVariables = Internal.convertVariables +});