@yoroi/common
Version:
The Common package of Yoroi SDK
104 lines (98 loc) • 3.78 kB
JavaScript
;
import { Api } from '@yoroi/types';
import { freeze } from 'immer';
import { isRight } from '../utils/monads';
import { hasEntryValue } from '../utils/predicates';
import { cacheResolveRecordsSource } from './cache-resolve-records-source';
export const cacheManageMultiRequest = async ({
cachedInfosWithoutRecord,
ids,
request,
persistance,
unknownRecordFactory
}) => {
// resolve the ids to be fetched and the ones that should be read from cache
const {
toFetch,
fromCache
} = cacheResolveRecordsSource({
ids,
cachedInfosWithoutRecord
});
let updatedIds = [];
let toSaveRevalidatingCache = new Map();
let toRevalidateCache = new Map();
let toResolveLocally = new Set(fromCache);
let cachedRecords = new Map();
let unknownIds = [];
let unknownEmptyRecords = [];
let toSaveNewUnknownRecord = new Map();
let toSaveNewFromApi = new Map();
let recordsFromApi = {};
if (toFetch.length > 0) {
const apiResponse = await request(toFetch);
// if the request fails, we will show unknown tokens and wont block the user
if (isRight(apiResponse)) recordsFromApi = apiResponse.value.data;
}
// to make same request time for all records, to avoid ms of difference
const baseDate = Date.now();
toFetch.forEach(([id]) => {
const recordFromApi = recordsFromApi[id];
if (!recordFromApi) {
toResolveLocally.add(id);
return;
}
const [statusCode] = recordFromApi;
// when not-modified add to revalidate cache by updating expires
if (statusCode === Api.HttpStatusCode.NotModified) {
const [, maxAge] = recordFromApi;
toRevalidateCache.set(id, baseDate + maxAge * 1_000);
return;
}
// when internal server error, it skips the record
if (statusCode === Api.HttpStatusCode.InternalServerError) {
toResolveLocally.add(id);
return;
}
const [, record, eTag, maxAge] = recordFromApi;
toSaveNewFromApi.set(id, {
hash: eTag,
expires: baseDate + maxAge * 1_000,
record
});
});
updatedIds = [...toSaveNewFromApi.keys()];
// if there are empties (not-found) by the api and to refresh cache (not-modified)
if (toResolveLocally.size > 0 || toRevalidateCache.size > 0) {
const idsToReadFromPersistance = [...toResolveLocally.keys(), ...toRevalidateCache.keys()];
// WARN: should be the only reading operation
cachedRecords = new Map(persistance.read(idsToReadFromPersistance).filter(hasEntryValue));
toRevalidateCache.forEach((expires, id) => {
const recordFromApi = cachedRecords.get(id);
if (recordFromApi != null) toSaveRevalidatingCache.set(id, {
...recordFromApi,
expires
});
});
// ids not found at this point should create an unknown record if factory is provided
unknownIds = idsToReadFromPersistance.filter(id => !cachedRecords.has(id));
if (unknownRecordFactory) {
unknownIds.map(id => [id, unknownRecordFactory(id)]).forEach(([id, record]) => toSaveNewUnknownRecord.set(id, record));
} else {
unknownEmptyRecords = unknownIds.map(id => [id, null]);
}
}
// WARN: should be the only writing operation
const toSave = [...toSaveNewFromApi.entries(), ...toSaveRevalidatingCache.entries(), ...toSaveNewUnknownRecord.entries()];
if (toSave.length > 0) persistance.save(toSave);
const result = freeze({
records: new Map([...cachedRecords.entries(), ...toSave, ...unknownEmptyRecords]),
updatedIds,
unknownIds,
revalidatedIds: [...toSaveRevalidatingCache.keys()],
fromCacheIds: fromCache.filter(id => cachedRecords.has(id)),
isInvalidated: updatedIds.length > 0 || toSaveNewUnknownRecord.size > 0
});
return result;
};
//# sourceMappingURL=cache-manage-multi-request.js.map