Files
supabase/apps/studio/hooks/misc/useEdgeFunctionsDiff.ts
T
2025-06-17 13:05:14 +10:00

163 lines
5.5 KiB
TypeScript

import { useMemo } from 'react'
import { useQueries } from '@tanstack/react-query'
import {
getEdgeFunctionBody,
type EdgeFunctionBodyData,
} from 'data/edge-functions/edge-function-body-query'
import type { EdgeFunctionsData } from 'data/edge-functions/edge-functions-query'
import { basename } from 'path'
interface UseEdgeFunctionsDiffProps {
currentBranchFunctions?: EdgeFunctionsData
mainBranchFunctions?: EdgeFunctionsData
currentBranchRef?: string
mainBranchRef?: string
}
export interface EdgeFunctionsDiffResult {
addedSlugs: string[]
removedSlugs: string[]
modifiedSlugs: string[]
addedBodiesMap: Record<string, EdgeFunctionBodyData | undefined>
removedBodiesMap: Record<string, EdgeFunctionBodyData | undefined>
currentBodiesMap: Record<string, EdgeFunctionBodyData | undefined>
mainBodiesMap: Record<string, EdgeFunctionBodyData | undefined>
isLoading: boolean
hasChanges: boolean
}
// Small helper around path.basename but avoids importing the full Node path lib for the browser bundle
const fileKey = (fullPath: string) => basename(fullPath)
export const useEdgeFunctionsDiff = ({
currentBranchFunctions,
mainBranchFunctions,
currentBranchRef,
mainBranchRef,
}: UseEdgeFunctionsDiffProps): EdgeFunctionsDiffResult => {
// Identify added / removed / overlapping functions
const {
added = [],
removed = [],
overlap = [],
} = useMemo(() => {
if (!currentBranchFunctions || !mainBranchFunctions) {
return { added: [], removed: [], overlap: [] as typeof currentBranchFunctions }
}
const currentFuncs = currentBranchFunctions ?? []
const mainFuncs = mainBranchFunctions ?? []
const added = currentFuncs.filter((c) => !mainFuncs.find((m) => m.slug === c.slug))
const removed = mainFuncs.filter((m) => !currentFuncs.find((c) => c.slug === m.slug))
const overlap = currentFuncs.filter((c) => mainFuncs.find((m) => m.slug === c.slug))
return { added, removed, overlap }
}, [currentBranchFunctions, mainBranchFunctions])
const overlapSlugs = overlap.map((f) => f.slug)
const addedSlugs = added.map((f) => f.slug)
const removedSlugs = removed.map((f) => f.slug)
// Fetch function bodies ---------------------------------------------------
const currentBodiesQueries = useQueries({
queries: overlapSlugs.map((slug) => ({
queryKey: ['edge-function-body', currentBranchRef, slug],
queryFn: ({ signal }: { signal?: AbortSignal }) =>
getEdgeFunctionBody({ projectRef: currentBranchRef, slug }, signal),
enabled: !!currentBranchRef,
})),
})
const mainBodiesQueries = useQueries({
queries: overlapSlugs.map((slug) => ({
queryKey: ['edge-function-body', mainBranchRef, slug],
queryFn: ({ signal }: { signal?: AbortSignal }) =>
getEdgeFunctionBody({ projectRef: mainBranchRef, slug }, signal),
enabled: !!mainBranchRef,
})),
})
const addedBodiesQueries = useQueries({
queries: addedSlugs.map((slug) => ({
queryKey: ['edge-function-body', currentBranchRef, slug],
queryFn: ({ signal }: { signal?: AbortSignal }) =>
getEdgeFunctionBody({ projectRef: currentBranchRef, slug }, signal),
enabled: !!currentBranchRef,
})),
})
const removedBodiesQueries = useQueries({
queries: removedSlugs.map((slug) => ({
queryKey: ['edge-function-body', mainBranchRef, slug],
queryFn: ({ signal }: { signal?: AbortSignal }) =>
getEdgeFunctionBody({ projectRef: mainBranchRef, slug }, signal),
enabled: !!mainBranchRef,
})),
})
// Flatten loading flags ----------------------------------------------------
const isLoading = [
...currentBodiesQueries,
...mainBodiesQueries,
...addedBodiesQueries,
...removedBodiesQueries,
].some((q) => q.isLoading)
// Build lookup maps --------------------------------------------------------
const currentBodiesMap: Record<string, EdgeFunctionBodyData | undefined> = {}
currentBodiesQueries.forEach((q, idx) => {
if (q.data) currentBodiesMap[overlapSlugs[idx]] = q.data
})
const mainBodiesMap: Record<string, EdgeFunctionBodyData | undefined> = {}
mainBodiesQueries.forEach((q, idx) => {
if (q.data) mainBodiesMap[overlapSlugs[idx]] = q.data
})
const addedBodiesMap: Record<string, EdgeFunctionBodyData | undefined> = {}
addedBodiesQueries.forEach((q, idx) => {
if (q.data) addedBodiesMap[addedSlugs[idx]] = q.data
})
const removedBodiesMap: Record<string, EdgeFunctionBodyData | undefined> = {}
removedBodiesQueries.forEach((q, idx) => {
if (q.data) removedBodiesMap[removedSlugs[idx]] = q.data
})
// Determine modified slugs -------------------------------------------------
const modifiedSlugs = overlapSlugs.filter((slug) => {
const currentBody = currentBodiesMap[slug]
const mainBody = mainBodiesMap[slug]
if (!currentBody || !mainBody) return false
const keys = new Set([...currentBody, ...mainBody].map((f) => fileKey(f.name)))
for (const key of keys) {
const currentFile = currentBody.find((f) => fileKey(f.name) === key)
const mainFile = mainBody.find((f) => fileKey(f.name) === key)
if ((currentFile?.content || '') !== (mainFile?.content || '')) {
return true
}
}
return false
})
const hasChanges = addedSlugs.length > 0 || removedSlugs.length > 0 || modifiedSlugs.length > 0
return {
addedSlugs,
removedSlugs,
modifiedSlugs,
addedBodiesMap,
removedBodiesMap,
currentBodiesMap,
mainBodiesMap,
isLoading,
hasChanges,
}
}
export default useEdgeFunctionsDiff