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