Merge branch 'master' into feat/self-hosted-storage

This commit is contained in:
Francisco Mazzoni authored and GitHub committed 2022-11-15 17:40:48 -03:00
commit ff0f89472c
4835 files changed
+501176 -933217

No files matched your search

+7
View File
@@ -0,0 +1,7 @@
node_modules/
.vercel
.next
.env.local
.env.production
.env.dev
.env.*
+2
View File
@@ -1,2 +1,4 @@
/studio/ @supabase/frontend
/apps/www/ @supabase/frontend
/apps/docs/ @supabase/docs
/spec/ @supabase/docs
-27
View File
@@ -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
+19
View File
@@ -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 }}
+16 -16
View File
@@ -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 }}
+3
View File
@@ -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
+13 -14
View File
@@ -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: ./
+17 -16
View File
@@ -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: ./
+1
View File
@@ -0,0 +1 @@
^./i18n
Executable
+1
View File
@@ -0,0 +1 @@
cookie
+56 -145
View File
@@ -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.
---
+23 -9
View File
@@ -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).
![Architecture](https://supabase.com/docs/assets/images/supabase-architecture-9050a7317e9ec7efb7807f5194122e48.png)
![Architecture](https://user-images.githubusercontent.com/70828596/187547862-ffa9d058-0c3a-4851-a3e7-92ccfca4b596.png)
- [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
View File
@@ -1 +1 @@
web/static/.well-known/security.txt
apps/reference/static/.well-known/security.txt
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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.
+23
View File
@@ -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
+45
View File
@@ -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.
+51
View File
@@ -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
+53
View File
@@ -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>
)
}
+55
View File
@@ -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 }
+208
View File
@@ -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
+133
View File
@@ -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">&copy; Supabase Inc</small>
<DarkModeToggle />
</div>
</div>
</footer>
)
}
export default Footer
+106
View File
@@ -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
+17
View File
@@ -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>
)
}
+96
View File
@@ -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.
@@ -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
+216
View File
@@ -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
}
}
+191
View File
@@ -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
+53
View File
@@ -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.
+73
View File
@@ -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>
)
}
+66
View File
@@ -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
+56
View File
@@ -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
+191
View File
@@ -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.
+150
View File
@@ -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.
+29
View File
@@ -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.
+12
View File
@@ -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
+12
View File
@@ -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
+13
View File
@@ -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
+12
View File
@@ -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
+13
View File
@@ -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
+132
View File
@@ -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
+130
View File
@@ -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
+169
View File
@@ -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
+172
View File
@@ -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.
+156
View File
@@ -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:
![Supabase Architecture](/docs/img/supabase-architecture.png)
### PostgreSQL (Database)
PostgreSQL is the core of Supabase. We do not abstract the PostgreSQL database — you can access it and use it with full privileges. We simply provide tools which makes PostgreSQL as easy to use as Firebase.
- Official Docs: [postgresql.org/docs](https://www.postgresql.org/docs/current/index.html)
- Source code: [github.com/postgres/postgres](https://github.com/postgres/postgres) (mirror)
- License: [PostgreSQL Licence](https://www.postgresql.org/about/licence/)
- Language: C
### Studio (Dashboard)
An open source Dashboard for managing your database and services.
- Official Docs: [Supabase docs](/docs)
- Source code: [github.com/supabase/supabase](https://github.com/supabase/supabase/tree/master/studio)
- License: [Apache 2](https://github.com/supabase/supabase/blob/master/LICENSE)
- Language: TypeScript
### GoTrue (Auth)
A JWT-based API for managing users and issuing access tokens. This integrates with PostgreSQL's Row Level Security and the API servers.
- Official Docs: [Supabase Auth reference docs](/docs/reference/auth)
- Source code: [github.com/supabase/gotrue](https://github.com/supabase/gotrue)
- License: [MIT](https://github.com/supabase/gotrue/blob/master/LICENSE)
- Language: Go
### PostgREST (API)
A standalone web server that turns your PostgreSQL database directly into a RESTful API.
We use this with our [pg_graphql](https://github.com/supabase/pg_graphql) extension to provide a GraphQL API.
- Official Docs: [postgrest.org](https://postgrest.org/)
- Source code: [github.com/PostgREST/postgrest](https://github.com/PostgREST/postgrest)
- License: [MIT](https://github.com/PostgREST/postgrest/blob/main/LICENSE)
- Language: Haskel
### Realtime (API & multiplayer)
A scalable websocket engine for managing user Presence, broadcasting messages, and streaming database changes.
- Official Docs: [Supabase Realtime docs](/docs/guides/realtime)
- Source code: [github.com/supabase/realtime](https://github.com/supabase/realtime)
- License: [Apache 2](https://github.com/supabase/realtime/blob/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.
+88
View File
@@ -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.
+38
View File
@@ -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).
+172
View File
@@ -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` |
+375
View File
@@ -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