{"id":2306,"date":"2019-07-03T15:22:03","date_gmt":"2019-07-03T14:22:03","guid":{"rendered":"https:\/\/solidt.eu\/site\/?p=2306"},"modified":"2019-08-12T08:58:40","modified_gmt":"2019-08-12T07:58:40","slug":"typescript-cache-helper","status":"publish","type":"post","link":"https:\/\/solidt.eu\/site\/typescript-cache-helper\/","title":{"rendered":"Typescript Cache Helper"},"content":{"rendered":"\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import { dateAdd } from \".\/tsHelpers\";\nimport { appDependencies } from \"..\/domain\/AppDependencies\";\n\nexport interface ICachedSection&lt;T> {\n    key: string;\n    minutes: number;\n    forceGenerate?: boolean;\n    fetch: () => PromiseLike&lt;T>;\n    generate: () => PromiseLike&lt;T>;\n}\n\nasync function storeGenerate&lt;T>(key: string, fn: () => PromiseLike&lt;T>) {\n    const data = await fn();\n    appDependencies.ILocalRepository.store(key, new Date().getTime());\n    return data;\n}\n\nexport class CacheHelper {\n    static async section&lt;T>(cs: ICachedSection&lt;T>) {\n        const now = new Date();\n        if (cs.forceGenerate) {\n            return await storeGenerate(cs.key, cs.generate);\n        }\n        const data = await cs.fetch();\n        if (Array.isArray(data) &amp;&amp; data[\"length\"] === 0) {\n            return await storeGenerate(cs.key, cs.generate);\n        }\n\n        const lastGenerated = new Date(Number(appDependencies.ILocalRepository.getSync(cs.key)) || 0);\n        if (lastGenerated &lt; dateAdd(now, -(cs.minutes * 1000))) {\n            storeGenerate(cs.key, cs.generate); \/\/ return old data, do not await\n        }\n        return data;\n    }\n}\n<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import Dexie from \"dexie\";\nimport { IDebtorOrder } from \"src\/app\/domain\/models\/DebtorOrder\";\nimport { IInventoryItem } from \"src\/app\/domain\/models\/IInventoryItem\";\nimport { ICulture } from \"..\/..\/domain\/models\/ICulture\";\nimport { ICountry } from \"src\/app\/domain\/models\/ICountry\";\n\n\nclass DexiePlug extends Dexie {\n    \/\/ Declare implicit table properties.\n    \/\/ (just to inform Typescript. Instanciated by Dexie in stores() method)\n    inventoryItems: Dexie.Table&lt;IInventoryItem, string>; \/\/ number = type of the primkey\n    debtorOrderHistory: Dexie.Table&lt;IDebtorOrder, number>;\n    languages: Dexie.Table&lt;ICulture, string>;\n    countries: Dexie.Table&lt;ICountry, string>;\n    \/\/ ...other tables goes here...\n\n    constructor () {\n        super(\"AppDatabase\");\n        this.version(1).stores({\n            inventoryItems: \"id, brand\",\n            debtorOrderHistory: \"orderNumber, deliveryDate\",\n            languages: \"cultureCode\",\n            countries: \"name\"\n        });\n\n        this.version(2).stores({\n            debtorOrderHistory: \"orderNumber, deliveryDate, createdAt\"\n        });\n\n        \/\/ The following line is needed if your typescript\n        \/\/ is compiled using babel instead of tsc:\n        this.inventoryItems = this.table(\"inventoryItems\");\n        this.debtorOrderHistory = this.table(\"debtorOrderHistory\");\n        this.languages = this.table(\"languages\");\n        this.countries = this.table(\"countries\");\n    }\n}\n\n\/\/ tslint:disable-next-line: naming-convention\nexport const dexiePlug = new DexiePlug();<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import { CacheHelper } from \"src\/app\/helpers\/CacheHelper\";\nimport { appDomain } from \"..\/..\/domain\/AppDomain\";\nimport { IInventoryItem } from \"..\/..\/domain\/models\/IInventoryItem\";\nimport { IInventoryRepository } from \"..\/..\/domain\/ports\/secondary\/IInventoryRepository\";\nimport { dexiePlug } from \"..\/DexieAdapter\/DexiePlug\";\nimport { restApiHelper } from \"..\/helpers\/RestApiHelper\";\n\n\nconst getDefaultCurrency = (itemCurrencyCode) => itemCurrencyCode === \"-\" ? appDomain.IGetCurrentSession.getSession().getCompanyCurrencyCode() : itemCurrencyCode;\n\nconst webApiUrl = (window as any).config.WebApiUrl.replace(\/\\\/$\/g, \"\");\n\n\nconst getImages = (companyId: string, tableRowId: string, imageCount: number) => {\n    return Array(imageCount).fill(null)\n        .map((_, i) =>  webApiUrl + `\/api\/InventoryItem\/image\/${companyId}\/${tableRowId}\/${i}`);\n};\n\nconst inventoryItemMap = (x: any): IInventoryItem => ({\n    id: x.id,\n    name: x.name,\n    salesPrice: x.salesPrice,\n    brand: x.brandName,\n    cartons: x.cartons,\n    available: x.available || 0,\n    salesUnit: x.salesUnit,\n    nlRegistrationNumber: x.nlRegistrationNumber,\n    isAvailable: x.available > 0,\n    currencyCode: getDefaultCurrency(x.currencyCode),\n    tableRowId: x.tableRowId,\n    imageCount: x.imageCount\n});\n\nexport class InventoryRepository implements IInventoryRepository {\n    async getById(id: string): Task&lt;IInventoryItem> {\n        return await dexiePlug.inventoryItems.get(id);\n    }\n\n    async getAllInventoryItems(reload = false): Task&lt;IInventoryItem[]> {\n        return CacheHelper.section&lt;IInventoryItem[]>({\n            key: \"InventoryItems-LastUpdate\",\n            minutes: 60,\n            forceGenerate: reload,\n            fetch: async () => await dexiePlug.inventoryItems.toArray(),\n            generate: async () => {\n                const data = await restApiHelper.getData(\"api\/inventoryitem\/AllWithNlRegistration\");\n\n                const companyId = appDomain.IGetCurrentSession.getSession().getCompanyId();\n                const invItems  = data.map(inventoryItemMap);\n                invItems.forEach(inv => inv.images = getImages(companyId, inv.tableRowId, inv.imageCount));\n                await dexiePlug.inventoryItems.bulkPut(invItems);\n                return data;\n            }\n        });\n    }\n}\n<\/pre>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-2306","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts\/2306","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/comments?post=2306"}],"version-history":[{"count":3,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts\/2306\/revisions"}],"predecessor-version":[{"id":2469,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts\/2306\/revisions\/2469"}],"wp:attachment":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/media?parent=2306"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/categories?post=2306"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/tags?post=2306"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}