This repository has been archived by the owner on Apr 17, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 45
/
ApolloOfflineClient.ts
154 lines (139 loc) · 5.71 KB
/
ApolloOfflineClient.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import ApolloClient, { MutationOptions, OperationVariables } from "apollo-client";
import { NormalizedCacheObject } from "apollo-cache-inmemory";
import { OffixScheduler } from "offix-scheduler";
import { CachePersistor } from "apollo-cache-persist";
import { MutationHelperOptions, CacheUpdates, createMutationOptions } from "offix-cache";
import { FetchResult } from "apollo-link";
import {
ApolloOperationSerializer,
ApolloOfflineQueue,
ApolloOfflineStore,
addOptimisticResponse,
removeOptimisticResponse,
restoreOptimisticResponse,
replaceClientGeneratedIDsInQueue,
ApolloQueueEntryOperation,
ApolloOfflineQueueListener,
getBaseStateFromCache,
ApolloCacheWithData
} from "./apollo";
import { NetworkStatus } from "offix-offline";
import { ObjectState } from "offix-conflicts-client";
import { ApolloOfflineClientOptions, InputMapper } from "./config/ApolloOfflineClientOptions";
import { ApolloOfflineClientConfig } from "./config/ApolloOfflineClientConfig";
export class ApolloOfflineClient extends ApolloClient<NormalizedCacheObject> {
// wrapper around the apollo cache for persisting it across restarts
public persistor: CachePersistor<object>;
// the offix scheduler
public scheduler: OffixScheduler<MutationOptions>;
// the offline storage interface that persists offline data across restarts
public offlineStore?: ApolloOfflineStore;
// interface that performs conflict detection and resolution
public conflictProvider: ObjectState;
// the network status interface that determines online/offline state
public networkStatus: NetworkStatus;
// the in memory queue that holds offline data
public queue: ApolloOfflineQueue;
// cache update functions for mutations. Used to restore optimistic responses after restarts
public mutationCacheUpdates?: CacheUpdates;
// true after client is initialized
public initialized: boolean;
// mapper function for mapping mutation variables
public inputMapper?: InputMapper;
constructor(options: ApolloOfflineClientOptions) {
const config = new ApolloOfflineClientConfig(options);
super(config);
this.initialized = false;
this.mutationCacheUpdates = config.mutationCacheUpdates;
this.conflictProvider = config.conflictProvider;
this.inputMapper = config.inputMapper;
if (config.cachePersistor) {
if (!(config.cachePersistor instanceof CachePersistor)) {
throw new Error("Error: options.cachePersistor is not a CachePersistor instance");
}
this.persistor = config.cachePersistor;
} else {
this.persistor = new CachePersistor({
cache: this.cache,
serialize: false,
storage: config.cacheStorage,
maxSize: false,
debug: false
});
}
this.scheduler = new OffixScheduler<MutationOptions>({
executor: this,
storage: config.offlineStorage,
networkStatus: config.networkStatus,
serializer: ApolloOperationSerializer,
offlineQueueListener: config.offlineQueueListener
});
this.queue = this.scheduler.queue;
this.networkStatus = this.scheduler.networkStatus;
this.offlineStore = this.scheduler.offlineStore;
}
public async init() {
if (this.persistor) {
try {
await this.persistor.restore();
} catch(error) {
console.error("Error restoring Apollo cache from storage.", error);
console.error("Cache persistence will not be available.");
}
}
// Optimistic Responses
this.queue.registerOfflineQueueListener({
onOperationEnqueued: (operation: ApolloQueueEntryOperation) => {
addOptimisticResponse(this, operation);
},
onOperationSuccess: (operation: ApolloQueueEntryOperation, result: FetchResult) => {
replaceClientGeneratedIDsInQueue(this.scheduler.queue, operation, result);
removeOptimisticResponse(this, operation);
},
onOperationFailure: (operation: ApolloQueueEntryOperation, error) => {
removeOptimisticResponse(this, operation);
},
onOperationRequeued: (operation: ApolloQueueEntryOperation) => {
if (this.mutationCacheUpdates) {
restoreOptimisticResponse(this, this.mutationCacheUpdates, operation);
}
}
});
await this.scheduler.init();
this.initialized = true;
}
public async execute(options: MutationOptions) {
return this.mutate(options);
}
public async offlineMutate<T = any, TVariables = OperationVariables>(
options: MutationHelperOptions<T, TVariables>): Promise<FetchResult<T>> {
if (!this.initialized) {
throw new Error("cannot call client.offlineMutate until client is initialized");
}
const mutationOptions = this.createOfflineMutationOptions(options);
return this.scheduler.execute(mutationOptions as unknown as MutationOptions);
}
/**
* Add new listener for listening for queue changes
*
* @param listener
*/
public registerOfflineEventListener(listener: ApolloOfflineQueueListener) {
this.scheduler.registerOfflineQueueListener(listener);
}
protected createOfflineMutationOptions<T = any, TVariables = OperationVariables>(
options: MutationHelperOptions<T, TVariables>): MutationOptions<T, TVariables> {
options.inputMapper = this.inputMapper;
const offlineMutationOptions = createMutationOptions<T, TVariables>(options);
offlineMutationOptions.context.conflictBase = getBaseStateFromCache(
this.cache as unknown as ApolloCacheWithData,
this.conflictProvider,
offlineMutationOptions as unknown as MutationOptions,
this.inputMapper
);
if (!offlineMutationOptions.update && this.mutationCacheUpdates) {
offlineMutationOptions.update = this.mutationCacheUpdates[offlineMutationOptions.context.operationName];
}
return offlineMutationOptions;
}
}