Refactor/app router refs (#28095)

Migrates client SDK References to App Router. (Management and CLI API references aren't migrated yet, nor are self-hosting config references.)

Some notes:

Big changes to the way crawler pages are built and individual section URLs (e.g., javascript/select) are served. All of these used to be SSG-generated pages, but the number of heavy pages was just too much to handle -- slow as molasses and my laptop sounded like it was taking off, and CI sometimes refuses to build it all at all.

Tried various tricks with caching and pre-generating data but no dice.

So I changed to only building one copy of each SDK+version page, then serving the sub-URLs through a response rewrite. That's for the actual user-visible pages.

For the bot pages, each sub-URL needs to be its own page, but prebuilding it doesn't work, and rendering on demand from React components is too slow (looking for super-fast response here for SEO). Instead I changed to using an API route that serves very minimal, hand-crafted HTML. It looks ugly, but it's purely for the search bots.

You can test what bots see by running curl --user-agent "Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5376e Safari/8536.25 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" <URL_OF_PAGE>

Also added some smoke tests to run against prod for the crawler routes, since we don't keep an eye on those regularly, and Vercel config changes could surprise-break them. Tested the meta images on Open Graph and all seems to work fine.

With this approach, full production builds are really fast: ~5 minutes

Starts using the new type spec handling, which is better at finding params automatically, so I could remove some of the manually written ones from the spec files.
This commit is contained in:
Charis
2024-08-13 16:12:59 -04:00
committed by GitHub
parent d1b5c82ea8
commit fc164b5d07
98 changed files with 6729 additions and 2382 deletions

35
.github/workflows/docs-tests-smoke.yml vendored Normal file
View File

@@ -0,0 +1,35 @@
# Smoke tests against the docs production site, run daily
name: Docs Production Smoke Tests
on:
workflow_dispatch:
schedule:
- cron: '0 4 * * *'
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: |
apps/docs
packages
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Install deps
run: npm ci
- name: Run tests
run: npm --prefix="apps/docs" run test:smoke

View File

@@ -0,0 +1,72 @@
import { load } from 'cheerio'
import { describe, expect, it } from 'vitest'
const REFERENCE_DOCS_URL = 'https://supabase.com/docs/reference'
// For dev testing: comment out above and uncomment below
// const REFERENCE_DOCS_URL = 'http://localhost:3001/docs/reference'
describe('prod smoke test: crawler pages return correct data', () => {
/**
* No special tricks required to spoof the user agent. Tests are correctly
* detected as coming from bots. If they ever aren't, the `h1` test will fail
* as a different `h1` is served to non-bots.
*/
it('metadata: title, description, canonical, image', async () => {
const result = await fetch(REFERENCE_DOCS_URL + '/javascript/rangelte')
const text = await result.text()
const $ = load(text)
const title = $('title').text()
expect(title).toBe('JavaScript: Less than or equal to a range | Supabase Docs')
const metaDescription = $('meta[name="description"]')
expect(metaDescription.attr('content')).toBe(
'Supabase API reference for JavaScript: Less than or equal to a range'
)
const canonical = $('link[rel="canonical"]')
expect(canonical.attr('href')).toBe('https://supabase.com/docs/reference/javascript/rangelte')
const ogImage = $('meta[name="og:image"]')
expect(ogImage.attr('content')).toBe('https://supabase.com/docs/img/supabase-og-image.png')
const twitterImage = $('meta[name="twitter:image"]')
expect(twitterImage.attr('content')).toBe('https://supabase.com/docs/img/supabase-og-image.png')
})
it('markdown pages', async () => {
const result = await fetch(REFERENCE_DOCS_URL + '/javascript/introduction')
const text = await result.text()
const $ = load(text)
const h1 = $('h1').text()
expect(h1).toBe('JavaScript: Introduction')
const firstPara = $('h1').next().text()
expect(/JavaScript library/.test(firstPara)).toBe(true)
expect(/supabase-js/.test(firstPara)).toBe(true)
})
it('function pages', async () => {
const result = await fetch(REFERENCE_DOCS_URL + '/javascript/rangelte')
const text = await result.text()
const $ = load(text)
const h1 = $('h1').text()
expect(h1).toBe('JavaScript: Less than or equal to a range')
const description = $('h1').next().text()
expect(description).toBe(
'Only relevant for range columns. Match only rows where every element in column is either contained in range or less than any element in range.'
)
const headings = [] as Array<string | undefined>
$('h2').map(function () {
headings.push($(this).attr('id'))
})
expect(headings.includes('parameters')).toBe(true)
expect(headings.includes('examples')).toBe(true)
})
})

View File

@@ -0,0 +1,237 @@
import { toHtml } from 'hast-util-to-html'
import { fromMarkdown } from 'mdast-util-from-markdown'
import { mdxFromMarkdown } from 'mdast-util-mdx'
import { toHast } from 'mdast-util-to-hast'
import { mdxjs } from 'micromark-extension-mdxjs'
import { redirect } from 'next/navigation'
import { visit } from 'unist-util-visit'
import { REFERENCES } from '~/content/navigation.references'
import {
getFlattenedSections,
getFunctionsList,
getTypeSpec,
} from '~/features/docs/Reference.generated.singleton'
import { getRefMarkdown } from '~/features/docs/Reference.mdx'
import type { MethodTypes } from '~/features/docs/Reference.typeSpec'
import type { AbbrevCommonClientLibSection } from '~/features/docs/Reference.utils'
import { notFoundLink } from '~/features/recommendations/NotFound.utils'
import { BASE_PATH } from '~/lib/constants'
export async function GET(request: Request) {
const url = new URL(request.url)
let [, , lib, maybeVersion, slug] = url.pathname.split('/')
const libraryMeta = REFERENCES[lib]
const isVersion = /^v\d+$/.test(maybeVersion)
const version = isVersion ? maybeVersion : libraryMeta.versions[0]
if (!isVersion) {
slug = maybeVersion
}
const flattenedSections = await getFlattenedSections(lib, version)
const sectionsWithUrl: Array<AbbrevCommonClientLibSection & { url: URL }> =
flattenedSections!.map((section) => {
const url = new URL(request.url)
url.pathname = [BASE_PATH, 'reference', lib, isVersion ? version : null, section.slug]
.filter(Boolean)
.join('/')
return {
...section,
url,
}
})
const section = flattenedSections!.find(
(section) =>
(section.type === 'markdown' || section.type === 'function') && section.slug === slug
)
if (!section) {
redirect(notFoundLink(`${lib}/${slug}`))
}
const html = htmlShell(
lib,
isVersion ? version : null,
slug,
section,
libraryNav(sectionsWithUrl) + (await sectionDetails(lib, isVersion ? version : null, section))
)
const response = new Response(html)
response.headers.set('Content-Type', 'text/html; charset=utf-8')
return response
}
function htmlShell(
lib: string,
version: string | null,
slug: string,
section: AbbrevCommonClientLibSection,
body: string
) {
const libraryName = REFERENCES[lib].name
let title = libraryName + ': ' + section.title ?? ''
return (
'<!doctype html><html>' +
'<head>' +
`<title>${title} | Supabase Docs</title>` +
`<meta name="description" content="Supabase API reference for ${libraryName}${section.title ? ': ' + section.title : ''}">` +
`<meta name="og:image" content="https://supabase.com/docs/img/supabase-og-image.png">` +
`<meta name="twitter:image" content="https://supabase.com/docs/img/supabase-og-image.png">` +
`<link rel="canonical" href="https://supabase.com/docs/reference/${lib}` +
(version ? '/' + version : '') +
(slug ? '/' + slug : '') +
`">` +
'</head>' +
'<body>' +
body +
'</body></html>'
)
}
function libraryNav(sections: Array<AbbrevCommonClientLibSection & { url: URL }>) {
return (
'<nav><ul>' +
sections
.map((section) => `<li><a href="${section.url}">${section.title ?? ''}</a></li>`)
.join('') +
'</ul></nav>'
)
}
async function sectionDetails(lib: string, version: string, section: AbbrevCommonClientLibSection) {
const libraryName = REFERENCES[lib].name
let result = '<h1>' + (libraryName + ': ' + section.title ?? '') + '</h1>'
if (section.type === 'markdown') {
result += await markdown(lib, version, section)
} else {
result += await functionDetails(lib, version, section)
}
return result
}
async function markdown(
lib: string,
version: string | null,
section: AbbrevCommonClientLibSection
) {
const dir = !!section.meta?.shared ? 'shared' : lib + (version ? '/' + version : '')
let content = await getRefMarkdown(dir + '/' + section.slug)
content = mdxToHtml(content)
return content
}
async function functionDetails(
lib: string,
version: string | null,
section: AbbrevCommonClientLibSection
) {
const libraryMeta = REFERENCES[lib]
const fns = await getFunctionsList(lib, version ?? libraryMeta.versions[0])
const fn = fns!.find((fn) => fn.id === section.id)
if (!fn) return ''
let types: MethodTypes | undefined
if (libraryMeta.typeSpec && '$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((x) => typeof x === 'string')
.map(mdxToHtml)
.join('')
const parameters = parametersToHtml(fn, types)
const examples = examplesToHtml(fn)
return fullDescription + parameters + examples
}
function mdxToHtml(markdownUnescaped: string): string {
const markdown = markdownUnescaped.replace(/(?<!\\)\{/g, '\\{').replace(/(?<!\\)\}/g, '\\}')
const mdast = fromMarkdown(markdown, {
extensions: [mdxjs()],
mdastExtensions: [mdxFromMarkdown()],
})
visit(mdast, 'text', (node) => {
node.value = node.value.replace(/\n/g, ' ')
})
if (!mdast) return ''
const hast = toHast(mdast)
if (!hast) return ''
// @ts-ignore
const html = toHtml(hast)
return html
}
function parametersToHtml(fn: any, types: MethodTypes | undefined) {
let result = '<h2 id="parameters">Parameters</h2>'
if ('overwriteParams' in fn || 'params' in fn) {
const params = fn.overwriteParams ?? fn.params
if (params.length === 0) return ''
result +=
'<ul>' +
params
.map(
(param) =>
'<li>' +
`<h3>${param.name}</h3>` +
`<span>${param.isOptional ? '(Optional)' : '(Required)'}</span>` +
`<p>${param.description}</p>` +
'</li>'
)
.join('') +
'</ul>'
return result
}
if (!types?.params || types.params.length === 0) return ''
result +=
'<ul>' +
types.params
.map(
(param) =>
'<li>' +
`<h3>${String(param.name)}</h3>` +
`<span>${param.isOptional ? '(Optional)' : '(Required)'}</span>` +
`<p>${param.comment?.shortText ?? ''}</p>` +
'</li>'
)
.join('') +
'</ul>'
return result
}
function examplesToHtml(fn: any) {
if (!fn.examples || fn.examples.length === 0) return ''
let result = '<h2 id="examples">Examples</h2>'
result += fn.examples
.map((example) => `<h3>${example.name ?? ''}</h3>` + mdxToHtml(example.code ?? ''))
.join('')
return result
}

View File

@@ -0,0 +1,42 @@
import { redirect } from 'next/navigation'
import { REFERENCES } from '~/content/navigation.references'
import { ClientSdkReferencePage } from '~/features/docs/Reference.sdkPage'
import {
generateReferenceMetadata,
generateReferenceStaticParams,
parseReferencePath,
redirectNonexistentReferenceSection,
} from '~/features/docs/Reference.utils'
import { notFoundLink } from '~/features/recommendations/NotFound.utils'
export default async function ReferencePage({
params: { slug },
}: {
params: { slug: Array<string> }
}) {
if (!Object.keys(REFERENCES).includes(slug[0])) {
redirect(notFoundLink(slug.join('/')))
}
const parsedPath = parseReferencePath(slug)
const isClientSdkReference = parsedPath.__type === 'clientSdk'
if (isClientSdkReference) {
const { sdkId, maybeVersion, path } = parsedPath
const sdkData = REFERENCES[sdkId]
const latestVersion = sdkData.versions[0]
const version = maybeVersion ?? latestVersion
await redirectNonexistentReferenceSection(sdkId, version, path, version === latestVersion)
return <ClientSdkReferencePage sdkId={sdkId} libVersion={version} />
} else {
// Unimplemented -- eventually API and CLI
redirect(notFoundLink(slug.join('/')))
}
}
export const generateStaticParams = generateReferenceStaticParams
export const generateMetadata = generateReferenceMetadata

View File

@@ -0,0 +1,62 @@
import Link from 'next/link'
import { REFERENCES, clientSdkIds } from '~/content/navigation.references'
import { IconPanelWithIconPicker } from '~/features/ui/IconPanelWithIconPicker'
import { LayoutMainContent } from '~/layouts/DefaultLayout'
import { SidebarSkeleton } from '~/layouts/MainSkeleton'
export default function ReferenceIndexPage() {
return (
<SidebarSkeleton>
<LayoutMainContent>
<article className="prose">
<h1>API References</h1>
<p>
The Supabase client libraries help you interact with Supabase products, such as the
Postgres Database, Auth, and Realtime. They are available in several popular programming
languages.
</p>
<p>
Supabase also has a Management API to help with managing your Supabase Platform, and a
CLI for local development and CI workflows.
</p>
<h2 className="mb-8">Client Libraries</h2>
<div className="grid col-span-8 grid-cols-12 gap-6 not-prose">
{clientSdkIds.map((sdkId) => {
return (
<Link
key={REFERENCES[sdkId].name}
href={`/reference/${REFERENCES[sdkId].libPath}`}
passHref
className="col-span-6 md:col-span-4"
>
<IconPanelWithIconPicker
title={REFERENCES[sdkId].name}
icon={REFERENCES[sdkId].icon}
/>
</Link>
)
})}
</div>
<h2 className="mb-8">Management API and CLI</h2>
<div className="grid col-span-8 grid-cols-12 gap-6 not-prose">
<Link
href={`/reference/api/introduction`}
passHref
className="col-span-6 md:col-span-4"
>
<IconPanelWithIconPicker title="Management API" icon={REFERENCES['api'].icon} />
</Link>
<Link
href={`/reference/cli/introduction`}
passHref
className="col-span-6 md:col-span-4"
>
<IconPanelWithIconPicker title="CLI" icon={REFERENCES['cli'].icon} />
</Link>
</div>
</article>
</LayoutMainContent>
</SidebarSkeleton>
)
}

View File

@@ -1,8 +1,10 @@
'use client'
import React, { Fragment } from 'react'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import React, { Fragment } from 'react'
import { useBreakpoint } from 'common'
import {
Breadcrumb_Shadcn_ as Breadcrumb,
BreadcrumbList_Shadcn_ as BreadcrumbList,
@@ -23,9 +25,9 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from 'ui'
import * as NavItems from '~/components/Navigation/NavigationMenu/NavigationMenu.constants'
import { getMenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu.utils'
import { useBreakpoint } from 'common'
import * as NavItems from './Navigation/NavigationMenu/NavigationMenu.constants'
const Breadcrumbs = ({ className }: { className?: string }) => {
const pathname = usePathname()

View File

@@ -1,5 +1,6 @@
'use client'
import AuthErrorCodesTable from './auth_error_codes_table.mdx'
import AuthRateLimits from './auth_rate_limits.mdx'
import CreateClientSnippet from './create_client_snippet.mdx'
import DatabaseSetup from './database_setup.mdx'
@@ -15,6 +16,7 @@ import SocialProviderSettingsSupabase from './social_provider_settings_supabase.
import SocialProviderSetup from './social_provider_setup.mdx'
export {
AuthErrorCodesTable,
AuthRateLimits,
CreateClientSnippet,
DatabaseSetup,

View File

@@ -13,16 +13,6 @@ export interface NavMenuSection {
items: Partial<NavMenuSection>[]
}
export interface References {
[key: string]: {
name: string
library?: string
versions: string[]
icon: string
currentVersion?: string
}
}
type MenuItem = {
label: string
icon?: string

View File

@@ -1,7 +1,6 @@
'use client'
import React from 'react'
import {
IconBranching,
IconGitHub,
IconMenuApi,
IconMenuAuth,
IconMenuCli,
@@ -26,10 +25,8 @@ import {
IconMenuKotlin,
IconMenuAI,
IconMenuDevCli,
IconGitHub,
IconSupport,
IconTroubleshooting,
IconBranching,
} from './MenuIcons'
function getMenuIcon(menuKey: string, width: number = 16, height: number = 16, className?: string) {

View File

@@ -1,5 +1,4 @@
import { IS_DEV } from '~/lib/constants'
import type { GlobalMenuItems, NavMenuConstant, References } from '../Navigation.types'
import type { GlobalMenuItems, NavMenuConstant } from '../Navigation.types'
export const GLOBAL_MENU_ITEMS: GlobalMenuItems = [
[
@@ -100,38 +99,38 @@ export const GLOBAL_MENU_ITEMS: GlobalMenuItems = [
{
label: 'JavaScript',
icon: 'reference-javascript',
href: '/reference/javascript/introduction',
href: '/reference/javascript',
level: 'reference_javascript',
},
{
label: 'Flutter',
icon: 'reference-dart',
href: '/reference/dart/introduction',
href: '/reference/dart',
level: 'reference_dart',
},
{
label: 'Swift',
icon: 'reference-swift',
href: '/reference/swift/introduction',
href: '/reference/swift',
level: 'reference_swift',
},
{
label: 'Python',
icon: 'reference-python',
href: '/reference/python/introduction',
href: '/reference/python',
level: 'reference_python',
},
{
label: 'C#',
icon: 'reference-csharp',
href: '/reference/csharp/introduction',
href: '/reference/csharp',
level: 'reference_csharp',
community: true,
},
{
label: 'Kotlin',
icon: 'reference-kotlin',
href: '/reference/kotlin/introduction',
href: '/reference/kotlin',
level: 'reference_kotlin',
community: true,
},
@@ -202,51 +201,6 @@ export const GLOBAL_MENU_ITEMS: GlobalMenuItems = [
],
]
export const REFERENCES: References = {
javascript: {
name: 'supabase-js',
library: 'supabase-js',
versions: ['v2', 'v1'],
icon: '/img/libraries/javascript-icon',
},
dart: {
name: 'Flutter',
library: 'supabase-dart',
versions: ['v2', 'v1'],
icon: '/docs/img/libraries/flutter-icon.svg',
},
csharp: {
name: 'C#',
library: 'supabase-csharp',
versions: ['v1', 'v0'],
icon: '/docs/img/libraries/c-sharp-icon.svg',
},
swift: {
name: 'Swift',
library: 'supabase-swift',
versions: ['v2', 'v1'],
icon: '/docs/img/libraries/swift-icon.svg',
},
kotlin: {
name: 'Kotlin',
library: 'supabase-kt',
versions: ['v2', 'v1'],
icon: '/docs/img/libraries/kotlin-icon.svg',
},
cli: {
name: 'CLI',
library: undefined,
versions: [],
icon: '/docs/img/icons/cli-icon.svg',
},
api: {
name: 'API',
library: undefined,
versions: [],
icon: '/docs/img/icons/api-icon.svg',
},
}
export const gettingstarted: NavMenuConstant = {
icon: 'getting-started',
title: 'Start with Supabase',
@@ -2123,13 +2077,6 @@ export const reference = {
items: [],
icon: '/img/icons/menu/reference-kotlin',
},
// {
// name: 'supabase-python',
// url: '/reference/python/start',
// level: 'reference_python',
//
// icon: '/img/icons/menu/reference-javascript',
// },
],
},
{
@@ -2155,6 +2102,10 @@ export const reference_javascript_v1 = {
title: 'JavaScript',
url: '/guides/reference/javascript',
parent: '/reference',
pkg: {
name: '@supabase/supabase-js',
repo: 'https://github.com/supabase/supabase-js',
},
}
export const reference_javascript_v2 = {
@@ -2162,6 +2113,10 @@ export const reference_javascript_v2 = {
title: 'JavaScript',
url: '/guides/reference/javascript',
parent: '/reference',
pkg: {
name: '@supabase/supabase-js',
repo: 'https://github.com/supabase/supabase-js',
},
}
export const reference_dart_v1 = {
@@ -2169,6 +2124,10 @@ export const reference_dart_v1 = {
title: 'Flutter',
url: '/guides/reference/dart',
parent: '/reference',
pkg: {
name: 'supabase_flutter',
repo: 'https://github.com/supabase/supabase-flutter',
},
}
export const reference_dart_v2 = {
@@ -2176,6 +2135,10 @@ export const reference_dart_v2 = {
title: 'Flutter',
url: '/guides/reference/dart',
parent: '/reference',
pkg: {
name: 'supabase_flutter',
repo: 'https://github.com/supabase/supabase-flutter',
},
}
export const reference_csharp_v0 = {
@@ -2183,6 +2146,10 @@ export const reference_csharp_v0 = {
title: 'C#',
url: 'guides/reference/csharp',
parent: '/reference',
pkg: {
name: 'supabase',
repo: 'https://github.com/supabase-community/supabase-csharp',
},
}
export const reference_csharp_v1 = {
@@ -2190,6 +2157,10 @@ export const reference_csharp_v1 = {
title: 'C#',
url: 'guides/reference/csharp',
parent: '/reference',
pkg: {
name: 'supabase',
repo: 'https://github.com/supabase-community/supabase-csharp',
},
}
export const reference_python_v2 = {
@@ -2197,6 +2168,10 @@ export const reference_python_v2 = {
title: 'Python',
url: '/guides/reference/python',
parent: '/reference',
pkg: {
name: 'supabase-py',
repo: 'https://github.com/supabase/supabase-py',
},
}
export const reference_swift_v1 = {
@@ -2204,6 +2179,10 @@ export const reference_swift_v1 = {
title: 'swift',
url: 'guides/reference/swift',
parent: '/reference',
pkg: {
name: 'supabase-swift',
repo: 'https://github.com/supabase/supabase-swift',
},
}
export const reference_swift_v2 = {
@@ -2211,6 +2190,10 @@ export const reference_swift_v2 = {
title: 'swift',
url: 'guides/reference/swift',
parent: '/reference',
pkg: {
name: 'supabase-swift',
repo: 'https://github.com/supabase/supabase-swift',
},
}
export const reference_kotlin_v1 = {
@@ -2218,6 +2201,10 @@ export const reference_kotlin_v1 = {
title: 'kotlin',
url: 'guides/reference/kotlin',
parent: '/reference',
pkg: {
name: '@supabase-community/supabase-kt',
repo: 'https://github.com/supabase-community/supabase-kt',
},
}
export const reference_kotlin_v2 = {
@@ -2225,6 +2212,10 @@ export const reference_kotlin_v2 = {
title: 'kotlin',
url: 'guides/reference/kotlin',
parent: '/reference',
pkg: {
name: '@supabase-community/supabase-kt',
repo: 'https://github.com/supabase-community/supabase-kt',
},
}
export const reference_cli = {

View File

@@ -1,4 +1,5 @@
import { memo } from 'react'
import NavigationMenuGuideList from './NavigationMenuGuideList'
import NavigationMenuRefList from './NavigationMenuRefList'
import { useCloseMenuOnRouteChange } from './NavigationMenu.utils'
@@ -50,8 +51,7 @@ interface GuideMenu extends BaseMenu {
interface ReferenceMenu extends BaseMenu {
type: 'reference'
path: string
commonSectionsFile: string
specFile?: string
commonSectionsFile?: string
}
type Menu = GuideMenu | ReferenceMenu
@@ -115,133 +115,109 @@ const menus: Menu[] = [
},
{
id: MenuId.RefJavaScriptV1,
commonSectionsFile: 'common-client-libs-sections.json',
specFile: 'supabase_js_v1.yml',
type: 'reference',
path: '/reference/javascript/v1',
},
{
id: MenuId.RefJavaScriptV2,
commonSectionsFile: 'common-client-libs-sections.json',
specFile: 'supabase_js_v2.yml',
type: 'reference',
path: '/reference/javascript',
},
{
id: MenuId.RefDartV1,
commonSectionsFile: 'common-client-libs-sections.json',
specFile: 'supabase_dart_v1.yml',
type: 'reference',
path: '/reference/dart/v1',
},
{
id: MenuId.RefDartV2,
commonSectionsFile: 'common-client-libs-sections.json',
specFile: 'supabase_dart_v2.yml',
type: 'reference',
path: '/reference/dart',
},
{
id: MenuId.RefCSharpV0,
commonSectionsFile: 'common-client-libs-sections.json',
specFile: 'supabase_csharp_v0.yml',
type: 'reference',
path: '/reference/csharp/v0',
},
{
id: MenuId.RefCSharpV1,
commonSectionsFile: 'common-client-libs-sections.json',
specFile: 'supabase_csharp_v1.yml',
type: 'reference',
path: '/reference/csharp',
},
{
id: MenuId.RefPythonV2,
commonSectionsFile: 'common-client-libs-sections.json',
specFile: 'supabase_py_v2.yml',
type: 'reference',
path: '/reference/python',
},
{
id: MenuId.RefSwiftV1,
commonSectionsFile: 'common-client-libs-sections.json',
specFile: 'supabase_swift_v1.yml',
type: 'reference',
path: '/reference/swift',
},
{
id: MenuId.RefSwiftV2,
commonSectionsFile: 'common-client-libs-sections.json',
specFile: 'supabase_swift_v2.yml',
type: 'reference',
path: '/reference/swift',
},
{
id: MenuId.RefKotlinV1,
commonSectionsFile: 'common-client-libs-sections.json',
specFile: 'supabase_kt_v1.yml',
type: 'reference',
path: '/reference/kotlin/v1',
},
{
id: MenuId.RefKotlinV2,
commonSectionsFile: 'common-client-libs-sections.json',
specFile: 'supabase_kt_v2.yml',
type: 'reference',
path: '/reference/kotlin',
},
{
id: MenuId.RefCli,
commonSectionsFile: 'common-cli-sections.json',
type: 'reference',
path: '/reference/cli',
commonSectionsFile: 'common-cli-sections.json',
},
{
id: MenuId.RefApi,
commonSectionsFile: 'common-api-sections.json',
type: 'reference',
path: '/reference/api',
commonSectionsFile: 'common-api-sections.json',
},
{
id: MenuId.SelfHostingAuth,
commonSectionsFile: 'common-self-hosting-auth-sections.json',
type: 'reference',
path: '/reference/self-hosting-auth',
commonSectionsFile: 'common-self-hosting-auth-sections.json',
},
{
id: MenuId.SelfHostingStorage,
commonSectionsFile: 'common-self-hosting-storage-sections.json',
type: 'reference',
path: '/reference/self-hosting-storage',
commonSectionsFile: 'common-self-hosting-storage-sections.json',
},
{
id: MenuId.SelfHostingRealtime,
commonSectionsFile: 'common-self-hosting-realtime-sections.json',
type: 'reference',
path: '/reference/self-hosting-realtime',
commonSectionsFile: 'common-self-hosting-realtime-sections.json',
},
{
id: MenuId.SelfHostingAnalytics,
commonSectionsFile: 'common-self-hosting-analytics-sections.json',
type: 'reference',
path: '/reference/self-hosting-analytics',
commonSectionsFile: 'common-self-hosting-analytics-sections.json',
},
{
id: MenuId.SelfHostingFunctions,
commonSectionsFile: 'common-self-hosting-functions-sections.json',
type: 'reference',
path: '/reference/self-hosting-functions',
commonSectionsFile: 'common-self-hosting-functions-sections.json',
},
]
export function getMenuById(id: MenuId) {
function getMenuById(id: MenuId) {
return menus.find((menu) => menu.id === id)
}
function getMenuElement(menu: Menu | undefined) {
if (!menu) throw Error('No menu found for this menuId')
const menuType = menu.type
const menuType = menu?.type
switch (menuType) {
case 'guide':
return <NavigationMenuGuideList id={menu.id} />
@@ -251,7 +227,6 @@ function getMenuElement(menu: Menu | undefined) {
id={menu.id}
basePath={menu.path}
commonSectionsFile={menu.commonSectionsFile}
specFile={menu.specFile}
/>
)
default:
@@ -268,5 +243,5 @@ const NavigationMenu = ({ menuId }: { menuId: MenuId }) => {
return getMenuElement(menu)
}
export { MenuId }
export { MenuId, getMenuById }
export default memo(NavigationMenu)

View File

@@ -4,7 +4,7 @@ import { useEffect, useState } from 'react'
import { usePathname } from 'next/navigation'
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
import type { ICommonItem } from '~/components/reference/Reference.types'
import type { Json } from '~/types'
import type { Json } from '~/features/helpers.types'
import { menuState } from '../../../hooks/useMenuState'
export function getPathWithoutHash(relativePath: string) {
@@ -99,14 +99,6 @@ export function useSpec(specFile?: string) {
return spec
}
export const useCloseMenuOnRouteChange = () => {
const pathname = usePathname()
useEffect(() => {
menuState.setMenuMobileOpen(false)
}, [pathname])
}
export const getMenuId = (pathname: string | null) => {
pathname = (pathname ??= '').replace(/^\/guides\//, '')
@@ -141,3 +133,11 @@ export const getMenuId = (pathname: string | null) => {
return MenuId.GettingStarted
}
}
export const useCloseMenuOnRouteChange = () => {
const pathname = usePathname()
useEffect(() => {
menuState.setMenuMobileOpen(false)
}, [pathname])
}

View File

@@ -28,12 +28,6 @@ const NavigationMenuRefList = ({
}
const filteredSections = commonSections.filter((section) => {
if (section.type === 'category') {
section.items = section.items.filter((item) => {
return !item.excludes?.includes(id)
})
}
return !section.excludes?.includes(id)
})

View File

@@ -185,7 +185,6 @@ const NavigationMenuRefListItems = ({
<div className="flex items-center gap-3 my-3">
<MenuIconPicker icon={menu.icon} width={21} height={21} />
<HeaderLink title={menu.title} url={menu.url} id={id} />
<RevVersionDropdown />
</div>
<ul className="function-link-list flex flex-col gap-2 pb-5">
{filteredSections.map((section) => {

View File

@@ -2,7 +2,7 @@ import Link from 'next/link'
import Image from 'next/legacy/image'
import { usePathname } from 'next/navigation'
import { IconChevronRight, IconArrowLeft } from '~/../../packages/ui'
import { REFERENCES } from './NavigationMenu/NavigationMenu.constants'
import { REFERENCES } from '~/content/navigation.references'
import { NavMenuGroup, NavMenuSection } from './Navigation.types'
import * as Accordion from '@radix-ui/react-accordion'

View File

@@ -1,4 +1,8 @@
import { usePathname, useRouter } from 'next/navigation'
'use client'
import { ChevronDown } from 'lucide-react'
import { useRouter } from 'next/navigation'
import {
Badge,
DropdownMenu,
@@ -6,34 +10,32 @@ import {
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuTrigger,
IconChevronDown,
} from 'ui'
import { REFERENCES } from './Navigation/NavigationMenu/NavigationMenu.constants'
const RevVersionDropdown = () => {
const pathname = usePathname()
import { REFERENCES } from '~/content/navigation.references'
const RevVersionDropdown = ({
library,
currentVersion,
}: {
library: string
currentVersion: string
}) => {
const { push } = useRouter()
const pathSegments = pathname.split('/')
const library = pathSegments.length >= 3 ? pathSegments[2] : undefined
const libraryMeta = REFERENCES?.[library] ?? undefined
const versions = libraryMeta?.versions ?? []
const currentVersion = versions.includes(pathSegments[pathSegments.indexOf(library) + 1])
? pathSegments[pathSegments.indexOf(library) + 1]
: versions[0]
const onSelectVersion = (version: string) => {
if (!library) return
if (version === versions[0]) {
push(`/reference/${library}/start`)
} else {
push(`/reference/${library}/${version}/start`)
}
if (!versions || versions.length <= 1) {
return null
}
if (!versions || versions.length === 0) {
return <></>
const onSelectVersion = (version: string) => {
if (version === versions[0]) {
push(`/reference/${library}`)
} else {
push(`/reference/${library}/${version}`)
}
}
return (
@@ -52,11 +54,10 @@ const RevVersionDropdown = () => {
flex items-center gap-1 text-foreground-muted text-xs group-hover:text-foreground transition
"
>
{/* <span>version</span> */}
<span className="text-foreground text-sm group-hover:text-foreground transition">
{currentVersion}.0
</span>
<IconChevronDown size={14} strokeWidth={2} />
<ChevronDown size={14} strokeWidth={2} />
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" side="bottom" className="w-48">

View File

@@ -0,0 +1,123 @@
export const REFERENCES = {
javascript: {
type: 'sdk',
name: 'JavaScript',
library: 'supabase-js',
libPath: 'javascript',
versions: ['v2', 'v1'],
typeSpec: true,
icon: 'reference-javascript',
meta: {
v2: {
libId: 'reference_javascript_v2',
specFile: 'supabase_js_v2',
},
v1: {
libId: 'reference_javascript_v1',
specFile: 'supabase_js_v1',
},
},
},
dart: {
type: 'sdk',
name: 'Flutter',
library: 'supabase-dart',
libPath: 'dart',
versions: ['v2', 'v1'],
icon: 'reference-dart',
meta: {
v2: {
libId: 'reference_dart_v2',
specFile: 'supabase_dart_v2',
},
v1: {
libId: 'reference_dart_v1',
specFile: 'supabase_dart_v1',
},
},
},
csharp: {
type: 'sdk',
name: 'C#',
library: 'supabase-csharp',
libPath: 'csharp',
versions: ['v1', 'v0'],
icon: 'reference-csharp',
meta: {
v1: {
libId: 'reference_csharp_v1',
specFile: 'supabase_csharp_v1',
},
v0: {
libId: 'reference_csharp_v0',
specFile: 'supabase_csharp_v0',
},
},
},
swift: {
type: 'sdk',
name: 'Swift',
library: 'supabase-swift',
libPath: 'swift',
versions: ['v2', 'v1'],
icon: 'reference-swift',
meta: {
v2: {
libId: 'reference_swift_v2',
specFile: 'supabase_swift_v2',
},
v1: {
libId: 'reference_swift_v1',
specFile: 'supabase_swift_v1',
},
},
},
kotlin: {
type: 'sdk',
name: 'Kotlin',
library: 'supabase-kt',
libPath: 'kotlin',
versions: ['v2', 'v1'],
icon: 'reference-kotlin',
meta: {
v2: {
libId: 'reference_kotlin_v2',
specFile: 'supabase_kt_v2',
},
v1: {
libId: 'reference_kotlin_v1',
specFile: 'supabase_kt_v1',
},
},
},
python: {
type: 'sdk',
name: 'Python',
library: 'supabase-py',
libPath: 'python',
versions: ['v2'],
icon: 'reference-python',
meta: {
v2: {
libId: 'reference_python_v2',
specFile: 'supabase_py_v2',
},
},
},
cli: {
type: 'cli',
name: 'CLI',
versions: [],
icon: 'reference-cli',
},
api: {
type: 'api',
name: 'API',
versions: [],
icon: 'reference-api',
},
} as const
export const clientSdkIds = Object.keys(REFERENCES).filter(
(reference) => REFERENCES[reference].type === 'sdk'
)

View File

@@ -1,29 +1,9 @@
---
id: introduction
title: Introduction
hideTitle: true
---
This reference documents every object and method available in Supabase's C# library, [supabase](https://www.nuget.org/packages/supabase). You can use `Supabase` to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
<div className="flex items-start gap-6 not-prose" id="introduction">
<IconMenuCsharp width={35} height={35} />
<div className="flex flex-col gap-2">
<h1 className="text-3xl text-foreground m-0">C# Client Library</h1>
<h2 className="text-base font-mono text-foreground-light">
@supabase-community/supabase-csharp
</h2>
</div>
</div>
<Admonition type="note">
{/* prettier-ignore */}
<div className="max-w-xl">
This reference documents every object and method available in Supabase's C# library, [supabase](https://www.nuget.org/packages/supabase). You can use `Supabase` to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
</div>
The C# client library is created and maintained by the Supabase community, and is not an official library. Please be tolerant of areas where the library is still being developed, and — as with all the libraries — feel free to contribute wherever you find issues.
{/* prettier-ignore */}
<div className="max-w-xl bg-slate-300 px-4 py-2 rounded-md">
Huge thanks to official maintainer, [Joseph Schultz](https://github.com/acupofjose). As well as [Will Iverson](https://github.com/wiverson), [Ben Randall](https://github.com/veleek), and [Rhuan Barros](https://github.com/rhuanbarros) for their help.
The C# client library is created and maintained by the Supabase community, and is not an official library. Please be tolerant of areas where the library is still being developed, and — as with all the libraries — feel free to contribute wherever you find issues.
Huge thanks to official maintainer, [Joseph Schultz](https://github.com/acupofjose). As well as [Will Iverson](https://github.com/wiverson), [Ben Randall](https://github.com/veleek), and [Rhuan Barros](https://github.com/rhuanbarros) for their help.
</div>
</Admonition>

View File

@@ -1,29 +1,9 @@
---
id: introduction
title: Introduction
hideTitle: true
---
This reference documents every object and method available in Supabase's C# library, [supabase-csharp](https://www.nuget.org/packages/supabase-csharp). You can use `Supabase` to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
<div className="flex items-start gap-6 not-prose" id="introduction">
<IconMenuCsharp width={35} height={35} />
<div className="flex flex-col gap-2">
<h1 className="text-3xl text-foreground m-0">C# Client Library</h1>
<h2 className="text-base font-mono text-foreground-light">
@supabase-community/supabase-csharp
</h2>
</div>
</div>
<Admonition type="note">
{/* prettier-ignore */}
<div className="max-w-xl">
This reference documents every object and method available in Supabase's C# library, [supabase-csharp](https://www.nuget.org/packages/supabase-csharp). You can use `Supabase` to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
</div>
The C# client library is created and maintained by the Supabase community, and is not an official library. Please be tolerant of areas where the library is still being developed, and — as with all the libraries — feel free to contribute wherever you find issues.
{/* prettier-ignore */}
<div className="max-w-xl bg-slate-300 px-4 py-2 rounded-md">
Huge thanks to official maintainer, [Joseph Schultz](https://github.com/acupofjose). As well as [Will Iverson](https://github.com/wiverson), [Ben Randall](https://github.com/veleek), and [Rhuan Barros](https://github.com/rhuanbarros) for their help.
The C# client library is created and maintained by the Supabase community, and is not an official library. Please be tolerant of areas where the library is still being developed, and — as with all the libraries — feel free to contribute wherever you find issues.
Huge thanks to official maintainer, [Joseph Schultz](https://github.com/acupofjose). As well as [Will Iverson](https://github.com/wiverson), [Ben Randall](https://github.com/veleek), and [Rhuan Barros](https://github.com/rhuanbarros) for their help.
</div>
</Admonition>

View File

@@ -1,20 +1,3 @@
---
id: introduction
title: Introduction
hideTitle: true
---
This reference documents every object and method available in Supabase's Flutter library, [supabase-flutter](https://pub.dev/packages/supabase_flutter). You can use supabase-flutter to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
<div className="flex items-start gap-6 not-prose" id="introduction">
<IconMenuFlutter width={35} height={35} />
<div className="flex flex-col gap-2">
<h1 className="text-3xl text-foreground m-0">Flutter Client Library</h1>
<h2 className="text-base font-mono text-foreground-light">supabase-flutter</h2>
</div>
</div>
{/* prettier-ignore */}
<div className="max-w-xl">
This reference documents every object and method available in Supabase's Flutter library, [supabase-flutter](https://pub.dev/packages/supabase_flutter). You can use supabase-flutter to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
We also provide a [supabase](https://pub.dev/packages/supabase) package for non-Flutter projects.
</div>
We also provide a [supabase](https://pub.dev/packages/supabase) package for non-Flutter projects.

View File

@@ -1,24 +1,3 @@
---
id: introduction
title: Introduction
hideTitle: true
---
This reference documents every object and method available in Supabase's Flutter library, [supabase-flutter](https://pub.dev/packages/supabase_flutter). You can use supabase-flutter to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
<div className="flex items-start gap-6 not-prose" id="introduction">
<IconMenuFlutter width={35} height={35} />
<div className="flex flex-col gap-2">
<h1 className="text-3xl text-foreground m-0">Flutter Client Library</h1>
<h2 className="text-base font-mono text-foreground-light">supabase-flutter</h2>
</div>
</div>
<div className="max-w-xl">
<Admonition type="caution">
You're viewing the docs for an older version of the `supabase-flutter` library. Learn how to [upgrade to the latest version](/docs/reference/dart/v0/upgrade-guide).
</Admonition>
This reference documents every object and method available in Supabase's Flutter library, [supabase-flutter](https://pub.dev/packages/supabase_flutter). You can use supabase-flutter to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
We also provide a [supabase](https://pub.dev/packages/supabase) package for non-Flutter projects.
</div>
We also provide a [supabase](https://pub.dev/packages/supabase) package for non-Flutter projects.

View File

@@ -1,20 +1,3 @@
---
id: introduction
title: Introduction
hideTitle: true
---
This reference documents every object and method available in Supabase's Flutter library, [supabase-flutter](https://pub.dev/packages/supabase_flutter). You can use supabase-flutter to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
<div className="flex items-start gap-6 not-prose" id="introduction">
<IconMenuFlutter width={35} height={35} />
<div className="flex flex-col gap-2">
<h1 className="text-3xl text-foreground m-0">Flutter Client Library</h1>
<h2 className="text-base font-mono text-foreground-light">supabase-flutter</h2>
</div>
</div>
{/* prettier-ignore */}
<div className="max-w-xl">
This reference documents every object and method available in Supabase's Flutter library, [supabase-flutter](https://pub.dev/packages/supabase_flutter). You can use supabase-flutter to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
We also provide a [supabase](https://pub.dev/packages/supabase) package for non-Flutter projects.
</div>
We also provide a [supabase](https://pub.dev/packages/supabase) package for non-Flutter projects.

View File

@@ -4,15 +4,4 @@ title: Introduction
hideTitle: true
---
<div className="flex items-start gap-6 not-prose" id="introduction">
<IconMenuJavascript width={35} height={35} />
<div className="flex flex-col gap-2">
<h1 className="text-3xl text-foreground m-0">JavaScript Client Library</h1>
<h2 className="text-base font-mono text-foreground-light">@supabase/supabase-js</h2>
</div>
</div>
{/* prettier-ignore */}
<div className="max-w-xl">
This reference documents every object and method available in Supabase's isomorphic JavaScript library, supabase-js. You can use supabase-js to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
</div>
This reference documents every object and method available in Supabase's isomorphic JavaScript library, `supabase-js`. You can use `supabase-js` to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.

View File

@@ -1,25 +1 @@
---
id: introduction
title: Introduction
hideTitle: true
---
<div className="flex items-start gap-6 not-prose" id="introduction">
<IconMenuJavascript width={35} height={35} />
<div className="flex flex-col gap-2">
<h1 className="text-3xl text-foreground m-0">JavaScript Client Library</h1>
<h2 className="text-base font-mono text-foreground-light">@supabase/supabase-js</h2>
</div>
</div>
<div className="max-w-xl">
<Admonition type="caution">
You're viewing the docs for an older version of the `supabase-js` library. Learn how to [upgrade to the latest version](/docs/reference/javascript/v1/upgrade-guide).
</Admonition>
<p>
This reference documents every object and method available in Supabase's isomorphic JavaScript library, supabase-js. You can use supabase-js to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
</p>
</div>
This reference documents every object and method available in Supabase's isomorphic JavaScript library, supabase-js. You can use supabase-js to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.

View File

@@ -1,6 +0,0 @@
---
id: release-notes
title: Release Notes
---
## js v1 this is the release notes file.

View File

@@ -1,30 +1,13 @@
---
id: introduction
title: Introduction
hideTitle: true
---
<div className="flex items-start gap-6 not-prose" id="introduction">
<IconMenuKotlin width={35} height={35} />
<div className="flex flex-col gap-2">
<h1 className="text-3xl text-foreground m-0">Kotlin Client Library</h1>
<h2 className="text-base font-mono text-foreground-light">@supabase-community/supabase-kt</h2>
</div>
</div>
<div className="max-w-xl">
This reference documents every object and method available in Supabase's Kotlin Multiplatform library, [supabase-kt](https://github.com/supabase-community/supabase-kt). You can use supabase-kt to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
To see supported Kotlin targets, check the corresponding module README on [GitHub](https://github.com/supabase-community/supabase-kt).
To migrate from version 1.4.X to 2.0.0, see the [migration guide](https://github.com/supabase-community/supabase-kt/blob/master/MIGRATION.md)
</div>
<Admonition type="note">
<div className="max-w-xl bg-slate-300 px-4 py-2 rounded-md">
The Kotlin client library is created and maintained by the Supabase community, and is not an official library. Please be tolerant of areas where the library is still being developed, and — as with all the libraries — feel free to contribute wherever you find issues.
Huge thanks to official maintainer, [jan-tennert](https://github.com/jan-tennert).
</div>
</Admonition>

View File

@@ -0,0 +1,393 @@
---
id: installing
title: 'Installing & Initialization'
slug: installing
custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supabase.yml
---
### Add one or more modules to your project
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
<a href="https://github.com/supabase-community/supabase-kt/releases" style={{ marginRight: '8px' }}>
<img src="https://img.shields.io/github/release/supabase-community/supabase-kt?label=stable" alt="latest stable supabase-kt version"/>
</a>
<a href="https://github.com/supabase-community/supabase-kt/releases">
<img src="https://img.shields.io/maven-central/v/io.github.jan-tennert.supabase/supabase-kt?label=experimental" alt="latest supabase-kt version"/>
</a>
</div>
Add dependency to your build file using the BOM.
The available modules are:
- [**gotrue-kt**](https://github.com/supabase-community/supabase-kt/tree/master/GoTrue)
- [**realtime-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Realtime)
- [**storage-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Storage)
- [**functions-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Functions)
- [**postgrest-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Postgrest)
- Other plugins also available [here](https://github.com/supabase-community/supabase-kt/tree/master/plugins)
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
size="small"
type="underlined"
defaultActiveId="kotlin"
queryGroup="build-file"
>
<TabPanel id="kotlin" label="build.gradle.kts">
```kotlin
implementation(platform("io.github.jan-tennert.supabase:bom:VERSION"))
implementation("io.github.jan-tennert.supabase:postgrest-kt")
implementation("io.github.jan-tennert.supabase:gotrue-kt")
implementation("io.github.jan-tennert.supabase:realtime-kt")
```
</TabPanel>
<TabPanel id="groovy" label="build.gradle">
```groovy
implementation platform("io.github.jan-tennert.supabase:bom:VERSION")
implementation 'io.github.jan-tennert.supabase:postgrest-kt'
implementation 'io.github.jan-tennert.supabase:gotrue-kt'
implementation 'io.github.jan-tennert.supabase:realtime-kt'
```
</TabPanel>
<TabPanel id="xml" label="pom.xml">
```xml
<dependency>
<groupId>io.github.jan-tennert.supabase</groupId>
<artifactId>bom</artifactId>
<version>VERSION</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.github.jan-tennert.supabase</groupId>
<artifactId>postgrest-kt</artifactId>
</dependency>
<dependency>
<groupId>io.github.jan-tennert.supabase</groupId>
<artifactId>gotrue-kt</artifactId>
</dependency>
<dependency>
<groupId>io.github.jan-tennert.supabase</groupId>
<artifactId>realtime-kt</artifactId>
</dependency>
```
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
### Add Ktor Client Engine to each of your Kotlin targets (required)
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
You can find a list of engines [here](https://ktor.io/docs/http-client-engines.html)
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
size="small"
type="underlined"
defaultActiveId="kotlin"
queryGroup="build-file"
>
<TabPanel id="kotlin" label="build.gradle.kts">
```kotlin
implementation("io.ktor:ktor-client-[engine]:KTOR_VERSION")
```
</TabPanel>
<TabPanel id="groovy" label="build.gradle">
```groovy
implementation 'io.ktor:ktor-client-[engine]:KTOR_VERSION'
```
</TabPanel>
<TabPanel id="xml" label="pom.xml">
```xml
<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-client-[engine]</artifactId>
<version>KTOR_VERSION</version>
</dependency>
```
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
<RefSubLayout.Details>
Multiplatform example:
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
size="small"
type="underlined"
defaultActiveId="kotlin"
queryGroup="build-file"
>
<TabPanel id="kotlin" label="build.gradle.kts">
```kotlin
val commonMain by getting {
dependencies {
//supabase modules
}
}
val jvmMain by getting {
dependencies {
implementation("io.ktor:ktor-client-cio:KTOR_VERSION")
}
}
val androidMain by getting {
dependsOn(jvmMain)
}
val jsMain by getting {
dependencies {
implementation("io.ktor:ktor-client-js:KTOR_VERSION")
}
}
val iosMain by getting {
dependencies {
implementation("io.ktor:ktor-client-darwin:KTOR_VERSION")
}
}
```
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
### Serialization
supabase-kt provides several different ways to encode and decode your custom objects.
By default, [KotlinX Serialization](https://github.com/Kotlin/kotlinx.serialization) is used.
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
Use [KotlinX Serialization](https://github.com/Kotlin/kotlinx.serialization).
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
size="small"
type="underlined"
defaultActiveId="kotlin"
queryGroup="build-file"
>
<TabPanel id="kotlin" label="build.gradle.kts">
```kotlin
plugins {
kotlin("plugin.serialization") version "KOTLIN_VERSION"
}
```
</TabPanel>
<TabPanel id="groovy" label="build.gradle">
```groovy
plugins {
id 'org.jetbrains.kotlin.plugin.serialization' version 'KOTLIN_VERSION'
}
```
</TabPanel>
<TabPanel id="xml" label="pom.xml">
```xml
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
<configuration>
<compilerPlugins>
<plugin>kotlinx-serialization</plugin>
</compilerPlugins>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-serialization</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
```
</TabPanel>
</Tabs>
```kotlin
val supabase = createSupabaseClient(supabaseUrl, supabaseKey) {
//Already the default serializer, but you can provide a custom Json instance (optional):
defaultSerializer = KotlinXSerializer(Json {
//apply your custom config
})
}
```
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
Use [Moshi](https://github.com/square/moshi).
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
size="small"
type="underlined"
defaultActiveId="kotlin"
queryGroup="build-file"
>
<TabPanel id="kotlin" label="build.gradle.kts">
```kotlin
implementation("io.github.jan-tennert.supabase:serializer-moshi:VERSION")
```
</TabPanel>
<TabPanel id="groovy" label="build.gradle">
```groovy
implementation 'io.github.jan-tennert.supabase:serializer-moshi:VERSION'
```
</TabPanel>
<TabPanel id="xml" label="pom.xml">
```xml
<dependency>
<groupId>io.github.jan-tennert.supabase</groupId>
<artifactId>serializer-moshi</artifactId>
<version>VERSION</version>
</dependency>
```
</TabPanel>
</Tabs>
```kotlin
val supabase = createSupabaseClient(supabaseUrl, supabaseKey) {
defaultSerializer = MoshiSerializer()
}
```
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
Use [Jackson](https://github.com/FasterXML/jackson-module-kotlin).
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
size="small"
type="underlined"
defaultActiveId="kotlin"
queryGroup="build-file"
>
<TabPanel id="kotlin" label="build.gradle.kts">
```kotlin
implementation("io.github.jan-tennert.supabase:serializer-jackson:VERSION")
```
</TabPanel>
<TabPanel id="groovy" label="build.gradle">
```groovy
implementation 'io.github.jan-tennert.supabase:serializer-jackson:VERSION'
```
</TabPanel>
<TabPanel id="xml" label="pom.xml">
```xml
<dependency>
<groupId>io.github.jan-tennert.supabase</groupId>
<artifactId>serializer-jackson</artifactId>
<version>VERSION</version>
</dependency>
```
</TabPanel>
</Tabs>
```kotlin
val supabase = createSupabaseClient(supabaseUrl, supabaseKey) {
defaultSerializer = JacksonSerializer()
}
```
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
Use custom serializer.
</RefSubLayout.Details>
<RefSubLayout.Examples>
```kotlin
class CustomSerializer: SupabaseSerializer {
override fun <T : Any> encode(type: KType, value: T): String {
//encode value to string
}
override fun <T : Any> decode(type: KType, value: String): T {
//decode value
}
}
```
```kotlin
val supabase = createSupabaseClient(supabaseUrl, supabaseKey) {
defaultSerializer = CustomSerializer()
}
```
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>

View File

@@ -0,0 +1,13 @@
This reference documents every object and method available in Supabase's Kotlin Multiplatform library, [supabase-kt](https://github.com/supabase-community/supabase-kt). You can use supabase-kt to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
To see supported Kotlin targets, check the corresponding module README on [GitHub](https://github.com/supabase-community/supabase-kt).
To migrate from version 1.4.X to 2.0.0, see the [migration guide](https://github.com/supabase-community/supabase-kt/blob/master/MIGRATION.md)
<Admonition type="note">
The Kotlin client library is created and maintained by the Supabase community, and is not an official library. Please be tolerant of areas where the library is still being developed, and — as with all the libraries — feel free to contribute wherever you find issues.
Huge thanks to official maintainer, [jan-tennert](https://github.com/jan-tennert).
</Admonition>

View File

@@ -1,18 +1 @@
---
id: introduction
title: Introduction
hideTitle: true
---
<div className="flex items-start gap-6 not-prose" id="introduction">
<IconMenuPython width={35} height={35} />
<div className="flex flex-col gap-2">
<h1 className="text-3xl text-foreground m-0">Python Client Library</h1>
<h2 className="text-base font-mono text-foreground-light">@supabase/supabase-py</h2>
</div>
</div>
<div className="max-w-xl">
{/* prettier-ignore */}
<p>This reference documents every object and method available in Supabase's Python library, [supabase-py](https://github.com/supabase/supabase-py). You can use supabase-py to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.</p>
</div>
This reference documents every object and method available in Supabase's Python library, [supabase-py](https://github.com/supabase/supabase-py). You can use supabase-py to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.

View File

@@ -1,20 +1 @@
---
id: introduction
title: Introduction
hideTitle: true
---
<div className="flex items-start gap-6 not-prose" id="introduction">
<IconMenuSwift width={35} height={35} />
<div className="flex flex-col gap-2">
<h1 className="text-3xl text-foreground m-0">Swift Client Library</h1>
<h2 className="text-base font-mono text-foreground-light">@supabase/supabase-swift</h2>
</div>
</div>
{/* prettier-ignore */}
<div className="max-w-xl">
This reference documents every object and method available in Supabase's Swift library, [supabase-swift](https://github.com/supabase/supabase-swift). You can use supabase-swift to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
</div>
This reference documents every object and method available in Supabase's Swift library, [supabase-swift](https://github.com/supabase/supabase-swift). You can use supabase-swift to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.

View File

@@ -0,0 +1,63 @@
---
id: installing
title: 'Installing'
slug: installing
custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supabase.yml
---
### Install using Swift Package Manager
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
You can install Supabase package using Swift Package Manager.
The package exposes multiple libraries, you can choose between adding all of them using Supabase, or some of:
- `Auth`
- `Realtime`
- `Postgrest`
- `Functions`
- `Storage`
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
size="small"
type="underlined"
defaultActiveId="swift"
queryGroup="framework"
>
<TabPanel id="swift" label="Package.swift">
```swift
let package = Package(
...
dependencies: [
...
.package(
url: "https://github.com/supabase/supabase-swift.git",
from: "2.0.0"
),
],
targets: [
.target(
name: "YourTargetName",
dependencies: [
.product(
name: "Supabase", // Auth, Realtime, Postgrest, Functions, or Storage
package: "supabase-swift"
),
]
)
]
)
```
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>

View File

@@ -0,0 +1 @@
This reference documents every object and method available in Supabase's Swift library, [supabase-swift](https://github.com/supabase/supabase-swift). You can use supabase-swift to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.

View File

@@ -4,10 +4,12 @@ import { redirect } from 'next/navigation'
import { readFile, readdir } from 'node:fs/promises'
import { extname, join, sep } from 'node:path'
import { pluckPromise } from '~/features/helpers.fn'
import { cache_fullProcess_withDevCacheBust, existsFile } from '~/features/helpers.fs'
import type { OrPromise } from '~/features/helpers.types'
import { notFoundLink } from '~/features/recommendations/NotFound.utils'
import { BASE_PATH, MISC_URL } from '~/lib/constants'
import { generateOpenGraphImageMeta } from '~/features/seo/openGraph'
import { BASE_PATH } from '~/lib/constants'
import { GUIDES_DIRECTORY, isValidGuideFrontmatter, type GuideFrontmatter } from '~/lib/docs'
import { newEditLink } from './GuidesMdx.template'
@@ -108,9 +110,6 @@ const genGuidesStaticParams = (directory?: string) => async () => {
return result
}
const pluckPromise = <T, K extends keyof T>(promise: Promise<T>, key: K) =>
promise.then((data) => data[key])
const genGuideMeta =
<Params,>(
generate: (params: Params) => OrPromise<{ meta: GuideFrontmatter; pathname: `/${string}` }>
@@ -136,12 +135,11 @@ const genGuideMeta =
openGraph: {
...parentOg,
url: `${BASE_PATH}${pathname}`,
images: {
url: `${MISC_URL}/functions/v1/og-images?site=docs&type=${encodeURIComponent(ogType)}&title=${encodeURIComponent(meta.title)}&description=${encodeURIComponent(meta.description ?? 'undefined')}`,
width: 800,
height: 600,
alt: meta.title,
},
images: generateOpenGraphImageMeta({
type: ogType,
title: meta.title,
description: meta.description,
}),
},
}
}

View File

@@ -13,6 +13,7 @@ import ButtonCard from '~/components/ButtonCard'
import { Extensions } from '~/components/Extensions'
import { JwtGenerator } from '~/components/JwtGenerator'
import {
AuthErrorCodesTable,
AuthRateLimits,
CreateClientSnippet,
DatabaseSetup,
@@ -41,6 +42,7 @@ const components = {
Accordion,
AccordionItem,
Admonition,
AuthErrorCodesTable,
AuthRateLimits,
AuthSmsProviderConfig,
AppleSecretGenerator,

View File

@@ -0,0 +1,136 @@
import { keyBy, isPlainObject } from 'lodash'
import { mkdir, readFile, writeFile } from 'node:fs/promises'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { parse } from 'yaml'
import { REFERENCES, clientSdkIds } from '~/content/navigation.references'
import { parseTypeSpec } from '~/features/docs/Reference.typeSpec'
import type { AbbrevCommonClientLibSection } from '~/features/docs/Reference.utils'
import { deepFilterRec } from '~/features/helpers.fn'
import type { Json } from '~/features/helpers.types'
import commonClientLibSections from '~/spec/common-client-libs-sections.json' assert { type: 'json' }
const DOCS_DIRECTORY = join(dirname(fileURLToPath(import.meta.url)), '../..')
const SPEC_DIRECTORY = join(DOCS_DIRECTORY, 'spec')
const GENERATED_DIRECTORY = join(dirname(fileURLToPath(import.meta.url)), 'generated')
async function getSpec(specFile: string, { ext = 'yml' }: { ext?: string } = {}) {
const specFullPath = join(SPEC_DIRECTORY, `${specFile}.${ext}`)
const rawSpec = await readFile(specFullPath, 'utf-8')
return ext === 'yml' ? parse(rawSpec) : rawSpec
}
async function parseFnsList(rawSpec: Json): Promise<Array<{ id: unknown }>> {
if (isPlainObject(rawSpec) && 'functions' in (rawSpec as object)) {
const _rawSpec = rawSpec as { functions: unknown }
if (Array.isArray(_rawSpec.functions)) {
return _rawSpec.functions.filter(({ id }) => !!id)
}
}
return []
}
function genClientSdkSectionTree(fns: Array<{ id: unknown }>, excludeName: string) {
const validSections = deepFilterRec(
commonClientLibSections as Array<AbbrevCommonClientLibSection>,
'items',
(section) =>
section.type === 'markdown' || section.type === 'category'
? !('excludes' in section && section.excludes.includes(excludeName))
: section.type === 'function'
? fns.some(({ id }) => section.id === id)
: true
)
return validSections
}
export function flattenCommonClientLibSections(tree: Array<AbbrevCommonClientLibSection>) {
return tree.reduce((acc, elem) => {
if ('items' in elem) {
const prunedElem = { ...elem }
delete prunedElem.items
acc.push(prunedElem)
acc.push(...flattenCommonClientLibSections(elem.items))
} else {
acc.push(elem)
}
return acc
}, [] as Array<AbbrevCommonClientLibSection>)
}
async function writeTypes() {
const types = await parseTypeSpec()
await writeFile(
join(GENERATED_DIRECTORY, 'typeSpec.json'),
JSON.stringify(types, (key, value) => {
if (key === 'methods') {
return Object.fromEntries(value.entries())
} else {
return value
}
})
)
}
async function writeReferenceSections() {
return Promise.all(
clientSdkIds
.flatMap((sdkId) => {
const versions = REFERENCES[sdkId].versions
return versions.map((version) => ({
sdkId,
version,
}))
})
.flatMap(async ({ sdkId, version }) => {
const spec = await getSpec(REFERENCES[sdkId].meta[version].specFile)
const fnsList = await parseFnsList(spec)
const pendingFnListWrite = writeFile(
join(GENERATED_DIRECTORY, `${sdkId}.${version}.functions.json`),
JSON.stringify(fnsList)
)
const sectionTree = genClientSdkSectionTree(fnsList, REFERENCES[sdkId].meta[version].libId)
const pendingSectionTreeWrite = writeFile(
join(GENERATED_DIRECTORY, `${sdkId}.${version}.sections.json`),
JSON.stringify(sectionTree)
)
const flattened = flattenCommonClientLibSections(sectionTree)
const pendingFlattenedWrite = writeFile(
join(GENERATED_DIRECTORY, `${sdkId}.${version}.flat.json`),
JSON.stringify(flattened)
)
const sectionsBySlug = keyBy(flattened, (section) => section.slug)
const pendingSlugDictionaryWrite = writeFile(
join(GENERATED_DIRECTORY, `${sdkId}.${version}.bySlug.json`),
JSON.stringify(sectionsBySlug)
)
return [
pendingFnListWrite,
pendingSectionTreeWrite,
pendingFlattenedWrite,
pendingSlugDictionaryWrite,
]
})
)
}
async function run() {
try {
await mkdir(GENERATED_DIRECTORY, { recursive: true })
await Promise.all([writeTypes(), writeReferenceSections()])
} catch (err) {
console.error(err)
}
}
run()

View File

@@ -0,0 +1,100 @@
import { readFile } from 'node:fs/promises'
import { join } from 'node:path'
import type { ModuleTypes } from '~/features/docs/Reference.typeSpec'
import type { AbbrevCommonClientLibSection } from '~/features/docs/Reference.utils'
let typeSpec: Array<ModuleTypes>
async function _typeSpecSingleton() {
if (!typeSpec) {
const rawJson = await readFile(
join(process.cwd(), 'features/docs', './generated/typeSpec.json'),
'utf-8'
)
typeSpec = JSON.parse(rawJson, (key, value) => {
if (key === 'methods') {
return new Map(Object.entries(value))
} else {
return value
}
})
}
return typeSpec
}
export async function getTypeSpec(ref: string) {
const modules = await _typeSpecSingleton()
const delimiter = ref.indexOf('.')
const refMod = ref.substring(0, delimiter)
const mod = modules.find((mod) => mod.name === refMod)
return mod?.methods.get(ref)
}
const functionsList = new Map<string, Array<{ id: unknown }>>()
export async function getFunctionsList(sdkId: string, version: string) {
const key = `${sdkId}.${version}`
if (!functionsList.has(key)) {
const data = await readFile(
join(process.cwd(), 'features/docs', `./generated/${sdkId}.${version}.functions.json`),
'utf-8'
)
functionsList.set(key, JSON.parse(data))
}
return functionsList.get(key)
}
const referenceSections = new Map<string, Array<AbbrevCommonClientLibSection>>()
export async function getReferenceSections(sdkId: string, version: string) {
const key = `${sdkId}.${version}`
if (!referenceSections.has(key)) {
const data = await readFile(
join(process.cwd(), 'features/docs', `./generated/${sdkId}.${version}.sections.json`),
'utf-8'
)
referenceSections.set(key, JSON.parse(data))
}
return referenceSections.get(key)
}
const flatSections = new Map<string, Array<AbbrevCommonClientLibSection>>()
export async function getFlattenedSections(sdkId: string, version: string) {
const key = `${sdkId}.${version}`
if (!flatSections.has(key)) {
const data = await readFile(
join(process.cwd(), 'features/docs', `./generated/${sdkId}.${version}.flat.json`),
'utf-8'
)
flatSections.set(key, JSON.parse(data))
}
return flatSections.get(key)
}
const sectionsBySlug = new Map<string, Map<string, AbbrevCommonClientLibSection>>()
export async function getSectionsBySlug(sdkId: string, version: string) {
const key = `${sdkId}.${version}`
if (!sectionsBySlug.has(key)) {
const data = await readFile(
join(process.cwd(), 'features/docs', `./generated/${sdkId}.${version}.bySlug.json`),
'utf-8'
)
const asObject = JSON.parse(data)
sectionsBySlug.set(key, new Map(Object.entries(asObject)))
}
return sectionsBySlug.get(key)
}

View File

@@ -0,0 +1,54 @@
import { Github } from 'lucide-react'
import Link from 'next/link'
import { cn } from 'ui'
import MenuIconPicker from '~/components/Navigation/NavigationMenu/MenuIconPicker'
interface ClientLibHeaderProps {
menuData: {
title: string
icon?: string
pkg: {
name: string
repo: string
}
}
className?: string
}
function ClientLibHeader({ menuData, className }: ClientLibHeaderProps) {
return (
<div className={cn('flex items-start gap-6', className)}>
{'icon' in menuData && (
<MenuIconPicker
icon={menuData.icon}
width={35}
height={35}
className="text-foreground-light"
/>
)}
<div className="flex flex-col gap-2">
<h1 id="introduction" className="text-3xl text-foreground">
{menuData.title} Client Library
</h1>
<span
className={cn('text-base font-mono text-foreground-light', 'flex items-center gap-2')}
>
{menuData.pkg.name}
<Link
href={menuData.pkg.repo}
target="_blank"
rel="noreferrer noopener"
className="hover:text-brand focus-visible:text-brand transition-colors"
>
<span className="sr-only">View on GitHub</span>
<Github size={18} />
</Link>
</span>
</div>
</div>
)
}
export { ClientLibHeader }

View File

@@ -0,0 +1,67 @@
import { AlertTriangle } from 'lucide-react'
import Link from 'next/link'
import { Alert_Shadcn_, AlertDescription_Shadcn_, AlertTitle_Shadcn_, cn } from 'ui'
import { MDXRemoteBase } from '~/features/docs/MdxBase'
import { getRefMarkdown } from '~/features/docs/Reference.mdx'
import { ReferenceSectionWrapper } from '~/features/docs/Reference.ui.client'
import commonClientLibSections from '~/spec/common-client-libs-sections.json' assert { type: 'json' }
function hasIntro(sections: typeof commonClientLibSections, excludeName: string) {
return Boolean(
sections[0]?.type === 'markdown' &&
sections[0]?.slug === 'introduction' &&
!(
'excludes' in sections[0] &&
Array.isArray(sections[0].excludes) &&
sections[0].excludes?.includes(excludeName)
)
)
}
interface ClientLibIntroductionProps {
libPath: string
excludeName: string
version: string
isLatestVersion: boolean
}
export async function ClientLibIntroduction({
libPath,
excludeName,
version,
isLatestVersion,
}: ClientLibIntroductionProps) {
if (!hasIntro(commonClientLibSections, excludeName)) return null
const content = await getRefMarkdown(
`${libPath}/${isLatestVersion ? '' : `${version}/`}introduction`
)
return (
<ReferenceSectionWrapper
id="introduction"
link={`/docs/reference/${libPath}/${isLatestVersion ? '' : `${version}/`}introduction`}
className="prose"
>
<MDXRemoteBase source={content} />
</ReferenceSectionWrapper>
)
}
export function OldVersionAlert({ libPath, className }: { libPath: string; className?: string }) {
return (
<Alert_Shadcn_ variant="warning" className={cn('not-prose', className)}>
<AlertTriangle />
<AlertTitle_Shadcn_ className="font-medium">Version out of date</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
There&apos;s a newer version of this library! Migrate to the{' '}
<Link href={`/reference/${libPath}`} className="underline underline-offset-2">
newest version
</Link>
.
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
)
}

View File

@@ -0,0 +1,20 @@
'use client'
/**
* The MDXProvider is necessary so that MDX partials will have access
* to components.
*
* Since the reference pages are so heavy, keep the weight down by only
* including the bare minimum.
*/
import { MDXProvider } from '@mdx-js/react'
import { type PropsWithChildren } from 'react'
import { Admonition } from 'ui'
const components = { Admonition }
export function MDXProviderReference({ children }: PropsWithChildren) {
return <MDXProvider components={components}>{children}</MDXProvider>
}

View File

@@ -0,0 +1,49 @@
import matter from 'gray-matter'
import { readFile } from 'node:fs/promises'
import { join } from 'node:path'
import { MDXRemoteBase } from '~/features/docs/MdxBase'
import { components } from '~/features/docs/MdxBase.shared'
import { RefSubLayout } from '~/features/docs/Reference.ui'
import { cache_fullProcess_withDevCacheBust } from '~/features/helpers.fs'
import { REF_DOCS_DIRECTORY } from '~/lib/docs'
async function getRefMarkdownInternal(relPath: string) {
const fullPath = join(REF_DOCS_DIRECTORY, relPath + '.mdx')
try {
if (!fullPath.startsWith(REF_DOCS_DIRECTORY)) {
throw Error(`Accessing forbidden path outside of REF_DOCS_DIRECTORY: ${fullPath}`)
}
const mdx = await readFile(fullPath, 'utf-8')
const { content } = matter(mdx)
return content
} catch (err) {
console.error(`Error fetching reference markdown from file: ${fullPath}:\n\n${err}`)
return ''
}
}
/**
* Caching this for the entire process is fine because the Markdown content is
* baked into each deployment and cannot change. There's also nothing sensitive
* here: this is just reading the public MDX files from the codebase.
*/
const getRefMarkdown = cache_fullProcess_withDevCacheBust(
getRefMarkdownInternal,
REF_DOCS_DIRECTORY,
(filename: string) => JSON.stringify([filename.replace(/\.mdx$/, '')])
)
interface MDXRemoteRefsProps {
source: string
}
function MDXRemoteRefs({ source }: MDXRemoteRefsProps) {
const refComponents = { ...components, RefSubLayout }
return <MDXRemoteBase source={source} components={refComponents} />
}
export { getRefMarkdown, MDXRemoteRefs }

View File

@@ -0,0 +1,240 @@
'use client'
import * as Collapsible from '@radix-ui/react-collapsible'
import { debounce } from 'lodash'
import { ChevronUp } from 'lucide-react'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import type { HTMLAttributes, MouseEvent, PropsWithChildren } from 'react'
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
import { cn } from 'ui'
import { BASE_PATH } from '~/lib/constants'
import type { AbbrevCommonClientLibSection } from '~/features/docs/Reference.navigation'
import { isElementInViewport } from '~/features/ui/helpers.dom'
export const ReferenceContentInitiallyScrolledContext = createContext<boolean>(false)
export function ReferenceContentScrollHandler({
libPath,
version,
isLatestVersion,
children,
}: PropsWithChildren<{
libPath: string
version: string
isLatestVersion: boolean
}>) {
const checkedPathnameOnLoad = useRef(false)
const [initiallyScrolled, setInitiallyScrolled] = useState(false)
const pathname = usePathname()
useEffect(() => {
if (!checkedPathnameOnLoad.current) {
const initialSelectedSection = pathname.replace(
`/reference/${libPath}/${isLatestVersion ? '' : `${version}/`}`,
''
)
if (initialSelectedSection) {
const section = document.getElementById(initialSelectedSection)
section?.scrollIntoView()
section?.querySelector('h2')?.focus()
}
checkedPathnameOnLoad.current = true
setInitiallyScrolled(true)
}
}, [pathname, libPath, version, isLatestVersion])
return (
<ReferenceContentInitiallyScrolledContext.Provider value={initiallyScrolled}>
{children}
</ReferenceContentInitiallyScrolledContext.Provider>
)
}
export function ReferenceNavigationScrollHandler({
children,
...rest
}: PropsWithChildren & HTMLAttributes<HTMLDivElement>) {
const ref = useRef<HTMLDivElement>()
const initialScrollHappened = useContext(ReferenceContentInitiallyScrolledContext)
const scrollActiveIntoView = useCallback(() => {
const currentLink = ref.current?.querySelector('[aria-current=page]') as HTMLElement
if (currentLink && !isElementInViewport(currentLink)) {
currentLink.scrollIntoView({
block: 'center',
})
}
}, [])
useEffect(() => {
if (initialScrollHappened) {
scrollActiveIntoView()
}
}, [initialScrollHappened, scrollActiveIntoView])
useEffect(() => {
const debouncedScrollActiveIntoView = debounce(scrollActiveIntoView, 150)
window.addEventListener('scrollend', debouncedScrollActiveIntoView)
return () => window.removeEventListener('scrollend', debouncedScrollActiveIntoView)
}, [scrollActiveIntoView])
return (
<div ref={ref} {...rest}>
{children}
</div>
)
}
function deriveHref(basePath: string, section: AbbrevCommonClientLibSection) {
return 'slug' in section ? `${basePath}/${section.slug}` : ''
}
function getLinkStyles(isActive: boolean, className?: string) {
return cn(
'text-sm text-foreground-lighter',
!isActive && 'hover:text-foreground',
isActive && 'text-brand',
'transition-colors',
className
)
}
export function RefLink({
basePath,
section,
skipChildren = false,
className,
}: {
basePath: string
section: AbbrevCommonClientLibSection
skipChildren?: boolean
className?: string
}) {
const ref = useRef<HTMLAnchorElement>()
const pathname = usePathname()
const href = deriveHref(basePath, section)
const isActive =
pathname === href || (pathname === basePath && href.replace(basePath, '') === '/introduction')
if (!('title' in section)) return null
const isCompoundSection = !skipChildren && 'items' in section && section.items.length > 0
return (
<>
{isCompoundSection ? (
<CompoundRefLink basePath={basePath} section={section} />
) : (
<Link
ref={ref}
href={href}
aria-current={isActive ? 'page' : false}
className={getLinkStyles(isActive, className)}
onClick={(evt: MouseEvent) => {
/*
* We don't actually want to navigate or rerender anything since
* links are all to sections on the same page.
*/
evt.preventDefault()
history.pushState({}, '', `${BASE_PATH}${href}`)
if ('slug' in section) {
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
const domElement = document.getElementById(section.slug)
domElement?.scrollIntoView({
behavior: reduceMotion ? 'auto' : 'smooth',
})
domElement?.querySelector('h2')?.focus()
}
}}
>
{section.title}
</Link>
)}
</>
)
}
function useCompoundRefLinkActive(basePath: string, section: AbbrevCommonClientLibSection) {
const [open, _setOpen] = useState(false)
const pathname = usePathname()
const parentHref = deriveHref(basePath, section)
const isParentActive = pathname === parentHref
const childHrefs = useMemo(
() => new Set(section.items.map((item) => deriveHref(basePath, item))),
[basePath, section]
)
const isChildActive = childHrefs.has(pathname)
const isActive = isParentActive || isChildActive
const setOpen = (open: boolean) => {
// Disable closing if the section is active, to prevent the currently active
// link disappearing
if (open || !isActive) _setOpen(open)
}
if (isActive && !open) {
setOpen(true)
}
return { open, setOpen, isActive }
}
function CompoundRefLink({
basePath,
section,
}: {
basePath: string
section: AbbrevCommonClientLibSection
}) {
const { open, setOpen, isActive } = useCompoundRefLinkActive(basePath, section)
return (
<Collapsible.Root open={open} onOpenChange={setOpen}>
<Collapsible.Trigger asChild disabled={isActive}>
<button
className={cn(
'group',
'cursor-pointer',
'w-full',
'flex items-center justify-between gap-2'
)}
>
<span className={getLinkStyles(false)}>{section.title}</span>
<ChevronUp
width={16}
className={cn(
'group-disabled:cursor-not-allowed group-disabled:opacity-10',
'data-open-parent:rotate-0 data-closed-parent:rotate-90',
'transition'
)}
/>
</button>
</Collapsible.Trigger>
<Collapsible.Content
className={cn('border-l border-control pl-3 ml-1 data-open:mt-2 grid gap-2.5')}
>
<ul className="space-y-2">
<RefLink basePath={basePath} section={section} skipChildren />
{section.items.map((item, idx) => {
return (
<li key={`${section.id}-${idx}`}>
<RefLink basePath={basePath} section={item} />
</li>
)
})}
</ul>
</Collapsible.Content>
</Collapsible.Root>
)
}

View File

@@ -0,0 +1,105 @@
import { Fragment, type PropsWithChildren } from 'react'
import { cn } from 'ui'
import MenuIconPicker from '~/components/Navigation/NavigationMenu/MenuIconPicker'
import RefVersionDropdown from '~/components/RefVersionDropdown'
import { getReferenceSections } from '~/features/docs/Reference.generated.singleton'
import {
RefLink,
ReferenceNavigationScrollHandler,
} from '~/features/docs/Reference.navigation.client'
import { type AbbrevCommonClientLibSection } from '~/features/docs/Reference.utils'
interface ClientSdkNavigationProps {
sdkId: string
name: string
menuData: { icon?: string }
libPath: string
version: string
isLatestVersion: boolean
}
async function ClientSdkNavigation({
sdkId,
name,
menuData,
libPath,
version,
isLatestVersion,
}: ClientSdkNavigationProps) {
const navSections = await getReferenceSections(sdkId, version)
const basePath = `/reference/${libPath}${isLatestVersion ? '' : `/${version}`}`
return (
<ReferenceNavigationScrollHandler className="w-full flex flex-col pt-3 pb-5 gap-3">
<div className="flex items-center gap-3">
{'icon' in menuData && <MenuIconPicker icon={menuData.icon} width={21} height={21} />}
<span className="text-base text-brand-600">{name}</span>
<RefVersionDropdown library={libPath} currentVersion={version} />
</div>
<ul className="flex flex-col gap-2">
{navSections.map((section) => (
<Fragment key={section.id}>
{section.type === 'category' ? (
<li>
<RefCategory basePath={basePath} section={section} />
</li>
) : (
<li className={topLvlRefNavItemStyles}>
<RefLink basePath={basePath} section={section} />
</li>
)}
</Fragment>
))}
</ul>
</ReferenceNavigationScrollHandler>
)
}
const topLvlRefNavItemStyles = 'leading-5'
function RefCategory({
basePath,
section,
}: {
basePath: string
section: AbbrevCommonClientLibSection
}) {
if (!('items' in section && section.items.length > 0)) return null
return (
<>
<Divider />
{'title' in section && <SideMenuTitle className="py-2">{section.title}</SideMenuTitle>}
<ul className="space-y-2">
{section.items.map((item) => (
<li key={item.id} className={topLvlRefNavItemStyles}>
<RefLink basePath={basePath} section={item} />
</li>
))}
</ul>
</>
)
}
function Divider() {
return <hr className="w-full h-px my-3 bg-control" />
}
function SideMenuTitle({ children, className }: PropsWithChildren<{ className?: string }>) {
return (
<div
className={cn(
'font-mono font-medium text-xs text-foreground tracking-wider uppercase',
className
)}
>
{children}
</div>
)
}
export { ClientSdkNavigation }
export type { AbbrevCommonClientLibSection }

View File

@@ -0,0 +1,64 @@
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
import * as NavItems from '~/components/Navigation/NavigationMenu/NavigationMenu.constants'
import { REFERENCES } from '~/content/navigation.references'
import { ClientLibHeader } from '~/features/docs/Reference.header'
import { ClientLibIntroduction, OldVersionAlert } from '~/features/docs/Reference.introduction'
import { ClientSdkNavigation } from '~/features/docs/Reference.navigation'
import { ReferenceContentScrollHandler } from '~/features/docs/Reference.navigation.client'
import { ClientLibRefSections } from '~/features/docs/Reference.sections'
import { LayoutMainContent } from '~/layouts/DefaultLayout'
import { SidebarSkeleton } from '~/layouts/MainSkeleton'
interface ClientSdkReferenceProps {
sdkId: string
libVersion: string
}
export async function ClientSdkReferencePage({ sdkId, libVersion }: ClientSdkReferenceProps) {
const libraryMeta = REFERENCES[sdkId]
const versions = libraryMeta?.versions ?? []
const isLatestVersion = libVersion === versions[0]
const menuData = NavItems[libraryMeta.meta[libVersion].libId]
return (
<ReferenceContentScrollHandler
libPath={libraryMeta.libPath}
version={libVersion}
isLatestVersion={isLatestVersion}
>
<SidebarSkeleton
menuId={MenuId.RefJavaScriptV2}
NavigationMenu={
<ClientSdkNavigation
sdkId={sdkId}
name={menuData.title}
menuData={menuData}
libPath={libraryMeta.libPath}
version={libVersion}
isLatestVersion={isLatestVersion}
/>
}
>
<LayoutMainContent>
{!isLatestVersion && (
<OldVersionAlert
libPath={libraryMeta.libPath}
className="z-10 fixed top-[calc(var(--header-height)+1rem)] right-4 w-84 max-w-[calc(100vw-2rem)]"
/>
)}
<article className="@container/article">
<ClientLibHeader menuData={menuData} className="mt-4 mb-8" />
<ClientLibIntroduction
libPath={libraryMeta.libPath}
excludeName={libraryMeta.meta[libVersion].libId}
version={libVersion}
isLatestVersion={isLatestVersion}
/>
<ClientLibRefSections sdkId={sdkId} version={libVersion} />
</article>
</LayoutMainContent>
</SidebarSkeleton>
</ReferenceContentScrollHandler>
)
}

View File

@@ -0,0 +1,236 @@
import { Fragment } from 'react'
import { Tabs_Shadcn_, TabsContent_Shadcn_, TabsList_Shadcn_, TabsTrigger_Shadcn_, cn } from 'ui'
import { REFERENCES } from '~/content/navigation.references'
import { MDXRemoteRefs, getRefMarkdown } from '~/features/docs/Reference.mdx'
import { MDXProviderReference } from '~/features/docs/Reference.mdx.client'
import type { MethodTypes } from '~/features/docs/Reference.typeSpec'
import {
getFlattenedSections,
getFunctionsList,
getTypeSpec,
} from '~/features/docs/Reference.generated.singleton'
import {
CollapsibleDetails,
FnParameterDetails,
RefSubLayout,
ReturnTypeDetails,
StickyHeader,
} from '~/features/docs/Reference.ui'
import type { AbbrevCommonClientLibSection } from '~/features/docs/Reference.utils'
import { normalizeMarkdown } from '~/features/docs/Reference.utils'
type ClientLibRefSectionsProps = {
sdkId: string
version: string
}
async function ClientLibRefSections({ sdkId, version }: ClientLibRefSectionsProps) {
let flattenedSections = await getFlattenedSections(sdkId, version)
flattenedSections = trimIntro(flattenedSections)
return (
<MDXProviderReference>
<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 sdkId={sdkId} version={version} section={section} />
</Fragment>
))}
</div>
</MDXProviderReference>
)
}
function trimIntro(sections: Array<AbbrevCommonClientLibSection>) {
const hasIntro = sections[0]?.type === 'markdown' && sections[0]?.slug === 'introduction'
if (hasIntro) {
return sections.slice(1)
}
return sections
}
function SectionDivider() {
return <hr />
}
type SectionSwitchProps = {
sdkId: string
version: string
section: AbbrevCommonClientLibSection
}
export function SectionSwitch({ sdkId, version, section }: SectionSwitchProps) {
const libPath = REFERENCES[sdkId].libPath
const isLatestVersion = version === REFERENCES[sdkId].versions[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={sdkId}
version={version}
link={sectionLink}
section={section}
useTypeSpec={REFERENCES[sdkId].typeSpec}
/>
)
default:
console.error(`Unhandled type in reference sections: ${section.type}`)
return null
}
}
interface MarkdownSectionProps {
libPath: string
version: string
isLatestVersion: boolean
link: string
section: AbbrevCommonClientLibSection
}
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 FunctionSectionProps {
sdkId: string
version: string
link: string
section: AbbrevCommonClientLibSection
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_>
{'examples' in fn &&
Array.isArray(fn.examples) &&
fn.examples.map((example) => (
<TabsContent_Shadcn_ key={example.id} value={example.id}>
<MDXRemoteRefs source={example.code} />
<div className="flex flex-col gap-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 { ClientLibRefSections }

View File

@@ -1,10 +1,10 @@
import { describe, expect, it } from 'vitest'
import { __parseTypeSpec } from './Reference.typeSpec'
import { parseTypeSpec } from './Reference.typeSpec'
describe('TS type spec parsing', () => {
it('matches snapshot', async () => {
const parsed = await __parseTypeSpec()
const parsed = await parseTypeSpec()
const json = JSON.stringify(
parsed,
(key, value) => {

View File

@@ -6,10 +6,6 @@
* access to a function's type definition, given its name and module.
*/
import { join } from 'node:path'
import { cache_fullProcess_withDevCacheBust } from '~/features/helpers.fs'
import { SPEC_DIRECTORY } from '~/lib/docs'
import _typeSpec from '~/spec/enrichments/tsdoc_v2/combined.json' assert { type: 'json' }
// [Charis] 2024-07-10
@@ -26,7 +22,7 @@ export const TYPESPEC_NODE_ANONYMOUS = Symbol('anonymous')
* Definitions for the methods and types defined in each Supabase JS client
* library.
*/
interface ModuleTypes {
export interface ModuleTypes {
name: string
methods: Map<string, MethodTypes>
}
@@ -37,8 +33,14 @@ interface ModuleTypes {
export interface MethodTypes {
name: string | typeof TYPESPEC_NODE_ANONYMOUS
comment?: Comment
params: Array<ParamType>
params: Array<FunctionParameterType>
ret: ReturnType | undefined
altSignatures?: [
{
params: Array<FunctionParameterType>
ret: ReturnType | undefined
},
]
}
interface Comment {
@@ -46,18 +48,19 @@ interface Comment {
text?: string
}
interface ParamType {
export interface FunctionParameterType {
name: string | typeof TYPESPEC_NODE_ANONYMOUS
comment?: Comment
isOptional?: boolean
type: Type | undefined
type: TypeDetails | undefined
}
interface ReturnType {
type: Type | undefined
type: TypeDetails | undefined
comment?: Comment
}
type Type = IntrinsicType | LiteralType | CustomType
export type TypeDetails = IntrinsicType | LiteralType | CustomType
/**
* Type definition for an intrinsic (built-in) TypeScript type, for example,
@@ -91,24 +94,24 @@ interface NameOnlyType {
}
interface CustomObjectType {
type: 'customObject'
type: 'object'
name?: string | typeof TYPESPEC_NODE_ANONYMOUS
comment?: Comment
properties: Array<PropertyType>
properties: Array<CustomTypePropertyType>
}
interface CustomUnionType {
type: 'customUnion'
export interface CustomUnionType {
type: 'union'
name?: string | typeof TYPESPEC_NODE_ANONYMOUS
comment?: Comment
subTypes: Array<Type>
subTypes: Array<TypeDetails>
}
interface CustomFunctionType {
type: 'function'
name?: string | typeof TYPESPEC_NODE_ANONYMOUS
comment?: Comment
params: Array<ParamType>
params: Array<FunctionParameterType>
ret: ReturnType | undefined
}
@@ -116,71 +119,43 @@ interface ArrayType {
type: 'array'
name?: string | typeof TYPESPEC_NODE_ANONYMOUS
comment?: Comment
elemType: Type | undefined
elemType: TypeDetails | undefined
}
interface RecordType {
type: 'record'
name?: string | typeof TYPESPEC_NODE_ANONYMOUS
comment?: Comment
keyType: Type | undefined
valueType: Type | undefined
keyType: TypeDetails | undefined
valueType: TypeDetails | undefined
}
interface IndexSignatureType {
type: 'index signature'
name?: string | typeof TYPESPEC_NODE_ANONYMOUS
comment?: Comment
keyType: Type | undefined
valueType: Type | undefined
keyType: TypeDetails | undefined
valueType: TypeDetails | undefined
}
interface PromiseType {
type: 'promise'
name?: string | typeof TYPESPEC_NODE_ANONYMOUS
comment?: Comment
awaited: Type | undefined
awaited: TypeDetails | undefined
}
/**
* Type definition for a property on a custom type definition.
*/
interface PropertyType {
export interface CustomTypePropertyType {
name?: string | typeof TYPESPEC_NODE_ANONYMOUS
comment?: Comment
isOptional?: boolean
type: Type | undefined
type: TypeDetails | undefined
}
// The following is the API of this module, and the only externally exposed
// piece. Given a reference to a function (method), return its type definition.
/**
* Get the type definition for a function (a method).
*
* @param ref The method identifier, in the form `@supabase/supabase-js.index.SupabaseClient.constructor`.
*/
export async function getTypeSpec(ref: string) {
const modules = await parseTypeSpec()
const delimiter = ref.indexOf('.')
const refMod = ref.substring(0, delimiter)
const mod = modules.find((mod) => mod.name === refMod)
return mod?.methods.get(ref)
}
const parseTypeSpec = cache_fullProcess_withDevCacheBust(
__parseTypeSpec,
join(SPEC_DIRECTORY, 'enrichments/tsdoc_v2/combined.json'),
() => JSON.stringify([])
)
/**
* @private
* Exposed for testing purposes only, PRIVATE DO NOT USE externally.
*/
export function __parseTypeSpec() {
export function parseTypeSpec() {
const modules = (typeSpec.children ?? []).map(parseMod)
return modules as Array<ModuleTypes>
}
@@ -316,17 +291,23 @@ function parseMethod(
comment,
}
if (node.signatures.length > 1) {
types.altSignatures = node.signatures
.slice(1)
.map((signature) => parseSignature(signature, map))
}
res.methods.set($ref, types)
}
function parseSignature(
signature: any,
map: Map<number, any>
): { params: Array<ParamType>; ret: ReturnType; comment?: Comment } {
const params: Array<ParamType> = (signature.parameters ?? []).map((param: any) => {
): { params: Array<FunctionParameterType>; ret: ReturnType; comment?: Comment } {
const params: Array<FunctionParameterType> = (signature.parameters ?? []).map((param: any) => {
const type = parseType(param.type, map)
const res: ParamType = {
const res: FunctionParameterType = {
name: nameOrAnonymous(param),
type,
}
@@ -447,6 +428,14 @@ function parseReferenceType(type: any, map: Map<number, any>) {
return parsePromiseType(type, map)
}
if (type.package === 'typescript' && type.qualifiedName === 'Extract') {
return parseExtractType(type, map)
}
if (type.package === 'typescript' && type.qualifiedName === 'Pick') {
return parsePickType(type, map)
}
if (type.package && type.qualifiedName) {
return {
type: 'nameOnly',
@@ -500,7 +489,7 @@ function parseUnionType(type: any, map: Map<number, any>): CustomUnionType {
const subTypes = type.types.filter(Boolean).map((type) => parseType(type, map))
return {
type: 'customUnion',
type: 'union',
name: nameOrAnonymous(type),
subTypes,
}
@@ -525,10 +514,44 @@ function parsePromiseType(type: any, map: Map<number, any>): PromiseType {
}
}
function parseExtractType(type: any, map: Map<number, any>): CustomUnionType {
const extractedUnion = parseUnionType(type.typeArguments[1], map)
return extractedUnion
}
function parsePickType(type: any, map: Map<number, any>) {
if (type.typeArguments[0].type === 'reference') {
const dereferencedNode = map.get(type.typeArguments[0].id)
if (!dereferencedNode) return undefined
const dereferencedType = parseType(dereferencedNode, map)
if (!dereferencedType?.properties) return undefined
switch (type.typeArguments[1].type) {
case 'literal':
return dereferencedType.properties.find(
(property) => property.name === type.typeArguments[1].value
)
case 'union':
default:
const subTypes = dereferencedType.properties.filter((property) =>
type.typeArguments[1].types.some((type) => type.value === property.name)
)
if (subTypes.length === 0) return undefined
return {
type: 'union',
subTypes,
}
}
}
return undefined
}
function parseReflectionType(type: any, map: Map<number, any>) {
if (!type.declaration) return undefined
let res: Type
let res: TypeDetails
switch (type.declaration.kindString) {
case 'Type literal':
res = parseTypeLiteral(type, map)
@@ -540,14 +563,14 @@ function parseReflectionType(type: any, map: Map<number, any>) {
return res
}
function parseTypeLiteral(type: any, map: Map<number, any>): Type {
function parseTypeLiteral(type: any, map: Map<number, any>): TypeDetails {
const name = nameOrAnonymous(type)
if ('children' in type.declaration) {
const properties = type.declaration.children.map((child: any) => parseTypeInternals(child, map))
return {
name,
type: 'customObject',
type: 'object',
properties,
} satisfies CustomObjectType
}
@@ -582,7 +605,7 @@ function parseTypeLiteral(type: any, map: Map<number, any>): Type {
function parseIndexedAccessType(type: any, map: Map<number, any>) {
return {
type: 'nameOnly',
name: `${type.objectType?.name ?? ''}[${type.indexType.value ?? type.indexType.name ?? ''}]`,
name: `${type.objectType?.name ?? ''}['${type.indexType.value ?? type.indexType.name ?? ''}']`,
}
}
@@ -599,7 +622,7 @@ function parseInterface(type: any, map: Map<number, any>): CustomObjectType {
const properties = (type.children ?? []).map((child) => parseTypeInternals(child, map))
return {
type: 'customObject',
type: 'object',
name: nameOrAnonymous(type),
properties,
}
@@ -641,7 +664,7 @@ function parseInternalProperty(elem: any, map: Map<number, any>) {
const res = {
name,
type,
} as PropertyType
} as CustomTypePropertyType
if (elem.flags?.isOptional) {
res.isOptional = true

View File

@@ -0,0 +1,51 @@
'use client'
import type { HTMLAttributes, PropsWithChildren } from 'react'
import { useContext } from 'react'
import { useInView } from 'react-intersection-observer'
import { cn } from 'ui'
import { ReferenceContentInitiallyScrolledContext } from '~/features/docs/Reference.navigation.client'
/**
* Wrap a reference section with client-side functionality:
*
* - Intersection observer to auto-update the URL when the user scrolls the page
* - An ID to scroll to programmatically. This is on the entire section rather
* than the heading to avoid problems with scroll-to position when the heading
* is sticky.
*/
export function ReferenceSectionWrapper({
id,
link,
children,
className,
}: PropsWithChildren<{ id: string; link: string; className?: string }> &
HTMLAttributes<HTMLElement>) {
const initialScrollHappened = useContext(ReferenceContentInitiallyScrolledContext)
const { ref } = useInView({
threshold: 0,
rootMargin: '-10% 0% -50% 0%',
onChange: (inView) => {
if (
inView &&
initialScrollHappened &&
window.scrollY > 0 /* Don't update on first navigation to introduction */
) {
window.history.replaceState(null, '', link)
}
},
})
return (
<section
ref={ref}
id={id}
className={cn('scroll-mt-[calc(var(--header-height)+4rem)]', className)}
>
{children}
</section>
)
}

View File

@@ -0,0 +1,626 @@
import { isEqual } from 'lodash'
import { ChevronRight, XCircle } from 'lucide-react'
import type { PropsWithChildren } from 'react'
import { Collapsible_Shadcn_, CollapsibleContent_Shadcn_, CollapsibleTrigger_Shadcn_, cn } from 'ui'
import { MDXRemoteBase } from '~/features/docs/MdxBase'
import { MDXRemoteRefs } from '~/features/docs/Reference.mdx'
import type {
CustomTypePropertyType,
FunctionParameterType,
MethodTypes,
TypeDetails,
} from '~/features/docs/Reference.typeSpec'
import { TYPESPEC_NODE_ANONYMOUS } from '~/features/docs/Reference.typeSpec'
import { ReferenceSectionWrapper } from '~/features/docs/Reference.ui.client'
import { normalizeMarkdown } from '~/features/docs/Reference.utils'
interface SectionProps extends PropsWithChildren {
link: string
slug?: string
columns?: 'single' | 'double'
}
function Section({ slug, link, columns = 'single', children }: SectionProps) {
const singleColumn = columns === 'single'
return (
<ReferenceSectionWrapper
id={slug}
link={link}
className={cn(
'grid grid-cols-[1fr] gap-x-16 gap-y-8',
singleColumn ? 'max-w-3xl' : '@4xl/article:grid-cols-[1fr,1fr]'
)}
>
{children}
</ReferenceSectionWrapper>
)
}
function Details({ children }: PropsWithChildren) {
/*
* `min-w` is necessary because these are used as grid children, which have
* default `min-w-auto`
*/
return <div className="w-full min-w-full">{children}</div>
}
function Examples({ children }: PropsWithChildren) {
/*
* `min-w` is necessary because these are used as grid children, which have
* default `min-w-auto`
*/
return <div className="w-full min-w-full sticky top-32 self-start">{children}</div>
}
function EducationSection({ children, slug, ...props }: SectionProps) {
return (
<ReferenceSectionWrapper id={slug} className={'prose max-w-none'} {...props}>
{children}
</ReferenceSectionWrapper>
)
}
interface EducationRowProps extends PropsWithChildren {
className?: string
}
function EducationRow({ className, children }: EducationRowProps) {
return <div className={cn('grid lg:grid-cols-2 gap-8 lg:gap-16', className)}>{children}</div>
}
export const RefSubLayout = {
Section,
EducationSection,
EducationRow,
Details,
Examples,
}
interface StickyHeaderProps {
title?: string
monoFont?: boolean
className?: string
}
export function StickyHeader({ title, monoFont = false, className }: StickyHeaderProps) {
return (
<h2
tabIndex={-1} // For programmatic focus on auto-scroll to section
className={cn(
'sticky top-0 z-10',
'w-full',
// Enough padding to cover the background when stuck to the top,
// then readjust with negative margin to prevent it looking too
// spaced-out in regular position
'pt-[calc(var(--header-height)+1rem)] -mt-[calc(var(--header-height)+1rem-2px)]',
// Same for bottom
'pb-8 -mb-4',
'bg-gradient-to-b from-background from-85% to-transparent to-100%',
'text-2xl font-medium text-foreground',
'scroll-mt-[calc(var(--header-height)+1rem)]',
monoFont && 'font-mono',
className
)}
>
{title}
</h2>
)
}
export function CollapsibleDetails({ title, content }: { title: string; content: string }) {
return (
<Collapsible_Shadcn_>
<CollapsibleTrigger_Shadcn_
className={cn(
'group',
'w-full h-8',
'border bg-surface-100 rounded',
'px-5',
'flex items-center gap-3',
'text-xs text-foreground-light',
'data-[state=open]:bg-surface-200',
'data-[state=open]:rounded-b-none data-[state=open]:border-b-0',
'transition-safe-all ease-out'
)}
>
<ChevronRight
size={12}
className="group-data-[state=open]:rotate-90 transition-transform"
/>
{title}
</CollapsibleTrigger_Shadcn_>
<CollapsibleContent_Shadcn_
className={cn(
'border border-default bg-surface-100 rounded-b',
'px-5 py-2',
'prose max-w-none text-sm'
)}
>
<MDXRemoteRefs source={content} />
</CollapsibleContent_Shadcn_>
</Collapsible_Shadcn_>
)
}
export function FnParameterDetails({
parameters,
altParameters,
className,
}: {
parameters: Array<object> | undefined
altParameters?: Array<Array<FunctionParameterType>>
className?: string
}) {
if (!parameters || parameters.length === 0) return
const combinedParameters = altParameters
? mergeAlternateParameters(parameters, altParameters)
: parameters
return (
<div className={className ?? ''}>
<h3 className="mb-3 text-base text-foreground">Parameters</h3>
<ul>
{combinedParameters.map((parameter, index) => (
<li key={index} className="border-t last-of-type:border-b py-5 flex flex-col gap-3">
<ParamOrTypeDetails paramOrType={parameter} />
</li>
))}
</ul>
</div>
)
}
interface SubContent {
name: string
isOptional?: boolean | 'NA' // not applicable
type?: string
description?: string
subContent: Array<SubContent>
}
function ParamOrTypeDetails({ paramOrType }: { paramOrType: object }) {
if (!('name' in paramOrType)) return
const description: string =
'description' in paramOrType
? (paramOrType.description as string)
: isFromTypespec(paramOrType)
? paramOrType.comment?.shortText ?? ''
: ''
const subContent =
'subContent' in paramOrType
? (paramOrType.subContent as Array<SubContent>)
: isFromTypespec(paramOrType)
? getSubDetails(paramOrType)
: undefined
return (
<>
<div className="flex flex-wrap items-baseline gap-3">
<span className="font-mono text-sm font-medium text-foreground">
{paramOrType.name === TYPESPEC_NODE_ANONYMOUS
? '[Anonymous]'
: (paramOrType.name as string)}
</span>
<RequiredBadge
isOptional={
'isOptional' in paramOrType ? (paramOrType.isOptional as boolean | 'NA') : false
}
/>
{/* @ts-ignore */}
{paramOrType?.comment?.tags?.some((tag) => tag.tag === 'deprecated') && (
<span className="text-xs text-warning-600">Deprecated</span>
)}
<span className="text-xs text-foreground-muted">{getTypeName(paramOrType)}</span>
</div>
{description && (
<div className="prose text-sm">
<MDXRemoteBase source={normalizeMarkdown(description)} />
</div>
)}
{subContent && subContent.length > 0 && <TypeSubDetails details={subContent} />}
</>
)
}
export function ReturnTypeDetails({ returnType }: { returnType: MethodTypes['ret'] }) {
// These custom names that aren't defined aren't particularly meaningful, so
// just don't display them.
const isNameOnlyType = returnType.type.type === 'nameOnly'
if (isNameOnlyType) return
const subContent = getSubDetails(returnType)
return (
<div>
<h3 className="mb-3 text-base text-foreground">Return Type</h3>
<div className="border-t border-b py-5 flex flex-col gap-3">
<div className="text-xs text-foreground-muted">{getTypeName(returnType)}</div>
{returnType.comment?.shortText && (
<div className="prose text-sm">
<MDXRemoteBase source={normalizeMarkdown(returnType.comment?.shortText)} />
</div>
)}
{subContent && subContent.length > 0 && <TypeSubDetails details={subContent} />}
</div>
</div>
)
}
function TypeSubDetails({
details,
className,
}: {
details: Array<SubContent> | Array<CustomTypePropertyType> | Array<TypeDetails>
className?: string
}) {
return (
<Collapsible_Shadcn_>
<CollapsibleTrigger_Shadcn_
className={cn(
'group',
'w-fit rounded-full',
'px-5 py-1',
'border border-default',
'flex items-center gap-2',
'text-left text-sm text-foreground-light',
'hover:bg-surface-100',
'data-[state=open]:w-full',
'data-[state=open]:rounded-b-none data-[state=open]:rounded-tl-lg data-[state=open]:rounded-tr-lg',
'transition [transition-property:width,background-color]',
className
)}
>
<XCircle
size={14}
className={cn(
'text-foreground-muted',
'group-data-[state=closed]:rotate-45',
'transition-transform'
)}
/>
Details
</CollapsibleTrigger_Shadcn_>
<CollapsibleContent_Shadcn_>
<ul className={cn('border-b border-x border-default', 'rounded-b-lg')}>
{details.map(
(detail: SubContent | CustomTypePropertyType | TypeDetails, index: number) => (
<li
key={index}
className={cn(
'px-5 py-3',
'border-t border-default first:border-t-0',
'flex flex-col gap-3'
)}
>
<ParamOrTypeDetails paramOrType={detail} />
</li>
)
)}
</ul>
</CollapsibleContent_Shadcn_>
</Collapsible_Shadcn_>
)
}
function RequiredBadge({ isOptional }: { isOptional: boolean | 'NA' }) {
return isOptional === true ? (
<span className="font-mono text-[10px] text-foreground-lighter tracking-wide">Optional</span>
) : isOptional === false ? (
<span
className={cn(
'inline-block',
'px-2 py-0.25 rounded-full',
'-translate-y-[0.125rem]', // retranslate to undo visual misalignment from the y-padding
'border border-amber-700 bg-amber-300',
'font-mono text-[10px] text-amber-900 uppercase tracking-wide'
)}
>
Required
</span>
) : undefined
}
/**
* Whether the param comes from overwritten params in the library spec file or
* directly from the type spec.
*
* We're cheating here, this isn't a full validation but rather just checking
* that it isn't overwritten.
*/
function isFromTypespec(parameter: object): parameter is MethodTypes['params'][number] {
return !('__overwritten' in parameter)
}
function getTypeName(parameter: object): string {
if (!('type' in parameter)) return ''
if (typeof parameter.type === 'string') {
return parameter.type
}
if (typeof parameter.type !== 'object' || !('type' in parameter.type)) {
return ''
}
const type = parameter.type
switch (type.type) {
case 'nameOnly':
return nameOrDefault(type, '')
case 'intrinsic':
return nameOrDefault(type, '')
case 'literal':
return 'value' in type ? (type.value === null ? 'null' : `"${type.value as string}"`) : ''
case 'record':
// Needs an extra level of wrapping to fake the wrapping parameter
// @ts-ignore
return `Record<${getTypeName({ type: type.keyType })}, ${getTypeName({ type: type.valueType })}>`
case 'object':
return nameOrDefault(type, 'object')
case 'function':
return 'function'
case 'promise':
// Needs an extra level of wrapping to fake the wrapping parameter
// @ts-ignore
return `Promise<${getTypeName({ type: type.awaited })}>`
case 'union':
return 'Union: expand to see options'
case 'index signature':
// Needs an extra level of wrapping to fake the wrapping parameter
// @ts-ignore
return `{ [key: ${getTypeName({ type: type.keyType })}]: ${getTypeName({ type: type.valueType })} }`
case 'array':
// Needs an extra level of wrapping to fake the wrapping parameter
// @ts-ignore
return `Array<${getTypeName({ type: type.elemType })}>`
}
return ''
}
function nameOrDefault(node: object, fallback: string) {
return 'name' in node && node.name !== TYPESPEC_NODE_ANONYMOUS ? (node.name as string) : fallback
}
function getSubDetails(parentType: MethodTypes['params'][number] | MethodTypes['ret']) {
let subDetails: Array<any>
switch (parentType.type?.type) {
case 'object':
subDetails = parentType.type.properties
break
case 'function':
subDetails = [
...(parentType.type.params.length === 0
? []
: [
{
name: 'Parameters',
type: 'callback parameters',
isOptional: 'NA',
params: parentType.type.params.map((param) => ({
...param,
isOptional: 'NA',
})),
},
]),
{ name: 'Return', type: parentType.type.ret.type, isOptional: 'NA' },
]
break
// @ts-ignore -- Adding these fake types to take advantage of existing recursion
case 'callback parameters':
// @ts-ignore -- Adding these fake types to take advantage of existing recursion
subDetails = parentType.params
break
case 'union':
subDetails = parentType.type.subTypes.map((subType, index) => ({
name: `union option ${index + 1}`,
type: { ...subType },
isOptional: 'NA',
}))
break
case 'promise':
if (parentType.type.awaited.type === 'union') {
subDetails = parentType.type.awaited.subTypes.map((subType, index) => ({
name: `union option ${index + 1}`,
type: { ...subType },
isOptional: 'NA',
}))
} else if (parentType.type.awaited.type === 'object') {
subDetails = parentType.type.awaited.properties.map((property) => ({
...property,
isOptional: 'NA',
}))
} else if (parentType.type.awaited.type === 'array') {
subDetails = [
{ name: 'array element', type: parentType.type.awaited.elemType, isOptional: 'NA' },
]
}
break
case 'array':
if (parentType.type.elemType.type === 'union') {
subDetails = parentType.type.elemType.subTypes.map((subType, index) => ({
name: `union option ${index + 1}`,
type: { ...subType },
isOptional: 'NA',
}))
}
if (parentType.type.elemType.type === 'object') {
subDetails = parentType.type.elemType.properties
}
break
}
subDetails?.sort((a, b) => (a.isOptional === true ? 1 : 0) - (b.isOptional === true ? 1 : 0))
return subDetails
}
function mergeAlternateParameters(
parameters: Array<object>,
altParameters: Array<Array<FunctionParameterType>>
) {
const combinedParameters = parameters.map((parameter) => {
if (!isFromTypespec(parameter)) return parameter
const parameterWithoutType = { ...parameter }
if ('type' in parameterWithoutType) {
delete parameterWithoutType.type
}
for (const alternate of altParameters) {
const match = alternate.find((alternateParam) => {
const alternateWithoutType = { ...alternateParam }
if ('type' in alternateWithoutType) {
delete alternateWithoutType.type
}
return isEqual(parameterWithoutType, alternateWithoutType)
})
if (match) {
// @ts-ignore
parameter = applyParameterMergeStrategy(parameter, match)
}
}
return parameter
})
return combinedParameters
}
function applyParameterMergeStrategy(
parameter: Pick<FunctionParameterType, 'type'>,
alternateParameter: Pick<FunctionParameterType, 'type'>
) {
if (!alternateParameter.type) {
// Nothing to merge, abort
return parameter as FunctionParameterType
}
const clonedParameter = JSON.parse(JSON.stringify(parameter)) as FunctionParameterType
if (!clonedParameter.type) {
clonedParameter.type = alternateParameter.type
return clonedParameter
}
switch (clonedParameter.type.type) {
case 'nameOnly':
mergeIntoUnion()
break
case 'literal':
mergeIntoUnion()
break
case 'record':
mergeIntoUnion()
break
case 'union':
if (alternateParameter.type.type === 'union') {
// Both unions, merge them
for (const alternateSubType of alternateParameter.type.subTypes) {
if (
!clonedParameter.type.subTypes.some((subType) => isEqual(subType, alternateSubType))
) {
clonedParameter.type.subTypes.push(alternateSubType)
}
}
} else {
if (
!clonedParameter.type.subTypes.some((subType) =>
isEqual(subType, alternateParameter.type)
)
) {
clonedParameter.type.subTypes.push(alternateParameter.type)
}
}
break
case 'object':
if (alternateParameter.type.type === 'object') {
// Check if the base and alternate parameters have different sets of
// required properties. If so, they can't be merged without loss of
// meaning and have to be represented as a union.
const requiredOriginalProperties = new Set(
clonedParameter.type.properties.filter(
// @ts-ignore -- NA introduced as an additional flag for display logic
(property) => property.isOptional !== true && property.isOptional !== 'NA'
)
)
const requiredAlternateProperties = new Set(
alternateParameter.type.properties.filter(
// @ts-ignore -- NA introduced as an additional flag for display logic
(property) => property.isOptional !== true && property.isOptional !== 'NA'
)
)
if (requiredOriginalProperties.size !== requiredAlternateProperties.size) {
mergeIntoUnion()
break
}
const union = new Set([...requiredOriginalProperties, ...requiredAlternateProperties])
if (union.size !== requiredOriginalProperties.size) {
mergeIntoUnion()
break
}
const clonedParametersByName = new Map(
clonedParameter.type.properties.map((property) => [property.name, property])
)
const alternateParametersByName = new Map(
alternateParameter.type.properties.map((property) => [property.name, property])
)
for (const [key, alternateValue] of alternateParametersByName) {
if (clonedParametersByName.has(key)) {
clonedParametersByName.set(
key,
applyParameterMergeStrategy(clonedParametersByName.get(key), alternateValue)
)
} else {
clonedParametersByName.set(key, alternateValue)
}
}
clonedParameter.type.properties = [...clonedParametersByName.values()]
} else {
mergeIntoUnion()
}
}
return clonedParameter as FunctionParameterType
/*********
* Utils *
*********/
function mergeIntoUnion() {
if (alternateParameter.type.type === 'union') {
const originalType = clonedParameter.type
if (
alternateParameter.type.subTypes.some((subType) => isEqual(subType, clonedParameter.type))
) {
clonedParameter.type = alternateParameter.type
} else {
clonedParameter.type = {
type: 'union',
subTypes: [originalType, ...alternateParameter.type.subTypes],
}
}
} else {
const originalType = parameter.type
if (!isEqual(originalType, alternateParameter.type)) {
clonedParameter.type = {
type: 'union',
subTypes: [originalType, alternateParameter.type],
}
}
}
}
}

View File

@@ -0,0 +1,169 @@
import { fromMarkdown } from 'mdast-util-from-markdown'
import { toMarkdown } from 'mdast-util-to-markdown'
import { mdxFromMarkdown, mdxToMarkdown } from 'mdast-util-mdx'
import { mdxjs } from 'micromark-extension-mdxjs'
import type { Metadata, ResolvingMetadata } from 'next'
import { redirect } from 'next/navigation'
import { visit } from 'unist-util-visit'
import { REFERENCES, clientSdkIds } from '~/content/navigation.references'
import { getFlattenedSections } from '~/features/docs/Reference.generated.singleton'
import { generateOpenGraphImageMeta } from '~/features/seo/openGraph'
import { BASE_PATH } from '~/lib/constants'
export interface AbbrevCommonClientLibSection {
id: string
type: string
title?: string
slug?: string
items?: Array<AbbrevCommonClientLibSection>
excludes?: Array<string>
meta?: {
shared?: boolean
}
}
export function parseReferencePath(slug: Array<string>) {
const isClientSdkReference = clientSdkIds.includes(slug[0])
if (isClientSdkReference) {
let [sdkId, maybeVersion, maybeCrawlers, ...path] = slug
if (!/v\d+/.test(maybeVersion)) {
maybeVersion = null
maybeCrawlers = maybeVersion
path = [maybeCrawlers, ...path]
}
if (maybeCrawlers !== 'crawlers') {
maybeCrawlers = null
path = [maybeCrawlers, ...path]
}
return {
__type: 'clientSdk' as const,
sdkId,
maybeVersion,
maybeCrawlers,
path,
}
} else {
return {
__type: 'UNIMPLEMENTED' as const,
}
}
}
async function generateStaticParamsForSdkVersion(sdkId: string, version: string) {
const flattenedSections = await getFlattenedSections(sdkId, version)
return flattenedSections
.filter((section) => section.type !== 'category' && !!section.slug)
.map((section) => ({
slug: [
sdkId,
version === REFERENCES[sdkId].versions[0] ? null : version,
'crawlers',
section.slug,
].filter(Boolean),
}))
}
export async function generateReferenceStaticParams() {
const nonCrawlerPages = clientSdkIds
.flatMap((sdkId) =>
REFERENCES[sdkId].versions.map((version) => ({
sdkId,
version,
}))
)
.map(({ sdkId, version }) => ({
slug: [sdkId, version === REFERENCES[sdkId].versions[0] ? null : version].filter(Boolean),
}))
return nonCrawlerPages
}
export async function generateReferenceMetadata(
{ params: { slug } }: { params: { slug: Array<string> } },
resolvingParent: ResolvingMetadata
): Promise<Metadata> {
const { alternates: parentAlternates, openGraph: parentOg } = await resolvingParent
const parsedPath = parseReferencePath(slug)
const isClientSdkReference = parsedPath.__type === 'clientSdk'
if (isClientSdkReference) {
const { sdkId, maybeVersion } = parsedPath
const version = maybeVersion ?? REFERENCES[sdkId].versions[0]
const flattenedSections = await getFlattenedSections(sdkId, version)
const displayName = REFERENCES[sdkId].name
const sectionTitle =
slug.length > 0
? flattenedSections.find((section) => section.slug === slug[0])?.title
: undefined
const url = [BASE_PATH, 'reference', sdkId, maybeVersion, slug[0]].filter(Boolean).join('/')
const images = generateOpenGraphImageMeta({
type: 'API Reference',
title: `${displayName}${sectionTitle ? `: ${sectionTitle}` : ''}`,
})
return {
title: `${displayName} API Reference | Supabase Docs`,
description: `API reference for the ${displayName} Supabase SDK`,
...(slug.length > 0
? {
alternates: {
...parentAlternates,
canonical: url,
},
}
: {}),
openGraph: {
...parentOg,
url,
images,
},
}
} else {
return {}
}
}
export async function redirectNonexistentReferenceSection(
sdkId: string,
version: string,
path: Array<string>,
isLatestVersion: boolean
) {
const initialSelectedSection = path[0]
const validSlugs = await generateStaticParamsForSdkVersion(sdkId, version)
if (
initialSelectedSection &&
!validSlugs.some((params) => params.slug[0] === initialSelectedSection)
) {
redirect(`/reference/${sdkId}` + (!isLatestVersion ? '/' + version : ''))
}
}
export function normalizeMarkdown(markdownUnescaped: string): string {
const markdown = markdownUnescaped.replaceAll(/(?<!\\)\{/g, '\\{').replaceAll(/(?<!\\)\}/g, '\\}')
const mdxTree = fromMarkdown(markdown, {
extensions: [mdxjs()],
mdastExtensions: [mdxFromMarkdown()],
})
visit(mdxTree, 'text', (node) => {
node.value = node.value.replace(/\n/g, ' ')
})
const content = toMarkdown(mdxTree, {
extensions: [mdxToMarkdown()],
})
return content
}

View File

@@ -12,7 +12,7 @@ import { BUILD_PREVIEW_HTML, IS_PREVIEW } from '~/lib/constants'
* rerender in prod, but this is fine because IS_PREVIEW will never change on
* you within a single build.
*/
const ShortcutPreviewBuild = ({ children }: PropsWithChildren) => {
export const ShortcutPreviewBuild = ({ children }: PropsWithChildren) => {
if (!BUILD_PREVIEW_HTML && IS_PREVIEW) {
// eslint-disable-next-line react-hooks/rules-of-hooks
const [isMounted, setIsMounted] = useState(false)
@@ -27,5 +27,3 @@ const ShortcutPreviewBuild = ({ children }: PropsWithChildren) => {
return children
}
export { ShortcutPreviewBuild }

View File

@@ -0,0 +1,29 @@
type RecObj<T extends object, K extends keyof T> = T[K] extends Array<T> ? T : never
export function deepFilterRec<T extends object, K extends keyof T>(
arr: Array<RecObj<T, K>>,
recKey: K,
filterFn: (item: T) => boolean
) {
return arr.reduce(
(acc, elem) => {
if (!filterFn(elem)) return acc
if (recKey in elem) {
const newSubitems = deepFilterRec(elem[recKey] as Array<RecObj<T, K>>, recKey, filterFn)
const newElem = { ...elem, [recKey]: newSubitems }
if (newSubitems.length > 0 || filterFn(elem)) acc.push(newElem)
} else {
acc.push(elem)
}
return acc
},
[] as Array<RecObj<T, K>>
)
}
export function pluckPromise<T, K extends keyof T>(promise: Promise<T>, key: K) {
return promise.then((data) => data[key])
}

View File

@@ -1,8 +1,8 @@
import { watch } from 'node:fs'
import { stat } from 'node:fs/promises'
import { IS_DEV } from '~/lib/constants'
import type { OrPromise } from '~/features/helpers.types'
import { IS_DEV } from '~/lib/constants'
/**
* Caches a function for the length of the server process.

View File

@@ -1,5 +1,7 @@
type OrPromise<T> = T | Promise<T>
type Json = string | number | boolean | { [key: string]: Json } | Json[]
type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] }
export type { OrPromise, WithRequired }
export type { Json, OrPromise, WithRequired }

View File

@@ -0,0 +1,18 @@
import { MISC_URL } from '~/lib/constants'
export function generateOpenGraphImageMeta({
type,
title,
description,
}: {
type: string
title: string
description?: string
}) {
return {
url: `${MISC_URL}/functions/v1/og-images?site=docs&type=${encodeURIComponent(type)}&title=${encodeURIComponent(title)}&description=${encodeURIComponent(description ?? 'undefined')}`,
width: 800,
height: 600,
alt: title,
}
}

View File

@@ -0,0 +1,9 @@
export function isElementInViewport(element: HTMLElement) {
const { top, left, width, height } = element.getBoundingClientRect()
const { innerWidth, innerHeight } = window
return (
((top >= 0 && top < innerHeight) || (top < 0 && top + height > 0)) &&
((left >= 0 && left < innerWidth) || (left < 0 && left + width > 0))
)
}

View File

@@ -13,9 +13,6 @@ import { DOCS_CONTENT_CONTAINER_ID } from '~/features/ui/helpers.constants'
import { menuState, useMenuMobileOpen } from '~/hooks/useMenuState'
const Footer = dynamic(() => import('~/components/Navigation/Footer'))
const NavigationMenu = dynamic(
() => import('~/components/Navigation/NavigationMenu/NavigationMenu')
)
const levelsData = {
home: {

View File

@@ -0,0 +1,23 @@
'use client'
import 'katex/dist/katex.min.css'
import { type PropsWithChildren } from 'react'
import { type MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
import { LayoutMainContent } from '~/layouts/DefaultLayout'
import { SidebarSkeleton } from '~/layouts/MainSkeleton'
interface LayoutProps extends PropsWithChildren {
menuId: MenuId
}
function ReferenceLayout({ menuId, children }: LayoutProps) {
return (
<SidebarSkeleton menuId={menuId}>
<LayoutMainContent className="pb-0">{children}</LayoutMainContent>
</SidebarSkeleton>
)
}
export { ReferenceLayout }

View File

@@ -10,3 +10,4 @@ export const IS_PLATFORM = process.env.NEXT_PUBLIC_IS_PLATFORM === 'true'
export const IS_PREVIEW = process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview'
export const LOCAL_SUPABASE = process.env.NEXT_PUBLIC_LOCAL_SUPABASE === 'true'
export const MISC_URL = process.env.NEXT_PUBLIC_MISC_URL ?? ''
export const SKIP_BUILD_STATIC_GENERATION = process.env.SKIP_BUILD_STATIC_GENERATION === 'true'

View File

@@ -14,6 +14,7 @@ import codeHikeTheme from 'config/code-hike.theme.json' assert { type: 'json' }
const DOCS_DIRECTORY = join(dirname(fileURLToPath(import.meta.url)), '..')
export const GUIDES_DIRECTORY = join(DOCS_DIRECTORY, 'content/guides')
export const REF_DOCS_DIRECTORY = join(DOCS_DIRECTORY, 'docs/ref')
export const SPEC_DIRECTORY = join(DOCS_DIRECTORY, 'spec')
export type GuideFrontmatter = {

View File

@@ -1,34 +1,45 @@
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { isbot } from 'isbot'
import type { NextRequest } from 'next/server'
import { NextResponse } from 'next/server'
import { clientSdkIds } from '~/content/navigation.references'
import { BASE_PATH } from '~/lib/constants'
const REFERENCE_PATH = `${(BASE_PATH ?? '') + '/'}reference`
export function middleware(request: NextRequest) {
const specs = ['javascript', 'dart', 'csharp']
const url = new URL(request.url)
let version = ''
if (request.url.includes('/v1/')) {
version = 'v1'
}
if (request.url.includes('/v0/')) {
version = 'v0'
if (!url.pathname.startsWith(REFERENCE_PATH)) {
return NextResponse.next()
}
if (isbot(request.headers.get('user-agent'))) {
for (const lib of specs) {
if (request.url.includes(`reference/${lib}`)) {
const requestSlug = request.url.split('/').pop()
let [, lib, maybeVersion, ...slug] = url.pathname.replace(REFERENCE_PATH, '').split('/')
return NextResponse.rewrite(
new URL(
`/docs/reference/${lib}/${version ? version + '/' : ''}crawlers/${requestSlug}`,
request.url
).toString()
)
if (clientSdkIds.includes(lib)) {
const version = /v\d+/.test(maybeVersion) ? maybeVersion : undefined
if (!version) {
slug = [maybeVersion, ...slug]
}
if (slug.length > 0) {
const rewriteUrl = new URL(url)
rewriteUrl.pathname = (BASE_PATH ?? '') + '/api/crawlers'
return NextResponse.rewrite(rewriteUrl)
}
}
} else {
return NextResponse.next()
const [, lib, maybeVersion] = url.pathname.replace(REFERENCE_PATH, '').split('/')
if (clientSdkIds.includes(lib)) {
const version = /v\d+/.test(maybeVersion) ? maybeVersion : null
const rewritePath = [REFERENCE_PATH, lib, version].filter(Boolean).join('/')
return NextResponse.rewrite(new URL(rewritePath, request.url))
}
}
return NextResponse.next()
}
export const config = {

View File

@@ -64,6 +64,11 @@ const nextConfig = {
},
},
transpilePackages: ['ui', 'ui-patterns', 'common', 'dayjs', 'shared-data', 'api-types', 'icons'],
experimental: {
outputFileTracingIncludes: {
'/api/crawlers': ['./features/docs/generated/**/*', './docs/ref/**/*'],
},
},
/**
* The SQL to REST API translator relies on libpg-query, which packages a
* native Node.js module that wraps the Postgres query parser.

View File

@@ -3,19 +3,23 @@
"version": "0.0.0",
"private": true,
"scripts": {
"predev": "npm run codegen:references",
"dev": "next dev --port 3001",
"dev:secrets:pull": "AWS_PROFILE=supabase-dev node ../../scripts/getSecrets.js -n local/docs",
"prebuild": "npm run codegen:references",
"build": "next build",
"build:analyze": "ANALYZE=true next build",
"start": "next start",
"lint": "next lint",
"typecheck": "tsc --noEmit",
"test": "vitest",
"test": "vitest --exclude \"**/*.smoke.test.ts\"",
"test:smoke": "npm run codegen:references && vitest -t \"prod smoke test\"",
"build:sitemap": "node ./internals/generate-sitemap.mjs",
"embeddings": "tsx scripts/search/generate-embeddings.ts",
"embeddings:refresh": "npm run embeddings -- --refresh",
"last-changed": "tsx scripts/last-changed.ts",
"last-changed:reset": "npm run last-changed -- --reset",
"codegen:references": "tsx features/docs/Reference.generated.script.ts",
"codemod:frontmatter": "node ./scripts/codemod/mdx-meta.mjs && prettier --write \"content/**/*.mdx\"",
"postbuild": "node ./internals/generate-sitemap.mjs"
},
@@ -80,7 +84,8 @@
"unist-util-filter": "^4.0.1",
"unist-util-visit": "^4.1.2",
"uuid": "^9.0.1",
"valtio": "^1.12.0"
"valtio": "^1.12.0",
"yaml": "^2.4.5"
},
"devDependencies": {
"@aws-sdk/client-secrets-manager": "^3.410.0",
@@ -91,6 +96,7 @@
"@types/unist": "^2.0.6",
"acorn": "^8.11.3",
"api-types": "*",
"cheerio": "^1.0.0-rc.12",
"config": "*",
"dotenv": "^16.0.3",
"ejs": "^3.1.10",

View File

@@ -1,30 +0,0 @@
import clientLibsCommonSections from '~/spec/common-client-libs-sections.json'
import spec from '~/spec/supabase_csharp_v1.yml' assert { type: 'yml' }
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
const sections = flattenSections(clientLibsCommonSections)
const libraryPath = '/csharp'
export default function CSharpReference(props) {
return (
<RefSectionHandler
menuId={MenuId.RefCSharpV1}
sections={sections}
spec={spec}
pageProps={props}
type="client-lib"
/>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, libraryPath)
}
export async function getStaticPaths() {
return handleRefGetStaticPaths(sections)
}

View File

@@ -1,45 +0,0 @@
import clientLibsCommonSections from '~/spec/common-client-libs-sections.json'
import spec from '~/spec/supabase_csharp_v1.yml' assert { type: 'yml' }
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import { useRouter } from 'next/compat/router'
import RefSEO from '~/components/reference/RefSEO'
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
import type { TypeSpec } from '~/components/reference/Reference.types'
const sections = flattenSections(clientLibsCommonSections)
const libraryPath = '/csharp'
export default function CSharpReference(props) {
const router = useRouter()
const slug = router.query.slug[0]
const filteredSection = sections.filter((section) => section.id === slug)
const pageTitle = filteredSection[0]?.title
? `${filteredSection[0]?.title} | Supabase`
: 'Supabase'
return (
<>
<RefSEO title={pageTitle} />
<RefSectionHandler
menuId={MenuId.RefCSharpV1}
sections={filteredSection}
spec={spec}
pageProps={props}
type="client-lib"
/>
</>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, libraryPath)
}
export async function getStaticPaths() {
return handleRefGetStaticPaths(sections)
}

View File

@@ -1,30 +0,0 @@
import clientLibsCommonSections from '~/spec/common-client-libs-sections.json'
import spec from '~/spec/supabase_csharp_v0.yml' assert { type: 'yml' }
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
const sections = flattenSections(clientLibsCommonSections)
const libraryPath = '/csharp/v0'
export default function CSharpReference(props) {
return (
<RefSectionHandler
menuId={MenuId.RefCSharpV0}
sections={sections}
spec={spec}
pageProps={props}
type="client-lib"
/>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, libraryPath)
}
export async function getStaticPaths() {
return handleRefGetStaticPaths(sections)
}

View File

@@ -1,43 +0,0 @@
import clientLibsCommonSections from '~/spec/common-client-libs-sections.json'
import spec from '~/spec/supabase_csharp_v0.yml' assert { type: 'yml' }
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import { useRouter } from 'next/compat/router'
import RefSEO from '~/components/reference/RefSEO'
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
const sections = flattenSections(clientLibsCommonSections)
const libraryPath = '/csharp/v0'
export default function JSReference(props) {
const router = useRouter()
const slug = router.query.slug[0]
const filteredSection = sections.filter((section) => section.id === slug)
const pageTitle = filteredSection[0]?.title
? `${filteredSection[0]?.title} | Supabase`
: 'Supabase'
return (
<>
<RefSEO title={pageTitle} />
<RefSectionHandler
menuId={MenuId.RefCSharpV0}
sections={filteredSection}
spec={spec}
pageProps={props}
type="client-lib"
/>
</>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, libraryPath)
}
export async function getStaticPaths() {
return handleRefGetStaticPaths(sections)
}

View File

@@ -1,30 +0,0 @@
import clientLibsCommonSections from '~/spec/common-client-libs-sections.json'
import spec from '~/spec/supabase_dart_v2.yml' assert { type: 'yml' }
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
const sections = flattenSections(clientLibsCommonSections)
const libraryPath = '/dart'
export default function DartReference(props) {
return (
<RefSectionHandler
menuId={MenuId.RefDartV2}
sections={sections}
spec={spec}
pageProps={props}
type="client-lib"
/>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, libraryPath)
}
export async function getStaticPaths() {
return handleRefGetStaticPaths(sections)
}

View File

@@ -1,44 +0,0 @@
import clientLibsCommonSections from '~/spec/common-client-libs-sections.json'
import spec from '~/spec/supabase_dart_v2.yml' assert { type: 'yml' }
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import { useRouter } from 'next/compat/router'
import RefSEO from '~/components/reference/RefSEO'
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
const sections = flattenSections(clientLibsCommonSections)
const libraryPath = '/dart'
export default function DartReference(props) {
const router = useRouter()
const slug = router.query.slug[0]
const filteredSection = sections.filter((section) => section.slug === slug)
const pageTitle = filteredSection[0]?.title
? `${filteredSection[0]?.title} | Supabase`
: 'Supabase'
return (
<>
<RefSEO title={pageTitle} />
<RefSectionHandler
menuId={MenuId.RefDartV2}
sections={filteredSection}
spec={spec}
pageProps={props}
type="client-lib"
/>
</>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, libraryPath)
}
export async function getStaticPaths() {
return handleRefGetStaticPaths(sections)
}

View File

@@ -1,31 +0,0 @@
import clientLibsCommonSections from '~/spec/common-client-libs-sections.json'
import spec from '~/spec/supabase_dart_v1.yml' assert { type: 'yml' }
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
const sections = flattenSections(clientLibsCommonSections)
const libraryPath = '/dart/v0'
export default function JSReference(props) {
return (
<RefSectionHandler
menuId={MenuId.RefDartV1}
sections={sections}
spec={spec}
pageProps={props}
type="client-lib"
isOldVersion
/>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, libraryPath)
}
export async function getStaticPaths() {
return handleRefGetStaticPaths(sections)
}

View File

@@ -1,43 +0,0 @@
import clientLibsCommonSections from '~/spec/common-client-libs-sections.json'
import spec from '~/spec/supabase_dart_v1.yml' assert { type: 'yml' }
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import { useRouter } from 'next/compat/router'
import RefSEO from '~/components/reference/RefSEO'
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
const sections = flattenSections(clientLibsCommonSections)
const libraryPath = '/dart/v0'
export default function DartReference(props) {
const router = useRouter()
const slug = router.query.slug[0]
const filteredSection = sections.filter((section) => section.slug === slug)
const pageTitle = filteredSection[0]?.title
? `${filteredSection[0]?.title} | Supabase`
: 'Supabase'
return (
<>
<RefSEO title={pageTitle} />
<RefSectionHandler
menuId={MenuId.RefDartV1}
sections={filteredSection}
spec={spec}
pageProps={props}
type="client-lib"
/>
</>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, libraryPath)
}
export async function getStaticPaths() {
return handleRefGetStaticPaths(sections)
}

View File

@@ -1,33 +0,0 @@
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import clientLibsCommonSections from '~/spec/common-client-libs-sections.json' assert { type: 'json' }
import typeSpec from '~/spec/enrichments/tsdoc_v2/combined.json' assert { type: 'json' }
import spec from '~/spec/supabase_js_v2.yml' assert { type: 'yml' }
const sections = flattenSections(clientLibsCommonSections)
const libraryPath = '/javascript'
export default function JSReference(props) {
return (
<RefSectionHandler
menuId={MenuId.RefJavaScriptV2}
sections={sections}
spec={spec}
typeSpec={typeSpec}
pageProps={props}
type="client-lib"
/>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, libraryPath)
}
export async function getStaticPaths() {
return handleRefGetStaticPaths(sections)
}

View File

@@ -1,46 +0,0 @@
import clientLibsCommonSections from '~/spec/common-client-libs-sections.json'
import typeSpec from '~/spec/enrichments/tsdoc_v2/combined.json'
import spec from '~/spec/supabase_js_v2.yml' assert { type: 'yml' }
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import { useRouter } from 'next/compat/router'
import RefSEO from '~/components/reference/RefSEO'
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
const sections = flattenSections(clientLibsCommonSections)
const libraryPath = '/javascript'
export default function JSReference(props) {
const router = useRouter()
const slug = router.query.slug[0]
const filteredSection = sections.filter((section) => section.slug === slug)
const pageTitle = filteredSection[0]?.title
? `${filteredSection[0]?.title} | Supabase`
: 'Supabase'
return (
<>
<RefSEO title={pageTitle} />
<RefSectionHandler
menuId={MenuId.RefJavaScriptV2}
sections={filteredSection}
spec={spec}
typeSpec={typeSpec}
pageProps={props}
type="client-lib"
/>
</>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, libraryPath)
}
export async function getStaticPaths() {
return handleRefGetStaticPaths(sections)
}

View File

@@ -1,34 +0,0 @@
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import clientLibsCommonSections from '~/spec/common-client-libs-sections.json' assert { type: 'json' }
import typeSpec from '~/spec/enrichments/tsdoc_v1/combined.json' assert { type: 'json' }
import spec from '~/spec/supabase_js_v1.yml' assert { type: 'yml' }
const sections = flattenSections(clientLibsCommonSections)
const libraryPath = '/javascript/v1'
export default function JSReference(props) {
return (
<RefSectionHandler
menuId={MenuId.RefJavaScriptV1}
sections={sections}
spec={spec}
typeSpec={typeSpec}
pageProps={props}
type="client-lib"
isOldVersion
/>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, libraryPath)
}
export async function getStaticPaths() {
return handleRefGetStaticPaths(sections)
}

View File

@@ -1,46 +0,0 @@
import clientLibsCommonSections from '~/spec/common-client-libs-sections.json'
import typeSpec from '~/spec/enrichments/tsdoc_v1/combined.json'
import spec from '~/spec/supabase_js_v1.yml' assert { type: 'yml' }
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import { useRouter } from 'next/compat/router'
import RefSEO from '~/components/reference/RefSEO'
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
const sections = flattenSections(clientLibsCommonSections)
const libraryPath = '/javascript/v1'
export default function JSReference(props) {
const router = useRouter()
const slug = router.query.slug[0]
const filteredSection = sections.filter((section) => section.slug === slug)
const pageTitle = filteredSection[0]?.title
? `${filteredSection[0]?.title} | Supabase`
: 'Supabase'
return (
<>
<RefSEO title={pageTitle} />
<RefSectionHandler
menuId={MenuId.RefJavaScriptV1}
sections={filteredSection}
spec={spec}
typeSpec={typeSpec}
pageProps={props}
type="client-lib"
/>
</>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, libraryPath)
}
export async function getStaticPaths() {
return handleRefGetStaticPaths(sections)
}

View File

@@ -1,30 +0,0 @@
import clientLibsCommonSections from '~/spec/common-client-libs-sections.json'
import spec from '~/spec/supabase_kt_v2.yml' assert { type: 'yml' }
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
const sections = flattenSections(clientLibsCommonSections)
const libraryPath = '/kotlin'
export default function KotlinReference(props) {
return (
<RefSectionHandler
menuId={MenuId.RefKotlinV2}
sections={sections}
spec={spec}
pageProps={props}
type="client-lib"
/>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, libraryPath)
}
export async function getStaticPaths() {
return handleRefGetStaticPaths(sections)
}

View File

@@ -1,44 +0,0 @@
import clientLibsCommonSections from '~/spec/common-client-libs-sections.json'
import spec from '~/spec/supabase_kt_v2.yml' assert { type: 'yml' }
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import { useRouter } from 'next/compat/router'
import RefSEO from '~/components/reference/RefSEO'
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
const sections = flattenSections(clientLibsCommonSections)
const libraryPath = '/kotlin'
export default function KotlinReference(props) {
const router = useRouter()
const slug = router.query.slug[0]
const filteredSection = sections.filter((section) => section.slug === slug)
const pageTitle = filteredSection[0]?.title
? `${filteredSection[0]?.title} | Supabase`
: 'Supabase'
return (
<>
<RefSEO title={pageTitle} />
<RefSectionHandler
menuId={MenuId.RefKotlinV2}
sections={filteredSection}
spec={spec}
pageProps={props}
type="client-lib"
/>
</>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, libraryPath)
}
export async function getStaticPaths() {
return handleRefGetStaticPaths(sections)
}

View File

@@ -1,31 +0,0 @@
import clientLibsCommonSections from '~/spec/common-client-libs-sections.json'
import spec from '~/spec/supabase_kt_v1.yml' assert { type: 'yml' }
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
const sections = flattenSections(clientLibsCommonSections)
const libraryPath = '/kotlin/v1'
export default function KotlinReference(props) {
return (
<RefSectionHandler
menuId={MenuId.RefKotlinV1}
sections={sections}
spec={spec}
pageProps={props}
type="client-lib"
isOldVersion
/>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, libraryPath)
}
export async function getStaticPaths() {
return handleRefGetStaticPaths(sections)
}

View File

@@ -1,43 +0,0 @@
import clientLibsCommonSections from '~/spec/common-client-libs-sections.json'
import spec from '~/spec/supabase_kt_v1.yml' assert { type: 'yml' }
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import { useRouter } from 'next/compat/router'
import RefSEO from '~/components/reference/RefSEO'
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
const sections = flattenSections(clientLibsCommonSections)
const libraryPath = '/kotlin/v1'
export default function KotlinReference(props) {
const router = useRouter()
const slug = router.query.slug[0]
const filteredSection = sections.filter((section) => section.id === slug)
const pageTitle = filteredSection[0]?.title
? `${filteredSection[0]?.title} | Supabase`
: 'Supabase'
return (
<>
<RefSEO title={pageTitle} />
<RefSectionHandler
menuId={MenuId.RefKotlinV1}
sections={filteredSection}
spec={spec}
pageProps={props}
type="client-lib"
/>
</>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, libraryPath)
}
export async function getStaticPaths() {
return handleRefGetStaticPaths(sections)
}

View File

@@ -1,30 +0,0 @@
import clientLibsCommonSections from '~/spec/common-client-libs-sections.json'
import spec from '~/spec/supabase_py_v2.yml' assert { type: 'yml' }
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
const sections = flattenSections(clientLibsCommonSections)
const libraryPath = '/python'
export default function PyReference(props) {
return (
<RefSectionHandler
menuId={MenuId.RefPythonV2}
sections={sections}
spec={spec}
pageProps={props}
type="client-lib"
/>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, libraryPath)
}
export async function getStaticPaths() {
return handleRefGetStaticPaths(sections)
}

View File

@@ -1,44 +0,0 @@
import clientLibsCommonSections from '~/spec/common-client-libs-sections.json'
import spec from '~/spec/supabase_py_v2.yml' assert { type: 'yml' }
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import { useRouter } from 'next/compat/router'
import RefSEO from '~/components/reference/RefSEO'
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
const sections = flattenSections(clientLibsCommonSections)
const libraryPath = '/python'
export default function PyReference(props) {
const router = useRouter()
const slug = router.query.slug[0]
const filteredSection = sections.filter((section) => section.id === slug)
const pageTitle = filteredSection[0]?.title
? `${filteredSection[0]?.title} | Supabase`
: 'Supabase'
return (
<>
<RefSEO title={pageTitle} />
<RefSectionHandler
menuId={MenuId.RefPythonV2}
sections={filteredSection}
spec={spec}
pageProps={props}
type="client-lib"
/>
</>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, libraryPath)
}
export async function getStaticPaths() {
return handleRefGetStaticPaths(sections)
}

View File

@@ -1,30 +0,0 @@
import clientLibsCommonSections from '~/spec/common-client-libs-sections.json'
import spec from '~/spec/supabase_swift_v2.yml' assert { type: 'yml' }
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
const sections = flattenSections(clientLibsCommonSections)
const libraryPath = '/swift'
export default function SwiftReference(props) {
return (
<RefSectionHandler
menuId={MenuId.RefSwiftV2}
sections={sections}
spec={spec}
pageProps={props}
type="client-lib"
/>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, libraryPath)
}
export async function getStaticPaths() {
return handleRefGetStaticPaths(sections)
}

View File

@@ -1,44 +0,0 @@
import clientLibsCommonSections from '~/spec/common-client-libs-sections.json'
import spec from '~/spec/supabase_swift_v2.yml' assert { type: 'yml' }
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import { useRouter } from 'next/compat/router'
import RefSEO from '~/components/reference/RefSEO'
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
const sections = flattenSections(clientLibsCommonSections)
const libraryPath = '/swift'
export default function SwiftReference(props) {
const router = useRouter()
const slug = router.query.slug[0]
const filteredSection = sections.filter((section) => section.id === slug)
const pageTitle = filteredSection[0]?.title
? `${filteredSection[0]?.title} | Supabase`
: 'Supabase'
return (
<>
<RefSEO title={pageTitle} />
<RefSectionHandler
menuId={MenuId.RefSwiftV2}
sections={filteredSection}
spec={spec}
pageProps={props}
type="client-lib"
/>
</>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, libraryPath)
}
export async function getStaticPaths() {
return handleRefGetStaticPaths(sections)
}

View File

@@ -1,30 +0,0 @@
import clientLibsCommonSections from '~/spec/common-client-libs-sections.json'
import spec from '~/spec/supabase_swift_v2.yml' assert { type: 'yml' }
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
const sections = flattenSections(clientLibsCommonSections)
const libraryPath = '/swift/v2'
export default function SwiftReference(props) {
return (
<RefSectionHandler
menuId={MenuId.RefSwiftV1}
sections={sections}
spec={spec}
pageProps={props}
type="client-lib"
/>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, libraryPath)
}
export async function getStaticPaths() {
return handleRefGetStaticPaths(sections)
}

View File

@@ -1,43 +0,0 @@
import clientLibsCommonSections from '~/spec/common-client-libs-sections.json'
import spec from '~/spec/supabase_swift_v2.yml' assert { type: 'yml' }
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import { useRouter } from 'next/compat/router'
import RefSEO from '~/components/reference/RefSEO'
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
const sections = flattenSections(clientLibsCommonSections)
const libraryPath = '/swift/v2'
export default function SwiftReference(props) {
const router = useRouter()
const slug = router.query.slug[0]
const filteredSection = sections.filter((section) => section.id === slug)
const pageTitle = filteredSection[0]?.title
? `${filteredSection[0]?.title} | Supabase`
: 'Supabase'
return (
<>
<RefSEO title={pageTitle} />
<RefSectionHandler
menuId={MenuId.RefSwiftV1}
sections={filteredSection}
spec={spec}
pageProps={props}
type="client-lib"
/>
</>
)
}
export async function getStaticProps() {
return handleRefStaticProps(sections, libraryPath)
}
export async function getStaticPaths() {
return handleRefGetStaticPaths(sections)
}

View File

@@ -12,6 +12,7 @@
"type": "markdown",
"excludes": [
"reference_javascript_v1",
"reference_kotlin_v1",
"reference_swift_v1"
]
},
@@ -999,11 +1000,12 @@
"excludes": [
"reference_dart_v1",
"reference_dart_v2",
"reference_javascript_v1",
"reference_kotlin_v1",
"reference_kotlin_v2",
"reference_python_v2",
"reference_swift_v1",
"reference_swift_v2",
"reference_kotlin_v1",
"reference_kotlin_v2"
"reference_swift_v2"
],
"items": [
{

View File

@@ -3752,7 +3752,7 @@ functions:
with advanced operators.
- `unquoted text`: text not inside quote marks will be converted to terms separated by & operators, as if processed by plainto_tsquery.
- `"quoted text"`: text inside quote marks will be converted to terms separated by <-> operators, as if processed by phraseto_tsquery.
- `"quoted text"`: text inside quote marks will be converted to terms separated by `<->` operators, as if processed by phraseto_tsquery.
- `OR`: the word “or” will be converted to the | operator.
- `-`: a dash will be converted to the ! operator.

View File

@@ -1584,7 +1584,7 @@ functions:
with advanced operators.
- `unquoted text`: text not inside quote marks will be converted to terms separated by & operators, as if processed by plainto_tsquery.
- `"quoted text"`: text inside quote marks will be converted to terms separated by <-> operators, as if processed by phraseto_tsquery.
- `"quoted text"`: text inside quote marks will be converted to terms separated by `<->` operators, as if processed by phraseto_tsquery.
- `OR`: the word “or” will be converted to the | operator.
- `-`: a dash will be converted to the ! operator.

View File

@@ -1584,7 +1584,7 @@ functions:
with advanced operators.
- `unquoted text`: text not inside quote marks will be converted to terms separated by & operators, as if processed by plainto_tsquery.
- `"quoted text"`: text inside quote marks will be converted to terms separated by <-> operators, as if processed by phraseto_tsquery.
- `"quoted text"`: text inside quote marks will be converted to terms separated by `<->` operators, as if processed by phraseto_tsquery.
- `OR`: the word “or” will be converted to the | operator.
- `-`: a dash will be converted to the ! operator.

View File

@@ -2847,7 +2847,7 @@ functions:
with advanced operators.
- `unquoted text`: text not inside quote marks will be converted to terms separated by & operators, as if processed by plainto_tsquery.
- `"quoted text"`: text inside quote marks will be converted to terms separated by <-> operators, as if processed by phraseto_tsquery.
- `"quoted text"`: text inside quote marks will be converted to terms separated by `<->` operators, as if processed by phraseto_tsquery.
- `OR`: the word “or” will be converted to the | operator.
- `-`: a dash will be converted to the ! operator.

View File

@@ -6667,7 +6667,7 @@ functions:
with advanced operators.
- `unquoted text`: text not inside quote marks will be converted to terms separated by & operators, as if processed by plainto_tsquery.
- `"quoted text"`: text inside quote marks will be converted to terms separated by <-> operators, as if processed by phraseto_tsquery.
- `"quoted text"`: text inside quote marks will be converted to terms separated by `<->` operators, as if processed by phraseto_tsquery.
- `OR`: the word “or” will be converted to the | operator.
- `-`: a dash will be converted to the ! operator.

View File

@@ -238,45 +238,6 @@ functions:
- When both **Confirm email** and **Confirm phone** (even when phone provider is disabled) are enabled in [your project](/dashboard/project/_/auth/providers), an obfuscated/fake user object is returned.
- When either **Confirm email** or **Confirm phone** (even when phone provider is disabled) is disabled, the error message, `User already registered` is returned.
- To fetch the currently logged-in user, refer to [`getUser()`](/docs/reference/javascript/auth-getuser).
overwriteParams:
- name: credentials
type: SignUpWithPasswordCredentials
subContent:
- name: email
isOptional: true
type: string
description: One of `email` or `phone` must be provided.
- name: phone
isOptional: true
type: string
description: One of `email` or `phone` must be provided.
- name: password
type: string
- name: options
isOptional: true
type: object
subContent:
- name: emailRedirectTo
isOptional: true
type: string
description: >
Only for email signups.
The redirect URL embedded in the email link.
Must be a configured redirect URL for your Supabase instance.
- name: data
isOptional: true
type: object
description: >
A custom data object to store additional user metadata.
- name: captchaToken
isOptional: true
type: string
- name: channel
isOptional: true
type: sms | whatsapp
description: >
The channel to use for sending messages.
Only for phone signups.
examples:
- id: sign-up
name: Sign up with an email and password
@@ -710,27 +671,6 @@ functions:
- id: sign-in-with-password
title: 'signInWithPassword()'
$ref: '@supabase/auth-js.GoTrueClient.signInWithPassword'
overwriteParams:
- name: credentials
type: SignInWithPasswordCredentials
subContent:
- name: email
isOptional: true
type: string
description: One of `email` or `phone` must be provided.
- name: phone
isOptional: true
type: string
description: One of `email` or `phone` must be provided.
- name: password
type: string
- name: options
isOptional: true
type: object
subContent:
- name: captchaToken
isOptional: true
type: string
notes: |
- Requires either an email and password or a phone number and password.
examples:
@@ -844,49 +784,6 @@ functions:
- id: sign-in-with-otp
title: 'signInWithOtp()'
$ref: '@supabase/auth-js.GoTrueClient.signInWithOtp'
overwriteParams:
- name: credentials
type: SignInWithPasswordlessCredentials
subContent:
- name: email
isOptional: true
type: string
description: One of `email` or `phone` must be provided.
- name: phone
isOptional: true
type: string
description: One of `email` or `phone` must be provided.
- name: options
isOptional: true
type: object
subContent:
- name: emailRedirectTo
isOptional: true
type: string
description: >
Only for email signups.
The redirect URL embedded in the email link.
Must be a configured redirect URL for your Supabase instance.
- name: shouldCreateUser
isOptional: true
type: boolean
description: >
Whether to create the user if they don't already exist.
Defaults to true.
- name: data
isOptional: true
type: object
description: >
A custom data object to store additional user metadata.
- name: captchaToken
isOptional: true
type: string
- name: channel
isOptional: true
type: sms | whatsapp
description: >
The channel to use for sending messages.
Only for phone signups.
notes: |
- Requires either an email or phone number.
- This method is used for passwordless sign-ins where a OTP is sent to the user's email or phone number.
@@ -1095,33 +992,6 @@ functions:
- id: sign-in-with-sso
title: 'signInWithSSO()'
$ref: '@supabase/auth-js.GoTrueClient.signInWithSSO'
overwriteParams:
- name: params
type: SignInWithSSO
subContent:
- name: providerId
isOptional: true
type: string
description: >
UUID of the SSO provider.
One of `providerId` or `domain` is required.
- name: domain
isOptional: true
type: string
description: >
Domain name of the organization to use SSO with.
One of `providerId` or `domain` is required.
- name: options
isOptional: true
type: object
subContent:
- name: redirectTo
type: string
description: >
The URL to redirect the user to after they have signed in.
Must be a configured redirect URL for your Supabase instance.
- name: captchaToken
type: string
notes: |
- Before you can call this method you need to [establish a connection](/docs/guides/auth/sso/auth-sso-saml#managing-saml-20-connections) to an identity provider. Use the [CLI commands](/docs/reference/cli/supabase-sso) to do this.
- If you've associated an email domain to the identity provider, you can use the `domain` property to start a sign-in flow.
@@ -1181,44 +1051,6 @@ functions:
- id: verify-otp
title: 'verifyOtp()'
$ref: '@supabase/auth-js.GoTrueClient.verifyOtp'
overwriteParams:
- name: params
type: VerifyOtpParams
subContent:
- name: phone
isOptional: true
type: string
description: One of `phone`, `email`, or `token_hash` must be provided.
- name: email
isOptional: true
type: string
description: One of `phone`, `email`, or `token_hash` must be provided.
- name: token_hash
isOptional: true
type: string
description: >
The token hash from the user's email link.
One of `phone`, `email`, or `token_hash` must be provided.
- name: type
type: sms | phone_change | signup | invite | magiclink | recovery | email_change | email
- name: token
isOptional: true
type: string
description: The OTP sent to the user. Required if using `phone` or `email`.
- name: options
isOptional: true
type: object
subContent:
- name: redirectTo
isOptional: true
type: string
description: >
A URL to redirect the user to after they are confirmed.
Must be in your configured redirect URLs.
- name: captchaToken
isOptional: true
type: string
description: Deprecated.
notes: |
- The `verifyOtp` method takes in different verification types. If a phone number is used, the type can either be `sms` or `phone_change`. If an email address is used, the type can be one of the following: `email`, `recovery`, `invite` or `email_change` (`signup` and `magiclink` types are deprecated).
- The verification type used should be determined based on the corresponding auth method called before `verifyOtp` to sign up / sign-in a user.
@@ -1953,6 +1785,7 @@ functions:
const { session, user } = data
```
response: |
```json
{
"data": {
"user": {
@@ -2051,6 +1884,7 @@ functions:
},
"error": null
}
```
- id: refresh-session-using-a-passed-in-session
name: Refresh session using a refresh token
isSpotlight: false
@@ -5707,7 +5541,7 @@ functions:
with advanced operators.
- `unquoted text`: text not inside quote marks will be converted to terms separated by & operators, as if processed by plainto_tsquery.
- `"quoted text"`: text inside quote marks will be converted to terms separated by <-> operators, as if processed by phraseto_tsquery.
- `"quoted text"`: text inside quote marks will be converted to terms separated by `<->` operators, as if processed by phraseto_tsquery.
- `OR`: the word “or” will be converted to the | operator.
- `-`: a dash will be converted to the ! operator.

View File

@@ -1707,7 +1707,7 @@ functions:
with advanced operators.
- `unquoted text`: text not inside quote marks will be converted to terms separated by & operators, as if processed by plainto_tsquery.
- `"quoted text"`: text inside quote marks will be converted to terms separated by <-> operators, as if processed by phraseto_tsquery.
- `"quoted text"`: text inside quote marks will be converted to terms separated by `<->` operators, as if processed by phraseto_tsquery.
- `OR`: the word “or” will be converted to the | operator.
- `-`: a dash will be converted to the ! operator.

View File

@@ -2163,7 +2163,7 @@ functions:
with advanced operators.
- `unquoted text`: text not inside quote marks will be converted to terms separated by & operators, as if processed by plainto_tsquery.
- `"quoted text"`: text inside quote marks will be converted to terms separated by <-> operators, as if processed by phraseto_tsquery.
- `"quoted text"`: text inside quote marks will be converted to terms separated by `<->` operators, as if processed by phraseto_tsquery.
- `OR`: the word “or” will be converted to the | operator.
- `-`: a dash will be converted to the ! operator.

View File

@@ -3413,7 +3413,7 @@ functions:
with advanced operators.
- `unquoted text`: text not inside quote marks will be converted to terms separated by & operators, as if processed by plainto_tsquery.
- `"quoted text"`: text inside quote marks will be converted to terms separated by <-> operators, as if processed by phraseto_tsquery.
- `"quoted text"`: text inside quote marks will be converted to terms separated by `<->` operators, as if processed by phraseto_tsquery.
- `OR`: the word “or” will be converted to the | operator.
- `-`: a dash will be converted to the ! operator.

View File

@@ -33,7 +33,6 @@
"**/*.ts",
"**/*.tsx",
"pages/guides/append-test.js",
"jest.config.mjs",
".next/types/**/*.ts",
"./../../packages/ui/src/**/*.d.ts"
],

View File

@@ -1,3 +1 @@
export * from './next'
export type Json = string | number | boolean | { [key: string]: Json } | Json[]

200
package-lock.json generated
View File

@@ -988,7 +988,8 @@
"unist-util-filter": "^4.0.1",
"unist-util-visit": "^4.1.2",
"uuid": "^9.0.1",
"valtio": "^1.12.0"
"valtio": "^1.12.0",
"yaml": "^2.4.5"
},
"devDependencies": {
"@aws-sdk/client-secrets-manager": "^3.410.0",
@@ -999,6 +1000,7 @@
"@types/unist": "^2.0.6",
"acorn": "^8.11.3",
"api-types": "*",
"cheerio": "^1.0.0-rc.12",
"config": "*",
"dotenv": "^16.0.3",
"ejs": "^3.1.10",
@@ -17684,6 +17686,44 @@
"node": "*"
}
},
"node_modules/cheerio": {
"version": "1.0.0-rc.12",
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz",
"integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==",
"dev": true,
"dependencies": {
"cheerio-select": "^2.1.0",
"dom-serializer": "^2.0.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1",
"htmlparser2": "^8.0.1",
"parse5": "^7.0.0",
"parse5-htmlparser2-tree-adapter": "^7.0.0"
},
"engines": {
"node": ">= 6"
},
"funding": {
"url": "https://github.com/cheeriojs/cheerio?sponsor=1"
}
},
"node_modules/cheerio-select": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
"integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
"dev": true,
"dependencies": {
"boolbase": "^1.0.0",
"css-select": "^5.1.0",
"css-what": "^6.1.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/chokidar": {
"version": "3.5.3",
"funding": [
@@ -18677,6 +18717,22 @@
"hyphenate-style-name": "^1.0.3"
}
},
"node_modules/css-select": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
"dev": true,
"dependencies": {
"boolbase": "^1.0.0",
"css-what": "^6.1.0",
"domhandler": "^5.0.2",
"domutils": "^3.0.1",
"nth-check": "^2.0.1"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/css-to-react-native": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
@@ -19415,6 +19471,20 @@
"csstype": "^3.0.2"
}
},
"node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"dev": true,
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
"entities": "^4.2.0"
},
"funding": {
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
}
},
"node_modules/domelementtype": {
"version": "2.3.0",
"dev": true,
@@ -19437,6 +19507,35 @@
"node": ">=12"
}
},
"node_modules/domhandler": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
"dev": true,
"dependencies": {
"domelementtype": "^2.3.0"
},
"engines": {
"node": ">= 4"
},
"funding": {
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/domutils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
"dev": true,
"dependencies": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3"
},
"funding": {
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/dot-case": {
"version": "3.0.4",
"dev": true,
@@ -23387,6 +23486,25 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/htmlparser2": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
"dev": true,
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1",
"entities": "^4.4.0"
}
},
"node_modules/http-cache-semantics": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
@@ -32817,6 +32935,19 @@
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/parse5-htmlparser2-tree-adapter": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz",
"integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==",
"dev": true,
"dependencies": {
"domhandler": "^5.0.2",
"parse5": "^7.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/pascal-case": {
"version": "3.1.2",
"license": "MIT",
@@ -38823,22 +38954,6 @@
"url": "https://opencollective.com/svgo"
}
},
"node_modules/svgo/node_modules/css-select": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
"dev": true,
"dependencies": {
"boolbase": "^1.0.0",
"css-what": "^6.1.0",
"domhandler": "^5.0.2",
"domutils": "^3.0.1",
"nth-check": "^2.0.1"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/svgo/node_modules/css-tree": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
@@ -38852,49 +38967,6 @@
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
}
},
"node_modules/svgo/node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"dev": true,
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
"entities": "^4.2.0"
},
"funding": {
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
}
},
"node_modules/svgo/node_modules/domhandler": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
"dev": true,
"dependencies": {
"domelementtype": "^2.3.0"
},
"engines": {
"node": ">= 4"
},
"funding": {
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/svgo/node_modules/domutils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
"dev": true,
"dependencies": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3"
},
"funding": {
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/svgo/node_modules/mdn-data": {
"version": "2.0.30",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
@@ -42708,8 +42780,12 @@
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
},
"node_modules/yaml": {
"version": "2.3.2",
"license": "ISC",
"version": "2.4.5",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz",
"integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==",
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14"
}