Merge branch 'master' into meera/master

This commit is contained in:
Alaister Young committed 2023-02-13 11:56:49 +11:00
commit 351f630ab8
919 files changed
+68047 -42802

No files matched your search

+19 -6
View File
@@ -1,7 +1,20 @@
node_modules/
# Root folder of this turbo monorepo
# Doesn't send useless context to Docker daemon
.git
.github
about
apps
docker
examples
i18n
**/node_modules
spec
supabase
tests
**/.next
.vercel
.next
.env.local
.env.production
.env.dev
.env.*
.env.*
.dockerignore
studio/Dockerfile
+20
View File
@@ -0,0 +1,20 @@
name: reviewdog
on:
pull_request:
types: [opened, synchronize, reopened, edited]
jobs:
misspell:
name: runner / misspell
runs-on: ubuntu-latest
steps:
- name: Check out code.
uses: actions/checkout@v1
- name: misspell
uses: reviewdog/action-misspell@v1
with:
github_token: ${{ secrets.github_token }}
locale: "US"
reporter: github-pr-review
level: error
exclude: "*.css"
+6 -2
View File
@@ -19,12 +19,16 @@ jobs:
permissions:
contents: read
packages: write
id-token: write
steps:
- name: configure aws credentials
uses: aws-actions/configure-aws-credentials@v1
with:
role-to-assume: ${{ secrets.PROD_AWS_ROLE }}
aws-region: us-east-1
- uses: docker/login-action@v2
with:
registry: public.ecr.aws
username: ${{ secrets.PROD_ACCESS_KEY_ID }}
password: ${{ secrets.PROD_SECRET_ACCESS_KEY }}
- uses: docker/login-action@v2
with:
registry: ghcr.io
+2 -1
View File
@@ -3,4 +3,5 @@
node_modules
package-lock.json
docker*
apps/**/out
apps/**/out
**/**.mdx
+1 -1
View File
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2022 Supabase
Copyright 2023 Supabase
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+4 -4
View File
@@ -12,9 +12,9 @@
- [x] Hosted Postgres Database. [Docs](https://supabase.com/docs/guides/database)
- [x] Authentication and Authorization. [Docs](https://supabase.com/docs/guides/auth)
- [x] Auto-generated APIs.
- [x] REST. [Docs](https://supabase.com/docs/guides/api#rest-api)
- [x] Realtime subscriptions. [Docs](https://supabase.com/docs/guides/api#realtime-api)
- [x] GraphQL (Beta). [Docs](https://supabase.com/docs/guides/api#graphql-api)
- [x] REST. [Docs](https://supabase.com/docs/guides/database/api#rest-api)
- [x] Realtime subscriptions. [Docs](https://supabase.com/docs/guides/database/api#realtime-api)
- [x] GraphQL (Beta). [Docs](https://supabase.com/docs/guides/database/api#graphql-api)
- [x] Functions.
- [x] Database Functions. [Docs](https://supabase.com/docs/guides/database/functions)
- [x] Edge Functions [Docs](https://supabase.com/docs/guides/functions)
@@ -142,7 +142,7 @@ Our approach for client libraries is modular. Each sub-library is a standalone i
<td>-</td>
<td><a href="https://github.com/supabase-community/gotrue-java" target="_blank" rel="noopener noreferrer">gotrue-java</a></td>
<td>-</td>
<td><a href="https://github.com/supabase-community/storage-java" target="_blanke" rel="noopener noreferrer">storage-java</a></td>
<td><a href="https://github.com/supabase-community/storage-java" target="_blank" rel="noopener noreferrer">storage-java</a></td>
<td>-</td>
</tr>
<tr>
+8
View File
@@ -0,0 +1,8 @@
# PUBLIC
NEXT_PUBLIC_NEW_DOCS=true
NEXT_PUBLIC_SUPABASE_URL=http://localhost:54321
NEXT_PUBLIC_SUPABASE_ANON_KEY=
# PRIVATE
OPENAI_KEY=
SUPABASE_SERVICE_ROLE_KEY=
+98
View File
@@ -0,0 +1,98 @@
# Developing Supabase Docs
## Getting started
Thanks for your interest in [Supabase docs](https://supabase.com/docs) and for wanting to contribute! Before you begin, read the
[code of conduct](https://github.com/supabase/.github/blob/main/CODE_OF_CONDUCT.md) and check out the
[existing issues](https://github.com/supabase/supabase/issues).
This document describes how to set up your development environment to contribute to [Supabase docs](https://supabase.com/docs).
For a complete run-down on how all of our tools work together, see the main DEVELOPERS.md. That readme describes how to get set up locally in lots of detail, including minimum requirements, our Turborepo setup, installing packages, sharing components across projects, and more. This readme deals specifically with the docs site.
## Local setup
[supabase.com/docs](https://supabase.com/docs) is a Next.JS site. You can get setup by following the same steps for all of our other Next.JS projects:
1. Follow the steps outlined in the Local Development section of the main [DEVELOPERS.md](https://github.com/supabase/supabase/blob/master/DEVELOPERS.md)
2. Start the local docs site by navigating to `/apps/docs` and running `npm run dev`
3. Visit http://localhost:3001/docs in your browser - don't forget to append the `/docs` to the end
4. Your local site should look exactly like [https://supabase.com/docs](https://supabase.com/docs)
## Types of documentation
[https://supabase.com/docs](https://supabase.com/docs) has several different kinds of documentation, all coming from different sources.
### Guides
The primary, instructional type of content. Basically anything that lives on the `https://supabase.com/docs/guides` route. This includes Guides for Auth, Database, Storage, Realtime, Edge Functions, as well as general resources, self-hosting instructions, and integrations. These are all [`.mdx`](https://mdxjs.com/) files — a combination of Markdown and Javascript.
#### Things to know
Here's a simple [example](https://supabase.com/docs/guides/functions) `.mdx` Guide, and here is [the source on Github](https://raw.githubusercontent.com/supabase/supabase/master/apps/docs/pages/guides/functions.mdx).
Some things to note:
1. The files need to import a Layout at the top
2. The files need to export a `Page` at the bottom with the `<Layout>` component
3. The files frontmatter is stored in `const meta = {}`. You should always include `title` and `description`.
4. You can write Markdown as you normally would, but you can also write regular Javascript and JSX. Note the `examples` array that we iterate over.
5. Any Javascript variables you use in these files need to be exported in order to be used (i.e., `export const examples = []`).
##### Using components
You can use any standard React components in these `.mdx` files without having to explicitly import them in each file. All components get imported in a [common components](https://github.com/supabase/supabase/blob/master/apps/docs/components/index.tsx) file and can be used in any `.mdx` file. Components can also be "intercepted" and modified via this file. Note how we're intercepting the `h2`, `h3` and `code` tags and modifying them before converting the `mdx` to `html`.
### Reference docs for client libraries
We maintain client libraries for [Javascript](https://supabase.com/docs/reference/javascript) and [Flutter/Dart](https://supabase.com/docs/reference/dart) (with more to come). These reference docs document every object and method available for developers to use. The are assembled from different sources and work much differently than the `.mdx` Guides we just looked at.
The client libraries are essentially wrappers around the clients for the various tools we use — GoTrue, PostgREST, Storage, Functions, and Realtime. The easiest way to describe how the things fit together is to look at an example and trace where the various pieces of information are coming from.
#### Example
Let's look at the `updateUser()` function in the `supabase-js` library.
#### Common file
Several pieces of information for this function come from a [common file](https://github.com/supabase/supabase/blob/3d774b3b7bcdcb410e25726d832467584ebea686/spec/common-client-libs-sections.json#L548) where we store information shared by all libraries.
1. id — used to identify this function
2. title - the human-readable title
3. slug — the url slug
4. product - the Supabase tool or product that "owns" this function. Since `updateUser()` is an auth function, its product is `auth`
5. type — `updateUser()` is a function and marked as such, but we can also have sections of markdown interspersed with these function definitions.
When a new function is added, this info would need to be manually added to the common file.
#### Function Parameters
The `updateUser()` function takes one parameter: `attributes`. The details for this parameter live in the GoTrue client library, referenced via a `$ref` property in the `supabase-js` [spec file](https://github.com/supabase/supabase/blob/cb04d85262db6a371539dda7df9b00ba5a901e87/spec/supabase_js_v2.yml#L357). Here, the `$ref` property is pointing to the [actual function definition](https://github.com/supabase/gotrue-js/blob/2d60e79073b96ae8c97a6ce18e2601ed1e2a2712/src/GoTrueClient.ts#L590) in the `gotrue-js` library. The accepted values for the `attributes` parameter come from the [type definition](https://github.com/supabase/gotrue-js/blob/16d3deb822097e8640a3a15b94a5690b3beaf11b/src/lib/types.ts#L233).
These individual library spec files are fetched via this [Makefile](https://github.com/supabase/supabase/blob/master/spec/Makefile), and get [transformed](https://github.com/supabase/supabase/blob/master/spec/enrichments/tsdoc_v2/supabase_dereferenced.json) to combine the information we need (params, types, etc). Unless you're a library maintainer, you shouldn't need to worry about this part of the process.
If you are a library maintainer, the last important note about these library files is that the [Makefile](https://github.com/supabase/supabase/blob/master/spec/Makefile) pulls from the `gh-pages` branch of the client library repo. Here's an example of the [`realtime-js` spec file](https://github.com/supabase/realtime-js/blob/gh-pages/v2/spec.json). Updating something like function params or returns, the process is:
1. Get your changes merged to `master` in your library
2. This will kick off an action that automatically updates the spec file in the library's `gh-pages` branch
3. Run `make` in `/spec` of the `supabase/supabase` repo. This will regenerate all of the `tsdoc` files that the docs site uses
4. You should now see the changes you've made in the docs site locally
#### Function Examples
The `updateUser()` function has three examples listed with it. The examples are stored along with the `$ref` property in the [supabase_js_v2 spec file](https://github.com/supabase/supabase/blob/master/spec/supabase_js_v2.yml).
#### Rendering in Next.JS
These reference docs are rendered by Next.JS via a dynamic route using a [`[...slug.tsx]`](https://github.com/supabase/supabase/blob/master/apps/docs/pages/reference/javascript/%5B...slug%5D.tsx). Here, we use the library [spec file](https://github.com/supabase/supabase/blob/bd0514553c627db8f1e8d0b3ae440ccb6759d228/apps/docs/pages/reference/javascript/%5B...slug%5D.tsx#L4) and the [common file](https://github.com/supabase/supabase/blob/bd0514553c627db8f1e8d0b3ae440ccb6759d228/apps/docs/pages/reference/javascript/%5B...slug%5D.tsx#L1) to output the info you see on the page.
### Other reference docs
The reference docs for the [Supabase Management API](https://supabase.com/docs/reference/api) and the [Supabase CLI](https://supabase.com/docs/reference/cli) are a little more straightforward than the client libraries. Both files also have a [common file](https://github.com/supabase/supabase/blob/master/spec/common-cli-sections.json) which handles things like `title`, `id` and `slug`. Both also have a spec file detailing things like parameters, descriptions, and responses ([Management API](https://github.com/supabase/supabase/blob/master/spec/api_v0_openapi.json) / [CLI](https://github.com/supabase/supabase/blob/master/spec/cli_v1_commands.yaml))
On the Next.JS side of things, these work almost exactly the same as the client libaries with a dynamic [`[...slug.tsx]`](https://github.com/supabase/supabase/blob/master/apps/docs/pages/reference/cli/%5B...slug%5D.tsx).
### Misc
#### Search
Search is handled through Algolia. When the site is built, a [search script](https://github.com/supabase/supabase/blob/master/apps/docs/scripts/build-search.ts) runs through all of the types of content, generating search objects that are sent to Algolia to index.
+16 -48
View File
@@ -1,53 +1,21 @@
import providers from '../data/authProviders'
import ButtonCard from './ButtonCard'
import { IconPanel } from 'ui'
import Link from 'next/link'
export default function AuthProviders({ type }: { type: string }) {
const filterProviders = providers.filter((item) => item.authType === type)
export default function AuthProviders() {
return (
<div className="grid md:grid-cols-12 gap-4">
{providers.map((x) => (
<div key={x.name} className="col-span-6">
<ButtonCard to={x.href} title={x.name}>
<div className="px-6 py-4">
<div className="flex justify-between">
<p className="mt-0">{x.name}</p>
<p className="mt-0">
{x.official ? (
<span className={`badge badge--official`}>Official</span>
) : (
<span className={`badge badge--unofficial`}>Unofficial</span>
)}
</p>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
<div
className="code-block"
style={{
width: '100%',
display: 'flex',
justifyContent: 'space-between',
fontSize: '0.7rem',
}}
>
<span>Platform:</span>
<span>{x.platform.toString()}</span>
</div>
<div
className="code-block"
style={{
width: '100%',
display: 'flex',
justifyContent: 'space-between',
fontSize: '0.7rem',
}}
>
<span>Self-Hosted:</span>
<span>{x.selfHosted.toString()}</span>
</div>
</div>
</div>
</ButtonCard>
</div>
))}
</div>
<>
<div className="grid grid-cols-12 gap-10 not-prose py-8">
{filterProviders.map((x) => (
<Link href={`${x.href}`} key={x.name} passHref>
<a className="col-span-6 lg:col-span-4 xl:col-span-3">
<IconPanel title={x.name} icon={x.logo} />
</a>
</Link>
))}
</div>
</>
)
}
+32
View File
@@ -0,0 +1,32 @@
import { useTheme } from 'common/Providers'
import Image from 'next/image'
import { FC } from 'react'
import { createPortal } from 'react-dom'
import clippyImageDark from '../../public/img/clippy-dark.png'
import clippyImage from '../../public/img/clippy.png'
import ClippyBubble from './ClippyBubble'
import ClippyModal from './ClippyModal'
import { useClippy } from './ClippyProvider'
const Clippy: FC = () => {
const { isDarkMode } = useTheme()
const { isOpen, open, close } = useClippy()
return (
<>
{!isOpen && (
<div className="hidden md:flex flex-col items-end gap-1 md:gap-4">
{/* <>
<ClippyBubble onClick={open} />
<div className="w-[80px] lg:w-[120px] p-8 md:p-0">
<Image src={isDarkMode ? clippyImageDark : clippyImage} alt="Clippy" />
</div>
</> */}
</div>
)}
{isOpen && createPortal(<ClippyModal onClose={close} />, document.body)}
</>
)
}
export default Clippy
@@ -0,0 +1,36 @@
import { FC } from 'react'
import { IconCommand } from 'ui'
import Image from 'next/image'
interface Props {
className?: string
onClick?: () => void
}
const ClippyBubble: FC<Props> = ({ className, onClick }) => {
return (
<div
className={`flex gap-2 border bg-white dark:bg-scale-300 relative rounded-xl p-4 lg:p-6 max-w-sm lg:max-w-xs shadow-xl mb-1 lg:mb-4 cursor-pointer hover:bg-scale-200 transition-colors border-scale-500 hover:border-scale-700 overflow-hidden ${
className ? className : ''
}`}
onClick={onClick}
>
<div className="z-0 absolute -left-24 -top-4 w-[260px]">
<Image alt="Clippy blur" width={260} height={260} src="/docs/img/purple-glow-dark.png" />
</div>
What can I help you with?
<div className="flex items-center space-x-1">
<div className="text-scale-1200 flex items-center justify-center h-5 w-10 border rounded bg-scale-500 border-scale-700 gap-1">
<IconCommand size={12} strokeWidth={1.5} />
<span className="text-[12px]">/</span>
</div>
</div>
<div className="absolute -bottom-4 right-[90px] text-scale-600">
<svg viewBox="0 0 1 1" width={16} fill="currentColor">
<path d="M0,0 L1,0 L1,1 Z" />
</svg>
</div>
</div>
)
}
export default ClippyBubble
+267
View File
@@ -0,0 +1,267 @@
import type { CreateCompletionResponse } from 'openai'
import { FC, useCallback, useState } from 'react'
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import { SSE } from 'sse.js'
import clippyImageDark from '../../public/img/clippy-dark.png'
import clippyImage from '../../public/img/clippy.png'
import { useTheme } from 'common/Providers'
import Image from 'next/image'
import {
Button,
IconAlertCircle,
IconAlertTriangle,
IconLoader,
IconSearch,
Input,
Loading,
Modal,
} from 'ui'
import components from '~/components'
type Props = {
onClose?: () => void
}
const questions = [
'How do I get started with Supabase?',
'How do I run Supabase locally?',
'How do I connect to my database?',
'How do I run migrations? ',
'How do I listen to changes in a table?',
'How do I set up authentication?',
]
function getEdgeFunctionUrl() {
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL?.replace(/\/$/, '')
const isPlatform = supabaseUrl?.match(/(supabase\.co)|(supabase\.in)/)
if (isPlatform) {
const [schemeAndProjectId, domain, tld] = supabaseUrl.split('.')
return `${schemeAndProjectId}.functions.${domain}.${tld}`
} else {
return `${supabaseUrl}/functions/v1`
}
}
const edgeFunctionUrl = getEdgeFunctionUrl()
const ClippyModal: FC<Props> = ({ onClose }) => {
const { isDarkMode } = useTheme()
const [query, setQuery] = useState('')
const [answer, setAnswer] = useState('')
const [isLoading, setIsLoading] = useState(false)
const [isResponding, setIsResponding] = useState(false)
const [hasError, setHasError] = useState(false)
const cantHelp = answer?.trim() === "Sorry, I don't know how to help with that."
const status = isLoading
? 'Clippy is searching...'
: isResponding
? 'Clippy is responding...'
: cantHelp || hasError
? 'Clippy has failed you'
: undefined
const handleConfirm = useCallback(async (query: string) => {
setHasError(false)
setAnswer(undefined)
setIsLoading(true)
const eventSource = new SSE(`${edgeFunctionUrl}/clippy-search`, {
headers: {
apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
Authorization: `Bearer ${process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY}`,
'Content-Type': 'application/json',
},
payload: JSON.stringify({ query }),
})
function handleError<T>(err: T) {
setIsLoading(false)
setIsResponding(false)
setHasError(true)
console.error(err)
}
eventSource.addEventListener('error', handleError)
eventSource.addEventListener('message', (e) => {
try {
setIsLoading(false)
if (e.data === '[DONE]') {
setIsResponding(false)
return
}
setIsResponding(true)
const completionResponse: CreateCompletionResponse = JSON.parse(e.data)
const [{ text }] = completionResponse.choices
setAnswer((answer) => {
return (answer ?? '') + text
})
} catch (err) {
handleError(err)
}
})
eventSource.stream()
setIsLoading(true)
}, [])
function handleResetPrompt() {
setQuery('')
setAnswer(undefined)
setIsResponding(false)
setHasError(false)
}
return (
<Modal size="xlarge" visible={true} onCancel={onClose} closable={false} hideFooter>
<div
className={`mx-auto max-h-[50vh] lg:max-h-[75vh] flex flex-col gap-4 rounded-lg p-4 md:pt-6 md:px-6 pb-2 w-full shadow-2xl overflow-hidden border text-left border-scale-500 bg-scale-100 dark:bg-scale-300 cursor-auto relative min-w-[340px]`}
onClick={(e) => e.stopPropagation()}
>
<div className="relative">
<Input
className="w-full"
size="xlarge"
autoFocus
placeholder="Ask me anything about Supabase"
value={query}
onChange={(e) => setQuery(e.target.value)}
icon={<IconSearch size="small" />}
onKeyDown={(e) => {
switch (e.key) {
case 'Enter':
handleConfirm(query)
return
default:
return
}
}}
/>
<div className="absolute right-0 top-0 mt-3 mr-4 hidden md:block">
<Button type="default" size="tiny" onClick={onClose}>
esc
</Button>
</div>
{!isLoading && answer && (
<div className="absolute right-0 top-0 mt-3 mr-16 hidden md:block">
<Button type="text" size="tiny" onClick={handleResetPrompt}>
Try again
</Button>
</div>
)}
</div>
{!isLoading && !answer && !hasError && (
<div className="">
<div className="mt-2">
<h2 className="text-sm text-scale-1100">Not sure where to start?</h2>
<ul className="text-sm mt-4 text-scale-1100 grid md:flex gap-4 flex-wrap max-w-3xl">
{questions.map((question) => (
<li>
<button
className="hover:bg-slate-400 hover:dark:bg-slate-400 px-4 py-2 bg-slate-300 dark:bg-slate-200 rounded-lg transition-colors"
onClick={() => {
setQuery(question)
handleConfirm(question)
}}
>
{question}
</button>
</li>
))}
</ul>
</div>
</div>
)}
{answer && (
<div className="px-4 py-4 rounded-lg overflow-y-auto bg-scale-200">
{cantHelp ? (
<p className="flex flex-col gap-4 items-center p-4">
<div className="grid md:flex items-center gap-2 mt-4 text-center justify-items-center">
<IconAlertCircle />
<p>Sorry, I don&apos;t know how to help with that.</p>
</div>
<Button size="tiny" type="secondary" onClick={handleResetPrompt}>
Try again?
</Button>
</p>
) : (
<div className="prose dark:prose-dark">
<ReactMarkdown
linkTarget="_blank"
remarkPlugins={[remarkGfm]}
transformLinkUri={(href) => {
const supabaseUrl = new URL('https://supabase.com')
const linkUrl = new URL(href, 'https://supabase.com')
if (linkUrl.origin === supabaseUrl.origin) {
return linkUrl.toString()
}
return href
}}
components={components}
>
{answer}
</ReactMarkdown>
</div>
)}
</div>
)}
{isLoading && (
<div className="p-6 grid gap-6 mt-4">
<Loading active>{}</Loading>
<p className="text-lg text-center">Searching for results</p>
</div>
)}
{hasError && (
<div className="p-6 flex flex-col items-center gap-6 mt-4">
<IconAlertTriangle strokeWidth={1.5} size={40} />
<p className="text-lg text-center">Sorry, looks like Clippy is having a hard time!</p>
<p className="text-sm text-center">Please try again in a bit.</p>
<Button size="tiny" type="secondary" onClick={handleResetPrompt}>
Try again?
</Button>
</div>
)}
<div className="border-t border-scale-600 mt-4 text-scale-1100">
<div className="flex justify-between items-center py-2 text-xs">
<div className="flex items-centerp gap-1 pt-3 pb-1">
<span>Powered by OpenAI.</span>
<a href="/blog/chatgpt-supabase-docs" className="underline">
Read the blog post
</a>
</div>
<div className="flex items-center gap-6 py-1">
{status ? (
<span className="bg-scale-400 rounded-lg py-1 px-2 items-center gap-2 hidden md:flex">
{(isLoading || isResponding) && <IconLoader size={14} className="animate-spin" />}
{status}
</span>
) : (
<></>
)}
<Image
width={30}
height={34}
src={isDarkMode ? clippyImageDark : clippyImage}
alt="Clippy"
/>
</div>
</div>
</div>
</div>
</Modal>
)
}
export default ClippyModal
@@ -0,0 +1,72 @@
import {
createContext,
FC,
PropsWithChildren,
useCallback,
useContext,
useEffect,
useState,
} from 'react'
export type ClippyContextValue = {
isOpen: boolean
open: () => void
close: () => void
}
export const ClippyContext = createContext<ClippyContextValue>(null)
export const useClippy = () => {
const { isOpen, open, close } = useContext(ClippyContext)
return { isOpen, open, close }
}
const ClippyProvider: FC<PropsWithChildren<{}>> = ({ children }) => {
const [isModalOpen, setIsModalOpen] = useState(false)
const onOpen = useCallback(() => {
setIsModalOpen(true)
document.body.classList.add('DocSearch--active')
}, [])
const onClose = useCallback(() => {
setIsModalOpen(false)
document.body.classList.remove('DocSearch--active')
}, [])
useSearchKeyboardEvents({
onOpen,
onClose,
})
return (
<ClippyContext.Provider value={{ isOpen: isModalOpen, open: onOpen, close: onClose }}>
{children}
</ClippyContext.Provider>
)
}
function useSearchKeyboardEvents({ onOpen, onClose }) {
useEffect(() => {
function onKeyDown(event: KeyboardEvent) {
switch (event.key) {
case 'Escape':
onClose()
return
case '/':
if (event.metaKey || event.ctrlKey) {
onOpen()
}
return
}
}
window.addEventListener('keydown', onKeyDown)
return () => {
window.removeEventListener('keydown', onKeyDown)
}
}, [onOpen, onClose])
}
export default ClippyProvider
+15 -5
View File
@@ -1,4 +1,4 @@
import { FC } from 'react'
import { Children, FC } from 'react'
import CopyToClipboard from 'react-copy-to-clipboard'
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter'
import monokaiCustomTheme from './CodeBlock.utils'
@@ -6,6 +6,7 @@ import { Button, IconCheck, IconCopy } from 'ui'
import js from 'react-syntax-highlighter/dist/cjs/languages/hljs/javascript'
import ts from 'react-syntax-highlighter/dist/cjs/languages/hljs/typescript'
import csharp from 'react-syntax-highlighter/dist/cjs/languages/hljs/csharp'
import py from 'react-syntax-highlighter/dist/cjs/languages/hljs/python'
import sql from 'react-syntax-highlighter/dist/cjs/languages/hljs/sql'
import bash from 'react-syntax-highlighter/dist/cjs/languages/hljs/bash'
@@ -17,7 +18,7 @@ import { useTheme } from 'common/Providers'
interface Props {
title?: string
language: 'js' | 'jsx' | 'sql' | 'py' | 'bash' | 'ts' | 'dart' | 'json'
language: 'js' | 'jsx' | 'sql' | 'py' | 'bash' | 'ts' | 'dart' | 'json' | 'csharp'
linesToHighlight?: number[]
hideCopy?: boolean
hideLineNumbers?: boolean
@@ -48,10 +49,18 @@ const CodeBlock: FC<Props> = ({
}, 1000)
}
// Extract string when `children` has a single string node
const childrenArray = Children.toArray(children)
const [singleChild] = childrenArray.length === 1 ? childrenArray : []
const singleString = typeof singleChild === 'string' ? singleChild : undefined
let codeValue = value ?? singleString ?? children
codeValue = codeValue?.trimEnd?.() ?? codeValue
// check the length of the string inside the <code> tag
// if it's fewer than 70 characters, add a white-space: pre so it doesn't wrap
const shortCodeBlockClasses =
typeof children === 'string' && children.length < 70 ? 'short-inline-codeblock' : ''
typeof codeValue === 'string' && codeValue.length < 70 ? 'short-inline-codeblock' : ''
let lang = language ? language : className ? className.replace('language-', '') : 'js'
// force jsx to be js highlighted
@@ -62,6 +71,7 @@ const CodeBlock: FC<Props> = ({
SyntaxHighlighter.registerLanguage('sql', sql)
SyntaxHighlighter.registerLanguage('bash', bash)
SyntaxHighlighter.registerLanguage('dart', dart)
SyntaxHighlighter.registerLanguage('csharp', csharp)
SyntaxHighlighter.registerLanguage('json', json)
const large = false
@@ -116,7 +126,7 @@ const CodeBlock: FC<Props> = ({
paddingBottom: '4px',
}}
>
{(value || children)?.trimEnd()}
{codeValue}
</SyntaxHighlighter>
{!hideCopy && (value || children) && className ? (
<div
@@ -126,7 +136,7 @@ const CodeBlock: FC<Props> = ({
`${!title ? 'top-2' : 'top-[3.25rem]'}`,
].join(' ')}
>
{/* //
{/* //
@ts-ignore */}
<CopyToClipboard text={value || children}>
<Button
@@ -14,12 +14,22 @@ import { useInView } from 'react-intersection-observer'
*/
interface Props {
tag: string
tag?: string
parseAnchors?: boolean
customAnchor?: string
}
const Heading: React.FC<Props> = ({ tag, children }) => {
/**
* This TOC is used in .mdx files and in .tsx files.
* In mdx files, we need to parse the content and format them to match the
* expected tocList format (text, link,level).
*
* In tsx files, we can generate this tocList directly. For these files, we don't
* need to parse the <a> and generate anchors
*/
const Heading: React.FC<Props> = ({ tag, parseAnchors = false, customAnchor, children }) => {
const HeadingTag = `${tag}` as any
const anchor = getAnchor(children)
const anchor = customAnchor ? customAnchor : getAnchor(children)
const link = `#${anchor}`
const { ref } = useInView({
@@ -33,7 +43,7 @@ const Heading: React.FC<Props> = ({ tag, children }) => {
return (
<HeadingTag id={anchor} ref={ref} className="group scroll-mt-24">
{removeAnchor(children)}
{parseAnchors ? removeAnchor(children) : children}
{anchor && (
<a href={link} className="ml-2 opacity-0 group-hover:opacity-100 transition">
#
+17
View File
@@ -0,0 +1,17 @@
import React, { ReactNode } from 'react'
import { useFlag } from '~/hooks/useFlag'
interface Props {
children: ReactNode
name: string
}
function Flag({ children, name }: Props) {
const flagValue = useFlag(name)
if (!flagValue) {
return null
}
return <>{children}</>
}
export default Flag
+5
View File
@@ -0,0 +1,5 @@
import { createContext } from 'react'
const FlagContext = createContext({})
export default FlagContext
@@ -0,0 +1,35 @@
import { FC, useEffect, useState } from 'react'
// import createConfigCatClient from 'configcat-js'
import FlagContext from './FlagContext'
const FlagProvider: FC = ({ children }) => {
const [store, setStore] = useState({})
const { Provider } = FlagContext
// useEffect(() => {
// getFlags()
// }, [])
// const getFlags = async () => {
// const setFlagValues = async () => {
// const flagValues = await client.getAllValuesAsync()
// const flagStore: any = {}
// flagValues.forEach((item: any) => {
// flagStore[item.settingKey] = item.settingValue
// })
// setStore(flagStore)
// }
// const client = createConfigCatClient(process.env.NEXT_PUBLIC_CONFIGCAT_SDK_KEY ?? '', {
// configChanged: setFlagValues,
// pollIntervalSeconds: 600,
// })
// await setFlagValues()
// }
return <Provider value={store}>{children}</Provider>
}
export default FlagProvider
+34 -8
View File
@@ -1,12 +1,38 @@
const FooterHelpCallout = () => {
import { ReactMarkdown } from 'react-markdown/lib/react-markdown'
export type FooterHelpCalloutType = 'default' | 'postgres'
const content = {
default: {
title: 'Need some help?',
description: `Not to worry, our specialist engineers are here to help. Submit a support ticket through the [Dashboard](https://app.supabase.com/support/new).`,
},
postgres: {
title: 'Looking for Serverless Postgres?',
description: `Supabase is the fastest way to get started with Postgres in a serverless environment. [Learn more](https://supabase.com/database?utm=postgres-helpers).`,
},
}
const FooterHelpCallout = ({
footerHelpType = 'default',
title,
}: {
footerHelpType: FooterHelpCalloutType
title: any
}) => {
return (
<div className="mt-32">
<div className="min-w-full px-8 py-6 prose bg-scale-300 dark:bg-whiteA-200 rounded">
<h5 className="text-sm m-0 text-scale-1100">Need some help?</h5>
<p className="m-0 text-sm text-scale-900">
Not to worry, our specialist engineers are here to help. Submit a support ticket through
the <a href="https://app.supabase.com/support/new">Dashboard</a>.
</p>
<div className="mt-32 prose prose--remove-p-margin max-w-none">
<div
className="
min-w-full
px-8 py-6
bg-scale-300 dark:bg-whiteA-200
rounded
text-sm text-scale-900
"
>
<h5 className="text-sm text-scale-1100 m-0">{content[footerHelpType].title}</h5>
<ReactMarkdown>{content[footerHelpType].description}</ReactMarkdown>
</div>
</div>
)
+7 -12
View File
@@ -1,7 +1,7 @@
import React, { useState } from 'react'
import KJUR from 'jsrsasign'
import CodeBlock from './CodeBlock/CodeBlock'
import { Button } from 'ui'
import { Button, Select, Input } from 'ui'
const JWT_HEADER = { alg: 'HS256', typ: 'JWT' }
const now = new Date()
@@ -46,9 +46,8 @@ export default function JwtGenerator({}) {
<div>
<div className="grid mb-8">
<label for="secret">JWT Secret:</label>
<input
<Input
id="secret"
className="border rounded bg-gray-200 w-full"
type="text"
placeholder="JWT Secret (at least 32 characters)"
value={jwtSecret}
@@ -57,19 +56,15 @@ export default function JwtGenerator({}) {
</div>
<div className="grid mb-8">
<label for="service">Preconfigured Payload:</label>
<select
id="service"
onChange={handleKeySelection}
className="border rounded bg-gray-200 w-full"
>
<option value="anon">ANON_KEY</option>
<option value="service">SERVICE_KEY</option>
</select>
<Select id="service" onChange={handleKeySelection}>
<Select.Option value="anon">ANON_KEY</Select.Option>
<Select.Option value="service">SERVICE_KEY</Select.Option>
</Select>
</div>
<div className="grid mb-8">
<label for="token">Payload:</label>
<textarea
<Input.TextArea
id="token"
type="text"
rows="5"
+10 -22
View File
@@ -2,22 +2,19 @@ import UserManagementSQLTemplate from './user_management_quickstart_sql_template
import { Tabs } from 'ui'
export const TabPanel = Tabs.Panel
# Project set up
## Project setup
Before we start building we're going to set up our Database and API. This is as simple as starting a new Project in Supabase
and then creating a "schema" inside the database.
Before we start building we're going to set up our Database and API. This is as simple as starting a new Project in Supabase and then creating a "schema" inside the database.
## Create a project
### Create a project
1. Go to [app.supabase.com](https://app.supabase.com).
1. Click on "New Project".
1. [Create a new project](https://app.supabase.com) in the Supabase Dashboard.
1. Enter your project details.
1. Wait for the new database to launch.
## Set up the database schema
### Set up the database schema
Now we are going to set up the database schema. We can use the "User Management Starter" quickstart in the SQL Editor,
or you can just copy/paste the SQL from below and run it yourself.
Now we are going to set up the database schema. We can use the "User Management Starter" quickstart in the SQL Editor, or you can just copy/paste the SQL from below and run it yourself.
<Tabs
scrollable
@@ -31,10 +28,6 @@ or you can just copy/paste the SQL from below and run it yourself.
2. Click **User Management Starter**.
3. Click **Run**.
<video width="99%" muted playsInline controls="true">
<source src="/docs/videos/sql-user-management-starter.mp4" type="video/mp4" muted playsInline />
</video>
</TabPanel>
<TabPanel id="sql" label="SQL">
@@ -43,15 +36,10 @@ or you can just copy/paste the SQL from below and run it yourself.
</TabPanel>
</Tabs>
## Get the API Keys
### Get the API Keys
Now that you've created some database tables, you are ready to insert data using the auto-generated API.
We just need to get the URL and `anon` key from the API settings.
We just need to get the Project URL and `anon` key from the API settings.
1. Go to the [Settings](https://app.supabase.com/project/_/settings) page in the Dashboard.
2. Click **API** in the sidebar.
3. Find your API `URL`, `anon`, and `service_role` keys on this page.
<video width="99%" muted playsInline controls="true">
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
</video>
1. Go to the [API Settings](https://app.supabase.com/project/_/settings/api) page in the Dashboard.
1. Find your Project `URL`, `anon`, and `service_role` keys on this page.
@@ -1,9 +1,5 @@
This example provides the steps to build a basic user management app. It includes:
This tutorial demonstrates how to build a basic user management app. The app authenticates and identifies the user, stores their profile information in the database, and allows the user to log in, update their profile details, and upload a profile photo. The app uses:
- Supabase [Database](/docs/guides/database): a Postgres database for storing your user data.
- Supabase [Auth](/docs/guides/auth): users can sign in with magic links (no passwords, only email).
- Supabase [Storage](/docs/guides/storage): users can upload a photo.
- [Row Level Security](/docs/guides/auth#row-level-security): data is protected so that individuals can only access their own data.
- Instant [APIs](/docs/guides/api): APIs will be automatically generated when you create your database tables.
By the end of this guide you'll have an app which allows users to login and update some basic profile details:
- [Supabase Database](/docs/guides/database) - a Postgres database for storing your user data and [Row Level Security](/docs/guides/auth#row-level-security) so data is protected and users can only access their own information.
- [Supabase Auth](/docs/guides/auth) - users log in through magic links sent to their email (without having to set up passwords).
- [Supabase Storage](/docs/guides/storage) - users can upload a profile photo.
@@ -0,0 +1,108 @@
If you upload additional profile photos, they'll accumulate
in the `avatars` bucket because of their random names with only the latest being referenced
from `public.profiles` and the older versions getting orphaned.
To automatically remove obsolete storage objects, extend the database
triggers. Note that it is not sufficient to delete the objects from the
`storage.objects` table because that would orphan and leak the actual storage objects in
the S3 backend. Instead, invoke the storage API within Postgres via the `http` extension.
Enable the [http extension for the `extensions` schema](https://app.supabase.com/project/_/database/extensions) in the Dashboard.
Then, define the following SQL functions in the SQL Editor to delete
storage objects via the API:
```SQL
create or replace function delete_storage_object(bucket text, object text, out status int, out content varchar)
returns record
language 'plpgsql'
security definer
as $$
declare
project_url varchar := '<YOURPROJECTURL>';
service_role_key varchar := '<YOURSERVICEROLEKEY>'; -- full access needed
url varchar := project_url||'/storage/v1/object/'||bucket||'/'||object;
begin
select
into status, content
result.status::int, result.content::varchar
FROM extensions.http((
'DELETE',
url,
ARRAY[extensions.http_header('authorization','Bearer '||service_role_key)],
NULL,
NULL)::extensions.http_request) as result;
end;
$$;
create or replace function delete_avatar(avatar_url text, out status int, out content varchar)
returns record
language 'plpgsql'
security definer
as $$
begin
select
into status, content
result.status, result.content
from public.delete_storage_object('avatars', avatar_url) as result;
end;
$$;
```
Next, add a trigger that removes any obsolete avatar whenever the
profile is updated or deleted:
```SQL
create or replace function delete_old_avatar()
returns trigger
language 'plpgsql'
security definer
as $$
declare
status int;
content varchar;
begin
if coalesce(old.avatar_url, '') <> ''
and (tg_op = 'DELETE' or (old.avatar_url <> new.avatar_url)) then
select
into status, content
result.status, result.content
from public.delete_avatar(old.avatar_url) as result;
if status <> 200 then
raise warning 'Could not delete avatar: % %', status, content;
end if;
end if;
if tg_op = 'DELETE' then
return old;
end if;
return new;
end;
$$;
create trigger before_profile_changes
before update of avatar_url or delete on public.profiles
for each row execute function public.delete_old_avatar();
```
Finally, delete the `public.profile` row before a user is deleted.
If this step is omitted, you won't be able to delete users without
first manually deleting their avatar image.
```SQL
create or replace function delete_old_profile()
returns trigger
language 'plpgsql'
security definer
as $$
begin
delete from public.profiles where id = old.id;
return old;
end;
$$;
create trigger before_delete_user
before delete on auth.users
for each row execute function public.delete_old_profile();
```
+11 -1
View File
@@ -1,6 +1,6 @@
import Image from 'next/image'
import Link from 'next/link'
import { Button, IconGitHub, IconTwitter } from '~/../../packages/ui'
import { Button, IconGitHub, IconTwitter, IconYoutube } from '~/../../packages/ui'
import footerData from '~/data/footer.json'
import { useTheme } from 'common/Providers'
@@ -20,6 +20,16 @@ const Footer = () => {
))}
</div>
<div className="flex items-center gap-2">
<Button
type="text"
as="a"
// @ts-ignore
href="https://youtube.com/c/supabase"
target="_blank"
rel="noreferrer noopener"
>
<IconYoutube size={16} />
</Button>
<Button
type="text"
as="a"
@@ -178,8 +178,8 @@ export const menuItems: NavMenu = {
name: 'Serverless APIs',
url: undefined,
items: [
{ name: 'Overview', url: '/guides/api', items: [] },
{ name: 'Generating Types', url: '/guides/api/generating-types', items: [] },
{ name: 'Overview', url: '/guides/database/api', items: [] },
{ name: 'Generating Types', url: '/guides/database/api/generating-types', items: [] },
],
},
{
@@ -204,6 +204,11 @@ export const menuItems: NavMenu = {
url: '/guides/database/extensions/plv8',
items: [],
},
{
name: 'PostGIS: Geo queries',
url: '/guides/database/extensions/postgis',
items: [],
},
{
name: 'uuid-ossp: Unique Identifiers',
url: '/guides/database/extensions/uuid-ossp',
@@ -238,6 +243,8 @@ export const menuItems: NavMenu = {
items: [
{ name: 'Overview', url: '/guides/realtime', items: [] },
{ name: 'Quickstart', url: '/guides/realtime/quickstart', items: [] },
{ name: 'Broadcast', url: '/guides/realtime/broadcast', items: [] },
{ name: 'Presence', url: '/guides/realtime/presence', items: [] },
{ name: 'Postgres Changes', url: '/guides/realtime/postgres-changes', items: [] },
{ name: 'Rate Limits', url: '/guides/realtime/rate-limits', items: [] },
],
@@ -259,6 +266,7 @@ export const menuItems: NavMenu = {
{ name: 'Compute Add-ons', url: '/guides/platform/compute-add-ons', items: [] },
{ name: 'Custom Domains', url: '/guides/platform/custom-domains', items: [] },
{ name: 'Database Usage', url: '/guides/platform/database-usage', items: [] },
{ name: 'HTTP Status Codes', url: '/guides/platform/http-status-codes', items: [] },
{ name: 'Logging', url: '/guides/platform/logs', items: [] },
{ name: 'Metrics', url: '/guides/platform/metrics', items: [] },
{
@@ -270,6 +278,8 @@ export const menuItems: NavMenu = {
{ name: 'Performance Tuning', url: '/guides/platform/performance', items: [] },
{ name: 'Permissions', url: '/guides/platform/permissions', items: [] },
{ name: 'Production Readiness', url: '/guides/platform/going-into-prod', items: [] },
{ name: 'SSL Enforcement', url: '/guides/platform/ssl-enforcement', items: [] },
{ name: 'Troubleshooting', url: '/guides/platform/troubleshooting', items: [] },
],
},
{
@@ -300,6 +310,7 @@ export const menuItems: NavMenu = {
{ name: 'Authsignal', url: '/guides/integrations/authsignal', items: [] },
{ name: 'Clerk', url: '/guides/integrations/clerk', items: [] },
{ name: 'keyri', url: '/guides/integrations/keyri', items: [] },
{ name: 'Picket', url: '/guides/integrations/picket', items: [] },
{ name: 'Stytch', url: '/guides/integrations/stytch', items: [] },
{ name: 'SuperTokens', url: '/guides/integrations/supertokens', items: [] },
],
@@ -333,6 +344,7 @@ export const menuItems: NavMenu = {
{ name: 'Draftbit', url: '/guides/integrations/draftbit', items: [] },
{ name: 'Plasmic', url: '/guides/integrations/plasmic', items: [] },
{ name: 'WeWeb', url: '/guides/integrations/weweb', items: [] },
{ name: 'ILLA', url: '/guides/integrations/illa', items: [] },
],
},
],
@@ -3,6 +3,7 @@ import Image from 'next/image'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { Fragment } from 'react'
import { Badge } from '~/../../packages/ui'
const home = [
[
@@ -95,7 +96,21 @@ const home = [
level: 'reference_dart',
},
{
label: 'Tools Reference',
label: 'Python',
icon: '/img/icons/menu/reference-python',
href: '/reference/python/introduction',
level: 'reference_python',
community: true,
},
{
label: 'C#',
icon: '/img/icons/menu/reference-csharp',
href: '/reference/csharp/introduction',
level: 'reference_csharp',
community: true,
},
{
label: 'Tools',
},
{
label: 'Management API',
@@ -106,7 +121,7 @@ const home = [
{
label: 'Supabase CLI',
icon: '/img/icons/menu/reference-cli',
href: '/reference/cli/introduction',
href: '/guides/cli',
level: 'reference_javascript',
},
],
@@ -165,6 +180,7 @@ const NavigationMenuHome = ({ active }) => {
className="w-4 h-4 group-hover:scale-110 ease-out transition-all"
/>
{link.label}
{link.community && <Badge size="small">Community</Badge>}
</li>
</a>
</Link>
@@ -22,6 +22,12 @@ export const REFERENCES: References = {
versions: ['v1', 'v0'],
icon: '/docs/img/libraries/flutter-icon.svg',
},
csharp: {
name: 'C#',
library: 'supabase-csharp',
versions: ['v0'],
icon: '/docs/img/libraries/c-sharp-icon.svg',
},
cli: {
name: 'CLI',
library: undefined,
@@ -42,18 +48,22 @@ export const gettingstarted = {
items: [
{ name: 'Features', url: '/guides/getting-started/features', items: [] },
{ name: 'Architecture', url: '/guides/getting-started/architecture', items: [] },
// {
// name: 'Quickstarts',
// items: [
// { name: 'NextJS', url: '/guides/getting-started/quickstarts/nextjs', items: [] },
// { name: 'ReactJS', url: '/guides/getting-started/quickstarts/reactjs', items: [] },
// ],
// },
{
name: 'Framework Quickstarts',
items: [
{ name: 'React', url: '/guides/getting-started/quickstarts/reactjs', items: [] },
{ name: 'NextJS', url: '/guides/getting-started/quickstarts/nextjs', items: [] },
{ name: 'Flutter', url: '/guides/getting-started/quickstarts/flutter', items: [] },
{ name: 'SvelteKit', url: '/guides/getting-started/quickstarts/sveltekit', items: [] },
{ name: 'SolidJS', url: '/guides/getting-started/quickstarts/solidjs', items: [] },
{ name: 'Vue', url: '/guides/getting-started/quickstarts/vue', items: [] },
],
},
{
name: 'Web app tutorials',
items: [
{
name: 'Next.js',
name: 'NextJS',
url: '/guides/getting-started/tutorials/with-nextjs',
items: [],
},
@@ -319,6 +329,11 @@ export const auth = {
{ name: 'Overview', url: '/guides/auth/auth-helpers', items: [] },
{ name: 'Auth UI', url: '/guides/auth/auth-helpers/auth-ui', items: [] },
{ name: 'Next.js', url: '/guides/auth/auth-helpers/nextjs', items: [] },
{
name: 'Next.js Server Components',
url: '/guides/auth/auth-helpers/nextjs-server-components',
items: [],
},
{ name: 'Remix', url: '/guides/auth/auth-helpers/remix', items: [] },
{ name: 'SvelteKit', url: '/guides/auth/auth-helpers/sveltekit', items: [] },
],
@@ -364,8 +379,8 @@ export const database = {
name: 'Serverless APIs',
url: undefined,
items: [
{ name: 'Overview', url: '/guides/api', items: [] },
{ name: 'Generating Types', url: '/guides/api/generating-types', items: [] },
{ name: 'Overview', url: '/guides/database/api', items: [] },
{ name: 'Generating Types', url: '/guides/database/api/generating-types', items: [] },
],
},
{
@@ -373,28 +388,68 @@ export const database = {
url: undefined,
items: [
{ name: 'Overview', url: '/guides/database/extensions', items: [] },
{
name: 'HypoPG: Hypothetical indexes',
url: '/guides/database/extensions/hypopg',
items: [],
},
{
name: 'plv8: Javascript Language',
url: '/guides/database/extensions/plv8',
items: [],
},
{ name: 'http: RESTful Client', url: '/guides/database/extensions/http', items: [] },
{
name: 'PGroonga: Multilingual Full Text Search',
url: '/guides/database/extensions/pgroonga',
items: [],
},
{
name: 'pg_cron: Job Scheduling',
url: '/guides/database/extensions/pgcron',
items: [],
},
{
name: 'pg_jsonschema: JSON Schema Validation',
url: '/guides/database/extensions/pg_jsonschema',
items: [],
},
{
name: 'pg_net: Async Networking',
url: '/guides/database/extensions/pgnet',
items: [],
},
{
name: 'pgvector: Embeddings and vector similarity',
url: '/guides/database/extensions/pgvector',
items: [],
},
{
name: 'pg_stat_statements: SQL Planning and Execution Statistics',
url: '/guides/database/extensions/pg_stat_statements',
items: [],
},
{
name: 'pg_repack: Storage Optimization',
url: '/guides/database/extensions/pgrepack',
items: [],
},
{
name: 'PostGIS: Geo queries',
url: '/guides/database/extensions/postgis',
items: [],
},
{ name: 'pgTAP: Unit Testing', url: '/guides/database/extensions/pgtap', items: [] },
{
name: 'uuid-ossp: Unique Identifiers',
url: '/guides/database/extensions/uuid-ossp',
items: [],
},
{
name: 'RUM: inverted index for full-text search',
url: '/guides/database/extensions/rum',
items: [],
},
],
},
{
@@ -415,14 +470,36 @@ export const functions = {
label: 'Edge Functions',
url: '/guides/functions',
items: [
{ name: 'Quickstart', url: '/guides/functions/quickstart', items: [] },
{ name: 'Auth', url: '/guides/functions/auth', items: [] },
{ name: 'CI/CD Workflow', url: '/guides/functions/cicd-workflow', items: [] },
{ name: 'Best Practices', url: '/guides/functions/best-practices', items: [] },
{ name: 'Quickstart', url: '/guides/functions/quickstart', items: [] },
{ name: 'CORS', url: '/guides/functions/cors', items: [] },
{ name: 'Debugging', url: '/guides/functions/debugging', items: [] },
{ name: 'Import Maps', url: '/guides/functions/import-maps', items: [] },
{ name: 'Local Development', url: '/guides/functions/local-development', items: [] },
{ name: 'Managing Secrets', url: '/guides/functions/secrets', items: [] },
{ name: 'Schedule Functions', url: '/guides/functions/schedule-functions', items: [] },
{
name: 'Examples',
url: '/guides/functions/examples',
items: [{ name: 'OG Image', url: '/guides/functions/examples/og-image', items: [] }],
items: [
{
name: 'Cloudflare Turnstile',
url: '/guides/functions/examples/cloudflare-turnstile',
items: [],
},
{
name: 'Connect to Postgres',
url: '/guides/functions/examples/connect-to-postgres',
items: [],
},
{ name: 'GitHub Actions', url: '/guides/functions/examples/github-actions', items: [] },
{ name: 'OG Image', url: '/guides/functions/examples/og-image', items: [] },
{ name: 'Storage Caching', url: '/guides/functions/examples/storage-caching', items: [] },
{ name: 'Stripe Webhooks', url: '/guides/functions/examples/stripe-webhooks', items: [] },
{ name: 'Telegram Bot', url: '/guides/functions/examples/telegram-bot', items: [] },
{ name: 'Upstash Redis', url: '/guides/functions/examples/upstash-redis', items: [] },
],
},
],
}
@@ -438,6 +515,8 @@ export const realtime = {
name: 'Channels',
url: undefined,
items: [
{ name: 'Broadcast', url: '/guides/realtime/broadcast', items: [] },
{ name: 'Presence', url: '/guides/realtime/presence', items: [] },
{ name: 'Postgres Changes', url: '/guides/realtime/postgres-changes', items: [] },
{ name: 'Rate Limits', url: '/guides/realtime/rate-limits', items: [] },
],
@@ -458,6 +537,25 @@ export const storage = {
],
}
export const supabase_cli = {
icon: 'reference-cli',
title: 'Supabase CLI',
url: '/guides/cli',
items: [
{ name: 'Overview', url: '/guides/cli', items: [] },
{ name: 'Local Development', url: '/guides/cli/local-development', items: [] },
{ name: 'Managing Environments', url: '/guides/cli/managing-environments', items: [] },
{
name: 'Reference',
url: undefined,
items: [
{ name: 'Commands', url: '/reference/cli/introduction', items: [] },
{ name: 'Configuration', url: '/reference/cli/config', items: [] },
],
},
],
}
export const platform = {
icon: 'platform',
label: 'Platform',
@@ -478,6 +576,7 @@ export const platform = {
items: [
{ name: 'Access Control', url: '/guides/platform/access-control', items: [] },
{ name: 'Database Usage', url: '/guides/platform/database-usage', items: [] },
{ name: 'HTTP Status Codes', url: '/guides/platform/http-status-codes', items: [] },
{ name: 'Logging', url: '/guides/platform/logs', items: [] },
{ name: 'Metrics', url: '/guides/platform/metrics', items: [] },
{
@@ -488,6 +587,7 @@ export const platform = {
{ name: 'Network Restrictions', url: '/guides/platform/network-restrictions', items: [] },
{ name: 'Performance Tuning', url: '/guides/platform/performance', items: [] },
{ name: 'Permissions', url: '/guides/platform/permissions', items: [] },
{ name: 'SSL Enforcement', url: '/guides/platform/ssl-enforcement', items: [] },
],
},
{
@@ -513,6 +613,17 @@ export const platform = {
url: undefined,
items: [{ name: 'Production Readiness', url: '/guides/platform/going-into-prod', items: [] }],
},
{
name: 'Troubleshooting',
url: undefined,
items: [
{
name: 'HTTP and Project Issues',
url: '/guides/platform/troubleshooting',
items: [],
},
],
},
],
}
@@ -521,8 +632,7 @@ export const resources = {
label: 'Resources',
url: '/guides/resources',
items: [
// removing until the examples page is reworked
// { name: 'Examples', url: '/guides/resources/examples', items: [] },
{ name: 'Examples', url: '/guides/resources/examples', items: [] },
{ name: 'Glossary', url: '/guides/resources/glossary', items: [] },
{
name: 'Migrate to Supabase',
@@ -548,25 +658,28 @@ export const resources = {
url: '/guides/resources/migrating-to-supabase/heroku',
items: [],
},
{
name: 'Render',
url: '/guides/resources/migrating-to-supabase/render',
items: [],
},
],
},
{
name: 'Supabase CLI',
name: 'Postgres resources',
url: undefined,
items: [
{
name: 'Overview',
url: '/guides/resources/supabase-cli',
items: [],
name: 'Drop all tables in schema',
url: '/guides/resources/postgres/dropping-all-tables-in-schema',
},
{
name: 'Local Development',
url: '/guides/resources/supabase-cli/local-development',
items: [],
name: 'Select first row per group',
url: '/guides/resources/postgres/first-row-in-group',
},
{
name: 'Managing Environments',
url: '/guides/resources/supabase-cli/managing-environments',
items: [],
name: 'Print PostgreSQL version',
url: '/guides/resources/postgres/which-version-of-postgres',
},
],
},
@@ -612,6 +725,7 @@ export const migrate = {
{ name: 'Firestore Data', url: '/guides/migrations/firestore-data', items: [] },
{ name: 'Firebase Storage', url: '/guides/migrations/firebase-storage', items: [] },
{ name: 'Heroku', url: '/guides/migrations/heroku', items: [] },
{ name: 'Render', url: '/guides/migrations/render', items: [] },
],
}
@@ -665,7 +779,9 @@ export const integrations = {
{ name: 'DhiWise', url: '/guides/integrations/dhiwise', items: [] },
{ name: 'Directus', url: '/guides/integrations/directus', items: [] },
{ name: 'Draftbit', url: '/guides/integrations/draftbit', items: [] },
{ name: 'FlutterFlow', url: '/guides/integrations/flutterflow', items: [] },
{ name: 'Plasmic', url: '/guides/integrations/plasmic', items: [] },
{ name: 'ILLA', url: '/guides/integrations/illa', items: [] },
],
},
{
@@ -697,6 +813,20 @@ export const reference = {
items: [],
icon: '/img/icons/menu/reference-dart',
},
{
name: 'supabase-csharp',
url: '/reference/csharp/start',
level: 'reference_csharp',
items: [],
icon: '/img/icons/menu/reference-csharp',
},
{
name: 'supbase-python',
url: '/reference/python/start',
level: 'reference_python',
items: [],
icon: '/img/icons/menu/reference-python',
},
// {
// name: 'supabase-python',
// url: '/reference/python/start',
@@ -728,31 +858,46 @@ export const reference = {
export const reference_javascript_v1 = {
icon: 'reference-javascript',
title: 'javascript',
title: 'JavaScript',
url: '/guides/reference/javascript',
parent: '/reference',
}
export const reference_javascript_v2 = {
icon: 'reference-javascript',
title: 'javascript',
title: 'JavaScript',
url: '/guides/reference/javascript',
parent: '/reference',
}
export const reference_dart_v0 = {
icon: 'reference-dart',
title: 'dart',
title: 'Flutter',
url: '/guides/reference/dart',
parent: '/reference',
}
export const reference_dart_v1 = {
icon: 'reference-dart',
title: 'dart',
title: 'Flutter',
url: '/guides/reference/dart',
parent: '/reference',
}
export const reference_csharp_v0 = {
icon: 'reference-csharp',
title: 'C#',
url: 'guides/reference/csharp',
parent: '/reference',
}
export const reference_python_v2 = {
icon: 'reference-python',
title: 'Python',
url: '/guides/reference/python',
parent: '/reference',
}
export const reference_cli = {
icon: 'reference-cli',
title: 'Supabase CLI',
@@ -846,6 +991,13 @@ export const references = [
icon: '/docs/img/icons/dart-icon.svg',
url: '/reference/dart/start',
},
{
label: 'supabase-csharp',
versions: ['v0'],
description: 'something about the reference',
icon: '/docs/img/icons/c-sharp-icon.svg',
url: '/reference/csharp/start',
},
],
},
{
@@ -11,6 +11,11 @@ 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' }
// @ts-expect-error
import spec_csharp_v0 from '~/../../spec/supabase_csharp_v0.yml' assert { type: 'yml' }
// @ts-expect-error
import spec_python_v2 from '~/../../spec/supabase_py_v2.yml' assert { type: 'yml' }
// import { gen_v3 } from '~/lib/refGenerator/helpers'
import apiCommonSections from '~/../../spec/common-api-sections.json'
import cliCommonSections from '~/../../spec/common-cli-sections.json'
@@ -47,6 +52,8 @@ export type RefIdOptions =
| 'reference_javascript_v2'
| 'reference_dart_v0'
| 'reference_dart_v1'
| 'reference_csharp_v0'
| 'reference_python_v2'
| 'reference_cli'
| 'reference_api'
| 'reference_self_hosting_auth'
@@ -56,6 +63,8 @@ export type RefIdOptions =
export type RefKeyOptions =
| 'javascript'
| 'dart'
| 'csharp'
| 'python'
| 'cli'
| 'api'
| 'self-hosting-auth'
@@ -65,16 +74,6 @@ export type RefKeyOptions =
const NavigationMenu = () => {
const router = useRouter()
let version = ''
if (router.asPath.includes('v1')) {
version = '_v1'
}
if (router.asPath.includes('v0')) {
version = '_v0'
}
function handleRouteChange(url: string) {
switch (url) {
case `/docs`:
@@ -110,6 +109,9 @@ const NavigationMenu = () => {
case url.includes(`/docs/guides/integrations`) && url:
menuState.setMenuLevelId('integrations')
break
case url.includes(`/docs/guides/cli`) && url:
menuState.setMenuLevelId('supabase_cli')
break
// JS v1
case url.includes(`/docs/reference/javascript/v1`) && url:
menuState.setMenuLevelId('reference_javascript_v1')
@@ -126,6 +128,17 @@ const NavigationMenu = () => {
case url.includes(`/docs/reference/dart`) && url:
menuState.setMenuLevelId('reference_dart_v1')
break
// C# v0 (latest)
case url.includes(`/docs/reference/csharp`) && url:
menuState.setMenuLevelId('reference_csharp_v0')
break
// puthon v2 (latest)
case url.includes(`/docs/reference/python`) && url:
menuState.setMenuLevelId('reference_python_v2')
break
case url.includes(`/docs/reference/cli/config`) && url:
menuState.setMenuLevelId('supabase_cli')
break
case url.includes(`/docs/reference/cli`) && url:
menuState.setMenuLevelId('reference_cli')
break
@@ -165,6 +178,7 @@ const NavigationMenu = () => {
const isFunctionsActive = 'functions' === level
const isRealtimeActive = 'realtime' === level
const isStorageActive = 'storage' === level
const issupabase_cliActive = 'supabase_cli' === level
const isPlatformActive = 'platform' === level
const isResourcesActive = 'resources' === level
const isSelfHosting = 'self_hosting' === level
@@ -175,6 +189,8 @@ const NavigationMenu = () => {
const isReference_Javascript_V2 = 'reference_javascript_v2' === level
const isReference_Dart_V0 = 'reference_dart_v0' === level
const isReference_Dart_V1 = 'reference_dart_v1' === level
const isReference_Csharp_V0 = 'reference_csharp_v0' === level
const isReference_Python_V2 = 'reference_python_v2' === level
const isReference_Cli = 'reference_cli' === level
const isReference_Api = 'reference_api' === level
const isReference_Self_Hosting_Auth = 'reference_self_hosting_auth' === level
@@ -191,6 +207,7 @@ const NavigationMenu = () => {
<NavigationMenuGuideList id={'functions'} active={isFunctionsActive} />
<NavigationMenuGuideList id={'realtime'} active={isRealtimeActive} />
<NavigationMenuGuideList id={'storage'} active={isStorageActive} />
<NavigationMenuGuideList id={'supabase_cli'} active={issupabase_cliActive} />
<NavigationMenuGuideList id={'platform'} active={isPlatformActive} />
<NavigationMenuGuideList id={'resources'} active={isResourcesActive} />
<NavigationMenuGuideList id={'self_hosting'} active={isSelfHosting} />
@@ -229,6 +246,23 @@ const NavigationMenu = () => {
lib="dart"
spec={spec_dart_v1}
/>
<NavigationMenuRefList
key={'reference-csharp-menu-v0'}
id={'reference_csharp_v0'}
active={isReference_Csharp_V0}
commonSections={libCommonSections}
lib="csharp"
spec={spec_csharp_v0}
/>
<NavigationMenuRefList
key={'reference-python-menu-v2'}
id={'reference_python_v2'}
active={isReference_Python_V2}
commonSections={libCommonSections}
lib="python"
spec={spec_python_v2}
/>
{/* // Tools */}
<NavigationMenuRefList
key={'reference-cli-menu'}
@@ -12,8 +12,17 @@ const NavigationMenuGuideList: React.FC<Props> = ({ id, active }) => {
// get url
const url = router.asPath
// remove the end of the url if a deep link
const firstLevelRoute = url?.split('/')?.slice(0, 4)?.join('/')
// We need to decide how deep we want the menu to be for matching urls
// if the links are really deep, we don't want to match all the way out
// But we need to reach out further to make the structure of /resources/postgres/ work
// look at /resources/postgres/ vs /auth/phone-login for how these are different
let firstLevelRoute
if (url.includes('resources/postgres/')) {
firstLevelRoute = url?.split('/')?.slice(0, 5)?.join('/')
} else {
firstLevelRoute = url?.split('/')?.slice(0, 4)?.join('/')
}
return (
<Accordion.Root
@@ -7,10 +7,6 @@ import { IconChevronLeft } from '~/../../packages/ui'
import * as NavItems from './NavigationMenu.constants'
import * as Accordion from '@radix-ui/react-accordion'
interface ImPureProps {
// users: Array<User>
}
const HeaderImage = React.memo(function HeaderImage(props: any) {
const router = useRouter()
const { isDarkMode } = useTheme()
@@ -45,12 +41,6 @@ const ContentAccordionLink = React.memo(function ContentAccordionLink(props: any
const router = useRouter()
const { isDarkMode } = useTheme()
let subItemMenuOpen = false
if (router.asPath.includes(props.subItem.url)) {
subItemMenuOpen = true
}
const LinkContainer = (props) => {
return (
<Link href={props.url} passHref>
@@ -188,7 +178,7 @@ const Content = (props) => {
return (
<div key={x.name}>
{x.items && x.items.length > 0 ? (
<div>
<div className="flex flex-col gap-1">
{x.items.map((subItem, subItemIndex) => {
return (
<ContentAccordionLink
@@ -25,6 +25,10 @@ const NavigationMenuRefList: React.FC<INavigationMenuRefList> = ({
active,
spec,
}) => {
const filteredSections = commonSections.filter((section) => {
return !section.excludes?.includes(id)
})
return (
<div
className={[
@@ -38,7 +42,7 @@ const NavigationMenuRefList: React.FC<INavigationMenuRefList> = ({
!active ? 'opacity-0 invisible absolute h-0 overflow-hidden' : '',
].join(' ')}
>
<NavigationMenuRefListItems id={id} lib={lib} commonSections={commonSections} spec={spec} />
<NavigationMenuRefListItems id={id} lib={lib} commonSections={filteredSections} spec={spec} />
</div>
)
}
@@ -148,7 +148,7 @@ const RenderLink = React.memo(function RenderLink(props: any) {
const SideMenuTitle = ({ title }: { title: string }) => {
return (
<span className="font-mono text-xs uppercase text-scale-1200 font-medium tracking-wider mb-3">
<span className="font-mono text-xs uppercase text-scale-1200 font-medium tracking-wider">
{title}
</span>
)
@@ -179,7 +179,6 @@ const Content: React.FC<INavigationMenuRefList> = ({ id, lib, commonSections, sp
if (!sections) console.error('no common sections imported')
const menu = NavItems[id]
const databaseFunctions = find(sections, { title: 'Database' })
? find(sections, { title: 'Database' }).items
: []
@@ -245,7 +244,7 @@ const Content: React.FC<INavigationMenuRefList> = ({ id, lib, commonSections, sp
<RevVersionDropdown />
</div>
<ul className="function-link-list">
<ul className="function-link-list flex flex-col gap-1">
{sections.map((fn: any, fnIndex) => {
// run allow check
if (isFuncNotInLibraryOrVersion(fn.id, fn.type, allowedKeys)) {
@@ -257,20 +256,18 @@ const Content: React.FC<INavigationMenuRefList> = ({ id, lib, commonSections, sp
<Fragment key={fn.id}>
<RenderLink {...fn} lib={lib} />
{fn.items &&
fn.items
//.filter((item) => item.libs.includes(lib))
.map((item) => (
<RenderLink
{...item}
library={menu.title}
index={fnIndex}
modifierIds={modifierIds}
filterIds={filterIds}
authServerIds={authServerIds}
lib={lib}
allowedKeys={allowedKeys}
/>
))}
fn.items.map((item) => (
<RenderLink
{...item}
library={menu.title}
index={fnIndex}
modifierIds={modifierIds}
filterIds={filterIds}
authServerIds={authServerIds}
lib={lib}
allowedKeys={allowedKeys}
/>
))}
</Fragment>
) : (
<Fragment key={fn.title}>
@@ -6,13 +6,17 @@ import { Button, IconCommand, IconGitHub, IconMoon, IconSearch, IconSun } from '
import { SearchButton } from '~/components/DocSearch'
import { REFERENCES } from '~/components/Navigation/Navigation.constants'
import { useTheme } from 'common/Providers'
import clippyImageDark from '../../../public/img/clippy-dark.png'
import clippyImage from '../../../public/img/clippy.png'
import { getPageType } from '~/lib/helpers'
import { useClippy } from '~/components/Clippy/ClippyProvider'
const TopNavBarRef: FC = () => {
const { isDarkMode, toggleTheme } = useTheme()
const [mounted, setMounted] = useState(false)
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
const { open: openClippy } = useClippy()
const { asPath, push } = useRouter()
const pathSegments = asPath.split('/')
@@ -84,8 +88,8 @@ const TopNavBarRef: FC = () => {
{/* {router.asPath.includes('/reference/') && <RefSwitcher />} */}
</div>
<div className="flex items-center gap-12">
<SearchButton className="w-full lg:w-96">
<div className="flex items-center gap-6">
<SearchButton className="md:w-full lg:w-96 order-2 lg:order-1">
<div
className="
flex
@@ -113,6 +117,16 @@ const TopNavBarRef: FC = () => {
</div>
</div>
</SearchButton>
<div className="flex cursor-pointer order-1 lg:order-2">
<Image
onClick={openClippy}
width={26}
height={29}
src={isDarkMode ? clippyImageDark : clippyImage}
alt="Clippy"
/>
</div>
</div>
<div className="hidden lg:flex grow items-center justify-end gap-3">
<Button
+3 -1
View File
@@ -70,7 +70,9 @@ const Option: FC<IOption> = (props) => {
</span>
<span className="text-scale-900 text-xs">{props.type ?? 'no type'}</span>
</div>
<p className="text-sm text-scale-1000 m-0">{props.description ?? 'nodescription'}</p>
{props.description && <p className="text-sm text-scale-1000 m-0">{props.description}</p>}
{props.children}
</div>
)
+3 -1
View File
@@ -20,7 +20,9 @@ const Param: FC<IParamProps> = (paramItem) => {
</span>
<span className="text-scale-900 text-xs">{paramItem.type ?? 'no type'}</span>
</div>
<p className="text-sm text-scale-1000 m-0">{paramItem.description ?? 'nodescription'}</p>
{paramItem.description && (
<p className="text-sm text-scale-1000 m-0">{paramItem.description} </p>
)}
{paramItem.children}
</li>
)
+6 -1
View File
@@ -1,4 +1,5 @@
import { Button, Tabs, Alert } from 'ui'
import { Button, Tabs, Alert, GlassPanel } from 'ui'
import Link from 'next/link'
// Common components
import Admonition from './Admonition'
@@ -19,6 +20,7 @@ import QuickstartIntro from './MDX/quickstart_intro.mdx'
import ProjectSetup from './MDX/project_setup.mdx'
import SocialProviderSetup from './MDX/social_provider_setup.mdx'
import SocialProviderSettingsSupabase from './MDX/social_provider_settings_supabase.mdx'
import StorageManagement from './MDX/storage_management.mdx'
import { Mermaid } from 'mdx-mermaid/lib/Mermaid'
import InlineCodeTag from './CustomHTMLElements/InlineCode'
import React from 'react'
@@ -38,6 +40,8 @@ const components = {
Button,
ButtonCard,
CodeBlock,
GlassPanel,
Link,
Frameworks,
AuthProviders,
FunctionsExamples,
@@ -46,6 +50,7 @@ const components = {
ProjectSetup,
SocialProviderSetup,
SocialProviderSettingsSupabase,
StorageManagement,
Mermaid,
Extensions,
Alert: (props: any) => (
@@ -1,25 +1,12 @@
import { IconChevronRight } from '~/../../packages/ui'
import ReactMarkdown from 'react-markdown'
import { IconChevronRight, Tabs } from '~/../../packages/ui'
// @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'
import RefSubLayout from '~/layouts/ref/RefSubLayout'
interface ICommonFunc {
id: string
title: string
slug: string
product: string
libs: string
items: ICommonFunc[]
}
interface IRefFunctionSection {
funcData: any
commonFuncData: ICommonFunc
spec: any
}
import RefDetailCollapse from './RefDetailCollapse'
export type Flag = {
id: string
@@ -37,16 +24,25 @@ export type AcceptedValue = {
description?: string
}
export type Example = {
id: string
name: string
code: string
response: string
description?: string
}
export type Command = {
id: string
title: string
description: string
flags?: Flag[]
summary: string
tags?: []
links?: []
subcommands?: []
tags?: string[]
links?: string[]
subcommands?: string[]
usage?: string
examples?: Example[]
}
const CliCommandSection = (props) => {
@@ -70,17 +66,17 @@ const CliCommandSection = (props) => {
' mb-16',
].join(' ')}
>
<p className="capitalize mb-4 scroll-mt-16 mt-0 text-scale-1100 text-base">
{command.summary}
</p>
{command.description ? (
<div className="prose">
<ReactMarkdown>{command.description}</ReactMarkdown>
</div>
) : (
<p className="capitalize mb-4 scroll-mt-16 mt-0 text-scale-1100 text-base">
{command.summary}
</p>
)}
</header>
{/* {command.usage && (
<CodeBlock language="bash" className="relative">
{command.usage}
</CodeBlock>
)} */}
{command.subcommands.length > 0 && (
<div className="mb-3">
<h3 className="text-lg text-scale-1200 mb-3">Available Commands</h3>
@@ -126,13 +122,82 @@ const CliCommandSection = (props) => {
</div>
</div>
</RefSubLayout.Details>
<RefSubLayout.Examples>
{command.usage && (
<CodeBlock language="bash" className="relative">
{command.usage}
</CodeBlock>
)}
</RefSubLayout.Examples>
{(command.examples || command.usage) && (
<RefSubLayout.Examples>
<div className="overflow-hidden w-full">
<Tabs
defaultActiveId={`${command.id}-basic-usage`}
size="tiny"
type="rounded-pills"
scrollable
>
{command.examples ? (
command.examples.map((example) => {
const exampleId = `${command.id}-${example.id}`
return (
<Tabs.Panel
id={exampleId}
key={exampleId}
label={example.name}
className="flex flex-col gap-3"
>
<CodeBlock
className="useless-code-block-class"
language="bash"
hideLineNumbers={true}
>
{example.code}
</CodeBlock>
<RefDetailCollapse
id={`${exampleId}-response`}
label="Response"
defaultOpen={false}
>
<CodeBlock
className="useless-code-block-class rounded !rounded-tl-none !rounded-tr-none border border-scale-500"
language="bash"
hideLineNumbers={true}
>
{example.response}
</CodeBlock>
</RefDetailCollapse>
{example.description && (
<RefDetailCollapse
id={`${exampleId}-notes`}
label="Notes"
defaultOpen={false}
>
<div className="bg-scale-300 border border-scale-500 rounded !rounded-tl-none !rounded-tr-none prose max-w-none px-5 py-2">
<ReactMarkdown className="text-sm">{example.description}</ReactMarkdown>
</div>
</RefDetailCollapse>
)}
</Tabs.Panel>
)
})
) : (
// TODO: remove this block once all commands have examples
<Tabs.Panel
id={`${command.id}-basic-usage`}
key={`${command.id}-basic-usage`}
label="Basic usage"
className="flex flex-col gap-3"
>
<CodeBlock
className="useless-code-block-class"
language="bash"
hideLineNumbers={true}
>
{command.usage}
</CodeBlock>
</Tabs.Panel>
)}
</Tabs>
</div>
</RefSubLayout.Examples>
)}
</RefSubLayout.Section>
)
}
@@ -10,22 +10,7 @@ import { extractTsDocNode, generateParameters } from '~/lib/refGenerator/helpers
import RefDetailCollapse from '~/components/reference/RefDetailCollapse'
import { Fragment } from 'react'
interface ICommonFunc {
id: string
title: string
slug: string
product: string
libs: string
items: ICommonFunc[]
}
interface IRefFunctionSection {
funcData: any
commonFuncData: ICommonFunc
spec: any
typeSpec?: any
}
import { IRefFunctionSection } from './Reference.types'
const RefFunctionSection: React.FC<IRefFunctionSection> = (props) => {
const item = props.spec.functions.find((x: any) => x.id === props.funcData.id)
@@ -130,6 +115,16 @@ const RefFunctionSection: React.FC<IRefFunctionSection> = (props) => {
{item.examples &&
item.examples.map((example, exampleIndex) => {
const exampleString = ''
const codeBlockLang = example?.code?.startsWith('```js')
? 'js'
: example?.code?.startsWith('```ts')
? 'ts'
: example?.code?.startsWith('```dart')
? 'dart'
: example?.code?.startsWith('```c#')
? 'csharp'
: 'js'
// `
// import { createClient } from '@supabase/supabase-js'
@@ -150,18 +145,25 @@ const RefFunctionSection: React.FC<IRefFunctionSection> = (props) => {
label={example.name}
className="flex flex-col gap-3"
>
{example.description && (
<div className="prose">
<ReactMarkdown className="text-sm">
{example.description}
</ReactMarkdown>
</div>
)}
<CodeBlock
className="useless-code-block-class"
language={codeBlockLang}
hideLineNumbers={true}
>
{exampleString +
(example.code &&
example.code
.replace(/```/g, '')
.replace('js', '')
.replace('ts', '')
.replace('dart', '')
.replace('c#', ''))}
</CodeBlock>
{((tables && tables.length > 0) || sql) && (
<RefDetailCollapse
id={`${example.id}-${exampleIndex}-data`}
label="Example data source"
label="Data source"
defaultOpen={false}
>
<>
@@ -196,34 +198,35 @@ const RefFunctionSection: React.FC<IRefFunctionSection> = (props) => {
</RefDetailCollapse>
)}
<CodeBlock
className="useless-code-block-class"
language="js"
hideLineNumbers={true}
>
{exampleString +
(example.code &&
example.code
.replace(/```/g, '')
.replace('js', '')
.replace('ts', ''))}
</CodeBlock>
{response && (
<RefDetailCollapse
id={`${example.id}-${exampleIndex}-response`}
label="Example response"
label="Response"
defaultOpen={false}
>
<CodeBlock
className="useless-code-block-class"
language="js"
className="useless-code-block-class rounded !rounded-tl-none !rounded-tr-none border border-scale-500"
language={codeBlockLang}
hideLineNumbers={true}
>
{response.replace(/```/g, '').replace('json', '')}
</CodeBlock>
</RefDetailCollapse>
)}
{example.description && (
<RefDetailCollapse
id={`${example.id}-${exampleIndex}-notes`}
label="Notes"
defaultOpen={false}
>
<div className="bg-scale-300 border border-scale-500 rounded !rounded-tl-none !rounded-tr-none prose max-w-none px-5 py-2">
<ReactMarkdown className="text-sm">
{example.description}
</ReactMarkdown>
</div>
</RefDetailCollapse>
)}
</Tabs.Panel>
)
})}
@@ -1,35 +1,31 @@
import { MDXRemote } from 'next-mdx-remote'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import components from '~/components'
import RefEducationSection from '~/components/reference/RefEducationSection'
import RefFunctionSection from '~/components/reference/RefFunctionSection'
import RefSubLayout from '~/layouts/ref/RefSubLayout'
import ApiOperationSection from './ApiOperationSection'
import CliCommandSection from './CLICommandSection'
import { IAPISpec, ICommonFunc, IRefStaticDoc, ISpec, TypeSpec } from './Reference.types'
interface Props {
sections: any[] // to do
spec: any // to do
typeSpec: any // to do
pageProps: any // to do, from staticProps
interface RefSectionHandlerProps {
sections: ICommonFunc[]
spec?: ISpec | IAPISpec
typeSpec?: TypeSpec
pageProps: { docs: IRefStaticDoc[] }
type: 'client-lib' | 'cli' | 'api'
}
const RefSectionHandler = (props) => {
const RefSectionHandler = (props: RefSectionHandlerProps) => {
const router = useRouter()
const slug = router.query.slug[0]
const isNewDocs = process.env.NEXT_PUBLIC_NEW_DOCS === 'true'
// When user lands on a url like http://supabase.com/docs/reference/javascript/sign-up
// find the #sign-up element and scroll to that
useEffect(() => {
if (isNewDocs && document && slug !== 'start') {
if (document && slug !== 'start') {
document.querySelector(`#${slug}`) && document.querySelector(`#${slug}`).scrollIntoView()
}
})
@@ -0,0 +1,109 @@
import { enrichedOperation } from '~/lib/refGenerator/helpers'
export interface ISpec {
openref: any
info: {
id: string
title: string
description: string
definition: string
libraries: any
slugPrefix: string
specUrl: string
}
functions: IFunctionDefinition[]
}
export interface IAPISpec {
info: {
title: string
description?: string
version: string
contact?: {}
}
operations: enrichedOperation[]
sections: any
}
export interface IFunctionDefinition {
title: string
id: string
$ref: string
description: string
examples?: []
}
export interface ICommonFunc {
id: string
title: string
slug: string
product: string
type: string
parent?: string
items: ICommonFunc[]
}
export interface IRefFunctionSection {
funcData: any
commonFuncData: ICommonFunc
spec: any
typeSpec?: TypeSpec
}
export interface IRefStaticDoc {
id: string
title: string
meta: {
id: string
title: string
hideTitle: boolean
}
content: {
compiledSource: string
frontmatter: {}
scope: {}
}
}
export type TypeSpec = {
name: string
children: TypeSpecChild[]
}
export type TypeSpecChild = {
id: number
name: string
kind: number
kindString?: string
flags?: {}
originalName?: string
children?: TypeSpecChild[]
defaultValue?: string
default?: any
groups?: any
sources?: any
target?: any
comment?: TypeSpecChildComment
typeParameter?: any
implementedTypes?: any
extendedTypes?: any
dereferenced?: TypeSpecChild
extendedBy?: any
indexSignature?: any
extendsType?: any
objectType?: any
trueType?: any
falseType?: any
type?: any
signatures?: any
overwrites?: any
inheritedFrom?: any
implementationOf?: any
}
export interface TypeSpecChildComment {
shortText?: string
text?: string | null
returns?: string | null
tags?: any
}
@@ -15,6 +15,7 @@ const CliGlobalFlagsHandler = () => {
return (
<Param
{...flag}
id={`${spec.id}-${flag.id}`}
isOptional={flag.required === undefined ? true : !flag.required}
></Param>
)
+38 -17
View File
@@ -1,172 +1,193 @@
const authProviders = [
{
name: 'Apple',
// logo: '/img/libraries/dart-icon.svg',
logo: '/docs/img/icons/apple-icon',
href: '/guides/auth/social-login/auth-apple',
official: true,
supporter: 'Supabase',
platform: true,
selfHosted: true,
authType: 'social',
},
{
name: 'Azure',
// logo: '/img/libraries/dart-icon.svg',
logo: '/docs/img/icons/microsoft-icon',
href: '/guides/auth/social-login/auth-azure',
official: false,
supporter: 'TBD',
platform: true,
selfHosted: true,
authType: 'social',
},
{
name: 'Bitbucket',
// logo: '/img/libraries/dart-icon.svg',
logo: '/docs/img/icons/bitbucket-icon',
href: '/guides/auth/social-login/auth-bitbucket',
official: true,
supporter: 'Supabase',
platform: true,
selfHosted: true,
authType: 'social',
},
{
name: 'Discord',
// logo: '/img/libraries/dart-icon.svg',
logo: '/docs/img/icons/discord-icon',
href: '/guides/auth/social-login/auth-discord',
official: true,
supporter: 'Supabase',
platform: true,
selfHosted: true,
authType: 'social',
},
{
name: 'Facebook',
// logo: '/img/libraries/dart-icon.svg',
logo: '/docs/img/icons/facebook-icon',
href: '/guides/auth/social-login/auth-facebook',
official: true,
supporter: 'Supabase',
platform: true,
selfHosted: true,
authType: 'social',
},
{
name: 'GitHub',
// logo: '/img/libraries/dart-icon.svg',
logo: '/docs/img/icons/github-icon',
href: '/guides/auth/social-login/auth-github',
official: true,
supporter: 'Supabase',
platform: true,
selfHosted: true,
authType: 'social',
},
{
name: 'GitLab',
// logo: '/img/libraries/dart-icon.svg',
logo: '/docs/img/icons/gitlab-icon',
href: '/guides/auth/social-login/auth-gitlab',
official: true,
supporter: 'Supabase',
platform: true,
selfHosted: true,
authType: 'social',
},
{
name: 'Google',
// logo: '/img/libraries/dart-icon.svg',
logo: '/docs/img/icons/google-icon',
href: '/guides/auth/social-login/auth-google',
official: true,
supporter: 'Supabase',
platform: true,
selfHosted: true,
authType: 'social',
},
{
name: 'Keycloak',
logo: '/docs/img/icons/keycloak-icon',
href: '/guides/auth/social-login/auth-keycloak',
official: true,
supporter: 'Supabase',
platform: true,
selfHosted: true,
authType: 'social',
},
{
name: 'LinkedIn',
// logo: '/img/libraries/dart-icon.svg',
logo: '/docs/img/icons/linkedin-icon',
href: '/guides/auth/social-login/auth-linkedin',
official: true,
supporter: 'Supabase',
platform: true,
selfHosted: true,
authType: 'social',
},
{
name: 'MessageBird',
// logo: '/img/libraries/dart-icon.svg',
logo: '/docs/img/icons/messagebird-icon',
href: '/guides/auth/phone-login/messagebird',
official: false,
supporter: 'MessageBird',
platform: true,
selfHosted: true,
authType: 'phone'
},
{
name: 'Notion',
// logo: '/img/libraries/notion-icon.svg',
logo: '/docs/img/icons/notion-icon',
href: '/guides/auth/social-login/auth-notion',
official: true,
supporter: 'Supabase',
platform: true,
selfHosted: true,
authType: 'social',
},
{
name: 'Slack',
// logo: '/img/libraries/dart-icon.svg',
logo: '/docs/img/icons/slack-icon',
href: '/guides/auth/social-login/auth-slack',
official: true,
supporter: 'Supabase',
platform: true,
selfHosted: true,
authType: 'social',
},
{
name: 'Spotify',
// logo: '/img/libraries/dart-icon.svg',
logo: '/docs/img/icons/spotify-icon',
href: '/guides/auth/social-login/auth-spotify',
official: true,
supporter: 'Supabase',
platform: true,
selfHosted: true,
authType: 'social',
},
{
name: 'Twitter',
// logo: '/img/libraries/dart-icon.svg',
logo: '/docs/img/icons/twitter-icon',
href: '/guides/auth/social-login/auth-twitter',
official: true,
supporter: 'Supabase',
platform: true,
selfHosted: true,
authType: 'social',
},
{
name: 'Twitch',
// logo: '/img/libraries/dart-icon.svg',
logo: '/docs/img/icons/twitch-icon',
href: '/guides/auth/social-login/auth-twitch',
official: true,
supporter: 'Supabase',
platform: true,
selfHosted: true,
authType: 'social',
},
{
name: 'Zoom',
// logo: '/img/libraries/dart-icon.svg',
logo: '/docs/img/icons/zoom-icon',
href: '/guides/auth/social-login/auth-zoom',
official: true,
supporter: 'Supabase',
platform: true,
selfHosted: true,
authType: 'social',
},
{
name: 'Twilio',
// logo: '/img/libraries/dart-icon.svg',
logo: '/docs/img/icons/twilio-icon',
href: '/guides/auth/phone-login/twilio',
official: true,
supporter: 'Supabase',
platform: true,
selfHosted: true,
authType: 'phone',
},
{
name: 'Vonage',
logo: '/docs/img/icons/vonage-icon',
href: '/guides/auth/phone-login/vonage',
official: false,
supporter: 'Supabase',
platform: true,
selfHosted: true,
authType: 'phone',
},
]
+21
View File
@@ -468,11 +468,32 @@
"installed_version": "1.1",
"comment": "generate universally unique identifiers (UUIDs)"
},
{
"name": "vector",
"schema": "extensions",
"default_version": "0.4.0",
"installed_version": null,
"comment": "vector data type with similarity search"
},
{
"name": "xml2",
"schema": null,
"default_version": "1.1",
"installed_version": null,
"comment": "XPath querying and XSLT"
},
{
"name": "rum",
"schema": "extensions",
"default_version": "1.3.13",
"installed_version": "1.3.13",
"comment": "GIN-like index for text search"
},
{
"name": "pg_repack",
"schema": "extensions",
"default_version": "1.4.8",
"installed_version": "1.4.8",
"comment": "optimize physical storage and remove bloat from tables and indexes"
}
]
+134
View File
@@ -0,0 +1,134 @@
const Nav = [
{
label: 'Getting Started',
items: [
{ name: 'Supabase C# Library', url: '/reference/csharp', items: [] },
{ name: 'Installing', url: '/reference/csharp/installing', items: [] },
{ name: 'Initializing', url: '/reference/csharp/initializing', items: [] },
],
},
{
label: 'Auth',
items: [
{ name: 'signUp()', url: '/reference/csharp/auth-signup', items: [] },
{
name: 'signInWithPassword()',
url: '/reference/csharp/auth-signinwithpassword',
items: [],
},
{ name: 'signInWithOtp()', url: '/reference/csharp/auth-signinwithotp', items: [] },
{ name: 'signInWithOAuth()', url: '/reference/csharp/auth-signinwithoauth', items: [] },
{ name: 'signOut()', url: '/reference/csharp/auth-signout', items: [] },
{ name: 'verifyOtp()', url: '/reference/csharp/auth-verifyotp', items: [] },
{ name: 'currentSession', url: '/reference/csharp/auth-currentsession', items: [] },
{ name: 'currentUser', url: '/reference/csharp/auth-currentuser', items: [] },
{ name: 'updateUser()', url: '/reference/csharp/auth-updateuser', items: [] },
{ name: 'onAuthStateChange()', url: '/reference/csharp/auth-onauthstatechange', items: [] },
{
name: 'resetPasswordForEmail()',
url: '/reference/csharp/auth-resetpasswordforemail',
items: [],
},
],
},
{
label: 'Functions',
items: [{ name: 'invoke()', url: '/reference/csharp/invoke', items: [] }],
},
{
label: 'Database',
items: [
{ name: 'Fetch data: select()', url: '/reference/csharp/select', items: [] },
{ name: 'Create data: insert()', url: '/reference/csharp/insert', items: [] },
{ name: 'Modify data: update()', url: '/reference/csharp/update', items: [] },
{ name: 'Upsert data: upsert()', url: '/reference/csharp/upsert', items: [] },
{ name: 'Delete data: delete()', url: '/reference/csharp/delete', items: [] },
{ name: 'Stored Procedures: rpc()', url: '/reference/csharp/rpc', items: [] },
{
name: 'Filters',
url: undefined,
items: [
{ name: 'Using Filters', url: '/reference/csharp/using-filters', items: [] },
{ name: 'eq()', url: '/reference/csharp/eq', items: [] },
{ name: 'neq()', url: '/reference/csharp/neq', items: [] },
{ name: 'gt()', url: '/reference/csharp/gt', items: [] },
{ name: 'gte()', url: '/reference/csharp/gte', items: [] },
{ name: 'lt()', url: '/reference/csharp/lt', items: [] },
{ name: 'lte()', url: '/reference/csharp/lte', items: [] },
{ name: 'like()', url: '/reference/csharp/like', items: [] },
{ name: 'ilike()', url: '/reference/csharp/ilike', items: [] },
{ name: 'is_()', url: '/reference/csharp/is_', items: [] },
{ name: 'in_()', url: '/reference/csharp/in_', items: [] },
{ name: 'contains()', url: '/reference/csharp/contains', items: [] },
{ name: 'containedBy()', url: '/reference/csharp/containedby', items: [] },
{ name: 'rangeGt()', url: '/reference/csharp/rangegt', items: [] },
{ name: 'rangeGte()', url: '/reference/csharp/rangegte', items: [] },
{ name: 'rangeLt()', url: '/reference/csharp/rangelt', items: [] },
{ name: 'rangeLte()', url: '/reference/csharp/rangelte', items: [] },
{ name: 'rangeAdjacent()', url: '/reference/csharp/rangeadjacent', items: [] },
{ name: 'overlaps()', url: '/reference/csharp/overlaps', items: [] },
{ name: 'textSearch()', url: '/reference/csharp/textsearch', items: [] },
{ name: 'match()', url: '/reference/csharp/match', items: [] },
{ name: 'not()', url: '/reference/csharp/not', items: [] },
{ name: 'or()', url: '/reference/csharp/or', items: [] },
{ name: 'filter()', url: '/reference/csharp/filter', items: [] },
],
},
{
name: 'Modifiers',
url: undefined,
items: [
{ name: 'Using Modifiers', url: '/reference/csharp/using-modifiers', items: [] },
{ name: 'order()', url: '/reference/csharp/order', items: [] },
{ name: 'limit()', url: '/reference/csharp/limit', items: [] },
{ name: 'range()', url: '/reference/csharp/range', items: [] },
{ name: 'single()', url: '/reference/csharp/single', items: [] },
],
},
],
},
{
label: 'Realtime',
items: [
{ name: 'stream()', url: '/reference/csharp/stream', items: [] },
{ name: 'on().subscribe()', url: '/reference/csharp/subscribe', items: [] },
{ name: 'removeChannel()', url: '/reference/csharp/removechannel', items: [] },
{ name: 'removeAllChannels()', url: '/reference/csharp/removeallchannels', items: [] },
{ name: 'getChannels()', url: '/reference/csharp/getchannels', items: [] },
],
},
{
label: 'Storage',
items: [
{ name: 'createBucket()', url: '/reference/csharp/storage-createbucket', items: [] },
{ name: 'getBucket()', url: '/reference/csharp/storage-getbucket', items: [] },
{ name: 'listBuckets()', url: '/reference/csharp/storage-listbuckets', items: [] },
{ name: 'updateBucket()', url: '/reference/csharp/storage-updatebucket', items: [] },
{ name: 'deleteBucket()', url: '/reference/csharp/storage-deletebucket', items: [] },
{ name: 'emptyBucket()', url: '/reference/csharp/storage-emptybucket', items: [] },
{ name: 'from.upload()', url: '/reference/csharp/storage-from-upload', items: [] },
{ name: 'from.download()', url: '/reference/csharp/storage-from-download', items: [] },
{ name: 'from.list()', url: '/reference/csharp/storage-from-list', items: [] },
{ name: 'from.update()', url: '/reference/csharp/storage-from-update', items: [] },
{ name: 'from.move()', url: '/reference/csharp/storage-from-move', items: [] },
{ name: 'from.remove()', url: '/reference/csharp/storage-from-remove', items: [] },
{
name: 'from.createSignedUrl()',
url: '/reference/csharp/storage-from-createsignedurl',
items: [],
},
{
name: 'from.createSignedUrls()',
url: '/reference/csharp/storage-from-createsignedurls',
items: [],
},
{
name: 'from.getPublicUrl()',
url: '/reference/csharp/storage-from-getpublicurl',
items: [],
},
],
},
]
export default Nav
+5 -2
View File
@@ -2,7 +2,7 @@ const nonGeneratedReferencePages = [
'docs/reference/javascript',
'docs/reference/javascript/installing',
'docs/reference/javascript/release-notes',
'docs/reference/javascript/upgrade-guide',
'docs/reference/javascript/v1/upgrade-guide',
'docs/reference/javascript/typescript-support',
'docs/reference/javascript/v1',
'docs/reference/javascript/v1/installing',
@@ -11,8 +11,11 @@ const nonGeneratedReferencePages = [
'docs/reference/dart',
'docs/reference/dart/installing',
'docs/reference/dart/initializing',
'docs/reference/dart/upgrade-guide',
'docs/reference/dart/v0/upgrade-guide',
'docs/reference/dart/v0',
'docs/reference/csharp',
'docs/reference/csharp/installing',
'docs/reference/csharp/initializing',
'docs/reference/api',
'docs/reference/api/release-notes',
'docs/reference/cli',
+1 -1
View File
@@ -17,7 +17,7 @@ hideTitle: true
The Supabase CLI provides tools to develop your project locally and deploy to the Supabase Platform.
The CLI is still under development, but it contains all the functionality for working with your Supabase projects and the Supabase Platform.
- Run Supabase locally: [`supabase start`](/docs/reference/cli/usage#supabase-start)
- Run Supabase locally: [`supabase init`](/docs/reference/cli/usage#supabase-init) and [`supabase start`](/docs/reference/cli/usage#supabase-start)
- Manage database migrations: [`supabase migration`](/docs/reference/cli/usage#supabase-migration)
- CI/CD for releasing to production: [`supabase db push`](/docs/reference/cli/usage#supabase-db-push)
- Manage your Supabase projects: [`supabase projects`](/docs/reference/cli/usage#supabase-projects)
+33
View File
@@ -0,0 +1,33 @@
---
id: installing
title: 'Installing & Initialization'
slug: installing
custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supabase.yml
---
### Install from NuGet
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
You can install Supabase package from [nuget.org](https://www.nuget.org/packages/supabase-csharp/)
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
size="small"
type="underlined"
defaultActiveId="csharp">
<TabPanel id="csharp" label="Terminal">
```sh Terminal
dotnet add package supabase-csharp
```
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
@@ -0,0 +1,31 @@
---
id: introduction
title: Introduction
hideTitle: true
---
<div className="flex items-start gap-6 not-prose" id="introduction">
<img src="/docs/img/icons/menu/reference-csharp.svg" className="w-8 h-8 rounded" />
<div className="flex flex-col gap-2">
<h1 className="text-3xl text-scale-1200 m-0">C# Client Library</h1>
<h2 className="text-base font-mono text-scale-1100">@supabase-community/supabase-csharp</h2>
</div>
</div>
<div className="max-w-xl">
This reference documents every object and method available in Supabase's C# library,
[supabase-csharp](https://www.nuget.org/packages/supabase-csharp). You can use supabase-csharp to
interact with your Postgres database, listen to database changes, invoke Deno Edge Functions,
build login and user management functionality, and manage large files.
</div>
<div className="max-w-xl bg-slate-300 px-4 py-2 rounded-md">
<p>The C# client library is created and maintained by the Supabase community, and is not an official library. Please be tolerant of areas where the library is still being developed, and — as with all the libraries — feel free to contribute wherever you find issues.</p>
<p>
Huge thanks to official maintainer, [Joseph Schultz](https://github.com/acupofjose), and to [Ben
Randall](https://github.com/veleek) and [Rhuan Barros](https://github.com/rhuanbarros) for their
help.
</p>
</div>
+206
View File
@@ -0,0 +1,206 @@
---
id: release-notes
title: Release Notes
---
## 0.8.0 - 2023-01-31
- Update dependency: realtime-csharp@5.0.0
- Re: [#21](https://github.com/supabase-community/realtime-csharp/pull/21) Provide API for `presence`, `broadcast` and `postgres_changes`
- [Major, New] `Channel.PostgresChanges` event will receive the wildcard `*` changes event, not `Channel.OnMessage`.
- [Major] `Channel.OnInsert`, `Channel.OnUpdate`, and `Channel.OnDelete` now conform to the server's payload of `Response.Payload.**Data**`
- [Major] `Channel.OnInsert`, `Channel.OnUpdate`, and `Channel.OnDelete` now return `PostgresChangesEventArgs`
- [Minor] Rename `Channel` to `RealtimeChannel`
- Supports better handling of disconnects in `RealtimeSocket` and adds a `Client.OnReconnect` event.
- [Minor] Moves `ChannelOptions` to `Channel.ChannelOptions`
- [Minor] Moves `ChannelStateChangedEventArgs` to `Channel.ChannelStateChangedEventArgs`
- [Minor] Moves `Push` to `Channel.Push`
- [Minor] Moves `Channel.ChannelState` to `Constants.ChannelState`
- [Minor] Moves `SocketResponse`, `SocketRequest`, `SocketResponsePayload`, `SocketResponseEventArgs`, and `SocketStateChangedEventArgs` to `Socket` namespace.
- [New] Adds `RealtimeBroadcast`
- [New] Adds `RealtimePresence`
- [Improvement] Better handling of disconnection/reconnection
- Update dependency: postgrest-csharp@3.1.3
- Another fix for [#61](https://github.com/supabase-community/postgrest-csharp/issues/61) which further typechecks nullable values.
## 0.7.2 - 2023-01-27
- Update dependency: gotrue-csharp@3.0.4
- Makes `Session.CreatedAt` a publicly settable property, which should fix incorrect dates on retrieved `Session`s.
- Update dependency: postgrest-csharp@3.1.2
- Fix [#61](https://github.com/supabase-community/postgrest-csharp/issues/61) which did not correctly parse Linq `Where` when encountering a nullable type.
- Add missing support for transforming for `== null` and `!= null`
## 0.7.1 - 2023-01-17
- Update dependency: postgrest-csharp@3.1.1
- Fix issue from supabase-community/supabase-csharp#48 where boolean model properties would not be evaluated in predicate expressions
## 0.7.0 - 2023-01-16
- Update dependency: postgrest-csharp@3.1.0
- [Minor] Breaking API Change: `PrimaryKey` attribute defaults to `shouldInsert: false` as most uses will have the Database generate the primary key.
- Merged [#60](https://github.com/supabase-community/postgrest-csharp/pull/60) which Added linq support for `Select`, `Where`, `OnConflict`, `Columns`, `Order`, `Update`, `Set`, and `Delete`
## 0.6.2 - 2022-11-22
- Update dependency: postgrest-csharp@3.0.4
- `GetHeaders` is now passed to `ModeledResponse` and `BaseModel` so that the default `Update` and `Delete` methods use the latest credentials
- `GetHeaders` is used in `Rpc` calls (re: [#39](https://github.com/supabase-community/supabase-csharp/issues/39))
## 0.6.1 - 2022-11-12
- [Hotfix] `GetHeaders` was not passing properly to `SupabaseTable` and `Gotrue.Api`
## 0.6.0 - 2022-11-12
[BREAKING CHANGES]
- `Client` is no longer a singleton, singleton interactions (if desired) are left to the developer to implement.
- `Client` supports injection of dependent clients after initialization via property:
- `Auth`
- `Functions`
- `Realtime`
- `Postgrest`
- `Storage`
- `SupabaseModel` contains no logic but remains for backwards compatibility. (Marked `Obsolete`)
- `ClientOptions.ShouldInitializeRealtime` was removed (no longer auto initialized)
- `ClientOptions` now references an `ISupabaseSessionHandler` which specifies expected functionality for session persistence on Gotrue (replaces `ClientOptions.SessionPersistor`, `ClientOptions.SessionRetriever`, and `ClientOptions.SessionDestroyer`).
- `supabase-csharp` and all child libraries now have support `nullity`
Other Changes:
- Update dependency: functions-csharp@1.2.1
- Update dependency: gotrue-csharp@3.0.2
- Update dependency: postgrest-csharp@3.0.2
- Update dependency: realtime-csharp@4.0.1
- Update dependency: supabase-storage-csharp@1.2.3
- Update dependency: supabase-core@0.0.2
Big thank you to [@veleek](https://github.com/veleek) for his insight into these changes.
Re: [#35](https://github.com/supabase-community/supabase-csharp/issues/35), [#34](https://github.com/supabase-community/supabase-csharp/issues/34), [#23](https://github.com/supabase-community/supabase-csharp/issues/23), [#36](https://github.com/supabase-community/supabase-csharp/pull/36)
## 0.5.3 - 2022-10-11
- Update dependency: postgrest-csharp@2.1.0
- [Minor] Breaking API change: Remove `BaseModel.PrimaryKeyValue` and `BaseModel.PrimaryKeyColumn` in favor of a `PrimaryKey` dictionary with support for composite keys.
- Re: [#48](https://github.com/supabase-community/postgrest-csharp/issues/48) - Add support for derived models on `ReferenceAttribute`
- Re: [#49](https://github.com/supabase-community/postgrest-csharp/issues/49) - Added `Match(T model)`
## 0.5.2 - 2022-9-13
- Update dependency: postgrest-csharp@2.0.12
- Merged [#47](https://github.com/supabase-community/postgrest-csharp/pull/49) which added cancellation token support to `Table<T>` methods. Thanks [@devpikachu](https://github.com/devpikachu)!
## 0.5.1 - 2022-8-1
- Update dependency: postgrest-csharp@2.0.11
- Update dependency: supabase-storage-csharp@1.1.1
## 0.5.0 - 2022-7-17
- Update dependency: postgrest-csharp@2.0.9
- Update dependency: realtime-csharp@3.0.1
- Update dependency: supabase-storage-csharp@1.1.0
- API Change [Breaking/Minor] Library no longer uses `WebClient` and instead leverages `HttpClient`. Progress events on `Upload` and `Download` are now handled with `EventHandler<float>` instead of `WebClient` EventHandlers.
## 0.4.4 - 2022-5-24
- Update dependency: gotrue-csharp@2.4.5
- Update dependency: postgrest-csharp@2.0.8
## 0.4.3 - 2022-5-13
- Update dependency: gotrue-csharp@2.4.4
## 0.4.2 - 2022-4-30
- Update dependency: gotrue-csharp@2.4.3
## 0.4.1 - 2022-4-23
- Update dependency: gotrue-csharp@2.4.2
## 0.4.0 - 2022-4-12
- Add support for functions-csharp@1.0.1, giving access to invoking Supabase's edge functions.
- Update dependency: gotrue-csharp@2.4.1
## 0.3.5 - 2022-4-11
- Update dependency: postgres-csharp@2.0.7
## 0.3.4 - 2022-03-28
- Update dependency: gotrue-csharp@2.4.0
## 0.3.3 - 2022-02-27
- Update dependency: gotrue-csharp@2.3.6
- Update dependency: supabase-storage-csharp@1.0.2
## 0.3.2 - 2022-02-18
- Update dependency: realtime-csharp@3.0.0
- Exchange existing websocket client: [WebSocketSharp](https://github.com/sta/websocket-sharp) for [Marfusios/websocket-client](https://github.com/Marfusios/websocket-client) which adds support for Blazor WASM apps.
Ref: [#14](https://github.com/supabase-community/realtime-csharp/pull/14)
## 0.3.1 - 2022-01-20
- Update dependency: gotrue-csharp@2.3.5
- [#23](https://github.com/supabase-community/gotrue-csharp/pull/23) Added `redirect_url` option for MagicLink sign in (Thanks [@MisterJimson](https://github.com/MisterJimson))
- [#21](https://github.com/supabase-community/gotrue-csharp/pull/21) Added SignOut method to Stateless Client (Thanks [@fplaras](https://github.com/fplaras))
## 0.3.0 - 2021-12-30
- Update dependency: postgrest-csharp@2.0.6
- Add support for `NullValueHandling` to be specified on a `Column` Attribute and for it to be honored on Inserts and Updates. Defaults to: `NullValueHandling.Include`.
- Implements [#38](https://github.com/supabase-community/postgrest-csharp/issues/38)
- Update dependency: realtime-csharp@2.0.8
- Implement Upstream Realtime RLS Error Broadcast Handler
- Implements [#12](https://github.com/supabase-community/realtime-csharp/issues/12)
- `SocketResponse` now exposes a method: `OldModel`, that hydrates the `OldRecord` property into a model.
## 0.2.12 - 2021-12-29
- Update dependency: gotrue-csharp@2.3.3
- `SignUp` will return a `Session` with a _populated `User` object_ on an unconfirmed signup.
- Fixes [#19](https://github.com/supabase-community/gotrue-csharp/issues/19)
- Developers who were using a `null` check on `Session.User` will need to adjust accordingly.
- Update dependency: postgrest-csharp@2.0.5
- Fix for [#37](https://github.com/supabase-community/postgrest-csharp/issues/37) - Return Type `minimal` would fail to resolve because of incorrect `Accept` headers. Added header and test to verify for future.
- Fix for [#36](https://github.com/supabase-community/postgrest-csharp/issues/36) - Inserting/Upserting bulk records would fail while doing an unnecessary generic coercion.
## 0.2.11 - 2021-12-24
- Update dependency: gotrue-csharp@2.3.2 (changes CreateUser parameters to conform to `AdminUserAttributes`)
- See [#15](https://github.com/supabase-community/supabase-csharp/issues/15)
- See [#16](https://github.com/supabase-community/supabase-csharp/issues/16)
- Update dependency: realtime-csharp@2.0.7
- See [#13](https://github.com/supabase-community/supabase-csharp/issues/13)
## 0.2.10 - 2021-12-23
- Update dependency: gotrue-csharp@2.3.0 (adds metadata support for user signup, see [#14](https://github.com/supabase/community/issues/14))
## 0.2.9 - 2021-12-9
- Separate Storage client from Supabase repo and into `storage-csharp`, `supabase-csharp` now references new repo.
## 0.2.8 - 2021-12-4
- Update gotrue-csharp to 2.2.4
- Adds support for `ListUsers` (paginate, sort, filter), `GetUserById`, `CreateUser`, and `UpdateById`
## 0.2.7 - 2021-12-2
- Update gotrue-csharp to 2.2.3
- Adds support for sending password resets to users.
## 0.2.6 - 2021-11-29
- Support for [#12](https://github.com/supabase-community/supabase-csharp/issues/12)
- Update realtime-csharp to 2.0.6
- Update gotrue-csharp to 2.2.2
- Add `StatelessClient` re:[#7](https://github.com/supabase-community/supabase-csharp/issues/7)
@@ -0,0 +1,6 @@
---
id: release-notes
title: Release Notes
---
## dart v0 this is the release notes file.
-50
View File
@@ -1,50 +0,0 @@
---
id: initializing
title: 'Initializing'
slug: initializing
---
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
Initialize Supabase with the static `initialize()` method on `Supabase` class.
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
size="small"
type="underlined"
defaultActiveId="flutter"
>
<TabPanel id="flutter" label="Flutter">
```dart main.dart
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Supabase.initialize(
url: 'https://xyzcompany.supabase.co',
anonKey: 'public-anon-key',
);
runApp(MyApp());
}
```
</TabPanel>
<TabPanel id="dart" label="Other Dart Project">
```dart
final supabase = SupabaseClient(
'https://xyzcompany.supabase.co',
'public-anon-key',
);
```
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
+8 -7
View File
@@ -13,11 +13,12 @@ hideTitle: true
</div>
<div className="max-w-xl">
This reference documents every object and method available in Supabase's Flutter
library, [supabase-flutter](https://pub.dev/packages/supabase_flutter). You can
use supabase-flutter to interact with your Postgres database, listen to database changes, invoke
Deno Edge Functions, build login and user management functionality, and manage large files.
<br />
<br />
We also provide [supabase](https://pub.dev/packages/supabase) package for non-Flutter projects.
This reference documents every object and method available in Supabase's Flutter
library, [supabase-flutter](https://pub.dev/packages/supabase_flutter). You can
use supabase-flutter to interact with your Postgres database, listen to database changes, invoke
Deno Edge Functions, build login and user management functionality, and manage large files.
We also provide a [supabase](https://pub.dev/packages/supabase) package for non-Flutter projects.
</div>
@@ -0,0 +1,28 @@
---
id: introduction
title: Introduction
hideTitle: true
---
<div className="flex items-start gap-6 not-prose" id="introduction">
<img src="/docs/img/icons/menu/reference-dart.svg" className="w-8 h-8 rounded" />
<div className="flex flex-col gap-2">
<h1 className="text-3xl text-scale-1200 m-0">Flutter Client Library</h1>
<h2 className="text-base font-mono text-scale-1100">supabase-flutter</h2>
</div>
</div>
<div className="max-w-xl">
<Admonition type="caution">
You're viewing the docs for an older version of the `supabase-flutter` library. Learn how to
[upgrade to the latest version](/docs/reference/dart/v0/upgrade-guide).
</Admonition>
This reference documents every object and method available in Supabase's Flutter library, [supabase-flutter](https://pub.dev/packages/supabase_flutter).
You can use supabase-flutter to interact with your Postgres database, listen to database changes, invoke
Deno Edge Functions, build login and user management functionality, and manage large files.
We also provide a [supabase](https://pub.dev/packages/supabase) package for non-Flutter projects.
</div>
@@ -4,23 +4,35 @@ title: Upgrade to supabase-flutter v1
description: 'Learn how to upgrade to supabase-flutter v1.'
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
supabase-flutter focuses on improving the developer experience and making it easier to use. This guide will help you upgrade from supabase-flutter v0 to v1.
supabase-flutter focuses on improving the developer experience and making it easier to use. This guide demonstrates how to upgrade from supabase-flutter v0 to v1.
## Upgrade the client library
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
Update the package in your pubspec.yaml file.
</RefSubLayout.Details>
<RefSubLayout.Examples>
```yaml
supabase_flutter: ^1.0.0
```
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
## Error handling
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
The way supabase-flutter throws error has changed in v1. In v0, errors were returned as a response. In v1, errors are thrown as exceptions. This makes it more intuitive as a Flutter developer to handle errors.
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -52,11 +64,20 @@ try {
</TabPanel>
</Tabs>
## Auth classes / methods
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
## Auth classes and methods
### Usage of `SupabaseAuthState` and `SupabaseAuthRequiredState` classes
In v0, `SupabaseAuthState` and `SupabaseAuthRequiredState` were required to handle automatic token refresh and to listen to auth state change. In v1, `SupabaseAuthState` and `SupabaseAuthRequiredState` are deprecated, and token refresh will happen automatically just by initializing Supabase. [`onAuthStateChange`](/docs/reference/dart/upgrade-guide#listening-to-auth-state-change) can be used to action on auth state change.
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
In v0, `SupabaseAuthState` and `SupabaseAuthRequiredState` were required to handle automatic token refresh and to listen to auth state change. In v1, `SupabaseAuthState` and `SupabaseAuthRequiredState` are deprecated, and token refresh will happen automatically just by initializing Supabase. [`onAuthStateChange`](#listening-to-auth-state-change) can be used to action on auth state change.
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
@@ -97,10 +118,19 @@ await Supabase.initialize(
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
### Listening to auth state change
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
`onAuthStateChange` now returns a `Stream`.
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -135,9 +165,18 @@ authSubscription.cancel();
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
### Sign in with email and password
The signIn() method has been deprecated in favor of more explicit method signatures to help with type hinting. Previously it was difficult for developers to know what they were missing (e.g., a lot of developers didn't realize they could use passwordless magic links).
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
`signIn()` has been deprecated in favor of more explicit method signatures to help with type hinting. Previously it was difficult for developers to know what they were missing (e.g., a lot of developers didn't realize they could use passwordless magic links).
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
@@ -161,8 +200,17 @@ await supabase.auth.signInWithPassword(email: email, password: password);
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
### Sign in with magic link
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -185,8 +233,17 @@ await supabase.auth.signInWithOtp(email: email);
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
### Sign in with a third-party OAuth provider
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -218,8 +275,17 @@ await supabase.auth.signInWithOAuth(
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
### Sign in with phone
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -248,8 +314,17 @@ await supabase.auth.signInWithPassword(
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
### Sign in with phone using OTP
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -281,8 +356,17 @@ await supabase.auth.verifyOTP(
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
### Reset password for email
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -312,8 +396,17 @@ await supabase.auth.resetPasswordForEmail(
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
### Get the user's current session
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -336,8 +429,17 @@ final Session? session = supabase.auth.currentSession;
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
### Get the logged-in user
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -360,8 +462,17 @@ final User? user = supabase.auth.currentUser;
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
### Update user data for a logged-in user
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -390,16 +501,23 @@ await supabase.updateUser(
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
## Data methods
`.insert()` / `.upsert()` / `.update()` / `.delete()` don't return rows by default.
`.insert()` / `.upsert()` / `.update()` / `.delete()` no longer return rows by default. Previously, these methods return inserted/updated/deleted rows by default (which caused [some confusion](https://github.com/supabase/supabase/discussions/1548)), and you can opt to not return it by specifying `returning: 'minimal'`. Now the default behavior is to not return rows. To return inserted/updated/deleted rows, add a `.select()` call at the end.
Previously, these methods return inserted/updated/deleted rows by default (which caused [some confusion](https://github.com/supabase/supabase/discussions/1548)), and you can opt to not return it by specifying `returning: 'minimal'`. Now the default behavior is to not return rows. To return inserted/updated/deleted rows, add a `.select()` call at the end.
Also, calling `.execute()` at the end of the query was a requirement in v0, but deprecated in v1.
Also, calling `.execute()` at the end of the query was a requirement in v0, but in v1 `.execute` is deperecated.
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
### Insert without returning inserted data
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -426,8 +544,17 @@ await supabase.from('my_table').insert(data);
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
### Insert with returning inserted data
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -453,12 +580,21 @@ final insertedData = await supabase.from('my_table').insert(data).select();
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
## Realtime methods
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
### Stream
`.stream()` no longer needs the `.execute()` at the end. Also, filtering by `eq` is a lot easier now. `primaryKey` is now a named parameter to make it more obvious what to pass.
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -486,8 +622,17 @@ supabase.from('my_table')
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
### Subscribe
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -522,8 +667,17 @@ channel.on(
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
### Unsubscribe
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
@@ -545,3 +699,6 @@ await supabase.removeChannel(channel);
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
@@ -13,8 +13,15 @@ hideTitle: true
</div>
<div className="max-w-xl">
This reference documents every object and method available in Supabase's isomorphic JavaScript
library, supabase-js. You can use supabase-js to interact with your Postgres database, listen to
database changes, invoke Deno Edge Functions, build login and user management functionality, and
manage large files.
<Admonition type="caution">
You're viewing the docs for an older version of the `supabase-js` library. Learn how to [upgrade
to the latest version](/docs/reference/javascript/v1/upgrade-guide).
</Admonition>
This reference documents every object and method available in Supabase's isomorphic JavaScript
library, supabase-js. You can use supabase-js to interact with your Postgres database, listen to
database changes, invoke Deno Edge Functions, build login and user management functionality, and
manage large files.
</div>
@@ -89,19 +89,23 @@ The signIn() method has been deprecated in favor of more explicit method signatu
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
{/* prettier-ignore */}
```ts
const { user, error } = await supabase.auth.signIn({ email, password })
const { user, error } = await supabase
.auth
.signIn({ email, password })
```
</TabPanel>
<TabPanel id="2.0x" label="After">
{/* prettier-ignore */}
```ts
const {
data: { user },
error,
} = await supabase.auth.signInWithPassword({ email, password })
} = await supabase
.auth
.signInWithPassword({ email, password })
```
</TabPanel>
@@ -124,16 +128,20 @@ const {
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
{/* prettier-ignore */}
```ts
const { error } = await supabase.auth.signIn({ email })
const { error } = await supabase
.auth
.signIn({ email })
```
</TabPanel>
<TabPanel id="2.0x" label="After">
{/* prettier-ignore */}
```ts
const { error } = await supabase.auth.signInWithOtp({ email })
const { error } = await supabase
.auth
.signInWithOtp({ email })
```
</TabPanel>
@@ -157,16 +165,20 @@ const { error } = await supabase.auth.signInWithOtp({ email })
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
{/* prettier-ignore */}
```ts
const { error } = await supabase.auth.signIn({ provider })
const { error } = await supabase
.auth
.signIn({ provider })
```
</TabPanel>
<TabPanel id="2.0x" label="After">
{/* prettier-ignore */}
```ts
const { error } = await supabase.auth.signInWithOAuth({ provider })
const { error } = await supabase
.auth
.signInWithOAuth({ provider })
```
</TabPanel>
@@ -189,16 +201,20 @@ const { error } = await supabase.auth.signInWithOAuth({ provider })
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
{/* prettier-ignore */}
```ts
const { error } = await supabase.auth.signIn({ phone, password })
const { error } = await supabase
.auth
.signIn({ phone, password })
```
</TabPanel>
<TabPanel id="2.0x" label="After">
{/* prettier-ignore */}
```ts
const { error } = await supabase.auth.signInWithPassword({ phone, password })
const { error } = await supabase
.auth
.signInWithPassword({ phone, password })
```
</TabPanel>
@@ -222,19 +238,26 @@ const { error } = await supabase.auth.signInWithPassword({ phone, password })
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
{/* prettier-ignore */}
```ts
const { error } = await supabase.auth.api.sendMobileOTP(phone)
const { error } = await supabase
.auth
.api
.sendMobileOTP(phone)
```
</TabPanel>
<TabPanel id="2.0x" label="After">
{/* prettier-ignore */}
```ts
const { data, error } = await supabase.auth.signInWithOtp({ phone })
const { data, error } = await supabase
.auth
.signInWithOtp({ phone })
// After receiving a SMS with a OTP.
const { data, error } = await supabase.auth.verifyOtp({ phone, token })
const { data, error } = await supabase
.auth
.verifyOtp({ phone, token })
```
</TabPanel>
@@ -258,16 +281,21 @@ const { data, error } = await supabase.auth.verifyOtp({ phone, token })
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
{/* prettier-ignore */}
```ts
const { data, error } = await supabase.auth.api.resetPasswordForEmail(email)
const { data, error } = await supabase
.auth
.api
.resetPasswordForEmail(email)
```
</TabPanel>
<TabPanel id="2.0x" label="After">
{/* prettier-ignore */}
```ts
const { data, error } = await supabase.auth.resetPasswordForEmail(email)
const { data, error } = await supabase
.auth
.resetPasswordForEmail(email)
```
</TabPanel>
@@ -362,9 +390,11 @@ const { user } = session
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
{/* prettier-ignore */}
```ts
const { user, error } = await supabase.auth.update({ attributes })
const { user, error } = await supabase
.auth
.update({ attributes })
```
</TabPanel>
@@ -405,15 +435,19 @@ const { user, error } = supabase.auth.setAuth(access_token)
</TabPanel>
<TabPanel id="2.0x" label="After">
{/* prettier-ignore */}
```ts
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
global: {
headers: {
Authorization: `Bearer ${access_token}`,
const supabase = createClient(
SUPABASE_URL,
SUPABASE_ANON_KEY,
{
global: {
headers: {
Authorization: `Bearer ${access_token}`,
},
},
},
})
}
)
```
</TabPanel>
@@ -452,16 +486,21 @@ Previously, these methods return inserted/updated/deleted rows by default (which
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
{/* prettier-ignore */}
```ts
const { data, error } = await supabase.auth.insert({ new_data })
const { data, error } = await supabase
.from('my_table')
.insert({ new_data })
```
</TabPanel>
<TabPanel id="2.0x" label="After">
{/* prettier-ignore */}
```ts
const { data, error } = await supabase.auth.insert({ new_data }).select()
const { data, error } = await supabase
.from('my_table')
.insert({ new_data })
.select()
```
</TabPanel>
@@ -485,16 +524,101 @@ const { data, error } = await supabase.auth.insert({ new_data }).select()
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
{/* prettier-ignore */}
```ts
const { data, error } = await supabase.auth.update({ new_data }).eq('id', id)
const { data, error } = await supabase
.from('my_table')
.update({ new_data })
.eq('id', id)
```
</TabPanel>
<TabPanel id="2.0x" label="After">
{/* prettier-ignore */}
```ts
const { data, error } = await supabase.auth.update({ new_data }).eq('id', id).select()
const { data, error } = await supabase
.from('my_table')
.update({ new_data })
.eq('id', id)
.select()
```
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
#### Upsert and return data
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
{/* prettier-ignore */}
```ts
const { data, error } = await supabase
.from('my_table')
.upsert({ new_data })
```
</TabPanel>
<TabPanel id="2.0x" label="After">
{/* prettier-ignore */}
```ts
const { data, error } = await supabase
.from('my_table')
.upsert({ new_data })
.select()
```
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
#### Delete and return data
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
{/* prettier-ignore */}
```ts
const { data, error } = await supabase
.from('my_table')
.delete()
.eq('id', id)
```
</TabPanel>
<TabPanel id="2.0x" label="After">
{/* prettier-ignore */}
```ts
const { data, error } = await supabase
.from('my_table')
.delete()
.eq('id', id)
.select()
```
</TabPanel>
@@ -0,0 +1,21 @@
---
id: introduction
title: Introduction
hideTitle: true
---
<div className="flex items-start gap-6 not-prose" id="introduction">
<img src="/docs/img/icons/menu/reference-python.svg" className="w-8 h-8 rounded" />
<div className="flex flex-col gap-2">
<h1 className="text-3xl text-scale-1200 m-0">Python Client Library</h1>
<h2 className="text-base font-mono text-scale-1100">@supabase-community/supabase-py</h2>
</div>
</div>
<div className="max-w-xl">
This reference documents every object and method available in the
[supabase-py](https://github.com/supabase-community/supabase-py) library from the Supabase
community. You can use `supabase-py` to interact with your Postgres database, listen to database
changes, invoke Deno Edge Functions, build login and user management functionality, and manage
large files.
</div>
@@ -0,0 +1,10 @@
---
id: release-notes
title: Release Notes
---
The community is actively working on the library and we will be upgrading the Authentication library, `gotrue-py`, to mirror the Supabase-js v2 lib.
## Storage Transformations
We currently support [image transformations](https://supabase.com/docs/guides/storage/image-transformations) in our storage library.
@@ -0,0 +1,67 @@
---
id: initializing
title: 'Initializing'
slug: initializing
custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supabase.yml
---
### Initializing a new Client
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
Initializing a new client is pretty straightforward. Find your project url and public key from the
admin panel and pass it into your client initialization function.
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
size="small"
type="underlined"
defaultActiveId="csharp-init-standard">
<TabPanel id="csharp-init-standard" label="Standard">
```csharp
var url = Environment.GetEnvironmentVariable("SUPABASE_URL");
var key = Environment.GetEnvironmentVariable("SUPABASE_KEY");
var options = new Supabase.SupabaseOptions
{
AutoConnectRealtime = true
};
var supabase = new Supabase.Client(url, key, options);
await supabase.InitializeAsync();
```
</TabPanel>
<TabPanel id="csharp-init-maui" label="Dependency Injection (Maui-like)">
```csharp
public static MauiApp CreateMauiApp()
{
// ...
var builder = MauiApp.CreateBuilder();
var url = Environment.GetEnvironmentVariable("SUPABASE_URL");
var key = Environment.GetEnvironmentVariable("SUPABASE_KEY");
var options = new SupabaseOptions
{
AutoRefreshToken = true,
AutoConnectRealtime = true,
SessionHandler = new SupabaseSessionHandler()
};
// Note the creation as a singleton.
builder.Services.AddSingleton(provider => new Supabase.Client(url, key, options));
}
```
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
@@ -0,0 +1,33 @@
---
id: installing
title: 'Installing & Initialization'
slug: installing
custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supabase.yml
---
### Install from NuGet
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
You can install Supabase package from [nuget.org](https://www.nuget.org/packages/supabase-csharp/)
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
size="small"
type="underlined"
defaultActiveId="csharp">
<TabPanel id="csharp" label="Terminal">
```sh Terminal
dotnet add package supabase-csharp
```
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
@@ -0,0 +1,20 @@
---
id: introduction
title: Introduction
hideTitle: true
---
<div className="flex items-start gap-6 not-prose" id="introduction">
<img src="/docs/img/icons/menu/reference-csharp.svg" className="w-8 h-8 rounded" />
<div className="flex flex-col gap-2">
<h1 className="text-3xl text-scale-1200 m-0">C# Client Library</h1>
<h2 className="text-base font-mono text-scale-1100">supabase-csharp</h2>
</div>
</div>
<div className="max-w-xl">
This reference documents every object and method available in Supabase's C#
library, [supabase-csharp](https://www.nuget.org/packages/supabase-csharp). You can
use supabase-csharp to interact with your Postgres database, listen to database changes, invoke
Deno Edge Functions, build login and user management functionality, and manage large files.
</div>
@@ -0,0 +1,6 @@
---
id: release-notes
title: Release Notes
---
## dart v0 this is the release notes file.
@@ -1,439 +0,0 @@
---
id: upgrade-guide
title: Upgrade to supabase-js v2
description: 'Learn how to upgrade to supabase-js v2.'
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
supabase-js v2 focuses on "quality-of-life" improvements for developers and addresses some of the largest pain points in v1. v2 includes type support, a rebuilt Auth library with async methods, improved errors, and more.
No new features will be added to supabase-js v1 , but we'll continuing merging security fixes to v1, with maintenance patches for the next 3 months.
## Upgrade the client library
```sh
npm install @supabase/supabase-js@2
```
_Optionally_ if you are using custom configuration with `createClient` then follow below:
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts title=src/supabaseClient.ts
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
schema: 'custom',
persistSession: false,
})
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts title=src/supabaseClient.ts
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
db: {
schema: 'custom',
},
auth: {
persistSession: true,
},
})
```
</TabPanel>
</Tabs>
Read more about the [constructor options](/docs/reference/javascript/release-notes#explicit-constructor-options).
### Auth methods
The signIn() method has been deprecated in favor of more explicit method signatures to help with type hinting. Previously it was difficult for developers to know what they were missing (e.g., a lot of developers didn't realize they could use passwordless magic links).
#### Sign in with email and password
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts
const { user, error } = await supabase.auth.signIn({ email, password })
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts
const {
data: { user },
error,
} = await supabase.auth.signInWithPassword({ email, password })
```
</TabPanel>
</Tabs>
#### Sign in with magic link
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts
const { error } = await supabase.auth.signIn({ email })
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts
const { error } = await supabase.auth.signInWithOtp({ email })
```
</TabPanel>
</Tabs>
#### Sign in with a third-party provider
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts
const { error } = await supabase.auth.signIn({ provider })
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts
const { error } = await supabase.auth.signInWithOAuth({ provider })
```
</TabPanel>
</Tabs>
#### Sign in with phone
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts
const { error } = await supabase.auth.signIn({ phone, password })
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts
const { error } = await supabase.auth.signInWithPassword({ phone, password })
```
</TabPanel>
</Tabs>
#### Sign in with phone using OTP
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts
const { error } = await supabase.auth.api.sendMobileOTP(phone)
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts
const { data, error } = await supabase.auth.signInWithOtp({ phone })
// After receiving a SMS with a OTP.
const { data, error } = await supabase.auth.verifyOtp({ phone, token })
```
</TabPanel>
</Tabs>
#### Reset password for email
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts
const { data, error } = await supabase.auth.api.resetPasswordForEmail(email)
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts
const { data, error } = await supabase.auth.resetPasswordForEmail(email)
```
</TabPanel>
</Tabs>
#### Get the user's current session
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts
const session = supabase.auth.session()
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts
const {
data: { session },
} = await supabase.auth.getSession()
```
</TabPanel>
</Tabs>
#### Get the logged-in user
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts
const user = supabase.auth.user()
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts
const {
data: { session },
} = await supabase.auth.getSession()
const { user } = session
```
</TabPanel>
</Tabs>
#### Update user data for a logged-in user
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts
const { user, error } = await supabase.auth.update({ attributes })
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts
const {
data: { user },
error,
} = await supabase.auth.updateUser({ attributes })
```
</TabPanel>
</Tabs>
#### Use a custom `access_token` JWT with Supabase
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts
const { user, error } = supabase.auth.setAuth(access_token)
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
global: {
headers: {
Authorization: `Bearer ${access_token}`,
},
},
})
```
</TabPanel>
</Tabs>
#### Cookie methods
The cookie-related methods like `setAuthCookie` and `getUserByCookie` have been removed.
For Next.js you can use the [Auth Helpers](https://supabase.com/docs/guides/auth/auth-helpers/nextjs) to help you manage cookies.
If you can't use the Auth Helpers, you can use [server-side rendering](https://supabase.com/docs/guides/auth/server-side-rendering).
Some the [PR](https://github.com/supabase/gotrue-js/pull/340) for additional background information.
### Data methods
`.insert()` / `.upsert()` / `.update()` / `.delete()` don't return rows by default: [PR](https://github.com/supabase/postgrest-js/pull/276).
Previously, these methods return inserted/updated/deleted rows by default (which caused [some confusion](https://github.com/supabase/supabase/discussions/1548)), and you can opt to not return it by specifying `returning: 'minimal'`. Now the default behavior is to not return rows. To return inserted/updated/deleted rows, add a `.select()` call at the end.
#### Insert and return data
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts
const { data, error } = await supabase.auth.insert({ new_data })
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts
const { data, error } = await supabase.auth.insert({ new_data }).select()
```
</TabPanel>
</Tabs>
#### Update and return data
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts
const { data, error } = await supabase.auth.update({ new_data }).eq('id', id)
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts
const { data, error } = await supabase.auth.update({ new_data }).eq('id', id).select()
```
</TabPanel>
</Tabs>
### Realtime methods
#### Subscribe
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts
const userListener = supabase
.from('users')
.on('*', (payload) => handleAllEventsPayload(payload.new))
.subscribe()
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts
const userListener = supabase
.channel('public:user')
.on('postgres_changes', { event: '*', schema: 'public', table: 'user' }, (payload) =>
handleAllEventsPayload()
)
.subscribe()
```
</TabPanel>
</Tabs>
#### Unsubscribe
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="1.0x"
>
<TabPanel id="1.0x" label="Before">
```ts
userListener.unsubscribe()
```
</TabPanel>
<TabPanel id="2.0x" label="After">
```ts
supabase.removeChannel(userListener)
```
</TabPanel>
</Tabs>
@@ -8,7 +8,7 @@ custom_edit_url: https://github.com/supabase/supabase/edit/master/spec/supabase_
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
Supabase will soon release native type generators that dump your database types for various languages. For now, we support TypeScript [through third-party tools](/docs/guides/api/generating-types).
Supabase will soon release native type generators that dump your database types for various languages. For now, we support TypeScript [through third-party tools](/docs/guides/database/api/generating-types).
## Usage with TypeScript
+7
View File
@@ -0,0 +1,7 @@
import { useContext } from 'react'
import FlagContext from 'components/Flag/FlagContext'
export function useFlag(name: string) {
const store: any = useContext(FlagContext)
return store[name]
}
+8 -1
View File
@@ -1,8 +1,15 @@
import { FC } from 'react'
import { FooterHelpCalloutType } from '~/components/FooterHelpCallout'
import GuideLayout from './guides'
interface Props {
meta: { title: string; description?: string; hide_table_of_contents?: boolean; video?: string }
meta: {
title: string
description?: string
hide_table_of_contents?: boolean
video?: string
footerHelpType?: FooterHelpCalloutType
}
children: any
toc?: any
currentPage?: string
+1 -1
View File
@@ -19,7 +19,7 @@ const Layout: FC<Props> = (props: Props) => {
return (
<>
<Head>
<title>{props.meta?.title} | Supabase</title>
<title>{props.meta?.title} | Supabase Docs</title>
<meta name="description" content={props.meta?.description} />
<meta content="width=device-width, initial-scale=1" name="viewport" />
<link rel="icon" href="/docs/favicon.ico" />
+17 -2
View File
@@ -7,6 +7,7 @@ import TopNavBarRef from '~/components/Navigation/NavigationMenu/TopNavBarRef'
import FooterHelpCallout from '~/components/FooterHelpCallout'
import { memo, useEffect } from 'react'
import Clippy from '~/components/Clippy/Clippy'
import Footer from '~/components/Navigation/Footer'
import { menuState, useMenuLevelId, useMenuMobileOpen } from '~/hooks/useMenuState'
@@ -29,7 +30,7 @@ const levelsData = {
},
functions: {
icon: '/docs/img/icons/menu/functions',
name: 'Functions',
name: 'Edge Functions',
},
realtime: {
icon: '/docs/img/icons/menu/realtime',
@@ -39,6 +40,10 @@ const levelsData = {
icon: '/docs/img/icons/menu/storage',
name: 'Storage',
},
supabase_cli: {
icon: '/docs/img/icons/menu/reference-cli',
name: 'Supabase CLI',
},
platform: {
icon: '/docs/img/icons/menu/platform',
name: 'Platform',
@@ -71,6 +76,14 @@ const levelsData = {
icon: '/docs/img/icons/menu/reference-dart',
name: 'Dart Reference v0.0',
},
reference_csharp_v0: {
icon: '/docs/img/icons/menu/reference-csharp',
name: 'C# Reference v0.0',
},
reference_python_v2: {
icon: '/docs/img/icons/menu/reference-python',
name: 'Python Reference v2.0',
},
reference_cli: {
icon: '/docs/img/icons/menu/reference-cli',
name: 'CLI Reference',
@@ -322,11 +335,13 @@ const SiteLayout = ({ children }) => {
</div>
<div className="grow px-5 max-w-7xl mx-auto py-16">
{children}
<FooterHelpCallout />
<Footer />
</div>
<MobileMenuBackdrop />
</Container>
<div className=" absolute right-4 md:right-12 bottom-8 z-[300]">
<Clippy />
</div>
</div>
</main>
)
+22 -3
View File
@@ -6,9 +6,9 @@ import { FC, useEffect, useRef, useState } from 'react'
import { IconExternalLink } from 'ui'
import components from '~/components'
import { highlightSelectedTocItem } from '~/components/CustomHTMLElements/CustomHTMLElements.utils'
import FooterHelpCallout, { FooterHelpCalloutType } from '~/components/FooterHelpCallout'
import GuidesTableOfContents from '~/components/GuidesTableOfContents'
import useHash from '~/hooks/useHash'
import { getPageType } from '~/lib/helpers'
interface Props {
meta: {
@@ -17,6 +17,9 @@ interface Props {
hide_table_of_contents?: boolean
breadcrumb?: string
subtitle?: string
footerHelpType?: FooterHelpCalloutType
video?: string
canonical?: string
}
children: any
toc?: any
@@ -65,12 +68,28 @@ const Layout: FC<Props> = (props) => {
return (
<>
<NextSeo
title={`${props.meta?.title} | Supabase`}
title={`${props.meta?.title} | Supabase Docs`}
description={props.meta?.description ? props.meta?.description : props.meta?.title}
canonical={props.meta?.canonical ?? `https://supabase.com/docs${asPath}`}
openGraph={{
title: props.meta?.title,
description: props.meta?.description,
url: `https://supabase.com/docs${asPath}`,
type: 'article',
videos: props.meta?.video && [
{
// youtube based video meta
url: props.meta?.video,
width: 640,
height: 385,
type: 'application/x-shockwave-flash',
},
],
article: {
publishedTime: new Date().toISOString(),
modifiedTime: new Date().toISOString(),
authors: ['Supabase'],
},
images: [
{
url: `https://obuldanrptloktxcffvn.functions.supabase.co/og-images?site=docs${
@@ -82,7 +101,6 @@ const Layout: FC<Props> = (props) => {
],
}}
/>
<div className={['grid grid-cols-12 relative gap-4'].join(' ')}>
<div
className={[
@@ -145,6 +163,7 @@ const Layout: FC<Props> = (props) => {
</div>
)}
</div>
<FooterHelpCallout footerHelpType={props.meta?.footerHelpType} title={props.meta?.title} />
</>
)
}
+5 -26
View File
@@ -1,33 +1,12 @@
import generateOldRefMarkdown from '~/lib/mdx/generateOldRefMarkdown'
import generateRefMarkdown from '~/lib/mdx/generateRefMarkdown'
async function handleRefStaticProps(sections, params, librarypath, urlPath) {
async function handleRefStaticProps(sections, librarypath) {
let markdownContent = await generateRefMarkdown(sections, librarypath)
/*
* old content generation
* this is for grabbing to old markdown files
*/
let slug
if (params.slug.length > 1) {
slug = `docs/reference${urlPath}/${params.slug.join('/')}`
} else {
slug = `docs/reference${urlPath}/${params.slug[0]}`
}
/*
* handle old ref pages
*/
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'false') {
const oldMarkdown = await generateOldRefMarkdown(slug)
return oldMarkdown
} else {
return {
props: {
docs: markdownContent,
},
}
return {
props: {
docs: markdownContent,
},
}
}
+6
View File
@@ -218,6 +218,12 @@ export type enrichedOperation = OpenAPIV3.OperationObject & {
operationId: string
operation: string
responseList: []
description?: string
parameters?: []
responses?: {}
security?: []
summary?: string
tags?: []
}
export function gen_v3(spec: OpenAPIV3.Document, dest: string, { apiUrl }: { apiUrl: string }) {
+1 -1
View File
@@ -3,7 +3,7 @@ import type { NextRequest } from 'next/server'
import isbot from 'isbot'
export function middleware(request: NextRequest) {
const specs = ['javascript', 'dart']
const specs = ['javascript', 'dart', 'csharp']
let version = ''
if (request.url.includes('/v1/')) {
+2 -1
View File
@@ -53,6 +53,7 @@ const nextConfig = {
'github.com',
'user-images.githubusercontent.com',
'raw.githubusercontent.com',
'weweb-changelog.ghost.io',
],
},
experimental: {
@@ -84,6 +85,6 @@ const nextConfig = {
// next.config.js
export default () => {
// const plugins = [withMDX]/
const plugins = [withTM(['ui', 'common']), withMDX, withYaml]
const plugins = [withTM(['ui', 'common', '@supabase/auth-helpers-nextjs']), withMDX, withYaml]
return plugins.reduce((acc, next) => next(acc), nextConfig)
}
+20 -2
View File
@@ -8,8 +8,9 @@
"start": "next start",
"lint": "next lint",
"build:sitemap": "node ./internals/generate-sitemap.mjs",
"build:embeddings": "tsx scripts/generate-embeddings.ts",
"postbuild": "ts-node ./scripts/build-search.ts && node ./internals/generate-sitemap.mjs",
"generate:all": "npm-run-all --parallel gen:api gen:cli gen:gotrue gen:storage gen:supabase-dart:v0 gen:supabase-dart:v1 gen:supabase-js:v1 gen:supabase-js:v2 gen:realtime",
"generate:all": "npm-run-all --parallel gen:api gen:cli gen:gotrue gen:storage gen:supabase-dart:v0 gen:supabase-dart:v1 gen:supabase-csharp:v0 gen:supabase-js:v1 gen:supabase-js:v2 gen:realtime",
"gen:api": "npm-run-all gen:api:usage",
"gen:api:usage": "ts-node ./generator/index.ts gen --type api --url https://api.supabase.com --input ../../spec/transforms/api_v0_openapi_deparsed.json --output ./docs/reference/api/generated/usage.mdx",
"gen:cli": "npm-run-all gen:cli:commands gen:cli:config",
@@ -25,6 +26,8 @@
"gen:supabase-dart:v0:ref": "ts-node ./generator/index.ts gen --type legacy --input ../../spec/supabase_dart_v0.yml --output ./docs/reference/dart/v0/generated",
"gen:supabase-dart:v1": "npm-run-all gen:supabase-dart:v1:ref",
"gen:supabase-dart:v1:ref": "ts-node ./generator/index.ts gen --type legacy --input ../../spec/supabase_dart_v1.yml --output ./docs/reference/dart/generated",
"gen:supabase-csharp:v0": "npm-run-all gen:supabase-csharp:v0:ref",
"gen:supabase-csharp:v0:ref": "ts-node ./generator/index.ts gen --type legacy --input ../../spec/supabase_csharp_v0.yml --output ./docs/reference/csharp/generated",
"gen:supabase-js:v1": "npm-run-all gen:supabase-js:v1:ref",
"gen:supabase-js:v1:ref": "ts-node ./generator/index.ts gen --type legacy --input ../../spec/supabase_js_v1.yml --output ./docs/reference/javascript/v1/generated",
"gen:supabase-js:v2": "npm-run-all gen:supabase-js:v2:ref",
@@ -40,8 +43,12 @@
"@mdx-js/react": "^1.6.22",
"@next/mdx": "^12.0.4",
"@radix-ui/react-accordion": "^1.0.1",
"@supabase/auth-helpers-nextjs": "^0.5.4",
"@supabase/auth-helpers-react": "^0.3.1",
"@supabase/supabase-js": "^2.4.1",
"algoliasearch": "^4.14.2",
"babel": "^6.23.0",
"clsx": "^1.2.1",
"common": "*",
"config": "*",
"gray-matter": "^4.0.3",
@@ -49,7 +56,12 @@
"jsrsasign": "^10.5.26",
"lodash": "^4.17.21",
"markdown-toc": "^1.2.0",
"mdast": "^3.0.0",
"mdast-util-from-markdown": "^1.2.0",
"mdast-util-mdx": "^2.0.0",
"mdast-util-to-markdown": "^1.5.0",
"mdx-mermaid": "2.0.0-rc3",
"micromark-extension-mdxjs": "^1.0.0",
"next": "12.3.2",
"next-compose-plugins": "^2.2.1",
"next-mdx-remote": "^4.1.0",
@@ -57,25 +69,30 @@
"next-plugin-yaml": "^1.0.1",
"next-seo": "^5.14.1",
"next-transpile-modules": "^9.0.0",
"openai": "^3.1.0",
"react": "17.0.2",
"react-copy-to-clipboard": "^5.0.2",
"react-dom": "17.0.2",
"react-intersection-observer": "^9.4.0",
"react-markdown": "^8.0.3",
"react-syntax-highlighter": "^15.3.1",
"rehype-slug": "^5.1.0",
"remark": "^14.0.2",
"remark-admonitions": "^1.2.1",
"remark-gfm": "^3.0.1",
"remark-slug": "^7.0.1",
"shiki": "^0.11.1",
"sse.js": "^0.6.1",
"ui": "*",
"unist-builder": "^3.0.1",
"unist-util-filter": "^4.0.1",
"valtio": "^1.7.6"
},
"devDependencies": {
"dotenv": "^16.0.3",
"@types/node": "^17.0.12",
"@types/react": "17.0.39",
"config": "*",
"dotenv": "^16.0.3",
"ejs": "^3.1.8",
"eslint": "8.9.0",
"globby": "^12.0.2",
@@ -87,6 +104,7 @@
"sass": "^1.55.0",
"ts-node": "^10.9.1",
"tsconfig": "*",
"tsx": "^3.12.2",
"typescript": "^4.5.3"
}
}
+18 -10
View File
@@ -1,21 +1,25 @@
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
import { SessionContextProvider } from '@supabase/auth-helpers-react'
import { ThemeProvider } from 'common/Providers'
import { DefaultSeo } from 'next-seo'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { useEffect, useState } from 'react'
import { AppPropsWithLayout } from 'types'
import ClippyProvider from '~/components/Clippy/ClippyProvider'
import { SearchProvider } from '~/components/DocSearch'
import Favicons from '~/components/Favicons'
import { ThemeProvider } from 'common/Providers'
import SiteLayout from '~/layouts/SiteLayout'
import { post } from '~/lib/fetchWrappers'
import '../styles/algolia-search.scss'
import '../styles/ch.scss'
import '../styles/docsearch.scss'
import '../styles/main.scss?v=1.0.0'
import '../styles/new-docs.scss'
import '../styles/prism-okaidia.scss'
import { post } from '~/lib/fetchWrappers'
function MyApp({ Component, pageProps }: AppPropsWithLayout) {
const router = useRouter()
const [supabase] = useState(() => createBrowserSupabaseClient())
function telemetry(route: string) {
return post(`https://api.supabase.io/platform/telemetry/page`, {
@@ -81,13 +85,17 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
cardType: 'summary_large_image',
}}
/>
<ThemeProvider>
<SearchProvider>
<SiteLayout>
<Component {...pageProps} />
</SiteLayout>
</SearchProvider>
</ThemeProvider>
<SessionContextProvider supabaseClient={supabase}>
<ThemeProvider>
<SearchProvider>
<ClippyProvider>
<SiteLayout>
<Component {...pageProps} />
</SiteLayout>
</ClippyProvider>
</SearchProvider>
</ThemeProvider>
</SessionContextProvider>
</>
)
}
-161
View File
@@ -1,161 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
title: 'Overview',
description: 'An overview of Supabase\'s architecture and product principles.',
}
Supabase is open source. We choose open source tools which are scalable and make them simple to use.
Supabase is not a 1-to-1 mapping of Firebase. While we are building many of the features that Firebase offers, we are not going about it the same way:
our technological choices are quite different; everything we use is open source; and wherever possible, we use and support existing tools rather than developing from scratch.
Most notably, we use Postgres rather than a NoSQL store. This choice was deliberate. We believe that no other database offers the functionality required to compete with Firebase,
while maintaining the scalability required to go beyond it.
## Architecture
Each Supabase project consists of several tools:
![Supabase Architecture](/docs/img/supabase-architecture.png)
### PostgreSQL (Database)
PostgreSQL is the core of Supabase. We do not abstract the PostgreSQL database — you can access it and use it with full privileges. We simply provide tools which makes PostgreSQL as easy to use as Firebase.
- Official Docs: [postgresql.org/docs](https://www.postgresql.org/docs/current/index.html)
- Source code: [github.com/postgres/postgres](https://github.com/postgres/postgres) (mirror)
- License: [PostgreSQL Licence](https://www.postgresql.org/about/licence/)
- Language: C
### Studio (Dashboard)
An open source Dashboard for managing your database and services.
- Official Docs: [Supabase docs](/docs)
- Source code: [github.com/supabase/supabase](https://github.com/supabase/supabase/tree/master/studio)
- License: [Apache 2](https://github.com/supabase/supabase/blob/master/LICENSE)
- Language: TypeScript
### GoTrue (Auth)
A JWT-based API for managing users and issuing access tokens. This integrates with PostgreSQL's Row Level Security and the API servers.
- Official Docs: [Supabase Auth reference docs](/docs/reference/auth)
- Source code: [github.com/supabase/gotrue](https://github.com/supabase/gotrue)
- License: [MIT](https://github.com/supabase/gotrue/blob/master/LICENSE)
- Language: Go
### PostgREST (API)
A standalone web server that turns your PostgreSQL database directly into a RESTful API.
We use this with our [pg_graphql](https://github.com/supabase/pg_graphql) extension to provide a GraphQL API.
- Official Docs: [postgrest.org](https://postgrest.org/)
- Source code: [github.com/PostgREST/postgrest](https://github.com/PostgREST/postgrest)
- License: [MIT](https://github.com/PostgREST/postgrest/blob/main/LICENSE)
- Language: Haskel
### Realtime (API & multiplayer)
A scalable websocket engine for managing user Presence, broadcasting messages, and streaming database changes.
- Official Docs: [Supabase Realtime docs](/docs/guides/realtime)
- Source code: [github.com/supabase/realtime](https://github.com/supabase/realtime)
- License: [Apache 2](https://github.com/supabase/realtime/blob/main/LICENSE)
- Language: Elixir
### Storage API (large file storage)
An S3-compatible object storage service that stores metadata in Postgres.
- Official Docs: [Supabase Storage reference docs](/docs/reference/storage)
- Source code: [github.com/supabase/storage-api](https://github.com/supabase/storage-api)
- License: [Apache 2.0](https://github.com/supabase/storage-api/blob/master/LICENSE)
- Language: NodeJS / TypeScript
### Deno (Edge Functions)
A modern runtime for JavaScript and TypeScript.
- Official Docs: [deno.land](https://deno.land/)
- Source code: [github.com/denoland/deno](https://github.com/denoland/deno)
- License: [MIT](https://github.com/denoland/deno/blob/main/LICENSE.md)
- Language: TypeScript / Rust
### postgres-meta (Database management)
A RESTful API for managing your Postgres. Fetch tables, add roles, and run queries.
- Official Docs: [supabase.github.io/postgres-meta](https://supabase.github.io/postgres-meta/)
- Source code: [github.com/supabase/postgres-meta](https://github.com/supabase/postgres-meta)
- License: [Apache 2.0](https://github.com/supabase/postgres-meta/blob/master/LICENSE)
- Language: NodeJS / TypeScript
### PgBouncer
A lightweight connection pooler for PostgreSQL. This is useful for connecting to Postgres when using Serverless functions.
- Official Docs: [pgbouncer.org](http://www.pgbouncer.org/)
- Source code: [pgbouncer/pgbouncer](https://github.com/pgbouncer/pgbouncer)
- License: [ISC](https://github.com/pgbouncer/pgbouncer/blob/master/COPYRIGHT)
- Language: C
### Kong (API Gateway)
A cloud-native API gateway, built on top of Nginx.
- Official Docs: [docs.konghq.com](https://docs.konghq.com/)
- Source code: [github.com/kong/kong](https://github.com/kong/kong)
- License: [Apache 2.0](https://github.com/Kong/kong/blob/master/LICENSE)
- Language: Lua
## Product Principles
It is our goal to provide an architecture that any large-scale company would design for themselves,
and then provide tooling around that architecture that is easy-to-use for indie-developers and small teams.
We use a series of principles to ensure that scalability and usability are never mutually exclusive:
### Everything works in isolation
Each system must work as a standalone tool with as few moving parts as possible.
The litmus test for this is: "Can a user run this product with nothing but a Postgres database?"
### Everything is integrated
Supabase is composable. Even though every product works in isolation, each product on the platform needs to 10x the other products.
For integration, each tool should expose an API and Webhooks.
### Everything is extensible
We're deliberate about adding a new tool, and prefer instead to extend an existing one.
This is the opposite of many cloud providers whose product offering expands into niche use-cases. We provide _primitives_ for developers, which allow them to achieve any goal.
Less, but better.
### Everything is portable
To avoid lock-in, we make it easy to migrate in and out. Our cloud offering is compatible with our self-hosted product.
We use existing standards to increase portability (like pg_dump an CSV files). If a new standard emerges which competes with a "Supabase" approach, we will deprecate the approach in favor of the standard.
This forces us compete on experience. We aim to be the best Postgres hosting service.
### Play the long game
We sacrifice short-term wins for long-term gains. For example, it is tempting to run a fork of Postgres with additional functionality which only our customers need.
Instead, we prefer to support efforts to upstream missing functionality so that the entire community benefits. This has the additional benefit of ensuring portability and longevity.
### Build for developers
"Developers" are a specific profile of user: they are _builders_.
When assessing impact as a function of effort, developers have a large efficiency due to the type of products and systems they can build.
As the profile of a developer changes over time, Supabase will continue to evolve the product to fit this evolving profile.
### Support existing tools
Supabase supports existing tools and communities wherever possible. Supabase is more like a "community of communities" - each tool typically has its own community
which we work with.
Open source is something we approach [collaboratively](https://supabase.com/blog/supabase-series-b#giving-back): we employ maintainers, sponsor projects, invest in businesses, and develop our own open source tools.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -4,6 +4,7 @@ export const meta = {
id: 'auth-captcha',
title: 'Enable Captcha Protection',
description: 'Add Captcha Protection to your Supabase project',
video: 'https://www.youtube.com/v/em1cpOAXknM',
}
Supabase provides you with the option of adding captcha to your sign-in, sign-up, and password reset forms. This keeps your website safe from bots and malicious scripts. Supabase authentication has support for [hCaptcha](https://www.hcaptcha.com/).
+1 -1
View File
@@ -2,7 +2,7 @@ import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'index',
title: 'Auth Helpers',
title: 'Auth Helpers Overview',
description: 'A collection of framework-specific Auth utilities for working with Supabase.',
sidebar_label: 'Overview',
}
@@ -34,10 +34,7 @@ Pass `supabaseClient` from `@supabase/supabase-js` as a prop to the component.
import { createClient } from '@supabase/supabase-js'
import { Auth } from '@supabase/auth-ui-react'
const supabase = createClient(
'<INSERT PROJECT URL>',
'<INSERT PROJECT ANON API KEY>'
)
const supabase = createClient('<INSERT PROJECT URL>', '<INSERT PROJECT ANON API KEY>')
const App = () => <Auth supabaseClient={supabase} />
```
@@ -75,20 +72,29 @@ The Auth component also supports login with [offical social providers](../../aut
import { createClient } from '@supabase/supabase-js'
import { Auth, ThemeSupa } from '@supabase/auth-ui-react'
const supabase = createClient(
'<INSERT PROJECT URL>',
'<INSERT PROJECT ANON API KEY>'
)
const supabase = createClient('<INSERT PROJECT URL>', '<INSERT PROJECT ANON API KEY>')
const App = () => (
<Auth
supabaseClient={supabase}
appearance={{ theme: ThemeSupa }}
providers={['google', 'facebook', 'twitter']}
providers={['google', 'facebook', 'twitter']}
/>
)
```
### Supported Views
The Auth component is currently shipped with the following views:
- [Email Login](../auth-email)
- [Magic Link login](../auth-magic-link)
- [Social Login](../social-login)
- Update password
- Forgotten password
We are planning on adding more views in the future. Follow along on that [repo](https://github.com/supabase/auth-ui).
## Customization
There are several ways to customize Auth UI:
@@ -161,10 +167,7 @@ Auth UI themes can be overridden using variable tokens. See the [list of variabl
import { createClient } from '@supabase/supabase-js'
import { Auth, ThemeSupa } from '@supabase/auth-ui-react'
const supabase = createClient(
'<INSERT PROJECT URL>',
'<INSERT PROJECT ANON API KEY>'
)
const supabase = createClient('<INSERT PROJECT URL>', '<INSERT PROJECT ANON API KEY>')
const App = () => (
<Auth
@@ -247,10 +250,7 @@ You can use custom CSS classes for the following elements:
import { createClient } from '@supabase/supabase-js'
import { Auth } from '@supabase/auth-ui-react'
const supabase = createClient(
'<INSERT PROJECT URL>',
'<INSERT PROJECT ANON API KEY>'
)
const supabase = createClient('<INSERT PROJECT URL>', '<INSERT PROJECT ANON API KEY>')
const App = () => (
<Auth
@@ -275,10 +275,7 @@ You can use custom CSS inline styles for the following elements:
import { createClient } from '@supabase/supabase-js'
import { Auth } from '@supabase/auth-ui-react'
const supabase = createClient(
'<INSERT PROJECT URL>',
'<INSERT PROJECT ANON API KEY>'
)
const supabase = createClient('<INSERT PROJECT URL>', '<INSERT PROJECT ANON API KEY>')
const App = () => (
<Auth
@@ -302,10 +299,7 @@ You can use custom labels with `localization.variables`. See the [list of labels
import { createClient } from '@supabase/supabase-js'
import { Auth } from '@supabase/auth-ui-react'
const supabase = createClient(
'<INSERT PROJECT URL>',
'<INSERT PROJECT ANON API KEY>'
)
const supabase = createClient('<INSERT PROJECT URL>', '<INSERT PROJECT ANON API KEY>')
const App = () => (
<Auth
@@ -10,6 +10,8 @@ export const meta = {
This submodule provides experimental convenience helpers for implementing user authentication in Next.js Server Components - the `app` directory. For examples using the `pages` directory check out [Auth Helpers in Next.js](/docs/guides/auth/auth-helpers/nextjs).
> For a complete implementation example, check out [this repo](https://github.com/supabase/auth-helpers/tree/main/examples/nextjs-server-components).
> To learn more about fetching and caching Supabase data with Next.js 13 Server Components, check out our [blog](/blog/fetching-and-caching-supabase-data-in-next-js-server-components) or [live stream](https://www.youtube.com/watch?v=QH0P5xZt5wY).
## Install the Next.js helper library
@@ -52,6 +54,8 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
## Creating a Supabase Client
### Server-side
<Tabs
scrollable
size="small"
@@ -60,23 +64,13 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
>
<TabPanel id="js" label="JavaScript">
Create a new file at `/utils/supabase-browser.js` and populate with the following:
```js title="/utils/supabase-browser.js"
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
export default createBrowserSupabaseClient()
```
This will be used any time we need to create a Supabase client _client-side_ - in `useEffect`, for example.
Create a new file at `/utils/supabase-server.js` and populate with the following:
```js title="/utils/supabase-server.js"
import { headers, cookies } from 'next/headers'
import { createServerComponentSupabaseClient } from '@supabase/auth-helpers-nextjs'
export default () =>
export const createClient = () =>
createServerComponentSupabaseClient({
headers,
cookies,
@@ -85,33 +79,19 @@ export default () =>
> This needs to export a function, as the headers and cookies are not populated with values until the Server Component is requesting data.
This will be used any time we need to create a Supabase client _server-side_ - in a Server Component, for example.
</TabPanel>
<TabPanel id="ts" label="TypeScript">
Create a new file at `/utils/supabase-browser.ts` and populate with the following:
```ts
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
import { Database } from '../lib/database.types'
export default createBrowserSupabaseClient<Database>()
```
> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createBrowserSupabaseClient` to add type support to the Supabase client.
This will be used any time we need to create a Supabase client _client-side_ - in `useEffect`, for example.
Create a new file at `/utils/supabase-server.ts` and populate with the following:
```ts title="/utils/supabase-server.ts"
import { headers, cookies } from 'next/headers'
import { createServerComponentSupabaseClient } from '@supabase/auth-helpers-nextjs'
import { Database } from '../lib/database.types'
export default () =>
import type { Database } from '../lib/database.types'
export const createClient = () =>
createServerComponentSupabaseClient<Database>({
headers,
cookies,
@@ -122,14 +102,14 @@ export default () =>
> This needs to export a function, as the headers and cookies are not populated with values until the Server Component is requesting data.
This will be used any time we need to create a Supabase client _server-side_ - in a Server Component, for example.
</TabPanel>
</Tabs>
## Middleware
This will be used any time we need to create a Supabase client _server-side_ - in a Server Component, for example.
Middleware runs before every route declared in the `matcher` array. Since we don't have access to set cookies or headers from Server Components, we need to create a Middleware Supabase client and refresh the user's session by calling `getSession()`.
Next, we need a middleware file to refresh the user's session on navigation.
> If you were using Middleware prior to 12.2, see the [upgrade guide](https://nextjs.org/docs/messages/middleware-upgrade-guide).
<Tabs
scrollable
@@ -139,7 +119,7 @@ Middleware runs before every route declared in the `matcher` array. Since we don
>
<TabPanel id="js" label="JavaScript">
Create a new file at `/pages/middleware.js` and populate with the following:
Create a new `middleware.js` file at the same level as your `app` (in the root or `src` directory) and populate with the following:
```jsx title="middleware.js"
import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs'
@@ -156,27 +136,25 @@ export async function middleware(req) {
return res
}
export const config = {
matcher: ['/optional-session', '/required-session', '/realtime'],
}
```
</TabPanel>
<TabPanel id="ts" label="TypeScript">
Create a new file at `/middleware.ts` (a sibling of /pages) and populate with the following:
Create a new `middleware.ts` file at the same level as your `app` (in the root or `src` directory) and populate with the following:
```tsx title="middleware.ts"
import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs'
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import type { Database } from '../lib/database.types'
export async function middleware(req: NextRequest) {
const res = NextResponse.next()
const supabase = createMiddlewareSupabaseClient({ req, res })
const supabase = createMiddlewareSupabaseClient<Database>({ req, res })
const {
data: { session },
@@ -184,20 +162,34 @@ export async function middleware(req: NextRequest) {
return res
}
export const config = {
matcher: ['/optional-session', '/required-session', '/realtime'],
}
```
> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createMiddlewareSupabaseClient` to add type support to the Supabase client.
</TabPanel>
</Tabs>
> Any Server Component route that uses a Supabase client must be added to this middleware's `matcher` array. Without this, the Server Component may try to make a request to Supabase with an expired `access_token`.
We can now use our server-side Supabase client to fetch data in Server Components.
## Supabase Listener
```jsx
import 'server-only'
We need to set up a listener to fetch fresh data whenever our user logs in or out.
import { createClient } from '../../utils/supabase-server'
// do not cache this page
export const revalidate = 0
export default async function ServerComponent() {
const supabase = createClient()
const { data } = await supabase.from('posts').select('*')
return <pre>{JSON.stringify({ data }, null, 2)}</pre>
}
```
### Client-side
We still need a Supabase instance client-side for authentication and realtime subscriptions. It is important, when using Supabase client-side, to have a single instance of a client. We can share this singleton instance across our components using providers and React context.
<Tabs
scrollable
@@ -207,284 +199,298 @@ We need to set up a listener to fetch fresh data whenever our user logs in or ou
>
<TabPanel id="js" label="JavaScript">
Create a `/components/supabase-listener.jsx` file and add the following:
Create a new file at `/utils/supabase-browser.js` and populate with the following:
```js title="/utils/supabase-browser.js"
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
export const createClient = () => createBrowserSupabaseClient()
```
</TabPanel>
<TabPanel id="ts" label="TypeScript">
Create a new file at `/utils/supabase-browser.ts` and populate with the following:
```ts
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
import { Database } from '../lib/database.types'
export const createClient = () => createBrowserSupabaseClient<Database>()
```
> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createBrowserSupabaseClient` to add type support to the Supabase client.
</TabPanel>
</Tabs>
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
Next, we need to create a single instance of Supabase to use client-side. Let's create a new Provider for Supabase at `/components/supabase-provider.jsx` and populate with the following:
```jsx title=components/supabase-provider.jsx
'use client'
import { createContext, useContext, useState } from 'react'
import { createClient } from '../utils/supabase-browser'
const Context = createContext()
export default function SupabaseProvider({ children }) {
const [supabase] = useState(() => createClient())
useEffect(() => {
const { data: { subscription } } = supabase.auth.onAuthStateChange((event, session) => {
if (session?.access_token !== accessToken) {
router.refresh()
}
})
return () => subscription.unsubscribe()
}, [accessToken])
return (
<Context.Provider value={{ supabase }}>
<>{children}</>
</Context.Provider>
)
}
export const useSupabase = () => useContext(Context)
```
</TabPanel>
<TabPanel id="ts" label="TypeScript">
Next, we need to create a single instance of Supabase to use client-side. Let's create a new Provider for Supabase at `/components/supabase-provider.tsx` and populate with the following:
```tsx title=components/supabase-provider.tsx
'use client'
import { createContext, useContext, useState } from 'react'
import { createClient } from '../utils/supabase-browser'
import type { SupabaseClient } from '@supabase/auth-helpers-nextjs'
import type { Database } from '../lib/database.types'
type SupabaseContext = {
supabase: SupabaseClient<Database>
}
const Context = createContext<SupabaseContext | undefined>(undefined)
export default function SupabaseProvider({ children }: { children: React.ReactNode }) {
const [supabase] = useState(() => createClient())
return (
<Context.Provider value={{ supabase }}>
<>{children}</>
</Context.Provider>
)
}
export const useSupabase = () => {
let context = useContext(Context);
if (context === undefined) {
throw new Error("useSupabase must be used inside SupabaseProvider");
} else {
return context;
}
}
```
> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createBrowserSupabaseClient` to add type support to the Supabase client.
</TabPanel>
</Tabs>
We need to set up a listener to fetch fresh data whenever our user logs in or out. For this we need to check whether our client and server sessions match. Let's start by installing the `server-only` package.
```bash
npm install server-only
```
This will ensure that any component that imports this package will be a Server Component, and excluded from the browser bundle.
Next, let's modify our root layout to fetch the user's session, wrap our application in our Supabase Provider, and pass the server access token as a prop to the `<SupabaseListener />` component (we will create this next).
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
```jsx title=app/layout.jsx
import 'server-only'
import SupabaseListener from '../components/supabase-listener'
import SupabaseProvider from '../components/supabase-provider'
import './globals.css'
import { createClient } from '../utils/supabase-server'
// do not cache this layout
export const revalidate = 0
export default async function RootLayout({ children }) {
const supabase = createClient()
const {
data: { session },
} = await supabase.auth.getSession()
return (
<html lang="en">
{/*
<head /> will contain the components returned by the nearest parent
head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head
*/}
<head />
<body>
<SupabaseProvider>
<SupabaseListener serverAccessToken={session?.access_token} />
{children}
</SupabaseProvider>
</body>
</html>
)
}
```
</TabPanel>
<TabPanel id="ts" label="TypeScript">
```tsx title=app/layout.tsx
import 'server-only'
import SupabaseListener from '../components/supabase-listener'
import SupabaseProvider from '../components/supabase-provider'
import './globals.css'
import { createClient } from '../utils/supabase-server'
// do not cache this layout
export const revalidate = 0
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const supabase = createClient()
const {
data: { session },
} = await supabase.auth.getSession()
return (
<html lang="en">
{/*
<head /> will contain the components returned by the nearest parent
head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head
*/}
<head />
<body>
<SupabaseProvider>
<SupabaseListener serverAccessToken={session?.access_token} />
{children}
</SupabaseProvider>
</body>
</html>
)
}
```
</TabPanel>
</Tabs>
And now create our Supabase listener component that uses the singleton Supabase instance to listen for auth changes.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
```jsx title="/components/supabase-listener.jsx"
'use client'
import { useRouter } from 'next/navigation'
import { useEffect } from 'react'
import supabase from '../utils/supabase-browser'
import { useSupabase } from './supabase-provider'
export default function SupabaseListener({ accessToken }) {
export default function SupabaseListener({ serverAccessToken }) {
const { supabase } = useSupabase()
const router = useRouter()
useEffect(() => {
supabase.auth.onAuthStateChange((event, session) => {
if (session?.access_token !== accessToken) {
router.reload()
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((event, session) => {
if (session?.access_token !== serverAccessToken) {
router.refresh()
}
})
}, [accessToken])
return () => {
subscription.unsubscribe()
}
}, [serverAccessToken, router, supabase])
return null
}
```
> `use client` tells Next.js that this is a [Client Component](https://beta.nextjs.org/docs/rendering/server-and-client-components#client-components). Only Client Components can use hooks like `useEffect` and `useRouter`.
The function we pass to `onAuthStateChange` is automatically called by Supabase whenever a user's session changes. This component takes an `accessToken` prop, which will be the server's state for our user (we'll set this up next). If the `accessToken` from the server and the new `access_token` do not match then the client and server are out of sync, therefore, we want to reload the active route.
Lastly, fetch the server-side session in the `RootLayout` and pass it to our new `<SupabaseListener />` component.
```jsx title="/app/layout.jsx"
import SupabaseListener from '../components/supabase-listener'
import createClient from '../utils/supabase-server'
export default async function RootLayout({ children }) {
const supabase = createClient()
const {
data: { session },
} = await supabase.auth.getSession()
return (
// html and head section omitted
<body>
<SupabaseListener accessToken={session?.access_token} />
{children}
</body>
)
}
```
We don't want Next.js to cache this `session` value, so we need to export a `revalidate` value of `0` in the layout file.
```jsx
export const revalidate = 0
```
We also want to tell Next.js to explicitly exclude this component's code from the client bundle by installing the `server-only` package:
```bash
npm install server-only
```
And importing it at the top of our component.
```jsx
import 'server-only'
```
The entire Layout component should look something like this:
```jsx title="/app/layout.jsx"
import 'server-only'
import SupabaseListener from '../components/supabase-listener'
import './globals.css'
import createClient from '../utils/supabase-server'
// do not cache this layout
export const revalidate = 0
export default async function RootLayout({ children }) {
const supabase = createClient()
const {
data: { session },
} = await supabase.auth.getSession()
return (
<html lang="en">
{/*
<head /> will contain the components returned by the nearest parent
head.jsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head
*/}
<head />
<body>
<SupabaseListener accessToken={session?.access_token} />
{children}
</body>
</html>
)
}
```
</TabPanel>
<TabPanel id="ts" label="TypeScript">
Create a `/components/supabase-listener.tsx` file and add the following:
```tsx title="/components/supabase-listener.tsx"
'use client'
import { useRouter } from 'next/navigation'
import { useEffect } from 'react'
import supabase from '../utils/supabase-browser'
import { useSupabase } from './supabase-provider'
export default function SupabaseListener({ accessToken }: { accessToken?: string }) {
export default function SupabaseListener({ serverAccessToken }: { serverAccessToken?: string }) {
const { supabase } = useSupabase()
const router = useRouter()
useEffect(() => {
supabase.auth.onAuthStateChange((event, session) => {
if (session?.access_token !== accessToken) {
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((event, session) => {
if (session?.access_token !== serverAccessToken) {
router.refresh()
}
})
}, [accessToken])
return () => {
subscription.unsubscribe()
}
}, [serverAccessToken, router, supabase])
return null
}
```
</TabPanel>
</Tabs>
> `use client` tells Next.js that this is a [Client Component](https://beta.nextjs.org/docs/rendering/server-and-client-components#client-components). Only Client Components can use hooks like `useEffect` and `useRouter`.
The function we pass to `onAuthStateChange` is automatically called by Supabase whenever a user's session changes. This component takes an `accessToken` prop, which will be the server's state for our user (we'll set this up next). If the `accessToken` from the server and the new `access_token` do not match then the client and server are out of sync, therefore, we want to reload the active route.
The function we pass to `onAuthStateChange` is automatically called by Supabase whenever a user's session changes. This component takes an `serverAccessToken` prop, which is the server's state for our user. If the `serverAccessToken` and the new session's `access_token` do not match then the client and server are out of sync, therefore, we want to reload the active route.
Lastly, fetch the server-side session in the `RootLayout` and pass it to our new `<SupabaseListener />` component.
```tsx title="/app/layout.tsx"
import SupabaseListener from '../components/supabase-listener'
import createClient from '../utils/supabase-server'
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const supabase = createClient()
const {
data: { session },
} = await supabase.auth.getSession()
return (
// html and head section omitted
<body>
<SupabaseListener accessToken={session?.access_token} />
{children}
</body>
)
}
```
> Again, TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createServerComponentSupabaseClient` to add type support to the Supabase client.
We don't want Next.js to cache this `session` value, so we need to export a `revalidate` value of `0`.
```tsx
export const revalidate = 0
```
We also want to tell Next.js to explicitly exclude this component's code from the client bundle by installing the `server-only` package:
```bash
npm install server-only
```
And importing it at the top of our component.
```tsx
import 'server-only'
```
The entire Layout component should look something like this:
```tsx title="/app/layout.tsx"
import 'server-only'
import SupabaseListener from '../components/supabase-listener'
import './globals.css'
import createClient from '../utils/supabase-server'
// do not cache this layout
export const revalidate = 0
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const supabase = createClient()
const {
data: { session },
} = await supabase.auth.getSession()
return (
<html lang="en">
{/*
<head /> will contain the components returned by the nearest parent
head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head
*/}
<head />
<body>
<SupabaseListener accessToken={session?.access_token} />
{children}
</body>
</html>
)
}
```
</TabPanel>
</Tabs>
Supabase can now be used in any Client or Server component.
## Server Components
Next.js recommends fetching data in Server Components whenever possible.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
```jsx
import 'server-only'
import createClient from '../../utils/supabase-server'
// do not cache this page
export const revalidate = 0
export default async function ServerComponent() {
const supabase = createClient()
const { data } = await supabase.from('posts').select('*')
return <pre>{JSON.stringify({ data }, null, 2)}</pre>
}
```
</TabPanel>
<TabPanel id="ts" label="TypeScript">
```tsx
import 'server-only'
import createClient from '../../utils/supabase-server'
// do not cache this page
export const revalidate = 0
export default async function ServerComponent() {
const supabase = createClient()
const { data } = await supabase.from('posts').select('*')
return <pre>{JSON.stringify({ data }, null, 2)}</pre>
}
```
</TabPanel>
</Tabs>
## Client Components
While Next.js recommend doing all data fetching in Server Components, we still need Supabase client-side for things like authentication and subscribing to realtime updates.
Now we can use our `useSupabase` hook throughout our client-side components.
### Authentication
We can call any of [Supabase's authentication methods](https://supabase.com/docs/reference/javascript/v1/auth-signin) - such as `supabase.auth.signInWithOAuth` - from a client component.
<Tabs
scrollable
size="small"
@@ -496,30 +502,33 @@ We can call any of [Supabase's authentication methods](https://supabase.com/docs
```jsx title="/components/login.jsx"
'use client'
import supabase from '../utils/supabase-browser'
import { useSupabase } from './supabase-provider'
// Supabase auth needs to be triggered client-side
export default function Login() {
const handleLogin = async () => {
const { error } = await supabase.auth.signInWithOAuth({
const { supabase, session } = useSupabase()
const handleEmailLogin = async () => {
await supabase.auth.signInWithPassword({
email: 'jon@supabase.com',
password: 'password',
})
}
const handleGitHubLogin = async () => {
await supabase.auth.signInWithOAuth({
provider: 'github',
})
if (error) {
console.log({ error })
}
}
const handleLogout = async () => {
const { error } = await supabase.auth.signOut()
if (error) {
console.log({ error })
}
await supabase.auth.signOut()
}
return (
<>
<button onClick={handleLogin}>Login</button>
<button onClick={handleEmailLogin}>Email Login</button>
<button onClick={handleGitHubLogin}>GitHub Login</button>
<button onClick={handleLogout}>Logout</button>
</>
)
@@ -533,30 +542,33 @@ export default function Login() {
```tsx title="/components/login.tsx"
'use client'
import supabase from '../utils/supabase-browser'
import { useSupabase } from './supabase-provider'
// Supabase auth needs to be triggered client-side
export default function Login() {
const handleLogin = async () => {
const { error } = await supabase.auth.signInWithOAuth({
const { supabase, session } = useSupabase()
const handleEmailLogin = async () => {
await supabase.auth.signInWithPassword({
email: 'jon@supabase.com',
password: 'password',
})
}
const handleGitHubLogin = async () => {
await supabase.auth.signInWithOAuth({
provider: 'github',
})
if (error) {
console.log({ error })
}
}
const handleLogout = async () => {
const { error } = await supabase.auth.signOut()
if (error) {
console.log({ error })
}
await supabase.auth.signOut()
}
return (
<>
<button onClick={handleLogin}>Login</button>
<button onClick={handleEmailLogin}>Email Login</button>
<button onClick={handleGitHubLogin}>GitHub Login</button>
<button onClick={handleLogout}>Logout</button>
</>
)
@@ -586,10 +598,11 @@ Create a new file at `/app/realtime/posts.jsx` and populate with the following:
'use client'
import { useEffect, useState } from 'react'
import supabase from '../../utils/supabase-browser'
import { useSupabase } from '../../components/supabase-provider'
export default function Posts({ serverPosts }) {
const [posts, setPosts] = useState(serverPosts)
const { supabase } = useSupabase()
useEffect(() => {
setPosts(serverPosts)
@@ -606,14 +619,12 @@ export default function Posts({ serverPosts }) {
return () => {
supabase.removeChannel(channel)
}
}, [serverPosts])
}, [supabase, setPosts, posts])
return <pre>{JSON.stringify(posts, null, 2)}</pre>
}
```
> The first `useEffect` is required to overwrite posts if the `serverPosts` prop changes.
This can now be used in a Server Component to subscribe to realtime updates.
Create a new file at `/app/realtime/page.jsx` and populate with the following:
@@ -621,7 +632,7 @@ Create a new file at `/app/realtime/page.jsx` and populate with the following:
```jsx title="/app/realtime/page.jsx"
import 'server-only'
import createClient from '../../utils/supabase-server'
import { createClient } from '../../utils/supabase-server'
import Posts from './posts'
// do not cache this page
@@ -645,13 +656,15 @@ Create a new file at `/app/realtime/posts.tsx` and populate with the following:
'use client'
import { useEffect, useState } from 'react'
import { Database } from '../lib/database.types'
import supabase from '../../utils/supabase-browser'
import { useSupabase } from '../../components/supabase-provider'
import type { Database } from '../../lib/database.types'
type Post = Database['public']['Tables']['posts']['Row']
export default function Posts({ serverPosts }: { serverPosts: Post[] }) {
const [posts, setPosts] = useState(serverPosts)
const { supabase } = useSupabase()
useEffect(() => {
setPosts(serverPosts)
@@ -668,13 +681,13 @@ export default function Posts({ serverPosts }: { serverPosts: Post[] }) {
return () => {
supabase.removeChannel(channel)
}
}, [serverPosts])
}, [supabase, setPosts, posts])
return <pre>{JSON.stringify(posts, null, 2)}</pre>
}
```
> The first `useEffect` is required to overwrite posts if the `serverPosts` prop changes.
> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createServerSupabaseClient` to add type support to the Supabase client.
This can now be used in a Server Component to subscribe to realtime updates.
@@ -683,7 +696,7 @@ Create a new file at `/app/realtime/page.tsx` and populate with the following:
```tsx title="/app/realtime/page.tsx"
import 'server-only'
import createClient from '../../utils/supabase-server'
import { createClient } from '../../utils/supabase-server'
import Posts from './posts'
// do not cache this page
@@ -584,7 +584,7 @@ export async function middleware(req: NextRequest) {
}
export const config = {
matcher: '/middleware-protected',
matcher: '/middleware-protected/:path*',
}
```
@@ -594,7 +594,7 @@ export const config = {
To make these helpers more flexible as well as more maintainable and easier to upgrade for new versions of Next.js, we're stripping them down to the most useful part which is managing the cookies and giving you an authenticated supabase-js client in any environment (client, server, middleware/edge).
Therefore we're marking the `withApiAuth`, `withPageAuth`, and `withMiddlewareAuth` higher order functions as deprectaed and they will be removed in the next **minor** release (v0.6.X).
Therefore we're marking the `withApiAuth`, `withPageAuth`, and `withMiddlewareAuth` higher order functions as deprecated and they will be removed in the next **minor** release (v0.6.X).
Please follow the steps below to update your API routes, pages, and middleware handlers. Thanks!
@@ -782,7 +782,7 @@ export const config = {
### Migrating to v0.4.X and supabase-js v2
With the update to `supabase-js` v2 the `auth` API routes are no longer required, therefore you can go ahead and delete your `auth` directory under the `/pages/api/` directory. Please refer to the [v2 migration guide](/docs/reference/javascript/upgrade-guide) for the full set of changes within supabase-js.
With the update to `supabase-js` v2 the `auth` API routes are no longer required, therefore you can go ahead and delete your `auth` directory under the `/pages/api/` directory. Please refer to the [v2 migration guide](/docs/reference/javascript/v1/upgrade-guide) for the full set of changes within supabase-js.
The `/api/auth/logout` API route has been removed, please use the `signout` method instead:
@@ -39,7 +39,7 @@ yarn add @supabase/auth-helpers-sveltekit
### Configuration
Set up the fillowing env vars. For local development you can set them in a `.env` file. See an [example](https://github.com/supabase/auth-helpers/blob/main/examples/sveltekit/.env.example).
Set up the following env vars. For local development you can set them in a `.env` file. See an [example](https://github.com/supabase/auth-helpers/blob/main/examples/sveltekit/.env.example).
```bash
# Find these in your Supabase project settings > API
@@ -257,7 +257,7 @@ Wrap an Action to check that the user has a valid session. If they're not logged
```ts title=src/routes/posts/+page.server.ts
import type { Actions } from './$types'
import { getSupabase } from '@supabase/auth-helpers-sveltekit'
import { error, invalid } from '@sveltejs/kit'
import { error, fail } from '@sveltejs/kit'
export const actions: Actions = {
createPost: async (event) => {
@@ -276,7 +276,7 @@ export const actions: Actions = {
.insert({ content })
if (createPostError) {
return invalid(500, {
return fail(500, {
supabaseErrorMessage: createPostError.message,
})
}
@@ -293,7 +293,7 @@ If you try to submit a form with the action `?/createPost` without a valid sessi
```ts
import type { Actions } from './$types'
import { invalid, redirect } from '@sveltejs/kit'
import { fail, redirect } from '@sveltejs/kit'
import { getSupabase } from '@supabase/auth-helpers-sveltekit'
import { AuthApiError } from '@supabase/supabase-js'
@@ -313,14 +313,14 @@ export const actions: Actions = {
if (error) {
if (error instanceof AuthApiError && error.status === 400) {
return invalid(400, {
return fail(400, {
error: 'Invalid credentials.',
values: {
email,
},
})
}
return invalid(500, {
return fail(500, {
error: 'Server error. Try again later.',
values: {
email,
@@ -12,9 +12,7 @@ Even though Supabase provides an `auth.users` table, it can be helpful to create
## Creating user tables
When you create tables to store user data, it's helpful to reference the `auth.users` table in the primary key. This ensures data integrity.
For example, a `public.profiles` table might look like this:
When you create tables to store user data, it's helpful to reference the `auth.users` table in the primary key to ensure data integrity. Also specify the `on delete cascade` clause when referencing `auth.users`. Omitting it may cause problems when deleting users. For example, a `public.profiles` table might look like this:
```sql
create table public.profiles (
@@ -28,12 +26,18 @@ create table public.profiles (
alter table public.profiles enable row level security;
```
<Admonition type="note">
<Admonition type="caution">
Make sure to specify the `on delete cascade` clause when referencing `auth.users`. Omitting it may cause problems when deleting users.
Only use primary keys as [foreign key references](https://www.postgresql.org/docs/current/tutorial-fk.html) for schemas and tables like `auth.users` which are managed by Supabase. PostgreSQL lets you specify a foreign key reference for columns backed by a unique index (not necessarily primary keys).
Primary keys are **guaranteed not to change**. Columns, indices, constraints or other database objects managed by Supabase **may change at any time** and you should be careful when referencing them directly.
</Admonition>
## Deleting Users
You may delete users directly or via the management console at Authentication > Users. Note that deleting a user from the `auth.users` table does not automatically sign out a user. As Supabase makes use of JSON Web Tokens (JWT), a user's JWT will remain "valid" until it has expired. Should you wish to immediately revoke access for a user, do considering making use of a Row Level Security policy as described below.
## Public access
Since Row Level Security is enabled, this table is accessible via the API but no data will be returned unless we set up some Policies.
@@ -93,7 +97,7 @@ If the trigger fails however, it could block the user sign ups - so make sure th
For example:
```sql
-- inserts a row into public.users
-- inserts a row into public.profiles
create function public.handle_new_user()
returns trigger
language plpgsql
+30 -12
View File
@@ -1,5 +1,5 @@
import Layout from '~/layouts/DefaultGuideLayout'
import NewContent from '~/pages/new/auth.mdx'
import { useRouter } from 'next/router'
export const meta = {
@@ -7,6 +7,7 @@ export const meta = {
title: 'Auth',
description: 'Use Supabase to Authenticate and Authorize your users.',
sidebar_label: 'Overview',
video: 'https://www.youtube.com/v/6ow_jW4epf8',
}
## Overview
@@ -43,8 +44,16 @@ You can authenticate your users in several ways:
We provide a suite of Providers and login methods, as well as [Auth helpers](/docs/guides/auth/auth-helpers/).
#### Social Auth
<div className="container" style={{ padding: 0 }}>
<AuthProviders />
<AuthProviders type="social" />
</div>
#### Phone Auth
<div className="container" style={{ padding: 0 }}>
<AuthProviders type="phone" />
</div>
### Configure third-party providers
@@ -115,7 +124,12 @@ Get started with our [Row Level Security Guides](/docs/guides/auth/row-level-sec
Authentication only gets you so far. When you need granular authorization rules, nothing beats PostgreSQL's [Row Level Security (RLS)](https://www.postgresql.org/docs/current/ddl-rowsecurity.html). Supabase makes it simple to turn RLS on and off.
<video width="99%" muted playsInline controls="true">
<source src="/docs/videos/rls-zoom2.mp4" type="video/mp4" muted playsInline />
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/rls-zoom2.mp4"
type="video/mp4"
muted
playsInline
/>
</video>
### Policies
@@ -123,7 +137,12 @@ Authentication only gets you so far. When you need granular authorization rules,
[Policies](https://www.postgresql.org/docs/current/sql-createpolicy.html) are PostgreSQL's rule engine. They are incredibly powerful and flexible, allowing you to write complex SQL rules which fit your unique business needs.
<video width="99%" muted playsInline controls="true">
<source src="/docs/videos/policies-zoom2.mp4" type="video/mp4" muted playsInline />
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/policies-zoom2.mp4"
type="video/mp4"
muted
playsInline
/>
</video>
With policies, your database becomes the rules engine. Instead of repetitively filtering your queries, like this ...
@@ -171,15 +190,14 @@ Supabase provides multiple endpoints to authenticate and manage your users:
When users sign up, Supabase assigns them a unique ID. You can reference this ID anywhere in your database. For example, you might create a `profiles` table referencing `id` in the `auth.users` table using a `user_id` field.
<video width="99%" muted playsInline controls="true">
<source src="/docs/videos/auth-zoom2.mp4" type="video/mp4" muted playsInline />
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/auth-zoom2.mp4"
type="video/mp4"
muted
playsInline
/>
</video>
export const Page = ({ children }) => {
const router = useRouter()
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true' && !router.asPath.includes('overview')) {
return NewContent()
}
return <Layout meta={meta} children={children} />
}
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -4,6 +4,7 @@ export const meta = {
id: 'auth-twilio',
title: 'Phone Auth with Twilio',
description: 'How to set up and use Mobile OTP with Twilio and Supabase.',
video: 'https://www.youtube.com/v/akScoPO01bc',
}
## Overview
@@ -4,6 +4,7 @@ export const meta = {
id: 'row-level-security',
title: 'Row Level Security',
description: 'Secure your data using Postgres Row Level Security.',
video: 'https://www.youtube.com/v/Ow_Uzedfohk',
}
When you need granular authorization rules, nothing beats PostgreSQL's [Row Level Security (RLS)](https://www.postgresql.org/docs/current/ddl-rowsecurity.html).
@@ -74,14 +74,12 @@ When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/aut
```js
async function signInWithAzure() {
const { data, error } = await supabase.auth.signInWithOAuth(
{
provider: 'azure',
},
{
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'azure',
options: {
scopes: 'email',
}
)
},
})
}
```
@@ -6,7 +6,7 @@ export const meta = {
description: 'Add Twitter OAuth to your Supabase project',
}
To enable Twitter Auth for your project, you need to set up a Twitter OAuth application with [elevated access](https://developer.twitter.com/en/portal/products/elevated)and add the application credentials in the Supabase Dashboard.
To enable Twitter Auth for your project, you need to set up a Twitter OAuth application with [elevated access](https://developer.twitter.com/en/portal/products/elevated) and add the application credentials in the Supabase Dashboard.
## Overview
@@ -1,12 +1,9 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'cli',
title: 'Supabase CLI',
description:
'The Supabase CLI provides tools to develop your project locally and deploy to the Supabase Platform.',
sidebar_label: 'Overview',
toc_max_heading_level: 2,
}
The Supabase CLI provides tools to develop your project locally and deploy to the Supabase Platform.
@@ -113,7 +110,8 @@ brew upgrade supabase
## See also
- [Supabase CLI Reference](/docs/reference/cli/usage)
- [Supabase CLI Reference](/docs/reference/cli/introduction)
- [Supabase CLI Configuration](/docs/reference/cli/config)
- [Local Development](/docs/guides/cli/local-development)
- [Managing Environments](/docs/guides/cli/managing-environments)
@@ -4,6 +4,7 @@ export const meta = {
id: 'local-development',
title: 'Local Development',
description: 'How to use Supabase on your local development machine.',
video: 'https://www.youtube.com/v/vyHyYpvjaks',
}
Learn how to use the Supabase CLI to develop your project locally and deploy to the Supabase Platform.
@@ -31,6 +32,12 @@ Make sure you have these installed on your local machine:
supabase login
```
<Admonition type="note">
If you installed the Supabase CLI via NPM you may have to use `npx supabase login`.
</Admonition>
## Initialize your project
Create a new folder for your project and start a new git repository:
@@ -145,10 +152,11 @@ Database changes are managed through "migrations." Database migrations are a com
For this guide, create a table called `employees`. In Supabase Studio, navigate to the **SQL Editor** page and run the following SQL command:
```sql
create table employees (
id integer primary key generated always as identity,
name text
);
create table
employees (
id integer primary key generated always as identity,
name text
);
```
<Admonition type="note">
@@ -170,8 +178,9 @@ This creates a new migration named `supabase/migrations/<timestamp>_create_emplo
Use the seed script in `supabase/seed.sql` (created with [`supabase init`](/docs/reference/cli/usage#supabase-init)) to add sample data to the table.
```sql
-- in supabase/seed.sql
insert into public.employees (name)
-- in supabase/seed.sql
insert into
public.employees (name)
values
('Erlich Bachman'),
('Richard Hendricks'),
@@ -191,9 +200,9 @@ You should now see the contents of `employees` in Studio.
Use the [`reset`](/docs/reference/cli/usage#supabase-db-reset) command to revert any changes to the local database.
```sql
-- run on local database to make a change
alter table employees
add department text default 'Hooli';
-- run on local database to make a change
alter table
employees add department text default 'Hooli';
```
Run the following command to reset the local database:
@@ -251,10 +260,6 @@ The local development environment is not as feature-complete as the Supabase Pla
- Logs are not supported through the interface (however you can access them through the Docker containers).
- You cannot update your project settings in the Dashboard—this must be done using the CLI.
```
```
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -4,6 +4,7 @@ export const meta = {
id: 'managing-environments',
title: 'Managing Environments',
description: 'How to deploy Supabase schema changes with a CI / CD pipeline.',
video: 'https://www.youtube.com/v/rOLyOsBR1Uc',
}
## Overview
@@ -215,8 +216,6 @@ jobs:
- uses: actions/checkout@v3
- uses: supabase/setup-cli@v1
with:
version: 1.0.0
- name: Start Supabase local development setup
run: supabase start
@@ -256,8 +255,6 @@ jobs:
- uses: actions/checkout@v3
- uses: supabase/setup-cli@v1
with:
version: 1.0.0
- run: |
supabase link --project-ref $STAGING_PROJECT_ID
@@ -289,8 +286,6 @@ jobs:
- uses: actions/checkout@v3
- uses: supabase/setup-cli@v1
with:
version: 1.0.0
- run: |
supabase link --project-ref $PRODUCTION_PROJECT_ID
+22 -4
View File
@@ -34,7 +34,10 @@ You don't have to be a database expert to start using Supabase. Our table view m
Dig into the relationships within your data.
<video width="99%" loop="" muted="" playsInline="" controls="true">
<source src="/docs/videos/relational-drilldown-zoom.mp4" type="video/mp4" />
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/relational-drilldown-zoom.mp4"
type="video/mp4"
/>
</video>
### Clone tables
@@ -42,7 +45,12 @@ Dig into the relationships within your data.
You can duplicate your tables, just like you would inside a spreadsheet.
<video width="99%" muted playsInline controls={true}>
<source src="/docs/videos/duplicate-tables.mp4" type="video/mp4" muted playsInline />
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/duplicate-tables.mp4"
type="video/mp4"
muted
playsInline
/>
</video>
### The SQL Editor
@@ -50,7 +58,12 @@ You can duplicate your tables, just like you would inside a spreadsheet.
Supabase comes with a SQL Editor. You can also save your favorite queries to run later!
<video width="99%" muted playsInline controls={true}>
<source src="/docs/videos/favorites.mp4" type="video/mp4" muted playsInline />
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/favorites.mp4"
type="video/mp4"
muted
playsInline
/>
</video>
### Additional features
@@ -74,7 +87,12 @@ To expand the functionality of your Postgres database, you can use extensions.
You can enable Postgres extensions with the click of a button within the Supabase dashboard.
<video width="99%" muted playsInline controls={true}>
<source src="/docs/videos/toggle-extensions.mp4" type="video/mp4" muted playsInline />
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/toggle-extensions.mp4"
type="video/mp4"
muted
playsInline
/>
</video>
[Learn more](/docs/guides/database/extensions) about all the extensions provided on Supabase.
@@ -5,6 +5,7 @@ export const meta = {
title: 'Serverless APIs',
description: 'Auto-generating and Realtime APIs.',
sidebar_label: 'Overview',
video: 'https://www.youtube.com/v/rPAJJFdtPw0',
}
Supabase auto-generates three types of API directly from your database schema.
@@ -55,6 +56,7 @@ GraphQL in Supabase works through [pg_graphql](https://supabase.com/blog/pg-grap
Supabase provides a Realtime API using [Realtime](https://github.com/supabase/realtime). You can use this to listen to database changes over websockets.
Realtime leverages PostgreSQL's built-in logical replication. You can manage your Realtime API simply by managing Postgres publications.
Go to your project's [Replication section](https://app.supabase.com/project/_/database/replication) to get started.
## Getting started
@@ -83,7 +85,12 @@ This creates a corresponding route `todos` which can accept `GET`, `POST`, `PATC
1. Click **Save**.
<video width="99%" muted playsInline controls="true">
<source src={'/docs/videos/api/api-create-table-sm.mp4'} type="video/mp4" muted playsInline />
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/api/api-create-table-sm.mp4"
type="video/mp4"
muted
playsInline
/>
</video>
</TabPanel>
@@ -111,7 +118,12 @@ Every Supabase project has a unique API URL. Your API is secured behind an API g
3. Find your API `URL`, `anon`, and `service_role` keys on this page.
<video width="99%" muted playsInline controls="true">
<source src={'/docs/videos/api/api-url-and-key.mp4'} type="video/mp4" muted playsInline />
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/api/api-url-and-key.mp4"
type="video/mp4"
muted
playsInline
/>
</video>
The REST API and the GraphQL API are both accessible through this URL:
@@ -140,7 +152,12 @@ Let's view the documentation for a `countries` table which we created in our dat
3. Switch between the JavaScript and the cURL docs using the tabs.
<video width="99%" muted playsInline controls="true">
<source src={'/docs/videos/api/api-docs.mp4'} type="video/mp4" muted playsInline />
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/api/api-docs.mp4"
type="video/mp4"
muted
playsInline
/>
</video>
#### GraphQL
@@ -192,12 +209,12 @@ curl '<SUPABASE_URL>/rest/v1/todos' \
</TabPanel>
</Tabs>
JS Reference: [select()](../reference/javascript/select),
[insert()](../reference/javascript/insert),
[update()](../reference/javascript/update),
[upsert()](../reference/javascript/upsert),
[delete()](../reference/javascript/delete),
[rpc()](../reference/javascript/rpc) (call Postgres functions).
JS Reference: [select()](/reference/javascript/select),
[insert()](/reference/javascript/insert),
[update()](/reference/javascript/update),
[upsert()](/reference/javascript/upsert),
[delete()](/reference/javascript/delete),
[rpc()](/reference/javascript/rpc) (call Postgres functions).
### GraphQL API
@@ -217,7 +234,7 @@ import { createClient, useQuery } from 'urql'
// Prepare API key and Authorization header
const headers = {
apikey: <SUPABASE_ANON_KEY>,
authorization: `Bearer ${<SUPABASE_ANON_KEY}`>
authorization: `Bearer ${<SUPABASE_ANON_KEY>}`,
}
// Create GraphQL client
@@ -285,7 +302,12 @@ By default Realtime is disabled on your database. Let's turn on Realtime for the
4. Control which tables broadcast changes by selecting **Source** and toggling each table.
<video width="99%" muted playsInline controls="true">
<source src={'/docs/videos/api/api-realtime.mp4'} type="video/mp4" muted playsInline />
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/api/api-realtime.mp4"
type="video/mp4"
muted
playsInline
/>
</video>
</TabPanel>
@@ -4,10 +4,20 @@ export const meta = {
id: 'generating-types',
title: 'Generating Types',
description: 'How to generate types for your API and Supabase libraries.',
video: 'https://www.youtube.com/v/7CqlTU9aOR4',
}
Supabase APIs are generated from your database, which means that we can use database introspection to generate type-safe API definitions.
<div className="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/7CqlTU9aOR4"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
## Generating types using Supabase CLI
The Supabase CLI is a single binary Go application that provides everything you need to setup a local development environment.
@@ -28,15 +28,15 @@ You can use these simple questions to determine which connection method to use:
## API
Supabase provides an auto-updating [API](/docs/guides/api). This is the easiest way to get started if you are managing data (fetching, inserting, updating).
Supabase provides an auto-updating [API](/docs/guides/database/api). This is the easiest way to get started if you are managing data (fetching, inserting, updating).
### Interfaces
We provides several types of API to suit your preferences and use-case:
- [REST](/docs/guides/api#rest-api): interact with your database through a REST interface.
- [GraphQL](/docs/guides/api#graphql-api): interact with your database through a GraphQL interface.
- [Realtime](/docs/guides/api#realtime-api): listen to database changes over websockets.
- [REST](/docs/guides/database/api#rest-api): interact with your database through a REST interface.
- [GraphQL](/docs/guides/database/api#graphql-api): interact with your database through a GraphQL interface.
- [Realtime](/docs/guides/database/api#realtime-api): listen to database changes over websockets.
You cannot manage the database schema via the API (for security reasons). To do that you can use the dashboard or connect directly to your database.
@@ -45,7 +45,12 @@ You cannot manage the database schema via the API (for security reasons). To do
You can find the API URL and Keys in the [Dashboard](https://app.supabase.com/project/_/settings/api).
<video width="99%" muted playsInline controls="true">
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/api/api-url-and-key.mp4"
type="video/mp4"
muted
playsInline
/>
</video>
## Direct connections
@@ -59,7 +64,12 @@ Every Supabase project provides a full Postgres database. You can connect to the
3. Find your Connection Info and Connection String. Direct connections are on port `5432`.
<video width="99%" muted playsInline controls="true">
<source src="/docs/videos/postgres-connection.mp4" type="video/mp4" muted playsInline />
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/postgres-connection.mp4"
type="video/mp4"
muted
playsInline
/>
</video>
## Connection Pool
@@ -104,7 +114,12 @@ This is the most granular option. Connections are returned to the pool after eve
3. Find your Connection Info and Connection String. Connection pooling is on port `6543`.
<video width="99%" muted playsInline controls="true">
<source src="/docs/videos/connection-pool-config.mp4" type="video/mp4" muted playsInline />
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/connection-pool-config.mp4"
type="video/mp4"
muted
playsInline
/>
</video>
## Connecting with SSL
@@ -2,7 +2,7 @@ import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'extensions',
title: 'Overview',
title: 'Postgres Extensions Overview',
description: 'Using Postgres extensions.',
}
@@ -24,7 +24,12 @@ Supabase has pre-installed some of the most useful open source extensions.
3. Enable or disable an extension.
<video width="99%" muted playsInline controls={true}>
<source src="/docs/videos/toggle-extensions.mp4" type="video/mp4" muted playsInline />
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/toggle-extensions.mp4"
type="video/mp4"
muted
playsInline
/>
</video>
</TabPanel>
@@ -4,6 +4,7 @@ export const meta = {
id: 'http',
title: 'http: RESTful Client',
description: 'An HTTP Client for PostgreSQL Functions.',
video: 'https://www.youtube.com/v/rARgrELRCwY',
}
The `http` extension allows you to call RESTful endpoints within Postgres.
@@ -0,0 +1,121 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'hypopg',
title: 'HypoPG: Hypothetical indexes',
description: 'Quickly check if an index can be used without creating it.',
}
`HypoPG` is PostgreSQL extension for creating hypothetical/virtual indexes. HypoPG allows users to rapidly create hypothetical/virtual indexes that have no resource cost (CPU, disk, memory) that are visible to the PostgreSQL query planner.
That allows users to quickly search for an index to improve a slow query without waiting for them to build.
## Usage
### Enable the extension
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard"
>
<TabPanel id="dashboard" label="Dashboard">
1. Go to the [Database](https://app.supabase.com/project/_/database/tables) page in the Dashboard.
2. Click on **Extensions** in the sidebar.
3. Search for "hypopg" and enable the extension.
</TabPanel>
<TabPanel id="sql" label="SQL">
{/* prettier-ignore */}
```sql
-- Enable the "hypopg" extension
create extension hypopg with schema extensions;
-- Disable the "hypopg" extension
drop extension if exists hypopg;
```
Even though the SQL code is `create extension`, this is the equivalent of "enabling the extension".
To disable an extension you can call `drop extension`.
It's good practice to create the extension within a separate schema (like `extensions`) to keep your database clean.
</TabPanel>
</Tabs>
### Speeding up a query
Given the following table and a simple query to select from the table by id:
{/* prettier-ignore */}
```sql
create table account (
id int,
address text
);
insert into account(id, address)
select
id,
id || ' main street'
from
generate_series(1, 10000) id;
```
We can generate an explain plan for a description of how the PostgreSQL query planner
intends to execute the query.
{/* prettier-ignore */}
```sql
explain select * from account where id=1;
QUERY PLAN
-------------------------------------------------------
Seq Scan on account (cost=0.00..180.00 rows=1 width=13)
Filter: (id = 1)
(2 rows)
```
Using HypoPG, we can create a hypothetical index on the `account(id)` column to check if it would be useful to the query planner and then re-run the explain plan.
Note that the virtual indexes created by HypoPG are only visible in the PostgreSQL connection that they were created in. Supabase connects to PostgreSQL through a connection pooler so the `hypopg_create_index` statement and the `explain` statement should be executed in a single query.
{/* prettier-ignore */}
```sql
select * from hypopg_create_index('create index on account(id)');
explain select * from account where id=1;
QUERY PLAN
------------------------------------------------------------------------------------
Index Scan using <13504>btree_account_id on hypo (cost=0.29..8.30 rows=1 width=13)
Index Cond: (id = 1)
(2 rows)
```
The query plan has changed from a `Seq Scan` to an `Index Scan` using the newly created virtual index, so we may choose to create a real version of the index to improve performance on the target query:
{/* prettier-ignore */}
```sql
create index on account(id);
```
## Functions
- [`hypo_create_index(text)`](https://hypopg.readthedocs.io/en/rel1_stable/usage.html#create-a-hypothetical-index): A function to create a hypothetical index.
- [`hypopg_list_indexes`](https://hypopg.readthedocs.io/en/rel1_stable/usage.html#manipulate-hypothetical-indexes): A View that lists all hypothetical indexes that have been created.
- [`hypopg()`](https://hypopg.readthedocs.io/en/rel1_stable/usage.html#manipulate-hypothetical-indexes): A function that lists all hypothetical indexes that have been created with the same format as pg_index.
- [`hypopg_get_index_def(oid)`](https://hypopg.readthedocs.io/en/rel1_stable/usage.html#manipulate-hypothetical-indexes): A function to display the `create index` statement that would create the index.
- [`hypopg_get_relation_size(oid)`](https://hypopg.readthedocs.io/en/rel1_stable/usage.html#manipulate-hypothetical-indexes): A function to estimate how large a hypothetical index would be.
- [`hypopg_drop_index(oid)`](https://hypopg.readthedocs.io/en/rel1_stable/usage.html#manipulate-hypothetical-indexes): A function to remove a given hypothetical index by oid.
- [`hypopg_reset()`](https://hypopg.readthedocs.io/en/rel1_stable/usage.html#manipulate-hypothetical-indexes): A function to remove all hypothetical indexes.
## Resources
- Official [HypoPG documentation](https://hypopg.readthedocs.io/en/rel1_stable/)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -0,0 +1,113 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'pg_jsonschema',
title: 'pg_jsonschema: JSON Schema Validation',
description: 'Validate json/jsonb with JSON Schema in PostgreSQL.',
}
[JSON Schema](https://json-schema.org) is a language for annotating and validating JSON documents. [`pg_jsonschema`](https://json-schema.org) is a PostgreSQL extension that adds the ability to validate PostgreSQL's built-in `json` and `jsonb` data types against a JSON Schema document.
## Usage
### Enable the extension
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard"
>
<TabPanel id="dashboard" label="Dashboard">
1. Go to the [Database](https://app.supabase.com/project/_/database/tables) page in the Dashboard.
2. Click on **Extensions** in the sidebar.
3. Search for "pg_jsonschema" and enable the extension.
</TabPanel>
<TabPanel id="sql" label="SQL">
{/* prettier-ignore */}
```sql
-- Enable the "pg_jsonschema" extension
create extension pg_jsonschema with schema extensions;
-- Disable the "pg_jsonschema" extension
drop extension if exists pg_jsonschema;
```
Even though the SQL code is `create extension`, this is the equivalent of "enabling the extension".
To disable an extension you can call `drop extension`.
It's good practice to create the extension within a separate schema (like `extensions`) to keep your database clean.
</TabPanel>
</Tabs>
## Functions
- [`json_matches_schema(schema json, instance json)`](https://github.com/supabase/pg_jsonschema#api): Checks if a `json` _instance_ conforms to a JSON Schema _schema_.
- [`jsonb_matches_schema(schema json, instance jsonb)`](https://github.com/supabase/pg_jsonschema#api): Checks if a `jsonb` _instance_ conforms to a JSON Schema _schema_.
### Validating a document
Since `pg_jsonschema` exposes its utilities as functions, we can test it with a simple select statement:
{/* prettier-ignore */}
```sql
select
extensions.json_matches_schema(
schema := '{"type": "object"}',
instance := '{}'
);
```
`pg_jsonschema` is generally used in tandem with a [check constraint](https://www.postgresql.org/docs/current/ddl-constraints.html) as a way to constrain the contents of a json/b field to match a JSON Schema.
{/* prettier-ignore */}
```sql
create table customer(
id serial primary key,
...
metadata json,
check (
json_matches_schema(
'{
"type": "object",
"properties": {
"tags": {
"type": "array",
"items": {
"type": "string",
"maxLength": 16
}
}
}
}',
metadata
)
)
);
-- Example: Valid Payload
insert into customer(metadata)
values ('{"tags": ["vip", "darkmode-ui"]}');
-- Result:
-- INSERT 0 1
-- Example: Invalid Payload
insert into customer(metadata)
values ('{"tags": [1, 3]}');
-- Result:
-- ERROR: new row for relation "customer" violates check constraint "customer_metadata_check"
-- DETAIL: Failing row contains (2, {"tags": [1, 3]}).
```
## Resources
- Official [`pg_jsonschema` documentation](https://github.com/supabase/pg_jsonschema)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
Loaded 100 of 919 files, more files were not shown because too many files have changed in this diff. Show more