Skip to content
This repository has been archived by the owner on Apr 17, 2023. It is now read-only.

Commit

Permalink
chore: release 0.15.0
Browse files Browse the repository at this point in the history
  • Loading branch information
darahayes authored and wtrocki committed Mar 9, 2020
1 parent 831d42e commit 9fd38f3
Show file tree
Hide file tree
Showing 15 changed files with 589 additions and 22 deletions.
15 changes: 15 additions & 0 deletions docs/ref-release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ title: What is new in Offix
sidebar_label: Release notes
---

# 0.15.0

feat: allow for adding conflict listeners after client creation (#393)

Adds `addConflictListener` and `removeConflictListener` methods to `ApolloOfflineClient`.

# 0.14.0

fix: persist queue entries after updating optimistic ids (#389)
fix: improve queue handling of optimistic ids + InputMapper (#381)
fix: react example app build issue (#387)
breaking: refactor offline mutate (#378)
breaking: refactor network status interface (#368)
chore: Stop generating source maps (#375)

# 0.13.2

Add useNetworkStatus React hook which can be used to build components that render differently depending on the network state.
Expand Down
8 changes: 4 additions & 4 deletions examples/react/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "offix-react-example",
"version": "0.14.0",
"version": "0.15.0",
"main": "index.js",
"license": "Apache-2.0",
"private": true,
Expand All @@ -19,11 +19,11 @@
"apollo-link-ws": "^1.0.19",
"graphql": "^14.3.1",
"graphql-tag": "^2.10.1",
"offix-cache": "0.14.0",
"offix-client": "0.14.0",
"offix-cache": "0.15.0",
"offix-client": "0.15.0",
"react": "^16.13.0",
"react-dom": "^16.8.0",
"react-offix-hooks": "0.14.0",
"react-offix-hooks": "0.15.0",
"spectre.css": "^0.5.8",
"subscriptions-transport-ws": "^0.9.16"
},
Expand Down
2 changes: 1 addition & 1 deletion examples/server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "apollo-runtime-template",
"version": "0.14.0",
"version": "0.15.0",
"description": "",
"private": true,
"license": "Apache 2.0",
Expand Down
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
],
"npmClient": "yarn",
"useWorkspaces": true,
"version": "0.14.0",
"version": "0.15.0",
"command": {
"publish": {
"exact": true
Expand Down
2 changes: 1 addition & 1 deletion packages/offix-cache/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "offix-cache",
"version": "0.14.0",
"version": "0.15.0",
"description": "GraphQL Mutation and Subscription Helpers",
"main": "dist/index.js",
"types": "types/index.d.ts",
Expand Down
6 changes: 3 additions & 3 deletions packages/offix-client-boost/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "offix-client-boost",
"version": "0.14.0",
"version": "0.15.0",
"description": "simplifies setup and usage of offix-client",
"main": "dist/index.js",
"types": "types/index.d.ts",
Expand All @@ -24,8 +24,8 @@
"apollo-link-retry": "2.2.15",
"apollo-link-ws": "1.0.19",
"apollo-upload-client": "12.1.0",
"offix-client": "0.14.0",
"offix-conflicts-client": "0.14.0",
"offix-client": "0.15.0",
"offix-conflicts-client": "0.15.0",
"subscriptions-transport-ws": "0.9.16"
},
"devDependencies": {
Expand Down
10 changes: 5 additions & 5 deletions packages/offix-client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "offix-client",
"version": "0.14.0",
"version": "0.15.0",
"description": "Offix GraphQL Offline Client",
"main": "dist/index.js",
"types": "types/index.d.ts",
Expand Down Expand Up @@ -50,10 +50,10 @@
"apollo-link-error": "1.1.12",
"apollo-link-http": "1.5.16",
"apollo-link-retry": "2.2.15",
"offix-cache": "0.14.0",
"offix-conflicts-client": "0.14.0",
"offix-offline": "0.14.0",
"offix-scheduler": "0.14.0",
"offix-cache": "0.15.0",
"offix-conflicts-client": "0.15.0",
"offix-offline": "0.15.0",
"offix-scheduler": "0.15.0",
"traverse": "0.6.6"
},
"peerDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/offix-conflicts-client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "offix-conflicts-client",
"version": "0.14.0",
"version": "0.15.0",
"description": "adds client side conflict detection and resolution",
"main": "dist/index.js",
"types": "types/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/offix-conflicts-server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "offix-conflicts-server",
"version": "0.14.0",
"version": "0.15.0",
"description": "Offix GraphQL server",
"main": "dist/index.js",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/offix-offline/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "offix-offline",
"version": "0.14.0",
"version": "0.15.0",
"description": "Offix package that exposes network interfaces",
"main": "dist/index.js",
"types": "types/index.d.ts",
Expand Down
4 changes: 2 additions & 2 deletions packages/offix-scheduler/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "offix-scheduler",
"version": "0.14.0",
"version": "0.15.0",
"description": "",
"main": "dist/index.js",
"types": "types/index.d.ts",
Expand Down Expand Up @@ -34,7 +34,7 @@
"typescript": "3.7.5"
},
"dependencies": {
"offix-offline": "0.14.0"
"offix-offline": "0.15.0"
},
"peerDependencies": {
"graphql": "^0.12.0 || ^0.13.0 || ^14.0.0"
Expand Down
4 changes: 2 additions & 2 deletions packages/react-offix-hooks/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-offix-hooks",
"version": "0.14.0",
"version": "0.15.0",
"description": "Use offix-client in react hooks",
"keywords": [
"offix",
Expand Down Expand Up @@ -48,7 +48,7 @@
"typescript": "3.7.5"
},
"dependencies": {
"offix-client": "0.14.0"
"offix-client": "0.15.0"
},
"peerDependencies": {
"graphql": "^0.12.0 || ^0.13.0 || ^14.0.0",
Expand Down
150 changes: 150 additions & 0 deletions website/versioned_docs/version-0.15.0/ref-conflict-client.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
---
id: version-0.15.0-conflict-client
title: Client Side Conflict Resolution
sidebar_label: Conflicts Client
original_id: conflict-client
---

When performing data synchronization between multiple clients it is common for remote devices to become offline for a certain amount of time. As a result of being offline, data that is modified by a client can become outdated with the server. Further operations on that record can cause a conflict (often called a collision). For more information about offline workflows, please see [Offline Support](ref-offline.md)

Offix provides a way to manage and resolve these conflicts for any GraphQL type.
Conflict implementation will require additional elements in your application in order to work:

- Mutation `returnType` added on context to any mutation
- Additional metadata inside types (for example version field) depending on conflict implementation

Conflict mechanism is divided between:

- Conflict detection
Developers can detect conflicts on resolver level
- Conflict resolution
Conflicts are sent back to client and resolved by resending data back to server.

Offix allows developers to detect and resolve conflict without user interactions.
By default when no changes were made on the same fields, the implementation will try to resend the modified payload back to the server. When changes on the server and the client cover the same fields one of the specified conflict resolution strategies can be used. The default strategy will apply client changes on top of the server side.
Developers can modify strategies to suit their needs.

## Working with Conflict Resolution on Client

To enable conflict resolution we fist need to configure our server side resolvers to perform conflict detection. Detection can rely on many different implementations and return the conflict error back to the client. For more information about how to do this, please see [Server Side Conflict Resolution](ref-conflict-server.md)

The client will then automatically resolve them based on the current strategy and notify listeners if the developer supplied any.

Conflict resolution will work out of the box with the recommended defaults and does not require any specific handling on the client.

> For advanced use cases developers may customize their conflict implementation by supplying a custom `conflictProvider` in config. See Conflict Resolution Strategies below.
## Default Conflict Implementation

By default, conflict resolution is configured to rely on a `version` field on each GraphQL type.
Version field will also need to be saved to the database in order to detect changes on the server

For example:

```javascript
type User {
id: ID!
version: String!
name: String!
}
```

The version field is controlled on the server and will map the last version that was sent from the server. All operations on the version field happen automatically, however, users need to make sure that the version field is always passed to server for mutations that supports conflict resolution:

```javascript
type Mutation {
updateUser(id: ID!, version: String!): User
}
```

Alternatively developers can create input elements that can be reused in every mutation and support conflict resolution.

```javascript
type Mutation {
updateUser(user: UserInput): User
}
```

### Custom Conflict implementation by extening ObjectState

Offix enables flexibility on how conflicts are detected and resolved.
In many cases developers may need different ways of detecting conflits than relying on version field
stored in database. For example if database already have `changedAt` field that is being supported by trigger it can be used as custom conflict implemementation.

Under the hood conflicts implementations are extending an `ObjectState` interface.
This interface exist on both client and server and provides functions that help with detection and resolution of conflicts.

The default implementation is `VersionedObjectState`. This means Offix expects all data which could be conflicted to have a version field. If this is not the case, developers can also provide custom state which Offix will then use for conflict resolution. To do this, Offix expects certain functions to be available under the `conflictProvider` option in config. These functions and their signatures are:

`assignServerState(client, server)` - assigns the server state to the client state to reduce the chance of a second conflict.

`hasConlict(client, server)` - detects whether or not both sets of data are conflicted.

`getStateFields()` - returns an array of fields that should not be taken into account for conflict purposes.

`currentState(objectWithState)` - returns the current state of the object.

## Conflict Resolution Strategies

Offix allows developers to define custom conflict resolution strategies. You can provide custom conflict resolution strategies to the client in the config by using the provided `ConflictResolutionStrategies` type. By default developers do not need to pass any strategy as `UseClient` is the default. Custom strategies can be used also to provide different resolution strategy for certain operations:

```javascript
let customStrategy = {
resolve = (base, server, client, operationName) => {
let resolvedData;
switch (operationName) {
case "updateUser":
delete client.socialKey
resolvedData = Object.assign(base, server, client)
break
case "updateRole":
client.role = "none"
resolvedData = Object.assign(base, server, client)
break
default:
resolvedData = Object.assign(base, server, client)
}
return resolvedData
}
}
```

This custom strategy provides two custom strategies to be used when a conflict occurs. They are based on the name of the operation to give developers granular control. To use this custom strategy pass it as an argument to conflictStrategy in your config object:

```javascript
let config = {
...
conflictStrategy: customStrategy
...
}
```

## Listening to Conflicts

Offix allows developers to receive information about the data conflict that occurred between the client and the server. The client can be notified in one of two scenarios.

When a conflict occurs Offix will attempt to do a field level resolution of data - meaning it will check all fields of your type to see if both the client or server has changed them.

If both client and server have changed any of the same fields then the `conflictOccurred` method of your `ConflictListener` will be triggered.

If the client and server have not changed any of the same fields and the data can be easily merged then the `mergeOccurred` method of your `ConflictListener` will be triggered.

Developers can supply their own `conflictListener` implementation

```typescript
class ConflictLogger implements ConflictListener {
conflictOccurred(operationName, resolvedData, server, client) {
console.log("Conflict occurred with the following:")
console.log(`data: ${JSON.stringify(resolvedData)}, server: ${JSON.stringify(server)}, client: ${JSON.stringify(client)}, operation: ${JSON.stringify(operationName)}`);
}
mergeOccurred(operationName, resolvedData, server, client) {
console.log("Merge occurred with the following:")
console.log(`data: ${JSON.stringify(resolvedData)}, server: ${JSON.stringify(server)}, client: ${JSON.stringify(client)}, operation: ${JSON.stringify(operationName)}`);
}
}
}

const listener = new ConflictLogger()

client.addConflictListener(listener)
```
Loading

0 comments on commit 9fd38f3

Please sign in to comment.