Merge pull request #10910 from supabase/master

Prod deploy dashboard
This commit is contained in:
Joshen Lim authored and GitHub committed 2022-12-14 11:57:33 +07:00
commit fe50988422
89 files changed
+38932 -2839

No files matched your search

+9
View File
@@ -8,6 +8,7 @@
- [Running turborepo](#running-turborepo)
- [Shared components](#shared-components)
- [Installing packages](#installing-packages)
- [New Supabase docs](#new-supabase-docs)
3. [Create a pull request](#create-a-pull-request)
- [Common tasks](#common-tasks)
@@ -106,6 +107,14 @@ For example:
You do not need to install `devDependencies` in each workspace. These can all be installed in the root package.
#### New Supabase docs
Following the changes to the [Supabase docs](https://supabase.com/blog/new-supabase-docs-built-with-nextjs) the following is needed to run the new docs locally:
- Inside of `apps/docs` create a `.env.local` file with the following: `NEXT_PUBLIC_NEW_DOCS=true`
Now when you run a local devlopment docs server you will see the new docs site.
---
## Create a pull request
@@ -248,6 +248,7 @@ export const menuItems: NavMenu = {
{ name: 'Quickstart', url: '/guides/storage/quickstart', items: [] },
{ name: 'Access Control', url: '/guides/storage/access-control', items: [] },
{ name: 'CDN', url: '/guides/storage/cdn', items: [] },
{ name: 'Image Transformations', url: '/guides/storage/image-transformations', items: [] },
],
},
{
@@ -0,0 +1,181 @@
import Image from 'next/image'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useMenuLevelId } from '~/hooks/useMenuState'
import { useTheme } from 'common/Providers'
const home = [
[
{
label: 'Home',
icon: '/img/icons/menu/home',
href: '/',
level: 'home',
},
{
label: 'Getting Started',
icon: '/img/icons/menu/getting-started',
href: '/guides/getting-started',
level: 'gettingstarted',
},
],
[
{
label: 'Database',
icon: '/img/icons/menu/database',
href: '/guides/database',
level: 'database',
},
{
label: 'Auth',
icon: '/img/icons/menu/auth',
href: '/guides/auth/overview',
level: 'auth',
},
{
label: 'Edge Functions',
icon: '/img/icons/menu/functions',
href: '/guides/functions',
level: 'functions',
},
{
label: 'Realtime',
icon: '/img/icons/menu/realtime',
href: '/guides/realtime',
level: 'realtime',
},
{
label: 'Storage',
icon: '/img/icons/menu/storage',
href: '/guides/storage',
level: 'storage',
},
],
[
{
label: 'Platform',
icon: '/img/icons/menu/platform',
href: '/guides/platform',
level: 'platform',
},
{
label: 'Resources',
icon: '/img/icons/menu/platform',
href: '/guides/resources',
level: 'resources',
},
{
label: 'Integrations',
icon: '/img/icons/menu/integrations',
hasLightIcon: true,
href: '/guides/integrations',
level: 'integrations',
},
],
[
{
label: 'Client Library Reference',
},
{
label: 'JavaScript',
icon: '/img/icons/menu/reference-javascript',
href: '/reference/javascript/introduction',
level: 'reference_javascript',
},
{
label: 'Flutter',
icon: '/img/icons/menu/reference-dart',
href: '/reference/dart/introduction',
level: 'reference_dart',
},
{
label: 'Tools Reference',
},
{
label: 'Management API',
icon: '/img/icons/menu/reference-api',
href: '/reference/api/introduction',
level: 'reference_javascript',
},
{
label: 'Supabase CLI',
icon: '/img/icons/menu/reference-cli',
href: '/reference/cli/introduction',
level: 'reference_javascript',
},
],
]
const NavigationMenuHome = () => {
const level = useMenuLevelId()
const router = useRouter()
const { isDarkMode } = useTheme()
return (
<div
className={[
'transition-all duration-150 ease-out',
level === 'home' || !level
? 'opacity-100 ml-0 delay-150'
: 'opacity-0 -ml-8 invisible absolute',
].join(' ')}
>
<ul className="relative w-full flex flex-col gap-4">
{home.map((section, sectionIndex) => {
return (
<>
{sectionIndex !== 0 && (
<div
className="h-px w-full bg-blackA-300 dark:bg-whiteA-300"
key={`section-${sectionIndex}-border`}
></div>
)}
<div key={`section-${sectionIndex}`}>
<div className="flex flex-col gap-3">
{section.map((link) => {
if (!link.href) {
return (
<span
key={link.label}
className="font-mono uppercase text-xs text-scale-900"
>
{link.label}
</span>
)
} else {
return (
<Link href={link.href} passHref key={link.label}>
<a>
<li
className={[
'group flex items-center gap-3',
'text-base transition-all duration-150 text-scale-1200 hover:text-brand-900 hover:cursor-pointer ',
].join(' ')}
>
<Image
alt={link.label}
src={`${router.basePath}${
isDarkMode ? link.icon : `${link.icon}-light`
}${!link.icon.includes('png') ? '.svg' : ''}`}
width={17}
height={17}
className="w-4 h-4 group-hover:scale-110 ease-out transition-all"
/>
{link.label}
</li>
</a>
</Link>
)
}
})}
</div>
</div>
</>
)
})}
</ul>
</div>
)
}
export default NavigationMenuHome
@@ -454,6 +454,7 @@ export const storage = {
{ name: 'Quickstart', url: '/guides/storage/quickstart', items: [] },
{ name: 'Access Control', url: '/guides/storage/access-control', items: [] },
{ name: 'CDN', url: '/guides/storage/cdn', items: [] },
{ name: 'Image Transformations', url: '/guides/storage/image-transformations', items: [] },
],
}
@@ -560,19 +561,19 @@ export const resources = {
{
name: 'Auth Server',
url: '/reference/self-hosting-auth/introduction',
icon: '/docs/img/icons/menu/reference-auth',
icon: '/img/icons/menu/reference-auth',
items: [],
},
{
name: 'Storage Server',
url: '/reference/self-hosting-storage/introduction',
icon: '/docs/img/icons/menu/reference-storage',
icon: '/img/icons/menu/reference-storage',
items: [],
},
{
name: 'Realtime Server',
url: '/reference/self-hosting-realtime/introduction',
icon: '/docs/img/icons/menu/reference-realtime',
icon: '/img/icons/menu/reference-realtime',
items: [],
},
],
@@ -668,21 +669,21 @@ export const reference = {
url: '/reference/javascript/start',
level: 'reference_javascript',
items: [],
icon: '/img/icons/javascript.svg',
icon: 'docs/img/icons/javascript.svg',
},
{
name: 'supabase-dart',
url: '/reference/dart/start',
level: 'reference_dart',
items: [],
icon: '/img/icons/javascript.svg',
icon: 'docs/img/icons/javascript.svg',
},
{
name: 'supbase-python',
url: '/reference/python/start',
level: 'reference_python',
items: [],
icon: '/img/icons/javascript.svg',
icon: 'docs/img/icons/javascript.svg',
},
],
},
@@ -693,13 +694,13 @@ export const reference = {
name: 'Supabase CLI',
url: '/reference/cli/start',
items: [],
icon: '/img/icons/cli.svg',
icon: 'docs/img/icons/cli-icon.svg',
},
{
name: 'Management API',
url: '/reference/javascript',
items: [],
icon: '/img/icons/javascript.svg',
icon: 'docs/img/icons/javascript-icon.svg',
},
],
},
@@ -1,12 +1,8 @@
import { useTheme } from 'common/Providers'
import Image from 'next/image'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { memo, useEffect, useState } from 'react'
import { memo, useEffect } from 'react'
import { menuState } from '~/hooks/useMenuState'
import NavigationMenuGuideList from './NavigationMenuGuideList'
import NavigationMenuRefList from './NavigationMenuRefList'
import { menuState } from '~/hooks/useMenuState'
// @ts-expect-error
import spec_js_v2 from '~/../../spec/supabase_js_v2.yml' assert { type: 'yml' }
// @ts-expect-error
@@ -15,16 +11,15 @@ import spec_js_v1 from '~/../../spec/supabase_js_v1.yml' assert { type: 'yml' }
import spec_dart_v1 from '~/../../spec/supabase_dart_v1.yml' assert { type: 'yml' }
// @ts-expect-error
import spec_dart_v0 from '~/../../spec/supabase_dart_v0.yml' assert { type: 'yml' }
// import { gen_v3 } from '~/lib/refGenerator/helpers'
import libCommonSections from '~/../../spec/common-client-libs-sections.json'
import apiCommonSections from '~/../../spec/common-api-sections.json'
import cliCommonSections from '~/../../spec/common-cli-sections.json'
import libCommonSections from '~/../../spec/common-client-libs-sections.json'
import authServerCommonSections from '~/../../spec/common-self-hosting-auth-sections.json'
import storageServerCommonSections from '~/../../spec/common-self-hosting-storage-sections.json'
import realtimeServerCommonSections from '~/../../spec/common-self-hosting-realtime-sections.json'
import storageServerCommonSections from '~/../../spec/common-self-hosting-storage-sections.json'
import { flattenSections } from '~/lib/helpers'
import { useMenuLevelId } from '~/hooks/useMenuState'
import NavigationMenuHome from './HomeMenu'
// Filter libCommonSections for just the relevant sections in the current library
function generateAllowedClientLibKeys(sections, spec) {
@@ -69,9 +64,6 @@ export type RefKeyOptions =
const SideNav = () => {
const router = useRouter()
const { isDarkMode } = useTheme()
const level = useMenuLevelId()
let version = ''
@@ -161,225 +153,24 @@ const SideNav = () => {
}
}, [router.events])
const home = [
[
{
label: 'Home',
icon: '/img/icons/menu/home',
href: '/',
level: 'home',
},
{
label: 'Getting Started',
icon: '/img/icons/menu/getting-started',
href: '/guides/getting-started',
level: 'gettingstarted',
},
],
[
{
label: 'Database',
icon: '/img/icons/menu/database',
href: '/guides/database',
level: 'database',
},
{
label: 'Auth',
icon: '/img/icons/menu/auth',
href: '/guides/auth/overview',
level: 'auth',
},
{
label: 'Edge Functions',
icon: '/img/icons/menu/functions',
href: '/guides/functions',
level: 'functions',
},
{
label: 'Realtime',
icon: '/img/icons/menu/realtime',
href: '/guides/realtime',
level: 'realtime',
},
{
label: 'Storage',
icon: '/img/icons/menu/storage',
href: '/guides/storage',
level: 'storage',
},
],
[
{
label: 'Platform',
icon: '/img/icons/menu/platform',
href: '/guides/platform',
level: 'platform',
},
{
label: 'Resources',
icon: '/img/icons/menu/platform',
href: '/guides/resources',
level: 'resources',
},
{
label: 'Integrations',
icon: '/img/icons/menu/integrations',
href: '/guides/integrations',
level: 'integrations',
},
],
[
{
label: 'Client Library Reference',
},
{
label: 'JavaScript',
icon: '/img/icons/menu/reference-javascript',
hasLightIcon: true,
href: '/reference/javascript/introduction',
level: 'reference_javascript',
},
{
label: 'Flutter',
icon: '/img/icons/menu/reference-dart',
hasLightIcon: true,
href: '/reference/dart/introduction',
level: 'reference_dart',
},
{
label: 'Tools Reference',
},
{
label: 'Management API',
icon: '/img/icons/menu/reference-api',
hasLightIcon: true,
href: '/reference/api/introduction',
level: 'reference_javascript',
},
{
label: 'Supabase CLI',
icon: '/img/icons/menu/reference-cli',
hasLightIcon: true,
href: '/reference/cli/introduction',
level: 'reference_javascript',
},
// {
// label: 'Self-Hosting Reference',
// },
// {
// label: 'Self-Hosting Auth',
// icon: '/img/icons/menu/reference-auth',
// hasLightIcon: true,
// href: '/reference/self-hosting-auth/introduction',
// level: 'reference_self_hosting_auth',
// },
// {
// label: 'Self-Hosting Storage',
// hasLightIcon: true,
// icon: '/img/icons/menu/reference-storage',
// href: '/reference/self-hosting-storage/introduction',
// level: 'reference_self_hosting_auth',
// },
// {
// label: 'Self-Hosting Realtime',
// hasLightIcon: true,
// icon: '/img/icons/menu/reference-realtime',
// href: '/reference/self-hosting-realtime/introduction',
// level: 'reference_self_hosting_auth',
// },
],
]
return (
<div className="flex relative">
{/* // main menu */}
<div
className={[
'transition-all duration-150 ease-out',
level === 'home' || !level
? 'opacity-100 ml-0 delay-150'
: 'opacity-0 -ml-8 invisible absolute',
].join(' ')}
>
<ul className="relative w-full flex flex-col gap-4">
{home.map((section, sectionIndex) => {
return (
<>
{sectionIndex !== 0 && (
<div
className="h-px w-full bg-blackA-300 dark:bg-whiteA-300"
key={`section-${sectionIndex}-border`}
></div>
)}
<div key={`section-${sectionIndex}`}>
<div className="flex flex-col gap-3">
{section.map((link) => {
if (!link.href) {
return (
<span
key={link.label}
className="font-mono uppercase text-xs text-scale-900"
>
{link.label}
</span>
)
} else {
return (
<Link href={link.href} passHref key={link.label}>
<a>
<li
className={[
'group flex items-center gap-3',
'text-base transition-all duration-150 text-scale-1200 hover:text-brand-900 hover:cursor-pointer ',
].join(' ')}
>
<Image
alt={link.label}
src={`${router.basePath}${
Object.hasOwn(link, 'hasLightIcon') && !link.hasLightIcon
? link.icon
: isDarkMode
? link.icon
: `${link.icon}-light`
}${!link.icon.includes('png') ? '.svg' : ''}`}
width={17}
height={17}
className="w-4 h-4 group-hover:scale-110 ease-out transition-all"
/>
{link.label}
</li>
</a>
</Link>
)
}
})}
</div>
</div>
</>
)
})}
</ul>
</div>
<NavigationMenuGuideList id={'gettingstarted'} currentLevel={level} />
<NavigationMenuGuideList id={'database'} currentLevel={level} />
<NavigationMenuGuideList id={'auth'} currentLevel={level} />
<NavigationMenuGuideList id={'functions'} currentLevel={level} />
<NavigationMenuGuideList id={'realtime'} currentLevel={level} />
<NavigationMenuGuideList id={'storage'} currentLevel={level} />
<NavigationMenuGuideList id={'platform'} currentLevel={level} />
<NavigationMenuGuideList
id={'resources'}
currentLevel={level}
setMenuLevelId={menuState.setMenuLevelId}
/>
<NavigationMenuGuideList id={'integrations'} currentLevel={level} />
<NavigationMenuGuideList id={'reference'} currentLevel={level} />
<NavigationMenuHome />
<NavigationMenuGuideList id={'gettingstarted'} />
<NavigationMenuGuideList id={'database'} />
<NavigationMenuGuideList id={'auth'} />
<NavigationMenuGuideList id={'functions'} />
<NavigationMenuGuideList id={'realtime'} />
<NavigationMenuGuideList id={'storage'} />
<NavigationMenuGuideList id={'platform'} />
<NavigationMenuGuideList id={'resources'} />
<NavigationMenuGuideList id={'integrations'} />
<NavigationMenuGuideList id={'reference'} />
{/* // Client Libs */}
<NavigationMenuRefList
key={'reference-js-menu'}
id={'reference_javascript_v1'}
currentLevel={level}
commonSections={libCommonSections}
lib="javascript"
allowedClientKeys={generateAllowedClientLibKeys(libCommonSections, spec_js_v1)}
@@ -387,7 +178,6 @@ const SideNav = () => {
<NavigationMenuRefList
key={'reference-js-menu'}
id={'reference_javascript_v2'}
currentLevel={level}
commonSections={libCommonSections}
lib="javascript"
allowedClientKeys={generateAllowedClientLibKeys(libCommonSections, spec_js_v2)}
@@ -395,7 +185,6 @@ const SideNav = () => {
<NavigationMenuRefList
key={'reference-dart-menu'}
id={'reference_dart_v0'}
currentLevel={level}
commonSections={libCommonSections}
lib="dart"
allowedClientKeys={generateAllowedClientLibKeys(libCommonSections, spec_dart_v0)}
@@ -403,7 +192,6 @@ const SideNav = () => {
<NavigationMenuRefList
key={'reference-dart-menu'}
id={'reference_dart_v1'}
currentLevel={level}
commonSections={libCommonSections}
lib="dart"
allowedClientKeys={generateAllowedClientLibKeys(libCommonSections, spec_dart_v1)}
@@ -412,14 +200,12 @@ const SideNav = () => {
<NavigationMenuRefList
key={'reference-cli-menu'}
id={'reference_cli'}
currentLevel={level}
commonSections={cliCommonSections}
lib="cli"
/>
<NavigationMenuRefList
key={'reference-api-menu'}
id={'reference_api'}
currentLevel={level}
commonSections={apiCommonSections}
lib="api"
/>
@@ -427,21 +213,18 @@ const SideNav = () => {
<NavigationMenuRefList
key={'reference-self-hosting-auth-menu'}
id={'reference_self_hosting_auth'}
currentLevel={level}
commonSections={authServerCommonSections}
lib="self-hosting-auth"
/>
<NavigationMenuRefList
key={'reference-self-hosting-storage-menu'}
id={'reference_self_hosting_storage'}
currentLevel={level}
commonSections={storageServerCommonSections}
lib="self-hosting-storage"
/>
<NavigationMenuRefList
key={'reference-self-hosting-realtime-menu'}
id={'reference_self_hosting_realtime'}
currentLevel={level}
commonSections={realtimeServerCommonSections}
lib="self-hosting-auth"
/>
@@ -5,14 +5,14 @@ import Link from 'next/link'
import { useRouter } from 'next/router'
import React from 'react'
import { IconChevronLeft } from '~/../../packages/ui'
import { useMenuLevelId } from '~/hooks/useMenuState'
import * as NavItems from './NavigationMenu.constants'
interface Props {
currentLevel: string
id: string
setMenuLevelId?: any
}
const NavigationMenuGuideList: React.FC<Props> = ({ currentLevel, id, setMenuLevelId }) => {
const NavigationMenuGuideList: React.FC<Props> = ({ id, setMenuLevelId }) => {
const router = useRouter()
const { isDarkMode } = useTheme()
@@ -25,6 +25,8 @@ const NavigationMenuGuideList: React.FC<Props> = ({ currentLevel, id, setMenuLev
// remove the end of the url if a deep link
const firstLevelRoute = url?.split('/')?.slice(0, 4)?.join('/')
const level = useMenuLevelId()
return (
<Accordion.Root
collapsible
@@ -34,12 +36,12 @@ const NavigationMenuGuideList: React.FC<Props> = ({ currentLevel, id, setMenuLev
className={[
'transition-all ml-8 duration-150 ease-out',
// enabled
currentLevel === id && 'opacity-100 ml-0 delay-150',
currentLevel === 'home' && 'ml-12',
level === id && 'opacity-100 ml-0 delay-150',
level === 'home' && 'ml-12',
// disabled
currentLevel !== 'home' && currentLevel !== id ? '-ml-8' : '',
currentLevel !== id ? 'opacity-0 invisible absolute' : '',
level !== 'home' && level !== id ? '-ml-8' : '',
level !== id ? 'opacity-0 invisible absolute' : '',
].join(' ')}
>
<ul className={['relative w-full flex flex-col gap-0'].join(' ')}>
@@ -81,7 +83,7 @@ const NavigationMenuGuideList: React.FC<Props> = ({ currentLevel, id, setMenuLev
: 'hover:text-brand-900 text-scale-1200',
].join(' ')}
>
{menu.title ?? currentLevel}
{menu.title ?? level}
</span>
</div>
</a>
@@ -105,24 +107,11 @@ const NavigationMenuGuideList: React.FC<Props> = ({ currentLevel, id, setMenuLev
}
const LinkContainer = (props) => {
if (props.url) {
return (
<Link href={props.url} passHref>
<a className={props.className}>{props.children}</a>
</Link>
)
} else {
return (
<button
className={props.className}
onClick={() => {
if (props.parent && setMenuLevelId) setMenuLevelId(props.parent)
}}
>
{props.children}
</button>
)
}
return (
<Link href={props.url} passHref>
<a className={props.className}>{props.children}</a>
</Link>
)
}
return (
@@ -148,25 +137,19 @@ const NavigationMenuGuideList: React.FC<Props> = ({ currentLevel, id, setMenuLev
].join(' ')}
parent={subItem.parent}
>
{subItem.icon && <img src={`${subItem.icon}.svg`} className="w-3" />}
{subItem.icon && (
<Image
alt={subItem.name + router.basePath}
src={
`${router.basePath}` +
`${subItem.icon}${!isDarkMode ? '-light' : ''}.svg`
}
width={15}
height={15}
/>
)}
{subItem.name}
</LinkContainer>
{/* <Link href={`${subItem.url}`} passHref>
<a
className={[
'flex items-center gap-2',
'cursor-pointer transition text-sm',
subItem.url === router.pathname
? 'text-brand-900'
: 'hover:text-brand-900 text-scale-1000',
].join(' ')}
>
{subItem.icon && (
<img src={`${subItem.icon}.svg`} className="w-3" />
)}
{subItem.name}
</a>
</Link> */}
</li>
{subItem.items && subItem.items.length > 0 && (
@@ -11,7 +11,7 @@ import { useTheme } from 'common/Providers'
// import apiCommonSections from '~/../../spec/common-client-libs-sections.json'
import RevVersionDropdown from '~/components/RefVersionDropdown'
import { useMenuActiveRefId } from '~/hooks/useMenuState'
import { useMenuActiveRefId, useMenuLevelId } from '~/hooks/useMenuState'
import { RefIdOptions, RefKeyOptions } from './NavigationMenu'
const FunctionLink = ({
@@ -32,10 +32,20 @@ const FunctionLink = ({
const router = useRouter()
const activeAccordianItem = useMenuActiveRefId()
// check if we're on a versioned page
let version = ''
if (router.asPath.includes('v1')) {
version = 'v1'
}
if (router.asPath.includes('v0')) {
version = 'v0'
}
const active = activeAccordianItem === id
return (
<li key={id} className="function-link-item">
<Link href={`/reference/${library}/${slug}`} passHref>
<Link href={`/reference/${library}/${version ? version + '/' : ''}${slug}`} passHref>
<a
className={[
'cursor-pointer transition text-sm hover:text-brand-900 flex gap-3',
@@ -63,8 +73,6 @@ const Divider = () => {
}
interface INavigationMenuRefList {
currentLevel: string
id: RefIdOptions
lib: RefKeyOptions
commonSections: any[] // to do type up
@@ -75,7 +83,6 @@ interface INavigationMenuRefList {
}
const NavigationMenuRefList: React.FC<INavigationMenuRefList> = ({
currentLevel,
id,
lib,
commonSections,
@@ -133,17 +140,22 @@ const NavigationMenuRefList: React.FC<INavigationMenuRefList> = ({
}).items.map((x) => x.id)
: []
// console.log(filterIds)
// console.log(modifierIds)
const level = useMenuLevelId()
return (
<div
className={[
'transition-all ml-8 duration-150 ease-out',
// enabled
currentLevel === id && 'opacity-100 ml-0 delay-150 h-auto',
level === id && 'opacity-100 ml-0 delay-150 h-auto',
// move menu back to margin-left
currentLevel === 'home' && 'ml-12',
level === 'home' && 'ml-12',
// disabled
currentLevel !== 'home' && currentLevel !== id ? '-ml-8' : '',
currentLevel !== id ? 'opacity-0 invisible absolute h-0 overflow-hidden' : '',
level !== 'home' && level !== id ? '-ml-8' : '',
level !== id ? 'opacity-0 invisible absolute h-0 overflow-hidden' : '',
].join(' ')}
>
<div className={'w-full flex flex-col gap-0 sticky top-8'}>
@@ -1,3 +1,5 @@
// @ts-expect-error
import spec from '~/../../spec/cli_v1_commands.yaml' assert { type: 'yml' }
import CodeBlock from '~/components/CodeBlock/CodeBlock'
import Options from '~/components/Options'
import Param from '~/components/Params'
@@ -46,7 +48,7 @@ export type Command = {
}
const CliCommandSection = (props) => {
const command = props.spec.commands.find((x: any) => x.id === props.funcData.id)
const command = spec.commands.find((x: any) => x.id === props.funcData.id)
return (
<RefSubLayout.Section
@@ -72,10 +74,10 @@ const CliCommandSection = (props) => {
</header>
{/* {command.usage && (
<CodeBlock language="bash" className="relative">
{command.usage}
</CodeBlock>
)} */}
<CodeBlock language="bash" className="relative">
{command.usage}
</CodeBlock>
)} */}
{command.subcommands.length > 0 && (
<div className="">
@@ -65,7 +65,7 @@ const RefSectionHandler = (props) => {
/>
)
case 'cli-command':
return <CliCommandSection funcData={x} commonFuncData={x} spec={props.spec} />
return <CliCommandSection funcData={x} commonFuncData={x} />
break
case 'operation':
return <ApiOperationSection funcData={x} commonFuncData={x} spec={props.spec} />
+1 -1
View File
@@ -90,7 +90,7 @@
},
{
"text": "SupaSquad",
"url": "/docs/handbook/supasquad"
"url": "/supasquad"
},
{
"text": "DevTo",
+1
View File
@@ -9,6 +9,7 @@ export const menuState = proxy({
},
menuLevelId: '',
setMenuLevelId: (value) => {
menuState.menuMobileOpen = false
menuState.menuLevelId = value
},
menuMobileOpen: false,
+2 -39
View File
@@ -1,11 +1,8 @@
import { MDXProvider } from '@mdx-js/react'
import Head from 'next/head'
import { FC, useEffect, useState } from 'react'
import { FC } from 'react'
import components from '~/components'
import Footer from '~/components/Footer'
import SideBar from '~/components/Navigation/SideBar'
import TableOfContents from '~/components/TableOfContents'
import NavBar from '~/components/Navigation/NavBar'
interface Props {
meta: { title: string; description?: string; hide_table_of_contents?: boolean; video?: string }
@@ -15,34 +12,6 @@ interface Props {
}
const Layout: FC<Props> = (props: Props) => {
// const contentString = renderToString(props.children)
const [active, setActive] = useState(false)
useEffect(() => {
const key = localStorage.getItem('supabaseDarkMode')
if (!key) {
// Default to dark mode if no preference config
document.documentElement.className = 'dark'
} else {
document.documentElement.className = key === 'true' ? 'dark' : ''
}
}, [])
useEffect(() => {
setTimeout(function () {
setActive(true)
}, 150)
}, [])
// const contentString = renderToString(props.children)
// const content = serialize(contentString || '')
// console.log('contentString', contentString)
// const _toc = toc('#hello world', { maxdepth: 1, firsth1: false })
const hasTableOfContents =
props.toc !== undefined &&
props.toc.json.filter((item) => item.lvl !== 1 && item.lvl <= 3).length > 0
@@ -60,13 +29,7 @@ const Layout: FC<Props> = (props: Props) => {
<meta property="og:title" content={props.meta?.title} />
</Head>
<div
className={[
'relative transition-all ease-out',
'duration-150 ',
active ? 'opacity-100 left-0' : 'opacity-0 left-5',
].join(' ')}
>
<div className={['relative transition-all ease-out', 'duration-150 '].join(' ')}>
{/* <p className="text-brand-900 tracking-wider">Tutorials</p> */}
<article className="prose dark:prose-dar max-w-none">
<h1>{props.meta.title}</h1>
+294 -43
View File
@@ -1,14 +1,17 @@
import { useEffect } from 'react'
import { useTheme } from 'common/Providers'
import Image from 'next/image'
import Link from 'next/link'
import { useRouter } from 'next/router'
import Footer from '~/components/Footer'
import NavigationMenu from '~/components/Navigation/NavigationMenu/NavigationMenu'
import TopNavBar from '~/components/Navigation/NavigationMenu/TopNavBar'
import SiteRefLayout from './SiteRefLayout'
import Image from 'next/image'
import TopNavBarRef from '~/components/Navigation/NavigationMenu/TopNavBarRef'
const Layout = (props) => {
const router = useRouter()
import FooterHelpCallout from '~/components/FooterHelpCallout'
import { menuState, useMenuLevelId, useMenuMobileOpen } from '~/hooks/useMenuState'
import { useEffect } from 'react'
const SiteRefLayout = ({ children }) => {
const { isDarkMode } = useTheme()
useEffect(() => {
const key = localStorage.getItem('supabaseDarkMode')
@@ -20,50 +23,298 @@ const Layout = (props) => {
}
}, [])
if (process.env.NEXT_PUBLIC_NEW_DOCS !== 'true') {
return <>{props.children}</>
}
const menuLevel = useMenuLevelId()
const mobileMenuOpen = useMenuMobileOpen()
return SiteRefLayout(props)
if (process.env.NEXT_PUBLIC_EXPERIMENTAL_REF && router.asPath.includes('/reference/')) {
return SiteRefLayout(props)
const levelsData = {
home: {
icon: '/docs/img/icons/menu/home',
name: 'Home',
},
gettingstarted: {
icon: '/docs/img/icons/menu/getting-started',
name: 'Getting Started',
},
database: {
icon: '/docs/img/icons/menu/database',
name: 'Database',
},
auth: {
icon: '/docs/img/icons/menu/auth',
name: 'Auth',
},
functions: {
icon: '/docs/img/icons/menu/functions',
name: 'Functions',
},
realtime: {
icon: '/docs/img/icons/menu/realtime',
name: 'Realtime',
},
storage: {
icon: '/docs/img/icons/menu/storage',
name: 'Storage',
},
platform: {
icon: '/docs/img/icons/menu/platform',
name: 'Platform',
},
resources: {
icon: '/docs/img/icons/menu/resources',
name: 'Resources',
},
integrations: {
icon: '/docs/img/icons/menu/integrations',
name: 'Integrations',
},
reference_javascript_v1: {
icon: '/docs/img/icons/menu/reference-javascript',
name: 'Javascript Reference v1.0',
},
reference_javascript_v2: {
icon: '/docs/img/icons/menu/reference-javascript',
name: 'Javascript Reference v2.0',
},
reference_dart_v0: {
icon: '/docs/img/icons/menu/reference-dart',
name: 'Dart Reference v0.0',
},
reference_dart_v1: {
icon: '/docs/img/icons/menu/reference-dart',
name: 'Dart Reference v0.0',
},
reference_cli: {
icon: '/docs/img/icons/menu/reference-cli',
name: 'CLI Reference',
},
reference_api: {
icon: '/docs/img/icons/menu/reference-api',
name: 'Management API Reference',
},
reference_self_hosting_auth: {
icon: '/docs/img/icons/menu/reference-auth',
name: 'Auth Server Reference',
},
reference_self_hosting_storage: {
icon: '/docs/img/icons/menu/reference-storage',
name: 'Storage Server Reference',
},
reference_self_hosting_realtime: {
icon: '/docs/img/icons/menu/reference-realtime',
name: 'Realtime Server Reference',
},
}
return (
<>
<main>
<Image
width={608}
height={975}
alt="background"
<main>
{/* <img
src={`${router.basePath}/img/gradient-bg.png`}
className="absolute left-0 w-[520px] top-0 -z-10"
/>
<div className="mx-auto sticky top-0 z-10">
<TopNavBar />
</div>
<div className="grid grid-cols-12 opacity-100 duration-100 max-w-[1400px] mx-auto py-16 gap-4 px-5">
<div className="col-span-3">
<NavigationMenu />
</div>
<div className="col-span-9">
{props.children}
<hr className="border-scale-400 mt-32"></hr>
<div className="flex flex-row gap-3 mt-6">
<span className="text-xs text-scale-900">Supabase 2022</span>
<span className="text-xs text-scale-900">—</span>
<a className="text-xs text-scale-800">Contributing</a>
<a className="text-xs text-scale-800">Author Styleguide</a>
<a className="text-xs text-scale-800">Changelog</a>
<a className="text-xs text-scale-800">Opensource</a>
<a className="text-xs text-scale-800">Supasquad</a>
className="absolute left-0 w-[520px] top-0 -z-1 pointer-events-none"
/> */}
<div className="flex flex-row h-screen">
<div
className={[
'hidden', // experiment hidden
'transition-all',
'top-0',
'relative',
'h-screen w-[420px]',
'z-10',
'lg:flex flex-col ml-0',
].join(' ')}
>
<div
className={[
'top-0',
'relative',
'w-auto',
'z-10',
'border-r overflow-auto h-screen',
'backdrop-blur backdrop-filter bg-white-1200 dark:bg-blackA-300',
'flex flex-col',
].join(' ')}
>
<div className="top-0 sticky z-10">
<div>
<div>
<div
className={[
'hidden md:flex md:height-auto',
'pt-8 bg-scale-200 flex-col gap-8',
].join(' ')}
>
<Link href="/">
<a className="px-10 flex items-center gap-2">
<Image
className="cursor-pointer"
src={isDarkMode ? '/docs/supabase-dark.svg' : '/docs/supabase-light.svg'}
width={96}
height={24}
alt="Supabase Logo"
/>
<span className="font-mono text-sm font-medium text-brand-900">DOCS</span>
</a>
</Link>
{/* {router.asPath.includes('/reference/') && <RefSwitcher />} */}
</div>
<div className="h-4 bg-scale-200 w-full"></div>
<div className="bg-gradient-to-b from-scale-200 to-transparent h-4 w-full"></div>
</div>
</div>
</div>
<div className={['pb-10 px-5 md:px-10 md:flex'].join(' ')}>
<NavigationMenu />
</div>
</div>
</div>
</main>
</>
<div
// #docs-content-container is used by layout to scroll to top
id="docs-content-container"
className={[
'w-full h-screen transition-all ease-out',
mobileMenuOpen ? 'overflow-hidden' : 'overflow-auto',
].join(' ')}
>
<div className="flex flex-col relative">
{/* <NavigationMenu /> */}
<div className={['lg:sticky top-0 z-10 overflow-hidden'].join(' ')}>
<TopNavBarRef />
</div>
<div
className={[
'sticky transition-all top-0',
'z-10',
'backdrop-blur backdrop-filter bg-white-1200 dark:bg-blackA-300',
].join(' ')}
>
<div className={['lg:hidden', 'px-5 ', 'border-b z-10'].join(' ')}>
<div
className={[
'transition-all ease-out z-10',
'top-0',
mobileMenuOpen && 'absolute',
'flex items-center h-[40px]',
mobileMenuOpen ? 'gap-0' : 'gap-3',
].join(' ')}
>
<button
className={['mr-2', mobileMenuOpen && 'mt-0.5'].join(' ')}
onClick={() => menuState.setMenuMobileOpen(!mobileMenuOpen)}
>
<div
className={[
'space-y-1 group cursor-pointer relative',
mobileMenuOpen ? 'w-4 h-4' : 'w-4 h-[8px]',
].join(' ')}
>
<span
className={[
'transition-all ease-out block w-4 h-px bg-scale-900 group-hover:bg-scale-1200',
!mobileMenuOpen ? 'w-4' : 'absolute rotate-45 top-[6px]',
].join(' ')}
></span>
<span
className={[
'transition-all ease-out block h-px bg-scale-900 group-hover:bg-scale-1200',
!mobileMenuOpen
? 'w-3 group-hover:w-4'
: 'absolute w-4 -rotate-45 top-[2px]',
].join(' ')}
></span>
</div>
</button>
<div className={[].join(' ')}>
<img
src={
menuLevel
? levelsData[menuLevel].icon + '.svg'
: levelsData['home'].icon + '.svg'
}
className={[
'transition-all duration-200',
mobileMenuOpen ? 'invisible w-0 h-0' : 'w-4 h-4',
].join(' ')}
/>
</div>
<span
className={[
'transition-all duration-200',
'text-scale-1200',
mobileMenuOpen ? 'text-xs' : 'text-sm',
].join(' ')}
>
{mobileMenuOpen
? 'Close'
: menuLevel
? levelsData[menuLevel].name
: levelsData['home'].name}
</span>
</div>
<div
className={[
'transition-all ease-out duration-200',
'absolute left-0 right-0 h-screen',
'px-5 pl-5 py-16',
'top-[0px]',
'bg-scale-200',
mobileMenuOpen
? 'overflow-y-auto opacity-100 left-[0px] visible'
: 'left-[-40px] h-0 opacity-0 invisible',
].join(' ')}
>
<NavigationMenu />
</div>
</div>
</div>
<div className="grow px-5 max-w-7xl mx-auto py-16">
{children}
<div className="mt-32">
<FooterHelpCallout />
</div>
<hr className="border-scale-400 mt-8"></hr>
<div className="flex flex-col lg:flex-row gap-3 mt-6">
<span className="text-xs text-scale-900">Supabase 2022</span>
<span className="text-xs text-scale-900">—</span>
<Link href="/handbook/contributing">
<a className="text-xs text-scale-800 hover:underline">Contributing</a>
</Link>
<Link href="https://supabase.com/changelog">
<a className="text-xs text-scale-800 hover:underline">Changelog</a>
</Link>
<Link href="https://github.com/supabase/supabase/blob/master/DEVELOPERS.md">
<a className="text-xs text-scale-800 hover:underline">Author Styleguide</a>
</Link>
<Link href="https://supabase.com/docs/oss">
<a className="text-xs text-scale-800 hover:underline">Open Source</a>
</Link>
<Link href="https://supabase.com/supasquad">
<a className="text-xs text-scale-800 hover:underline">SupaSquad</a>
</Link>
</div>
</div>
<div
className={[
// 'top-0 left-0',
'hidden', // experiment hidden
'h-full',
'left-0',
'right-0',
'z-10',
'backdrop-blur-sm backdrop-filter bg-white-1200 dark:bg-blackA-600',
mobileMenuOpen ? 'absolute MOBILE-MENU-OPEN' : 'hidden h-0 MOBILE-MENU-CLOSED',
].join(' ')}
onClick={() => menuState.setMenuMobileOpen(!mobileMenuOpen)}
></div>
</div>
</div>
</div>
</main>
)
}
export default Layout
export default SiteRefLayout
+203 -203
View File
@@ -1,27 +1,31 @@
import { useTheme } from 'common/Providers'
import Image from 'next/image'
import Link from 'next/link'
import { useRouter } from 'next/router'
import NavigationMenu from '~/components/Navigation/NavigationMenu/NavigationMenu'
import TopNavBarRef from '~/components/Navigation/NavigationMenu/TopNavBarRef'
import { useTheme } from 'common/Providers'
import RefSwitcher from '~/components/Navigation/RefSwitcher'
import { useEffect, useState, memo } from 'react'
import FooterHelpCallout from '~/components/FooterHelpCallout'
import { NavigationMenuContextProvider } from '~/components/Navigation/NavigationMenu/NavigationMenu.Context'
import { useMenuLevelId, useMenuMobileOpen } from '~/hooks/useMenuState'
import { menuState } from '~/hooks/useMenuState'
import { useEffect } from 'react'
const SiteRefLayout = ({ children }) => {
const router = useRouter()
const { isDarkMode } = useTheme()
const menuLevel = useMenuLevelId()
const mobileMenuOpen = useMenuMobileOpen()
useEffect(() => {
const key = localStorage.getItem('supabaseDarkMode')
if (!key) {
// Default to dark mode if no preference config
document.documentElement.className = 'dark'
} else {
document.documentElement.className = key === 'true' ? 'dark' : ''
}
}, [])
//console.log('menuLevel', menuLevel)
const menuLevel = 'home'
// useMenuLevelId()
const mobileMenuOpen = false
const levelsData = {
home: {
@@ -103,218 +107,214 @@ const SiteRefLayout = ({ children }) => {
}
return (
<NavigationMenuContextProvider activeRefItem={''} setActiveRefItem={() => {}}>
<main>
{/* <img
<main>
{/* <img
src={`${router.basePath}/img/gradient-bg.png`}
className="absolute left-0 w-[520px] top-0 -z-1 pointer-events-none"
/> */}
<div className="flex flex-row h-screen">
<div className="flex flex-row h-screen">
<div
className={[
'hidden', // experiment hidden
'transition-all',
'top-0',
'relative',
'h-screen w-[420px]',
'z-10',
'lg:flex flex-col ml-0',
].join(' ')}
>
<div
className={[
'hidden', // experiment hidden
'transition-all',
'top-0',
'relative',
'h-screen w-[420px]',
'w-auto',
'z-10',
'lg:flex flex-col ml-0',
'border-r overflow-auto h-screen',
'backdrop-blur backdrop-filter bg-white-1200 dark:bg-blackA-300',
'flex flex-col',
].join(' ')}
>
<div
className={[
'top-0',
'relative',
'w-auto',
'z-10',
'border-r overflow-auto h-screen',
'backdrop-blur backdrop-filter bg-white-1200 dark:bg-blackA-300',
'flex flex-col',
].join(' ')}
>
<div className="top-0 sticky z-10">
<div className="top-0 sticky z-10">
<div>
<div>
<div>
<div
className={[
'hidden md:flex md:height-auto',
'pt-8 bg-scale-200 flex-col gap-8',
].join(' ')}
>
<Link href="/">
<a className="px-10 flex items-center gap-2">
<Image
className="cursor-pointer"
src={
isDarkMode ? '/docs/supabase-dark.svg' : '/docs/supabase-light.svg'
}
width={96}
height={24}
alt="Supabase Logo"
/>
<span className="font-mono text-sm font-medium text-brand-900">DOCS</span>
</a>
</Link>
{/* {router.asPath.includes('/reference/') && <RefSwitcher />} */}
</div>
<div className="h-4 bg-scale-200 w-full"></div>
<div className="bg-gradient-to-b from-scale-200 to-transparent h-4 w-full"></div>
<div
className={[
'hidden md:flex md:height-auto',
'pt-8 bg-scale-200 flex-col gap-8',
].join(' ')}
>
<Link href="/">
<a className="px-10 flex items-center gap-2">
<Image
className="cursor-pointer"
src={isDarkMode ? '/docs/supabase-dark.svg' : '/docs/supabase-light.svg'}
width={96}
height={24}
alt="Supabase Logo"
/>
<span className="font-mono text-sm font-medium text-brand-900">DOCS</span>
</a>
</Link>
{/* {router.asPath.includes('/reference/') && <RefSwitcher />} */}
</div>
<div className="h-4 bg-scale-200 w-full"></div>
<div className="bg-gradient-to-b from-scale-200 to-transparent h-4 w-full"></div>
</div>
</div>
<div className={['pb-10 px-5 md:px-10 md:flex'].join(' ')}>
<NavigationMenu />
</div>
</div>
</div>
<div
// #docs-content-container is used by layout to scroll to top
id="docs-content-container"
className={[
'w-full h-screen transition-all ease-out',
mobileMenuOpen ? 'overflow-hidden' : 'overflow-auto',
].join(' ')}
>
<div className="flex flex-col relative">
{/* <NavigationMenu /> */}
<div className={['lg:sticky top-0 z-10 overflow-hidden'].join(' ')}>
<TopNavBarRef />
</div>
<div
className={[
'sticky transition-all top-0',
'z-10',
'backdrop-blur backdrop-filter bg-white-1200 dark:bg-blackA-300',
].join(' ')}
>
<div className={['lg:hidden', 'px-5 ', 'border-b z-10'].join(' ')}>
<div
className={[
'transition-all ease-out z-10',
'top-0',
mobileMenuOpen && 'absolute',
'flex items-center h-[40px]',
mobileMenuOpen ? 'gap-0' : 'gap-3',
].join(' ')}
>
<button
className={['mr-2', mobileMenuOpen && 'mt-0.5'].join(' ')}
onClick={() => menuState.setMenuMobileOpen(!mobileMenuOpen)}
>
<div
className={[
'space-y-1 group cursor-pointer relative',
mobileMenuOpen ? 'w-4 h-4' : 'w-4 h-[8px]',
].join(' ')}
>
<span
className={[
'transition-all ease-out block w-4 h-px bg-scale-900 group-hover:bg-scale-1200',
!mobileMenuOpen ? 'w-4' : 'absolute rotate-45 top-[6px]',
].join(' ')}
></span>
<span
className={[
'transition-all ease-out block h-px bg-scale-900 group-hover:bg-scale-1200',
!mobileMenuOpen
? 'w-3 group-hover:w-4'
: 'absolute w-4 -rotate-45 top-[2px]',
].join(' ')}
></span>
</div>
</button>
<div className={[].join(' ')}>
<img
src={
menuLevel
? levelsData[menuLevel].icon + '.svg'
: levelsData['home'].icon + '.svg'
}
className={[
'transition-all duration-200',
mobileMenuOpen ? 'invisible w-0 h-0' : 'w-4 h-4',
].join(' ')}
/>
</div>
<span
className={[
'transition-all duration-200',
'text-scale-1200',
mobileMenuOpen ? 'text-xs' : 'text-sm',
].join(' ')}
>
{mobileMenuOpen
? 'Close'
: menuLevel
? levelsData[menuLevel].name
: levelsData['home'].name}
</span>
</div>
<div
className={[
'transition-all ease-out duration-200',
'absolute left-0 right-0 h-screen',
'px-5 pl-5 py-16',
'top-[0px]',
'bg-scale-200',
mobileMenuOpen
? 'overflow-y-auto opacity-100 left-[0px] visible'
: 'left-[-40px] h-0 opacity-0 invisible',
].join(' ')}
>
<NavigationMenu />
</div>
</div>
</div>
<div className="grow px-5 max-w-7xl mx-auto py-16">
{children}
<div className="mt-32">
<FooterHelpCallout />
</div>
<hr className="border-scale-400 mt-8"></hr>
<div className="flex flex-col lg:flex-row gap-3 mt-6">
<span className="text-xs text-scale-900">Supabase 2022</span>
<span className="text-xs text-scale-900">—</span>
<Link href="/handbook/contributing">
<a className="text-xs text-scale-800 hover:underline">Contributing</a>
</Link>
<Link href="https://supabase.com/changelog">
<a className="text-xs text-scale-800 hover:underline">Changelog</a>
</Link>
<Link href="https://github.com/supabase/supabase/blob/master/DEVELOPERS.md">
<a className="text-xs text-scale-800 hover:underline">Author Styleguide</a>
</Link>
<Link href="https://supabase.com/docs/oss">
<a className="text-xs text-scale-800 hover:underline">Open Source</a>
</Link>
<Link href="https://supabase.com/docs/handbook/supasquad">
<a className="text-xs text-scale-800 hover:underline">Supasquad</a>
</Link>
</div>
</div>
<div
className={[
// 'top-0 left-0',
'hidden', // experiment hidden
'h-full',
'left-0',
'right-0',
'z-10',
'backdrop-blur-sm backdrop-filter bg-white-1200 dark:bg-blackA-600',
mobileMenuOpen ? 'absolute MOBILE-MENU-OPEN' : 'hidden h-0 MOBILE-MENU-CLOSED',
].join(' ')}
onClick={() => menuState.setMenuMobileOpen(!mobileMenuOpen)}
></div>
<div className={['pb-10 px-5 md:px-10 md:flex'].join(' ')}>
<NavigationMenu />
</div>
</div>
</div>
</main>
</NavigationMenuContextProvider>
<div
// #docs-content-container is used by layout to scroll to top
id="docs-content-container"
className={[
'w-full h-screen transition-all ease-out',
mobileMenuOpen ? 'overflow-hidden' : 'overflow-auto',
].join(' ')}
>
<div className="flex flex-col relative">
{/* <NavigationMenu /> */}
<div className={['lg:sticky top-0 z-10 overflow-hidden'].join(' ')}>
<TopNavBarRef />
</div>
<div
className={[
'sticky transition-all top-0',
'z-10',
'backdrop-blur backdrop-filter bg-white-1200 dark:bg-blackA-300',
].join(' ')}
>
<div className={['lg:hidden', 'px-5 ', 'border-b z-10'].join(' ')}>
<div
className={[
'transition-all ease-out z-10',
'top-0',
mobileMenuOpen && 'absolute',
'flex items-center h-[40px]',
mobileMenuOpen ? 'gap-0' : 'gap-3',
].join(' ')}
>
<button
className={['mr-2', mobileMenuOpen && 'mt-0.5'].join(' ')}
onClick={() => menuState.setMenuMobileOpen(!mobileMenuOpen)}
>
<div
className={[
'space-y-1 group cursor-pointer relative',
mobileMenuOpen ? 'w-4 h-4' : 'w-4 h-[8px]',
].join(' ')}
>
<span
className={[
'transition-all ease-out block w-4 h-px bg-scale-900 group-hover:bg-scale-1200',
!mobileMenuOpen ? 'w-4' : 'absolute rotate-45 top-[6px]',
].join(' ')}
></span>
<span
className={[
'transition-all ease-out block h-px bg-scale-900 group-hover:bg-scale-1200',
!mobileMenuOpen
? 'w-3 group-hover:w-4'
: 'absolute w-4 -rotate-45 top-[2px]',
].join(' ')}
></span>
</div>
</button>
<div className={[].join(' ')}>
<img
src={
menuLevel
? levelsData[menuLevel].icon + '.svg'
: levelsData['home'].icon + '.svg'
}
className={[
'transition-all duration-200',
mobileMenuOpen ? 'invisible w-0 h-0' : 'w-4 h-4',
].join(' ')}
/>
</div>
<span
className={[
'transition-all duration-200',
'text-scale-1200',
mobileMenuOpen ? 'text-xs' : 'text-sm',
].join(' ')}
>
{mobileMenuOpen
? 'Close'
: menuLevel
? levelsData[menuLevel].name
: levelsData['home'].name}
</span>
</div>
<div
className={[
'transition-all ease-out duration-200',
'absolute left-0 right-0 h-screen',
'px-5 pl-5 py-16',
'top-[0px]',
'bg-scale-200',
mobileMenuOpen
? 'overflow-y-auto opacity-100 left-[0px] visible'
: 'left-[-40px] h-0 opacity-0 invisible',
].join(' ')}
>
<NavigationMenu />
</div>
</div>
</div>
<div className="grow px-5 max-w-7xl mx-auto py-16">
{children}
<div className="mt-32">
<FooterHelpCallout />
</div>
<hr className="border-scale-400 mt-8"></hr>
<div className="flex flex-col lg:flex-row gap-3 mt-6">
<span className="text-xs text-scale-900">Supabase 2022</span>
<span className="text-xs text-scale-900">—</span>
<Link href="/handbook/contributing">
<a className="text-xs text-scale-800 hover:underline">Contributing</a>
</Link>
<Link href="https://supabase.com/changelog">
<a className="text-xs text-scale-800 hover:underline">Changelog</a>
</Link>
<Link href="https://github.com/supabase/supabase/blob/master/DEVELOPERS.md">
<a className="text-xs text-scale-800 hover:underline">Author Styleguide</a>
</Link>
<Link href="https://supabase.com/docs/oss">
<a className="text-xs text-scale-800 hover:underline">Open Source</a>
</Link>
<Link href="https://supabase.com/supasquad">
<a className="text-xs text-scale-800 hover:underline">SupaSquad</a>
</Link>
</div>
</div>
<div
className={[
// 'top-0 left-0',
'hidden', // experiment hidden
'h-full',
'left-0',
'right-0',
'z-10',
'backdrop-blur-sm backdrop-filter bg-white-1200 dark:bg-blackA-600',
mobileMenuOpen ? 'absolute MOBILE-MENU-OPEN' : 'hidden h-0 MOBILE-MENU-CLOSED',
].join(' ')}
onClick={() => menuState.setMenuMobileOpen(!mobileMenuOpen)}
></div>
</div>
</div>
</div>
</main>
)
}
-114
View File
@@ -1,114 +0,0 @@
import { MDXProvider } from '@mdx-js/react'
import { NextSeo } from 'next-seo'
import { useRouter } from 'next/router'
import { FC, useEffect, useRef, useState } from 'react'
import components from '~/components'
import Footer from '~/components/Footer'
import GuidesTableOfContents from '~/components/GuidesTableOfContents'
import NavBar from '~/components/Navigation/NavBar'
import { menuItems } from '~/components/Navigation/Navigation.constants'
import SideBar from '~/components/Navigation/SideBar'
import { getPageType } from '~/lib/helpers'
interface Props {
meta: { title: string; description?: string; hide_table_of_contents?: boolean }
children: any
toc?: any
currentPage?: string
}
const Layout: FC<Props> = ({ meta, children }) => {
const { asPath } = useRouter()
const page = getPageType(asPath)
useEffect(() => {
const key = localStorage.getItem('supabaseDarkMode')
if (!key) {
// Default to dark mode if no preference config
document.documentElement.className = 'dark'
} else {
document.documentElement.className = key === 'true' ? 'dark' : ''
}
}, [])
const articleRef = useRef()
const [tocList, setTocList] = useState([])
useEffect(() => {
const articleEl = articleRef.current as HTMLElement
if (!articleRef.current) return
const headings = Array.from(articleEl.querySelectorAll('h2, h3'))
const newHeadings = headings
.filter((heading) => heading.id)
.map((heading) => {
const text = heading.textContent.replace('#', '')
const link = heading.querySelector('a').getAttribute('href')
const level = heading.tagName === 'H2' ? 2 : 3
return { text, link, level }
})
setTocList(newHeadings)
}, [])
const hasTableOfContents = tocList.length > 0
return (
<>
<NextSeo
title={`${meta?.title} | Supabase`}
description={meta?.description ? meta?.description : meta?.title}
openGraph={{
title: meta?.title,
description: meta?.description,
url: `https://supabase.com/docs${asPath}`,
images: [
{
url: `https://supabase.com/docs/img/supabase-og-image.png`,
},
],
}}
/>
<main>
<NavBar
// currentPage={page}
/>
<div className="flex w-full flex-row">
<SideBar menuItems={menuItems[page]} />
<div className="main-content-pane docs-width grid md:grid-cols-12 gap-4 justify-between p-4 pb-8 w-full">
<div
className={`${
meta?.hide_table_of_contents || !hasTableOfContents
? 'col-span-12 xl:col-start-2 xl:col-span-10 2xl:col-start-3 2xl:col-span-8'
: 'col-span-12 lg:col-span-9'
} py-2 lg:py-4 px-2 lg:px-8 mx-auto`}
>
<article
ref={articleRef}
className={`${
meta?.hide_table_of_contents || !hasTableOfContents ? 'xl:min-w-[880px]' : ''
} doc-content-container prose dark:prose-dark dark:bg-scale-200 width-full mt-8 2xl:max-w-[880px]`}
>
{meta?.title && <h1>{meta.title}</h1>}
<MDXProvider components={components}>{children}</MDXProvider>
</article>
</div>
{hasTableOfContents && !meta?.hide_table_of_contents && (
<div
className={[
'border-scale-400 dark:bg-scale-200 table-of-contents-height border-l',
'thin-scrollbar overflow-y-auto sticky hidden xl:block md:col-span-3 px-2',
].join(' ')}
>
<GuidesTableOfContents list={tocList} />
</div>
)}
</div>
</div>
<Footer />
</main>
</>
)
}
export default Layout
-143
View File
@@ -1,143 +0,0 @@
import { MDXProvider } from '@mdx-js/react'
import { NextSeo } from 'next-seo'
import { useRouter } from 'next/router'
import { FC, useEffect, useRef, useState } from 'react'
import components from '~/components'
import { highlightSelectedTocItem } from '~/components/CustomHTMLElements/CustomHTMLElements.utils'
import GuidesTableOfContents from '~/components/GuidesTableOfContents'
import useHash from '~/hooks/useHash'
import { getPageType } from '~/lib/helpers'
interface Props {
meta: {
title: string
description?: string
hide_table_of_contents?: boolean
breadcrumb?: string
subtitle?: string
}
children: any
toc?: any
currentPage?: string
hideToc?: boolean
}
const Layout: FC<Props> = (props) => {
const [hash] = useHash()
const [active, setActive] = useState(false)
const articleRef = useRef()
const [tocList, setTocList] = useState([])
const { asPath } = useRouter()
const page = getPageType(asPath)
useEffect(() => {
const key = localStorage.getItem('supabaseDarkMode')
if (!key) {
// Default to dark mode if no preference config
document.documentElement.className = 'dark'
} else {
document.documentElement.className = key === 'true' ? 'dark' : ''
}
}, [])
useEffect(() => {
if (hash && tocList.length > 0) {
highlightSelectedTocItem(hash as string)
}
}, [hash, JSON.stringify(tocList)])
useEffect(() => {
const articleEl = articleRef.current as HTMLElement
if (!articleRef.current) return
const headings = Array.from(articleEl.querySelectorAll('h2, h3'))
const newHeadings = headings
.filter((heading) => heading.id)
.map((heading) => {
const text = heading.textContent.replace('#', '')
const link = heading.querySelector('a').getAttribute('href')
const level = heading.tagName === 'H2' ? 2 : 3
return { text, link, level }
})
setTocList(newHeadings)
}, [])
useEffect(() => {
setTimeout(function () {
setActive(true)
}, 150)
}, [])
const hasTableOfContents = tocList.length > 0
return (
<>
<NextSeo
title={`${props.meta?.title} | Supabase`}
description={props.meta?.description ? props.meta?.description : props.meta?.title}
openGraph={{
title: props.meta?.title,
description: props.meta?.description,
url: `https://supabase.com/docs${asPath}`,
images: [
{
url: `https://supabase.com/docs/img/supabase-og-image.png`,
},
],
}}
/>
<div className={['grid grid-cols-12 relative'].join(' ')}>
<div
className={[
'relative',
!props.hideToc ? 'col-span-12 md:col-span-9' : 'col-span-12',
'transition-all ease-out',
'duration-100',
active ? 'opacity-100 left-0' : 'opacity-0 left-6',
].join(' ')}
>
{props.meta.breadcrumb && (
<p className="text-brand-900 tracking-wider mb-3">{props.meta.breadcrumb}</p>
)}
<article
ref={articleRef}
className={`${
props.meta?.hide_table_of_contents || !hasTableOfContents ? '' : ''
} prose dark:prose-dark max-w-none`}
>
<h1 className="mb-0">{props.meta.title}</h1>
{props.meta.subtitle && (
<h2 className="mt-3 text-xl text-scale-1100">{props.meta.subtitle}</h2>
)}
<div className="max-w-xs w-32 h-[1px] bg-gradient-to-r from-brand-800 to-brand-900 my-8"></div>
<MDXProvider components={components}>{props.children}</MDXProvider>
</article>
</div>
{!props.hideToc && hasTableOfContents && !props.meta?.hide_table_of_contents && (
<div
className={[
'col-span-3',
'border-scale-400 dark:bg-scale-200 table-of-contents-height',
'thin-scrollbar overflow-y-auto sticky hidden md:block md:col-span-3 px-2',
'transition-all ease-out',
'duration-100',
active ? 'opacity-100 left-0' : 'opacity-0 left-6',
].join(' ')}
>
<div className="border-l">
<span className="block font-mono text-xs uppercase text-scale-1200 px-5 mb-6">
On this page
</span>
<GuidesTableOfContents list={tocList} />
</div>
</div>
)}
</div>
</>
)
}
export default Layout
+113 -12
View File
@@ -1,23 +1,124 @@
import NewLayout from './current'
import OldLayout from './archived'
import { FC } from 'react'
import { MDXProvider } from '@mdx-js/react'
import { NextSeo } from 'next-seo'
import { useRouter } from 'next/router'
import { FC, useEffect, useRef, useState } from 'react'
import components from '~/components'
import { highlightSelectedTocItem } from '~/components/CustomHTMLElements/CustomHTMLElements.utils'
import GuidesTableOfContents from '~/components/GuidesTableOfContents'
import useHash from '~/hooks/useHash'
import { getPageType } from '~/lib/helpers'
interface Props {
meta: { title: string; description?: string; hide_table_of_contents?: boolean; video?: string }
meta: {
title: string
description?: string
hide_table_of_contents?: boolean
breadcrumb?: string
subtitle?: string
}
children: any
toc?: any
currentPage?: string
hideToc?: boolean
}
const Layout: FC<Props> = (props) => {
switch (process.env.NEXT_PUBLIC_NEW_DOCS) {
case 'true':
return NewLayout(props)
break
default:
return OldLayout(props)
break
}
const [hash] = useHash()
const articleRef = useRef()
const [tocList, setTocList] = useState([])
const { asPath } = useRouter()
const page = getPageType(asPath)
useEffect(() => {
if (hash && tocList.length > 0) {
highlightSelectedTocItem(hash as string)
}
}, [hash, JSON.stringify(tocList)])
useEffect(() => {
const articleEl = articleRef.current as HTMLElement
if (!articleRef.current) return
const headings = Array.from(articleEl.querySelectorAll('h2, h3'))
const newHeadings = headings
.filter((heading) => heading.id)
.map((heading) => {
const text = heading.textContent.replace('#', '')
const link = heading.querySelector('a').getAttribute('href')
const level = heading.tagName === 'H2' ? 2 : 3
return { text, link, level }
})
setTocList(newHeadings)
}, [])
const hasTableOfContents = tocList.length > 0
return (
<>
<NextSeo
title={`${props.meta?.title} | Supabase`}
description={props.meta?.description ? props.meta?.description : props.meta?.title}
openGraph={{
title: props.meta?.title,
description: props.meta?.description,
url: `https://supabase.com/docs${asPath}`,
images: [
{
url: `https://supabase.com/docs/img/supabase-og-image.png`,
},
],
}}
/>
<div className={['grid grid-cols-12 relative'].join(' ')}>
<div
className={[
'relative',
!props.hideToc ? 'col-span-12 md:col-span-9' : 'col-span-12',
'transition-all ease-out',
'duration-100',
].join(' ')}
>
{props.meta.breadcrumb && (
<p className="text-brand-900 tracking-wider mb-3">{props.meta.breadcrumb}</p>
)}
<article
ref={articleRef}
className={`${
props.meta?.hide_table_of_contents || !hasTableOfContents ? '' : ''
} prose dark:prose-dark max-w-none`}
>
<h1 className="mb-0">{props.meta.title}</h1>
{props.meta.subtitle && (
<h2 className="mt-3 text-xl text-scale-1100">{props.meta.subtitle}</h2>
)}
<div className="max-w-xs w-32 h-[1px] bg-gradient-to-r from-brand-800 to-brand-900 my-8"></div>
<MDXProvider components={components}>{props.children}</MDXProvider>
</article>
</div>
{!props.hideToc && hasTableOfContents && !props.meta?.hide_table_of_contents && (
<div
className={[
'col-span-3',
'border-scale-400 dark:bg-scale-200 table-of-contents-height',
'thin-scrollbar overflow-y-auto sticky hidden md:block md:col-span-3 px-2',
'transition-all ease-out',
'duration-100',
].join(' ')}
>
<div className="border-l">
<span className="block font-mono text-xs uppercase text-scale-1200 px-5 mb-6">
On this page
</span>
<GuidesTableOfContents list={tocList} />
</div>
</div>
)}
</div>
</>
)
}
export default Layout
+2 -2
View File
@@ -3,9 +3,9 @@ import fs from 'fs'
import matter from 'gray-matter'
import { serialize } from 'next-mdx-remote/serialize'
import { remarkCodeHike } from '@code-hike/mdx'
// import { remarkCodeHike } from '@code-hike/mdx'
// import codeHikeTheme from '~/codeHikeTheme.js'
import theme from 'shiki/themes/solarized-dark.json'
// import theme from 'shiki/themes/solarized-dark.json'
async function generateRefMarkdown(sections, slug) {
let markdownContent = []
+3
View File
@@ -50,6 +50,9 @@ const nextConfig = {
dangerouslyAllowSVG: true,
domains: ['avatars.githubusercontent.com', 'github.com', 'user-images.githubusercontent.com'],
},
experimental: {
mdxRs: true,
},
async headers() {
return [
{
+1 -12
View File
@@ -35,21 +35,15 @@
"dependencies": {
"@algolia/autocomplete-js": "^1.7.2",
"@algolia/autocomplete-plugin-recent-searches": "^1.7.2",
"@apollo/client": "^3.6.6",
"@code-hike/mdx": "^0.7.4",
"@docsearch/react": "^3.3.0",
"@mdx-js/loader": "^1.6.22",
"@mdx-js/react": "^1.6.22",
"@next/mdx": "^12.0.4",
"@radix-ui/react-accordion": "^1.0.1",
"@radix-ui/react-collapsible": "^1.0.1",
"@tailwindcss/typography": "^0.4.1",
"algoliasearch": "^4.14.2",
"babel": "^6.23.0",
"common": "*",
"config": "*",
"dotenv": "^16.0.3",
"graphql": "^16.5.0",
"gray-matter": "^4.0.3",
"isbot": "^3.6.5",
"jsrsasign": "^10.5.26",
@@ -62,9 +56,7 @@
"next-mdx-toc": "^0.1.3",
"next-plugin-yaml": "^1.0.1",
"next-seo": "^5.14.1",
"next-themes": "0.0.15",
"next-transpile-modules": "^9.0.0",
"octokit": "^2.0.10",
"react": "17.0.2",
"react-copy-to-clipboard": "^5.0.2",
"react-dom": "17.0.2",
@@ -73,17 +65,14 @@
"react-syntax-highlighter": "^15.3.1",
"remark": "^14.0.2",
"remark-admonitions": "^1.2.1",
"remark-autolink-headings": "^7.0.1",
"remark-gfm": "^3.0.1",
"remark-html": "^15.0.0",
"remark-mdx": "^2.0.0-rc.2",
"remark-prism": "^1.3.6",
"remark-slug": "^7.0.1",
"shiki": "^0.11.1",
"ui": "*",
"valtio": "^1.7.6"
},
"devDependencies": {
"dotenv": "^16.0.3",
"@types/node": "^17.0.12",
"@types/react": "17.0.39",
"config": "*",
-3
View File
@@ -36,7 +36,6 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
* handle "scroll to top" behaviour on route change
*/
if (document) {
console.log('url', url)
// do not scroll to top for reference docs
if (!url.includes('reference/')) {
// scroll container div to top
@@ -45,10 +44,8 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
if (container) container.scrollTop = 0
}
}
menuState.setMenuMobileOpen(false)
}
function handlePagrScroll() {}
// Listen for page changes after a navigation or when the query changes
router.events.on('routeChangeComplete', handleRouteChange)
return () => {
+12 -6
View File
@@ -109,16 +109,20 @@ Unofficial libraries [supported by the community](https://github.com/supabase-co
<br />
## File Storage
## Storage
### Large File storage
### File storage
Supabase Storage makes it simple to store and serve large files. [Docs](/docs/guides/storage).
Supabase Storage makes it simple to store and serve files. [Docs](/docs/guides/storage).
### Storage CDN
Cache large files using the Supabase CDN. [Docs](/docs/guides/storage-cdn).
### Image Transformations
Transform images on the fly. [Docs](/docs/guides/storage/image-transformations).
<br />
## Edge Functions
@@ -159,9 +163,11 @@ Both Postgres and the Supabase Platform are production-ready. Some tools we offe
| Realtime | Postgres Changes | `GA` |
| Realtime | Broadcast | `beta` |
| Realtime | Presence | `beta` |
| Storage | Backend (S3) | `GA` |
| Storage | API | `beta` |
| Storage | CDN | `beta` |
| Storage | | `GA` |
| Storage | S3 Backend | `GA` |
| Storage | CDN | `GA` |
| Storage | Smart CDN | `beta` |
| Storage | Image Transformations | `beta` |
| Edge Functions | | `beta` |
| Auth | OAuth Providers | `beta` |
| Auth | Passwordless | `beta` |
@@ -5,7 +5,8 @@ import { PhoneLoginsItems } from '~/components/Navigation/NavigationMenu/Navigat
export const meta = {
title: 'Passwordless Login',
description: 'Learn about logging in with email one-time passwords (magic links) and SMS one-time passwords.',
description:
'Learn about logging in with email one-time passwords (magic links) and SMS one-time passwords.',
}
Passwordless login is a method of authentication that allows users to log in to a website or application without using a password. Instead of entering a password, the user may be asked to provide some other form of authentication, such as a one-time code sent via email or SMS, or by using a biometric factor like their fingerprint or facial recognition.
@@ -32,7 +33,7 @@ a link which they can click to log in to their account) and one-time passwords v
</div>
<div className="grid grid-cols-3 gap-10 not-prose py-8">
<Link href={`/auth/auth-magic-link`} passHref>
<Link href={`/guides/auth/auth-magic-link`} passHref>
<a>
<IconPanel title={'Magic Link'} span="col-span-6" icon={
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5 text-gold-1000">
@@ -51,7 +52,7 @@ a link which they can click to log in to their account) and one-time passwords v
### Phone SMS One-Time Password (OTP)
<div className="grid grid-cols-12 gap-10 not-prose py-8">
{PhoneLoginsItems.map((item) => (
{PhoneLoginsItems.map((item) => (
<Link href={`/${item.url}`} key={item.name} passHref>
<a className="col-span-4">
<IconPanel
@@ -147,7 +147,7 @@ channel.on('presence', { event: 'join' }, ({ newPresences }) => {
})
channel.on('presence', { event: 'leave' }, ({ leftPresences }) => {
console.log('Users have left: ', newPresences)
console.log('Users have left: ', leftPresences)
})
channel.subscribe(async (status) => {
+48 -5
View File
@@ -9,20 +9,63 @@ export const meta = {
All assets uploaded to Supabase Storage are cached on a Content Delivery Network (CDN) to improve the latency for users all around the world. CDNs are a geographically distributed set of servers or **nodes** which caches content from an **origin server**. For Supabase Storage, the origin is the storage server running in the [same region as your project](https://app.supabase.com/project/_/settings/general). Aside from performance, CDNs also help with security and availability by mitigating Distributed Denial of Service and other application attacks.
## Example
## Basic CDN
Let’s walk through an example of how a CDN helps with performance. A new bucket is created for a Supabase project launched in Singapore. All requests to the Supabase Storage API first hit the CDN. A user from the United States requests an object and is routed to the U.S. CDN. At this point, that CDN node does not have the object in its cache and pings the origin server in Singapore. Another user, also in the United States, requests the same object and is served directly from the CDN cache in the United States instead of routing the request back to Singapore.
Our basic CDN caches objects based on the cache time set when uploading objects.
## Cache duration
### Example
By default, assets are cached both in the CDN and in the user's browser for 1 hour. After this, the CDN nodes ping the storage server to see if an object has been updated. You can modify this cache time when you are [uploading](/docs/reference/javascript/storage-from-upload) or [updating](/docs/reference/javascript/storage-from-update) an object by modifying the `cacheControl` parameter. If you expect the object to not change at a given URL, setting a longer cache duration is preferable.
Let’s walk through an example of how a CDN helps with performance.
If you need to update the version of the object stored in the CDN, there are various cache-busting techniques you can use. The most common way to do this is to add a version query parameter in the URL. For example, you can use a URL like `/storage/v1/object/sign/profile-pictures/cat.jpg?token=eyJh...&version=1` in your applications and set a long cache time of 1 year. When you want to update the cat picture, you can increment the version query parameter in the URL. The CDN will treat `/storage/v1/object/sign/profile-pictures/cat.jpg?token=eyJh...&version=2` as a new object and pings the origin for the updated version.
A new bucket is created for a Supabase project launched in Singapore. All requests to the Supabase Storage API are routed to the CDN first.
A user from the United States requests an object and is routed to the U.S. CDN. At this point, that CDN node does not have the object in its cache and pings the origin server in Singapore.
![CDN cache miss](/docs/img/cdn-cache-miss.png)
Another user, also in the United States, requests the same object and is served directly from the CDN cache in the United States instead of routing the request back to Singapore.
![CDN cache hit](/docs/img/cdn-cache-hit.png)
### Cache duration
By default, assets are cached both in the CDN and in the user's browser for 1 hour. After this, the CDN nodes ping the storage server to see if an object has been updated. You can modify this cache time when you are [uploading](/docs/reference/javascript/storage-from-upload) or [updating](/docs/reference/javascript/storage-from-update) an object by modifying the `cacheControl` parameter.
If you expect the object to not change at a given URL, setting a longer cache duration is preferable.
If you need to update the version of the object stored in the CDN, there are various cache-busting techniques you can use. The most common way to do this is to add a version query parameter in the URL.
For example, you can use a URL like `/storage/v1/object/sign/profile-pictures/cat.jpg?token=eyJh...&version=1` in your applications and set a long cache time of 1 year.
When you want to update the cat picture, you can increment the version query parameter in the URL. The CDN treats `/storage/v1/object/sign/profile-pictures/cat.jpg?token=eyJh...&version=2` as a new object and pings the origin for the updated version.
If your asset is updated frequently, we recommend that you re-upload the new asset in a different key, this way you'll always have the latest changes available immediately.
Note that CDNs might still evict your object from their cache if it has not been requested for a while from a specific region. For example, if no user from United States requests your object, it will be removed from the CDN cache even if you set a very long cache control duration.
The cache status of a particular request is sent in the `cf-cache-status` header. A cache status of `MISS` indicates that the CDN node did not have the object in its cache and had to ping the origin to get it. A cache status of `HIT` indicates that the object was sent directly from the CDN.
## Smart CDN Caching
<Admonition type="note">
Smart CDN caching is automatically enabled for [Pro tiers and
above](https://supabase.com/pricing).
</Admonition>
With Smart CDN caching enabled, the asset metadata in your database is synchronized to the edge. This automatically revalidates the cache when the asset is changed or deleted.
Additionally, the smart CDN has a higher cache hit ratio as the origin server is shielded from asset requests that haven't changed when using different query strings in the URL.
### Cache duration
When Smart CDN is enabled, the asset is cached on the CDN for as long as possible. You can still control how long assets are stored in the browser using the [cacheControl](/docs/reference/javascript/storage-from-upload) option when uploading a file. Smart CDN caching works with all types of storage operations including signed URLs.
When a file is updated or deleted, the CDN cache is automatically invalidated to reflect the change (including transformed images).
It can take **up to 60 seconds** for the CDN cache to be invalidated as the asset metadata has to propagate across all the data-centers around the globe.
When an asset is invalidated at the CDN level, browsers may not update its cache.
If your asset is updated frequently, we recommend that you re-upload the new asset in a different key, this way you'll always have the latest changes available without waiting the propagation delay.
If you expect your asset to be deleted, we recommend setting a low browser TTL value using the `cacheControl` option when using smart CDN caching, the default is 1 hour which generally is a good default.
## Public vs Private Buckets
Objects in public buckets do not require any Authorization to access objects. This leads to a better cache hit rate compared to private buckets. For private buckets, permissions for accessing each object is checked on a per user level. For example, if two different users access the same object in a private bucket from the same region, it results in a cache miss for both the users since they might have different security policies attached to them. On the other hand, if two different users access the same object in a public bucket from the same region, it results in a cache hit for the second user.
@@ -0,0 +1,154 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'storage-image-transformations',
title: 'Storage Image Transformations',
description: 'Transform images with Storage',
sidebar_label: 'Image Transformations',
}
Supabase Storage offers the functionality to transform and resize images dynamically. Any image stored in your buckets can be transformed and optimized for fast delivery.
<Admonition type="note">
Image Resizing is currently in beta and enabled for [Pro tier and
above](https://supabase.com/pricing).
</Admonition>
## Get a public URL for a transformed image
You can pass a `transform` option to the functions you are currently using to interact with your objects. This returns the public URL that serves the resized image.
```ts
supabase.storage.from('bucket').getPublicUrl('image.jpg', {
transform: {
width: 500,
height: 600,
},
})
```
An example URL could look like this:
`https://project_id.supabase.co/storage/v1/render/image/public/bucket/image.jpg?width=500&height=600`
## Signing URLs with transformation options
To share a transformed image for a fixed amount of time, provide the transform options when you create the signed URL:
```ts
supabase.storage.from('bucket').createSignedUrl('image.jpg', 60000, {
transform: {
width: 200,
height: 200,
},
})
```
The transformation options are embedded into the token—they cannot be changed once signed.
## Downloading images
To download a transformed image, pass the `transform` option to the `download` function.
```ts
supabase.storage.from('bucket').download('image.jpg', {
transform: {
width: 800,
height: 300,
},
})
```
## NextJS Loader
You can use Supabase Image Transformation to optimise your NextJS images using a custom [Loader](https://nextjs.org/docs/api-reference/next/image#loader-configuration).
To get started, create a `supabase-image-loader.js` file in your NextJS project which exports a default function:
```ts
const projectId = '' // your supabase project id
export default function supabaseLoader({ src, width, height, quality }) {
return `https://${projectId}.supabase.co/storage/v1/${src}?width=${width}&height=${
height || 0
}&quality=${quality || 75}`
}
```
In your `nextjs.config.js` file add the following configuration to instruct NextJS to use our custom loader
```js
module.exports = {
images: {
loader: 'custom',
loaderFile: './supabase-image-loader.js',
},
}
```
At this point you are ready to use the `Image` component provided by NextJS
```tsx
import Image from 'next/image'
const MyImage = (props) => {
return <Image src="bucket/image.png" alt="Picture of the author" width={500} height={500} />
}
```
## Transformation options
We currently support a few transformation options focusing on resizing and cropping images.
### Resizing
You can use `width` and `height` parameters to resize an image to a specific dimension. If only one parameter is specified, the image will be resized and cropped, maintaining the aspect ratio.
### Modes
You can use different resizing modes to fit your needs, each of them uses a different approach to resize the image:
Use the `resize` parameter with one of the following values:
- `cover` : resizes the image while keeping the aspect ratio to fill a given size and crops projecting parts. (default)
- `contain` : resizes the image while keeping the aspect ratio to fit a given size.
- `fill` : resizes the image without keeping the aspect ratio.
Example:
```ts
supabase.storage.from('bucket').download('image.jpg', {
transform: {
width: 800,
height: 300,
resize: 'contain', // 'cover' | 'fill'
},
})
```
### Limits
- Width and height must be an integer value between 1-2500.
- The image size cannot exceed 25MB.
- The image resolution cannot exceed 50MP.
### Supported Image Formats
| Format | Extension | Source | Result |
| ------ | --------- | ------ | ------ |
| PNG | `png` | ☑️ | ☑️ |
| JPEG | `jpg` | ☑️ | ☑️ |
| WebP | `webp` | ☑️ | ☑️ |
| AVIF | `avif` | ☑️ | ☑️ |
| GIF | `gif` | ☑️ | ☑️ |
| ICO | `ico` | ☑️ | ☑️ |
| SVG | `svg` | ☑️ | ☑️ |
| HEIC | `heic` | ☑️ | ❌ |
| BMP | `bmp` | ☑️ | ☑️ |
| TIFF | `tiff` | ☑️ | ☑️ |
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
+4 -2
View File
@@ -11,8 +11,10 @@ export const meta = {
<h2 className="text-base max-w-xl m-0">
Supabase is an open source Firebase alternative providing all the backend features you need to
build a product. Choose a quickstart for an overview or dive straight into the different products
and APIs.
build a product.
[Learn more](/docs/architecture) about Supabase, start with the
[tutorials](/docs/guides/getting-started#tutorials), or dive straight into the different products and APIs.
</h2>
{/* start container */}
Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 713 KiB

+391 -359
View File
@@ -73,7 +73,7 @@
</url>
<url>
<loc>https://supabase.com/docs/handbook/supasquad</loc>
<loc>https://supabase.com/supasquad</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
@@ -126,6 +126,12 @@
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/platform</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/realtime</loc>
<changefreq>weekly</changefreq>
@@ -234,6 +240,12 @@
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/reference/index</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/new/auth</loc>
<changefreq>weekly</changefreq>
@@ -252,36 +264,6 @@
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/reference/index</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/company/aup</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/company/privacy</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/company/sla</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/company/terms</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/learn/auth-deep-dive/auth-deep-dive-jwts</loc>
<changefreq>weekly</changefreq>
@@ -313,13 +295,7 @@
</url>
<url>
<loc>https://supabase.com/docs/guides/cli/local-development</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/cli/managing-environments</loc>
<loc>https://supabase.com/docs/guides/api/generating-types</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
@@ -510,6 +486,66 @@
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/cli/local-development</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/cli/managing-environments</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/functions/auth</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/functions/best-practices</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/functions/cicd-workflow</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/functions/quickstart</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/architecture</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/features</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/hosting/docker</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/hosting/overview</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/database/arrays</loc>
<changefreq>weekly</changefreq>
@@ -600,138 +636,6 @@
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/functions/auth</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/functions/best-practices</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/functions/cicd-workflow</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/functions/quickstart</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/architecture</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/features</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/hosting/docker</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/hosting/overview</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/platform</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/migrations/firebase-auth</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/migrations/firebase-storage</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/migrations/firestore-data</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/migrations/heroku</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/platform/compute-add-ons</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/platform/custom-domains</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/platform/database-usage</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/platform/going-into-prod</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/platform/logs</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/platform/metrics</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/platform/migrating-and-upgrading-projects</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/platform/performance</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/platform/permissions</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/integrations/appsmith</loc>
<changefreq>weekly</changefreq>
@@ -871,7 +775,19 @@
</url>
<url>
<loc>https://supabase.com/docs/guides/api/generating-types</loc>
<loc>https://supabase.com/docs/guides/realtime/postgres-changes</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/realtime/quickstart</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/realtime/rate-limits</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
@@ -901,7 +817,79 @@
</url>
<url>
<loc>https://supabase.com/docs/new/auth/auth-login</loc>
<loc>https://supabase.com/docs/guides/migrations/firebase-auth</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/migrations/firebase-storage</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/migrations/firestore-data</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/migrations/heroku</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/platform/compute-add-ons</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/platform/custom-domains</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/platform/database-usage</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/platform/going-into-prod</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/platform/logs</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/platform/metrics</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/platform/migrating-and-upgrading-projects</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/platform/performance</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/platform/permissions</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
@@ -936,48 +924,6 @@
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/realtime/postgres-changes</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/realtime/quickstart</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/realtime/rate-limits</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/auth/passwordless-login/email-otp-magic-link</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/auth/passwordless-login/phone-sms-otp-messagebird</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/auth/passwordless-login/phone-sms-otp-twilio</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/auth/passwordless-login/phone-sms-otp-vonage</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/auth/auth-helpers/auth-ui</loc>
<changefreq>weekly</changefreq>
@@ -1008,6 +954,114 @@
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/functions/examples/og-image</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/quickstarts/nextjs</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/quickstarts/nuxtjs</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/quickstarts/reactjs</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/tutorials/with-angular</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/tutorials/with-expo</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/tutorials/with-flutter</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/tutorials/with-ionic-angular</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/tutorials/with-ionic-react</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/tutorials/with-ionic-vue</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/tutorials/with-nextjs</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/tutorials/with-nuxt-3</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/tutorials/with-react</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/tutorials/with-redwoodjs</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/tutorials/with-solidjs</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/tutorials/with-svelte</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/tutorials/with-sveltekit</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/tutorials/with-vue-3</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/database/extensions/http</loc>
<changefreq>weekly</changefreq>
@@ -1045,7 +1099,73 @@
</url>
<url>
<loc>https://supabase.com/docs/guides/functions/examples/og-image</loc>
<loc>https://supabase.com/docs/new/auth/auth-login</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/resources/migrating-to-supabase/firebase-auth</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/resources/migrating-to-supabase/firebase-data</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/resources/migrating-to-supabase/firebase-storage</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/resources/migrating-to-supabase/heroku</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/resources/self-hosting/docker</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/auth/passwordless-login/email-otp-magic-link</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/auth/passwordless-login/phone-sms-otp-messagebird</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/auth/passwordless-login/phone-sms-otp-twilio</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/auth/passwordless-login/phone-sms-otp-vonage</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/resources/supabase-cli/local-development</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/resources/supabase-cli/managing-enviroments</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
@@ -1158,150 +1278,6 @@
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/quickstarts/nextjs</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/quickstarts/nuxtjs</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/quickstarts/reactjs</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/tutorials/with-angular</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/tutorials/with-expo</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/tutorials/with-flutter</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/tutorials/with-ionic-angular</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/tutorials/with-ionic-react</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/tutorials/with-ionic-vue</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/tutorials/with-nextjs</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/tutorials/with-nuxt-3</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/tutorials/with-react</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/tutorials/with-redwoodjs</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/tutorials/with-solidjs</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/tutorials/with-svelte</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/tutorials/with-sveltekit</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/getting-started/tutorials/with-vue-3</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/resources/self-hosting/docker</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/resources/supabase-cli/local-development</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/resources/supabase-cli/managing-enviroments</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/resources/migrating-to-supabase/firebase-auth</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/resources/migrating-to-supabase/firebase-data</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/resources/migrating-to-supabase/firebase-storage</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/guides/resources/migrating-to-supabase/heroku</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/reference/api</loc>
<changefreq>weekly</changefreq>
@@ -1416,6 +1392,18 @@
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/reference/javascript/auth-admin-mfa-deletefactor</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/reference/javascript/auth-admin-mfa-listfactors</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/reference/javascript/auth-admin-resetpasswordforemail</loc>
<changefreq>weekly</changefreq>
@@ -1428,6 +1416,12 @@
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/reference/javascript/auth-api</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/reference/javascript/auth-getsession</loc>
<changefreq>weekly</changefreq>
@@ -1440,6 +1434,44 @@
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/reference/javascript/auth-mfa-challenge</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/reference/javascript/auth-mfa-challengeandverify</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/reference/javascript/auth-mfa-enroll</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc
>https://supabase.com/docs/reference/javascript/auth-mfa-getauthenticatorassurancelevel</loc
>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/reference/javascript/auth-mfa-unenroll</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/reference/javascript/auth-mfa-verify</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/docs/reference/javascript/auth-onauthstatechange</loc>
<changefreq>weekly</changefreq>
@@ -110,8 +110,8 @@ Today the Community are releasing both the Flutter and Dart libraries in Beta (w
### Get involved
- Help [write](/docs/handbook/supasquad#author) the Flutter Docs.
- Help [maintain](/docs/handbook/supasquad#maintainer) the [Flutter](https://github.com/supabase/supabase-flutter/)
- Help [write](/supasquad#author) the Flutter Docs.
- Help [maintain](/supasquad#maintainer) the [Flutter](https://github.com/supabase/supabase-flutter/)
and [Dart](https://github.com/supabase/supabase-dart/) libraries.
## Supabase Discord
@@ -81,7 +81,7 @@ The beauty of building in the open is that the community can get involved in add
## SupaSquad updates
The [SupaSquad](/docs/handbook/supasquad) is an official Supabase advocate program where community members help build and manage the Supabase community.
The [SupaSquad](/supasquad) is an official Supabase advocate program where community members help build and manage the Supabase community.
Among many other awesome things, the SupaSquad has been absolutely on fire building [client libraries](/docs/reference/javascript/installing) for all flavors of backends and environments.
@@ -24,7 +24,7 @@ As always, it was challenging to pick the winners from all the great submissions
[Swappy.one](https://github.com/zernonia/swappy-one) - by [@zernonia](https://twitter.com/zernonia)
Zernonia is one of our [SupaSquad](https://supabase.com/docs/handbook/supasquad) members and continuously blows our minds with his creativity for the open-source projects he creates. For example, he's also the mastermind behind [madewithsupabase.com](https://www.madewithsupabase.com/), one of our favourite resources to see what y'all are building.
Zernonia is one of our [SupaSquad](https://supabase.com/supasquad) members and continuously blows our minds with his creativity for the open-source projects he creates. For example, he's also the mastermind behind [madewithsupabase.com](https://www.madewithsupabase.com/), one of our favourite resources to see what y'all are building.
Now he can add a gold tee to his collection, and we're starting to run out of swag ideas to show our appreciation! (Send us some ideas on [Twitter](https://twitter.com/supabase)!)
@@ -25,7 +25,7 @@ Supabase Launch Week was born out of [YC Demo Day](https://www.ycombinator.com/d
With Launch Week, we replicate Demo Day conditions, but for a whole week! This is the secret formula that works for us:
- The Friday before, we publish a blog post about a topic of interest (e.g. [How we launch at Supabase](https://supabase.com/blog/supabase-how-we-launch), or this time around we [explore whether you should open source your company](https://supabase.com/blog/should-i-open-source-my-company) — hint: we think you most likely should!) as well as an [overview blogpost](https://supabase.com/blog/supabase-launch-week-four) with tons of memes and hints to what we’re shipping each day.
- Monday is Community Day, where we rally many of the awesome contributors and partners together and showcase awesome things around Supabase and open-source. This is also the time to watch out for the [#SupaLaunchWeek hashtag on Twitter](https://twitter.com/hashtag/SupaLaunchWeek?src=hashtag_click) as we arm our guest speakers, [Angels](https://supabase.com/blog/angels-of-supabase), and [SupaSquad](https://supabase.com/docs/handbook/supasquad) with free swag codes.
- Monday is Community Day, where we rally many of the awesome contributors and partners together and showcase awesome things around Supabase and open-source. This is also the time to watch out for the [#SupaLaunchWeek hashtag on Twitter](https://twitter.com/hashtag/SupaLaunchWeek?src=hashtag_click) as we arm our guest speakers, [Angels](https://supabase.com/blog/angels-of-supabase), and [SupaSquad](https://supabase.com/supasquad) with free swag codes.
- Tuesday, Wednesday, and Thursday are the big feature launches.
- Friday has become the feature kitchen sink, as we ship too many things to fit into one week. It’s our “One more thing(s)” day.
- And last but not least, coming out of Launch Week, we roll into a 10 day virtual hackathon with the community submitting tons of awesome projects to [madewithsupabase.com](https://www.madewithsupabase.com/), which, once upon a time, was a hackathon project itself. I know, quite the inception kinda stuff - so cool!
@@ -200,7 +200,7 @@ Can’t make the event? Chuck your name on the [mailing list](https://jonmeyers.
After a flood of positive feedback on Jon’s “[Build a SaaS product with Next.js, Supabase and Stripe](https://egghead.io/courses/build-a-saas-product-with-next-js-supabase-and-stripe-61f2bc20)” [Egghead.io](http://Egghead.io) course, we’re doubling down with Egghead this year, partnering with them to build more tailored pathways for learning Supabase.
We’ll be working with our fabulous [SupaSquad](https://supabase.com/docs/handbook/supasquad) to identify the right instructors for all the different topics we’re looking to cover. If you’re interested in becoming an instructor for Supabase on Egghead, fill in the [form to join](https://supabase.com/docs/handbook/supasquad#how-to-join) the SupaSquad.
We’ll be working with our fabulous [SupaSquad](https://supabase.com/supasquad) to identify the right instructors for all the different topics we’re looking to cover. If you’re interested in becoming an instructor for Supabase on Egghead, fill in the [form to join](https://supabase.com/supasquad#how-to-join) the SupaSquad.
Also, do let us know what topics you’d like to learn about [on Twitter](https://twitter.com/supabase).
@@ -57,7 +57,7 @@ Remix Auth Helpers are in the works. You can follow the development of this on o
## Contribute
Contributors are welcome! You can help by creating pull requests, reporting bugs, writing documentation, replying to issues on our GitHub issue tracker, and helping others with questions they may have about using the library. If you’re already an active contributor to Supabase or any associated open source projects, consider applying to join the [SupaSquad](https://supabase.com/docs/handbook/supasquad).
Contributors are welcome! You can help by creating pull requests, reporting bugs, writing documentation, replying to issues on our GitHub issue tracker, and helping others with questions they may have about using the library. If you’re already an active contributor to Supabase or any associated open source projects, consider applying to join the [SupaSquad](https://supabase.com/supasquad).
> Big shoutout to [Willow aka GHOST](https://twitter.com/onlyspaceghost) for her help with the Svelte/SvelteKit auth helper libraries.
@@ -0,0 +1,207 @@
---
title: 'Supabase Storage v2: Image resizing and Smart CDN'
description: "We're introducing new features for Supabase Storage: Image resizing and a Smart CDN."
author: fabrizio,inian
image: lw6-storage/Image-resizing.jpg
thumb: lw6-storage/Image-resizing.jpg
tags:
- launch-week
date: '2022-12-13'
toc_depth: 3
youtubeHero: https://www.youtube-nocookie.com/embed/NpEl20iuOtg
---
Today we're introducing three new features for Supabase Storage: Image resizing, webhooks, and a Smart CDN.
These features are designed to work together to deliver a next-gen image resizing system. Let's explore how it works.
<div className="bg-gray-300 rounded-lg p-6 italic">
Image Resizing is in Beta. While in Beta, it is only enabled for Pro-tier and Enterprise customers.
</div>
## Image resizing
Image resizing has been a popular request since we released Supabase Storage. For good reason - if you're providing an image upload function to your users, you can't expect them to optimize the images before uploading.
Supabase developers have been hacking together all sorts of workarounds using client-side compression, or external providers like Cloudinary and Imgix.
Today's release changes that. Resizing is now as easy as adding a few lines of code:
```js
supabase.storage.from('bucket').getPublicUrl('image.jpg', {
transform: {
width: 500,
height: 600,
},
})
```
### Choosing a resizer
Supabase Storage uses [imgproxy](https://github.com/imgproxy/imgproxy). As usual, we assessed various open source tools available, including [Imaginary](https://github.com/h2non/imaginary) (MIT) and [Sharp](https://github.com/lovell/sharp) (Apache2).
All of these have very friendly open source licenses for self-hosting, so ultimately it came down to features and performance.
While Imaginary doesn't support some modern image formats yet (like AVIF), Sharp and imgproxy are both very feature-complete and well-maintained
In a head-to-head benchmark, we found the performance to be almost identical, with the deciding factor instead coming down to resources.
### Developer Experience
We spent a long time considering what a good developer experience would be for image resizing.
![Resizing sane defaults](/images/blog/launch-week-6/day-2-storage-resize/resize.png)
We landed on a simple, on-demand interface. Downloading a resized image in JavaScript looks like this:
```jsx
supabase.storage.from('bucket').download('image.jpg', {
transform: {
width: 800,
height: 300,
resize: 'contain', // 'contain' | 'cover' | 'fill'
},
})
```
Or you can make a direct request to the Storage API (see `width` and `height` params):
```bash
GET https://project_id.supabase.co/storage/v1/render/image/public/bucket/image.jpg?width=500&height=600
```
If you've never used a resizing service before, you might be interested to learn that there are various approaches. Some services require you to resize your images “server side” before you can download them in the browser. Other services allow you to resize directly from the client. We opted for this client-based approach. It feels more developer friendly.
We might _additionally_ launch server-side presets as we add transformations because they provide a good DX for chaining multiple transformations (resize, rotate, and watermark for example). With server-side presets, transformations can be bundled together into a single command - `?preset=profile` instead of adding `?width=200&rotate=90&watermark=image` to all your image URLs.
Behind the scenes, we've provided a few small niceties:
- **No footguns:** an image will never be resized bigger than original dimensions (even if the user specifies larger dimensions)
- **Sane defaults:** default mode maintains aspect ratio and the resize mode is `cover`
- **Standardization**: resize modes mirror the [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) property of CSS. We support the contain, cover and fill modes.
## Smart CDN
Resizing images can get expensive, so before this release we knew we needed a better caching system. We already provide a “basic” CDN for all projects (including Free-tier), so the important update here is some smarter cache invalidation whenever there are changes.
![Caching is hard](/images/blog/launch-week-6/day-2-storage-resize/caching.png)
Our Basic CDN works by adding a 1-hour cache header to every request. After this header expires, we re-check the origin (S3), to see if an image is still available. This parameter is configurable (for example, you can make it one day, or one year).
This approach introduces the possibility of a “stale” image. If you update an image in S3, users will continue to see the old, stale image for one hour. Similarly for deleted images - the cache will continue serving the deleted image until the cache expires.
With this release we're introducing a Smart CDN. Whenever you update or delete an image, the cache will be busted within 60 seconds - for every region in the world.
As a developer, you no longer need to worry about adjusting your cache parameters - the cache “just works”.
If you're interested in _how_ the cache works, see the deep-dive later in this blog post.
## Webhooks + Rate-limiting
As a “pre-release”, we've added webhooks and rate-limiting to the Storage API. We haven't exposed these parameters on our platform yet, but they are coming soon.
If you're self-hosting then you can use them today. It's as simple as enabling a few environment variables.
The webhook events we added are:
```bash
ObjectCreated:Post # A new object is created
ObjectCreated:Put # A new object is updated
ObjectCreated:Copy # An object is duplicated
ObjectCreated:Move # An object is created as part of a move operation
ObjectRemoved:Delete # An object is deleted
ObjectRemoved:Move # An object is deleted as part of a move operation
ObjectUpdated:Metadata # An object's metadata is updated
```
## Smart CDN Deep Dive
Let's put everything together to see how the new Smart CDN works with Imgproxy.
Some developers have a high “resize” workload, and some have a high “read” workload. This means that there are different scaling requirements for every developer. As a result, we kept the image resizer and the Storage API as two separate containers. This allows them to scale independently depending on your workload.
Let's step through a few different flows to see how it all works:
- Cache miss: the image has never been resized
- Cache hit: the image has been resized and is stored in the cache
- Cache refresh: an image is updated/deleted and the CDN needs to be updated
### Cache miss
Let's imagine one of your users makes a request for a resized cat (kitten?) image in your application. Here's the flow:
![Cache miss flow](/images/blog/launch-week-6/day-2-storage-resize/cache-miss.png)
1. The request hits our globally-distributed API Gateway. This gateway reads the image metadata from a global KV store (this metadata is created when the image was uploaded).
2. Inside the image metadata is a Cache Key - this is comprised of an [Etag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag) + tenant ID + bucketname + image name. The Gateway tries to find a cached object in our CDN using the Cache Key. If the image has never been accessed, it is a “Cache miss”.
3. The request falls back to the Storage server. Because this is a resize request, it is forwards a short-lived S3 URL to Imgproxy.
4. Imgproxy resizes the image and returns it to the Storage Server which in then returns it to the API Gateway, with stores it in the Cache (which means that the next time the Etag is checked, the result will return directly from the cache).
### Cache hit
The cat image goes viral, and so now multiple users want to see the resized image.
![Cache hit flow](/images/blog/launch-week-6/day-2-storage-resize/cache-hit.png)
1. The request hits our globally-distributed API Gateway. This gateway reads the image metadata from a global KV store.
2. Using the Cache Key the Gateway tries to find a cached object in our CDN. Because this image has already been resized, it returns it directly from the cache.
### Cache refresh
To complete this architecture, we need to keep the global KV store (which stores the image metadata) synchronized whenever an image is changed. To avoid coupling any CDN-specific logic into the Storage server, we added Webhooks that publish events for every object created, updated and deleted.
Using our cat picture from above:
![Cache refresh flow](/images/blog/launch-week-6/day-2-storage-resize/cache-refresh.png)
1. You update the cat picture to a dog picture. This triggers an `ObjectCreated:Put` event from the Supabase Storage server.
2. A cache manager service subscribes to these events and updates the global metadata store with the new Etag. This takes around 60 seconds to propagate globally.
3. The next time there is a request for the resized image, it will be a cache miss.
Most CDNs offer this functionality “natively”. However there is usually a hard-limit to the number of daily cache purges. This implementation gives us greater control over when the cache is cleared.
### The impact
Besides solving issues with stale data, one of the biggest benefits this architecture offers is higher cache hits. Why is that important? Because image resizing is slow. Every cache miss is increased latency for your users.
![Cache hit improvement](/images/blog/launch-week-6/day-2-storage-resize/hit%20rate%20improvement.png)
The green line here shows the number of cache hits to our CDN. As you can see, when we enabled the Smart CDN, the number of requests cache hits increased significantly (which means faster cat images).
## Availability and Pricing
Image Resizing is in Beta. While in Beta, it is only enabled for Pro-tier and Enterprise customers.
When choosing a pricing model there were a few options that competitors offered:
- Pay for every transformation, but don't pay for a CDN hit
- Don't pay for transformations, but pay for every request (even if it's a CDN hit)
We tried to find a middle ground, where you pay for the number of images you want to transform rather than the number of transformations:
- You can transform 100 images per project, at no additional cost. Beyond this, it is $5 per additional 1000 images transformed.
- Each image can be transformed up to 20 times. This is based on fair-usage. If you exceed the limit, we won't return an error and your site will continue to function (but you will get a sternly-worded email from [@AntWilson](https://twitter.com/AntWilson)).
Of course, all of this functionality is free in the self-hosting setup too. You can self-host the Storage API either as a standalone service or as part of the Supabase stack.
For local development, this is available in our CLI from v1.23.0.
Upgrade to supabase-js v2.2.0.
## Coming soon
A few features to look out for in the future:
- **Webhooks**: we'll expose all Storage webhooks and events so that you can consume them from your own services.
- **More Transformations**: quality, rotate, blur? Cast your vote on [this GitHub Discussion](https://github.com/supabase/supabase/discussions/10918).
- **Integrations**: Native integration with [next/image](https://nextjs.org/docs/api-reference/next/image) and [Edge Functions](https://supabase.com/edge-functions) for advanced transformations.
## Getting started
[Upgrade to Pro](https://supabase.com/pricing) to get started with Image Resizing today.
- Check out the Docs for [Image Resizing](https://supabase.com/docs/guides/storage/image-transformations).
- Check out the Docs for the [Storage CDN](https://supabase.com/docs/guides/storage/cdn).
- Get Started today with the [JavaScript](https://supabase.com/docs/reference/javascript/storage-from-download) and [Dart](https://supabase.com/docs/reference/dart/storage-from-list) client libraries.
+19 -4
View File
@@ -20,16 +20,31 @@
]
},
{
"title": "Day 2",
"shipped": false,
"title": "Image Processing: Resize V2 + CDN",
"shipped": true,
"date": "13 Dec 2022",
"description": "Day 2",
"description": "Image Transformations",
"d": 2,
"dd": "Tuesday",
"youtube_id": "iqZlPtl_b-I",
"blogpost": "https://supabase.com/blog",
"docs": "https://supabase.com/docs",
"steps": []
"steps": [
{
"title": "Image Transformations",
"blog": "/blog/storage-image-resizing-smart-cdn",
"docs": "/docs/guides/storage/image-transformations",
"isNew": true,
"description": ""
},
{
"title": "Smart CDN",
"description": "Faster asset delivery, now even faster.",
"blog": "/blog/storage-image-resizing-smart-cdn",
"docs": "/docs/guides/storage/cdn",
"isNew": true
}
]
},
{
"title": "Day 3",
+1 -1
View File
@@ -86,7 +86,7 @@
},
{
"text": "SupaSquad",
"url": "/docs/handbook/supasquad"
"url": "/supasquad"
},
{
"text": "DevTo",
+9 -1
View File
@@ -159,7 +159,7 @@
"icon": "M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4",
"features": [
{
"title": "Storage total",
"title": "Storage",
"tiers": {
"free": "Up to 1 GB",
"pro": "100 GB included, then $0.021 per GB",
@@ -191,6 +191,14 @@
"pro": "Up to 5GB",
"enterprise": "Unlimited"
}
},
{
"title": "Asset transformations",
"tiers": {
"free": false,
"pro": "Unlimited transformations for 100 origin images, then 5$ per 1000 origin images",
"enterprise": "Unlimited"
}
}
]
},
+7
View File
@@ -243,5 +243,12 @@
"position": "Engineering",
"author_url": "https://github.com/mildtomato",
"author_image_url": "https://github.com/mildtomato.png"
},
{
"author_id": "fabrizio",
"author": "Fabrizio Fenoglio",
"position": "Engineering",
"author_url": "https://github.com/fenos",
"author_image_url": "https://github.com/fenos.png"
}
]
+5
View File
@@ -1600,4 +1600,9 @@ module.exports = [
source: '/docs/reference',
destination: '/docs#reference-documentation',
},
{
permanent: true,
source: '/docs/handbook/supasquad',
destination: '/supasquad',
},
]
+22 -20
View File
@@ -38,7 +38,7 @@ export default function launchweek() {
const { isDarkMode } = useTheme()
const title = 'Launch Week 6'
const description = 'Supabase Launch Week 6 | 12-18 Dec 2022'
const liveDay = 'Monday'
const liveDay = 'Tuesday'
const [supabase] = useState(() =>
createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!)
@@ -102,7 +102,7 @@ export default function launchweek() {
<Badge
className={`!bg-transparent !py-1 !px-4 text-transparent bg-clip-text bg-gradient-to-r from-[#99bbab] to-[#396f55] !border-[#4d898c] dark:from-white dark:to-[#DFFFF1] dark:!border-[#DFFFF1] h-fit relative ${
shipped
? 'after:absolute after:rounded-full text-black after:bg-white after:w-full after:h-full after:top-0 after:right-0 after:bottom-0 after:left-0 after:bg-gradient-to-br after:from-[#dceef0] after:to-[#b6b6b6] dark:text-transparent dark:border-[#1f3738] after:dark:from-[#14292c] after:dark:to-[#141516] after:border-[#c8d8d9] after:dark:border-[#1f3536] after:-z-10 border-[#DFFFF1]'
? 'after:absolute after:rounded-full text-black after:bg-white after:w-full after:h-full after:top-0 after:right-0 after:bottom-0 after:left-0 after:bg-gradient-to-br after:from-[#dceef0] after:to-[#b6b6b6] dark:text-transparent dark:!border-[#163837] after:dark:from-[#14292c] after:dark:to-[#141516] after:border-[#c8d8d9] after:dark:border-[#1f3536] after:-z-10 border-[#DFFFF1]'
: ''
}`}
>
@@ -224,10 +224,10 @@ export default function launchweek() {
<div className="relative flex items-center mb-4 sm:mb-0">
<div className="flex">
<Image
src="/images/launchweek/antcopple.png"
className="rounded-xl w-32 h-32"
width={128}
height={72}
src="/images/launchweek/antcopplecall.png"
className="rounded-xl w-32 h-32 brightness-125"
width={120}
height={80}
></Image>
</div>
<div className="flex flex-col lg:flex-row ml-8 sm:ml-10">
@@ -264,7 +264,7 @@ export default function launchweek() {
justified={false}
bordered={false}
chevronAlign="right"
defaultValue={[day1.d.toString()]}
defaultValue={[day2.d.toString()]}
>
<div className="border-b pb-3">
<Accordion.Item
@@ -321,24 +321,26 @@ export default function launchweek() {
{day2.steps.length > 0 && (
<div className="h-[800px] lg:h-[400px] flex flex-col gap-5 lg:flex-row">
<div
className={`flex-1 flex flex-col justify-between basis-1/2 lg:basis-2/3 border rounded-xl h-full p-14 text-2xl bg-no-repeat bg-[url('/images/launchweek/image-processing-bg-light.png')] dark:bg-[url('/images/launchweek/image-processing-bg.png')] bg-[right_28px_top_35px] bg-contain`}
className={`flex-1 flex flex-col items-center gap-5 lg:items-start justify-between basis-1/2 lg:basis-2/3 border rounded-xl h-full p-14 xs:text-2xl text-xl text-center bg-no-repeat bg-[url('/images/launchweek/image-processing-bg-light-alt.png')] xs:bg-[url('/images/launchweek/image-processing-bg-light.png')] dark:bg-[url('/images/launchweek/image-processing-bg-alt.png')] xs:dark:bg-[url('/images/launchweek/image-processing-bg.png')] bg-[right_30px_top_35px] lg:bg-[top_25px_right_25px] bg-contain`}
>
<div className="flex items-center">
<div className="flex items-center justify-between flex-col-reverse lg:flex-row lg:justify-start gap-2 text-black dark:text-white">
<div>{day2.steps[0].title}</div>
<Badge className="!bg-transparent h-fit ml-4 text-sm !py-1 !px-4 text-transparent bg-clip-text bg-gradient-to-r from-[#99bbab] to-[#396f55] border-[#598973] dark:from-white dark:to-[#1a7a4ca1] dark:border-[#598973]">
<Badge className="!bg-transparent h-fit ml-4 text-sm !py-1 !px-4 text-transparent bg-clip-text bg-gradient-to-r from-[#99bbab] to-[#396f55] border-[#598973] dark:from-white dark:to-[#1a7a4ca1] dark:border-[#DFFFF1]">
New
</Badge>
</div>
<SectionButtons docs={day2.steps[0].docs} blog={day2.steps[0].blog} />
</div>
<div
className={`flex-1 flex flex-col items-center justify-between basis-1/2 lg:basis-1/3 border rounded-xl h-full bg-no-repeat p-14 text-2xl bg-[url('/images/launchweek/cdn-caching-bg-light.png')] dark:bg-[url('/images/launchweek/cdn-caching-bg.png')] bg-[center_bottom] bg-contain`}
className={`flex-1 flex flex-col items-center justify-between basis-1/2 lg:basis-1/3 border rounded-xl h-full bg-no-repeat p-14 text-2xl bg-[url('/images/launchweek/cdn-caching-bg-light.png')] dark:bg-[url('/images/launchweek/cdn-caching-bg.png')] bg-[top_170px_center] lg:bg-[center_bottom] bg-contain`}
>
<div className="flex flex-col items-center gap-2 min-w-[300px]">
<Badge className="!bg-transparent h-fit text-sm !py-1 !px-4 text-transparent bg-clip-text bg-gradient-to-r from-[#99bbab] to-[#396f55] border-[#598973] dark:from-white dark:to-[#1a7a4ca1] dark:border-[#598973]">
<Badge className="!bg-transparent h-fit text-sm !py-1 !px-4 text-transparent bg-clip-text bg-gradient-to-r from-[#99bbab] to-[#396f55] border-[#598973] dark:from-white dark:to-[#1a7a4ca1] dark:border-[#DFFFF1]">
New
</Badge>
<span>{day2.steps[1].title}</span>
<span className="text-black dark:text-white">
{day2.steps[1].title}
</span>
<p className="text-sm text-slate-900">{day2.steps[1].description}</p>
</div>
<SectionButtons docs={day2.steps[1].docs} blog={day2.steps[1].blog} />
@@ -469,7 +471,7 @@ export default function launchweek() {
)}
<SectionContainer className="!pt-0 sm:!pb-20 !pb-40">
<div className="flex flex-col mb-16">
<Badge className="!bg-transparent h-fit text-sm !py-1 !px-4 text-transparent bg-clip-text bg-gradient-to-r from-[#99bbab] to-[#396f55] border-[#598973] dark:from-white dark:to-[#1a7a4ca1] dark:border-[#598973] w-fit after:bg-white mb-4">
<Badge className="mb-4 w-fit bg-gradient-to-r from-[#0E3737C2] to-[#67947F] hover:to-[#39617D94] dark:hover:to-[#A6FFD899] text-whiteA-1200 dark:text-black font-normal !py-1 !px-4 dark:from-white dark:via-white dark:to-[#1a7a4c75] bg-slate-1200">
Happening all week
</Badge>
<h2 className="text-3xl text-black dark:text-white mb-2">Launch Week Hackathon</h2>
@@ -553,7 +555,7 @@ export default function launchweek() {
<div className="border border-slate-400 dark:border-[#2E2E2E] rounded-xl pl-5 pr-9 py-4 mb-4 bg-white dark:bg-[#121212] w-fit relative max-w-[250px]">
<img
src="/images/launchweek/link.svg"
className="absolute top-[10px] right-[10px] text-brand-900"
className="absolute top-[16px] right-[10px] text-brand-900"
></img>
<h3 className="text-black dark:text-white">Supabase Cache Helpers</h3>
<p className="text-slate-1000 text-xs">Previous Best Overall Project Winner</p>
@@ -567,7 +569,7 @@ export default function launchweek() {
<div className="border border-slate-400 dark:border-[#2E2E2E] rounded-xl pl-5 pr-9 py-4 mb-4 bg-white dark:bg-[#121212] w-fit relative max-w-[250px]">
<img
src="/images/launchweek/link.svg"
className="absolute top-[10px] right-[10px] text-brand-900"
className="absolute top-[16px] right-[10px] text-brand-900"
></img>
<h3 className="text-black dark:text-white">Super UI</h3>
<p className="text-slate-1000 text-xs">Previous Best Overall Project Runner Up</p>
@@ -582,7 +584,7 @@ export default function launchweek() {
<div className="border border-slate-400 dark:border-[#2E2E2E] rounded-xl pl-5 pr-9 py-4 mb-4 bg-white dark:bg-[#121212] w-fit relative max-w-[250px]">
<img
src="/images/launchweek/link.svg"
className="absolute top-[10px] right-[10px] text-brand-900"
className="absolute top-[16px] right-[10px] text-brand-900"
></img>
<h3 className="text-black dark:text-white">Quid Faciam Hodie?</h3>
<p className="text-slate-1000 text-xs">Winner Best Flutter Project</p>
@@ -592,7 +594,7 @@ export default function launchweek() {
<div className="border border-slate-400 dark:border-[#2E2E2E] rounded-xl pl-5 pr-9 py-4 mb-4 bg-white dark:bg-[#121212] w-fit relative max-w-[250px]">
<img
src="/images/launchweek/link.svg"
className="absolute top-[10px] right-[10px] text-brand-900"
className="absolute top-[16px] right-[10px] text-brand-900"
></img>
<h3 className="text-black dark:text-white">That Hot Dog Game</h3>
<p className="text-slate-1000 text-xs">Previous Winner: Most Fun/Interesting</p>
@@ -607,7 +609,7 @@ export default function launchweek() {
<div className="border border-slate-400 dark:border-[#2E2E2E] rounded-xl pl-5 pr-9 py-4 bg-white dark:bg-[#121212] w-fit relative max-w-[250px]">
<img
src="/images/launchweek/link.svg"
className="absolute top-[10px] right-[10px] text-brand-900"
className="absolute top-[16px] right-[10px] text-brand-900"
></img>
<h3 className="text-black dark:text-white">RepoWatch</h3>
<p className="text-slate-1000 text-xs">Previous Winner Best Realtime Project</p>
@@ -653,7 +655,7 @@ export default function launchweek() {
</div>
<div className="flex-1">
<Badge className="mb-6 bg-gradient-to-r from-[#0E3737C2] to-[#67947F] hover:to-[#39617D94] dark:hover:to-[#A6FFD899] text-whiteA-1200 dark:text-black font-normal !py-1 !px-4 dark:from-white dark:via-white dark:to-[#1a7a4c75] bg-slate-1200">
Live
Currently Happening
</Badge>
<h2 className="text-3xl dark:text-white mb-2">The Supabase Content Storm</h2>
<p className="text-slate-900 md:max-w-[80%] mb-16">
+22 -1
View File
@@ -187,6 +187,17 @@ function StoragePage() {
title="CDN"
text="Serve from the edge to reduce latency."
/>
<Link href="/docs/guides/storage/cdn" passHref>
<Button
as="a"
size="small"
type="default"
className="mt-4"
icon={<IconArrowUpRight />}
>
Explore docs
</Button>
</Link>
</div>
<div className="col-span-6 lg:col-span-12 xl:col-span-4">
<FeatureColumn
@@ -194,7 +205,17 @@ function StoragePage() {
title="Transformations"
text="Resize and compress your media before you serve it."
/>
<Badge>Coming soon</Badge>
<Link href="/docs/guides/storage/image-transformations" passHref>
<Button
as="a"
size="small"
type="default"
className="mt-4"
icon={<IconArrowUpRight />}
>
Explore docs
</Button>
</Link>
</div>
</div>,
]}
@@ -1,18 +1,22 @@
import Layout from '~/layouts/DefaultGuideLayout'
import Layout from '../layouts/Layout'
import SectionContainer from '../components/Layouts/SectionContainer'
export const meta = {
id: 'supasquad',
title: 'SupaSquad',
description: 'Supabase Advocate program',
}
<SectionContainer>
# SupaSquad
The SupaSquad is an official Supabase advocate program where community members help build and manage the Supabase community.
- Official recognition in the Supabase community.
- Direct connection to the Supabase team.
- Help steer the Supabase community.
![Supabase SupaSquad](/docs/img/supabase-squad.png)
![Supabase SupaSquad](/images/supabase-squad.png)
## Requirements
@@ -89,6 +93,6 @@ Apply to join the program using [this form](https://airtable.com/shr0FtLqLfhpuEy
the Supabase ecosystem.
</details>
export const Page = ({ children }) => <Layout meta={meta} children={children} />
</SectionContainer>
export default Page
export default (context) => <Layout meta={meta} children={context.children} context={context} />
Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 869 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 362 KiB

After

Width:  |  Height:  |  Size: 338 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 KiB

+9 -2
View File
@@ -5,13 +5,20 @@
<link>https://supabase.com</link>
<description>Latest news from Supabase</description>
<language>en</language>
<lastBuildDate>Sun, 11 Dec 2022 16:04:00 +0000</lastBuildDate>
<lastBuildDate>Mon, 12 Dec 2022 16:04:00 +0000</lastBuildDate>
<atom:link href="https://supabase.com/rss.xml" rel="self" type="application/rss+xml"/>
<item>
<guid>https://supabase.com/blog/storage-image-resizing-smart-cdn</guid>
<title>Supabase Storage v2: Image resizing and Smart CDN</title>
<link>https://supabase.com/blog/storage-image-resizing-smart-cdn</link>
<description>We're introducing new features for Supabase Storage: Image resizing and a Smart CDN.</description>
<pubDate>Mon, 12 Dec 2022 16:04:00 +0000</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/new-supabase-docs-built-with-nextjs</guid>
<title>New Supabase Docs, built with Next.js</title>
<link>https://supabase.com/blog/new-supabase-docs-built-with-nextjs</link>
<description>We've redesign our Docs and migrated to Next.js</description>
<description>We've redesignd our Docs and migrated to Next.js</description>
<pubDate>Sun, 11 Dec 2022 16:04:00 +0000</pubDate>
</item>
<item>
+13 -13
View File
@@ -66,18 +66,6 @@
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/database</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/edge-functions</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/contact/enterprise</loc>
<changefreq>weekly</changefreq>
@@ -91,7 +79,13 @@
</url>
<url>
<loc>https://supabase.com/launch-week/ticket-image</loc>
<loc>https://supabase.com/database</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/edge-functions</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
@@ -108,6 +102,12 @@
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/launch-week/ticket-image</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/nextjs</loc>
<changefreq>weekly</changefreq>
+383 -1094
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -17,7 +17,7 @@
"@radix-ui/react-dialog": "^0.1.5",
"@radix-ui/react-dropdown-menu": "^0.1.4",
"@radix-ui/react-popover": "^0.1.0",
"@radix-ui/react-portal": "^0.1.3",
"@radix-ui/react-portal": "^1.0.1",
"@radix-ui/react-tabs": "^0.1.0",
"@tailwindcss/forms": "^0.4.0",
"@tailwindcss/typography": "^0.5.0",
@@ -1,10 +1,8 @@
import React from 'react'
import * as React from 'react'
import * as RadixPopover from '@radix-ui/react-popover'
import type * as RadixPopoverTypes from '@radix-ui/react-popover/'
import { IconX } from '../Icon/icons/IconX'
import styleHandler from '../../lib/theme/styleHandler'
interface RootProps {
+2 -2
View File
@@ -382,7 +382,7 @@ export default {
`,
},
'rounded-pills': {
list: 'flex space-x-2',
list: 'flex flex-wrap gap-2',
base: `
relative
cursor-pointer
@@ -1564,7 +1564,7 @@ export default {
transition cursor-pointer select-none relative py-2 pl-3 pr-9
text-scale-900
text-sm
hover:bg-scale-300 dark:hover:bg-scale-500
focus:bg-scale-300 dark:focus:bg-scale-500
focus:text-scale-1200
border-none
-1
View File
@@ -118,4 +118,3 @@ combine-raw.tsdoc.v2:
###############################################################################
format:
npx prettier --write .
+43 -43
View File
@@ -1,4 +1,4 @@
clispec: "001"
clispec: '001'
info:
id: cli
version: 1.23.0
@@ -20,19 +20,19 @@ flags:
- id: debug
name: --debug
description: output debug logs to stderr
default_value: "false"
default_value: 'false'
- id: experimental
name: --experimental
description: enable experimental features
default_value: "false"
default_value: 'false'
- id: help
name: -h, --help
description: help for supabase
default_value: "false"
default_value: 'false'
- id: workdir
name: --workdir <string>
description: path to a Supabase project directory
default_value: ""
default_value: ''
commands:
- id: supabase-vanity-subdomains
title: supabase vanity-subdomains
@@ -53,7 +53,7 @@ commands:
- id: project-ref
name: --project-ref <string>
description: Project ref of the Supabase project.
default_value: ""
default_value: ''
- id: supabase-vanity-subdomains-get
title: supabase vanity-subdomains get
summary: Get the current vanity subdomain
@@ -84,7 +84,7 @@ commands:
name: --desired-subdomain <string>
description: |
The desired vanity subdomain to use for your Supabase project.
default_value: ""
default_value: ''
- id: supabase-vanity-subdomains-activate
title: supabase vanity-subdomains activate
summary: Activate a vanity subdomain
@@ -102,7 +102,7 @@ commands:
name: --desired-subdomain <string>
description: |
The desired vanity subdomain to use for your Supabase project.
default_value: ""
default_value: ''
- id: supabase-test
title: supabase test
summary: Run tests on local Supabase containers
@@ -132,7 +132,7 @@ commands:
- id: backup
name: --backup
description: Backs up the current database before stopping.
default_value: "false"
default_value: 'false'
- id: supabase-status
title: supabase status
summary: Show status of local Supabase containers
@@ -212,7 +212,7 @@ commands:
- id: env-file
name: --env-file <string>
description: Read secrets from a .env file.
default_value: ""
default_value: ''
- id: supabase-secrets-list
title: supabase secrets list
summary: List all secrets on Supabase
@@ -252,15 +252,15 @@ commands:
- id: db-password
name: --db-password <string>
description: Database password of the project.
default_value: ""
default_value: ''
- id: interactive
name: -i, --interactive
description: Enables interactive mode.
default_value: "false"
default_value: 'false'
- id: org-id
name: --org-id <string>
description: Organization ID to create the project in.
default_value: ""
default_value: ''
- id: plan
name: --plan <[ free | pro ]>
description: Select a plan that suits your needs.
@@ -275,7 +275,7 @@ commands:
- id: region
name: --region <string>
description: Select a region close to you for the best performance.
default_value: ""
default_value: ''
accepted_values:
- id: ap-northeast-1
name: ap-northeast-1
@@ -347,7 +347,7 @@ commands:
- id: project-ref
name: --project-ref <string>
description: Project ref of the Supabase project.
default_value: ""
default_value: ''
- id: supabase-network-restrictions-update
title: supabase network-restrictions update
summary: Update network restrictions
@@ -359,7 +359,7 @@ commands:
- id: bypass-cidr-checks
name: --bypass-cidr-checks
description: Bypass some of the CIDR validation checks.
default_value: "false"
default_value: 'false'
- id: db-allow-cidr
name: --db-allow-cidr <strings>
description: CIDR to allow DB connections from.
@@ -395,7 +395,7 @@ commands:
name: --status <[ applied | reverted ]>
description: Version status to update.
required: true
default_value: ""
default_value: ''
accepted_values:
- id: applied
name: applied
@@ -422,7 +422,7 @@ commands:
- id: password
name: -p, --password <string>
description: Password to your remote Postgres database.
default_value: ""
default_value: ''
- id: supabase-login
title: supabase login
summary: Authenticate using an access token
@@ -444,12 +444,12 @@ commands:
- id: password
name: -p, --password <string>
description: Password to your remote Postgres database.
default_value: ""
default_value: ''
- id: project-ref
name: --project-ref <string>
description: Project ref of the Supabase project.
required: true
default_value: ""
default_value: ''
- id: supabase-init
title: supabase init
summary: Initialize a local project
@@ -489,19 +489,19 @@ commands:
- id: db-url
name: --db-url <string>
description: Generate types from a database url.
default_value: ""
default_value: ''
- id: linked
name: --linked
description: Generate types from the linked project.
default_value: "false"
default_value: 'false'
- id: local
name: --local
description: Generate types from the local dev database.
default_value: "false"
default_value: 'false'
- id: project-id
name: --project-id <string>
description: Generate types from a project ID.
default_value: ""
default_value: ''
- id: schema
name: --schema <stringArray>
description: Schemas to generate types for.
@@ -530,11 +530,11 @@ commands:
name: --env-file <string>
description: |
Path to an env file to be populated to the Function environment.
default_value: ""
default_value: ''
- id: no-verify-jwt
name: --no-verify-jwt
description: Disable JWT verification for the Function.
default_value: "false"
default_value: 'false'
- id: supabase-functions-new
title: supabase functions new
summary: Create a new Function locally
@@ -555,15 +555,15 @@ commands:
- id: legacy-bundle
name: --legacy-bundle
description: Use legacy bundling mechanism.
default_value: "false"
default_value: 'false'
- id: no-verify-jwt
name: --no-verify-jwt
description: Disable JWT verification for the Function.
default_value: "false"
default_value: 'false'
- id: project-ref
name: --project-ref <string>
description: Project ref of the Supabase project.
default_value: ""
default_value: ''
- id: supabase-functions-delete
title: supabase functions delete
summary: Delete a Function from Supabase
@@ -577,7 +577,7 @@ commands:
- id: project-ref
name: --project-ref <string>
description: Project ref of the Supabase project.
default_value: ""
default_value: ''
- id: supabase-domains
title: supabase domains
summary: Manage custom domain names for Supabase projects
@@ -598,11 +598,11 @@ commands:
- id: include-raw-output
name: --include-raw-output
description: Include raw output (useful for debugging).
default_value: "false"
default_value: 'false'
- id: project-ref
name: --project-ref <string>
description: Project ref of the Supabase project.
default_value: ""
default_value: ''
- id: supabase-domains-reverify
title: supabase domains reverify
summary: Re-verify the custom hostname config for your project
@@ -644,7 +644,7 @@ commands:
- id: custom-hostname
name: --custom-hostname <string>
description: The custom hostname to use for your Supabase project.
default_value: ""
default_value: ''
- id: supabase-domains-activate
title: supabase domains activate
summary: Activate the custom hostname for a project
@@ -699,7 +699,7 @@ commands:
- id: password
name: -p, --password <string>
description: Password to your remote Postgres database.
default_value: ""
default_value: ''
- id: supabase-db-remote-commit
title: supabase db remote commit
summary: Commit remote changes as a new migration
@@ -720,11 +720,11 @@ commands:
name: --dry-run
description: |
Print the migrations that would be applied, but don't actually apply them.
default_value: "false"
default_value: 'false'
- id: password
name: -p, --password <string>
description: Password to your remote Postgres database.
default_value: ""
default_value: ''
- id: supabase-db-lint
title: supabase db lint
summary: Checks local database for typing error
@@ -759,11 +759,11 @@ commands:
- id: file
name: -f, --file <string>
description: Saves schema diff to a file.
default_value: ""
default_value: ''
- id: linked
name: --linked
description: Diffs local schema against linked project.
default_value: "false"
default_value: 'false'
- id: schema
name: -s, --schema <strings>
description: List of schema to include.
@@ -771,7 +771,7 @@ commands:
- id: use-migra
name: --use-migra
description: Use migra to generate schema diff.
default_value: "false"
default_value: 'false'
- id: supabase-completion
title: supabase completion
summary: Generate the autocompletion script for the specified shell
@@ -821,7 +821,7 @@ commands:
- id: no-descriptions
name: --no-descriptions
description: disable completion descriptions
default_value: "false"
default_value: 'false'
- id: supabase-completion-powershell
title: supabase completion powershell
summary: Generate the autocompletion script for powershell
@@ -842,7 +842,7 @@ commands:
- id: no-descriptions
name: --no-descriptions
description: disable completion descriptions
default_value: "false"
default_value: 'false'
- id: supabase-completion-fish
title: supabase completion fish
summary: Generate the autocompletion script for fish
@@ -866,7 +866,7 @@ commands:
- id: no-descriptions
name: --no-descriptions
description: disable completion descriptions
default_value: "false"
default_value: 'false'
- id: supabase-completion-bash
title: supabase completion bash
summary: Generate the autocompletion script for bash
@@ -899,4 +899,4 @@ commands:
- id: no-descriptions
name: --no-descriptions
description: disable completion descriptions
default_value: "false"
default_value: 'false'
-6
View File
@@ -267,12 +267,6 @@
"slug": "supabase-db-remote-commit",
"type": "cli-command"
},
{
"id": "supabase-db-remote-changes",
"title": "supabase db remote changes",
"slug": "supabase-db-remote-changes",
"type": "cli-command"
},
{
"id": "supabase-db-push",
"title": "supabase db push",
+1 -1
View File
@@ -932,7 +932,7 @@
},
{
"id": "from-get-public-url",
"title": "Retrieve an asset's URL",
"title": "Retrieve public URL",
"slug": "storage-from-getpublicurl",
"product": "storage",
"type": "function",
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -1112,7 +1112,8 @@
"type": {
"type": "reference",
"id": 5,
"name": "SchemaName"
"name": "SchemaName",
"dereferenced": {}
}
}
],
@@ -1291,7 +1292,8 @@
"type": {
"type": "reference",
"id": 5,
"name": "SchemaName"
"name": "SchemaName",
"dereferenced": {}
}
},
{
File diff suppressed because it is too large. Load diff
@@ -2157,7 +2157,138 @@
"type": {
"type": "reference",
"id": 357,
"name": "Bucket"
"name": "Bucket",
"dereferenced": {
"id": 357,
"name": "Bucket",
"kind": 256,
"kindString": "Interface",
"flags": {},
"children": [
{
"id": 361,
"name": "created_at",
"kind": 1024,
"kindString": "Property",
"flags": {},
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 5,
"character": 2
}
],
"type": {
"type": "intrinsic",
"name": "string"
}
},
{
"id": 358,
"name": "id",
"kind": 1024,
"kindString": "Property",
"flags": {},
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 2,
"character": 2
}
],
"type": {
"type": "intrinsic",
"name": "string"
}
},
{
"id": 359,
"name": "name",
"kind": 1024,
"kindString": "Property",
"flags": {},
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 3,
"character": 2
}
],
"type": {
"type": "intrinsic",
"name": "string"
}
},
{
"id": 360,
"name": "owner",
"kind": 1024,
"kindString": "Property",
"flags": {},
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 4,
"character": 2
}
],
"type": {
"type": "intrinsic",
"name": "string"
}
},
{
"id": 363,
"name": "public",
"kind": 1024,
"kindString": "Property",
"flags": {},
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 7,
"character": 2
}
],
"type": {
"type": "intrinsic",
"name": "boolean"
}
},
{
"id": 362,
"name": "updated_at",
"kind": 1024,
"kindString": "Property",
"flags": {},
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 6,
"character": 2
}
],
"type": {
"type": "intrinsic",
"name": "string"
}
}
],
"groups": [
{
"title": "Properties",
"kind": 1024,
"children": [361, 358, 359, 360, 363, 362]
}
],
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 1,
"character": 17
}
]
}
}
},
{
@@ -2544,7 +2675,70 @@
"type": {
"type": "reference",
"id": 374,
"name": "SortBy"
"name": "SortBy",
"dereferenced": {
"id": 374,
"name": "SortBy",
"kind": 256,
"kindString": "Interface",
"flags": {},
"children": [
{
"id": 375,
"name": "column",
"kind": 1024,
"kindString": "Property",
"flags": {
"isOptional": true
},
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 23,
"character": 2
}
],
"type": {
"type": "intrinsic",
"name": "string"
}
},
{
"id": 376,
"name": "order",
"kind": 1024,
"kindString": "Property",
"flags": {
"isOptional": true
},
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 24,
"character": 2
}
],
"type": {
"type": "intrinsic",
"name": "string"
}
}
],
"groups": [
{
"title": "Properties",
"kind": 1024,
"children": [375, 376]
}
],
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 22,
"character": 17
}
]
}
}
}
],
@@ -4724,7 +4918,112 @@
"type": {
"type": "reference",
"id": 390,
"name": "TransformOptions"
"name": "TransformOptions",
"dereferenced": {
"id": 390,
"name": "TransformOptions",
"kind": 256,
"kindString": "Interface",
"flags": {},
"children": [
{
"id": 392,
"name": "height",
"kind": 1024,
"kindString": "Property",
"flags": {
"isOptional": true
},
"comment": {
"shortText": "The height of the image in pixels."
},
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 84,
"character": 2
}
],
"type": {
"type": "intrinsic",
"name": "number"
}
},
{
"id": 393,
"name": "resize",
"kind": 1024,
"kindString": "Property",
"flags": {
"isOptional": true
},
"comment": {
"shortText": "The resize mode can be cover, contain or fill. Defaults to cover.\nCover resizes the image to maintain it's aspect ratio while filling the entire width and height.\nContain resizes the image to maintain it's aspect ratio while fitting the entire image within the width and height.\nFill resizes the image to fill the entire width and height. If the object's aspect ratio does not match the width and height, the image will be stretched to fit."
},
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 91,
"character": 2
}
],
"type": {
"type": "union",
"types": [
{
"type": "literal",
"value": "cover"
},
{
"type": "literal",
"value": "contain"
},
{
"type": "literal",
"value": "fill"
}
]
}
},
{
"id": 391,
"name": "width",
"kind": 1024,
"kindString": "Property",
"flags": {
"isOptional": true
},
"comment": {
"shortText": "The width of the image in pixels."
},
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 80,
"character": 2
}
],
"type": {
"type": "intrinsic",
"name": "number"
}
}
],
"groups": [
{
"title": "Properties",
"kind": 1024,
"children": [392, 393, 391]
}
],
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 76,
"character": 17
}
]
}
}
}
],
@@ -5306,7 +5605,112 @@
"type": {
"type": "reference",
"id": 390,
"name": "TransformOptions"
"name": "TransformOptions",
"dereferenced": {
"id": 390,
"name": "TransformOptions",
"kind": 256,
"kindString": "Interface",
"flags": {},
"children": [
{
"id": 392,
"name": "height",
"kind": 1024,
"kindString": "Property",
"flags": {
"isOptional": true
},
"comment": {
"shortText": "The height of the image in pixels."
},
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 84,
"character": 2
}
],
"type": {
"type": "intrinsic",
"name": "number"
}
},
{
"id": 393,
"name": "resize",
"kind": 1024,
"kindString": "Property",
"flags": {
"isOptional": true
},
"comment": {
"shortText": "The resize mode can be cover, contain or fill. Defaults to cover.\nCover resizes the image to maintain it's aspect ratio while filling the entire width and height.\nContain resizes the image to maintain it's aspect ratio while fitting the entire image within the width and height.\nFill resizes the image to fill the entire width and height. If the object's aspect ratio does not match the width and height, the image will be stretched to fit."
},
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 91,
"character": 2
}
],
"type": {
"type": "union",
"types": [
{
"type": "literal",
"value": "cover"
},
{
"type": "literal",
"value": "contain"
},
{
"type": "literal",
"value": "fill"
}
]
}
},
{
"id": 391,
"name": "width",
"kind": 1024,
"kindString": "Property",
"flags": {
"isOptional": true
},
"comment": {
"shortText": "The width of the image in pixels."
},
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 80,
"character": 2
}
],
"type": {
"type": "intrinsic",
"name": "number"
}
}
],
"groups": [
{
"title": "Properties",
"kind": 1024,
"children": [392, 393, 391]
}
],
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 76,
"character": 17
}
]
}
}
}
],
@@ -5558,7 +5962,112 @@
"type": {
"type": "reference",
"id": 390,
"name": "TransformOptions"
"name": "TransformOptions",
"dereferenced": {
"id": 390,
"name": "TransformOptions",
"kind": 256,
"kindString": "Interface",
"flags": {},
"children": [
{
"id": 392,
"name": "height",
"kind": 1024,
"kindString": "Property",
"flags": {
"isOptional": true
},
"comment": {
"shortText": "The height of the image in pixels."
},
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 84,
"character": 2
}
],
"type": {
"type": "intrinsic",
"name": "number"
}
},
{
"id": 393,
"name": "resize",
"kind": 1024,
"kindString": "Property",
"flags": {
"isOptional": true
},
"comment": {
"shortText": "The resize mode can be cover, contain or fill. Defaults to cover.\nCover resizes the image to maintain it's aspect ratio while filling the entire width and height.\nContain resizes the image to maintain it's aspect ratio while fitting the entire image within the width and height.\nFill resizes the image to fill the entire width and height. If the object's aspect ratio does not match the width and height, the image will be stretched to fit."
},
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 91,
"character": 2
}
],
"type": {
"type": "union",
"types": [
{
"type": "literal",
"value": "cover"
},
{
"type": "literal",
"value": "contain"
},
{
"type": "literal",
"value": "fill"
}
]
}
},
{
"id": 391,
"name": "width",
"kind": 1024,
"kindString": "Property",
"flags": {
"isOptional": true
},
"comment": {
"shortText": "The width of the image in pixels."
},
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 80,
"character": 2
}
],
"type": {
"type": "intrinsic",
"name": "number"
}
}
],
"groups": [
{
"title": "Properties",
"kind": 1024,
"children": [392, 393, 391]
}
],
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 76,
"character": 17
}
]
}
}
}
],
@@ -5795,7 +6304,70 @@
"type": {
"type": "reference",
"id": 374,
"name": "SortBy"
"name": "SortBy",
"dereferenced": {
"id": 374,
"name": "SortBy",
"kind": 256,
"kindString": "Interface",
"flags": {},
"children": [
{
"id": 375,
"name": "column",
"kind": 1024,
"kindString": "Property",
"flags": {
"isOptional": true
},
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 23,
"character": 2
}
],
"type": {
"type": "intrinsic",
"name": "string"
}
},
{
"id": 376,
"name": "order",
"kind": 1024,
"kindString": "Property",
"flags": {
"isOptional": true
},
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 24,
"character": 2
}
],
"type": {
"type": "intrinsic",
"name": "string"
}
}
],
"groups": [
{
"title": "Properties",
"kind": 1024,
"children": [375, 376]
}
],
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 22,
"character": 17
}
]
}
}
}
],
@@ -460,7 +460,8 @@
"type": {
"type": "reference",
"id": 78,
"name": "SchemaName"
"name": "SchemaName",
"dereferenced": {}
}
}
],
@@ -526,7 +527,31 @@
"type": {
"type": "reference",
"id": 60,
"name": "Fetch"
"name": "Fetch",
"dereferenced": {
"id": 60,
"name": "Fetch",
"kind": 4194304,
"kindString": "Type alias",
"flags": {},
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 8,
"character": 12
}
],
"type": {
"type": "query",
"queryType": {
"type": "reference",
"id": 18,
"qualifiedName": "fetch",
"package": "typescript",
"name": "fetch"
}
}
}
}
},
{
@@ -2750,7 +2775,8 @@
"type": {
"type": "reference",
"id": 78,
"name": "SchemaName"
"name": "SchemaName",
"dereferenced": {}
}
}
],
@@ -2816,7 +2842,31 @@
"type": {
"type": "reference",
"id": 60,
"name": "Fetch"
"name": "Fetch",
"dereferenced": {
"id": 60,
"name": "Fetch",
"kind": 4194304,
"kindString": "Type alias",
"flags": {},
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 8,
"character": 12
}
],
"type": {
"type": "query",
"queryType": {
"type": "reference",
"id": 18,
"qualifiedName": "fetch",
"package": "typescript",
"name": "fetch"
}
}
}
}
},
{
@@ -3829,7 +3879,8 @@
"type": {
"type": "reference",
"id": 78,
"name": "SchemaName"
"name": "SchemaName",
"dereferenced": {}
}
}
],
@@ -3895,7 +3946,31 @@
"type": {
"type": "reference",
"id": 60,
"name": "Fetch"
"name": "Fetch",
"dereferenced": {
"id": 60,
"name": "Fetch",
"kind": 4194304,
"kindString": "Type alias",
"flags": {},
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 8,
"character": 12
}
],
"type": {
"type": "query",
"queryType": {
"type": "reference",
"id": 18,
"qualifiedName": "fetch",
"package": "typescript",
"name": "fetch"
}
}
}
}
},
{
@@ -4253,7 +4328,8 @@
"type": {
"type": "reference",
"id": 78,
"name": "SchemaName"
"name": "SchemaName",
"dereferenced": {}
}
}
],
@@ -4319,7 +4395,31 @@
"type": {
"type": "reference",
"id": 60,
"name": "Fetch"
"name": "Fetch",
"dereferenced": {
"id": 60,
"name": "Fetch",
"kind": 4194304,
"kindString": "Type alias",
"flags": {},
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 8,
"character": 12
}
],
"type": {
"type": "query",
"queryType": {
"type": "reference",
"id": 18,
"qualifiedName": "fetch",
"package": "typescript",
"name": "fetch"
}
}
}
}
},
{
@@ -5608,7 +5708,8 @@
"type": {
"type": "reference",
"id": 78,
"name": "SchemaName"
"name": "SchemaName",
"dereferenced": {}
}
}
],
@@ -5674,7 +5775,31 @@
"type": {
"type": "reference",
"id": 60,
"name": "Fetch"
"name": "Fetch",
"dereferenced": {
"id": 60,
"name": "Fetch",
"kind": 4194304,
"kindString": "Type alias",
"flags": {},
"sources": [
{
"fileName": "src/lib/types.ts",
"line": 8,
"character": 12
}
],
"type": {
"type": "query",
"queryType": {
"type": "reference",
"id": 18,
"qualifiedName": "fetch",
"package": "typescript",
"name": "fetch"
}
}
}
}
},
{
+19
View File
@@ -69,6 +69,17 @@ function dereferenceReducer(child: any, kv: KV) {
if (!!child.parameters) {
child.parameters.forEach((x: any) => dereferenceReducer(x, kv))
}
if (
!!child.type &&
!!child.type.declaration &&
!!child.type.declaration.children &&
child.type.type === 'reflection'
) {
child.type.declaration.children.forEach((x: any) =>
dereferenceReducer(x, kv)
)
}
const final = { ...child }
if (
// For now I can only dereference parameters
@@ -81,6 +92,14 @@ function dereferenceReducer(child: any, kv: KV) {
const dereferenced = kv[final.type.id]
final.type.dereferenced = dereferenced || {}
return final
} else if (
final.kindString === 'Property' &&
final.type?.type === 'reference' &&
final.type?.id
) {
const dereferenced = kv[final.type.id]
final.type.dereferenced = dereferenced || {}
return final
} else if (
final.kindString === 'Type alias' &&
final.type?.type === 'union'
+237 -184
View File
@@ -504,7 +504,7 @@ functions:
isSpotlight: true
code: |
```dart
final data = await supabase
await supabase
.from('cities')
.insert({'name': 'The Shire', 'country_id': 554});
```
@@ -512,11 +512,21 @@ functions:
name: Bulk create
code: |
```dart
final res = await supabase.from('cities').insert([
await supabase.from('cities').insert([
{'name': 'The Shire', 'country_id': 554},
{'name': 'Rohan', 'country_id': 555},
]);
```
- id: fetch-inserted-data
name: Fetch inserted record
code: |
```dart
final List<Map<String, dynamic>> data =
await supabase.from('cities').insert([
{'name': 'The Shire', 'country_id': 554},
{'name': 'Rohan', 'country_id': 555},
]).select();
```
- id: update
description: |
@@ -526,17 +536,17 @@ functions:
- `update()` should always be combined with [Filters](/docs/reference/dart/using-filters) to target the item(s) you wish to update.
examples:
- id: updating-your-data
name: Updating your data
name: Update your data
isSpotlight: true
code: |
```dart
final data = await supabase
await supabase
.from('cities')
.update({ 'name': 'Middle Earth' })
.match({ 'name': 'Auckland' });
```
- id: updating-json-data
name: Updating JSON data
name: Update JSON data
description: |
Postgres offers a
[number of operators](https://www.postgresql.org/docs/current/functions-json.html)
@@ -544,7 +554,7 @@ functions:
but we are [working on ideas](https://github.com/PostgREST/postgrest/issues/465) for updating individual keys.
code: |
```dart
final data = await supabase
await supabase
.from('users')
.update({
'address': {
@@ -554,6 +564,18 @@ functions:
})
.eq('address->postcode', 90210);
```
- id: fetch-updated-rows
name: Fetch updated rows
code: |
```dart
final List<Map<String, dynamic>> data = await supabase
.from('users')
.update({
'address': {'street': 'Melrose Place', 'postcode': 90210}
})
.eq('address->postcode', 90210)
.select();
```
- id: upsert
description: |
@@ -568,7 +590,7 @@ functions:
isSpotlight: true
code: |
```dart
final data = await supabase
await supabase
.from('messages')
.upsert({ 'id': 3, 'message': 'foo', 'username': 'supabot' });
```
@@ -581,7 +603,7 @@ functions:
isSpotlight: true
code: |
```dart
final data = await supabase
await supabase
.from('users')
.upsert({ 'username': 'supabot' }, { 'onConflict': 'username' });
```
@@ -612,11 +634,21 @@ functions:
isSpotlight: true
code: |
```dart
final data = await supabase
await supabase
.from('cities')
.delete()
.match({ 'id': 666 });
```
- id: fetch-delete-records
name: Fetch deleted records
code: |
```dart
final List<Map<String,dynamic>> data = await supabase
.from('cities')
.delete()
.match({ 'id': 666 })
.select();
```
- id: rpc
title: 'Stored Procedures: rpc()'
@@ -1082,10 +1114,26 @@ functions:
isSpotlight: true
code: |
```dart
final String signedUrl = await supabase
.storage
.from('avatars')
.createSignedUrl('avatar1.png', 60);
final String signedUrl = await supabase
.storage
.from('avatars')
.createSignedUrl('avatar1.png', 60);
```
- id: create-signed-url-with-transform
name: With transform
code: |
```dart
final String signedUrl = await supabase
.storage
.from('avatars')
.createSignedUrl(
'avatar1.png',
60,
transform: TransformOptions(
width: 200,
height: 200,
),
);
```
- id: from-get-public-url
@@ -1103,10 +1151,26 @@ functions:
isSpotlight: true
code: |
```dart
final String publicUrl = supabase
.storage
.from('public-bucket')
.getPublicUrl('avatar1.png');
final String publicUrl = supabase
.storage
.from('public-bucket')
.getPublicUrl('avatar1.png');
```
- id: returns-the-url-for-an-asset-in-a-public-bucket-with-transform
name: With transform
isSpotlight: true
code: |
```dart
final String publicUrl = await supabase
.storage
.from('public-bucket')
.getPublicUrl(
'avatar1.png',
transform: TransformOptions(
width: 200,
height: 200,
),
);
```
- id: from-download
@@ -1128,6 +1192,22 @@ functions:
.from('avatars')
.download('avatar1.png');
```
- id: download-file-with-transform
name: With transform
isSpotlight: true
code: |
```dart
final Uint8List file = await supabase
.storage
.from('avatars')
.download(
'avatar1.png',
transform: TransformOptions(
width: 200,
height: 200,
),
);
```
- id: from-remove
description: |
@@ -1272,178 +1352,151 @@ functions:
Filters can be used on `select()`, `update()`, and `delete()` queries.
If a Database function returns a table response, you can also apply filters.
examples:
- id: applying-filters
name: Applying Filters
description: |
Filters must be applied after any of `select()`, `update()`, `upsert()`,
`delete()`, and `rpc()` and before
[modifiers](/docs/reference/dart/using-modifiers).
code: |
```dart
final data = await supabase
.from('cities')
.select('name, country_id')
.eq('name', 'The Shire'); // Correct
### Applying Filters
final data = await supabase
.from('cities')
.eq('name', 'The Shire') // Incorrect
.select('name, country_id');
```
- id: chaining-filters
name: Chaining Filters
description: |
Filters must be applied after any of `select()`, `update()`, `upsert()`,
`delete()`, and `rpc()` and before
[modifiers](/docs/reference/dart/using-modifiers).
code: |
```dart
final data = await supabase
.from('cities')
.select('name, country_id')
.eq('name', 'The Shire'); // Correct
Filters must be applied after any of `select()`, `update()`, `upsert()`,
`delete()`, and `rpc()` and before
[modifiers](/docs/reference/dart/using-modifiers).
final data = await supabase
.from('cities')
.eq('name', 'The Shire') // Incorrect
.select('name, country_id');
```
- id: conditional-chaining
name: Conditional Chaining
description: |
Filters can be built up one step at a time and then executed. For example:
code: |
```dart
final data = await supabase
.from('users')
.select()
.eq('address->postcode', 90210);
```
- id: filter-by-value-within-json-column
name: Filter by values within a JSON column
description: |
Filters can be built up one step at a time and then executed. For example:
data:
sql: |
```sql
create table
users (
id int8 primary key,
name text,
address jsonb
);
```dart
final data = await supabase
.from('cities')
.select('name, country_id')
.eq('name', 'The Shire'); // Correct
final data = await supabase
.from('cities')
.eq('name', 'The Shire') // Incorrect
.select('name, country_id');
```
### Chaining
Filters can be chained together to produce advanced queries. For example,
to query cities with population between 1,000 and 10,000:
```dart
final data = await supabase
.from('cities')
.select('name, country_id')
.gte('population', 1000)
.lt('population', 10000);
```
### Conditional Chaining
Filters can be built up one step at a time and then executed. For example:
```dart
final filterByName = null;
final filterPopLow = 1000;
final filterPopHigh = 10000;
var query = supabase
.from('cities')
.select('name, country_id');
if (filterByName != null) { query = query.eq('name', filterByName); }
if (filterPopLow != null) { query = query.gte('population', filterPopLow); }
if (filterPopHigh != null) { query = query.lt('population', filterPopHigh); }
final data = await query;
```
### Filter by values within a JSON column
<Tabs>
<TabItem value="schema" label="Schema">
```sql
create table
users (
id int8 primary key,
name text,
address jsonb
);
insert into
users (id, name, address)
values
(1, 'Michael', '{ "postcode": 90210 }'),
(2, 'Jane', null);
```
</TabItem>
<TabItem default value="dart" label="Flutter">
```dart
final data = await supabase
.from('users')
.select()
.eq('address->postcode', 90210);
```
</TabItem>
<TabItem value="result" label="Result">
```json
{
"data": [
{
"id": 1,
"name": "Michael",
"address": {
"postcode": 90210
}
}
],
"status": 200,
"statusText": "OK"
}
```
</TabItem>
</Tabs>
### Filter Foreign Tables
You can filter on foreign tables in your `select()` query using dot
notation:
<Tabs>
<TabItem value="schema" label="Schema">
```sql
create table
countries (id int8 primary key, name text);
create table
cities (
id int8 primary key,
country_id int8 not null references countries,
name text
);
insert into
countries (id, name)
values
(1, 'Germany'),
(2, 'Indonesia');
insert into
cities (id, country_id, name)
values
(1, 2, 'Bali'),
(2, 1, 'Munich');
```
</TabItem>
<TabItem default value="dart" label="Flutter">
```dart
final data = await supabase
.from('countries')
.select('''
name,
cities!inner (
name
)
''')
.eq('cities.name', 'Bali');
```
</TabItem>
<TabItem value="result" label="Result">
```json
{
"data": [
{
"name": "Indonesia",
"cities": [
{
"name": "Bali"
insert into
users (id, name, address)
values
(1, 'Michael', '{ "postcode": 90210 }'),
(2, 'Jane', null);
```
response: |
```json
{
"data": [
{
"id": 1,
"name": "Michael",
"address": {
"postcode": 90210
}
]
}
],
"status": 200,
"statusText": "OK"
}
```
code: |
```dart
final data = await supabase
.from('users')
.select()
.eq('address->postcode', 90210);
```
- id: filter-foreign-tables
name: Filter Foreign Tables
code: |
```dart
final data = await supabase
.from('countries')
.select('''
name,
cities!inner (
name
)
''')
.eq('cities.name', 'Bali');
```
data:
sql: |
```sql
create table
countries (id int8 primary key, name text);
create table
cities (
id int8 primary key,
country_id int8 not null references countries,
name text
);
insert into
countries (id, name)
values
(1, 'Germany'),
(2, 'Indonesia');
insert into
cities (id, country_id, name)
values
(1, 2, 'Bali'),
(2, 1, 'Munich');
```
response: |
```json
{
"data": [
{
"name": "Indonesia",
"cities": [
{
"name": "Bali"
}
]
}
],
"status": 200,
"statusText": "OK"
}
],
"status": 200,
"statusText": "OK"
}
```
</TabItem>
</Tabs>
```
- id: or
title: or()
description: |
+69
View File
@@ -7404,6 +7404,33 @@ functions:
.from('avatars')
.createSignedUrl('folder/avatar1.png', 60)
```
- id: create-signed-url-with-transformations
name: Create a signed URL for an asset with transformations
isSpotlight: true
code: |
```js
const { data } = supabase
.storage
.from('avatars')
.createSignedUrl('folder/avatar1.png', 60, {
transform: {
width: 100,
height: 100,
}
})
```
- id: create-signed-url-with-download
name: Create a signed URL which triggers the download of the asset
isSpotlight: true
code: |
```js
const { data } = supabase
.storage
.from('avatars')
.createSignedUrl('folder/avatar1.png', 60, {
download: true,
})
```
- id: from-create-signed-urls
title: from.createSignedUrls()
@@ -7445,6 +7472,33 @@ functions:
.from('public-bucket')
.getPublicUrl('folder/avatar1.png')
```
- id: transform-asset-in-public-bucket
name: Returns the URL for an asset in a public bucket with transformations
isSpotlight: true
code: |
```js
const { data } = supabase
.storage
.from('public-bucket')
.getPublicUrl('folder/avatar1.png', {
transform: {
width: 100,
height: 100,
}
})
```
- id: download-asset-in-public-bucket
name: Returns the URL which triggers the download of an asset in a public bucket
isSpotlight: true
code: |
```js
const { data } = supabase
.storage
.from('public-bucket')
.getPublicUrl('folder/avatar1.png', {
download: true,
})
```
- id: from-download
title: from.download()
@@ -7465,6 +7519,21 @@ functions:
.from('avatars')
.download('folder/avatar1.png')
```
- id: download-file-with-transformations
name: Download file with transformations
isSpotlight: true
code: |
```js
const { data, error } = await supabase
.storage
.from('avatars')
.download('folder/avatar1.png', {
transform: {
width: 100,
height: 100,
}
})
```
- id: from-remove
title: from.remove()
@@ -56,19 +56,21 @@ const EnableExtensionModal: FC<Props> = ({ visible, extension, onCancel }) => {
setSubmitting(true)
const schema =
defaultSchema !== undefined
defaultSchema !== undefined && defaultSchema !== null
? defaultSchema
: values.schema === 'custom'
? values.name
: values.schema
const { error: createSchemaError } = await meta.query(`create schema if not exists ${schema}`)
if (createSchemaError) {
return ui.setNotification({
error: createSchemaError,
category: 'error',
message: `Failed to create schema: ${createSchemaError.message}`,
})
if (!schema.startsWith('pg_')) {
const { error: createSchemaError } = await meta.query(`create schema if not exists ${schema}`)
if (createSchemaError) {
return ui.setNotification({
error: createSchemaError,
category: 'error',
message: `Failed to create schema: ${createSchemaError.message}`,
})
}
}
const { error: createExtensionError } = await meta.extensions.create({
@@ -3,7 +3,6 @@ import { Form, IconDatabase, Input, Listbox, SidePanel, Modal, IconPlus } from '
import { useStore } from 'hooks'
import { Table, TableOption } from './Wrappers.types'
import { makeValidateRequired } from './Wrappers.utils'
import MultiSelect from 'components/ui/MultiSelect'
import ActionBar from 'components/interfaces/TableGridEditor/SidePanelEditor/ActionBar'
export type WrapperTableEditorProps = {
@@ -201,25 +200,44 @@ const TableForm = ({
{requiredOptions.map((option) => (
<Option key={option.name} option={option} />
))}
<MultiSelect
label="Columns"
options={table.availableColumns.map((column) => {
return {
id: column.name,
value: column.name,
name: column.name,
description: column.type,
disabled: false,
}
})}
error={errors.columns}
value={values.columns}
placeholder="Select at least one column"
searchPlaceholder="Search for a column"
onChange={(columns) => {
resetForm({ values: { ...values, columns } })
}}
/>
<div className="form-group">
<label className="!w-full">Select the columns to be added to your table</label>
<div className="flex flex-wrap gap-2">
{table.availableColumns.map((column) => {
const isSelected = values.columns.includes(column.name)
return (
<div
key={column.name}
className={[
'px-2 py-1 bg-scale-500 rounded cursor-pointer transition',
`${isSelected ? 'bg-brand-800' : 'hover:bg-scale-700'}`,
].join(' ')}
onClick={() => {
if (isSelected) {
resetForm({
values: {
...values,
columns: values.columns.filter((x: string) => x !== column.name),
},
})
} else {
resetForm({
values: { ...values, columns: values.columns.concat([column.name]) },
})
}
}}
>
<p className="text-sm">{column.name}</p>
</div>
)
})}
</div>
{errors.columns && (
<span className="text-red-900 text-sm mt-2">{errors.columns}</span>
)}
</div>
{optionalOptions.map((option) => (
<Option key={option.name} option={option} />
))}
@@ -108,18 +108,22 @@ const AccountLayout: FC<Props> = ({ children, title, breadcrumbs }) => {
},
],
},
{
key: 'logout-link',
links: [
{
key: `logout`,
icon: '/icons/feather/power.svg',
label: 'Logout',
href: undefined,
onClick: onClickLogout,
},
],
},
...(IS_PLATFORM
? [
{
key: 'logout-link',
links: [
{
key: `logout`,
icon: '/icons/feather/power.svg',
label: 'Logout',
href: undefined,
onClick: onClickLogout,
},
],
},
]
: []),
]
return (
@@ -132,7 +136,7 @@ const AccountLayout: FC<Props> = ({ children, title, breadcrumbs }) => {
<div className="flex h-full">
<main
style={{ height: maxHeight, maxHeight }}
className="flex w-full flex-1 flex-col overflow-y-auto"
className="flex flex-col flex-1 w-full overflow-y-auto"
>
<WithSidebar title={title} breadcrumbs={breadcrumbs} sections={sectionsWithHeaders}>
{children}
@@ -1,5 +1,6 @@
import { useStore } from 'hooks'
import { usePushNext } from 'hooks/misc/useAutoAuthRedirect'
import { IS_PLATFORM } from 'lib/constants'
import { auth, getReturnToPath, STORAGE_KEY } from 'lib/gotrue'
import { observer } from 'mobx-react-lite'
import Head from 'next/head'
@@ -77,19 +78,21 @@ const SignInLayout = ({
return (
<>
<Head>
<script
dangerouslySetInnerHTML={{
__html: `window._getReturnToPath = ${getReturnToPath.toString()};if (localStorage.getItem('${STORAGE_KEY}') && !(new URLSearchParams(location.search).has('next'))) {location.replace(window._getReturnToPath())}`,
}}
/>
</Head>
{IS_PLATFORM && (
<Head>
<script
dangerouslySetInnerHTML={{
__html: `window._getReturnToPath = ${getReturnToPath.toString()};if (localStorage.getItem('${STORAGE_KEY}') && !(new URLSearchParams(location.search).has('next'))) {location.replace(window._getReturnToPath())}`,
}}
/>
</Head>
)}
<div className="flex-1 flex flex-col bg-scale-100">
<div className="absolute top-0 mx-auto w-full px-8 pt-6 sm:px-6 lg:px-8">
<div className="flex flex-col flex-1 bg-scale-100">
<div className="absolute top-0 w-full px-8 pt-6 mx-auto sm:px-6 lg:px-8">
<nav className="relative flex items-center justify-between sm:h-10">
<div className="flex flex-shrink-0 flex-grow items-center lg:flex-grow-0">
<div className="flex w-full items-center justify-between md:w-auto">
<div className="flex items-center flex-grow flex-shrink-0 lg:flex-grow-0">
<div className="flex items-center justify-between w-full md:w-auto">
<Link href={logoLinkToMarketingSite ? 'https://supabase.com' : '/projects'}>
<a>
<Image
@@ -103,7 +106,7 @@ const SignInLayout = ({
</div>
</div>
<div className="hidden items-center space-x-3 md:ml-10 md:flex md:pr-4">
<div className="items-center hidden space-x-3 md:ml-10 md:flex md:pr-4">
<Link href="https://supabase.com/docs">
<a target="_blank">
<Button type="default" icon={<IconFileText />}>
@@ -115,12 +118,12 @@ const SignInLayout = ({
</nav>
</div>
<div className="flex-1 flex">
<main className="flex-1 flex-shrink-0 flex flex-col items-center bg-scale-200 px-5 pt-16 pb-8 border-r border-scale-500 shadow-lg">
<div className="flex flex-1">
<main className="flex flex-col items-center flex-1 flex-shrink-0 px-5 pt-16 pb-8 border-r shadow-lg bg-scale-200 border-scale-500">
<div className="flex-1 flex flex-col justify-center w-[384px]">
<div className="mb-10">
<h1 className="text-2xl lg:text-3xl mt-8 mb-2">{heading}</h1>
<h2 className="text-scale-1100 text-sm">{subheading}</h2>
<h1 className="mt-8 mb-2 text-2xl lg:text-3xl">{heading}</h1>
<h2 className="text-sm text-scale-1100">{subheading}</h2>
</div>
{children}
@@ -143,14 +146,14 @@ const SignInLayout = ({
)}
</main>
<aside className="hidden flex-1 flex-shrink basis-1/4 xl:flex flex-col justify-center items-center">
<aside className="flex-col items-center justify-center flex-1 flex-shrink hidden basis-1/4 xl:flex">
{quote !== null && (
<div className="relative flex flex-col gap-6">
<div className="absolute -top-12 -left-11 select-none">
<div className="absolute select-none -top-12 -left-11">
<span className="text-[160px] leading-none text-scale-600">{'“'}</span>
</div>
<blockquote className="text-3xl z-10 max-w-lg">{quote.text}</blockquote>
<blockquote className="z-10 max-w-lg text-3xl">{quote.text}</blockquote>
<a
href={quote.url}
@@ -161,11 +164,11 @@ const SignInLayout = ({
<img
src={`https://supabase.com${quote.img_url}`}
alt={quote.handle}
className="rounded-full w-12 h-12"
className="w-12 h-12 rounded-full"
/>
<div className="flex flex-col">
<cite className="font-medium not-italic text-scale-1100 whitespace-nowrap">
<cite className="not-italic font-medium text-scale-1100 whitespace-nowrap">
@{quote.handle}
</cite>
</div>
+2 -3
View File
@@ -51,12 +51,11 @@ const ConfirmationModal: FC<Props> = ({
size={size}
onCancel={onSelectCancel}
customFooter={
<div className="flex w-full items-center space-x-3">
<Button block type="secondary" disabled={loading} onClick={onSelectCancel}>
<div className="flex justify-end w-full items-center space-x-3">
<Button type="default" disabled={loading} onClick={onSelectCancel}>
Cancel
</Button>
<Button
block
type={danger ? 'danger' : 'primary'}
loading={loading}
disabled={loading}
+9 -1
View File
@@ -1,6 +1,14 @@
import { useRef, useEffect, useState, FormEvent, KeyboardEvent, ReactNode } from 'react'
import { orderBy, filter, without } from 'lodash'
import { Popover, IconCheck, IconAlertCircle, IconSearch, IconPlus, IconChevronDown } from 'ui'
import {
Popover,
IconCheck,
IconAlertCircle,
IconSearch,
IconPlus,
IconChevronDown,
Input,
} from 'ui'
import { BadgeDisabled, BadgeSelected } from './Badges'
+8 -6
View File
@@ -84,11 +84,13 @@ export function withAuth<T>(
<Head>
{/* This script will quickly (before the main JS loads) redirect the user
to the login page if they are guaranteed (no token at all) to not be logged in. */}
<script
dangerouslySetInnerHTML={{
__html: `window._getReturnToPath = ${getReturnToPath.toString()};if (!localStorage.getItem('${STORAGE_KEY}') && !location.hash) {const searchParams = new URLSearchParams(location.search);searchParams.set('returnTo', location.pathname);location.replace('/sign-in' + '?' + searchParams.toString())}`,
}}
/>
{IS_PLATFORM && (
<script
dangerouslySetInnerHTML={{
__html: `window._getReturnToPath = ${getReturnToPath.toString()};if (!localStorage.getItem('${STORAGE_KEY}') && !location.hash) {const searchParams = new URLSearchParams(location.search);searchParams.set('returnTo', location.pathname);location.replace('/sign-in' + '?' + searchParams.toString())}`,
}}
/>
)}
</Head>
<WrappedComponent {...props} />
</>
@@ -105,7 +107,7 @@ export function withAuth<T>(
}
function defaultRedirectTo(ref: string | string[] | undefined) {
return IS_PLATFORM ? '/sign-in' : ref !== undefined ? `/project/${ref}` : '/sign-in'
return IS_PLATFORM ? '/sign-in' : ref !== undefined ? `/project/${ref}` : '/projects'
}
function checkRedirectTo(
-1
View File
@@ -107,7 +107,6 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
<Head>
<title>Supabase</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<link rel="stylesheet" type="text/css" href="/css/fonts.css" />
</Head>
<PageTelemetry>
+1
View File
@@ -18,6 +18,7 @@ class MyDocument extends Document {
data-name="vs/editor/editor.main"
href="https://cdn.jsdelivr.net/npm/monaco-editor@0.28.1/min/vs/editor/editor.main.css"
/>
<link rel="stylesheet" type="text/css" href="/css/fonts.css" />
</Head>
<body className="dark">
<Main />
+62 -7
View File
@@ -1,9 +1,10 @@
import { useEffect, useState } from 'react'
import { NextPage } from 'next'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { observer } from 'mobx-react-lite'
import { isUndefined, isNaN } from 'lodash'
import { Checkbox, Modal } from 'ui'
import { Alert, Button, Checkbox, IconExternalLink, Modal } from 'ui'
import { PostgresTable, PostgresColumn } from '@supabase/postgres-meta'
import Base64 from 'lib/base64'
@@ -124,7 +125,7 @@ const TableEditorPage: NextPage = () => {
const onConfirmDeleteColumn = async () => {
try {
const response: any = await meta.columns.del(selectedColumnToDelete!.id)
const response: any = await meta.columns.del(selectedColumnToDelete!.id, isDeleteWithCascade)
if (response.error) throw response.error
removeDeletedColumnFromFiltersAndSorts(selectedColumnToDelete!.name)
@@ -200,22 +201,54 @@ const TableEditorPage: NextPage = () => {
/>
<ConfirmationModal
danger
size="small"
visible={isDeleting && !isUndefined(selectedColumnToDelete)}
header={`Confirm deletion of column "${selectedColumnToDelete?.name}"`}
children={
<Modal.Content>
<p className="py-4 text-sm text-scale-1100">
Are you sure you want to delete the selected column? This action cannot be undone.
</p>
<div className="py-4 space-y-4">
<p className="text-sm text-scale-1100">
Are you sure you want to delete the selected column? This action cannot be undone.
</p>
<Checkbox
label="Drop column with cascade?"
description="Deletes the column and its dependent objects"
checked={isDeleteWithCascade}
onChange={() => setIsDeleteWithCascade(!isDeleteWithCascade)}
/>
{isDeleteWithCascade && (
<Alert
withIcon
variant="warning"
title="Warning: Dropping with cascade may result in unintended consequences"
>
<p className="mb-4">
All dependent objects will be removed, as will any objects that depend on them,
recursively.
</p>
<Link href="https://www.postgresql.org/docs/current/ddl-depend.html">
<a target="_blank">
<Button size="tiny" type="default" icon={<IconExternalLink />}>
About dependency tracking
</Button>
</a>
</Link>
</Alert>
)}
</div>
</Modal.Content>
}
buttonLabel="Delete"
buttonLoadingLabel="Deleting"
onSelectCancel={() => setIsDeleting(false)}
onSelectCancel={() => {
setIsDeleting(false)
setSelectedColumnToDelete(undefined)
}}
onSelectConfirm={onConfirmDeleteColumn}
/>
<ConfirmationModal
danger
size="small"
visible={isDeleting && !isUndefined(selectedTableToDelete)}
header={
<span className="break-words">{`Confirm deletion of table "${selectedTableToDelete?.name}"`}</span>
@@ -232,12 +265,34 @@ const TableEditorPage: NextPage = () => {
checked={isDeleteWithCascade}
onChange={() => setIsDeleteWithCascade(!isDeleteWithCascade)}
/>
{isDeleteWithCascade && (
<Alert
withIcon
variant="warning"
title="Warning: Dropping with cascade may result in unintended consequences"
>
<p className="mb-4">
All dependent objects will be removed, as will any objects that depend on them,
recursively.
</p>
<Link href="https://www.postgresql.org/docs/current/ddl-depend.html">
<a target="_blank">
<Button size="tiny" type="default" icon={<IconExternalLink />}>
About dependency tracking
</Button>
</a>
</Link>
</Alert>
)}
</div>
</Modal.Content>
}
buttonLabel="Delete"
buttonLoadingLabel="Deleting"
onSelectCancel={() => setIsDeleting(false)}
onSelectCancel={() => {
setIsDeleting(false)
setSelectedTableToDelete(undefined)
}}
onSelectConfirm={onConfirmDeleteTable}
/>
</TableEditorLayout>
+14 -3
View File
@@ -6,10 +6,21 @@ import SignInForm from 'components/interfaces/SignIn/SignInForm'
import SignInWithGitHub from 'components/interfaces/SignIn/SignInWithGitHub'
import SignInWithSSO from 'components/interfaces/SignIn/SignInWithSSO'
import { SignInLayout } from 'components/layouts'
import { useEffect } from 'react'
import { IS_PLATFORM } from 'lib/constants'
import { useRouter } from 'next/router'
const SignInPage: NextPageWithLayout = () => {
const showSsoLogin = useFlag('mfaSso')
const router = useRouter()
useEffect(() => {
// if the dashboard is running locally, redirect straight to the projects page
if (!IS_PLATFORM) {
router.replace('/projects')
}
}, [])
return (
<>
<div className="flex flex-col gap-5">
@@ -21,18 +32,18 @@ const SignInPage: NextPageWithLayout = () => {
<div className="w-full border-t border-scale-700" />
</div>
<div className="relative flex justify-center text-sm">
<span className="bg-scale-200 px-2 text-sm text-scale-1200">or</span>
<span className="px-2 text-sm bg-scale-200 text-scale-1200">or</span>
</div>
</div>
<SignInForm />
</div>
<div className="my-8 self-center text-sm">
<div className="self-center my-8 text-sm">
<div>
<span className="text-scale-1000">Don't have an account?</span>{' '}
<Link href="/sign-up">
<a className="underline text-scale-1200 hover:text-scale-1100 transition">
<a className="underline transition text-scale-1200 hover:text-scale-1100">
Sign Up Now
</a>
</Link>
+1 -1
View File
@@ -420,7 +420,7 @@ export default class MetaStore implements IMetaStore {
this.rootStore.ui.setNotification({
id: toastId,
category: 'error',
message: `An error occured while creating the column "${payload.name}"`,
message: `An error occurred while creating the column "${payload.name}"`,
})
const query = `alter table "${selectedTable.name}" drop column if exists "${payload.name}";`
await this.rootStore.meta.query(query)