From bc920356831fba1d1291bee1e343c34a4eba7081 Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Tue, 9 Jul 2024 08:01:10 -0700 Subject: [PATCH] Share tanstack QueryClient between dashboard and IDE (#10431) * Share tanstack QueryClient between dashboard and IDE Part of #10400. * Lint * Review: Use enso-common * Remove outdated README * Naming * Fix * Lint * enso-common CODEOWNERS: GUI+Dashboard * Review: Prepare for GUI to be run from cloud entry point * Lint * Lint * Fix e2e tests * Fix e2e tests in CI? * Clean CI build * Revert "Clean CI build" This reverts commit 73f2fb797219dafc12abb996cfc6cbcfb596c094. * Fix redundant dependency * Work around a vue-query bug * Lint * fmt --- .github/CODEOWNERS | 3 + app/gui2/package.json | 1 + app/gui2/src/entrypoint.ts | 15 +- app/ide-desktop/lib/common/README.md | 7 - app/ide-desktop/lib/common/package.json | 13 +- app/ide-desktop/lib/common/src/queryClient.ts | 175 ++++++++++++++++++ app/ide-desktop/lib/common/src/vueQuery.ts | 99 ++++++++++ app/ide-desktop/lib/dashboard/package.json | 3 +- app/ide-desktop/lib/dashboard/src/App.tsx | 1 + .../lib/dashboard/src/entrypoint.ts | 3 + app/ide-desktop/lib/dashboard/src/index.tsx | 4 +- .../lib/dashboard/src/reactQueryClient.ts | 171 ----------------- pnpm-lock.yaml | 65 ++++++- 13 files changed, 367 insertions(+), 193 deletions(-) delete mode 100644 app/ide-desktop/lib/common/README.md create mode 100644 app/ide-desktop/lib/common/src/queryClient.ts create mode 100644 app/ide-desktop/lib/common/src/vueQuery.ts delete mode 100644 app/ide-desktop/lib/dashboard/src/reactQueryClient.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 320fdc9de3fc..dfa31a4357fc 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -45,3 +45,6 @@ Cargo.toml # The data-link schema is owned by the libraries team /app/ide-desktop/lib/dashboard/src/data/datalinkSchema.json @radeusgd @jdunkerley @GregoryTravis @AdRiley @marthasharkey /app/ide-desktop/lib/dashboard/src/data/__tests__ @radeusgd @jdunkerley @GregoryTravis @AdRiley @marthasharkey @PabloBuchu @indiv0 @somebody1234 + +# GUI / Dashboard shared +/app/ide-desktop/lib/common @PabloBuchu @indiv0 @somebody1234 @MrFlashAccount @Frizi @farmaazon @vitvakatu @kazcw @AdRiley diff --git a/app/gui2/package.json b/app/gui2/package.json index 0e91215b9084..613c7d7ab60c 100644 --- a/app/gui2/package.json +++ b/app/gui2/package.json @@ -64,6 +64,7 @@ "@lezer/highlight": "^1.1.6", "@noble/hashes": "^1.3.2", "@open-rpc/client-js": "^1.8.1", + "@tanstack/vue-query": ">= 5.45.0 < 5.46.0", "@vueuse/core": "^10.4.1", "ag-grid-community": "^30.2.1", "ag-grid-enterprise": "^30.2.1", diff --git a/app/gui2/src/entrypoint.ts b/app/gui2/src/entrypoint.ts index 208324e4706e..51965f277615 100644 --- a/app/gui2/src/entrypoint.ts +++ b/app/gui2/src/entrypoint.ts @@ -1,9 +1,12 @@ import { baseConfig, configValue, mergeConfig } from '@/util/config' import { urlParams } from '@/util/urlParams' +import * as vueQuery from '@tanstack/vue-query' import { isOnLinux } from 'enso-common/src/detect' +import * as commonQuery from 'enso-common/src/queryClient' import * as dashboard from 'enso-dashboard' import { isDevMode } from 'shared/util/detect' import { lazyVueInReact } from 'veaury' +import { type App } from 'vue' import 'enso-dashboard/src/tailwind.css' import type { EditorRunner } from '../../ide-desktop/lib/types/types' @@ -46,8 +49,6 @@ window.addEventListener('resize', () => { scamWarningHandle = window.setTimeout(printScamWarning, SCAM_WARNING_TIMEOUT) }) -const appRunner = lazyVueInReact(AsyncApp as any /* async VueComponent */) as EditorRunner - /** The entrypoint into the IDE. */ function main() { /** Note: Signing out always redirects to `/`. It is impossible to make this work, @@ -74,6 +75,15 @@ function main() { const projectManagerUrl = config.engine.projectManagerUrl || PROJECT_MANAGER_URL const ydocUrl = config.engine.ydocUrl === '' ? YDOC_SERVER_URL : config.engine.ydocUrl const initialProjectName = config.startup.project || null + const queryClient = commonQuery.createQueryClient() + + const registerPlugins = (app: App) => { + app.use(vueQuery.VueQueryPlugin, { queryClient }) + } + + const appRunner = lazyVueInReact(AsyncApp as any /* async VueComponent */, { + beforeVueAppMount: (app) => registerPlugins(app as App), + }) as EditorRunner dashboard.run({ appRunner, @@ -96,6 +106,7 @@ function main() { } } }, + queryClient, }) } diff --git a/app/ide-desktop/lib/common/README.md b/app/ide-desktop/lib/common/README.md deleted file mode 100644 index 0ffcb7c32e49..000000000000 --- a/app/ide-desktop/lib/common/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Common utilities - -This module contains utilities that are used by multiple modules (or multiple -different build commands). - -It is highly NOT RECOMMENDED to add files to this package - prefer creating a -new package with a narrower set of responsibilities instead. diff --git a/app/ide-desktop/lib/common/package.json b/app/ide-desktop/lib/common/package.json index c4de52675d11..8a5a5b7eee49 100644 --- a/app/ide-desktop/lib/common/package.json +++ b/app/ide-desktop/lib/common/package.json @@ -10,6 +10,17 @@ "./src/buildUtils": "./src/buildUtils.js", "./src/detect": "./src/detect.ts", "./src/gtag": "./src/gtag.ts", - "./src/load": "./src/load.ts" + "./src/load": "./src/load.ts", + "./src/queryClient": "./src/queryClient.ts" + }, + "peerDependencies": { + "@tanstack/query-core": "5.45.0", + "@tanstack/vue-query": ">= 5.45.0 < 5.46.0" + }, + "dependencies": { + "idb-keyval": "^6.2.1", + "@tanstack/query-persist-client-core": "^5.45.0", + "@tanstack/vue-query": ">= 5.45.0 < 5.46.0", + "vue": "^3.4.19" } } diff --git a/app/ide-desktop/lib/common/src/queryClient.ts b/app/ide-desktop/lib/common/src/queryClient.ts new file mode 100644 index 000000000000..6d6fbcbd0ae0 --- /dev/null +++ b/app/ide-desktop/lib/common/src/queryClient.ts @@ -0,0 +1,175 @@ +/** + * @file + * + * Tanstack Query client for Enso IDE and dashboard. + */ + +import * as idbKeyval from 'idb-keyval' +import * as persistClientCore from '@tanstack/query-persist-client-core' +import * as queryCore from '@tanstack/query-core' +import * as vueQuery from './vueQuery' + +declare module '@tanstack/query-core' { + /** + * Query client with additional methods. + */ + interface QueryClient { + /** + * Clear the cache stored in Tanstack Query and the persister storage. + * Please use this method with caution, as it will clear all cache data. + * Usually you should use `queryClient.invalidateQueries` instead. + */ + readonly clearWithPersister: () => Promise + /** + * Clear the cache stored in the persister storage. + */ + readonly nukePersister: () => Promise + } + /** + * Specifies the invalidation behavior of a mutation. + */ + interface Register { + readonly mutationMeta: { + /** + * List of query keys to invalidate when the mutation succeeds. + */ + readonly invalidates?: queryCore.QueryKey[] + /** + * List of query keys to await invalidation before the mutation is considered successful. + * + * If `true`, all `invalidates` are awaited. + * + * If `false`, no invalidations are awaited. + * + * You can also provide an array of query keys to await. + * + * Queries that are not listed in invalidates will be ignored. + * @default false + */ + readonly awaitInvalidates?: queryCore.QueryKey[] | boolean + } + + readonly queryMeta: { + /** + * Whether to persist the query cache in the storage. Defaults to `true`. + * Use `false` to disable persistence for a specific query, for example for + * a sensitive data or data that can't be persisted, e.g. class instances. + * @default true + */ + readonly persist?: boolean + } + } +} + +/** Query Client type suitable for shared use in React and Vue. */ +export type QueryClient = vueQuery.QueryClient + +// eslint-disable-next-line @typescript-eslint/no-magic-numbers +const DEFAULT_QUERY_STALE_TIME_MS = 2 * 60 * 1000 +// eslint-disable-next-line @typescript-eslint/no-magic-numbers +const DEFAULT_QUERY_PERSIST_TIME_MS = 30 * 24 * 60 * 60 * 1000 // 30 days + +const DEFAULT_BUSTER = 'v1.1' + +/** + * Create a new Tanstack Query client. + */ +export function createQueryClient(): QueryClient { + const store = idbKeyval.createStore('enso', 'query-persist-cache') + queryCore.onlineManager.setOnline(navigator.onLine) + + const persister = persistClientCore.experimental_createPersister({ + storage: { + getItem: key => idbKeyval.get(key, store), + setItem: (key, value) => idbKeyval.set(key, value, store), + removeItem: key => idbKeyval.del(key, store), + }, + // Prefer online first and don't rely on the local cache if user is online + // fallback to the local cache only if the user is offline + maxAge: queryCore.onlineManager.isOnline() ? -1 : DEFAULT_QUERY_PERSIST_TIME_MS, + buster: DEFAULT_BUSTER, + filters: { predicate: query => query.meta?.persist !== false }, + prefix: 'enso:query-persist:', + serialize: persistedQuery => persistedQuery, + deserialize: persistedQuery => persistedQuery, + }) + + const queryClient: QueryClient = new vueQuery.QueryClient({ + mutationCache: new queryCore.MutationCache({ + onSuccess: (_data, _variables, _context, mutation) => { + const shouldAwaitInvalidates = mutation.meta?.awaitInvalidates ?? false + const invalidates = mutation.meta?.invalidates ?? [] + const invalidatesToAwait = (() => { + if (Array.isArray(shouldAwaitInvalidates)) { + return shouldAwaitInvalidates + } else { + return shouldAwaitInvalidates ? invalidates : [] + } + })() + const invalidatesToIgnore = invalidates.filter( + queryKey => !invalidatesToAwait.includes(queryKey) + ) + + for (const queryKey of invalidatesToIgnore) { + void queryClient.invalidateQueries({ + predicate: query => queryCore.matchQuery({ queryKey }, query), + }) + } + + if (invalidatesToAwait.length > 0) { + // eslint-disable-next-line no-restricted-syntax + return Promise.all( + invalidatesToAwait.map(queryKey => + queryClient.invalidateQueries({ + predicate: query => queryCore.matchQuery({ queryKey }, query), + }) + ) + ) + } + }, + }), + defaultOptions: { + queries: { + persister, + refetchOnReconnect: 'always', + staleTime: DEFAULT_QUERY_STALE_TIME_MS, + retry: (failureCount, error: unknown) => { + // eslint-disable-next-line @typescript-eslint/no-magic-numbers + const statusesToIgnore = [401, 403, 404] + const errorStatus = + typeof error === 'object' && + error != null && + 'status' in error && + typeof error.status === 'number' + ? error.status + : -1 + + if (statusesToIgnore.includes(errorStatus)) { + return false + } else { + return failureCount < 3 + } + }, + }, + }, + }) + + Object.defineProperty(queryClient, 'nukePersister', { + value: () => idbKeyval.clear(store), + enumerable: false, + configurable: false, + writable: false, + }) + + Object.defineProperty(queryClient, 'clearWithPersister', { + value: () => { + queryClient.clear() + return queryClient.nukePersister() + }, + enumerable: false, + configurable: false, + writable: false, + }) + + return queryClient +} diff --git a/app/ide-desktop/lib/common/src/vueQuery.ts b/app/ide-desktop/lib/common/src/vueQuery.ts new file mode 100644 index 000000000000..eb0173d7288d --- /dev/null +++ b/app/ide-desktop/lib/common/src/vueQuery.ts @@ -0,0 +1,99 @@ +/** @file QueryClient based on the '@tanstack/vue-query' implementation. */ + +import * as vueQuery from '@tanstack/vue-query' +import * as queryCore from '@tanstack/query-core' +import * as vue from 'vue' + +/** The QueryClient from vue-query, but with immediate query invalidation. */ +export class QueryClient extends vueQuery.QueryClient { + /** Like the `invalidateQueries` method of `vueQuery.QueryClient`, but invalidates queries immediately. */ + // Workaround for https://github.com/TanStack/query/issues/7694 + override invalidateQueries( + filters: MaybeRefDeep = {}, + options: MaybeRefDeep = {} + ): Promise { + const filtersValue = cloneDeepUnref(filters) + const optionsValue = cloneDeepUnref(options) + queryCore.notifyManager.batch(() => { + this.getQueryCache() + .findAll(filtersValue) + .forEach(query => { + query.invalidate() + }) + }) + if (filtersValue.refetchType === 'none') { + return Promise.resolve() + } else { + const refetchType = filtersValue.refetchType + return vue.nextTick(() => + queryCore.notifyManager.batch(() => { + const refetchFilters: queryCore.RefetchQueryFilters = { + ...filtersValue, + type: refetchType ?? filtersValue.type ?? 'active', + } + return this.refetchQueries(refetchFilters, optionsValue) + }) + ) + } + } +} + +/* eslint-disable */ + +function isPlainObject(value: unknown): value is Object { + if (Object.prototype.toString.call(value) !== '[object Object]') { + return false + } + + const prototype = Object.getPrototypeOf(value) + return prototype === null || prototype === Object.prototype +} + +function cloneDeep( + value: MaybeRefDeep, + customize?: (val: MaybeRefDeep) => T | undefined +): T { + if (customize) { + const result = customize(value) + // If it's a ref of undefined, return undefined + if (result === undefined && vue.isRef(value)) { + return result as T + } + if (result !== undefined) { + return result + } + } + + if (Array.isArray(value)) { + return value.map(val => cloneDeep(val, customize)) as unknown as T + } + + if (typeof value === 'object' && isPlainObject(value)) { + const entries = Object.entries(value).map(([key, val]) => [key, cloneDeep(val, customize)]) + return Object.fromEntries(entries) + } + + return value as T +} + +function cloneDeepUnref(obj: MaybeRefDeep): T { + return cloneDeep(obj, val => { + if (vue.isRef(val)) { + return cloneDeepUnref(vue.unref(val)) + } + + return undefined + }) +} + +type MaybeRefDeep = vue.MaybeRef< + T extends Function + ? T + : T extends object + ? { + [Property in keyof T]: MaybeRefDeep + } + : T +> + +/* eslint-enable */ diff --git a/app/ide-desktop/lib/dashboard/package.json b/app/ide-desktop/lib/dashboard/package.json index 02050c3dcc1d..7debcc85e37c 100644 --- a/app/ide-desktop/lib/dashboard/package.json +++ b/app/ide-desktop/lib/dashboard/package.json @@ -36,12 +36,11 @@ "@monaco-editor/react": "4.6.0", "@sentry/react": "^7.74.0", "@tanstack/react-query": "5.45.1", - "@tanstack/query-persist-client-core": "5.45.0", + "@tanstack/vue-query": ">= 5.45.0 < 5.46.0", "ajv": "^8.12.0", "clsx": "^2.1.1", "enso-assets": "workspace:*", "enso-common": "workspace:*", - "idb-keyval": "6.2.1", "is-network-error": "^1.0.1", "monaco-editor": "0.48.0", "react": "^18.3.1", diff --git a/app/ide-desktop/lib/dashboard/src/App.tsx b/app/ide-desktop/lib/dashboard/src/App.tsx index 955deadf72e3..1f4e4f1b0376 100644 --- a/app/ide-desktop/lib/dashboard/src/App.tsx +++ b/app/ide-desktop/lib/dashboard/src/App.tsx @@ -158,6 +158,7 @@ export interface AppProps { readonly appRunner: types.EditorRunner | null readonly portalRoot: Element readonly httpClient: HttpClient + readonly queryClient: reactQuery.QueryClient } /** Component called by the parent module, returning the root React component for this diff --git a/app/ide-desktop/lib/dashboard/src/entrypoint.ts b/app/ide-desktop/lib/dashboard/src/entrypoint.ts index 253325f29d5a..def182513db0 100644 --- a/app/ide-desktop/lib/dashboard/src/entrypoint.ts +++ b/app/ide-desktop/lib/dashboard/src/entrypoint.ts @@ -1,4 +1,6 @@ /** @file Entry point into the cloud dashboard. */ +import * as commonQuery from 'enso-common/src/queryClient' + import '#/tailwind.css' import * as main from '#/index' @@ -27,4 +29,5 @@ main.run({ projectManagerUrl: null, ydocUrl: null, appRunner: testAppRunner.TestAppRunner, + queryClient: commonQuery.createQueryClient(), }) diff --git a/app/ide-desktop/lib/dashboard/src/index.tsx b/app/ide-desktop/lib/dashboard/src/index.tsx index 20cde738807c..d6e3a85098bf 100644 --- a/app/ide-desktop/lib/dashboard/src/index.tsx +++ b/app/ide-desktop/lib/dashboard/src/index.tsx @@ -13,7 +13,6 @@ import * as detect from 'enso-common/src/detect' import type * as app from '#/App' import App from '#/App' -import * as reactQueryClientModule from '#/reactQueryClient' import LoadingScreen from '#/pages/authentication/LoadingScreen' @@ -46,7 +45,7 @@ export // This export declaration must be broken up to satisfy the `require-jsdo // This is not a React component even though it contains JSX. // eslint-disable-next-line no-restricted-syntax function run(props: Omit) { - const { vibrancy, supportsDeepLinks } = props + const { vibrancy, supportsDeepLinks, queryClient } = props if ( !detect.IS_DEV_MODE && process.env.ENSO_CLOUD_SENTRY_DSN != null && @@ -93,7 +92,6 @@ function run(props: Omit) { : supportsDeepLinks && detect.isOnElectron() const httpClient = new HttpClient() - const queryClient = reactQueryClientModule.createReactQueryClient() React.startTransition(() => { reactDOM.createRoot(root).render( diff --git a/app/ide-desktop/lib/dashboard/src/reactQueryClient.ts b/app/ide-desktop/lib/dashboard/src/reactQueryClient.ts deleted file mode 100644 index abcd07208f6a..000000000000 --- a/app/ide-desktop/lib/dashboard/src/reactQueryClient.ts +++ /dev/null @@ -1,171 +0,0 @@ -/** - * @file - * - * React Query client for the dashboard. - */ - -import * as persistClientCore from '@tanstack/query-persist-client-core' -import * as reactQuery from '@tanstack/react-query' -import * as idbKeyval from 'idb-keyval' - -declare module '@tanstack/react-query' { - /** - * React Query client with additional methods. - */ - interface QueryClient { - /** - * Clear the cache stored in React Query and the persister storage. - * Please use this method with caution, as it will clear all cache data. - * Usually you should use `queryClient.invalidateQueries` instead. - */ - readonly clearWithPersister: () => Promise - /** - * Clear the cache stored in the persister storage. - */ - readonly nukePersister: () => Promise - } - /** - * Specifies the invalidation behavior of a mutation. - */ - interface Register { - readonly mutationMeta: { - /** - * List of query keys to invalidate when the mutation succeeds. - */ - readonly invalidates?: reactQuery.QueryKey[] - /** - * List of query keys to await invalidation before the mutation is considered successful. - * - * If `true`, all `invalidates` are awaited. - * - * If `false`, no invalidations are awaited. - * - * You can also provide an array of query keys to await. - * - * Queries that are not listed in invalidates will be ignored. - * @default false - */ - readonly awaitInvalidates?: reactQuery.QueryKey[] | boolean - } - - readonly queryMeta: { - /** - * Whether to persist the query cache in the storage. Defaults to `true`. - * Use `false` to disable persistence for a specific query, for example for - * a sensitive data or data that can't be persisted, e.g. class instances. - * @default true - */ - readonly persist?: boolean - } - } -} - -// eslint-disable-next-line @typescript-eslint/no-magic-numbers -const DEFAULT_QUERY_STALE_TIME_MS = 2 * 60 * 1000 -// eslint-disable-next-line @typescript-eslint/no-magic-numbers -const DEFAULT_QUERY_PERSIST_TIME_MS = 30 * 24 * 60 * 60 * 1000 // 30 days - -const DEFAULT_BUSTER = 'v1.1' - -/** - * Create a new React Query client. - */ -export function createReactQueryClient() { - const store = idbKeyval.createStore('enso', 'query-persist-cache') - reactQuery.onlineManager.setOnline(navigator.onLine) - - const persister = persistClientCore.experimental_createPersister({ - storage: { - getItem: key => idbKeyval.get(key, store), - setItem: (key, value) => idbKeyval.set(key, value, store), - removeItem: key => idbKeyval.del(key, store), - }, - // Prefer online first and don't rely on the local cache if user is online - // fallback to the local cache only if the user is offline - maxAge: reactQuery.onlineManager.isOnline() ? -1 : DEFAULT_QUERY_PERSIST_TIME_MS, - buster: DEFAULT_BUSTER, - filters: { predicate: query => query.meta?.persist !== false }, - prefix: 'enso:query-persist:', - serialize: persistedQuery => persistedQuery, - deserialize: persistedQuery => persistedQuery, - }) - - const queryClient: reactQuery.QueryClient = new reactQuery.QueryClient({ - mutationCache: new reactQuery.MutationCache({ - onSuccess: (_data, _variables, _context, mutation) => { - const shouldAwaitInvalidates = mutation.meta?.awaitInvalidates ?? false - const invalidates = mutation.meta?.invalidates ?? [] - const invalidatesToAwait = (() => { - if (Array.isArray(shouldAwaitInvalidates)) { - return shouldAwaitInvalidates - } else { - return shouldAwaitInvalidates ? invalidates : [] - } - })() - const invalidatesToIgnore = invalidates.filter( - queryKey => !invalidatesToAwait.includes(queryKey) - ) - - for (const queryKey of invalidatesToIgnore) { - void queryClient.invalidateQueries({ - predicate: query => reactQuery.matchQuery({ queryKey }, query), - }) - } - - if (invalidatesToAwait.length > 0) { - // eslint-disable-next-line no-restricted-syntax - return Promise.all( - invalidatesToAwait.map(queryKey => - queryClient.invalidateQueries({ - predicate: query => reactQuery.matchQuery({ queryKey }, query), - }) - ) - ) - } - }, - }), - defaultOptions: { - queries: { - persister, - refetchOnReconnect: 'always', - staleTime: DEFAULT_QUERY_STALE_TIME_MS, - retry: (failureCount, error: unknown) => { - // eslint-disable-next-line @typescript-eslint/no-magic-numbers - const statusesToIgnore = [401, 403, 404] - const errorStatus = - typeof error === 'object' && - error != null && - 'status' in error && - typeof error.status === 'number' - ? error.status - : -1 - - if (statusesToIgnore.includes(errorStatus)) { - return false - } else { - return failureCount < 3 - } - }, - }, - }, - }) - - Object.defineProperty(queryClient, 'nukePersister', { - value: () => idbKeyval.clear(store), - enumerable: false, - configurable: false, - writable: false, - }) - - Object.defineProperty(queryClient, 'clearWithPersister', { - value: () => { - queryClient.clear() - return queryClient.nukePersister() - }, - enumerable: false, - configurable: false, - writable: false, - }) - - return queryClient -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bbf63bf742e1..77a0c314b940 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -115,6 +115,9 @@ importers: '@open-rpc/client-js': specifier: ^1.8.1 version: 1.8.1 + '@tanstack/vue-query': + specifier: '>= 5.45.0 < 5.46.0' + version: 5.45.0(vue@3.4.31(typescript@5.5.3)) '@vueuse/core': specifier: ^10.4.1 version: 10.11.0(vue@3.4.31(typescript@5.5.3)) @@ -523,7 +526,23 @@ importers: specifier: ^5.3.3 version: 5.3.3(@types/node@20.11.21)(lightningcss@1.25.1) - app/ide-desktop/lib/common: {} + app/ide-desktop/lib/common: + dependencies: + '@tanstack/query-core': + specifier: 5.45.0 + version: 5.45.0 + '@tanstack/query-persist-client-core': + specifier: ^5.45.0 + version: 5.45.0 + '@tanstack/vue-query': + specifier: '>= 5.45.0 < 5.46.0' + version: 5.45.0(vue@3.4.31(typescript@5.5.3)) + idb-keyval: + specifier: ^6.2.1 + version: 6.2.1 + vue: + specifier: ^3.4.19 + version: 3.4.31(typescript@5.5.3) app/ide-desktop/lib/content-config: dependencies: @@ -554,12 +573,12 @@ importers: '@sentry/react': specifier: ^7.74.0 version: 7.118.0(react@18.3.1) - '@tanstack/query-persist-client-core': - specifier: 5.45.0 - version: 5.45.0 '@tanstack/react-query': specifier: 5.45.1 version: 5.45.1(react@18.3.1) + '@tanstack/vue-query': + specifier: '>= 5.45.0 < 5.46.0' + version: 5.45.0(vue@3.4.31(typescript@5.5.3)) ajv: specifier: ^8.12.0 version: 8.16.0 @@ -572,9 +591,6 @@ importers: enso-common: specifier: workspace:* version: link:../common - idb-keyval: - specifier: 6.2.1 - version: 6.2.1 is-network-error: specifier: ^1.0.1 version: 1.1.0 @@ -2601,6 +2617,10 @@ packages: resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} engines: {node: '>=10'} + '@tanstack/match-sorter-utils@8.15.1': + resolution: {integrity: sha512-PnVV3d2poenUM31ZbZi/yXkBu3J7kd5k2u51CGwwNojag451AjTH9N6n41yjXz2fpLeewleyLBmNS6+HcGDlXw==} + engines: {node: '>=12'} + '@tanstack/query-core@5.45.0': resolution: {integrity: sha512-RVfIZQmFUTdjhSAAblvueimfngYyfN6HlwaJUPK71PKd7yi43Vs1S/rdimmZedPWX/WGppcq/U1HOj7O7FwYxw==} @@ -2621,6 +2641,15 @@ packages: peerDependencies: react: ^18.0.0 + '@tanstack/vue-query@5.45.0': + resolution: {integrity: sha512-WogAH4+xDPWbiK9CUXAE4cQiCyvWeYZI3g3/onKbkb3tVnoEPRhbGHANgxpfAEFY165Vj4afKnI3hkVQvr7aHA==} + peerDependencies: + '@vue/composition-api': ^1.1.2 + vue: ^2.6.0 || ^3.3.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + '@tootallnate/once@2.0.0': resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} @@ -3009,6 +3038,9 @@ packages: '@vue/compiler-ssr@3.4.31': resolution: {integrity: sha512-RtefmITAje3fJ8FSg1gwgDhdKhZVntIVbwupdyZDSifZTRMiWxWehAOTCc8/KZDnBOcYQ4/9VWxsTbd3wT0hAA==} + '@vue/devtools-api@6.6.3': + resolution: {integrity: sha512-0MiMsFma/HqA6g3KLKn+AGpL1kgKhFWszC9U29NfpWK5LE7bjeXxySWJrOJ77hBz+TBrBQ7o4QJqbPbqbs8rJw==} + '@vue/devtools-core@7.3.5': resolution: {integrity: sha512-uSC3IkIp6MtyJYSh5xzY99sgqlAXLq+peE2KKXTi6JeRHOtMngFWFWENXi70IJ1EVGYztiFQoHhI9WZcgKBz8g==} peerDependencies: @@ -6204,6 +6236,9 @@ packages: resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} engines: {node: '>= 0.4'} + remove-accents@0.5.0: + resolution: {integrity: sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==} + request@2.88.2: resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==} engines: {node: '>= 6'} @@ -9832,6 +9867,10 @@ snapshots: dependencies: defer-to-connect: 2.0.1 + '@tanstack/match-sorter-utils@8.15.1': + dependencies: + remove-accents: 0.5.0 + '@tanstack/query-core@5.45.0': {} '@tanstack/query-devtools@5.37.1': {} @@ -9851,6 +9890,14 @@ snapshots: '@tanstack/query-core': 5.45.0 react: 18.3.1 + '@tanstack/vue-query@5.45.0(vue@3.4.31(typescript@5.5.3))': + dependencies: + '@tanstack/match-sorter-utils': 8.15.1 + '@tanstack/query-core': 5.45.0 + '@vue/devtools-api': 6.6.3 + vue: 3.4.31(typescript@5.5.3) + vue-demi: 0.14.8(vue@3.4.31(typescript@5.5.3)) + '@tootallnate/once@2.0.0': {} '@tsconfig/node18@18.2.4': {} @@ -10368,6 +10415,8 @@ snapshots: '@vue/compiler-dom': 3.4.31 '@vue/shared': 3.4.31 + '@vue/devtools-api@6.6.3': {} + '@vue/devtools-core@7.3.5(vite@5.3.3(@types/node@20.11.21)(lightningcss@1.25.1))(vue@3.4.31(typescript@5.5.3))': dependencies: '@vue/devtools-kit': 7.3.5 @@ -14043,6 +14092,8 @@ snapshots: es-errors: 1.3.0 set-function-name: 2.0.2 + remove-accents@0.5.0: {} + request@2.88.2: dependencies: aws-sign2: 0.7.0