From 9275d3054b7923573f3d74e119439054607f35e9 Mon Sep 17 00:00:00 2001 From: Nestor Date: Tue, 25 Feb 2020 18:01:29 +0100 Subject: [PATCH 1/7] Mutations init --- docs/src/mutations/api.md | 63 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/docs/src/mutations/api.md b/docs/src/mutations/api.md index b0d5c88..d766e9b 100644 --- a/docs/src/mutations/api.md +++ b/docs/src/mutations/api.md @@ -1 +1,64 @@ # API Reference + +## Functions + +### `createMutations` + +| Argument | Type | Description | +|----------|:-------------:|:------| +| `mutations` | JS Module | Built mutations module. This module should export resolvers object, config object and optionally stateBuilder object | +| `subgraph` | string | Name of the subgraph | +| `config` | Object | Object whose properties must match the resolvers module's config object 1:1 | + +### `createMutationsLink` + +| Argument | Type | Description | +|----------|:-------------:|:------| +| `mutations` | `Mutations` | Mutations object, returned from `createMutations` function | + +## Objects + +### `stateUpdater` + +#### Properties + +| Argument | Type | Description | +|----------|:-------------:|:------| +| `current` | `MutationState` | Inmutable copy of current state | + +#### Methods + +* `dispatch`: **async** method used to emit/dispatch events to the UI, in real time. It modifies the `state` object, appending the dispatched event to its `events` array property. + +| Argument | Type | Description | +|----------|:-------------:|:------| +| `eventName` | string | Name of the event to dispatch, it must match the key used for this event in the custom `EventMap` or `CoreEvents` interface. | +| `payload` | Object | Payload of the event to dispatch, its type must match the interface used for this event in the custom `EventMap` or `CoreEvents` interface | + +**Returns** a void promise. + +## `stateBuilder` + +**Optional** object that implements the `StateBuilder` interface. It is implemented to add an initial values to custom state properties and custom reducers for custom events. It can implement a catch-all reducer. If it exists it should be exported in the resolvers module. + +It implements the following methods and properties: + +* `getInitialState`: method that returns a `Partial`, where `TState` is a custom defined `MutationState` interface. +* `reducers`: object that contains the reducers for custom events. Each event reducer is a key in this object, that must match the event's key in the `EventMap` interface, and its value is a function with the following arguments: + + | Argument | Type | Description | + |----------|:-------------:|:------| + | `state` | `MutationState` | Inmutable copy of current state | + | `payload` | `MutationState` | Payload of the event to dispatch, its type must match the interface used for this event in the custom `EventMap` or `CoreEvents` interface | + + Each of these reducers **must return** a `Partial` +* `reducer` **(Optional)**: catch-all reducer. It runs after any event is dispatched. It receives the following arguments: + + | Argument | Type | Description | + |----------|:-------------:|:------| + | `state` | `MutationState` | Inmutable copy of current state | + | `event` | `Event` | Object with dispatched event's name and payload. The name matches each event's key used in the `EventMap` or `CoreEvents` interface | + +## Interfaces + +### \ No newline at end of file From 8b2617c347c51e85acdd3e7de83410cc465f8342 Mon Sep 17 00:00:00 2001 From: Nestor Date: Thu, 27 Feb 2020 16:41:06 +0100 Subject: [PATCH 2/7] Mutations API docs progress --- docs/src/mutations/api.md | 126 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 120 insertions(+), 6 deletions(-) diff --git a/docs/src/mutations/api.md b/docs/src/mutations/api.md index d766e9b..ea016e8 100644 --- a/docs/src/mutations/api.md +++ b/docs/src/mutations/api.md @@ -37,10 +37,128 @@ **Returns** a void promise. -## `stateBuilder` +## Interfaces and types + +### `CoreEvents` + +Type that has pre-defined events, and their payload types mapped, that can be dispatched through the `stateUpdater` object's `dispatch` method. + +```ts + +type CoreEvents = { + TRANSACTION_CREATED: TransactionCreatedEvent + TRANSACTION_COMPLETED: TransactionCompletedEvent + TRANSACTION_ERROR: TransactionErrorEvent + PROGRESS_UPDATE: ProgressUpdateEvent +} + +``` + +### `CoreState` + +Interface that partially defines the state data of a mutation, that will be dispatched through the `stateUpdater` object's `dispatch` method in a `MutationStates` object. Does not include the `events` state property, which is an array containing all dispatched events and their payloads, and which is also dispatched in the same state object. + +```ts + +interface CoreState { + uuid: string + progress: number +} + +``` + +It contains the following properties: + +| Property | Description | + |----------|:-------------| + | `uuid` | Unique identifier for each event. It is randomly and automatically generated. | + | `progress` | Number between 0 and 1 that indicates the progress the invoked mutation has. It is modified through the `PROGRESS_UPDATE` core event dispatch | + + +### `Event` + +It contains + +```ts + +interface Event { + name: keyof MutationEvents + payload: EventPayload +} + +``` + +It contains the following properties: + +| Property | Description | + |----------|:-------------| + | `name` | Name of the event. It matches the key used to name this event either in the `CoreEvents` type or a custom `EventMap` type. | + | `payload` | Payload of the event dispatched. Its type matches the type mapped to the event's name either in the `CoreEvents` type or a custom `EventMap` type. | + +### `EventMap` + +Optional custom extension of the `CoreEvents` type. It defines custom events, and their payload types, that can be dispatched through the `stateUpdater` object's `dispatch` method. + +Example: + +```ts + +type EventMap = { + CUSTOM_EVENT: CustomEvent +} + +``` + +### `MutationState` + +Interface that fully defines the state data of a mutation that will be dispatched through the `stateUpdater` object's `dispatch` method, in a `MutationStates` object. It contains an `events` key which is an array that contains all dispatched events and their payloads, along with the properties defined in the `CoreState` interface and the optionally custom `TState` interface. + +```ts + +type MutationState< + TState = CoreState, + TEventMap extends EventTypeMap = CoreEvents +> = { events: EventLog } & CoreState & TState + +``` + +### `MutationStates` + +```ts + +type MutationStates< + TState = CoreState, + TEventMap extends EventTypeMap = CoreEvents +> = { + [mutation: string]: MutationState +} + +``` + +A type that defines the actual object passed to the UI, through the `stateUpdater` object's `dispatch` method. It is an object that has each `MutationState`'s state data mapped to its mutation name. In the case where the same mutation gets called more than once in the same query, the mutation's name changes to the mutation name followed by an underscore and a consecutive integer. Example: 'addMutation_2'. + +### `StateBuilder` **Optional** object that implements the `StateBuilder` interface. It is implemented to add an initial values to custom state properties and custom reducers for custom events. It can implement a catch-all reducer. If it exists it should be exported in the resolvers module. +```ts + +interface StateBuilder { + getInitialState(uuid: string): TState + reducers?: { + [TEvent in keyof MutationEvents]?: ( + state: MutationState, + payload: InferEventPayload, + ) => MaybeAsync>> + } + reducer?: ( + state: MutationState, + event: Event, + ) => MaybeAsync>> +} + +``` + It implements the following methods and properties: * `getInitialState`: method that returns a `Partial`, where `TState` is a custom defined `MutationState` interface. @@ -57,8 +175,4 @@ It implements the following methods and properties: | Argument | Type | Description | |----------|:-------------:|:------| | `state` | `MutationState` | Inmutable copy of current state | - | `event` | `Event` | Object with dispatched event's name and payload. The name matches each event's key used in the `EventMap` or `CoreEvents` interface | - -## Interfaces - -### \ No newline at end of file + | `event` | `Event` | Object with dispatched event's name and payload. The name matches each event's key used in the `EventMap` or `CoreEvents` interface | \ No newline at end of file From 3064a64f909e34968f2df8cfe6bb0bd9bde41f4a Mon Sep 17 00:00:00 2001 From: Nestor Date: Fri, 28 Feb 2020 01:17:15 +0100 Subject: [PATCH 3/7] Apollo-React API Docs --- docs/src/mutations-apollo-react/api.md | 42 ++++++++++++++++++++++++++ docs/src/mutations/api.md | 2 +- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/docs/src/mutations-apollo-react/api.md b/docs/src/mutations-apollo-react/api.md index b0d5c88..7041b53 100644 --- a/docs/src/mutations-apollo-react/api.md +++ b/docs/src/mutations-apollo-react/api.md @@ -1 +1,43 @@ # API Reference + +## Hooks + +### `useMutation` + +Wrapper of Apollo's useMutation React hook. Takes the same parameters but returns an additional "state" value. Native Apollo's `useMutation` hook full reference can be found [here](https://www.apollographql.com/docs/react/api/react-hooks/#options-2). However parameters and results worth mentioning because of specific behavior for this wrapper implementation are mentioned below: + +* **Parameters** + + * **Options**: + +| Property | Type | Description | +|----------|:-------------:|:------| +| `client` | `ApolloClient` | `ApolloClient` instance. If passed, this will be the GraphQL client used to query the graph-node for results after a succesful transaction in the resolver function of the mutation invoked. If not passed, a default non-Apollo GraphQL client implementation will be used | + +* **Result** + + * **MutationResult**: + +| Property | Type | Description | +|----------|:-------------:|:------| +| `state` | `MutationStates` | `MutationStates` object passed through resolver's `StateUpdater` object's `dispatch` method (see Mutations package API docs). It contains a key for each mutation called in the mutation query. In the case where the same mutation gets called more than once in the same query, the mutation's name changes to the mutation name followed by an underscore and a consecutive integer. Example: 'addMutation_2'. This object contains the latest mutation state and an `EventLog` with all the events, and their payloads, dispatched so far | + +## Components + +### `Mutation` + +Wrapper of Apollo's Mutation React component. Takes the same props but the renderProps function passed as a child to it, receives an additional "state" object as parameter. Native Apollo's `Mutation` component full reference can be found [here](https://www.apollographql.com/docs/react/api/react-components/#mutation). However props and renderProps parameters worth mentioning because of specific behavior for this wrapper implementation are mentioned below: + +* **Props** + +| Property | Type | Description | +|----------|:-------------:|:------| +| `client` | `ApolloClient` | `ApolloClient` instance. If passed, this will be the GraphQL client used to query the graph-node for results after a succesful transaction in the resolver function of the mutation invoked. If not passed, a default non-Apollo GraphQL client implementation will be used | + +* **RenderProps function parameters** + + * **MutationResult**: + +| Property | Type | Description | +|----------|:-------------:|:------| +| `state` | `MutationStates` | `MutationStates` object passed through resolver's `StateUpdater` object's `dispatch` method (see Mutations package API docs). It contains a key for each mutation called in the mutation query. In the case where the same mutation gets called more than once in the same query, the mutation's name changes to the mutation name followed by an underscore and a consecutive integer. Example: 'addMutation_2'. This object contains the latest mutation state and an `EventLog` with all the events, and their payloads, dispatched so far | \ No newline at end of file diff --git a/docs/src/mutations/api.md b/docs/src/mutations/api.md index ea016e8..9a4c42b 100644 --- a/docs/src/mutations/api.md +++ b/docs/src/mutations/api.md @@ -24,7 +24,7 @@ | Argument | Type | Description | |----------|:-------------:|:------| -| `current` | `MutationState` | Inmutable copy of current state | +| `current` | `MutationStates` | Inmutable copy of current state | #### Methods From 9a0d56c68c0cad84aad66de393d97b87f2781d30 Mon Sep 17 00:00:00 2001 From: Nestor Amesty Date: Fri, 28 Feb 2020 16:08:43 +0100 Subject: [PATCH 4/7] [WIP] User guides --- docs/src/mutations-apollo-react/user-guide.md | 129 ++++++++++++++++++ docs/src/mutations/user-guide.md | 71 ++++++++++ 2 files changed, 200 insertions(+) diff --git a/docs/src/mutations-apollo-react/user-guide.md b/docs/src/mutations-apollo-react/user-guide.md index cd3d452..2c9cdae 100644 --- a/docs/src/mutations-apollo-react/user-guide.md +++ b/docs/src/mutations-apollo-react/user-guide.md @@ -1 +1,130 @@ # User Guide + +## useMutation hook + +Developers can just import `useMutation` from the 'mutations-apollo-react' package, and consuming the state object can be done like so: + +```ts +import { useMutation } from '@graphprotocol/mutations-apollo-react' + +const MY_MUTATION = gql` + mutation MyMutation() { + myMutation + } +`; + +const Component = () => { + const [exec, { data, loading, state }] = useMutation(MY_MUTATION) + + return ( + {loading ? +
state.myMutation.progress
: +
data.value
+ } + ) +} +``` + +In order to access extended state properties, we can templatize the state object using the `State` interface we exported from the mutations module: +```ts +import { Mutation } from '@graphprotocol/mutations-apollo-react' +import { MutationState } from '@graphprotocol/mutations' +import { State } from 'my-mutations' + +const MY_MUTATION = gql` + mutation MyMutation() { + myMutation + } +`; + +const Component = () => { + const [exec, { data, loading, state }] = useMutation(MY_MUTATION) + + return ( + {loading ? +
state.myMutation.myValue
: +
data.value
+ } + + + {(exec, { data, loading, state: MutationState }) => ( + ... + )} + + ) +} +``` + +## Mutation component + +Developers can just import `Mutation` component from the 'mutations-apollo-react' package, and consuming the state object can be done like so: + +```ts +import { Mutation } from '@graphprotocol/mutations-apollo-react' + +const MY_MUTATION = gql` + mutation MyMutation() { + myMutation + } +`; + +const Component = () => { + return ( + + {(exec, { data, loading, state }) => ( + ... + )} + + ) +} +``` + +In order to access extended state properties, we can templatize the state object using the `State` interface we exported from the mutations module: +```ts +import { Mutation } from '@graphprotocol/mutations-apollo-react' +import { MutationState } from '@graphprotocol/mutations' +import { State } from 'my-mutations' + +const MY_MUTATION = gql` + mutation MyMutation() { + myMutation + } +`; + +const Component = () => { + + return ( + + {(exec, { data, loading, state: MutationState }) => ( + ... + )} + + ) +} +``` + +## Additional notes + +The `state` object gets refreshed every time the component re-renders, and any updates received by it will also trigger a component re-render. The state's structure is an object where each of its keys is the name of a mutation called in the mutation query. Each key has a `MutationState` object as value. In the case the same mutation is called more than once in the same mutation query, then each duplicated state key will be renamed to ${mutationName}_${n} where n is a consecutive number that corresponds to the nth time the mutation was called. For example: +```ts +const MY_MUTATION_TWICE = gql` + mutation MyMutation() { + myMutation + myMutation + } +`; + +const Component = () => { + const [exec, { data, loading, state }] = useMutation(MY_MUTATION_TWICE) + + return ( + {loading ? + <> +
state.myMutation_1.myValue
+
state.myMutation_2.myValue
+ : +
data.value
+ } + ) +} +``` \ No newline at end of file diff --git a/docs/src/mutations/user-guide.md b/docs/src/mutations/user-guide.md index cd3d452..43da309 100644 --- a/docs/src/mutations/user-guide.md +++ b/docs/src/mutations/user-guide.md @@ -1 +1,72 @@ # User Guide + +## 1. Instantiating The `Mutations` Module + +In order to use the mutation resolvers module, we must first instantiate it using `createMutations`: +`App.tsx` +```ts +import myMutations from 'my-mutations' +import { createMutations } from '@graphprotocol/mutations' + +const mutations = createMutations({ + mutations: gravatarMutations, + subgraph: "example", + node: "http://localhost:8080", + config: { + // Configuration arguments can be defined as values, + // or functions that return values + ethereum: async () => { + const { ethereum } = (window as any) + + if (!ethereum) { + throw Error("Please install metamask") + } + + await ethereum.enable() + return (window as any).web3.currentProvider + }, + ipfs: "http://localhost:5001" + } +}) + +... +``` + +## 2. Creating an Apollo Link & Client + +For ease of use, we'll create an Apollo Client that can be used inside of a React application to easily integrate our mutation queries with our UI. +First, we must wrap our `mutations` instance with an Apollo Link: +```ts +import { createMutationsLink } from `@graphprotocol/mutations` + +const mutationLink = createMutationsLink({ mutations }) +``` + +And use the link within an Apollo Client. We'll first create a "root" link that splits our `query` and `mutation` queries. This way our data queries will be sent to the subgraph, and our mutation queries will be sent to our local resolvers: + +```tsx +import { createHttpLink } from 'apollo-link-http' +import { split } from 'apollo-link' +import ApolloClient from 'apollo-client' +import { InMemoryCache } from 'apollo-cache-inmemory' + +const queryLink = createHttpLink({ uri: `http://localhost:8080/subgraphs/name/example` }) + +// Combine (split) the query & mutation links +const link = split( + ({ query }) => { + const node = getMainDefinition(query) + return node.kind === "OperationDefinition" && node.operation === "mutation" + }, + mutationLink, + queryLink +) + +// Create the Apollo Client +const client = new ApolloClient({ + link, + cache: new InMemoryCache() +}) +``` + +At this point, you have a fully functional ApolloClient that can be used to send `query` and `mutation` queries, the same way you would within a web2 GraphQL application. \ No newline at end of file From cb38be92242a34f9a1e76ff2cb0fb5544c8af2a0 Mon Sep 17 00:00:00 2001 From: dOrgJelli Date: Mon, 2 Mar 2020 00:38:56 -0500 Subject: [PATCH 5/7] doc updates --- docs/src/SUMMARY.md | 4 +- docs/src/introduction.md | 1 + docs/src/mutations-apollo-react/api.md | 8 + docs/src/mutations-apollo-react/index.md | 3 +- docs/src/mutations-apollo-react/user-guide.md | 41 +++- docs/src/mutations/api.md | 210 +++++------------- docs/src/mutations/index.md | 9 +- docs/src/mutations/user-guide.md | 41 +--- yarn.lock | 105 ++++++++- 9 files changed, 212 insertions(+), 210 deletions(-) diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 606d781..a346e38 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -1,9 +1,9 @@ # Summary - [Introduction](./introduction.md) -- [Mutations](./mutations/index.md) +- [@graphprotocol/mutations](./mutations/index.md) - [User Guide](./mutations/user-guide.md) - [API Reference](./mutations/api.md) -- [Mutations (Apollo & React)](./mutations-apollo-react/index.md) +- [@graphprotocol/mutations-apollo-react](./mutations-apollo-react/index.md) - [User Guide](./mutations-apollo-react/user-guide.md) - [API Reference](./mutations-apollo-react/api.md) diff --git a/docs/src/introduction.md b/docs/src/introduction.md index e10b99d..67374e6 100644 --- a/docs/src/introduction.md +++ b/docs/src/introduction.md @@ -1 +1,2 @@ # Introduction +For a complete overview on how to add mutations to a subgraph, please read more [here](https://thegraph.com/docs/mutations). diff --git a/docs/src/mutations-apollo-react/api.md b/docs/src/mutations-apollo-react/api.md index 7041b53..79b625f 100644 --- a/docs/src/mutations-apollo-react/api.md +++ b/docs/src/mutations-apollo-react/api.md @@ -1,5 +1,13 @@ # API Reference +## Functions + +### `createMutationsLink` + +| Argument | Type | Description | +|----------|:-------------:|:------| +| `mutations` | `Mutations` | Mutations object, returned from `createMutations` function | + ## Hooks ### `useMutation` diff --git a/docs/src/mutations-apollo-react/index.md b/docs/src/mutations-apollo-react/index.md index babcd08..ce99b69 100644 --- a/docs/src/mutations-apollo-react/index.md +++ b/docs/src/mutations-apollo-react/index.md @@ -1 +1,2 @@ -# Mutations (Apollo & React) +# @graphprotocol/mutations-apollo-react +Utility wrappers around commonly used Apollo & React functions, hooks, and components. \ No newline at end of file diff --git a/docs/src/mutations-apollo-react/user-guide.md b/docs/src/mutations-apollo-react/user-guide.md index 2c9cdae..fca71cf 100644 --- a/docs/src/mutations-apollo-react/user-guide.md +++ b/docs/src/mutations-apollo-react/user-guide.md @@ -1,5 +1,44 @@ # User Guide +## Creating an Apollo Link & Client + +For ease of use, we'll create an Apollo Client that can be used inside of a React application to easily integrate our mutation queries with our UI. +First, we must wrap our `mutations` instance with an Apollo Link: +```ts +import { createMutationsLink } from `@graphprotocol/mutations` + +const mutationLink = createMutationsLink({ mutations }) +``` + +And use the link within an Apollo Client. We'll first create a "root" link that splits our `query` and `mutation` queries. This way our data queries will be sent to the subgraph, and our mutation queries will be sent to our local resolvers: + +```tsx +import { createHttpLink } from 'apollo-link-http' +import { split } from 'apollo-link' +import ApolloClient from 'apollo-client' +import { InMemoryCache } from 'apollo-cache-inmemory' + +const queryLink = createHttpLink({ uri: `http://localhost:8080/subgraphs/name/example` }) + +// Combine (split) the query & mutation links +const link = split( + ({ query }) => { + const node = getMainDefinition(query) + return node.kind === "OperationDefinition" && node.operation === "mutation" + }, + mutationLink, + queryLink +) + +// Create the Apollo Client +const client = new ApolloClient({ + link, + cache: new InMemoryCache() +}) +``` + +At this point, you have a fully functional ApolloClient that can be used to send `query` and `mutation` queries, the same way you would within a web2 GraphQL application. + ## useMutation hook Developers can just import `useMutation` from the 'mutations-apollo-react' package, and consuming the state object can be done like so: @@ -120,8 +159,8 @@ const Component = () => { return ( {loading ? <> +
state.myMutation.myValue
state.myMutation_1.myValue
-
state.myMutation_2.myValue
:
data.value
} diff --git a/docs/src/mutations/api.md b/docs/src/mutations/api.md index 9a4c42b..6a1482d 100644 --- a/docs/src/mutations/api.md +++ b/docs/src/mutations/api.md @@ -1,178 +1,68 @@ # API Reference -## Functions +## function `createMutations(...)` -### `createMutations` +| Argument | Type | Description | +|------------|:--------------:|:-------------| +| `options` | **`CreateMutationsOptions`** | Options | -| Argument | Type | Description | -|----------|:-------------:|:------| -| `mutations` | JS Module | Built mutations module. This module should export resolvers object, config object and optionally stateBuilder object | -| `subgraph` | string | Name of the subgraph | -| `config` | Object | Object whose properties must match the resolvers module's config object 1:1 | +| Return Type | Description | +|-------------|-------------| +| **`Mutations`** | Mutations that can be executed & re-configured | -### `createMutationsLink` +## interface **`CreateMutationsOptions`** -| Argument | Type | Description | -|----------|:-------------:|:------| -| `mutations` | `Mutations` | Mutations object, returned from `createMutations` function | +| Property | Type | Description | +|----------|:--------------:|:-------------| +| `mutations` | **`MutationsModule`** | Mutations module | +| `subgraph` | **`string`** | Name of the subgraph | +| `node` | **`string`** | URL of a graph-node | +| `config` | **`ConfigArguments`** | All configuration arguments required by the `mutations` module | +| (optional) `mutationExecutor` | **`MutationExecutor`** | Function that will execute the mutations being queried | +| (optional) `queryExecutor` | **`QueryExecutor`** | Function that will execute any query requests coming from the mutation resolvers | -## Objects +## interface **`Mutations`** -### `stateUpdater` +| Method | Arguments | Return Type | Description | +|--------|-----------|-------------|-------------| +| `execute` | `query: `**`MutationQuery`** | **`Promise`** | Execute a GraphQL mutation query | +| `configure` | `config: `**`ConfigArguments`** | **`Promise`** | Re-configure the mutations module | -#### Properties +## interface **`MutationQuery`** +| Property | Type | Description | +|----------|------|-------------| +| `query` | **`DocumentNode`** | GraphQL DocumentNode | +| (optional) `variables` | **`Record`** | Variables to pass into the mutation resolver | +| (optional) `extensions` | **`Record`** | GraphQL type system extensions | +| (optional) `stateSubject` | **`MutationStatesSubject`** | Mutation state observer (rxjs Subject) | -| Argument | Type | Description | -|----------|:-------------:|:------| -| `current` | `MutationStates` | Inmutable copy of current state | +| Method | Arguments | Return Type | Description | +|--------|-----------|-------------|-------------| +| `setContext` | `context: `**`any`** | **`any`** | Set the context, and return the updated context | +| `getContext` | | **`any`** | Get the context | -#### Methods +## interface **`MutationResult`** -* `dispatch`: **async** method used to emit/dispatch events to the UI, in real time. It modifies the `state` object, appending the dispatched event to its `events` array property. +Equivalent to GraphQL's [**`ExecutionResult`**](https://graphql.org/graphql-js/execution/) -| Argument | Type | Description | -|----------|:-------------:|:------| -| `eventName` | string | Name of the event to dispatch, it must match the key used for this event in the custom `EventMap` or `CoreEvents` interface. | -| `payload` | Object | Payload of the event to dispatch, its type must match the interface used for this event in the custom `EventMap` or `CoreEvents` interface | +## function type **`MutationExecutor`** -**Returns** a void promise. +| Argument | Type | Description | +|----------|------|-------------| +| `query` | **`MutationQuery`** | A GraphQL mutation query | +| `schema` | **`GraphQLSchema`** | A built GraphQL schema | -## Interfaces and types +| Return Type | Description | +|-------------|-------------| +| **`Promise`** | The result of the mutation | -### `CoreEvents` +## function type **`QueryExecutor`** -Type that has pre-defined events, and their payload types mapped, that can be dispatched through the `stateUpdater` object's `dispatch` method. +| Argument | Type | Description | +|----------|------|-------------| +| `query` | **`Query`** | A GraphQL query | +| `uri` | **`string`** | GraphQL server URI | -```ts - -type CoreEvents = { - TRANSACTION_CREATED: TransactionCreatedEvent - TRANSACTION_COMPLETED: TransactionCompletedEvent - TRANSACTION_ERROR: TransactionErrorEvent - PROGRESS_UPDATE: ProgressUpdateEvent -} - -``` - -### `CoreState` - -Interface that partially defines the state data of a mutation, that will be dispatched through the `stateUpdater` object's `dispatch` method in a `MutationStates` object. Does not include the `events` state property, which is an array containing all dispatched events and their payloads, and which is also dispatched in the same state object. - -```ts - -interface CoreState { - uuid: string - progress: number -} - -``` - -It contains the following properties: - -| Property | Description | - |----------|:-------------| - | `uuid` | Unique identifier for each event. It is randomly and automatically generated. | - | `progress` | Number between 0 and 1 that indicates the progress the invoked mutation has. It is modified through the `PROGRESS_UPDATE` core event dispatch | - - -### `Event` - -It contains - -```ts - -interface Event { - name: keyof MutationEvents - payload: EventPayload -} - -``` - -It contains the following properties: - -| Property | Description | - |----------|:-------------| - | `name` | Name of the event. It matches the key used to name this event either in the `CoreEvents` type or a custom `EventMap` type. | - | `payload` | Payload of the event dispatched. Its type matches the type mapped to the event's name either in the `CoreEvents` type or a custom `EventMap` type. | - -### `EventMap` - -Optional custom extension of the `CoreEvents` type. It defines custom events, and their payload types, that can be dispatched through the `stateUpdater` object's `dispatch` method. - -Example: - -```ts - -type EventMap = { - CUSTOM_EVENT: CustomEvent -} - -``` - -### `MutationState` - -Interface that fully defines the state data of a mutation that will be dispatched through the `stateUpdater` object's `dispatch` method, in a `MutationStates` object. It contains an `events` key which is an array that contains all dispatched events and their payloads, along with the properties defined in the `CoreState` interface and the optionally custom `TState` interface. - -```ts - -type MutationState< - TState = CoreState, - TEventMap extends EventTypeMap = CoreEvents -> = { events: EventLog } & CoreState & TState - -``` - -### `MutationStates` - -```ts - -type MutationStates< - TState = CoreState, - TEventMap extends EventTypeMap = CoreEvents -> = { - [mutation: string]: MutationState -} - -``` - -A type that defines the actual object passed to the UI, through the `stateUpdater` object's `dispatch` method. It is an object that has each `MutationState`'s state data mapped to its mutation name. In the case where the same mutation gets called more than once in the same query, the mutation's name changes to the mutation name followed by an underscore and a consecutive integer. Example: 'addMutation_2'. - -### `StateBuilder` - -**Optional** object that implements the `StateBuilder` interface. It is implemented to add an initial values to custom state properties and custom reducers for custom events. It can implement a catch-all reducer. If it exists it should be exported in the resolvers module. - -```ts - -interface StateBuilder { - getInitialState(uuid: string): TState - reducers?: { - [TEvent in keyof MutationEvents]?: ( - state: MutationState, - payload: InferEventPayload, - ) => MaybeAsync>> - } - reducer?: ( - state: MutationState, - event: Event, - ) => MaybeAsync>> -} - -``` - -It implements the following methods and properties: - -* `getInitialState`: method that returns a `Partial`, where `TState` is a custom defined `MutationState` interface. -* `reducers`: object that contains the reducers for custom events. Each event reducer is a key in this object, that must match the event's key in the `EventMap` interface, and its value is a function with the following arguments: - - | Argument | Type | Description | - |----------|:-------------:|:------| - | `state` | `MutationState` | Inmutable copy of current state | - | `payload` | `MutationState` | Payload of the event to dispatch, its type must match the interface used for this event in the custom `EventMap` or `CoreEvents` interface | - - Each of these reducers **must return** a `Partial` -* `reducer` **(Optional)**: catch-all reducer. It runs after any event is dispatched. It receives the following arguments: - - | Argument | Type | Description | - |----------|:-------------:|:------| - | `state` | `MutationState` | Inmutable copy of current state | - | `event` | `Event` | Object with dispatched event's name and payload. The name matches each event's key used in the `EventMap` or `CoreEvents` interface | \ No newline at end of file +| Return Type | Description | +|-------------|-------------| +| **`Promise`** | The result of the query | diff --git a/docs/src/mutations/index.md b/docs/src/mutations/index.md index 030abf0..a6e215b 100644 --- a/docs/src/mutations/index.md +++ b/docs/src/mutations/index.md @@ -1 +1,8 @@ -# Mutations +# @graphprotocol/mutations +Javascript API used within mutation modules & dApps. This API includes functions, classes, and interfaces to support: + * Mutation Resolvers + * Mutation Context + * Mutation State + * Mutation Configuration + * Instantiation + * Executing diff --git a/docs/src/mutations/user-guide.md b/docs/src/mutations/user-guide.md index 43da309..82f4278 100644 --- a/docs/src/mutations/user-guide.md +++ b/docs/src/mutations/user-guide.md @@ -1,6 +1,6 @@ # User Guide -## 1. Instantiating The `Mutations` Module +## Instantiating The `Mutations` Module In order to use the mutation resolvers module, we must first instantiate it using `createMutations`: `App.tsx` @@ -30,43 +30,6 @@ const mutations = createMutations({ }) ... -``` - -## 2. Creating an Apollo Link & Client - -For ease of use, we'll create an Apollo Client that can be used inside of a React application to easily integrate our mutation queries with our UI. -First, we must wrap our `mutations` instance with an Apollo Link: -```ts -import { createMutationsLink } from `@graphprotocol/mutations` - -const mutationLink = createMutationsLink({ mutations }) -``` - -And use the link within an Apollo Client. We'll first create a "root" link that splits our `query` and `mutation` queries. This way our data queries will be sent to the subgraph, and our mutation queries will be sent to our local resolvers: - -```tsx -import { createHttpLink } from 'apollo-link-http' -import { split } from 'apollo-link' -import ApolloClient from 'apollo-client' -import { InMemoryCache } from 'apollo-cache-inmemory' - -const queryLink = createHttpLink({ uri: `http://localhost:8080/subgraphs/name/example` }) - -// Combine (split) the query & mutation links -const link = split( - ({ query }) => { - const node = getMainDefinition(query) - return node.kind === "OperationDefinition" && node.operation === "mutation" - }, - mutationLink, - queryLink -) - -// Create the Apollo Client -const client = new ApolloClient({ - link, - cache: new InMemoryCache() -}) ``` -At this point, you have a fully functional ApolloClient that can be used to send `query` and `mutation` queries, the same way you would within a web2 GraphQL application. \ No newline at end of file +TODO execute, listen to state updates, configure, define resolvers, define config, define state builder + events diff --git a/yarn.lock b/yarn.lock index 5f75786..b9ea6fa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1338,7 +1338,7 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.149.tgz#1342d63d948c6062838fbf961012f74d4e638440" integrity sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ== -"@types/minimatch@*": +"@types/minimatch@*", "@types/minimatch@3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== @@ -1859,6 +1859,13 @@ babel-preset-jest@^25.1.0: "@babel/plugin-syntax-object-rest-spread" "^7.0.0" babel-plugin-jest-hoist "^25.1.0" +backbone@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/backbone/-/backbone-1.4.0.tgz#54db4de9df7c3811c3f032f34749a4cd27f3bd12" + integrity sha512-RLmDrRXkVdouTg38jcgHhyQ/2zjg7a8E6sz2zxfz21Hh17xDJYUHBZimVIt5fUyS8vbfpeSmTL3gUjTEvUV3qQ== + dependencies: + underscore ">=1.8.3" + balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" @@ -3429,7 +3436,7 @@ glob-to-regexp@^0.3.0: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= -glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: +glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -3491,7 +3498,7 @@ growly@^1.3.0: resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= -handlebars@^4.4.0: +handlebars@^4.4.0, handlebars@^4.7.2, handlebars@^4.7.3: version "4.7.3" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.3.tgz#8ece2797826886cf8082d1726ff21d2a022550ee" integrity sha512-SRGwSYuNfx8DwHD/6InAPzD6RgeruWLT+B8e8a7gGs8FWgHzlExpTFMEq2IA6QpAfOClpKHy6+8IqTjeBCu6Kg== @@ -3573,6 +3580,11 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +highlight.js@^9.17.1: + version "9.18.1" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.18.1.tgz#ed21aa001fe6252bb10a3d76d47573c6539fe13c" + integrity sha512-OrVKYz70LHsnCgmbXctv/bfuvntIKDz177h0Co37DQ5jamGZLVmoCVMtjMtNZY3X9DrCcKfklHPNeA0uPZhSJg== + hosted-git-info@^2.1.4, hosted-git-info@^2.7.1: version "2.8.5" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.5.tgz#759cfcf2c4d156ade59b0b2dfabddc42a6b9c70c" @@ -3772,6 +3784,11 @@ inquirer@^6.2.0: strip-ansi "^5.1.0" through "^2.3.6" +interpret@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" + integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== + ip-regex@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" @@ -4468,6 +4485,11 @@ jest@^25.1.0: import-local "^3.0.2" jest-cli "^25.1.0" +jquery@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2" + integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -4797,6 +4819,11 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" +lunr@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.8.tgz#a8b89c31f30b5a044b97d2d28e2da191b6ba2072" + integrity sha512-oxMeX/Y35PNFuZoHp+jUj5OSEmLCaIH4KTFJh7a93cHBoFmpw2IoPs22VIz7vyO2YUnx2Tn9dzIwO2P/4quIRg== + macos-release@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.3.0.tgz#eb1930b036c0800adebccd5f17bc4c12de8bb71f" @@ -4875,6 +4902,11 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +marked@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/marked/-/marked-0.8.0.tgz#ec5c0c9b93878dc52dd54be8d0e524097bd81a99" + integrity sha512-MyUe+T/Pw4TZufHkzAfDj6HarCBWia2y27/bhuYkTaiUnfDYFnCP3KUN+9oM7Wi6JA2rymtVYbQu3spE0GCmxQ== + meow@^3.3.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" @@ -4980,7 +5012,7 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -minimatch@^3.0.4: +minimatch@^3.0.0, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -5840,6 +5872,11 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +progress@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" @@ -6120,6 +6157,13 @@ realpath-native@^1.1.0: dependencies: util.promisify "^1.0.0" +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + integrity sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q= + dependencies: + resolve "^1.1.6" + redent@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" @@ -6262,7 +6306,7 @@ resolve@1.1.7: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@1.x, resolve@^1.10.0, resolve@^1.3.2: +resolve@1.x, resolve@^1.1.6, resolve@^1.10.0, resolve@^1.3.2: version "1.15.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== @@ -6448,6 +6492,15 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +shelljs@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.3.tgz#a7f3319520ebf09ee81275b2368adb286659b097" + integrity sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A== + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + shellwords@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" @@ -7164,7 +7217,42 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^3.5.2: +typedoc-default-themes@^0.7.2: + version "0.7.2" + resolved "https://registry.yarnpkg.com/typedoc-default-themes/-/typedoc-default-themes-0.7.2.tgz#1e9896f920b58e6da0bba9d7e643738d02405a5a" + integrity sha512-fiFKlFO6VTqjcno8w6WpTsbCgXmfPHVjnLfYkmByZE7moaz+E2DSpAT+oHtDHv7E0BM5kAhPrHJELP2J2Y2T9A== + dependencies: + backbone "^1.4.0" + jquery "^3.4.1" + lunr "^2.3.8" + underscore "^1.9.1" + +typedoc-plugin-markdown@^2.2.17: + version "2.2.17" + resolved "https://registry.yarnpkg.com/typedoc-plugin-markdown/-/typedoc-plugin-markdown-2.2.17.tgz#aaef7420e8268170e62c764f43740e10f842548d" + integrity sha512-eE6cTeqsZIbjur6RG91Lhx1vTwjR49OHwVPRlmsxY3dthS4FNRL8sHxT5Y9pkosBwv1kSmNGQEPHjMYy1Ag6DQ== + dependencies: + fs-extra "^8.1.0" + handlebars "^4.7.3" + +typedoc@^0.16.11: + version "0.16.11" + resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.16.11.tgz#95f862c6eba78533edc9af7096d2295b718eddc1" + integrity sha512-YEa5i0/n0yYmLJISJ5+po6seYfJQJ5lQYcHCPF9ffTF92DB/TAZO/QrazX5skPHNPtmlIht5FdTXCM2kC7jQFQ== + dependencies: + "@types/minimatch" "3.0.3" + fs-extra "^8.1.0" + handlebars "^4.7.2" + highlight.js "^9.17.1" + lodash "^4.17.15" + marked "^0.8.0" + minimatch "^3.0.0" + progress "^2.0.3" + shelljs "^0.8.3" + typedoc-default-themes "^0.7.2" + typescript "3.7.x" + +typescript@3.7.x, typescript@^3.5.2: version "3.7.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae" integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw== @@ -7187,6 +7275,11 @@ umask@^1.1.0: resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d" integrity sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0= +underscore@>=1.8.3, underscore@^1.9.1: + version "1.9.2" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.2.tgz#0c8d6f536d6f378a5af264a72f7bec50feb7cf2f" + integrity sha512-D39qtimx0c1fI3ya1Lnhk3E9nONswSKhnffBI0gME9C99fYOkNi04xs8K6pePLhvl1frbDemkaBQ5ikWllR2HQ== + union-value@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" From 67411ae346956522b411604bc818909d5b44e940 Mon Sep 17 00:00:00 2001 From: dOrgJelli Date: Mon, 2 Mar 2020 12:57:36 -0500 Subject: [PATCH 6/7] @graphprotocol/mutations first draft --- docs/src/mutations/user-guide.md | 278 ++++++++++++++++++++++++++++++- 1 file changed, 271 insertions(+), 7 deletions(-) diff --git a/docs/src/mutations/user-guide.md b/docs/src/mutations/user-guide.md index 82f4278..d5a12bc 100644 --- a/docs/src/mutations/user-guide.md +++ b/docs/src/mutations/user-guide.md @@ -1,35 +1,299 @@ # User Guide +# dApp Developer ## Instantiating The `Mutations` Module -In order to use the mutation resolvers module, we must first instantiate it using `createMutations`: -`App.tsx` +In order to use the mutations module, we must first instantiate it using `createMutations`: ```ts -import myMutations from 'my-mutations' +import exampleMutations from 'example-mutations' import { createMutations } from '@graphprotocol/mutations' const mutations = createMutations({ - mutations: gravatarMutations, + mutations: exampleMutations, subgraph: "example", node: "http://localhost:8080", config: { // Configuration arguments can be defined as values, // or functions that return values ethereum: async () => { - const { ethereum } = (window as any) + const { ethereum } = window if (!ethereum) { throw Error("Please install metamask") } await ethereum.enable() - return (window as any).web3.currentProvider + return window.web3.currentProvider }, ipfs: "http://localhost:5001" } }) +``` + +## Execute Mutations +`NOTE: If you're using Apollo Client, please see the @graphprotocol/mutations-apollo-react docs.` + +Once the mutations are instantiated with `createMutations(...)`, we can now call the `execute(...)` method to execute a mutation like so: +```typescript +import gql from 'graphql-tag' + +let context = { } + +const { data } = await mutations.execute({ + query: gql` + mutation Example($var: String!) { + example(var: $var) + } + `, + setContext: (newContext: any) => { + context = newContext + return context + }, + getContext: () => context +}) + +console.log(data.example) // example's return value +``` + +## Get Mutation State Updates +If the mutations module dispatches state updates, you can subscribe to them like so: +```typescript +import { + MutationStatesSubject, + MutationStates +} from '@graphprotocol/mutations' + +// Custom mutation state (optional) +import { State, EventMap } from 'example-mutations` + +const subject = new MutationStatesSubject({ }) + +// Called every time there's a state update +subject.subscribe((state: MutationStates) => { + // All state values can be accessed like so: + state.example.progress + + // Including any custom state variables defined + // in 'example-mutations' + state.example.customValue +}) + +let context = { } + +const { data } = await mutations.execute({ + query: gql` + mutation Example($var: String!) { + example(var: $var) + } + `, + stateSubject: subject, + setContext: (newContext: any) => { + context = newContext + return context + }, + getContext: () => context +}) +``` + +## Re-Configure Mutations Module +If you'd like to re-configure the mutations module, you can do so like so: +```typescript +await mutations.configure({ + ethereum: ..., + ipfs: "..." +}) +``` + +# Subgraph Developer +For more in-depth information on how to add mutations to your subgraph's project, please read more [here](https://thegraph.com/docs/mutations). In the following sections we'll only cover the parts that involve the `@graphprotocol/mutations` API. + +## Implement Mutation Resolvers +```typescript +import { + MutationContext, + MutationResolvers +} from '@graphprotocol/mutations' + +// Define Config (see sections below) +... + +// Define Custom State (see sections below) +... + +type Context = MutationContext + +const resolvers: MutationResolvers = { + Mutation: { + example: (_: any, variables: any, context: Context) => { + const { var } = variables + ... + } + } +} + +// Ensure the resolvers are exported +// as apart of the default export +export default { + resolvers, + ... +} +``` + +## Mutation Module Configuration +```typescript +import { AsyncSendable, Web3Provider } from 'ethers/providers' +import ipfsHttpClient from 'ipfs-http-client' + +const config = { + ethereum: (provider: AsyncSendable): Web3Provider => { + return new Web3Provider(provider) + }, + ipfs: (endpoint: string) => { + return ipfsHttpClient(endpoint) + }, +} + +type Config = typeof config + +... + +// Ensure the config is exported +// as part of the default export +export default { + ... + config, + ... +} + +// Additionally, we export the config's type +export { Config } +``` + +## Mutation State +```typescript +import { + EventPayload, + MutationState, + StateBuilder, +} from '@graphprotocol/mutations' + +interface State { + customValue: string +} + +interface CustomEvent extends EventPayload { + value: string +} + +type EventMap = { + CUSTOM_EVENT: CustomEvent +} + +const stateBuilder: StateBuilder = { + getInitialState(): State { + return { + customValue: '' + } + }, + reducers: { + CUSTOM_EVENT: async (state: MutationState, payload: CustomEvent) => { + return { + customValue: payload.value + } + } + } +} ... + +// Ensure the stateBuilder is exported +// as part of the default export +export default { + ... + stateBuilder +} + +// Additionally, we export the state & event map types, +// along with all custom event types +export { State, EventMap, CustomEvent } ``` -TODO execute, listen to state updates, configure, define resolvers, define config, define state builder + events +## All Together +Putting all the pieces together: +```typescript +import { + EventPayload, + MutationContext, + MutationResolvers, + MutationState, + StateBuilder +} from '@graphprotocol/mutations' + +import { + AsyncSendable, + Web3Provider +} from 'ethers/providers' +import ipfsHttpClient from 'ipfs-http-client' + +interface State { + customValue: string +} + +interface CustomEvent extends EventPayload { + value: string +} + +type EventMap = { + CUSTOM_EVENT: CustomEvent +} + +const stateBuilder: StateBuilder = { + getInitialState(): State { + return { + customValue: '' + } + }, + reducers: { + CUSTOM_EVENT: async (state: MutationState, payload: CustomEvent) => { + return { + customValue: payload.value + } + } + } +} + +const config = { + ethereum: (provider: AsyncSendable): Web3Provider => { + return new Web3Provider(provider) + }, + ipfs: (endpoint: string) => { + return ipfsHttpClient(endpoint) + }, +} + +type Config = typeof config + +type Context = MutationContext + +const resolvers: MutationResolvers = { + Mutation: { + example: (_: any, variables: any, context: Context) => { + const { var } = variables + ... + } + } +} + +export default { + config, + resolvers, + stateBuilder +} + +export { + Config, + CustomEvent, + EventMap, + State +} +``` From 14db278f35be44afc7616030e16bafd52ab27d8a Mon Sep 17 00:00:00 2001 From: dOrgJelli Date: Mon, 2 Mar 2020 23:39:21 -0500 Subject: [PATCH 7/7] final edits --- docs/src/mutations-apollo-react/api.md | 58 +++++++------------ docs/src/mutations-apollo-react/user-guide.md | 18 ++---- 2 files changed, 26 insertions(+), 50 deletions(-) diff --git a/docs/src/mutations-apollo-react/api.md b/docs/src/mutations-apollo-react/api.md index 79b625f..bb4458c 100644 --- a/docs/src/mutations-apollo-react/api.md +++ b/docs/src/mutations-apollo-react/api.md @@ -1,51 +1,33 @@ # API Reference -## Functions - -### `createMutationsLink` +## function `createMutationsLink(...)` | Argument | Type | Description | |----------|:-------------:|:------| -| `mutations` | `Mutations` | Mutations object, returned from `createMutations` function | - -## Hooks - -### `useMutation` - -Wrapper of Apollo's useMutation React hook. Takes the same parameters but returns an additional "state" value. Native Apollo's `useMutation` hook full reference can be found [here](https://www.apollographql.com/docs/react/api/react-hooks/#options-2). However parameters and results worth mentioning because of specific behavior for this wrapper implementation are mentioned below: - -* **Parameters** - - * **Options**: - -| Property | Type | Description | -|----------|:-------------:|:------| -| `client` | `ApolloClient` | `ApolloClient` instance. If passed, this will be the GraphQL client used to query the graph-node for results after a succesful transaction in the resolver function of the mutation invoked. If not passed, a default non-Apollo GraphQL client implementation will be used | - -* **Result** +| `options` | **`{ mutations: Mutations }`** | Mutations object that was returned from `createMutations(...)` | - * **MutationResult**: +| Return Type | Description | +|-------------|-------------| +| **`ApolloLink`** | Apollo link that executes `mutation` queries using the `mutations`' resolvers | -| Property | Type | Description | -|----------|:-------------:|:------| -| `state` | `MutationStates` | `MutationStates` object passed through resolver's `StateUpdater` object's `dispatch` method (see Mutations package API docs). It contains a key for each mutation called in the mutation query. In the case where the same mutation gets called more than once in the same query, the mutation's name changes to the mutation name followed by an underscore and a consecutive integer. Example: 'addMutation_2'. This object contains the latest mutation state and an `EventLog` with all the events, and their payloads, dispatched so far | +## function `useMutation(...)` -## Components +Wrapper around Apollo's `useMutation(...)` React hook. Native Apollo's `useMutation(...)` hook full reference can be found [here](https://www.apollographql.com/docs/react/api/react-hooks/#options-2). The only difference is that this wrapper returns an additional `state` value alongside `data`: -### `Mutation` +```typescript +const [exec, { data, state }] = useMutation(MY_MUTATION) -Wrapper of Apollo's Mutation React component. Takes the same props but the renderProps function passed as a child to it, receives an additional "state" object as parameter. Native Apollo's `Mutation` component full reference can be found [here](https://www.apollographql.com/docs/react/api/react-components/#mutation). However props and renderProps parameters worth mentioning because of specific behavior for this wrapper implementation are mentioned below: +state.myMutation.progress +``` -* **Props** +## React.Component `Mutation` -| Property | Type | Description | -|----------|:-------------:|:------| -| `client` | `ApolloClient` | `ApolloClient` instance. If passed, this will be the GraphQL client used to query the graph-node for results after a succesful transaction in the resolver function of the mutation invoked. If not passed, a default non-Apollo GraphQL client implementation will be used | +Wrapper around Apollo's `Mutation` JSX React Component. Native Apollo's `Mutation` Component full reference can be found [here](https://www.apollographql.com/docs/react/api/react-components/#mutation). The only difference is that this wrapper returns an additional `state` value alongside `data`: -* **RenderProps function parameters** - - * **MutationResult**: - -| Property | Type | Description | -|----------|:-------------:|:------| -| `state` | `MutationStates` | `MutationStates` object passed through resolver's `StateUpdater` object's `dispatch` method (see Mutations package API docs). It contains a key for each mutation called in the mutation query. In the case where the same mutation gets called more than once in the same query, the mutation's name changes to the mutation name followed by an underscore and a consecutive integer. Example: 'addMutation_2'. This object contains the latest mutation state and an `EventLog` with all the events, and their payloads, dispatched so far | \ No newline at end of file +```html + + {(exec, { data, state }) => ( + ... + )} + +``` diff --git a/docs/src/mutations-apollo-react/user-guide.md b/docs/src/mutations-apollo-react/user-guide.md index fca71cf..6a11eca 100644 --- a/docs/src/mutations-apollo-react/user-guide.md +++ b/docs/src/mutations-apollo-react/user-guide.md @@ -39,9 +39,9 @@ const client = new ApolloClient({ At this point, you have a fully functional ApolloClient that can be used to send `query` and `mutation` queries, the same way you would within a web2 GraphQL application. -## useMutation hook +## `useMutation(...)` hook -Developers can just import `useMutation` from the 'mutations-apollo-react' package, and consuming the state object can be done like so: +Developers can just import `useMutation` from the `@graphprotocol/mutations-apollo-react` package, and consuming the state object can be done like so: ```ts import { useMutation } from '@graphprotocol/mutations-apollo-react' @@ -58,7 +58,7 @@ const Component = () => { return ( {loading ?
state.myMutation.progress
: -
data.value
+
data.myMutation
} ) } @@ -82,14 +82,8 @@ const Component = () => { return ( {loading ?
state.myMutation.myValue
: -
data.value
+
data.myMutation
} - - - {(exec, { data, loading, state: MutationState }) => ( - ... - )} - ) } ``` @@ -162,8 +156,8 @@ const Component = () => {
state.myMutation.myValue
state.myMutation_1.myValue
: -
data.value
+
data.myMutation
} ) } -``` \ No newline at end of file +```