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

Commit

Permalink
feat: allow for adding conflict listeners after client creation (#393)
Browse files Browse the repository at this point in the history
  • Loading branch information
darahayes authored Mar 9, 2020
1 parent 0300699 commit 831d42e
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 10 deletions.
8 changes: 3 additions & 5 deletions docs/ref-conflict-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,7 @@ class ConflictLogger implements ConflictListener {
}
}

let config = {
...
conflictListener: new ConflictLogger()
...
}
const listener = new ConflictLogger()

client.addConflictListener(listener)
```
56 changes: 56 additions & 0 deletions packages/offix-client/integration_test/test/conflict.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,62 @@ describe('Conflicts', function () {

});

describe('all conflict listeners should be called', function() {
it('should succeed', async function() {

let client = await newClient({ networkStatus: newNetworkStatus(), offlineStorage: new TestStore() });
let conflictedClient = await newClient({ networkStatus: newNetworkStatus(), offlineStorage: new TestStore() });

const response = await client.mutate({
mutation: CREATE_TASK,
variables: newTask
});

const task = response.data.createTask;

await conflictedClient.query({
query: FIND_ALL_TASKS
})

await client.offlineMutate({
mutation: UPDATE_TASK,
returnType: 'Task',
variables: {
id: task.id,
version: task.version,
description: 'updated description',
title: 'updated title'
}
})

let conflictListenerCallCount = 0;

conflictedClient.addConflictListener({
conflictOccurred() {
conflictListenerCallCount++
}
});

conflictedClient.addConflictListener({
conflictOccurred() {
conflictListenerCallCount++
}
})

await conflictedClient.offlineMutate({
mutation: UPDATE_TASK,
returnType: 'Task',
variables: {
id: task.id,
version: task.version,
description: 'updated description again',
title: 'updated title'
}
})
expect(conflictListenerCallCount).to.equal(2);
})
})

describe('merge should be called for mergeable conflict', function () {

it('should succeed', async function () {
Expand Down
26 changes: 24 additions & 2 deletions packages/offix-client/src/ApolloOfflineClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ import {
ApolloQueueEntryOperation,
ApolloOfflineQueueListener,
getBaseStateFromCache,
ApolloCacheWithData
ApolloCacheWithData,
CompositeConflictListener
} from "./apollo";
import { NetworkStatus } from "offix-offline";
import { ObjectState } from "offix-conflicts-client";
import { ObjectState, ConflictListener } from "offix-conflicts-client";
import { ApolloOfflineClientOptions, InputMapper } from "./config/ApolloOfflineClientOptions";
import { ApolloOfflineClientConfig } from "./config/ApolloOfflineClientConfig";

Expand All @@ -32,6 +33,8 @@ export class ApolloOfflineClient extends ApolloClient<NormalizedCacheObject> {
public offlineStore?: ApolloOfflineStore;
// interface that performs conflict detection and resolution
public conflictProvider: ObjectState;
// composite conflict listener object that calls all listeners provided by users
public conflictListener: CompositeConflictListener;
// the network status interface that determines online/offline state
public networkStatus: NetworkStatus;
// the in memory queue that holds offline data
Expand All @@ -48,6 +51,7 @@ export class ApolloOfflineClient extends ApolloClient<NormalizedCacheObject> {
super(config);

this.initialized = false;
this.conflictListener = config.conflictListener;
this.mutationCacheUpdates = config.mutationCacheUpdates;
this.conflictProvider = config.conflictProvider;
this.inputMapper = config.inputMapper;
Expand Down Expand Up @@ -134,6 +138,24 @@ export class ApolloOfflineClient extends ApolloClient<NormalizedCacheObject> {
this.scheduler.registerOfflineQueueListener(listener);
}

/**
* Add new listener for conflict related events
*
* @param listener
*/
public addConflictListener(listener: ConflictListener){
this.conflictListener.addConflictListener(listener);
}

/**
* remove a conflict listener
*
* @param listener
*/
public removeConflictListener(listener: ConflictListener) {
this.conflictListener.removeConflictListener(listener);
}

protected createOfflineMutationOptions<T = any, TVariables = OperationVariables>(
options: MutationHelperOptions<T, TVariables>): MutationOptions<T, TVariables> {
options.inputMapper = this.inputMapper;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { ConflictListener, ConflictResolutionData } from "offix-conflicts-client";


/**
* Composite Conflict Listener class that can accept register and remove individual listener functions as needed
* Gets passed down to the conflict link
*/
export class CompositeConflictListener implements ConflictListener {

private listeners: ConflictListener[] = [];

addConflictListener(listener: ConflictListener) {
this.listeners.push(listener);
}

removeConflictListener(listener: ConflictListener) {
const index = this.listeners.indexOf(listener);
if (index >= 0) {
this.listeners.splice(index, 1);
}
}

mergeOccurred(operationName: string, resolvedData: ConflictResolutionData, server: ConflictResolutionData, client: ConflictResolutionData) {
for (const listener of this.listeners) {
if (listener.mergeOccurred) {
listener.mergeOccurred(operationName, resolvedData, server, client);
}
}
}

conflictOccurred(operationName: string, resolvedData: ConflictResolutionData, server: ConflictResolutionData, client: ConflictResolutionData) {
for (const listener of this.listeners) {
if (listener.conflictOccurred) {
listener.conflictOccurred(operationName, resolvedData, server, client);
}
}
}
}
1 change: 1 addition & 0 deletions packages/offix-client/src/apollo/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from "./LinksBuilder";
export * from "./optimisticResponseHelpers";
export * from "./conflicts/baseHelpers";
export * from "./conflicts/ConflictLink";
export * from "./conflicts/CompositeConflictListener";
9 changes: 6 additions & 3 deletions packages/offix-client/src/config/ApolloOfflineClientConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@ import { ApolloOfflineClientOptions, InputMapper } from "./ApolloOfflineClientOp
import { NetworkStatus } from "offix-offline";
import {
ConflictResolutionStrategy,
ConflictListener,
UseClient,
VersionedState,
ObjectState
} from "offix-conflicts-client";
import { createDefaultCacheStorage } from "../cache";
import { ApolloLink } from "apollo-link";
import { CacheUpdates } from "offix-cache";
import { ApolloOfflineQueueListener, createDefaultLink } from "../apollo";
import { ApolloOfflineQueueListener, createDefaultLink, CompositeConflictListener } from "../apollo";
import { CachePersistor } from "apollo-cache-persist";

/**
Expand All @@ -31,7 +30,7 @@ export class ApolloOfflineClientConfig implements ApolloOfflineClientOptions {
public terminatingLink: ApolloLink | undefined;
public cacheStorage: PersistentStore<PersistedData>;
public offlineStorage: PersistentStore<PersistedData>;
public conflictListener?: ConflictListener;
public conflictListener: CompositeConflictListener;
public mutationCacheUpdates?: CacheUpdates;
public cachePersistor?: CachePersistor<object>;
public link?: ApolloLink;
Expand All @@ -56,6 +55,10 @@ export class ApolloOfflineClientConfig implements ApolloOfflineClientOptions {
this.offlineStorage = options.offlineStorage || createDefaultOfflineStorage();
this.conflictStrategy = options.conflictStrategy || UseClient;
this.conflictProvider = options.conflictProvider || new VersionedState();
this.conflictListener = new CompositeConflictListener();
if (options.conflictListener) {
this.conflictListener.addConflictListener(options.conflictListener);
}
this.link = createDefaultLink(this);
}
}

0 comments on commit 831d42e

Please sign in to comment.