Skip to content

Commit 1dc0809

Browse files
committed
[WIP] feat(api): improve cache resolution & logging
1 parent 39530b6 commit 1dc0809

File tree

2 files changed

+135
-76
lines changed

2 files changed

+135
-76
lines changed

dist/index.js

+74-44
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ var OfflineFirstAPI = (function () {
7373
}
7474
OfflineFirstAPI.prototype.fetch = function (service, options) {
7575
return __awaiter(this, void 0, void 0, function () {
76-
var serviceDefinition, fullPath, middlewares, fetchOptions, fetchHeaders, shouldCache, requestId, expiration, _sha, expirationDelay, cachedData, parsedRes, res, err_1;
76+
var serviceDefinition, fullPath, middlewares, fetchOptions, fetchHeaders, shouldCache, requestId, expiration, _sha, expirationDelay, cachedData, parsedResponseData, res, err_1;
7777
return __generator(this, function (_a) {
7878
switch (_a.label) {
7979
case 0:
@@ -84,7 +84,7 @@ var OfflineFirstAPI = (function () {
8484
fullPath = this._constructPath(serviceDefinition, options);
8585
_a.label = 1;
8686
case 1:
87-
_a.trys.push([1, 9, , 10]);
87+
_a.trys.push([1, 8, , 9]);
8888
return [4 /*yield*/, this._applyMiddlewares(serviceDefinition, options)];
8989
case 2:
9090
middlewares = _a.sent();
@@ -94,43 +94,51 @@ var OfflineFirstAPI = (function () {
9494
!(serviceDefinition.disableCache || (options && options.disableCache));
9595
requestId = void 0;
9696
expiration = void 0;
97-
if (!shouldCache) return [3 /*break*/, 4];
9897
_sha = new sha('SHA-1', 'TEXT');
9998
_sha.update(fullPath + ":" + (fetchHeaders ? 'headersOnly' : '') + ":" + JSON.stringify(fetchOptions));
10099
requestId = _sha.getHash('HEX');
101100
expirationDelay = (options && options.expiration) || serviceDefinition.expiration || this._APIOptions.cacheExpiration;
102101
expiration = Date.now() + expirationDelay;
103-
return [4 /*yield*/, this._getCachedData(service, requestId)];
102+
return [4 /*yield*/, this._getCachedData(service, requestId, fullPath)];
104103
case 3:
105104
cachedData = _a.sent();
106-
if (cachedData) {
107-
return [2 /*return*/, cachedData];
105+
if (cachedData.success && cachedData.fresh) {
106+
this._log("Using fresh cache for " + fullPath);
107+
return [2 /*return*/, cachedData.data];
108108
}
109-
_a.label = 4;
110-
case 4:
111109
// Network fetch
112110
this._logNetwork(serviceDefinition, fetchHeaders, options);
113111
this._log('full URL for request', fullPath);
114112
this._log('full fetch options for request', fetchOptions);
115-
parsedRes = void 0;
116-
return [4 /*yield*/, fetch(fullPath, fetchOptions)];
117-
case 5:
113+
parsedResponseData = void 0;
114+
return [4 /*yield*/, this._fetch(fullPath, fetchOptions)];
115+
case 4:
118116
res = _a.sent();
117+
if (!res.success) {
118+
if (cachedData.success && cachedData.data) {
119+
this._log("Using stale cache for " + fullPath + " (network request failed)");
120+
return [2 /*return*/, cachedData.data];
121+
}
122+
else {
123+
throw new Error("Cannot fetch data for " + service + " online, no cache either.");
124+
}
125+
}
119126
this._log('raw network response', res);
120-
if (!fetchHeaders) return [3 /*break*/, 6];
121-
parsedRes = res.headers && res.headers.map ? res.headers.map : {};
122-
return [3 /*break*/, 8];
123-
case 6: return [4 /*yield*/, res.json()];
127+
if (!fetchHeaders) return [3 /*break*/, 5];
128+
parsedResponseData = res.data.headers && res.data.headers.map ? res.data.headers.map : {};
129+
return [3 /*break*/, 7];
130+
case 5: return [4 /*yield*/, res.data.json()];
131+
case 6:
132+
parsedResponseData = _a.sent();
133+
_a.label = 7;
124134
case 7:
125-
parsedRes = _a.sent();
126-
_a.label = 8;
135+
res.data.ok && shouldCache && this._cache(service, requestId, parsedResponseData, expiration);
136+
this._log('parsed network response', parsedResponseData);
137+
return [2 /*return*/, parsedResponseData];
127138
case 8:
128-
res.ok && shouldCache && this._cache(service, requestId, parsedRes, expiration);
129-
return [2 /*return*/, parsedRes];
130-
case 9:
131139
err_1 = _a.sent();
132140
throw new Error(err_1);
133-
case 10: return [2 /*return*/];
141+
case 9: return [2 /*return*/];
134142
}
135143
});
136144
});
@@ -168,9 +176,27 @@ var OfflineFirstAPI = (function () {
168176
this._APIDriver = driver;
169177
this._log('custom driver set');
170178
};
179+
OfflineFirstAPI.prototype._fetch = function (url, options) {
180+
return __awaiter(this, void 0, void 0, function () {
181+
var _a, err_3;
182+
return __generator(this, function (_b) {
183+
switch (_b.label) {
184+
case 0:
185+
_b.trys.push([0, 2, , 3]);
186+
_a = { success: true };
187+
return [4 /*yield*/, fetch(url, options)];
188+
case 1: return [2 /*return*/, (_a.data = _b.sent(), _a)];
189+
case 2:
190+
err_3 = _b.sent();
191+
return [2 /*return*/, { success: false }];
192+
case 3: return [2 /*return*/];
193+
}
194+
});
195+
});
196+
};
171197
OfflineFirstAPI.prototype._cache = function (service, requestId, response, expiration) {
172198
return __awaiter(this, void 0, void 0, function () {
173-
var err_3;
199+
var err_4;
174200
return __generator(this, function (_a) {
175201
switch (_a.label) {
176202
case 0:
@@ -187,50 +213,54 @@ var OfflineFirstAPI = (function () {
187213
this._log("Updated cache for request " + requestId);
188214
return [2 /*return*/, true];
189215
case 4:
190-
err_3 = _a.sent();
216+
err_4 = _a.sent();
191217
throw new Error("Error while caching API response for " + requestId);
192218
case 5: return [2 /*return*/];
193219
}
194220
});
195221
});
196222
};
197-
OfflineFirstAPI.prototype._getCachedData = function (service, requestId) {
223+
OfflineFirstAPI.prototype._getCachedData = function (service, requestId, fullPath) {
198224
return __awaiter(this, void 0, void 0, function () {
199-
var serviceDictionary, expiration, cachedData, err_4;
225+
var serviceDictionary, expiration, rawCachedData, parsedCachedData, err_5;
200226
return __generator(this, function (_a) {
201227
switch (_a.label) {
202228
case 0: return [4 /*yield*/, this._APIDriver.getItem(this._getServiceDictionaryKey(service))];
203229
case 1:
204230
serviceDictionary = _a.sent();
205231
serviceDictionary = JSON.parse(serviceDictionary) || {};
206232
expiration = serviceDictionary[requestId];
207-
if (!expiration) return [3 /*break*/, 8];
208-
this._log(requestId + " already cached, expiring at : " + expiration);
209-
if (!(expiration > Date.now())) return [3 /*break*/, 6];
233+
if (!expiration) return [3 /*break*/, 6];
234+
this._log(fullPath + " already cached, expiring at : " + expiration);
210235
_a.label = 2;
211236
case 2:
212237
_a.trys.push([2, 4, , 5]);
213238
return [4 /*yield*/, this._APIDriver.getItem(this._getCacheObjectKey(requestId))];
214239
case 3:
215-
cachedData = _a.sent();
216-
return [2 /*return*/, JSON.parse(cachedData)];
240+
rawCachedData = _a.sent();
241+
parsedCachedData = JSON.parse(rawCachedData);
242+
if (expiration > Date.now()) {
243+
return [2 /*return*/, { success: true, fresh: true, data: parsedCachedData }];
244+
}
245+
else {
246+
return [2 /*return*/, { success: true, fresh: false, data: parsedCachedData }];
247+
}
248+
return [3 /*break*/, 5];
217249
case 4:
218-
err_4 = _a.sent();
219-
throw new Error(err_4);
250+
err_5 = _a.sent();
251+
throw new Error(err_5);
220252
case 5: return [3 /*break*/, 7];
221-
case 6: return [2 /*return*/, false];
222-
case 7: return [3 /*break*/, 9];
223-
case 8:
224-
this._log(requestId + " not yet cached");
225-
return [2 /*return*/, false];
226-
case 9: return [2 /*return*/];
253+
case 6:
254+
this._log(fullPath + " not yet cached");
255+
return [2 /*return*/, { success: false }];
256+
case 7: return [2 /*return*/];
227257
}
228258
});
229259
});
230260
};
231261
OfflineFirstAPI.prototype._addKeyToServiceDictionary = function (service, requestId, expiration) {
232262
return __awaiter(this, void 0, void 0, function () {
233-
var serviceDictionaryKey, dictionary, err_5;
263+
var serviceDictionaryKey, dictionary, err_6;
234264
return __generator(this, function (_a) {
235265
switch (_a.label) {
236266
case 0:
@@ -249,8 +279,8 @@ var OfflineFirstAPI = (function () {
249279
this._APIDriver.setItem(serviceDictionaryKey, JSON.stringify(dictionary));
250280
return [2 /*return*/, true];
251281
case 2:
252-
err_5 = _a.sent();
253-
throw new Error(err_5);
282+
err_6 = _a.sent();
283+
throw new Error(err_6);
254284
case 3: return [2 /*return*/];
255285
}
256286
});
@@ -264,7 +294,7 @@ var OfflineFirstAPI = (function () {
264294
};
265295
OfflineFirstAPI.prototype._applyMiddlewares = function (serviceDefinition, options) {
266296
return __awaiter(this, void 0, void 0, function () {
267-
var middlewares, resolvedMiddlewares, err_6;
297+
var middlewares, resolvedMiddlewares, err_7;
268298
return __generator(this, function (_a) {
269299
switch (_a.label) {
270300
case 0:
@@ -279,8 +309,8 @@ var OfflineFirstAPI = (function () {
279309
resolvedMiddlewares = _a.sent();
280310
return [2 /*return*/, _merge.apply(void 0, resolvedMiddlewares)];
281311
case 3:
282-
err_6 = _a.sent();
283-
throw new Error("Error while applying middlewares for " + serviceDefinition.path + " : " + err_6);
312+
err_7 = _a.sent();
313+
throw new Error("Error while applying middlewares for " + serviceDefinition.path + " : " + err_7);
284314
case 4: return [3 /*break*/, 6];
285315
case 5: return [2 /*return*/, {}];
286316
case 6: return [2 /*return*/];

src/index.ts

+61-32
Original file line numberDiff line numberDiff line change
@@ -56,38 +56,47 @@ export default class OfflineFirstAPI {
5656
let requestId;
5757
let expiration;
5858

59-
if (shouldCache) {
60-
const _sha = new sha('SHA-1', 'TEXT');
61-
_sha.update(`${fullPath}:${fetchHeaders ? 'headersOnly': ''}:${JSON.stringify(fetchOptions)}`);
62-
requestId = _sha.getHash('HEX');
63-
64-
const expirationDelay =
65-
(options && options.expiration) || serviceDefinition.expiration || this._APIOptions.cacheExpiration;
66-
expiration = Date.now() + expirationDelay;
67-
const cachedData = await this._getCachedData(service, requestId);
68-
69-
if (cachedData) {
70-
return cachedData;
71-
}
59+
const _sha = new sha('SHA-1', 'TEXT');
60+
_sha.update(`${fullPath}:${fetchHeaders ? 'headersOnly' : ''}:${JSON.stringify(fetchOptions)}`);
61+
requestId = _sha.getHash('HEX');
62+
63+
const expirationDelay =
64+
(options && options.expiration) || serviceDefinition.expiration || this._APIOptions.cacheExpiration;
65+
expiration = Date.now() + expirationDelay;
66+
const cachedData = await this._getCachedData(service, requestId, fullPath);
67+
68+
if (cachedData.success && cachedData.fresh) {
69+
this._log(`Using fresh cache for ${fullPath}`);
70+
return cachedData.data;
7271
}
7372

7473
// Network fetch
7574
this._logNetwork(serviceDefinition, fetchHeaders, options);
7675
this._log('full URL for request', fullPath);
7776
this._log('full fetch options for request', fetchOptions);
78-
let parsedRes;
79-
const res = await fetch(fullPath, fetchOptions);
80-
this._log('raw network response', res);
77+
let parsedResponseData;
78+
const res = await this._fetch(fullPath, fetchOptions);
79+
80+
if (!res.success) {
81+
if (cachedData.success && cachedData.data) {
82+
this._log(`Using stale cache for ${fullPath} (network request failed)`);
83+
return cachedData.data;
84+
} else {
85+
throw new Error(`Cannot fetch data for ${service} online, no cache either.`);
86+
}
87+
}
8188

89+
this._log('raw network response', res);
8290
if (fetchHeaders) {
83-
parsedRes = res.headers && res.headers.map ? res.headers.map : {};
91+
parsedResponseData = res.data.headers && res.data.headers.map ? res.data.headers.map : {};
8492
} else {
85-
parsedRes = await res.json();
93+
parsedResponseData = await res.data.json();
8694
}
8795

88-
res.ok && shouldCache && this._cache(service, requestId, parsedRes, expiration);
96+
res.data.ok && shouldCache && this._cache(service, requestId, parsedResponseData, expiration);
8997

90-
return parsedRes;
98+
this._log('parsed network response', parsedResponseData);
99+
return parsedResponseData;
91100
} catch (err) {
92101
throw new Error(err);
93102
}
@@ -123,6 +132,14 @@ export default class OfflineFirstAPI {
123132
this._log('custom driver set');
124133
}
125134

135+
private async _fetch (url: string, options?: any): Promise<IFetchResponse> {
136+
try {
137+
return { success: true, data: await fetch(url, options) };
138+
} catch (err) {
139+
return { success: false }
140+
}
141+
}
142+
126143
private async _cache (service: string, requestId: string, response: any, expiration: number): Promise<void|boolean> {
127144
this._log(`Caching ${requestId} ...`);
128145
try {
@@ -135,26 +152,27 @@ export default class OfflineFirstAPI {
135152
}
136153
}
137154

138-
private async _getCachedData (service: string, requestId: string): Promise<object|boolean> {
155+
private async _getCachedData (service: string, requestId: string, fullPath: string): Promise<ICachedData> {
139156
let serviceDictionary = await this._APIDriver.getItem(this._getServiceDictionaryKey(service));
140157
serviceDictionary = JSON.parse(serviceDictionary) || {};
141158

142159
const expiration = serviceDictionary[requestId];
143160
if (expiration) {
144-
this._log(`${requestId} already cached, expiring at : ${expiration}`)
145-
if (expiration > Date.now()) {
146-
try {
147-
const cachedData = await this._APIDriver.getItem(this._getCacheObjectKey(requestId));
148-
return JSON.parse(cachedData);
149-
} catch (err) {
150-
throw new Error(err);
161+
this._log(`${fullPath} already cached, expiring at : ${expiration}`)
162+
try {
163+
const rawCachedData = await this._APIDriver.getItem(this._getCacheObjectKey(requestId));
164+
const parsedCachedData = JSON.parse(rawCachedData);
165+
if (expiration > Date.now()) {
166+
return { success: true, fresh: true, data: parsedCachedData };
167+
} else {
168+
return { success: true, fresh: false, data: parsedCachedData };
151169
}
152-
} else {
153-
return false;
170+
} catch (err) {
171+
throw new Error(err);
154172
}
155173
} else {
156-
this._log(`${requestId} not yet cached`);
157-
return false;
174+
this._log(`${fullPath} not yet cached`);
175+
return { success: false };
158176
}
159177
}
160178

@@ -317,6 +335,17 @@ interface IFetchOptions extends IAPIService {
317335
fetchOptions?: any;
318336
};
319337

338+
interface IFetchResponse {
339+
success: boolean;
340+
data?: any;
341+
}
342+
343+
interface ICachedData {
344+
success: boolean;
345+
data?: any;
346+
fresh?: boolean;
347+
}
348+
320349
interface IAPIDriver {
321350
getItem(key: string, callback?: (error?: Error, result?: string) => void);
322351
setItem(key: string, value: string, callback?: (error?: Error) => void);

0 commit comments

Comments
 (0)