diff --git a/packages/api/src/controllers/connections.js b/packages/api/src/controllers/connections.js index 3a06afbcc..a4a6eb7e1 100644 --- a/packages/api/src/controllers/connections.js +++ b/packages/api/src/controllers/connections.js @@ -39,14 +39,14 @@ module.exports = { } else { res = await this.datastore.insert(connection); } - socket.emit('connection-list-changed'); + socket.emitChanged('connection-list-changed'); return res; }, delete_meta: 'post', async delete(connection) { const res = await this.datastore.remove(_.pick(connection, '_id')); - socket.emit('connection-list-changed'); + socket.emitChanged('connection-list-changed'); return res; }, diff --git a/packages/api/src/controllers/databaseConnections.js b/packages/api/src/controllers/databaseConnections.js index 7e9e96c55..f4ef8ec6d 100644 --- a/packages/api/src/controllers/databaseConnections.js +++ b/packages/api/src/controllers/databaseConnections.js @@ -13,7 +13,7 @@ module.exports = { const existing = this.opened.find((x) => x.conid == conid && x.database == database); if (!existing) return; existing.structure = structure; - socket.emit(`database-structure-changed-${conid}-${database}`); + socket.emitChanged(`database-structure-changed-${conid}-${database}`); }, handle_error(conid, database, props) { const { error } = props; diff --git a/packages/api/src/controllers/serverConnections.js b/packages/api/src/controllers/serverConnections.js index 1b28b2dd7..8ba9e3c62 100644 --- a/packages/api/src/controllers/serverConnections.js +++ b/packages/api/src/controllers/serverConnections.js @@ -9,7 +9,7 @@ module.exports = { const existing = this.opened.find(x => x.conid == conid); if (!existing) return; existing.databases = databases; - socket.emit(`database-list-changed-${conid}`); + socket.emitChanged(`database-list-changed-${conid}`); }, handle_error(conid, { error }) { console.log(`Error in server connection ${conid}: ${error}`); diff --git a/packages/api/src/utility/socket.js b/packages/api/src/utility/socket.js index ba599c080..12aed9dca 100644 --- a/packages/api/src/utility/socket.js +++ b/packages/api/src/utility/socket.js @@ -11,4 +11,9 @@ module.exports = { console.log('EMIT:', message, data); socket.emit(message, data); }, + emitChanged(key) { + console.log('EMIT_CHANGED:', key); + socket.emit(key); + socket.emit('clean-cache', key); + } }; diff --git a/packages/web/src/utility/SocketProvider.js b/packages/web/src/utility/SocketProvider.js index 72797f9a3..cbaa80d9e 100644 --- a/packages/web/src/utility/SocketProvider.js +++ b/packages/web/src/utility/SocketProvider.js @@ -1,6 +1,7 @@ import io from 'socket.io-client'; import React from 'react'; import resolveApi from './resolveApi'; +import { cacheClean } from './cache'; const SocketContext = React.createContext(null); @@ -10,6 +11,7 @@ export function SocketProvider({ children }) { // const newSocket = io('http://localhost:3000', { transports: ['websocket'] }); const newSocket = io(resolveApi()); setSocket(newSocket); + newSocket.on('clean-cache', (reloadTrigger) => cacheClean(reloadTrigger)); }, []); return {children}; } diff --git a/packages/web/src/utility/cache.js b/packages/web/src/utility/cache.js new file mode 100644 index 000000000..695738944 --- /dev/null +++ b/packages/web/src/utility/cache.js @@ -0,0 +1,33 @@ +let cachedByKey = {}; +let cachedPromisesByKey = {}; +const cachedKeysByReloadTrigger = {}; + +export function cacheGet(key) { + return cachedByKey[key]; +} + +export function cacheSet(key, value, reloadTrigger) { + cachedByKey[key] = value; + if (!(reloadTrigger in cachedKeysByReloadTrigger)) { + cachedKeysByReloadTrigger[reloadTrigger] = []; + } + cachedKeysByReloadTrigger[reloadTrigger].push(key); +} + +export function cacheClean(reloadTrigger) { + const keys = cachedKeysByReloadTrigger[reloadTrigger]; + if (keys) { + for (const key of keys) { + delete cachedByKey[key]; + delete cachedPromisesByKey[key]; + } + } + delete cachedKeysByReloadTrigger[reloadTrigger]; +} + +export function getCachedPromise(key, func) { + if (key in cachedPromisesByKey) return cachedPromisesByKey[key]; + const promise = func(); + cachedPromisesByKey[key] = promise; + return promise; +} diff --git a/packages/web/src/utility/metadataLoaders.js b/packages/web/src/utility/metadataLoaders.js index 22cd639e9..a1188cd67 100644 --- a/packages/web/src/utility/metadataLoaders.js +++ b/packages/web/src/utility/metadataLoaders.js @@ -1,5 +1,7 @@ import useFetch from './useFetch'; import axios from './axios'; +import { cacheGet, cacheSet, getCachedPromise } from './cache'; +import stableStringify from 'json-stable-stringify'; const tableInfoLoader = ({ conid, database, schemaName, pureName }) => ({ url: 'metadata/table-info', @@ -9,21 +11,34 @@ const tableInfoLoader = ({ conid, database, schemaName, pureName }) => ({ async function getCore(loader, args) { const { url, params, reloadTrigger } = loader(args); - const resp = await axios.request({ - method: 'get', - url, - params, - }); - return resp.data; + const key = stableStringify({ url, ...params }); + + async function doLoad() { + const resp = await axios.request({ + method: 'get', + url, + params, + }); + return resp.data; + } + + const fromCache = cacheGet(key); + if (fromCache) return fromCache; + const res = getCachedPromise(key, doLoad); + + cacheSet(key, res, reloadTrigger); + return res; } function useCore(loader, args) { const { url, params, reloadTrigger } = loader(args); + const cacheKey = stableStringify({ url, ...params }); const res = useFetch({ url, params, reloadTrigger, + cacheKey, }); return res; diff --git a/packages/web/src/utility/useFetch.js b/packages/web/src/utility/useFetch.js index 4cd181273..7fb2c9c16 100644 --- a/packages/web/src/utility/useFetch.js +++ b/packages/web/src/utility/useFetch.js @@ -3,6 +3,7 @@ import _ from 'lodash'; import axios from './axios'; import useSocket from './SocketProvider'; import stableStringify from 'json-stable-stringify'; +import { getCachedPromise, cacheGet, cacheSet } from './cache'; export default function useFetch({ url, @@ -10,6 +11,7 @@ export default function useFetch({ params = undefined, defaultValue = undefined, reloadTrigger = undefined, + cacheKey = undefined, ...config }) { const [value, setValue] = React.useState([defaultValue, []]); @@ -23,14 +25,27 @@ export default function useFetch({ const indicators = [url, stableStringify(data), stableStringify(params), loadCounter]; async function loadValue(loadedIndicators) { - const resp = await axios.request({ - method: 'get', - params, - url, - data, - ...config, - }); - setValue([resp.data, loadedIndicators]); + async function doLoad() { + const resp = await axios.request({ + method: 'get', + params, + url, + data, + ...config, + }); + return resp.data; + } + + if (cacheKey) { + const fromCache = cacheGet(cacheKey); + if (fromCache) return fromCache; + const res = await getCachedPromise(cacheKey, doLoad); + setValue([res, loadedIndicators]); + cacheSet(cacheKey, res, reloadTrigger); + } else { + const res = await doLoad(); + setValue([res, loadedIndicators]); + } } // React.useEffect(() => {