diff --git a/packages/api/src/controllers/apps.js b/packages/api/src/controllers/apps.js index ba2afe58..43c2d6e2 100644 --- a/packages/api/src/controllers/apps.js +++ b/packages/api/src/controllers/apps.js @@ -58,7 +58,7 @@ module.exports = { refreshFiles_meta: true, async refreshFiles({ folder }) { - socket.emitChanged(`app-files-changed-${folder}`); + socket.emitChanged('app-files-changed', { app: folder }); }, refreshFolders_meta: true, @@ -69,7 +69,7 @@ module.exports = { deleteFile_meta: true, async deleteFile({ folder, file, fileType }) { await fs.unlink(path.join(appdir(), folder, `${file}.${fileType}`)); - socket.emitChanged(`app-files-changed-${folder}`); + socket.emitChanged('app-files-changed', { app: folder }); this.emitChangedDbApp(folder); }, @@ -79,7 +79,7 @@ module.exports = { path.join(path.join(appdir(), folder), `${file}.${fileType}`), path.join(path.join(appdir(), folder), `${newFile}.${fileType}`) ); - socket.emitChanged(`app-files-changed-${folder}`); + socket.emitChanged('app-files-changed', { app: folder }); this.emitChangedDbApp(folder); }, @@ -95,7 +95,7 @@ module.exports = { if (!folder) throw new Error('Missing folder parameter'); await fs.rmdir(path.join(appdir(), folder), { recursive: true }); socket.emitChanged(`app-folders-changed`); - socket.emitChanged(`app-files-changed-${folder}`); + socket.emitChanged('app-files-changed', { app: folder }); socket.emitChanged('used-apps-changed'); }, @@ -219,7 +219,7 @@ module.exports = { await fs.writeFile(file, JSON.stringify(json, undefined, 2)); - socket.emitChanged(`app-files-changed-${appFolder}`); + socket.emitChanged('app-files-changed', { app: appFolder }); socket.emitChanged('used-apps-changed'); }, @@ -271,7 +271,7 @@ module.exports = { const file = path.join(appdir(), appFolder, fileName); if (!(await fs.exists(file))) { await fs.writeFile(file, JSON.stringify(content, undefined, 2)); - socket.emitChanged(`app-files-changed-${appFolder}`); + socket.emitChanged('app-files-changed', { app: appFolder }); socket.emitChanged('used-apps-changed'); return true; } diff --git a/packages/api/src/controllers/archive.js b/packages/api/src/controllers/archive.js index a9147169..50bb96fa 100644 --- a/packages/api/src/controllers/archive.js +++ b/packages/api/src/controllers/archive.js @@ -75,7 +75,7 @@ module.exports = { refreshFiles_meta: true, async refreshFiles({ folder }) { - socket.emitChanged(`archive-files-changed-${folder}`); + socket.emitChanged('archive-files-changed', { folder }); }, refreshFolders_meta: true, @@ -86,7 +86,7 @@ module.exports = { deleteFile_meta: true, async deleteFile({ folder, file, fileType }) { await fs.unlink(path.join(resolveArchiveFolder(folder), `${file}.${fileType}`)); - socket.emitChanged(`archive-files-changed-${folder}`); + socket.emitChanged(`archive-files-changed`, { folder }); }, renameFile_meta: true, @@ -95,7 +95,7 @@ module.exports = { path.join(resolveArchiveFolder(folder), `${file}.${fileType}`), path.join(resolveArchiveFolder(folder), `${newFile}.${fileType}`) ); - socket.emitChanged(`archive-files-changed-${folder}`); + socket.emitChanged(`archive-files-changed`, { folder }); }, renameFolder_meta: true, @@ -119,7 +119,7 @@ module.exports = { saveFreeTable_meta: true, async saveFreeTable({ folder, file, data }) { await saveFreeTableData(path.join(resolveArchiveFolder(folder), `${file}.jsonl`), data); - socket.emitChanged(`archive-files-changed-${folder}`); + socket.emitChanged(`archive-files-changed`, { folder }); return true; }, @@ -147,7 +147,7 @@ module.exports = { saveText_meta: true, async saveText({ folder, file, text }) { await fs.writeFile(path.join(resolveArchiveFolder(folder), `${file}.jsonl`), text); - socket.emitChanged(`archive-files-changed-${folder}`); + socket.emitChanged(`archive-files-changed`, { folder }); return true; }, @@ -156,7 +156,7 @@ module.exports = { const source = getJslFileName(jslid); const target = path.join(resolveArchiveFolder(folder), `${file}.jsonl`); await fs.copyFile(source, target); - socket.emitChanged(`archive-files-changed-${folder}`); + socket.emitChanged(`archive-files-changed`, { folder }); return true; }, diff --git a/packages/api/src/controllers/databaseConnections.js b/packages/api/src/controllers/databaseConnections.js index 2ace4869..3601b473 100644 --- a/packages/api/src/controllers/databaseConnections.js +++ b/packages/api/src/controllers/databaseConnections.js @@ -43,19 +43,19 @@ module.exports = { const existing = this.opened.find(x => x.conid == conid && x.database == database); if (!existing) return; existing.structure = structure; - socket.emitChanged(`database-structure-changed-${conid}-${database}`); + socket.emitChanged('database-structure-changed', { conid, database }); }, handle_structureTime(conid, database, { analysedTime }) { const existing = this.opened.find(x => x.conid == conid && x.database == database); if (!existing) return; existing.analysedTime = analysedTime; - socket.emitChanged(`database-status-changed-${conid}-${database}`); + socket.emitChanged(`database-status-changed`, { conid, database }); }, handle_version(conid, database, { version }) { const existing = this.opened.find(x => x.conid == conid && x.database == database); if (!existing) return; existing.serverVersion = version; - socket.emitChanged(`database-server-version-changed-${conid}-${database}`); + socket.emitChanged(`database-server-version-changed`, { conid, database }); }, handle_error(conid, database, props) { @@ -73,7 +73,7 @@ module.exports = { if (!existing) return; if (existing.status && status && existing.status.counter > status.counter) return; existing.status = status; - socket.emitChanged(`database-status-changed-${conid}-${database}`); + socket.emitChanged(`database-status-changed`, { conid, database }); }, handle_ping() {}, @@ -317,7 +317,7 @@ module.exports = { }, structure: existing.structure, }; - socket.emitChanged(`database-status-changed-${conid}-${database}`); + socket.emitChanged(`database-status-changed`, { conid, database }); } }, diff --git a/packages/api/src/controllers/files.js b/packages/api/src/controllers/files.js index 484429db..1e3c76b7 100644 --- a/packages/api/src/controllers/files.js +++ b/packages/api/src/controllers/files.js @@ -49,7 +49,7 @@ module.exports = { async delete({ folder, file }, req) { if (!hasPermission(`files/${folder}/write`, req)) return false; await fs.unlink(path.join(filesdir(), folder, file)); - socket.emitChanged(`files-changed-${folder}`); + socket.emitChanged(`files-changed`, { folder }); socket.emitChanged(`all-files-changed`); return true; }, @@ -58,7 +58,7 @@ module.exports = { async rename({ folder, file, newFile }, req) { if (!hasPermission(`files/${folder}/write`, req)) return false; await fs.rename(path.join(filesdir(), folder, file), path.join(filesdir(), folder, newFile)); - socket.emitChanged(`files-changed-${folder}`); + socket.emitChanged(`files-changed`, { folder }); socket.emitChanged(`all-files-changed`); return true; }, @@ -66,7 +66,7 @@ module.exports = { refresh_meta: true, async refresh({ folders }, req) { for (const folder of folders) { - socket.emitChanged(`files-changed-${folder}`); + socket.emitChanged(`files-changed`, { folder }); socket.emitChanged(`all-files-changed`); } return true; @@ -76,7 +76,7 @@ module.exports = { async copy({ folder, file, newFile }, req) { if (!hasPermission(`files/${folder}/write`, req)) return false; await fs.copyFile(path.join(filesdir(), folder, file), path.join(filesdir(), folder, newFile)); - socket.emitChanged(`files-changed-${folder}`); + socket.emitChanged(`files-changed`, { folder }); socket.emitChanged(`all-files-changed`); return true; }, @@ -112,13 +112,13 @@ module.exports = { if (!hasPermission(`archive/write`, req)) return false; const dir = resolveArchiveFolder(folder.substring('archive:'.length)); await fs.writeFile(path.join(dir, file), serialize(format, data)); - socket.emitChanged(`archive-files-changed-${folder.substring('archive:'.length)}`); + socket.emitChanged(`archive-files-changed`, { folder: folder.substring('archive:'.length) }); return true; } else if (folder.startsWith('app:')) { if (!hasPermission(`apps/write`, req)) return false; const app = folder.substring('app:'.length); await fs.writeFile(path.join(appdir(), app, file), serialize(format, data)); - socket.emitChanged(`app-files-changed-${app}`); + socket.emitChanged(`app-files-changed`, { app }); socket.emitChanged('used-apps-changed'); apps.emitChangedDbApp(folder); return true; @@ -129,7 +129,7 @@ module.exports = { await fs.mkdir(dir); } await fs.writeFile(path.join(dir, file), serialize(format, data)); - socket.emitChanged(`files-changed-${folder}`); + socket.emitChanged(`files-changed`, { folder }); socket.emitChanged(`all-files-changed`); if (folder == 'shell') { scheduler.reload(); diff --git a/packages/api/src/controllers/serverConnections.js b/packages/api/src/controllers/serverConnections.js index 831dda44..ccdbefe1 100644 --- a/packages/api/src/controllers/serverConnections.js +++ b/packages/api/src/controllers/serverConnections.js @@ -21,13 +21,13 @@ module.exports = { const existing = this.opened.find(x => x.conid == conid); if (!existing) return; existing.databases = databases; - socket.emitChanged(`database-list-changed-${conid}`); + socket.emitChanged(`database-list-changed`, { conid }); }, handle_version(conid, { version }) { const existing = this.opened.find(x => x.conid == conid); if (!existing) return; existing.version = version; - socket.emitChanged(`server-version-changed-${conid}`); + socket.emitChanged(`server-version-changed`, { conid }); }, handle_status(conid, { status }) { const existing = this.opened.find(x => x.conid == conid); diff --git a/packages/api/src/utility/socket.js b/packages/api/src/utility/socket.js index 5b519f10..b0f9fc3c 100644 --- a/packages/api/src/utility/socket.js +++ b/packages/api/src/utility/socket.js @@ -1,4 +1,5 @@ const _ = require('lodash'); +const stableStringify = require('json-stable-stringify'); const sseResponses = []; let electronSender = null; @@ -27,12 +28,12 @@ module.exports = { electronSender.send(message, data == null ? null : data); } for (const res of sseResponses) { - res.write(`event: ${message}\ndata: ${JSON.stringify(data == null ? null : data)}\n\n`); + res.write(`event: ${message}\ndata: ${stableStringify(data == null ? null : data)}\n\n`); } }, - emitChanged(key) { + emitChanged(key, params = undefined) { // console.log('EMIT CHANGED', key); - this.emit('changed-cache', key); + this.emit('changed-cache', { key, ...params }); // this.emit(key); }, }; diff --git a/packages/web/src/utility/cache.ts b/packages/web/src/utility/cache.ts index 4de67e25..8e4443ea 100644 --- a/packages/web/src/utility/cache.ts +++ b/packages/web/src/utility/cache.ts @@ -1,5 +1,6 @@ import { apiOn } from './api'; import getAsArray from './getAsArray'; +import stableStringify from 'json-stable-stringify'; const cachedByKey = {}; const cachedPromisesByKey = {}; @@ -15,10 +16,11 @@ function cacheGet(key) { function addCacheKeyToReloadTrigger(cacheKey, reloadTrigger) { for (const item of getAsArray(reloadTrigger)) { - if (!(item in cachedKeysByReloadTrigger)) { - cachedKeysByReloadTrigger[item] = []; + const itemString = stableStringify(item); + if (!(itemString in cachedKeysByReloadTrigger)) { + cachedKeysByReloadTrigger[itemString] = []; } - cachedKeysByReloadTrigger[item].push(cacheKey); + cachedKeysByReloadTrigger[itemString].push(cacheKey); } } @@ -32,7 +34,8 @@ function cacheSet(cacheKey, value, reloadTrigger, generation) { function cacheClean(reloadTrigger) { cacheGeneration += 1; for (const item of getAsArray(reloadTrigger)) { - const keys = cachedKeysByReloadTrigger[item]; + const itemString = stableStringify(item); + const keys = cachedKeysByReloadTrigger[itemString]; if (keys) { for (const key of keys) { delete cachedByKey[key]; @@ -40,7 +43,7 @@ function cacheClean(reloadTrigger) { cacheGenerationByKey[key] = cacheGeneration; } } - delete cachedKeysByReloadTrigger[item]; + delete cachedKeysByReloadTrigger[itemString]; } } @@ -87,20 +90,24 @@ export async function loadCachedValue(reloadTrigger, cacheKey, func) { export async function subscribeCacheChange(reloadTrigger, cacheKey, reloadHandler) { for (const item of getAsArray(reloadTrigger)) { - if (!subscriptionsByReloadTrigger[item]) { - subscriptionsByReloadTrigger[item] = []; + const itemString = stableStringify(item); + if (!subscriptionsByReloadTrigger[itemString]) { + subscriptionsByReloadTrigger[itemString] = []; } - subscriptionsByReloadTrigger[item].push(reloadHandler); + subscriptionsByReloadTrigger[itemString].push(reloadHandler); } } export async function unsubscribeCacheChange(reloadTrigger, cacheKey, reloadHandler) { for (const item of getAsArray(reloadTrigger)) { - if (subscriptionsByReloadTrigger[item]) { - subscriptionsByReloadTrigger[item] = subscriptionsByReloadTrigger[item].filter(x => x != reloadHandler); + const itemString = stableStringify(item); + if (subscriptionsByReloadTrigger[itemString]) { + subscriptionsByReloadTrigger[itemString] = subscriptionsByReloadTrigger[itemString].filter( + x => x != reloadHandler + ); } - if (subscriptionsByReloadTrigger[item].length == 0) { - delete subscriptionsByReloadTrigger[item]; + if (subscriptionsByReloadTrigger[itemString].length == 0) { + delete subscriptionsByReloadTrigger[itemString]; } } } @@ -110,8 +117,9 @@ export function dispatchCacheChange(reloadTrigger) { cacheClean(reloadTrigger); for (const item of getAsArray(reloadTrigger)) { - if (subscriptionsByReloadTrigger[item]) { - for (const handler of subscriptionsByReloadTrigger[item]) { + const itemString = stableStringify(item); + if (subscriptionsByReloadTrigger[itemString]) { + for (const handler of subscriptionsByReloadTrigger[itemString]) { handler(); } } diff --git a/packages/web/src/utility/metadataLoaders.ts b/packages/web/src/utility/metadataLoaders.ts index a29404a5..3ea4c276 100644 --- a/packages/web/src/utility/metadataLoaders.ts +++ b/packages/web/src/utility/metadataLoaders.ts @@ -9,7 +9,7 @@ import { apiCall, apiOff, apiOn } from './api'; const databaseInfoLoader = ({ conid, database }) => ({ url: 'database-connections/structure', params: { conid, database }, - reloadTrigger: `database-structure-changed-${conid}-${database}`, + reloadTrigger: { key: `database-structure-changed`, conid, database }, transform: extendDatabaseInfo, }); @@ -28,31 +28,31 @@ const databaseInfoLoader = ({ conid, database }) => ({ const connectionInfoLoader = ({ conid }) => ({ url: 'connections/get', params: { conid }, - reloadTrigger: 'connection-list-changed', + reloadTrigger: { key: 'connection-list-changed' }, }); const configLoader = () => ({ url: 'config/get', params: {}, - reloadTrigger: 'config-changed', + reloadTrigger: { key: 'config-changed' }, }); const settingsLoader = () => ({ url: 'config/get-settings', params: {}, - reloadTrigger: 'settings-changed', + reloadTrigger: { key: 'settings-changed' }, }); const platformInfoLoader = () => ({ url: 'config/platform-info', params: {}, - reloadTrigger: 'platform-info-changed', + reloadTrigger: { key: 'platform-info-changed' }, }); const favoritesLoader = () => ({ url: 'files/favorites', params: {}, - reloadTrigger: 'files-changed-favorites', + reloadTrigger: { key: 'files-changed-favorites' }, }); // const sqlObjectListLoader = ({ conid, database }) => ({ @@ -64,13 +64,13 @@ const favoritesLoader = () => ({ const databaseStatusLoader = ({ conid, database }) => ({ url: 'database-connections/status', params: { conid, database }, - reloadTrigger: `database-status-changed-${conid}-${database}`, + reloadTrigger: { key: `database-status-changed`, conid, database }, }); const databaseListLoader = ({ conid }) => ({ url: 'server-connections/list-databases', params: { conid }, - reloadTrigger: `database-list-changed-${conid}`, + reloadTrigger: { key: `database-list-changed`, conid }, onLoaded: value => { if (value?.length > 0) setLocalStorage(`database_list_${conid}`, value); }, @@ -85,37 +85,37 @@ const databaseListLoader = ({ conid }) => ({ const serverVersionLoader = ({ conid }) => ({ url: 'server-connections/version', params: { conid }, - reloadTrigger: `server-version-changed-${conid}`, + reloadTrigger: { key: `server-version-changed`, conid }, }); const databaseServerVersionLoader = ({ conid, database }) => ({ url: 'database-connections/server-version', params: { conid, database }, - reloadTrigger: `database-server-version-changed-${conid}-${database}`, + reloadTrigger: { key: `database-server-version-changed`, conid, database }, }); const archiveFoldersLoader = () => ({ url: 'archive/folders', params: {}, - reloadTrigger: `archive-folders-changed`, + reloadTrigger: { key: `archive-folders-changed` }, }); const archiveFilesLoader = ({ folder }) => ({ url: 'archive/files', params: { folder }, - reloadTrigger: `archive-files-changed-${folder}`, + reloadTrigger: { key: `archive-files-changed`, folder }, }); const appFoldersLoader = () => ({ url: 'apps/folders', params: {}, - reloadTrigger: `app-folders-changed`, + reloadTrigger: { key: `app-folders-changed` }, }); const appFilesLoader = ({ folder }) => ({ url: 'apps/files', params: { folder }, - reloadTrigger: `app-files-changed-${folder}`, + reloadTrigger: { key: `app-files-changed`, app: folder }, }); // const dbAppsLoader = ({ conid, database }) => ({ @@ -127,41 +127,41 @@ const appFilesLoader = ({ folder }) => ({ const usedAppsLoader = ({ conid, database }) => ({ url: 'apps/get-used-apps', params: {}, - reloadTrigger: `used-apps-changed`, + reloadTrigger: { key: `used-apps-changed` }, }); const serverStatusLoader = () => ({ url: 'server-connections/server-status', params: {}, - reloadTrigger: `server-status-changed`, + reloadTrigger: { key: `server-status-changed` }, }); const connectionListLoader = () => ({ url: 'connections/list', params: {}, - reloadTrigger: `connection-list-changed`, + reloadTrigger: { key: `connection-list-changed` }, }); const installedPluginsLoader = () => ({ url: 'plugins/installed', params: {}, - reloadTrigger: `installed-plugins-changed`, + reloadTrigger: { key: `installed-plugins-changed` }, }); const filesLoader = ({ folder }) => ({ url: 'files/list', params: { folder }, - reloadTrigger: `files-changed-${folder}`, + reloadTrigger: { key: `files-changed`, folder }, }); const allFilesLoader = () => ({ url: 'files/list-all', params: {}, - reloadTrigger: `all-files-changed`, + reloadTrigger: { key: `all-files-changed` }, }); const authTypesLoader = ({ engine }) => ({ url: 'plugins/auth-types', params: { engine }, - reloadTrigger: `installed-plugins-changed`, + reloadTrigger: { key: `installed-plugins-changed` }, errorValue: null, });