From f50b6d4cf70e563af1c79f5a4c8d803c99fd365c Mon Sep 17 00:00:00 2001 From: MUI bot <2109932+Janpot@users.noreply.github.com> Date: Tue, 29 Aug 2023 20:22:31 +0200 Subject: [PATCH 1/7] WIP --- packages/toolpad-app/cli/server.ts | 8 +- packages/toolpad-app/src/api.ts | 85 +----------------- packages/toolpad-app/src/canvas/index.tsx | 3 +- packages/toolpad-app/src/rpcClient.ts | 90 ++++++++++++++++++- .../toolpad-app/src/runtime/ToolpadApp.tsx | 41 +++------ packages/toolpad-app/src/runtime/api.ts | 20 +++++ .../toolpad-app/src/runtime/useDataQuery.ts | 36 +------- .../toolpad-app/src/server/DataManager.ts | 52 ++++++----- packages/toolpad-app/src/server/rpc.ts | 50 +---------- .../src/server/rpcRuntimeServer.ts | 16 ++++ packages/toolpad-app/src/server/rpcServer.ts | 48 ++++++++++ .../src/server/toolpadAppServer.ts | 7 +- 12 files changed, 236 insertions(+), 220 deletions(-) create mode 100644 packages/toolpad-app/src/runtime/api.ts create mode 100644 packages/toolpad-app/src/server/rpcRuntimeServer.ts create mode 100644 packages/toolpad-app/src/server/rpcServer.ts diff --git a/packages/toolpad-app/cli/server.ts b/packages/toolpad-app/cli/server.ts index 954b171916e..c167e6796f7 100644 --- a/packages/toolpad-app/cli/server.ts +++ b/packages/toolpad-app/cli/server.ts @@ -24,10 +24,12 @@ import { initProject, } from '../src/server/localMode'; import type { Command as AppDevServerCommand, AppViteServerConfig, WorkerRpc } from './appServer'; -import { createRpcHandler, createRpcServer } from '../src/server/rpc'; +import { createRpcHandler } from '../src/server/rpc'; import { RUNTIME_CONFIG_WINDOW_PROPERTY } from '../src/constants'; import type { RuntimeConfig } from '../src/config'; import { createWorkerRpcServer } from '../src/server/workerRpc'; +import { createRpcServer } from '../src/server/rpcServer'; +import { createRpcRuntimeServer } from '../src/server/rpcRuntimeServer'; const DEFAULT_PORT = 3000; @@ -86,7 +88,9 @@ async function createDevHandler( worker.postMessage({ kind: 'reload-components' } satisfies AppDevServerCommand); }); - handler.use('/api/data', project.dataManager.createDataHandler(project)); + handler.use('/api/data', project.dataManager.createDataHandler()); + const runtimeRpcServer = createRpcRuntimeServer(project); + handler.use('/api/runtime-rpc', createRpcHandler(runtimeRpcServer)); handler.use( (req, res, next) => { // Stall the request until the dev server is ready diff --git a/packages/toolpad-app/src/api.ts b/packages/toolpad-app/src/api.ts index 2efbed068b7..e78901d9998 100644 --- a/packages/toolpad-app/src/api.ts +++ b/packages/toolpad-app/src/api.ts @@ -1,15 +1,6 @@ -import invariant from 'invariant'; -import { - QueryClient, - useMutation, - UseMutationOptions, - UseMutationResult, - useQuery, - UseQueryOptions, - UseQueryResult, -} from '@tanstack/react-query'; -import type { Definition, MethodsGroup, MethodsOf, ServerDefinition } from './server/rpc'; -import { createRpcClient } from './rpcClient'; +import { QueryClient } from '@tanstack/react-query'; +import { createRpcApi } from './rpcClient'; +import type { ServerDefinition } from './server/rpcServer'; export const queryClient = new QueryClient({ defaultOptions: { @@ -22,72 +13,4 @@ export const queryClient = new QueryClient({ }, }); -export interface UseQueryFnOptions any> - extends Omit< - UseQueryOptions>, unknown, Awaited>, any[]>, - 'queryKey' | 'queryFn' - > {} - -interface UseQueryFn { - ( - name: K, - params: Parameters | null, - options?: UseQueryFnOptions, - ): UseQueryResult>>; -} - -interface UseMutationFn { - ( - name: K, - options?: UseMutationOptions>, - ): UseMutationResult>, unknown, Parameters>; -} - -interface RpcClient { - query: D['query']; - mutation: D['mutation']; -} - -interface ApiClient extends RpcClient { - query: D['query']; - mutation: D['mutation']; - useQuery: UseQueryFn; - useMutation: UseMutationFn; - refetchQueries: ( - key: K, - params?: Parameters, - ) => Promise; - invalidateQueries: ( - key: K, - params?: Parameters, - ) => Promise; -} - -function createClient>(endpoint: string): ApiClient { - const { query, mutation } = createRpcClient(endpoint); - - return { - query, - mutation, - useQuery: (key, params, options) => { - return useQuery({ - ...options, - enabled: !!params && options?.enabled !== false, - queryKey: [key, params], - queryFn: () => { - invariant(params, `"enabled" prop of useQuery should prevent this call'`); - return query[key](...params); - }, - }); - }, - useMutation: (key, options) => useMutation((params) => mutation[key](...params), options), - refetchQueries(key, params?) { - return queryClient.refetchQueries(params ? [key, params] : [key]); - }, - invalidateQueries(key, params?) { - return queryClient.invalidateQueries(params ? [key, params] : [key]); - }, - }; -} - -export default createClient('/api/rpc'); +export default createRpcApi(queryClient, '/api/rpc'); diff --git a/packages/toolpad-app/src/canvas/index.tsx b/packages/toolpad-app/src/canvas/index.tsx index acf970fc5e3..69f6769ca67 100644 --- a/packages/toolpad-app/src/canvas/index.tsx +++ b/packages/toolpad-app/src/canvas/index.tsx @@ -2,7 +2,8 @@ import * as React from 'react'; import invariant from 'invariant'; import { throttle } from 'lodash-es'; import { CanvasEventsContext } from '@mui/toolpad-core/runtime'; -import ToolpadApp, { LoadComponents, queryClient } from '../runtime/ToolpadApp'; +import ToolpadApp, { LoadComponents } from '../runtime/ToolpadApp'; +import { queryClient } from '../runtime/api'; import { AppCanvasState } from '../types'; import getPageViewState from './getPageViewState'; import { rectContainsPoint } from '../utils/geometry'; diff --git a/packages/toolpad-app/src/rpcClient.ts b/packages/toolpad-app/src/rpcClient.ts index da92e952335..8736023e957 100644 --- a/packages/toolpad-app/src/rpcClient.ts +++ b/packages/toolpad-app/src/rpcClient.ts @@ -1,5 +1,22 @@ import { parse as superjsonParse } from 'superjson'; -import type { MethodsOf, MethodsOfGroup, RpcRequest, RpcResponse } from './server/rpc'; +import invariant from 'invariant'; +import { + QueryClient, + useMutation, + UseMutationOptions, + UseMutationResult, + useQuery, + UseQueryOptions, + UseQueryResult, +} from '@tanstack/react-query'; +import type { + MethodsOf, + MethodsOfGroup, + RpcRequest, + RpcResponse, + Definition, + MethodsGroup, +} from './server/rpc'; function createFetcher(endpoint: string, type: 'query' | 'mutation'): MethodsOfGroup { return new Proxy( @@ -51,3 +68,74 @@ export function createRpcClient>(endpoint: string): Rpc const mutation = createFetcher(endpoint, 'mutation'); return { query, mutation }; } + +export interface UseQueryFnOptions any> + extends Omit< + UseQueryOptions>, unknown, Awaited>, any[]>, + 'queryKey' | 'queryFn' + > {} + +export interface UseQueryFn { + ( + name: K, + params: Parameters | null, + options?: UseQueryFnOptions, + ): UseQueryResult>>; +} + +export interface UseMutationFn { + ( + name: K, + options?: UseMutationOptions>, + ): UseMutationResult>, unknown, Parameters>; +} + +export interface RpcPiClient { + query: D['query']; + mutation: D['mutation']; +} + +export interface ApiClient extends RpcPiClient { + query: D['query']; + mutation: D['mutation']; + useQuery: UseQueryFn; + useMutation: UseMutationFn; + refetchQueries: ( + key: K, + params?: Parameters, + ) => Promise; + invalidateQueries: ( + key: K, + params?: Parameters, + ) => Promise; +} + +export function createRpcApi>( + queryClient: QueryClient, + endpoint: string, +): ApiClient { + const { query, mutation } = createRpcClient(endpoint); + + return { + query, + mutation, + useQuery: (key, params, options) => { + return useQuery({ + ...options, + enabled: !!params && options?.enabled !== false, + queryKey: [key, params], + queryFn: () => { + invariant(params, `"enabled" prop of useQuery should prevent this call'`); + return query[key](...params); + }, + }); + }, + useMutation: (key, options) => useMutation((params) => mutation[key](...params), options), + refetchQueries(key, params?) { + return queryClient.refetchQueries(params ? [key, params] : [key]); + }, + invalidateQueries(key, params?) { + return queryClient.invalidateQueries(params ? [key, params] : [key]); + }, + }; +} diff --git a/packages/toolpad-app/src/runtime/ToolpadApp.tsx b/packages/toolpad-app/src/runtime/ToolpadApp.tsx index 565e0cb1e6f..93f7f1ebf33 100644 --- a/packages/toolpad-app/src/runtime/ToolpadApp.tsx +++ b/packages/toolpad-app/src/runtime/ToolpadApp.tsx @@ -36,7 +36,7 @@ import { import { createProvidedContext, useAssertedContext } from '@mui/toolpad-utils/react'; import { mapProperties, mapValues } from '@mui/toolpad-utils/collections'; import { set as setObjectPath } from 'lodash-es'; -import { QueryClient, QueryClientProvider, useMutation } from '@tanstack/react-query'; +import { QueryClientProvider } from '@tanstack/react-query'; import { BrowserRouter, Routes, @@ -79,11 +79,12 @@ import evalJsBindings, { } from './evalJsBindings'; import { HTML_ID_EDITOR_OVERLAY } from './constants'; import { layoutBoxArgTypes } from './toolpadComponents/layoutBox'; -import { execDataSourceQuery, useDataQuery, UseFetch } from './useDataQuery'; +import { useDataQuery, UseFetch } from './useDataQuery'; import { NavigateToPage } from './CanvasHooksContext'; import PreviewHeader from './PreviewHeader'; import useEvent from '../utils/useEvent'; import { AppLayout } from './AppLayout'; +import api, { queryClient } from './api'; const browserJsRuntime = getBrowserRuntime(); @@ -1266,7 +1267,6 @@ function MutationNode({ node, page }: MutationNodeProps) { const { bindings } = useAssertedContext(RuntimeScopeContext); - const queryId = node.id; const { value: params } = resolveBindables( bindings, `${node.id}.params`, @@ -1275,22 +1275,16 @@ function MutationNode({ node, page }: MutationNodeProps) { const { isLoading, - data: responseData = EMPTY_OBJECT, + data: responseData, error: fetchError, mutateAsync, - } = useMutation( - async (overrides: any = {}) => - execDataSourceQuery({ - pageName: page.name, - queryName: node.name, - params: { ...params, ...overrides }, - }), - { - mutationKey: [queryId, params], - }, - ); + } = api.useMutation('execQuery'); + + const call = useEvent(async (overrides: any = {}) => { + await mutateAsync([page.name, node.name, { ...params, ...overrides }]); + }); - const { data, error: apiError } = responseData; + const { data, error: apiError } = responseData || EMPTY_OBJECT; const error = apiError || fetchError; @@ -1302,13 +1296,13 @@ function MutationNode({ node, page }: MutationNodeProps) { error, data, rows: Array.isArray(data) ? data : EMPTY_ARRAY, - call: mutateAsync, - fetch: mutateAsync, + call, + fetch: call, refetch: () => { throw new Error(`refetch is not supported in manual queries`); }, }), - [isLoading, error, mutateAsync, data], + [isLoading, error, data, call], ); React.useEffect(() => { @@ -1471,15 +1465,6 @@ function AppError({ error }: FallbackProps) { ); } -export const queryClient = new QueryClient({ - defaultOptions: { - queries: { - retry: false, - staleTime: 60 * 1000, - }, - }, -}); - export interface ToolpadAppLayoutProps { dom: appDom.RenderTree; hasShell?: boolean; diff --git a/packages/toolpad-app/src/runtime/api.ts b/packages/toolpad-app/src/runtime/api.ts new file mode 100644 index 00000000000..5c6b0ed1c7c --- /dev/null +++ b/packages/toolpad-app/src/runtime/api.ts @@ -0,0 +1,20 @@ +import { QueryClient } from '@tanstack/react-query'; +import { createRpcApi } from '../rpcClient'; +import type { ServerDefinition } from '../server/rpcRuntimeServer'; + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + networkMode: 'always', + }, + mutations: { + networkMode: 'always', + }, + }, +}); + +export default createRpcApi( + queryClient, + `${process.env.BASE_URL}/api/runtime-rpc`, +); diff --git a/packages/toolpad-app/src/runtime/useDataQuery.ts b/packages/toolpad-app/src/runtime/useDataQuery.ts index e5c71a5cd89..ef077050864 100644 --- a/packages/toolpad-app/src/runtime/useDataQuery.ts +++ b/packages/toolpad-app/src/runtime/useDataQuery.ts @@ -5,39 +5,7 @@ import * as React from 'react'; import { useQuery, UseQueryOptions } from '@tanstack/react-query'; import { CanvasHooksContext } from './CanvasHooksContext'; import * as appDom from '../appDom'; - -interface ExecDataSourceQueryParams { - signal?: AbortSignal; - pageName: string; - queryName: string; - params: any; -} - -export async function execDataSourceQuery({ - signal, - pageName, - queryName, - params, -}: ExecDataSourceQueryParams) { - const dataUrl = new URL(`${process.env.BASE_URL}/api/data/`, window.location.href); - const url = new URL( - `./${encodeURIComponent(pageName)}/${encodeURIComponent(queryName)}`, - dataUrl, - ); - - const res = await fetch(String(url), { - method: 'POST', - body: JSON.stringify(params), - headers: [['content-type', 'application/json']], - signal, - }); - - if (!res.ok) { - throw new Error(`HTTP ${res.status} while fetching "${url}"`); - } - - return res.json(); -} +import api from './api'; export type UseDataQueryConfig = Pick< UseQueryOptions, @@ -84,7 +52,7 @@ export function useDataQuery( refetch, } = useQuery( [nodeHash, pageName, queryName, params], - ({ signal }) => execDataSourceQuery({ signal, pageName, queryName, params }), + () => api.mutation.execQuery(pageName, queryName, params), { ...options, enabled: isNodeAvailableOnServer && enabled, diff --git a/packages/toolpad-app/src/server/DataManager.ts b/packages/toolpad-app/src/server/DataManager.ts index e6f25d49c41..39b137f8578 100644 --- a/packages/toolpad-app/src/server/DataManager.ts +++ b/packages/toolpad-app/src/server/DataManager.ts @@ -76,7 +76,10 @@ export default class DataManager { await this.project.saveDom(dom); } - async execQuery(dataNode: appDom.QueryNode, params: Q): Promise> { + async execDataNodeQuery( + dataNode: appDom.QueryNode, + params: Q, + ): Promise> { const dataSource: ServerDataSource | undefined = dataNode.attributes.dataSource ? this.dataSources.get(dataNode.attributes.dataSource) : undefined; @@ -102,6 +105,28 @@ export default class DataManager { return result; } + async execQuery(pageName: string, queryName: string, params: any): Promise> { + const dom = await this.project.loadDom(); + + const page = appDom.getPageByName(dom, pageName); + + if (!page) { + throw new Error(`Unknown page "${pageName}"`); + } + + const dataNode = appDom.getQueryByName(dom, page, queryName); + + if (!dataNode) { + throw new Error(`Unknown query "${queryName}"`); + } + + if (!appDom.isQuery(dataNode)) { + throw new Error(`Invalid node type for data request`); + } + + return this.execDataNodeQuery(dataNode, params); + } + async dataSourceFetchPrivate( dataSourceId: string, connectionId: NodeId | null, @@ -140,7 +165,7 @@ export default class DataManager { return dataSource.api[method](...args); } - createDataHandler(project: IToolpadProject) { + createDataHandler() { const router = express.Router(); router.use( @@ -158,33 +183,12 @@ export default class DataManager { const { pageName, queryName } = req.params; invariant(typeof pageName === 'string', 'pageName url param required'); - invariant(typeof queryName === 'string', 'queryName url variable required'); - const dom = await project.loadDom(); - - const page = appDom.getPageByName(dom, pageName); - - if (!page) { - res.status(404).end(); - return; - } - - const dataNode = appDom.getQueryByName(dom, page, queryName); - - if (!dataNode) { - res.status(404).end(); - return; - } - - if (!appDom.isQuery(dataNode)) { - throw new Error(`Invalid node type for data request`); - } - try { const ctx = createServerContext(req); const result = await withContext(ctx, async () => { - return this.execQuery(dataNode, req.body); + return this.execQuery(pageName, queryName, req.body); }); res.json(withSerializedError(result)); } catch (error) { diff --git a/packages/toolpad-app/src/server/rpc.ts b/packages/toolpad-app/src/server/rpc.ts index e20c23ccfc6..f8d35fb5582 100644 --- a/packages/toolpad-app/src/server/rpc.ts +++ b/packages/toolpad-app/src/server/rpc.ts @@ -7,7 +7,6 @@ import { hasOwnProperty } from '@mui/toolpad-utils/collections'; import { errorFrom, serializeError } from '@mui/toolpad-utils/errors'; import { withContext, createServerContext } from '@mui/toolpad-core/server'; import { asyncHandler } from '../utils/express'; -import type { ToolpadProject } from './localMode'; export interface Method

{ (...params: P): Promise; @@ -99,55 +98,10 @@ interface ResolverInput

{ res: ServerResponse; } -interface MethodResolver { +export interface MethodResolver { (input: ResolverInput>): ReturnType; } -function createMethod(handler: MethodResolver): MethodResolver { +export function createMethod(handler: MethodResolver): MethodResolver { return handler; } - -export function createRpcServer(project: ToolpadProject) { - return { - query: { - dataSourceFetchPrivate: createMethod( - ({ params }) => { - return project.dataManager.dataSourceFetchPrivate(...params); - }, - ), - execQuery: createMethod(({ params }) => { - return project.dataManager.execQuery(...params); - }), - loadDom: createMethod(({ params }) => { - return project.loadDom(...params); - }), - getVersionInfo: createMethod(({ params }) => { - return project.getVersionInfo(...params); - }), - }, - mutation: { - saveDom: createMethod(({ params }) => { - return project.saveDom(...params); - }), - applyDomDiff: createMethod(({ params }) => { - return project.applyDomDiff(...params); - }), - openCodeEditor: createMethod(({ params }) => { - return project.openCodeEditor(...params); - }), - createComponent: createMethod(({ params }) => { - return project.createComponent(...params); - }), - deletePage: createMethod(({ params }) => { - return project.deletePage(...params); - }), - dataSourceExecPrivate: createMethod( - ({ params }) => { - return project.dataManager.dataSourceExecPrivate(...params); - }, - ), - }, - } as const; -} - -export type ServerDefinition = MethodsOf>; diff --git a/packages/toolpad-app/src/server/rpcRuntimeServer.ts b/packages/toolpad-app/src/server/rpcRuntimeServer.ts new file mode 100644 index 00000000000..c9c199f1206 --- /dev/null +++ b/packages/toolpad-app/src/server/rpcRuntimeServer.ts @@ -0,0 +1,16 @@ +import { createMethod, MethodsOf } from './rpc'; +import type { ToolpadProject } from './localMode'; + +// Methods exposed to the Toolpad editor +export function createRpcRuntimeServer(project: ToolpadProject) { + return { + query: {}, + mutation: { + execQuery: createMethod(({ params }) => { + return project.dataManager.execQuery(...params); + }), + }, + } as const; +} + +export type ServerDefinition = MethodsOf>; diff --git a/packages/toolpad-app/src/server/rpcServer.ts b/packages/toolpad-app/src/server/rpcServer.ts new file mode 100644 index 00000000000..d0370865ebb --- /dev/null +++ b/packages/toolpad-app/src/server/rpcServer.ts @@ -0,0 +1,48 @@ +import { createMethod, MethodsOf } from './rpc'; +import type { ToolpadProject } from './localMode'; + +// Methods exposed to the Toolpad editor +export function createRpcServer(project: ToolpadProject) { + return { + query: { + dataSourceFetchPrivate: createMethod( + ({ params }) => { + return project.dataManager.dataSourceFetchPrivate(...params); + }, + ), + execQuery: createMethod(({ params }) => { + return project.dataManager.execDataNodeQuery(...params); + }), + loadDom: createMethod(({ params }) => { + return project.loadDom(...params); + }), + getVersionInfo: createMethod(({ params }) => { + return project.getVersionInfo(...params); + }), + }, + mutation: { + saveDom: createMethod(({ params }) => { + return project.saveDom(...params); + }), + applyDomDiff: createMethod(({ params }) => { + return project.applyDomDiff(...params); + }), + openCodeEditor: createMethod(({ params }) => { + return project.openCodeEditor(...params); + }), + createComponent: createMethod(({ params }) => { + return project.createComponent(...params); + }), + deletePage: createMethod(({ params }) => { + return project.deletePage(...params); + }), + dataSourceExecPrivate: createMethod( + ({ params }) => { + return project.dataManager.dataSourceExecPrivate(...params); + }, + ), + }, + } as const; +} + +export type ServerDefinition = MethodsOf>; diff --git a/packages/toolpad-app/src/server/toolpadAppServer.ts b/packages/toolpad-app/src/server/toolpadAppServer.ts index 2a78bc201aa..543cb388026 100644 --- a/packages/toolpad-app/src/server/toolpadAppServer.ts +++ b/packages/toolpad-app/src/server/toolpadAppServer.ts @@ -6,6 +6,8 @@ import { postProcessHtml } from './toolpadAppBuilder'; import { ToolpadProject, getAppOutputFolder } from './localMode'; import { asyncHandler } from '../utils/express'; import { basicAuthUnauthorized, checkBasicAuthHeader } from './basicAuth'; +import { createRpcRuntimeServer } from './rpcRuntimeServer'; +import { createRpcHandler } from './rpc'; export interface CreateViteConfigParams { server?: Server; @@ -32,7 +34,10 @@ export async function createProdHandler(project: ToolpadProject) { basicAuthUnauthorized(res); }); - handler.use('/api/data', project.dataManager.createDataHandler(project)); + handler.use('/api/data', project.dataManager.createDataHandler()); + + const runtimeRpcServer = createRpcRuntimeServer(project); + handler.use('/api/runtime-rpc', createRpcHandler(runtimeRpcServer)); handler.use( asyncHandler(async (req, res) => { From 725042e515fe4166c096e05920a362ce8d413be6 Mon Sep 17 00:00:00 2001 From: MUI bot <2109932+Janpot@users.noreply.github.com> Date: Wed, 30 Aug 2023 15:01:44 +0200 Subject: [PATCH 2/7] dewfs --- packages/toolpad-app/package.json | 3 +-- .../toolpad-app/src/runtime/ToolpadApp.tsx | 23 +++++++++++-------- yarn.lock | 5 ---- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/packages/toolpad-app/package.json b/packages/toolpad-app/package.json index 2ad8f279b3c..104fb9bb2b1 100644 --- a/packages/toolpad-app/package.json +++ b/packages/toolpad-app/package.json @@ -118,7 +118,7 @@ "semver": "7.5.4", "serialize-javascript": "6.0.1", "superjson": "1.13.1", - "typescript": "5.1.6", + "typescript": "5.2.2", "vite": "4.4.9", "vm-browserify": "^1.1.2", "whatwg-url": "13.0.0", @@ -158,7 +158,6 @@ "monaco-editor": "0.40.0", "react-devtools-inline": "4.28.0", "react-transition-group": "4.4.5", - "typescript": "5.2.2", "webpack": "5.88.2" }, "typings": "./index.d.ts", diff --git a/packages/toolpad-app/src/runtime/ToolpadApp.tsx b/packages/toolpad-app/src/runtime/ToolpadApp.tsx index 93f7f1ebf33..b8d5ebd6f75 100644 --- a/packages/toolpad-app/src/runtime/ToolpadApp.tsx +++ b/packages/toolpad-app/src/runtime/ToolpadApp.tsx @@ -36,7 +36,7 @@ import { import { createProvidedContext, useAssertedContext } from '@mui/toolpad-utils/react'; import { mapProperties, mapValues } from '@mui/toolpad-utils/collections'; import { set as setObjectPath } from 'lodash-es'; -import { QueryClientProvider } from '@tanstack/react-query'; +import { QueryClientProvider, useMutation } from '@tanstack/react-query'; import { BrowserRouter, Routes, @@ -1275,14 +1275,17 @@ function MutationNode({ node, page }: MutationNodeProps) { const { isLoading, - data: responseData, + data: responseData = EMPTY_OBJECT, error: fetchError, mutateAsync, - } = api.useMutation('execQuery'); - - const call = useEvent(async (overrides: any = {}) => { - await mutateAsync([page.name, node.name, { ...params, ...overrides }]); - }); + } = useMutation( + async (overrides: any = {}) => { + await api.mutation.execQuery(page.name, node.name, { ...params, ...overrides }); + }, + { + mutationKey: [node.name, params], + }, + ); const { data, error: apiError } = responseData || EMPTY_OBJECT; @@ -1296,13 +1299,13 @@ function MutationNode({ node, page }: MutationNodeProps) { error, data, rows: Array.isArray(data) ? data : EMPTY_ARRAY, - call, - fetch: call, + call: mutateAsync, + fetch: mutateAsync, refetch: () => { throw new Error(`refetch is not supported in manual queries`); }, }), - [isLoading, error, data, call], + [isLoading, error, data, mutateAsync], ); React.useEffect(() => { diff --git a/yarn.lock b/yarn.lock index 0c18b3a10e7..9eb93c31a0f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13772,11 +13772,6 @@ typescript@3.8.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== -typescript@5.1.6: - version "5.1.6" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274" - integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA== - typescript@5.2.2, "typescript@>=3 < 6": version "5.2.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" From 73ff80d269a1a5e93bd6ac4a2f3c4d256fd286e4 Mon Sep 17 00:00:00 2001 From: MUI bot <2109932+Janpot@users.noreply.github.com> Date: Wed, 30 Aug 2023 15:04:33 +0200 Subject: [PATCH 3/7] Update DataManager.ts --- .../toolpad-app/src/server/DataManager.ts | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/toolpad-app/src/server/DataManager.ts b/packages/toolpad-app/src/server/DataManager.ts index 39b137f8578..db353542e9e 100644 --- a/packages/toolpad-app/src/server/DataManager.ts +++ b/packages/toolpad-app/src/server/DataManager.ts @@ -124,7 +124,12 @@ export default class DataManager { throw new Error(`Invalid node type for data request`); } - return this.execDataNodeQuery(dataNode, params); + try { + const result = await this.execDataNodeQuery(dataNode, params); + return withSerializedError(result); + } catch (error) { + return withSerializedError({ error }); + } } async dataSourceFetchPrivate( @@ -185,15 +190,11 @@ export default class DataManager { invariant(typeof pageName === 'string', 'pageName url param required'); invariant(typeof queryName === 'string', 'queryName url variable required'); - try { - const ctx = createServerContext(req); - const result = await withContext(ctx, async () => { - return this.execQuery(pageName, queryName, req.body); - }); - res.json(withSerializedError(result)); - } catch (error) { - res.json(withSerializedError({ error })); - } + const ctx = createServerContext(req); + const result = await withContext(ctx, async () => { + return this.execQuery(pageName, queryName, req.body); + }); + res.json(result); }), ); From bb2af8bdc0afdc5da29bd77dba65d004c7de1fe4 Mon Sep 17 00:00:00 2001 From: MUI bot <2109932+Janpot@users.noreply.github.com> Date: Wed, 30 Aug 2023 16:10:23 +0200 Subject: [PATCH 4/7] Update context.tsx --- packages/toolpad-app/src/toolpadDataSources/context.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/toolpad-app/src/toolpadDataSources/context.tsx b/packages/toolpad-app/src/toolpadDataSources/context.tsx index a9cc172382e..27bea8421af 100644 --- a/packages/toolpad-app/src/toolpadDataSources/context.tsx +++ b/packages/toolpad-app/src/toolpadDataSources/context.tsx @@ -1,7 +1,8 @@ import { UseQueryResult } from '@tanstack/react-query'; import { NodeId } from '@mui/toolpad-core'; import { createProvidedContext } from '@mui/toolpad-utils/react'; -import client, { UseQueryFnOptions } from '../api'; +import client from '../api'; +import { UseQueryFnOptions } from '../rpcClient'; export interface ConnectionContext { dataSourceId: string; From f3858a1ff8b5bb07f249c6bbcc1426714d5ca63d Mon Sep 17 00:00:00 2001 From: MUI bot <2109932+Janpot@users.noreply.github.com> Date: Wed, 30 Aug 2023 16:35:55 +0200 Subject: [PATCH 5/7] fix --- .../toolpad-app/src/runtime/ToolpadApp.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/toolpad-app/src/runtime/ToolpadApp.tsx b/packages/toolpad-app/src/runtime/ToolpadApp.tsx index b8d5ebd6f75..21977e2927c 100644 --- a/packages/toolpad-app/src/runtime/ToolpadApp.tsx +++ b/packages/toolpad-app/src/runtime/ToolpadApp.tsx @@ -1280,7 +1280,7 @@ function MutationNode({ node, page }: MutationNodeProps) { mutateAsync, } = useMutation( async (overrides: any = {}) => { - await api.mutation.execQuery(page.name, node.name, { ...params, ...overrides }); + return api.mutation.execQuery(page.name, node.name, { ...params, ...overrides }); }, { mutationKey: [node.name, params], @@ -1292,21 +1292,23 @@ function MutationNode({ node, page }: MutationNodeProps) { const error = apiError || fetchError; // Stabilize the mutation and prepare for inclusion in global scope - const mutationResult: UseFetch = React.useMemo( - () => ({ + const mutationResult: UseFetch = React.useMemo(() => { + const call = async (overrides: any = {}) => { + await mutateAsync(overrides); + }; + return { isLoading, isFetching: isLoading, error, data, rows: Array.isArray(data) ? data : EMPTY_ARRAY, - call: mutateAsync, - fetch: mutateAsync, + call, + fetch: call, refetch: () => { throw new Error(`refetch is not supported in manual queries`); }, - }), - [isLoading, error, data, mutateAsync], - ); + }; + }, [isLoading, error, data, mutateAsync]); React.useEffect(() => { for (const [key, value] of Object.entries(mutationResult)) { From 7f2af4ab0fa8d77ce87212469096f4ef91a6302d Mon Sep 17 00:00:00 2001 From: MUI bot <2109932+Janpot@users.noreply.github.com> Date: Wed, 30 Aug 2023 17:07:19 +0200 Subject: [PATCH 6/7] auth fix --- packages/toolpad-app/src/rpcClient.ts | 6 +++--- packages/toolpad-app/src/runtime/api.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/toolpad-app/src/rpcClient.ts b/packages/toolpad-app/src/rpcClient.ts index 8736023e957..4f64fb8614e 100644 --- a/packages/toolpad-app/src/rpcClient.ts +++ b/packages/toolpad-app/src/rpcClient.ts @@ -18,7 +18,7 @@ import type { MethodsGroup, } from './server/rpc'; -function createFetcher(endpoint: string, type: 'query' | 'mutation'): MethodsOfGroup { +function createFetcher(endpoint: string | URL, type: 'query' | 'mutation'): MethodsOfGroup { return new Proxy( {}, { @@ -63,7 +63,7 @@ export interface RpcClient> { mutation: D['mutation']; } -export function createRpcClient>(endpoint: string): RpcClient { +export function createRpcClient>(endpoint: string | URL): RpcClient { const query = createFetcher(endpoint, 'query'); const mutation = createFetcher(endpoint, 'mutation'); return { query, mutation }; @@ -112,7 +112,7 @@ export interface ApiClient extends RpcPiClient { export function createRpcApi>( queryClient: QueryClient, - endpoint: string, + endpoint: string | URL, ): ApiClient { const { query, mutation } = createRpcClient(endpoint); diff --git a/packages/toolpad-app/src/runtime/api.ts b/packages/toolpad-app/src/runtime/api.ts index 5c6b0ed1c7c..028511db3e7 100644 --- a/packages/toolpad-app/src/runtime/api.ts +++ b/packages/toolpad-app/src/runtime/api.ts @@ -16,5 +16,5 @@ export const queryClient = new QueryClient({ export default createRpcApi( queryClient, - `${process.env.BASE_URL}/api/runtime-rpc`, + new URL(`${process.env.BASE_URL}/api/runtime-rpc`, window.location.href), ); From 1acbc73c6f205500531d905fbbdb160f03a49ad5 Mon Sep 17 00:00:00 2001 From: MUI bot <2109932+Janpot@users.noreply.github.com> Date: Wed, 30 Aug 2023 17:24:43 +0200 Subject: [PATCH 7/7] flakyness --- test/integration/editor/index.spec.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/integration/editor/index.spec.ts b/test/integration/editor/index.spec.ts index 5a14996d64f..c0379294667 100644 --- a/test/integration/editor/index.spec.ts +++ b/test/integration/editor/index.spec.ts @@ -89,7 +89,11 @@ test('can delete elements from page', async ({ page }) => { // Delete element by pressing key + // Wait for the page to settle after the previous action. It seems that + await page.waitForTimeout(200); + await clickCenter(page, firstTextFieldLocator); + await page.keyboard.press('Backspace'); await expect(canvasInputLocator).toHaveCount(0); @@ -108,6 +112,9 @@ test('can delete elements from page', async ({ page }) => { name: 'last in column', }); + // Wait for the page to settle after the previous action. It seems that + await page.waitForTimeout(200); + await removeElementByClick(lastButtonInColumnLocator); await expect(canvasButtonLocator).toHaveCount(3);