@quesmed/types 1.3.15 → 1.3.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/gql_input_output_types/User.mjs +1 -0
  2. package/gql_input_output_types/index.d.ts +1 -0
  3. package/gql_input_output_types/index.mjs +1 -0
  4. package/index.mjs +17 -0
  5. package/models/Author.mjs +1 -0
  6. package/models/Book.mjs +1 -0
  7. package/models/Card.mjs +1 -0
  8. package/models/Chapter.mjs +1 -0
  9. package/models/Concept.mjs +1 -0
  10. package/models/Difficulty.mjs +7 -0
  11. package/models/Feedback.mjs +1 -0
  12. package/models/File.mjs +1 -0
  13. package/models/Marksheet.mjs +6 -0
  14. package/models/MockTest.mjs +1 -0
  15. package/models/OsceMarksheet.mjs +20 -0
  16. package/models/OsceStation.mjs +42 -0
  17. package/models/Picture.mjs +5 -0
  18. package/models/Promo.mjs +1 -0
  19. package/models/Question.mjs +30 -0
  20. package/models/Subscription.mjs +6 -0
  21. package/models/Todo.mjs +1 -0
  22. package/models/Token.mjs +1 -0
  23. package/models/Topic.mjs +10 -0
  24. package/models/Type.mjs +1 -0
  25. package/models/University.mjs +1 -0
  26. package/models/User.mjs +1 -0
  27. package/models/Video.mjs +1 -0
  28. package/models/index.mjs +23 -0
  29. package/package.json +16 -3
  30. package/resolvers/apollo.d.ts +8 -0
  31. package/resolvers/apollo.js +2 -0
  32. package/resolvers/apollo.mjs +1 -0
  33. package/resolvers/mutation/admin/algoliaSync.mjs +1 -0
  34. package/resolvers/mutation/admin/index.mjs +2 -0
  35. package/resolvers/mutation/admin/token.mjs +1 -0
  36. package/resolvers/mutation/index.mjs +2 -0
  37. package/resolvers/mutation/restricted/agora.mjs +1 -0
  38. package/resolvers/mutation/restricted/contactUs.mjs +1 -0
  39. package/resolvers/mutation/restricted/index.mjs +10 -0
  40. package/resolvers/mutation/restricted/marksheet.mjs +1 -0
  41. package/resolvers/mutation/restricted/mockTest.mjs +1 -0
  42. package/resolvers/mutation/restricted/osce.mjs +1 -0
  43. package/resolvers/mutation/restricted/questionDiscussion.mjs +1 -0
  44. package/resolvers/mutation/restricted/todo.d.ts +5 -0
  45. package/resolvers/mutation/restricted/todo.js +27 -0
  46. package/resolvers/mutation/restricted/todo.mjs +25 -0
  47. package/resolvers/mutation/restricted/token.mjs +1 -0
  48. package/resolvers/mutation/restricted/users.mjs +1 -0
  49. package/resolvers/mutation/restricted/video.mjs +1 -0
  50. package/resolvers/mutation/stripe.mjs +1 -0
  51. package/resolvers/mutation/users.mjs +1 -0
  52. package/resolvers/mutation/validUserToken/index.mjs +1 -0
  53. package/resolvers/mutation/validUserToken/user.mjs +1 -0
  54. package/resolvers/query/admin/getUserToken.mjs +1 -0
  55. package/resolvers/query/admin/index.mjs +1 -0
  56. package/resolvers/query/author.mjs +1 -0
  57. package/resolvers/query/book.mjs +1 -0
  58. package/resolvers/query/feedback.mjs +1 -0
  59. package/resolvers/query/index.mjs +9 -0
  60. package/resolvers/query/restricted/anatomy.mjs +1 -0
  61. package/resolvers/query/restricted/index.mjs +10 -0
  62. package/resolvers/query/restricted/marksheet.mjs +1 -0
  63. package/resolvers/query/restricted/mockTests.mjs +1 -0
  64. package/resolvers/query/restricted/osce.mjs +1 -0
  65. package/resolvers/query/restricted/quesBook.mjs +1 -0
  66. package/resolvers/query/restricted/todos.mjs +1 -0
  67. package/resolvers/query/restricted/topics.mjs +1 -0
  68. package/resolvers/query/restricted/university.mjs +1 -0
  69. package/resolvers/query/restricted/user.mjs +1 -0
  70. package/resolvers/query/restricted/video.mjs +1 -0
  71. package/resolvers/query/sampleCards.mjs +1 -0
  72. package/resolvers/query/sampleQuestions.mjs +1 -0
  73. package/resolvers/query/subscription.mjs +1 -0
  74. package/resolvers/query/university.mjs +1 -0
  75. package/resolvers/query/user.mjs +1 -0
  76. package/resolvers/query/video.mjs +1 -0
  77. package/resolvers/subscription/index.mjs +1 -0
  78. package/resolvers/subscription/osce.mjs +39 -0
  79. package/utils/commonFunctions.mjs +217 -0
  80. package/utils/index.d.ts +5 -0
  81. package/utils/index.js +17 -0
  82. package/utils/index.mjs +5 -0
  83. package/utils/lightgallery.d.ts +4 -2
  84. package/utils/lightgallery.js +4 -1
  85. package/utils/lightgallery.mjs +87 -0
  86. package/utils/offlineLink.d.ts +64 -0
  87. package/utils/offlineLink.js +237 -0
  88. package/utils/offlineLink.mjs +230 -0
  89. package/utils/uuid4.mjs +285 -0
  90. package/utils/wordsToNumber.mjs +44 -0
@@ -0,0 +1,217 @@
1
+ import { EQuestionType, } from '../models';
2
+ import { wordsToNumber } from './wordsToNumber';
3
+ const floatRegex = /([0-9]*[.])?[0-9]+/gm;
4
+ const ACCEPTED_FREQ = ['stat', 'od', 'bd', 'tds', 'qds'];
5
+ export function mapPrescribeMarkToAnswer(obj) {
6
+ return {
7
+ drug: {
8
+ value: obj.drug,
9
+ display: false,
10
+ },
11
+ dose: {
12
+ value: parseFloat(obj.dose),
13
+ display: false,
14
+ },
15
+ units: {
16
+ value: obj.units,
17
+ display: false,
18
+ },
19
+ route: {
20
+ value: obj.route,
21
+ display: false,
22
+ },
23
+ frequency: {
24
+ value: obj.frequency,
25
+ display: false,
26
+ },
27
+ duration: {
28
+ value: obj.duration,
29
+ display: false,
30
+ },
31
+ };
32
+ }
33
+ export function formatPrescribeAnswer(obj) {
34
+ obj.drug.value = obj.drug.value.toLowerCase().replace(/\s/g, '');
35
+ obj.route.value = obj.route.value.toLowerCase().replace(/\s/g, '');
36
+ if (obj.route.value === 'oral') {
37
+ obj.route.value = 'po';
38
+ }
39
+ else if (obj.route.value.includes('intravenous')) {
40
+ obj.route.value = 'iv';
41
+ }
42
+ else if (obj.route.value.includes('intramuscular')) {
43
+ obj.route.value = 'im';
44
+ }
45
+ else if (obj.route.value.includes('subcutaneous')) {
46
+ obj.route.value = 'sc';
47
+ }
48
+ else if (obj.route.value.includes('inhale')) {
49
+ obj.route.value = 'inh';
50
+ }
51
+ else if (obj.route.value.includes('nebuli')) {
52
+ obj.route.value = 'neb';
53
+ }
54
+ else if (obj.route.value.includes('topical')) {
55
+ obj.route.value = 'top';
56
+ }
57
+ obj.units.value = obj.units.value.toLowerCase().replace(/\s/g, '');
58
+ if (obj.units.value === 'milligram' || obj.units.value === 'milligrams') {
59
+ obj.units.value = 'mg';
60
+ }
61
+ else if (obj.units.value === 'microgram' || obj.units.value === 'micrograms') {
62
+ obj.units.value = 'mcg';
63
+ }
64
+ else if (obj.units.value === 'gram' || obj.units.value === 'grams') {
65
+ obj.units.value = 'g';
66
+ }
67
+ else if (obj.units.value === 'unit') {
68
+ obj.units.value = 'units';
69
+ }
70
+ let m;
71
+ const durationMatches = new Set();
72
+ while ((m = floatRegex.exec(obj.duration.value)) !== null) {
73
+ if (m.index === floatRegex.lastIndex) {
74
+ floatRegex.lastIndex++;
75
+ }
76
+ m.forEach((match) => durationMatches.add(match));
77
+ }
78
+ const durationWords = wordsToNumber(obj.duration.value, false);
79
+ if (durationWords !== 0) {
80
+ durationMatches.add(durationWords.toString());
81
+ }
82
+ obj.duration.value = [...durationMatches].filter((x) => !!x).join(',');
83
+ const orgFrequencyValue = obj.frequency.value.toLowerCase();
84
+ obj.frequency.value = orgFrequencyValue.replace(/\s/g, '');
85
+ const frequencyIncHourly = obj.frequency.value.includes('hour');
86
+ const hourlyFreqInt = parseInt(obj.frequency.value);
87
+ if (obj.frequency.value === 'once-off') {
88
+ obj.frequency.value = 'stat';
89
+ }
90
+ else if (obj.frequency.value.includes('once') || (frequencyIncHourly && hourlyFreqInt === 24)) {
91
+ obj.frequency.value = 'od';
92
+ }
93
+ else if (obj.frequency.value.includes('nightly') || obj.frequency.value === 'on') {
94
+ obj.frequency.value = 'od';
95
+ }
96
+ else if (obj.frequency.value.includes('twice') ||
97
+ (frequencyIncHourly && hourlyFreqInt === 12)) {
98
+ obj.frequency.value = 'bd';
99
+ }
100
+ else if (obj.frequency.value.includes('trice') || (frequencyIncHourly && hourlyFreqInt === 8)) {
101
+ obj.frequency.value = 'tds';
102
+ }
103
+ else if (frequencyIncHourly && hourlyFreqInt === 6) {
104
+ obj.frequency.value = 'qds';
105
+ }
106
+ else if (!ACCEPTED_FREQ.includes(obj.frequency.value)) {
107
+ obj.frequency.value = wordsToNumber(orgFrequencyValue, true).toString();
108
+ }
109
+ return obj;
110
+ }
111
+ const answerRegex = /answer/gi;
112
+ export function correctMark(mark) {
113
+ const data = {
114
+ correct: false,
115
+ incorrect: false,
116
+ correctIndex: -1,
117
+ };
118
+ if (!mark || !mark.mark) {
119
+ return data;
120
+ }
121
+ try {
122
+ const qKeys = Object.keys(mark.question);
123
+ const answerKey = qKeys.find((k) => answerRegex.exec(k)) || 'answer';
124
+ const answer = mark.question[answerKey];
125
+ const flatAnswer = answer[0];
126
+ const flatAttempt = mark.mark[0];
127
+ if (EQuestionType.SINGLE_BEST_ANSWER === mark.question.typeId) {
128
+ const answer = flatAnswer;
129
+ const attempt = flatAttempt;
130
+ if (answer === attempt) {
131
+ data.correct = true;
132
+ }
133
+ else {
134
+ data.incorrect = true;
135
+ }
136
+ }
137
+ else if (EQuestionType.QUESTION_ANSWER === mark.question.typeId) {
138
+ const answer = flatAnswer.dose.toLowerCase().replace(/\s/g, '');
139
+ let attempt = '';
140
+ if (flatAttempt) {
141
+ attempt = flatAttempt.toLowerCase().replace(/\s/g, '');
142
+ }
143
+ if (answer === attempt) {
144
+ data.correct = true;
145
+ }
146
+ else {
147
+ data.incorrect = true;
148
+ }
149
+ }
150
+ else if (EQuestionType.MULTIPLE_ANSWERS === mark.question.typeId) {
151
+ const [answerA, answerB] = answer.map((x) => x.sort());
152
+ let [attemptA, attemptB] = [[], []];
153
+ if (Array.isArray(mark.mark) &&
154
+ mark.mark.length === 2 &&
155
+ Array.isArray(mark.mark[0]) &&
156
+ Array.isArray(mark.mark[1])) {
157
+ [attemptA, attemptB] = mark.mark.map((x) => x.sort());
158
+ }
159
+ if (answerA.length !== attemptA.length || answerB.length !== attemptB.length) {
160
+ data.incorrect = true;
161
+ }
162
+ else if (attemptA.every((x, i) => x === answerA[i]) &&
163
+ attemptB.every((x, i) => x === answerB[i])) {
164
+ data.correct = true;
165
+ }
166
+ else {
167
+ data.incorrect = true;
168
+ }
169
+ }
170
+ else if (EQuestionType.PRESCRIPTION_ANSWER === mark.question.typeId) {
171
+ const answers = answer.map(formatPrescribeAnswer);
172
+ let attempt = formatPrescribeAnswer(mapPrescribeMarkToAnswer({
173
+ drug: '',
174
+ dose: -1,
175
+ units: '',
176
+ route: '',
177
+ frequency: '',
178
+ duration: '',
179
+ }));
180
+ if (typeof flatAttempt === 'object' && Object.keys(flatAttempt).length === 6) {
181
+ attempt = formatPrescribeAnswer(mapPrescribeMarkToAnswer(flatAttempt));
182
+ }
183
+ let foundCorrect = false;
184
+ let index = 0;
185
+ for (const answer of answers) {
186
+ const doseCorrect = answer.dose.display || answer.dose.value === attempt.dose.value;
187
+ const drugCorrect = answer.drug.display || answer.drug.value === attempt.drug.value;
188
+ const durationCorrect = answer.duration.display ||
189
+ answer.duration.value.split(',').includes(attempt.duration.value);
190
+ const frequencyCorrect = answer.frequency.display || answer.frequency.value === attempt.frequency.value;
191
+ const routeCorrect = answer.route.display || answer.route.value === attempt.route.value;
192
+ const unitsCorrect = answer.units.display || answer.units.value === attempt.units.value;
193
+ if (doseCorrect &&
194
+ drugCorrect &&
195
+ durationCorrect &&
196
+ frequencyCorrect &&
197
+ routeCorrect &&
198
+ unitsCorrect) {
199
+ foundCorrect = true;
200
+ break;
201
+ }
202
+ index++;
203
+ }
204
+ if (foundCorrect) {
205
+ data.correct = true;
206
+ data.correctIndex = index;
207
+ }
208
+ else {
209
+ data.incorrect = true;
210
+ }
211
+ }
212
+ }
213
+ catch (e) {
214
+ console.error(e);
215
+ }
216
+ return data;
217
+ }
@@ -0,0 +1,5 @@
1
+ export * from './commonFunctions';
2
+ export * from './lightgallery';
3
+ export * from './offlineLink';
4
+ export * from './uuid4';
5
+ export * from './wordsToNumber';
package/utils/index.js ADDED
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
10
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ __exportStar(require("./commonFunctions"), exports);
14
+ __exportStar(require("./lightgallery"), exports);
15
+ __exportStar(require("./offlineLink"), exports);
16
+ __exportStar(require("./uuid4"), exports);
17
+ __exportStar(require("./wordsToNumber"), exports);
@@ -0,0 +1,5 @@
1
+ export * from './commonFunctions';
2
+ export * from './lightgallery';
3
+ export * from './offlineLink';
4
+ export * from './uuid4';
5
+ export * from './wordsToNumber';
@@ -1,7 +1,9 @@
1
+ import { Platform } from 'react-native';
2
+ import { CacheManager } from 'react-native-expo-image-cache';
1
3
  import { IOsceMarksheet, IOsceStation } from '../models';
2
4
  export interface ILightGalleryCache {
3
- CacheManager: any;
4
- Platform: any;
5
+ CacheManager: typeof CacheManager;
6
+ Platform: typeof Platform;
5
7
  }
6
8
  export declare function lightgalleryOsceResolve(data: IOsceStation, cache?: ILightGalleryCache): Promise<IOsceStation>;
7
9
  export declare function lightgalleryOsceResolve(data: IOsceMarksheet, cache?: ILightGalleryCache): Promise<IOsceMarksheet>;
@@ -67,7 +67,10 @@ const lightgalleryMutation = (text, pictures, cache) => __awaiter(void 0, void 0
67
67
  try {
68
68
  const localPath = yield cache.CacheManager.get(uri, {}).getPath();
69
69
  if (localPath) {
70
- file = cache.Platform.OS === 'ios' ? localPath.replace('file://', '') : localPath;
70
+ file =
71
+ cache.Platform.OS === 'ios'
72
+ ? localPath.replace('file://', '')
73
+ : localPath;
71
74
  }
72
75
  }
73
76
  catch (e) {
@@ -0,0 +1,87 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ const lightgalleryRegex = /\[lightgallery\]/;
11
+ function isOsceMarksheet(data) {
12
+ return 'osceStationId' in data;
13
+ }
14
+ export function lightgalleryOsceResolve(data, cache) {
15
+ return __awaiter(this, void 0, void 0, function* () {
16
+ let station;
17
+ if (isOsceMarksheet(data)) {
18
+ if (!data.osceStation) {
19
+ throw new Error('OsceStation not found on OsceMarksheet');
20
+ }
21
+ station = data.osceStation;
22
+ }
23
+ else {
24
+ station = data;
25
+ }
26
+ const upOsceStation = {
27
+ candidateBrief: station.candidateBrief,
28
+ actorBrief: station.actorBrief,
29
+ examinerBrief: station.examinerBrief,
30
+ explanation: station.explanation,
31
+ };
32
+ if (station.candidatePictures && station.candidatePictures.length > 0) {
33
+ upOsceStation.candidateBrief = yield lightgalleryMutation(upOsceStation.candidateBrief, station.candidatePictures, cache);
34
+ }
35
+ if (station.actorPictures && station.actorPictures.length > 0) {
36
+ upOsceStation.actorBrief = yield lightgalleryMutation(upOsceStation.actorBrief, station.actorPictures, cache);
37
+ }
38
+ if (station.examinerPictures && station.examinerPictures.length > 0) {
39
+ upOsceStation.examinerBrief = yield lightgalleryMutation(upOsceStation.examinerBrief, station.examinerPictures, cache);
40
+ }
41
+ if (station.walkthroughPictures && station.walkthroughPictures.length > 0) {
42
+ upOsceStation.explanation = yield lightgalleryMutation(upOsceStation.explanation, station.walkthroughPictures, cache);
43
+ }
44
+ if (isOsceMarksheet(data)) {
45
+ return Object.assign(Object.assign({}, data), { osceStation: Object.assign(Object.assign({}, data.osceStation), upOsceStation) });
46
+ }
47
+ else {
48
+ return Object.assign(Object.assign({}, data), upOsceStation);
49
+ }
50
+ });
51
+ }
52
+ const lightgalleryMutation = (text, pictures, cache) => __awaiter(void 0, void 0, void 0, function* () {
53
+ var _a;
54
+ if (pictures.length === 0) {
55
+ return text;
56
+ }
57
+ const picturesHTML = [];
58
+ for (const { picture } of pictures) {
59
+ const pic = picture.path;
60
+ const uri = `https://app.quesmed.com/${pic}`;
61
+ let file = '';
62
+ if (cache) {
63
+ try {
64
+ const localPath = yield cache.CacheManager.get(uri, {}).getPath();
65
+ if (localPath) {
66
+ file =
67
+ cache.Platform.OS === 'ios'
68
+ ? localPath.replace('file://', '')
69
+ : localPath;
70
+ }
71
+ }
72
+ catch (e) {
73
+ console.error(e);
74
+ }
75
+ }
76
+ if (((_a = picture.caption) === null || _a === void 0 ? void 0 : _a.length) > 0) {
77
+ picturesHTML.push(`![${file}](${uri} "${picture.caption}")`);
78
+ }
79
+ picturesHTML.push(`![${file}](${uri})`);
80
+ }
81
+ if (lightgalleryRegex.test(text)) {
82
+ return text.replace(lightgalleryRegex, picturesHTML[0]);
83
+ }
84
+ else {
85
+ return text + '\n\n' + picturesHTML[0] + '\n';
86
+ }
87
+ });
@@ -0,0 +1,64 @@
1
+ /// <reference types="node" />
2
+ import { ApolloClient, ApolloLink, MutationOptions, NextLink, Observable, Operation } from '@apollo/client';
3
+ import AsyncStorage from '@react-native-async-storage/async-storage';
4
+ import Localforage from 'localforage';
5
+ export interface IOfflineLinkParam {
6
+ storage: typeof Localforage | typeof AsyncStorage;
7
+ retryInterval?: number;
8
+ sequential?: boolean;
9
+ retryOnServerError?: boolean;
10
+ }
11
+ export declare class OfflineLink extends ApolloLink {
12
+ storage: typeof Localforage | typeof AsyncStorage;
13
+ retryInterval: number;
14
+ sequential: boolean;
15
+ retryOnServerError: boolean;
16
+ queue: Map<string, MutationOptions>;
17
+ prefix: string;
18
+ client?: ApolloClient<any>;
19
+ timeoutId?: NodeJS.Timeout;
20
+ /**
21
+ * storage
22
+ * Provider that will persist the mutation queue. This can be any AsyncStorage compatible storage instance.
23
+ *
24
+ * retryInterval
25
+ * Milliseconds between attempts to retry failed mutations. Defaults to 0, which disables automatic retry.
26
+ *
27
+ * sequential
28
+ * Indicates if the attempts should be retried in order. Defaults to false which retries all failed mutations in parallel.
29
+ *
30
+ * retryOnServerError
31
+ * Indicates if mutations should be reattempted if there are server side errors, useful to retry mutations on session expiration. Defaults to false.
32
+ */
33
+ constructor({ storage, retryInterval, sequential, retryOnServerError, }: IOfflineLinkParam);
34
+ request(operation: Operation, forward: NextLink): Observable<any>;
35
+ /**
36
+ * Obtains the queue of mutations that must be sent to the server.
37
+ * These are kept in a Map to preserve the order of the mutations in the queue.
38
+ */
39
+ getQueue(): Promise<Map<any, any>>;
40
+ /**
41
+ * Persist the queue so mutations can be retried at a later point in time.
42
+ */
43
+ private saveQueue;
44
+ /**
45
+ * Add a mutation attempt to the queue so that it can be retried at a later point in time.
46
+ */
47
+ private add;
48
+ /**
49
+ * Remove a mutation attempt from the queue.
50
+ */
51
+ private remove;
52
+ /**
53
+ * Takes the mutations in the queue and try to send them to the server again.
54
+ */
55
+ sync(): Promise<void>;
56
+ /**
57
+ * Runs sync() after the retryInterval
58
+ */
59
+ private delayedSync;
60
+ /**
61
+ * Configure the link to use Apollo Client and immediately try to sync the queue (if there's anything there).
62
+ */
63
+ setup(client: ApolloClient<any>): Promise<void>;
64
+ }
@@ -0,0 +1,237 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.OfflineLink = void 0;
16
+ const client_1 = require("@apollo/client");
17
+ const uuid4_1 = __importDefault(require("./uuid4"));
18
+ class OfflineLink extends client_1.ApolloLink {
19
+ /**
20
+ * storage
21
+ * Provider that will persist the mutation queue. This can be any AsyncStorage compatible storage instance.
22
+ *
23
+ * retryInterval
24
+ * Milliseconds between attempts to retry failed mutations. Defaults to 0, which disables automatic retry.
25
+ *
26
+ * sequential
27
+ * Indicates if the attempts should be retried in order. Defaults to false which retries all failed mutations in parallel.
28
+ *
29
+ * retryOnServerError
30
+ * Indicates if mutations should be reattempted if there are server side errors, useful to retry mutations on session expiration. Defaults to false.
31
+ */
32
+ constructor({ storage, retryInterval = 0, sequential = false, retryOnServerError = false, }) {
33
+ super();
34
+ this.queue = new Map();
35
+ this.prefix = 'offlineLink:';
36
+ if (!storage) {
37
+ throw new Error('Storage is required, it can be an AsyncStorage compatible storage instance.');
38
+ }
39
+ this.storage = storage;
40
+ this.sequential = sequential;
41
+ this.retryInterval = retryInterval;
42
+ this.retryOnServerError = retryOnServerError;
43
+ }
44
+ request(operation, forward) {
45
+ const context = operation.getContext();
46
+ const { query, variables } = operation || {};
47
+ if (!context.optimisticResponse) {
48
+ // If the mutation does not have an optimisticResponse then we can't defer it
49
+ return forward(operation);
50
+ }
51
+ return new client_1.Observable((observer) => {
52
+ const mutationId = uuid4_1.default.asBase64();
53
+ this.add({
54
+ mutation: query,
55
+ variables,
56
+ optimisticResponse: context.optimisticResponse,
57
+ }, mutationId);
58
+ const subscription = forward(operation).subscribe({
59
+ next: (result) => {
60
+ this.remove(mutationId);
61
+ observer.next(result);
62
+ },
63
+ error: () => {
64
+ // Mutation failed so we try again after a certain amount of time.
65
+ this.delayedSync();
66
+ // Resolve the mutation with the optimistic response so the UI can be updated
67
+ observer.next({
68
+ data: context.optimisticResponse,
69
+ dataPresent: true,
70
+ errors: [],
71
+ });
72
+ // Say we're all done so the UI is re-rendered.
73
+ observer.complete();
74
+ },
75
+ complete: () => observer.complete(),
76
+ });
77
+ return () => {
78
+ subscription.unsubscribe();
79
+ };
80
+ });
81
+ }
82
+ /**
83
+ * Obtains the queue of mutations that must be sent to the server.
84
+ * These are kept in a Map to preserve the order of the mutations in the queue.
85
+ */
86
+ getQueue() {
87
+ return __awaiter(this, void 0, void 0, function* () {
88
+ const map = new Map();
89
+ try {
90
+ const storedMutations = yield this.storage.getItem(this.prefix + 'Mutations');
91
+ if (!storedMutations) {
92
+ return map;
93
+ }
94
+ const mutationIds = storedMutations.split(',');
95
+ for (const mutationId of mutationIds) {
96
+ const storedMutation = yield this.storage.getItem(this.prefix + mutationId);
97
+ if (!storedMutation) {
98
+ continue;
99
+ }
100
+ map.set(mutationId, JSON.parse(storedMutation));
101
+ }
102
+ return map;
103
+ }
104
+ catch (e) {
105
+ console.error(e);
106
+ return map;
107
+ }
108
+ });
109
+ }
110
+ /**
111
+ * Persist the queue so mutations can be retried at a later point in time.
112
+ */
113
+ saveQueue(mutationId, mutation) {
114
+ return __awaiter(this, void 0, void 0, function* () {
115
+ try {
116
+ if (mutationId && mutation) {
117
+ yield this.storage.setItem(this.prefix + mutationId, JSON.stringify(mutation));
118
+ }
119
+ yield this.storage.setItem(this.prefix + 'Mutations', [...this.queue.keys()].join());
120
+ }
121
+ catch (e) {
122
+ console.error(e);
123
+ }
124
+ });
125
+ }
126
+ /**
127
+ * Add a mutation attempt to the queue so that it can be retried at a later point in time.
128
+ */
129
+ add(item, mutationId) {
130
+ return __awaiter(this, void 0, void 0, function* () {
131
+ // We give the mutation attempt a random id so that it is easy to remove when needed (in sync loop)
132
+ if (!mutationId) {
133
+ mutationId = uuid4_1.default.asBase64();
134
+ }
135
+ this.queue.set(mutationId, item);
136
+ yield this.saveQueue(mutationId, item);
137
+ return mutationId;
138
+ });
139
+ }
140
+ /**
141
+ * Remove a mutation attempt from the queue.
142
+ */
143
+ remove(mutationId) {
144
+ return __awaiter(this, void 0, void 0, function* () {
145
+ this.queue.delete(mutationId);
146
+ yield this.storage.removeItem(this.prefix + mutationId);
147
+ yield this.saveQueue();
148
+ });
149
+ }
150
+ /**
151
+ * Takes the mutations in the queue and try to send them to the server again.
152
+ */
153
+ sync() {
154
+ return __awaiter(this, void 0, void 0, function* () {
155
+ if (!this.client) {
156
+ throw new Error('Offline Link not setup with ApolloClient');
157
+ }
158
+ if (this.queue.size === 0) {
159
+ return;
160
+ }
161
+ const attempts = Array.from(this.queue);
162
+ if (this.sequential) {
163
+ for (const [mutationId, attempt] of attempts) {
164
+ let serverProcessed = false;
165
+ try {
166
+ yield this.client.mutate(Object.assign(Object.assign({}, attempt), { optimisticResponse: undefined }));
167
+ serverProcessed = true;
168
+ }
169
+ catch (e) {
170
+ const err = e;
171
+ const networkError = err.networkError;
172
+ if (!this.retryOnServerError && (networkError === null || networkError === void 0 ? void 0 : networkError.response)) {
173
+ // There are GraphQL errors, which means the server processed the request so we can remove the mutation from the queue
174
+ serverProcessed = true;
175
+ }
176
+ }
177
+ if (serverProcessed) {
178
+ try {
179
+ yield this.remove(mutationId);
180
+ }
181
+ catch (e) {
182
+ console.error(e);
183
+ }
184
+ }
185
+ }
186
+ }
187
+ else {
188
+ yield Promise.all(attempts.map(([mutationId, attempt]) => __awaiter(this, void 0, void 0, function* () {
189
+ var _a;
190
+ let serverProcessed = false;
191
+ try {
192
+ yield ((_a = this.client) === null || _a === void 0 ? void 0 : _a.mutate(Object.assign(Object.assign({}, attempt), { optimisticResponse: undefined })));
193
+ serverProcessed = true;
194
+ }
195
+ catch (e) {
196
+ const err = e;
197
+ const networkError = err.networkError;
198
+ if (!this.retryOnServerError && (networkError === null || networkError === void 0 ? void 0 : networkError.response)) {
199
+ serverProcessed = true;
200
+ }
201
+ }
202
+ if (serverProcessed) {
203
+ yield this.remove(mutationId);
204
+ }
205
+ })));
206
+ }
207
+ // Remaining mutations in the queue are persisted
208
+ yield this.saveQueue();
209
+ if (this.queue.size !== 0 || this.retryInterval !== 0) {
210
+ this.delayedSync();
211
+ }
212
+ });
213
+ }
214
+ /**
215
+ * Runs sync() after the retryInterval
216
+ */
217
+ delayedSync() {
218
+ if (this.retryInterval === 0) {
219
+ return;
220
+ }
221
+ if (this.timeoutId) {
222
+ clearTimeout(this.timeoutId);
223
+ }
224
+ this.timeoutId = setTimeout(() => this.sync, this.retryInterval);
225
+ }
226
+ /**
227
+ * Configure the link to use Apollo Client and immediately try to sync the queue (if there's anything there).
228
+ */
229
+ setup(client) {
230
+ return __awaiter(this, void 0, void 0, function* () {
231
+ this.client = client;
232
+ this.queue = yield this.getQueue();
233
+ yield this.sync();
234
+ });
235
+ }
236
+ }
237
+ exports.OfflineLink = OfflineLink;