mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
chore: reuse MetaStores instead of recreating on projectRef change
This commit is contained in:
1 parent
5b00919091
commit
d4967a84ea
8 files changed
+154
-69
No files matched your search
@@ -14,8 +14,8 @@ interface Props {}
|
||||
|
||||
const NavigationBar: FC<Props> = ({}) => {
|
||||
const router = useRouter()
|
||||
const { ui } = useStore()
|
||||
const projectRef = ui.selectedProjectRef as string
|
||||
const { selectedProjectRef, ui } = useStore()
|
||||
const projectRef = selectedProjectRef ?? '[ref]'
|
||||
const projectBaseInfo = ui.selectedProjectBaseInfo
|
||||
|
||||
const ongoingIncident = useFlag('ongoingIncident')
|
||||
|
||||
@@ -64,7 +64,9 @@ export function withAuth<T>(
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading && router.isReady) {
|
||||
rootStore.setProjectRef(ref ? String(ref) : undefined)
|
||||
if (ref) {
|
||||
rootStore.setProjectRef(Array.isArray(ref) ? ref[0] : ref)
|
||||
}
|
||||
rootStore.setOrganizationSlug(slug ? String(slug) : undefined)
|
||||
}
|
||||
}, [isLoading, router.isReady, ref, slug])
|
||||
|
||||
+33
-44
@@ -24,7 +24,10 @@ export interface IRootStore {
|
||||
functions: IProjectFunctionsStore
|
||||
backups: IProjectBackupsStore
|
||||
authConfig: IProjectAuthConfigStore
|
||||
setProjectRef: (value?: string) => void
|
||||
|
||||
selectedProjectRef?: string
|
||||
|
||||
setProjectRef: (value: string) => void
|
||||
setOrganizationSlug: (value?: string) => void
|
||||
}
|
||||
export class RootStore implements IRootStore {
|
||||
@@ -36,43 +39,17 @@ export class RootStore implements IRootStore {
|
||||
backups: IProjectBackupsStore
|
||||
authConfig: IProjectAuthConfigStore
|
||||
|
||||
selectedProjectRef: string | undefined
|
||||
|
||||
constructor() {
|
||||
this.app = new AppStore(this)
|
||||
this.ui = new UiStore(this)
|
||||
this.meta = new MetaStore(this, { projectRef: '', connectionString: '' })
|
||||
|
||||
// @ts-ignore
|
||||
this.content = new ProjectContentStore(this, { projectRef: '' })
|
||||
this.functions = new ProjectFunctionsStore(this, { projectRef: '' })
|
||||
this.backups = new ProjectBackupsStore(this, { projectRef: '' })
|
||||
this.authConfig = new ProjectAuthConfigStore(this, { projectRef: '' })
|
||||
|
||||
/**
|
||||
* TODO: meta and content are not observable
|
||||
* meaning that when meta and content object change mobx doesnt trigger new event
|
||||
*
|
||||
* Workaround for now
|
||||
* we need to use ui.selectedProject along with meta and content
|
||||
* cos whenever ui.selectedProject changes, the reaction will create new meta and content stores
|
||||
*/
|
||||
reaction(
|
||||
() => this.ui.selectedProject,
|
||||
(selectedProject) => {
|
||||
if (selectedProject) {
|
||||
// @ts-ignore
|
||||
this.meta = new MetaStore(this, {
|
||||
projectRef: selectedProject.ref,
|
||||
connectionString: selectedProject.connectionString ?? '',
|
||||
})
|
||||
} else {
|
||||
// @ts-ignore
|
||||
this.meta = new MetaStore(this, {
|
||||
projectRef: '',
|
||||
connectionString: '',
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,23 +57,35 @@ export class RootStore implements IRootStore {
|
||||
*
|
||||
* This method will also trigger project detail loading when it's not available
|
||||
*/
|
||||
setProjectRef(value?: string) {
|
||||
if (this.ui.selectedProject?.ref === value) return
|
||||
if (value) {
|
||||
// fetch project detail when
|
||||
// - project not found yet. projectStore is loading
|
||||
// - connectionString is not available. projectStore loaded
|
||||
const found = this.app.projects.find((x: Project) => x.ref == value)
|
||||
if (!found || !found.connectionString) {
|
||||
this.app.projects.fetchDetail(value)
|
||||
}
|
||||
setProjectRef(value: string) {
|
||||
if (this.selectedProjectRef === value) return
|
||||
this.selectedProjectRef = value
|
||||
|
||||
// reset ui projectRef in case of switching projects
|
||||
// this will show the loading screen instead of showing the previous project
|
||||
this.ui.setProjectRef(undefined)
|
||||
|
||||
const setProjectRefs = (project: Project) => {
|
||||
this.meta.setProjectDetails(project)
|
||||
this.functions.setProjectRef(project.ref)
|
||||
this.authConfig.setProjectRef(project.ref)
|
||||
this.content.setProjectRef(project.ref)
|
||||
this.backups.setProjectRef(project.ref)
|
||||
// ui set must come last
|
||||
this.ui.setProjectRef(project.ref)
|
||||
}
|
||||
|
||||
this.ui.setProjectRef(value)
|
||||
this.functions.setProjectRef(value)
|
||||
this.authConfig.setProjectRef(value)
|
||||
this.content.setProjectRef(value)
|
||||
this.backups.setProjectRef(value)
|
||||
// fetch project detail when
|
||||
// - project not found yet. projectStore is loading
|
||||
// - connectionString is not available. projectStore loaded
|
||||
const found = this.app.projects.find((x: Project) => x.ref === value)
|
||||
if (!found || !found.connectionString) {
|
||||
this.app.projects.fetchDetail(value, (project) => {
|
||||
setProjectRefs(project)
|
||||
})
|
||||
} else {
|
||||
setProjectRefs(found)
|
||||
}
|
||||
}
|
||||
|
||||
setOrganizationSlug(value?: string) {
|
||||
|
||||
@@ -57,7 +57,7 @@ export default class UiStore implements IUiStore {
|
||||
get selectedProject() {
|
||||
if (this.selectedProjectRef) {
|
||||
const found = this.rootStore.app.projects.find(
|
||||
(x: Project) => x.ref == this.selectedProjectRef
|
||||
(x: Project) => x.ref === this.selectedProjectRef
|
||||
)
|
||||
return !!found?.connectionString ? found : undefined
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import PostgresMetaInterface, { IPostgresMetaInterface } from '../common/Postgre
|
||||
import pingPostgrest from 'lib/pingPostgrest'
|
||||
|
||||
export interface IProjectStore extends IPostgresMetaInterface<Project> {
|
||||
fetchDetail: (projectRef: string) => Promise<void>
|
||||
fetchDetail: (projectRef: string, callback?: (project: Project) => void) => Promise<void>
|
||||
}
|
||||
|
||||
export default class ProjectStore extends PostgresMetaInterface<Project> {
|
||||
@@ -44,7 +44,7 @@ export default class ProjectStore extends PostgresMetaInterface<Project> {
|
||||
this.data = formattedValue
|
||||
}
|
||||
|
||||
async fetchDetail(projectRef: string) {
|
||||
async fetchDetail(projectRef: string, callback?: (project: Project) => void) {
|
||||
const url = `${this.url}/${projectRef}`
|
||||
const headers = constructHeaders(this.headers)
|
||||
const response = await get(url, { headers })
|
||||
@@ -56,6 +56,8 @@ export default class ProjectStore extends PostgresMetaInterface<Project> {
|
||||
// update project detail by key id
|
||||
this.data[project.id] = project
|
||||
|
||||
callback?.(project)
|
||||
|
||||
// lazy fetches
|
||||
if (IS_PLATFORM) {
|
||||
this.fetchSubscriptionTier(project)
|
||||
|
||||
@@ -22,6 +22,9 @@ export interface IPostgresMetaInterface<T> {
|
||||
find: (filter?: any) => T | undefined
|
||||
byId: (id: number | string) => T | undefined
|
||||
initialDataArray: (value: T[]) => void
|
||||
|
||||
setUrl: (url: string) => void
|
||||
setHeaders: (headers: { [prop: string]: any }) => void
|
||||
}
|
||||
|
||||
// [TODO] Need to refactor the logic for 'isInitialized'
|
||||
@@ -161,8 +164,8 @@ export default class PostgresMetaInterface<T> implements IPostgresMetaInterface<
|
||||
if (response.error) throw response.error
|
||||
|
||||
const data = response as T
|
||||
const indentity = response[this.identifier]
|
||||
this.data[indentity] = data
|
||||
const identity = response[this.identifier]
|
||||
this.data[identity] = data
|
||||
return data
|
||||
} catch (error: any) {
|
||||
return { data: null, error }
|
||||
@@ -197,4 +200,17 @@ export default class PostgresMetaInterface<T> implements IPostgresMetaInterface<
|
||||
return { error }
|
||||
}
|
||||
}
|
||||
|
||||
setUrl(url: string) {
|
||||
this.url = url
|
||||
|
||||
// if the url changes, we need to reset the state
|
||||
this.state = this.STATES.INITIAL
|
||||
this.data = {}
|
||||
this.error = null
|
||||
}
|
||||
|
||||
setHeaders(headers: { [prop: string]: any }) {
|
||||
this.headers = headers
|
||||
}
|
||||
}
|
||||
@@ -120,6 +120,8 @@ export interface IMetaStore {
|
||||
columns: ColumnField[],
|
||||
isRealtimeEnabled: boolean
|
||||
) => any
|
||||
|
||||
setProjectDetails: (details: { ref: string; connectionString?: string }) => void
|
||||
}
|
||||
export default class MetaStore implements IMetaStore {
|
||||
rootStore: IRootStore
|
||||
@@ -140,6 +142,7 @@ export default class MetaStore implements IMetaStore {
|
||||
projectRef?: string
|
||||
connectionString?: string
|
||||
baseUrl: string
|
||||
headers: { [prop: string]: any }
|
||||
|
||||
// [Joshen] I'm going to treat this as a list of system schemas
|
||||
excludedSchemas = [
|
||||
@@ -158,33 +161,45 @@ export default class MetaStore implements IMetaStore {
|
||||
'graphql_public',
|
||||
]
|
||||
|
||||
constructor(rootStore: IRootStore, options: { projectRef: string; connectionString: string }) {
|
||||
constructor(rootStore: IRootStore, options: { projectRef: string; connectionString?: string }) {
|
||||
const { projectRef, connectionString } = options
|
||||
this.rootStore = rootStore
|
||||
this.projectRef = projectRef
|
||||
this.baseUrl = `${API_URL}/pg-meta/${projectRef}`
|
||||
|
||||
const headers: any = {}
|
||||
this.headers = {}
|
||||
if (IS_PLATFORM && connectionString) {
|
||||
this.connectionString = connectionString
|
||||
headers['x-connection-encrypted'] = connectionString
|
||||
this.headers['x-connection-encrypted'] = connectionString
|
||||
}
|
||||
|
||||
this.openApi = new OpenApiStore(rootStore, `${API_URL}/props/project/${projectRef}/api`)
|
||||
this.tables = new TableStore(rootStore, `${this.baseUrl}/tables`, headers)
|
||||
this.columns = new ColumnStore(rootStore, `${this.baseUrl}/columns`, headers)
|
||||
this.schemas = new SchemaStore(rootStore, `${this.baseUrl}/schemas`, headers)
|
||||
this.openApi = new OpenApiStore(
|
||||
this.rootStore,
|
||||
`${API_URL}/props/project/${this.projectRef}/api`
|
||||
)
|
||||
this.tables = new TableStore(this.rootStore, `${this.baseUrl}/tables`, this.headers)
|
||||
this.columns = new ColumnStore(this.rootStore, `${this.baseUrl}/columns`, this.headers)
|
||||
this.schemas = new SchemaStore(this.rootStore, `${this.baseUrl}/schemas`, this.headers)
|
||||
|
||||
this.roles = new RolesStore(rootStore, `${this.baseUrl}/roles`, headers)
|
||||
this.policies = new PoliciesStore(rootStore, `${this.baseUrl}/policies`, headers)
|
||||
this.hooks = new HooksStore(rootStore, `${this.baseUrl}/triggers`, headers)
|
||||
this.triggers = new TriggersStore(rootStore, `${this.baseUrl}/triggers`, headers)
|
||||
this.functions = new FunctionsStore(rootStore, `${this.baseUrl}/functions`, headers)
|
||||
this.extensions = new ExtensionsStore(rootStore, `${this.baseUrl}/extensions`, headers, {
|
||||
identifier: 'name',
|
||||
})
|
||||
this.publications = new PublicationStore(rootStore, `${this.baseUrl}/publications`, headers)
|
||||
this.types = new TypesStore(rootStore, `${this.baseUrl}/types`, headers)
|
||||
this.roles = new RolesStore(this.rootStore, `${this.baseUrl}/roles`, this.headers)
|
||||
this.policies = new PoliciesStore(this.rootStore, `${this.baseUrl}/policies`, this.headers)
|
||||
this.hooks = new HooksStore(this.rootStore, `${this.baseUrl}/triggers`, this.headers)
|
||||
this.triggers = new TriggersStore(this.rootStore, `${this.baseUrl}/triggers`, this.headers)
|
||||
this.functions = new FunctionsStore(this.rootStore, `${this.baseUrl}/functions`, this.headers)
|
||||
this.extensions = new ExtensionsStore(
|
||||
this.rootStore,
|
||||
`${this.baseUrl}/extensions`,
|
||||
this.headers,
|
||||
{
|
||||
identifier: 'name',
|
||||
}
|
||||
)
|
||||
this.publications = new PublicationStore(
|
||||
this.rootStore,
|
||||
`${this.baseUrl}/publications`,
|
||||
this.headers
|
||||
)
|
||||
this.types = new TypesStore(this.rootStore, `${this.baseUrl}/types`, this.headers)
|
||||
|
||||
makeObservable(this, {
|
||||
excludedSchemas: observable,
|
||||
@@ -803,4 +818,49 @@ export default class MetaStore implements IMetaStore {
|
||||
onProgressUpdate(insertProgress * 100)
|
||||
}
|
||||
}
|
||||
|
||||
setProjectDetails({ ref, connectionString }: { ref: string; connectionString?: string }) {
|
||||
this.projectRef = ref
|
||||
this.baseUrl = `${API_URL}/pg-meta/${ref}`
|
||||
if (IS_PLATFORM && connectionString) {
|
||||
this.connectionString = connectionString
|
||||
this.headers['x-connection-encrypted'] = connectionString
|
||||
}
|
||||
|
||||
this.openApi.setUrl(`${API_URL}/props/project/${this.projectRef}/api`)
|
||||
this.openApi.setHeaders(this.headers)
|
||||
|
||||
this.tables.setUrl(`${this.baseUrl}/tables`)
|
||||
this.tables.setHeaders(this.headers)
|
||||
|
||||
this.columns.setUrl(`${this.baseUrl}/columns`)
|
||||
this.columns.setHeaders(this.headers)
|
||||
|
||||
this.schemas.setUrl(`${this.baseUrl}/schemas`)
|
||||
this.schemas.setHeaders(this.headers)
|
||||
|
||||
this.roles.setUrl(`${this.baseUrl}/roles`)
|
||||
this.roles.setHeaders(this.headers)
|
||||
|
||||
this.policies.setUrl(`${this.baseUrl}/policies`)
|
||||
this.policies.setHeaders(this.headers)
|
||||
|
||||
this.hooks.setUrl(`${this.baseUrl}/triggers`)
|
||||
this.hooks.setHeaders(this.headers)
|
||||
|
||||
this.triggers.setUrl(`${this.baseUrl}/triggers`)
|
||||
this.triggers.setHeaders(this.headers)
|
||||
|
||||
this.functions.setUrl(`${this.baseUrl}/functions`)
|
||||
this.functions.setHeaders(this.headers)
|
||||
|
||||
this.extensions.setUrl(`${this.baseUrl}/extensions`)
|
||||
this.extensions.setHeaders(this.headers)
|
||||
|
||||
this.publications.setUrl(`${this.baseUrl}/publications`)
|
||||
this.publications.setHeaders(this.headers)
|
||||
|
||||
this.types.setUrl(`${this.baseUrl}/types`)
|
||||
this.types.setHeaders(this.headers)
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,9 @@ export interface IOpenApiStore {
|
||||
isLoading: boolean
|
||||
|
||||
load: () => void
|
||||
|
||||
setUrl: (url: string) => void
|
||||
setHeaders: (headers: { [prop: string]: any }) => void
|
||||
}
|
||||
export default class OpenApiStore implements IOpenApiStore {
|
||||
STATES = {
|
||||
@@ -126,4 +129,17 @@ export default class OpenApiStore implements IOpenApiStore {
|
||||
setError(value: any) {
|
||||
this.error = value
|
||||
}
|
||||
|
||||
setUrl(url: string) {
|
||||
this.url = url
|
||||
|
||||
// if the url changes, we need to reset the state
|
||||
this.state = this.STATES.INITIAL
|
||||
this.data = undefined
|
||||
this.error = null
|
||||
}
|
||||
|
||||
setHeaders(headers: { [prop: string]: any }) {
|
||||
this.headers = headers
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user