mirror of
https://github.com/enso-org/enso.git
synced 2024-12-21 04:51:38 +03:00
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:
parent
e4da96e943
commit
bc92035683
3
.github/CODEOWNERS
vendored
3
.github/CODEOWNERS
vendored
@ -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
|
||||||
|
@ -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",
|
||||||
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
175
app/ide-desktop/lib/common/src/queryClient.ts
Normal file
175
app/ide-desktop/lib/common/src/queryClient.ts
Normal 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
|
||||||
|
}
|
99
app/ide-desktop/lib/common/src/vueQuery.ts
Normal file
99
app/ide-desktop/lib/common/src/vueQuery.ts
Normal 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 */
|
@ -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",
|
||||||
|
@ -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
|
||||||
|
@ -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(),
|
||||||
})
|
})
|
||||||
|
@ -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(
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user