Files
threshold-ui/addons/api/addon/handlers/indexed-db-handler.js
T
Zhihe LiandLisbet Alvarez d72fa0d6e3 feat: 🎸 Add indexedDb parser (#2068)
* feat: 🎸 Add indexedDb parser

✅ Closes: https://hashicorp.atlassian.net/browse/ICU-12164

---------

Co-authored-by: Lisbet Alvarez <lisbet.alvarez@hashicorp.com>
2024-04-12 14:55:33 -04:00

126 lines
4.1 KiB
JavaScript

import { modelIndexes } from 'api/services/indexed-db';
import { inject as service } from '@ember/service';
import { getOwner, setOwner } from '@ember/application';
import { queryIndexedDb } from '../utils/indexed-db-query';
import { paginateResults } from '../utils/paginate-results';
/**
* Handler to sit in front of the API request layer
* so we can handle any caching with indexedDb first
*/
export default class IndexedDbHandler {
@service indexedDb;
constructor(context) {
setOwner(this, getOwner(context));
}
async request(context, next) {
switch (context.request.op) {
case 'query': {
const { store, data } = context.request;
const { type, query, options: { pushToStore = true } = {} } = data;
const supportedModels = Object.keys(modelIndexes);
const { db: indexedDb } = this.indexedDb ?? {};
// Go through normal flow if we don't yet support the model
// or if we don't have an indexedDb instance
if (!supportedModels.includes(type) || !indexedDb) {
return next(context.request);
}
let { page, pageSize, query: queryObj, ...remainingQuery } = query;
const listToken = await indexedDb.token.get(type);
const adapter = store.adapterFor(type);
const schema = store.modelFor(type);
let payload;
try {
payload = await adapter.query(store, schema, {
...remainingQuery,
scope_id: 'global',
recursive: true,
list_token: listToken?.token,
});
} catch (e) {
// If we get any error that's not a rate limiting error,
// assume the token is not good anymore. We'll delete the token and
// clear the DB and try again without a token.
// TODO: Probably should check for 400 and bad token error specifically instead?
if (e?.errors[0].status !== 429) {
await indexedDb[type].clear();
await indexedDb.token.delete(type);
payload = await adapter.query(store, schema, {
...remainingQuery,
scope_id: 'global',
recursive: true,
list_token: null,
});
} else {
throw e;
}
}
// Store the token we just got back from the payload
await indexedDb.token.put({
id: type,
token: payload.list_token,
});
// Remove any records from the DB if the API indicates they've been deleted
if (payload.removed_ids?.length > 0) {
await indexedDb[type].bulkDelete(payload.removed_ids);
}
const serializer = store.serializerFor(type);
const normalizedPayload = serializer.normalizeResponse(
store,
schema,
payload,
null,
'query',
);
const { data: payloadData } = normalizedPayload;
// Store the new data we just got back from the API refresh
const items = payloadData.map((datum) =>
this.indexedDb.normalizeData(datum, true),
);
await indexedDb[type].bulkPut(items);
const indexedDbResults = await queryIndexedDb(
indexedDb,
type,
queryObj,
);
const dbRecords = paginateResults(indexedDbResults, page, pageSize).map(
this.indexedDb.normalizeData,
);
// Return the raw data if we don't push to the store.
let records = dbRecords.map((record) => ({
...record.attributes,
id: record.id,
}));
if (pushToStore) {
records = store.push({ data: dbRecords });
}
// Set a meta property on the array to store the total items of the results.
// This isn't conventional but is better than returning an ArrayProxy
// or EmberArray since the ember store query method asserts it has to be an array
// so we can't just return an object.
records.meta = { totalItems: indexedDbResults.length };
return records;
}
default:
return next(context.request);
}
}
}