mirror of
https://github.com/supabase/supabase.git
synced 2026-10-13 05:09:12 +03:00
The filtering for `sdk:auth` feature flag on the navigation was a a bit too aggressive, kicking in on the Management API reference when it should only apply to the Client SDK references.
507 lines
17 KiB
TypeScript
507 lines
17 KiB
TypeScript
import { Fragment } from 'react'
|
|
import ReactMarkdown from 'react-markdown'
|
|
import { Tabs_Shadcn_, TabsContent_Shadcn_, TabsList_Shadcn_, TabsTrigger_Shadcn_, cn } from 'ui'
|
|
|
|
import { isFeatureEnabled } from 'common'
|
|
import ApiSchema from '~/components/ApiSchema'
|
|
import { clientSdkIds, REFERENCES } from '~/content/navigation.references'
|
|
import { MDXRemoteRefs, getRefMarkdown } from '~/features/docs/Reference.mdx'
|
|
import type { MethodTypes } from '~/features/docs/Reference.typeSpec'
|
|
import {
|
|
getApiEndpointById,
|
|
getCliSpec,
|
|
getFlattenedSections,
|
|
getFunctionsList,
|
|
getSelfHostedApiEndpointById,
|
|
getTypeSpec,
|
|
} from '~/features/docs/Reference.generated.singleton'
|
|
import { type IApiEndPoint } from './Reference.api.utils'
|
|
import {
|
|
ApiOperationRequestBodyDetails,
|
|
ApiSchemaParamDetails,
|
|
CollapsibleDetails,
|
|
FnParameterDetails,
|
|
RefSubLayout,
|
|
RequiredBadge,
|
|
ReturnTypeDetails,
|
|
StickyHeader,
|
|
} from '~/features/docs/Reference.ui'
|
|
import type { AbbrevApiReferenceSection } from '~/features/docs/Reference.utils'
|
|
import { normalizeMarkdown } from '~/features/docs/Reference.utils'
|
|
import { CodeBlock } from '~/features/ui/CodeBlock/CodeBlock'
|
|
import { RefInternalLink } from './Reference.navigation.client'
|
|
import { ApiOperationBodySchemeSelector } from './Reference.ui.client'
|
|
|
|
type RefSectionsProps = {
|
|
libraryId: string
|
|
version: string
|
|
}
|
|
|
|
async function RefSections({ libraryId, version }: RefSectionsProps) {
|
|
let flattenedSections = await getFlattenedSections(libraryId, version)
|
|
if (flattenedSections) {
|
|
flattenedSections = trimIntro(flattenedSections)
|
|
}
|
|
|
|
if (!isFeatureEnabled('sdk:auth') && clientSdkIds.includes(libraryId)) {
|
|
flattenedSections = flattenedSections?.filter(
|
|
(section) =>
|
|
'product' in section && section.product !== 'auth' && section.product !== 'auth-admin'
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-col my-16 gap-16">
|
|
{(flattenedSections || [])
|
|
.filter((section) => section.type !== 'category')
|
|
.map((section, idx) => (
|
|
<Fragment key={`${section.id}-${idx}`}>
|
|
<SectionDivider />
|
|
<SectionSwitch libraryId={libraryId} version={version} section={section} />
|
|
</Fragment>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function trimIntro(sections: Array<AbbrevApiReferenceSection>) {
|
|
const hasIntro = sections[0]?.type === 'markdown' && sections[0]?.slug === 'introduction'
|
|
if (hasIntro) {
|
|
return sections.slice(1)
|
|
}
|
|
return sections
|
|
}
|
|
|
|
function SectionDivider() {
|
|
return <hr />
|
|
}
|
|
|
|
type SectionSwitchProps = {
|
|
libraryId: string
|
|
version: string
|
|
section: AbbrevApiReferenceSection
|
|
}
|
|
|
|
export function SectionSwitch({ libraryId, version, section }: SectionSwitchProps) {
|
|
const libPath = REFERENCES[libraryId.replaceAll('-', '_')].libPath
|
|
const allAvailableVersions = REFERENCES[libraryId.replaceAll('-', '_')].versions
|
|
const isLatestVersion = allAvailableVersions.length === 0 || version === allAvailableVersions[0]
|
|
|
|
const sectionLink = `/docs/reference/${libPath}/${isLatestVersion ? '' : `${version}/`}${section.slug}`
|
|
|
|
switch (section.type) {
|
|
case 'markdown':
|
|
return (
|
|
<MarkdownSection
|
|
libPath={libPath}
|
|
version={version}
|
|
isLatestVersion={isLatestVersion}
|
|
link={sectionLink}
|
|
section={section}
|
|
/>
|
|
)
|
|
case 'function':
|
|
return (
|
|
<FunctionSection
|
|
sdkId={libraryId}
|
|
version={version}
|
|
link={sectionLink}
|
|
section={section}
|
|
useTypeSpec={REFERENCES[libraryId].typeSpec}
|
|
/>
|
|
)
|
|
case 'cli-command':
|
|
return <CliCommandSection link={sectionLink} section={section} />
|
|
case 'operation':
|
|
return <ApiEndpointSection link={sectionLink} section={section} />
|
|
case 'self-hosted-operation':
|
|
return <ApiEndpointSection servicePath={libraryId} link={sectionLink} section={section} />
|
|
default:
|
|
console.error(`Unhandled type in reference sections: ${section.type}`)
|
|
return null
|
|
}
|
|
}
|
|
|
|
interface MarkdownSectionProps {
|
|
libPath: string
|
|
version: string
|
|
isLatestVersion: boolean
|
|
link: string
|
|
section: AbbrevApiReferenceSection
|
|
}
|
|
|
|
async function MarkdownSection({
|
|
libPath,
|
|
version,
|
|
isLatestVersion,
|
|
link,
|
|
section,
|
|
}: MarkdownSectionProps) {
|
|
const content = await getRefMarkdown(
|
|
section.meta?.shared
|
|
? `shared/${section.id}`
|
|
: `${libPath}/${isLatestVersion ? '' : `${version}/`}${section.id}`
|
|
)
|
|
|
|
return (
|
|
<RefSubLayout.EducationSection link={link} {...section}>
|
|
<StickyHeader {...section} />
|
|
<MDXRemoteRefs source={content} />
|
|
</RefSubLayout.EducationSection>
|
|
)
|
|
}
|
|
|
|
interface CliCommandSectionProps {
|
|
link: string
|
|
section: AbbrevApiReferenceSection
|
|
}
|
|
|
|
async function CliCommandSection({ link, section }: CliCommandSectionProps) {
|
|
const cliSpec = await getCliSpec()
|
|
const command = ((cliSpec as any).commands ?? []).find((cmd) => cmd.id === section.id)
|
|
|
|
if (!command) return null
|
|
|
|
return (
|
|
<RefSubLayout.Section columns="double" link={link} {...section}>
|
|
<StickyHeader title={command.title} className="col-[1_/_-1]" monoFont={true} />
|
|
<div className="w-full min-w-0">
|
|
{command.description && (
|
|
<ReactMarkdown className="prose w-full break-words mb-8">
|
|
{command.description}
|
|
</ReactMarkdown>
|
|
)}
|
|
{command.usage && (
|
|
<div className="mb-8">
|
|
<h3 className="mb-2 text-base text-foreground">Usage</h3>
|
|
<CodeBlock lang="bash">{command.usage}</CodeBlock>
|
|
</div>
|
|
)}
|
|
{(command.subcommands ?? []).length > 0 && (
|
|
<>
|
|
<h3 className="mb-3 text-base text-foreground">Subcommands</h3>
|
|
<ul className="prose">
|
|
{command.subcommands.map((subcommand, index) => {
|
|
const subcommandDetails = (cliSpec as any).commands.find(
|
|
(cmd) => cmd.id === subcommand
|
|
)
|
|
if (!subcommandDetails) return null
|
|
return (
|
|
<li key={index} className="ml-4">
|
|
<RefInternalLink
|
|
href={`/reference/cli/${subcommandDetails.id}`}
|
|
sectionSlug={subcommandDetails.id}
|
|
>
|
|
{subcommandDetails.title}
|
|
</RefInternalLink>
|
|
</li>
|
|
)
|
|
})}
|
|
</ul>
|
|
</>
|
|
)}
|
|
{(command.flags ?? []).length > 0 && (
|
|
<>
|
|
<h3 className="mb-3 text-base text-foreground">Flags</h3>
|
|
<ul>
|
|
{command.flags.map((flag, index) => (
|
|
<li key={index} className="border-t last-of-type:border-b py-5 flex flex-col gap-3">
|
|
<div className="flex flex-wrap items-baseline gap-3">
|
|
<span className="font-mono text-sm font-medium text-foreground">
|
|
{flag.name}
|
|
</span>
|
|
<RequiredBadge isOptional={!flag.required} />
|
|
</div>
|
|
{flag.description && (
|
|
<ReactMarkdown className="prose break-words text-sm">
|
|
{flag.description}
|
|
</ReactMarkdown>
|
|
)}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</>
|
|
)}
|
|
</div>
|
|
<div className="overflow-auto">
|
|
{'examples' in command &&
|
|
Array.isArray(command.examples) &&
|
|
command.examples.length > 0 && (
|
|
<Tabs_Shadcn_ defaultValue={command.examples[0].id}>
|
|
<TabsList_Shadcn_ className="flex-wrap gap-2 border-0">
|
|
{command.examples.map((example) => (
|
|
<TabsTrigger_Shadcn_
|
|
key={example.id}
|
|
value={example.id}
|
|
className={cn(
|
|
'px-2.5 py-1 rounded-full',
|
|
'border-0 bg-surface-200 hover:bg-surface-300',
|
|
'text-xs text-foreground-lighter',
|
|
// Undoing styles from primitive component
|
|
'data-[state=active]:border-0 data-[state=active]:shadow-0',
|
|
'data-[state=active]:bg-foreground data-[state=active]:text-background',
|
|
'transition'
|
|
)}
|
|
>
|
|
{example.name}
|
|
</TabsTrigger_Shadcn_>
|
|
))}
|
|
</TabsList_Shadcn_>
|
|
{command.examples.map((example) => (
|
|
<TabsContent_Shadcn_ key={example.id} value={example.id}>
|
|
<CodeBlock lang="bash" className="mb-6">
|
|
{example.code}
|
|
</CodeBlock>
|
|
<h3 className="text-foreground-lighter text-sm mb-2">Response</h3>
|
|
<CodeBlock lang="txt">{example.response}</CodeBlock>
|
|
</TabsContent_Shadcn_>
|
|
))}
|
|
</Tabs_Shadcn_>
|
|
)}
|
|
</div>
|
|
</RefSubLayout.Section>
|
|
)
|
|
}
|
|
|
|
interface ApiEndpointSectionProps {
|
|
link: string
|
|
section: AbbrevApiReferenceSection
|
|
servicePath?: string
|
|
}
|
|
|
|
async function ApiEndpointSection({ link, section, servicePath }: ApiEndpointSectionProps) {
|
|
const endpointDetails = servicePath
|
|
? await getSelfHostedApiEndpointById(servicePath, section.id)
|
|
: await getApiEndpointById(section.id)
|
|
if (!endpointDetails) return null
|
|
|
|
const pathParameters = (endpointDetails.parameters ?? []).filter((param) => param.in === 'path')
|
|
const queryParameters = (endpointDetails.parameters ?? []).filter((param) => param.in === 'query')
|
|
const bodyParameters =
|
|
endpointDetails.requestBody ??
|
|
(endpointDetails.parameters ?? [])
|
|
.filter((param) => param.in === 'body')
|
|
.map(
|
|
(bodyParam) =>
|
|
({
|
|
content: {
|
|
'application/json': {
|
|
schema: bodyParam.schema,
|
|
},
|
|
},
|
|
}) satisfies IApiEndPoint['requestBody']
|
|
)[0]
|
|
|
|
const first2xxCode = Object.keys(endpointDetails.responses ?? {})
|
|
.filter((code) => code.startsWith('2'))
|
|
.sort()[0]
|
|
|
|
return (
|
|
<RefSubLayout.Section columns="double" link={link} {...section}>
|
|
<StickyHeader
|
|
title={
|
|
<>
|
|
{endpointDetails.summary}
|
|
{endpointDetails.deprecated && (
|
|
<span
|
|
className="
|
|
uppercase
|
|
whitespace-nowrap align-middle inline-block -translate-y-0.5
|
|
border border-amber-700 bg-amber-300 text-amber-900
|
|
rounded-full font-mono font-medium text-xs px-2 py-0.5 ml-2"
|
|
>
|
|
deprecated
|
|
</span>
|
|
)}
|
|
</>
|
|
}
|
|
className="col-[1_/_-1]"
|
|
/>
|
|
<div className="flex flex-col gap-12">
|
|
<div className="flex items-center gap-2">
|
|
<span
|
|
className={cn(
|
|
'uppercase text-sm whitespace-nowrap bg-foreground text-background rounded-full font-mono font-medium px-2 py-0.5',
|
|
endpointDetails.deprecated && 'line-through'
|
|
)}
|
|
>
|
|
{endpointDetails.method}
|
|
</span>
|
|
<code
|
|
className={cn(
|
|
'text-foreground-lighter break-all',
|
|
endpointDetails.deprecated && 'line-through'
|
|
)}
|
|
>
|
|
{endpointDetails.path}
|
|
</code>
|
|
</div>
|
|
{endpointDetails.description && (
|
|
<ReactMarkdown className="prose break-words mb-8">
|
|
{endpointDetails.description}
|
|
</ReactMarkdown>
|
|
)}
|
|
{pathParameters.length > 0 && (
|
|
<section>
|
|
<h3 className="mb-3 text-base text-foreground">Path parameters</h3>
|
|
<ul>
|
|
{pathParameters.map((param, index) => (
|
|
<ApiSchemaParamDetails key={index} param={param} />
|
|
))}
|
|
</ul>
|
|
</section>
|
|
)}
|
|
{queryParameters.length > 0 && (
|
|
<section>
|
|
<h3 className="mb-3 text-base text-foreground">Query parameters</h3>
|
|
<ul>
|
|
{queryParameters.map((param, index) => (
|
|
<ApiSchemaParamDetails key={index} param={param} />
|
|
))}
|
|
</ul>
|
|
</section>
|
|
)}
|
|
{bodyParameters && (
|
|
<section>
|
|
<ApiOperationBodySchemeSelector requestBody={bodyParameters} className="mb-3" />
|
|
<ApiOperationRequestBodyDetails requestBody={bodyParameters} />
|
|
</section>
|
|
)}
|
|
{endpointDetails.responses && (
|
|
<section>
|
|
<h3 className="mb-3 text-base text-foreground">Response codes</h3>
|
|
<ul>
|
|
{Object.keys(endpointDetails.responses).map((code) => (
|
|
<li key={code} className="list-['-'] ml-2 pl-2">
|
|
<span className="font-mono text-sm font-medium text-foreground">{code}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</section>
|
|
)}
|
|
</div>
|
|
{endpointDetails.responses && first2xxCode && (
|
|
<div className="overflow-auto">
|
|
<h3 className="mb-3 text-base text-foreground">{`Response (${first2xxCode})`}</h3>
|
|
<ApiSchema
|
|
id={`${section.id}-2xx-response`}
|
|
schema={
|
|
endpointDetails.responses[first2xxCode].content?.['application/json']?.schema ??
|
|
// @ts-ignore - schema is here in older versions
|
|
endpointDetails.responses[first2xxCode].schema ??
|
|
{}
|
|
}
|
|
/>
|
|
</div>
|
|
)}
|
|
</RefSubLayout.Section>
|
|
)
|
|
}
|
|
|
|
interface FunctionSectionProps {
|
|
sdkId: string
|
|
version: string
|
|
link: string
|
|
section: AbbrevApiReferenceSection
|
|
useTypeSpec: boolean
|
|
}
|
|
|
|
async function FunctionSection({
|
|
sdkId,
|
|
version,
|
|
link,
|
|
section,
|
|
useTypeSpec,
|
|
}: FunctionSectionProps) {
|
|
const fns = await getFunctionsList(sdkId, version)
|
|
|
|
const fn = fns?.find((fn) => fn.id === section.id)
|
|
if (!fn) return null
|
|
|
|
let types: MethodTypes | undefined
|
|
if (useTypeSpec && '$ref' in fn) {
|
|
types = await getTypeSpec(fn['$ref'] as string)
|
|
}
|
|
|
|
const fullDescription = [
|
|
types?.comment?.shortText,
|
|
'description' in fn && (fn.description as string),
|
|
'notes' in fn && (fn.notes as string),
|
|
]
|
|
.filter(Boolean)
|
|
.map(normalizeMarkdown)
|
|
.join('\n\n')
|
|
|
|
return (
|
|
<RefSubLayout.Section columns="double" link={link} {...section}>
|
|
<StickyHeader {...section} className="col-[1_/_-1]" />
|
|
<div className="overflow-hidden flex flex-col gap-8">
|
|
<div className="prose break-words text-sm">
|
|
<MDXRemoteRefs source={fullDescription} />
|
|
</div>
|
|
<FnParameterDetails
|
|
parameters={
|
|
'overwriteParams' in fn
|
|
? (fn.overwriteParams as Array<object>).map((overwrittenParams) => ({
|
|
...overwrittenParams,
|
|
__overwritten: true,
|
|
}))
|
|
: 'params' in fn
|
|
? (fn.params as Array<object>).map((param) => ({ ...param, __overwritten: true }))
|
|
: types?.params
|
|
}
|
|
altParameters={types?.altSignatures?.map(({ params }) => params)}
|
|
className="max-w-[80ch]"
|
|
/>
|
|
{!!types?.ret && <ReturnTypeDetails returnType={types.ret} />}
|
|
</div>
|
|
<div className="overflow-auto">
|
|
{'examples' in fn && Array.isArray(fn.examples) && fn.examples.length > 0 && (
|
|
<Tabs_Shadcn_ defaultValue={fn.examples[0].id}>
|
|
<TabsList_Shadcn_ className="flex-wrap gap-2 border-0">
|
|
{fn.examples.map((example) => (
|
|
<TabsTrigger_Shadcn_
|
|
key={example.id}
|
|
value={example.id}
|
|
className={cn(
|
|
'px-2.5 py-1 rounded-full',
|
|
'border-0 bg-surface-200 hover:bg-surface-300',
|
|
'text-xs text-foreground-lighter',
|
|
// Undoing styles from primitive component
|
|
'data-[state=active]:border-0 data-[state=active]:shadow-0',
|
|
'data-[state=active]:bg-foreground data-[state=active]:text-background',
|
|
'transition'
|
|
)}
|
|
>
|
|
{example.name}
|
|
</TabsTrigger_Shadcn_>
|
|
))}
|
|
</TabsList_Shadcn_>
|
|
{fn.examples.map((example) => (
|
|
<TabsContent_Shadcn_ key={example.id} value={example.id}>
|
|
<MDXRemoteRefs source={example.code} />
|
|
<div className="flex flex-col gap-2 mt-2">
|
|
{!!example.data?.sql && (
|
|
<CollapsibleDetails title="Data source" content={example.data.sql} />
|
|
)}
|
|
{!!example.response && (
|
|
<CollapsibleDetails title="Response" content={example.response} />
|
|
)}
|
|
{!!example.description && (
|
|
<CollapsibleDetails
|
|
title="Notes"
|
|
content={normalizeMarkdown(example.description)}
|
|
/>
|
|
)}
|
|
</div>
|
|
</TabsContent_Shadcn_>
|
|
))}
|
|
</Tabs_Shadcn_>
|
|
)}
|
|
</div>
|
|
</RefSubLayout.Section>
|
|
)
|
|
}
|
|
|
|
export { RefSections }
|