mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
Merge branch 'master' into feat/self-hosted-storage
This commit is contained in:
4835 files changed
+501176
-933217
No files matched your search
@@ -0,0 +1,7 @@
|
||||
node_modules/
|
||||
.vercel
|
||||
.next
|
||||
.env.local
|
||||
.env.production
|
||||
.env.dev
|
||||
.env.*
|
||||
@@ -1,2 +1,4 @@
|
||||
/studio/ @supabase/frontend
|
||||
/apps/www/ @supabase/frontend
|
||||
/apps/docs/ @supabase/docs
|
||||
/spec/ @supabase/docs
|
||||
@@ -1,27 +0,0 @@
|
||||
name: Deploy studio app to production on vercel
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- studio
|
||||
paths:
|
||||
- 'studio/**'
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-18.04
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# remove ./studio/pages/api folder before deploying to vercel
|
||||
- uses: JesseTG/rm@v1.0.2
|
||||
with:
|
||||
path: ./studio/pages/api
|
||||
- uses: amondnet/vercel-action@v19
|
||||
with:
|
||||
vercel-token: ${{ secrets.VERCEL_TOKEN }} # Required
|
||||
github-comment: false
|
||||
vercel-args: '--prod -A .vercel/production.json'
|
||||
vercel-org-id: team_E6KJ1W561hMTjon1QSwOh0WO #Required
|
||||
vercel-project-id: Qmd7Mw8yFW8SbtvTWFV9uyRzw9P1rJLmgtHjSezywhtYec #Required
|
||||
working-directory: ./studio #Your Working Directory, Optional
|
||||
vercel-project-name: app
|
||||
@@ -1,27 +0,0 @@
|
||||
name: Deploy studio app to staging on vercel
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'studio/**'
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-18.04
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# remove ./studio/pages/api folder before deploying to vercel
|
||||
- uses: JesseTG/rm@v1.0.2
|
||||
with:
|
||||
path: ./studio/pages/api
|
||||
- uses: amondnet/vercel-action@v19
|
||||
with:
|
||||
vercel-token: ${{ secrets.VERCEL_TOKEN }} # Required
|
||||
github-comment: false
|
||||
vercel-args: '--prod -A .vercel/staging.json'
|
||||
vercel-org-id: team_E6KJ1W561hMTjon1QSwOh0WO #Required
|
||||
vercel-project-id: QmcmhbiAtCMFTAHCuGgQscNbke4TzgWULECctNcKmxWCoT #Required
|
||||
working-directory: ./studio #Your Working Directory, Optional
|
||||
vercel-project-name: staging-app
|
||||
@@ -1,48 +0,0 @@
|
||||
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: Docs Compilation Check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'web/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [16.x]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
# Ref: https://github.com/actions/setup-node/issues/214
|
||||
- name: Reconfigure git to use HTTP authentication
|
||||
run: >
|
||||
git config --global url."https://github.com/".insteadOf
|
||||
ssh://git@github.com/
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install Deps
|
||||
run: npm ci
|
||||
working-directory: ./web
|
||||
|
||||
- name: Pull Specs
|
||||
run: make
|
||||
working-directory: ./web/spec
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
working-directory: ./web
|
||||
@@ -0,0 +1,19 @@
|
||||
name: Automatically fix typos
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: master
|
||||
- uses: sobolevn/misspell-fixer-action@master
|
||||
- uses: peter-evans/create-pull-request@v4
|
||||
env:
|
||||
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,15 +1,9 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
name: Tests
|
||||
|
||||
# Controls when the workflow will run
|
||||
on:
|
||||
# Triggers the workflow on push or pull request events but only for the main branch
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
schedule:
|
||||
- cron: '0 4/6 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
@@ -41,17 +35,23 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run infrastructure
|
||||
run: |
|
||||
cp ../docker/.env.example ../docker/.env
|
||||
npm run docker:up
|
||||
- uses: supabase/setup-cli@v1
|
||||
- run: supabase start
|
||||
|
||||
- name: Run Test
|
||||
run: npm run test
|
||||
run: npm run test:local
|
||||
env:
|
||||
SUPABASE_DB_PORT: 54322
|
||||
SUPABASE_DB_PASS: postgres
|
||||
SUPABASE_DB_HOST: localhost
|
||||
SUPABASE_GOTRUE: http://localhost:54321
|
||||
SUPABASE_URL: http://localhost:54321
|
||||
SUPABASE_KEY_ANON: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs
|
||||
SUPABASE_KEY_ADMIN: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSJ9.vI9obAHOGyVVKa3pD--kJlyxp-Z2zV9UUMAhKpNLAcU
|
||||
|
||||
- name: Stop infrastructure
|
||||
if: always()
|
||||
run: npm run docker:down
|
||||
run: supabase stop
|
||||
|
||||
- name: Get Allure history
|
||||
uses: actions/checkout@v2
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
keep_reports: 50
|
||||
|
||||
- name: Deploy report to Github Pages
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
if: always()
|
||||
uses: peaceiris/actions-gh-pages@v2
|
||||
env:
|
||||
EXTERNAL_REPOSITORY: supabase/test-reports
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
PUBLISH_DIR: allure-history
|
||||
|
||||
- name: Post the link to the report
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
if: always()
|
||||
uses: Sibz/github-status-action@v1
|
||||
with:
|
||||
authToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -2,6 +2,8 @@ name: Publish to Image Registry
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'studio'
|
||||
tags:
|
||||
- '*'
|
||||
workflow_dispatch:
|
||||
@@ -25,6 +27,7 @@ jobs:
|
||||
latest=false
|
||||
tags: |
|
||||
type=ref,event=tag
|
||||
type=sha,prefix={{date 'YYYYMMDD'}},enable=${{ github.ref_type == 'branch' }}
|
||||
type=raw,value=${{ inputs.version }},enable=${{ github.event_name != 'push' }}
|
||||
|
||||
- uses: docker/setup-qemu-action@v2
|
||||
|
||||
@@ -9,24 +9,23 @@ on:
|
||||
- 'studio/**'
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
node-version: [16.x]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
- name: Install deps
|
||||
run: npm i
|
||||
working-directory: ./studio
|
||||
- name: Run build
|
||||
run: npm run build
|
||||
working-directory: ./studio
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
- name: Install deps
|
||||
run: npm ci
|
||||
working-directory: ./
|
||||
- name: Run build
|
||||
run: npx turbo run build --filter=studio
|
||||
working-directory: ./
|
||||
@@ -5,31 +5,32 @@ name: Studio Unit Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'studio/**'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'studio/**'
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
node-version: [16.x]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
- name: Install deps
|
||||
run: npm i
|
||||
working-directory: ./studio
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
working-directory: ./studio
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
- name: Install deps
|
||||
run: npm ci
|
||||
working-directory: ./
|
||||
- name: Run tests
|
||||
run: npm run test:studio
|
||||
working-directory: ./
|
||||
@@ -0,0 +1 @@
|
||||
^./i18n
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
cookie
|
||||
+56
-145
@@ -1,45 +1,47 @@
|
||||
# Developing Supabase
|
||||
|
||||
1. [Development setup](#development-setup)
|
||||
- [Install dependencies](#install-dependencies)
|
||||
- [Fork the repository](#fork-the-repository)
|
||||
1. [Build Supabase](#build-supabase)
|
||||
- [Choose a directory](#choose-a-directory)
|
||||
1. [Start a development server](#start-a-development-server)
|
||||
- [Supabase Website](#supabase-website)
|
||||
- [Supabase Docs](#supabase-docs)
|
||||
- [Supabase Studio](#supabase-studio)
|
||||
1. [Create a pull request](#create-a-pull-request)
|
||||
1. [Getting started](#getting-started)
|
||||
- [Install dependencies](#install-dependencies)
|
||||
2. [Local development](#local-development)
|
||||
- [Fork the repository](#fork-the-repository)
|
||||
- [Clone the repo](#clone-the-repo)
|
||||
- [Running turborepo](#running-turborepo)
|
||||
- [Shared components](#shared-components)
|
||||
- [Installing packages](#installing-packages)
|
||||
3. [Create a pull request](#create-a-pull-request)
|
||||
|
||||
- [Common tasks](#common-tasks)
|
||||
- [Add a redirect](#add-a-redirect)
|
||||
- [Monorepo](#monorepo)
|
||||
- [Getting started](#getting-started)
|
||||
- [Shared components](#shared-components)
|
||||
- [Installing packages](#installing-packages)
|
||||
- [Development](#development)
|
||||
- [Community channels](#community-channels)
|
||||
|
||||
## Development setup
|
||||
## Getting started
|
||||
|
||||
Thanks for your interest in Supabase and for wanting to contribute! Before you begin, read the
|
||||
Thanks for your interest in [Supabase](https://supabase.com) 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 build and test Supabase.
|
||||
This document describes how to set up your development environment to build and test [Supabase](https://supabase.com).
|
||||
|
||||
### Install dependencies
|
||||
|
||||
You need to install and configure the following dependencies on your machine to build Supabase:
|
||||
You need to install and configure the following dependencies on your machine to build [Supabase](https://supabase.com):
|
||||
|
||||
- [Git](http://git-scm.com/)
|
||||
- [Node.js v16.x (LTS)](http://nodejs.org)
|
||||
- [npm](https://www.npmjs.com/) version 7+ or [Yarn](https://yarnpkg.com/)
|
||||
|
||||
## Local development
|
||||
|
||||
We are in the process of migrating this repository to monorepo, using [Turborepo](https://turborepo.org/docs).
|
||||
|
||||
Eventually, all the apps will be run using [Turborepo](https://turborepo.org/docs), which will significantly improve the developer workflow.
|
||||
|
||||
If you are working on the ([docs site](https://supabase.com/docs)), please refer to this [developers guide](https://github.com/supabase/supabase/tree/master/apps/reference/DEVELOPERS.md).
|
||||
|
||||
### Fork the repository
|
||||
|
||||
To contribute code to Supabase, you must fork the [Supabase Repository](https://github.com/supabase/supabase).
|
||||
To contribute code to [Supabase](https://supabase.com), you must fork the [Supabase Repository](https://github.com/supabase/supabase).
|
||||
|
||||
## Build Supabase
|
||||
### Clone the repo
|
||||
|
||||
1. Clone your GitHub forked repository:
|
||||
|
||||
@@ -52,104 +54,61 @@ To contribute code to Supabase, you must fork the [Supabase Repository](https://
|
||||
cd supabase
|
||||
```
|
||||
|
||||
### Choose a directory
|
||||
### Running turborepo
|
||||
|
||||
Choose if you want to work on the [Supabase Website](https://supabase.com), [Supabase Docs](https://supabase.com/docs), or [Supabase Studio](https://app.supabase.com).
|
||||
[Supabase](https://supabase.com) uses [Turborepo](https://turborepo.org/docs) to manage and run this monorepo.
|
||||
|
||||
1. Go to the [Supabase Website](https://supabase.com) directory
|
||||
1. Install the dependences in the root of the repo.
|
||||
|
||||
```sh
|
||||
cd apps/www
|
||||
npm install # install dependencies
|
||||
```
|
||||
|
||||
Go to the [Supabase Docs](https://supabase.com/docs) directory
|
||||
|
||||
2. You can then run the apps simultaneously with the following.
|
||||
```sh
|
||||
cd apps/reference
|
||||
npm run dev # start all the applications
|
||||
```
|
||||
|
||||
Go to the [Supabase Studio](https://app.supabase.com) directory
|
||||
Then visit, and edit, any of the following sites:
|
||||
|
||||
```sh
|
||||
cd studio
|
||||
```
|
||||
| Site | Directory | Scope name | Description | Local development server |
|
||||
| -------------------------------------------- | -------------- | ---------- | ------------------------------------ | ------------------------ |
|
||||
| [supabase.com](https://supabase.com) | `/apps/www` | www | The main website | http://localhost:3000 |
|
||||
| [app.supabase.com](https://app.supabase.com) | `/studio` | studio | Studio dashboard | http://localhost:8082 |
|
||||
| [supabase.com/docs] Next.js based Docs site | `/apps/docs` . | docs | Guides and Reference (Next.js based) | http://localhost:3001 |
|
||||
|
||||
1. Install npm/yarn dependencies:
|
||||
#### Running sites individually
|
||||
|
||||
npm
|
||||
You can run any of the sites indiviudally by using the scope name. For example:
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
```sh
|
||||
npm run dev:www
|
||||
```
|
||||
|
||||
or with yarn
|
||||
#### Shared components
|
||||
|
||||
```sh
|
||||
yarn install
|
||||
```
|
||||
The monorepo has a set of shared components under `/packages`:
|
||||
|
||||
## Start a development server
|
||||
- `/packages/common`: Common React code, shared between all sites.
|
||||
- `/packages/config`: All shared config
|
||||
- `/packages/spec`: Generates documentation using spec files.
|
||||
- `/packages/tsconfig`: Shared Typescript settings
|
||||
|
||||
To debug code and to see your changes in real time, it is often useful to have a local HTTP server. Click one of the three links below to choose which development server you want to start.
|
||||
#### Installing packages
|
||||
|
||||
- [Supabase Website](#supabase-website)
|
||||
- [Supabase Docs](#supabase-docs)
|
||||
- [Supabase Studio](#supabase-studio)
|
||||
Installing a package with NPM workspaces requires you to add the `-w` flag to tell NPM which workspace you want to install into. Do not install dependencies in their local folder, install them from the route using the `-w` flag.
|
||||
|
||||
### Supabase Website
|
||||
The format is: `npm install <package name> -w=<workspace to install in>`.
|
||||
|
||||
The website is moving to a new monorepo setup. See the [Monorepo](#monorepo) section below.
|
||||
For example:
|
||||
|
||||
### Supabase Docs
|
||||
- `npm install react -w common`: installs into `./packages/common`
|
||||
- `npm install react -w www`: installs into `./apps/www`
|
||||
- `npm install react -w studio`: installs into `./studio`
|
||||
|
||||
1. Build development server
|
||||
You do not need to install `devDependencies` in each workspace. These can all be installed in the root package.
|
||||
|
||||
npm
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
|
||||
or with yarn
|
||||
|
||||
```sh
|
||||
yarn build
|
||||
```
|
||||
|
||||
1. Start development server
|
||||
|
||||
npm
|
||||
|
||||
```sh
|
||||
npm run start
|
||||
```
|
||||
|
||||
or with yarn
|
||||
|
||||
```sh
|
||||
yarn start
|
||||
```
|
||||
|
||||
1. Access the local server in your web browser at http://localhost:3010/docs.
|
||||
|
||||
### Supabase Studio
|
||||
|
||||
1. Start development server
|
||||
|
||||
npm
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
or with yarn
|
||||
|
||||
```sh
|
||||
yarn dev
|
||||
```
|
||||
|
||||
1. Access the local server in your web browser at http://localhost:8082/.
|
||||
See the [Supabase Studio readme](./studio/README.md) for more information.
|
||||
---
|
||||
|
||||
## Create a pull request
|
||||
|
||||
@@ -163,55 +122,7 @@ Did you have an issue, like a merge conflict, or don't know how to open a pull r
|
||||
|
||||
### Add a redirect
|
||||
|
||||
Create a new entry in the [`next.config.js`](https://github.com/supabase/supabase/blob/master/apps/www/next.config.js) file in our main site.
|
||||
|
||||
## Monorepo
|
||||
|
||||
We are in the process of migrating this repository to monorepo, using Turborepo.
|
||||
Eventually, the docs and the Studio will be run using Turborepo, which will significantly improve the developer workflow.
|
||||
You must be using NPM 7 or higher.
|
||||
|
||||
### Getting started
|
||||
|
||||
```sh
|
||||
npm install # install dependencies
|
||||
npm run dev # start all the applications
|
||||
```
|
||||
|
||||
Then edit and visit any of the following sites:
|
||||
|
||||
Site | Directory | Description | Local development server
|
||||
---- | --------- | ----------- | ------------------------
|
||||
[supabase.com](https://supabase.com) | `/apps/www` | The main website | http://localhost:3000
|
||||
[supabase.com/docs](https://supabase.com/docs) | `apps/reference` | Guides and Reference documentaion | http://localhost:3010/docs
|
||||
[POC] Community forum | `/apps/temp-community-forum` | GitHub Discussions in a Next.js site | http://localhost:3002
|
||||
[POC] DEV articles site | `/apps/temp-community-tutorials` | A Next.js site for our DEV articles (which community members can write) | http://localhost:3003
|
||||
|
||||
### Shared components
|
||||
|
||||
The monorepo has a set of shared components under `/packages`:
|
||||
|
||||
- `/packages/common`: Common React code, shared between all sites.
|
||||
- `/packages/config`: All shared config
|
||||
- `/packages/spec`: Generates documentation using spec files.
|
||||
- `/packages/tsconfig`: Shared Typescript settings
|
||||
|
||||
### Installing packages
|
||||
|
||||
Installing a package with NPM workspaces requires you to add the `-w` flag to tell NPM which workspace you want to install into.
|
||||
|
||||
The format is: `npm install <package name> -w=<workspace to install in>`.
|
||||
|
||||
For example:
|
||||
|
||||
- `npm install @supabase/ui -w common`: installs into `./packages/common`
|
||||
- `npm install @supabase/ui -w www`: installs into `./apps/www`
|
||||
|
||||
You do not need to install `devDependencies` in each workspace. These can all be installed in the root package.
|
||||
|
||||
### Development
|
||||
|
||||
`npm run dev`
|
||||
Create a new entry in the [`redirects.js`](https://github.com/supabase/supabase/blob/master/apps/www/lib/redirects.js) file in our main site.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ To see how to Contribute, visit [Getting Started](./DEVELOPERS.md)
|
||||
|
||||
We are currently in Public Beta. Watch "releases" of this repo to get notified of major updates.
|
||||
|
||||
<kbd><img src="https://gitcdn.link/repo/supabase/supabase/master/web/static/watch-repo.gif" alt="Watch this repo"/></kbd>
|
||||
<kbd><img src="https://raw.githubusercontent.com/supabase/supabase/d5f7f413ab356dc1a92075cb3cee4e40a957d5b1/web/static/watch-repo.gif" alt="Watch this repo"/></kbd>
|
||||
|
||||
---
|
||||
|
||||
@@ -58,7 +58,7 @@ Supabase is a combination of open source tools. We’re building the features of
|
||||
Supabase is a [hosted platform](https://app.supabase.com). You can sign up and start using Supabase without installing anything.
|
||||
You can also [self-host](https://supabase.com/docs/guides/hosting/overview) and [develop locally](https://supabase.com/docs/guides/local-development).
|
||||
|
||||

|
||||

|
||||
|
||||
- [PostgreSQL](https://www.postgresql.org/) is an object-relational database system with over 30 years of active development that has earned it a strong reputation for reliability, feature robustness, and performance.
|
||||
- [Realtime](https://github.com/supabase/realtime) is an Elixir server that allows you to listen to PostgreSQL inserts, updates, and deletes using websockets. Realtime polls Postgres' built-in replication functionality for database changes, converts changes to JSON, then broadcasts the JSON over websockets to authorized clients.
|
||||
@@ -76,7 +76,7 @@ Our approach for client libraries is modular. Each sub-library is a standalone i
|
||||
<tr>
|
||||
<th>Language</th>
|
||||
<th>Client</th>
|
||||
<th colspan="4">Feature-Clients (bundled in Supabase client)</th>
|
||||
<th colspan="5">Feature-Clients (bundled in Supabase client)</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th></th>
|
||||
@@ -85,6 +85,7 @@ Our approach for client libraries is modular. Each sub-library is a standalone i
|
||||
<th><a href="https://github.com/supabase/gotrue" target="_blank" rel="noopener noreferrer">GoTrue</a></th>
|
||||
<th><a href="https://github.com/supabase/realtime" target="_blank" rel="noopener noreferrer">Realtime</a></th>
|
||||
<th><a href="https://github.com/supabase/storage-api" target="_blank" rel="noopener noreferrer">Storage</a></th>
|
||||
<th>Functions</th>
|
||||
</tr>
|
||||
<!-- TEMPLATE FOR NEW ROW -->
|
||||
<!-- START ROW
|
||||
@@ -97,7 +98,7 @@ Our approach for client libraries is modular. Each sub-library is a standalone i
|
||||
<td><a href="https://github.com/supabase-community/storage-lang" target="_blank" rel="noopener noreferrer">storage-lang</a></td>
|
||||
</tr>
|
||||
END ROW -->
|
||||
<th colspan="6">⚡️ Official ⚡️</th>
|
||||
<th colspan="7">⚡️ Official ⚡️</th>
|
||||
<tr>
|
||||
<td>JavaScript (TypeScript)</td>
|
||||
<td><a href="https://github.com/supabase/supabase-js" target="_blank" rel="noopener noreferrer">supabase-js</a></td>
|
||||
@@ -105,8 +106,9 @@ Our approach for client libraries is modular. Each sub-library is a standalone i
|
||||
<td><a href="https://github.com/supabase/gotrue-js" target="_blank" rel="noopener noreferrer">gotrue-js</a></td>
|
||||
<td><a href="https://github.com/supabase/realtime-js" target="_blank" rel="noopener noreferrer">realtime-js</a></td>
|
||||
<td><a href="https://github.com/supabase/storage-js" target="_blank" rel="noopener noreferrer">storage-js</a></td>
|
||||
<td><a href="https://github.com/supabase/functions-js" target="_blank" rel="noopener noreferrer">functions-js</a></td>
|
||||
</tr>
|
||||
<th colspan="6">💚 Community 💚</th>
|
||||
<th colspan="7">💚 Community 💚</th>
|
||||
<tr>
|
||||
<td>C#</td>
|
||||
<td><a href="https://github.com/supabase-community/supabase-csharp" target="_blank" rel="noopener noreferrer">supabase-csharp</a></td>
|
||||
@@ -114,14 +116,16 @@ Our approach for client libraries is modular. Each sub-library is a standalone i
|
||||
<td><a href="https://github.com/supabase-community/gotrue-csharp" target="_blank" rel="noopener noreferrer">gotrue-csharp</a></td>
|
||||
<td><a href="https://github.com/supabase-community/realtime-csharp" target="_blank" rel="noopener noreferrer">realtime-csharp</a></td>
|
||||
<td><a href="https://github.com/supabase-community/storage-csharp" target="_blank" rel="noopener noreferrer">storage-csharp</a></td>
|
||||
<td><a href="https://github.com/supabase-community/functions-csharp" target="_blank" rel="noopener noreferrer">functions-csharp</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dart (Flutter)</td>
|
||||
<td><a href="https://github.com/supabase/supabase-dart" target="_blank" rel="noopener noreferrer">supabase-dart</a></td>
|
||||
<td>Flutter</td>
|
||||
<td><a href="https://github.com/supabase/supabase-flutter" target="_blank" rel="noopener noreferrer">supabase-flutter</a></td>
|
||||
<td><a href="https://github.com/supabase/postgrest-dart" target="_blank" rel="noopener noreferrer">postgrest-dart</a></td>
|
||||
<td><a href="https://github.com/supabase/gotrue-dart" target="_blank" rel="noopener noreferrer">gotrue-dart</a></td>
|
||||
<td><a href="https://github.com/supabase/realtime-dart" target="_blank" rel="noopener noreferrer">realtime-dart</a></td>
|
||||
<td><a href="https://github.com/supabase/storage-dart" target="_blank" rel="noopener noreferrer">storage-dart</a></td>
|
||||
<td><a href="https://github.com/supabase-community/functions-dart" target="_blank" rel="noopener noreferrer">functions-dart</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Go</td>
|
||||
@@ -129,7 +133,8 @@ Our approach for client libraries is modular. Each sub-library is a standalone i
|
||||
<td><a href="https://github.com/supabase-community/postgrest-go" target="_blank" rel="noopener noreferrer">postgrest-go</a></td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td><a href="https://github.com/supabase-community/storage-go" target="_blank" rel="noopener noreferrer">storage-go</a></td>
|
||||
<td><a href="https://github.com/supabase-community/functions-go" target="_blank" rel="noopener noreferrer">functions-go</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Java</td>
|
||||
@@ -138,6 +143,7 @@ Our approach for client libraries is modular. Each sub-library is a standalone i
|
||||
<td><a href="https://github.com/supabase-community/gotrue-java" target="_blank" rel="noopener noreferrer">gotrue-java</a></td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Kotlin</td>
|
||||
@@ -146,6 +152,7 @@ Our approach for client libraries is modular. Each sub-library is a standalone i
|
||||
<td><a href="https://github.com/supabase-community/gotrue-kt" target="_blank" rel="noopener noreferrer">gotrue-kt</a></td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Python</td>
|
||||
@@ -153,7 +160,8 @@ Our approach for client libraries is modular. Each sub-library is a standalone i
|
||||
<td><a href="https://github.com/supabase-community/postgrest-py" target="_blank" rel="noopener noreferrer">postgrest-py</a></td>
|
||||
<td><a href="https://github.com/supabase-community/gotrue-py" target="_blank" rel="noopener noreferrer">gotrue-py</a></td>
|
||||
<td><a href="https://github.com/supabase-community/realtime-py" target="_blank" rel="noopener noreferrer">realtime-py</a></td>
|
||||
<td>-</td>
|
||||
<td><a href="https://github.com/supabase-community/storage-py" target="_blank" rel="noopener noreferrer">storage-py</a></td>
|
||||
<td><a href="https://github.com/supabase-community/functions-py" target="_blank" rel="noopener noreferrer">functions-py</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ruby</td>
|
||||
@@ -162,6 +170,7 @@ Our approach for client libraries is modular. Each sub-library is a standalone i
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Rust</td>
|
||||
@@ -170,6 +179,7 @@ Our approach for client libraries is modular. Each sub-library is a standalone i
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Swift</td>
|
||||
@@ -178,6 +188,7 @@ Our approach for client libraries is modular. Each sub-library is a standalone i
|
||||
<td><a href="https://github.com/supabase-community/gotrue-swift" target="_blank" rel="noopener noreferrer">gotrue-swift</a></td>
|
||||
<td><a href="https://github.com/supabase-community/realtime-swift" target="_blank" rel="noopener noreferrer">realtime-swift</a></td>
|
||||
<td><a href="https://github.com/supabase-community/storage-swift" target="_blank" rel="noopener noreferrer">storage-swift</a></td>
|
||||
<td>-</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -189,10 +200,12 @@ Our approach for client libraries is modular. Each sub-library is a standalone i
|
||||
- [Arabic | العربية](/i18n/README.ar.md)
|
||||
- [Albanian / Shqip](/i18n/README.sq.md)
|
||||
- [Bangla / বাংলা](/i18n/README.bn.md)
|
||||
- [Bulgarian / Български](/i18n/README.bg.md)
|
||||
- [Catalan / Català](/i18n/README.ca.md)
|
||||
- [Danish / Dansk](/i18n/README.da.md)
|
||||
- [Dutch / Nederlands](/i18n/README.nl.md)
|
||||
- [English](https://github.com/supabase/supabase)
|
||||
- [Finnish / Suomalainen](/i18n/README.fi.md)
|
||||
- [French / Français](/i18n/README.fr.md)
|
||||
- [German / Deutsch](/i18n/README.de.md)
|
||||
- [Greek / Ελληνικά](/i18n/README.gr.md)
|
||||
@@ -212,6 +225,7 @@ Our approach for client libraries is modular. Each sub-library is a standalone i
|
||||
- [Portuguese (Brazilian) / Português Brasileiro](/i18n/README.pt-br.md)
|
||||
- [Romanian / Română](/i18n/README.ro.md)
|
||||
- [Russian / Pусский](/i18n/README.ru.md)
|
||||
- [Serbian / Srpski](/i18n/README.sr.md)
|
||||
- [Sinhala / සිංහල](/i18n/README.si.md)
|
||||
- [Spanish / Español](/i18n/README.es.md)
|
||||
- [Simplified Chinese / 简体中文](/i18n/README.zh-cn.md)
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
web/static/.well-known/security.txt
|
||||
apps/reference/static/.well-known/security.txt
|
||||
@@ -15,7 +15,7 @@ import Process from '@site/docs/careers/_snippets/process.mdx'
|
||||
|
||||
We're looking for Graphic / Web Designers to join our team to help change the way developers build their applications.
|
||||
|
||||
You'll be primarily responsible for growing our marketing and brand collataral across the entire Supabase ecosystem.
|
||||
You'll be primarily responsible for growing our marketing and brand collateral across the entire Supabase ecosystem.
|
||||
|
||||
You'll be an indispensable part of team, by working very closely with our sales, marketing, front end team, founders and backend engineers; you'll be directly contributing to brand decisions, building a team culture and co authoring internal processes for a growing team. We want to expand our marketing efforts rapidly throughout 2022 and want you to be at the centre of growing and nurturing our brand, while also nurturing new talent that come on board.
|
||||
|
||||
|
||||
@@ -690,7 +690,7 @@ div[class*=' codeBlockTitle'] {
|
||||
font-size: 0.8rem !important;
|
||||
border-color: #444;
|
||||
}
|
||||
.repsonse-header {
|
||||
.response-header {
|
||||
border-radius: 0 0 4px 4px;
|
||||
}
|
||||
|
||||
|
||||
File renamed without changes.
@@ -0,0 +1,23 @@
|
||||
# Dependencies
|
||||
/node_modules
|
||||
|
||||
# Production
|
||||
/build
|
||||
|
||||
# Generated files
|
||||
.cache-loader
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
**/*/generated/**/*
|
||||
!**/*/generated/.gitkeep
|
||||
!**/*/generated/**/.gitkeep
|
||||
@@ -0,0 +1,45 @@
|
||||
# Reference Docs
|
||||
|
||||
Supabase Reference Docs
|
||||
|
||||
## Maintainers
|
||||
|
||||
If you are a maintainer of any tools in the Supabase ecosystem, you can use this site to provide documentation for the tools & libraries that you maintain.
|
||||
|
||||
## Types of docs
|
||||
|
||||
There are many types of docs:
|
||||
|
||||
1. Guides: teach developers how to use a product. "I have XX problem, how do I solve it?"
|
||||
2. Tutorials: walk-throughs, have a large outcome. "Build a React application with Supabase".
|
||||
3. Explanations: teach developers about a broad topic. "What is a database?"
|
||||
4. Reference: technical descriptions of tools and how to use them. "What errors does the API return?"
|
||||
|
||||
In these docs, you should focus only on the fourth type: "Reference Docs".
|
||||
|
||||
## Versioning
|
||||
|
||||
All tools have versioned docs, which are kept in separate folders. For example, the CLI has the following folders and files:
|
||||
|
||||
- `cli`: the "next" release.
|
||||
- `cli_spec`: contains the DocSpec for the "next" release (see below).
|
||||
- `cli_versioned_docs`: a version of the documentation for every release (including the most current version).
|
||||
- `cli_versioned_sidebars`: a version of the sidebar for every release (including the most current version).
|
||||
|
||||
When you release a new version of a tool, you should also release a new version of the docs. You can do this via the command line. For example, if you just released the CLI version `1.0.1`:
|
||||
|
||||
```
|
||||
npm run cli:version 1.0.1
|
||||
```
|
||||
|
||||
## DocSpec
|
||||
|
||||
We use documentation specifications which can be used to generate human-readable docs.
|
||||
|
||||
- OpenAPI: for documenting API endpoints.
|
||||
- SDKSpec (custom to Supabase): for SDKs and client libraries.
|
||||
- ConfigSpec (custom to Supabase): for configuration options.
|
||||
- CLISpec (custom to Supabase): for CLI commands and usage.
|
||||
|
||||
The benefit of using custom specifications is that we can generate many other types from a strict schema (eg, HTML and manpages).
|
||||
It also means that we can switch any documentation system we want. On this site we use Next.JS, but in Supabase's official website we use a custom React site and expose only a subset of the available API for each tool.
|
||||
@@ -0,0 +1,51 @@
|
||||
import { FC } from 'react'
|
||||
import { IconInfo, IconHelpCircle, IconAlertTriangle } from 'ui'
|
||||
|
||||
interface Props {
|
||||
type: 'note' | 'tip' | 'info' | 'caution' | 'danger'
|
||||
label?: string
|
||||
children: any
|
||||
}
|
||||
|
||||
const Admonition: FC<Props> = ({ type = 'note', label, children }) => {
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
'shadow p-4 rounded border-l-[5px] space-y-2 my-4',
|
||||
`${
|
||||
type === 'note'
|
||||
? 'bg-scale-400 border-scale-800'
|
||||
: type === 'info'
|
||||
? 'bg-scale-500 border-scale-800'
|
||||
: type === 'tip'
|
||||
? 'bg-brand-300 border-brand-800'
|
||||
: type === 'caution'
|
||||
? 'bg-yellow-400 border-yellow-800'
|
||||
: type === 'danger'
|
||||
? 'bg-red-500 border-red-800'
|
||||
: 'bg-scale-500 border-scale-800'
|
||||
}`,
|
||||
].join(' ')}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
{type === 'note' ? (
|
||||
<IconInfo className="text-scale-1200" size={18} strokeWidth={1.5} />
|
||||
) : type === 'info' ? (
|
||||
<IconInfo className="text-scale-1200" size={18} strokeWidth={1.5} />
|
||||
) : type === 'tip' ? (
|
||||
<IconHelpCircle className="text-scale-1200" size={18} strokeWidth={1.5} />
|
||||
) : type === 'caution' ? (
|
||||
<IconAlertTriangle className="text-scale-1200" size={18} strokeWidth={1.5} />
|
||||
) : type === 'danger' ? (
|
||||
<IconAlertTriangle className="text-scale-1200" size={18} strokeWidth={1.5} />
|
||||
) : (
|
||||
<IconInfo className="text-scale-1200" size={18} strokeWidth={1.5} />
|
||||
)}
|
||||
<p className="text-sm text-scale-1200 uppercase my-0 font-bold">{label || type}</p>
|
||||
</div>
|
||||
<div className="admonition-content text-scale-1200 text-base space-y-1">{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Admonition
|
||||
@@ -0,0 +1,53 @@
|
||||
import providers from '../data/authProviders'
|
||||
import ButtonCard from './ButtonCard'
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import React, { FC, ReactNode } from 'react'
|
||||
import Link from 'next/link'
|
||||
import Image from 'next/image'
|
||||
|
||||
interface Props {
|
||||
title: string
|
||||
description?: string
|
||||
to: string
|
||||
icon?: string | ReactNode
|
||||
children?: ReactNode
|
||||
layout?: 'vertical' | 'horizontal'
|
||||
}
|
||||
|
||||
const ButtonCard: FC<Props> = ({
|
||||
children = undefined,
|
||||
icon = undefined,
|
||||
title,
|
||||
description = '',
|
||||
to,
|
||||
layout = 'vertical',
|
||||
}) => {
|
||||
return (
|
||||
<Link href={to}>
|
||||
<a
|
||||
className={[
|
||||
'h-full block shadow-none bg-scale-300 rounded transition',
|
||||
'border border-transparent hover:border-scale-600',
|
||||
].join('')}
|
||||
>
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
<div
|
||||
className={[
|
||||
'px-6 py-4 gap-x-4 gap-y-2 flex',
|
||||
`${layout === 'vertical' ? 'flex-col' : 'items-center'}`,
|
||||
].join(' ')}
|
||||
>
|
||||
{icon && typeof icon == 'string' ? (
|
||||
<div className="w-[24px] h-[24px]">
|
||||
<Image className="m-0" src={icon} width={24} height={24} alt={title} />
|
||||
</div>
|
||||
) : (
|
||||
icon
|
||||
)}
|
||||
<h3 className="my-0 text-base">{title}</h3>
|
||||
<p className="my-0 text-sm">{description}</p>
|
||||
</div>
|
||||
)}
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
export default ButtonCard
|
||||
@@ -0,0 +1,137 @@
|
||||
import { FC } from 'react'
|
||||
import CopyToClipboard from 'react-copy-to-clipboard'
|
||||
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter'
|
||||
import monokaiCustomTheme from './CodeBlock.utils'
|
||||
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 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'
|
||||
import dart from 'react-syntax-highlighter/dist/cjs/languages/hljs/dart'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useTheme } from '../Providers'
|
||||
|
||||
interface Props {
|
||||
title?: string
|
||||
language: 'js' | 'jsx' | 'sql' | 'py' | 'bash' | 'ts' | 'dart'
|
||||
linesToHighlight?: number[]
|
||||
hideCopy?: boolean
|
||||
hideLineNumbers?: boolean
|
||||
className?: string
|
||||
value?: string
|
||||
children?: string
|
||||
}
|
||||
|
||||
const CodeBlock: FC<Props> = ({
|
||||
title,
|
||||
language,
|
||||
linesToHighlight = [],
|
||||
className,
|
||||
value,
|
||||
children,
|
||||
hideCopy = false,
|
||||
hideLineNumbers = false,
|
||||
}) => {
|
||||
const { isDarkMode } = useTheme()
|
||||
const monokaiTheme = monokaiCustomTheme(isDarkMode)
|
||||
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
const handleCopy = () => {
|
||||
setCopied(true)
|
||||
setTimeout(() => {
|
||||
setCopied(false)
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
let lang = language ? language : className ? className.replace('language-', '') : 'js'
|
||||
// force jsx to be js highlighted
|
||||
if (lang === 'jsx') lang = 'js'
|
||||
SyntaxHighlighter.registerLanguage('js', js)
|
||||
SyntaxHighlighter.registerLanguage('ts', ts)
|
||||
SyntaxHighlighter.registerLanguage('py', py)
|
||||
SyntaxHighlighter.registerLanguage('sql', sql)
|
||||
SyntaxHighlighter.registerLanguage('bash', bash)
|
||||
SyntaxHighlighter.registerLanguage('dart', dart)
|
||||
|
||||
const large = false
|
||||
// don't show line numbers if bash == lang
|
||||
const showLineNumbers = hideLineNumbers || lang !== 'bash'
|
||||
|
||||
return (
|
||||
<div className="relative my-2 max-w-[90vw] md:max-w-none overflow-auto">
|
||||
{title && (
|
||||
<div className="rounded-t-md bg-scale-300 py-2 px-4 border-b border-scale-500 text-blue-1100 font-sans">
|
||||
{title.replace(/%20/g, ' ')}
|
||||
</div>
|
||||
)}
|
||||
{className ? (
|
||||
<SyntaxHighlighter
|
||||
language={lang}
|
||||
wrapLines={true}
|
||||
style={monokaiTheme}
|
||||
className={[
|
||||
'code-block border p-4 w-full !my-0 !bg-scale-300',
|
||||
`${!title ? '!rounded-md' : '!rounded-t-none !rounded-b-md'}`,
|
||||
`${!showLineNumbers ? 'pl-6' : ''}`,
|
||||
].join(' ')}
|
||||
customStyle={{
|
||||
fontSize: large ? 18 : 13,
|
||||
lineHeight: large ? 1.5 : 1.4,
|
||||
}}
|
||||
showLineNumbers={showLineNumbers}
|
||||
lineProps={(lineNumber) => {
|
||||
if (linesToHighlight.includes(lineNumber)) {
|
||||
return {
|
||||
style: { display: 'block', backgroundColor: 'var(--colors-scale6)' },
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}}
|
||||
lineNumberContainerStyle={{
|
||||
paddingTop: '128px',
|
||||
}}
|
||||
lineNumberStyle={{
|
||||
minWidth: '44px',
|
||||
paddingLeft: '4px',
|
||||
paddingRight: '4px',
|
||||
marginRight: '12px',
|
||||
color: '#828282',
|
||||
textAlign: 'center',
|
||||
fontSize: large ? 14 : 12,
|
||||
paddingTop: '4px',
|
||||
paddingBottom: '4px',
|
||||
}}
|
||||
>
|
||||
{(value || children)?.trimEnd()}
|
||||
</SyntaxHighlighter>
|
||||
) : (
|
||||
<code>{value || children}</code>
|
||||
)}
|
||||
{!hideCopy && (value || children) && className ? (
|
||||
<div
|
||||
className={[
|
||||
'absolute right-2',
|
||||
`${isDarkMode ? 'dark' : ''}`,
|
||||
`${!title ? 'top-2' : 'top-[3.25rem]'}`,
|
||||
].join(' ')}
|
||||
>
|
||||
<CopyToClipboard text={value || children}>
|
||||
<Button
|
||||
type="default"
|
||||
icon={copied ? <IconCheck /> : <IconCopy />}
|
||||
onClick={() => handleCopy()}
|
||||
>
|
||||
{copied ? 'Copied' : ''}
|
||||
</Button>
|
||||
</CopyToClipboard>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CodeBlock
|
||||
@@ -0,0 +1,146 @@
|
||||
// https://github.com/euank/node-parse-numeric-range/blob/master/index.js
|
||||
export function parseNumericRange(string) {
|
||||
let res = []
|
||||
let m
|
||||
|
||||
for (let str of string.split(',').map((str) => str.trim())) {
|
||||
// just a number
|
||||
if (/^-?\d+$/.test(str)) {
|
||||
res.push(parseInt(str, 10))
|
||||
} else if ((m = str.match(/^(-?\d+)(-|\.\.\.?|\u2025|\u2026|\u22EF)(-?\d+)$/))) {
|
||||
// 1-5 or 1..5 (equivalent) or 1...5 (doesn't include 5)
|
||||
let [_, lhs, sep, rhs] = m
|
||||
|
||||
if (lhs && rhs) {
|
||||
lhs = parseInt(lhs)
|
||||
rhs = parseInt(rhs)
|
||||
const incr = lhs < rhs ? 1 : -1
|
||||
|
||||
// Make it inclusive by moving the right 'stop-point' away by one.
|
||||
if (sep === '-' || sep === '..' || sep === '\u2025') rhs += incr
|
||||
|
||||
for (let i = lhs; i !== rhs; i += incr) res.push(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
const monokaiCustomTheme = (isDarkMode) => {
|
||||
return {
|
||||
hljs: {
|
||||
display: 'block',
|
||||
overflowX: 'auto',
|
||||
background: '#272822',
|
||||
color: '#ddd',
|
||||
},
|
||||
'hljs-tag': {
|
||||
color: '#569cd6',
|
||||
},
|
||||
'hljs-keyword': {
|
||||
color: '#569cd6',
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
'hljs-selector-tag': {
|
||||
color: '#569cd6',
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
'hljs-literal': {
|
||||
color: '#569cd6',
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
'hljs-strong': {
|
||||
color: '#569cd6',
|
||||
},
|
||||
'hljs-name': {
|
||||
color: '#569cd6',
|
||||
},
|
||||
'hljs-code': {
|
||||
color: '#66d9ef',
|
||||
},
|
||||
'hljs-class .hljs-title': {
|
||||
color: 'gray',
|
||||
},
|
||||
'hljs-attribute': {
|
||||
color: '#bf79db',
|
||||
},
|
||||
'hljs-symbol': {
|
||||
color: '#bf79db',
|
||||
},
|
||||
'hljs-regexp': {
|
||||
color: '#bf79db',
|
||||
},
|
||||
'hljs-link': {
|
||||
color: '#bf79db',
|
||||
},
|
||||
'hljs-string': {
|
||||
color: '#3ECF8E',
|
||||
},
|
||||
'hljs-bullet': {
|
||||
color: '#3ECF8E',
|
||||
},
|
||||
'hljs-subst': {
|
||||
color: '#3ECF8E',
|
||||
},
|
||||
'hljs-title': {
|
||||
color: '#3ECF8E',
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
'hljs-section': {
|
||||
color: '#3ECF8E',
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
'hljs-emphasis': {
|
||||
color: '#3ECF8E',
|
||||
},
|
||||
'hljs-type': {
|
||||
color: '#3ECF8E',
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
'hljs-built_in': {
|
||||
color: '#3ECF8E',
|
||||
},
|
||||
'hljs-builtin-name': {
|
||||
color: '#3ECF8E',
|
||||
},
|
||||
'hljs-selector-attr': {
|
||||
color: '#3ECF8E',
|
||||
},
|
||||
'hljs-selector-pseudo': {
|
||||
color: '#3ECF8E',
|
||||
},
|
||||
'hljs-addition': {
|
||||
color: '#3ECF8E',
|
||||
},
|
||||
'hljs-variable': {
|
||||
color: '#3ECF8E',
|
||||
},
|
||||
'hljs-template-tag': {
|
||||
color: '#3ECF8E',
|
||||
},
|
||||
'hljs-template-variable': {
|
||||
color: '#3ECF8E',
|
||||
},
|
||||
'hljs-comment': {
|
||||
color: isDarkMode ? '#999' : '#888',
|
||||
},
|
||||
'hljs-quote': {
|
||||
color: '#75715e',
|
||||
},
|
||||
'hljs-deletion': {
|
||||
color: '#75715e',
|
||||
},
|
||||
'hljs-meta': {
|
||||
color: '#75715e',
|
||||
},
|
||||
'hljs-doctag': {
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
'hljs-selector-id': {
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default monokaiCustomTheme
|
||||
@@ -0,0 +1,79 @@
|
||||
// Check if heading has custom anchor first, before forming the anchor based on the title
|
||||
export const getAnchor = (text: any): string | undefined => {
|
||||
if (typeof text === 'object') {
|
||||
if (Array.isArray(text)) {
|
||||
const customAnchor = text.find(
|
||||
(x) => typeof x === 'string' && x.includes('{#') && x.endsWith('}')
|
||||
)
|
||||
if (customAnchor !== undefined) return customAnchor.slice(3, customAnchor.indexOf('}'))
|
||||
|
||||
const formattedText = text
|
||||
.map((x) => {
|
||||
if (typeof x !== 'string') return x.props.children
|
||||
else return x.trim()
|
||||
})
|
||||
.map((x) => {
|
||||
if (typeof x !== 'string') return x
|
||||
else
|
||||
return x
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9- ]/g, '')
|
||||
.replace(/[ ]/g, '-')
|
||||
})
|
||||
|
||||
return formattedText.join('-').toLowerCase()
|
||||
} else {
|
||||
const anchor = text.props.children
|
||||
if (typeof anchor === 'string') {
|
||||
return anchor
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9- ]/g, '')
|
||||
.replace(/[ ]/g, '-')
|
||||
}
|
||||
return anchor
|
||||
}
|
||||
} else if (typeof text === 'string') {
|
||||
if (text.includes('{#') && text.endsWith('}')) {
|
||||
return text.slice(text.indexOf('{#') + 2, text.indexOf('}'))
|
||||
} else {
|
||||
return text
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9- ]/g, '')
|
||||
.replace(/[ ]/g, '-')
|
||||
}
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export const removeAnchor = (text: any) => {
|
||||
if (typeof text === 'object' && Array.isArray(text)) {
|
||||
return text.filter((x) => !(typeof x === 'string' && x.includes('{#') && x.endsWith('}')))
|
||||
} else if (typeof text === 'string') {
|
||||
if (text.indexOf('{#') > 0) return text.slice(0, text.indexOf('{#'))
|
||||
else return text
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
export const highlightSelectedTocItem = (id: string) => {
|
||||
const tocMenuItems = document.querySelectorAll('.toc-menu a')
|
||||
|
||||
// find any currently active items and remove them
|
||||
const currentActiveItem = document.querySelector('.toc-menu .toc__menu-item--active')
|
||||
currentActiveItem?.classList.remove('toc__menu-item--active')
|
||||
|
||||
// Add active class to the current item
|
||||
tocMenuItems.forEach((item) => {
|
||||
// @ts-ignore
|
||||
if (item.href.split('#')[1] === id) {
|
||||
item.classList.add('toc__menu-item--active')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// find any currently active items and remove them on route change
|
||||
export const unHighlightSelectedTocItems = () => {
|
||||
const currentActiveItem = document.querySelector('.toc-menu .toc__menu-item--active')
|
||||
currentActiveItem?.classList.remove('toc__menu-item--active')
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import {
|
||||
getAnchor,
|
||||
removeAnchor,
|
||||
highlightSelectedTocItem,
|
||||
unHighlightSelectedTocItems,
|
||||
} from './CustomHTMLElements.utils'
|
||||
import { useInView } from 'react-intersection-observer'
|
||||
|
||||
const Heading = ({ tag, children }) => {
|
||||
const HeadingTag = `${tag}` as any
|
||||
|
||||
const anchor = getAnchor(children)
|
||||
const link = `#${anchor}`
|
||||
|
||||
// check if current heading is in view, update TOC active item accordingly
|
||||
// [Joshen] Ideally we highlight the section in the TOC when it's at the top of the page
|
||||
// much like when we click on the item in the TOC itself, the rootMargin fix is insufficient
|
||||
const { ref } = useInView({
|
||||
threshold: 1,
|
||||
onChange: (inView, entry) => {
|
||||
if (window.scrollY === 0) unHighlightSelectedTocItems()
|
||||
if (inView) highlightSelectedTocItem(entry.target.id)
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<HeadingTag id={anchor} ref={ref} className="group scroll-mt-24">
|
||||
{removeAnchor(children)}
|
||||
{anchor && (
|
||||
<a href={link} className="ml-2 opacity-0 group-hover:opacity-100 transition">
|
||||
#
|
||||
</a>
|
||||
)}
|
||||
</HeadingTag>
|
||||
)
|
||||
}
|
||||
export default Heading
|
||||
@@ -0,0 +1,3 @@
|
||||
import Heading from './Heading'
|
||||
|
||||
export { Heading }
|
||||
@@ -0,0 +1,208 @@
|
||||
import { useState, useCallback, useRef, createContext, useContext, useEffect } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import Link from 'next/link'
|
||||
import Head from 'next/head'
|
||||
import { useRouter } from 'next/router'
|
||||
import { DocSearchModal } from '@docsearch/react'
|
||||
import clsx from 'clsx'
|
||||
import { useActionKey } from '~/hooks/useActionKey'
|
||||
|
||||
// [Joshen] We're currently using this over Algolia search for the time being
|
||||
// Refer to the comment in Navigation/AlgoliaSearch for more information about this
|
||||
|
||||
const INDEX_NAME = process.env.NEXT_PUBLIC_ALGOLIA_INDEX_NAME
|
||||
const API_KEY = process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_API_KEY
|
||||
const APP_ID = process.env.NEXT_PUBLIC_ALGOLIA_APP_ID
|
||||
|
||||
const SearchContext = createContext(null)
|
||||
|
||||
export function SearchProvider({ children }: any) {
|
||||
const router = useRouter()
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [initialQuery, setInitialQuery] = useState(null)
|
||||
|
||||
const onOpen = useCallback(() => {
|
||||
setIsOpen(true)
|
||||
}, [setIsOpen])
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
setIsOpen(false)
|
||||
}, [setIsOpen])
|
||||
|
||||
const onInput = useCallback(
|
||||
(e) => {
|
||||
setIsOpen(true)
|
||||
setInitialQuery(e.key)
|
||||
},
|
||||
[setIsOpen, setInitialQuery]
|
||||
)
|
||||
|
||||
useDocSearchKeyboardEvents({
|
||||
isOpen,
|
||||
onOpen,
|
||||
onClose,
|
||||
// @ts-ignore
|
||||
onInput,
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<link rel="preconnect" href={`https://${APP_ID}-dsn.algolia.net`} crossOrigin="true" />
|
||||
</Head>
|
||||
<SearchContext.Provider
|
||||
// @ts-ignore
|
||||
value={{
|
||||
isOpen,
|
||||
onOpen,
|
||||
onClose,
|
||||
onInput,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SearchContext.Provider>
|
||||
{isOpen &&
|
||||
createPortal(
|
||||
<DocSearchModal
|
||||
// @ts-ignore
|
||||
initialQuery={initialQuery}
|
||||
initialScrollY={window.scrollY}
|
||||
placeholder="Search documentation"
|
||||
onClose={onClose}
|
||||
indexName={INDEX_NAME}
|
||||
apiKey={API_KEY}
|
||||
appId={APP_ID}
|
||||
// @ts-ignore
|
||||
navigator={{
|
||||
navigate({ suggestionUrl }) {
|
||||
setIsOpen(false)
|
||||
router.push(suggestionUrl)
|
||||
},
|
||||
}}
|
||||
hitComponent={Hit}
|
||||
transformItems={(items) => {
|
||||
return items.map((item, index) => {
|
||||
// We transform the absolute URL into a relative URL to
|
||||
// leverage Next's preloading.
|
||||
const a = document.createElement('a')
|
||||
a.href = item.url
|
||||
|
||||
const hash = a.hash === '#content-wrapper' ? '' : a.hash
|
||||
|
||||
return {
|
||||
...item,
|
||||
url: `${a.pathname}${hash}`,
|
||||
__is_result: () => true,
|
||||
__is_parent: () => item.type === 'lvl1' && items.length > 1 && index === 0,
|
||||
__is_child: () =>
|
||||
item.type !== 'lvl1' &&
|
||||
items.length > 1 &&
|
||||
items[0].type === 'lvl1' &&
|
||||
index !== 0,
|
||||
__is_first: () => index === 1,
|
||||
__is_last: () => index === items.length - 1 && index !== 0,
|
||||
}
|
||||
})
|
||||
}}
|
||||
/>,
|
||||
document.body
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
function Hit({ hit, children }) {
|
||||
return (
|
||||
<Link href={hit.url}>
|
||||
<a
|
||||
className={clsx({
|
||||
'DocSearch-Hit--Result': hit.__is_result?.(),
|
||||
'DocSearch-Hit--Parent': hit.__is_parent?.(),
|
||||
'DocSearch-Hit--FirstChild': hit.__is_first?.(),
|
||||
'DocSearch-Hit--LastChild': hit.__is_last?.(),
|
||||
'DocSearch-Hit--Child': hit.__is_child?.(),
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
export function SearchButton({ children, ...props }) {
|
||||
let searchButtonRef = useRef()
|
||||
let actionKey = useActionKey()
|
||||
// @ts-ignore
|
||||
let { onOpen, onInput } = useContext(SearchContext)
|
||||
|
||||
useEffect(() => {
|
||||
// @ts-ignore
|
||||
function onKeyDown(event) {
|
||||
if (searchButtonRef && searchButtonRef.current === document.activeElement && onInput) {
|
||||
if (/[a-zA-Z0-9]/.test(String.fromCharCode(event.keyCode))) {
|
||||
onInput(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
window.addEventListener('keydown', onKeyDown)
|
||||
return () => {
|
||||
window.removeEventListener('keydown', onKeyDown)
|
||||
}
|
||||
}, [onInput, searchButtonRef])
|
||||
|
||||
return (
|
||||
// @ts-ignore
|
||||
<button type="button" ref={searchButtonRef} onClick={onOpen} {...props}>
|
||||
{typeof children === 'function' ? children({ actionKey }) : children}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
function useDocSearchKeyboardEvents({ isOpen, onOpen, onClose }) {
|
||||
useEffect(() => {
|
||||
// @ts-ignore
|
||||
function onKeyDown(event) {
|
||||
function open() {
|
||||
// We check that no other DocSearch modal is showing before opening
|
||||
// another one.
|
||||
if (!document.body.classList.contains('DocSearch--active')) {
|
||||
onOpen()
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(event.keyCode === 27 && isOpen) ||
|
||||
(event.key === 'k' && (event.metaKey || event.ctrlKey)) ||
|
||||
(!isEditingContent(event) && event.key === '/' && !isOpen)
|
||||
) {
|
||||
event.preventDefault()
|
||||
|
||||
if (isOpen) {
|
||||
onClose()
|
||||
} else if (!document.body.classList.contains('DocSearch--active')) {
|
||||
open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', onKeyDown)
|
||||
return () => {
|
||||
window.removeEventListener('keydown', onKeyDown)
|
||||
}
|
||||
}, [isOpen, onOpen, onClose])
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
function isEditingContent(event) {
|
||||
let element = event.target
|
||||
let tagName = element.tagName
|
||||
return (
|
||||
element.isContentEditable ||
|
||||
tagName === 'INPUT' ||
|
||||
tagName === 'SELECT' ||
|
||||
tagName === 'TEXTAREA'
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { useTheme } from '~/components/Providers'
|
||||
import { IconSun, IconMoon } from '@supabase/ui'
|
||||
|
||||
function DarkModeToggle() {
|
||||
const { isDarkMode, toggleTheme } = useTheme()
|
||||
|
||||
const toggleDarkMode = () => {
|
||||
localStorage.setItem('supabaseDarkMode', (!isDarkMode).toString())
|
||||
toggleTheme()
|
||||
|
||||
const key = localStorage.getItem('supabaseDarkMode')
|
||||
document.documentElement.className = key === 'true' ? 'dark' : ''
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<IconSun className="text-scale-900" strokeWidth={2} />
|
||||
<button
|
||||
type="button"
|
||||
aria-pressed="false"
|
||||
className={`
|
||||
relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent
|
||||
transition-colors duration-200 ease-in-out focus:outline-none ${
|
||||
isDarkMode ? 'bg-scale-500' : 'bg-scale-900'
|
||||
} mx-5
|
||||
`}
|
||||
onClick={() => toggleDarkMode()}
|
||||
>
|
||||
<span className="sr-only">Toggle Themes</span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={`
|
||||
${
|
||||
isDarkMode ? 'translate-x-5' : 'translate-x-0'
|
||||
} dark:bg-scale-300 inline-block h-5 w-5
|
||||
transform rounded-full bg-white shadow-lg ring-0 transition duration-200 ease-in-out
|
||||
`}
|
||||
/>
|
||||
</button>
|
||||
<IconMoon className="text-scale-900" strokeWidth={2} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DarkModeToggle
|
||||
@@ -0,0 +1,133 @@
|
||||
import { Badge } from 'ui'
|
||||
import Link from 'next/link'
|
||||
import Image from 'next/image'
|
||||
import FooterLinks from 'data/footer.json'
|
||||
import DarkModeToggle from './DarkModeToggle'
|
||||
import { useTheme } from '../Providers'
|
||||
import * as supabaseLogoWordmarkDark from 'common/assets/images/supabase-logo-wordmark--dark.png'
|
||||
import * as supabaseLogoWordmarkLight from 'common/assets/images/supabase-logo-wordmark--light.png'
|
||||
|
||||
const Footer = () => {
|
||||
const { isDarkMode } = useTheme()
|
||||
|
||||
return (
|
||||
<footer
|
||||
className="border-scale-500 dark:border-scale-600 border-t"
|
||||
aria-labelledby="footerHeading"
|
||||
>
|
||||
<h2 id="footerHeading" className="sr-only">
|
||||
Footer
|
||||
</h2>
|
||||
<div className="sm:py-18 container relative mx-auto px-6 py-16 md:py-24 lg:px-16 lg:py-24 xl:px-20">
|
||||
<div className="xl:grid xl:grid-cols-3 xl:gap-8">
|
||||
<div className="space-y-8 xl:col-span-1">
|
||||
<Link href="#" as="/">
|
||||
<a className="w-40">
|
||||
<Image
|
||||
src={isDarkMode ? supabaseLogoWordmarkDark : supabaseLogoWordmarkLight}
|
||||
width={160}
|
||||
height={30}
|
||||
alt="Supabase"
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
<div className="flex space-x-5">
|
||||
<a
|
||||
href="https://twitter.com/supabase"
|
||||
className="text-scale-900 hover:text-scale-1200 transition"
|
||||
>
|
||||
<span className="sr-only">Twitter</span>
|
||||
<svg className="h-6 w-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0022 5.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.072 4.072 0 012.8 9.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 012 18.407a11.616 11.616 0 006.29 1.84" />
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://github.com/supabase"
|
||||
className="text-scale-900 hover:text-scale-1200 transition"
|
||||
>
|
||||
<span className="sr-only">GitHub</span>
|
||||
<svg className="h-6 w-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://discord.supabase.com/"
|
||||
className="text-scale-900 hover:text-scale-1200 transition"
|
||||
>
|
||||
<span className="sr-only">Discord</span>
|
||||
<svg className="h-6 w-6" fill="currentColor" viewBox="0 0 71 55" aria-hidden="true">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M60.1045 4.8978C55.5792 2.8214 50.7265 1.2916 45.6527 0.41542C45.5603 0.39851 45.468 0.440769 45.4204 0.525289C44.7963 1.6353 44.105 3.0834 43.6209 4.2216C38.1637 3.4046 32.7345 3.4046 27.3892 4.2216C26.905 3.0581 26.1886 1.6353 25.5617 0.525289C25.5141 0.443589 25.4218 0.40133 25.3294 0.41542C20.2584 1.2888 15.4057 2.8186 10.8776 4.8978C10.8384 4.9147 10.8048 4.9429 10.7825 4.9795C1.57795 18.7309 -0.943561 32.1443 0.293408 45.3914C0.299005 45.4562 0.335386 45.5182 0.385761 45.5576C6.45866 50.0174 12.3413 52.7249 18.1147 54.5195C18.2071 54.5477 18.305 54.5139 18.3638 54.4378C19.7295 52.5728 20.9469 50.6063 21.9907 48.5383C22.0523 48.4172 21.9935 48.2735 21.8676 48.2256C19.9366 47.4931 18.0979 46.6 16.3292 45.5858C16.1893 45.5041 16.1781 45.304 16.3068 45.2082C16.679 44.9293 17.0513 44.6391 17.4067 44.3461C17.471 44.2926 17.5606 44.2813 17.6362 44.3151C29.2558 49.6202 41.8354 49.6202 53.3179 44.3151C53.3935 44.2785 53.4831 44.2898 53.5502 44.3433C53.9057 44.6363 54.2779 44.9293 54.6529 45.2082C54.7816 45.304 54.7732 45.5041 54.6333 45.5858C52.8646 46.6197 51.0259 47.4931 49.0921 48.2228C48.9662 48.2707 48.9102 48.4172 48.9718 48.5383C50.038 50.6034 51.2554 52.5699 52.5959 54.435C52.6519 54.5139 52.7526 54.5477 52.845 54.5195C58.6464 52.7249 64.529 50.0174 70.6019 45.5576C70.6551 45.5182 70.6887 45.459 70.6943 45.3942C72.1747 30.0791 68.2147 16.7757 60.1968 4.9823C60.1772 4.9429 60.1437 4.9147 60.1045 4.8978ZM23.7259 37.3253C20.2276 37.3253 17.3451 34.1136 17.3451 30.1693C17.3451 26.225 20.1717 23.0133 23.7259 23.0133C27.308 23.0133 30.1626 26.2532 30.1066 30.1693C30.1066 34.1136 27.28 37.3253 23.7259 37.3253ZM47.3178 37.3253C43.8196 37.3253 40.9371 34.1136 40.9371 30.1693C40.9371 26.225 43.7636 23.0133 47.3178 23.0133C50.9 23.0133 53.7545 26.2532 53.6986 30.1693C53.6986 34.1136 50.9 37.3253 47.3178 37.3253Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://youtube.com/c/supabase"
|
||||
className="text-scale-900 hover:text-scale-1200 transition"
|
||||
>
|
||||
<span className="sr-only">Youtube</span>
|
||||
<svg
|
||||
className="h-6 w-6"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 576 512"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M549.655 124.083c-6.281-23.65-24.787-42.276-48.284-48.597C458.781 64 288 64 288 64S117.22 64 74.629 75.486c-23.497 6.322-42.003 24.947-48.284 48.597-11.412 42.867-11.412 132.305-11.412 132.305s0 89.438 11.412 132.305c6.281 23.65 24.787 41.5 48.284 47.821C117.22 448 288 448 288 448s170.78 0 213.371-11.486c23.497-6.321 42.003-24.171 48.284-47.821 11.412-42.867 11.412-132.305 11.412-132.305s0-89.438-11.412-132.305zm-317.51 213.508V175.185l142.739 81.205-142.739 81.201z" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-12 grid grid-cols-1 gap-8 xl:col-span-2 xl:mt-0">
|
||||
<div className="grid grid-cols-2 gap-8 md:grid-cols-4">
|
||||
{FooterLinks.map((segment: any) => {
|
||||
return (
|
||||
<div key={`footer_${segment.title}`}>
|
||||
<h6 className="text-scale-1200 overwrite text-base">{segment.title}</h6>
|
||||
<ul className="mt-4 space-y-2">
|
||||
{segment.links.map((link: any, idx: number) => (
|
||||
<li key={`${segment.title}_link_${idx}`}>
|
||||
<a
|
||||
href={link.url}
|
||||
className={`text-sm transition-colors ${
|
||||
link.url
|
||||
? 'text-scale-1100 hover:text-scale-1200 '
|
||||
: 'text-scale-900 hover:text-scale-900'
|
||||
} `}
|
||||
>
|
||||
{link.text}
|
||||
{!link.url && (
|
||||
<div className="ml-2 inline text-xs xl:ml-0 xl:block 2xl:ml-2 2xl:inline">
|
||||
<Badge color="scale" size="small">
|
||||
Coming soon
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-scale-500 dark:border-scale-600 mt-32 flex justify-between border-t pt-8">
|
||||
<small className="small">© Supabase Inc</small>
|
||||
<DarkModeToggle />
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
export default Footer
|
||||
@@ -0,0 +1,106 @@
|
||||
import ButtonCard from './ButtonCard'
|
||||
import { useTheme } from '~/components/Providers'
|
||||
|
||||
const Frameworks = () => {
|
||||
const { isDarkMode } = useTheme()
|
||||
|
||||
const frameworks = [
|
||||
{
|
||||
name: 'Angular',
|
||||
logo: {
|
||||
light: '/docs/img/libraries/angular-icon.svg',
|
||||
dark: '/docs/img/libraries/angular-icon.svg',
|
||||
},
|
||||
href: '/guides/with-angular',
|
||||
},
|
||||
{
|
||||
name: 'Expo',
|
||||
logo: {
|
||||
light: '/docs/img/libraries/expo-icon.svg',
|
||||
dark: '/docs/img/libraries/expo-icon-dark.svg',
|
||||
},
|
||||
href: '/guides/with-expo',
|
||||
},
|
||||
{
|
||||
name: 'Flutter',
|
||||
logo: {
|
||||
light: '/docs/img/libraries/flutter-icon.svg',
|
||||
dark: '/docs/img/libraries/flutter-icon.svg',
|
||||
},
|
||||
href: '/guides/with-flutter',
|
||||
},
|
||||
{
|
||||
name: 'JavaScript',
|
||||
logo: {
|
||||
light: '/docs/img/libraries/javascript-icon.svg',
|
||||
dark: '/docs/img/libraries/javascript-icon.svg',
|
||||
},
|
||||
href: '/reference/javascript/installing#javascript',
|
||||
},
|
||||
{
|
||||
name: 'Next.js',
|
||||
logo: {
|
||||
light: '/docs/img/libraries/nextjs-light-icon.svg',
|
||||
dark: '/docs/img/libraries/nextjs-dark-icon.svg',
|
||||
},
|
||||
href: '/guides/with-nextjs',
|
||||
},
|
||||
{
|
||||
name: 'React',
|
||||
logo: {
|
||||
light: '/docs/img/libraries/react-icon.svg',
|
||||
dark: '/docs/img/libraries/react-icon.svg',
|
||||
},
|
||||
href: '/guides/with-react',
|
||||
},
|
||||
{
|
||||
name: 'RedwoodJS',
|
||||
logo: {
|
||||
light: '/docs/img/libraries/redwoodjs-icon.svg',
|
||||
dark: '/docs/img/libraries/redwoodjs-icon.svg',
|
||||
},
|
||||
href: '/guides/with-redwoodjs',
|
||||
},
|
||||
{
|
||||
name: 'SolidJS',
|
||||
logo: {
|
||||
light: '/docs/img/libraries/solidjs-icon.svg',
|
||||
dark: '/docs/img/libraries/solidjs-icon.svg',
|
||||
},
|
||||
href: '/guides/with-solidjs',
|
||||
},
|
||||
{
|
||||
name: 'Svelte',
|
||||
logo: {
|
||||
light: '/docs/img/libraries/svelte-icon.svg',
|
||||
dark: '/docs/img/libraries/svelte-icon.svg',
|
||||
},
|
||||
href: '/guides/with-svelte',
|
||||
},
|
||||
{
|
||||
name: 'Vue',
|
||||
logo: {
|
||||
light: '/docs/img/libraries/vuejs-icon.svg',
|
||||
dark: '/docs/img/libraries/vuejs-icon.svg',
|
||||
},
|
||||
href: '/guides/with-vue-3',
|
||||
},
|
||||
]
|
||||
return (
|
||||
<div className="grid md:grid-cols-12 gap-4">
|
||||
{frameworks.map((x) => (
|
||||
<div key={x.name} className="col-span-3">
|
||||
<ButtonCard
|
||||
layout="horizontal"
|
||||
to={x.href}
|
||||
title={x.name}
|
||||
// [Joshen] Nice to have: theming
|
||||
icon={isDarkMode ? x.logo.dark : x.logo.light}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Frameworks
|
||||
@@ -0,0 +1,60 @@
|
||||
import ButtonCard from 'components/ButtonCard'
|
||||
|
||||
const examples = [
|
||||
{
|
||||
name: 'With supabase-js',
|
||||
description: 'Use the Supabase client inside your Edge Function.',
|
||||
href: 'https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/select-from-table-with-auth-rls/index.ts',
|
||||
},
|
||||
{
|
||||
name: 'With CORS headers',
|
||||
description: 'Send CORS headers for invoking from the browser.',
|
||||
href: 'https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/browser-with-cors/index.ts',
|
||||
},
|
||||
{
|
||||
name: 'React Native with Stripe',
|
||||
description: 'Full example for using Supabase and Stripe, with Expo.',
|
||||
href: 'https://github.com/supabase-community/expo-stripe-payments-with-supabase-functions',
|
||||
},
|
||||
{
|
||||
name: 'Flutter with Stripe',
|
||||
description: 'Full example for using Supabase and Stripe, with Flutter.',
|
||||
href: 'https://github.com/supabase-community/flutter-stripe-payments-with-supabase-functions',
|
||||
},
|
||||
{
|
||||
name: 'Building a RESTful Service API',
|
||||
description:
|
||||
'Learn how to use HTTP methods and paths to build a RESTful service for managing tasks.',
|
||||
href: 'https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/restful-tasks/index.ts',
|
||||
},
|
||||
{
|
||||
name: 'Working with Supabase Storage',
|
||||
description: 'An example on reading a file from Supbase Storage.',
|
||||
href: 'https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/read-storage/index.ts',
|
||||
},
|
||||
{
|
||||
name: 'OpenGraph Image Generation',
|
||||
description:
|
||||
'Generate Open Graph images with Deno and Supabase Edge Functions, no framework needed.',
|
||||
href: 'https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/opengraph',
|
||||
},
|
||||
{
|
||||
name: 'Get User Location',
|
||||
description: `Get user location data from user's IP address.`,
|
||||
href: 'https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/location',
|
||||
},
|
||||
]
|
||||
|
||||
const FunctionsExamples = () => {
|
||||
return (
|
||||
<div className="grid md:grid-cols-12 gap-4">
|
||||
{examples.map((x) => (
|
||||
<div className="col-span-6">
|
||||
<ButtonCard to={x.href} title={x.name} description={x.description} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FunctionsExamples
|
||||
@@ -0,0 +1,17 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function GithubCard({ title, description, href, stars, handle }) {
|
||||
return (
|
||||
<a className="h-full" href={href}>
|
||||
<div className="card__body">
|
||||
<h4 className="uppercase my-2 font-semibold">{title}</h4>
|
||||
<span className="text-sm">{description}</span>
|
||||
</div>
|
||||
<hr className="my-2" />
|
||||
<div className="flex justify-between py-2 text-xs">
|
||||
<div>@{handle}</div>
|
||||
<div>{stars} ★</div>
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import React, { useState } from 'react'
|
||||
import KJUR from 'jsrsasign'
|
||||
import CodeBlock from './CodeBlock/CodeBlock'
|
||||
import { Button } from 'ui'
|
||||
|
||||
const JWT_HEADER = { alg: 'HS256', typ: 'JWT' }
|
||||
const now = new Date()
|
||||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
||||
const fiveYears = new Date(now.getFullYear() + 5, now.getMonth(), now.getDate())
|
||||
const anonToken = `
|
||||
{
|
||||
"role": "anon",
|
||||
"iss": "supabase",
|
||||
"iat": ${Math.floor(today / 1000)},
|
||||
"exp": ${Math.floor(fiveYears / 1000)}
|
||||
}
|
||||
`.trim()
|
||||
|
||||
const serviceToken = `
|
||||
{
|
||||
"role": "service_role",
|
||||
"iss": "supabase",
|
||||
"iat": ${Math.floor(today / 1000)},
|
||||
"exp": ${Math.floor(fiveYears / 1000)}
|
||||
}
|
||||
`.trim()
|
||||
|
||||
export default function JwtGenerator({}) {
|
||||
const secret = [...Array(40)].map(() => Math.random().toString(36)[2]).join('')
|
||||
|
||||
const [jwtSecret, setJwtSecret] = useState(secret)
|
||||
const [token, setToken] = useState(anonToken)
|
||||
const [signedToken, setSignedToken] = useState('')
|
||||
|
||||
const handleKeySelection = (e) => {
|
||||
const val = e.target.value
|
||||
if (val == 'service') setToken(serviceToken)
|
||||
else setToken(anonToken)
|
||||
}
|
||||
const generate = () => {
|
||||
const signedJWT = KJUR.jws.JWS.sign(null, JWT_HEADER, token, jwtSecret)
|
||||
setSignedToken(signedJWT)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="grid mb-8">
|
||||
<label for="secret">JWT Secret:</label>
|
||||
<input
|
||||
id="secret"
|
||||
className="border rounded bg-gray-200 w-full"
|
||||
type="text"
|
||||
placeholder="JWT Secret (at least 32 characters)"
|
||||
value={jwtSecret}
|
||||
onChange={(e) => setJwtSecret(e.target.value)}
|
||||
/>
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<div className="grid mb-8">
|
||||
<label for="token">Payload:</label>
|
||||
<textarea
|
||||
id="token"
|
||||
type="text"
|
||||
rows="5"
|
||||
placeholder="A valid JWT Token"
|
||||
value={token}
|
||||
onChange={(e) => setToken(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button type="primary" onClick={generate}>
|
||||
Generate JWT
|
||||
</Button>
|
||||
|
||||
{signedToken && (
|
||||
<div className="mt-8">
|
||||
<h4>Generated Token:</h4>
|
||||
<CodeBlock language="bash" className="relative">
|
||||
{signedToken}
|
||||
</CodeBlock>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,55 @@
|
||||
import UserManagementSQLTemplate from './user_management_quickstart_sql_template.mdx'
|
||||
|
||||
## Project set up
|
||||
|
||||
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
|
||||
|
||||
1. Go to [app.supabase.com](https://app.supabase.com).
|
||||
1. Click on "New Project".
|
||||
1. Enter your project details.
|
||||
1. Wait for the new database to launch.
|
||||
|
||||
### 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.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="dashboard"
|
||||
>
|
||||
<TabPanel id="dashboard" label="Dashboard">
|
||||
|
||||
1. Go to the [SQL Editor](https://app.supabase.com/project/_/sql) page in the Dashboard.
|
||||
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">
|
||||
|
||||
<UserManagementSQLTemplate />
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### 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.
|
||||
|
||||
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>
|
||||
@@ -0,0 +1,11 @@
|
||||
## Intro
|
||||
|
||||
This example provides the steps to build a basic user management app. It includes:
|
||||
|
||||
- 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:
|
||||
@@ -0,0 +1,55 @@
|
||||
```sql
|
||||
-- Create a table for public profiles
|
||||
create table profiles (
|
||||
id uuid references auth.users not null primary key,
|
||||
updated_at timestamp with time zone,
|
||||
username text unique,
|
||||
full_name text,
|
||||
avatar_url text,
|
||||
website text,
|
||||
|
||||
constraint username_length check (char_length(username) >= 3)
|
||||
);
|
||||
-- Set up Row Level Security (RLS)
|
||||
-- See https://supabase.com/docs/guides/auth/row-level-security for more details.
|
||||
alter table profiles
|
||||
enable row level security;
|
||||
|
||||
create policy "Public profiles are viewable by everyone." on profiles
|
||||
for select using (true);
|
||||
|
||||
create policy "Users can insert their own profile." on profiles
|
||||
for insert with check (auth.uid() = id);
|
||||
|
||||
create policy "Users can update own profile." on profiles
|
||||
for update using (auth.uid() = id);
|
||||
|
||||
-- This trigger automatically creates a profile entry when a new user signs up via Supabase Auth.
|
||||
-- See https://supabase.com/docs/guides/auth/managing-user-data#using-triggers for more details.
|
||||
create function public.handle_new_user()
|
||||
returns trigger as $$
|
||||
begin
|
||||
insert into public.profiles (id, full_name, avatar_url)
|
||||
values (new.id, new.raw_user_meta_data->>'full_name', new.raw_user_meta_data->>'avatar_url');
|
||||
return new;
|
||||
end;
|
||||
$$ language plpgsql security definer;
|
||||
create trigger on_auth_user_created
|
||||
after insert on auth.users
|
||||
for each row execute procedure public.handle_new_user();
|
||||
|
||||
-- Set up Storage!
|
||||
insert into storage.buckets (id, name)
|
||||
values ('avatars', 'avatars');
|
||||
|
||||
-- Set up access controls for storage.
|
||||
-- See https://supabase.com/docs/guides/storage#policy-examples for more details.
|
||||
create policy "Avatar images are publicly accessible." on storage.objects
|
||||
for select using (bucket_id = 'avatars');
|
||||
|
||||
create policy "Anyone can upload an avatar." on storage.objects
|
||||
for insert with check (bucket_id = 'avatars');
|
||||
|
||||
create policy "Anyone can update their own avatar." on storage.objects
|
||||
for update using (auth.uid() = owner) with check (bucket_id = 'avatars');
|
||||
```
|
||||
@@ -0,0 +1,167 @@
|
||||
import { render } from 'react-dom'
|
||||
import { IconCommand } from 'ui'
|
||||
import { createElement, FC, useEffect, useRef, Fragment } from 'react'
|
||||
import algoliasearch from 'algoliasearch/lite'
|
||||
import { autocomplete, getAlgoliaResults } from '@algolia/autocomplete-js'
|
||||
import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
// [Joshen] We're currently using DocSearch from Algolia as it provides a nice
|
||||
// UI out of the box + some good preconfigured search settings (e.g hierarchy).
|
||||
// However, we're using our own Algolia account to store the records in the indexes
|
||||
// (rather than going through the DocSearch program from Algolia where they'll crawl
|
||||
// our site for us). Refer to scripts/build-search on how we're saving the records.
|
||||
|
||||
// Using Algolia's autocomplete library gives us full flexbility in terms of customizing
|
||||
// our search experience, but that will take time to figure out. Hence why for now we're just
|
||||
// using DocSearch with our own records.
|
||||
|
||||
// Potentially for search, we could
|
||||
// - Go ahead with the DocSearch program and let them crawl our site to generate the records
|
||||
// - But we need to ensure that our site is semantically correct first
|
||||
// - Use Algolia itself to flesh out our own search logic
|
||||
// - The basics are already set up to be honest, but will take time to make it great
|
||||
// - Go back to Typesense if we deem that Algolia is not helpful in the long run
|
||||
|
||||
const searchClient = algoliasearch(
|
||||
process.env.NEXT_PUBLIC_ALGOLIA_APP_ID,
|
||||
process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_API_KEY
|
||||
)
|
||||
|
||||
// [Joshen] Not working properly, but lets not get stuck on this
|
||||
// Priority just to get a search working
|
||||
const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({
|
||||
key: 'docs-search',
|
||||
limit: 3,
|
||||
//@ts-ignore
|
||||
transformSource({ source }) {
|
||||
return {
|
||||
...source,
|
||||
templates: {
|
||||
...source.templates,
|
||||
header({ state }) {
|
||||
if (state.query) return null
|
||||
return (
|
||||
<Fragment>
|
||||
<span className="aa-SourceHeaderTitle">Your searches</span>
|
||||
<div className="aa-SourceHeaderLine" />
|
||||
</Fragment>
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
interface Props {}
|
||||
|
||||
const AlgoliaSearch: FC<Props> = ({}) => {
|
||||
const searchRef = useRef(null)
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
if (!searchRef.current) {
|
||||
return undefined
|
||||
}
|
||||
const search = autocomplete({
|
||||
openOnFocus: true,
|
||||
container: searchRef.current,
|
||||
defaultActiveItemId: 0,
|
||||
detachedMediaQuery: '',
|
||||
// @ts-ignore
|
||||
renderer: { createElement, Fragment, render },
|
||||
placeholder: 'Search docs',
|
||||
plugins: [recentSearchesPlugin],
|
||||
renderNoResults({ state, render }, root) {
|
||||
render(
|
||||
<div className="text-scale-1100 py-2 text-sm px-4">
|
||||
No results found for {state.query}.
|
||||
</div>,
|
||||
root
|
||||
)
|
||||
},
|
||||
navigator: {
|
||||
navigate({ itemUrl }) {
|
||||
router.push(itemUrl)
|
||||
},
|
||||
},
|
||||
onSelect({ item, setQuery, setIsOpen, refresh }) {
|
||||
console.log('onSelect', item.url)
|
||||
},
|
||||
// @ts-ignore
|
||||
getSources({ query }) {
|
||||
return [
|
||||
{
|
||||
sourceId: 'pages',
|
||||
templates: {
|
||||
item({ item, components }) {
|
||||
return (
|
||||
<a
|
||||
href={item.url as string}
|
||||
className="aa-ItemLink truncate flex justify-between space-x-4"
|
||||
>
|
||||
<div className="aa-ItemContent w-full">
|
||||
<div className="aa-ItemTitle flex items-center space-x-1">
|
||||
{item.category && (
|
||||
<p
|
||||
className={`${
|
||||
['cli', 'api'].includes(item.category as string)
|
||||
? 'uppercase'
|
||||
: 'capitalize'
|
||||
}`}
|
||||
>
|
||||
{item.category}
|
||||
{item.version ? ` (${item.version})` : ''}:
|
||||
</p>
|
||||
)}
|
||||
<p>
|
||||
<components.Highlight hit={item} attribute="title" />
|
||||
</p>
|
||||
</div>
|
||||
<p className="aa-ItemContentSubtitle">{item.description}</p>
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
},
|
||||
},
|
||||
getItemUrl({ item }) {
|
||||
return item.url
|
||||
},
|
||||
getItems() {
|
||||
// if (!query) return []
|
||||
return getAlgoliaResults({
|
||||
searchClient,
|
||||
queries: [
|
||||
{
|
||||
indexName: 'dev_docs',
|
||||
query,
|
||||
},
|
||||
],
|
||||
})
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
})
|
||||
|
||||
return () => {
|
||||
search.destroy()
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="w-[200px] relative">
|
||||
<div ref={searchRef} />
|
||||
<div className="flex items-center space-x-1 absolute top-[7px] right-2">
|
||||
<div className="text-scale-1200 flex items-center justify-center h-6 w-6 rounded bg-scale-500">
|
||||
<IconCommand size={12} strokeWidth={1.5} />
|
||||
</div>
|
||||
<div className="text-xs text-scale-1200 flex items-center justify-center h-6 w-6 rounded bg-scale-500">
|
||||
K
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AlgoliaSearch
|
||||
@@ -0,0 +1,216 @@
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useState, useEffect, FC } from 'react'
|
||||
import { IconMenu, IconMoon, IconSearch, IconSun, IconCommand, Listbox } from 'ui'
|
||||
import { useTheme } from '../Providers'
|
||||
import { REFERENCES } from './Navigation.constants'
|
||||
import { SearchButton } from '../DocSearch'
|
||||
|
||||
interface Props {
|
||||
currentPage: string
|
||||
}
|
||||
|
||||
const NavBar: FC<Props> = ({ currentPage }) => {
|
||||
const { isDarkMode, toggleTheme } = useTheme()
|
||||
const [mounted, setMounted] = useState(false)
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
||||
|
||||
const { asPath, push } = useRouter()
|
||||
const pathSegments = asPath.split('/')
|
||||
|
||||
const library = pathSegments.length >= 3 ? pathSegments[2] : undefined
|
||||
const libraryMeta = REFERENCES?.[library] ?? undefined
|
||||
const versions = libraryMeta?.versions ?? []
|
||||
|
||||
const version = versions.includes(pathSegments[pathSegments.indexOf(library) + 1])
|
||||
? pathSegments[pathSegments.indexOf(library) + 1]
|
||||
: versions[0]
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [isDarkMode])
|
||||
|
||||
const pageLinks = [
|
||||
{ text: 'Guides', key: 'docs', link: '/' },
|
||||
{ text: 'Reference', key: 'reference', link: '/reference' },
|
||||
]
|
||||
|
||||
const toggleDarkMode = () => {
|
||||
localStorage.setItem('supabaseDarkMode', (!isDarkMode).toString())
|
||||
toggleTheme()
|
||||
|
||||
const key = localStorage.getItem('supabaseDarkMode')
|
||||
document.documentElement.className = key === 'true' ? 'dark' : ''
|
||||
}
|
||||
|
||||
const onSelectVersion = (version: string) => {
|
||||
// [Joshen] Ideally we use <Link> but this works for now
|
||||
if (!library) return
|
||||
if (version === versions[0]) {
|
||||
push(`/reference/${library}`)
|
||||
} else {
|
||||
push(`/reference/${library}/${version}`)
|
||||
}
|
||||
}
|
||||
|
||||
// [Joshen] Kaizen: Use UI library's SidePanel for this
|
||||
const toggleMobileMenu = () => {
|
||||
setMobileMenuOpen(!mobileMenuOpen)
|
||||
const sidebar = document.querySelector('.sidebar-menu-container')
|
||||
const contentPane = document.querySelector('.main-content-pane')
|
||||
|
||||
sidebar.classList.toggle('hidden')
|
||||
contentPane.classList.toggle('hidden')
|
||||
}
|
||||
|
||||
return (
|
||||
<nav
|
||||
className={[
|
||||
'sticky top-0 z-10 flex items-center justify-between',
|
||||
'h-[60px] border-b bg-white px-4 backdrop-blur backdrop-filter',
|
||||
'dark:border-scale-400 dark:bg-scale-200',
|
||||
].join(' ')}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<button className="mr-4 block stroke-2 lg:hidden" onClick={toggleMobileMenu}>
|
||||
<IconMenu className="text-scale-1100" />
|
||||
</button>
|
||||
{mounted && (
|
||||
<Link href="/">
|
||||
<a className="flex items-center">
|
||||
<Image
|
||||
className="cursor-pointer"
|
||||
src={isDarkMode ? '/docs/supabase-dark.svg' : '/docs/supabase-light.svg'}
|
||||
width={124}
|
||||
height={24}
|
||||
alt="Supabase Logo"
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
)}
|
||||
<nav className="ml-8 hidden lg:flex">
|
||||
<ul className="hidden space-x-8 lg:flex">
|
||||
{pageLinks.map((p) => (
|
||||
<li key={`${p.text}-${p.link}`}>
|
||||
<Link href={p.link}>
|
||||
<a
|
||||
className={`text-sm ${
|
||||
currentPage.includes(p.key) ? 'text-brand-900' : 'text-scale-1100'
|
||||
}`}
|
||||
>
|
||||
{p.text}
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
{versions.length > 0 && (
|
||||
<div className="ml-8">
|
||||
<Listbox
|
||||
size="small"
|
||||
defaultValue={version}
|
||||
style={{ width: '70px' }}
|
||||
onChange={onSelectVersion}
|
||||
>
|
||||
{versions.map((version) => (
|
||||
<Listbox.Option key={version} label={version} value={version}>
|
||||
{version}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="hidden items-center md:flex">
|
||||
<ul className="flex items-center">
|
||||
<li className="px-4">
|
||||
<a
|
||||
href="https://app.supabase.com"
|
||||
className="text-scale-1100 text-sm"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
Dashboard
|
||||
</a>
|
||||
</li>
|
||||
<li className="px-4">
|
||||
<a
|
||||
href="https://github.com/supabase/supabase"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<div
|
||||
className="h-4 w-4 !bg-no-repeat !bg-center"
|
||||
style={{
|
||||
background: isDarkMode
|
||||
? "url(\"data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%23fff' d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E\") 50% no-repeat"
|
||||
: "url(\"data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E\")",
|
||||
}}
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li className="px-4">
|
||||
<a
|
||||
href="https://discord.supabase.com"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
className="text-scale-1100"
|
||||
>
|
||||
<div
|
||||
className="h-4 w-4 !bg-no-repeat !bg-center"
|
||||
style={{
|
||||
background: isDarkMode
|
||||
? "url(\"data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 71 55' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg clip-path='url(%23a)'%3E%3Cpath d='M60.105 4.898A58.55 58.55 0 0 0 45.653.415a.22.22 0 0 0-.233.11 40.784 40.784 0 0 0-1.8 3.697c-5.456-.817-10.886-.817-16.23 0-.485-1.164-1.201-2.587-1.828-3.697a.228.228 0 0 0-.233-.11 58.386 58.386 0 0 0-14.451 4.483.207.207 0 0 0-.095.082C1.578 18.73-.944 32.144.293 45.39a.244.244 0 0 0 .093.167c6.073 4.46 11.955 7.167 17.729 8.962a.23.23 0 0 0 .249-.082 42.08 42.08 0 0 0 3.627-5.9.225.225 0 0 0-.123-.312 38.772 38.772 0 0 1-5.539-2.64.228.228 0 0 1-.022-.378c.372-.279.744-.569 1.1-.862a.22.22 0 0 1 .23-.03c11.619 5.304 24.198 5.304 35.68 0a.219.219 0 0 1 .233.027c.356.293.728.586 1.103.865a.228.228 0 0 1-.02.378 36.384 36.384 0 0 1-5.54 2.637.227.227 0 0 0-.121.315 47.249 47.249 0 0 0 3.624 5.897.225.225 0 0 0 .249.084c5.801-1.794 11.684-4.502 17.757-8.961a.228.228 0 0 0 .092-.164c1.48-15.315-2.48-28.618-10.497-40.412a.18.18 0 0 0-.093-.084Zm-36.38 32.427c-3.497 0-6.38-3.211-6.38-7.156 0-3.944 2.827-7.156 6.38-7.156 3.583 0 6.438 3.24 6.382 7.156 0 3.945-2.827 7.156-6.381 7.156Zm23.593 0c-3.498 0-6.38-3.211-6.38-7.156 0-3.944 2.826-7.156 6.38-7.156 3.582 0 6.437 3.24 6.38 7.156 0 3.945-2.798 7.156-6.38 7.156Z' fill='%23fff'/%3E%3C/g%3E%3Cdefs%3E%3CclipPath id='a'%3E%3Cpath fill='%23fff' d='M0 0h71v55H0z'/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E\") 50% no-repeat"
|
||||
: "url(\"data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 71 55' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg clip-path='url(%23a)'%3E%3Cpath d='M60.105 4.898A58.55 58.55 0 0 0 45.653.415a.22.22 0 0 0-.233.11 40.784 40.784 0 0 0-1.8 3.697c-5.456-.817-10.886-.817-16.23 0-.485-1.164-1.201-2.587-1.828-3.697a.228.228 0 0 0-.233-.11 58.386 58.386 0 0 0-14.451 4.483.207.207 0 0 0-.095.082C1.578 18.73-.944 32.144.293 45.39a.244.244 0 0 0 .093.167c6.073 4.46 11.955 7.167 17.729 8.962a.23.23 0 0 0 .249-.082 42.08 42.08 0 0 0 3.627-5.9.225.225 0 0 0-.123-.312 38.772 38.772 0 0 1-5.539-2.64.228.228 0 0 1-.022-.378c.372-.279.744-.569 1.1-.862a.22.22 0 0 1 .23-.03c11.619 5.304 24.198 5.304 35.68 0a.219.219 0 0 1 .233.027c.356.293.728.586 1.103.865a.228.228 0 0 1-.02.378 36.384 36.384 0 0 1-5.54 2.637.227.227 0 0 0-.121.315 47.249 47.249 0 0 0 3.624 5.897.225.225 0 0 0 .249.084c5.801-1.794 11.684-4.502 17.757-8.961a.228.228 0 0 0 .092-.164c1.48-15.315-2.48-28.618-10.497-40.412a.18.18 0 0 0-.093-.084Zm-36.38 32.427c-3.497 0-6.38-3.211-6.38-7.156 0-3.944 2.827-7.156 6.38-7.156 3.583 0 6.438 3.24 6.382 7.156 0 3.945-2.827 7.156-6.381 7.156Zm23.593 0c-3.498 0-6.38-3.211-6.38-7.156 0-3.944 2.826-7.156 6.38-7.156 3.582 0 6.437 3.24 6.38 7.156 0 3.945-2.798 7.156-6.38 7.156Z' fill='%2323272A'/%3E%3C/g%3E%3Cdefs%3E%3CclipPath id='a'%3E%3Cpath fill='%23fff' d='M0 0h71v55H0z'/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E\")",
|
||||
}}
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li className="px-4">
|
||||
<a href="https://twitter.com/supabase" target="_blank" rel="noreferrer noopener">
|
||||
<div
|
||||
className="h-4 w-4 !bg-no-repeat !bg-center"
|
||||
style={{
|
||||
background: isDarkMode
|
||||
? "url(\"data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 335 276' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%23fff' d='M302 70A195 195 0 0 1 3 245a142 142 0 0 0 97-30 70 70 0 0 1-58-47 70 70 0 0 0 31-2 70 70 0 0 1-57-66 70 70 0 0 0 28 5 70 70 0 0 1-18-90 195 195 0 0 0 141 72 67 67 0 0 1 116-62 117 117 0 0 0 43-17 65 65 0 0 1-31 38 117 117 0 0 0 39-11 65 65 0 0 1-32 35'/%3E%3C/svg%3E\") 50% no-repeat"
|
||||
: "url(\"data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 335 276' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M302 70A195 195 0 0 1 3 245a142 142 0 0 0 97-30 70 70 0 0 1-58-47 70 70 0 0 0 31-2 70 70 0 0 1-57-66 70 70 0 0 0 28 5 70 70 0 0 1-18-90 195 195 0 0 0 141 72 67 67 0 0 1 116-62 117 117 0 0 0 43-17 65 65 0 0 1-31 38 117 117 0 0 0 39-11 65 65 0 0 1-32 35'/%3E%3C/svg%3E\")",
|
||||
}}
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<li className="px-4">
|
||||
<div className="cursor-pointer" onClick={toggleDarkMode}>
|
||||
{isDarkMode ? (
|
||||
<IconMoon size={18} strokeWidth={2} className="text-scale-1200" />
|
||||
) : (
|
||||
<IconSun size={18} strokeWidth={2} className="text-scale-1200" />
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<SearchButton>
|
||||
<div className="flex items-center justify-between space-x-6 bg-scale-300 border border-scale-700 pl-3 pr-1.5 py-1.5 rounded">
|
||||
<div className="flex items-center space-x-2">
|
||||
<IconSearch className="text-scale-1100" size={18} strokeWidth={2} />
|
||||
<p className="text-scale-800 text-sm">Search</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<div className="hidden text-scale-1200 md:flex items-center justify-center h-6 w-6 rounded bg-scale-500">
|
||||
<IconCommand size={12} strokeWidth={1.5} />
|
||||
</div>
|
||||
<div className="hidden text-xs text-scale-1200 md:flex items-center justify-center h-6 w-6 rounded bg-scale-500">
|
||||
K
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SearchButton>
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
export default NavBar
|
||||
@@ -0,0 +1,356 @@
|
||||
// [Terry] Also need to look at getPageType() in ./lib/helpers
|
||||
// to set a menu to a page
|
||||
import SupabaseJsV1Nav from 'data/nav/supabase-js/v1'
|
||||
import SupabaseJsV2Nav from 'data/nav/supabase-js/v2'
|
||||
import SupabaseDartV0Nav from 'data/nav/supabase-dart/v0'
|
||||
import SupabaseDartV1Nav from 'data/nav/supabase-dart/v1'
|
||||
import SupabaseCLINav from 'data/nav/supabase-cli'
|
||||
import SupabaseAPINav from 'data/nav/supabase-api'
|
||||
import AuthServerNav from 'data/nav/auth-server'
|
||||
import RealtimeServerNav from 'data/nav/realtime-server'
|
||||
import StorageServerNav from 'data/nav/storage-server'
|
||||
|
||||
import { NavMenu, References } from './Navigation.types'
|
||||
|
||||
export const REFERENCES: References = {
|
||||
javascript: {
|
||||
name: 'supabase-js',
|
||||
library: 'supabase-js',
|
||||
versions: ['v2', 'v1'],
|
||||
icon: '/docs/img/libraries/javascript-icon.svg',
|
||||
},
|
||||
dart: {
|
||||
name: 'Flutter',
|
||||
library: 'supabase-dart',
|
||||
versions: ['v1', 'v0'],
|
||||
icon: '/docs/img/libraries/flutter-icon.svg',
|
||||
},
|
||||
cli: {
|
||||
name: 'CLI',
|
||||
library: undefined,
|
||||
versions: [],
|
||||
icon: '/docs/img/icons/cli-icon.svg',
|
||||
},
|
||||
api: {
|
||||
name: 'API',
|
||||
library: undefined,
|
||||
versions: [],
|
||||
icon: '/docs/img/icons/api-icon.svg',
|
||||
},
|
||||
}
|
||||
|
||||
export const menuItems: NavMenu = {
|
||||
docs: [
|
||||
{
|
||||
label: 'Overview',
|
||||
items: [
|
||||
{ name: 'Introduction', url: '/', items: [] },
|
||||
{ name: 'Features', url: '/features', items: [] },
|
||||
{ name: 'Architecture', url: '/architecture', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Quickstarts',
|
||||
items: [
|
||||
{ name: 'Angular', url: '/guides/with-angular', items: [] },
|
||||
{ name: 'Expo', url: '/guides/with-expo', items: [] },
|
||||
{ name: 'Flutter', url: '/guides/with-flutter', items: [] },
|
||||
{ name: 'Ionic Angular', url: '/guides/with-ionic-angular', items: [] },
|
||||
{ name: 'Ionic React', url: '/guides/with-ionic-react', items: [] },
|
||||
{ name: 'Ionic Vue', url: '/guides/with-ionic-vue', items: [] },
|
||||
{ name: 'Next.js', url: '/guides/with-nextjs', items: [] },
|
||||
{ name: 'Nuxt 3', url: '/guides/with-nuxt-3', items: [] },
|
||||
{ name: 'React', url: '/guides/with-react', items: [] },
|
||||
{ name: 'RedwoodJS', url: '/guides/with-redwoodjs', items: [] },
|
||||
{ name: 'SolidJS', url: '/guides/with-solidjs', items: [] },
|
||||
{ name: 'Svelte', url: '/guides/with-svelte', items: [] },
|
||||
{ name: 'SvelteKit', url: '/guides/with-sveltekit', items: [] },
|
||||
{ name: 'Vue 3', url: '/guides/with-vue-3', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '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: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Auth',
|
||||
items: [
|
||||
{ name: 'Overview', url: '/guides/auth', items: [] },
|
||||
{
|
||||
name: 'Authentication',
|
||||
url: undefined,
|
||||
items: [
|
||||
{ name: 'Login with Email', url: '/guides/auth/auth-email', items: [] },
|
||||
{ name: 'Login with Magic Link', url: '/guides/auth/auth-magic-link', items: [] },
|
||||
{ name: 'Login with Apple', url: '/guides/auth/auth-apple', items: [] },
|
||||
{ name: 'Login with Azure', url: '/guides/auth/auth-azure', items: [] },
|
||||
{ name: 'Login with Bitbucket', url: '/guides/auth/auth-bitbucket', items: [] },
|
||||
{ name: 'Login with Discord', url: '/guides/auth/auth-discord', items: [] },
|
||||
{ name: 'Login with Facebook', url: '/guides/auth/auth-facebook', items: [] },
|
||||
{ name: 'Login with Github', url: '/guides/auth/auth-github', items: [] },
|
||||
{ name: 'Login with Gitlab', url: '/guides/auth/auth-gitlab', items: [] },
|
||||
{ name: 'Login with Google', url: '/guides/auth/auth-google', items: [] },
|
||||
{ name: 'Login with Keycloak', url: '/guides/auth/auth-keycloak', items: [] },
|
||||
{ name: 'Login with LinkedIn', url: '/guides/auth/auth-linkedin', items: [] },
|
||||
{ name: 'Login with Notion', url: '/guides/auth/auth-notion', items: [] },
|
||||
{ name: 'Login with Slack', url: '/guides/auth/auth-slack', items: [] },
|
||||
{ name: 'Login with Spotify', url: '/guides/auth/auth-spotify', items: [] },
|
||||
{ name: 'Login with Twitch', url: '/guides/auth/auth-twitch', items: [] },
|
||||
{ name: 'Login with Twitter', url: '/guides/auth/auth-twitter', items: [] },
|
||||
{ name: 'Login with WorkOS', url: '/guides/auth/auth-workos', items: [] },
|
||||
{ name: 'Phone Auth with Twilio', url: '/guides/auth/auth-twilio', items: [] },
|
||||
{ name: 'Phone Auth with Vonage', url: '/guides/auth/auth-vonage', items: [] },
|
||||
{
|
||||
name: 'Phone Auth with MessageBird',
|
||||
url: '/guides/auth/auth-messagebird',
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Authorization',
|
||||
url: undefined,
|
||||
items: [
|
||||
{ name: 'Row Level Security', url: '/guides/auth/row-level-security', items: [] },
|
||||
{ name: 'Managing User Data', url: '/guides/auth/managing-user-data', items: [] },
|
||||
{ name: 'Enable Captcha Protection', url: '/guides/auth/auth-captcha', items: [] },
|
||||
{ name: 'Server-side Rendering', url: '/guides/auth/server-side-rendering', items: [] },
|
||||
{ name: 'Multi-Factor Authentication', url: '/guides/auth/auth-mfa', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Auth Helpers',
|
||||
url: undefined,
|
||||
items: [
|
||||
{ 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: 'SvelteKit', url: '/guides/auth/auth-helpers/sveltekit', items: [] },
|
||||
{ name: 'Remix', url: '/guides/auth/auth-helpers/remix', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Deep Dive',
|
||||
url: undefined,
|
||||
items: [
|
||||
{
|
||||
name: 'Part One: JWTs',
|
||||
url: '/learn/auth-deep-dive/auth-deep-dive-jwts',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'Part Two: Row Level Security',
|
||||
url: '/learn/auth-deep-dive/auth-row-level-security',
|
||||
items: [],
|
||||
},
|
||||
{ name: 'Part Three: Policies', url: '/learn/auth-deep-dive/auth-policies', items: [] },
|
||||
{ name: 'Part Four: GoTrue', url: '/learn/auth-deep-dive/auth-gotrue', items: [] },
|
||||
{
|
||||
name: 'Part Five: Google OAuth',
|
||||
url: '/learn/auth-deep-dive/auth-google-oauth',
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Database',
|
||||
items: [
|
||||
{ name: 'Overview', url: '/guides/database', items: [] },
|
||||
{ name: 'Database Connections', url: '/guides/database/connecting-to-postgres', items: [] },
|
||||
{ name: 'Tables and Data', url: '/guides/database/tables', items: [] },
|
||||
{ name: 'Database Functions', url: '/guides/database/functions', items: [] },
|
||||
{ name: 'Database Webhooks', url: '/guides/database/webhooks', items: [] },
|
||||
{ name: 'Full Text Search', url: '/guides/database/full-text-search', items: [] },
|
||||
{ name: 'Database Testing', url: '/guides/database/testing', items: [] },
|
||||
{
|
||||
name: 'Serverless APIs',
|
||||
url: undefined,
|
||||
items: [
|
||||
{ name: 'Overview', url: '/guides/api', items: [] },
|
||||
{ name: 'Generating Types', url: '/guides/api/generating-types', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Extensions',
|
||||
url: undefined,
|
||||
items: [
|
||||
{ name: 'Overview', url: '/guides/database/extensions', items: [] },
|
||||
{ name: 'http: RESTful Client', url: '/guides/database/extensions/http', items: [] },
|
||||
{
|
||||
name: 'pg_cron: Job Scheduling',
|
||||
url: '/guides/database/extensions/pgcron',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'pg_net: Async Networking',
|
||||
url: '/guides/database/extensions/pgnet',
|
||||
items: [],
|
||||
},
|
||||
{ name: 'pgTAP: Unit Testing', url: '/guides/database/extensions/pgtap', items: [] },
|
||||
{
|
||||
name: 'plv8: Javascript Language',
|
||||
url: '/guides/database/extensions/plv8',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'uuid-ossp: Unique Identifiers',
|
||||
url: '/guides/database/extensions/uuid-ossp',
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Configuration',
|
||||
url: undefined,
|
||||
items: [
|
||||
{ name: 'Timeouts', url: '/guides/database/timeouts', items: [] },
|
||||
{ name: 'Replication', url: '/guides/database/replication', items: [] },
|
||||
{ name: 'Passwords', url: '/guides/database/managing-passwords', items: [] },
|
||||
{ name: 'Timezones', url: '/guides/database/managing-timezones', items: [] },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Edge Functions',
|
||||
items: [
|
||||
{ name: 'Overview', url: '/guides/functions', items: [] },
|
||||
{ name: 'Auth', url: '/guides/functions/auth', items: [] },
|
||||
{ name: 'Examples', url: '/guides/functions/examples', items: [] },
|
||||
{ name: 'CI/CD Workflow', url: '/guides/functions/cicd-workflow', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Realtime',
|
||||
items: [
|
||||
{ name: 'Overview', url: '/guides/realtime', items: [] },
|
||||
{ name: 'Quickstart', url: '/guides/realtime/quickstart', items: [] },
|
||||
{ name: 'Postgres CDC', url: '/guides/realtime/postgres-cdc', items: [] },
|
||||
{ name: 'Rate Limits', url: '/guides/realtime/rate-limits', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Storage',
|
||||
items: [
|
||||
{ name: 'Overview', url: '/guides/storage', items: [] },
|
||||
{ name: 'CDN', url: '/guides/storage-cdn', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Platform',
|
||||
items: [
|
||||
{ name: 'Overview', url: '/guides/hosting/platform', items: [] },
|
||||
{ name: 'Custom Domains', url: '/guides/platform/custom-domains', items: [] },
|
||||
{ name: 'Database Usage', url: '/guides/platform/database-usage', items: [] },
|
||||
{ name: 'Logging', url: '/guides/platform/logs', items: [] },
|
||||
{ name: 'Metrics', url: '/guides/platform/metrics', items: [] },
|
||||
{
|
||||
name: 'Migrating and upgrading',
|
||||
url: '/guides/platform/migrating-and-upgrading-projects',
|
||||
items: [],
|
||||
},
|
||||
{ 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: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Self Hosting',
|
||||
items: [
|
||||
{ name: 'Overview', url: '/guides/hosting/overview', items: [] },
|
||||
{ name: 'Docker', url: '/guides/hosting/docker', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Migrate to Supabase',
|
||||
items: [
|
||||
{ name: 'Firebase Auth', url: '/guides/migrations/firebase-auth', items: [] },
|
||||
{ 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: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Integrations',
|
||||
items: [
|
||||
{ name: 'Supabase Marketplace', url: '/guides/integrations/integrations', items: [] },
|
||||
{
|
||||
name: 'Auth',
|
||||
url: undefined,
|
||||
items: [
|
||||
{ name: 'Auth0', url: '/guides/integrations/auth0', items: [] },
|
||||
{ name: 'Authsignal', url: '/guides/integrations/authsignal', items: [] },
|
||||
{ name: 'Clerk', url: '/guides/integrations/clerk', items: [] },
|
||||
{ name: 'keyri', url: '/guides/integrations/keyri', items: [] },
|
||||
{ name: 'Stytch', url: '/guides/integrations/stytch', items: [] },
|
||||
{ name: 'SuperTokens', url: '/guides/integrations/supertokens', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Caching / Offline-first',
|
||||
url: undefined,
|
||||
items: [{ name: 'Polyscale', url: '/guides/integrations/polyscale', items: [] }],
|
||||
},
|
||||
{
|
||||
name: 'Developer Tools',
|
||||
url: undefined,
|
||||
items: [
|
||||
{ name: 'pgMustard', url: '/guides/integrations/pgmustard', items: [] },
|
||||
{ name: 'Prisma', url: '/guides/integrations/prisma', items: [] },
|
||||
{ name: 'Sequin', url: '/guides/integrations/sequin', items: [] },
|
||||
{ name: 'Snaplet', url: '/guides/integrations/snaplet', items: [] },
|
||||
{ name: 'Vercel', url: '/guides/integrations/vercel', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Low-code',
|
||||
url: undefined,
|
||||
items: [
|
||||
{ name: 'Appsmith', url: '/guides/integrations/appsmith', items: [] },
|
||||
{ name: 'Dashibase', url: '/guides/integrations/dashibase', items: [] },
|
||||
{ name: 'DhiWise', url: '/guides/integrations/dhiwise', items: [] },
|
||||
{ name: 'Directus', url: '/guides/integrations/directus', items: [] },
|
||||
{ name: 'Draftbit', url: '/guides/integrations/draftbit', items: [] },
|
||||
{ name: 'Plasmic', url: '/guides/integrations/plasmic', items: [] },
|
||||
{ name: 'WeWeb', url: '/guides/integrations/weweb', items: [] },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
reference: [
|
||||
{
|
||||
label: 'Official',
|
||||
items: [
|
||||
{ name: 'Reference Documentation', url: '/reference', items: [] },
|
||||
{ name: 'Supabase JavaScript Library', url: '/reference/javascript', items: [] },
|
||||
{ name: 'Supabase Flutter Library', url: '/reference/dart', items: [] },
|
||||
{ name: 'Supabase CLI', url: '/reference/cli', items: [] },
|
||||
{ name: 'Management API', url: '/reference/api', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Self-hosting',
|
||||
items: [
|
||||
{ name: 'Auth Server', url: '/reference/auth', items: [] },
|
||||
{ name: 'Realtime Server', url: '/reference/realtime', items: [] },
|
||||
{ name: 'Storage Server', url: '/reference/storage', items: [] },
|
||||
],
|
||||
},
|
||||
],
|
||||
'reference/javascript': SupabaseJsV2Nav,
|
||||
'reference/javascript/v1': SupabaseJsV1Nav,
|
||||
'reference/dart': SupabaseDartV1Nav,
|
||||
'reference/dart/v0': SupabaseDartV0Nav,
|
||||
'reference/cli': SupabaseCLINav,
|
||||
'reference/api': SupabaseAPINav,
|
||||
'reference/auth': AuthServerNav,
|
||||
'reference/realtime': RealtimeServerNav,
|
||||
'reference/storage': StorageServerNav,
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
export interface NavMenu {
|
||||
[key: string]: NavMenuGroup[]
|
||||
}
|
||||
|
||||
export interface NavMenuGroup {
|
||||
label: string
|
||||
items: NavMenuSection[]
|
||||
}
|
||||
|
||||
export interface NavMenuSection {
|
||||
name: string
|
||||
url?: string
|
||||
items: NavMenuSection[]
|
||||
}
|
||||
|
||||
export interface References {
|
||||
[key: string]: {
|
||||
name: string
|
||||
library?: string
|
||||
versions: string[]
|
||||
icon: string
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
import Link from 'next/link'
|
||||
import Image from 'next/image'
|
||||
import { useRouter } from 'next/router'
|
||||
import { IconChevronRight, IconArrowLeft } from '~/../../packages/ui'
|
||||
import { REFERENCES } from './Navigation.constants'
|
||||
import { NavMenuGroup, NavMenuSection } from './Navigation.types'
|
||||
import * as Accordion from '@radix-ui/react-accordion'
|
||||
|
||||
const SideBar = ({ menuItems = [] }: { menuItems: any }) => {
|
||||
const { asPath } = useRouter()
|
||||
const pathSegments = asPath.split('/')
|
||||
|
||||
const isInReferencePages = pathSegments.includes('reference') && pathSegments.length >= 3
|
||||
const referenceMeta = pathSegments.length >= 3 ? REFERENCES[pathSegments[2]] : undefined
|
||||
|
||||
const currentSection: NavMenuGroup = menuItems.find((group) => {
|
||||
const foundItem = group.items.find((section) => {
|
||||
if (section.items.length > 0) {
|
||||
const foundSubItem = section.items.find((item) => {
|
||||
if (item.url === asPath) return item
|
||||
})
|
||||
if (foundSubItem) return section
|
||||
} else {
|
||||
if (section.url === asPath) return section
|
||||
}
|
||||
})
|
||||
if (foundItem) return group
|
||||
})
|
||||
|
||||
const currentSubSection: NavMenuSection =
|
||||
currentSection !== undefined
|
||||
? currentSection.items.find((section) => {
|
||||
if (section.items.length === 0) {
|
||||
return undefined
|
||||
} else {
|
||||
return section.items.find((item) => {
|
||||
if (item.url === asPath) return item
|
||||
})
|
||||
}
|
||||
})
|
||||
: undefined
|
||||
|
||||
return (
|
||||
<div
|
||||
className="dark:bg-scale-200 dark:border-scale-400 sidebar-width sticky top-16
|
||||
h-screen overflow-y-scroll border-r py-8 px-6 sidebar-menu-container hidden lg:block"
|
||||
>
|
||||
{isInReferencePages && (
|
||||
<>
|
||||
<Link href="/reference">
|
||||
<a>
|
||||
<div className="flex items-center space-x-4 opacity-75 hover:opacity-100 transition">
|
||||
<IconArrowLeft size={16} strokeWidth={2} className="text-scale-1200" />
|
||||
<span className="text-sm text-scale-1200">All Reference Docs</span>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
{referenceMeta !== undefined && (
|
||||
<div className="my-5 flex items-center space-x-4">
|
||||
<div className="h-10 w-10 rounded bg-scale-500 flex items-center justify-center">
|
||||
<Image
|
||||
className="rounded"
|
||||
width={24}
|
||||
height={24}
|
||||
alt={referenceMeta.name}
|
||||
src={referenceMeta.icon}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-scale-1200 font-bold">{referenceMeta.name}</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{menuItems.length === 1 ? (
|
||||
<div className="my-2">
|
||||
{menuItems[0].items.map((item) => (
|
||||
<Link key={item.name} href={item.url}>
|
||||
<a>
|
||||
<div
|
||||
key={item.name}
|
||||
className={[
|
||||
'py-1.5 px-5 rounded text-sm transition',
|
||||
`${
|
||||
item.url === asPath
|
||||
? 'bg-scale-200 text-brand-900'
|
||||
: 'text-scale-1100 hover:text-scale-1200'
|
||||
}`,
|
||||
].join(' ')}
|
||||
>
|
||||
{item.name}
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<Accordion.Root
|
||||
collapsible
|
||||
type="single"
|
||||
defaultValue={currentSection?.label}
|
||||
className="w-full space-y-0.5"
|
||||
>
|
||||
{menuItems.map((group: NavMenuGroup) => (
|
||||
<Accordion.Item key={group.label} value={group.label}>
|
||||
<Accordion.Trigger className="w-full flex items-center space-x-2 py-1.5">
|
||||
<IconChevronRight
|
||||
className="transition text-scale-1000 data-open-parent:rotate-90"
|
||||
size={14}
|
||||
strokeWidth={2}
|
||||
/>
|
||||
<span className="text-scale-1200 text-sm group-hover:text-brand-900 transition">
|
||||
{group.label}
|
||||
</span>
|
||||
</Accordion.Trigger>
|
||||
<Accordion.Content className="transition my-2 data-open:animate-slide-down data-closed:animate-slide-up">
|
||||
{group.items.map((section: NavMenuSection) => {
|
||||
if (section.items.length === 0) {
|
||||
return (
|
||||
<Link href={section.url} key={section.name}>
|
||||
<a>
|
||||
<div
|
||||
className={[
|
||||
'py-1.5 px-5 rounded text-sm transition',
|
||||
`${
|
||||
section.url === asPath
|
||||
? 'bg-scale-200 text-brand-900'
|
||||
: 'text-scale-1100 hover:text-scale-1200'
|
||||
}`,
|
||||
].join(' ')}
|
||||
>
|
||||
{section.name}
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Accordion.Root
|
||||
collapsible
|
||||
key={section.name}
|
||||
type="single"
|
||||
className="space-y-0.5"
|
||||
defaultValue={currentSubSection?.name}
|
||||
>
|
||||
<Accordion.Item value={section.name}>
|
||||
<Accordion.Trigger className="flex items-center space-x-2 px-4 py-1.5">
|
||||
<IconChevronRight
|
||||
className="transition text-scale-1000 data-open-parent:rotate-90"
|
||||
size={14}
|
||||
strokeWidth={2}
|
||||
/>
|
||||
<span className="text-scale-1200 text-sm group-hover:text-brand-900 transition">
|
||||
{section.name}
|
||||
</span>
|
||||
</Accordion.Trigger>
|
||||
<Accordion.Content className="my-2 data-open:animate-slide-down data-closed:animate-slide-up">
|
||||
{section.items.map((item: NavMenuSection) => (
|
||||
<Link key={item.name} href={item.url}>
|
||||
<a>
|
||||
<div
|
||||
key={item.name}
|
||||
className={[
|
||||
'py-1.5 ml-4 px-5 rounded text-sm transition',
|
||||
`${
|
||||
item.url === asPath
|
||||
? 'bg-scale-200 text-brand-900'
|
||||
: 'text-scale-1100 hover:text-scale-1200'
|
||||
}`,
|
||||
].join(' ')}
|
||||
>
|
||||
{item.name}
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
))}
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
</Accordion.Root>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
))}
|
||||
</Accordion.Root>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SideBar
|
||||
@@ -0,0 +1,53 @@
|
||||
import Link from 'next/link'
|
||||
import { IconArrowLeft, IconArrowRight } from 'ui'
|
||||
|
||||
const Pagination = ({ currentPage, totalCount }: { currentPage: number; totalCount: number }) => {
|
||||
// TODO: not sure if this is the most efficient way to do this. may need to refactor.
|
||||
const totalArray = Array.from({ length: totalCount }, (_, i: number) => i + 1)
|
||||
const pages = totalArray.filter((page: number) => {
|
||||
return page >= currentPage - 2 && page <= currentPage + 2
|
||||
})
|
||||
|
||||
return (
|
||||
<ul className="flex justify-center space-x-1 text-xs font-medium">
|
||||
<li>
|
||||
<Link href={`/discussions?page=${currentPage - 1}`}>
|
||||
<a className="border-scale-600 bg-scale-300 inline-flex h-8 w-8 items-center justify-center rounded border">
|
||||
<IconArrowLeft
|
||||
className="stroke-2 transition group-hover:-translate-x-1"
|
||||
height={12.5}
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
{pages.map((page: number, i: number) => {
|
||||
i = i + 1
|
||||
return (
|
||||
<li key={i}>
|
||||
<Link href={`/discussions?page=${page}`}>
|
||||
<a
|
||||
className={`border-scale-600 inline-flex h-8 w-8 items-center justify-center rounded border ${
|
||||
currentPage === page ? 'bg-brand-900' : 'bg-scale-300'
|
||||
}`}
|
||||
>
|
||||
{page}
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
<li>
|
||||
<Link href={`/discussions?page=${currentPage + 1}`}>
|
||||
<a className="border-scale-600 bg-scale-300 inline-flex h-8 w-8 items-center justify-center rounded border">
|
||||
<IconArrowRight
|
||||
className="stroke-2 transition group-hover:-translate-x-1"
|
||||
height={12.5}
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
export default Pagination
|
||||
File renamed without changes.
@@ -0,0 +1,73 @@
|
||||
import Image from 'next/image'
|
||||
import React from 'react'
|
||||
import sponsors from '../data/sponsors.json'
|
||||
|
||||
export default function Sponsors() {
|
||||
const tiers = [
|
||||
{
|
||||
tier_name: '$5,000 a month',
|
||||
heading: 'Enterprise: $5,000 per month',
|
||||
transactions: sponsors.filter((x) => x.tier == '$5,000 a month'),
|
||||
},
|
||||
{
|
||||
tier_name: '$2,500 a month',
|
||||
heading: 'Agency: $2,500 per month',
|
||||
transactions: sponsors.filter((x) => x.tier == '$2,500 a month'),
|
||||
},
|
||||
{
|
||||
tier_name: '$1,000 a month',
|
||||
heading: 'Startup: $1,000 per month',
|
||||
transactions: sponsors.filter((x) => x.tier == '$1,000 a month'),
|
||||
},
|
||||
{
|
||||
tier_name: '$49 a month',
|
||||
heading: 'Evangelist: $49 per month',
|
||||
transactions: sponsors.filter((x) => x.tier == '$49 a month'),
|
||||
},
|
||||
{
|
||||
tier_name: '$19 a month',
|
||||
heading: 'Supporter: $19 per month',
|
||||
transactions: sponsors.filter((x) => x.tier == '$19 a month'),
|
||||
},
|
||||
{
|
||||
tier_name: '$5 a month',
|
||||
heading: 'Contributor: $5 per month',
|
||||
transactions: sponsors.filter((x) => x.tier == '$5 a month'),
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div>
|
||||
{tiers.map(
|
||||
(t, index) =>
|
||||
!!t.transactions.length && (
|
||||
<div key={index}>
|
||||
<h4 className="font-bold">{t.heading}</h4>
|
||||
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6 pl-4">
|
||||
{t.transactions.map((x, index) => (
|
||||
<div className="" key={index}>
|
||||
<a
|
||||
className="flex items-center gap-4 shadow-none shrink-0"
|
||||
href={`https://github.com/${x.sponsor}`}
|
||||
>
|
||||
<Image
|
||||
alt="Sponsor"
|
||||
className="mt-4 mb-4 rounded-full w-12 h-12"
|
||||
width={45}
|
||||
height={45}
|
||||
src={`https://github.com/${x.sponsor}.png`}
|
||||
/>
|
||||
<div className="flex items-center flex-col justify-center text-center">
|
||||
<h5 className="avatar__name">{x.sponsor}</h5>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<br />
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { FC } from 'react'
|
||||
import { getAnchor, removeAnchor } from './CustomHTMLElements/CustomHTMLElements.utils'
|
||||
|
||||
interface TOCHeader {
|
||||
id: number
|
||||
lvl: number
|
||||
seen: number
|
||||
content: string
|
||||
slug: string
|
||||
}
|
||||
|
||||
interface Props {
|
||||
toc: any
|
||||
}
|
||||
|
||||
const formatSlug = (slug: string) => {
|
||||
// [Joshen] We will still provide support for headers declared like this:
|
||||
// ## REST API {#rest-api-overview}
|
||||
// At least for now, this was a docusaurus thing.
|
||||
if (slug.includes('#')) return slug.split('-#')[1]
|
||||
return slug
|
||||
}
|
||||
|
||||
const formatTOCHeader = (content: string) => {
|
||||
let begin = false
|
||||
const res = []
|
||||
for (const x of content) {
|
||||
if (x === '`') {
|
||||
if (!begin) {
|
||||
begin = true
|
||||
res.push(`<code class="text-xs border rounded bg-scale-400 border-scale-500">`)
|
||||
} else {
|
||||
begin = false
|
||||
res.push(`</code>`)
|
||||
}
|
||||
} else {
|
||||
res.push(x)
|
||||
}
|
||||
}
|
||||
return res.join('')
|
||||
}
|
||||
|
||||
const TableOfContents: FC<Props> = ({ toc }) => {
|
||||
// [Joshen] markdown-toc doesn't seem to read maxdepth from the options passed in
|
||||
// Our first level headers will be H2s (H1 is ignored), and we only show up to H3
|
||||
|
||||
return (
|
||||
<ul className="toc-menu list-none pl-4 text-[0.8rem] grid gap-2 mt-12">
|
||||
{(toc.json as TOCHeader[])
|
||||
.filter((item) => item.lvl !== 1 && item.lvl <= 3)
|
||||
.map((item: any, i: number) => {
|
||||
return (
|
||||
<li key={i} id={item.lvl} style={{ marginLeft: `${(item.lvl - 2) * 1}rem` }}>
|
||||
<a
|
||||
href={`#${formatSlug(item.slug)}`}
|
||||
className="text-scale-1000 hover:text-brand-900 transition-colors"
|
||||
dangerouslySetInnerHTML={{ __html: formatTOCHeader(removeAnchor(item.content)) }}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
export default TableOfContents
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Button, Tabs, Alert } from 'ui'
|
||||
|
||||
// Common components
|
||||
import Admonition from './Admonition'
|
||||
import ButtonCard from './ButtonCard'
|
||||
import CodeBlock from './CodeBlock/CodeBlock'
|
||||
import { parseNumericRange } from './CodeBlock/CodeBlock.utils'
|
||||
import JwtGenerator from './JwtGenerator'
|
||||
|
||||
// Page specific components
|
||||
import Frameworks from './Frameworks'
|
||||
import AuthProviders from './AuthProviders'
|
||||
import FunctionsExamples from './FunctionsExamples'
|
||||
|
||||
// Other components
|
||||
import { Heading } from './CustomHTMLElements'
|
||||
import QuickstartIntro from './MDX/quickstart_intro.mdx'
|
||||
import ProjectSetup from './MDX/project_setup.mdx'
|
||||
|
||||
const components = {
|
||||
Admonition,
|
||||
Button,
|
||||
ButtonCard,
|
||||
CodeBlock,
|
||||
Frameworks,
|
||||
AuthProviders,
|
||||
FunctionsExamples,
|
||||
JwtGenerator,
|
||||
QuickstartIntro,
|
||||
ProjectSetup,
|
||||
Alert: (props: any) => (
|
||||
<Alert {...props} className="not-prose">
|
||||
{props.children}
|
||||
</Alert>
|
||||
),
|
||||
Tabs,
|
||||
TabPanel: (props: any) => <Tabs.Panel {...props}>{props.children}</Tabs.Panel>,
|
||||
|
||||
h2: (props: any) => (
|
||||
<Heading tag="h2" {...props}>
|
||||
{props.children}
|
||||
</Heading>
|
||||
),
|
||||
h3: (props: any) => (
|
||||
<Heading tag="h3" {...props}>
|
||||
{props.children}
|
||||
</Heading>
|
||||
),
|
||||
code: (props: any) => {
|
||||
const linesToHighlight = parseNumericRange(props.lines ?? '')
|
||||
return <CodeBlock {...props} linesToHighlight={linesToHighlight} />
|
||||
},
|
||||
mono: (props: any) => <code className="text-sm">{props.children}</code>,
|
||||
}
|
||||
|
||||
export default components
|
||||
@@ -0,0 +1,191 @@
|
||||
const authProviders = [
|
||||
// {
|
||||
// name: 'Email',
|
||||
// // logo: '/img/libraries/dart-icon.svg',
|
||||
// href: '/docs/guides/auth/auth-apple',
|
||||
// official: true,
|
||||
// supporter: 'Supabase',
|
||||
// platform: true,
|
||||
// selfHosted: true,
|
||||
// },
|
||||
// {
|
||||
// name: 'Magic Links',
|
||||
// // logo: '/img/libraries/dart-icon.svg',
|
||||
// href: '/docs/guides/auth/auth-apple',
|
||||
// official: true,
|
||||
// supporter: 'Supabase',
|
||||
// platform: true,
|
||||
// selfHosted: true,
|
||||
// },
|
||||
{
|
||||
name: 'Apple',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
href: '/guides/auth/auth-apple',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
},
|
||||
{
|
||||
name: 'Azure',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
href: '/guides/auth/auth-azure',
|
||||
official: false,
|
||||
supporter: 'TBD',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
},
|
||||
{
|
||||
name: 'Bitbucket',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
href: '/guides/auth/auth-bitbucket',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
},
|
||||
{
|
||||
name: 'Discord',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
href: '/guides/auth/auth-discord',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
},
|
||||
{
|
||||
name: 'Facebook',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
href: '/guides/auth/auth-facebook',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
},
|
||||
{
|
||||
name: 'GitHub',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
href: '/guides/auth/auth-github',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
},
|
||||
{
|
||||
name: 'GitLab',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
href: '/guides/auth/auth-gitlab',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
},
|
||||
{
|
||||
name: 'Google',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
href: '/guides/auth/auth-google',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
},
|
||||
{
|
||||
name: 'Keycloak',
|
||||
href: '/guides/auth/auth-keycloak',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
},
|
||||
{
|
||||
name: 'LinkedIn',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
href: '/guides/auth/auth-linkedin',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
},
|
||||
{
|
||||
name: 'MessageBird',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
href: '/guides/auth/auth-messagebird',
|
||||
official: false,
|
||||
supporter: 'MessageBird',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
},
|
||||
{
|
||||
name: 'Notion',
|
||||
// logo: '/img/libraries/notion-icon.svg',
|
||||
href: '/guides/auth/auth-notion',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
},
|
||||
{
|
||||
name: 'Slack',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
href: '/guides/auth/auth-slack',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
},
|
||||
{
|
||||
name: 'Spotify',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
href: '/guides/auth/auth-spotify',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
},
|
||||
{
|
||||
name: 'Twitter',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
href: '/guides/auth/auth-twitter',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
},
|
||||
{
|
||||
name: 'Twitch',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
href: '/guides/auth/auth-twitch',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
},
|
||||
{
|
||||
name: 'Zoom',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
href: '/guides/auth/auth-zoom',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
},
|
||||
{
|
||||
name: 'Twilio',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
href: '/guides/auth/auth-twilio',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
},
|
||||
{
|
||||
name: 'Vonage',
|
||||
href: '/guides/auth/auth-vonage',
|
||||
official: false,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
},
|
||||
]
|
||||
|
||||
export default authProviders
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,150 @@
|
||||
[
|
||||
{
|
||||
"title": "Product",
|
||||
"links": [
|
||||
{
|
||||
"text": "Database",
|
||||
"url": "/database"
|
||||
},
|
||||
{
|
||||
"text": "Auth",
|
||||
"url": "/auth"
|
||||
},
|
||||
{
|
||||
"text": "Functions",
|
||||
"url": "/edge-functions"
|
||||
},
|
||||
{
|
||||
"text": "Realtime",
|
||||
"url": "/realtime"
|
||||
},
|
||||
{
|
||||
"text": "Storage",
|
||||
"url": "/storage"
|
||||
},
|
||||
{
|
||||
"text": "Pricing",
|
||||
"url": "/pricing"
|
||||
},
|
||||
{
|
||||
"text": "Beta",
|
||||
"url": "/beta"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Resources",
|
||||
"links": [
|
||||
{
|
||||
"text": "Support",
|
||||
"url": "/support"
|
||||
},
|
||||
{
|
||||
"text": "System Status",
|
||||
"url": "https://status.supabase.com/"
|
||||
},
|
||||
{
|
||||
"text": "Integrations",
|
||||
"url": "/partners/integrations"
|
||||
},
|
||||
{
|
||||
"text": "Experts",
|
||||
"url": "/partners/experts"
|
||||
},
|
||||
{
|
||||
"text": "Brand Assets / Logos",
|
||||
"url": "/brand-assets"
|
||||
},
|
||||
{
|
||||
"text": "DPA",
|
||||
"url": "/legal/dpa"
|
||||
},
|
||||
{
|
||||
"text": "SOC2",
|
||||
"url": "/legal/soc2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Developers",
|
||||
"links": [
|
||||
{
|
||||
"text": "Documentation",
|
||||
"url": "/docs"
|
||||
},
|
||||
{
|
||||
"text": "API Reference",
|
||||
"url": "/docs/reference"
|
||||
},
|
||||
{
|
||||
"text": "Changelog",
|
||||
"url": "/changelog"
|
||||
},
|
||||
{
|
||||
"text": "Contributing",
|
||||
"url": "/docs/handbook/contributing"
|
||||
},
|
||||
{
|
||||
"text": "Open Source",
|
||||
"url": "/docs/oss"
|
||||
},
|
||||
{
|
||||
"text": "SupaSquad",
|
||||
"url": "/docs/handbook/supasquad"
|
||||
},
|
||||
{
|
||||
"text": "DevTo",
|
||||
"url": "https://dev.to/supabase"
|
||||
},
|
||||
{
|
||||
"text": "RSS",
|
||||
"url": "/rss.xml"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Company",
|
||||
"links": [
|
||||
{
|
||||
"text": "Blog",
|
||||
"url": "/blog"
|
||||
},
|
||||
{
|
||||
"text": "Careers",
|
||||
"url": "https://about.supabase.com/careers"
|
||||
},
|
||||
{
|
||||
"text": "Company",
|
||||
"url": "/company"
|
||||
},
|
||||
{
|
||||
"text": "Terms of Service",
|
||||
"url": "/docs/company/terms"
|
||||
},
|
||||
{
|
||||
"text": "Privacy Policy",
|
||||
"url": "/docs/company/privacy"
|
||||
},
|
||||
{
|
||||
"text": "Acceptable Use Policy",
|
||||
"url": "/docs/company/aup"
|
||||
},
|
||||
{
|
||||
"text": "Service Level Agreement",
|
||||
"url": "/docs/company/sla"
|
||||
},
|
||||
{
|
||||
"text": "Humans.txt",
|
||||
"url": "/humans.txt"
|
||||
},
|
||||
{
|
||||
"text": "Lawyers.txt",
|
||||
"url": "/lawyers.txt"
|
||||
},
|
||||
{
|
||||
"text": "Security.txt",
|
||||
"url": "/.well-known/security.txt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,29 @@
|
||||
# [WIP] Adding navigation for a new section
|
||||
|
||||
[Note] This isn't the best way for versioning in my opinion, but this is the current way to do it while we're migrating to Next.js
|
||||
|
||||
This folder holds the structure for the side navigation menu in the reference documentation pages. Each reference section (as well as their individual versions where applicable) have its own set of navigation.
|
||||
|
||||
The following are relevant to adding navigation for a new section:
|
||||
|
||||
- `getPageType()` from `lib/helpers`
|
||||
- `menuItems` and `REFERENCES` from `components/Navigation/Navigation.constants`
|
||||
- `NavMenu` from `components/Navigation/Navigation.types`
|
||||
|
||||
## Adding navigation for a section that doesn't require versioning
|
||||
|
||||
Example for standalone sections are the API and CLI reference pages. You'll just need to add a new file in this folder following the `NavMenu` interface (naming is arbitrary).
|
||||
|
||||
Thereafter, update `getPageType` to be able to get the page type based on the URL path - preferably follow the syntax of `reference/{section_name}`.
|
||||
|
||||
Finally, add the menu to `menuItems` under a new key - the key should be what you added to `getPageType` before.
|
||||
|
||||
## Adding navigation for a section that requires versioning
|
||||
|
||||
This is more applicable for client libraries (e.g supabase-js and supabase-dart). Versions will all sit within their own folder - if the folder doesn't exist yet, just create one. The navigation for each section will then be named as 'v1', 'v2', and so on.
|
||||
|
||||
Then, update `REFERENCES` accordingly.
|
||||
|
||||
Thereafter, update `getPageType` to be able to get the page type based on the URL path - preferably follow the syntax of `reference/{section_name}/{version}`. Typically the latest version available will not have the `/{version}` in it.
|
||||
|
||||
Finally, add the menu to `menuItems` under a new key - the key should be what you added to `getPageType` before.
|
||||
@@ -0,0 +1,12 @@
|
||||
const Nav = [
|
||||
{
|
||||
label: 'Auth',
|
||||
items: [
|
||||
{ name: 'Auth Server', url: '/reference/auth', items: [] },
|
||||
{ name: 'Configuration', url: '/reference/auth/config', items: [] },
|
||||
{ name: 'Release Notes', url: '/reference/auth/release-notes', items: [] },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export default Nav
|
||||
@@ -0,0 +1,12 @@
|
||||
const Nav = [
|
||||
{
|
||||
label: 'Realtime',
|
||||
items: [
|
||||
{ name: 'Realtime Server', url: '/reference/realtime', items: [] },
|
||||
{ name: 'Configuration', url: '/reference/realtime/config', items: [] },
|
||||
{ name: 'Release Notes', url: '/reference/realtime/release-notes', items: [] },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export default Nav
|
||||
@@ -0,0 +1,13 @@
|
||||
const Nav = [
|
||||
{
|
||||
label: 'Storage',
|
||||
items: [
|
||||
{ name: 'Storage Server', url: '/reference/storage', items: [] },
|
||||
{ name: 'Usage', url: '/reference/storage/usage', items: [] },
|
||||
{ name: 'Configuration', url: '/reference/storage/config', items: [] },
|
||||
{ name: 'Release Notes', url: '/reference/storage/release-notes', items: [] },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export default Nav
|
||||
@@ -0,0 +1,12 @@
|
||||
const Nav = [
|
||||
{
|
||||
label: 'API',
|
||||
items: [
|
||||
{ name: 'Management API', url: '/reference/api', items: [] },
|
||||
{ name: 'Usage', url: '/reference/api/usage', items: [] },
|
||||
{ name: 'Release Notes', url: '/reference/api/release-notes', items: [] },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export default Nav
|
||||
@@ -0,0 +1,13 @@
|
||||
const Nav = [
|
||||
{
|
||||
label: 'CLI',
|
||||
items: [
|
||||
{ name: 'Supabase CLI', url: '/reference/cli', items: [] },
|
||||
{ name: 'Usage', url: '/reference/cli/usage', items: [] },
|
||||
{ name: 'Configuration', url: '/reference/cli/config', items: [] },
|
||||
{ name: 'Release Notes', url: '/reference/cli/release-notes', items: [] },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export default Nav
|
||||
@@ -0,0 +1,132 @@
|
||||
const Nav = [
|
||||
{
|
||||
label: 'Getting Started',
|
||||
items: [
|
||||
{ name: 'Supabase Dart Library', url: '/reference/dart/v0', items: [] },
|
||||
{ name: 'Installing', url: '/reference/dart/v0/installing', items: [] },
|
||||
{ name: 'Initializing', url: '/reference/dart/v0/initializing', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Auth',
|
||||
items: [
|
||||
{ name: 'signUp()', url: '/reference/dart/v0/auth-signup', items: [] },
|
||||
{ name: 'signIn()', url: '/reference/dart/v0/auth-signin', items: [] },
|
||||
{
|
||||
name: 'signInWithProvider()',
|
||||
url: '/reference/dart/v0/auth-signinwithprovider',
|
||||
items: [],
|
||||
},
|
||||
{ name: 'signOut()', url: '/reference/dart/v0/auth-signout', items: [] },
|
||||
{ name: 'session()', url: '/reference/dart/v0/auth-session', items: [] },
|
||||
{ name: 'user()', url: '/reference/dart/v0/auth-user', items: [] },
|
||||
{ name: 'update()', url: '/reference/dart/v0/auth-update', items: [] },
|
||||
{
|
||||
name: 'onAuthStateChange()',
|
||||
url: '/reference/dart/v0/auth-onauthstatechanged',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'Reset Password (Email)',
|
||||
url: '/reference/dart/v0/reset-password-email',
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Functions',
|
||||
items: [{ name: 'invoke()', url: '/reference/dart/v0/invoke', items: [] }],
|
||||
},
|
||||
{
|
||||
label: 'Database',
|
||||
items: [
|
||||
{ name: 'Fetch data: select()', url: '/reference/dart/v0/select', items: [] },
|
||||
{ name: 'Create data: insert()', url: '/reference/dart/v0/insert', items: [] },
|
||||
{ name: 'Modify data: update()', url: '/reference/dart/v0/update', items: [] },
|
||||
{ name: 'Upsert data: upsert()', url: '/reference/dart/v0/upsert', items: [] },
|
||||
{ name: 'Delete data: delete()', url: '/reference/dart/v0/delete', items: [] },
|
||||
{ name: 'Stored Procedures: rpc()', url: '/reference/dart/v0/rpc', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Realtime',
|
||||
items: [
|
||||
{ name: 'stream', url: '/reference/dart/v0/stream', items: [] },
|
||||
{ name: 'on().subscribe()', url: '/reference/dart/v0/subscribe', items: [] },
|
||||
{
|
||||
name: 'removeSubscription()',
|
||||
url: '/reference/dart/v0/removesubscription',
|
||||
items: [],
|
||||
},
|
||||
{ name: 'getSubscriptions()', url: '/reference/dart/v0/getsubscriptions', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Storage',
|
||||
items: [
|
||||
{ name: 'createBucket()', url: '/reference/dart/v0/storage-createbucket', items: [] },
|
||||
{ name: 'getBucket()', url: '/reference/dart/v0/storage-getbucket', items: [] },
|
||||
{ name: 'listBuckets()', url: '/reference/dart/v0/storage-listbuckets', items: [] },
|
||||
{ name: 'updateBucket()', url: '/reference/dart/v0/storage-updatebucket', items: [] },
|
||||
{ name: 'deleteBucket()', url: '/reference/dart/v0/storage-deletebucket', items: [] },
|
||||
{ name: 'emptyBucket()', url: '/reference/dart/v0/storage-emptybucket', items: [] },
|
||||
{ name: 'from.upload()', url: '/reference/dart/v0/storage-from-upload', items: [] },
|
||||
{ name: 'from.download()', url: '/reference/dart/v0/storage-from-download', items: [] },
|
||||
{ name: 'from.list()', url: '/reference/dart/v0/storage-from-list', items: [] },
|
||||
{ name: 'from.update()', url: '/reference/dart/v0/storage-from-update', items: [] },
|
||||
{ name: 'from.move()', url: '/reference/dart/v0/storage-from-move', items: [] },
|
||||
{ name: 'from.remove()', url: '/reference/dart/v0/storage-from-remove', items: [] },
|
||||
{
|
||||
name: 'from.createSignedUrl()',
|
||||
url: '/reference/dart/v0/storage-from-createsignedurl',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'from.getPublicUrl()',
|
||||
url: '/reference/dart/v0/storage-from-getpublicurl',
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Filters',
|
||||
items: [
|
||||
{ name: 'Using Filters', url: '/reference/dart/v0/using-filters', items: [] },
|
||||
{ name: 'eq()', url: '/reference/dart/v0/eq', items: [] },
|
||||
{ name: 'neq()', url: '/reference/dart/v0/neq', items: [] },
|
||||
{ name: 'gt()', url: '/reference/dart/v0/gt', items: [] },
|
||||
{ name: 'gte()', url: '/reference/dart/v0/gte', items: [] },
|
||||
{ name: 'lt()', url: '/reference/dart/v0/lt', items: [] },
|
||||
{ name: 'lte()', url: '/reference/dart/v0/lte', items: [] },
|
||||
{ name: 'like()', url: '/reference/dart/v0/like', items: [] },
|
||||
{ name: 'ilike()', url: '/reference/dart/v0/ilike', items: [] },
|
||||
{ name: 'is_()', url: '/reference/dart/v0/is_', items: [] },
|
||||
{ name: 'in_()', url: '/reference/dart/v0/in_', items: [] },
|
||||
{ name: 'contains()', url: '/reference/dart/v0/contains', items: [] },
|
||||
{ name: 'containedBy()', url: '/reference/dart/v0/containedby', items: [] },
|
||||
{ name: 'rangeGt()', url: '/reference/dart/v0/rangegt', items: [] },
|
||||
{ name: 'rangeGte()', url: '/reference/dart/v0/rangegte', items: [] },
|
||||
{ name: 'rangeLt()', url: '/reference/dart/v0/rangelt', items: [] },
|
||||
{ name: 'rangeLte()', url: '/reference/dart/v0/rangelte', items: [] },
|
||||
{ name: 'rangeAdjacent()', url: '/reference/dart/v0/rangeadjacent', items: [] },
|
||||
{ name: 'overlaps()', url: '/reference/dart/v0/overlaps', items: [] },
|
||||
{ name: 'textSearch()', url: '/reference/dart/v0/textsearch', items: [] },
|
||||
{ name: 'match()', url: '/reference/dart/v0/match', items: [] },
|
||||
{ name: 'not()', url: '/reference/dart/v0/not', items: [] },
|
||||
{ name: 'or()', url: '/reference/dart/v0/or', items: [] },
|
||||
{ name: 'filter()', url: '/reference/dart/v0/filter', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Modifiers',
|
||||
items: [
|
||||
{ name: 'Using Modifiers', url: '/reference/dart/v0/using-modifiers', items: [] },
|
||||
{ name: 'order()', url: '/reference/dart/v0/order', items: [] },
|
||||
{ name: 'limit()', url: '/reference/dart/v0/limit', items: [] },
|
||||
{ name: 'range()', url: '/reference/dart/v0/range', items: [] },
|
||||
{ name: 'single()', url: '/reference/dart/v0/single', items: [] },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export default Nav
|
||||
@@ -0,0 +1,130 @@
|
||||
const Nav = [
|
||||
{
|
||||
label: 'Getting Started',
|
||||
items: [
|
||||
{ name: 'Supabase Flutter Library', url: '/reference/dart', items: [] },
|
||||
{ name: 'Installing', url: '/reference/dart/installing', items: [] },
|
||||
{ name: 'Initializing', url: '/reference/dart/initializing', items: [] },
|
||||
{ name: 'Upgrading to supabase-flutter v1', url: '/reference/dart/upgrade-guide', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Auth',
|
||||
items: [
|
||||
{ name: 'signUp()', url: '/reference/dart/auth-signup', items: [] },
|
||||
{
|
||||
name: 'signInWithPassword()',
|
||||
url: '/reference/dart/auth-signinwithpassword',
|
||||
items: [],
|
||||
},
|
||||
{ name: 'signInWithOtp()', url: '/reference/dart/auth-signinwithotp', items: [] },
|
||||
{ name: 'signInWithOAuth()', url: '/reference/dart/auth-signinwithoauth', items: [] },
|
||||
{ name: 'signOut()', url: '/reference/dart/auth-signout', items: [] },
|
||||
{ name: 'verifyOtp()', url: '/reference/dart/auth-verifyotp', items: [] },
|
||||
{ name: 'currentSession', url: '/reference/dart/auth-currentsession', items: [] },
|
||||
{ name: 'currentUser', url: '/reference/dart/auth-currentuser', items: [] },
|
||||
{ name: 'updateUser()', url: '/reference/dart/auth-updateuser', items: [] },
|
||||
{ name: 'onAuthStateChange()', url: '/reference/dart/auth-onauthstatechange', items: [] },
|
||||
{
|
||||
name: 'resetPasswordForEmail()',
|
||||
url: '/reference/dart/auth-resetpasswordforemail',
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Functions',
|
||||
items: [{ name: 'invoke()', url: '/reference/dart/invoke', items: [] }],
|
||||
},
|
||||
{
|
||||
label: 'Database',
|
||||
items: [
|
||||
{ name: 'Fetch data: select()', url: '/reference/dart/select', items: [] },
|
||||
{ name: 'Create data: insert()', url: '/reference/dart/insert', items: [] },
|
||||
{ name: 'Modify data: update()', url: '/reference/dart/update', items: [] },
|
||||
{ name: 'Upsert data: upsert()', url: '/reference/dart/upsert', items: [] },
|
||||
{ name: 'Delete data: delete()', url: '/reference/dart/delete', items: [] },
|
||||
{ name: 'Stored Procedures: rpc()', url: '/reference/dart/rpc', items: [] },
|
||||
{
|
||||
name: 'Filters',
|
||||
url: undefined,
|
||||
items: [
|
||||
{ name: 'Using Filters', url: '/reference/dart/using-filters', items: [] },
|
||||
{ name: 'eq()', url: '/reference/dart/eq', items: [] },
|
||||
{ name: 'neq()', url: '/reference/dart/neq', items: [] },
|
||||
{ name: 'gt()', url: '/reference/dart/gt', items: [] },
|
||||
{ name: 'gte()', url: '/reference/dart/gte', items: [] },
|
||||
{ name: 'lt()', url: '/reference/dart/lt', items: [] },
|
||||
{ name: 'lte()', url: '/reference/dart/lte', items: [] },
|
||||
{ name: 'like()', url: '/reference/dart/like', items: [] },
|
||||
{ name: 'ilike()', url: '/reference/dart/ilike', items: [] },
|
||||
{ name: 'is_()', url: '/reference/dart/is_', items: [] },
|
||||
{ name: 'in_()', url: '/reference/dart/in_', items: [] },
|
||||
{ name: 'contains()', url: '/reference/dart/contains', items: [] },
|
||||
{ name: 'containedBy()', url: '/reference/dart/containedby', items: [] },
|
||||
{ name: 'rangeGt()', url: '/reference/dart/rangegt', items: [] },
|
||||
{ name: 'rangeGte()', url: '/reference/dart/rangegte', items: [] },
|
||||
{ name: 'rangeLt()', url: '/reference/dart/rangelt', items: [] },
|
||||
{ name: 'rangeLte()', url: '/reference/dart/rangelte', items: [] },
|
||||
{ name: 'rangeAdjacent()', url: '/reference/dart/rangeadjacent', items: [] },
|
||||
{ name: 'overlaps()', url: '/reference/dart/overlaps', items: [] },
|
||||
{ name: 'textSearch()', url: '/reference/dart/textsearch', items: [] },
|
||||
{ name: 'match()', url: '/reference/dart/match', items: [] },
|
||||
{ name: 'not()', url: '/reference/dart/not', items: [] },
|
||||
{ name: 'or()', url: '/reference/dart/or', items: [] },
|
||||
{ name: 'filter()', url: '/reference/dart/filter', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Modifiers',
|
||||
url: undefined,
|
||||
items: [
|
||||
{ name: 'Using Modifiers', url: '/reference/dart/using-modifiers', items: [] },
|
||||
{ name: 'order()', url: '/reference/dart/order', items: [] },
|
||||
{ name: 'limit()', url: '/reference/dart/limit', items: [] },
|
||||
{ name: 'range()', url: '/reference/dart/range', items: [] },
|
||||
{ name: 'single()', url: '/reference/dart/single', items: [] },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Realtime',
|
||||
items: [
|
||||
{ name: 'stream()', url: '/reference/dart/stream', items: [] },
|
||||
{ name: 'on().subscribe()', url: '/reference/dart/subscribe', items: [] },
|
||||
{ name: 'removeChannel()', url: '/reference/dart/removechannel', items: [] },
|
||||
{ name: 'removeAllChannels()', url: '/reference/dart/removeallchannels', items: [] },
|
||||
{ name: 'getChannels()', url: '/reference/dart/getchannels', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Storage',
|
||||
items: [
|
||||
{ name: 'createBucket()', url: '/reference/dart/storage-createbucket', items: [] },
|
||||
{ name: 'getBucket()', url: '/reference/dart/storage-getbucket', items: [] },
|
||||
{ name: 'listBuckets()', url: '/reference/dart/storage-listbuckets', items: [] },
|
||||
{ name: 'updateBucket()', url: '/reference/dart/storage-updatebucket', items: [] },
|
||||
{ name: 'deleteBucket()', url: '/reference/dart/storage-deletebucket', items: [] },
|
||||
{ name: 'emptyBucket()', url: '/reference/dart/storage-emptybucket', items: [] },
|
||||
{ name: 'from.upload()', url: '/reference/dart/storage-from-upload', items: [] },
|
||||
{ name: 'from.download()', url: '/reference/dart/storage-from-download', items: [] },
|
||||
{ name: 'from.list()', url: '/reference/dart/storage-from-list', items: [] },
|
||||
{ name: 'from.update()', url: '/reference/dart/storage-from-update', items: [] },
|
||||
{ name: 'from.move()', url: '/reference/dart/storage-from-move', items: [] },
|
||||
{ name: 'from.remove()', url: '/reference/dart/storage-from-remove', items: [] },
|
||||
{
|
||||
name: 'from.createSignedUrl()',
|
||||
url: '/reference/dart/storage-from-createsignedurl',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'from.getPublicUrl()',
|
||||
url: '/reference/dart/storage-from-getpublicurl',
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export default Nav
|
||||
@@ -0,0 +1,169 @@
|
||||
const Nav = [
|
||||
{
|
||||
label: 'Getting Started',
|
||||
items: [
|
||||
{ name: 'Supabase JavaScript Library', url: '/reference/javascript/v1', items: [] },
|
||||
{ name: 'Installing', url: '/reference/javascript/v1/installing', items: [] },
|
||||
{ name: 'Initializing', url: '/reference/javascript/v1/initializing', items: [] },
|
||||
{ name: 'Generating Types', url: '/reference/javascript/v1/generating-types', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Auth',
|
||||
items: [
|
||||
{ name: 'signUp()', url: '/reference/javascript/v1/auth-signup', items: [] },
|
||||
{ name: 'signIn()', url: '/reference/javascript/v1/auth-signin', items: [] },
|
||||
{ name: 'signOut()', url: '/reference/javascript/v1/auth-signout', items: [] },
|
||||
{ name: 'session()', url: '/reference/javascript/v1/auth-session', items: [] },
|
||||
{ name: 'user()', url: '/reference/javascript/v1/auth-user', items: [] },
|
||||
{ name: 'update()', url: '/reference/javascript/v1/auth-update', items: [] },
|
||||
{ name: 'setAuth()', url: '/reference/javascript/v1/auth-setauth', items: [] },
|
||||
{
|
||||
name: 'onAuthStateChange()',
|
||||
url: '/reference/javascript/v1/auth-onauthstatechanged',
|
||||
items: [],
|
||||
},
|
||||
{ name: 'getUser()', url: '/reference/javascript/v1/auth-api-getuser', items: [] },
|
||||
{
|
||||
name: 'resetPasswordForEmail()',
|
||||
url: '/reference/javascript/v1/auth-api-resetpasswordforemail',
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Auth (Server Only)',
|
||||
items: [
|
||||
{ name: 'listUsers()', url: '/reference/javascript/v1/auth-api-listusers', items: [] },
|
||||
{ name: 'createUser()', url: '/reference/javascript/v1/auth-api-createuser', items: [] },
|
||||
{ name: 'deleteUser()', url: '/reference/javascript/v1/auth-api-deleteuser', items: [] },
|
||||
{
|
||||
name: 'generateLink()',
|
||||
url: '/reference/javascript/v1/auth-api-generatelink',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'inviteUserByEmail()',
|
||||
url: '/reference/javascript/v1/auth-api-inviteuserbyemail',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'sendMobileOTP()',
|
||||
url: '/reference/javascript/v1/auth-api-sendmobileotp',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'updateUserById()',
|
||||
url: '/reference/javascript/v1/auth-api-updateuserbyid',
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Functions',
|
||||
items: [{ name: 'invoke()', url: '/reference/javascript/v1/invoke', items: [] }],
|
||||
},
|
||||
{
|
||||
label: 'Database',
|
||||
items: [
|
||||
{ name: 'Fetch data: select()', url: '/reference/javascript/v1/select', items: [] },
|
||||
{ name: 'Create data: insert()', url: '/reference/javascript/v1/insert', items: [] },
|
||||
{ name: 'Modify data: update()', url: '/reference/javascript/v1/update', items: [] },
|
||||
{ name: 'Upsert data: upsert()', url: '/reference/javascript/v1/upsert', items: [] },
|
||||
{ name: 'Delete data: delete()', url: '/reference/javascript/v1/delete', items: [] },
|
||||
{ name: 'Postgres functions: rpc()', url: '/reference/javascript/v1/rpc', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Realtime',
|
||||
items: [
|
||||
{ name: 'on().subscribe()', url: '/reference/javascript/v1/subscribe', items: [] },
|
||||
{
|
||||
name: 'removeSubscription()',
|
||||
url: '/reference/javascript/v1/removesubscription',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'removeAllSubscriptions()',
|
||||
url: '/reference/javascript/v1/removeallsubscriptions',
|
||||
items: [],
|
||||
},
|
||||
{ name: 'getSubscriptions()', url: '/reference/javascript/v1/getsubscriptions', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Storage',
|
||||
items: [
|
||||
{ name: 'createBucket()', url: '/reference/javascript/v1/storage-createbucket', items: [] },
|
||||
{ name: 'getBucket()', url: '/reference/javascript/v1/storage-getbucket', items: [] },
|
||||
{ name: 'listBuckets()', url: '/reference/javascript/v1/storage-listbuckets', items: [] },
|
||||
{ name: 'updateBucket()', url: '/reference/javascript/v1/storage-updatebucket', items: [] },
|
||||
{ name: 'deleteBucket()', url: '/reference/javascript/v1/storage-deletebucket', items: [] },
|
||||
{ name: 'emptyBucket()', url: '/reference/javascript/v1/storage-emptybucket', items: [] },
|
||||
{ name: 'from.upload()', url: '/reference/javascript/v1/storage-from-upload', items: [] },
|
||||
{ name: 'from.download()', url: '/reference/javascript/v1/storage-from-download', items: [] },
|
||||
{ name: 'from.list()', url: '/reference/javascript/v1/storage-from-list', items: [] },
|
||||
{ name: 'from.update()', url: '/reference/javascript/v1/storage-from-update', items: [] },
|
||||
{ name: 'from.move()', url: '/reference/javascript/v1/storage-from-move', items: [] },
|
||||
{ name: 'from.copy()', url: '/reference/javascript/v1/storage-from-copy', items: [] },
|
||||
{ name: 'from.remove()', url: '/reference/javascript/v1/storage-from-remove', items: [] },
|
||||
{
|
||||
name: 'from.createSignedUrl()',
|
||||
url: '/reference/javascript/v1/storage-from-createsignedurl',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'from.createSignedUrls()',
|
||||
url: '/reference/javascript/v1/storage-from-createsignedurls',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'from.getPublicUrl()',
|
||||
url: '/reference/javascript/v1/storage-from-getpublicurl',
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Filters',
|
||||
items: [
|
||||
{ name: 'Using Filters', url: '/reference/javascript/v1/using-filters', items: [] },
|
||||
{ name: 'eq()', url: '/reference/javascript/v1/eq', items: [] },
|
||||
{ name: 'neq()', url: '/reference/javascript/v1/neq', items: [] },
|
||||
{ name: 'gt()', url: '/reference/javascript/v1/gt', items: [] },
|
||||
{ name: 'gte()', url: '/reference/javascript/v1/gte', items: [] },
|
||||
{ name: 'lt()', url: '/reference/javascript/v1/lt', items: [] },
|
||||
{ name: 'lte()', url: '/reference/javascript/v1/lte', items: [] },
|
||||
{ name: 'like()', url: '/reference/javascript/v1/like', items: [] },
|
||||
{ name: 'ilike()', url: '/reference/javascript/v1/ilike', items: [] },
|
||||
{ name: 'is()', url: '/reference/javascript/v1/is', items: [] },
|
||||
{ name: 'in()', url: '/reference/javascript/v1/in', items: [] },
|
||||
{ name: 'contains()', url: '/reference/javascript/v1/contains', items: [] },
|
||||
{ name: 'containedBy()', url: '/reference/javascript/v1/containedby', items: [] },
|
||||
{ name: 'rangeGt()', url: '/reference/javascript/v1/rangegt', items: [] },
|
||||
{ name: 'rangeGte()', url: '/reference/javascript/v1/rangegte', items: [] },
|
||||
{ name: 'rangeLt()', url: '/reference/javascript/v1/rangelt', items: [] },
|
||||
{ name: 'rangeLte()', url: '/reference/javascript/v1/rangelte', items: [] },
|
||||
{ name: 'rangeAdjacent()', url: '/reference/javascript/v1/rangeadjacent', items: [] },
|
||||
{ name: 'overlaps()', url: '/reference/javascript/v1/overlaps', items: [] },
|
||||
{ name: 'textSearch()', url: '/reference/javascript/v1/textsearch', items: [] },
|
||||
{ name: 'match()', url: '/reference/javascript/v1/match', items: [] },
|
||||
{ name: 'not()', url: '/reference/javascript/v1/not', items: [] },
|
||||
{ name: 'or()', url: '/reference/javascript/v1/or', items: [] },
|
||||
{ name: 'filter()', url: '/reference/javascript/v1/filter', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Modifiers',
|
||||
items: [
|
||||
{ name: 'Using Modifiers', url: '/reference/javascript/v1/using-modifiers', items: [] },
|
||||
{ name: 'order()', url: '/reference/javascript/v1/order', items: [] },
|
||||
{ name: 'limit()', url: '/reference/javascript/v1/limit', items: [] },
|
||||
{ name: 'range()', url: '/reference/javascript/v1/range', items: [] },
|
||||
{ name: 'single()', url: '/reference/javascript/v1/single', items: [] },
|
||||
{ name: 'maybeSingle()', url: '/reference/javascript/v1/maybesingle', items: [] },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export default Nav
|
||||
@@ -0,0 +1,172 @@
|
||||
const Nav = [
|
||||
{
|
||||
label: 'Getting Started',
|
||||
items: [
|
||||
{ name: 'Supabase JavaScript Library', url: '/reference/javascript', items: [] },
|
||||
{ name: 'Installing', url: '/reference/javascript/installing', items: [] },
|
||||
{ name: 'Initializing', url: '/reference/javascript/initializing', items: [] },
|
||||
{ name: 'Typescript Support', url: '/reference/javascript/typescript-support', items: [] },
|
||||
{ name: 'Release Notes', url: '/reference/javascript/release-notes', items: [] },
|
||||
{
|
||||
name: 'Upgrading to supabase-js v2',
|
||||
url: '/reference/javascript/upgrade-guide',
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Auth',
|
||||
items: [
|
||||
{ name: 'signUp()', url: '/reference/javascript/auth-signup', items: [] },
|
||||
{
|
||||
name: 'signInWithPassword()',
|
||||
url: '/reference/javascript/auth-signinwithpassword',
|
||||
items: [],
|
||||
},
|
||||
{ name: 'signInWithOtp()', url: '/reference/javascript/auth-signinwithotp', items: [] },
|
||||
{ name: 'signInWithOAuth()', url: '/reference/javascript/auth-signinwithoauth', items: [] },
|
||||
{ name: 'signOut()', url: '/reference/javascript/auth-signout', items: [] },
|
||||
{ name: 'verifyOtp()', url: '/reference/javascript/auth-verifyotp', items: [] },
|
||||
{ name: 'getSession()', url: '/reference/javascript/auth-getsession', items: [] },
|
||||
{ name: 'refreshSession()', url: '/reference/javascript/auth-refreshsession', items: [] },
|
||||
{ name: 'getUser()', url: '/reference/javascript/auth-getuser', items: [] },
|
||||
{ name: 'updateUser()', url: '/reference/javascript/auth-updateuser', items: [] },
|
||||
{ name: 'setSession()', url: '/reference/javascript/auth-setsession', items: [] },
|
||||
{
|
||||
name: 'onAuthStateChange()',
|
||||
url: '/reference/javascript/auth-onauthstatechange',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'resetPasswordForEmail()',
|
||||
url: '/reference/javascript/auth-resetpasswordforemail',
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Auth (Server Only)',
|
||||
items: [
|
||||
{ name: 'Overview', url: '/reference/javascript/supabase-auth-admin-api', items: [] },
|
||||
{ name: 'listUsers()', url: '/reference/javascript/auth-admin-listusers', items: [] },
|
||||
{ name: 'createUser()', url: '/reference/javascript/auth-admin-createuser', items: [] },
|
||||
{ name: 'deleteUser()', url: '/reference/javascript/auth-admin-deleteuser', items: [] },
|
||||
{ name: 'generateLink()', url: '/reference/javascript/auth-admin-generatelink', items: [] },
|
||||
{
|
||||
name: 'inviteUserByEmail()',
|
||||
url: '/reference/javascript/auth-admin-inviteuserbyemail',
|
||||
items: [],
|
||||
},
|
||||
{ name: 'getUserById()', url: '/reference/javascript/auth-admin-getuserbyid', items: [] },
|
||||
{
|
||||
name: 'updateUserById()',
|
||||
url: '/reference/javascript/auth-admin-updateuserbyid',
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Database',
|
||||
items: [
|
||||
{ name: 'Fetch data: select()', url: '/reference/javascript/select', items: [] },
|
||||
{ name: 'Create data: insert()', url: '/reference/javascript/insert', items: [] },
|
||||
{ name: 'Modify data: update()', url: '/reference/javascript/update', items: [] },
|
||||
{ name: 'Upsert data: upsert()', url: '/reference/javascript/upsert', items: [] },
|
||||
{ name: 'Delete data: delete()', url: '/reference/javascript/delete', items: [] },
|
||||
{ name: 'Postgres functions: rpc()', url: '/reference/javascript/rpc', items: [] },
|
||||
{
|
||||
name: 'Filters',
|
||||
url: undefined,
|
||||
items: [
|
||||
{ name: 'Using Filters', url: '/reference/javascript/using-filters', items: [] },
|
||||
{ name: 'eq()', url: '/reference/javascript/eq', items: [] },
|
||||
{ name: 'neq()', url: '/reference/javascript/neq', items: [] },
|
||||
{ name: 'gt()', url: '/reference/javascript/gt', items: [] },
|
||||
{ name: 'gte()', url: '/reference/javascript/gte', items: [] },
|
||||
{ name: 'lt()', url: '/reference/javascript/lt', items: [] },
|
||||
{ name: 'lte()', url: '/reference/javascript/lte', items: [] },
|
||||
{ name: 'like()', url: '/reference/javascript/like', items: [] },
|
||||
{ name: 'ilike()', url: '/reference/javascript/ilike', items: [] },
|
||||
{ name: 'is()', url: '/reference/javascript/is', items: [] },
|
||||
{ name: 'in()', url: '/reference/javascript/in', items: [] },
|
||||
{ name: 'contains()', url: '/reference/javascript/contains', items: [] },
|
||||
{ name: 'containedBy()', url: '/reference/javascript/containedby', items: [] },
|
||||
{ name: 'rangeGt()', url: '/reference/javascript/rangegt', items: [] },
|
||||
{ name: 'rangeGte()', url: '/reference/javascript/rangegte', items: [] },
|
||||
{ name: 'rangeLt()', url: '/reference/javascript/rangelt', items: [] },
|
||||
{ name: 'rangeLte()', url: '/reference/javascript/rangelte', items: [] },
|
||||
{ name: 'rangeAdjacent()', url: '/reference/javascript/rangeadjacent', items: [] },
|
||||
{ name: 'overlaps()', url: '/reference/javascript/overlaps', items: [] },
|
||||
{ name: 'textSearch()', url: '/reference/javascript/textsearch', items: [] },
|
||||
{ name: 'match()', url: '/reference/javascript/match', items: [] },
|
||||
{ name: 'not()', url: '/reference/javascript/not', items: [] },
|
||||
{ name: 'or()', url: '/reference/javascript/or', items: [] },
|
||||
{ name: 'filter()', url: '/reference/javascript/filter', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Modifiers',
|
||||
url: undefined,
|
||||
items: [
|
||||
{ name: 'Using Modifiers', url: '/reference/javascript/using-modifiers', items: [] },
|
||||
{ name: 'select()', url: '/reference/javascript/db-modifiers-select', items: [] },
|
||||
{ name: 'order()', url: '/reference/javascript/order', items: [] },
|
||||
{ name: 'limit()', url: '/reference/javascript/limit', items: [] },
|
||||
{ name: 'range()', url: '/reference/javascript/range', items: [] },
|
||||
{ name: 'abortSignal()', url: '/reference/javascript/db-abortsignal', items: [] },
|
||||
{ name: 'single()', url: '/reference/javascript/single', items: [] },
|
||||
{ name: 'maybeSingle()', url: '/reference/javascript/maybesingle', items: [] },
|
||||
{ name: 'csv()', url: '/reference/javascript/db-csv', items: [] },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Functions',
|
||||
items: [{ name: 'invoke()', url: '/reference/javascript/invoke', items: [] }],
|
||||
},
|
||||
{
|
||||
label: 'Realtime',
|
||||
items: [
|
||||
{ name: 'on().subscribe()', url: '/reference/javascript/subscribe', items: [] },
|
||||
{ name: 'removeChannel()', url: '/reference/javascript/removechannel', items: [] },
|
||||
{ name: 'removeAllChannels()', url: '/reference/javascript/removeallchannels', items: [] },
|
||||
{ name: 'getChannels()', url: '/reference/javascript/getchannels', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Storage',
|
||||
items: [
|
||||
{ name: 'createBucket()', url: '/reference/javascript/storage-createbucket', items: [] },
|
||||
{ name: 'getBucket()', url: '/reference/javascript/storage-getbucket', items: [] },
|
||||
{ name: 'listBuckets()', url: '/reference/javascript/storage-listbuckets', items: [] },
|
||||
{ name: 'updateBucket()', url: '/reference/javascript/storage-updatebucket', items: [] },
|
||||
{ name: 'deleteBucket()', url: '/reference/javascript/storage-deletebucket', items: [] },
|
||||
{ name: 'emptyBucket()', url: '/reference/javascript/storage-emptybucket', items: [] },
|
||||
{ name: 'from.upload()', url: '/reference/javascript/storage-from-upload', items: [] },
|
||||
{ name: 'from.download()', url: '/reference/javascript/storage-from-download', items: [] },
|
||||
{ name: 'from.list()', url: '/reference/javascript/storage-from-list', items: [] },
|
||||
{ name: 'from.update()', url: '/reference/javascript/storage-from-update', items: [] },
|
||||
{ name: 'from.move()', url: '/reference/javascript/storage-from-move', items: [] },
|
||||
{ name: 'from.copy()', url: '/reference/javascript/storage-from-copy', items: [] },
|
||||
{ name: 'from.remove()', url: '/reference/javascript/storage-from-remove', items: [] },
|
||||
{
|
||||
name: 'from.createSignedUrl()',
|
||||
url: '/reference/javascript/storage-from-createsignedurl',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'from.createSignedUrls()',
|
||||
url: '/reference/javascript/storage-from-createsignedurls',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'from.getPublicUrl()',
|
||||
url: '/reference/javascript/storage-from-getpublicurl',
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export default Nav
|
||||
@@ -0,0 +1,28 @@
|
||||
const nonGeneratedReferencePages = [
|
||||
'docs/reference/javascript',
|
||||
'docs/reference/javascript/installing',
|
||||
'docs/reference/javascript/release-notes',
|
||||
'docs/reference/javascript/upgrade-guide',
|
||||
'docs/reference/javascript/typescript-support',
|
||||
'docs/reference/javascript/v1',
|
||||
'docs/reference/javascript/v1/installing',
|
||||
'docs/reference/javascript/v1/initializing',
|
||||
'docs/reference/javascript/v1/generating-types',
|
||||
'docs/reference/dart',
|
||||
'docs/reference/dart/installing',
|
||||
'docs/reference/dart/initializing',
|
||||
'docs/reference/dart/upgrade-guide',
|
||||
'docs/reference/dart/v0',
|
||||
'docs/reference/api',
|
||||
'docs/reference/api/release-notes',
|
||||
'docs/reference/cli',
|
||||
'docs/reference/cli/release-notes',
|
||||
'docs/reference/auth',
|
||||
'docs/reference/auth/release-notes',
|
||||
'docs/reference/storage',
|
||||
'docs/reference/storage/release-notes',
|
||||
'docs/reference/realtime',
|
||||
'docs/reference/realtime/release-notes',
|
||||
]
|
||||
|
||||
export default nonGeneratedReferencePages
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,156 @@
|
||||
---
|
||||
id: architecture
|
||||
title: Architecture
|
||||
description: 'Supabase design and architecture'
|
||||
---
|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
### 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/master/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.
|
||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,88 @@
|
||||
---
|
||||
id: sla
|
||||
title: Service Level Agreement
|
||||
---
|
||||
|
||||
The following Service Level Agreement, which is incorporated into and forms part of the Subscription Agreement between Supabase, Inc. ("Supabase") and Customer (the "Agreement"), will apply to the Services for Enterprise Customers specified in an Order Form during the applicable Subscription Term:
|
||||
|
||||
## Platform
|
||||
|
||||
### 1. Uptime Commitment
|
||||
|
||||
Supabase will provide Actual Availability for at least ninety-nine and nine tenths percent (99.9%) of the total time in each calendar month during the Subscription Term, as measured by Supabase (the **"Uptime Commitment"**).
|
||||
|
||||
### 2. Service Credits
|
||||
|
||||
If the Uptime Commitment is not met during any particular calendar month during the Subscription Term, then Customer will be eligible for a service credit ("Service Credit"), provided that Customer reports to Supabase such failure to meet the Uptime Commitment and requests such Service Credit in accordance with this Exhibit. The amount of any Service Credit due hereunder shall be calculated as follows:
|
||||
X \* Y, where X = the total fees due from Customer to Supabase for the affected Services for the relevant calendar month (regardless of when billed or payable), and Y = the Credit Percentage corresponding with the Actual Availability provided (as a percentage of total time) for the relevant calendar month, as set forth in the table below.
|
||||
|
||||
| Actual Availability | Credit Percentage |
|
||||
| -------------------------------------------------- | ----------------- |
|
||||
| Less than 99.9% but greater than or equal to 99.0% | 10% |
|
||||
| Less than 99.0% but greater than or equal to 98.0% | 15% |
|
||||
| Less than 98.0% but greater than or equal to 96.0% | 20% |
|
||||
| Less than 96.0% | 30% |
|
||||
|
||||
### 3. Credit Requests and Payment
|
||||
|
||||
To request a Service Credit, Customer must send an email to Supabase at support@supabase.io within thirty (30) days of the end of the month in which the Uptime Commitment was not met. Customer must include either its account ID or registered email address, and the previously reported dates and times that there was no Service Availability. If Supabase confirms that Customer is eligible for a Service Credit, Supabase will issue a credit to Customer’s account within thirty (30) days. Service Credits are not refunds, cannot be exchanged into a cash amount, and may only be used against future billing charges. Except as set forth in Section 4 below, the Service Credits shall be Customer’s sole and exclusive remedy, and Supabase’s sole and exclusive liability, for any failure by Supabase to meet the Uptime Commitment.
|
||||
|
||||
### 4. Definitions
|
||||
|
||||
All capitalized words used but not defined in this Service Level Agreement have the meaning set forth in the Agreement.
|
||||
|
||||
#### 4.1 Scheduled Availability
|
||||
|
||||
"Scheduled Availability" means the time, in minutes, that the applicable Services are generally accessible and available to Customer’s Permitted Users.
|
||||
|
||||
#### 4.2 Unscheduled Downtime
|
||||
|
||||
"Unscheduled Downtime" means the time, in minutes, that the applicable Services are not generally accessible and available to Customer’s Permitted Users, excluding inaccessibility or unavailability due to Customer’s or Permitted Users’ acts or omissions, force majeure events, scheduled maintenance disclosed with at least 24 hours’ notice by email, hacking or virus attacks, or reasonable emergency maintenance.
|
||||
|
||||
#### 4.3 Actual Availability
|
||||
|
||||
"Actual Availability" means Scheduled Availability less Unscheduled Downtime.
|
||||
|
||||
## Support
|
||||
|
||||
Supabase Support Service Level Agreements.
|
||||
|
||||
### 1. Urgent
|
||||
|
||||
**Critical Issue**
|
||||
|
||||
Defect resulting in full or partial system outage or a condition that makes Supabase unusable
|
||||
or unavailable in production for all of Customer’s Users.
|
||||
|
||||
### 2. High
|
||||
|
||||
**Significant Business Disruption**
|
||||
|
||||
Issue resulting in a situation meaning major functionality is impacted and
|
||||
significant performance degradation is experienced. Issue impacts significant proportion of user base and / or major
|
||||
Supabase functionality.
|
||||
|
||||
### 3. Normal
|
||||
|
||||
**Minor Feature or Functional Issue / General Question**
|
||||
|
||||
Issue results in a component of Supabase not
|
||||
performing as expected or documented. An inquiry by a Customer representative regarding a general technical issue
|
||||
or general question.
|
||||
|
||||
### 4. Low
|
||||
|
||||
**Minor Issue / Feature Request**
|
||||
|
||||
An Information request about Supabase or feature request.
|
||||
|
||||
## Target response times
|
||||
|
||||
| Severity Level | Standard | Priority | Priority Plus |
|
||||
| -------------- | ------------------------------------- | ------------------------------------- | -------------------------------------- |
|
||||
| 1. Urgent | 1 business hour<br />24/7 × 365 | 1 business hour<br />24/7 × 365 | 1 business hour<br />24/7 × 365 |
|
||||
| 2. High | 4 business hours<br />Monday - Friday | 2 business hours<br />Monday - Friday | 2 business hours<br />24/7 × 365 |
|
||||
| 3. Normal | 1 business day<br />Monday - Friday | 1 business day<br />Monday - Friday | 12 business hours<br />Monday - Friday |
|
||||
| 4. Low | 2 business days<br />Monday - Friday | 2 business days<br />Monday - Friday | 1 business day<br />Monday - Friday |
|
||||
|
||||
Business hours are from 6am to 6pm (local time), except where otherwise stated.
|
||||
File renamed without changes.
@@ -0,0 +1,38 @@
|
||||
---
|
||||
id: faq
|
||||
title: FAQs
|
||||
description: 'Most frequently asked questions regarding Supabase'
|
||||
---
|
||||
|
||||
### Where do I find support?
|
||||
|
||||
Choose the support channel relevant for your situation here: [supabase.com/support](https://supabase.com/support)
|
||||
|
||||
### How much does it cost?
|
||||
|
||||
Self-hosting Supabase is free. If you wish to use our cloud-platform, we provide [simple, predictable pricing](https://supabase.com/pricing).
|
||||
|
||||
### How do I host Supabase?
|
||||
|
||||
You can use the docker-compose script [here](https://github.com/supabase/supabase/tree/master/docker), and find detailed instructions [here](/docs/guides/hosting/overview).
|
||||
|
||||
Supabase is an amalgamation of open source tools. Some of these tools are made by Supabase (like our [Realtime Server](https://github.com/supabase/realtime)), some we support directly (like [PostgREST](http://postgrest.org/en/v7.0.0/)), and some are third-party tools (like [KonSupabase is an amalgamation open sourceg](https://github.com/Kong/kong)).
|
||||
|
||||
All of the tools we use in Supabase are MIT, Apache 2.0, or PostgreSQL licensed. This is one of the requirements to be considered for the Supabase stack.
|
||||
|
||||
### How can you be a Firebase alternative if you're built with a relational database?
|
||||
|
||||
We started Supabase because we love the functionality of Firebase, but we personally experienced the scaling issues that many others experienced. We chose Postgres because it's well-trusted, with phenomenal scalability.
|
||||
|
||||
Our goal is to make Postgres as easy to use as Firebase, so that you no longer have to choose between usability and scalability.
|
||||
We're sure that once you start using Postgres, you'll love it more than any other database.
|
||||
|
||||
### Do you support `[some other database]`?
|
||||
|
||||
We only support PostgreSQL. It's unlikely we'll ever move away from Postgres; however, you can [vote on a new database](https://github.com/supabase/supabase/discussions/6) if you want us to start development.
|
||||
|
||||
### Do you have a library for `[some other language]`?
|
||||
|
||||
We officially support [JavaScript](/docs/reference/javascript/installing) and [Flutter](/docs/reference/dart/installing).
|
||||
|
||||
You can find community-supported libraries in our [GitHub Community](https://github.com/supabase-community), and you can also help us to identify the most popular languages by [voting for a new client library](https://github.com/supabase/supabase/discussions/5).
|
||||
Executable
+172
@@ -0,0 +1,172 @@
|
||||
---
|
||||
id: features
|
||||
title: Features
|
||||
description: 'Supabase features'
|
||||
---
|
||||
|
||||
This is a non-exhaustive list of features that Supabase provides for every project.
|
||||
|
||||
## Database
|
||||
|
||||
### Postgres Database
|
||||
|
||||
Every project is a full Postgres database. [Docs](/docs/guides/database).
|
||||
|
||||
### Database Extensions
|
||||
|
||||
Every database comes with a full set of Postgres extensions. [Docs](/docs/guides/database/extensions).
|
||||
|
||||
### Database Functions
|
||||
|
||||
Create custom database functions which you can call from the browser. [Docs](/docs/guides/database/functions).
|
||||
|
||||
### Database Triggers
|
||||
|
||||
Attach triggers to your tables to handle database changes. [Docs](/docs/guides/auth/managing-user-data#using-triggers).
|
||||
|
||||
### Database Webhooks
|
||||
|
||||
Send database changes to any external service using Webhooks. [Link](https://supabase.com/blog/supabase-functions-updates#database-webhooks-alpha).
|
||||
|
||||
### Database Backups
|
||||
|
||||
Projects are backed up daily with the option to upgrade to Point in Time recovery.
|
||||
|
||||
### Search
|
||||
|
||||
Build search functionality using Postgres Full Text Search. [Docs](/docs/guides/database/full-text-search).
|
||||
|
||||
### Secrets and encryption
|
||||
|
||||
Encrypt sensitive data and store secrets using our Postgres extension, Supabase Vault. [Link](https://supabase.com/blog/supabase-vault).
|
||||
|
||||
### Database migrations
|
||||
|
||||
Develop locally and push your changes to your production database using migrations. [Docs](/docs/guides/cli/local-development#database-migrations)
|
||||
|
||||
<br />
|
||||
|
||||
## Auth
|
||||
|
||||
### Email & Password Logins
|
||||
|
||||
Build email logins for your application or website. [Docs](/docs/guides/auth/auth-email).
|
||||
|
||||
### Magic Links
|
||||
|
||||
Build passwordless logins for your application or website.[Docs](/docs/guides/auth/auth-magic-link).
|
||||
|
||||
### Social Logins
|
||||
|
||||
Provide social logins - everything from Apple, to GitHub, to Slack. [Docs](/docs/guides/auth/auth-apple).
|
||||
|
||||
### Phone Logins
|
||||
|
||||
Provide phone logins using a 3rd-party SMS provider. [Docs](/docs/guides/auth/auth-twilio).
|
||||
|
||||
### Row Level Security
|
||||
|
||||
Control the data each user can access with Postgres Policies. [Docs](/docs/guides/auth/row-level-security).
|
||||
|
||||
### Serverside Auth Helpers
|
||||
|
||||
Helpers for implementing user authentication in popular frameworks like [Next.js](/docs/guides/auth/auth-helpers/nextjs) and [SvelteKit](/docs/guides/auth/auth-helpers/sveltekit)
|
||||
|
||||
### Auth UI Kit
|
||||
|
||||
Build login and registration pages with custom themes. [Docs](/docs/guides/auth/auth-helpers/auth-ui).
|
||||
|
||||
<br />
|
||||
|
||||
## APIs & Client libraries
|
||||
|
||||
### Auto-generated REST API
|
||||
|
||||
RESTful APIs are autogenerated from your database, without a single line of code. [Docs](/docs/guides/api#rest-api-overview).
|
||||
|
||||
### Auto-generated GraphQL API
|
||||
|
||||
Fast GraphQL APIs using our custom Postgres GraphQL extension. [Docs](/docs/guides/api#graphql-api-overview).
|
||||
|
||||
### Realtime Database changes
|
||||
|
||||
Receive your database changes through websockets. [Docs](/docs/guides/realtime/postgres-cdc).
|
||||
|
||||
### User Broadcasting
|
||||
|
||||
Send messages between connected users through websockets. [Docs](/docs/guides/realtime#broadcast).
|
||||
|
||||
### User Presence
|
||||
|
||||
Synchronize shared state across your users, including online status and typing indicators. [Docs](/docs/guides/realtime#presence).
|
||||
|
||||
### Client libraries
|
||||
|
||||
Official client libraries for [JavaScript](/docs/reference/javascript/) and [Dart](/docs/reference/dart).
|
||||
Unofficial libraries [supported by the community](https://github.com/supabase-community#client-libraries).
|
||||
|
||||
<br />
|
||||
|
||||
## File Storage
|
||||
|
||||
### Large File storage
|
||||
|
||||
Supabase Storage makes it simple to store and serve large files. [Docs](/docs/guides/storage).
|
||||
|
||||
### Storage CDN
|
||||
|
||||
Cache large files using the Supabase CDN. [Docs](/docs/guides/storage-cdn).
|
||||
|
||||
<br />
|
||||
|
||||
## Edge Functions
|
||||
|
||||
### Deno Edge Functions
|
||||
|
||||
Globally distributed TypeScript functions to execute custom business logic. [Docs](/docs/guides/functions).
|
||||
|
||||
<br />
|
||||
|
||||
## Project Managment
|
||||
|
||||
### CLI
|
||||
|
||||
Use our CLI to develop your project locally and deploy to the Supabase Platform. [Docs](/docs/reference/cli).
|
||||
|
||||
### Management API
|
||||
|
||||
Manage your projects programmatically. [Docs](/docs/reference/api).
|
||||
|
||||
<br />
|
||||
|
||||
## Feature Status
|
||||
|
||||
Both Postgres and the Supabase Platform are production-ready. Some tools we offer on top of Postgres are still under development.
|
||||
|
||||
| Product | Feature | Stage |
|
||||
| -------------------------- | ---------------------- | ------- |
|
||||
| Database | Postgres | `GA` |
|
||||
| Database | Triggers | `GA` |
|
||||
| Database | Functions | `GA` |
|
||||
| Database | Extensions | `GA` |
|
||||
| Database | Full Text Search | `GA` |
|
||||
| Database | Webhooks | `alpha` |
|
||||
| Database | Point-in-Time Recovery | `alpha` |
|
||||
| Database | Vault | `alpha` |
|
||||
| Studio | | `GA` |
|
||||
| Realtime | Postgres CDC | `beta` |
|
||||
| Realtime | Broadcast | `alpha` |
|
||||
| Realtime | Presence | `alpha` |
|
||||
| Storage | Backend (S3) | `GA` |
|
||||
| Storage | API | `beta` |
|
||||
| Storage | CDN | `beta` |
|
||||
| Edge Functions | | `beta` |
|
||||
| Auth | OAuth Providers | `beta` |
|
||||
| Auth | Passwordless | `beta` |
|
||||
| Auth | Next.js Auth Helpers | `alpha` |
|
||||
| Auth | SvelteKit Auth Helpers | `alpha` |
|
||||
| Auth | Remix Auth Helpers | `alpha` |
|
||||
| Management API | | `beta` |
|
||||
| CLI | | `beta` |
|
||||
| Client Library: JavaScript | | `GA` |
|
||||
| Client Library: Dart | | `beta` |
|
||||
@@ -0,0 +1,375 @@
|
||||
---
|
||||
id: api
|
||||
title: Serverless APIs
|
||||
description: Auto-generating and Realtime APIs.
|
||||
sidebar_label: Overview
|
||||
---
|
||||
|
||||
Supabase auto-generates three types of API directly from your database schema.
|
||||
|
||||
- REST - interact with your database through a restful interface.
|
||||
- Realtime - listen to database changes.
|
||||
- GraphQL - [in beta](https://supabase.com/blog/pg-graphql).
|
||||
|
||||
The APIs are:
|
||||
|
||||
- **Instant and auto-generated.** <br />As you update your database the changes are immediately accessible through your API.
|
||||
- **Self documenting.** <br />Supabase generates documentation in the Dashboard which updates as you make database changes.
|
||||
- **Secure.** <br />The API is configured to work with PostgreSQL's Row Level Security, provisioned behind an API gateway with key-auth enabled.
|
||||
- **Fast.** <br />Our benchmarks for basic reads are more than 300% faster than Firebase. The API is a very thin layer on top of Postgres, which does most of the heavy lifting.
|
||||
- **Scalable.** <br />The API can serve thousands of simultaneous requests, and works well for Serverless workloads.
|
||||
|
||||
## REST API {#rest-api-overview}
|
||||
|
||||
Supabase provides a RESTful API using [PostgREST](https://postgrest.org/). This is a very thin API layer on top of Postgres.
|
||||
It provides everything you need from a CRUD API:
|
||||
|
||||
- Basic CRUD operations
|
||||
- Deeply nested joins, allowing you to fetch data from multiple tables in a single fetch
|
||||
- Works with Postgres Views
|
||||
- Works with Postgres Functions
|
||||
- Works with the Postgres security model - including Row Level Security, Roles, and Grants.
|
||||
|
||||
<div className="video-container">
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/rPAJJFdtPw0"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
## GraphQL API {#graphql-api-overview}
|
||||
|
||||
<Admonition type='note'>
|
||||
|
||||
GraphQL is in Beta, and may have breaking changes. It is only available on self-hosted setups and Supabase projects created after 28th March 2022.
|
||||
|
||||
</Admonition>
|
||||
|
||||
GraphQL in Supabase works through [pg_graphql](https://supabase.com/blog/pg-graphql), an open source PostgreSQL extension for GraphQL.
|
||||
|
||||
## Realtime API {#realtime-api-overview}
|
||||
|
||||
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.
|
||||
|
||||
## Getting started
|
||||
|
||||
All APIs are auto-created from Database tables. After you have added tables or functions to your database, you can use the APIs provided.
|
||||
|
||||
### Creating API Routes
|
||||
|
||||
API routes are automatically created when you create Postgres Tables, Views, or Functions.
|
||||
|
||||
Let's create our first
|
||||
API route by creating a table called `todos` to store tasks.
|
||||
This creates a corresponding route `todos` which can accept `GET`, `POST`, `PATCH`, & `DELETE` requests.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="dashboard"
|
||||
>
|
||||
<TabPanel id="dashboard" label="Dashboard">
|
||||
|
||||
1. Go to the [Table editor](https://app.supabase.com/project/_/editor) page in the Dashboard.
|
||||
1. Click **New Table** and create a table with the name `todos`.
|
||||
1. Click **Save**.
|
||||
1. Click **New Column** and create a column with the name `task` and type `text`.
|
||||
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 />
|
||||
</video>
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="sql" label="SQL">
|
||||
|
||||
```sql
|
||||
-- Create a table called "todos" with a column to store tasks.
|
||||
|
||||
create table todos (
|
||||
id bigint generated by default as identity primary key,
|
||||
task text check (char_length(task) > 3)
|
||||
);
|
||||
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### API URL and Keys
|
||||
|
||||
Every Supabase project has a unique API URL. Your API is secured behind an API gateway which requires an API Key for every request.
|
||||
|
||||
1. Go to the [Settings](https://app.supabase.com/project/_/settings/general) 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>
|
||||
|
||||
The REST API and the GraphQL API are both accessible through this URL:
|
||||
|
||||
- REST: `https://<project_ref>.supabase.co/rest/v1`
|
||||
- GraphQL: `https://<project_ref>.supabase.co/graphql/v1`
|
||||
|
||||
Both of these routes require the `anon` key to be passed through an `apikey` header.
|
||||
|
||||
#### API Keys
|
||||
|
||||
You are provided with two keys:
|
||||
|
||||
- an `anon` key, which is safe to be used in a browser context.
|
||||
- a `service_role` key, which should only be used on a server. This key can bypass Row Level Security. NEVER use this key in a browser.
|
||||
|
||||
### Accessing the docs in the Dashboard
|
||||
|
||||
#### REST API {#rest-api-dashboard-docs}
|
||||
|
||||
Supabase generates documentation in the [Dashboard](https://app.supabase.com) which updates as you make database changes.
|
||||
Let's view the documentation for a `countries` table which we created in our database.
|
||||
|
||||
1. Go to the [API](https://app.supabase.com/project/_/api) page in the Dashboard.
|
||||
2. Find the `countries` table under **Tables and Views** in the sidebar.
|
||||
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 />
|
||||
</video>
|
||||
|
||||
#### GraphQL
|
||||
|
||||
The GraphQL Endpoint that we provide (`https://<project_ref>.supabase.co/graphql/v1`) is compatible with any GraphiQL implementation that can pass an `apikey` header.
|
||||
Some suggested applications:
|
||||
|
||||
- [paw.cloud](https://paw.cloud)
|
||||
- [insomnia.rest](https://insomnia.rest)
|
||||
- [postman.com/graphql](https://www.postman.com/graphql/)
|
||||
- Self-hosted GraphiQL: GraphiQL can be served through a simple HTML file. See [this discussion](https://github.com/supabase/supabase/discussions/6144) for more details.
|
||||
|
||||
## Using the API
|
||||
|
||||
### REST API
|
||||
|
||||
You can interact with your API directly via HTTP requests, or you can use the client libraries which we provide.
|
||||
|
||||
Let's see how to make a request to the `todos` table which we created in the first step,
|
||||
using the API URL (`SUPABASE_URL`) and Key (`SUPABASE_ANON_KEY`) we provided:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="javascript"
|
||||
>
|
||||
<TabPanel id="javascript" label="Javascript">
|
||||
|
||||
```javascript
|
||||
// Initialize the JS client
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
|
||||
|
||||
// Make a request
|
||||
const { data: todos, error } = await supabase.from('todos').select('*')
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
# Append /rest/v1/ to your URL, and then use the table name as the route
|
||||
curl '<SUPABASE_URL>/rest/v1/todos' \
|
||||
-H "apikey: <SUPABASE_ANON_KEY>" \
|
||||
-H "Authorization: Bearer <SUPABASE_ANON_KEY>"
|
||||
```
|
||||
|
||||
</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).
|
||||
|
||||
### GraphQL API
|
||||
|
||||
You can use any GraphQL client with the Supabase GraphQL API. For our GraphQL example we will use [urql](https://formidable.com/open-source/urql/docs/).
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="javascript"
|
||||
>
|
||||
<TabPanel id="javascript" label="Javascript">
|
||||
|
||||
```javascript
|
||||
import { createClient, useQuery } from 'urql'
|
||||
|
||||
// Prepare API key and Authorization header
|
||||
const headers = {
|
||||
apikey: <SUPABASE_ANON_KEY>,
|
||||
authorization: `Bearer ${<SUPABASE_ANON_KEY}`>
|
||||
}
|
||||
|
||||
// Create GraphQL client
|
||||
// See: https://formidable.com/open-source/urql/docs/basics/react-preact/#setting-up-the-client
|
||||
const client = createClient({
|
||||
url: '<SUPABASE_URL>/graphql/v1',
|
||||
fetchOptions: function createFetchOptions() {
|
||||
return { headers }
|
||||
},
|
||||
})
|
||||
|
||||
// Prepare our GraphQL query
|
||||
const TodosQuery = `
|
||||
query {
|
||||
todosCollection {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
// Query for the data (React)
|
||||
const [result, reexecuteQuery] = useQuery({
|
||||
query: TodosQuery,
|
||||
})
|
||||
|
||||
// Read the result
|
||||
const { data, fetching, error } = result
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
# Append /graphql/v1/ to your URL, and then use the table name as the route
|
||||
curl --request POST '<SUPABASE_URL>/graphql/v1' \
|
||||
-H 'apikey: <SUPABASE_ANON_KEY>' \
|
||||
-H 'Authorization: Bearer <SUPABASE_ANON_KEY>' \
|
||||
-d '{ "query":"{ todos(first: 3) { edges { node { id } } } }" }'
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Realtime API
|
||||
|
||||
By default Realtime is disabled on your database. Let's turn on Realtime for the `todos` table.
|
||||
|
||||
<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 **Replication** in the sidebar.
|
||||
3. Control which database events are sent by toggling **Insert**, **Update**, and **Delete**.
|
||||
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 />
|
||||
</video>
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="sql" label="SQL">
|
||||
|
||||
```sql
|
||||
alter publication supabase_realtime add table todos;
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
From the client, we can listen to any new data that is inserted into the `todos` table:
|
||||
|
||||
```javascript
|
||||
// Initialize the JS client
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
|
||||
|
||||
// Create a function to handle inserts
|
||||
const handleInserts = (payload) => {
|
||||
console.log('Change received!', payload)
|
||||
}
|
||||
|
||||
// Listen to inserts
|
||||
const { data: todos, error } = await supabase.from('todos').on('INSERT', handleInserts).subscribe()
|
||||
```
|
||||
|
||||
Use [subscribe()](/docs/reference/javascript/subscribe) to listen to database changes.
|
||||
The Realtime API works through PostgreSQL's replication functionality. Postgres sends database changes to a [publication](/docs/guides/database/replication#publications)
|
||||
called `supabase_realtime`, and by managing this publication you can control which data is broadcast.
|
||||
|
||||
## API Security
|
||||
|
||||
### Securing your Routes
|
||||
|
||||
Your API is designed to work with Postgres Row Level Security (RLS). If you use Supabase [Auth](/guides/auth), you can restrict data based on the logged-in user.
|
||||
To control access to your data, you can use [Policies](/guides/auth#policies).
|
||||
When you create a table in Postgres, Row Level Security is disabled by default. To enable RLS:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="dashboard"
|
||||
>
|
||||
<TabPanel id="dashboard" label="Dashboard">
|
||||
|
||||
1. Go to the [Authentication](https://app.supabase.com/project/_/auth/users) page in the Dashboard.
|
||||
2. Click on **Policies** in the sidebar.
|
||||
3. Select **Enable RLS** to enable Row Level Security.
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="sql" label="SQL">
|
||||
|
||||
```sql
|
||||
alter table todos enable row level security;
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### The `service_role` key
|
||||
|
||||
Never expose the `service_role` key in a browser or anywhere where a user can see it. This Key is designed to bypass Row Level Security - so it should only be used on a private server.
|
||||
|
||||
A common use case for the `service_role` key is to run data analytics jobs on the backend. To support joins on user id, it is often useful to grant the service role read access to `auth.users` table.
|
||||
|
||||
```sql
|
||||
grant select on table auth.users to service_role;
|
||||
```
|
||||
|
||||
We have [partnered with GitHub](https://github.blog/changelog/2022-03-28-supabase-is-now-a-github-secret-scanning-partner/) to scan for Supabase `service_role` keys pushed to public repositories.
|
||||
If they detect any keys with service_role privileges being pushed to GitHub, they will forward the API key to us, so that we can automatically revoke the detected secrets and notify you, protecting your data against malicious actors.
|
||||
|
||||
### Safeguards towards accidental deletes and updates
|
||||
|
||||
For all projects, by default, the Postgres extension [safeupdate](https://github.com/eradman/pg-safeupdate) is enabled for all queries coming from the API.
|
||||
This ensures that any `delete()` or `update()` would fail if there are no accompanying filters provided.
|
||||
To confirm that safeupdate is enabled for queries going through the API of your project, the following query could be run:
|
||||
|
||||
```sql
|
||||
select usename,useconfig from pg_shadow where usename = 'authenticator' ;
|
||||
```
|
||||
|
||||
The expected value for `useconfig` should be:
|
||||
|
||||
```sql
|
||||
['session_preload_libraries=supautils, safeupdate']
|
||||
```
|
||||
@@ -0,0 +1,107 @@
|
||||
---
|
||||
id: generating-types
|
||||
title: 'Generating Types'
|
||||
description: How to generate types for your API and Supabase libraries.
|
||||
---
|
||||
|
||||
Supabase APIs are generated from your database, which means that we can use database introspection to generate type-safe API definitions.
|
||||
|
||||
## 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.
|
||||
|
||||
You can [install the CLI](https://www.npmjs.com/package/supabase) via npm or other supported package managers. The minimum required version of the CLI is [v1.8.1](https://github.com/supabase/cli/releases).
|
||||
|
||||
```bash
|
||||
npm i supabase@">=1.8.1" --save-dev
|
||||
```
|
||||
|
||||
Login with your Personal Access Token:
|
||||
|
||||
```bash
|
||||
npx supabase login
|
||||
```
|
||||
|
||||
Generate types for your project to produce the `types/supabase.ts` file:
|
||||
|
||||
```bash
|
||||
npx supabase gen types typescript --project-id "$PROJECT_ID" --schema public > types/supabase.ts
|
||||
```
|
||||
|
||||
After you have generated your types, you can use them in `src/index.ts`
|
||||
|
||||
```tsx
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
import { Database } from '../types/supabase'
|
||||
|
||||
const supabase = createClient<Database>(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL,
|
||||
process.env.SUPABASE_SECRET_KEY
|
||||
)
|
||||
|
||||
export default async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const allOnlineUsers = await supabase.from('users').select('*').eq('status', 'ONLINE')
|
||||
res.status(200).json(allOnlineUsers)
|
||||
}
|
||||
```
|
||||
|
||||
## Update types automatically with GitHub Actions
|
||||
|
||||
One way to keep your type definitions in sync with your database is to set up a GitHub action that runs on a schedule.
|
||||
|
||||
Add the script above to your `package.json` to run it using `npm run update-types`
|
||||
|
||||
```json
|
||||
"update-types": "npx supabase gen types typescript --project-id \"$PROJECT_ID\" > types/supabase.ts"
|
||||
```
|
||||
|
||||
Create a file `.github/workflows/update-types.yml` with the following snippet to define the action along with the environment variables. This script will commit new type changes to your repo every night.
|
||||
|
||||
```yaml
|
||||
name: Update database types
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# sets the action to run daily. You can modify this to run the action more or less frequently
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
jobs:
|
||||
update:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SUPABASE_ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||
PROJECT_ID: <your-project-id>
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v2.1.5
|
||||
with:
|
||||
node-version: 16
|
||||
- run: npm run update-types
|
||||
- name: check for file changes
|
||||
id: git_status
|
||||
run: |
|
||||
echo "::set-output name=status::$(git status -s)"
|
||||
- name: Commit files
|
||||
if: ${{contains(steps.git_status.outputs.status, ' ')}}
|
||||
run: |
|
||||
git add types/database/index.ts
|
||||
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
git commit -m "Update database types" -a
|
||||
- name: Push changes
|
||||
if: ${{contains(steps.git_status.outputs.status, ' ')}}
|
||||
uses: ad-m/github-push-action@master
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: ${{ github.ref }}
|
||||
```
|
||||
|
||||
Alternatively, you can use a community-supported GitHub action: [generate-supabase-db-types-github-action](https://github.com/lyqht/generate-supabase-db-types-github-action).
|
||||
|
||||
## Resources
|
||||
|
||||
- [Generating Supabase types with GitHub Actions](https://blog.esteetey.dev/how-to-create-and-test-a-github-action-that-generates-types-from-supabase-database)
|
||||
Loaded 100 of 4835 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user