diff --git a/docs/ref-release-notes.md b/docs/ref-release-notes.md index 378b9f181..f82963993 100644 --- a/docs/ref-release-notes.md +++ b/docs/ref-release-notes.md @@ -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. diff --git a/examples/react/package.json b/examples/react/package.json index 2a9795a20..43f75c5c7 100644 --- a/examples/react/package.json +++ b/examples/react/package.json @@ -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, @@ -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" }, diff --git a/examples/server/package.json b/examples/server/package.json index 5079d5589..f6ea58e3f 100644 --- a/examples/server/package.json +++ b/examples/server/package.json @@ -1,6 +1,6 @@ { "name": "apollo-runtime-template", - "version": "0.14.0", + "version": "0.15.0", "description": "", "private": true, "license": "Apache 2.0", diff --git a/lerna.json b/lerna.json index a5e174afd..2bd0c39a9 100644 --- a/lerna.json +++ b/lerna.json @@ -6,7 +6,7 @@ ], "npmClient": "yarn", "useWorkspaces": true, - "version": "0.14.0", + "version": "0.15.0", "command": { "publish": { "exact": true diff --git a/packages/offix-cache/package.json b/packages/offix-cache/package.json index 025cdde11..596d5346e 100644 --- a/packages/offix-cache/package.json +++ b/packages/offix-cache/package.json @@ -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", diff --git a/packages/offix-client-boost/package.json b/packages/offix-client-boost/package.json index bf7400889..debc82f37 100644 --- a/packages/offix-client-boost/package.json +++ b/packages/offix-client-boost/package.json @@ -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", @@ -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": { diff --git a/packages/offix-client/package.json b/packages/offix-client/package.json index c7d6ff822..d56f2f99a 100644 --- a/packages/offix-client/package.json +++ b/packages/offix-client/package.json @@ -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", @@ -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": { diff --git a/packages/offix-conflicts-client/package.json b/packages/offix-conflicts-client/package.json index 2a6601d08..3528dcdfb 100644 --- a/packages/offix-conflicts-client/package.json +++ b/packages/offix-conflicts-client/package.json @@ -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", diff --git a/packages/offix-conflicts-server/package.json b/packages/offix-conflicts-server/package.json index eff8b5241..db50c52cd 100644 --- a/packages/offix-conflicts-server/package.json +++ b/packages/offix-conflicts-server/package.json @@ -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": { diff --git a/packages/offix-offline/package.json b/packages/offix-offline/package.json index ea4673362..cac273046 100644 --- a/packages/offix-offline/package.json +++ b/packages/offix-offline/package.json @@ -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", diff --git a/packages/offix-scheduler/package.json b/packages/offix-scheduler/package.json index 0f24fa6e2..22b204c26 100644 --- a/packages/offix-scheduler/package.json +++ b/packages/offix-scheduler/package.json @@ -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", @@ -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" diff --git a/packages/react-offix-hooks/package.json b/packages/react-offix-hooks/package.json index fb8d487d1..685de789c 100644 --- a/packages/react-offix-hooks/package.json +++ b/packages/react-offix-hooks/package.json @@ -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", @@ -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", diff --git a/website/versioned_docs/version-0.15.0/ref-conflict-client.md b/website/versioned_docs/version-0.15.0/ref-conflict-client.md new file mode 100644 index 000000000..c571858b0 --- /dev/null +++ b/website/versioned_docs/version-0.15.0/ref-conflict-client.md @@ -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) +``` diff --git a/website/versioned_docs/version-0.15.0/ref-release-notes.md b/website/versioned_docs/version-0.15.0/ref-release-notes.md new file mode 100644 index 000000000..f49a9d3d2 --- /dev/null +++ b/website/versioned_docs/version-0.15.0/ref-release-notes.md @@ -0,0 +1,401 @@ +--- +id: version-0.15.0-release-notes +title: What is new in Offix +sidebar_label: Release notes +original_id: 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. + +# 0.13.1 + +0.13.1 fixes package install issues that were introduced in 0.13.0. It also introduces some minor dependency updates. + +# 0.13.0 + +The 0.13.0 release is a release contains a couple of bug fixes, dependency updates and the new ability to configure your own `CachePersistor` object as requested in [#273](https://github.com/aerogear/offix/issues/273) and [#281](https://github.com/aerogear/offix/issues/281). + +The [CachePersistor](https://github.com/apollographql/apollo-cache-persist#using-cachepersistor) is used by the client to persist the Apollo Cache across application restarts. Pass your own instance to override the one that is created by default. + +Example: + +```js +import { ApolloOfflineClient, createDefaultCacheStorage } from "offix-client"; +import { HttpLink } from "apollo-link-http"; +import { InMemoryCache } from "apollo-cache-inmemory"; +import { CachePersistor } from "apollo-cache-persist"; + +const link = new HttpLink({ uri: "http://example.com/graphql" }); +const cache = new InMemoryCache() + +const cachePersistor = new CachePersistor({ + cache, + storage: createDefaultCacheStorage() +}) + +const client = new ApolloOfflineClient({ + cache, + cachePersistor, + link +}); +``` + +Note: if using TypeScript, you may need to declare the cachePersistor as follows `const cachePersistor = new CachePersistor(...options)` or you may experience compiler errors. + +This example uses `createDefaultCacheStorage` to create the default IndexedDB based storage driver. +The storage can be swapped depending on the platform. For example `window.localstorage` in older browsers or `AsyncStorage` in [React Native](https://offix.dev/docs/react-native). + +# 0.12.0 + +The 0.12.0 release brings a new `offix-client-boost` package. This package is a batteries-included wrapper around the `offix-client` that brings +everything needed to get started with GraphQL quickly. This includes a cache and support for subscriptions and file uploads. + +Check out the new documentation at [offix.dev](https://offix.dev). + +# 0.11.0 + +The 0.11.0 release simplifies the creation of offline clients. + +## Breaking Changes + +### OfflineClient replaced with ApolloOfflineClient + +The `OfflineClient` class has been replaced with `ApolloOfflineClient`. `OfflineClient` was a wrapper that would internally create an `ApolloClient` which could be accessed after initialization. The flow looked something like this. + +```js +const offlineClient = new OfflineClient(options) +const apolloClient = await offlineClient.init() +``` + +The new `ApolloOfflineClient` directly `extends` `ApolloClient` which makes initialization simpler and more flexible as all of the standard Apollo Client options are supported. + +Initialization is slightly different, mainly, and `ApolloLink` must be passed that can connect with the GraphQL server. See the example code below. + +```js +import { ApolloOfflineClient } from 'offix-client' + +const link = new HttpLink({ uri: 'http://localhost/graphql' }) + +const config = { + link, + typeDefs, + resolvers, + ...otherOptions +} + +const client = new ApolloOfflineClient(config) +await client.init() +... + +// `client` is an ApolloClient so you can call all of the methods you'd expect +client.query(...) +client.mutate(...) +client.offlineMutate(...) +``` + +# 0.10.0 + +The `0.10.0` release builds upon the `0.9.0` release and adds two new packages. `offix-scheduler` and `offix-conflicts-client` + +## offix-scheduler + +`offix-scheduler` now holds the core functionalities of the Offix project. That includes queueing, scheduling, persistence and fulfilment of offline operations. + +`offix-scheduler` can and will be used to create new clients in the future that are not based on Apollo Client. Clients we are investigating are URQL (see https://github.com/aerogear/offix/issues/235), Relay (see https://github.com/aerogear/offix/issues/236) and plain HTTP/REST. `offix-client` now uses `offix-scheduler` under the hood. + +There have been no functional changes to `offix-client` and everything should remain the same. + +## offix-conflicts-client + +The `offix-conflicts-client` package contains all the interfaces and current implementations for client side pluggable conflict detection and resolution. Right now this package is used in to deliver the conflict capabilities in `offix-client` and it could also be used by package maintainers to implement conflict capabilities in new clients also. It is not important for application developers looking to use `offix-client` + +# 0.9.0 + +The `0.9.0` release is a significant refactor of the internals of Offix and introduces a couple of breaking changes to the end user API. + +* We have a new documentation site available at [offix.dev](https://offix.dev). Special thanks to [@LakshanKarunathilake](https://github.com/LakshanKarunathilake) for the complete overhaul. +* All queueing, scheduling, persistence, and replaying of offline operations now happens outside of the Apollo Link chain. Instead we use a much more generic queueing mechanism that opens the door to great flexibility. +* It paves the way for new features in the future. Most importantly, the ability to use Offix with other GraphQL clients, or even with regular RESTful clients (and more). +* The internal architecture of Offix is drastically simplified. It is much easier to understand, maintain and test. + +With this release, `OfflineClient` behaves mostly the same way as it has before but there were a couple of breaking changes which are outlined below. + +## Background + +Previous versions of Offix relied heavily on something called [Apollo Link](https://www.apollographql.com/docs/link/overview/) which is essentially chain of "middleware" functions that can modify the behaviour and results from calls like `ApolloClient.mutate()` and `ApolloClient.query()`. Most of the underlying queueing, scheduling, persistence and replaying of offline mutations done by Offix happened inside the of the Apollo Link chain. This approach seemed like a good idea, but over time we have realised it made things difficult to maintain and it kept us limited in the features we could provide. + +## Breaking Changes + +### `client.offlineMutation` has been deprecated in favour of `client.offlineMutate` + +It didn't make sense to have a `mutate` and `offlineMutation` method. `offlineMutation` has been deprecated in favour of `offlineMutate`. `offlineMutation` can still be used, but it logs a deprecation warning to the console and it will be removed in the next release. + +**Suggestion:** Change all uses of `client.offlineMutation` to `client.offlineMutate` + +### client.mutate no longer does any offline scheduling + +A side effect of our Apollo Link architecture was that `client.mutate()` would also schedule operations while offline (as well as `client.offlineMutation`). Using `client.mutate()` for offline operations was never recommended but it was possible. This is no longer the case. + +**Suggestion:** any places where you intentionally have offline behaviour using `client.mutate()` should use `client.offlineMutate()` instead. + +### Removed `@OnlineOnly` directive + +Because `client.mutate()` does not schedule offline operations anymore, the `@OnlineOnly` directive is no longer useful and has been completely removed. + +**Suggestion:** remove all instances of the `@OnlineOnly` directive and ensure mutations that used it are called with `client.mutate()`. + +### Errors from `client.offlineMutate()` do not have `networkError` property. + +Errors originating from the Apollo Link chain are found on `error.networkError`. +This led to checks in application code such as `if (error.networkError.offline)`, +where `error.networkError` is the actual error thrown. + +This is no longer the case. Now the everything is found on the top level `error` object. +See the example below: + +```js +const options = { + mutation: gql` + mutation greeting($name: String!){ + greeting(name: $name) { + body + } + }`, + variables: { + name: 'hello world!' + } +}; + +client.offlineMutate(options).catch((error) => { + // This used to be `if (error.networkError.offline)` + if(error.offline) { + // This used to be `error.networkError.watchOfflineChange()` + error.watchOfflineChange().then(...) + } +}); +``` + +This is the same for local conflict errors: + +```js +client.offlineMutate(options).catch((error) => { + // This used to be `if (error.networkError.localConflict)` + if (error.localConflict) { + // handle local conflict + } +}); +``` + +**Suggestion:** review all code where `error.networkError.` is being accessed and change it to `error.` + +### `OfflineQueue` and `OfflineStore` are generic (TypeScript Users Only) + +The `OfflineQueue` and `OfflineStore` classes and some related interfaces have been refactored to handle generic objects. TypeScript users may experience compilation issues if their application references these types. + +New types have been added to `offix-client` for Apollo specific usage. + +**Suggestion:** Migrate the following references: + +* `OfflineQueue` becomes `ApolloOfflineQueue` +* `OfflineStore` becomes `ApolloOfflineStore` +* `OfflineQueueListener` becomes `ApolloOfflineQueueListener` +* `IResultProcessor` becomes `ApolloIResultProcessor` + +### New Arguments passed to registerOfflineEventListener functions + +`registerOfflineEventListener` registers functions that are called on events originating from the `OfflineQueue`. + +```js +client.registerOfflineEventListener({ + onOperationEnqueued(operation) { + // called when operation was placed on the queue + }, + onOperationFailure: (operation) => { + // called when the operation failed + }, + onOperationSuccess: (operation) => { + // called when the operation was fulfilled + }, + onOperationRequeued: (operation) => { + // called when an operation was loaded in from storage and placed back on the queue + // This would happen across app restarts + }, + queueCleared() { + // called when all operations are fulfilled and the queue is cleared + } +}); +``` + +In previous versions of `offix-client`, these functions had an [Apollo Operation](https://www.apollographql.com/docs/link/overview/) object passed to them. Because Offix no longer uses Apollo Link, this is no longer the case. Instead an [`ApolloQueueEntryOperation`](https://github.com/aerogear/offix/blob/b1e42936ff05a3d21c93795eadba07217bd13f23/packages/offix-client/src/apollo/ApolloOfflineClient.ts#L53) is passed. See the example object below. + +``` +{ + qid: 'client:abc123' + op: { + context: { + operationName: 'createItem', + conflictBase: undefined, + idField: 'id', + returnType: 'Item' + }, + mutation: , + optimisticResponse: , + variables: + } +} +``` + +`ApolloQueueEntryOperation` objects have two top level fields: + +* `qid` - Queue ID. This ID is randomly generated and mostly used by the `OfflineQueue`. +* `op` - The operation. In `offix-client` It's of type `MutationOptions`, the options object passed into `client.offlineMutate` with some extra metadata set by `offix-client`. + +**Suggestion:** review any code where `registerOfflineEventListener` is used and refactor the listener functions to use the new data structure being passed. + +### Operations Stored in OfflineStore are not backwards compatible + +Because of the architectural changes to Offix, the objects stored in the OfflineQueue and OfflineStore are different. Previously, the queue and storage mechanisms were based around the [Apollo Operation](https://www.apollographql.com/docs/link/overview/). + +`offix-client` now queues and stores objects based off the `MutationOptions` object. Unfortunately these changes were not backwards compatible. Existing applications using `offix-client` that have pending offline operations will not be able to load and requeue those operations after an upgrade to the latest version. The data will still exist in the store and it will be accessible manually. Developers will have to migrate the data themselves. **Data in the Apollo Cache is not affected.** We hope this issue will affect very few users if any at all. + +To ensure this doesn't happen in future, we have implemented versioning and new serialize/deserialize interfaces that will allow our storage mechanism to handle these types of upgrades/migrations. + +**Suggestions:** + +* Ensure users have no pending offline operations before administering the update. +* Manually migrate the data using custom application code. +* If it's not critical, users can re-enter the data or redo their offline operations. + +## Features + +### New Documentation Website! + +Our documentation website has been rebuilt using Docusaurus. Special thanks to [@LakshanKarunathilake](https://github.com/LakshanKarunathilake) for the complete overhaul. + +### OfflineQueue is directly accessible on the client + +`client.queue` is now directly accessible. This opens up the possibility for your application to directly see the operations in the queue. It also means you can manually call `client.queue.forwardOperations()` to execute all operations in the queue. + +### OfflineClient accepts user provided `InMemoryCache` + +It is now possible to pass your own `InMemoryCache` into `OfflineClient`. This makes it possible to configure things like cache redirects. + +```js +const cache = new InMemoryCache(yourCacheConfig); + +const offlineClient = new OfflineClient({ + httpUrl: 'https://example.com', + cache +}); +``` + +### OfflineClient enables wiping out cache using persistor interface + +`offlineClient.persitor.purge()` method will wipe entire persistence layer for Apollo cache. + +> NOTE: InMemoryCache needs to be wiped out as well along with the client. Please execute `offlineClient.cache.rest()` + +# 0.8.0 + +## Features + +### Offix React Hooks Alpha released + +Offix React Hooks provides helpers for using offix within React and React Native. +Please refer to package README for more information + +### Ability to customize Apollo Link chain + +`OffixClientConfig.terminatingLink` allows to customize client by adding additional links +for handling authentication, network requests etc. + +### New way to access Apollo Client + +`OfflineClient.apolloClient` is now public. This means `apolloClient` is directly accessible after `OfflineClient.init()`. + +## Breaking changes + +### Changes for Subscriptions and File Uploads + +Subscriptions and file uploads were removed from the main library. +Developers can still configure Subscription access directly int their application by +creating Apollo link acording to documentation and passing `OffixClientConfig.terminatingLink` + +### 0.7.1 + +#### Offline operations persist optimistic response + +Offline operations will now cache update functional and automatically apply optimistic response +`OffixClientConfig.mutationCacheUpdates` is still required to see optimistic responses after application restart. + +#### watchOfflineChange returns mutation result + +We have discovered bug where `watchOfflineChange` method from `OfflineError` was missing mutation results. +This is now fixed so your UI can instantly watch for offline chances and render when succeeded. + +### 0.7.0 + +#### Support Apollo 2.6.x + +Apollo Client 2.6.x with new typings is now supported. + +#### Extended conflict support + +New conflict implementation requires changes on both client and server. +On server we have changed conflict detection mechanism to single method. +Server side conflict resolution was removed due to the fact that we could not provide +reliable diff source without separate store. + +##### Server side implementation: + +```javascript + const conflictError = conflictHandler.checkForConflict(greeting, args); + if (conflictError) { + throw conflictError; + } +} +``` + +##### Client side implementation: + +Client side implementation now requires users to apply `returnType` to context when performing a mutation. +Conflict interface now has an additional method `mergeOccured` that will be triggered when a conflict was resolved without data loss. + +Please refer to documentation for more details. + +#### Breaking changes + +##### DataSync Config renamed + +`DataSyncConfig` interface was renamed to `OffixClientConfig`. +Please review if your configuration still conforms to the new interface. + +##### Cache Helper Interface + +Cache Helper interface now will now accept object instead of individual parameters: + +```javascript +const updateFunction = getUpdateFunction({ + mutationName, + idField, + operationType, + updateQuery +}); +``` diff --git a/website/versions.json b/website/versions.json index f4523a709..36c412148 100644 --- a/website/versions.json +++ b/website/versions.json @@ -1,4 +1,5 @@ [ + "0.15.0", "0.14.0", "0.13.0", "0.12.0",