From 9b35106a718c53a4d8ce2c056a35544c93b32fb8 Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Thu, 25 Sep 2025 10:28:10 -0600 Subject: [PATCH 1/3] switch search endpoint --- .../src/api/asset_interfaces/search/index.ts | 1 - .../api/asset_interfaces/search/interfaces.ts | 10 ---- .../api/buster_rest/search/queryRequests.ts | 15 ++++-- .../src/api/buster_rest/search/requests.ts | 13 ++--- apps/web/src/api/query_keys/search.ts | 7 ++- .../features/dashboard/AddMetricModal.tsx | 47 ++++++++++--------- apps/web/src/styles/styles.css | 5 ++ packages/database/src/schema-types/search.ts | 3 +- packages/server-shared/package.json | 4 ++ packages/server-shared/src/search/search.ts | 3 +- .../src/type-utilities/pagination.ts | 4 +- 11 files changed, 59 insertions(+), 53 deletions(-) delete mode 100644 apps/web/src/api/asset_interfaces/search/index.ts delete mode 100644 apps/web/src/api/asset_interfaces/search/interfaces.ts diff --git a/apps/web/src/api/asset_interfaces/search/index.ts b/apps/web/src/api/asset_interfaces/search/index.ts deleted file mode 100644 index 56e2ab61f..000000000 --- a/apps/web/src/api/asset_interfaces/search/index.ts +++ /dev/null @@ -1 +0,0 @@ -export type * from './interfaces'; diff --git a/apps/web/src/api/asset_interfaces/search/interfaces.ts b/apps/web/src/api/asset_interfaces/search/interfaces.ts deleted file mode 100644 index 457e0318c..000000000 --- a/apps/web/src/api/asset_interfaces/search/interfaces.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { ShareAssetType } from '@buster/server-shared/share'; - -export interface BusterSearchResult { - id: string; - highlights: string[]; - name: string; - updated_at: string; - type: ShareAssetType; - score: number; -} diff --git a/apps/web/src/api/buster_rest/search/queryRequests.ts b/apps/web/src/api/buster_rest/search/queryRequests.ts index 70b37d685..3b181602f 100644 --- a/apps/web/src/api/buster_rest/search/queryRequests.ts +++ b/apps/web/src/api/buster_rest/search/queryRequests.ts @@ -1,15 +1,22 @@ +import type { SearchTextResponse } from '@buster/server-shared/search'; import { keepPreviousData, type UseQueryOptions, useQuery } from '@tanstack/react-query'; +import type { RustApiError } from '@/api/errors'; import { searchQueryKeys } from '@/api/query_keys/search'; import { search } from './requests'; -export const useSearch = ( +export const useSearch = ( params: Parameters[0], - options?: Omit>>, 'queryKey' | 'queryFn'> + options?: Omit, 'queryKey' | 'queryFn'>, + postQueryOptions?: { + doNotUnwrapData?: boolean; + } ) => { - return useQuery({ + const { doNotUnwrapData = false } = postQueryOptions || {}; + return useQuery({ ...searchQueryKeys.getSearchResult(params), queryFn: () => search(params), - placeholderData: keepPreviousData, + select: options?.select, ...options, + placeholderData: keepPreviousData, }); }; diff --git a/apps/web/src/api/buster_rest/search/requests.ts b/apps/web/src/api/buster_rest/search/requests.ts index 27a327cc5..248095260 100644 --- a/apps/web/src/api/buster_rest/search/requests.ts +++ b/apps/web/src/api/buster_rest/search/requests.ts @@ -1,11 +1,6 @@ -import type { AssetType } from '@buster/server-shared/assets'; -import type { BusterSearchResult } from '@/api/asset_interfaces/search'; -import { mainApi } from '../instances'; +import type { SearchTextRequest, SearchTextResponse } from '@buster/server-shared/search'; +import { mainApiV2 } from '../instances'; -export const search = async (params: { - query: string; - asset_types: Extract[]; - num_results?: number; -}) => { - return mainApi.post('/search', params).then((res) => res.data); +export const search = async (params: SearchTextRequest) => { + return mainApiV2.get('/search', { params }).then((res) => res.data); }; diff --git a/apps/web/src/api/query_keys/search.ts b/apps/web/src/api/query_keys/search.ts index 5ecb49c20..944612157 100644 --- a/apps/web/src/api/query_keys/search.ts +++ b/apps/web/src/api/query_keys/search.ts @@ -1,9 +1,8 @@ +import type { SearchTextRequest, SearchTextResponse } from '@buster/server-shared'; import { queryOptions } from '@tanstack/react-query'; -import type { BusterSearchResult } from '@/api/asset_interfaces/search'; -import type { search } from '../buster_rest/search'; -export const getSearchResult = (params: Parameters[0]) => - queryOptions({ +export const getSearchResult = (params: SearchTextRequest) => + queryOptions({ queryKey: ['search', 'results', params] as const, staleTime: 1000 * 15, // 15 seconds, }); diff --git a/apps/web/src/components/features/dashboard/AddMetricModal.tsx b/apps/web/src/components/features/dashboard/AddMetricModal.tsx index 0b2fa226f..52bbc61b7 100644 --- a/apps/web/src/components/features/dashboard/AddMetricModal.tsx +++ b/apps/web/src/components/features/dashboard/AddMetricModal.tsx @@ -1,5 +1,5 @@ -import React, { useEffect, useMemo, useState } from 'react'; -import type { BusterSearchResult } from '@/api/asset_interfaces/search'; +import type { SearchTextRequest, SearchTextResponse } from '@buster/server-shared/search'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useSearch } from '@/api/buster_rest/search'; import { Button } from '@/components/ui/buttons'; import { @@ -46,29 +46,34 @@ export const AddMetricModal: React.FC<{ const { data: searchResults } = useSearch( { query: debouncedSearchTerm, - asset_types: ['metric_file'], - num_results: 100, - }, - { enabled: true } + assetTypes: ['metric_file'], + page_size: 25, + page: 1, + } satisfies SearchTextRequest, + { enabled: true, select: useCallback((data: SearchTextResponse) => data.data, []) } ); - const columns = useMemo['columns']>( + const columns = useMemo['columns']>( () => [ { - title: 'Name', - dataIndex: 'name', - }, - { - title: 'Updated', - dataIndex: 'updated_at', - width: 140, + title: 'Title', + dataIndex: 'title', render: (value) => { - return formatDate({ - date: value, - format: 'lll', - }); + // biome-ignore lint/security/noDangerouslySetInnerHtml: + return ; }, }, + // { + // title: 'Updated', + // dataIndex: 'updated_at', + // width: 140, + // render: (value) => { + // return formatDate({ + // date: value, + // format: 'lll', + // }); + // }, + // }, ], [] ); @@ -76,8 +81,8 @@ export const AddMetricModal: React.FC<{ const rows = useMemo(() => { return ( searchResults?.map((result) => ({ - id: result.id, - dataTestId: `item-${result.id}`, + id: result.assetId, + dataTestId: `item-${result.assetId}`, data: result, })) || [] ); @@ -100,7 +105,7 @@ export const AddMetricModal: React.FC<{ const item = rows.find((row) => row.id === id); return { id: id, - name: item?.data?.name || id, + name: item?.data?.title || id, }; }); diff --git a/apps/web/src/styles/styles.css b/apps/web/src/styles/styles.css index 0f49854c8..a3e6769d1 100644 --- a/apps/web/src/styles/styles.css +++ b/apps/web/src/styles/styles.css @@ -53,3 +53,8 @@ p { th { @apply text-base font-normal; } + +strong, +b { + @apply font-semibold +} diff --git a/packages/database/src/schema-types/search.ts b/packages/database/src/schema-types/search.ts index 265a186d7..7c53340e1 100644 --- a/packages/database/src/schema-types/search.ts +++ b/packages/database/src/schema-types/search.ts @@ -1,8 +1,9 @@ import z from 'zod'; +import { AssetTypeSchema } from './asset'; export const TextSearchResultSchema = z.object({ assetId: z.string().uuid(), - assetType: z.string(), + assetType: AssetTypeSchema, title: z.string(), additionalText: z.string().nullable(), }); diff --git a/packages/server-shared/package.json b/packages/server-shared/package.json index 1edaf014d..5451c8d1a 100644 --- a/packages/server-shared/package.json +++ b/packages/server-shared/package.json @@ -99,6 +99,10 @@ "./healthcheck": { "types": "./dist/healthcheck/index.d.ts", "default": "./dist/healthcheck/index.js" + }, + "./search": { + "types": "./dist/search/index.d.ts", + "default": "./dist/search/index.js" } }, "module": "src/index.ts", diff --git a/packages/server-shared/src/search/search.ts b/packages/server-shared/src/search/search.ts index 4882d2f2e..e01f83479 100644 --- a/packages/server-shared/src/search/search.ts +++ b/packages/server-shared/src/search/search.ts @@ -28,7 +28,8 @@ export const SearchTextRequestSchema = z } return Boolean(val); }, z.boolean()) - .default(false), + .default(false) + .optional(), endDate: z.string().datetime().optional(), startDate: z.string().datetime().optional(), }) diff --git a/packages/server-shared/src/type-utilities/pagination.ts b/packages/server-shared/src/type-utilities/pagination.ts index eb18eb4f6..7f858d510 100644 --- a/packages/server-shared/src/type-utilities/pagination.ts +++ b/packages/server-shared/src/type-utilities/pagination.ts @@ -18,6 +18,6 @@ export const PaginatedResponseSchema = (schema: z.ZodType) => export type PaginatedResponse = z.infer>>; export const PaginatedRequestSchema = z.object({ - page: z.coerce.number().min(1).default(1), - page_size: z.coerce.number().min(1).max(5000).default(250), + page: z.coerce.number().min(1).optional().default(1).optional(), + page_size: z.coerce.number().min(1).max(5000).default(250).optional(), }); From 34914026a5027c18e06788af96d1c182a85c7511 Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Thu, 25 Sep 2025 10:46:31 -0600 Subject: [PATCH 2/3] query serializer --- apps/web/package.json | 2 + .../src/api/buster_rest/search/requests.ts | 1 + apps/web/src/api/createAxiosInstance.ts | 4 ++ .../features/dashboard/AddMetricModal.tsx | 37 ++++++++++++------- pnpm-lock.yaml | 13 ++++++- 5 files changed, 42 insertions(+), 15 deletions(-) diff --git a/apps/web/package.json b/apps/web/package.json index bf89bcc3e..1ba724ed6 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -127,6 +127,7 @@ "@tiptap/react": "^3.5.0", "@tiptap/starter-kit": "^3.5.0", "@tiptap/suggestion": "^3.5.0", + "@types/qs": "^6.14.0", "@udecode/cn": "^49.0.15", "@uploadthing/react": "^7.3.3", "axios": "catalog:", @@ -157,6 +158,7 @@ "platejs": "49.2.21", "pluralize": "^8.0.0", "posthog-js": "^1.268.1", + "qs": "^6.14.0", "react": "catalog:", "react-colorful": "^5.6.1", "react-day-picker": "^8.10.1", diff --git a/apps/web/src/api/buster_rest/search/requests.ts b/apps/web/src/api/buster_rest/search/requests.ts index 248095260..fe9efb9d4 100644 --- a/apps/web/src/api/buster_rest/search/requests.ts +++ b/apps/web/src/api/buster_rest/search/requests.ts @@ -1,4 +1,5 @@ import type { SearchTextRequest, SearchTextResponse } from '@buster/server-shared/search'; +import qs from 'qs'; import { mainApiV2 } from '../instances'; export const search = async (params: SearchTextRequest) => { diff --git a/apps/web/src/api/createAxiosInstance.ts b/apps/web/src/api/createAxiosInstance.ts index 30d49d40c..a4cb571ef 100644 --- a/apps/web/src/api/createAxiosInstance.ts +++ b/apps/web/src/api/createAxiosInstance.ts @@ -1,6 +1,7 @@ import { isServer } from '@tanstack/react-query'; import type { AxiosRequestHeaders } from 'axios'; import axios, { type AxiosError, type InternalAxiosRequestConfig } from 'axios'; +import qs from 'qs'; import { getSupabaseSession } from '@/integrations/supabase/getSupabaseUserClient'; import { Route as AuthRoute } from '@/routes/auth.login'; import { BASE_URL_V2 } from './config'; @@ -15,6 +16,9 @@ export const createAxiosInstance = (baseURL = BASE_URL_V2) => { headers: { 'Content-Type': 'application/json', }, + paramsSerializer: (params) => { + return qs.stringify(params, { arrayFormat: 'repeat' }); //💰 + }, }); // Response interceptor with retry logic for auth errors diff --git a/apps/web/src/components/features/dashboard/AddMetricModal.tsx b/apps/web/src/components/features/dashboard/AddMetricModal.tsx index 52bbc61b7..e2e8c98d1 100644 --- a/apps/web/src/components/features/dashboard/AddMetricModal.tsx +++ b/apps/web/src/components/features/dashboard/AddMetricModal.tsx @@ -9,6 +9,7 @@ import { import { useDebounce } from '@/hooks/useDebounce'; import { useMemoizedFn } from '@/hooks/useMemoizedFn'; import { formatDate } from '@/lib/date'; +import { assetTypeToIcon } from '../icons/assetIcons'; export const AddMetricModal: React.FC<{ open: boolean; @@ -58,22 +59,30 @@ export const AddMetricModal: React.FC<{ { title: 'Title', dataIndex: 'title', - render: (value) => { - // biome-ignore lint/security/noDangerouslySetInnerHtml: - return ; + render: (value, record) => { + const Icon = assetTypeToIcon(record.assetType); + return ( +
+ + + + {/* biome-ignore lint/security/noDangerouslySetInnerHtml: this endpoint is sanitized */} + +
+ ); + }, + }, + { + title: 'Updated at', + dataIndex: 'updatedAt', + width: 140, + render: (value) => { + return formatDate({ + date: value, + format: 'lll', + }); }, }, - // { - // title: 'Updated', - // dataIndex: 'updated_at', - // width: 140, - // render: (value) => { - // return formatDate({ - // date: value, - // format: 'lll', - // }); - // }, - // }, ], [] ); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a9b36c0ef..6dbd03272 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -703,6 +703,9 @@ importers: '@tiptap/suggestion': specifier: ^3.5.0 version: 3.5.0(@tiptap/core@3.5.0(@tiptap/pm@3.5.0))(@tiptap/pm@3.5.0) + '@types/qs': + specifier: ^6.14.0 + version: 6.14.0 '@udecode/cn': specifier: ^49.0.15 version: 49.0.15(@types/react@19.1.13)(class-variance-authority@0.7.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(tailwind-merge@3.3.1) @@ -793,6 +796,9 @@ importers: posthog-js: specifier: ^1.268.1 version: 1.268.1 + qs: + specifier: ^6.14.0 + version: 6.14.0 react: specifier: 'catalog:' version: 19.1.1 @@ -6356,6 +6362,9 @@ packages: '@types/prismjs@1.26.5': resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==} + '@types/qs@6.14.0': + resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} + '@types/react-dom@19.1.9': resolution: {integrity: sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==} peerDependencies: @@ -19323,6 +19332,8 @@ snapshots: '@types/prismjs@1.26.5': {} + '@types/qs@6.14.0': {} + '@types/react-dom@19.1.9(@types/react@19.1.13)': dependencies: '@types/react': 19.1.13 @@ -19591,7 +19602,7 @@ snapshots: sirv: 3.0.1 tinyglobby: 0.2.14 tinyrainbow: 2.0.0 - vitest: 3.2.4(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@22.18.1)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.11.3(@types/node@22.18.1)(typescript@5.9.2))(sass@1.93.1)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1) + vitest: 3.2.4(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@24.3.1)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.11.3(@types/node@24.3.1)(typescript@5.9.2))(sass@1.93.1)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1) '@vitest/utils@3.2.4': dependencies: From bf1b08344cd972e0ab09ffa1f059616d24f5e845 Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Thu, 25 Sep 2025 10:59:24 -0600 Subject: [PATCH 3/3] table content copy --- apps/web/src/api/asset_interfaces/index.ts | 1 - .../collections/AddToCollectionModal.tsx | 25 ++++++++++--------- .../components/features/icons/assetIcons.tsx | 1 - .../MetricChartCard/MetricChartCard.tsx | 11 ++++---- .../elements/MetricElement/MetricContent.tsx | 6 +++++ apps/web/src/styles/styles.css | 2 +- .../src/type-utilities/pagination.ts | 4 +-- 7 files changed, 28 insertions(+), 22 deletions(-) diff --git a/apps/web/src/api/asset_interfaces/index.ts b/apps/web/src/api/asset_interfaces/index.ts index 584aae590..bf6113131 100644 --- a/apps/web/src/api/asset_interfaces/index.ts +++ b/apps/web/src/api/asset_interfaces/index.ts @@ -7,7 +7,6 @@ export type * from './datasources'; export type * from './metric'; export type * from './permission'; export type * from './permission_groups'; -export type * from './search'; export type * from './sql'; export type * from './teams'; export type * from './terms'; diff --git a/apps/web/src/components/features/collections/AddToCollectionModal.tsx b/apps/web/src/components/features/collections/AddToCollectionModal.tsx index e16451095..dfb36b625 100644 --- a/apps/web/src/components/features/collections/AddToCollectionModal.tsx +++ b/apps/web/src/components/features/collections/AddToCollectionModal.tsx @@ -1,6 +1,6 @@ +import type { SearchTextResponse } from '@buster/server-shared/search'; import type { ShareAssetType } from '@buster/server-shared/share'; import React, { useLayoutEffect, useMemo, useState } from 'react'; -import type { BusterSearchResult } from '@/api/asset_interfaces/search'; import { useAddAndRemoveAssetsFromCollection, useGetCollection, @@ -33,8 +33,9 @@ export const AddToCollectionModal: React.FC<{ const { data: searchResults } = useSearch({ query: debouncedSearchTerm, - asset_types: ['metric_file', 'dashboard_file'], - num_results: 100, + assetTypes: ['metric_file', 'dashboard_file'], + page_size: 50, + page: 1, }); const [selectedAssets, setSelectedAssets] = useState([]); @@ -42,13 +43,13 @@ export const AddToCollectionModal: React.FC<{ return selectedAssets.map((asset) => asset.id); }, [selectedAssets]); - const columns = useMemo['columns']>( + const columns = useMemo['columns']>( () => [ { title: 'Name', - dataIndex: 'name', - render: (name, data) => { - const Icon = assetTypeToIcon(data.type) || ASSET_ICONS.metrics; + dataIndex: 'title', + render: (name, record) => { + const Icon = assetTypeToIcon(record.assetType) || ASSET_ICONS.metrics; return (
@@ -61,7 +62,7 @@ export const AddToCollectionModal: React.FC<{ }, { title: 'Updated', - dataIndex: 'updated_at', + dataIndex: 'updatedAt', width: 140, render: (value: string) => { return formatDate({ @@ -74,10 +75,10 @@ export const AddToCollectionModal: React.FC<{ [] ); - const rows: BusterListRowItem[] = useMemo(() => { + const rows: BusterListRowItem[] = useMemo(() => { return ( - searchResults?.map((asset) => ({ - id: asset.id, + searchResults?.data?.map((asset) => ({ + id: asset.assetId, data: asset, })) || [] ); @@ -86,7 +87,7 @@ export const AddToCollectionModal: React.FC<{ const createKeySearchResultMap = useMemoizedFn(() => { const map = new Map(); rows.forEach((asset) => { - if (asset.data?.type) map.set(asset.id, { type: asset.data?.type, id: asset.id }); + if (asset.data?.assetType) map.set(asset.id, { type: asset.data?.assetType, id: asset.id }); }); return map; }); diff --git a/apps/web/src/components/features/icons/assetIcons.tsx b/apps/web/src/components/features/icons/assetIcons.tsx index 21d4fb171..6a7236394 100644 --- a/apps/web/src/components/features/icons/assetIcons.tsx +++ b/apps/web/src/components/features/icons/assetIcons.tsx @@ -11,7 +11,6 @@ import { SquareChart, Table, } from '@/components/ui/icons'; -import type { iconProps } from '@/components/ui/icons/NucleoIconOutlined/iconProps'; import SquareChartPlus from '@/components/ui/icons/NucleoIconOutlined/square-chart-plus'; export const ASSET_ICONS = { diff --git a/apps/web/src/components/features/metrics/MetricChartCard/MetricChartCard.tsx b/apps/web/src/components/features/metrics/MetricChartCard/MetricChartCard.tsx index a3f1b9bcd..e3562063a 100644 --- a/apps/web/src/components/features/metrics/MetricChartCard/MetricChartCard.tsx +++ b/apps/web/src/components/features/metrics/MetricChartCard/MetricChartCard.tsx @@ -16,7 +16,6 @@ export type MetricChartCardProps = { metricId: string; versionNumber: number | undefined; readOnly?: boolean; - className?: string; attributes?: DraggableAttributes; listeners?: DraggableSyntheticListeners; headerSecondaryContent?: React.ReactNode; @@ -25,7 +24,7 @@ export type MetricChartCardProps = { renderChartContent?: boolean; // we do this to avoid expensive rendering if off screen disableTooltip?: boolean; cacheDataId?: string; -}; +} & React.HTMLAttributes; const stableMetricSelect = ({ chart_config, @@ -62,6 +61,7 @@ export const MetricChartCard = React.memo( renderChartContent = true, disableTooltip, cacheDataId, + ...rest }, ref ) => { @@ -102,6 +102,7 @@ export const MetricChartCard = React.memo( errorData={errorData} isTable={isTable} className={className} + {...rest} > ; const MetricViewChartCardContainer = React.forwardRef< HTMLDivElement, MetricViewChartCardContainerProps ->(({ children, loadingData, hasData, errorData, isTable, className }, ref) => { +>(({ children, loadingData, hasData, errorData, isTable, className, ...divProps }, ref) => { const cardClass = React.useMemo(() => { if (loadingData || errorData || !hasData) return 'h-full max-h-[600px]'; if (isTable) return 'h-full'; @@ -161,6 +161,7 @@ const MetricViewChartCardContainer = React.forwardRef<
) => { + e.stopPropagation(); + }); + return ( ) } + onCopy={handleCopy} /> ); } diff --git a/apps/web/src/styles/styles.css b/apps/web/src/styles/styles.css index a3e6769d1..3345b9523 100644 --- a/apps/web/src/styles/styles.css +++ b/apps/web/src/styles/styles.css @@ -56,5 +56,5 @@ th { strong, b { - @apply font-semibold + @apply font-semibold; } diff --git a/packages/server-shared/src/type-utilities/pagination.ts b/packages/server-shared/src/type-utilities/pagination.ts index 7f858d510..806fecfa1 100644 --- a/packages/server-shared/src/type-utilities/pagination.ts +++ b/packages/server-shared/src/type-utilities/pagination.ts @@ -18,6 +18,6 @@ export const PaginatedResponseSchema = (schema: z.ZodType) => export type PaginatedResponse = z.infer>>; export const PaginatedRequestSchema = z.object({ - page: z.coerce.number().min(1).optional().default(1).optional(), - page_size: z.coerce.number().min(1).max(5000).default(250).optional(), + page: z.coerce.number().min(1).optional().default(1), + page_size: z.coerce.number().min(1).max(5000).default(250), });