cacheService.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import api from "src/api";
  2. const createDbConnection = (dbName = "apiCache", version = 1) => {
  3. let db = null;
  4. const ready = new Promise((resolve, reject) => {
  5. const request = indexedDB.open(dbName, version);
  6. request.onerror = () => reject(request.error);
  7. request.onsuccess = () => {
  8. db = request.result;
  9. resolve(db);
  10. };
  11. request.onupgradeneeded = (event) => {
  12. const db = event.target.result;
  13. if (!db.objectStoreNames.contains("apiCache")) {
  14. db.createObjectStore("apiCache", { keyPath: "key" });
  15. }
  16. };
  17. });
  18. return {
  19. getDb: () => ready,
  20. };
  21. };
  22. const { getDb } = createDbConnection();
  23. const getFromCache = async (key) => {
  24. const db = await getDb();
  25. return new Promise((resolve) => {
  26. const transaction = db.transaction(["apiCache"], "readonly");
  27. const store = transaction.objectStore("apiCache");
  28. const request = store.get(key);
  29. request.onsuccess = () => {
  30. const entry = request.result;
  31. if (entry?.timestamp + entry?.ttl > Date.now()) {
  32. resolve(entry.data);
  33. } else {
  34. resolve(null);
  35. }
  36. };
  37. });
  38. };
  39. const setInCache = async (key, data, ttl) => {
  40. const db = await getDb();
  41. const entry = {
  42. key,
  43. data,
  44. timestamp: Date.now(),
  45. ttl: ttl * 1000,
  46. };
  47. return new Promise((resolve) => {
  48. const transaction = db.transaction(["apiCache"], "readwrite");
  49. const store = transaction.objectStore("apiCache");
  50. store.put(entry);
  51. transaction.oncomplete = () => resolve();
  52. });
  53. };
  54. const clearCache = async (cacheKey) => {
  55. const db = await getDb();
  56. const transaction = db.transaction(["apiCache"], "readwrite");
  57. const store = transaction.objectStore("apiCache");
  58. return new Promise((resolve) => {
  59. if (!cacheKey) {
  60. store.clear();
  61. transaction.oncomplete = () => resolve();
  62. return;
  63. }
  64. const request = store.getAllKeys();
  65. request.onsuccess = () => {
  66. const deletePromises = request.result
  67. .filter((key) => key.includes(cacheKey))
  68. .map(
  69. (key) =>
  70. new Promise((resolve) => {
  71. const deleteRequest = store.delete(key);
  72. deleteRequest.onsuccess = () => resolve();
  73. }),
  74. );
  75. Promise.all(deletePromises).then(() => resolve());
  76. };
  77. });
  78. };
  79. const isCacheableValue = (value) => {
  80. if (value === null) {
  81. return false;
  82. }
  83. if (
  84. typeof value === "object" &&
  85. Object.keys(value).length === 1 &&
  86. Object.prototype.hasOwnProperty.call(value, "data")
  87. ) {
  88. return false;
  89. }
  90. if (
  91. typeof value === "object" &&
  92. !Array.isArray(value) &&
  93. Object.keys(value).length === 0
  94. ) {
  95. return false;
  96. }
  97. if (Array.isArray(value?.payload) && value?.payload?.length === 0) {
  98. return false;
  99. }
  100. if (value?.payload == null) {
  101. return false;
  102. }
  103. return true;
  104. };
  105. export const createCachedApi = (namespace, ttl = 3600) => {
  106. const getCacheKey = (path) => `${namespace}:${path}`;
  107. const getResourcePaths = (path) => {
  108. const segments = path.split("/").filter(Boolean);
  109. const paths = [];
  110. for (let i = 0; i <= segments.length; i++) {
  111. const currentPath = "/" + segments.slice(0, i).join("/");
  112. paths.push(currentPath);
  113. }
  114. return paths;
  115. };
  116. const invalidateAll = async (path) => {
  117. const paths = getResourcePaths(path);
  118. await Promise.all(paths.map((path) => clearCache(getCacheKey(path))));
  119. };
  120. const get = async (path, options = {}) => {
  121. const key = getCacheKey(path);
  122. const cached = await getFromCache(key);
  123. if (isCacheableValue(cached)) {
  124. return { data: cached };
  125. }
  126. try {
  127. const { data } = await api.get(path);
  128. await setInCache(key, data, options.ttl ?? ttl);
  129. return { data };
  130. } catch (error) {
  131. console.error(`API request for ${key} failed.`, error);
  132. throw error;
  133. }
  134. };
  135. const post = async (path, payload) => {
  136. const { data } = await api.post(path, payload);
  137. await invalidateAll(path);
  138. return { data };
  139. };
  140. const put = async (path, payload) => {
  141. const { data } = await api.put(path, payload);
  142. await invalidateAll(path);
  143. return { data };
  144. };
  145. const del = async (path) => {
  146. const { data } = await api.delete(path);
  147. await invalidateAll(path);
  148. return { data };
  149. };
  150. return { get, post, put, del, invalidateAll };
  151. };