diff --git a/apps/cms/.eslintrc.cjs b/apps/cms/.eslintrc.cjs
deleted file mode 100644
index 4f1d845f8cfdc..0000000000000
--- a/apps/cms/.eslintrc.cjs
+++ /dev/null
@@ -1,3 +0,0 @@
-module.exports = {
- extends: ['eslint-config-supabase/next'],
-}
diff --git a/apps/cms/eslint.config.cjs b/apps/cms/eslint.config.cjs
new file mode 100644
index 0000000000000..fec3ac9b3ab84
--- /dev/null
+++ b/apps/cms/eslint.config.cjs
@@ -0,0 +1,4 @@
+const { defineConfig } = require('eslint/config')
+const supabaseConfig = require('eslint-config-supabase/next')
+
+module.exports = defineConfig([supabaseConfig])
diff --git a/apps/cms/package.json b/apps/cms/package.json
index 25169cc2c0b1c..cc7a53cf52a81 100644
--- a/apps/cms/package.json
+++ b/apps/cms/package.json
@@ -12,7 +12,7 @@
"devsafe": "rm -rf .next && cross-env NODE_OPTIONS=--no-deprecation next dev",
"generate:importmap": "cross-env NODE_OPTIONS=--no-deprecation payload generate:importmap",
"generate:types": "cross-env NODE_OPTIONS=--no-deprecation payload generate:types",
- "lint": "cross-env NODE_OPTIONS=--no-deprecation next lint",
+ "lint": "eslint .",
"migrate": "cross-env NODE_OPTIONS=--no-deprecation tsx scripts/migrate.ts",
"payload": "cross-env NODE_OPTIONS=--no-deprecation payload",
"start": "cross-env NODE_OPTIONS=--no-deprecation next start",
diff --git a/apps/design-system/.eslintrc.cjs b/apps/design-system/.eslintrc.cjs
deleted file mode 100644
index 4f1d845f8cfdc..0000000000000
--- a/apps/design-system/.eslintrc.cjs
+++ /dev/null
@@ -1,3 +0,0 @@
-module.exports = {
- extends: ['eslint-config-supabase/next'],
-}
diff --git a/apps/design-system/eslint.config.cjs b/apps/design-system/eslint.config.cjs
new file mode 100644
index 0000000000000..fec3ac9b3ab84
--- /dev/null
+++ b/apps/design-system/eslint.config.cjs
@@ -0,0 +1,4 @@
+const { defineConfig } = require('eslint/config')
+const supabaseConfig = require('eslint-config-supabase/next')
+
+module.exports = defineConfig([supabaseConfig])
diff --git a/apps/design-system/package.json b/apps/design-system/package.json
index daeefc6cec0d0..cc8cd3e77cd06 100644
--- a/apps/design-system/package.json
+++ b/apps/design-system/package.json
@@ -10,7 +10,7 @@
"build": "pnpm run content:build && pnpm run build:registry && next build --turbopack",
"build:registry": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/build-registry.mts && prettier --log-level silent --write \"registry/**/*.{ts,tsx,mdx}\" --cache",
"start": "next start",
- "lint": "next lint",
+ "lint": "eslint .",
"content:dev": "contentlayer2 dev",
"content:build": "contentlayer2 build",
"clean": "rimraf node_modules .next .turbo",
diff --git a/apps/docs/.eslintrc.cjs b/apps/docs/.eslintrc.cjs
deleted file mode 100644
index 4f1d845f8cfdc..0000000000000
--- a/apps/docs/.eslintrc.cjs
+++ /dev/null
@@ -1,3 +0,0 @@
-module.exports = {
- extends: ['eslint-config-supabase/next'],
-}
diff --git a/apps/docs/eslint.config.cjs b/apps/docs/eslint.config.cjs
new file mode 100644
index 0000000000000..fec3ac9b3ab84
--- /dev/null
+++ b/apps/docs/eslint.config.cjs
@@ -0,0 +1,4 @@
+const { defineConfig } = require('eslint/config')
+const supabaseConfig = require('eslint-config-supabase/next')
+
+module.exports = defineConfig([supabaseConfig])
diff --git a/apps/docs/package.json b/apps/docs/package.json
index 17a773cb51dc5..e085fcb357aa4 100644
--- a/apps/docs/package.json
+++ b/apps/docs/package.json
@@ -22,7 +22,7 @@
"embeddings:nimbus:refresh": "ENABLED_FEATURES_OVERRIDE_DISABLE_ALL=true pnpm run embeddings:refresh",
"last-changed": "tsx scripts/last-changed.ts",
"last-changed:reset": "pnpm run last-changed -- --reset",
- "lint": "next lint",
+ "lint": "eslint .",
"lint:mdx": "supa-mdx-lint content --config ../../supa-mdx-lint.config.toml",
"postbuild": "pnpm run build:sitemap && pnpm run build:llms && ./../../scripts/upload-static-assets.sh",
"prebuild": "pnpm run codegen:graphql && pnpm run codegen:references && pnpm run codegen:examples",
diff --git a/apps/docs/turbo.json b/apps/docs/turbo.json
index 5e22a06e3d6bc..817dd4625c712 100644
--- a/apps/docs/turbo.json
+++ b/apps/docs/turbo.json
@@ -27,6 +27,9 @@
"NEXT_PUBLIC_SENTRY_DSN",
"NEXT_PUBLIC_VERCEL_ENV",
"NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA",
+ "VERCEL",
+ "VERCEL_ENV",
+ "VERCEL_GIT_COMMIT_SHA",
// These envs are used in the packages
"NEXT_PUBLIC_STORAGE_KEY",
"NEXT_PUBLIC_AUTH_DEBUG_KEY",
diff --git a/apps/studio/.eslintrc.js b/apps/studio/.eslintrc.js
deleted file mode 100644
index 45017c942f867..0000000000000
--- a/apps/studio/.eslintrc.js
+++ /dev/null
@@ -1,10 +0,0 @@
-module.exports = {
- extends: ['eslint-config-supabase/next'],
- plugins: ['eslint-plugin-barrel-files'],
- rules: {
- '@next/next/no-img-element': 'off',
- 'react/no-unescaped-entities': 'off',
- 'react/display-name': 'warn',
- 'barrel-files/avoid-re-export-all': 'error',
- },
-}
diff --git a/apps/studio/components/interfaces/Auth/EmailTemplates/TemplateEditor.tsx b/apps/studio/components/interfaces/Auth/EmailTemplates/TemplateEditor.tsx
index 0ef99c3b567ed..e70e547934be4 100644
--- a/apps/studio/components/interfaces/Auth/EmailTemplates/TemplateEditor.tsx
+++ b/apps/studio/components/interfaces/Auth/EmailTemplates/TemplateEditor.tsx
@@ -76,7 +76,6 @@ const TemplateEditor = ({ template }: TemplateEditorProps) => {
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false)
const [isSavingTemplate, setIsSavingTemplate] = useState(false)
- // eslint-disable-next-line react-hooks/exhaustive-deps
const spamRules = (validationResult?.rules ?? []).filter((rule) => rule.score > 0)
const preventSaveFromSpamCheck = builtInSMTP && spamRules.length > 0
diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/CostControl/SpendCapSidePanel.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/CostControl/SpendCapSidePanel.tsx
index 6051d14608b12..160d294d00ee5 100644
--- a/apps/studio/components/interfaces/Organization/BillingSettings/CostControl/SpendCapSidePanel.tsx
+++ b/apps/studio/components/interfaces/Organization/BillingSettings/CostControl/SpendCapSidePanel.tsx
@@ -74,7 +74,6 @@ const SpendCapSidePanel = () => {
if (visible && subscription !== undefined) {
setSelectedOption(isSpendCapOn ? 'on' : 'off')
}
- // eslint-disable-next-line react-hooks/exhaustive-deps
}, [visible, isLoading, subscription, isSpendCapOn])
const onConfirm = async () => {
diff --git a/apps/studio/components/interfaces/Organization/Usage/Usage.tsx b/apps/studio/components/interfaces/Organization/Usage/Usage.tsx
index 7d9aee83ae9f9..17644f6ff2f78 100644
--- a/apps/studio/components/interfaces/Organization/Usage/Usage.tsx
+++ b/apps/studio/components/interfaces/Organization/Usage/Usage.tsx
@@ -127,7 +127,6 @@ export const Usage = () => {
setSelectedProjectRefInputValue(projectRef)
}
// [Joshen] Since we're already looking at isSuccess
- // eslint-disable-next-line react-hooks/exhaustive-deps
}, [projectRef, isSuccessProjectDetail])
return (
diff --git a/apps/studio/components/interfaces/QueryPerformance/EnableIndexAdvisorButton.tsx b/apps/studio/components/interfaces/QueryPerformance/IndexAdvisor/EnableIndexAdvisorButton.tsx
similarity index 100%
rename from apps/studio/components/interfaces/QueryPerformance/EnableIndexAdvisorButton.tsx
rename to apps/studio/components/interfaces/QueryPerformance/IndexAdvisor/EnableIndexAdvisorButton.tsx
diff --git a/apps/studio/components/interfaces/QueryPerformance/IndexAdvisorDisabledState.tsx b/apps/studio/components/interfaces/QueryPerformance/IndexAdvisor/IndexAdvisorDisabledState.tsx
similarity index 98%
rename from apps/studio/components/interfaces/QueryPerformance/IndexAdvisorDisabledState.tsx
rename to apps/studio/components/interfaces/QueryPerformance/IndexAdvisor/IndexAdvisorDisabledState.tsx
index 18e8be8ae945f..be9fdbe3a5a56 100644
--- a/apps/studio/components/interfaces/QueryPerformance/IndexAdvisorDisabledState.tsx
+++ b/apps/studio/components/interfaces/QueryPerformance/IndexAdvisor/IndexAdvisorDisabledState.tsx
@@ -8,7 +8,7 @@ import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-ex
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { DOCS_URL } from 'lib/constants'
import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, Button } from 'ui'
-import { Markdown } from '../Markdown'
+import { Markdown } from '../../Markdown'
import { getIndexAdvisorExtensions } from './index-advisor.utils'
export const IndexAdvisorDisabledState = () => {
diff --git a/apps/studio/components/interfaces/QueryPerformance/IndexImprovementText.tsx b/apps/studio/components/interfaces/QueryPerformance/IndexAdvisor/IndexImprovementText.tsx
similarity index 100%
rename from apps/studio/components/interfaces/QueryPerformance/IndexImprovementText.tsx
rename to apps/studio/components/interfaces/QueryPerformance/IndexAdvisor/IndexImprovementText.tsx
diff --git a/apps/studio/components/interfaces/QueryPerformance/IndexSuggestionIcon.tsx b/apps/studio/components/interfaces/QueryPerformance/IndexAdvisor/IndexSuggestionIcon.tsx
similarity index 97%
rename from apps/studio/components/interfaces/QueryPerformance/IndexSuggestionIcon.tsx
rename to apps/studio/components/interfaces/QueryPerformance/IndexAdvisor/IndexSuggestionIcon.tsx
index 7ecc597535998..5aed88b1479b1 100644
--- a/apps/studio/components/interfaces/QueryPerformance/IndexSuggestionIcon.tsx
+++ b/apps/studio/components/interfaces/QueryPerformance/IndexAdvisor/IndexSuggestionIcon.tsx
@@ -14,8 +14,8 @@ import {
WarningIcon,
} from 'ui'
import { IndexImprovementText } from './IndexImprovementText'
-import { QueryPanelScoreSection } from './QueryPanel'
-import { useIndexInvalidation } from './hooks/useIndexInvalidation'
+import { QueryPanelScoreSection } from '../QueryPanel'
+import { useIndexInvalidation } from '../hooks/useIndexInvalidation'
import { createIndexes } from './index-advisor.utils'
interface IndexSuggestionIconProps {
diff --git a/apps/studio/components/interfaces/QueryPerformance/index-advisor.utils.ts b/apps/studio/components/interfaces/QueryPerformance/IndexAdvisor/index-advisor.utils.ts
similarity index 97%
rename from apps/studio/components/interfaces/QueryPerformance/index-advisor.utils.ts
rename to apps/studio/components/interfaces/QueryPerformance/IndexAdvisor/index-advisor.utils.ts
index b6275421cbd37..134aea1c3226c 100644
--- a/apps/studio/components/interfaces/QueryPerformance/index-advisor.utils.ts
+++ b/apps/studio/components/interfaces/QueryPerformance/IndexAdvisor/index-advisor.utils.ts
@@ -96,7 +96,7 @@ export async function createIndexes({
* @returns Whether there are index recommendations available
*/
export function hasIndexRecommendations(
- result: GetIndexAdvisorResultResponse | undefined,
+ result: GetIndexAdvisorResultResponse | undefined | null,
isSuccess: boolean
): boolean {
return Boolean(isSuccess && result?.index_statements && result.index_statements.length > 0)
diff --git a/apps/studio/components/interfaces/QueryPerformance/QueryDetail.tsx b/apps/studio/components/interfaces/QueryPerformance/QueryDetail.tsx
index f35e1e9e30f3c..6234374fc9874 100644
--- a/apps/studio/components/interfaces/QueryPerformance/QueryDetail.tsx
+++ b/apps/studio/components/interfaces/QueryPerformance/QueryDetail.tsx
@@ -1,11 +1,9 @@
-import { Lightbulb, ChevronsUpDown, Expand } from 'lucide-react'
+import { Lightbulb, ChevronsUpDown } from 'lucide-react'
import { useEffect, useState } from 'react'
import dynamic from 'next/dynamic'
-import dayjs from 'dayjs'
import { formatSql } from 'lib/formatSql'
import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, Button, cn } from 'ui'
-import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { QueryPanelContainer, QueryPanelSection } from './QueryPanel'
import {
QUERY_PERFORMANCE_COLUMNS,
@@ -170,7 +168,7 @@ export const QueryDetail = ({ selectedRow, onClickViewSuggestion }: QueryDetailP
return (
{x.name}
- {typeof rawValue === 'string' ? (
+ {typeof rawValue === 'string' || typeof rawValue === 'number' ? (
+ `
+select
+ id,
+ pgl.timestamp as timestamp,
+ 'postgres' as log_type,
+ CAST(pgl_parsed.sql_state_code AS STRING) as status,
+ CASE
+ WHEN pgl_parsed.error_severity = 'LOG' THEN 'success'
+ WHEN pgl_parsed.error_severity = 'WARNING' THEN 'warning'
+ WHEN pgl_parsed.error_severity = 'FATAL' THEN 'error'
+ WHEN pgl_parsed.error_severity = 'ERROR' THEN 'error'
+ ELSE null
+ END as level,
+ event_message as event_message
+from postgres_logs as pgl
+cross join unnest(pgl.metadata) as pgl_metadata
+cross join unnest(pgl_metadata.parsed) as pgl_parsed
+WHERE pgl.event_message LIKE '%[pg_stat_monitor]%'
+ AND pgl.timestamp >= CAST('${startTime}' AS TIMESTAMP)
+ AND pgl.timestamp <= CAST('${endTime}' AS TIMESTAMP)
+ORDER BY timestamp DESC
+`.trim()
+
+export const PG_STAT_MONITOR_LOGS_QUERY = getPgStatMonitorLogsQuery(
+ new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
+ new Date().toISOString()
+)
diff --git a/apps/studio/components/interfaces/QueryPerformance/QueryPerformance.tsx b/apps/studio/components/interfaces/QueryPerformance/QueryPerformance.tsx
index d57961890d567..82844f1ca1900 100644
--- a/apps/studio/components/interfaces/QueryPerformance/QueryPerformance.tsx
+++ b/apps/studio/components/interfaces/QueryPerformance/QueryPerformance.tsx
@@ -1,158 +1,50 @@
-import { X } from 'lucide-react'
-import { useEffect, useState } from 'react'
-import { toast } from 'sonner'
+import { useEffect } from 'react'
-import { LOCAL_STORAGE_KEYS, useParams } from 'common'
-import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
-import { formatDatabaseID } from 'data/read-replicas/replicas.utils'
-import { executeSql } from 'data/sql/execute-sql-query'
+import { WithMonitor } from './WithMonitor/WithMonitor'
+import { WithStatements } from './WithStatements/WithStatements'
+import { useParams } from 'common'
import { DbQueryHook } from 'hooks/analytics/useDbQuery'
-import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
-import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
-import { DOCS_URL, IS_PLATFORM } from 'lib/constants'
import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
-import { Button, LoadingLine, cn } from 'ui'
-import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
-import { Markdown } from '../Markdown'
import { PresetHookResult } from '../Reports/Reports.utils'
-import { QueryPerformanceFilterBar } from './QueryPerformanceFilterBar'
-import { QueryPerformanceGrid } from './QueryPerformanceGrid'
-import { QueryPerformanceMetrics } from './QueryPerformanceMetrics'
interface QueryPerformanceProps {
queryHitRate: PresetHookResult
queryPerformanceQuery: DbQueryHook
queryMetrics: PresetHookResult
+ isPgStatMonitorEnabled: boolean
+ dateRange?: {
+ period_start: { date: string; time_period: string }
+ period_end: { date: string; time_period: string }
+ interval: string
+ }
+ onDateRangeChange?: (from: string, to: string) => void
}
export const QueryPerformance = ({
queryHitRate,
queryPerformanceQuery,
queryMetrics,
+ isPgStatMonitorEnabled,
+ dateRange,
+ onDateRangeChange,
}: QueryPerformanceProps) => {
const { ref } = useParams()
- const { data: project } = useSelectedProjectQuery()
const state = useDatabaseSelectorStateSnapshot()
- const { isLoading, isRefetching } = queryPerformanceQuery
- const isPrimaryDatabase = state.selectedDatabaseId === ref
- const formattedDatabaseId = formatDatabaseID(state.selectedDatabaseId ?? '')
-
- const [showResetgPgStatStatements, setShowResetgPgStatStatements] = useState(false)
-
- const [showBottomSection, setShowBottomSection] = useLocalStorageQuery(
- LOCAL_STORAGE_KEYS.QUERY_PERF_SHOW_BOTTOM_SECTION,
- true
- )
-
- const handleRefresh = () => {
- queryPerformanceQuery.runQuery()
- queryHitRate.runQuery()
- queryMetrics.runQuery()
- }
-
- const { data: databases } = useReadReplicasQuery({ projectRef: ref })
-
useEffect(() => {
state.setSelectedDatabaseId(ref)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ref])
- return (
- <>
-
- setShowResetgPgStatStatements(true)}
- />
-
-
-
-
-
-
setShowBottomSection(false)}
- >
-
-
-
-
Reset report
-
- Consider resetting the analysis after optimizing any queries
-
-
setShowResetgPgStatStatements(true)}
- >
- Reset report
-
-
-
-
-
How is this report generated?
-
-
-
-
-
Inspect your database for potential issues
-
-
-
-
- setShowResetgPgStatStatements(false)}
- onConfirm={async () => {
- const connectionString = databases?.find(
- (db) => db.identifier === state.selectedDatabaseId
- )?.connectionString
-
- if (IS_PLATFORM && !connectionString) {
- return toast.error('Unable to run query: Connection string is missing')
- }
+ if (isPgStatMonitorEnabled) {
+ return
+ }
- try {
- await executeSql({
- projectRef: project?.ref,
- connectionString,
- sql: `SELECT pg_stat_statements_reset();`,
- })
- handleRefresh()
- setShowResetgPgStatStatements(false)
- } catch (error: any) {
- toast.error(`Failed to reset analysis: ${error.message}`)
- }
- }}
- >
-
- This will reset the pg_stat_statements table in the extensions schema on your{' '}
-
- {isPrimaryDatabase ? 'primary database' : `read replica (ID: ${formattedDatabaseId})`}
-
- , which is used to calculate query performance. This data will repopulate immediately
- after.
-
-
- >
+ return (
+
)
}
diff --git a/apps/studio/components/interfaces/QueryPerformance/QueryPerformance.types.ts b/apps/studio/components/interfaces/QueryPerformance/QueryPerformance.types.ts
new file mode 100644
index 0000000000000..e7499b46d9a9f
--- /dev/null
+++ b/apps/studio/components/interfaces/QueryPerformance/QueryPerformance.types.ts
@@ -0,0 +1,18 @@
+import { GetIndexAdvisorResultResponse } from 'data/database/retrieve-index-advisor-result-query'
+
+export interface QueryPerformanceRow {
+ query: string
+ prop_total_time: number
+ total_time: number
+ calls: number
+ max_time: number
+ mean_time: number
+ min_time: number
+ rows_read: number
+ cache_hit_rate: number
+ rolname: string
+ index_advisor_result?: GetIndexAdvisorResultResponse | null
+ _total_cache_hits?: number
+ _total_cache_misses?: number
+ _count?: number
+}
diff --git a/apps/studio/components/interfaces/QueryPerformance/QueryPerformance.utils.ts b/apps/studio/components/interfaces/QueryPerformance/QueryPerformance.utils.ts
index ce7d211123e92..fb13634e4b244 100644
--- a/apps/studio/components/interfaces/QueryPerformance/QueryPerformance.utils.ts
+++ b/apps/studio/components/interfaces/QueryPerformance/QueryPerformance.utils.ts
@@ -24,3 +24,14 @@ export const formatDuration = (milliseconds: number) => {
return parts.length > 0 ? parts.join(' ') : '0s'
}
+
+export const transformLogsToJSON = (log: string) => {
+ try {
+ let jsonString = log.replace('[pg_stat_monitor] ', '')
+ jsonString = jsonString.replace(/""/g, '","')
+ const jsonObject = JSON.parse(jsonString)
+ return jsonObject
+ } catch (error) {
+ return null
+ }
+}
diff --git a/apps/studio/components/interfaces/QueryPerformance/QueryPerformanceChart.tsx b/apps/studio/components/interfaces/QueryPerformance/QueryPerformanceChart.tsx
new file mode 100644
index 0000000000000..30fa2578e1f65
--- /dev/null
+++ b/apps/studio/components/interfaces/QueryPerformance/QueryPerformanceChart.tsx
@@ -0,0 +1,360 @@
+import { useState, useMemo } from 'react'
+import { Tabs_Shadcn_, TabsContent_Shadcn_, TabsList_Shadcn_, TabsTrigger_Shadcn_ } from 'ui'
+import { QUERY_PERFORMANCE_CHART_TABS } from './QueryPerformance.constants'
+import { Loader2 } from 'lucide-react'
+import { ComposedChart } from 'components/ui/Charts/ComposedChart'
+import type { MultiAttribute } from 'components/ui/Charts/ComposedChart.utils'
+import type { ChartDataPoint } from './WithMonitor/WithMonitor.utils'
+
+interface QueryPerformanceChartProps {
+ dateRange?: {
+ period_start: { date: string; time_period: string }
+ period_end: { date: string; time_period: string }
+ interval: string
+ }
+ onDateRangeChange?: (from: string, to: string) => void
+ chartData: ChartDataPoint[]
+ isLoading: boolean
+ error: any
+ currentSelectedQuery: string | null
+ parsedLogs: any[]
+}
+
+const QueryMetricBlock = ({
+ label,
+ value,
+}: {
+ label: string
+ value: string | number | undefined
+}) => {
+ return (
+
+ {label}
+ {value}
+
+ )
+}
+
+const formatTimeValue = (value: number): string => {
+ if (value >= 1000) {
+ return `${(value / 1000).toFixed(1)}s`
+ }
+ return `${value.toFixed(1)}ms`
+}
+
+const formatNumberValue = (value: number): string => {
+ return value.toLocaleString()
+}
+
+export const QueryPerformanceChart = ({
+ onDateRangeChange,
+ chartData,
+ isLoading,
+ error,
+ currentSelectedQuery,
+ parsedLogs,
+}: QueryPerformanceChartProps) => {
+ const [selectedMetric, setSelectedMetric] = useState('query_latency')
+
+ const currentMetrics = useMemo(() => {
+ if (!chartData || chartData.length === 0) return []
+
+ switch (selectedMetric) {
+ case 'query_latency': {
+ const avgP95 = chartData.reduce((sum, d) => sum + d.p95_time, 0) / chartData.length
+
+ return [
+ {
+ label: 'Average p95',
+ value: avgP95 >= 100 ? `${(avgP95 / 1000).toFixed(2)}s` : `${Math.round(avgP95)}ms`,
+ },
+ ]
+ }
+ case 'rows_read': {
+ const totalRowsRead = chartData.reduce((sum, d) => sum + d.rows_read, 0)
+
+ return [
+ {
+ label: 'Total Rows Read',
+ value: totalRowsRead.toLocaleString(),
+ },
+ ]
+ }
+ case 'calls': {
+ const totalCalls = chartData.reduce((sum, d) => sum + d.calls, 0)
+
+ return [
+ {
+ label: 'Total Calls',
+ value: totalCalls.toLocaleString(),
+ },
+ ]
+ }
+ case 'cache_hits': {
+ const totalHits = chartData.reduce((sum, d) => sum + d.cache_hits, 0)
+ const totalMisses = chartData.reduce((sum, d) => sum + d.cache_misses, 0)
+ const total = totalHits + totalMisses
+ const hitRate = total > 0 ? (totalHits / total) * 100 : 0
+
+ return [
+ {
+ label: 'Cache Hit Rate',
+ value: `${hitRate.toFixed(2)}%`,
+ },
+ ]
+ }
+ default:
+ return []
+ }
+ }, [chartData, selectedMetric])
+
+ const transformedChartData = useMemo(() => {
+ if (selectedMetric !== 'query_latency') return chartData
+
+ const transformed = chartData.map((dataPoint) => ({
+ ...dataPoint,
+ p50_time: parseFloat((dataPoint.p50_time / 1000).toFixed(3)),
+ p95_time: parseFloat((dataPoint.p95_time / 1000).toFixed(3)),
+ }))
+
+ return transformed
+ }, [chartData, selectedMetric])
+
+ const querySpecificData = useMemo(() => {
+ if (!currentSelectedQuery || !parsedLogs.length) return null
+
+ const normalizedSelected = currentSelectedQuery.replace(/\s+/g, ' ').trim()
+ const queryLogs = parsedLogs.filter((log) => {
+ const normalized = (log.query || '').replace(/\s+/g, ' ').trim()
+ return normalized === normalizedSelected
+ })
+
+ const queryDataMap = new Map<
+ number,
+ {
+ time: number
+ rows_read: number
+ calls: number
+ cache_hits: number
+ }
+ >()
+
+ queryLogs.forEach((log) => {
+ const timestamps = [log.bucket_start_time, log.bucket, log.timestamp, log.ts]
+ const validTimestamp = timestamps.find((t) => t && !isNaN(new Date(t).getTime()))
+
+ if (!validTimestamp) return
+
+ const time = new Date(validTimestamp).getTime()
+ const meanTime = log.mean_time ?? log.mean_exec_time ?? log.mean_query_time ?? 0
+ const rowsRead = log.rows_read ?? log.rows ?? 0
+ const calls = log.calls ?? 0
+ const cacheHits = log.shared_blks_hit ?? log.cache_hits ?? 0
+
+ queryDataMap.set(time, {
+ time: parseFloat(String(meanTime)),
+ rows_read: parseFloat(String(rowsRead)),
+ calls: parseFloat(String(calls)),
+ cache_hits: parseFloat(String(cacheHits)),
+ })
+ })
+
+ return queryDataMap
+ }, [currentSelectedQuery, parsedLogs])
+
+ const mergedChartData = useMemo(() => {
+ if (!querySpecificData || !currentSelectedQuery) {
+ return transformedChartData
+ }
+
+ return transformedChartData.map((dataPoint) => {
+ const queryData = querySpecificData.get(dataPoint.period_start)
+
+ return {
+ ...dataPoint,
+ selected_query_time:
+ queryData?.time !== undefined
+ ? selectedMetric === 'query_latency'
+ ? queryData.time / 1000
+ : queryData.time
+ : null,
+ selected_query_rows_read: queryData?.rows_read !== undefined ? queryData.rows_read : null,
+ selected_query_calls: queryData?.calls !== undefined ? queryData.calls : null,
+ selected_query_cache_hits:
+ queryData?.cache_hits !== undefined ? queryData.cache_hits : null,
+ }
+ })
+ }, [transformedChartData, querySpecificData, currentSelectedQuery, selectedMetric])
+
+ const getChartAttributes = useMemo((): MultiAttribute[] => {
+ const attributeMap: Record = {
+ query_latency: [
+ {
+ attribute: 'p50_time',
+ label: 'p50',
+ provider: 'logs',
+ type: 'line',
+ color: { light: '#8B5CF6', dark: '#8B5CF6' },
+ },
+ {
+ attribute: 'p95_time',
+ label: 'p95',
+ provider: 'logs',
+ type: 'line',
+ color: { light: '#65BCD9', dark: '#65BCD9' },
+ },
+ ],
+ rows_read: [
+ {
+ attribute: 'rows_read',
+ label: 'Rows Read',
+ provider: 'logs',
+ },
+ ],
+ calls: [
+ {
+ attribute: 'calls',
+ label: 'Calls',
+ provider: 'logs',
+ },
+ ],
+ cache_hits: [
+ {
+ attribute: 'cache_hits',
+ label: 'Cache Hits',
+ provider: 'logs',
+ type: 'line',
+ color: { light: '#10B981', dark: '#10B981' },
+ },
+ ],
+ }
+
+ const baseAttributes = attributeMap[selectedMetric] || []
+
+ // Add selected query line based on current metric
+ if (currentSelectedQuery && querySpecificData) {
+ const selectedQueryAttributes: Record = {
+ query_latency: {
+ attribute: 'selected_query_time',
+ label: 'Selected Query',
+ provider: 'logs',
+ type: 'line',
+ color: { light: '#10B981', dark: '#10B981' },
+ strokeWidth: 3,
+ },
+ rows_read: {
+ attribute: 'selected_query_rows_read',
+ label: 'Selected Query',
+ provider: 'logs',
+ type: 'line',
+ color: { light: '#F59E0B', dark: '#F59E0B' },
+ strokeWidth: 3,
+ },
+ calls: {
+ attribute: 'selected_query_calls',
+ label: 'Selected Query',
+ provider: 'logs',
+ type: 'line',
+ color: { light: '#EC4899', dark: '#EC4899' },
+ strokeWidth: 3,
+ },
+ cache_hits: {
+ attribute: 'selected_query_cache_hits',
+ label: 'Selected Query',
+ provider: 'logs',
+ type: 'line',
+ color: { light: '#8B5CF6', dark: '#8B5CF6' },
+ strokeWidth: 3,
+ },
+ }
+
+ const selectedQueryAttr = selectedQueryAttributes[selectedMetric]
+ if (selectedQueryAttr) {
+ return [...baseAttributes, selectedQueryAttr]
+ }
+ }
+
+ return baseAttributes
+ }, [selectedMetric, currentSelectedQuery, querySpecificData])
+
+ const updateDateRange = (from: string, to: string) => {
+ onDateRangeChange?.(from, to)
+ }
+
+ const getYAxisFormatter = useMemo(() => {
+ if (selectedMetric === 'query_latency') {
+ return formatTimeValue
+ }
+ return formatNumberValue
+ }, [selectedMetric])
+
+ return (
+
+
setSelectedMetric(value as string)}
+ className="w-full"
+ >
+
+ {QUERY_PERFORMANCE_CHART_TABS.map((tab) => (
+
+ {tab.label}
+
+ ))}
+
+
+
+
+ {isLoading ? (
+
+ ) : error ? (
+
+ Error loading chart data
+
+ ) : (
+
+
+ {currentMetrics.map((metric, index) => (
+
+ ))}
+
+
+
+ )}
+
+
+
+
+ )
+}
diff --git a/apps/studio/components/interfaces/QueryPerformance/QueryPerformanceFilterBar.tsx b/apps/studio/components/interfaces/QueryPerformance/QueryPerformanceFilterBar.tsx
index a2d91081a21d1..9c115b62f843a 100644
--- a/apps/studio/components/interfaces/QueryPerformance/QueryPerformanceFilterBar.tsx
+++ b/apps/studio/components/interfaces/QueryPerformance/QueryPerformanceFilterBar.tsx
@@ -1,60 +1,23 @@
import { useDebounce } from '@uidotdev/usehooks'
-import { RefreshCw, Search, X } from 'lucide-react'
-import { parseAsArrayOf, parseAsString, useQueryStates } from 'nuqs'
-import { ChangeEvent, useEffect, useState } from 'react'
+import { Search, X } from 'lucide-react'
+import { parseAsString, useQueryStates } from 'nuqs'
+import { ChangeEvent, ReactNode, useEffect, useState } from 'react'
-import { LOCAL_STORAGE_KEYS, useParams } from 'common'
-import { DownloadResultsButton } from 'components/ui/DownloadResultsButton'
-import { FilterPopover } from 'components/ui/FilterPopover'
-import { useDatabaseRolesQuery } from 'data/database-roles/database-roles-query'
-import { DbQueryHook } from 'hooks/analytics/useDbQuery'
-import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
-import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { Button, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
import { Input } from 'ui-patterns/DataInputs/Input'
import { useQueryPerformanceSort } from './hooks/useQueryPerformanceSort'
-export const QueryPerformanceFilterBar = ({
- queryPerformanceQuery,
- onResetReportClick,
-}: {
- queryPerformanceQuery: DbQueryHook
- onResetReportClick?: () => void
-}) => {
- const { ref } = useParams()
- const { data: project } = useSelectedProjectQuery()
+export const QueryPerformanceFilterBar = ({ actions }: { actions?: ReactNode }) => {
const { sort, clearSort } = useQueryPerformanceSort()
- const [showBottomSection] = useLocalStorageQuery(
- LOCAL_STORAGE_KEYS.QUERY_PERF_SHOW_BOTTOM_SECTION,
- true
- )
- const [{ search: searchQuery, roles: defaultFilterRoles }, setSearchParams] = useQueryStates({
+ const [{ search: searchQuery }, setSearchParams] = useQueryStates({
search: parseAsString.withDefault(''),
- roles: parseAsArrayOf(parseAsString).withDefault([]),
})
const [inputValue, setInputValue] = useState(searchQuery)
const debouncedInputValue = useDebounce(inputValue, 500)
const searchValue = inputValue.length === 0 ? inputValue : debouncedInputValue
- const [filters, setFilters] = useState<{ roles: string[]; query: string }>({
- roles: defaultFilterRoles,
- query: '',
- })
-
- const { isLoading, isRefetching } = queryPerformanceQuery
- const { data, isLoading: isLoadingRoles } = useDatabaseRolesQuery({
- projectRef: project?.ref,
- connectionString: project?.connectionString,
- })
- const roles = (data ?? []).sort((a, b) => a.name.localeCompare(b.name))
-
- const onFilterRolesChange = (roles: string[]) => {
- setFilters({ ...filters, roles })
- setSearchParams({ roles })
- }
-
const onSearchQueryChange = (value: string) => {
setSearchParams({ search: value || '' })
}
@@ -65,7 +28,7 @@ export const QueryPerformanceFilterBar = ({
}, [searchValue])
return (
-
+
-
-
{sort && (
@@ -116,32 +69,7 @@ export const QueryPerformanceFilterBar = ({
)}
-
-
- {!showBottomSection && onResetReportClick && (
- onResetReportClick()}>
- Reset report
-
- )}
- queryPerformanceQuery.runQuery()}
- disabled={isLoading || isRefetching}
- icon={
-
- }
- >
- Refresh
-
-
-
+
{actions}
)
}
diff --git a/apps/studio/components/interfaces/QueryPerformance/QueryPerformanceGrid.tsx b/apps/studio/components/interfaces/QueryPerformance/QueryPerformanceGrid.tsx
index e8e3c87ee0b80..a35322a52def4 100644
--- a/apps/studio/components/interfaces/QueryPerformance/QueryPerformanceGrid.tsx
+++ b/apps/studio/components/interfaces/QueryPerformance/QueryPerformanceGrid.tsx
@@ -1,29 +1,30 @@
-import { ArrowDown, ArrowUp, ChevronDown, TextSearch } from 'lucide-react'
+import { ArrowDown, ArrowRight, ArrowUp, ChevronDown, TextSearch } from 'lucide-react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import DataGrid, { Column, DataGridHandle, Row } from 'react-data-grid'
import { useParams } from 'common'
-import { DbQueryHook } from 'hooks/analytics/useDbQuery'
+import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import {
Button,
+ CodeBlock,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
Sheet,
SheetContent,
+ SheetTitle,
TabsContent_Shadcn_,
TabsList_Shadcn_,
TabsTrigger_Shadcn_,
Tabs_Shadcn_,
cn,
- CodeBlock,
- SheetTitle,
} from 'ui'
import { InfoTooltip } from 'ui-patterns/info-tooltip'
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
-import { hasIndexRecommendations } from './index-advisor.utils'
-import { IndexSuggestionIcon } from './IndexSuggestionIcon'
+import { useQueryPerformanceSort } from './hooks/useQueryPerformanceSort'
+import { hasIndexRecommendations } from './IndexAdvisor/index-advisor.utils'
+import { IndexSuggestionIcon } from './IndexAdvisor/IndexSuggestionIcon'
import { QueryDetail } from './QueryDetail'
import { QueryIndexes } from './QueryIndexes'
import {
@@ -31,26 +32,14 @@ import {
QUERY_PERFORMANCE_REPORT_TYPES,
QUERY_PERFORMANCE_ROLE_DESCRIPTION,
} from './QueryPerformance.constants'
-import { useQueryPerformanceSort } from './hooks/useQueryPerformanceSort'
+import { QueryPerformanceRow } from './QueryPerformance.types'
import { formatDuration } from './QueryPerformance.utils'
-import { GetIndexAdvisorResultResponse } from 'data/database/retrieve-index-advisor-result-query'
interface QueryPerformanceGridProps {
- queryPerformanceQuery: DbQueryHook
-}
-
-interface QueryPerformanceRow {
- query: string
- prop_total_time: number
- total_time: number
- calls: number
- max_time: number
- mean_time: number
- min_time: number
- rows_read: number
- cache_hit_rate: string
- rolname: string
- index_advisor_result: GetIndexAdvisorResultResponse | null
+ aggregatedData: QueryPerformanceRow[]
+ isLoading: boolean
+ currentSelectedQuery?: string | null // Make optional
+ onCurrentSelectQuery?: (query: string) => void // Make optional
}
const calculateTimeConsumedWidth = (data: QueryPerformanceRow[]) => {
@@ -75,11 +64,15 @@ const calculateTimeConsumedWidth = (data: QueryPerformanceRow[]) => {
return Math.min(maxWidth, 300)
}
-export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformanceGridProps) => {
+export const QueryPerformanceGrid = ({
+ aggregatedData,
+ isLoading,
+ currentSelectedQuery,
+ onCurrentSelectQuery,
+}: QueryPerformanceGridProps) => {
const { sort, setSortConfig } = useQueryPerformanceSort()
const gridRef = useRef(null)
const { sort: urlSort, order, roles, search } = useParams()
- const { isLoading, data } = queryPerformanceQuery
const dataGridContainerRef = useRef(null)
const [view, setView] = useState<'details' | 'suggestion'>('details')
@@ -95,7 +88,9 @@ export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformance
cellClass: `column-${col.id}`,
resizable: true,
minWidth:
- col.id === 'prop_total_time' ? calculateTimeConsumedWidth(data ?? []) : col.minWidth ?? 120,
+ col.id === 'prop_total_time'
+ ? calculateTimeConsumedWidth((aggregatedData as any) ?? [])
+ : col.minWidth ?? 120,
sortable: !nonSortableColumns.includes(col.id),
headerCellClass: 'first:pl-6 cursor-pointer',
renderHeaderCell: () => {
@@ -156,26 +151,43 @@ export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformance
const value = props.row?.[col.id]
if (col.id === 'query') {
return (
-
- {hasIndexRecommendations(props.row.index_advisor_result, true) && (
-
{
- setSelectedRow(props.rowIdx)
- setView('suggestion')
- gridRef.current?.scrollToCell({ idx: 0, rowIdx: props.rowIdx })
- }}
- />
- )}
+
+
+ {hasIndexRecommendations(props.row.index_advisor_result, true) && (
+ {
+ setSelectedRow(props.rowIdx)
+ setView('suggestion')
+ gridRef.current?.scrollToCell({ idx: 0, rowIdx: props.rowIdx })
+ }}
+ />
+ )}
+
+ {onCurrentSelectQuery && (
+
}
+ size="tiny"
+ type="default"
+ onClick={(e) => {
+ e.stopPropagation()
+ setSelectedRow(props.rowIdx)
+ setView('details')
+ gridRef.current?.scrollToCell({ idx: 0, rowIdx: props.rowIdx })
+ }}
+ className="p-1 flex-shrink-0 -translate-x-2 group-hover:flex hidden"
+ />
+ )}
)
}
@@ -267,21 +279,12 @@ export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformance
)
}
- const cacheHitRateToNumber = (value: number | string) => {
- if (typeof value === 'number') return value
- return parseFloat(value.toString().replace('%', '')) || 0
- }
-
if (col.id === 'cache_hit_rate') {
return (
- {typeof value === 'string' ? (
-
- {cacheHitRateToNumber(value).toLocaleString(undefined, {
+ {typeof value === 'number' && !isNaN(value) && isFinite(value) ? (
+
+ {value.toLocaleString(undefined, {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})}
@@ -325,27 +328,36 @@ export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformance
})
const reportData = useMemo(() => {
- const rawData = data ?? []
+ let data = [...aggregatedData]
- if (sort?.column === 'prop_total_time') {
- const sortedData = [...rawData].sort((a, b) => {
- const getNumericValue = (value: number | string) => {
- if (!value || value === 'n/a') return 0
- if (typeof value === 'number') return value
- return parseFloat(value.toString().replace('%', '')) || 0
- }
+ if (search && typeof search === 'string' && search.length > 0) {
+ data = data.filter((row) => row.query.toLowerCase().includes(search.toLowerCase()))
+ }
- const aValue = getNumericValue(a.prop_total_time)
- const bValue = getNumericValue(b.prop_total_time)
+ if (roles && Array.isArray(roles) && roles.length > 0) {
+ data = data.filter((row) => row.rolname && roles.includes(row.rolname))
+ }
+ if (sort?.column === 'prop_total_time') {
+ data.sort((a, b) => {
+ const aValue = a.prop_total_time || 0
+ const bValue = b.prop_total_time || 0
return sort.order === 'asc' ? aValue - bValue : bValue - aValue
})
+ } else if (sort?.column && sort.column !== 'query') {
+ data.sort((a, b) => {
+ const aValue = a[sort.column as keyof QueryPerformanceRow] || 0
+ const bValue = b[sort.column as keyof QueryPerformanceRow] || 0
- return sortedData
+ if (typeof aValue === 'number' && typeof bValue === 'number') {
+ return sort.order === 'asc' ? aValue - bValue : bValue - aValue
+ }
+ return 0
+ })
}
- return rawData
- }, [data, sort])
+ return data
+ }, [aggregatedData, sort, search, roles])
const selectedQuery = selectedRow !== undefined ? reportData[selectedRow]?.query : undefined
const query = (selectedQuery ?? '').trim().toLowerCase()
@@ -357,7 +369,6 @@ export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformance
useEffect(() => {
setSelectedRow(undefined)
- // eslint-disable-next-line react-hooks/exhaustive-deps
}, [search, roles, urlSort, order])
const handleKeyDown = useCallback(
@@ -366,7 +377,6 @@ export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformance
if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown') return
- // stop default RDG behavior (which moves focus to header when selectedRow is 0)
event.stopPropagation()
let nextIndex = selectedRow
@@ -410,9 +420,14 @@ export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformance
rows={reportData}
rowClass={(_, idx) => {
const isSelected = idx === selectedRow
+ const query = reportData[idx]?.query
+ const isCharted = currentSelectedQuery ? currentSelectedQuery === query : false
+
return [
`${isSelected ? 'bg-surface-300 dark:bg-surface-300' : 'bg-200'} cursor-pointer`,
- `${isSelected ? '[&>div:first-child]:border-l-4 border-l-secondary [&>div]:border-l-foreground' : ''}`,
+ `${isSelected ? '[&>div:first-child]:border-l-4 border-l-secondary [&>div]:!border-l-foreground' : ''}`,
+ `${isCharted ? 'bg-surface-200 dark:bg-surface-200' : ''}`,
+ `${isCharted ? '[&>div:first-child]:border-l-4 border-l-secondary [&>div]:border-l-brand' : ''}`,
'[&>.rdg-cell]:box-border [&>.rdg-cell]:outline-none [&>.rdg-cell]:shadow-none',
'[&>.rdg-cell.column-prop_total_time]:relative',
].join(' ')
@@ -427,12 +442,17 @@ export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformance
event.stopPropagation()
if (typeof idx === 'number' && idx >= 0) {
- setSelectedRow(idx)
- gridRef.current?.scrollToCell({ idx: 0, rowIdx: idx })
-
- const rowQuery = reportData[idx]?.query ?? ''
- if (!rowQuery.trim().toLowerCase().startsWith('select')) {
+ // If onCurrentSelectQuery is provided, use the chart selection logic
+ if (onCurrentSelectQuery) {
+ const query = reportData[idx]?.query
+ if (query) {
+ onCurrentSelectQuery(query)
+ }
+ } else {
+ // Otherwise, open the detail panel
+ setSelectedRow(idx)
setView('details')
+ gridRef.current?.scrollToCell({ idx: 0, rowIdx: idx })
}
}
}}
diff --git a/apps/studio/components/interfaces/QueryPerformance/TextSearchPopover.tsx b/apps/studio/components/interfaces/QueryPerformance/TextSearchPopover.tsx
deleted file mode 100644
index 1e6be9f3c9ed6..0000000000000
--- a/apps/studio/components/interfaces/QueryPerformance/TextSearchPopover.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import { useState } from 'react'
-import {
- Button,
- PopoverContent_Shadcn_,
- PopoverTrigger_Shadcn_,
- Popover_Shadcn_,
- TextArea_Shadcn_,
-} from 'ui'
-
-interface TextSearchPopoverProps {
- name: string
- value: string
- placeholder?: string
- rows?: number
- onSaveText: (value: string) => void
-}
-
-export const TextSearchPopover = ({
- name,
- value = '',
- placeholder,
- rows = 4,
- onSaveText,
-}: TextSearchPopoverProps) => {
- const [open, setOpen] = useState(false)
- const [search, setSearch] = useState(value)
-
- const applySearch = () => {
- onSaveText(search)
- setOpen(false)
- }
-
- return (
-
-
- 0 ? 'default' : 'dashed'}
- onClick={() => setOpen(false)}
- >
-
- {name}
- {value.length > 0 && : }
- {value}
-
-
-
-
-
- setSearch(event.target.value)}
- rows={rows}
- className="text-xs font-mono tracking-tight"
- placeholder={placeholder ?? 'Search for a query'}
- onKeyDown={(event) => {
- if (event.metaKey && (event.code === 'Enter' || event.code === 'NumpadEnter'))
- applySearch()
- }}
- />
-
-
- {
- onSaveText('')
- setSearch('')
- setOpen(false)
- }}
- >
- Clear
-
- applySearch()}>
- Apply search
-
-
-
-
- )
-}
diff --git a/apps/studio/components/interfaces/QueryPerformance/WithMonitor/WithMonitor.tsx b/apps/studio/components/interfaces/QueryPerformance/WithMonitor/WithMonitor.tsx
new file mode 100644
index 0000000000000..db34a8def4c6c
--- /dev/null
+++ b/apps/studio/components/interfaces/QueryPerformance/WithMonitor/WithMonitor.tsx
@@ -0,0 +1,111 @@
+import { QueryPerformanceGrid } from '../QueryPerformanceGrid'
+import { LoadingLine } from 'ui'
+import { QueryPerformanceChart } from '../QueryPerformanceChart'
+import { QueryPerformanceFilterBar } from '../QueryPerformanceFilterBar'
+import { useMemo, useState } from 'react'
+import dayjs from 'dayjs'
+import utc from 'dayjs/plugin/utc'
+import useLogsQuery from 'hooks/analytics/useLogsQuery'
+import { getPgStatMonitorLogsQuery } from '../QueryPerformance.constants'
+import {
+ parsePgStatMonitorLogs,
+ transformLogsToChartData,
+ aggregateLogsByQuery,
+} from './WithMonitor.utils'
+import { useParams } from 'common'
+import { DownloadResultsButton } from 'components/ui/DownloadResultsButton'
+
+dayjs.extend(utc)
+
+interface WithMonitorProps {
+ dateRange?: {
+ period_start: { date: string; time_period: string }
+ period_end: { date: string; time_period: string }
+ interval: string
+ }
+ onDateRangeChange?: (from: string, to: string) => void
+}
+
+export const WithMonitor = ({ dateRange, onDateRangeChange }: WithMonitorProps) => {
+ const { ref } = useParams()
+ const [selectedQuery, setSelectedQuery] = useState(null)
+
+ // [kemal]: Fetch pg_stat_monitor logs. This will need to change when we move to the actual extension.
+ const effectiveDateRange = useMemo(() => {
+ if (dateRange) {
+ return {
+ iso_timestamp_start: dateRange.period_start.date,
+ iso_timestamp_end: dateRange.period_end.date,
+ }
+ }
+
+ // [kemal]: Fallback to default 24 hours
+ const end = dayjs.utc()
+ const start = end.subtract(24, 'hours')
+ return {
+ iso_timestamp_start: start.toISOString(),
+ iso_timestamp_end: end.toISOString(),
+ }
+ }, [dateRange])
+
+ const queryWithTimeRange = useMemo(() => {
+ return getPgStatMonitorLogsQuery(
+ effectiveDateRange.iso_timestamp_start,
+ effectiveDateRange.iso_timestamp_end
+ )
+ }, [effectiveDateRange])
+
+ const pgStatMonitorLogs = useLogsQuery(ref as string, {
+ sql: queryWithTimeRange,
+ iso_timestamp_start: effectiveDateRange.iso_timestamp_start,
+ iso_timestamp_end: effectiveDateRange.iso_timestamp_end,
+ })
+
+ const { logData, isLoading: isLogsLoading, error: logsError } = pgStatMonitorLogs
+
+ const parsedLogs = useMemo(() => {
+ return parsePgStatMonitorLogs(logData || [])
+ }, [logData])
+
+ const chartData = useMemo(() => {
+ return transformLogsToChartData(parsedLogs)
+ }, [parsedLogs])
+
+ const aggregatedGridData = useMemo(() => {
+ return aggregateLogsByQuery(parsedLogs)
+ }, [parsedLogs])
+
+ const handleSelectQuery = (query: string) => {
+ setSelectedQuery((prev) => (prev === query ? null : query))
+ }
+
+ return (
+ <>
+
+
+ }
+ />
+
+
+ >
+ )
+}
diff --git a/apps/studio/components/interfaces/QueryPerformance/WithMonitor/WithMonitor.utils.ts b/apps/studio/components/interfaces/QueryPerformance/WithMonitor/WithMonitor.utils.ts
new file mode 100644
index 0000000000000..f65cfb30930e8
--- /dev/null
+++ b/apps/studio/components/interfaces/QueryPerformance/WithMonitor/WithMonitor.utils.ts
@@ -0,0 +1,301 @@
+import dayjs from 'dayjs'
+import utc from 'dayjs/plugin/utc'
+import { transformLogsToJSON } from '../QueryPerformance.utils'
+import { QueryPerformanceRow } from '../QueryPerformance.types'
+
+dayjs.extend(utc)
+
+export interface ParsedLogEntry {
+ bucket_start_time?: string
+ bucket?: string
+ timestamp?: string
+ ts?: string
+ mean_time?: number
+ mean_exec_time?: number
+ mean_query_time?: number
+ min_time?: number
+ min_exec_time?: number
+ min_query_time?: number
+ max_time?: number
+ max_exec_time?: number
+ max_query_time?: number
+ stddev_time?: number
+ stddev_exec_time?: number
+ stddev_query_time?: number
+ rows?: number
+ calls?: number
+ shared_blks_hit?: number
+ shared_blks_read?: number
+ query?: string
+ userid?: string
+ rolname?: string
+ resp_calls?: number[]
+ [key: string]: any
+}
+
+export interface ChartDataPoint {
+ period_start: number
+ timestamp: string
+ query_latency: number
+ mean_time: number
+ min_time: number
+ max_time: number
+ stddev_time: number
+ p50_time: number
+ p95_time: number
+ rows_read: number
+ calls: number
+ cache_hits: number
+ cache_misses: number
+}
+
+export const parsePgStatMonitorLogs = (logData: any[]): ParsedLogEntry[] => {
+ if (!logData || logData.length === 0) return []
+
+ const validParsedLogs = logData
+ .map((log) => ({
+ ...log,
+ parsedEventMessage: transformLogsToJSON(log.event_message),
+ }))
+ .filter((log) => log.parsedEventMessage !== null)
+ .filter((log) => log.parsedEventMessage?.event === 'bucket_query')
+
+ return validParsedLogs.map((log) => log.parsedEventMessage)
+}
+
+export const transformLogsToChartData = (parsedLogs: ParsedLogEntry[]): ChartDataPoint[] => {
+ if (!parsedLogs || parsedLogs.length === 0) return []
+
+ // [kemal]: here for debugging
+ // if (parsedLogs.length > 0) {
+ // console.log('🟡 Parsed logs:', parsedLogs)
+ // }
+
+ return parsedLogs
+ .map((log: ParsedLogEntry) => {
+ const possibleTimestamps = [log.bucket_start_time, log.bucket, log.timestamp, log.ts]
+
+ let periodStart: number | null = null
+
+ for (const ts of possibleTimestamps) {
+ if (ts) {
+ const date = new Date(ts)
+ const time = date.getTime()
+ if (!isNaN(time) && time > 0 && time > 946684800000) {
+ periodStart = time
+ break
+ }
+ }
+ }
+
+ if (!periodStart) {
+ return null
+ }
+
+ const percentiles =
+ log.resp_calls && Array.isArray(log.resp_calls)
+ ? calculatePercentilesFromHistogram(log.resp_calls)
+ : { p50: 0, p95: 0 }
+
+ return {
+ period_start: periodStart,
+ timestamp: possibleTimestamps.find((t) => t) || '',
+ query_latency: parseFloat(
+ String(log.mean_time ?? log.mean_exec_time ?? log.mean_query_time ?? 0)
+ ),
+ mean_time: parseFloat(
+ String(log.mean_time ?? log.mean_exec_time ?? log.mean_query_time ?? 0)
+ ),
+ min_time: parseFloat(String(log.min_time ?? log.min_exec_time ?? log.min_query_time ?? 0)),
+ max_time: parseFloat(String(log.max_time ?? log.max_exec_time ?? log.max_query_time ?? 0)),
+ stddev_time: parseFloat(
+ String(log.stddev_time ?? log.stddev_exec_time ?? log.stddev_query_time ?? 0)
+ ),
+ p50_time: percentiles.p50,
+ p95_time: percentiles.p95,
+ rows_read: parseInt(String(log.rows ?? 0), 10),
+ calls: parseInt(String(log.calls ?? 0), 10),
+ cache_hits: parseFloat(String(log.shared_blks_hit ?? 0)),
+ cache_misses: parseFloat(String(log.shared_blks_read ?? 0)),
+ }
+ })
+ .filter((item): item is NonNullable => item !== null)
+ .sort((a, b) => a.period_start - b.period_start)
+}
+
+const normalizeQuery = (query: string): string => {
+ return query.replace(/\s+/g, ' ').trim()
+}
+
+export const aggregateLogsByQuery = (parsedLogs: ParsedLogEntry[]): QueryPerformanceRow[] => {
+ if (!parsedLogs || parsedLogs.length === 0) return []
+
+ const queryGroups = new Map()
+
+ parsedLogs.forEach((log) => {
+ const query = normalizeQuery(log.query || '')
+ if (!query) return
+
+ if (!queryGroups.has(query)) {
+ queryGroups.set(query, [])
+ }
+ queryGroups.get(query)!.push(log)
+ })
+
+ const aggregatedData: QueryPerformanceRow[] = []
+ let totalExecutionTime = 0
+
+ const queryStats = Array.from(queryGroups.entries()).map(([query, logs]) => {
+ const count = logs.length
+ let totalCalls = 0
+ let totalRowsRead = 0
+ let totalCacheHits = 0
+ let totalCacheMisses = 0
+ let rolname = logs[0].username
+ let minTime = Infinity
+ let maxTime = -Infinity
+ let totalExecutionTimeForQuery = 0
+
+ logs.forEach((log) => {
+ const logMeanTime = parseFloat(
+ String(log.mean_time ?? log.mean_exec_time ?? log.mean_query_time ?? 0)
+ )
+ const logMinTime = parseFloat(
+ String(log.min_time ?? log.min_exec_time ?? log.min_query_time ?? 0)
+ )
+ const logMaxTime = parseFloat(
+ String(log.max_time ?? log.max_exec_time ?? log.max_query_time ?? 0)
+ )
+ const logCalls = parseInt(String(log.calls ?? 0), 10)
+ const logRows = parseInt(String(log.rows ?? 0), 10)
+ const logCacheHits = parseFloat(String(log.shared_blks_hit ?? 0))
+ const logCacheMisses = parseFloat(String(log.shared_blks_read ?? 0))
+
+ minTime = Math.min(minTime, logMinTime)
+ maxTime = Math.max(maxTime, logMaxTime)
+ totalCalls += logCalls
+ totalRowsRead += logRows
+ totalCacheHits += logCacheHits
+ totalCacheMisses += logCacheMisses
+ totalExecutionTimeForQuery += logMeanTime * logCalls
+ })
+
+ // Overall mean time is the weighted average
+ const avgMeanTime = totalCalls > 0 ? totalExecutionTimeForQuery / totalCalls : 0
+ const finalMinTime = minTime === Infinity ? 0 : minTime
+ const finalMaxTime = maxTime === -Infinity ? 0 : maxTime
+
+ totalExecutionTime += totalExecutionTimeForQuery
+
+ return {
+ query,
+ rolname,
+ count,
+ avgMeanTime,
+ minTime: finalMinTime,
+ maxTime: finalMaxTime,
+ totalCalls,
+ totalRowsRead,
+ totalTime: totalExecutionTimeForQuery,
+ totalCacheHits,
+ totalCacheMisses,
+ }
+ })
+
+ queryStats.forEach((stats) => {
+ const totalCacheAccess = stats.totalCacheHits + stats.totalCacheMisses
+ const cacheHitRate = totalCacheAccess > 0 ? (stats.totalCacheHits / totalCacheAccess) * 100 : 0
+
+ const propTotalTime = totalExecutionTime > 0 ? (stats.totalTime / totalExecutionTime) * 100 : 0
+
+ aggregatedData.push({
+ query: stats.query,
+ rolname: stats.rolname,
+ calls: stats.totalCalls,
+ mean_time: stats.avgMeanTime,
+ min_time: stats.minTime,
+ max_time: stats.maxTime,
+ total_time: stats.totalTime,
+ rows_read: stats.totalRowsRead,
+ cache_hit_rate: cacheHitRate,
+ prop_total_time: propTotalTime,
+ index_advisor_result: null,
+ _total_cache_hits: stats.totalCacheHits,
+ _total_cache_misses: stats.totalCacheMisses,
+ _count: stats.count,
+ })
+ })
+
+ return aggregatedData.sort((a, b) => b.total_time - a.total_time)
+}
+
+export const calculatePercentilesFromHistogram = (
+ respCalls: number[]
+): {
+ p50: number
+ p95: number
+} => {
+ const bucketBoundaries = [
+ { min: 0, max: 1 },
+ { min: 1, max: 10 },
+ { min: 10, max: 100 },
+ { min: 100, max: 1000 },
+ { min: 1000, max: 10000 },
+ { min: 10000, max: 100000 },
+ ]
+
+ const totalCalls = respCalls.reduce((sum, count) => sum + count, 0)
+
+ if (totalCalls === 0) {
+ return { p50: 0, p95: 0 }
+ }
+
+ const distribution: {
+ minValue: number
+ maxValue: number
+ cumulativeCount: number
+ count: number
+ }[] = []
+ let cumulativeCount = 0
+
+ respCalls.forEach((count, index) => {
+ if (count > 0 && index < bucketBoundaries.length) {
+ const bucket = bucketBoundaries[index]
+ cumulativeCount += count
+ distribution.push({
+ minValue: bucket.min,
+ maxValue: bucket.max,
+ cumulativeCount,
+ count,
+ })
+ }
+ })
+
+ const getPercentile = (percentile: number): number => {
+ const targetCount = totalCalls * percentile
+
+ for (let i = 0; i < distribution.length; i++) {
+ const prevCumulativeCount = i > 0 ? distribution[i - 1].cumulativeCount : 0
+
+ if (distribution[i].cumulativeCount >= targetCount) {
+ const positionInBucket = (targetCount - prevCumulativeCount) / distribution[i].count
+ const bucketMin = distribution[i].minValue
+ const bucketMax = distribution[i].maxValue
+ const logMin = Math.log10(Math.max(bucketMin, 0.1))
+ const logMax = Math.log10(bucketMax)
+ const logValue = logMin + positionInBucket * (logMax - logMin)
+
+ return Math.pow(10, logValue)
+ }
+ }
+
+ return distribution[distribution.length - 1]?.maxValue || 0
+ }
+
+ const result = {
+ p50: getPercentile(0.5),
+ p95: getPercentile(0.95),
+ }
+
+ return result
+}
diff --git a/apps/studio/components/interfaces/QueryPerformance/WithStatements/WithStatements.tsx b/apps/studio/components/interfaces/QueryPerformance/WithStatements/WithStatements.tsx
new file mode 100644
index 0000000000000..b35864d3a4659
--- /dev/null
+++ b/apps/studio/components/interfaces/QueryPerformance/WithStatements/WithStatements.tsx
@@ -0,0 +1,186 @@
+import { LoadingLine, cn } from 'ui'
+import { useState, useEffect, useMemo } from 'react'
+
+import { Button } from 'ui'
+import { X, RefreshCw, RotateCcw } from 'lucide-react'
+import { Markdown } from '../../Markdown'
+import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
+import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
+import { LOCAL_STORAGE_KEYS, useParams } from 'common'
+import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
+import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
+import { DOCS_URL, IS_PLATFORM } from 'lib/constants'
+import { executeSql } from 'data/sql/execute-sql-query'
+import { toast } from 'sonner'
+import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
+import { formatDatabaseID } from 'data/read-replicas/replicas.utils'
+import { PresetHookResult } from 'components/interfaces/Reports/Reports.utils'
+import { DbQueryHook } from 'hooks/analytics/useDbQuery'
+import { QueryPerformanceMetrics } from '../QueryPerformanceMetrics'
+import { QueryPerformanceFilterBar } from '../QueryPerformanceFilterBar'
+import { QueryPerformanceGrid } from '../QueryPerformanceGrid'
+import { transformStatementDataToRows } from './WithStatements.utils'
+import { DownloadResultsButton } from 'components/ui/DownloadResultsButton'
+
+interface WithStatementsProps {
+ queryHitRate: PresetHookResult
+ queryPerformanceQuery: DbQueryHook
+ queryMetrics: PresetHookResult
+}
+
+export const WithStatements = ({
+ queryHitRate,
+ queryPerformanceQuery,
+ queryMetrics,
+}: WithStatementsProps) => {
+ const { ref } = useParams()
+ const { data: project } = useSelectedProjectQuery()
+ const state = useDatabaseSelectorStateSnapshot()
+ const { data, isLoading, isRefetching } = queryPerformanceQuery
+ const isPrimaryDatabase = state.selectedDatabaseId === ref
+ const formattedDatabaseId = formatDatabaseID(state.selectedDatabaseId ?? '')
+
+ const [showResetgPgStatStatements, setShowResetgPgStatStatements] = useState(false)
+
+ const [showBottomSection, setShowBottomSection] = useLocalStorageQuery(
+ LOCAL_STORAGE_KEYS.QUERY_PERF_SHOW_BOTTOM_SECTION,
+ true
+ )
+
+ const handleRefresh = () => {
+ queryPerformanceQuery.runQuery()
+ queryHitRate.runQuery()
+ queryMetrics.runQuery()
+ }
+
+ const processedData = useMemo(() => {
+ return transformStatementDataToRows(data || [])
+ }, [data])
+
+ const { data: databases } = useReadReplicasQuery({ projectRef: ref })
+
+ useEffect(() => {
+ state.setSelectedDatabaseId(ref)
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [ref])
+
+ return (
+ <>
+
+
+ }
+ onClick={handleRefresh}
+ loading={isRefetching}
+ >
+ Refresh
+
+ }
+ onClick={() => setShowResetgPgStatStatements(true)}
+ >
+ Reset report
+
+
+ >
+ }
+ />
+
+
+
+
setShowBottomSection(false)}
+ >
+
+
+
+
Reset report
+
+ Consider resetting the analysis after optimizing any queries
+
+
setShowResetgPgStatStatements(true)}
+ >
+ Reset report
+
+
+
+
+
How is this report generated?
+
+
+
+
+
Inspect your database for potential issues
+
+
+
+
+ setShowResetgPgStatStatements(false)}
+ onConfirm={async () => {
+ const connectionString = databases?.find(
+ (db) => db.identifier === state.selectedDatabaseId
+ )?.connectionString
+
+ if (IS_PLATFORM && !connectionString) {
+ return toast.error('Unable to run query: Connection string is missing')
+ }
+
+ try {
+ await executeSql({
+ projectRef: project?.ref,
+ connectionString,
+ sql: `SELECT pg_stat_statements_reset();`,
+ })
+ handleRefresh()
+ setShowResetgPgStatStatements(false)
+ } catch (error: any) {
+ toast.error(`Failed to reset analysis: ${error.message}`)
+ }
+ }}
+ >
+
+ This will reset the pg_stat_statements table in the extensions schema on your{' '}
+
+ {isPrimaryDatabase ? 'primary database' : `read replica (ID: ${formattedDatabaseId})`}
+
+ , which is used to calculate query performance. This data will repopulate immediately
+ after.
+
+
+ >
+ )
+}
diff --git a/apps/studio/components/interfaces/QueryPerformance/WithStatements/WithStatements.utils.ts b/apps/studio/components/interfaces/QueryPerformance/WithStatements/WithStatements.utils.ts
new file mode 100644
index 0000000000000..8e5ba0b86f328
--- /dev/null
+++ b/apps/studio/components/interfaces/QueryPerformance/WithStatements/WithStatements.utils.ts
@@ -0,0 +1,22 @@
+import { QueryPerformanceRow } from '../QueryPerformance.types'
+
+export const transformStatementDataToRows = (data: any[]): QueryPerformanceRow[] => {
+ if (!data || data.length === 0) return []
+
+ const totalTimeAcrossAllQueries = data.reduce((sum, row) => sum + (row.total_time || 0), 0)
+
+ return data.map((row) => ({
+ query: row.query,
+ rolname: row.rolname || undefined,
+ calls: row.calls || 0,
+ mean_time: row.mean_time || 0,
+ min_time: row.min_time || 0,
+ max_time: row.max_time || 0,
+ total_time: row.total_time || 0,
+ rows_read: row.rows_read || 0,
+ cache_hit_rate: row.cache_hit_rate || 0,
+ prop_total_time:
+ totalTimeAcrossAllQueries > 0 ? (row.total_time / totalTimeAcrossAllQueries) * 100 : 0,
+ index_advisor_result: row.index_advisor_result || null,
+ }))
+}
diff --git a/apps/studio/components/interfaces/QueryPerformance/hooks/useIsIndexAdvisorStatus.ts b/apps/studio/components/interfaces/QueryPerformance/hooks/useIsIndexAdvisorStatus.ts
index 5775541954b01..7001611ce788b 100644
--- a/apps/studio/components/interfaces/QueryPerformance/hooks/useIsIndexAdvisorStatus.ts
+++ b/apps/studio/components/interfaces/QueryPerformance/hooks/useIsIndexAdvisorStatus.ts
@@ -1,4 +1,4 @@
-import { getIndexAdvisorExtensions } from 'components/interfaces/QueryPerformance/index-advisor.utils'
+import { getIndexAdvisorExtensions } from 'components/interfaces/QueryPerformance/IndexAdvisor/index-advisor.utils'
import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
diff --git a/apps/studio/components/interfaces/Reports/v2/ReportChartV2.test.tsx b/apps/studio/components/interfaces/Reports/v2/ReportChartV2.test.tsx
new file mode 100644
index 0000000000000..85b134bb5e036
--- /dev/null
+++ b/apps/studio/components/interfaces/Reports/v2/ReportChartV2.test.tsx
@@ -0,0 +1,35 @@
+import { describe, it, expect } from 'vitest'
+import { computePeriodTotal } from './ReportChartV2'
+
+describe('computePeriodTotal', () => {
+ const attrs = [
+ { attribute: 'SignInAttempts', label: 'Password', enabled: true },
+ { attribute: 'SignInAttempts', label: 'PKCE', enabled: true },
+ { attribute: 'SignInAttempts', label: 'Refresh Token', enabled: true },
+ { attribute: 'SignInAttempts', label: 'ID Token', enabled: true },
+ ]
+
+ it('deduplicates attributes that map to same field', () => {
+ const data = [
+ { timestamp: 1, SignInAttempts: 1 },
+ { timestamp: 2, SignInAttempts: 0 },
+ ]
+ expect(computePeriodTotal(data as any, attrs as any)).toBe(1)
+ })
+
+ it('excludes reference lines, max values, omitted and disabled attributes', () => {
+ const data = [
+ { timestamp: 1, a: 1, b: 2, c: 4, d: 8 },
+ { timestamp: 2, a: 1, b: 2, c: 4, d: 8 },
+ ]
+ const attributes = [
+ { attribute: 'a', enabled: true },
+ { attribute: 'b', provider: 'reference-line', enabled: true },
+ { attribute: 'c', isMaxValue: true, enabled: true },
+ { attribute: 'd', omitFromTotal: true, enabled: true },
+ { attribute: 'e', enabled: false },
+ ]
+ // Only 'a' should count: period total = 1+1 = 2
+ expect(computePeriodTotal(data as any, attributes as any)).toBe(2)
+ })
+})
diff --git a/apps/studio/components/interfaces/Reports/v2/ReportChartV2.tsx b/apps/studio/components/interfaces/Reports/v2/ReportChartV2.tsx
index 01f9ed5f0040b..b84e320db062f 100644
--- a/apps/studio/components/interfaces/Reports/v2/ReportChartV2.tsx
+++ b/apps/studio/components/interfaces/Reports/v2/ReportChartV2.tsx
@@ -26,6 +26,32 @@ export interface ReportChartV2Props {
highlightActions?: ChartHighlightAction[]
}
+// Compute total across entire period over unique attribute keys.
+// Excludes attributes that are disabled, reference lines, max values, or marked omitFromTotal.
+export function computePeriodTotal(chartData: any[], dynamicAttributes: any[]): number {
+ const attributeKeys = Array.from(
+ new Set(
+ (dynamicAttributes as any[])
+ .filter(
+ (a) =>
+ a?.enabled !== false &&
+ a?.provider !== 'reference-line' &&
+ !a?.isMaxValue &&
+ !a?.omitFromTotal
+ )
+ .map((a: any) => a.attribute)
+ )
+ )
+
+ return chartData.reduce((sum: number, row: any) => {
+ const rowTotal = attributeKeys.reduce((acc: number, key: string) => {
+ const value = row?.[key]
+ return acc + (typeof value === 'number' ? value : 0)
+ }, 0)
+ return sum + rowTotal
+ }, 0)
+}
+
export const ReportChartV2 = ({
report,
projectRef,
@@ -72,6 +98,8 @@ export const ReportChartV2 = ({
const chartData = queryResult?.data || []
const dynamicAttributes = queryResult?.attributes || []
+ const headerTotal = computePeriodTotal(chartData, dynamicAttributes)
+
/**
* Depending on the source the timestamp key could be 'timestamp' or 'period_start'
*/
@@ -124,7 +152,7 @@ export const ReportChartV2 = ({
xAxisKey={report.xAxisKey ?? 'timestamp'}
yAxisKey={report.yAxisKey ?? dynamicAttributes[0]?.attribute}
hideHighlightedValue={report.hideHighlightedValue}
- highlightedValue={0}
+ highlightedValue={headerTotal}
title={report.label}
customDateFormat={undefined}
chartStyle={chartStyle}
diff --git a/apps/studio/components/interfaces/Storage/StorageExplorer/StorageExplorer.tsx b/apps/studio/components/interfaces/Storage/StorageExplorer/StorageExplorer.tsx
index e827c71f24d10..c6471fd917445 100644
--- a/apps/studio/components/interfaces/Storage/StorageExplorer/StorageExplorer.tsx
+++ b/apps/studio/components/interfaces/Storage/StorageExplorer/StorageExplorer.tsx
@@ -53,7 +53,6 @@ export const StorageExplorer = ({ bucket }: StorageExplorerProps) => {
// Things like showing results from a search filter is "temporary", hence we use react state to manage
const [itemSearchString, setItemSearchString] = useState('')
- // eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => {
const fetchContents = async () => {
if (view === STORAGE_VIEWS.LIST) {
diff --git a/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.tsx b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.tsx
index d2250d0576ab9..328489afa925a 100644
--- a/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.tsx
+++ b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.tsx
@@ -285,7 +285,6 @@ export const UnifiedLogs = () => {
useEffect(() => {
debouncedApplyFilterSearch()
- // eslint-disable-next-line react-hooks/exhaustive-deps
}, [columnFilters, debouncedApplyFilterSearch])
useEffect(() => {
diff --git a/apps/studio/components/layouts/AdvisorsLayout/AdvisorsMenu.utils.ts b/apps/studio/components/layouts/AdvisorsLayout/AdvisorsMenu.utils.tsx
similarity index 86%
rename from apps/studio/components/layouts/AdvisorsLayout/AdvisorsMenu.utils.ts
rename to apps/studio/components/layouts/AdvisorsLayout/AdvisorsMenu.utils.tsx
index ee22c2d7ba5a2..5566c59aff190 100644
--- a/apps/studio/components/layouts/AdvisorsLayout/AdvisorsMenu.utils.ts
+++ b/apps/studio/components/layouts/AdvisorsLayout/AdvisorsMenu.utils.tsx
@@ -1,6 +1,7 @@
import type { ProductMenuGroup } from 'components/ui/ProductMenu/ProductMenu.types'
import type { Project } from 'data/projects/project-detail-query'
import { IS_PLATFORM } from 'lib/constants'
+import { ArrowUpRight } from 'lucide-react'
export const generateAdvisorsMenu = (
project?: Project,
@@ -27,8 +28,9 @@ export const generateAdvisorsMenu = (
{
name: 'Query Performance',
key: 'query-performance',
- url: `/project/${ref}/advisors/query-performance`,
+ url: `/project/${ref}/reports/query-performance`,
items: [],
+ rightIcon: ,
},
],
},
diff --git a/apps/studio/components/layouts/ReportsLayout/Reports.Commands.tsx b/apps/studio/components/layouts/ReportsLayout/Reports.Commands.tsx
index 1895819ffac5f..20d2216ec6e5e 100644
--- a/apps/studio/components/layouts/ReportsLayout/Reports.Commands.tsx
+++ b/apps/studio/components/layouts/ReportsLayout/Reports.Commands.tsx
@@ -77,7 +77,7 @@ export function useReportsGotoCommands(options?: CommandOptions) {
{
id: QUERY_PERFORMANCE_COMMAND_ID,
name: 'Query Performance Reports',
- route: `/project/${ref}/advisors/query-performance`,
+ route: `/project/${ref}/reports/query-performance`,
defaultHidden: true,
},
]
diff --git a/apps/studio/components/layouts/ReportsLayout/ReportsMenu.tsx b/apps/studio/components/layouts/ReportsLayout/ReportsMenu.tsx
index a770362494a59..f0644ef774088 100644
--- a/apps/studio/components/layouts/ReportsLayout/ReportsMenu.tsx
+++ b/apps/studio/components/layouts/ReportsLayout/ReportsMenu.tsx
@@ -1,5 +1,5 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
-import { Plus, ArrowUpRight } from 'lucide-react'
+import { Plus } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useMemo, useState } from 'react'
@@ -149,8 +149,7 @@ const ReportsMenu = () => {
{
name: 'Query Performance',
key: 'query-performance',
- url: `/project/${ref}/advisors/query-performance`,
- rightIcon: ,
+ url: `/project/${ref}/reports/query-performance${preservedQueryParams}`,
},
...(postgrestReportEnabled
? [
@@ -261,9 +260,6 @@ const ReportsMenu = () => {
className="flex-grow h-7 flex justify-between items-center pl-3"
>
{subItem.name}
- {subItem.rightIcon && (
- {subItem.rightIcon}
- )}
))}
diff --git a/apps/studio/components/ui/Charts/ChartHeader.tsx b/apps/studio/components/ui/Charts/ChartHeader.tsx
index 86a8383414632..94df3ecdab928 100644
--- a/apps/studio/components/ui/Charts/ChartHeader.tsx
+++ b/apps/studio/components/ui/Charts/ChartHeader.tsx
@@ -9,6 +9,7 @@ import {
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
+import { cn } from 'ui'
import { useParams } from 'common'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
@@ -26,6 +27,8 @@ export interface ChartHeaderProps {
highlightedLabel?: number | string | any | null
highlightedValue?: number | string | any | null
hideHighlightedValue?: boolean
+ hideHighlightedLabel?: boolean
+ hideHighlightArea?: boolean
hideChartType?: boolean
chartStyle?: string
onChartStyleChange?: (style: string) => void
@@ -50,6 +53,8 @@ export const ChartHeader = ({
highlightedValue,
highlightedLabel,
hideHighlightedValue = false,
+ hideHighlightedLabel = false,
+ hideHighlightArea = false,
title,
minimalHeader = false,
hideChartType = false,
@@ -202,11 +207,14 @@ export const ChartHeader = ({
if (minimalHeader) {
return (
-
+
{title && chartTitle}
{highlightedValue !== undefined && !hideHighlightedValue && highlighted}
- {label}
+ {!hideHighlightedLabel && label}
)
@@ -215,12 +223,17 @@ export const ChartHeader = ({
const hasHighlightedValue = highlightedValue !== undefined && !hideHighlightedValue
return (
-
+
{title && chartTitle}
{hasHighlightedValue && highlighted}
- {label}
+ {!hideHighlightedLabel && label}
diff --git a/apps/studio/components/ui/Charts/ComposedChart.tsx b/apps/studio/components/ui/Charts/ComposedChart.tsx
index 94ef08ac6f980..acbcfc62db54c 100644
--- a/apps/studio/components/ui/Charts/ComposedChart.tsx
+++ b/apps/studio/components/ui/Charts/ComposedChart.tsx
@@ -62,6 +62,8 @@ export interface ComposedChartProps
extends CommonChartProps {
titleTooltip?: string
hideYAxis?: boolean
hideHighlightedValue?: boolean
+ hideHighlightedLabel?: boolean
+ hideHighlightArea?: boolean
syncId?: string
docsUrl?: string
sql?: string
@@ -101,6 +103,8 @@ export function ComposedChart({
updateDateRange,
hideYAxis,
hideHighlightedValue,
+ hideHighlightedLabel = false,
+ hideHighlightArea = false,
syncId,
docsUrl,
sql,
@@ -316,6 +320,8 @@ export function ComposedChart({
hideHighlightedValue={hideHighlightedValue}
title={title}
format={format}
+ hideHighlightedLabel={hideHighlightedLabel}
+ hideHighlightArea={hideHighlightArea}
titleTooltip={titleTooltip}
customDateFormat={customDateFormat}
highlightedValue={formatHighlightedValue(resolvedHighlightedValue)}
diff --git a/apps/studio/data/analytics/infra-monitoring-queries.ts b/apps/studio/data/analytics/infra-monitoring-queries.ts
index dad4edd42583d..7cc001a93900d 100644
--- a/apps/studio/data/analytics/infra-monitoring-queries.ts
+++ b/apps/studio/data/analytics/infra-monitoring-queries.ts
@@ -1,6 +1,6 @@
-import { useInfraMonitoringQuery } from './infra-monitoring-query'
-import type { InfraMonitoringAttribute } from './infra-monitoring-query'
import { AnalyticsInterval } from './constants'
+import type { InfraMonitoringAttribute } from './infra-monitoring-query'
+import { useInfraMonitoringQuery } from './infra-monitoring-query'
export function useInfraMonitoringQueries(
attributes: InfraMonitoringAttribute[],
@@ -13,6 +13,7 @@ export function useInfraMonitoringQueries(
isVisible: boolean
) {
return attributes.map((attribute) =>
+ // eslint-disable-next-line react-hooks/rules-of-hooks
useInfraMonitoringQuery(
{
projectRef: ref as string,
diff --git a/apps/studio/data/analytics/project-daily-stats-queries.ts b/apps/studio/data/analytics/project-daily-stats-queries.ts
index 92e29be93f81c..d039864975993 100644
--- a/apps/studio/data/analytics/project-daily-stats-queries.ts
+++ b/apps/studio/data/analytics/project-daily-stats-queries.ts
@@ -1,5 +1,5 @@
-import { useProjectDailyStatsQuery } from './project-daily-stats-query'
import type { ProjectDailyStatsAttribute } from './project-daily-stats-query'
+import { useProjectDailyStatsQuery } from './project-daily-stats-query'
export function useProjectDailyStatsQueries(
attributes: ProjectDailyStatsAttribute[],
@@ -10,6 +10,7 @@ export function useProjectDailyStatsQueries(
isVisible: boolean
) {
return attributes.map((attribute) =>
+ // eslint-disable-next-line react-hooks/rules-of-hooks
useProjectDailyStatsQuery(
{
projectRef: ref as string,
diff --git a/apps/studio/data/graphql/fragment-masking.ts b/apps/studio/data/graphql/fragment-masking.ts
index 494960811faac..20d21c67a3a39 100644
--- a/apps/studio/data/graphql/fragment-masking.ts
+++ b/apps/studio/data/graphql/fragment-masking.ts
@@ -1,5 +1,4 @@
-/* eslint-disable */
-import { ResultOf, DocumentTypeDecoration } from '@graphql-typed-document-node/core'
+import { DocumentTypeDecoration, ResultOf } from '@graphql-typed-document-node/core'
import { Incremental, TypedDocumentString } from './graphql'
export type FragmentType> =
diff --git a/apps/studio/data/graphql/gql.ts b/apps/studio/data/graphql/gql.ts
index 1f4ff76f6e6ff..9164d77dbcdcf 100644
--- a/apps/studio/data/graphql/gql.ts
+++ b/apps/studio/data/graphql/gql.ts
@@ -1,4 +1,3 @@
-/* eslint-disable */
import * as types from './graphql'
/**
diff --git a/apps/studio/data/graphql/graphql.ts b/apps/studio/data/graphql/graphql.ts
index 99d32bef6b994..aac84b54d9967 100644
--- a/apps/studio/data/graphql/graphql.ts
+++ b/apps/studio/data/graphql/graphql.ts
@@ -1,4 +1,3 @@
-/* eslint-disable */
import { DocumentTypeDecoration } from '@graphql-typed-document-node/core'
export type Maybe = T | null
export type InputMaybe = Maybe
diff --git a/apps/studio/data/graphql/index.ts b/apps/studio/data/graphql/index.ts
index f9bc8e591d00f..83851531f643a 100644
--- a/apps/studio/data/graphql/index.ts
+++ b/apps/studio/data/graphql/index.ts
@@ -1,2 +1,2 @@
-export * from './fragment-masking'
-export * from './gql'
+export {} from './fragment-masking'
+export { graphql } from './gql'
diff --git a/apps/studio/eslint.config.cjs b/apps/studio/eslint.config.cjs
new file mode 100644
index 0000000000000..5b3f0df7fd398
--- /dev/null
+++ b/apps/studio/eslint.config.cjs
@@ -0,0 +1,19 @@
+const { defineConfig } = require('eslint/config')
+const barrelFiles = require('eslint-plugin-barrel-files')
+const supabaseConfig = require('eslint-config-supabase/next')
+
+module.exports = defineConfig([
+ { files: ['**/*.ts', '**/*.tsx'] },
+ supabaseConfig,
+ {
+ plugins: {
+ 'barrel-files': barrelFiles,
+ },
+ rules: {
+ '@next/next/no-img-element': 'off',
+ 'react/no-unescaped-entities': 'off',
+ 'react/display-name': 'warn',
+ 'barrel-files/avoid-re-export-all': 'error',
+ },
+ },
+])
diff --git a/apps/studio/hooks/misc/useOrganizationRestrictions.ts b/apps/studio/hooks/misc/useOrganizationRestrictions.ts
index 1486275bf510e..54bdd88a012bc 100644
--- a/apps/studio/hooks/misc/useOrganizationRestrictions.ts
+++ b/apps/studio/hooks/misc/useOrganizationRestrictions.ts
@@ -4,8 +4,6 @@ import { RESTRICTION_MESSAGES } from 'components/interfaces/Organization/restric
import { useOverdueInvoicesQuery } from 'data/invoices/invoices-overdue-query'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
-import { useBillingCustomerDataForm } from 'components/interfaces/Organization/BillingSettings/BillingCustomerData/useBillingCustomerDataForm'
-import { useOrganizationCustomerProfileQuery } from 'data/organizations/organization-customer-profile-query'
import { useIsFeatureEnabled } from './useIsFeatureEnabled'
export type WarningBannerProps = {
@@ -20,7 +18,6 @@ export function useOrganizationRestrictions() {
const { data: overdueInvoices } = useOverdueInvoicesQuery()
const { data: organizations } = useOrganizationsQuery()
- const { data: billingCustomer } = useOrganizationCustomerProfileQuery({ slug: org?.slug })
const warnings: WarningBannerProps[] = []
@@ -36,13 +33,7 @@ export function useOrganizationRestrictions() {
(invoice) => invoice.organization_id === org?.id
)
- if (
- org &&
- org.plan.id !== 'free' &&
- billingCustomer &&
- !billingCustomer.address?.line1 &&
- !org.billing_partner
- ) {
+ if (org && org.organization_missing_address && !org.billing_partner) {
warnings.push({
type: 'danger',
title: RESTRICTION_MESSAGES.MISSING_BILLING_INFO.title,
diff --git a/apps/studio/package.json b/apps/studio/package.json
index 5a0b527c809ee..842f137b5faf8 100644
--- a/apps/studio/package.json
+++ b/apps/studio/package.json
@@ -7,7 +7,7 @@
"dev": "next dev --turbopack -p 8082",
"build": "next build && ./../../scripts/upload-static-assets.sh",
"start": "next start",
- "lint": "next lint",
+ "lint": "eslint .",
"clean": "rimraf node_modules tsconfig.tsbuildinfo .next .turbo",
"test": "vitest --run --coverage",
"test:watch": "vitest watch",
diff --git a/apps/studio/pages/_document.tsx b/apps/studio/pages/_document.tsx
index c8c36e2c3486f..e91481eb0f32f 100644
--- a/apps/studio/pages/_document.tsx
+++ b/apps/studio/pages/_document.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable @next/next/no-css-tags */
import { BASE_PATH, IS_PLATFORM } from 'lib/constants'
import Document, { DocumentContext, Head, Html, Main, NextScript } from 'next/document'
diff --git a/apps/studio/pages/project/[ref]/advisors/performance.tsx b/apps/studio/pages/project/[ref]/advisors/performance.tsx
index 2e830a927cf5a..9d87e820be386 100644
--- a/apps/studio/pages/project/[ref]/advisors/performance.tsx
+++ b/apps/studio/pages/project/[ref]/advisors/performance.tsx
@@ -35,7 +35,6 @@ const ProjectLints: NextPageWithLayout = () => {
const activeLints = useMemo(() => {
return [...(data ?? [])]?.filter((x) => x.categories.includes('PERFORMANCE'))
- // eslint-disable-next-line react-hooks/exhaustive-deps
}, [data])
const currentTabFilters = (filters.find((filter) => filter.level === currentTab)?.filters ||
[]) as string[]
diff --git a/apps/studio/pages/project/[ref]/advisors/query-performance.tsx b/apps/studio/pages/project/[ref]/reports/query-performance.tsx
similarity index 61%
rename from apps/studio/pages/project/[ref]/advisors/query-performance.tsx
rename to apps/studio/pages/project/[ref]/reports/query-performance.tsx
index 4db0331e91116..f0a78fb3f4d76 100644
--- a/apps/studio/pages/project/[ref]/advisors/query-performance.tsx
+++ b/apps/studio/pages/project/[ref]/reports/query-performance.tsx
@@ -1,7 +1,7 @@
import { parseAsArrayOf, parseAsString, useQueryStates } from 'nuqs'
import { useParams } from 'common'
-import { EnableIndexAdvisorButton } from 'components/interfaces/QueryPerformance/EnableIndexAdvisorButton'
+import { EnableIndexAdvisorButton } from 'components/interfaces/QueryPerformance/IndexAdvisor/EnableIndexAdvisorButton'
import { useIndexAdvisorStatus } from 'components/interfaces/QueryPerformance/hooks/useIsIndexAdvisorStatus'
import { useQueryPerformanceSort } from 'components/interfaces/QueryPerformance/hooks/useQueryPerformanceSort'
import { QueryPerformance } from 'components/interfaces/QueryPerformance/QueryPerformance'
@@ -9,19 +9,32 @@ import { PRESET_CONFIG } from 'components/interfaces/Reports/Reports.constants'
import { useQueryPerformanceQuery } from 'components/interfaces/Reports/Reports.queries'
import { Presets } from 'components/interfaces/Reports/Reports.types'
import { queriesFactory } from 'components/interfaces/Reports/Reports.utils'
-import AdvisorsLayout from 'components/layouts/AdvisorsLayout/AdvisorsLayout'
+import ReportsLayout from 'components/layouts/ReportsLayout/ReportsLayout'
import DefaultLayout from 'components/layouts/DefaultLayout'
import DatabaseSelector from 'components/ui/DatabaseSelector'
import { DocsButton } from 'components/ui/DocsButton'
import { FormHeader } from 'components/ui/Forms/FormHeader'
import { DOCS_URL } from 'lib/constants'
import type { NextPageWithLayout } from 'types'
+import { LogsDatePicker } from 'components/interfaces/Settings/Logs/Logs.DatePickers'
+import { useReportDateRange } from 'hooks/misc/useReportDateRange'
+import { REPORT_DATERANGE_HELPER_LABELS } from 'components/interfaces/Reports/Reports.constants'
+import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
const QueryPerformanceReport: NextPageWithLayout = () => {
const { ref } = useParams()
+ const { data: project } = useSelectedProjectQuery()
const { isIndexAdvisorEnabled } = useIndexAdvisorStatus()
const { sort: sortConfig } = useQueryPerformanceSort()
+ const {
+ selectedDateRange,
+ datePickerValue,
+ datePickerHelpers,
+ updateDateRange,
+ handleDatePickerChange,
+ } = useReportDateRange(REPORT_DATERANGE_HELPER_LABELS.LAST_60_MINUTES)
+
const [{ search: searchQuery, roles }] = useQueryStates({
sort: parseAsString,
order: parseAsString,
@@ -42,18 +55,32 @@ const QueryPerformanceReport: NextPageWithLayout = () => {
runIndexAdvisor: isIndexAdvisorEnabled,
})
+ const isPgStatMonitorEnabled = project?.dbVersion === '17.4.1.076-psml-1'
+
return (
+
+ {isPgStatMonitorEnabled && (
+
+ h.text === REPORT_DATERANGE_HELPER_LABELS.LAST_60_MINUTES ||
+ h.text === REPORT_DATERANGE_HELPER_LABELS.LAST_3_HOURS ||
+ h.text === REPORT_DATERANGE_HELPER_LABELS.LAST_24_HOURS
+ )}
+ onSubmit={handleDatePickerChange}
+ />
+ )}
}
/>
@@ -61,6 +88,9 @@ const QueryPerformanceReport: NextPageWithLayout = () => {
queryHitRate={queryHitRate}
queryPerformanceQuery={queryPerformanceQuery}
queryMetrics={queryMetrics}
+ isPgStatMonitorEnabled={isPgStatMonitorEnabled}
+ dateRange={selectedDateRange}
+ onDateRangeChange={updateDateRange}
/>
)
@@ -68,7 +98,7 @@ const QueryPerformanceReport: NextPageWithLayout = () => {
QueryPerformanceReport.getLayout = (page) => (
- {page}
+ {page}
)
diff --git a/apps/studio/tests/helpers.tsx b/apps/studio/tests/helpers.tsx
index 2cfef6e0b9a49..64dfcfcc5fc26 100644
--- a/apps/studio/tests/helpers.tsx
+++ b/apps/studio/tests/helpers.tsx
@@ -63,6 +63,7 @@ export const createMockOrganization = (details: Partial): Organiza
opt_in_tags: [],
restriction_status: null,
restriction_data: null,
+ organization_missing_address: false,
}
return Object.assign(base, details)
diff --git a/apps/studio/types/index.ts b/apps/studio/types/index.ts
index cacc37f912d10..e53ca46b6e038 100644
--- a/apps/studio/types/index.ts
+++ b/apps/studio/types/index.ts
@@ -1,6 +1,14 @@
-export * from './base'
-export type * from './ui'
-export type * from './userContent'
+export {
+ ResponseError,
+ type Dictionary,
+ type Organization,
+ type Permission,
+ type ResponseFailure,
+ type Role,
+ type SupaResponse,
+} from './base'
+export type * from './form'
export type * from './next'
export { isNextPageWithLayout } from './next'
-export type * from './form'
+export type * from './ui'
+export type * from './userContent'
diff --git a/apps/ui-library/.eslintrc.cjs b/apps/ui-library/.eslintrc.cjs
deleted file mode 100644
index 4f1d845f8cfdc..0000000000000
--- a/apps/ui-library/.eslintrc.cjs
+++ /dev/null
@@ -1,3 +0,0 @@
-module.exports = {
- extends: ['eslint-config-supabase/next'],
-}
diff --git a/apps/ui-library/eslint.config.cjs b/apps/ui-library/eslint.config.cjs
new file mode 100644
index 0000000000000..fec3ac9b3ab84
--- /dev/null
+++ b/apps/ui-library/eslint.config.cjs
@@ -0,0 +1,4 @@
+const { defineConfig } = require('eslint/config')
+const supabaseConfig = require('eslint-config-supabase/next')
+
+module.exports = defineConfig([supabaseConfig])
diff --git a/apps/ui-library/package.json b/apps/ui-library/package.json
index c398bbeeb9f8f..05c09a053f4b6 100644
--- a/apps/ui-library/package.json
+++ b/apps/ui-library/package.json
@@ -10,7 +10,7 @@
"build:registry": "rimraf -G public/r/* && tsx --tsconfig ./tsconfig.scripts.json ./scripts/build-registry.mts && shadcn build public/r/registry.json && tsx scripts/clean-registry.ts",
"build:llms": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/build-llms-txt.ts",
"start": "next start",
- "lint": "next lint",
+ "lint": "eslint .",
"lint:mdx": "supa-mdx-lint content --config ../../supa-mdx-lint.config.toml",
"content:build": "contentlayer2 build",
"clean": "rimraf node_modules .next .turbo",
diff --git a/apps/www/.eslintrc.js b/apps/www/.eslintrc.js
deleted file mode 100644
index d6f7ef0c0539d..0000000000000
--- a/apps/www/.eslintrc.js
+++ /dev/null
@@ -1,9 +0,0 @@
-module.exports = {
- extends: ['eslint-config-supabase/next'],
- rules: {
- 'react-hooks/rules-of-hooks': 'warn',
- 'react/no-unescaped-entities': 'warn',
- 'react/display-name': 'warn',
- 'react/no-children-prop': 'warn',
- },
-}
diff --git a/apps/www/eslint.config.cjs b/apps/www/eslint.config.cjs
new file mode 100644
index 0000000000000..8ebf71dad2af9
--- /dev/null
+++ b/apps/www/eslint.config.cjs
@@ -0,0 +1,14 @@
+const { defineConfig } = require('eslint/config')
+const supabaseConfig = require('eslint-config-supabase/next')
+
+module.exports = defineConfig([
+ supabaseConfig,
+ {
+ rules: {
+ 'react-hooks/rules-of-hooks': 'warn',
+ 'react/no-unescaped-entities': 'warn',
+ 'react/display-name': 'warn',
+ 'react/no-children-prop': 'warn',
+ },
+ },
+])
diff --git a/apps/www/internals/generate-sitemap.mjs b/apps/www/internals/generate-sitemap.mjs
index d884aa295fc39..f8231787875ac 100644
--- a/apps/www/internals/generate-sitemap.mjs
+++ b/apps/www/internals/generate-sitemap.mjs
@@ -220,9 +220,7 @@ async function generate() {
/**
* write sitemaps
*/
- // eslint-disable-next-line no-sync
writeFileSync('public/sitemap.xml', sitemapRouter)
- // eslint-disable-next-line no-sync
writeFileSync('public/sitemap_www.xml', formatted)
}
diff --git a/apps/www/package.json b/apps/www/package.json
index ad034c28b758e..dc40a6c0c6bca 100644
--- a/apps/www/package.json
+++ b/apps/www/package.json
@@ -9,7 +9,7 @@
"build": "pnpm run content:build && next build",
"export": "next export",
"start": "next start",
- "lint": "next lint",
+ "lint": "eslint .",
"clean": "rimraf node_modules",
"pretypecheck": "next typegen",
"typecheck": "pnpm run content:build && tsc --noEmit",
diff --git a/package.json b/package.json
index d8b192fe306d6..9e900a392dafd 100644
--- a/package.json
+++ b/package.json
@@ -48,7 +48,7 @@
"devDependencies": {
"@aws-sdk/client-secrets-manager": "^3.410.0",
"@types/node": "catalog:",
- "eslint": "^8.57.0",
+ "eslint": "^9.0.0",
"prettier": "3.2.4",
"prettier-plugin-sql-cst": "^0.11.0",
"rimraf": "^6.0.0",
diff --git a/packages/api-types/types/platform.d.ts b/packages/api-types/types/platform.d.ts
index b2a53293795be..fdfe2f256116b 100644
--- a/packages/api-types/types/platform.d.ts
+++ b/packages/api-types/types/platform.d.ts
@@ -5013,6 +5013,7 @@ export interface components {
is_owner: boolean
name: string
opt_in_tags: string[]
+ organization_missing_address: boolean
organization_requires_mfa: boolean
plan: {
/** @enum {string} */
@@ -7259,6 +7260,7 @@ export interface components {
is_owner: boolean
name: string
opt_in_tags: string[]
+ organization_missing_address: boolean
organization_requires_mfa: boolean
plan: {
/** @enum {string} */
@@ -8238,8 +8240,14 @@ export interface components {
max_events_per_second: number | null
/** @description Sets maximum number of joins per second rate limit */
max_joins_per_second: number | null
+ /** @description Sets maximum number of payload size in KB rate limit */
+ max_payload_size_in_kb: number | null
+ /** @description Sets maximum number of presence events per second rate limit */
+ max_presence_events_per_second: number | null
/** @description Whether to only allow private channels */
private_only: boolean | null
+ /** @description Whether to suspend realtime */
+ suspend: boolean | null
}
RegionsInfo: {
all: {
@@ -9989,8 +9997,14 @@ export interface components {
max_events_per_second?: number
/** @description Sets maximum number of joins per second rate limit */
max_joins_per_second?: number
+ /** @description Sets maximum number of payload size in KB rate limit */
+ max_payload_size_in_kb?: number
+ /** @description Sets maximum number of presence events per second rate limit */
+ max_presence_events_per_second?: number
/** @description Whether to only allow private channels */
private_only?: boolean
+ /** @description Whether to suspend realtime */
+ suspend?: boolean
}
UpdateReplicationDestinationBody: {
/** @description Destination configuration */
diff --git a/packages/eslint-config-supabase/next.js b/packages/eslint-config-supabase/next.js
index f3f39c45b6639..b19ed52548189 100644
--- a/packages/eslint-config-supabase/next.js
+++ b/packages/eslint-config-supabase/next.js
@@ -1,16 +1,43 @@
-module.exports = {
- extends: ['prettier', 'next/core-web-vitals', 'eslint-config-turbo'],
- rules: {
- '@next/next/no-html-link-for-pages': 'off',
- 'react/jsx-key': 'off',
- 'no-restricted-exports': ['warn', { restrictDefaultExports: { direct: true } }],
+const { defineConfig } = require('eslint/config')
+const js = require('@eslint/js')
+const { FlatCompat } = require('@eslint/eslintrc')
+const prettierConfig = require('eslint-config-prettier/flat')
+const { default: turboConfig } = require('eslint-config-turbo/flat')
+const { off } = require('process')
+
+const compat = new FlatCompat({
+ baseDirectory: __dirname,
+ recommendedConfig: js.configs.recommended,
+ allConfig: js.configs.all,
+})
+
+module.exports = defineConfig([
+ // Global ignore for the .next folder
+ { ignores: ['.next', 'public'] },
+ turboConfig,
+ prettierConfig,
+ {
+ extends: compat.extends('next/core-web-vitals'),
+ linterOptions: {
+ reportUnusedDisableDirectives: 'warn',
+ },
+ rules: {
+ '@next/next/no-html-link-for-pages': 'off',
+ 'react/jsx-key': 'off',
+ },
},
- overrides: [
- {
- files: ['pages/**', 'app/**'],
- rules: {
- 'no-restricted-exports': 'off',
- },
+ {
+ // check for default exports in all files except app and pages folders.
+ ignores: ['pages/**.tsx', 'app/**.tsx'],
+ rules: {
+ 'no-restricted-exports': [
+ 'warn',
+ {
+ restrictDefaultExports: {
+ direct: true,
+ },
+ },
+ ],
},
- ],
-}
+ },
+])
diff --git a/packages/eslint-config-supabase/package.json b/packages/eslint-config-supabase/package.json
index e278f95c21c5d..5610d5edeef11 100644
--- a/packages/eslint-config-supabase/package.json
+++ b/packages/eslint-config-supabase/package.json
@@ -8,11 +8,13 @@
"clean": "rimraf node_modules"
},
"dependencies": {
- "eslint-config-next": "15.3.1",
- "eslint-config-prettier": "^9.1.0",
- "eslint-config-turbo": "^2.0.4"
+ "eslint-config-next": "^15.5.0",
+ "eslint-config-prettier": "^10.0.0",
+ "eslint-config-turbo": "^2.5.0"
},
"devDependencies": {
+ "@eslint/eslintrc": "^3.0.0",
+ "@eslint/js": "^9.0.0",
"typescript": "catalog:"
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2f8426a1e62b9..655a22e78d140 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -68,8 +68,8 @@ importers:
specifier: 'catalog:'
version: 22.13.14
eslint:
- specifier: ^8.57.0
- version: 8.57.0(supports-color@8.1.1)
+ specifier: ^9.0.0
+ version: 9.37.0(jiti@2.5.1)(supports-color@8.1.1)
prettier:
specifier: 3.2.4
version: 3.2.4
@@ -1200,7 +1200,7 @@ importers:
version: link:../../packages/eslint-config-supabase
eslint-plugin-barrel-files:
specifier: ^2.0.7
- version: 2.0.7(eslint@8.57.0(supports-color@8.1.1))
+ version: 2.0.7(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))
graphql-ws:
specifier: 5.14.1
version: 5.14.1(graphql@16.11.0)
@@ -1820,7 +1820,7 @@ importers:
version: 1.15.4
nuxt:
specifier: ^4.0.3
- version: 4.1.2(@electric-sql/pglite@0.2.15)(@parcel/watcher@2.5.1)(@types/node@22.13.14)(@vue/compiler-sfc@3.5.21)(aws4fetch@1.0.20)(db0@0.3.2(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(eslint@8.57.0(supports-color@8.1.1))(ioredis@5.7.0(supports-color@8.1.1))(magicast@0.3.5)(rollup@4.50.2)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@6.3.6(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(yaml@2.8.1)
+ version: 4.1.2(@electric-sql/pglite@0.2.15)(@parcel/watcher@2.5.1)(@types/node@22.13.14)(@vue/compiler-sfc@3.5.21)(aws4fetch@1.0.20)(db0@0.3.2(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(ioredis@5.7.0(supports-color@8.1.1))(magicast@0.3.5)(rollup@4.50.2)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@6.3.6(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(yaml@2.8.1)
devDependencies:
shadcn:
specifier: ^3.0.0
@@ -2057,15 +2057,21 @@ importers:
packages/eslint-config-supabase:
dependencies:
eslint-config-next:
- specifier: 15.3.1
- version: 15.3.1(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)
+ specifier: ^15.5.0
+ version: 15.5.4(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)
eslint-config-prettier:
- specifier: ^9.1.0
- version: 9.1.0(eslint@8.57.0(supports-color@8.1.1))
+ specifier: ^10.0.0
+ version: 10.1.8(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))
eslint-config-turbo:
- specifier: ^2.0.4
- version: 2.0.4(eslint@8.57.0(supports-color@8.1.1))
+ specifier: ^2.5.0
+ version: 2.5.8(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(turbo@2.3.3)
devDependencies:
+ '@eslint/eslintrc':
+ specifier: ^3.0.0
+ version: 3.3.1(supports-color@8.1.1)
+ '@eslint/js':
+ specifier: ^9.0.0
+ version: 9.37.0
typescript:
specifier: 'catalog:'
version: 5.9.2
@@ -2121,7 +2127,7 @@ importers:
version: 8.11.11
'@vitest/coverage-v8':
specifier: ^3.0.9
- version: 3.0.9(supports-color@8.1.1)(vitest@3.0.9(@types/node@22.13.14)(jiti@2.5.1)(jsdom@20.0.3(supports-color@8.1.1))(msw@2.11.3(@types/node@22.13.14)(typescript@5.9.2))(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))
+ version: 3.0.9(supports-color@8.1.1)(vitest@3.0.9)
npm-run-all:
specifier: ^4.1.5
version: 4.1.5
@@ -2357,7 +2363,7 @@ importers:
version: 15.5.7
'@vitest/coverage-v8':
specifier: ^3.0.9
- version: 3.0.9(supports-color@8.1.1)(vitest@3.0.9(@types/node@22.13.14)(jiti@2.5.1)(jsdom@20.0.3(supports-color@8.1.1))(msw@2.11.3(@types/node@22.13.14)(typescript@5.9.2))(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))
+ version: 3.0.9(supports-color@8.1.1)(vitest@3.0.9)
common:
specifier: workspace:*
version: link:../common
@@ -3748,14 +3754,14 @@ packages:
cpu: [x64]
os: [win32]
- '@eslint-community/eslint-utils@4.4.0':
- resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
+ '@eslint-community/eslint-utils@4.7.0':
+ resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
- '@eslint-community/eslint-utils@4.7.0':
- resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==}
+ '@eslint-community/eslint-utils@4.9.0':
+ resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
@@ -3764,17 +3770,33 @@ packages:
resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
- '@eslint-community/regexpp@4.9.1':
- resolution: {integrity: sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==}
- engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+ '@eslint/config-array@0.21.0':
+ resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@eslint/eslintrc@2.1.4':
- resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==}
- engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ '@eslint/config-helpers@0.4.0':
+ resolution: {integrity: sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@eslint/js@8.57.0':
- resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==}
- engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ '@eslint/core@0.16.0':
+ resolution: {integrity: sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/eslintrc@3.3.1':
+ resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/js@9.37.0':
+ resolution: {integrity: sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/object-schema@2.1.6':
+ resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/plugin-kit@0.4.0':
+ resolution: {integrity: sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@exodus/schemasafe@1.3.0':
resolution: {integrity: sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==}
@@ -4212,10 +4234,13 @@ packages:
peerDependencies:
react-hook-form: ^7.0.0
- '@humanwhocodes/config-array@0.11.14':
- resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==}
- engines: {node: '>=10.10.0'}
- deprecated: Use @eslint/config-array instead
+ '@humanfs/core@0.19.1':
+ resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanfs/node@0.16.7':
+ resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==}
+ engines: {node: '>=18.18.0'}
'@humanwhocodes/module-importer@1.0.1':
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
@@ -4225,9 +4250,9 @@ packages:
resolution: {integrity: sha512-RE815I4arJFtt+FVeU1Tgp9/Xvecacji8w/V6XtXsWWH/wz/eNkNbhb+ny/+PlVZjV0rxQpRSQKNKE3lcktHEA==}
engines: {node: '>=10.10.0'}
- '@humanwhocodes/object-schema@2.0.2':
- resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==}
- deprecated: Use @eslint/object-schema instead
+ '@humanwhocodes/retry@0.4.3':
+ resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
+ engines: {node: '>=18.18'}
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
@@ -4738,8 +4763,8 @@ packages:
'@next/env@15.5.2':
resolution: {integrity: sha512-Qe06ew4zt12LeO6N7j8/nULSOe3fMXE4dM6xgpBQNvdzyK1sv5y4oAP3bq4LamrvGCZtmRYnW8URFCeX5nFgGg==}
- '@next/eslint-plugin-next@15.3.1':
- resolution: {integrity: sha512-oEs4dsfM6iyER3jTzMm4kDSbrQJq8wZw5fmT6fg2V3SMo+kgG+cShzLfEV20senZzv8VF+puNLheiGPlBGsv2A==}
+ '@next/eslint-plugin-next@15.5.4':
+ resolution: {integrity: sha512-SR1vhXNNg16T4zffhJ4TS7Xn7eq4NfKfcOsRwea7RIAHrjRpI9ALYbamqIJqkAhowLlERffiwk0FMvTLNdnVtw==}
'@next/mdx@15.3.1':
resolution: {integrity: sha512-dnpuJRfqqCPFfLDy2hIej41JAl424zk1JOgRd7jjWu2aTeX6oi0gXdcnMAK4lhf7Xl9zSkL2stzDc1YtlB1xyg==}
@@ -10303,10 +10328,6 @@ packages:
resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==}
engines: {node: '>= 0.4'}
- array.prototype.flatmap@1.3.2:
- resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==}
- engines: {node: '>= 0.4'}
-
array.prototype.flatmap@1.3.3:
resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==}
engines: {node: '>= 0.4'}
@@ -12212,8 +12233,8 @@ packages:
resolution: {integrity: sha512-FVVO4DNEPub7/VWGERJytT+tllOP2SRt8rdlcneHNWXSECMU7Aj1nXwGfhhsL3h1aZGJbYokIe27wyN7CJpAfA==}
engines: {node: '>= 10'}
- eslint-config-next@15.3.1:
- resolution: {integrity: sha512-GnmyVd9TE/Ihe3RrvcafFhXErErtr2jS0JDeCSp3vWvy86AXwHsRBt0E3MqP/m8ACS1ivcsi5uaqjbhsG18qKw==}
+ eslint-config-next@15.5.4:
+ resolution: {integrity: sha512-BzgVVuT3kfJes8i2GHenC1SRJ+W3BTML11lAOYFOOPzrk2xp66jBOAGEFRw+3LkYCln5UzvFsLhojrshb5Zfaw==}
peerDependencies:
eslint: ^7.23.0 || ^8.0.0 || ^9.0.0
typescript: '>=3.3.1'
@@ -12221,16 +12242,17 @@ packages:
typescript:
optional: true
- eslint-config-prettier@9.1.0:
- resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==}
+ eslint-config-prettier@10.1.8:
+ resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==}
hasBin: true
peerDependencies:
eslint: '>=7.0.0'
- eslint-config-turbo@2.0.4:
- resolution: {integrity: sha512-zGvU+bxoNWVvSl0prGItrnH9FgeNzKEAjRmv8ruqql1psI37T8IoLF/XeOzT3CzzYzJxuI3wW1yb2agDFYQdHQ==}
+ eslint-config-turbo@2.5.8:
+ resolution: {integrity: sha512-wzxmN7dJNFGDwOvR/4j8U2iaIH/ruYez8qg/sCKrezJ3+ljbFMvJLmgKKt/1mDuyU9wj5aZqO6VijP3QH169FA==}
peerDependencies:
eslint: '>6.6.0'
+ turbo: '>2.0.0'
eslint-import-resolver-node@0.3.9:
resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
@@ -12263,27 +12285,6 @@ packages:
eslint-import-resolver-webpack:
optional: true
- eslint-module-utils@2.8.1:
- resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==}
- engines: {node: '>=4'}
- peerDependencies:
- '@typescript-eslint/parser': '*'
- eslint: '*'
- eslint-import-resolver-node: '*'
- eslint-import-resolver-typescript: '*'
- eslint-import-resolver-webpack: '*'
- peerDependenciesMeta:
- '@typescript-eslint/parser':
- optional: true
- eslint:
- optional: true
- eslint-import-resolver-node:
- optional: true
- eslint-import-resolver-typescript:
- optional: true
- eslint-import-resolver-webpack:
- optional: true
-
eslint-plugin-barrel-files@2.0.7:
resolution: {integrity: sha512-t0q23FIpvHDTtnORW+bDJziGsal5uh9RJTJ1fyH8drd4lICOoXhJ5pLMUZ5C0VQei6dNmwTzzoTRgMkO9JgHEQ==}
peerDependencies:
@@ -12318,18 +12319,19 @@ packages:
peerDependencies:
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
- eslint-plugin-turbo@2.0.4:
- resolution: {integrity: sha512-Ozn//vTXJeqIEvEkThM2vuuldMckPqAne7vg/S3GxF+BBY516cjdp7+dYpCU5Q0083hVm638c8542ubccNE+8w==}
+ eslint-plugin-turbo@2.5.8:
+ resolution: {integrity: sha512-bVjx4vTH0oTKIyQ7EGFAXnuhZMrKIfu17qlex/dps7eScPnGQLJ3r1/nFq80l8xA+8oYjsSirSQ2tXOKbz3kEw==}
peerDependencies:
eslint: '>6.6.0'
+ turbo: '>2.0.0'
eslint-scope@5.1.1:
resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
engines: {node: '>=8.0.0'}
- eslint-scope@7.2.2:
- resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
- engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ eslint-scope@8.4.0:
+ resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
eslint-visitor-keys@3.4.3:
resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
@@ -12339,19 +12341,23 @@ packages:
resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- eslint@8.57.0:
- resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==}
- engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
- deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
+ eslint@9.37.0:
+ resolution: {integrity: sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
hasBin: true
+ peerDependencies:
+ jiti: '*'
+ peerDependenciesMeta:
+ jiti:
+ optional: true
esniff@2.0.1:
resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==}
engines: {node: '>=0.10'}
- espree@9.6.1:
- resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
- engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ espree@10.4.0:
+ resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
esprima@4.0.1:
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
@@ -12670,9 +12676,9 @@ packages:
resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==}
engines: {node: '>=18'}
- file-entry-cache@6.0.1:
- resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
- engines: {node: ^10.12.0 || >=12.0.0}
+ file-entry-cache@8.0.0:
+ resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
+ engines: {node: '>=16.0.0'}
file-saver@2.0.5:
resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==}
@@ -12741,9 +12747,9 @@ packages:
react-dom:
optional: true
- flat-cache@3.1.0:
- resolution: {integrity: sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==}
- engines: {node: '>=12.0.0'}
+ flat-cache@4.0.1:
+ resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
+ engines: {node: '>=16'}
flatted@3.3.2:
resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==}
@@ -13050,9 +13056,9 @@ packages:
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
engines: {node: '>=4'}
- globals@13.24.0:
- resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
- engines: {node: '>=8'}
+ globals@14.0.0:
+ resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
+ engines: {node: '>=18'}
globals@15.15.0:
resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==}
@@ -13812,10 +13818,6 @@ packages:
resolution: {integrity: sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==}
engines: {node: '>=12'}
- is-path-inside@3.0.3:
- resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
- engines: {node: '>=8'}
-
is-path-inside@4.0.0:
resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==}
engines: {node: '>=12'}
@@ -14225,8 +14227,8 @@ packages:
resolution: {integrity: sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==}
hasBin: true
- keyv@4.5.3:
- resolution: {integrity: sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==}
+ keyv@4.5.4:
+ resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
khroma@2.1.0:
resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==}
@@ -15718,10 +15720,6 @@ packages:
resolution: {integrity: sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==}
engines: {node: '>=0.10.0'}
- object.values@1.2.0:
- resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==}
- engines: {node: '>= 0.4'}
-
object.values@1.2.1:
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
engines: {node: '>= 0.4'}
@@ -18457,9 +18455,6 @@ packages:
text-decoder@1.2.3:
resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==}
- text-table@0.2.0:
- resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
-
thenify-all@1.6.0:
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
engines: {node: '>=0.8'}
@@ -18804,10 +18799,6 @@ packages:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
- type-fest@0.20.2:
- resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
- engines: {node: '>=10'}
-
type-fest@0.21.3:
resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==}
engines: {node: '>=10'}
@@ -22283,26 +22274,40 @@ snapshots:
'@esbuild/win32-x64@0.25.2':
optional: true
- '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0(supports-color@8.1.1))':
+ '@eslint-community/eslint-utils@4.7.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))':
dependencies:
- eslint: 8.57.0(supports-color@8.1.1)
+ eslint: 9.37.0(jiti@2.5.1)(supports-color@8.1.1)
eslint-visitor-keys: 3.4.3
- '@eslint-community/eslint-utils@4.7.0(eslint@8.57.0(supports-color@8.1.1))':
+ '@eslint-community/eslint-utils@4.9.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))':
dependencies:
- eslint: 8.57.0(supports-color@8.1.1)
+ eslint: 9.37.0(jiti@2.5.1)(supports-color@8.1.1)
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.1': {}
- '@eslint-community/regexpp@4.9.1': {}
+ '@eslint/config-array@0.21.0(supports-color@8.1.1)':
+ dependencies:
+ '@eslint/object-schema': 2.1.6
+ debug: 4.4.3(supports-color@8.1.1)
+ minimatch: 3.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/config-helpers@0.4.0':
+ dependencies:
+ '@eslint/core': 0.16.0
- '@eslint/eslintrc@2.1.4(supports-color@8.1.1)':
+ '@eslint/core@0.16.0':
+ dependencies:
+ '@types/json-schema': 7.0.15
+
+ '@eslint/eslintrc@3.3.1(supports-color@8.1.1)':
dependencies:
ajv: 6.12.6
debug: 4.4.3(supports-color@8.1.1)
- espree: 9.6.1
- globals: 13.24.0
+ espree: 10.4.0
+ globals: 14.0.0
ignore: 5.3.2
import-fresh: 3.3.0
js-yaml: 4.1.0
@@ -22311,7 +22316,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@eslint/js@8.57.0': {}
+ '@eslint/js@9.37.0': {}
+
+ '@eslint/object-schema@2.1.6': {}
+
+ '@eslint/plugin-kit@0.4.0':
+ dependencies:
+ '@eslint/core': 0.16.0
+ levn: 0.4.1
'@exodus/schemasafe@1.3.0': {}
@@ -23079,19 +23091,18 @@ snapshots:
dependencies:
react-hook-form: 7.47.0(react@18.3.1)
- '@humanwhocodes/config-array@0.11.14(supports-color@8.1.1)':
+ '@humanfs/core@0.19.1': {}
+
+ '@humanfs/node@0.16.7':
dependencies:
- '@humanwhocodes/object-schema': 2.0.2
- debug: 4.4.3(supports-color@8.1.1)
- minimatch: 3.1.2
- transitivePeerDependencies:
- - supports-color
+ '@humanfs/core': 0.19.1
+ '@humanwhocodes/retry': 0.4.3
'@humanwhocodes/module-importer@1.0.1': {}
'@humanwhocodes/momoa@2.0.4': {}
- '@humanwhocodes/object-schema@2.0.2': {}
+ '@humanwhocodes/retry@0.4.3': {}
'@iconify/types@2.0.0': {}
@@ -23766,7 +23777,7 @@ snapshots:
'@next/env@15.5.2': {}
- '@next/eslint-plugin-next@15.3.1':
+ '@next/eslint-plugin-next@15.5.4':
dependencies:
fast-glob: 3.3.1
@@ -24055,7 +24066,7 @@ snapshots:
transitivePeerDependencies:
- magicast
- '@nuxt/vite-builder@4.1.2(@types/node@22.13.14)(eslint@8.57.0(supports-color@8.1.1))(magicast@0.3.5)(rollup@4.50.2)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vue@3.5.21(typescript@5.9.2))(yaml@2.8.1)':
+ '@nuxt/vite-builder@4.1.2(@types/node@22.13.14)(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(magicast@0.3.5)(rollup@4.50.2)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vue@3.5.21(typescript@5.9.2))(yaml@2.8.1)':
dependencies:
'@nuxt/kit': 4.1.2(magicast@0.3.5)
'@rollup/plugin-replace': 6.0.2(rollup@4.50.2)
@@ -24084,7 +24095,7 @@ snapshots:
unenv: 2.0.0-rc.21
vite: 7.1.5(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1)
vite-node: 3.2.4(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1)
- vite-plugin-checker: 0.10.3(eslint@8.57.0(supports-color@8.1.1))(typescript@5.9.2)(vite@7.1.5(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))
+ vite-plugin-checker: 0.10.3(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(typescript@5.9.2)(vite@7.1.5(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))
vue: 3.5.21(typescript@5.9.2)
vue-bundle-renderer: 2.1.2
transitivePeerDependencies:
@@ -29939,15 +29950,15 @@ snapshots:
'@types/zxcvbn@4.4.2': {}
- '@typescript-eslint/eslint-plugin@8.34.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2))(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)':
+ '@typescript-eslint/eslint-plugin@8.34.1(@typescript-eslint/parser@7.2.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2))(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)':
dependencies:
'@eslint-community/regexpp': 4.12.1
- '@typescript-eslint/parser': 7.2.0(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)
+ '@typescript-eslint/parser': 7.2.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)
'@typescript-eslint/scope-manager': 8.34.1
- '@typescript-eslint/type-utils': 8.34.1(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)
- '@typescript-eslint/utils': 8.34.1(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)
+ '@typescript-eslint/type-utils': 8.34.1(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)
+ '@typescript-eslint/utils': 8.34.1(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)
'@typescript-eslint/visitor-keys': 8.34.1
- eslint: 8.57.0(supports-color@8.1.1)
+ eslint: 9.37.0(jiti@2.5.1)(supports-color@8.1.1)
graphemer: 1.4.0
ignore: 7.0.5
natural-compare: 1.4.0
@@ -29956,14 +29967,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@7.2.0(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)':
+ '@typescript-eslint/parser@7.2.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)':
dependencies:
'@typescript-eslint/scope-manager': 7.2.0
'@typescript-eslint/types': 7.2.0
'@typescript-eslint/typescript-estree': 7.2.0(supports-color@8.1.1)(typescript@5.9.2)
'@typescript-eslint/visitor-keys': 7.2.0
debug: 4.4.3(supports-color@8.1.1)
- eslint: 8.57.0(supports-color@8.1.1)
+ eslint: 9.37.0(jiti@2.5.1)(supports-color@8.1.1)
optionalDependencies:
typescript: 5.9.2
transitivePeerDependencies:
@@ -29992,12 +30003,12 @@ snapshots:
dependencies:
typescript: 5.9.2
- '@typescript-eslint/type-utils@8.34.1(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)':
+ '@typescript-eslint/type-utils@8.34.1(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)':
dependencies:
'@typescript-eslint/typescript-estree': 8.34.1(supports-color@8.1.1)(typescript@5.9.2)
- '@typescript-eslint/utils': 8.34.1(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)
+ '@typescript-eslint/utils': 8.34.1(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)
debug: 4.4.3(supports-color@8.1.1)
- eslint: 8.57.0(supports-color@8.1.1)
+ eslint: 9.37.0(jiti@2.5.1)(supports-color@8.1.1)
ts-api-utils: 2.1.0(typescript@5.9.2)
typescript: 5.9.2
transitivePeerDependencies:
@@ -30038,13 +30049,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/utils@8.34.1(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)':
+ '@typescript-eslint/utils@8.34.1(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)':
dependencies:
- '@eslint-community/eslint-utils': 4.7.0(eslint@8.57.0(supports-color@8.1.1))
+ '@eslint-community/eslint-utils': 4.7.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))
'@typescript-eslint/scope-manager': 8.34.1
'@typescript-eslint/types': 8.34.1
'@typescript-eslint/typescript-estree': 8.34.1(supports-color@8.1.1)(typescript@5.9.2)
- eslint: 8.57.0(supports-color@8.1.1)
+ eslint: 9.37.0(jiti@2.5.1)(supports-color@8.1.1)
typescript: 5.9.2
transitivePeerDependencies:
- supports-color
@@ -30204,24 +30215,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@vitest/coverage-v8@3.0.9(supports-color@8.1.1)(vitest@3.0.9(@types/node@22.13.14)(jiti@2.5.1)(jsdom@20.0.3(supports-color@8.1.1))(msw@2.11.3(@types/node@22.13.14)(typescript@5.9.2))(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))':
- dependencies:
- '@ampproject/remapping': 2.3.0
- '@bcoe/v8-coverage': 1.0.2
- debug: 4.4.3(supports-color@8.1.1)
- istanbul-lib-coverage: 3.2.2
- istanbul-lib-report: 3.0.1
- istanbul-lib-source-maps: 5.0.6(supports-color@8.1.1)
- istanbul-reports: 3.1.7
- magic-string: 0.30.19
- magicast: 0.3.5
- std-env: 3.9.0
- test-exclude: 7.0.1
- tinyrainbow: 2.0.0
- vitest: 3.0.9(@types/node@22.13.14)(@vitest/ui@3.0.4)(jiti@2.5.1)(jsdom@20.0.3(supports-color@8.1.1))(msw@2.11.3(@types/node@22.13.14)(typescript@5.9.2))(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1)
- transitivePeerDependencies:
- - supports-color
-
'@vitest/coverage-v8@3.0.9(supports-color@8.1.1)(vitest@3.0.9)':
dependencies:
'@ampproject/remapping': 2.3.0
@@ -30236,7 +30229,7 @@ snapshots:
std-env: 3.9.0
test-exclude: 7.0.1
tinyrainbow: 2.0.0
- vitest: 3.0.9(@types/node@22.13.14)(@vitest/ui@3.0.4)(jiti@2.5.1)(jsdom@20.0.3(supports-color@8.1.1))(msw@2.4.11(typescript@5.9.2))(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.1)
+ vitest: 3.0.9(@types/node@22.13.14)(@vitest/ui@3.0.4)(jiti@2.5.1)(jsdom@20.0.3(supports-color@8.1.1))(msw@2.11.3(@types/node@22.13.14)(typescript@5.9.2))(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1)
transitivePeerDependencies:
- supports-color
@@ -30315,7 +30308,7 @@ snapshots:
sirv: 3.0.0
tinyglobby: 0.2.14
tinyrainbow: 2.0.0
- vitest: 3.0.9(@types/node@22.13.14)(@vitest/ui@3.0.4)(jiti@2.5.1)(jsdom@20.0.3(supports-color@8.1.1))(msw@2.4.11(typescript@5.9.2))(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.1)
+ vitest: 3.0.9(@types/node@22.13.14)(@vitest/ui@3.0.4)(jiti@2.5.1)(jsdom@20.0.3(supports-color@8.1.1))(msw@2.11.3(@types/node@22.13.14)(typescript@5.9.2))(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1)
'@vitest/utils@3.0.4':
dependencies:
@@ -30874,13 +30867,6 @@ snapshots:
es-abstract: 1.24.0
es-shim-unscopables: 1.0.2
- array.prototype.flatmap@1.3.2:
- dependencies:
- call-bind: 1.0.8
- define-properties: 1.2.1
- es-abstract: 1.24.0
- es-shim-unscopables: 1.0.2
-
array.prototype.flatmap@1.3.3:
dependencies:
call-bind: 1.0.8
@@ -32815,33 +32801,34 @@ snapshots:
eslint-barrel-file-utils-win32-ia32-msvc: 0.0.10
eslint-barrel-file-utils-win32-x64-msvc: 0.0.10
- eslint-config-next@15.3.1(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2):
+ eslint-config-next@15.5.4(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2):
dependencies:
- '@next/eslint-plugin-next': 15.3.1
+ '@next/eslint-plugin-next': 15.5.4
'@rushstack/eslint-patch': 1.10.3
- '@typescript-eslint/eslint-plugin': 8.34.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2))(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)
- '@typescript-eslint/parser': 7.2.0(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)
- eslint: 8.57.0(supports-color@8.1.1)
+ '@typescript-eslint/eslint-plugin': 8.34.1(@typescript-eslint/parser@7.2.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2))(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)
+ '@typescript-eslint/parser': 7.2.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)
+ eslint: 9.37.0(jiti@2.5.1)(supports-color@8.1.1)
eslint-import-resolver-node: 0.3.9(supports-color@8.1.1)
- eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9(supports-color@8.1.1))(eslint-plugin-import@2.31.0)(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)
- eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)
- eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.0(supports-color@8.1.1))
- eslint-plugin-react: 7.37.5(eslint@8.57.0(supports-color@8.1.1))
- eslint-plugin-react-hooks: 5.2.0(eslint@8.57.0(supports-color@8.1.1))
+ eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9(supports-color@8.1.1))(eslint-plugin-import@2.31.0)(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.6.1)(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)
+ eslint-plugin-jsx-a11y: 6.10.2(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))
+ eslint-plugin-react: 7.37.5(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))
+ eslint-plugin-react-hooks: 5.2.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))
optionalDependencies:
typescript: 5.9.2
transitivePeerDependencies:
- eslint-import-resolver-webpack
- supports-color
- eslint-config-prettier@9.1.0(eslint@8.57.0(supports-color@8.1.1)):
+ eslint-config-prettier@10.1.8(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1)):
dependencies:
- eslint: 8.57.0(supports-color@8.1.1)
+ eslint: 9.37.0(jiti@2.5.1)(supports-color@8.1.1)
- eslint-config-turbo@2.0.4(eslint@8.57.0(supports-color@8.1.1)):
+ eslint-config-turbo@2.5.8(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(turbo@2.3.3):
dependencies:
- eslint: 8.57.0(supports-color@8.1.1)
- eslint-plugin-turbo: 2.0.4(eslint@8.57.0(supports-color@8.1.1))
+ eslint: 9.37.0(jiti@2.5.1)(supports-color@8.1.1)
+ eslint-plugin-turbo: 2.5.8(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(turbo@2.3.3)
+ turbo: 2.3.3
eslint-import-resolver-node@0.3.9(supports-color@8.1.1):
dependencies:
@@ -32851,13 +32838,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9(supports-color@8.1.1))(eslint-plugin-import@2.31.0)(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1):
+ eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9(supports-color@8.1.1))(eslint-plugin-import@2.31.0)(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1):
dependencies:
debug: 4.4.3(supports-color@8.1.1)
enhanced-resolve: 5.18.1
- eslint: 8.57.0(supports-color@8.1.1)
- eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9(supports-color@8.1.1))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)
- eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)
+ eslint: 9.37.0(jiti@2.5.1)(supports-color@8.1.1)
+ eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.2.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9(supports-color@8.1.1))(eslint-import-resolver-typescript@3.6.1)(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.6.1)(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)
fast-glob: 3.3.3
get-tsconfig: 4.10.0
is-core-module: 2.16.1
@@ -32868,74 +32855,63 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
- eslint-module-utils@2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9(supports-color@8.1.1))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1):
+ eslint-module-utils@2.12.0(@typescript-eslint/parser@7.2.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9(supports-color@8.1.1))(eslint-import-resolver-typescript@3.6.1)(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1):
dependencies:
debug: 3.2.7(supports-color@8.1.1)
optionalDependencies:
- '@typescript-eslint/parser': 7.2.0(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)
- eslint: 8.57.0(supports-color@8.1.1)
+ '@typescript-eslint/parser': 7.2.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)
+ eslint: 9.37.0(jiti@2.5.1)(supports-color@8.1.1)
eslint-import-resolver-node: 0.3.9(supports-color@8.1.1)
- eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9(supports-color@8.1.1))(eslint-plugin-import@2.31.0)(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)
+ eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9(supports-color@8.1.1))(eslint-plugin-import@2.31.0)(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
- eslint-module-utils@2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9(supports-color@8.1.1))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1):
+ eslint-plugin-barrel-files@2.0.7(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1)):
dependencies:
- debug: 3.2.7(supports-color@8.1.1)
- optionalDependencies:
- '@typescript-eslint/parser': 7.2.0(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)
- eslint: 8.57.0(supports-color@8.1.1)
- eslint-import-resolver-node: 0.3.9(supports-color@8.1.1)
- eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9(supports-color@8.1.1))(eslint-plugin-import@2.31.0)(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)
- transitivePeerDependencies:
- - supports-color
-
- eslint-plugin-barrel-files@2.0.7(eslint@8.57.0(supports-color@8.1.1)):
- dependencies:
- eslint: 8.57.0(supports-color@8.1.1)
+ eslint: 9.37.0(jiti@2.5.1)(supports-color@8.1.1)
eslint-barrel-file-utils: 0.0.10
requireindex: 1.2.0
- eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1):
+ eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.6.1)(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.8
array.prototype.findlastindex: 1.2.5
array.prototype.flat: 1.3.2
- array.prototype.flatmap: 1.3.2
+ array.prototype.flatmap: 1.3.3
debug: 3.2.7(supports-color@8.1.1)
doctrine: 2.1.0
- eslint: 8.57.0(supports-color@8.1.1)
+ eslint: 9.37.0(jiti@2.5.1)(supports-color@8.1.1)
eslint-import-resolver-node: 0.3.9(supports-color@8.1.1)
- eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9(supports-color@8.1.1))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)
+ eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.2.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9(supports-color@8.1.1))(eslint-import-resolver-typescript@3.6.1)(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)
hasown: 2.0.2
is-core-module: 2.16.1
is-glob: 4.0.3
minimatch: 3.1.2
object.fromentries: 2.0.8
object.groupby: 1.0.3
- object.values: 1.2.0
+ object.values: 1.2.1
semver: 6.3.1
string.prototype.trimend: 1.0.9
tsconfig-paths: 3.15.0
optionalDependencies:
- '@typescript-eslint/parser': 7.2.0(eslint@8.57.0(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)
+ '@typescript-eslint/parser': 7.2.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(supports-color@8.1.1)(typescript@5.9.2)
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
- eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.0(supports-color@8.1.1)):
+ eslint-plugin-jsx-a11y@6.10.2(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1)):
dependencies:
aria-query: 5.3.2
array-includes: 3.1.8
- array.prototype.flatmap: 1.3.2
+ array.prototype.flatmap: 1.3.3
ast-types-flow: 0.0.8
axe-core: 4.10.3
axobject-query: 4.1.0
damerau-levenshtein: 1.0.8
emoji-regex: 9.2.2
- eslint: 8.57.0(supports-color@8.1.1)
+ eslint: 9.37.0(jiti@2.5.1)(supports-color@8.1.1)
hasown: 2.0.2
jsx-ast-utils: 3.3.5
language-tags: 1.0.9
@@ -32944,11 +32920,11 @@ snapshots:
safe-regex-test: 1.1.0
string.prototype.includes: 2.0.1
- eslint-plugin-react-hooks@5.2.0(eslint@8.57.0(supports-color@8.1.1)):
+ eslint-plugin-react-hooks@5.2.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1)):
dependencies:
- eslint: 8.57.0(supports-color@8.1.1)
+ eslint: 9.37.0(jiti@2.5.1)(supports-color@8.1.1)
- eslint-plugin-react@7.37.5(eslint@8.57.0(supports-color@8.1.1)):
+ eslint-plugin-react@7.37.5(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1)):
dependencies:
array-includes: 3.1.8
array.prototype.findlast: 1.2.5
@@ -32956,7 +32932,7 @@ snapshots:
array.prototype.tosorted: 1.1.4
doctrine: 2.1.0
es-iterator-helpers: 1.2.1
- eslint: 8.57.0(supports-color@8.1.1)
+ eslint: 9.37.0(jiti@2.5.1)(supports-color@8.1.1)
estraverse: 5.3.0
hasown: 2.0.2
jsx-ast-utils: 3.3.5
@@ -32970,17 +32946,18 @@ snapshots:
string.prototype.matchall: 4.0.12
string.prototype.repeat: 1.0.0
- eslint-plugin-turbo@2.0.4(eslint@8.57.0(supports-color@8.1.1)):
+ eslint-plugin-turbo@2.5.8(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(turbo@2.3.3):
dependencies:
dotenv: 16.0.3
- eslint: 8.57.0(supports-color@8.1.1)
+ eslint: 9.37.0(jiti@2.5.1)(supports-color@8.1.1)
+ turbo: 2.3.3
eslint-scope@5.1.1:
dependencies:
esrecurse: 4.3.0
estraverse: 4.3.0
- eslint-scope@7.2.2:
+ eslint-scope@8.4.0:
dependencies:
esrecurse: 4.3.0
estraverse: 5.3.0
@@ -32989,46 +32966,45 @@ snapshots:
eslint-visitor-keys@4.2.1: {}
- eslint@8.57.0(supports-color@8.1.1):
+ eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1):
dependencies:
- '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0(supports-color@8.1.1))
- '@eslint-community/regexpp': 4.9.1
- '@eslint/eslintrc': 2.1.4(supports-color@8.1.1)
- '@eslint/js': 8.57.0
- '@humanwhocodes/config-array': 0.11.14(supports-color@8.1.1)
+ '@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))
+ '@eslint-community/regexpp': 4.12.1
+ '@eslint/config-array': 0.21.0(supports-color@8.1.1)
+ '@eslint/config-helpers': 0.4.0
+ '@eslint/core': 0.16.0
+ '@eslint/eslintrc': 3.3.1(supports-color@8.1.1)
+ '@eslint/js': 9.37.0
+ '@eslint/plugin-kit': 0.4.0
+ '@humanfs/node': 0.16.7
'@humanwhocodes/module-importer': 1.0.1
- '@nodelib/fs.walk': 1.2.8
- '@ungap/structured-clone': 1.2.0
+ '@humanwhocodes/retry': 0.4.3
+ '@types/estree': 1.0.8
+ '@types/json-schema': 7.0.15
ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.6
- debug: 4.3.7(supports-color@8.1.1)
- doctrine: 3.0.0
+ debug: 4.4.3(supports-color@8.1.1)
escape-string-regexp: 4.0.0
- eslint-scope: 7.2.2
- eslint-visitor-keys: 3.4.3
- espree: 9.6.1
+ eslint-scope: 8.4.0
+ eslint-visitor-keys: 4.2.1
+ espree: 10.4.0
esquery: 1.5.0
esutils: 2.0.3
fast-deep-equal: 3.1.3
- file-entry-cache: 6.0.1
+ file-entry-cache: 8.0.0
find-up: 5.0.0
glob-parent: 6.0.2
- globals: 13.24.0
- graphemer: 1.4.0
- ignore: 5.2.4
+ ignore: 5.3.2
imurmurhash: 0.1.4
is-glob: 4.0.3
- is-path-inside: 3.0.3
- js-yaml: 4.1.0
json-stable-stringify-without-jsonify: 1.0.1
- levn: 0.4.1
lodash.merge: 4.6.2
minimatch: 3.1.2
natural-compare: 1.4.0
optionator: 0.9.3
- strip-ansi: 6.0.1
- text-table: 0.2.0
+ optionalDependencies:
+ jiti: 2.5.1
transitivePeerDependencies:
- supports-color
@@ -33039,11 +33015,11 @@ snapshots:
event-emitter: 0.3.5
type: 2.7.3
- espree@9.6.1:
+ espree@10.4.0:
dependencies:
acorn: 8.15.0
acorn-jsx: 5.3.2(acorn@8.15.0)
- eslint-visitor-keys: 3.4.3
+ eslint-visitor-keys: 4.2.1
esprima@4.0.1: {}
@@ -33432,9 +33408,9 @@ snapshots:
dependencies:
is-unicode-supported: 2.1.0
- file-entry-cache@6.0.1:
+ file-entry-cache@8.0.0:
dependencies:
- flat-cache: 3.1.0
+ flat-cache: 4.0.1
file-saver@2.0.5: {}
@@ -33512,11 +33488,10 @@ snapshots:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
- flat-cache@3.1.0:
+ flat-cache@4.0.1:
dependencies:
flatted: 3.3.2
- keyv: 4.5.3
- rimraf: 3.0.2
+ keyv: 4.5.4
flatted@3.3.2: {}
@@ -33858,9 +33833,7 @@ snapshots:
globals@11.12.0: {}
- globals@13.24.0:
- dependencies:
- type-fest: 0.20.2
+ globals@14.0.0: {}
globals@15.15.0: {}
@@ -34795,8 +34768,6 @@ snapshots:
is-obj@3.0.0: {}
- is-path-inside@3.0.3: {}
-
is-path-inside@4.0.0: {}
is-plain-obj@4.1.0: {}
@@ -35186,7 +35157,7 @@ snapshots:
array-includes: 3.1.8
array.prototype.flat: 1.3.2
object.assign: 4.1.7
- object.values: 1.2.0
+ object.values: 1.2.1
katex@0.16.21:
dependencies:
@@ -35196,7 +35167,7 @@ snapshots:
dependencies:
commander: 8.3.0
- keyv@4.5.3:
+ keyv@4.5.4:
dependencies:
json-buffer: 3.0.1
@@ -37448,7 +37419,7 @@ snapshots:
next: 15.5.2(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)
react-router: 7.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- nuxt@4.1.2(@electric-sql/pglite@0.2.15)(@parcel/watcher@2.5.1)(@types/node@22.13.14)(@vue/compiler-sfc@3.5.21)(aws4fetch@1.0.20)(db0@0.3.2(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(eslint@8.57.0(supports-color@8.1.1))(ioredis@5.7.0(supports-color@8.1.1))(magicast@0.3.5)(rollup@4.50.2)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@6.3.6(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(yaml@2.8.1):
+ nuxt@4.1.2(@electric-sql/pglite@0.2.15)(@parcel/watcher@2.5.1)(@types/node@22.13.14)(@vue/compiler-sfc@3.5.21)(aws4fetch@1.0.20)(db0@0.3.2(@electric-sql/pglite@0.2.15)(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3)))(drizzle-orm@0.44.2(@electric-sql/pglite@0.2.15)(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(pg@8.16.3))(encoding@0.1.13)(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(ioredis@5.7.0(supports-color@8.1.1))(magicast@0.3.5)(rollup@4.50.2)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vite@6.3.6(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))(yaml@2.8.1):
dependencies:
'@nuxt/cli': 3.28.0(magicast@0.3.5)
'@nuxt/devalue': 2.0.2
@@ -37456,7 +37427,7 @@ snapshots:
'@nuxt/kit': 4.1.2(magicast@0.3.5)
'@nuxt/schema': 4.1.2
'@nuxt/telemetry': 2.6.6(magicast@0.3.5)
- '@nuxt/vite-builder': 4.1.2(@types/node@22.13.14)(eslint@8.57.0(supports-color@8.1.1))(magicast@0.3.5)(rollup@4.50.2)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vue@3.5.21(typescript@5.9.2))(yaml@2.8.1)
+ '@nuxt/vite-builder': 4.1.2(@types/node@22.13.14)(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(magicast@0.3.5)(rollup@4.50.2)(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(typescript@5.9.2)(vue@3.5.21(typescript@5.9.2))(yaml@2.8.1)
'@unhead/vue': 2.0.14(vue@3.5.21(typescript@5.9.2))
'@vue/shared': 3.5.21
c12: 3.3.0(magicast@0.3.5)
@@ -37658,12 +37629,6 @@ snapshots:
dependencies:
isobject: 3.0.1
- object.values@1.2.0:
- dependencies:
- call-bind: 1.0.8
- define-properties: 1.2.1
- es-object-atoms: 1.1.1
-
object.values@1.2.1:
dependencies:
call-bind: 1.0.8
@@ -41064,8 +41029,6 @@ snapshots:
dependencies:
b4a: 1.6.7
- text-table@0.2.0: {}
-
thenify-all@1.6.0:
dependencies:
thenify: 3.3.1
@@ -41379,8 +41342,6 @@ snapshots:
dependencies:
prelude-ls: 1.2.1
- type-fest@0.20.2: {}
-
type-fest@0.21.3: {}
type-fest@0.7.1: {}
@@ -42223,7 +42184,7 @@ snapshots:
- tsx
- yaml
- vite-plugin-checker@0.10.3(eslint@8.57.0(supports-color@8.1.1))(typescript@5.9.2)(vite@7.1.5(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1)):
+ vite-plugin-checker@0.10.3(eslint@9.37.0(jiti@2.5.1)(supports-color@8.1.1))(typescript@5.9.2)(vite@7.1.5(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1)):
dependencies:
'@babel/code-frame': 7.27.1
chokidar: 4.0.3
@@ -42236,7 +42197,7 @@ snapshots:
vite: 7.1.5(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1)
vscode-uri: 3.1.0
optionalDependencies:
- eslint: 8.57.0(supports-color@8.1.1)
+ eslint: 9.37.0(jiti@2.5.1)(supports-color@8.1.1)
typescript: 5.9.2
vite-plugin-inspect@11.3.3(@nuxt/kit@3.19.2(magicast@0.3.5))(supports-color@8.1.1)(vite@6.3.6(@types/node@22.13.14)(jiti@2.5.1)(sass@1.77.4)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1)):
diff --git a/turbo.json b/turbo.json
index 27e175cb07e73..c6d76db1c1f8f 100644
--- a/turbo.json
+++ b/turbo.json
@@ -67,8 +67,12 @@
"NEXT_PUBLIC_VERCEL_BRANCH_URL",
"NEXT_PUBLIC_GOOGLE_MAPS_KEY",
"NEXT_RUNTIME",
+ "NIMBUS_PROD_PROJECTS_URL",
+ "NIMBUS_PROD_PROJECTS_URL_WS",
"NODE_ENV",
"SUPABASE_URL",
+ "VERCEL",
+ "VERCEL_ENV",
// These envs are used in the packages
"NEXT_PUBLIC_STORAGE_KEY",
"NEXT_PUBLIC_AUTH_DEBUG_KEY",
@@ -123,6 +127,7 @@
"dependsOn": ["^build"],
"env": [
"ANALYZE",
+ "CI",
"CF_ACCESS_CLIENT_ID",
"CF_ACCESS_CLIENT_SECRET",
"CMS_API_KEY",
@@ -145,6 +150,9 @@
"NEXT_PUBLIC_SENTRY_DSN",
"NEXT_PUBLIC_SURVEY_SUPABASE_URL",
"NEXT_PUBLIC_SURVEY_SUPABASE_ANON_KEY",
+ "VERCEL",
+ "VERCEL_ENV",
+ "VERCEL_GIT_COMMIT_SHA",
// These envs are used in the packages
"NEXT_PUBLIC_STORAGE_KEY",
"NEXT_PUBLIC_AUTH_DEBUG_KEY",