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 73f2fb7972.

* Fix redundant dependency

* Work around a vue-query bug

* Lint

* fmt
This commit is contained in:
Kaz Wesley 2024-07-09 08:01:10 -07:00 committed by GitHub
parent e4da96e943
commit bc92035683
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 367 additions and 193 deletions

3
.github/CODEOWNERS vendored
View File

@ -45,3 +45,6 @@ Cargo.toml
# The data-link schema is owned by the libraries team # 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/datalinkSchema.json @radeusgd @jdunkerley @GregoryTravis @AdRiley @marthasharkey
/app/ide-desktop/lib/dashboard/src/data/__tests__ @radeusgd @jdunkerley @GregoryTravis @AdRiley @marthasharkey @PabloBuchu @indiv0 @somebody1234 /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

View File

@ -64,6 +64,7 @@
"@lezer/highlight": "^1.1.6", "@lezer/highlight": "^1.1.6",
"@noble/hashes": "^1.3.2", "@noble/hashes": "^1.3.2",
"@open-rpc/client-js": "^1.8.1", "@open-rpc/client-js": "^1.8.1",
"@tanstack/vue-query": ">= 5.45.0 < 5.46.0",
"@vueuse/core": "^10.4.1", "@vueuse/core": "^10.4.1",
"ag-grid-community": "^30.2.1", "ag-grid-community": "^30.2.1",
"ag-grid-enterprise": "^30.2.1", "ag-grid-enterprise": "^30.2.1",

View File

@ -1,9 +1,12 @@
import { baseConfig, configValue, mergeConfig } from '@/util/config' import { baseConfig, configValue, mergeConfig } from '@/util/config'
import { urlParams } from '@/util/urlParams' import { urlParams } from '@/util/urlParams'
import * as vueQuery from '@tanstack/vue-query'
import { isOnLinux } from 'enso-common/src/detect' import { isOnLinux } from 'enso-common/src/detect'
import * as commonQuery from 'enso-common/src/queryClient'
import * as dashboard from 'enso-dashboard' import * as dashboard from 'enso-dashboard'
import { isDevMode } from 'shared/util/detect' import { isDevMode } from 'shared/util/detect'
import { lazyVueInReact } from 'veaury' import { lazyVueInReact } from 'veaury'
import { type App } from 'vue'
import 'enso-dashboard/src/tailwind.css' import 'enso-dashboard/src/tailwind.css'
import type { EditorRunner } from '../../ide-desktop/lib/types/types' import type { EditorRunner } from '../../ide-desktop/lib/types/types'
@ -46,8 +49,6 @@ window.addEventListener('resize', () => {
scamWarningHandle = window.setTimeout(printScamWarning, SCAM_WARNING_TIMEOUT) scamWarningHandle = window.setTimeout(printScamWarning, SCAM_WARNING_TIMEOUT)
}) })
const appRunner = lazyVueInReact(AsyncApp as any /* async VueComponent */) as EditorRunner
/** The entrypoint into the IDE. */ /** The entrypoint into the IDE. */
function main() { function main() {
/** Note: Signing out always redirects to `/`. It is impossible to make this work, /** 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 projectManagerUrl = config.engine.projectManagerUrl || PROJECT_MANAGER_URL
const ydocUrl = config.engine.ydocUrl === '' ? YDOC_SERVER_URL : config.engine.ydocUrl const ydocUrl = config.engine.ydocUrl === '' ? YDOC_SERVER_URL : config.engine.ydocUrl
const initialProjectName = config.startup.project || null 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({ dashboard.run({
appRunner, appRunner,
@ -96,6 +106,7 @@ function main() {
} }
} }
}, },
queryClient,
}) })
} }

View File

@ -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.

View File

@ -10,6 +10,17 @@
"./src/buildUtils": "./src/buildUtils.js", "./src/buildUtils": "./src/buildUtils.js",
"./src/detect": "./src/detect.ts", "./src/detect": "./src/detect.ts",
"./src/gtag": "./src/gtag.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"
} }
} }

View File

@ -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<void>
/**
* Clear the cache stored in the persister storage.
*/
readonly nukePersister: () => Promise<void>
}
/**
* 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<persistClientCore.PersistedQuery>(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
}

View File

@ -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<queryCore.InvalidateQueryFilters> = {},
options: MaybeRefDeep<queryCore.InvalidateOptions> = {}
): Promise<void> {
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<T>(
value: MaybeRefDeep<T>,
customize?: (val: MaybeRefDeep<T>) => 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<T>(obj: MaybeRefDeep<T>): T {
return cloneDeep(obj, val => {
if (vue.isRef(val)) {
return cloneDeepUnref(vue.unref(val))
}
return undefined
})
}
type MaybeRefDeep<T> = vue.MaybeRef<
T extends Function
? T
: T extends object
? {
[Property in keyof T]: MaybeRefDeep<T[Property]>
}
: T
>
/* eslint-enable */

View File

@ -36,12 +36,11 @@
"@monaco-editor/react": "4.6.0", "@monaco-editor/react": "4.6.0",
"@sentry/react": "^7.74.0", "@sentry/react": "^7.74.0",
"@tanstack/react-query": "5.45.1", "@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", "ajv": "^8.12.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"enso-assets": "workspace:*", "enso-assets": "workspace:*",
"enso-common": "workspace:*", "enso-common": "workspace:*",
"idb-keyval": "6.2.1",
"is-network-error": "^1.0.1", "is-network-error": "^1.0.1",
"monaco-editor": "0.48.0", "monaco-editor": "0.48.0",
"react": "^18.3.1", "react": "^18.3.1",

View File

@ -158,6 +158,7 @@ export interface AppProps {
readonly appRunner: types.EditorRunner | null readonly appRunner: types.EditorRunner | null
readonly portalRoot: Element readonly portalRoot: Element
readonly httpClient: HttpClient readonly httpClient: HttpClient
readonly queryClient: reactQuery.QueryClient
} }
/** Component called by the parent module, returning the root React component for this /** Component called by the parent module, returning the root React component for this

View File

@ -1,4 +1,6 @@
/** @file Entry point into the cloud dashboard. */ /** @file Entry point into the cloud dashboard. */
import * as commonQuery from 'enso-common/src/queryClient'
import '#/tailwind.css' import '#/tailwind.css'
import * as main from '#/index' import * as main from '#/index'
@ -27,4 +29,5 @@ main.run({
projectManagerUrl: null, projectManagerUrl: null,
ydocUrl: null, ydocUrl: null,
appRunner: testAppRunner.TestAppRunner, appRunner: testAppRunner.TestAppRunner,
queryClient: commonQuery.createQueryClient(),
}) })

View File

@ -13,7 +13,6 @@ import * as detect from 'enso-common/src/detect'
import type * as app from '#/App' import type * as app from '#/App'
import App from '#/App' import App from '#/App'
import * as reactQueryClientModule from '#/reactQueryClient'
import LoadingScreen from '#/pages/authentication/LoadingScreen' 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. // This is not a React component even though it contains JSX.
// eslint-disable-next-line no-restricted-syntax // eslint-disable-next-line no-restricted-syntax
function run(props: Omit<app.AppProps, 'httpClient' | 'portalRoot'>) { function run(props: Omit<app.AppProps, 'httpClient' | 'portalRoot'>) {
const { vibrancy, supportsDeepLinks } = props const { vibrancy, supportsDeepLinks, queryClient } = props
if ( if (
!detect.IS_DEV_MODE && !detect.IS_DEV_MODE &&
process.env.ENSO_CLOUD_SENTRY_DSN != null && process.env.ENSO_CLOUD_SENTRY_DSN != null &&
@ -93,7 +92,6 @@ function run(props: Omit<app.AppProps, 'httpClient' | 'portalRoot'>) {
: supportsDeepLinks && detect.isOnElectron() : supportsDeepLinks && detect.isOnElectron()
const httpClient = new HttpClient() const httpClient = new HttpClient()
const queryClient = reactQueryClientModule.createReactQueryClient()
React.startTransition(() => { React.startTransition(() => {
reactDOM.createRoot(root).render( reactDOM.createRoot(root).render(

View File

@ -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<void>
/**
* Clear the cache stored in the persister storage.
*/
readonly nukePersister: () => Promise<void>
}
/**
* 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<persistClientCore.PersistedQuery>(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
}

View File

@ -115,6 +115,9 @@ importers:
'@open-rpc/client-js': '@open-rpc/client-js':
specifier: ^1.8.1 specifier: ^1.8.1
version: 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': '@vueuse/core':
specifier: ^10.4.1 specifier: ^10.4.1
version: 10.11.0(vue@3.4.31(typescript@5.5.3)) version: 10.11.0(vue@3.4.31(typescript@5.5.3))
@ -523,7 +526,23 @@ importers:
specifier: ^5.3.3 specifier: ^5.3.3
version: 5.3.3(@types/node@20.11.21)(lightningcss@1.25.1) 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: app/ide-desktop/lib/content-config:
dependencies: dependencies:
@ -554,12 +573,12 @@ importers:
'@sentry/react': '@sentry/react':
specifier: ^7.74.0 specifier: ^7.74.0
version: 7.118.0(react@18.3.1) version: 7.118.0(react@18.3.1)
'@tanstack/query-persist-client-core':
specifier: 5.45.0
version: 5.45.0
'@tanstack/react-query': '@tanstack/react-query':
specifier: 5.45.1 specifier: 5.45.1
version: 5.45.1(react@18.3.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: ajv:
specifier: ^8.12.0 specifier: ^8.12.0
version: 8.16.0 version: 8.16.0
@ -572,9 +591,6 @@ importers:
enso-common: enso-common:
specifier: workspace:* specifier: workspace:*
version: link:../common version: link:../common
idb-keyval:
specifier: 6.2.1
version: 6.2.1
is-network-error: is-network-error:
specifier: ^1.0.1 specifier: ^1.0.1
version: 1.1.0 version: 1.1.0
@ -2601,6 +2617,10 @@ packages:
resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==}
engines: {node: '>=10'} 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': '@tanstack/query-core@5.45.0':
resolution: {integrity: sha512-RVfIZQmFUTdjhSAAblvueimfngYyfN6HlwaJUPK71PKd7yi43Vs1S/rdimmZedPWX/WGppcq/U1HOj7O7FwYxw==} resolution: {integrity: sha512-RVfIZQmFUTdjhSAAblvueimfngYyfN6HlwaJUPK71PKd7yi43Vs1S/rdimmZedPWX/WGppcq/U1HOj7O7FwYxw==}
@ -2621,6 +2641,15 @@ packages:
peerDependencies: peerDependencies:
react: ^18.0.0 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': '@tootallnate/once@2.0.0':
resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
@ -3009,6 +3038,9 @@ packages:
'@vue/compiler-ssr@3.4.31': '@vue/compiler-ssr@3.4.31':
resolution: {integrity: sha512-RtefmITAje3fJ8FSg1gwgDhdKhZVntIVbwupdyZDSifZTRMiWxWehAOTCc8/KZDnBOcYQ4/9VWxsTbd3wT0hAA==} 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': '@vue/devtools-core@7.3.5':
resolution: {integrity: sha512-uSC3IkIp6MtyJYSh5xzY99sgqlAXLq+peE2KKXTi6JeRHOtMngFWFWENXi70IJ1EVGYztiFQoHhI9WZcgKBz8g==} resolution: {integrity: sha512-uSC3IkIp6MtyJYSh5xzY99sgqlAXLq+peE2KKXTi6JeRHOtMngFWFWENXi70IJ1EVGYztiFQoHhI9WZcgKBz8g==}
peerDependencies: peerDependencies:
@ -6204,6 +6236,9 @@ packages:
resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
remove-accents@0.5.0:
resolution: {integrity: sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==}
request@2.88.2: request@2.88.2:
resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==} resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@ -9832,6 +9867,10 @@ snapshots:
dependencies: dependencies:
defer-to-connect: 2.0.1 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-core@5.45.0': {}
'@tanstack/query-devtools@5.37.1': {} '@tanstack/query-devtools@5.37.1': {}
@ -9851,6 +9890,14 @@ snapshots:
'@tanstack/query-core': 5.45.0 '@tanstack/query-core': 5.45.0
react: 18.3.1 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': {} '@tootallnate/once@2.0.0': {}
'@tsconfig/node18@18.2.4': {} '@tsconfig/node18@18.2.4': {}
@ -10368,6 +10415,8 @@ snapshots:
'@vue/compiler-dom': 3.4.31 '@vue/compiler-dom': 3.4.31
'@vue/shared': 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))': '@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: dependencies:
'@vue/devtools-kit': 7.3.5 '@vue/devtools-kit': 7.3.5
@ -14043,6 +14092,8 @@ snapshots:
es-errors: 1.3.0 es-errors: 1.3.0
set-function-name: 2.0.2 set-function-name: 2.0.2
remove-accents@0.5.0: {}
request@2.88.2: request@2.88.2:
dependencies: dependencies:
aws-sign2: 0.7.0 aws-sign2: 0.7.0