Typescript Cache Helper

Date: 2019-07-03
import { dateAdd } from "./tsHelpers";
import { appDependencies } from "../domain/AppDependencies";

export interface ICachedSection<T> {
    key: string;
    minutes: number;
    forceGenerate?: boolean;
    fetch: () => PromiseLike<T>;
    generate: () => PromiseLike<T>;
}

async function storeGenerate<T>(key: string, fn: () => PromiseLike<T>) {
    const data = await fn();
    appDependencies.ILocalRepository.store(key, new Date().getTime());
    return data;
}

export class CacheHelper {
    static async section<T>(cs: ICachedSection<T>) {
        const now = new Date();
        if (cs.forceGenerate) {
            return await storeGenerate(cs.key, cs.generate);
        }
        const data = await cs.fetch();
        if (Array.isArray(data) && data["length"] === 0) {
            return await storeGenerate(cs.key, cs.generate);
        }

        const lastGenerated = new Date(Number(appDependencies.ILocalRepository.getSync(cs.key)) || 0);
        if (lastGenerated < dateAdd(now, -(cs.minutes * 1000))) {
            storeGenerate(cs.key, cs.generate); // return old data, do not await
        }
        return data;
    }
}
import Dexie from "dexie";
import { IDebtorOrder } from "src/app/domain/models/DebtorOrder";
import { IInventoryItem } from "src/app/domain/models/IInventoryItem";
import { ICulture } from "../../domain/models/ICulture";
import { ICountry } from "src/app/domain/models/ICountry";


class DexiePlug extends Dexie {
    // Declare implicit table properties.
    // (just to inform Typescript. Instanciated by Dexie in stores() method)
    inventoryItems: Dexie.Table<IInventoryItem, string>; // number = type of the primkey
    debtorOrderHistory: Dexie.Table<IDebtorOrder, number>;
    languages: Dexie.Table<ICulture, string>;
    countries: Dexie.Table<ICountry, string>;
    // ...other tables goes here...

    constructor () {
        super("AppDatabase");
        this.version(1).stores({
            inventoryItems: "id, brand",
            debtorOrderHistory: "orderNumber, deliveryDate",
            languages: "cultureCode",
            countries: "name"
        });

        this.version(2).stores({
            debtorOrderHistory: "orderNumber, deliveryDate, createdAt"
        });

        // The following line is needed if your typescript
        // is compiled using babel instead of tsc:
        this.inventoryItems = this.table("inventoryItems");
        this.debtorOrderHistory = this.table("debtorOrderHistory");
        this.languages = this.table("languages");
        this.countries = this.table("countries");
    }
}

// tslint:disable-next-line: naming-convention
export const dexiePlug = new DexiePlug();
import { CacheHelper } from "src/app/helpers/CacheHelper";
import { appDomain } from "../../domain/AppDomain";
import { IInventoryItem } from "../../domain/models/IInventoryItem";
import { IInventoryRepository } from "../../domain/ports/secondary/IInventoryRepository";
import { dexiePlug } from "../DexieAdapter/DexiePlug";
import { restApiHelper } from "../helpers/RestApiHelper";


const getDefaultCurrency = (itemCurrencyCode) => itemCurrencyCode === "-" ? appDomain.IGetCurrentSession.getSession().getCompanyCurrencyCode() : itemCurrencyCode;

const webApiUrl = (window as any).config.WebApiUrl.replace(/\/$/g, "");


const getImages = (companyId: string, tableRowId: string, imageCount: number) => {
    return Array(imageCount).fill(null)
        .map((_, i) =>  webApiUrl + `/api/InventoryItem/image/${companyId}/${tableRowId}/${i}`);
};

const inventoryItemMap = (x: any): IInventoryItem => ({
    id: x.id,
    name: x.name,
    salesPrice: x.salesPrice,
    brand: x.brandName,
    cartons: x.cartons,
    available: x.available || 0,
    salesUnit: x.salesUnit,
    nlRegistrationNumber: x.nlRegistrationNumber,
    isAvailable: x.available > 0,
    currencyCode: getDefaultCurrency(x.currencyCode),
    tableRowId: x.tableRowId,
    imageCount: x.imageCount
});

export class InventoryRepository implements IInventoryRepository {
    async getById(id: string): Task<IInventoryItem> {
        return await dexiePlug.inventoryItems.get(id);
    }

    async getAllInventoryItems(reload = false): Task<IInventoryItem[]> {
        return CacheHelper.section<IInventoryItem[]>({
            key: "InventoryItems-LastUpdate",
            minutes: 60,
            forceGenerate: reload,
            fetch: async () => await dexiePlug.inventoryItems.toArray(),
            generate: async () => {
                const data = await restApiHelper.getData("api/inventoryitem/AllWithNlRegistration");

                const companyId = appDomain.IGetCurrentSession.getSession().getCompanyId();
                const invItems  = data.map(inventoryItemMap);
                invItems.forEach(inv => inv.images = getImages(companyId, inv.tableRowId, inv.imageCount));
                await dexiePlug.inventoryItems.bulkPut(invItems);
                return data;
            }
        });
    }
}
23060cookie-checkTypescript Cache Helper