mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
chore(docs): turn on strictNullChecks (#36180)
strictNullChecks was off for docs, which lets errors slip through and leads to incorrect required/optional typing on Zod-inferred types. This PR enables strictNullChecks and fixes all the existing violations.
This commit is contained in:
1 parent
5862505164
commit
cf3ecc93eb
70 files changed
+487
-531
No files matched your search
@@ -29,8 +29,8 @@ export async function GET(request: Request) {
|
||||
slug = maybeVersion
|
||||
}
|
||||
|
||||
let section: AbbrevApiReferenceSection
|
||||
let sectionsWithUrl: Array<AbbrevApiReferenceSection & { url: URL }>
|
||||
let section: AbbrevApiReferenceSection | undefined
|
||||
let sectionsWithUrl: Array<AbbrevApiReferenceSection & { url: URL }> = []
|
||||
try {
|
||||
const flattenedSections = (await getFlattenedSections(lib, version)) ?? []
|
||||
sectionsWithUrl = flattenedSections.map((section) => {
|
||||
|
||||
@@ -22,9 +22,9 @@ export function ContributingToc({ className }: { className?: string }) {
|
||||
...document.querySelectorAll('article.prose > h2,h3:not(#feedback-title)'),
|
||||
] as Array<HTMLHeadingElement>
|
||||
const tocItems = headings
|
||||
.filter((heading) => !!heading.id)
|
||||
.filter((heading) => !!heading.id && heading.textContent)
|
||||
.map((heading) => ({
|
||||
label: heading.textContent.substring(0, heading.textContent.length - 1), // Remove ending `#`
|
||||
label: heading.textContent!.substring(0, heading.textContent!.length - 1), // Remove ending `#`
|
||||
anchor: heading.id,
|
||||
}))
|
||||
setTocItems(tocItems)
|
||||
|
||||
@@ -83,6 +83,12 @@ export async function generateAiPromptMetadata(props: { params: Promise<{ slug:
|
||||
const { slug } = await props.params
|
||||
const prompt = await getAiPrompt(slug)
|
||||
|
||||
if (!prompt) {
|
||||
return {
|
||||
title: 'AI Prompt | Supabase Docs',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
title: `AI Prompt: ${prompt.heading} | Supabase Docs`,
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { notFound } from 'next/navigation'
|
||||
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
|
||||
import {
|
||||
generateAiPromptMetadata,
|
||||
@@ -12,7 +13,12 @@ export default async function AiPromptsPage(props: { params: Promise<{ slug: str
|
||||
|
||||
const { slug } = params
|
||||
|
||||
let { heading, content } = await getAiPrompt(slug)
|
||||
const prompt = await getAiPrompt(slug)
|
||||
if (!prompt) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
let { heading, content } = prompt
|
||||
content = `
|
||||
## How to use
|
||||
|
||||
|
||||
+16
-10
@@ -15,8 +15,12 @@ const generateMetadata = async (_, parent: ResolvingMetadata): Promise<Metadata>
|
||||
|
||||
return {
|
||||
alternates: {
|
||||
...parentAlternates,
|
||||
canonical: `${BASE_PATH}`,
|
||||
...(parentAlternates && {
|
||||
languages: parentAlternates.languages || undefined,
|
||||
media: parentAlternates.media || undefined,
|
||||
types: parentAlternates.types || undefined,
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -251,15 +255,17 @@ const HomePage = () => (
|
||||
</div>
|
||||
|
||||
<ul className="grid col-span-8 grid-cols-12 gap-6 not-prose">
|
||||
{MIGRATION_PAGES.sort((a, b) => a.name.localeCompare(b.name)).map((guide) => {
|
||||
return (
|
||||
<li key={guide.name} className="col-span-6 md:col-span-4">
|
||||
<Link href={guide.url} passHref>
|
||||
<IconPanel {...guide} title={guide.name} background={true} showLink={false} />
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
{MIGRATION_PAGES.sort((a, b) => (a.name || '').localeCompare(b.name || '')).map(
|
||||
(guide) => {
|
||||
return (
|
||||
<li key={guide.name} className="col-span-6 md:col-span-4">
|
||||
<Link href={guide.url || '#'} passHref>
|
||||
<IconPanel {...guide} title={guide.name} background={true} showLink={false} />
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -6,11 +6,11 @@ import { Tabs, TabPanel } from '~/features/ui/Tabs'
|
||||
type IParamProps = any
|
||||
|
||||
const ApiSchema = ({ schema, id }: IParamProps) => {
|
||||
let example: string
|
||||
let example: string | undefined
|
||||
try {
|
||||
example = sample(schema, { skipReadOnly: true, quiet: true })
|
||||
} catch {
|
||||
// ignore
|
||||
example = undefined
|
||||
}
|
||||
return (
|
||||
<Tabs
|
||||
|
||||
@@ -133,7 +133,7 @@ const AppleSecretGenerator = () => {
|
||||
<input
|
||||
type="file"
|
||||
onChange={(e) => {
|
||||
setFile({ file: e.target.files[0] })
|
||||
setFile({ file: e.target.files?.[0] || null })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -156,7 +156,7 @@ const AppleSecretGenerator = () => {
|
||||
keyID,
|
||||
teamID,
|
||||
serviceID,
|
||||
file.file
|
||||
file.file!
|
||||
)
|
||||
setKeyID(kid)
|
||||
setSecretKey(jwt)
|
||||
|
||||
@@ -181,7 +181,7 @@ function useBreadcrumbs() {
|
||||
return findMenuItemByUrl(menu, pathname, [])
|
||||
}
|
||||
|
||||
function findMenuItemByUrl(menu, targetUrl, parents = []) {
|
||||
function findMenuItemByUrl(menu: any, targetUrl: string, parents: any[] = []) {
|
||||
// If the menu has items, recursively search through them
|
||||
if (menu.items) {
|
||||
for (let item of menu.items) {
|
||||
|
||||
@@ -24,7 +24,7 @@ function getLinkTarget(link: string): LinkTarget {
|
||||
}
|
||||
|
||||
function getUniqueTags(json: Extension[]): string[] {
|
||||
const tags = []
|
||||
const tags: string[] = []
|
||||
for (const item of json) {
|
||||
if (item.tags) {
|
||||
tags.push(...item.tags)
|
||||
|
||||
@@ -96,6 +96,8 @@ function Feedback({ className }: { className?: string }) {
|
||||
const showNo = unanswered || isNo
|
||||
|
||||
async function sendFeedbackVote(response: Response) {
|
||||
if (!supabase) return
|
||||
|
||||
const { error } = await supabase.from('feedback').insert({
|
||||
vote: response,
|
||||
page: pathname,
|
||||
|
||||
@@ -59,7 +59,7 @@ export default function JwtGenerator() {
|
||||
const [jwtSecret, setJwtSecret] = useState(secret)
|
||||
const [token, setToken] = useState(anonToken)
|
||||
const [signedToken, setSignedToken] = useState('')
|
||||
const [err, setErr] = useState<string>(null)
|
||||
const [err, setErr] = useState<string>('')
|
||||
|
||||
const handleKeySelection = (e: ChangeEvent<HTMLSelectElement>) => {
|
||||
const val = e.target.value
|
||||
@@ -71,7 +71,7 @@ export default function JwtGenerator() {
|
||||
try {
|
||||
const newTok = JSON.parse(e.target.value)
|
||||
setToken(newTok)
|
||||
setErr(null)
|
||||
setErr('')
|
||||
} catch (err) {
|
||||
const errMessage =
|
||||
!!err && typeof err === 'object' && 'message' in err && typeof err.message === 'string'
|
||||
|
||||
@@ -1,196 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { ChevronsUpDown } from 'lucide-react'
|
||||
import {
|
||||
createContext,
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useContext,
|
||||
useId,
|
||||
useState,
|
||||
PropsWithChildren,
|
||||
useRef,
|
||||
useEffect,
|
||||
} from 'react'
|
||||
|
||||
import {
|
||||
cn,
|
||||
Badge,
|
||||
Checkbox_Shadcn_ as Checkbox,
|
||||
Popover_Shadcn_ as Popover,
|
||||
PopoverContent_Shadcn_ as PopoverContent,
|
||||
PopoverTrigger_Shadcn_ as PopoverTrigger,
|
||||
} from 'ui'
|
||||
|
||||
interface Item {
|
||||
value: string
|
||||
label: string
|
||||
}
|
||||
|
||||
const MultiSelectContext = createContext<{
|
||||
selected: Item[]
|
||||
handleSelect: (item: Item) => void
|
||||
setOpen: (open: boolean) => void
|
||||
}>({
|
||||
selected: [],
|
||||
handleSelect: () => {},
|
||||
setOpen: () => {},
|
||||
})
|
||||
|
||||
function MultiSelectProvider({
|
||||
selected,
|
||||
onSelectedChange,
|
||||
setOpen,
|
||||
children,
|
||||
}: PropsWithChildren<{
|
||||
selected: Item[]
|
||||
onSelectedChange: (selected: Item[] | ((selected: Item[]) => Item[])) => void
|
||||
setOpen: (open: boolean) => void
|
||||
}>) {
|
||||
const handleSelect = useCallback(
|
||||
(item: Item) => {
|
||||
onSelectedChange((current: Item[]) => {
|
||||
const isSelected = current.some((currItem) => currItem.value === item.value)
|
||||
if (isSelected) {
|
||||
return current.filter((currItem) => currItem.value !== item.value)
|
||||
} else {
|
||||
return [...current, item]
|
||||
}
|
||||
})
|
||||
},
|
||||
[onSelectedChange]
|
||||
)
|
||||
|
||||
return (
|
||||
<MultiSelectContext.Provider value={{ selected, handleSelect, setOpen }}>
|
||||
{children}
|
||||
</MultiSelectContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
function useMultiSelect() {
|
||||
const context = useContext(MultiSelectContext)
|
||||
if (!context) {
|
||||
throw new Error('useMultiSelect must be used within a MultiSelectProvider')
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
export function MultiSelect({
|
||||
open: _controlledOpen,
|
||||
setOpen: _setControlledOpen,
|
||||
selected,
|
||||
onSelectedChange,
|
||||
children,
|
||||
...props
|
||||
}: PropsWithChildren<
|
||||
{
|
||||
open?: boolean
|
||||
setOpen?: React.Dispatch<React.SetStateAction<boolean>>
|
||||
selected?: Item[]
|
||||
onSelectedChange: (selected: Item[] | ((selected: Item[]) => Item[])) => void
|
||||
} & React.ComponentProps<typeof Popover>
|
||||
>) {
|
||||
const [_internalOpen, _setInternalOpen] = useState(false)
|
||||
const open = _controlledOpen ?? _internalOpen
|
||||
const setOpen = _setControlledOpen ?? _setInternalOpen
|
||||
|
||||
return (
|
||||
<MultiSelectProvider selected={selected} onSelectedChange={onSelectedChange} setOpen={setOpen}>
|
||||
<Popover open={open} onOpenChange={setOpen} {...props}>
|
||||
{children}
|
||||
</Popover>
|
||||
</MultiSelectProvider>
|
||||
)
|
||||
}
|
||||
|
||||
const Trigger = forwardRef<
|
||||
HTMLButtonElement,
|
||||
{ label?: string; className?: string } & React.ComponentProps<typeof PopoverTrigger>
|
||||
>(({ label, className, ...props }, ref) => {
|
||||
const { selected } = useMultiSelect()
|
||||
|
||||
const [measuredWidth, setMeasuredWidth] = useState(0)
|
||||
const measuredRef = (node: HTMLElement) => {
|
||||
if (node) {
|
||||
setMeasuredWidth(node.getBoundingClientRect().width)
|
||||
}
|
||||
}
|
||||
|
||||
const badgeWidth = 100 // Approximate width of a badge in pixels
|
||||
const maxBadges = Math.max(1, Math.floor((measuredWidth || 200) / badgeWidth))
|
||||
const visibleBadges = selected.slice(0, maxBadges)
|
||||
const extraBadgesCount = Math.max(0, selected.length - maxBadges)
|
||||
|
||||
const badgeClasses = 'bg-200'
|
||||
|
||||
return (
|
||||
<PopoverTrigger asChild ref={ref}>
|
||||
<button
|
||||
role="combobox"
|
||||
className={cn(
|
||||
'flex w-full min-w-[200px] items-center justify-between rounded-md border',
|
||||
'border-alternative bg-foreground/[.026] px-3 py-2 text-sm',
|
||||
'ring-offset-background placeholder:text-muted-foreground',
|
||||
'focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'hover:border-primary transition-colors duration-200',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{selected.length === 0 ? (
|
||||
// Leading prevents shift when switching to badges
|
||||
<span className="text-muted-foreground leading-[1.375rem]">{label}</span>
|
||||
) : (
|
||||
<div ref={measuredRef} className="flex gap-1 overflow-hidden">
|
||||
{visibleBadges.map((item) => (
|
||||
<Badge key={item.value} className={cn('shrink-0', badgeClasses)}>
|
||||
{item.label}
|
||||
</Badge>
|
||||
))}
|
||||
{extraBadgesCount > 0 && <Badge className={badgeClasses}>+{extraBadgesCount}</Badge>}
|
||||
</div>
|
||||
)}
|
||||
<ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
)
|
||||
})
|
||||
Trigger.displayName = 'Trigger'
|
||||
MultiSelect.Trigger = Trigger
|
||||
|
||||
const Content = forwardRef<
|
||||
HTMLDivElement,
|
||||
PropsWithChildren<{ className?: string } & React.ComponentProps<typeof PopoverContent>>
|
||||
>(({ children, className, ...props }, ref) => {
|
||||
return (
|
||||
<PopoverContent ref={ref} className={cn('w-full p-4 space-y-4', className)} {...props}>
|
||||
{children}
|
||||
</PopoverContent>
|
||||
)
|
||||
})
|
||||
Content.displayName = 'Content'
|
||||
MultiSelect.Content = Content
|
||||
|
||||
export function Item({ item, className }: { item: Item; className?: string }) {
|
||||
const id = useId()
|
||||
const { selected, handleSelect } = useMultiSelect()
|
||||
|
||||
return (
|
||||
<div className={cn('flex items-center space-x-2', className)}>
|
||||
<Checkbox
|
||||
id={`${id}-checkbox-${item.value}`}
|
||||
checked={selected.some((sel) => sel.value === item.value)}
|
||||
onCheckedChange={() => handleSelect(item)}
|
||||
/>
|
||||
<label
|
||||
htmlFor={`${id}-checkbox-${item.value}`}
|
||||
className="text-sm leading-none cursor-pointer peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
{item.label}
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
MultiSelect.Item = Item
|
||||
@@ -71,7 +71,7 @@ const AccordionMenuItem = ({ section }: { section: DropdownMenuItem[] }) => {
|
||||
</Accordion.Item>
|
||||
) : (
|
||||
<Link
|
||||
href={section[0].href}
|
||||
href={section[0].href || '#'}
|
||||
className={cn(activeLabel === section[0].label && '!text-foreground', itemClassName)}
|
||||
>
|
||||
{section[0].label}
|
||||
|
||||
@@ -81,7 +81,7 @@ const GlobalNavigationMenu: FC = () => {
|
||||
)}
|
||||
>
|
||||
{section[0].label === 'Home' ? (
|
||||
<MenuIconPicker icon={section[0].icon} />
|
||||
<MenuIconPicker icon={section[0].icon || ''} />
|
||||
) : (
|
||||
section[0].label
|
||||
)}
|
||||
@@ -125,7 +125,7 @@ const GlobalNavigationMenu: FC = () => {
|
||||
>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link
|
||||
href={section[0].href}
|
||||
href={section[0].href || '#'}
|
||||
className={cn(
|
||||
navigationMenuTriggerStyle(),
|
||||
triggerClassName,
|
||||
@@ -133,7 +133,7 @@ const GlobalNavigationMenu: FC = () => {
|
||||
)}
|
||||
>
|
||||
{section[0].label === 'Home' ? (
|
||||
<MenuIconPicker icon={section[0].icon} />
|
||||
<MenuIconPicker icon={section[0].icon || ''} />
|
||||
) : (
|
||||
section[0].label
|
||||
)}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { createContext, useContext } from 'react'
|
||||
|
||||
interface ContextProps {
|
||||
activeRefItem: string
|
||||
activeRefItem: string | undefined
|
||||
setActiveRefItem: (x: string) => void
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ interface Provider extends ContextProps {
|
||||
// createContext matches the shape that the consumers expect!
|
||||
const NavMenuContext = createContext<ContextProps>({
|
||||
activeRefItem: undefined,
|
||||
setActiveRefItem: undefined,
|
||||
setActiveRefItem: () => {},
|
||||
})
|
||||
|
||||
export const NavigationMenuContextProvider = (props: Provider) => {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { memo } from 'react'
|
||||
|
||||
import type { NavMenuSection } from '../Navigation.types'
|
||||
import { useCloseMenuOnRouteChange } from './NavigationMenu.utils'
|
||||
import NavigationMenuGuideList from './NavigationMenuGuideList'
|
||||
import NavigationMenuRefList from './NavigationMenuRefList'
|
||||
import { useCloseMenuOnRouteChange } from './NavigationMenu.utils'
|
||||
|
||||
enum MenuId {
|
||||
GettingStarted = 'gettingstarted',
|
||||
@@ -251,6 +251,8 @@ function getMenuById(id: MenuId) {
|
||||
}
|
||||
|
||||
function getMenuElement(menu: Menu | undefined, props?: any) {
|
||||
if (!menu) return null
|
||||
|
||||
const menuType = menu?.type
|
||||
switch (menuType) {
|
||||
case 'guide':
|
||||
@@ -260,7 +262,7 @@ function getMenuElement(menu: Menu | undefined, props?: any) {
|
||||
<NavigationMenuRefList
|
||||
id={menu.id}
|
||||
basePath={menu.path}
|
||||
commonSectionsFile={menu.commonSectionsFile}
|
||||
commonSectionsFile={menu.commonSectionsFile || ''}
|
||||
/>
|
||||
)
|
||||
default:
|
||||
@@ -283,5 +285,5 @@ const NavigationMenu = ({
|
||||
return getMenuElement(menu, { additionalNavItems })
|
||||
}
|
||||
|
||||
export { MenuId, getMenuById }
|
||||
export { getMenuById, MenuId }
|
||||
export default memo(NavigationMenu)
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
|
||||
import type { ICommonItem } from '~/components/reference/Reference.types'
|
||||
import type { Json } from '~/features/helpers.types'
|
||||
@@ -52,7 +52,10 @@ export function deepFilterSections<T extends ICommonItem>(
|
||||
*
|
||||
* See https://webpack.js.org/api/module-methods/#dynamic-expressions-in-import
|
||||
*/
|
||||
export function useCommonSections(commonSectionsFile: string) {
|
||||
export function useCommonSections(
|
||||
commonSectionsFile: string,
|
||||
{ enabled = true }: { enabled: boolean }
|
||||
) {
|
||||
const [commonSections, setCommonSections] = useState<ICommonItem[]>()
|
||||
|
||||
useEffect(() => {
|
||||
@@ -67,6 +70,10 @@ export function useCommonSections(commonSectionsFile: string) {
|
||||
fetchCommonSections()
|
||||
}, [commonSectionsFile])
|
||||
|
||||
if (!enabled) {
|
||||
return null
|
||||
}
|
||||
|
||||
return commonSections
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ const NavigationMenuRefList = ({
|
||||
commonSectionsFile,
|
||||
specFile,
|
||||
}: NavigationMenuRefListProps) => {
|
||||
const commonSections = useCommonSections(commonSectionsFile)
|
||||
const commonSections = useCommonSections(commonSectionsFile, { enabled: !!commonSectionsFile })
|
||||
const spec = useSpec(specFile)
|
||||
|
||||
if (!commonSections) {
|
||||
|
||||
@@ -28,7 +28,7 @@ const SideBar = ({ menuItems = [] }: { menuItems: any }) => {
|
||||
if (foundItem) return group
|
||||
})
|
||||
|
||||
const currentSubSection: NavMenuSection =
|
||||
const currentSubSection: NavMenuSection | undefined =
|
||||
currentSection !== undefined
|
||||
? currentSection.items.find((section) => {
|
||||
if (section.items.length === 0) {
|
||||
@@ -113,7 +113,7 @@ const SideBar = ({ menuItems = [] }: { menuItems: any }) => {
|
||||
{group.items.map((section: NavMenuSection) => {
|
||||
if (section.items.length === 0) {
|
||||
return (
|
||||
<Link href={section.url} key={section.name}>
|
||||
<Link href={section.url || '#'} key={section.name}>
|
||||
<div
|
||||
className={[
|
||||
'py-1.5 px-5 rounded text-sm transition',
|
||||
@@ -150,7 +150,7 @@ const SideBar = ({ menuItems = [] }: { menuItems: any }) => {
|
||||
</Accordion.Trigger>
|
||||
<Accordion.Content className="my-2 data-open:animate-slide-down data-closed:animate-slide-up">
|
||||
{section.items.map((item: NavMenuSection) => (
|
||||
<Link key={item.name} href={item.url}>
|
||||
<Link key={item.name} href={item.url || '#'}>
|
||||
<div
|
||||
key={item.name}
|
||||
className={[
|
||||
|
||||
@@ -8,7 +8,7 @@ import type {
|
||||
} from '~/components/ProjectConfigVariables/ProjectConfigVariables.utils'
|
||||
import type { ProjectApiData } from '~/lib/fetch/projectApi'
|
||||
|
||||
import { Copy, Check } from 'lucide-react'
|
||||
import { Check, Copy } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import CopyToClipboard from 'react-copy-to-clipboard'
|
||||
@@ -16,7 +16,7 @@ import { withErrorBoundary } from 'react-error-boundary'
|
||||
import { proxy, useSnapshot } from 'valtio'
|
||||
|
||||
import { LOCAL_STORAGE_KEYS, useIsLoggedIn, useIsUserLoading } from 'common'
|
||||
import { Button_Shadcn_ as Button, Input_Shadcn_ as Input, cn } from 'ui'
|
||||
import { Button_Shadcn_ as Button, cn, Input_Shadcn_ as Input } from 'ui'
|
||||
|
||||
import {
|
||||
ComboBox,
|
||||
@@ -35,7 +35,7 @@ import { useBranchesQuery } from '~/lib/fetch/branches'
|
||||
import { useOrganizationsQuery } from '~/lib/fetch/organizations'
|
||||
import { type SupavisorConfigData, useSupavisorConfigQuery } from '~/lib/fetch/pooler'
|
||||
import { useProjectApiQuery } from '~/lib/fetch/projectApi'
|
||||
import { isProjectPaused, type ProjectsData, useProjectsQuery } from '~/lib/fetch/projects'
|
||||
import { isProjectPaused, useProjectsQuery } from '~/lib/fetch/projects'
|
||||
import { retrieve, storeOrRemoveNull } from '~/lib/storage'
|
||||
import { useOnLogout } from '~/lib/userAuth'
|
||||
|
||||
@@ -122,14 +122,16 @@ function OrgProjectSelector() {
|
||||
() =>
|
||||
stateSummary !== 'loggedIn.dataSuccess.hasData'
|
||||
? []
|
||||
: projects.map((project) => {
|
||||
const organization = organizations.find((org) => org.id === project.organization_id)
|
||||
return {
|
||||
id: project.ref,
|
||||
value: toOrgProjectValue(organization, project),
|
||||
displayName: toDisplayNameOrgProject(organization, project),
|
||||
}
|
||||
}),
|
||||
: (projects!
|
||||
.map((project) => {
|
||||
const organization = organizations!.find((org) => org.id === project.organization_id)!
|
||||
return {
|
||||
id: project.ref,
|
||||
value: toOrgProjectValue(organization, project),
|
||||
displayName: toDisplayNameOrgProject(organization, project),
|
||||
}
|
||||
})
|
||||
.filter(Boolean) as ComboBoxOption[]),
|
||||
[organizations, projects, stateSummary]
|
||||
)
|
||||
|
||||
@@ -138,18 +140,18 @@ function OrgProjectSelector() {
|
||||
const storedMaybeOrgId = retrieve('local', LOCAL_STORAGE_KEYS.SAVED_ORG)
|
||||
const storedMaybeProjectRef = retrieve('local', LOCAL_STORAGE_KEYS.SAVED_PROJECT)
|
||||
|
||||
let storedOrg: Org
|
||||
let storedProject: Project
|
||||
let storedOrg: Org | undefined
|
||||
let storedProject: Project | undefined
|
||||
if (storedMaybeOrgId && storedMaybeProjectRef) {
|
||||
storedOrg = organizations.find((org) => org.id === Number(storedMaybeOrgId))
|
||||
storedProject = projects.find((project) => project.ref === storedMaybeProjectRef)
|
||||
storedOrg = organizations!.find((org) => org.id === Number(storedMaybeOrgId))
|
||||
storedProject = projects!.find((project) => project.ref === storedMaybeProjectRef)
|
||||
}
|
||||
|
||||
if (storedOrg && storedProject && storedProject.organization_id === storedOrg.id) {
|
||||
setSelectedOrgProject(storedOrg, storedProject)
|
||||
} else {
|
||||
const firstProject = projects[0]
|
||||
const matchingOrg = organizations.find((org) => org.id === firstProject.organization_id)
|
||||
} else if (projects!.length > 0) {
|
||||
const firstProject = projects![0]
|
||||
const matchingOrg = organizations!.find((org) => org.id === firstProject.organization_id)
|
||||
if (matchingOrg) setSelectedOrgProject(matchingOrg, firstProject)
|
||||
}
|
||||
}
|
||||
@@ -172,8 +174,8 @@ function OrgProjectSelector() {
|
||||
const [orgId, projectRef] = fromOrgProjectValue(optionValue)
|
||||
if (!orgId || !projectRef) return
|
||||
|
||||
const org = organizations.find((org) => org.id === orgId)
|
||||
const project = projects.find((project) => project.ref === projectRef)
|
||||
const org = organizations?.find((org) => org.id === orgId)
|
||||
const project = projects?.find((project) => project.ref === projectRef)
|
||||
|
||||
if (org && project && project.organization_id === org.id) {
|
||||
setSelectedOrgProject(org, project)
|
||||
@@ -214,7 +216,7 @@ function BranchSelector() {
|
||||
const formattedData: ComboBoxOption[] =
|
||||
stateSummary !== 'loggedIn.branches.dataSuccess.hasData'
|
||||
? []
|
||||
: data.map((branch) => ({
|
||||
: data!.map((branch) => ({
|
||||
id: branch.id,
|
||||
displayName: branch.name,
|
||||
value: toBranchValue(branch),
|
||||
@@ -224,18 +226,18 @@ function BranchSelector() {
|
||||
if (stateSummary === 'loggedIn.branches.dataSuccess.hasData' && !selectedBranch) {
|
||||
const storedMaybeBranchId = retrieve('local', LOCAL_STORAGE_KEYS.SAVED_BRANCH)
|
||||
|
||||
let storedBranch: Branch
|
||||
let storedBranch: Branch | undefined
|
||||
if (storedMaybeBranchId) {
|
||||
storedBranch = data.find((branch) => branch.id === storedMaybeBranchId)
|
||||
storedBranch = data!.find((branch) => branch.id === storedMaybeBranchId)
|
||||
}
|
||||
|
||||
if (storedBranch) {
|
||||
setSelectedBranch(storedBranch)
|
||||
} else {
|
||||
const productionBranch = data.find(
|
||||
const productionBranch = data!.find(
|
||||
(branch) => branch.project_ref === branch.parent_project_ref
|
||||
)
|
||||
setSelectedBranch(productionBranch ?? data[0])
|
||||
setSelectedBranch(productionBranch ?? data![0])
|
||||
}
|
||||
}
|
||||
}, [data, selectedBranch, setSelectedBranch, stateSummary])
|
||||
@@ -255,7 +257,7 @@ function BranchSelector() {
|
||||
onSelectOption={(option) => {
|
||||
const [branchId] = fromBranchValue(option)
|
||||
if (branchId) {
|
||||
const branch = data.find((branch) => branch.id === branchId)
|
||||
const branch = data?.find((branch) => branch.id === branchId)
|
||||
if (branch) setSelectedBranch(branch)
|
||||
}
|
||||
}}
|
||||
@@ -322,23 +324,23 @@ function VariableView({ variable, className }: { variable: Variable; className?:
|
||||
? 'loggedIn.selectedProject.dataPending'
|
||||
: (
|
||||
needsApiQuery
|
||||
? isApiError || isInvalidApiData(apiData)
|
||||
: isSupavisorError || isInvalidSupavisorData(supavisorConfig)
|
||||
? isApiError || isInvalidApiData(apiData!)
|
||||
: isSupavisorError || isInvalidSupavisorData(supavisorConfig!)
|
||||
)
|
||||
? 'loggedIn.selectedProject.dataError'
|
||||
: 'loggedIn.selectedProject.dataSuccess'
|
||||
|
||||
let variableValue: string = null
|
||||
let variableValue: string = ''
|
||||
if (stateSummary === 'loggedIn.selectedProject.dataSuccess') {
|
||||
switch (variable) {
|
||||
case 'url':
|
||||
variableValue = `https://${apiData.app_config!.endpoint}`
|
||||
variableValue = `https://${apiData?.app_config?.endpoint}`
|
||||
break
|
||||
case 'anonKey':
|
||||
variableValue = apiData.service_api_keys!.find((key) => key.tags === 'anon')!.api_key
|
||||
variableValue = apiData?.service_api_keys?.find((key) => key.tags === 'anon')?.api_key || ''
|
||||
break
|
||||
case 'sessionPooler':
|
||||
variableValue = supavisorConfig[0].connection_string
|
||||
variableValue = supavisorConfig?.[0]?.connection_string || ''
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -137,7 +137,11 @@ export default function RealtimeLimitsEstimater({}) {
|
||||
<Button
|
||||
type="text"
|
||||
icon={
|
||||
<ChevronDown size={18} strokeWidth={2} className={expandPreview && 'rotate-180'} />
|
||||
<ChevronDown
|
||||
size={18}
|
||||
strokeWidth={2}
|
||||
className={expandPreview ? 'rotate-180' : undefined}
|
||||
/>
|
||||
}
|
||||
className="px-1"
|
||||
onClick={() => setExpandPreview(!expandPreview)}
|
||||
|
||||
@@ -2,7 +2,10 @@ import { createContext } from 'react'
|
||||
|
||||
// Make sure the shape of the default value passed to
|
||||
// createContext matches the shape that the consumers expect!
|
||||
export const StepHikeContext = createContext({
|
||||
export const StepHikeContext = createContext<{
|
||||
activeStep: { titleId: string; step: number } | undefined
|
||||
steps: any[] | undefined
|
||||
}>({
|
||||
activeStep: undefined,
|
||||
steps: undefined,
|
||||
})
|
||||
@@ -2,21 +2,25 @@ import { useEffect, useState } from 'react'
|
||||
import { useInView } from 'react-intersection-observer'
|
||||
import { StepHikeContext } from './StepHikeContext'
|
||||
|
||||
const StepHike = ({ children, title }) => {
|
||||
const [activeStep, setActiveStep] = useState(undefined)
|
||||
const StepHike = ({ children }) => {
|
||||
const [activeStep, setActiveStep] = useState<{ titleId: string; step: number } | undefined>(
|
||||
undefined
|
||||
)
|
||||
|
||||
// check if there are any children
|
||||
if (!children) throw 'StepHike component requires <StepHike.Step> children'
|
||||
|
||||
const steps = children.filter((x) => {
|
||||
const steps = children.filter((x: any) => {
|
||||
return x.type.name === 'Step'
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
setActiveStep({
|
||||
titleId: steps[0].props.title.replaceAll(' ', '-').toLowerCase(),
|
||||
step: 0,
|
||||
})
|
||||
if (steps.length > 0) {
|
||||
setActiveStep({
|
||||
titleId: steps[0].props.title.replaceAll(' ', '-').toLowerCase(),
|
||||
step: 0,
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
|
||||
// check if there is at least 1 StepHike subcomponent
|
||||
|
||||
@@ -4,7 +4,7 @@ import { cn } from 'ui'
|
||||
type TableProps = TableHTMLAttributes<HTMLTableElement>
|
||||
|
||||
const Table = ({ children, ...props }: TableProps) => {
|
||||
const containerRef = useRef(null)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const [showShadow, setShowShadow] = useState(true)
|
||||
|
||||
const handleScroll = () => {
|
||||
@@ -18,8 +18,11 @@ const Table = ({ children, ...props }: TableProps) => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
containerRef?.current?.addEventListener('scroll', handleScroll)
|
||||
return () => containerRef?.current?.removeEventListener('scroll', handleScroll)
|
||||
const container = containerRef.current
|
||||
if (container) {
|
||||
container.addEventListener('scroll', handleScroll)
|
||||
return () => container.removeEventListener('scroll', handleScroll)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
|
||||
@@ -236,7 +236,7 @@ async function fetchSourceCodeContent(tree: Root, deps: Dependencies) {
|
||||
|
||||
function rewriteNodes(contentMap: Map<MdxJsxFlowElement, [CodeSampleMeta, string]>) {
|
||||
for (const [node, [meta, content]] of contentMap) {
|
||||
const lang = matchLang(meta.path.split('.').pop())
|
||||
const lang = matchLang(meta.path.split('.').pop() || '')
|
||||
|
||||
const source = isExternalSource(meta)
|
||||
? `https://github.com/${meta.org}/${meta.repo}/blob/${meta.commit}${meta.path}`
|
||||
@@ -286,6 +286,7 @@ function rewriteNodes(contentMap: Map<MdxJsxFlowElement, [CodeSampleMeta, string
|
||||
existingWrapper.attributes[0].value = newSource
|
||||
} else if (
|
||||
typeof existingSource !== 'string' &&
|
||||
existingSource &&
|
||||
existingSource.type === 'mdxJsxAttributeValueExpression'
|
||||
) {
|
||||
const existingSourceArray =
|
||||
@@ -405,7 +406,10 @@ export function _createElidedLine(
|
||||
|
||||
function isContainedInJsx(tree: acorn.Node, line: number) {
|
||||
const acornNodeContainsLine = (node: acorn.Node, line) =>
|
||||
node.loc?.start.line <= line && node.loc?.end.line >= line
|
||||
node.loc?.start?.line != null &&
|
||||
node.loc?.end?.line != null &&
|
||||
node.loc.start.line <= line &&
|
||||
node.loc.end.line >= line
|
||||
if (!acornNodeContainsLine(tree, line)) {
|
||||
return false
|
||||
}
|
||||
@@ -430,10 +434,18 @@ function isContainedInJsx(tree: acorn.Node, line: number) {
|
||||
continue
|
||||
} else {
|
||||
if (
|
||||
child.loc?.start?.line > candidateNarrowestContainingNode.loc?.start?.line ||
|
||||
child.loc.end.line < candidateNarrowestContainingNode.loc?.end?.line ||
|
||||
child.loc.start.column > candidateNarrowestContainingNode.loc?.start.column ||
|
||||
child.loc.end.column < candidateNarrowestContainingNode.loc?.end.column
|
||||
(child.loc?.start?.line != null &&
|
||||
candidateNarrowestContainingNode.loc?.start?.line != null &&
|
||||
child.loc.start.line > candidateNarrowestContainingNode.loc.start.line) ||
|
||||
(child.loc?.end?.line != null &&
|
||||
candidateNarrowestContainingNode.loc?.end?.line != null &&
|
||||
child.loc.end.line < candidateNarrowestContainingNode.loc.end.line) ||
|
||||
(child.loc?.start?.column != null &&
|
||||
candidateNarrowestContainingNode.loc?.start?.column != null &&
|
||||
child.loc.start.column > candidateNarrowestContainingNode.loc.start.column) ||
|
||||
(child.loc?.end?.column != null &&
|
||||
candidateNarrowestContainingNode.loc?.end?.column != null &&
|
||||
child.loc.end.column < candidateNarrowestContainingNode.loc.end.column)
|
||||
) {
|
||||
candidateNarrowestContainingNode = child
|
||||
getNarrowestContainingNode(child, line)
|
||||
|
||||
@@ -35,7 +35,7 @@ const TocAnchorsProvider = ({ children }: PropsWithChildren) => {
|
||||
const newHeadings = headings
|
||||
.filter((heading) => heading.id)
|
||||
.map((heading) => {
|
||||
const text = heading.textContent.replace('#', '')
|
||||
const text = heading.textContent?.replace('#', '') || ''
|
||||
const link = heading.querySelector('a')?.getAttribute('href')
|
||||
if (!link) return null
|
||||
|
||||
|
||||
@@ -92,14 +92,14 @@ const getGuidesMarkdown = cache_fullProcess_withDevCacheBust(
|
||||
const genGuidesStaticParams = (directory?: string) => async () => {
|
||||
const promises = directory
|
||||
? (await readdir(join(GUIDES_DIRECTORY, directory), { recursive: true }))
|
||||
.filter((file) => extname(file) === '.mdx' && !file.split(sep).at(-1).startsWith('_'))
|
||||
.filter((file) => extname(file) === '.mdx' && !file.split(sep).at(-1)?.startsWith('_'))
|
||||
.map((file) => ({ slug: file.replace(/\.mdx$/, '').split(sep) }))
|
||||
.concat(
|
||||
(await existsFile(join(GUIDES_DIRECTORY, `${directory}.mdx`))) ? [{ slug: [] }] : []
|
||||
)
|
||||
: PUBLISHED_SECTIONS.map(async (section) =>
|
||||
(await readdir(join(GUIDES_DIRECTORY, section), { recursive: true }))
|
||||
.filter((file) => extname(file) === '.mdx' && !file.split(sep).at(-1).startsWith('_'))
|
||||
.filter((file) => extname(file) === '.mdx' && !file.split(sep).at(-1)?.startsWith('_'))
|
||||
.map((file) => ({
|
||||
slug: [section, ...file.replace(/\.mdx$/, '').split(sep)],
|
||||
}))
|
||||
|
||||
@@ -78,12 +78,12 @@ function mapEndpointsById(
|
||||
const endpoints = spec.paths
|
||||
const endpointsById = new Map<string, IApiEndPoint>()
|
||||
|
||||
Object.entries(endpoints).forEach(([path, methods]) => {
|
||||
Object.entries(methods).forEach(([method, details]) => {
|
||||
Object.entries(endpoints as Record<string, any>).forEach(([path, methods]) => {
|
||||
Object.entries(methods as Record<string, any>).forEach(([method, details]) => {
|
||||
endpointsById.set(getId(details), {
|
||||
id: getId(details),
|
||||
path,
|
||||
method,
|
||||
method: method as 'get' | 'post' | 'put' | 'delete' | 'patch',
|
||||
...details,
|
||||
})
|
||||
})
|
||||
@@ -92,13 +92,16 @@ function mapEndpointsById(
|
||||
return endpointsById
|
||||
}
|
||||
|
||||
function genClientSdkSectionTree(fns: Array<{ id: unknown }>, excludeName: string) {
|
||||
function genClientSdkSectionTree(
|
||||
fns: Array<{ id: unknown }>,
|
||||
excludeName: string
|
||||
): AbbrevApiReferenceSection[] {
|
||||
const validSections = deepFilterRec(
|
||||
commonClientLibSections as Array<AbbrevApiReferenceSection>,
|
||||
commonClientLibSections as AbbrevApiReferenceSection[],
|
||||
'items',
|
||||
(section) =>
|
||||
section.type === 'markdown' || section.type === 'category'
|
||||
? !('excludes' in section && section.excludes.includes(excludeName))
|
||||
? !('excludes' in section && section.excludes?.includes(excludeName))
|
||||
: section.type === 'function'
|
||||
? fns.some(({ id }) => section.id === id)
|
||||
: true
|
||||
@@ -106,11 +109,11 @@ function genClientSdkSectionTree(fns: Array<{ id: unknown }>, excludeName: strin
|
||||
return validSections
|
||||
}
|
||||
|
||||
async function genCliSectionTree() {
|
||||
async function genCliSectionTree(): Promise<AbbrevApiReferenceSection[]> {
|
||||
const cliSpec = await getSpec('cli_v1_commands', { ext: 'yaml' })
|
||||
|
||||
const validSections = deepFilterRec(
|
||||
cliCommonSections as Array<AbbrevApiReferenceSection>,
|
||||
cliCommonSections as AbbrevApiReferenceSection[],
|
||||
'items',
|
||||
(section) =>
|
||||
section.type === 'cli-command' ? cliSpec.commands.some(({ id }) => id === section.id) : true
|
||||
@@ -118,9 +121,9 @@ async function genCliSectionTree() {
|
||||
return validSections
|
||||
}
|
||||
|
||||
function genApiSectionTree(endpointsById: Map<string, IApiEndPoint>) {
|
||||
function genApiSectionTree(endpointsById: Map<string, IApiEndPoint>): AbbrevApiReferenceSection[] {
|
||||
const validSections = deepFilterRec(
|
||||
apiCommonSections as Array<AbbrevApiReferenceSection>,
|
||||
apiCommonSections as AbbrevApiReferenceSection[],
|
||||
'items',
|
||||
(section) => (section.type === 'operation' ? endpointsById.has(section.id) : true)
|
||||
)
|
||||
@@ -131,7 +134,7 @@ function genSelfHostedSectionTree(
|
||||
spec: Array<AbbrevApiReferenceSection>,
|
||||
endpointsById: Map<string, IApiEndPoint>
|
||||
) {
|
||||
const validSections = deepFilterRec(spec, 'items', (section) =>
|
||||
const validSections = deepFilterRec(spec as any, 'items', (section: any) =>
|
||||
section.type === 'self-hosted-operation' ? endpointsById.has(section.id) : true
|
||||
)
|
||||
return validSections
|
||||
@@ -143,7 +146,7 @@ export function flattenCommonClientLibSections(tree: Array<AbbrevApiReferenceSec
|
||||
const prunedElem = { ...elem }
|
||||
delete prunedElem.items
|
||||
acc.push(prunedElem)
|
||||
acc.push(...flattenCommonClientLibSections(elem.items))
|
||||
acc.push(...flattenCommonClientLibSections(elem.items || []))
|
||||
} else {
|
||||
acc.push(elem)
|
||||
}
|
||||
@@ -289,7 +292,7 @@ async function writeSelfHostingReferenceSections() {
|
||||
selfHostingSpecs.flatMap((service) => {
|
||||
let tasks: Promise<any>[] = []
|
||||
|
||||
let endpointsById: Map<string, IApiEndPoint>
|
||||
let endpointsById: Map<string, IApiEndPoint> = new Map()
|
||||
if (service.spec) {
|
||||
endpointsById = mapEndpointsById(service.spec, (details) =>
|
||||
slugify(details.summary || `dummy-id-${String(id++)}`, {
|
||||
@@ -313,7 +316,9 @@ async function writeSelfHostingReferenceSections() {
|
||||
)
|
||||
)
|
||||
|
||||
const flattenedSelfHostedSections = flattenCommonClientLibSections(selfHostedSectionTree)
|
||||
const flattenedSelfHostedSections = flattenCommonClientLibSections(
|
||||
selfHostedSectionTree as AbbrevApiReferenceSection[]
|
||||
)
|
||||
tasks.push(
|
||||
writeFile(
|
||||
join(GENERATED_DIRECTORY, `${service.id}.latest.flat.json`),
|
||||
|
||||
@@ -22,7 +22,7 @@ function ClientLibHeader({ menuData, className }: ClientLibHeaderProps) {
|
||||
<div className={cn('flex items-start gap-6', className)}>
|
||||
{'icon' in menuData && (
|
||||
<MenuIconPicker
|
||||
icon={menuData.icon}
|
||||
icon={menuData.icon || ''}
|
||||
width={35}
|
||||
height={35}
|
||||
className="text-foreground-light"
|
||||
|
||||
@@ -61,7 +61,7 @@ export function ReferenceNavigationScrollHandler({
|
||||
...rest
|
||||
}: PropsWithChildren & HTMLAttributes<HTMLDivElement>) {
|
||||
const parentRef = useRef<HTMLElement>()
|
||||
const ref = useRef<HTMLDivElement>()
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const initialScrollHappened = useContext(ReferenceContentInitiallyScrolledContext)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -70,7 +70,9 @@ export function ReferenceNavigationScrollHandler({
|
||||
let scrollingParent: HTMLElement = ref.current
|
||||
|
||||
while (scrollingParent && !(scrollingParent.scrollHeight > scrollingParent.clientHeight)) {
|
||||
scrollingParent = scrollingParent.parentElement
|
||||
const parent = scrollingParent.parentElement
|
||||
if (!parent) break
|
||||
scrollingParent = parent
|
||||
}
|
||||
|
||||
parentRef.current = scrollingParent
|
||||
@@ -180,7 +182,7 @@ export function RefLink({
|
||||
skipChildren?: boolean
|
||||
className?: string
|
||||
}) {
|
||||
const ref = useRef<HTMLAnchorElement>()
|
||||
const ref = useRef<HTMLAnchorElement>(null)
|
||||
|
||||
const pathname = usePathname()
|
||||
const href = deriveHref(basePath, section)
|
||||
@@ -189,7 +191,7 @@ export function RefLink({
|
||||
|
||||
useEffect(() => {
|
||||
if (ref.current) {
|
||||
ref.current.ariaCurrent = isActive ? 'page' : undefined
|
||||
ref.current.ariaCurrent = isActive ? 'page' : null
|
||||
ref.current.className = getLinkStyles(isActive, className)
|
||||
}
|
||||
}, [isActive, className])
|
||||
@@ -201,7 +203,8 @@ export function RefLink({
|
||||
|
||||
if (!('title' in section)) return null
|
||||
|
||||
const isCompoundSection = !skipChildren && 'items' in section && section.items.length > 0
|
||||
const isCompoundSection =
|
||||
!skipChildren && 'items' in section && section.items && section.items.length > 0
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -232,7 +235,7 @@ function useCompoundRefLinkActive(basePath: string, section: AbbrevApiReferenceS
|
||||
const isParentActive = pathname === parentHref
|
||||
|
||||
const childHrefs = useMemo(
|
||||
() => new Set(section.items.map((item) => deriveHref(basePath, item))),
|
||||
() => new Set((section.items || []).map((item) => deriveHref(basePath, item))),
|
||||
[basePath, section]
|
||||
)
|
||||
const isChildActive = childHrefs.has(pathname)
|
||||
@@ -288,7 +291,7 @@ function CompoundRefLink({
|
||||
>
|
||||
<ul className="space-y-2">
|
||||
<RefLink basePath={basePath} section={section} skipChildren />
|
||||
{section.items.map((item, idx) => {
|
||||
{(section.items || []).map((item, idx) => {
|
||||
return (
|
||||
<li key={`${section.id}-${idx}`}>
|
||||
<RefLink basePath={basePath} section={item} />
|
||||
|
||||
@@ -35,12 +35,12 @@ export async function ReferenceNavigation({
|
||||
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} />}
|
||||
{'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) => (
|
||||
{navSections?.map((section) => (
|
||||
<Fragment key={section.id}>
|
||||
{section.type === 'category' ? (
|
||||
<li>
|
||||
@@ -67,14 +67,14 @@ function RefCategory({
|
||||
basePath: string
|
||||
section: AbbrevApiReferenceSection
|
||||
}) {
|
||||
if (!('items' in section && section.items.length > 0)) return null
|
||||
if (!('items' in section && section.items && 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) => (
|
||||
{section.items?.map((item) => (
|
||||
<li key={item.id} className={topLvlRefNavItemStyles}>
|
||||
<RefLink basePath={basePath} section={item} />
|
||||
</li>
|
||||
|
||||
@@ -38,11 +38,13 @@ type RefSectionsProps = {
|
||||
|
||||
async function RefSections({ libraryId, version }: RefSectionsProps) {
|
||||
let flattenedSections = await getFlattenedSections(libraryId, version)
|
||||
flattenedSections = trimIntro(flattenedSections)
|
||||
if (flattenedSections) {
|
||||
flattenedSections = trimIntro(flattenedSections)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col my-16 gap-16">
|
||||
{flattenedSections
|
||||
{(flattenedSections || [])
|
||||
.filter((section) => section.type !== 'category')
|
||||
.map((section, idx) => (
|
||||
<Fragment key={`${section.id}-${idx}`}>
|
||||
@@ -405,7 +407,7 @@ async function FunctionSection({
|
||||
}: FunctionSectionProps) {
|
||||
const fns = await getFunctionsList(sdkId, version)
|
||||
|
||||
const fn = fns.find((fn) => fn.id === section.id)
|
||||
const fn = fns?.find((fn) => fn.id === section.id)
|
||||
if (!fn) return null
|
||||
|
||||
let types: MethodTypes | undefined
|
||||
|
||||
@@ -194,7 +194,7 @@ interface CommentBlockTag {
|
||||
content: CommentKind[]
|
||||
}
|
||||
|
||||
function normalizeComment(original: TypedocComment | Comment): Comment {
|
||||
function normalizeComment(original: TypedocComment | Comment | undefined): Comment | undefined {
|
||||
if (!original) return
|
||||
|
||||
if ('shortText' in original || 'text' in original) {
|
||||
@@ -299,7 +299,7 @@ function parseModInternal(
|
||||
case 'property':
|
||||
case 'reference':
|
||||
default:
|
||||
return undefined
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,7 +379,7 @@ function parseSignature(
|
||||
map: Map<number, any>
|
||||
): {
|
||||
params: Array<FunctionParameterType>
|
||||
ret: ReturnType
|
||||
ret: ReturnType | undefined
|
||||
comment?: Comment
|
||||
} {
|
||||
const params: Array<FunctionParameterType> = (signature.parameters ?? []).map((param: any) => {
|
||||
@@ -401,7 +401,7 @@ function parseSignature(
|
||||
return res
|
||||
})
|
||||
|
||||
let ret: ReturnType
|
||||
let ret: ReturnType | undefined
|
||||
if ('type' in signature) {
|
||||
const retType = parseType(signature.type, map)
|
||||
if (retType) {
|
||||
@@ -643,10 +643,10 @@ function parsePickType(type: any, map: Map<number, any>) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
function parseReflectionType(type: any, map: Map<number, any>) {
|
||||
function parseReflectionType(type: any, map: Map<number, any>): TypeDetails | undefined {
|
||||
if (!type.declaration) return undefined
|
||||
|
||||
let res: TypeDetails
|
||||
let res: TypeDetails | undefined
|
||||
switch ((type.declaration.kindString ?? type.declaration.variant).toLowerCase()) {
|
||||
case 'type literal':
|
||||
res = parseTypeLiteral(type, map)
|
||||
@@ -662,7 +662,7 @@ function parseReflectionType(type: any, map: Map<number, any>) {
|
||||
return res
|
||||
}
|
||||
|
||||
function parseTypeLiteral(type: any, map: Map<number, any>): TypeDetails {
|
||||
function parseTypeLiteral(type: any, map: Map<number, any>): TypeDetails | undefined {
|
||||
const name = nameOrAnonymous(type)
|
||||
|
||||
if ('children' in type.declaration) {
|
||||
|
||||
@@ -67,7 +67,7 @@ export function ApiOperationBodySchemeSelector({
|
||||
requestBody: IApiEndPoint['requestBody']
|
||||
className?: string
|
||||
}) {
|
||||
const availableSchemes = Object.keys(requestBody.content) as Array<
|
||||
const availableSchemes = Object.keys(requestBody?.content || {}) as Array<
|
||||
'application/json' | 'application/x-www-form-urlencoded'
|
||||
>
|
||||
const [selectedScheme, setSelectedScheme] = useState(availableSchemes[0])
|
||||
@@ -75,11 +75,10 @@ export function ApiOperationBodySchemeSelector({
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const allSchemeDetails = useRef<HTMLUListElement[]>([])
|
||||
useEffect(() => {
|
||||
allSchemeDetails.current = Array.from(
|
||||
containerRef.current?.querySelectorAll(
|
||||
`[${API_REFERENCE_REQUEST_BODY_SCHEMA_DATA_ATTRIBUTES.KEY}]`
|
||||
)
|
||||
const elements = containerRef.current?.querySelectorAll(
|
||||
`[${API_REFERENCE_REQUEST_BODY_SCHEMA_DATA_ATTRIBUTES.KEY}]`
|
||||
)
|
||||
allSchemeDetails.current = elements ? (Array.from(elements) as HTMLUListElement[]) : []
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -31,7 +31,7 @@ function Section({ slug, link, columns = 'single', children }: SectionProps) {
|
||||
|
||||
return (
|
||||
<ReferenceSectionWrapper
|
||||
id={slug}
|
||||
id={slug || ''}
|
||||
link={link}
|
||||
className={cn(
|
||||
'grid grid-cols-[1fr] gap-x-16 gap-y-8',
|
||||
@@ -61,7 +61,7 @@ function Examples({ children }: PropsWithChildren) {
|
||||
|
||||
function EducationSection({ children, slug, ...props }: SectionProps) {
|
||||
return (
|
||||
<ReferenceSectionWrapper id={slug} className={'prose max-w-none'} {...props}>
|
||||
<ReferenceSectionWrapper id={slug || ''} className={'prose max-w-none'} {...props}>
|
||||
{children}
|
||||
</ReferenceSectionWrapper>
|
||||
)
|
||||
@@ -231,7 +231,7 @@ function ParamOrTypeDetails({ paramOrType }: { paramOrType: object }) {
|
||||
</div>
|
||||
)}
|
||||
{subContent && subContent.length > 0 && (
|
||||
<TypeSubDetails details={subContent} defaultOpen={defaultOpen} />
|
||||
<TypeSubDetails details={subContent} defaultOpen={defaultOpen || false} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
@@ -240,27 +240,29 @@ function ParamOrTypeDetails({ paramOrType }: { paramOrType: object }) {
|
||||
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'
|
||||
const isNameOnlyType = returnType?.type?.type === 'nameOnly'
|
||||
if (isNameOnlyType) return
|
||||
|
||||
const subContent = getSubDetails(returnType)
|
||||
const isDefaultOpen = isDefaultExpanded(returnType)
|
||||
const isDefaultOpen = returnType ? isDefaultExpanded(returnType) : false
|
||||
|
||||
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="text-xs text-foreground-muted">
|
||||
{returnType ? getTypeName(returnType) : ''}
|
||||
</div>
|
||||
{returnType?.comment?.shortText && (
|
||||
<div className="prose text-sm">
|
||||
<MDXRemoteBase
|
||||
source={returnType.comment?.shortText}
|
||||
source={returnType?.comment?.shortText}
|
||||
customPreprocess={normalizeMarkdown}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{subContent && subContent.length > 0 && (
|
||||
<TypeSubDetails defaultOpen={isDefaultOpen} details={subContent} />
|
||||
<TypeSubDetails defaultOpen={isDefaultOpen || false} details={subContent} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -371,7 +373,7 @@ export function ApiOperationRequestBodyDetails({
|
||||
}: {
|
||||
requestBody: IApiEndPoint['requestBody']
|
||||
}) {
|
||||
const availableSchemes = Object.keys(requestBody.content) as Array<
|
||||
const availableSchemes = Object.keys(requestBody?.content || {}) as Array<
|
||||
'application/json' | 'application/x-www-form-urlencoded'
|
||||
>
|
||||
|
||||
@@ -380,7 +382,7 @@ export function ApiOperationRequestBodyDetails({
|
||||
{availableSchemes.map((scheme, index) => (
|
||||
<ApiOperationRequestBodyDetailsInternal
|
||||
key={index}
|
||||
schema={requestBody.content[scheme].schema}
|
||||
schema={requestBody?.content?.[scheme]?.schema || ({} as ISchema)}
|
||||
hidden={index > 0}
|
||||
{...{
|
||||
[API_REFERENCE_REQUEST_BODY_SCHEMA_DATA_ATTRIBUTES.KEY]: scheme,
|
||||
@@ -452,12 +454,12 @@ function ApiOperationRequestBodyDetailsInternal({
|
||||
} else if (schema.type === 'object') {
|
||||
return (
|
||||
<ul {...props}>
|
||||
{Object.keys(schema.properties)
|
||||
{Object.keys(schema.properties || {})
|
||||
.map((property) => ({
|
||||
name: property,
|
||||
required: schema.required?.includes(property),
|
||||
required: schema.required?.includes(property) || false,
|
||||
in: 'body' as const,
|
||||
schema: schema.properties[property],
|
||||
schema: schema.properties?.[property] || ({} as ISchema),
|
||||
}))
|
||||
.map((property, index) => (
|
||||
<ApiSchemaParamDetails key={index} param={property} />
|
||||
@@ -599,7 +601,11 @@ function getTypeName(parameter: object): string {
|
||||
return parameter.type
|
||||
}
|
||||
|
||||
if (typeof parameter.type !== 'object' || !('type' in parameter.type)) {
|
||||
if (
|
||||
typeof parameter.type !== 'object' ||
|
||||
parameter.type === null ||
|
||||
!('type' in parameter.type)
|
||||
) {
|
||||
return ''
|
||||
}
|
||||
|
||||
@@ -645,28 +651,28 @@ function nameOrDefault(node: object, fallback: string) {
|
||||
}
|
||||
|
||||
function getSubDetails(parentType: MethodTypes['params'][number] | MethodTypes['ret']) {
|
||||
let subDetails: Array<any>
|
||||
let subDetails: Array<any> = []
|
||||
|
||||
switch (parentType.type?.type) {
|
||||
switch (parentType?.type?.type) {
|
||||
case 'object':
|
||||
subDetails = parentType.type.properties
|
||||
subDetails = parentType?.type?.properties
|
||||
break
|
||||
case 'function':
|
||||
subDetails = [
|
||||
...(parentType.type.params.length === 0
|
||||
...(parentType?.type?.params?.length === 0
|
||||
? []
|
||||
: [
|
||||
{
|
||||
name: 'Parameters',
|
||||
type: 'callback parameters',
|
||||
isOptional: 'NA',
|
||||
params: parentType.type.params.map((param) => ({
|
||||
params: parentType?.type?.params?.map((param) => ({
|
||||
...param,
|
||||
isOptional: 'NA',
|
||||
})),
|
||||
},
|
||||
]),
|
||||
{ name: 'Return', type: parentType.type.ret.type, isOptional: 'NA' },
|
||||
{ name: 'Return', type: parentType?.type?.ret?.type, isOptional: 'NA' },
|
||||
]
|
||||
break
|
||||
// @ts-ignore -- Adding these fake types to take advantage of existing recursion
|
||||
@@ -675,27 +681,34 @@ function getSubDetails(parentType: MethodTypes['params'][number] | MethodTypes['
|
||||
subDetails = parentType.params
|
||||
break
|
||||
case 'union':
|
||||
subDetails = parentType.type.subTypes.map((subType, index) => ({
|
||||
subDetails = parentType?.type?.subTypes?.map((subType, index) => ({
|
||||
name: `Option ${index + 1}`,
|
||||
type: { ...subType },
|
||||
isOptional: 'NA',
|
||||
}))
|
||||
break
|
||||
case 'promise':
|
||||
if (parentType.type.awaited.type === 'union') {
|
||||
subDetails = parentType.type.awaited.subTypes.map((subType, index) => ({
|
||||
if (parentType?.type?.awaited?.type === 'union') {
|
||||
subDetails = parentType?.type?.awaited?.subTypes?.map((subType, index) => ({
|
||||
name: `Option ${index + 1}`,
|
||||
type: { ...subType },
|
||||
isOptional: 'NA',
|
||||
}))
|
||||
} else if (parentType.type.awaited.type === 'object') {
|
||||
subDetails = parentType.type.awaited.properties.map((property) => ({
|
||||
} else if (
|
||||
parentType?.type?.awaited?.type === 'object' &&
|
||||
'properties' in parentType.type.awaited
|
||||
) {
|
||||
subDetails = (parentType.type.awaited as any).properties?.map((property) => ({
|
||||
...property,
|
||||
isOptional: 'NA',
|
||||
}))
|
||||
} else if (parentType.type.awaited.type === 'array') {
|
||||
} else if (parentType?.type?.awaited?.type === 'array') {
|
||||
subDetails = [
|
||||
{ name: 'array element', type: parentType.type.awaited.elemType, isOptional: 'NA' },
|
||||
{
|
||||
name: 'array element',
|
||||
type: (parentType?.type?.awaited as any)?.elemType,
|
||||
isOptional: 'NA',
|
||||
},
|
||||
]
|
||||
}
|
||||
break
|
||||
@@ -836,7 +849,7 @@ function applyParameterMergeStrategy(
|
||||
if (clonedParametersByName.has(key)) {
|
||||
clonedParametersByName.set(
|
||||
key,
|
||||
applyParameterMergeStrategy(clonedParametersByName.get(key), alternateValue)
|
||||
applyParameterMergeStrategy(clonedParametersByName.get(key)!, alternateValue)
|
||||
)
|
||||
} else {
|
||||
clonedParametersByName.set(key, alternateValue)
|
||||
@@ -856,7 +869,7 @@ function applyParameterMergeStrategy(
|
||||
*********/
|
||||
|
||||
function mergeIntoUnion() {
|
||||
if (alternateParameter.type.type === 'union') {
|
||||
if (alternateParameter.type?.type === 'union') {
|
||||
const originalType = clonedParameter.type
|
||||
|
||||
if (
|
||||
@@ -866,7 +879,7 @@ function applyParameterMergeStrategy(
|
||||
} else {
|
||||
clonedParameter.type = {
|
||||
type: 'union',
|
||||
subTypes: [originalType, ...alternateParameter.type.subTypes],
|
||||
subTypes: [originalType as TypeDetails, ...(alternateParameter.type?.subTypes || [])],
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -874,7 +887,7 @@ function applyParameterMergeStrategy(
|
||||
if (!isEqual(originalType, alternateParameter.type)) {
|
||||
clonedParameter.type = {
|
||||
type: 'union',
|
||||
subTypes: [originalType, alternateParameter.type],
|
||||
subTypes: [originalType as TypeDetails, alternateParameter.type!],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -885,11 +898,13 @@ function isDefaultExpanded(meta: object) {
|
||||
return (
|
||||
'type' in meta &&
|
||||
typeof meta.type === 'object' &&
|
||||
meta.type &&
|
||||
'type' in meta.type &&
|
||||
(meta.type.type == 'union' ||
|
||||
(meta.type.type === 'promise' &&
|
||||
'awaited' in meta.type &&
|
||||
typeof meta.type.awaited === 'object' &&
|
||||
meta.type.awaited &&
|
||||
'type' in meta.type.awaited &&
|
||||
meta.type.awaited.type === 'union'))
|
||||
)
|
||||
|
||||
@@ -30,15 +30,21 @@ export function parseReferencePath(slug: Array<string>) {
|
||||
const isSelfHostingReference = slug[0].startsWith('self-hosting-')
|
||||
|
||||
if (isClientSdkReference) {
|
||||
let [sdkId, maybeVersion, maybeCrawlers, ...path] = slug
|
||||
let sdkId: string
|
||||
let maybeVersion: string | null
|
||||
let maybeCrawlers: string | null
|
||||
let path: string[]
|
||||
;[sdkId, maybeVersion, maybeCrawlers, ...path] = slug
|
||||
if (!/v\d+/.test(maybeVersion)) {
|
||||
maybeVersion = null
|
||||
maybeCrawlers = maybeVersion
|
||||
path = [maybeCrawlers, ...path]
|
||||
maybeCrawlers = maybeVersion
|
||||
}
|
||||
if (maybeCrawlers !== 'crawlers') {
|
||||
if (typeof maybeCrawlers === 'string') {
|
||||
path = [maybeCrawlers, ...path]
|
||||
}
|
||||
maybeCrawlers = null
|
||||
path = [maybeCrawlers, ...path]
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -75,7 +81,7 @@ export function parseReferencePath(slug: Array<string>) {
|
||||
async function generateStaticParamsForSdkVersion(sdkId: string, version: string) {
|
||||
const flattenedSections = await getFlattenedSections(sdkId, version)
|
||||
|
||||
return flattenedSections
|
||||
return (flattenedSections || [])
|
||||
.filter((section) => section.type !== 'category' && !!section.slug)
|
||||
.map((section) => ({
|
||||
slug: [
|
||||
@@ -139,7 +145,7 @@ export async function generateReferenceMetadata(
|
||||
const displayName = REFERENCES[sdkId].name
|
||||
const sectionTitle =
|
||||
slug.length > 0
|
||||
? flattenedSections.find((section) => section.slug === slug[0])?.title
|
||||
? flattenedSections?.find((section) => section.slug === slug[0])?.title
|
||||
: undefined
|
||||
const url = [BASE_PATH, 'reference', sdkId, path[0]].filter(Boolean).join('/')
|
||||
|
||||
@@ -154,7 +160,6 @@ export async function generateReferenceMetadata(
|
||||
...(slug.length > 0
|
||||
? {
|
||||
alternates: {
|
||||
...parentAlternates,
|
||||
canonical: url,
|
||||
},
|
||||
}
|
||||
@@ -219,8 +224,8 @@ export function normalizeMarkdown(markdownUnescaped: string): string {
|
||||
visit(markdownTree, ['code', 'inlineCode'], (node) => {
|
||||
codeBlocks.push({
|
||||
type: node.type,
|
||||
start: node.position.start.offset,
|
||||
end: node.position.end.offset,
|
||||
start: node.position?.start?.offset || 0,
|
||||
end: node.position?.end?.offset || 0,
|
||||
})
|
||||
})
|
||||
// Sort code blocks by start offset in descending order
|
||||
|
||||
@@ -58,7 +58,7 @@ export default async function TroubleshootingPage({ entry }: { entry: ITroublesh
|
||||
<hr className="my-6" aria-hidden />
|
||||
</>
|
||||
)}
|
||||
{entry.data.errors?.length > 0 && (
|
||||
{entry.data.errors?.length && entry.data.errors.length > 0 && (
|
||||
<>
|
||||
<h3 className="text-sm text-foreground-lighter mb-3">Related error codes</h3>
|
||||
<div className="flex flex-wrap gap-0.5">
|
||||
@@ -76,9 +76,9 @@ export default async function TroubleshootingPage({ entry }: { entry: ITroublesh
|
||||
<hr className="my-6" aria-hidden />
|
||||
</>
|
||||
)}
|
||||
{entry.data.keywords?.length > 0 && (
|
||||
{entry.data.keywords?.length && entry.data.keywords.length > 0 && (
|
||||
<>
|
||||
<h3 className="text-sm text-foreground-lighter mb-3">Tags</h3>
|
||||
<h3 className="text-sm text-foreground-lighter mb-3">Keywords</h3>
|
||||
<div className="flex flex-wrap gap-0.5">
|
||||
{entry.data.keywords.map((keyword) => (
|
||||
<Link
|
||||
|
||||
@@ -49,15 +49,15 @@ export async function TroubleshootingPreview({ entry }: { entry: ITroubleshootin
|
||||
{entry.data.title}
|
||||
</h3>
|
||||
</Link>
|
||||
{entry.data.errors?.length > 0 && (
|
||||
{entry.data.errors?.length && entry.data.errors.length > 0 && (
|
||||
<span className="text-xs text-foreground-lighter ml-1 @4xl/troubleshooting:ml-0">
|
||||
{entry.data.errors
|
||||
{(entry.data.errors || [])
|
||||
.map(formatError)
|
||||
.filter(Boolean)
|
||||
.map((error, index) => (
|
||||
<>
|
||||
<code key={index}>{error}</code>
|
||||
{index < entry.data.errors.length - 1 ? ', ' : ''}
|
||||
{index < (entry.data.errors?.length || 0) - 1 ? ', ' : ''}
|
||||
</>
|
||||
))}
|
||||
</span>
|
||||
|
||||
@@ -11,7 +11,7 @@ export const TROUBLESHOOTING_DATA_ATTRIBUTES = {
|
||||
ERRORS_LIST_ATTRIBUTE: 'data-errors',
|
||||
}
|
||||
|
||||
export function formatError(error: ITroubleshootingEntry['data']['errors'][number]) {
|
||||
export function formatError(error: NonNullable<ITroubleshootingEntry['data']['errors']>[number]) {
|
||||
return `${error.http_status_code ?? ''}${!!error.http_status_code && !!error.code ? ' ' : ''}${error.code ?? ''}`
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ export async function getAllTroubleshootingErrors() {
|
||||
const entries = await getAllTroubleshootingEntries()
|
||||
const allErrors = new Set(
|
||||
entries
|
||||
.flatMap((entry) => entry.data.errors)
|
||||
.flatMap((entry) => entry.data.errors ?? [])
|
||||
.filter((error) => error?.http_status_code || error?.code)
|
||||
)
|
||||
|
||||
@@ -81,8 +81,8 @@ export async function getAllTroubleshootingErrors() {
|
||||
}
|
||||
|
||||
function sortErrors(
|
||||
a: ITroubleshootingMetadata['errors'][number],
|
||||
b: ITroubleshootingMetadata['errors'][number]
|
||||
a: NonNullable<ITroubleshootingMetadata['errors']>[number],
|
||||
b: NonNullable<ITroubleshootingMetadata['errors']>[number]
|
||||
) {
|
||||
return formatError(a).localeCompare(formatError(b))
|
||||
}
|
||||
|
||||
@@ -1,32 +1,27 @@
|
||||
type RecObj<T extends object, K extends keyof T> = T[K] extends Array<T> ? T : never
|
||||
|
||||
const EMPTY_ARRAY = new Array(0)
|
||||
export function getEmptyArray() {
|
||||
return EMPTY_ARRAY
|
||||
}
|
||||
|
||||
export function deepFilterRec<T extends object, K extends keyof T>(
|
||||
arr: Array<RecObj<T, K>>,
|
||||
recKey: K,
|
||||
export function deepFilterRec<T extends object>(
|
||||
arr: Array<T>,
|
||||
recKey: string,
|
||||
filterFn: (item: T) => boolean
|
||||
) {
|
||||
return arr.reduce(
|
||||
(acc, elem) => {
|
||||
if (!filterFn(elem)) return acc
|
||||
): Array<T> {
|
||||
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 (recKey in elem && elem[recKey as keyof T]) {
|
||||
const newSubitems = deepFilterRec(elem[recKey as keyof T] as Array<T>, recKey, filterFn)
|
||||
const newElem = { ...elem, [recKey]: newSubitems }
|
||||
|
||||
if (newSubitems.length > 0 || filterFn(elem)) acc.push(newElem)
|
||||
} else {
|
||||
acc.push(elem)
|
||||
}
|
||||
if (newSubitems.length > 0 || filterFn(elem)) acc.push(newElem)
|
||||
} else {
|
||||
acc.push(elem)
|
||||
}
|
||||
|
||||
return acc
|
||||
},
|
||||
[] as Array<RecObj<T, K>>
|
||||
)
|
||||
return acc
|
||||
}, [] as Array<T>)
|
||||
}
|
||||
|
||||
export async function pluckPromise<T, K extends keyof T>(promise: Promise<T>, key: K) {
|
||||
@@ -37,12 +32,12 @@ export async function pluckPromise<T, K extends keyof T>(promise: Promise<T>, ke
|
||||
export class Result<Ok, Error> {
|
||||
constructor(private internal: { data: Ok; error: null } | { data: null; error: Error }) {}
|
||||
|
||||
static ok<Ok, Error>(data: Ok): Result<Ok, Error> {
|
||||
return new Result({ data, error: null })
|
||||
static ok<Error = unknown, Ok = unknown>(data: Ok): Result<Ok, Error> {
|
||||
return new Result<Ok, Error>({ data, error: null })
|
||||
}
|
||||
|
||||
static error<Ok, Error>(error: Error): Result<Ok, Error> {
|
||||
return new Result({ data: null, error })
|
||||
static error<Ok = unknown, Error = unknown>(error: Error): Result<Ok, Error> {
|
||||
return new Result<Ok, Error>({ data: null, error })
|
||||
}
|
||||
|
||||
static async tryCatchFlat<
|
||||
@@ -67,24 +62,24 @@ export class Result<Ok, Error> {
|
||||
}
|
||||
|
||||
map<Mapped>(fn: (data: Ok) => Mapped): Result<Mapped, Error> {
|
||||
if (this.isOk()) return Result.ok(fn(this.internal.data))
|
||||
if (this.isOk()) return Result.ok(fn(this.internal.data!))
|
||||
return this as unknown as Result<Mapped, Error>
|
||||
}
|
||||
|
||||
mapError<MappedError>(fn: (error: Error) => MappedError): Result<Ok, MappedError> {
|
||||
if (this.isOk()) return this as unknown as Result<Ok, MappedError>
|
||||
return Result.error(fn(this.internal.error))
|
||||
return Result.error(fn(this.internal.error!))
|
||||
}
|
||||
|
||||
flatMap<Mapped>(fn: (data: Ok) => Result<Mapped, Error>): Result<Mapped, Error> {
|
||||
if (this.isOk()) return fn(this.internal.data)
|
||||
if (this.isOk()) return fn(this.internal.data!)
|
||||
return this as unknown as Result<Mapped, Error>
|
||||
}
|
||||
|
||||
flatMapAsync<Mapped>(
|
||||
fn: (data: Ok) => Promise<Result<Mapped, Error>>
|
||||
): Promise<Result<Mapped, Error>> {
|
||||
if (this.isOk()) return fn(this.internal.data)
|
||||
if (this.isOk()) return fn(this.internal.data!)
|
||||
return Promise.resolve(this as unknown as Result<Mapped, Error>)
|
||||
}
|
||||
|
||||
@@ -92,7 +87,7 @@ export class Result<Ok, Error> {
|
||||
onOk: (data: Ok) => Mapped,
|
||||
onError: (error: Error) => MappedError
|
||||
): Mapped | MappedError {
|
||||
if (this.isOk()) return onOk(this.internal.data)
|
||||
return onError(this.internal.error)
|
||||
if (this.isOk()) return onOk(this.internal.data!)
|
||||
return onError(this.internal.error!)
|
||||
}
|
||||
}
|
||||
@@ -34,10 +34,10 @@ export async function CodeBlock({
|
||||
skipTypeGeneration?: boolean
|
||||
}>) {
|
||||
let code = (contents || extractCode(children)).trim()
|
||||
const lang = tryToBundledLanguage(langSetting) || extractLang(children)
|
||||
const lang = tryToBundledLanguage(langSetting || '') || extractLang(children)
|
||||
|
||||
let twoslashed = null as null | Map<number, Map<number, Array<NodeHover>>>
|
||||
if (!skipTypeGeneration && TWOSLASHABLE_LANGS.includes(lang)) {
|
||||
if (!skipTypeGeneration && lang && TWOSLASHABLE_LANGS.includes(lang)) {
|
||||
try {
|
||||
const { code: editedCode, nodes } = twoslasher(code)
|
||||
const hoverNodes: Array<NodeHover> = nodes.filter((node) => node.type === 'hover')
|
||||
@@ -53,7 +53,7 @@ export async function CodeBlock({
|
||||
}
|
||||
|
||||
const { tokens } = highlighter.codeToTokens(code, {
|
||||
lang,
|
||||
lang: lang || undefined,
|
||||
theme: 'Supabase Theme',
|
||||
})
|
||||
|
||||
@@ -121,7 +121,10 @@ function CodeLine({
|
||||
annotations={twoslash.get(token.offset)!}
|
||||
/>
|
||||
) : (
|
||||
<span key={token.offset} style={{ color: token.color, ...getFontStyle(token.fontStyle) }}>
|
||||
<span
|
||||
key={token.offset}
|
||||
style={{ color: token.color, ...getFontStyle(token.fontStyle || 0) }}
|
||||
>
|
||||
{token.content}
|
||||
</span>
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ enum FontStyle {
|
||||
}
|
||||
|
||||
export function getFontStyle(styleFlags: number): CSSProperties {
|
||||
let style: CSSProperties = null
|
||||
let style: CSSProperties = {}
|
||||
|
||||
if (styleFlags & FontStyle.Italic) {
|
||||
;(style ??= {}).fontStyle = 'italic'
|
||||
|
||||
@@ -62,7 +62,13 @@ const InfoTooltip = ({
|
||||
[_setTooltipOpen, id, isMobile]
|
||||
)
|
||||
|
||||
useEffect(() => () => clearTimeout(timeout.current), [])
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (timeout.current) {
|
||||
clearTimeout(timeout.current)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -13,10 +13,10 @@ const cliSpec = yaml.load(fs.readFileSync(`spec/cli_v1_commands.yaml`, 'utf8'))
|
||||
* @returns {Array<{link: string}>} - An array of CLI page links.
|
||||
*/
|
||||
export function generateCLIPages() {
|
||||
let cliPages = []
|
||||
let cliPages: Array<{ link: string }> = []
|
||||
|
||||
cliSpec.commands.map((section) => {
|
||||
const slug = flatCLISections.find((item) => item.id === section.id)?.slug
|
||||
cliSpec.commands.map((section: any) => {
|
||||
const slug = (flatCLISections as any[]).find((item: any) => item.id === section.id)?.slug
|
||||
if (slug) cliPages.push({ link: `reference/cli/${slug}` })
|
||||
})
|
||||
return cliPages
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export function flattenSections(sections) {
|
||||
let a = []
|
||||
export function flattenSections(sections: any[]): any[] {
|
||||
let a: any[] = []
|
||||
for (let i = 0; i < sections.length; i++) {
|
||||
if (sections[i].id) {
|
||||
// only push a section that has an id
|
||||
|
||||
@@ -89,7 +89,7 @@ const StickyHeader: FC<StickyHeader> = ({ icon, ...props }) => {
|
||||
|
||||
// we're serving search bots a different file (/crawlers/[...slug])
|
||||
// and need to modify content to suit that
|
||||
const isCrawlerPage = router.route.includes('/crawlers/[...slug]')
|
||||
const isCrawlerPage = router?.route.includes('/crawlers/[...slug]') || false
|
||||
|
||||
const { ref } = useInView({
|
||||
threshold: 1,
|
||||
|
||||
@@ -7,7 +7,7 @@ import remarkGfm from 'remark-gfm'
|
||||
import type { ICommonMarkdown } from '~/components/reference/Reference.types'
|
||||
|
||||
async function generateRefMarkdown(sections: ICommonMarkdown[], slug: string) {
|
||||
let markdownContent = []
|
||||
let markdownContent: any[] = []
|
||||
/**
|
||||
* Read all the markdown files that might have
|
||||
* - custom text
|
||||
|
||||
@@ -6,7 +6,7 @@ import { visit } from 'unist-util-visit'
|
||||
export type UrlTransformFunction = (url: string, node: Element) => string
|
||||
|
||||
function modify(node: Element, prop: string, fn?: UrlTransformFunction) {
|
||||
if (hasProperty(node, prop)) {
|
||||
if (node.properties && hasProperty(node, prop)) {
|
||||
const property = node.properties[prop]
|
||||
if (typeof property !== 'string') {
|
||||
return
|
||||
|
||||
@@ -25,7 +25,9 @@ const remarkMkDocsAdmonition = function () {
|
||||
|
||||
// Extract the admonition type, title, and remaining text
|
||||
const [, , typeTitle, value] = match
|
||||
const [, type, title] = typeTitle.match(/^(.+?) ?(?:"(.*)")?$/)
|
||||
const typeTitleMatch = typeTitle.match(/^(.+?) ?(?:"(.*)")?$/)
|
||||
if (!typeTitleMatch) return
|
||||
const [, type, title] = typeTitleMatch
|
||||
|
||||
// Rewrite the node's value to remove the admonition syntax
|
||||
firstChild.value = value
|
||||
@@ -76,7 +78,7 @@ const remarkMkDocsAdmonition = function () {
|
||||
* Splices the discovered siblings out of the original parent and returns them.
|
||||
*/
|
||||
function extractLinkedSiblings(parent: Root, node: Node, index: number, indentAmount = 4) {
|
||||
const { column } = node.position.start
|
||||
const { column } = node.position?.start || { column: 0 }
|
||||
|
||||
let nextSibling: Content
|
||||
let i = index
|
||||
|
||||
@@ -125,6 +125,7 @@ const remarkPyMdownTabs = function () {
|
||||
* Splices the discovered siblings out of the original parent and returns them.
|
||||
*/
|
||||
function extractLinkedSiblings(parent: Root, node: Node, index: number, indentAmount = 4) {
|
||||
if (!node.position) return []
|
||||
const { column } = node.position.start
|
||||
|
||||
let nextSibling: Content
|
||||
|
||||
+15
-10
@@ -10,13 +10,16 @@ let octokitInstance: Octokit
|
||||
|
||||
export function octokit() {
|
||||
if (!octokitInstance) {
|
||||
const privateKey = process.env.DOCS_GITHUB_APP_PRIVATE_KEY
|
||||
if (!privateKey) {
|
||||
throw new Error('DOCS_GITHUB_APP_PRIVATE_KEY environment variable is required')
|
||||
}
|
||||
|
||||
// https://github.com/gr2m/universal-github-app-jwt?tab=readme-ov-file#converting-pkcs1-to-pkcs8
|
||||
const privateKeyPkcs8 = crypto
|
||||
.createPrivateKey(process.env.DOCS_GITHUB_APP_PRIVATE_KEY)
|
||||
.export({
|
||||
type: 'pkcs8',
|
||||
format: 'pem',
|
||||
})
|
||||
const privateKeyPkcs8 = crypto.createPrivateKey(privateKey).export({
|
||||
type: 'pkcs8',
|
||||
format: 'pem',
|
||||
})
|
||||
|
||||
octokitInstance = new Octokit({
|
||||
authStrategy: createAppAuth,
|
||||
@@ -78,6 +81,7 @@ async function getGitHubFileContents({
|
||||
} catch (err) {
|
||||
console.error('Error fetching GitHub file: %o', err)
|
||||
onError?.(err)
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,10 +97,10 @@ export async function getGitHubFileContentsImmutableOnly({
|
||||
branch: string
|
||||
path: string
|
||||
options: {
|
||||
onError: (err?: unknown) => void
|
||||
fetch?: (info: RequestInfo, init?: RequestInit) => Promise<Response>
|
||||
onError: (error: unknown) => void
|
||||
fetch: (url: string) => Promise<Response>
|
||||
}
|
||||
}) {
|
||||
}): Promise<string> {
|
||||
const isImmutableCommit = await checkForImmutableCommit({
|
||||
org,
|
||||
repo,
|
||||
@@ -106,13 +110,14 @@ export async function getGitHubFileContentsImmutableOnly({
|
||||
throw Error('The commit is not an immutable commit SHA. Tags and branch names are not allowed.')
|
||||
}
|
||||
|
||||
return getGitHubFileContents({
|
||||
const result = await getGitHubFileContents({
|
||||
org,
|
||||
repo,
|
||||
branch,
|
||||
path,
|
||||
options: { onError, fetch },
|
||||
})
|
||||
return result || ''
|
||||
}
|
||||
|
||||
async function checkForImmutableCommit({
|
||||
|
||||
@@ -24,17 +24,17 @@ export function extractTsDocNode(nodeToFind: string, definition: any) {
|
||||
}
|
||||
|
||||
export function generateParameters(tsDefinition: any) {
|
||||
let functionDeclaration = null
|
||||
let functionDeclaration: any = null
|
||||
if (tsDefinition.kindString == 'Method') {
|
||||
functionDeclaration = tsDefinition
|
||||
} else if (tsDefinition.kindString == 'Constructor') {
|
||||
functionDeclaration = tsDefinition
|
||||
} else functionDeclaration = tsDefinition?.type?.declaration
|
||||
if (!functionDeclaration) return ''
|
||||
if (!functionDeclaration || !functionDeclaration.signatures) return ''
|
||||
|
||||
// Functions can have multiple signatures - select the last one since that
|
||||
// tends to be closer to primitive types (citation needed).
|
||||
const paramDefinitions: TsDoc.TypeDefinition[] = functionDeclaration.signatures.at(-1).parameters
|
||||
const paramDefinitions: TsDoc.TypeDefinition[] = functionDeclaration.signatures.at(-1)?.parameters
|
||||
if (!paramDefinitions) return ''
|
||||
|
||||
// const paramsComments: TsDoc.CommentTag = tsDefinition.comment?.tags?.filter(x => x.tag == 'param')
|
||||
@@ -46,7 +46,7 @@ function recurseThroughParams(paramDefinition: any) {
|
||||
const param = { ...paramDefinition }
|
||||
const labelParams = generateLabelParam(param)
|
||||
|
||||
let children: any[]
|
||||
let children: any[] | undefined
|
||||
if (param.type?.type === 'literal') {
|
||||
// skip: literal types have no children
|
||||
} else if (param.type?.type === 'intrinsic') {
|
||||
@@ -241,7 +241,7 @@ export function gen_v3(
|
||||
toArrayWithKey(val!, 'operation').forEach((o) => {
|
||||
const operation = o as v3OperationWithPath
|
||||
const operationId =
|
||||
type === 'mgmt-api' && isValidSlug(operation.operationId)
|
||||
type === 'mgmt-api' && operation.operationId && isValidSlug(operation.operationId)
|
||||
? operation.operationId
|
||||
: slugify(operation.summary!)
|
||||
const enriched = {
|
||||
|
||||
@@ -8,10 +8,14 @@ let supabaseAdminClient: SupabaseClient<Database> | null = null
|
||||
|
||||
export function supabaseAdmin() {
|
||||
if (!supabaseAdminClient) {
|
||||
supabaseAdminClient = createClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL,
|
||||
process.env.SUPABASE_SECRET_KEY
|
||||
)
|
||||
const url = process.env.NEXT_PUBLIC_SUPABASE_URL
|
||||
const key = process.env.SUPABASE_SECRET_KEY
|
||||
|
||||
if (!url || !key) {
|
||||
throw new Error('Missing required environment variables for Supabase admin client')
|
||||
}
|
||||
|
||||
supabaseAdminClient = createClient(url, key)
|
||||
}
|
||||
|
||||
return supabaseAdminClient
|
||||
|
||||
@@ -26,10 +26,14 @@ export abstract class SearchResultModel {
|
||||
await supabase().rpc('search_content', {
|
||||
embedding,
|
||||
include_full_content: includeFullContent,
|
||||
max_result: args.limit,
|
||||
max_result: args.limit ?? undefined,
|
||||
})
|
||||
)
|
||||
.map((matches) => matches.map(createModelFromMatch).filter(Boolean))
|
||||
.map((matches) =>
|
||||
matches
|
||||
.map(createModelFromMatch)
|
||||
.filter((item): item is SearchResultInterface => item !== null)
|
||||
)
|
||||
.mapError(convertPostgrestToApiError)
|
||||
|
||||
return matchResult
|
||||
@@ -55,7 +59,7 @@ function createModelFromMatch({
|
||||
})
|
||||
case 'reference':
|
||||
const { language } = metadata
|
||||
if (SDKLanguageValues.includes(language)) {
|
||||
if (language && SDKLanguageValues.includes(language)) {
|
||||
return new ReferenceSDKFunctionModel({
|
||||
title: page_title,
|
||||
href,
|
||||
@@ -71,7 +75,7 @@ function createModelFromMatch({
|
||||
subsections,
|
||||
})
|
||||
} else {
|
||||
break
|
||||
return null
|
||||
}
|
||||
case 'github-discussions':
|
||||
return new TroubleshootingModel({
|
||||
|
||||
@@ -269,7 +269,7 @@ export class GraphQLCollectionBuilder {
|
||||
const result = await fetch(args)
|
||||
const { items: fetchedItems, totalCount, hasNextPage = false, hasPreviousPage = false } = result
|
||||
const edges = fetchedItems.map((item) => {
|
||||
return { node: item, cursor: getCursor(item) }
|
||||
return { node: item, cursor: getCursor?.(item) ?? '' }
|
||||
})
|
||||
|
||||
return {
|
||||
@@ -291,7 +291,7 @@ export class GraphQLCollectionBuilder {
|
||||
return { node: item, cursor: getCursor(item, idx) }
|
||||
})
|
||||
|
||||
const { edges, hasPreviousPage, hasNextPage } = getRequestedSlice(allEdges, args)
|
||||
const { edges, hasPreviousPage, hasNextPage } = getRequestedSlice(allEdges, args ?? {})
|
||||
|
||||
return {
|
||||
edges,
|
||||
@@ -323,12 +323,12 @@ function getRequestedSlice<ItemType>(
|
||||
let beforeIndex = allEdges.length
|
||||
let afterIndex = -1
|
||||
|
||||
const requestedBefore = toNumber(pageArgs.before)
|
||||
const requestedBefore = pageArgs.before ? toNumber(pageArgs.before) : undefined
|
||||
if (requestedBefore && requestedBefore >= 0 && requestedBefore < beforeIndex) {
|
||||
beforeIndex = requestedBefore
|
||||
hasNextPage = true
|
||||
}
|
||||
const requestedAfter = toNumber(pageArgs.after)
|
||||
const requestedAfter = pageArgs.after ? toNumber(pageArgs.after) : undefined
|
||||
if (requestedAfter && requestedAfter >= 0) {
|
||||
afterIndex = requestedAfter
|
||||
hasPreviousPage = true
|
||||
@@ -336,10 +336,10 @@ function getRequestedSlice<ItemType>(
|
||||
|
||||
let edges = allEdges.slice(afterIndex + 1, beforeIndex)
|
||||
|
||||
if (pageArgs.first >= 0 && edges.length > pageArgs.first) {
|
||||
if (pageArgs.first != null && pageArgs.first >= 0 && edges.length > pageArgs.first) {
|
||||
edges = edges.slice(0, pageArgs.first)
|
||||
hasNextPage = true
|
||||
} else if (pageArgs.last >= 0 && edges.length > pageArgs.last) {
|
||||
} else if (pageArgs.last != null && pageArgs.last >= 0 && edges.length > pageArgs.last) {
|
||||
edges = edges.slice(edges.length - pageArgs.last)
|
||||
hasPreviousPage = true
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
type ValueNode,
|
||||
} from 'graphql'
|
||||
|
||||
let options = { processArguments: false, excludedFields: [] }
|
||||
let options = { processArguments: false, excludedFields: [] as string[] }
|
||||
|
||||
function getSelections(ast: FieldNode | FragmentDefinitionNode | InlineFragmentNode) {
|
||||
if (
|
||||
@@ -47,7 +47,7 @@ function getAST(ast: FragmentSpreadNode | InlineFragmentNode, info: GraphQLResol
|
||||
}
|
||||
|
||||
function getArguments(ast: FieldNode, info: GraphQLResolveInfo) {
|
||||
return ast.arguments.map((argument) => {
|
||||
return ast.arguments?.map((argument) => {
|
||||
const argumentValue = getArgumentValue(argument.value, info)
|
||||
|
||||
return {
|
||||
@@ -81,7 +81,8 @@ function getArgumentValue(arg: ValueNode, info: GraphQLResolveInfo) {
|
||||
}
|
||||
|
||||
function getDirectiveValue(directive: DirectiveNode, info: GraphQLResolveInfo) {
|
||||
const arg = directive.arguments[0]
|
||||
const arg = directive.arguments?.[0]
|
||||
if (!arg) return undefined
|
||||
if (arg.value.kind !== 'Variable') {
|
||||
return arg.value.kind === 'BooleanValue' ? arg.value.value : undefined
|
||||
}
|
||||
@@ -93,22 +94,26 @@ function getDirectiveResults(ast: SelectionNode, info: GraphQLResolveInfo) {
|
||||
shouldInclude: true,
|
||||
shouldSkip: false,
|
||||
}
|
||||
return ast.directives.reduce((result, directive) => {
|
||||
switch (directive.name.value) {
|
||||
case 'include':
|
||||
const directiveValue = getDirectiveValue(directive, info)
|
||||
if (directiveValue != undefined) {
|
||||
return { ...result, shouldInclude: directiveValue }
|
||||
}
|
||||
case 'skip':
|
||||
const directiveValule = getDirectiveValue(directive, info)
|
||||
if (directiveValue != undefined) {
|
||||
return { ...result, shouldSkip: directiveValue }
|
||||
}
|
||||
default:
|
||||
return result
|
||||
}
|
||||
}, directiveResult)
|
||||
return (
|
||||
ast.directives?.reduce((result, directive) => {
|
||||
switch (directive.name.value) {
|
||||
case 'include':
|
||||
const directiveValue = getDirectiveValue(directive, info)
|
||||
if (directiveValue != undefined) {
|
||||
return { ...result, shouldInclude: directiveValue }
|
||||
}
|
||||
return result
|
||||
case 'skip':
|
||||
const directiveSkipValue = getDirectiveValue(directive, info)
|
||||
if (directiveSkipValue != undefined) {
|
||||
return { ...result, shouldSkip: directiveSkipValue }
|
||||
}
|
||||
return result
|
||||
default:
|
||||
return result
|
||||
}
|
||||
}, directiveResult) ?? directiveResult
|
||||
)
|
||||
}
|
||||
|
||||
function flattenAST(
|
||||
@@ -128,7 +133,7 @@ function flattenAST(
|
||||
flattened = flattenAST(getAST(a, info), info, flattened)
|
||||
} else {
|
||||
const name = a.name.value
|
||||
if (options.excludedFields.indexOf(name) !== -1) {
|
||||
if (options.excludedFields && options.excludedFields.indexOf(name) !== -1) {
|
||||
return flattened
|
||||
}
|
||||
if (flattened[name] && flattened[name] !== '__arguments') {
|
||||
|
||||
@@ -45,7 +45,7 @@ function processMdx(content: string, options?: { yaml?: boolean }): ProcessedMdx
|
||||
mdastExtensions: [mdxFromMarkdown()],
|
||||
})
|
||||
|
||||
let meta: Record<string, unknown>
|
||||
let meta: Record<string, unknown> | undefined
|
||||
if (options?.yaml) {
|
||||
meta = frontmatter
|
||||
} else {
|
||||
@@ -83,7 +83,8 @@ function processMdx(content: string, options?: { yaml?: boolean }): ProcessedMdx
|
||||
const [firstNode] = tree.children
|
||||
const content = toMarkdown(tree)
|
||||
|
||||
const rawHeading: string = firstNode.type === 'heading' ? toString(firstNode) : undefined
|
||||
const rawHeading: string | undefined =
|
||||
firstNode.type === 'heading' ? toString(firstNode) : undefined
|
||||
|
||||
if (!rawHeading) {
|
||||
return { content }
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
* the last Git commit date.
|
||||
*/
|
||||
|
||||
import _configureDotEnv from './utils/dotenv'
|
||||
import _configureDotEnv from './utils/dotenv.js'
|
||||
|
||||
import { createClient, type SupabaseClient } from '@supabase/supabase-js'
|
||||
import matter from 'gray-matter'
|
||||
@@ -111,8 +111,8 @@ function parseOptions(): Options {
|
||||
|
||||
function createSupabaseClient() {
|
||||
return createClient(
|
||||
process.env[REQUIRED_ENV_VARS.SUPABASE_URL],
|
||||
process.env[REQUIRED_ENV_VARS.SERVICE_ROLE_KEY]
|
||||
process.env[REQUIRED_ENV_VARS.SUPABASE_URL]!,
|
||||
process.env[REQUIRED_ENV_VARS.SERVICE_ROLE_KEY]!
|
||||
)
|
||||
}
|
||||
|
||||
@@ -125,7 +125,9 @@ async function updateContentDates({ reset, ctx }: { reset: boolean; ctx: Ctx })
|
||||
const updateTasks: Array<Promise<void>> = []
|
||||
for (const file of mdxFiles) {
|
||||
const tasks = await updateTimestamps(file, { reset, timestamp, ctx })
|
||||
updateTasks.push(...tasks)
|
||||
if (tasks) {
|
||||
updateTasks.push(...tasks)
|
||||
}
|
||||
}
|
||||
await Promise.all(updateTasks)
|
||||
}
|
||||
@@ -183,7 +185,7 @@ function processMdx(rawContent: string): Array<SectionWithChecksum> {
|
||||
|
||||
let heading = rawHeading
|
||||
if (seenHeadings.has(rawHeading)) {
|
||||
const idx = seenHeadings.get(rawHeading) + 1
|
||||
const idx = (seenHeadings.get(rawHeading) ?? 0) + 1
|
||||
seenHeadings.set(rawHeading, idx)
|
||||
heading = `${rawHeading} (__UNIQUE_MARKER__${idx})`
|
||||
} else {
|
||||
|
||||
+32
-13
@@ -1,4 +1,4 @@
|
||||
import _configureDotEnv from './utils/dotenv'
|
||||
import './utils/dotenv.js'
|
||||
|
||||
import 'dotenv/config'
|
||||
import fs from 'node:fs/promises'
|
||||
@@ -15,8 +15,6 @@ import {
|
||||
type SearchSource,
|
||||
} from './search/sources/index.js'
|
||||
|
||||
const _ = _configureDotEnv
|
||||
|
||||
interface Source {
|
||||
title: string
|
||||
/**
|
||||
@@ -39,37 +37,58 @@ const SOURCES: Source[] = [
|
||||
{
|
||||
title: 'Supabase Reference (JavaScript)',
|
||||
relPath: 'llms/js.txt',
|
||||
fetch: fetchJsLibReferenceSource,
|
||||
fetch: async () =>
|
||||
(await fetchJsLibReferenceSource()).filter(
|
||||
(item): item is SearchSource => item !== undefined
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Supabase Reference (Dart)',
|
||||
relPath: 'llms/dart.txt',
|
||||
fetch: fetchDartLibReferenceSource,
|
||||
fetch: async () =>
|
||||
(await fetchDartLibReferenceSource()).filter(
|
||||
(item): item is SearchSource => item !== undefined
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Supabase Reference (Swift)',
|
||||
relPath: 'llms/swift.txt',
|
||||
fetch: fetchSwiftLibReferenceSource,
|
||||
fetch: async () =>
|
||||
(await fetchSwiftLibReferenceSource()).filter(
|
||||
(item): item is SearchSource => item !== undefined
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Supabase Reference (Kotlin)',
|
||||
relPath: 'llms/kt.txt',
|
||||
fetch: fetchKtLibReferenceSource,
|
||||
relPath: 'llms/kotlin.txt',
|
||||
fetch: async () =>
|
||||
(await fetchKtLibReferenceSource()).filter(
|
||||
(item): item is SearchSource => item !== undefined
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Supabase Reference (Python)',
|
||||
relPath: 'llms/py.txt',
|
||||
fetch: fetchPythonLibReferenceSource,
|
||||
relPath: 'llms/python.txt',
|
||||
fetch: async () =>
|
||||
(await fetchPythonLibReferenceSource()).filter(
|
||||
(item): item is SearchSource => item !== undefined
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Supabase Reference (C#)',
|
||||
relPath: 'llms/csharp.txt',
|
||||
fetch: fetchCSharpLibReferenceSource,
|
||||
fetch: async () =>
|
||||
(await fetchCSharpLibReferenceSource()).filter(
|
||||
(item): item is SearchSource => item !== undefined
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Supabase Reference (CLI)',
|
||||
title: 'Supabase CLI Reference',
|
||||
relPath: 'llms/cli.txt',
|
||||
fetch: fetchCliLibReferenceSource,
|
||||
fetch: async () =>
|
||||
(await fetchCliLibReferenceSource()).filter(
|
||||
(item): item is SearchSource => item !== undefined
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -2,24 +2,25 @@ import { readdir } from 'node:fs/promises'
|
||||
import { extname, join } from 'node:path'
|
||||
import {
|
||||
GLOBAL_MENU_ITEMS,
|
||||
gettingstarted,
|
||||
cli,
|
||||
auth,
|
||||
database,
|
||||
api,
|
||||
graphql,
|
||||
functions,
|
||||
realtime,
|
||||
storage,
|
||||
ai,
|
||||
api,
|
||||
auth,
|
||||
cli,
|
||||
database,
|
||||
functions,
|
||||
gettingstarted,
|
||||
graphql,
|
||||
platform,
|
||||
realtime,
|
||||
resources,
|
||||
self_hosting,
|
||||
} from '../../components/Navigation/NavigationMenu/NavigationMenu.constants'
|
||||
storage,
|
||||
} from '../../components/Navigation/NavigationMenu/NavigationMenu.constants.js'
|
||||
|
||||
const DOCS_ROOT_DIR = join(__dirname, '..', '..')
|
||||
const DEFAULT_DOCS_CONTENT_DIR = join(DOCS_ROOT_DIR, 'content')
|
||||
|
||||
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
||||
const DOCS_CONTENT_DIR = process.env.DOCS_CONTENT_DIR || DEFAULT_DOCS_CONTENT_DIR
|
||||
|
||||
const IGNORE_LIST = [
|
||||
@@ -47,7 +48,8 @@ type RefItem = {
|
||||
|
||||
const recGetUrl = (items: readonly RefItem[], acc: string[] = []) =>
|
||||
items.reduce((acc, item) => {
|
||||
if (item.href || item.url) acc.push(item.href || item.url)
|
||||
const url = item.href || item.url
|
||||
if (url) acc.push(url)
|
||||
if (item.items) acc.push(...recGetUrl(item.items, acc))
|
||||
return acc
|
||||
}, acc)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import _configureDotenv from '../utils/dotenv'
|
||||
import '../utils/dotenv.js'
|
||||
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
import { parseArgs } from 'node:util'
|
||||
@@ -7,8 +7,6 @@ import { v4 as uuidv4 } from 'uuid'
|
||||
import type { Json, Section } from '../helpers.mdx.js'
|
||||
import { fetchAllSources } from './sources/index.js'
|
||||
|
||||
const _ = _configureDotenv
|
||||
|
||||
const args = parseArgs({
|
||||
options: {
|
||||
refresh: {
|
||||
@@ -41,8 +39,8 @@ async function generateEmbeddings() {
|
||||
}
|
||||
|
||||
const supabaseClient = createClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL,
|
||||
process.env.SUPABASE_SECRET_KEY,
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
process.env.SUPABASE_SECRET_KEY!,
|
||||
{
|
||||
auth: {
|
||||
persistSession: false,
|
||||
|
||||
@@ -38,7 +38,7 @@ export async function fetchDiscussions(owner: string, repo: string, categoryId:
|
||||
auth: {
|
||||
appId,
|
||||
installationId,
|
||||
privateKey: crypto.createPrivateKey(privateKey).export({ type: 'pkcs8', format: 'pem' }),
|
||||
privateKey: crypto.createPrivateKey(privateKey!).export({ type: 'pkcs8', format: 'pem' }),
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -139,7 +139,11 @@ export async function fetchAllSources() {
|
||||
|
||||
const partnerIntegrationSources = fetchPartners()
|
||||
.then((partners) =>
|
||||
Promise.all(partners.map((partner) => new IntegrationLoader(partner.slug, partner).load()))
|
||||
partners
|
||||
? Promise.all(
|
||||
partners.map((partner) => new IntegrationLoader(partner.slug, partner).load())
|
||||
)
|
||||
: []
|
||||
)
|
||||
.then((data) => data.flat())
|
||||
|
||||
|
||||
@@ -46,6 +46,6 @@ export class MarkdownSource extends BaseSource {
|
||||
const sections = this.sections ?? []
|
||||
const sectionText = sections.map(({ content }) => content).join('\n\n')
|
||||
|
||||
return `# ${this.meta.title ?? ''}\n\n${this.meta.subtitle ?? ''}\n\n${sectionText}`
|
||||
return `# ${this.meta?.title ?? ''}\n\n${this.meta?.subtitle ?? ''}\n\n${sectionText}`
|
||||
}
|
||||
}
|
||||
@@ -8,15 +8,15 @@ type PartnerData = {
|
||||
overview: string // The Markdown content for indexing
|
||||
}
|
||||
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_MISC_URL
|
||||
const supabaseAnonKey = process.env.NEXT_PUBLIC_MISC_ANON_KEY
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_MISC_URL!
|
||||
const supabaseAnonKey = process.env.NEXT_PUBLIC_MISC_ANON_KEY!
|
||||
|
||||
let supabaseClient: SupabaseClient
|
||||
function getSupabaseClient() {
|
||||
if (!supabaseClient) {
|
||||
supabaseClient = createClient(supabaseUrl, supabaseAnonKey)
|
||||
return supabaseClient
|
||||
}
|
||||
return supabaseClient
|
||||
}
|
||||
|
||||
export async function fetchPartners() {
|
||||
@@ -27,7 +27,7 @@ export async function fetchPartners() {
|
||||
.eq('approved', true)
|
||||
// We want to show technology integrations, not agencies, in search
|
||||
.neq('type', 'expert')
|
||||
return partners
|
||||
return partners ?? []
|
||||
}
|
||||
|
||||
export class IntegrationLoader extends BaseLoader {
|
||||
|
||||
@@ -30,7 +30,7 @@ export abstract class ReferenceLoader<SpecSection> extends BaseLoader {
|
||||
super(source, path)
|
||||
}
|
||||
|
||||
async load() {
|
||||
async load(): Promise<BaseSource[]> {
|
||||
const specContents = await readFile(this.specFilePath, 'utf8')
|
||||
const refSectionsContents = await readFile(this.sectionsFilePath, 'utf8')
|
||||
|
||||
@@ -55,13 +55,13 @@ export abstract class ReferenceLoader<SpecSection> extends BaseLoader {
|
||||
this.enhanceMeta(specSection)
|
||||
)
|
||||
})
|
||||
.filter(Boolean)
|
||||
.filter((item): item is ReferenceSource<SpecSection> => item !== undefined)
|
||||
|
||||
return sections
|
||||
return sections as BaseSource[]
|
||||
}
|
||||
|
||||
abstract getSpecSections(specContents: string): SpecSection[]
|
||||
abstract matchSpecSection(specSections: SpecSection[], id: string): SpecSection
|
||||
abstract matchSpecSection(specSections: SpecSection[], id: string): SpecSection | undefined
|
||||
enhanceMeta(section: SpecSection): Json {
|
||||
return this.meta
|
||||
}
|
||||
@@ -136,7 +136,7 @@ export class OpenApiReferenceLoader extends ReferenceLoader<enrichedOperation> {
|
||||
|
||||
return generatedSpec.operations
|
||||
}
|
||||
matchSpecSection(operations: enrichedOperation[], id: string): enrichedOperation {
|
||||
matchSpecSection(operations: enrichedOperation[], id: string): enrichedOperation | undefined {
|
||||
return operations.find((operation) => operation.operationId === id)
|
||||
}
|
||||
}
|
||||
@@ -166,9 +166,9 @@ export class OpenApiReferenceSource extends ReferenceSource<enrichedOperation> {
|
||||
|
||||
extractIndexedContent(): string {
|
||||
const { summary, description, operation, tags } = this.specSection
|
||||
return `# ${this.meta.title ?? ''}\n\n${summary ?? ''}\n\n${description ?? ''}\n\n${operation ?? ''}\n\n${tags.join(
|
||||
', '
|
||||
)}`
|
||||
return `# ${this.meta.title ?? ''}\n\n${summary ?? ''}\n\n${description ?? ''}\n\n${operation ?? ''}\n\n${
|
||||
tags?.join(', ') ?? ''
|
||||
}`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +190,10 @@ export class ClientLibReferenceLoader extends ReferenceLoader<IFunctionDefinitio
|
||||
return spec.functions
|
||||
}
|
||||
|
||||
matchSpecSection(functionDefinitions: IFunctionDefinition[], id: string): IFunctionDefinition {
|
||||
matchSpecSection(
|
||||
functionDefinitions: IFunctionDefinition[],
|
||||
id: string
|
||||
): IFunctionDefinition | undefined {
|
||||
return functionDefinitions.find((functionDefinition) => functionDefinition.id === id)
|
||||
}
|
||||
|
||||
@@ -246,7 +249,7 @@ export class CliReferenceLoader extends ReferenceLoader<CliCommand> {
|
||||
|
||||
return spec.commands
|
||||
}
|
||||
matchSpecSection(cliCommands: CliCommand[], id: string): CliCommand {
|
||||
matchSpecSection(cliCommands: CliCommand[], id: string): CliCommand | undefined {
|
||||
return cliCommands.find((cliCommand) => cliCommand.id === id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,19 @@ function extractSectionsFromOpenApi(filePath, outputPath) {
|
||||
|
||||
try {
|
||||
const openApiJson = JSON.parse(data)
|
||||
const categories = []
|
||||
const sections = []
|
||||
const categories: string[] = []
|
||||
const sections: Array<{
|
||||
type: string
|
||||
title: string
|
||||
id?: string
|
||||
slug?: string
|
||||
items: Array<{
|
||||
type: string
|
||||
title: string
|
||||
id: string
|
||||
slug: string
|
||||
}>
|
||||
}> = []
|
||||
|
||||
if (openApiJson.paths) {
|
||||
for (const route in openApiJson.paths) {
|
||||
@@ -47,7 +58,7 @@ function extractSectionsFromOpenApi(filePath, outputPath) {
|
||||
}
|
||||
|
||||
const sectionCate = sections.find((i) => i.title === tag)
|
||||
sectionCate.items.push({
|
||||
sectionCate?.items.push({
|
||||
id: operationId,
|
||||
title: slugToTitle(operationId),
|
||||
slug: operationId,
|
||||
@@ -65,6 +76,7 @@ function extractSectionsFromOpenApi(filePath, outputPath) {
|
||||
id: 'introduction',
|
||||
slug: 'introduction',
|
||||
type: 'markdown',
|
||||
items: [],
|
||||
})
|
||||
|
||||
fs.writeFile(outputPath, JSON.stringify(sections, null, 2), 'utf8', (err) => {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"strictNullChecks": false
|
||||
"strictNullChecks": true
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
|
||||
@@ -67,7 +67,7 @@ const Input = forwardRef<
|
||||
setHidden(false)
|
||||
}
|
||||
|
||||
let inputClasses = []
|
||||
let inputClasses: string[] = []
|
||||
if (icon) inputClasses.push(__styles.with_icon)
|
||||
|
||||
return (
|
||||
|
||||
Reference in new issue
Block a user