From 500d91b3c38e33c82d803683388726b728b4a160 Mon Sep 17 00:00:00 2001 From: Andrey Lunyov Date: Mon, 7 Nov 2022 05:01:53 -0800 Subject: [PATCH] Support live resolvers that whant to return @weak type Summary: For live resolvers that what to return weak object our `weakObjectWrapper` was not fully correct. We need to wrap the result of the `read` function. This diff changes the LiveState value, by wrapping `read` into `weakObjectWrapper` HOC. Reviewed By: captbaritone Differential Revision: D41037791 fbshipit-source-id: fba126cda7fe12343cbb643f42b0e963460d9cc3 --- compiler/crates/relay-codegen/src/ast.rs | 1 + .../crates/relay-codegen/src/build_ast.rs | 1 + compiler/crates/relay-codegen/src/printer.rs | 9 +- .../relay-resolver-live-weak-object.expected | 166 ++++++++++++++++ .../relay-resolver-live-weak-object.graphql | 22 +++ .../tests/compile_relay_artifacts_test.rs | 9 +- .../__tests__/RelayResolverModel-test.js | 39 ++++ ...lverModelTestWeakLiveFieldQuery.graphql.js | 177 ++++++++++++++++++ packages/relay-runtime/experimental.js | 6 +- .../store/__tests__/resolvers/TodoModel.js | 22 +++ ..._todo_description$normalization.graphql.js | 49 +++++ .../LiveResolverCache.js | 11 +- .../isLiveStateValue.js | 21 +++ .../weakObjectWrapper.js | 39 +++- 14 files changed, 558 insertions(+), 14 deletions(-) create mode 100644 compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/relay-resolver-live-weak-object.expected create mode 100644 compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/relay-resolver-live-weak-object.graphql create mode 100644 packages/react-relay/__tests__/__generated__/RelayResolverModelTestWeakLiveFieldQuery.graphql.js create mode 100644 packages/relay-runtime/store/__tests__/resolvers/__generated__/Query__live_todo_description$normalization.graphql.js create mode 100644 packages/relay-runtime/store/experimental-live-resolvers/isLiveStateValue.js diff --git a/compiler/crates/relay-codegen/src/ast.rs b/compiler/crates/relay-codegen/src/ast.rs index cd15c087ef749..6bd708339601f 100644 --- a/compiler/crates/relay-codegen/src/ast.rs +++ b/compiler/crates/relay-codegen/src/ast.rs @@ -98,6 +98,7 @@ pub enum Primitive { resolver: Box, key: StringKey, plural: bool, + live: bool, }, } diff --git a/compiler/crates/relay-codegen/src/build_ast.rs b/compiler/crates/relay-codegen/src/build_ast.rs index c1adf5a646581..cefafab3776dd 100644 --- a/compiler/crates/relay-codegen/src/build_ast.rs +++ b/compiler/crates/relay-codegen/src/build_ast.rs @@ -1049,6 +1049,7 @@ impl<'schema, 'builder, 'config> CodegenBuilder<'schema, 'builder, 'config> { resolver: Box::new(resolver_module), key, plural, + live: relay_resolver_metadata.live, } } else { resolver_module diff --git a/compiler/crates/relay-codegen/src/printer.rs b/compiler/crates/relay-codegen/src/printer.rs index c24632b208646..cc089a5f38721 100644 --- a/compiler/crates/relay-codegen/src/printer.rs +++ b/compiler/crates/relay-codegen/src/printer.rs @@ -520,11 +520,13 @@ impl<'b> JSONPrinter<'b> { resolver, key, plural, + live, } => self.write_relay_resolver_weak_object_wrapper( f, resolver, *key, *plural, + *live, indent, is_dedupe_var, ), @@ -621,11 +623,16 @@ impl<'b> JSONPrinter<'b> { resolver: &Primitive, key: StringKey, plural: bool, + live: bool, indent: usize, is_dedupe_var: bool, ) -> FmtResult { let relay_runtime_experimental = "relay-runtime/experimental"; - let weak_object_wrapper = "weakObjectWrapper"; + let weak_object_wrapper = if live { + "weakObjectWrapperLive" + } else { + "weakObjectWrapper" + }; self.write_js_dependency( f, diff --git a/compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/relay-resolver-live-weak-object.expected b/compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/relay-resolver-live-weak-object.expected new file mode 100644 index 0000000000000..5005f7f027687 --- /dev/null +++ b/compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/relay-resolver-live-weak-object.expected @@ -0,0 +1,166 @@ +==================================== INPUT ==================================== +query relayResolverLiveWeakObject_Query { + client_types { + __typename + } +} + +# %extensions% + +scalar MyClientTypeModel @__RelayCustomScalar(path: "/path/to/test/fixture/weak-type.js", export_name: "MyClientType") + +type MyClientType @__RelayResolverModel @RelayOutputType @__RelayWeakObject { + __relay_model_instance: MyClientTypeModel +} + +extend type Query { + client_types: MyClientType @relay_resolver( + fragment_name: "MyClientType____relay_model_instance" + import_path: "./path/to/ClientTypeResolver.js" + has_output_type: true + live: true, + ) +} +==================================== OUTPUT =================================== +{ + "kind": "SplitOperation", + "metadata": {}, + "name": "Query__client_types$normalization", + "selections": [ + { + "kind": "ClientExtension", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__relay_model_instance", + "storageKey": null + } + ] + } + ] +} + +{ + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": { + "hasClientEdges": true + }, + "name": "relayResolverLiveWeakObject_Query", + "selections": [ + { + "kind": "ClientEdgeToClientObject", + "concreteType": "MyClientType", + "backingField": { + "alias": null, + "args": null, + "fragment": { + "args": null, + "kind": "FragmentSpread", + "name": "MyClientType____relay_model_instance" + }, + "kind": "RelayLiveResolver", + "name": "client_types", + "resolverModule": require('relay-runtime/experimental').weakObjectWrapperLive(require('ClientTypeResolver'), '__relay_model_instance', false), + "path": "client_types", + "normalizationInfo": { + "concreteType": "MyClientType", + "plural": false, + "normalizationNode": require('Query__client_types$normalization.graphql') + } + }, + "linkedField": { + "alias": null, + "args": null, + "concreteType": "MyClientType", + "kind": "LinkedField", + "name": "client_types", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + } + ], + "storageKey": null + } + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "relayResolverLiveWeakObject_Query", + "selections": [ + { + "kind": "ClientExtension", + "selections": [ + { + "name": "client_types", + "args": null, + "fragment": { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__relay_model_instance", + "storageKey": null + } + ], + "type": "MyClientType", + "abstractKey": null + }, + "kind": "RelayResolver", + "storageKey": null + } + ] + } + ] + }, + "params": { + "cacheID": "b9f4901608de1ebafc06cb1f4ecaec9a", + "id": null, + "metadata": {}, + "name": "relayResolverLiveWeakObject_Query", + "operationKind": "query", + "text": null + } +} + +QUERY: + +Query Text is Empty. + +{ + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "MyClientType____relay_model_instance", + "selections": [ + { + "kind": "ClientExtension", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__relay_model_instance", + "storageKey": null + } + ] + } + ], + "type": "MyClientType", + "abstractKey": null +} diff --git a/compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/relay-resolver-live-weak-object.graphql b/compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/relay-resolver-live-weak-object.graphql new file mode 100644 index 0000000000000..32ea2a6eac54e --- /dev/null +++ b/compiler/crates/relay-compiler/tests/compile_relay_artifacts/fixtures/relay-resolver-live-weak-object.graphql @@ -0,0 +1,22 @@ +query relayResolverLiveWeakObject_Query { + client_types { + __typename + } +} + +# %extensions% + +scalar MyClientTypeModel @__RelayCustomScalar(path: "/path/to/test/fixture/weak-type.js", export_name: "MyClientType") + +type MyClientType @__RelayResolverModel @RelayOutputType @__RelayWeakObject { + __relay_model_instance: MyClientTypeModel +} + +extend type Query { + client_types: MyClientType @relay_resolver( + fragment_name: "MyClientType____relay_model_instance" + import_path: "./path/to/ClientTypeResolver.js" + has_output_type: true + live: true, + ) +} diff --git a/compiler/crates/relay-compiler/tests/compile_relay_artifacts_test.rs b/compiler/crates/relay-compiler/tests/compile_relay_artifacts_test.rs index b8a60a82f9017..3901fb5f085e6 100644 --- a/compiler/crates/relay-compiler/tests/compile_relay_artifacts_test.rs +++ b/compiler/crates/relay-compiler/tests/compile_relay_artifacts_test.rs @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<2dc303928ff3615db2ad2ba0e7a5aff2>> + * @generated SignedSource<<91eb3240b55e761ddf8a237ee850be9c>> */ mod compile_relay_artifacts; @@ -1111,6 +1111,13 @@ fn relay_resolver_es_modules() { test_fixture(transform_fixture, "relay-resolver-es-modules.graphql", "compile_relay_artifacts/fixtures/relay-resolver-es-modules.expected", input, expected); } +#[test] +fn relay_resolver_live_weak_object() { + let input = include_str!("compile_relay_artifacts/fixtures/relay-resolver-live-weak-object.graphql"); + let expected = include_str!("compile_relay_artifacts/fixtures/relay-resolver-live-weak-object.expected"); + test_fixture(transform_fixture, "relay-resolver-live-weak-object.graphql", "compile_relay_artifacts/fixtures/relay-resolver-live-weak-object.expected", input, expected); +} + #[test] fn relay_resolver_named_import() { let input = include_str!("compile_relay_artifacts/fixtures/relay-resolver-named-import.graphql"); diff --git a/packages/react-relay/__tests__/RelayResolverModel-test.js b/packages/react-relay/__tests__/RelayResolverModel-test.js index 16532099ef779..8ac2020e0294f 100644 --- a/packages/react-relay/__tests__/RelayResolverModel-test.js +++ b/packages/react-relay/__tests__/RelayResolverModel-test.js @@ -30,6 +30,7 @@ const RelayNetwork = require('relay-runtime/network/RelayNetwork'); const {graphql} = require('relay-runtime/query/GraphQLTag'); const { addTodo, + completeTodo, resetStore, } = require('relay-runtime/store/__tests__/resolvers/ExampleTodoStore'); const LiveResolverStore = require('relay-runtime/store/experimental-live-resolvers/LiveResolverStore.js'); @@ -238,4 +239,42 @@ describe.each([ ); expect(renderer.toJSON()).toEqual('Test todo - red'); }); + + test('read live @weak resolver field', () => { + function TodoComponentWithPluralResolverComponent(props: {todoID: string}) { + const data = useClientQuery( + graphql` + query RelayResolverModelTestWeakLiveFieldQuery($id: ID!) { + live_todo_description(todoID: $id) { + text + color + } + } + `, + {id: props.todoID}, + ); + if (data?.live_todo_description == null) { + return null; + } + + return `${data.live_todo_description?.text ?? 'unknown'} - ${ + data.live_todo_description?.color ?? 'unknown' + }`; + } + + addTodo('Test todo'); + + const renderer = TestRenderer.create( + + + , + ); + expect(renderer.toJSON()).toEqual('Test todo - red'); + + TestRenderer.act(() => { + completeTodo('todo-1'); + jest.runAllImmediates(); + }); + expect(renderer.toJSON()).toEqual('Test todo - green'); + }); }); diff --git a/packages/react-relay/__tests__/__generated__/RelayResolverModelTestWeakLiveFieldQuery.graphql.js b/packages/react-relay/__tests__/__generated__/RelayResolverModelTestWeakLiveFieldQuery.graphql.js new file mode 100644 index 0000000000000..107ceaeed191d --- /dev/null +++ b/packages/react-relay/__tests__/__generated__/RelayResolverModelTestWeakLiveFieldQuery.graphql.js @@ -0,0 +1,177 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @oncall relay + * + * @generated SignedSource<> + * @flow + * @lightSyntaxTransform + * @nogrep + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ClientRequest, ClientQuery } from 'relay-runtime'; +import type { LiveState } from "relay-runtime/store/experimental-live-resolvers/LiveResolverStore"; +import type { TodoDescription____relay_model_instance$data } from "./../../../relay-runtime/store/__tests__/resolvers/__generated__/TodoDescription____relay_model_instance.graphql"; +import {live_todo_description as queryLiveTodoDescriptionResolver} from "../../../relay-runtime/store/__tests__/resolvers/TodoModel.js"; +// Type assertion validating that `queryLiveTodoDescriptionResolver` resolver is correctly implemented. +// A type error here indicates that the type signature of the resolver module is incorrect. +(queryLiveTodoDescriptionResolver: ( + args: {| + todoID: string, + |}, +) => LiveState); +import {color as todoDescriptionColorResolver} from "../../../relay-runtime/store/__tests__/resolvers/TodoDescription.js"; +// Type assertion validating that `todoDescriptionColorResolver` resolver is correctly implemented. +// A type error here indicates that the type signature of the resolver module is incorrect. +(todoDescriptionColorResolver: ( + __relay_model_instance: TodoDescription____relay_model_instance$data['__relay_model_instance'], +) => mixed); +import {text as todoDescriptionTextResolver} from "../../../relay-runtime/store/__tests__/resolvers/TodoDescription.js"; +// Type assertion validating that `todoDescriptionTextResolver` resolver is correctly implemented. +// A type error here indicates that the type signature of the resolver module is incorrect. +(todoDescriptionTextResolver: ( + __relay_model_instance: TodoDescription____relay_model_instance$data['__relay_model_instance'], +) => ?string); +import type { Query__live_todo_description$normalization } from "./../../../relay-runtime/store/__tests__/resolvers/__generated__/Query__live_todo_description$normalization.graphql"; +export type RelayResolverModelTestWeakLiveFieldQuery$variables = {| + id: string, +|}; +export type RelayResolverModelTestWeakLiveFieldQuery$data = {| + +live_todo_description: ?{| + +color: ?$Call<((...empty[]) => R) => R, typeof todoDescriptionColorResolver>, + +text: ?string, + |}, +|}; +export type RelayResolverModelTestWeakLiveFieldQuery = {| + response: RelayResolverModelTestWeakLiveFieldQuery$data, + variables: RelayResolverModelTestWeakLiveFieldQuery$variables, +|}; +*/ + +var node/*: ClientRequest*/ = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "id" + } +], +v1 = [ + { + "kind": "Variable", + "name": "todoID", + "variableName": "id" + } +], +v2 = { + "args": null, + "kind": "FragmentSpread", + "name": "TodoDescription____relay_model_instance" +}; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": { + "hasClientEdges": true + }, + "name": "RelayResolverModelTestWeakLiveFieldQuery", + "selections": [ + { + "kind": "ClientEdgeToClientObject", + "concreteType": "TodoDescription", + "backingField": { + "alias": null, + "args": (v1/*: any*/), + "fragment": null, + "kind": "RelayLiveResolver", + "name": "live_todo_description", + "resolverModule": require('relay-runtime/experimental').weakObjectWrapperLive(require('./../../../relay-runtime/store/__tests__/resolvers/TodoModel').live_todo_description, '__relay_model_instance', false), + "path": "live_todo_description", + "normalizationInfo": { + "concreteType": "TodoDescription", + "plural": false, + "normalizationNode": require('./../../../relay-runtime/store/__tests__/resolvers/__generated__/Query__live_todo_description$normalization.graphql') + } + }, + "linkedField": { + "alias": null, + "args": (v1/*: any*/), + "concreteType": "TodoDescription", + "kind": "LinkedField", + "name": "live_todo_description", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "fragment": (v2/*: any*/), + "kind": "RelayResolver", + "name": "text", + "resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('./../../../relay-runtime/store/__tests__/resolvers/__generated__/TodoDescription____relay_model_instance.graphql'), require('./../../../relay-runtime/store/__tests__/resolvers/TodoDescription').text, '__relay_model_instance', false), + "path": "text" + }, + { + "alias": null, + "args": null, + "fragment": (v2/*: any*/), + "kind": "RelayResolver", + "name": "color", + "resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('./../../../relay-runtime/store/__tests__/resolvers/__generated__/TodoDescription____relay_model_instance.graphql'), require('./../../../relay-runtime/store/__tests__/resolvers/TodoDescription').color, '__relay_model_instance', false), + "path": "color" + } + ], + "storageKey": null + } + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "RelayResolverModelTestWeakLiveFieldQuery", + "selections": [ + { + "kind": "ClientExtension", + "selections": [ + { + "name": "live_todo_description", + "args": (v1/*: any*/), + "fragment": null, + "kind": "RelayResolver", + "storageKey": null + } + ] + } + ] + }, + "params": { + "cacheID": "20a23dad2084a9bb9138f137a764c9e4", + "id": null, + "metadata": {}, + "name": "RelayResolverModelTestWeakLiveFieldQuery", + "operationKind": "query", + "text": null + } +}; +})(); + +if (__DEV__) { + (node/*: any*/).hash = "e3e60c655cd9eed2036fe61aa72f4402"; +} + +module.exports = ((node/*: any*/)/*: ClientQuery< + RelayResolverModelTestWeakLiveFieldQuery$variables, + RelayResolverModelTestWeakLiveFieldQuery$data, +>*/); diff --git a/packages/relay-runtime/experimental.js b/packages/relay-runtime/experimental.js index 56f156d5bb7cc..d12b6860612bc 100644 --- a/packages/relay-runtime/experimental.js +++ b/packages/relay-runtime/experimental.js @@ -17,12 +17,16 @@ const { suspenseSentinel, } = require('./store/experimental-live-resolvers/LiveResolverSuspenseSentinel'); const resolverDataInjector = require('./store/experimental-live-resolvers/resolverDataInjector'); -const weakObjectWrapper = require('./store/experimental-live-resolvers/weakObjectWrapper'); +const { + weakObjectWrapper, + weakObjectWrapperLive, +} = require('./store/experimental-live-resolvers/weakObjectWrapper'); module.exports = { resolverDataInjector, isSuspenseSentinel, LiveResolverStore, weakObjectWrapper, + weakObjectWrapperLive, suspenseSentinel, }; diff --git a/packages/relay-runtime/store/__tests__/resolvers/TodoModel.js b/packages/relay-runtime/store/__tests__/resolvers/TodoModel.js index 01e8fc05410c1..3a4c28b3384a1 100644 --- a/packages/relay-runtime/store/__tests__/resolvers/TodoModel.js +++ b/packages/relay-runtime/store/__tests__/resolvers/TodoModel.js @@ -76,10 +76,32 @@ function todo_model_null(): ?DataID { return null; } +/** + * @RelayResolver Query.live_todo_description(todoID: ID!): TodoDescription + * @live + * + */ +function live_todo_description(args: { + todoID: string, +}): LiveState { + return { + read() { + const todo = Selectors.getTodo(TODO_STORE.getState(), args.todoID); + return todo + ? createTodoDescription(todo.description, todo.isCompleted) + : null; + }, + subscribe(cb) { + return TODO_STORE.subscribe(args.todoID, cb); + }, + }; +} + module.exports = { todo_model_null, TodoModel, description, fancy_description, many_fancy_descriptions, + live_todo_description, }; diff --git a/packages/relay-runtime/store/__tests__/resolvers/__generated__/Query__live_todo_description$normalization.graphql.js b/packages/relay-runtime/store/__tests__/resolvers/__generated__/Query__live_todo_description$normalization.graphql.js new file mode 100644 index 0000000000000..7373b83d8d0cf --- /dev/null +++ b/packages/relay-runtime/store/__tests__/resolvers/__generated__/Query__live_todo_description$normalization.graphql.js @@ -0,0 +1,49 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @oncall relay + * + * @generated SignedSource<> + * @flow + * @lightSyntaxTransform + * @nogrep + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { NormalizationSplitOperation } from 'relay-runtime'; + +import type { TodoDescription } from "../TodoDescription.js"; +export type Query__live_todo_description$normalization = {| + +__relay_model_instance: ?TodoDescription, +|}; + +*/ + +var node/*: NormalizationSplitOperation*/ = { + "kind": "SplitOperation", + "metadata": {}, + "name": "Query__live_todo_description$normalization", + "selections": [ + { + "kind": "ClientExtension", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__relay_model_instance", + "storageKey": null + } + ] + } + ] +}; + +module.exports = node; diff --git a/packages/relay-runtime/store/experimental-live-resolvers/LiveResolverCache.js b/packages/relay-runtime/store/experimental-live-resolvers/LiveResolverCache.js index cec987c787db6..035a0bf0853f2 100644 --- a/packages/relay-runtime/store/experimental-live-resolvers/LiveResolverCache.js +++ b/packages/relay-runtime/store/experimental-live-resolvers/LiveResolverCache.js @@ -62,6 +62,7 @@ const RELAY_RESOLVER_LIVE_STATE_VALUE = '__resolverLiveStateValue'; const RELAY_RESOLVER_LIVE_STATE_DIRTY = '__resolverLiveStateDirty'; const RELAY_RESOLVER_RECORD_TYPENAME = '__RELAY_RESOLVER__'; const getOutputTypeRecordIDs = require('./getOutputTypeRecordIDs'); +const isLiveStateValue = require('./isLiveStateValue'); /** * An experimental fork of store/ResolverCache.js intended to let us experiment @@ -821,16 +822,6 @@ function expectRecord(source: RecordSource, recordID: DataID): Record { return record; } -// Validate that a value is live state -function isLiveStateValue(v: mixed): boolean { - return ( - v != null && - typeof v === 'object' && - typeof v.read === 'function' && - typeof v.subscribe === 'function' - ); -} - function getUpdatedDataIDs(updatedRecords: UpdatedRecords): DataIDSet { return updatedRecords; } diff --git a/packages/relay-runtime/store/experimental-live-resolvers/isLiveStateValue.js b/packages/relay-runtime/store/experimental-live-resolvers/isLiveStateValue.js new file mode 100644 index 0000000000000..838224a6819a5 --- /dev/null +++ b/packages/relay-runtime/store/experimental-live-resolvers/isLiveStateValue.js @@ -0,0 +1,21 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + * @oncall relay + */ + +'use strict'; + +module.exports = function isLiveStateValue(v: mixed): boolean { + return ( + v != null && + typeof v === 'object' && + typeof v.read === 'function' && + typeof v.subscribe === 'function' + ); +}; diff --git a/packages/relay-runtime/store/experimental-live-resolvers/weakObjectWrapper.js b/packages/relay-runtime/store/experimental-live-resolvers/weakObjectWrapper.js index 0a9bd49ce7579..5cbcfb2b8a61c 100644 --- a/packages/relay-runtime/store/experimental-live-resolvers/weakObjectWrapper.js +++ b/packages/relay-runtime/store/experimental-live-resolvers/weakObjectWrapper.js @@ -12,6 +12,40 @@ const invariant = require('invariant'); +const isLiveStateValue = require('./isLiveStateValue'); + +/** + * Wrap the return `value` of the @live resolver that return @weak + * object into {`key`: `value`} object. + */ +function weakObjectWrapperLive( + resolverFn: (key: TKey, args?: TArgs) => mixed, + key: string, + isPlural: boolean, +): (key: TKey, args?: TArgs) => mixed { + return (...args) => { + const liveState = resolverFn.apply(null, args); + invariant( + isLiveStateValue(liveState), + 'Resolver is expected to return a LiveState value.', + ); + return { + ...liveState, + read: weakObjectWrapper( + () => { + return (liveState: $FlowFixMe).read(); + }, + key, + isPlural, + ), + }; + }; +} + +/** + * Wrap the return `value` of the resolver that return @weak + * object into {`key`: `value`} object. + */ function weakObjectWrapper( resolverFn: (key: TKey, args?: TArgs) => mixed, key: string, @@ -36,4 +70,7 @@ function weakObjectWrapper( }; } -module.exports = weakObjectWrapper; +module.exports = { + weakObjectWrapperLive, + weakObjectWrapper, +};