mirror of
https://github.com/supabase/supabase.git
synced 2026-10-11 04:15:04 +03:00
refactor: add DI service layer for projects, orgs, profile, and permissions
Introduces service interfaces and live wiring for the four services that
back layout-level data dependencies, then migrates their hooks to read
from the registry instead of calling fetchers directly.
New files per service:
- *-service.ts — typed interface (ProjectsService, OrganizationsService,
ProfileService, PermissionsService)
- *-service-live.ts — live impl that delegates to the existing async fetcher
functions (getProjectDetail, getOrganizations, etc.),
which are themselves unchanged
Registry and live-registry updated to include all four services.
Hook migrations (no fallback — hooks always use the registry):
- useProjectDetailQuery → useService('projects').getProjectDetail
- useOrganizationsQuery → useService('organizations').getOrganizations
- useProfileQuery → useService('profile').getProfile
- usePermissionsQuery → useService('permissions').getPermissions
prefetchProjectDetail gains a required service parameter so prefetch paths
also go through the registry. Its single caller (usePrefetchProjectIndexPage)
supplies the service via useService('projects').
No behaviour change in production — live implementations delegate to the
same fetcher functions as before.
This commit is contained in:
1 parent
30f6660e68
commit
edc030d38b
15 files changed
+104
-27
No files matched your search
@@ -1,11 +1,12 @@
|
||||
import { QueryClient, useQuery } from '@tanstack/react-query'
|
||||
|
||||
import { components } from 'api-types'
|
||||
import { get, handleError } from 'data/fetchers'
|
||||
import { MANAGED_BY, ManagedBy } from 'lib/constants/infrastructure'
|
||||
import { useProfile } from 'lib/profile'
|
||||
import type { Organization, ResponseError, UseCustomQueryOptions } from 'types'
|
||||
|
||||
import { organizationKeys } from './keys'
|
||||
import { get, handleError } from '@/data/fetchers'
|
||||
import { MANAGED_BY, ManagedBy } from '@/lib/constants/infrastructure'
|
||||
import { useProfile } from '@/lib/profile'
|
||||
import { useService } from '@/lib/services/context'
|
||||
import type { Organization, ResponseError, UseCustomQueryOptions } from '@/types'
|
||||
|
||||
export type OrganizationBase = components['schemas']['OrganizationResponse']
|
||||
|
||||
@@ -54,9 +55,10 @@ export const useOrganizationsQuery = <TData = OrganizationsData>({
|
||||
...options
|
||||
}: UseCustomQueryOptions<OrganizationsData, OrganizationsError, TData> = {}) => {
|
||||
const { profile } = useProfile()
|
||||
const { getOrganizations: fetchOrganizations } = useService('organizations')
|
||||
return useQuery<OrganizationsData, OrganizationsError, TData>({
|
||||
queryKey: organizationKeys.list(),
|
||||
queryFn: ({ signal }) => getOrganizations({ signal }),
|
||||
queryFn: ({ signal }) => fetchOrganizations({ signal }),
|
||||
enabled: enabled && profile !== undefined,
|
||||
...options,
|
||||
staleTime: 30 * 60 * 1000,
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { getOrganizations } from './organizations-query'
|
||||
import type { OrganizationsService } from './organizations-service'
|
||||
|
||||
export const organizationsServiceLive: OrganizationsService = {
|
||||
getOrganizations,
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import type { Organization } from '@/types'
|
||||
|
||||
export interface OrganizationsService {
|
||||
getOrganizations: (params: { signal?: AbortSignal }) => Promise<Organization[]>
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
|
||||
import { useIsLoggedIn } from 'common'
|
||||
import { get, handleError } from 'data/fetchers'
|
||||
import { IS_PLATFORM } from 'lib/constants'
|
||||
import type { Permission, ResponseError, UseCustomQueryOptions } from 'types'
|
||||
|
||||
import { permissionKeys } from './keys'
|
||||
import { get, handleError } from '@/data/fetchers'
|
||||
import { IS_PLATFORM } from '@/lib/constants'
|
||||
import { useService } from '@/lib/services/context'
|
||||
import type { Permission, ResponseError, UseCustomQueryOptions } from '@/types'
|
||||
|
||||
export type PermissionsResponse = Permission[]
|
||||
|
||||
@@ -35,10 +36,11 @@ export const usePermissionsQuery = <TData = PermissionsData>({
|
||||
...options
|
||||
}: UseCustomQueryOptions<PermissionsData, PermissionsError, TData> = {}) => {
|
||||
const isLoggedIn = useIsLoggedIn()
|
||||
const { getPermissions: fetchPermissions } = useService('permissions')
|
||||
|
||||
return useQuery<PermissionsData, PermissionsError, TData>({
|
||||
queryKey: permissionKeys.list(),
|
||||
queryFn: ({ signal }) => getPermissions(signal),
|
||||
queryFn: ({ signal }) => fetchPermissions(signal),
|
||||
...options,
|
||||
enabled: IS_PLATFORM && enabled && isLoggedIn,
|
||||
staleTime: 5 * 60 * 1000,
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { getPermissions } from './permissions-query'
|
||||
import type { PermissionsService } from './permissions-service'
|
||||
|
||||
export const permissionsServiceLive: PermissionsService = {
|
||||
getPermissions,
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { Permission } from 'types'
|
||||
|
||||
export type PermissionsResponse = Permission[]
|
||||
|
||||
export interface PermissionsService {
|
||||
getPermissions: (signal?: AbortSignal) => Promise<PermissionsResponse>
|
||||
}
|
||||
@@ -3,11 +3,13 @@ import { useRouter } from 'next/router'
|
||||
import { PropsWithChildren, useCallback } from 'react'
|
||||
|
||||
import { prefetchProjectDetail } from 'data/projects/project-detail-query'
|
||||
import { useService } from 'lib/services/context'
|
||||
import PrefetchableLink, { PrefetchableLinkProps } from './PrefetchableLink'
|
||||
|
||||
export function usePrefetchProjectIndexPage() {
|
||||
const router = useRouter()
|
||||
const queryClient = useQueryClient()
|
||||
const projectsService = useService('projects')
|
||||
|
||||
return useCallback(
|
||||
({ projectRef }: { projectRef?: string }) => {
|
||||
@@ -15,13 +17,11 @@ export function usePrefetchProjectIndexPage() {
|
||||
router.prefetch(`/project/${projectRef}`)
|
||||
|
||||
// Prefetch data
|
||||
prefetchProjectDetail(queryClient, {
|
||||
ref: projectRef,
|
||||
}).catch(() => {
|
||||
prefetchProjectDetail(queryClient, { ref: projectRef }, projectsService).catch(() => {
|
||||
// eat prefetching errors as they are not critical
|
||||
})
|
||||
},
|
||||
[queryClient, router]
|
||||
[queryClient, router, projectsService]
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
|
||||
import { get, handleError } from 'data/fetchers'
|
||||
import { IS_PLATFORM } from 'lib/constants'
|
||||
import type { ResponseError, UseCustomQueryOptions } from 'types'
|
||||
import { profileKeys } from './keys'
|
||||
import type { Profile } from './types'
|
||||
import { get, handleError } from '@/data/fetchers'
|
||||
import { IS_PLATFORM } from '@/lib/constants'
|
||||
import { useService } from '@/lib/services/context'
|
||||
import type { ResponseError, UseCustomQueryOptions } from '@/types'
|
||||
|
||||
export async function getProfile(signal?: AbortSignal) {
|
||||
const { data, error } = await get('/platform/profile', {
|
||||
@@ -31,9 +32,10 @@ export const useProfileQuery = <TData = ProfileData>({
|
||||
enabled = true,
|
||||
...options
|
||||
}: UseCustomQueryOptions<ProfileData, ProfileError, TData> = {}) => {
|
||||
const { getProfile: fetchProfile } = useService('profile')
|
||||
return useQuery<ProfileData, ProfileError, TData>({
|
||||
queryKey: profileKeys.profile(),
|
||||
queryFn: ({ signal }) => getProfile(signal),
|
||||
queryFn: ({ signal }) => fetchProfile(signal),
|
||||
staleTime: 1000 * 60 * 30,
|
||||
...options,
|
||||
enabled,
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { getProfile } from './profile-query'
|
||||
import type { ProfileService } from './profile-service'
|
||||
|
||||
export const profileServiceLive: ProfileService = {
|
||||
getProfile,
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import type { Profile } from './types'
|
||||
|
||||
export interface ProfileService {
|
||||
getProfile: (signal?: AbortSignal) => Promise<Profile>
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
import { QueryClient, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { useCallback } from 'react'
|
||||
|
||||
import type { components } from 'data/api'
|
||||
import { get, handleError, isValidConnString } from 'data/fetchers'
|
||||
import type { ResponseError, UseCustomQueryOptions } from 'types'
|
||||
import { projectKeys } from './keys'
|
||||
import { OrgProjectsResponse } from './org-projects-infinite-query'
|
||||
import type { ProjectsService } from './projects-service'
|
||||
import type { components } from '@/data/api'
|
||||
import { get, handleError, isValidConnString } from '@/data/fetchers'
|
||||
import { useService } from '@/lib/services/context'
|
||||
import type { ResponseError, UseCustomQueryOptions } from '@/types'
|
||||
|
||||
type ProjectDetailVariables = { ref?: string }
|
||||
export type ProjectDetail = components['schemas']['ProjectDetailResponse']
|
||||
@@ -45,10 +47,12 @@ export const useProjectDetailQuery = <TData = ProjectDetailData>(
|
||||
enabled = true,
|
||||
...options
|
||||
}: UseCustomQueryOptions<ProjectDetailData, ProjectDetailError, TData> = {}
|
||||
) =>
|
||||
useQuery<ProjectDetailData, ProjectDetailError, TData>({
|
||||
) => {
|
||||
const { getProjectDetail: fetchProjectDetail } = useService('projects')
|
||||
|
||||
return useQuery<ProjectDetailData, ProjectDetailError, TData>({
|
||||
queryKey: projectKeys.detail(ref),
|
||||
queryFn: ({ signal }) => getProjectDetail({ ref }, signal),
|
||||
queryFn: ({ signal }) => fetchProjectDetail({ ref }, signal),
|
||||
enabled: enabled && typeof ref !== 'undefined',
|
||||
staleTime: 30 * 1000,
|
||||
refetchInterval: (query) => {
|
||||
@@ -64,11 +68,16 @@ export const useProjectDetailQuery = <TData = ProjectDetailData>(
|
||||
},
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
export function prefetchProjectDetail(client: QueryClient, { ref }: ProjectDetailVariables) {
|
||||
export function prefetchProjectDetail(
|
||||
client: QueryClient,
|
||||
{ ref }: ProjectDetailVariables,
|
||||
service: { getProjectDetail: ProjectsService['getProjectDetail'] }
|
||||
) {
|
||||
return client.fetchQuery({
|
||||
queryKey: projectKeys.detail(ref),
|
||||
queryFn: ({ signal }) => getProjectDetail({ ref }, signal),
|
||||
queryFn: ({ signal }) => service.getProjectDetail({ ref }, signal),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { getProjectDetail } from './project-detail-query'
|
||||
import type { ProjectsService } from './projects-service'
|
||||
|
||||
export const projectsServiceLive: ProjectsService = {
|
||||
getProjectDetail,
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import type { Project } from './project-detail-query'
|
||||
|
||||
export interface ProjectsService {
|
||||
getProjectDetail: (params: { ref?: string }, signal?: AbortSignal) => Promise<Project>
|
||||
}
|
||||
@@ -1,10 +1,18 @@
|
||||
import { authServiceLive } from './auth-service'
|
||||
import { featureFlagServiceLive } from './feature-flag-service'
|
||||
import type { ServiceRegistry } from './registry'
|
||||
import { organizationsServiceLive } from '@/data/organizations/organizations-service-live'
|
||||
import { permissionsServiceLive } from '@/data/permissions/permissions-service-live'
|
||||
import { profileServiceLive } from '@/data/profile/profile-service-live'
|
||||
import { projectsServiceLive } from '@/data/projects/projects-service-live'
|
||||
|
||||
export function createLiveRegistry(): ServiceRegistry {
|
||||
return {
|
||||
auth: authServiceLive,
|
||||
featureFlags: featureFlagServiceLive,
|
||||
organizations: organizationsServiceLive,
|
||||
permissions: permissionsServiceLive,
|
||||
profile: profileServiceLive,
|
||||
projects: projectsServiceLive,
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { AuthService } from './auth-service'
|
||||
import type { FeatureFlagService } from './feature-flag-service'
|
||||
import type { OrganizationsService } from '@/data/organizations/organizations-service'
|
||||
import type { PermissionsService } from '@/data/permissions/permissions-service'
|
||||
import type { ProfileService } from '@/data/profile/profile-service'
|
||||
import type { ProjectsService } from '@/data/projects/projects-service'
|
||||
|
||||
/**
|
||||
* Central registry of all injectable services.
|
||||
@@ -9,4 +13,8 @@ import type { FeatureFlagService } from './feature-flag-service'
|
||||
export interface ServiceRegistry {
|
||||
auth: AuthService
|
||||
featureFlags: FeatureFlagService
|
||||
organizations: OrganizationsService
|
||||
permissions: PermissionsService
|
||||
profile: ProfileService
|
||||
projects: ProjectsService
|
||||
}
|
||||
Reference in new issue
Block a user