mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 09:55:06 +03:00
Merge branch 'master' into docs/move-api-section
This commit is contained in:
158 files changed
+27644
-7176
No files matched your search
@@ -381,7 +381,7 @@ const userListener = supabase.from('users')
|
||||
const userListener = supabase.channel('public:user')
|
||||
.on(
|
||||
'postgres_changes',
|
||||
{ event: '*', schema: 'public', table: 'user' }
|
||||
{ event: '*', schema: 'public', table: 'user' },
|
||||
(payload) => handleAllEventsPayload()
|
||||
)
|
||||
.subscribe()
|
||||
@@ -408,7 +408,7 @@ userListener.unsubscribe()
|
||||
<TabItem value="2.x">
|
||||
|
||||
```ts
|
||||
supabase.removeChannel('public:users')
|
||||
supabase.removeChannel(userListener)
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
</Tabs>
|
||||
@@ -16,12 +16,9 @@ Setting up Email logins for your Supabase application.
|
||||
|
||||
## Configure email settings
|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
|
||||
- In the left sidebar, click the `Authentication` icon (near the top)
|
||||
- Click `Settings` from the list to go to the `Authentication Settings` page
|
||||
- Enter the final (hosted) URL of your app under `Site URL`
|
||||
- Under `Email Auth` turn `Enable Email Signup` to ON
|
||||
- Click `Save`
|
||||
1. Go to the [Authentication Settings](https://app.supabase.com/project/_/auth/settings) page in the Dashboard.
|
||||
1. For **Site URL**, enter the final (hosted) URL of your app.
|
||||
1. Under Auth Providers, **enable email provider**.
|
||||
|
||||
:::note Self hosting
|
||||
|
||||
|
||||
@@ -19,12 +19,9 @@ Setting up Magic Link logins for your Supabase application.
|
||||
|
||||
## Add Magic Link into your Supabase Project
|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
|
||||
- In the left sidebar, click the `Authentication` icon (near the top)
|
||||
- Click `Settings` from the list to go to the `Authentication Settings` page
|
||||
- Enter the final (hosted) URL of your app under `Site URL` (this is important)
|
||||
- Under `Email Auth` turn `Enable Email Signup` to ON
|
||||
- Click `Save`
|
||||
1. Go to the [Authentication Settings](https://app.supabase.com/project/_/auth/settings) page in the Dashboard.
|
||||
1. For **Site URL**, enter the final (hosted) URL of your app.
|
||||
1. Under Auth Providers, **enable email provider**.
|
||||
|
||||
## Add login code to your client app
|
||||
|
||||
|
||||
@@ -145,6 +145,7 @@ values={[
|
||||
let { session, error } = await supabase.auth.verifyOTP({
|
||||
phone: '+13334445555',
|
||||
token: '123456',
|
||||
type: 'sms',
|
||||
})
|
||||
```
|
||||
|
||||
@@ -260,6 +261,7 @@ values={[
|
||||
let { session, error } = await supabase.auth.verifyOTP({
|
||||
phone: '+13334445555',
|
||||
token: '123456',
|
||||
type: 'sms',
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
@@ -12,11 +12,11 @@ Supabase provides several options for programmatically connecting to your Postgr
|
||||
- Direct connections using Postgres' standard connection system.
|
||||
- Connection pooling using PgBouncer.
|
||||
|
||||
### API vs Direct vs Pooling
|
||||
### Direct vs Pooling vs API
|
||||
|
||||
- The API is an auto-generated REST interface. You should use this for all browser and application interactions.
|
||||
- A "direct connection" is when a connection is made to the database using Postgres' native connection implementation. You should use this for tools which are always alive - usually installed on a long-running server.
|
||||
- A "connection pool" is a system (external to Postgres) which keeps connections "open". You should use this for serverless functions and tools which disconnect from the database frequently.
|
||||
- The API is an auto-generated REST interface. You should use this for all browser and application interactions. The API server internally handles a connection pool.
|
||||
|
||||
Why would you use a connection pool? Primarily because the way that Postgres handles connections isn't very scalable for a large number of _temporary_ connections.
|
||||
You can use these simple questions to determine which connection method to use:
|
||||
|
||||
@@ -263,6 +263,10 @@ serve(async (req) => {
|
||||
})
|
||||
```
|
||||
|
||||
### Using HTTP Methods
|
||||
|
||||
Edge Functions supports `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, and `OPTIONS`. A function can be designed to perform different actions based on a request's HTTP method. See the [example on building a RESTful service](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/restful-tasks) to learn how to handle different HTTP methods in your function.
|
||||
|
||||
## Limitations
|
||||
|
||||
- Deno Deploy limitations
|
||||
@@ -270,5 +274,4 @@ serve(async (req) => {
|
||||
- Cannot write to File System
|
||||
- Edge Functions
|
||||
- Local development - only one function at a time
|
||||
- Supabase Functions only supports `POST`, `OPTIONS`, and `GET` requests.
|
||||
- Serving of HTML content is not supported (`GET` requests that return `text/html` will be rewritten to `text/plain`).
|
||||
@@ -26,6 +26,16 @@ const examples = [
|
||||
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',
|
||||
}
|
||||
]
|
||||
|
||||
You can find a list of useful [Edge Function Examples](https://github.com/supabase/supabase/tree/master/examples/edge-functions) in our GitHub repository.
|
||||
|
||||
@@ -95,6 +95,11 @@ POSTGRES_PORT=5432
|
||||
|
||||
Once you have done this, you can safely comment out the `db` section of the `docker-compose` file, and remove any instances where the services `depends_on` the `db` image.
|
||||
|
||||
### Setting database's `log_min_messages`
|
||||
|
||||
By default, `docker-compose` sets the database's `log_min_messages` configuration to `fatal` to prevent redundant logs generated by Realtime.
|
||||
However, you might miss important log messages such as database errors. Configure `log_min_messages` based on your needs.
|
||||
|
||||
## Deploying
|
||||
|
||||
See the following guides to deploy Docker Compose setup using your preferred tool and platform:
|
||||
|
||||
@@ -36,7 +36,7 @@ Each system has a number of configuration options which can be found in the rele
|
||||
|
||||
- [Postgres](https://hub.docker.com/_/postgres/)
|
||||
- [PostgREST](https://postgrest.org/en/stable/configuration.html)
|
||||
- [Realtime](https://github.com/supabase/realtime#server-set-up)
|
||||
- [Realtime](https://github.com/supabase/realtime#server)
|
||||
- [GoTrue](https://github.com/supabase/gotrue)
|
||||
- [Storage](https://github.com/supabase/storage-api)
|
||||
- [Kong](https://docs.konghq.com/install/docker/)
|
||||
@@ -46,7 +46,7 @@ Each system has a number of configuration options which can be found in the rele
|
||||
It is recommended that you decouple your database from the middleware so that you can upgrade the middleware without any downtime.
|
||||
The "middleware" is everything except Postgres, and it should work with any Postgres provider (such as AWS RDS), or your own Postgres cluster.
|
||||
|
||||
### Database Extensions
|
||||
### Extensions
|
||||
|
||||
Supabase requires some Postgres extensions to be enabled by default for the API and Auth system to work. You can find the extensions inside the
|
||||
[schema initialization script](https://github.com/supabase/supabase/blob/master/docker/volumes/db/init/00-initial-schema.sql).
|
||||
@@ -69,7 +69,7 @@ For UUID functions, required for PostgreSQL <13.
|
||||
|
||||
For working with JWT and Auth functions.
|
||||
|
||||
### Database Roles
|
||||
### Roles
|
||||
|
||||
Supabase creates several default roles in your Postgres database. To restore defaults at any time you can run the commands inside the
|
||||
[schema initialization script](https://github.com/supabase/supabase/blob/master/docker/volumes/db/init/00-initial-schema.sql).
|
||||
@@ -111,6 +111,10 @@ For running commands via the Supabase UI.
|
||||
|
||||
Supabase Administrative role for maintaining your database.
|
||||
|
||||
### Realtime Logs
|
||||
|
||||
Set your database's `log_min_messages` configuration to `fatal` to prevent redundant database logs generated by Realtime. However, you might miss important log messages such as database errors. Configure `log_min_messages` based on your needs.
|
||||
|
||||
## API Keys
|
||||
|
||||
The API Gateway (Kong) uses JWT to authenticate access through to the database. The JWT should correspond to a relevant Postgres Role,
|
||||
|
||||
@@ -47,12 +47,8 @@ Run `flutter pub get` to install the dependencies.
|
||||
|
||||
Now that we have the dependencies installed let's setup deep links so users who have logged in via magic link or OAuth can come back to the app.
|
||||
|
||||
```sh
|
||||
1. Go to the "Authentication" section.
|
||||
2. Click "Settings" in the sidebar.
|
||||
3. Type `io.supabase.flutterquickstart://login-callback/` in the Additional Redirect URLs input field.
|
||||
4. Hit save.
|
||||
```
|
||||
1. Go to [Authentication Settings](https://app.supabase.com/project/_/auth/settings) in the Dashboard.
|
||||
1. In **Redirect URLs**, add `io.supabase.flutterquickstart://login-callback/` as a new domain.
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -14,6 +14,17 @@ import ProjectSetup from './_partials/project_setup.mdx'
|
||||
|
||||

|
||||
|
||||
### Video Guide
|
||||
|
||||
<div class="video-container">
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/0Fs96oZ4se0"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
### GitHub
|
||||
|
||||
Should you get stuck while working through the guide, refer to [this repo](https://github.com/supabase/supabase/tree/master/examples/user-management/nextjs-ts-user-management).
|
||||
|
||||
@@ -16,7 +16,7 @@ import ProjectSetup from './_partials/project_setup.mdx'
|
||||
|
||||
### Github
|
||||
|
||||
Should you get stuck while working through the guide, refer to [this repo](https://github.com/supabase-community/nuxt3-quickstarter).
|
||||
Should you get stuck while working through the guide, refer to [this repo](https://github.com/supabase/supabase/tree/master/examples/user-management/nuxt3-user-management).
|
||||
|
||||
<ProjectSetup />
|
||||
|
||||
@@ -103,7 +103,7 @@ Let's set up a Vue component to manage logins and sign ups. We'll use Magic Link
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const { error } = await supabase.auth.signIn({ email: email.value })
|
||||
const { error } = await supabase.auth.signInWithOtp({ email: email.value })
|
||||
if (error) throw error
|
||||
alert('Check your email for the login link!')
|
||||
} catch (error) {
|
||||
@@ -207,6 +207,7 @@ Let's create a new component for that called `Account.vue`.
|
||||
loading.value = true
|
||||
let { error } = await supabase.auth.signOut()
|
||||
if (error) throw error
|
||||
user.value = null
|
||||
} catch (error) {
|
||||
alert(error.message)
|
||||
} finally {
|
||||
|
||||
@@ -52,7 +52,7 @@ PUBLIC_SUPABASE_ANON_KEY="YOUR_SUPABASE_KEY"
|
||||
Now that we have the API credentials in place, let's create a helper file to initialize the Supabase client. These variables will be exposed
|
||||
on the browser, and that's completely fine since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.
|
||||
|
||||
```js title="lib/supabaseClient.ts"
|
||||
```js title="src/lib/supabaseClient.ts"
|
||||
import { createClient } from '@supabase/auth-helpers-sveltekit'
|
||||
import { env } from '$env/dynamic/public'
|
||||
|
||||
@@ -62,167 +62,241 @@ export const supabaseClient = createClient(env.PUBLIC_SUPABASE_URL, env.PUBLIC_S
|
||||
And one optional step is to update the CSS file `public/global.css` to make the app look nice.
|
||||
You can find the full contents of this file [here](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/svelte-user-management/src/app.css).
|
||||
|
||||
### Supabase Auth Helpers
|
||||
|
||||
SvelteKit is a highly versatile framework offering pre-rendering at build time (SSG), server-side rendering at request time (SSR), API routes, and more.
|
||||
|
||||
It can be challenging to authenticate your users in all these different environments, that's why we've created the Supabase Auth Helpers to make user management and data fetching within SvelteKit as easy as possible.
|
||||
|
||||
Install the auth helpers for SvelteKit:
|
||||
|
||||
```bash
|
||||
npm install @supabase/auth-helpers-sveltekit
|
||||
```
|
||||
|
||||
Update your `src/routes/+layout.svelte`:
|
||||
|
||||
```html title="src/routes/+layout.svelte"
|
||||
<script lang="ts">
|
||||
import { supabaseClient } from '$lib/supabaseClient'
|
||||
import { invalidate } from '$app/navigation'
|
||||
import { onMount } from 'svelte'
|
||||
import './styles.css'
|
||||
|
||||
onMount(() => {
|
||||
const {
|
||||
data: { subscription }
|
||||
} = supabaseClient.auth.onAuthStateChange(() => {
|
||||
invalidate('supabase:auth')
|
||||
})
|
||||
|
||||
return () => {
|
||||
subscription.unsubscribe()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="container" style="padding: 50px 0 100px 0">
|
||||
<slot />
|
||||
</div>
|
||||
```
|
||||
|
||||
Create a new `src/routes/+layout.ts` file to handle the session on the client-side.
|
||||
|
||||
```ts title="src/routes/+layout.ts"
|
||||
import type { LayoutLoad } from './$types';
|
||||
import { getSupabase } from '@supabase/auth-helpers-sveltekit';
|
||||
|
||||
export const load: LayoutLoad = async (event) => {
|
||||
const { session } = await getSupabase(event);
|
||||
return { session };
|
||||
};
|
||||
```
|
||||
|
||||
Create a new `src/routes/+layout.server.ts` file to handle the session on the server-side.
|
||||
|
||||
```ts title="src/routes/+layout.server.ts"
|
||||
import type { LayoutServerLoad } from './$types';
|
||||
import { getServerSession } from '@supabase/auth-helpers-sveltekit';
|
||||
|
||||
export const load: LayoutServerLoad = async (event) => {
|
||||
return {
|
||||
session: await getServerSession(event)
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
Be sure to create `src/hooks.client.ts` and `src/hooks.server.ts` in order to get the auth helper started on the client and server-side.
|
||||
|
||||
```ts title="src/hooks.client.ts"
|
||||
import '$lib/supabaseClient'
|
||||
```
|
||||
|
||||
```ts title="src/hooks.server.ts"
|
||||
import '$lib/supabaseClient'
|
||||
```
|
||||
|
||||
### Set up a Login component
|
||||
|
||||
Let's set up a Svelte component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords.
|
||||
|
||||
```html title="lib/Auth.svelte"
|
||||
<script>
|
||||
import { supabase } from '$lib/supabaseClient'
|
||||
```html title="src/routes/Auth.svelte"
|
||||
<script lang="ts">
|
||||
import { supabaseClient } from '$lib/supabaseClient'
|
||||
|
||||
let loading = false
|
||||
let email
|
||||
let loading = false
|
||||
let email: string
|
||||
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
loading = true
|
||||
const { error } = await supabase.auth.signIn({ email })
|
||||
if (error) throw error
|
||||
alert('Check your email for the login link!')
|
||||
} catch (error) {
|
||||
alert(error.error_description || error.message)
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
loading = true
|
||||
const { error } = await supabaseClient.auth.signInWithOtp({ email })
|
||||
if (error) throw error
|
||||
alert('Check your email for the login link!')
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
alert(error.message)
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<form class="row flex-center flex" on:submit|preventDefault="{handleLogin}">
|
||||
<div class="col-6 form-widget">
|
||||
<h1 class="header">Supabase + Svelte</h1>
|
||||
<p class="description">Sign in via magic link with your email below</p>
|
||||
<div>
|
||||
<input
|
||||
class="inputField"
|
||||
type="email"
|
||||
placeholder="Your email"
|
||||
bind:value="{email}"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" class='button block' value={loading ? "Loading" :
|
||||
"Send magic link"} disabled={loading} />
|
||||
</div>
|
||||
</div>
|
||||
<form class="row flex-center flex" on:submit|preventDefault={handleLogin}>
|
||||
<div class="col-6 form-widget">
|
||||
<h1 class="header">Supabase + SvelteKit</h1>
|
||||
<p class="description">Sign in via magic link with your email below</p>
|
||||
<div>
|
||||
<input class="inputField" type="email" placeholder="Your email" bind:value={email} />
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="submit"
|
||||
class="button block"
|
||||
value={loading ? 'Loading' : 'Send magic link'}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
```
|
||||
|
||||
### User store
|
||||
### Account component
|
||||
|
||||
To access the user information in other places, we use a writable store. Create a new file called `sessionStore.js`
|
||||
After a user is signed in, they need to be able to edit their profile details and manage their account.
|
||||
Create a new `Account.svelte` component to handle this functionality.
|
||||
|
||||
```javascript title="lib/sessionStore.js"
|
||||
import { writable } from 'svelte/store'
|
||||
```html title="src/routes/Account.svelte"
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte'
|
||||
import type { AuthSession } from '@supabase/supabase-js'
|
||||
import { supabaseClient } from '$lib/supabaseClient'
|
||||
|
||||
export const user = writable(false)
|
||||
```
|
||||
export let session: AuthSession
|
||||
|
||||
### Account page
|
||||
let loading = false
|
||||
let username: string | null = null
|
||||
let website: string | null = null
|
||||
let avatarUrl: string | null = null
|
||||
|
||||
After a user is signed in we can allow them to edit their profile details and manage their account.
|
||||
Let's create a new component for that called `Profile.svelte`.
|
||||
onMount(() => {
|
||||
getProfile()
|
||||
})
|
||||
|
||||
```html title="lib/Profile.svelte"
|
||||
<script>
|
||||
import { supabase } from '$lib/supabaseClient'
|
||||
import { user } from '$lib/sessionStore'
|
||||
const getProfile = async () => {
|
||||
try {
|
||||
loading = true
|
||||
const { user } = session
|
||||
|
||||
let loading = true
|
||||
let username = null
|
||||
let website = null
|
||||
let avatar_url = null
|
||||
const { data, error, status } = await supabaseClient
|
||||
.from('profiles')
|
||||
.select(`username, website, avatar_url`)
|
||||
.eq('id', user.id)
|
||||
.single()
|
||||
|
||||
function getProfile(node) {
|
||||
try {
|
||||
loading = true
|
||||
const user = supabase.auth.user()
|
||||
if (data) {
|
||||
username = data.username
|
||||
website = data.website
|
||||
avatarUrl = data.avatar_url
|
||||
}
|
||||
|
||||
supabase
|
||||
.from('profiles')
|
||||
.select(`username, website, avatar_url`)
|
||||
.eq('id', user?.id)
|
||||
.single()
|
||||
.then(({ data, error, status }) => {
|
||||
if (data) {
|
||||
username = data.username
|
||||
website = data.website
|
||||
avatar_url = data.avatar_url
|
||||
}
|
||||
if (error && status !== 406) throw error
|
||||
})
|
||||
} catch (error) {
|
||||
alert(error.message)
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
if (error && status !== 406) throw error
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
alert(error.message)
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
|
||||
async function updateProfile() {
|
||||
try {
|
||||
loading = true
|
||||
const user = supabase.auth.user()
|
||||
async function updateProfile() {
|
||||
try {
|
||||
loading = true
|
||||
const { user } = session
|
||||
|
||||
const updates = {
|
||||
id: user.id,
|
||||
username,
|
||||
website,
|
||||
avatar_url,
|
||||
updated_at: new Date(),
|
||||
}
|
||||
const updates = {
|
||||
id: user.id,
|
||||
username,
|
||||
website,
|
||||
avatar_url: avatarUrl,
|
||||
updated_at: new Date()
|
||||
}
|
||||
|
||||
let { error } = await supabase.from('profiles').upsert(updates, {
|
||||
returning: 'minimal', // Don't return the value after inserting
|
||||
})
|
||||
let { error } = await supabaseClient.from('profiles').upsert(updates)
|
||||
|
||||
if (error) throw error
|
||||
} catch (error) {
|
||||
alert(error.message)
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
if (error) throw error
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
alert(error.message)
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
|
||||
async function signOut() {
|
||||
try {
|
||||
loading = true
|
||||
let { error } = await supabase.auth.signOut()
|
||||
if (error) throw error
|
||||
} catch (error) {
|
||||
alert(error.message)
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
async function signOut() {
|
||||
try {
|
||||
loading = true
|
||||
let { error } = await supabaseClient.auth.signOut()
|
||||
if (error) throw error
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
alert(error.message)
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<form
|
||||
use:getProfile
|
||||
class="form-widget"
|
||||
on:submit|preventDefault="{updateProfile}"
|
||||
>
|
||||
<div>
|
||||
<label for="email">Email</label>
|
||||
<input id="email" type="text" value="{$user.email}" disabled />
|
||||
</div>
|
||||
<div>
|
||||
<label for="username">Name</label>
|
||||
<input id="username" type="text" bind:value="{username}" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="website">Website</label>
|
||||
<input id="website" type="website" bind:value="{website}" />
|
||||
</div>
|
||||
<form class="form-widget" on:submit|preventDefault={updateProfile}>
|
||||
<div>
|
||||
<label for="email">Email</label>
|
||||
<input id="email" type="text" value={session.user.email} disabled />
|
||||
</div>
|
||||
<div>
|
||||
<label for="username">Name</label>
|
||||
<input id="username" type="text" bind:value={username} />
|
||||
</div>
|
||||
<div>
|
||||
<label for="website">Website</label>
|
||||
<input id="website" type="website" bind:value={website} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input type="submit" class="button block primary" value={loading ? 'Loading
|
||||
...' : 'Update'} disabled={loading}/>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="submit"
|
||||
class="button block primary"
|
||||
value={loading ? 'Loading...' : 'Update'}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button class="button block" on:click="{signOut}" disabled="{loading}">
|
||||
Sign Out
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button class="button block" on:click={signOut} disabled={loading}> Sign Out </button>
|
||||
</div>
|
||||
</form>
|
||||
```
|
||||
|
||||
@@ -232,25 +306,21 @@ Now that we have all the components in place, let's update `src/routes/+page.sve
|
||||
|
||||
```html title="src/routes/+page.svelte"
|
||||
<script>
|
||||
import { user } from '$lib/sessionStore'
|
||||
import { supabase } from '$lib/supabaseClient'
|
||||
import Auth from '$lib/Auth.svelte'
|
||||
import Profile from '$lib/Profile.svelte'
|
||||
|
||||
user.set(supabase.auth.user())
|
||||
|
||||
supabase.auth.onAuthStateChange((_, session) => {
|
||||
user.set(session.user)
|
||||
})
|
||||
import { page } from '$app/stores'
|
||||
import Account from './Account.svelte'
|
||||
import Auth from './Auth.svelte'
|
||||
</script>
|
||||
|
||||
<div class="container" style="padding: 50px 0 100px 0;">
|
||||
{#if $user}
|
||||
<Profile />
|
||||
{:else}
|
||||
<Auth />
|
||||
{/if}
|
||||
</div>
|
||||
<svelte:head>
|
||||
<title>Supabase + SvelteKit</title>
|
||||
<meta name="description" content="SvelteKit using supabase-js v2" />
|
||||
</svelte:head>
|
||||
|
||||
{#if !$page.data.session}
|
||||
<Auth />
|
||||
{:else}
|
||||
<Account session={$page.data.session} />
|
||||
{/if}
|
||||
```
|
||||
|
||||
Once that's done, run this in a terminal window:
|
||||
@@ -271,89 +341,95 @@ Every Supabase project is configured with [Storage](/docs/guides/storage) for ma
|
||||
|
||||
Let's create an avatar for the user so that they can upload a profile photo. We can start by creating a new component:
|
||||
|
||||
```html title="lib/Avatar.svelte"
|
||||
<script>
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { supabase } from '$lib/supabaseClient'
|
||||
```html title="src/routes/Avatar.svelte"
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { supabaseClient } from '$lib/supabaseClient'
|
||||
|
||||
export let path
|
||||
export let size = '10em'
|
||||
export let size = 10
|
||||
export let url: string
|
||||
|
||||
let uploading = false
|
||||
let src
|
||||
let files
|
||||
let avatarUrl: string | null = null
|
||||
let uploading = false
|
||||
let files: FileList
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function downloadImage() {
|
||||
supabase.storage
|
||||
.from('avatars')
|
||||
.download(path)
|
||||
.then(({ data, error }) => {
|
||||
if (error) throw error
|
||||
src = URL.createObjectURL(data)
|
||||
})
|
||||
.catch((error) =>
|
||||
console.error('Error downloading image: ', error.message)
|
||||
)
|
||||
}
|
||||
const downloadImage = async (path: string) => {
|
||||
try {
|
||||
const { data, error } = await supabaseClient.storage.from('avatars').download(path)
|
||||
|
||||
async function uploadAvatar() {
|
||||
try {
|
||||
uploading = true
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
|
||||
if (!files || files.length === 0) {
|
||||
throw new Error('You must select an image to upload.')
|
||||
}
|
||||
const url = URL.createObjectURL(data)
|
||||
avatarUrl = url
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.log('Error downloading image: ', error.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const file = files[0]
|
||||
const fileExt = file.name.split('.').pop()
|
||||
const fileName = `${Math.random()}.${fileExt}`
|
||||
const filePath = `${fileName}`
|
||||
const uploadAvatar = async () => {
|
||||
try {
|
||||
uploading = true
|
||||
|
||||
let { error: uploadError } = await supabase.storage
|
||||
.from('avatars')
|
||||
.upload(filePath, file)
|
||||
if (!files || files.length === 0) {
|
||||
throw new Error('You must select an image to upload.')
|
||||
}
|
||||
|
||||
if (uploadError) throw uploadError
|
||||
const file = files[0]
|
||||
const fileExt = file.name.split('.').pop()
|
||||
const filePath = `${Math.random()}.${fileExt}`
|
||||
|
||||
path = filePath
|
||||
dispatch('upload')
|
||||
} catch (error) {
|
||||
alert(error.message)
|
||||
} finally {
|
||||
uploading = false
|
||||
}
|
||||
}
|
||||
let { error } = await supabaseClient.storage.from('avatars').upload(filePath, file)
|
||||
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
|
||||
url = filePath
|
||||
dispatch('upload')
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
alert(error.message)
|
||||
}
|
||||
} finally {
|
||||
uploading = false
|
||||
}
|
||||
}
|
||||
|
||||
$: if (url) downloadImage(url)
|
||||
</script>
|
||||
|
||||
<div>
|
||||
{#if path}
|
||||
<img
|
||||
use:downloadImage
|
||||
{src}
|
||||
alt="Avatar"
|
||||
class="avatar image"
|
||||
style="height: {size}; width: {size};"
|
||||
/>
|
||||
{:else}
|
||||
<div class="avatar no-image" style="height: {size}; width: {size};" />
|
||||
{/if}
|
||||
{#if avatarUrl}
|
||||
<img
|
||||
src={avatarUrl}
|
||||
alt={avatarUrl ? 'Avatar' : 'No image'}
|
||||
class="avatar image"
|
||||
style="height: {size}em; width: {size}em;"
|
||||
/>
|
||||
{:else}
|
||||
<div class="avatar no-image" style="height: {size}em; width: {size}em;" />
|
||||
{/if}
|
||||
|
||||
<div style="width: {size};">
|
||||
<label class="button primary block" for="single">
|
||||
{uploading ? 'Uploading ...' : 'Upload'}
|
||||
</label>
|
||||
<input
|
||||
style="visibility: hidden; position:absolute;"
|
||||
type="file"
|
||||
id="single"
|
||||
accept="image/*"
|
||||
bind:files
|
||||
on:change="{uploadAvatar}"
|
||||
disabled="{uploading}"
|
||||
/>
|
||||
</div>
|
||||
<div style="width: {size}em;">
|
||||
<label class="button primary block" for="single">
|
||||
{uploading ? 'Uploading ...' : 'Upload'}
|
||||
</label>
|
||||
<input
|
||||
style="visibility: hidden; position:absolute;"
|
||||
type="file"
|
||||
id="single"
|
||||
accept="image/*"
|
||||
bind:files
|
||||
on:change={uploadAvatar}
|
||||
disabled={uploading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
@@ -361,10 +437,10 @@ Let's create an avatar for the user so that they can upload a profile photo. We
|
||||
|
||||
And then we can add the widget to the Account page:
|
||||
|
||||
```html title="$lib/Profile.svelte"
|
||||
```html title="src/routes/Account.svelte"
|
||||
<script>
|
||||
// Import the new component
|
||||
import Avatar from '$lib/Avatar.svelte'
|
||||
import Avatar from './Avatar.svelte'
|
||||
</script>
|
||||
|
||||
<form
|
||||
@@ -373,7 +449,7 @@ And then we can add the widget to the Account page:
|
||||
on:submit|preventDefault="{updateProfile}"
|
||||
>
|
||||
<!-- Add to body -->
|
||||
<Avatar bind:path="{avatar_url}" on:upload="{updateProfile}" />
|
||||
<Avatar bind:url={avatarUrl} size={10} on:upload={updateProfile} />
|
||||
|
||||
<!-- Other form elements -->
|
||||
</form>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# 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
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
slug: /
|
||||
sidebar_position: 1
|
||||
sidebar_label: Supabase API
|
||||
---
|
||||
|
||||
# Supabase API
|
||||
|
||||
The Supabase API allows you to manage your projects programmatically.
|
||||
|
||||
## Status
|
||||
|
||||
The Supabase API is in `beta`. It is usable in it's current state, but it's likely that there will be breaking changes.
|
||||
|
||||
## Authentication
|
||||
|
||||
All API requests require a Supabase Personal token to be included in the Authorization header: `Authorization Bearer <supabase_personal_token`.
|
||||
To generate or manage your API token, visit your [account](https://app.supabase.com/account/tokens) page.
|
||||
Your API tokens carry the same privileges as your user account, so be sure to keep it secret.
|
||||
|
||||
```bash
|
||||
$ curl https://api.supabase.com/v1/projects \
|
||||
-H "Authorization: Bearer sbp_bdd0••••••••••••••••••••••••••••••••4f23"
|
||||
```
|
||||
|
||||
All API requests must be authenticated and made over HTTPS.
|
||||
|
||||
## Rate limits
|
||||
|
||||
The API is currently subject to our fair-use policy. In the future, are likely to introduce rate limits.
|
||||
All resources created via the API are subject to the pricing detailed on our [Pricing](https://supabase.com/pricing) pages.
|
||||
|
||||
## Additional links
|
||||
|
||||
- [OpenAPI Docs](https://api.supabase.com/api/v1)
|
||||
- [OpenAPI Spec](https://api.supabase.com/api/v1-json)
|
||||
- Reporting bugs and issues: [github.com/supabase/supabase](https://github.com/supabase/supabase)
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
id: release-notes
|
||||
---
|
||||
|
||||
# Release Notes
|
||||
|
||||
## v1.0.0 {#1.0.0}
|
||||
|
||||
Adds the following routes:
|
||||
|
||||
- `/v1/organizations`: Manage Supabase organizations
|
||||
- `/v1/projects`: Manage Supabase projects
|
||||
File diff suppressed because it is too large.
Load diff
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
id: intro
|
||||
slug: /
|
||||
sidebar_position: 1
|
||||
sidebar_label: Supabase CLI
|
||||
hide_table_of_contents: true
|
||||
---
|
||||
|
||||
# Supabase CLI
|
||||
|
||||
The Supabase CLI provides tools to develop your project locally and deploy to the Supabase Platform.
|
||||
The CLI is still under development, but it contains all the functionality for working with your Supabase projects and the Supabase Platform.
|
||||
|
||||
- Run Supabase locally: [`supabase start`](https://supabase.com/docs/reference/cli/usage#supabase-start)
|
||||
- Manage database migrations: [`supabase migration`](https://supabase.com/docs/reference/cli/usage#supabase-migration)
|
||||
- CI/CD for releasing to production: [`supabase db push`](https://supabase.com/docs/reference/cli/usage#supabase-db-push)
|
||||
- Manage your Supabase projects: [`supabase projects`](https://supabase.com/docs/reference/cli/usage#supabase-projects)
|
||||
- Generate types directly from your database schema: [`supabase gen types`](https://supabase.com/docs/reference/cli/usage#supabase-gen)
|
||||
- A [community-supported GitHub Action](https://github.com/lyqht/generate-supabase-db-types-github-action) to generate TypeScript types
|
||||
- Shell autocomplete: [`supabase completion`](https://supabase.com/docs/reference/cli/usage#supabase-completion)
|
||||
- A [community-supported Fig autocomplete spec](https://fig.io/manual/supabase) for macOS terminal
|
||||
|
||||
## Additional Links
|
||||
|
||||
- [Install the Supabase CLI](/docs/guides/cli)
|
||||
- [Source code](https://github.com/supabase/cli)
|
||||
- [Known bugs and issues](https://github.com/supabase/cli/issues)
|
||||
- [Supabase CLI v1 and Admin API Beta](https://supabase.com/blog/supabase-cli-v1-and-admin-api-beta)
|
||||
- [The CLI team announcing CLI V1 and Admin API Beta - Video](https://www.youtube.com/watch?v=OpPOaJI_Z28)
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
id: release-notes
|
||||
---
|
||||
|
||||
# Release Notes
|
||||
|
||||
All release notes can be found in the [GitHub Releases page](https://github.com/supabase/cli/releases).
|
||||
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
id: config
|
||||
slug: /config
|
||||
title: Configuration
|
||||
toc_max_heading_level: 3
|
||||
---
|
||||
|
||||
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY -->
|
||||
|
||||
A `config.toml` file is generated after running `supabase init`.
|
||||
|
||||
This file is located in the `supabase` folder under `supabase/config.toml`.
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY -->
|
||||
|
||||
## General {#general}
|
||||
|
||||
|
||||
|
||||
### `project_id` {#project_id}
|
||||
|
||||
A string used to distinguish different Supabase projects on the same host. Defaults to the working directory name when running `supabase init`.
|
||||
|
||||
|
||||
<ul>
|
||||
<li>Required: <code>true</code></li>
|
||||
<li>Default: <code>None</code></li>
|
||||
</ul>
|
||||
|
||||
<br />
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
slug: /
|
||||
sidebar_position: 1
|
||||
sidebar_label: Auth Server
|
||||
---
|
||||
|
||||
# Supabase Auth Server
|
||||
|
||||
The Supabase Auth Server (GoTrue) is a JSON Web Token (JWT)-based API for managing users and issuing access tokens.
|
||||
|
||||
GoTrue is an open-source API written in Golang, that acts as a self-standing API service for handling user registration and authentication for JAM projects. It's based on OAuth2 and JWT and handles user signup, authentication, and custom user data.
|
||||
|
||||
## Client libraries
|
||||
|
||||
- [JavaScript](https://github.com/supabase/gotrue-js)
|
||||
- [Dart](https://github.com/supabase/gotrue-dart)
|
||||
|
||||
## Additional Links
|
||||
|
||||
- [Source Code](https://github.com/supabase/gotrue)
|
||||
- [Known bugs and issues](https://github.com/supabase/gotrue/issues)
|
||||
- [Auth Guides](https://supabase.com/docs/guides/auth)
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
id: release-notes
|
||||
---
|
||||
|
||||
# Release Notes
|
||||
|
||||
All release notes can be found in the [GitHub Releases](https://github.com/supabase/gotrue/releases) page.
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
id: usage
|
||||
slug: /usage
|
||||
title: Usage
|
||||
toc_max_heading_level: 3
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
|
||||
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY IF THIS IS VERSION "next" -->
|
||||
|
||||
Documentation of the gotrue API.
|
||||
|
||||
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY IF THIS IS VERSION "next" -->
|
||||
|
||||
|
||||
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
slug: /
|
||||
sidebar_position: 1
|
||||
sidebar_label: Storage Server
|
||||
---
|
||||
|
||||
# Supabase Storage Server
|
||||
|
||||
An S3 compatible object storage service that integrates with Postgres.
|
||||
|
||||
- Uses Postgres as it's datastore for storing metadata
|
||||
- Authorization rules are written as Postgres Row Level Security policies
|
||||
- Integrates with S3 as the storage backend (with more in the pipeline!)
|
||||
- Extremely lightweight and performant
|
||||
|
||||
Read [this post](https://supabase.com/blog/supabase-storage) on why we decided to build a new object storage service.
|
||||
|
||||
## Client libraries
|
||||
|
||||
- [JavaScript](https://github.com/supabase/storage-js)
|
||||
- [Dart](https://github.com/supabase/storage-dart)
|
||||
|
||||
## Additional Links
|
||||
|
||||
- [Source Code](https://github.com/supabase/storage-api)
|
||||
- [Known bugs and issues](https://github.com/supabase/storage-js/issues)
|
||||
- [Storage Guides](https://supabase.com/docs/guides/storage)
|
||||
- [OpenAPI Docs](https://supabase.github.io/storage-api/)
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
id: release-notes
|
||||
---
|
||||
|
||||
# Release Notes
|
||||
|
||||
All release notes can be found in the [GitHub Releases](https://github.com/supabase/storage-api/releases) page.
|
||||
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
id: initializing
|
||||
title: 'Initializing'
|
||||
slug: initializing
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
## Flutter
|
||||
|
||||
For `supabase-flutter`, you will be using the static `initialize()` method on `Supabase` class.
|
||||
|
||||
### Flutter `initialize()`
|
||||
|
||||
```dart title="main.dart"
|
||||
Future<void> main() async {
|
||||
await Supabase.initialize(url: 'https://xyzcompany.supabase.co', anonKey: 'public-anon-key');
|
||||
runApp(MyApp());
|
||||
}
|
||||
```
|
||||
|
||||
### Access `SupabaseClient` instance
|
||||
|
||||
Once you initialize Supabase in your `main()` method, you can access the `SupabaseClient` instance from anywhere in your app.
|
||||
|
||||
```dart
|
||||
final supabase = Supabase.instance.client;
|
||||
```
|
||||
|
||||
## Other Dart Projects
|
||||
|
||||
You can initialize a new Supabase client using the `SupabaseClient()` method.
|
||||
|
||||
The Supabase client is your entrypoint to the rest of the Supabase functionality
|
||||
and is the easiest way to interact with everything we offer within the Supabase ecosystem.
|
||||
|
||||
### Dart `SupabaseClient()`
|
||||
|
||||
```dart
|
||||
final supabase = SupabaseClient('https://xyzcompany.supabase.co', 'public-anon-key');
|
||||
```
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
id: installing
|
||||
title: 'Installing'
|
||||
slug: installing
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
## Flutter
|
||||
|
||||
For Flutter project, you can use [supabase_flutter](https://pub.dev/packages/supabase_flutter) package.
|
||||
|
||||
```bash
|
||||
flutter pub add supabase_flutter
|
||||
```
|
||||
|
||||
## Other Dart projects
|
||||
|
||||
For non-Flutter projects using Dart, such as server-side Dart or Angular-Dart, you can use the [supabase](https://pub.dev/packages/supabase) package.
|
||||
|
||||
```bash
|
||||
dart pub add supabase
|
||||
```
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
id: intro
|
||||
slug: /
|
||||
sidebar_label: Supabase Flutter Library
|
||||
---
|
||||
|
||||
# Supabase Flutter Library
|
||||
|
||||
This reference documents every object and method available in Supabase's isomorphic Flutter library, [`supabase-flutter`](https://pub.dev/packages/supabase_flutter).
|
||||
|
||||
You can use the `supabase-flutter` library to:
|
||||
|
||||
- interact with your Postgres database
|
||||
- listen to database changes
|
||||
- invoke Deno Edge Functions
|
||||
- build login and user management functionality
|
||||
- manage large files
|
||||
|
||||
## For non-Flutter projects
|
||||
|
||||
We also have [supabase-dart](https://github.com/supabase-community/supabase-dart) for non-Flutter Dart projects, such as server-side Dart or Angular-Dart.
|
||||
supabase-dart shares most of the APIs with supabase-flutter without being dependent on Flutter so that you can use Supabase anywhere you can run Dart!
|
||||
|
||||
## Additional Links
|
||||
|
||||
- Source Code: [github.com/supabase/supabase-flutter](https://github.com/supabase/supabase-flutter)
|
||||
- [Known bugs and issues](https://github.com/supabase/supabase-flutter/issues)
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
id: intro
|
||||
slug: /
|
||||
sidebar_label: Supabase Dart Library
|
||||
---
|
||||
|
||||
# Supabase Dart Library
|
||||
|
||||
:::caution
|
||||
You're viewing the docs for an older version of the `supabase-flutter` library.
|
||||
:::
|
||||
|
||||
|
||||
This reference documents every object and method available in Supabase's isomorphic Dart library, `supabase-dart`.
|
||||
|
||||
You can use the `supabase-dart` library to:
|
||||
|
||||
- interact with your Postgres database
|
||||
- listen to database changes
|
||||
- invoke Deno Edge Functions
|
||||
- build login and user management functionality
|
||||
- manage large files
|
||||
|
||||
## Additional Links
|
||||
|
||||
- Source Code: [github.com/supabase/supabase-dart](https://github.com/supabase/supabase-dart)
|
||||
- [Known bugs and issues](https://github.com/supabase/supabase-dart/issues)
|
||||
@@ -0,0 +1,120 @@
|
||||
{
|
||||
"sidebar": [
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Getting Started",
|
||||
"items": ["intro", "generated/installing", "generated/initializing"],
|
||||
"collapsed": true
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Auth",
|
||||
"items": [
|
||||
"generated/auth-signup",
|
||||
"generated/auth-signin",
|
||||
"generated/auth-signinwithprovider",
|
||||
"generated/auth-signout",
|
||||
"generated/auth-session",
|
||||
"generated/auth-user",
|
||||
"generated/auth-update",
|
||||
"generated/auth-onauthstatechange",
|
||||
"generated/reset-password-email"
|
||||
],
|
||||
"collapsed": true
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Functions",
|
||||
"items": ["generated/invoke"],
|
||||
"collapsed": true
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Database",
|
||||
"items": [
|
||||
"generated/select",
|
||||
"generated/insert",
|
||||
"generated/update",
|
||||
"generated/upsert",
|
||||
"generated/delete",
|
||||
"generated/rpc"
|
||||
],
|
||||
"collapsed": true
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Realtime",
|
||||
"items": [
|
||||
"generated/subscribe",
|
||||
"generated/removesubscription",
|
||||
"generated/getsubscriptions",
|
||||
"generated/stream"
|
||||
],
|
||||
"collapsed": true
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Storage",
|
||||
"items": [
|
||||
"generated/storage-createbucket",
|
||||
"generated/storage-getbucket",
|
||||
"generated/storage-listbuckets",
|
||||
"generated/storage-updatebucket",
|
||||
"generated/storage-deletebucket",
|
||||
"generated/storage-emptybucket",
|
||||
"generated/storage-from-upload",
|
||||
"generated/storage-from-download",
|
||||
"generated/storage-from-list",
|
||||
"generated/storage-from-update",
|
||||
"generated/storage-from-move",
|
||||
"generated/storage-from-remove",
|
||||
"generated/storage-from-createsignedurl",
|
||||
"generated/storage-from-getpublicurl"
|
||||
],
|
||||
"collapsed": true
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Modifiers",
|
||||
"items": [
|
||||
"generated/using-modifiers",
|
||||
"generated/limit",
|
||||
"generated/order",
|
||||
"generated/range",
|
||||
"generated/single"
|
||||
],
|
||||
"collapsed": true
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Filters",
|
||||
"items": [
|
||||
"generated/using-filters",
|
||||
"generated/or",
|
||||
"generated/not",
|
||||
"generated/match",
|
||||
"generated/eq",
|
||||
"generated/neq",
|
||||
"generated/gt",
|
||||
"generated/gte",
|
||||
"generated/lt",
|
||||
"generated/lte",
|
||||
"generated/like",
|
||||
"generated/ilike",
|
||||
"generated/is_",
|
||||
"generated/in_",
|
||||
"generated/contains",
|
||||
"generated/containedby",
|
||||
"generated/rangelt",
|
||||
"generated/rangegt",
|
||||
"generated/rangegte",
|
||||
"generated/rangelte",
|
||||
"generated/rangeadjacent",
|
||||
"generated/overlaps",
|
||||
"generated/textsearch",
|
||||
"generated/filter"
|
||||
],
|
||||
"collapsed": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
["v0"]
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
id: installing
|
||||
title: 'Installing'
|
||||
slug: installing
|
||||
custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supabase.yml
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
All JavaScript libraries are built directly by the Supabase team.
|
||||
|
||||
Other languages are built by the community and supported by Supabase.
|
||||
|
||||
## JavaScript
|
||||
|
||||
Via NPM
|
||||
|
||||
```bash
|
||||
npm install @supabase/supabase-js
|
||||
```
|
||||
|
||||
Via Yarn
|
||||
|
||||
```bash
|
||||
yarn add @supabase/supabase-js
|
||||
```
|
||||
|
||||
Find the source code on [GitHub](https://github.com/supabase/supabase-js).
|
||||
|
||||
Or via CDN
|
||||
|
||||
```js
|
||||
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
|
||||
//or
|
||||
<script src="https://unpkg.com/@supabase/supabase-js@2"></script>
|
||||
```
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
slug: /
|
||||
sidebar_position: 1
|
||||
sidebar_label: Supabase JavaScript Library
|
||||
hide_table_of_contents: true
|
||||
---
|
||||
|
||||
# Supabase JavaScript Library
|
||||
|
||||
This reference documents every object and method available in Supabase's isomorphic JavaScript library, `supabase-js`.
|
||||
|
||||
You can use the `supabase-js` library to:
|
||||
|
||||
- interact with your Postgres database
|
||||
- listen to database changes
|
||||
- invoke Deno Edge Functions
|
||||
- build login and user management functionality
|
||||
- manage large files
|
||||
|
||||
## Additional Links
|
||||
|
||||
- Source Code: [github.com/supabase/supabase-js](https://github.com/supabase/supabase-js)
|
||||
- TypeDoc: [supabase.github.io/supabase-js](https://supabase.github.io/supabase-js/v2/)
|
||||
- NPM: [npmjs.com/package/@supabase/supabase-js](https://www.npmjs.com/package/@supabase/supabase-js)
|
||||
- [Known bugs and issues](https://github.com/supabase/supabase-js/issues)
|
||||
@@ -0,0 +1,211 @@
|
||||
---
|
||||
id: release-notes
|
||||
---
|
||||
|
||||
# Release Notes
|
||||
|
||||
Supabase.js v2 release notes.
|
||||
|
||||
## 2.0.0
|
||||
|
||||
Install the latest with `npm install @supabase/supabase-js`.
|
||||
|
||||
### Explicit constructor options
|
||||
|
||||
All client specific options within the constructor are keyed to the library: [PR](https://github.com/supabase/supabase-js/pull/458):
|
||||
|
||||
```jsx
|
||||
const supabase = createClient(apiURL, apiKey, {
|
||||
db: {
|
||||
schema: 'public',
|
||||
},
|
||||
auth: {
|
||||
storage: AsyncStorage,
|
||||
autoRefreshToken: true,
|
||||
persistSession: true,
|
||||
detectSessionInUrl: true,
|
||||
},
|
||||
realtime: {
|
||||
channels,
|
||||
endpoint,
|
||||
},
|
||||
global: {
|
||||
fetch: customFetch,
|
||||
headers: DEFAULT_HEADERS,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Typescript support
|
||||
|
||||
The libraries now support typescript.
|
||||
|
||||
```ts
|
||||
// v2 - definitions are injected in `createClient()`
|
||||
import type { Database } from './DatabaseDefinitions'
|
||||
const supabase = createClient<Database>(SUPABASE_URL, ANON_KEY)
|
||||
const { data } = await supabase.from('messages').select().match({ id: 1 })
|
||||
|
||||
// v1 -- previously definitions were injected in the `from()` method
|
||||
supabase.from<Definitions['Message']>('messages').select('*')
|
||||
```
|
||||
|
||||
Types can be generated via the CLI:
|
||||
|
||||
```bash
|
||||
supabase start
|
||||
supabase gen types typescript --local > DatabaseDefinitions.ts
|
||||
```
|
||||
|
||||
### Data operations return minimal
|
||||
|
||||
`.insert()` / `.upsert()` / `.update()` / `.delete()` don't return rows by default: [PR](https://github.com/supabase/postgrest-js/pull/276).
|
||||
|
||||
Previously, these methods return inserted/updated/deleted rows by default (which caused [some confusion](https://github.com/supabase/supabase/discussions/1548)), and you can opt to not return it by specifying `returning: 'minimal'`. Now the default behavior is to not return rows. To return inserted/updated/deleted rows, add a `.select()` call at the end, e.g.:
|
||||
|
||||
```sql
|
||||
const { data, error } = await supabase
|
||||
.from('my_table')
|
||||
.delete()
|
||||
.eq('id', 1)
|
||||
.select()
|
||||
```
|
||||
|
||||
### New ordering defaults
|
||||
|
||||
`.order()` now defaults to Postgres’s default: [PR](https://github.com/supabase/postgrest-js/pull/283).
|
||||
|
||||
Previously `nullsFirst` defaults to `false` , meaning `null`s are ordered last. This is bad for performance if e.g. the column uses an index with `NULLS FIRST` (which is the default direction for indexes).
|
||||
|
||||
### Cookies and localstorage namespace
|
||||
|
||||
Storage key name in the Auth library has changed to include project reference which means that existing websites that had their JWT expiry set to a longer time could find their users logged out with this upgrade.
|
||||
|
||||
```jsx
|
||||
const defaultStorageKey = `sb-${new URL(this.authUrl).hostname.split('.')[0]}-auth-token`
|
||||
```
|
||||
|
||||
### New Auth Types
|
||||
|
||||
Typescript typings have been reworked. `Session` interface now guarantees that it will always have an `access_token`, `refresh_token` and `user`
|
||||
|
||||
```jsx
|
||||
interface Session {
|
||||
provider_token?: string | null
|
||||
access_token: string
|
||||
expires_in?: number
|
||||
expires_at?: number
|
||||
refresh_token: string
|
||||
token_type: string
|
||||
user: User
|
||||
}
|
||||
```
|
||||
|
||||
### New Auth methods
|
||||
|
||||
We're removing the `signIn()` method in favor of more explicit function signatures:
|
||||
`signInWithPassword()`, `signInWithPasswordless()`, and `signInWithOtp()`.
|
||||
|
||||
```ts
|
||||
// v2
|
||||
const { data } = await supabase.auth.signInWithPassword({
|
||||
email: 'hello@example',
|
||||
password: 'pass',
|
||||
})
|
||||
// v1
|
||||
const { data } = await supabase.auth.signIn({
|
||||
email: 'hello@example',
|
||||
password: 'pass',
|
||||
})
|
||||
```
|
||||
|
||||
### New Realtime methods
|
||||
|
||||
There is a new `channel()` method in the Realtime library, which will be used for our Multiplayer updates.
|
||||
|
||||
```ts
|
||||
supabaseClient
|
||||
.channel('any_string_you_want')
|
||||
.on('presence', { event: 'track' }, (payload) => {
|
||||
console.log(payload)
|
||||
})
|
||||
.subscribe()
|
||||
|
||||
supabaseClient
|
||||
.channel('any_string_you_want')
|
||||
.on(
|
||||
'postgres_changes',
|
||||
{
|
||||
event: 'INSERT',
|
||||
schema: 'public',
|
||||
table: 'movies',
|
||||
},
|
||||
(payload) => {
|
||||
console.log(payload)
|
||||
}
|
||||
)
|
||||
.subscribe()
|
||||
```
|
||||
|
||||
We will deprecate the `.from().on().subscribe()` method previosuly used for listening to postgres changes.
|
||||
|
||||
### Deprecated setAuth()
|
||||
|
||||
Deprecated and removed `setAuth()` . To set a custom `access_token` jwt instead, pass the custom header into the `createClient()` method provided: ([PR](https://github.com/supabase/gotrue-js/pull/340))
|
||||
|
||||
### All changes
|
||||
|
||||
- `supabase-js`
|
||||
- `shouldThrowOnError` has been removed until all the client libraries support this option ([PR](https://github.com/supabase/supabase-js/pull/490)).
|
||||
- `postgrest-js`
|
||||
- TypeScript typings have been reworked [PR](https://github.com/supabase/postgrest-js/pull/279)
|
||||
- Use `undefined` instead of `null` for function params, types, etc. (https://github.com/supabase/postgrest-js/pull/278)
|
||||
- Some features are now obsolete: (https://github.com/supabase/postgrest-js/pull/275)
|
||||
- filter shorthands (e.g. `cs` vs. `contains`)
|
||||
- `body` in response (vs. `data`)
|
||||
- `upsert`ing through the `.insert()` method
|
||||
- `auth` method on `PostgrestClient`
|
||||
- client-level `throwOnError`
|
||||
- `gotrue-js`
|
||||
- `supabase-js` client allows passing a `storageKey` param which will allow the user to set the key used in local storage for storing the session. By default, this will be namespace-d with the supabase project ref. ([PR](https://github.com/supabase/supabase-js/pull/460))
|
||||
- `signIn` method is now split into `signInWithPassword` , `signInWithPasswordless` , `signInWithOAuth` ([PR](https://github.com/supabase/gotrue-js/pull/304))
|
||||
- Deprecated and removed `session()` , `user()` in favour of using `getSession()` instead. `getSession()` will always return a valid session if a user is already logged in, meaning no more random logouts. ([PR](https://github.com/supabase/gotrue-js/pull/299))
|
||||
- Deprecated and removed setting for `multitab` support because `getSession()` and gotrue’s reuse interval setting takes care of session management across multiple tabs ([PR](https://github.com/supabase/gotrue-js/pull/366))
|
||||
- No more throwing of random errors, gotrue-js v2 always returns a custom error type: ([PR](https://github.com/supabase/gotrue-js/pull/341))
|
||||
- `AuthSessionMissingError`
|
||||
- Indicates that a session is expected but missing
|
||||
- `AuthNoCookieError`
|
||||
- Indicates that a cookie is expected but missing
|
||||
- `AuthInvalidCredentialsError`
|
||||
- Indicates that the incorrect credentials were passed
|
||||
- Renamed the `api` namespace to `admin` , the `admin` namespace will only contain methods that should only be used in a trusted server-side environment with the service role key
|
||||
- Moved `resetPasswordForEmail` , `getUser` and `updateUser` to the `GoTrueClient` which means they will be accessible from the `supabase.auth` namespace in `supabase-js` instead of having to do `supabase.auth.api` to access them
|
||||
- Removed `sendMobileOTP` , `sendMagicLinkEmail` in favor of `signInWithOtp`
|
||||
- Removed `signInWithEmail`, `signInWithPhone` in favor of `signInWithPassword`
|
||||
- Removed `signUpWithEmail` , `signUpWithPhone` in favor of `signUp`
|
||||
- Replaced `update` with `updateUser`
|
||||
- `storage-js`
|
||||
- Return types are more strict. Functions types used to indicate that the data returned could be null even if there was no error. We now make use of union types which only mark the data as null if there is an error and vice versa. ([PR](https://github.com/supabase/storage-js/pull/60))
|
||||
- The `upload` and `update` function returns the path of the object uploaded as the `path` parameter. Previously the returned value had the bucket name prepended to the path which made it harder to pass the value on to other storage-js methods since all methods take the bucket name and path separately. We also chose to call the returned value `path` instead of `Key` ([PR](https://github.com/supabase/storage-js/pull/75))
|
||||
- `getPublicURL` only returns the public URL inside the data object. This keeps it consistent with our other methods of returning only within the data object. No error is returned since this method cannot does not throw an error ([PR](https://github.com/supabase/storage-js/pull/93))
|
||||
- signed urls are returned as `signedUrl` instead of `signedURL` in both `createSignedUrl` and `createSignedUrls` ([PR](https://github.com/supabase/storage-js/pull/94))
|
||||
- Encodes URLs returned by `createSignedUrl`, `createSignedUrls` and `getPublicUrl` ([PR](https://github.com/supabase/storage-js/pull/86))
|
||||
- `createsignedUrl` used to return a url directly and and within the data object. This was inconsistent. Now we always return values only inside the data object across all methods. ([PR](https://www.notion.so/LW5-supabase-js-v2-7b0bfcdf571d4f20b9b7a9308883f24b))
|
||||
- `createBucket` returns a data object instead of the name of the bucket directly. ([PR](https://github.com/supabase/storage-js/pull/89))
|
||||
- Fixed types for metadata ([PR](https://github.com/supabase/storage-js/pull/90))
|
||||
- Better error types make it easier to track down what went wrong quicker.
|
||||
- `SupabaseStorageClient` is no longer exported. Use `StorageClient` instead. ([PR](https://github.com/supabase/storage-js/pull/92)).
|
||||
- `realtime-js`
|
||||
- `RealtimeSubscription` class no longer exists and replaced by `RealtimeChannel`.
|
||||
- `RealtimeClient`'s `disconnect` method now returns type of `void` . It used to return type of `Promise<{ error: Error | null; data: boolean }`.
|
||||
- Removed `removeAllSubscriptions` and `removeSubscription` methods from `SupabaseClient` class.
|
||||
- Removed `SupabaseRealtimeClient` class.
|
||||
- Removed `SupabaseQueryBuilder` class.
|
||||
- Removed `SupabaseEventTypes` type.
|
||||
- Thinking about renaming this to something like `RealtimePostgresChangeEvents` and moving it to `realtime-js` v2.
|
||||
- Removed `.from(’table’).on(’INSERT’, () ⇒ {}).subscribe()` in favor of new Realtime client API.
|
||||
- `functions-js`
|
||||
- supabase-js v1 only threw an error if the fetch call itself threw an error (network errors, etc) and not if the function returned HTTP errors like 400s or 500s. We have changed this behaviour to return an error if your function throws an error.
|
||||
- We have introduced new error types to distinguish between different kinds of errors. A `FunctionsHttpError` error is returned if your function throws an error, `FunctionsRelayError` if the Supabase Relay has an error processing your function and `FunctionsFetchError` if there is a network error in calling your function.
|
||||
- The correct content-type headers are automatically attached when sending the request if you don’t pass in a `Content-Type` header and pass in an argument to your function. We automatically attach the content type for `Blob`, `ArrayBuffer`, `File`, `FormData` ,`String` . If it doesn’t match any of these we assume the payload is `json` , we serialise the payload as JSON and attach the content type as `application/json`.
|
||||
- `responseType` does not need to be explicitly passed in. We parse the response based on the `Content-Type` response header sent by the function. We support parsing the responses as `text`, `json`, `blob`, `form-data` and are parsed as `text` by default.
|
||||
@@ -0,0 +1,461 @@
|
||||
---
|
||||
id: upgrade-guide
|
||||
title: Upgrade to supabase-js v2
|
||||
description: 'Learn how to upgrade to supabase-js v2.'
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
supabase-js v2 focuses on "quality-of-life" improvements for developers and addresses some of the largest pain points in v1. v2 includes type support, a rebuilt Auth library with async methods, improved errors, and more.
|
||||
|
||||
No new features will be added to supabase-js v1 , but we'll continuing merging security fixes to v1, with maintenance patches for the next 3 months.
|
||||
|
||||
## Upgrade the client library
|
||||
|
||||
```sh
|
||||
npm install @supabase/supabase-js@2
|
||||
```
|
||||
|
||||
_Optionally_ if you are using custom configuration with `createClient` then follow below:
|
||||
|
||||
<Tabs
|
||||
groupId="version"
|
||||
values={[
|
||||
{label: 'Before', value: '1.x'},
|
||||
{label: 'After', value: '2.x'},
|
||||
]}>
|
||||
|
||||
<TabItem value="1.x">
|
||||
|
||||
```ts title="src/supabaseClient.ts"
|
||||
const supabaseClient = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
|
||||
schema: 'custom',
|
||||
persistSession: false,
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="2.x">
|
||||
|
||||
```ts title="src/supabaseClient.ts"
|
||||
const supabaseClient = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
|
||||
db: {
|
||||
schema: 'custom',
|
||||
},
|
||||
auth: {
|
||||
persistSession: true,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Read more about the [constructor options](/docs/reference/javascript/release-notes#explicit-constructor-options).
|
||||
|
||||
### Auth methods
|
||||
|
||||
The signIn() method has been deprecated in favor of more explicit method signatures to help with type hinting. Previously it was difficult for developers to know what they were missing (e.g., a lot of developers didn't realize they could use passwordless magic links).
|
||||
|
||||
#### Sign in with email and password
|
||||
|
||||
<Tabs
|
||||
groupId="version"
|
||||
values={[
|
||||
{label: 'Before', value: '1.x'},
|
||||
{label: 'After', value: '2.x'},
|
||||
]}>
|
||||
|
||||
<TabItem value="1.x">
|
||||
|
||||
```ts
|
||||
const { user, error } = await supabase.auth.signIn({ email, password })
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="2.x">
|
||||
|
||||
```ts
|
||||
const {
|
||||
data: { user },
|
||||
error,
|
||||
} = await supabase.auth.signInWithPassword({ email, password })
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
#### Sign in with magic link
|
||||
|
||||
<Tabs
|
||||
groupId="version"
|
||||
values={[
|
||||
{label: 'Before', value: '1.x'},
|
||||
{label: 'After', value: '2.x'},
|
||||
]}>
|
||||
|
||||
<TabItem value="1.x">
|
||||
|
||||
```ts
|
||||
const { error } = await supabase.auth.signIn({ email })
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="2.x">
|
||||
|
||||
```ts
|
||||
const { error } = await supabase.auth.signInWithOtp({ email })
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
#### Sign in with a third-party provider
|
||||
|
||||
<Tabs
|
||||
groupId="version"
|
||||
values={[
|
||||
{label: 'Before', value: '1.x'},
|
||||
{label: 'After', value: '2.x'},
|
||||
]}>
|
||||
|
||||
<TabItem value="1.x">
|
||||
|
||||
```ts
|
||||
const { error } = await supabase.auth.signIn({ provider })
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="2.x">
|
||||
|
||||
```ts
|
||||
const { error } = await supabase.auth.signInWithOAuth({ provider })
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
#### Sign in with phone
|
||||
|
||||
<Tabs
|
||||
groupId="version"
|
||||
values={[
|
||||
{label: 'Before', value: '1.x'},
|
||||
{label: 'After', value: '2.x'},
|
||||
]}>
|
||||
|
||||
<TabItem value="1.x">
|
||||
|
||||
```ts
|
||||
const { error } = await supabase.auth.signIn({ phone, password })
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="2.x">
|
||||
|
||||
```ts
|
||||
const { error } = await supabase.auth.signInWithPassword({ phone, password })
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
#### Sign in with phone using OTP
|
||||
|
||||
<Tabs
|
||||
groupId="version"
|
||||
values={[
|
||||
{label: 'Before', value: '1.x'},
|
||||
{label: 'After', value: '2.x'},
|
||||
]}>
|
||||
|
||||
<TabItem value="1.x">
|
||||
|
||||
```ts
|
||||
const { error } = await supabase.auth.api.sendMobileOTP(phone)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="2.x">
|
||||
|
||||
```ts
|
||||
const { data, error } = await supabase.auth.signInWithOtp({ phone })
|
||||
|
||||
// After receiving a SMS with a OTP.
|
||||
const { data, error } = await supabase.auth.verifyOtp({ phone, token })
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
#### Reset password for email
|
||||
|
||||
<Tabs
|
||||
groupId="version"
|
||||
values={[
|
||||
{label: 'Before', value: '1.x'},
|
||||
{label: 'After', value: '2.x'},
|
||||
]}>
|
||||
|
||||
<TabItem value="1.x">
|
||||
|
||||
```ts
|
||||
const { data, error } = await supabase.auth.api.resetPasswordForEmail(email)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="2.x">
|
||||
|
||||
```ts
|
||||
const { data, error } = await supabase.auth.resetPasswordForEmail(email)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
#### Get the user's current session
|
||||
|
||||
<Tabs
|
||||
groupId="version"
|
||||
values={[
|
||||
{label: 'Before', value: '1.x'},
|
||||
{label: 'After', value: '2.x'},
|
||||
]}>
|
||||
|
||||
<TabItem value="1.x">
|
||||
|
||||
```ts
|
||||
const session = supabase.auth.session()
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="2.x">
|
||||
|
||||
```ts
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession()
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
#### Get the logged-in user
|
||||
|
||||
<Tabs
|
||||
groupId="version"
|
||||
values={[
|
||||
{label: 'Before', value: '1.x'},
|
||||
{label: 'After', value: '2.x'},
|
||||
]}>
|
||||
|
||||
<TabItem value="1.x">
|
||||
|
||||
```ts
|
||||
const user = supabase.auth.user()
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="2.x">
|
||||
|
||||
```ts
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession()
|
||||
const { user } = session
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
#### Update user data for a logged-in user
|
||||
|
||||
<Tabs
|
||||
groupId="version"
|
||||
values={[
|
||||
{label: 'Before', value: '1.x'},
|
||||
{label: 'After', value: '2.x'},
|
||||
]}>
|
||||
|
||||
<TabItem value="1.x">
|
||||
|
||||
```ts
|
||||
const { user, error } = await supabase.auth.update({ attributes })
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="2.x">
|
||||
|
||||
```ts
|
||||
const {
|
||||
data: { user },
|
||||
error,
|
||||
} = await supabase.auth.updateUser({ attributes })
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
#### Use a custom `access_token` JWT with Supabase
|
||||
|
||||
<Tabs
|
||||
groupId="version"
|
||||
values={[
|
||||
{label: 'Before', value: '1.x'},
|
||||
{label: 'After', value: '2.x'},
|
||||
]}>
|
||||
|
||||
<TabItem value="1.x">
|
||||
|
||||
```ts
|
||||
const { user, error } = supabase.auth.setAuth(access_token)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="2.x">
|
||||
|
||||
```ts
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
|
||||
global: {
|
||||
headers: {
|
||||
Authorization: `Bearer ${access_token}`,
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Data methods
|
||||
|
||||
`.insert()` / `.upsert()` / `.update()` / `.delete()` don't return rows by default: [PR](https://github.com/supabase/postgrest-js/pull/276).
|
||||
|
||||
Previously, these methods return inserted/updated/deleted rows by default (which caused [some confusion](https://github.com/supabase/supabase/discussions/1548)), and you can opt to not return it by specifying `returning: 'minimal'`. Now the default behavior is to not return rows. To return inserted/updated/deleted rows, add a `.select()` call at the end.
|
||||
|
||||
#### Insert and return data
|
||||
|
||||
<Tabs
|
||||
groupId="version"
|
||||
values={[
|
||||
{label: 'Before', value: '1.x'},
|
||||
{label: 'After', value: '2.x'},
|
||||
]}>
|
||||
|
||||
<TabItem value="1.x">
|
||||
|
||||
```ts
|
||||
const { data, error } = await supabase.auth.insert({ new_data })
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="2.x">
|
||||
|
||||
```ts
|
||||
const { data, error } = await supabase.auth.insert({ new_data }).select()
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
#### Update and return data
|
||||
|
||||
<Tabs
|
||||
groupId="version"
|
||||
values={[
|
||||
{label: 'Before', value: '1.x'},
|
||||
{label: 'After', value: '2.x'},
|
||||
]}>
|
||||
|
||||
<TabItem value="1.x">
|
||||
|
||||
```ts
|
||||
const { data, error } = await supabase.auth.update({ new_data }).eq('id', id)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="2.x">
|
||||
|
||||
```ts
|
||||
const { data, error } = await supabase.auth.update({ new_data }).eq('id', id).select()
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Realtime methods
|
||||
|
||||
#### Subscribe
|
||||
|
||||
<Tabs
|
||||
groupId="version"
|
||||
values={[
|
||||
{label: 'Before', value: '1.x'},
|
||||
{label: 'After', value: '2.x'},
|
||||
]}>
|
||||
|
||||
<TabItem value="1.x">
|
||||
|
||||
```ts
|
||||
const userListener = supabase
|
||||
.from('users')
|
||||
.on('*', (payload) => handleAllEventsPayload(payload.new))
|
||||
.subscribe()
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="2.x">
|
||||
|
||||
```ts
|
||||
const userListener = supabase.channel('public:user')
|
||||
.on(
|
||||
'postgres_changes',
|
||||
{ event: '*', schema: 'public', table: 'user' }
|
||||
(payload) => handleAllEventsPayload()
|
||||
)
|
||||
.subscribe()
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
#### Unsubscribe
|
||||
|
||||
<Tabs
|
||||
groupId="version"
|
||||
values={[
|
||||
{label: 'Before', value: '1.x'},
|
||||
{label: 'After', value: '2.x'},
|
||||
]}>
|
||||
|
||||
<TabItem value="1.x">
|
||||
|
||||
```ts
|
||||
userListener.unsubscribe()
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="2.x">
|
||||
|
||||
```ts
|
||||
supabase.removeChannel('public:users')
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
@@ -0,0 +1,81 @@
|
||||
---
|
||||
id: typescript-support
|
||||
---
|
||||
|
||||
# Typescript Support
|
||||
|
||||
`supabase-js` supports Typescript.
|
||||
|
||||
## Generating types
|
||||
|
||||
You can use our CLI to generate types:
|
||||
|
||||
```bash
|
||||
supabase start
|
||||
supabase gen types typescript --local > lib/database.types.ts
|
||||
```
|
||||
|
||||
These types are generated directly from your database. Given a table `public.movies`, the definition will provide the following data:
|
||||
|
||||
```ts
|
||||
interface Database {
|
||||
public: {
|
||||
Tables: {
|
||||
movies: {
|
||||
Row: {} // The data expected to be returned from a "select" statement.
|
||||
Insert: {} // The data expected passed to an "insert" statement.
|
||||
Update: {} // The data expected passed to an "update" statement.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There is a difference between `selects`, `inserts`, and `updates`, because often you will set default values in your database for specific columns.
|
||||
With default values you do not need to send any data over the network, even if that column is a "required" field. Our type system is granular
|
||||
enough to handle these situations.
|
||||
|
||||
## Injecting type definitions
|
||||
|
||||
You can enrich the supabase client with the types you generated with Supabase.
|
||||
|
||||
```ts
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
import { Database } from 'lib/database.types'
|
||||
|
||||
const supabase = createClient<Database>(process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY)
|
||||
```
|
||||
|
||||
## Type hints
|
||||
|
||||
`supabase-js` always returns a `data` object (for success), and an `error` response (for unsuccessful requests).
|
||||
This provides a simple interface to get the relevant types returned from any function:
|
||||
|
||||
```ts
|
||||
export async function getMovies() {
|
||||
return await supabase.from('movies').select(`id, title`)
|
||||
}
|
||||
|
||||
type MoviesResponse = Awaited<ReturnType<typeof getMovies>>
|
||||
export type MoviesResponseSuccess = MoviesResponse['data']
|
||||
export type MoviesResponseError = MoviesResponse['error']
|
||||
```
|
||||
|
||||
## Nested tables
|
||||
|
||||
For advanced queries such as nested tables, you may want to construct your own types.
|
||||
|
||||
```ts
|
||||
import supabase from '~/lib/supabase'
|
||||
import type { Database } from '~/lib/database.types'
|
||||
|
||||
async function getMovies() {
|
||||
return await supabase.from('movies').select('id, title, actors(*)')
|
||||
}
|
||||
|
||||
type Actors = Database['public']['Tables']['actors']['Row']
|
||||
type MoviesResponse = Awaited<ReturnType<typeof getMovies>>
|
||||
type MoviesResponseSuccess = MoviesResponse['data'] & {
|
||||
actors: Actors[]
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
id: generating-types
|
||||
title: 'Generating Types'
|
||||
slug: generating-types
|
||||
custom_edit_url: https://github.com/supabase/supabase/edit/master/spec/supabase_js_v1.yml
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
Supabase will soon release native type generators that dump your database types for various languages. For now, we support TypeScript [through third-party tools](/docs/guides/api/generating-types).
|
||||
|
||||
## Usage with TypeScript
|
||||
|
||||
`supabase-js` ships with type definitions for usage with TypeScript and for convenient IntelliSense auto-complete and documentation in your editor.
|
||||
|
||||
When using TypeScript, you can pass the type of database row as a type parameter to the `from` method to get better auto-completion support down the chain.
|
||||
If you don't provide a type for the row you need to explicitly pass `from<any>('tableName')`.
|
||||
|
||||
```ts
|
||||
type Message = {
|
||||
id: number
|
||||
inserted_at: string
|
||||
message: string
|
||||
user_id: string
|
||||
channel_id: number
|
||||
author: { username: string }
|
||||
}
|
||||
|
||||
const response = await supabase
|
||||
.from<Message>('messages') // Message maps to the type of the row in your database.
|
||||
.select('*, author:user_id(username)')
|
||||
.match({ channel_id: 2 }) // Your IDE will be able to help with auto-completion.
|
||||
response.data // Response data will be of type Array<Message>.
|
||||
|
||||
// If you don't provide a type for the row you need to explicitly pass `from<any>('tableName')`.
|
||||
const response = await supabase
|
||||
.from<any>('messages')
|
||||
.select('*, author:user_id(username)')
|
||||
.match({ channel_id: 2 })
|
||||
response.data // Response data will be of type Array<any>.
|
||||
```
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
id: index
|
||||
title: 'Supabase Client'
|
||||
slug: supabase-client
|
||||
custom_edit_url: ../../spec/supabase_js_v1.yml
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
Supabase JavaScript.
|
||||
@@ -0,0 +1,151 @@
|
||||
---
|
||||
id: initializing
|
||||
title: 'Initializing'
|
||||
slug: initializing
|
||||
custom_edit_url: https://github.com/supabase/supabase/edit/master/spec/supabase_js_v1.yml
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
You can initialize a new Supabase client using the `createClient()` method.
|
||||
|
||||
The Supabase client is your entrypoint to the rest of the Supabase functionality
|
||||
and is the easiest way to interact with everything we offer within the Supabase ecosystem.
|
||||
|
||||
## Parameters
|
||||
|
||||
<ul className="method-list-group">
|
||||
|
||||
<li className="method-list-item">
|
||||
<h4 className="method-list-item-label">
|
||||
<span className="method-list-item-label-name">
|
||||
supabaseUrl
|
||||
</span>
|
||||
<span className="method-list-item-label-badge required">
|
||||
required
|
||||
</span>
|
||||
<span className="method-list-item-validation">
|
||||
<code>string</code>
|
||||
</span>
|
||||
</h4>
|
||||
<div class="method-list-item-description">
|
||||
|
||||
The unique Supabase URL which is supplied when you create a new project in your project dashboard.
|
||||
|
||||
</div>
|
||||
|
||||
</li>
|
||||
|
||||
<li className="method-list-item">
|
||||
<h4 className="method-list-item-label">
|
||||
<span className="method-list-item-label-name">
|
||||
supabaseKey
|
||||
</span>
|
||||
<span className="method-list-item-label-badge required">
|
||||
required
|
||||
</span>
|
||||
<span className="method-list-item-validation">
|
||||
<code>string</code>
|
||||
</span>
|
||||
</h4>
|
||||
<div class="method-list-item-description">
|
||||
|
||||
The unique Supabase Key which is supplied when you create a new project in your project dashboard.
|
||||
|
||||
</div>
|
||||
|
||||
</li>
|
||||
|
||||
<li className="method-list-item">
|
||||
<h4 className="method-list-item-label">
|
||||
<span className="method-list-item-label-name">
|
||||
options
|
||||
</span>
|
||||
<span className="method-list-item-label-badge false">
|
||||
optional
|
||||
</span>
|
||||
<span className="method-list-item-validation">
|
||||
<code>SupabaseClientOptions</code>
|
||||
</span>
|
||||
</h4>
|
||||
<div class="method-list-item-description">
|
||||
|
||||
No description provided.
|
||||
|
||||
</div>
|
||||
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
## Examples
|
||||
|
||||
### createClient()
|
||||
|
||||
```js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
// Create a single supabase client for interacting with your database
|
||||
const supabase = createClient(
|
||||
'https://xyzcompany.supabase.co',
|
||||
'public-anon-key'
|
||||
)
|
||||
```
|
||||
|
||||
### With additional parameters
|
||||
|
||||
```js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
const options = {
|
||||
schema: 'public',
|
||||
headers: { 'x-my-custom-header': 'my-app-name' },
|
||||
autoRefreshToken: true,
|
||||
persistSession: true,
|
||||
detectSessionInUrl: true,
|
||||
}
|
||||
const supabase = createClient(
|
||||
'https://xyzcompany.supabase.co',
|
||||
'public-anon-key',
|
||||
options
|
||||
)
|
||||
```
|
||||
|
||||
### API schemas
|
||||
|
||||
```js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
// Provide a custom schema. Defaults to "public".
|
||||
const supabase = createClient(
|
||||
'https://xyzcompany.supabase.co',
|
||||
'public-anon-key',
|
||||
{
|
||||
schema: 'other_schema',
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
By default the API server points to the `public` schema. You can enable other database schemas within the Dashboard.
|
||||
Go to `Settings > API > Schema` and add the schema which you want to expose to the API.
|
||||
|
||||
Note: each client connection can only access a single schema, so the code above can access the `other_schema` schema but cannot access the `public` schema.
|
||||
|
||||
### Custom `fetch` implementation
|
||||
|
||||
```js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
const supabase = createClient(
|
||||
'https://xyzcompany.supabase.co',
|
||||
'public-anon-key',
|
||||
{
|
||||
fetch: fetch.bind(globalThis),
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
`supabase-js` uses the [`cross-fetch`](https://www.npmjs.com/package/cross-fetch) library to make HTTP requests,
|
||||
but an alternative `fetch` implementation can be provided as an option.
|
||||
This is most useful in environments where `cross-fetch` is not compatible (for instance Cloudflare Workers).
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
id: installing
|
||||
title: 'Installing'
|
||||
slug: installing
|
||||
custom_edit_url: https://github.com/supabase/supabase/edit/master/spec/supabase_js_v1.yml
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
All JavaScript libraries are built directly by the Supabase team.
|
||||
|
||||
Other languages are built by the community and supported by Supabase.
|
||||
|
||||
## JavaScript
|
||||
|
||||
Via NPM
|
||||
|
||||
```bash
|
||||
npm install @supabase/supabase-js
|
||||
```
|
||||
|
||||
Via Yarn
|
||||
|
||||
```bash
|
||||
yarn add @supabase/supabase-js
|
||||
```
|
||||
|
||||
Find the source code on [GitHub](https://github.com/supabase/supabase-js).
|
||||
|
||||
Or via CDN
|
||||
|
||||
```js
|
||||
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@1"></script>
|
||||
//or
|
||||
<script src="https://unpkg.com/@supabase/supabase-js@1"></script>
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
id: intro
|
||||
title: 'Supabase JavaScript Library'
|
||||
slug: /
|
||||
sidebar_label: Supabase JavaScript Library
|
||||
---
|
||||
|
||||
:::caution
|
||||
You're viewing the docs for an older version of the `supabase-js` library.
|
||||
:::
|
||||
|
||||
This reference documents every object and method available in Supabase's isomorphic JavaScript library, `supabase-js`.
|
||||
|
||||
You can use the `supabase-js` library to:
|
||||
|
||||
- interact with your Postgres database
|
||||
- listen to database changes
|
||||
- invoke Deno Edge Functions
|
||||
- build login and user management functionality
|
||||
- manage large files
|
||||
Whitespace-only changes.
@@ -0,0 +1,138 @@
|
||||
{
|
||||
"sidebar": [
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Getting Started",
|
||||
"items": ["intro", "installing", "initializing", "generating-types"],
|
||||
"collapsed": false
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Auth",
|
||||
"items": [
|
||||
"generated/auth-signup",
|
||||
"generated/auth-signin",
|
||||
"generated/auth-signout",
|
||||
"generated/auth-session",
|
||||
"generated/auth-user",
|
||||
"generated/auth-update",
|
||||
"generated/auth-setauth",
|
||||
"generated/auth-onauthstatechange",
|
||||
"generated/auth-api-getuser",
|
||||
"generated/auth-api-resetpasswordforemail"
|
||||
],
|
||||
"collapsed": true
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Auth (Server Only)",
|
||||
"items": [
|
||||
"generated/auth-api-listusers",
|
||||
"generated/auth-api-createuser",
|
||||
"generated/auth-api-deleteuser",
|
||||
"generated/auth-api-generatelink",
|
||||
"generated/auth-api-inviteuserbyemail",
|
||||
"generated/auth-api-sendmobileotp",
|
||||
"generated/auth-api-updateuserbyid"
|
||||
],
|
||||
"collapsed": true
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Functions",
|
||||
"items": ["generated/invoke"],
|
||||
"collapsed": true
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Database",
|
||||
"items": [
|
||||
"generated/select",
|
||||
"generated/insert",
|
||||
"generated/update",
|
||||
"generated/upsert",
|
||||
"generated/delete",
|
||||
"generated/rpc"
|
||||
],
|
||||
"collapsed": true
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Realtime",
|
||||
"items": [
|
||||
"generated/subscribe",
|
||||
"generated/removesubscription",
|
||||
"generated/removeallsubscriptions",
|
||||
"generated/getsubscriptions"
|
||||
],
|
||||
"collapsed": true
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Storage",
|
||||
"items": [
|
||||
"generated/storage-createbucket",
|
||||
"generated/storage-getbucket",
|
||||
"generated/storage-listbuckets",
|
||||
"generated/storage-updatebucket",
|
||||
"generated/storage-deletebucket",
|
||||
"generated/storage-emptybucket",
|
||||
"generated/storage-from-upload",
|
||||
"generated/storage-from-download",
|
||||
"generated/storage-from-list",
|
||||
"generated/storage-from-update",
|
||||
"generated/storage-from-move",
|
||||
"generated/storage-from-copy",
|
||||
"generated/storage-from-remove",
|
||||
"generated/storage-from-createsignedurl",
|
||||
"generated/storage-from-createsignedurls",
|
||||
"generated/storage-from-getpublicurl"
|
||||
],
|
||||
"collapsed": true
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Modifiers",
|
||||
"items": [
|
||||
"generated/using-modifiers",
|
||||
"generated/limit",
|
||||
"generated/order",
|
||||
"generated/range",
|
||||
"generated/single",
|
||||
"generated/maybesingle"
|
||||
],
|
||||
"collapsed": true
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Filters",
|
||||
"items": [
|
||||
"generated/using-filters",
|
||||
"generated/or",
|
||||
"generated/not",
|
||||
"generated/match",
|
||||
"generated/eq",
|
||||
"generated/neq",
|
||||
"generated/gt",
|
||||
"generated/gte",
|
||||
"generated/lt",
|
||||
"generated/lte",
|
||||
"generated/like",
|
||||
"generated/ilike",
|
||||
"generated/is",
|
||||
"generated/in",
|
||||
"generated/contains",
|
||||
"generated/containedby",
|
||||
"generated/rangelt",
|
||||
"generated/rangegt",
|
||||
"generated/rangegte",
|
||||
"generated/rangelte",
|
||||
"generated/rangeadjacent",
|
||||
"generated/overlaps",
|
||||
"generated/textsearch",
|
||||
"generated/filter"
|
||||
],
|
||||
"collapsed": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
["v1"]
|
||||
@@ -1,5 +1,5 @@
|
||||
import authProviders from '../data/authProviders'
|
||||
import { Card } from '@supabase/ui'
|
||||
import { Card } from 'ui'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function AuthProviders() {
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function ButtonCard({
|
||||
children,
|
||||
icon,
|
||||
title,
|
||||
description,
|
||||
to,
|
||||
layout = 'vertical',
|
||||
}) {
|
||||
return (
|
||||
<Link href={to}>
|
||||
<a className={['button-card h-full pb-4 block', `button-card--${layout}`].join(' ')}>
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
<div
|
||||
className={`button-card__inner p-4 gap-1 flex ${
|
||||
layout === 'vertical' ? 'flex-col' : 'items-center'
|
||||
}`}
|
||||
>
|
||||
{icon && typeof icon == 'string' ? (
|
||||
<img className="m-0" src={icon} width={24} alt={title} />
|
||||
) : (
|
||||
icon
|
||||
)}
|
||||
<h3 className="mt-0">{title}</h3>
|
||||
<p>{description}</p>
|
||||
</div>
|
||||
)}
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter'
|
||||
import monokaiCustomTheme from './CodeBlock.utils'
|
||||
import { Button, IconCheck, IconCopy } from '@supabase/ui'
|
||||
import { Button, IconCheck, IconCopy } from 'ui'
|
||||
import CopyToClipboard from 'react-copy-to-clipboard'
|
||||
|
||||
import js from 'react-syntax-highlighter/dist/cjs/languages/hljs/javascript'
|
||||
import py from 'react-syntax-highlighter/dist/cjs/languages/hljs/python'
|
||||
import sql from 'react-syntax-highlighter/dist/cjs/languages/hljs/sql'
|
||||
import { useState } from 'react'
|
||||
import { useTheme } from '../Providers'
|
||||
|
||||
interface Props {
|
||||
lang: 'js' | 'sql' | 'py'
|
||||
@@ -16,8 +17,10 @@ interface Props {
|
||||
children?: string
|
||||
size?: 'small' | 'medium' | 'large'
|
||||
}
|
||||
|
||||
function CodeBlock(props: Props) {
|
||||
const { isDarkMode } = useTheme()
|
||||
const monokaiTheme = monokaiCustomTheme(isDarkMode)
|
||||
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
const handleCopy = () => {
|
||||
@@ -42,26 +45,31 @@ function CodeBlock(props: Props) {
|
||||
// const large = props.size === 'large' ? true : false
|
||||
const large = false
|
||||
|
||||
// don't show line numbers if bash == lang
|
||||
const showLineNumbers = lang !== 'bash'
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SyntaxHighlighter
|
||||
language={lang}
|
||||
style={monokaiCustomTheme}
|
||||
className={'code-block border-scale-600 rounded-lg border'}
|
||||
style={monokaiTheme}
|
||||
className={`code-block rounded-lg border p-4 ${!showLineNumbers && 'pl-6'}`}
|
||||
customStyle={{
|
||||
padding: 0,
|
||||
fontSize: large ? 18 : 12,
|
||||
lineHeight: large ? 1.2 : 1.2,
|
||||
borderTop: '1px solid #393939',
|
||||
background: '#181818',
|
||||
lineHeight: large ? 1.4 : 1.2,
|
||||
// borderTop: '1px solid #393939',
|
||||
//background: isDarkMode ? 'bg-scale-700' : 'bg-scale-300',
|
||||
//background: isDarkMode ? '#444' : '#F1F3F5',
|
||||
// we really should support proper light mode, not just show dark in both modes
|
||||
background: isDarkMode ? '#444' : '#F1F3F5',
|
||||
}}
|
||||
showLineNumbers={lang === 'cli' ? false : true}
|
||||
showLineNumbers={showLineNumbers}
|
||||
lineNumberContainerStyle={{
|
||||
paddingTop: '128px',
|
||||
}}
|
||||
lineNumberStyle={{
|
||||
minWidth: '44px',
|
||||
background: '#1e1e1e',
|
||||
background: isDarkMode ? '#444' : '#F1F3F5',
|
||||
paddingLeft: '4px',
|
||||
paddingRight: '4px',
|
||||
marginRight: '12px',
|
||||
@@ -75,7 +83,7 @@ function CodeBlock(props: Props) {
|
||||
{props.children?.trimEnd()}
|
||||
</SyntaxHighlighter>
|
||||
{!props.hideCopy && props.children ? (
|
||||
<div className="dark absolute right-2 top-2">
|
||||
<div className={`${isDarkMode ?? 'dark'} absolute right-2 top-2`}>
|
||||
<CopyToClipboard text={props.children}>
|
||||
<Button
|
||||
type="default"
|
||||
|
||||
@@ -1,116 +1,117 @@
|
||||
const monokaiCustomTheme = {
|
||||
hljs: {
|
||||
display: 'block',
|
||||
overflowX: 'auto',
|
||||
padding: '0.5em',
|
||||
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: '#75715e',
|
||||
},
|
||||
'hljs-quote': {
|
||||
color: '#75715e',
|
||||
},
|
||||
'hljs-deletion': {
|
||||
color: '#75715e',
|
||||
},
|
||||
'hljs-meta': {
|
||||
color: '#75715e',
|
||||
},
|
||||
'hljs-doctag': {
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
'hljs-selector-id': {
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
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,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
|
||||
@@ -1,128 +1,132 @@
|
||||
import Image from 'next/image'
|
||||
import FooterLinks from 'data/Footer.json'
|
||||
import SectionContainer from './SectionContainer'
|
||||
import DarkModeToggle from './DarkModeToggle'
|
||||
import Link from 'next/link'
|
||||
import footer from '../data/footer.json'
|
||||
import { useTheme } from './Providers'
|
||||
import { Badge } from 'ui'
|
||||
import Image from 'next/image'
|
||||
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-400 dark:bg-scale-200 border-t bg-white p-10"
|
||||
className="border-scale-500 dark:border-scale-600 border-t"
|
||||
aria-labelledby="footerHeading"
|
||||
>
|
||||
<h2 id="footerHeading" className="sr-only">
|
||||
Footer
|
||||
</h2>
|
||||
<div className="xl:grid xl:grid-cols-3 xl:gap-8">
|
||||
<div className="space-y-8 xl:col-span-1">
|
||||
<Link href="/">
|
||||
<a>
|
||||
<Image
|
||||
src={isDarkMode ? `/supabase-dark.svg` : `/supabase-light.svg`}
|
||||
alt="Supabase"
|
||||
width={160}
|
||||
height={22}
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
<div className="flex space-x-5">
|
||||
<a
|
||||
href="https://twitter.com/supabase"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-scale-900 hover:text-scale-1100"
|
||||
>
|
||||
<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"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-scale-900 hover:text-scale-1100"
|
||||
>
|
||||
<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"
|
||||
<SectionContainer>
|
||||
<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"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</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://discord.supabase.com/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-scale-900 hover:text-scale-1100"
|
||||
>
|
||||
<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://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://youtube.com/c/supabase"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-scale-900 hover:text-scale-1100"
|
||||
>
|
||||
<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>
|
||||
<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>
|
||||
<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">
|
||||
{footer.map((segment: any) => {
|
||||
return (
|
||||
<div key={`footer_${segment.title}`}>
|
||||
<h3 className="text-scale-900 dark:text-scale-800 text-sm">{segment.title}</h3>
|
||||
<ul className="mt-4 space-y-2">
|
||||
{segment.links.map((link: any, idx: number) => (
|
||||
<li key={`${segment.title}_link_${idx}`}>
|
||||
<Link href={link.url}>
|
||||
<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
|
||||
className={`text-sm ${
|
||||
href={link.url}
|
||||
className={`text-sm transition-colors ${
|
||||
link.url
|
||||
? 'text-scale-1000 dark:text-scale-1000'
|
||||
: 'text-scale-300 dark:text-scale-300'
|
||||
} hover:text-brand-900 dark:hover:text-brand-1100 transition`}
|
||||
? 'text-scale-1100 hover:text-scale-1200 '
|
||||
: 'text-scale-900 hover:text-scale-900'
|
||||
} `}
|
||||
>
|
||||
{link.text}
|
||||
{!link.url && (
|
||||
<span className="dark:text-dark-300 block text-sm text-gray-300">
|
||||
Coming soon
|
||||
</span>
|
||||
<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>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="dark:border-scale-400 mt-32 flex justify-between border-t pt-8">
|
||||
<p className="dark:text-dark-400 text-scale-800 mb-0 self-center text-sm">
|
||||
© {new Date().getFullYear()} Supabase.
|
||||
</p>
|
||||
</div>
|
||||
<div className="border-scale-500 dark:border-scale-600 mt-32 flex justify-between border-t pt-8">
|
||||
<small className="small">© Supabase Inc</small>
|
||||
<DarkModeToggle />
|
||||
</div>
|
||||
</SectionContainer>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
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'
|
||||
|
||||
const INDEX_NAME = 'tailwindcss_dev'
|
||||
const API_KEY = '5fc87cef58bb80203d2207578309fab6'
|
||||
const APP_ID = 'KNPXZI5B0M'
|
||||
|
||||
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}
|
||||
searchParameters={{
|
||||
facetFilters: 'version:v3',
|
||||
distinct: 1,
|
||||
}}
|
||||
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,19 @@
|
||||
import classNames from 'classnames'
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
const SectionContainer = ({ children, className }: Props) => (
|
||||
<div
|
||||
className={classNames(
|
||||
`sm:py-18 container relative mx-auto px-6 py-16 md:py-24 lg:px-16 lg:py-24 xl:px-20`,
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
export default SectionContainer
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Tabs } from '@supabase/ui'
|
||||
import { Tabs } from 'ui'
|
||||
export default function TabPanel(props: { id: string; label: string | undefined; children: any }) {
|
||||
return (
|
||||
<Tabs.Panel id={props.id} label={props.label}>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Tabs } from '@supabase/ui'
|
||||
import { Tabs } from 'ui'
|
||||
|
||||
const TabWrapper = (props: any) => {
|
||||
return (
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import { useTheme } from './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 (
|
||||
<button onClick={() => toggleDarkMode()}>
|
||||
{isDarkMode ? (
|
||||
<IconMoon className="text-scale-1100 stroke-2" />
|
||||
) : (
|
||||
<IconSun className="text-scale-1100 stroke-2" />
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default DarkModeToggle
|
||||
@@ -1,22 +1,16 @@
|
||||
import LinkCard from './LinkCard'
|
||||
import LinkCardsWrapper from './LinkCardsWrapper'
|
||||
import { Tabs } from '@supabase/ui'
|
||||
import Sponsor from './Sponsor'
|
||||
import SponsorsWrapper from './SponsorsWrapper'
|
||||
import { Tabs, Alert } from 'ui'
|
||||
// import Tabs from '@theme/Tabs'
|
||||
// import TabItem from '@theme/TabItem'
|
||||
|
||||
import CodeBlock from './CodeBlock/CodeBlock'
|
||||
import AuthProviders from './AuthProviders'
|
||||
import ButtonCard from './ButtonCard'
|
||||
|
||||
const components = {
|
||||
// LinkCard,
|
||||
// LinkCardsWrapper,
|
||||
// SponsorsWrapper,
|
||||
// Sponsor,
|
||||
// AuthProviders,
|
||||
// Tabs: (props: any) => <Tabs {...props} type="underlined" size="small" />,
|
||||
// TabsPanel: (props: any) => {
|
||||
// return <Tabs.Panel {...props} />
|
||||
// },
|
||||
// code: (props: any) => <CodeBlock {...props} />,
|
||||
Alert,
|
||||
ButtonCard,
|
||||
Tabs,
|
||||
TabPanel: (props: any) => <Tabs.Panel {...props}>{props.children}</Tabs.Panel>,
|
||||
code: (props: any) => <CodeBlock {...props} />,
|
||||
}
|
||||
|
||||
export default components
|
||||
@@ -1,37 +0,0 @@
|
||||
import Head from 'next/head'
|
||||
import NavBar from '../nav/NavBar'
|
||||
import Footer from '../Footer'
|
||||
|
||||
const Layout = ({
|
||||
children,
|
||||
meta,
|
||||
currentPage,
|
||||
}: {
|
||||
meta: { title: string; description: string }
|
||||
children: React.ReactNode
|
||||
currentPage: string
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{meta?.title} | Supabase</title>
|
||||
<meta name="description" content={meta?.description} />
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content={meta?.title} />
|
||||
<meta property="og:description" content={meta?.description} />
|
||||
<meta property="og:title" content={meta?.title} />
|
||||
</Head>
|
||||
<main>
|
||||
<NavBar currentPage={currentPage} />
|
||||
<article className="text-scale-1200 mx-4 md:mx-8 xl:mx-auto xl:max-w-7xl">
|
||||
{children}
|
||||
</article>
|
||||
<Footer />
|
||||
</main>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Layout
|
||||
@@ -1,39 +1,28 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import {
|
||||
IconMenu,
|
||||
IconGitHub,
|
||||
IconTwitter,
|
||||
IconSearch,
|
||||
Input,
|
||||
IconCommand,
|
||||
Button,
|
||||
} from '@supabase/ui'
|
||||
import { IconMenu, IconGitHub, IconTwitter, IconSearch, Input, IconCommand, Button } from 'ui'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { useTheme } from '../Providers'
|
||||
import ThemeToggle from '../ThemeToggle'
|
||||
|
||||
const NavBar = ({ currentPage }: { currentPage: string }) => {
|
||||
const [mounted, setMounted] = useState(false)
|
||||
const { isDarkMode } = useTheme()
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [isDarkMode])
|
||||
|
||||
const pageLinks = [
|
||||
{ text: 'Overview', active: currentPage == 'Docs', link: '/' },
|
||||
{ text: 'Guides', active: currentPage == 'Guides', link: '/guides' },
|
||||
{ text: 'Guides', link: '/guides' },
|
||||
{
|
||||
text: 'Reference',
|
||||
active: currentPage == 'Reference',
|
||||
link: '/reference/javascript/supabase-client',
|
||||
},
|
||||
{ text: 'Discussions', active: currentPage == 'Discussions', link: '/discussions' },
|
||||
{ text: 'Discussions', link: '/discussions' },
|
||||
]
|
||||
|
||||
return (
|
||||
<nav className="dark:border-scale-400 dark:bg-scale-200 sticky top-0 z-10 flex h-[72px] items-center justify-between border-b bg-white p-4 backdrop-blur backdrop-filter">
|
||||
<nav className="dark:border-scale-400 dark:bg-scale-200 sticky top-0 z-10 flex h-[72px] items-center justify-between border-b bg-white p-4 backdrop-blur backdrop-filter font-semibold">
|
||||
<div className="flex items-center">
|
||||
<button className="mr-4 block stroke-2 lg:hidden">
|
||||
<IconMenu className="text-scale-1100" />
|
||||
@@ -51,37 +40,76 @@ const NavBar = ({ currentPage }: { currentPage: string }) => {
|
||||
</a>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<ul className="ml-8 hidden space-x-8 lg:flex">
|
||||
{pageLinks.map((p) => (
|
||||
<li key={`${p.text}-${p.link}`}>
|
||||
<Link href={p.link}>
|
||||
<a className={`text-sm ${p.active ? 'text-brand-900' : 'text-scale-1100'}`}>
|
||||
{p.text}
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
<li>
|
||||
<Link href="https://app.supabase.com">
|
||||
<a className={`text-scale-1100 text-sm`}>Login</a>
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
<nav className="ml-24 hidden space-x-8 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 === p.text ? 'text-brand-900' : 'text-scale-1100'
|
||||
}`}
|
||||
>
|
||||
{p.text}
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<div className="flex items-center space-x-6">
|
||||
<div className="hidden items-center space-x-6 md:flex">
|
||||
<Link href="https://github.com/supabase/supabase">
|
||||
<a target="_blank">
|
||||
<IconGitHub className="text-scale-1100 stroke-2" />
|
||||
</a>
|
||||
</Link>
|
||||
<Link href="https://twitter.com/supabase">
|
||||
<a target="_blank">
|
||||
<ul className="flex items-center gap-4">
|
||||
<li>
|
||||
<a
|
||||
href="https://app.supabase.com"
|
||||
className="text-scale-1100 text-sm font-semibold"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
Dashboard
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://github.com/supabase/supabase"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<IconGitHub className="text-scale-1100 stroke-2" />
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://discord.supabase.com"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
className="text-scale-1100"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="m8 17-.55 2C5.5 18.5 3.5 17.5 2 16.27 2 11 3.5 7.5 5 5c0 0 2.5-1 4.5-1l.5 1.36a4.82 4.82 0 0 1 4 .14l.5-1.5c2 0 4.5 1 4.5 1 1.5 2.5 3 6 3 11.27A14.68 14.68 0 0 1 16.55 19L16 17" />
|
||||
<path d="M7.5 16.5c.73.5 2 1.5 4.5 1.5s3.77-1 4.5-1.5" />
|
||||
<circle cx="9" cy="12" r="1" />
|
||||
<circle cx="15" cy="12" r="1" />
|
||||
</svg>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<a href="https://twitter.com/supabase" target="_blank" rel="noreferrer noopener">
|
||||
<IconTwitter className="text-scale-1100 stroke-2" />
|
||||
</a>
|
||||
</Link>
|
||||
<ThemeToggle />
|
||||
</ul>
|
||||
</div>
|
||||
<Input
|
||||
placeholder="Search"
|
||||
|
||||
@@ -1,55 +1,59 @@
|
||||
import { Menu } from '@supabase/ui'
|
||||
import { Menu } from 'ui'
|
||||
import { Fragment } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
const SideBar = ({ menuItems }: { menuItems: any }) => {
|
||||
const { asPath } = useRouter()
|
||||
return (
|
||||
<div className="dark:bg-scale-200 dark:border-scale-400 thin-scrollbar sidebar-width sticky top-0 flex h-screen overflow-y-scroll border-r py-4">
|
||||
<Menu className="w-full">
|
||||
<div>
|
||||
{Object.keys(menuItems).map((key) => {
|
||||
return (
|
||||
<div key={key} className="mb-4">
|
||||
<Menu.Group title={key} />
|
||||
{menuItems[key].map(
|
||||
(item: { link: string; text: string; sections: Array<any> }) => (
|
||||
<span key={item.link}>
|
||||
{Array.isArray(item.sections) ? (
|
||||
<>
|
||||
<Menu.Group title={item.text} />
|
||||
{item.sections.map((section) => (
|
||||
<Link href={section.link} key={`${item.text}-${section.text}`}>
|
||||
<a>
|
||||
<Menu.Item active={asPath == section.link}>
|
||||
{section.text}
|
||||
</Menu.Item>
|
||||
</a>
|
||||
</Link>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<Link href={item.link}>
|
||||
<a>
|
||||
<Menu.Item active={asPath === item.link}>
|
||||
<div
|
||||
className={
|
||||
asPath === item.link ? 'text-brand-900' : 'text-scale-1000'
|
||||
}
|
||||
>
|
||||
{item.text}
|
||||
</div>
|
||||
</Menu.Item>
|
||||
</a>
|
||||
</Link>
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div
|
||||
className="
|
||||
dark:bg-scale-200
|
||||
dark:border-scale-400
|
||||
sidebar-width
|
||||
sticky
|
||||
top-0
|
||||
flex
|
||||
h-screen
|
||||
overflow-y-scroll
|
||||
border-r
|
||||
py-10
|
||||
px-6"
|
||||
>
|
||||
<Menu className="w-full flex-col gap-12" type="pills">
|
||||
{Object.keys(menuItems).map((key) => {
|
||||
return (
|
||||
<div className="mb-8" key={key}>
|
||||
<Menu.Group title={key} />
|
||||
{menuItems[key].map((item: { link: string; text: string; sections: Array<any> }) => {
|
||||
return Array.isArray(item.sections) ? (
|
||||
<Fragment key={item.text}>
|
||||
<Menu.Group title={item.text} />
|
||||
{item.sections.map((section) => (
|
||||
<Link href={section.link} key={`${item.text}-${section.text}`}>
|
||||
<a>
|
||||
<Menu.Item active={asPath == section.link}>{section.text}</Menu.Item>
|
||||
</a>
|
||||
</Link>
|
||||
))}
|
||||
</Fragment>
|
||||
) : (
|
||||
<Link href={item.link} key={item.text}>
|
||||
<a>
|
||||
<Menu.Item active={asPath === item.link}>
|
||||
{/* <div
|
||||
// className={asPath === item.link ? 'text-brand-900' : 'text-scale-1000'}
|
||||
> */}
|
||||
{item.text}
|
||||
{/* </div> */}
|
||||
</Menu.Item>
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</Menu>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Link from 'next/link'
|
||||
import { IconArrowLeft, IconArrowRight } from '@supabase/ui'
|
||||
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.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: 'Architecture'
|
||||
description: 'Supabase Architecture'
|
||||
title: 'Architecture',
|
||||
description: 'Supabase Architecture',
|
||||
---
|
||||
|
||||
# Architecture
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
---
|
||||
title: 'FAQs'
|
||||
description: 'Supabase frequently asked questions'
|
||||
title: 'FAQs',
|
||||
description: 'Supabase frequently asked questions',
|
||||
---
|
||||
|
||||
# FAQs
|
||||
|
||||
### Have you built `[Firebase feature]` yet?
|
||||
|
||||
We're building as fast as we can! And we are even adding a few things that Firebase doesn't have, as we go. Here are the features we have built:
|
||||
|
||||
- A dashboard/UI for spinning up and managing your database in less than one minute.
|
||||
@@ -19,10 +20,13 @@ We're building as fast as we can! And we are even adding a few things that Fireb
|
||||
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 and it has 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. Also, we're sure that once you start using Postgres, you'll love it more than any other database.
|
||||
|
||||
### How do I host Supabase?
|
||||
|
||||
Supabase is an amalgamation of five open source tools (and growing). Some of these tools are made by Supabase (like our [Realtime Server](https://github.com/supabase/realtime)), some we support indirectly (like [PostgREST](http://postgrest.org/en/v7.0.0/)), and some are third-party tools (like [Kong](https://github.com/Kong/kong)). All of the tools we use in Supabase are MIT, Apache 2.0, or PostgreSQL licensed. You can use the docker-compose script [here](https://github.com/supabase/supabase/tree/master/docker) to build Supabase on your own environment, and find detailed instructions [here](https://supabase.com/docs/guides/hosting/overview).
|
||||
|
||||
### Do you support `[some other database]`?
|
||||
|
||||
At the moment, 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/issues/6) if you want us to start development.
|
||||
|
||||
### Do you have a library for `[some other language]`?
|
||||
|
||||
We currently have a JavaScript library. You can [vote on a new client library](https://github.com/supabase/supabase/issues/5) for your favorite language.
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: going-into-prod
|
||||
title: Going into Prod Checklist
|
||||
description: 'Things to do before making your app publicly available'
|
||||
id: 'going-into-prod',
|
||||
title: 'Going into Prod Checklist',
|
||||
description: 'Things to do before making your app publicly available',
|
||||
---
|
||||
|
||||
# Going into Prod Checklist
|
||||
@@ -15,39 +15,39 @@ After developing your project, and deciding it's time to Go Live With Real Users
|
||||
## Security
|
||||
|
||||
- Ensure RLS is enabled
|
||||
- Tables that do not have RLS enabled with reasonable policies allow any client to access and modify their data. This is unlikely to be what you want in the majority of cases.
|
||||
- [Learn more about RLS](/docs/guides/auth/row-level-security)
|
||||
- Tables that do not have RLS enabled with reasonable policies allow any client to access and modify their data. This is unlikely to be what you want in the majority of cases.
|
||||
- [Learn more about RLS](/docs/guides/auth/row-level-security)
|
||||
- Ensure replication is disabled for tables containing sensitive data:
|
||||
- Today, Realtime does not respect RLS policies and any client with your anon key can listen to changes on tables where replication is enabled.
|
||||
- Go to the Database > Replication page in the Supabase Dashboard to manage these settings
|
||||
- We are launching [RLS for realtime](https://github.com/supabase/walrus) soon, but in the meantime you should only use it for tables containing public data (scoreboards, blog posts, etc.)
|
||||
- Today, Realtime does not respect RLS policies and any client with your anon key can listen to changes on tables where replication is enabled.
|
||||
- Go to the Database > Replication page in the Supabase Dashboard to manage these settings
|
||||
- We are launching [RLS for realtime](https://github.com/supabase/walrus) soon, but in the meantime you should only use it for tables containing public data (scoreboards, blog posts, etc.)
|
||||
- Enable 2FA on Github
|
||||
- Since your Github account gives you administrative rights to your Supabase project, you should protect it with a strong password and 2FA using a U2F key or a TOTP app.
|
||||
- Since your Github account gives you administrative rights to your Supabase project, you should protect it with a strong password and 2FA using a U2F key or a TOTP app.
|
||||
- Ensure email confirmations are enabled in the Auth > Settings page
|
||||
- Use a custom SMTP server for auth emails so that your users can see that the mails are coming from a trusted domain (preferably the same domain that your app is hosted on). (grab SMTP credentials from any major email provider such as SendGrid, AWS SES, etc.)
|
||||
- Think hard about how *you* would abuse your service as an attacker, and mitigate
|
||||
- Think hard about how _you_ would abuse your service as an attacker, and mitigate
|
||||
- Review these [common cybersecurity threats](https://auth0.com/docs/security/prevent-threats)
|
||||
|
||||
## Performance
|
||||
|
||||
- Ensure that you have suitable indices to cater to your common query patterns
|
||||
- [learn more about indexes in postgres](https://www.enterprisedb.com/postgres-tutorials/overview-postgresql-indexes)
|
||||
- pg_stat_statements can help you [identify hot or slow queries](https://www.virtual-dba.com/blog/postgresql-performance-identifying-hot-and-slow-queries/)
|
||||
- [learn more about indexes in postgres](https://www.enterprisedb.com/postgres-tutorials/overview-postgresql-indexes)
|
||||
- pg_stat_statements can help you [identify hot or slow queries](https://www.virtual-dba.com/blog/postgresql-performance-identifying-hot-and-slow-queries/)
|
||||
- Perform load testing (preferably on a staging env)
|
||||
- tools like [k6](https://k6.io/) can simulate traffic from many different users
|
||||
- tools like [k6](https://k6.io/) can simulate traffic from many different users
|
||||
- If you find you require more resources, contact support@supabase.io to upgrade to PAYG for bigger servers
|
||||
|
||||
## Availability
|
||||
|
||||
- Use your own SMTP credentials so that you have full control over the deliverability of your transactional auth emails (see Auth > Settings)
|
||||
- you can grab SMTP credentials from any major email provider such as SendGrid, AWS SES, etc.
|
||||
- the default rate limit for auth emails provided by Supabase is 30 new users per hour, if doing a major public announcement you will likely require more than this
|
||||
- you can grab SMTP credentials from any major email provider such as SendGrid, AWS SES, etc.
|
||||
- the default rate limit for auth emails provided by Supabase is 30 new users per hour, if doing a major public announcement you will likely require more than this
|
||||
- If your application is on the free tier and is **not** expected to be queried at least once every 7 days, then it may be paused by Supabase to save on server resources
|
||||
- you can restore paused projects from the Supabase dashboard
|
||||
- upgrade to Pro to guarantee that your project will not be paused for inactivity
|
||||
- you can restore paused projects from the Supabase dashboard
|
||||
- upgrade to Pro to guarantee that your project will not be paused for inactivity
|
||||
- Database backups are not available for download on the free tier
|
||||
- You can set up your own backup systems using tools like [pg_dump](https://www.postgresqltutorial.com/postgresql-backup-database/) or [wal-g](https://github.com/wal-g/wal-g)
|
||||
- Nightly backups for Pro tier projects are available on the Supabase dashboard for up to 7 days
|
||||
- You can set up your own backup systems using tools like [pg_dump](https://www.postgresqltutorial.com/postgresql-backup-database/) or [wal-g](https://github.com/wal-g/wal-g)
|
||||
- Nightly backups for Pro tier projects are available on the Supabase dashboard for up to 7 days
|
||||
- upgrading to the Supabase Pro Tier will give you access to email support on support@supabase.io
|
||||
|
||||
This checklist is always growing so be sure to check back frequently, and also feel free to suggest additions and amendments by making a PR on [Github](https://github.com/supabase/supabase).
|
||||
@@ -50,7 +50,7 @@ Each system has a number of configuration options which can be found in the rele
|
||||
|
||||
- [Postgres](https://hub.docker.com/_/postgres/)
|
||||
- [PostgREST](https://postgrest.org/en/stable/configuration.html)
|
||||
- [Realtime](https://github.com/supabase/realtime#server-set-up)
|
||||
- [Realtime](https://github.com/supabase/realtime#server)
|
||||
- [GoTrue](https://github.com/supabase/gotrue)
|
||||
- [Storage](https://github.com/supabase/storage-api)
|
||||
- [Kong](https://docs.konghq.com/install/docker/)
|
||||
|
||||
@@ -4,76 +4,101 @@ title: Local Development
|
||||
description: How to use Supabase on your local development machine.
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabsPanel from '@theme/TabsPanel'
|
||||
## Local Development
|
||||
|
||||
Supabase provides a CLI so that you can develop your application locally, rather than connecting to a live project.
|
||||
// import Tabs from '@theme/Tabs'
|
||||
// import TabItem from '@theme/TabItem'
|
||||
|
||||
## Before you begin
|
||||
Learn how to use the Supabase CLI to develop your project locally and deploy to the Supabase Platform.
|
||||
|
||||
If you're using the CLI, please record your steps! You can help us improve by adding missing details and fixing errors.
|
||||
{/* This is just my experimentation to use the Tabs component from the UI library but looks odd */}
|
||||
|
||||
Our CLI is still under heavy development, and is missing some features:
|
||||
|
||||
- The self-hosted version of Supabase does not include a UI yet.
|
||||
We are working on this in stages, starting with our [UI library](https://github.com/supabase/ui)
|
||||
([more context](https://github.com/supabase/supabase/discussions/1001#discussioncomment-558696)).
|
||||
|
||||
In the meantime, here are some suggested tools for interacting with your Postgres Database:
|
||||
|
||||
- `pgadmin`: https://www.pgadmin.org
|
||||
- `dbeaver`: https://dbeaver.com
|
||||
- `BeeKeeper`: https://beekeeperstudio.io
|
||||
- `HeidiSQL`: https://heidisql.com
|
||||
- `Table Plus`: https://www.tableplus.io
|
||||
<Tabs scrollable size="small" type="underlined" listClassNames="px-8 pt-4" defaultActiveId="kong">
|
||||
<TabPanel id="postgres" label="Postgres">
|
||||
Hello
|
||||
</TabPanel>
|
||||
<TabPanel id="kong" label="API Gateway">
|
||||
World
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
## Prerequisites
|
||||
|
||||
You will need to have these in your environment:
|
||||
Make sure you have these installed on your local machine:
|
||||
|
||||
- Git
|
||||
- Docker (make sure the daemon is up and running)
|
||||
- Supabase CLI (instructions [here](https://github.com/supabase/cli))
|
||||
- [Docker](https://docs.docker.com/engine/install/)
|
||||
- [Git](https://github.com/git-guides/install-git)
|
||||
- [Supabase CLI](/docs/guides/cli)
|
||||
|
||||
## Log in to the Supabase CLI
|
||||
|
||||
```bash
|
||||
supabase login
|
||||
```
|
||||
|
||||
<Alert withIcon variant="warning" title="Successfully generated a new token!">
|
||||
<div className="w-full space-y-2">
|
||||
<p className="text-sm">
|
||||
To access the database from an edge function in your local Supabase setup, replace `localhost`
|
||||
with `host.docker.internal`.
|
||||
</p>
|
||||
</div>
|
||||
</Alert>
|
||||
|
||||
## Initialize your project
|
||||
|
||||
Create a new folder for your project and start a new git repository:
|
||||
|
||||
```bash
|
||||
# create your project folder
|
||||
mkdir your-project
|
||||
|
||||
# move into the new folder
|
||||
cd your-project
|
||||
|
||||
# start a new git repository
|
||||
git init
|
||||
```
|
||||
|
||||
```js
|
||||
function create() {
|
||||
console.log('hay')
|
||||
}
|
||||
```
|
||||
|
||||
## Start Supabase services
|
||||
|
||||
[Initialize](/reference/cli/usage#supabase-init) Supabase to set up the configuration for developing your project locally:
|
||||
|
||||
```bash
|
||||
supabase init
|
||||
```
|
||||
|
||||
This command will create a `supabase` folder which holds all the configuration for developing your project locally.
|
||||
|
||||
## Start
|
||||
Make sure Docker is running. The [start](/reference/cli/usage#supabase-start) command uses Docker to start the Supabase [services](/architecture).
|
||||
This command may take a while to run if this is the first time using the CLI.
|
||||
|
||||
```bash
|
||||
supabase start
|
||||
```
|
||||
|
||||
This command uses Docker Compose to start all the open source [services](/docs/#how-it-works) of Supabase. This command will take a while to run, there are a lot of services to build.
|
||||
Once all of the Supabase services are running, you'll see output containing your local Supabase credentials.
|
||||
You can use the [stop](/reference/cli/usage#supabase-stop) command at any time to stop all services.
|
||||
|
||||
Once this is running, you will see an output that contains your local Supabase credentials:
|
||||
## Access services
|
||||
|
||||
```txt
|
||||
Started local development setup.
|
||||
API URL: http://localhost:54321
|
||||
DB URL: postgresql://postgres:postgres@localhost:5432/postgres
|
||||
anon key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiJ9.ZopqoUt20nEV9cklpv9e3yw3PVyZLmKs5qLD6nGL1SI
|
||||
service_role key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIn0.M2d2z4SFn5C7HlJlaSLfrzuYim9nbY_XI40uWFN3hEE
|
||||
```
|
||||
|
||||
#### Accessing Services Directly
|
||||
You can access services directly with any Postgres client or through the API Gateway ([Kong](https://github.com/Kong/kong)).
|
||||
|
||||
<Tabs
|
||||
defaultActiveId="Postgres"
|
||||
values={[
|
||||
{label: 'Postgres', value: 'Postgres'},
|
||||
{label: 'API Gateway', value: 'Kong'},
|
||||
]}>
|
||||
<TabsPanel id="Postgres" label="Postgres">
|
||||
defaultValue="postgres"
|
||||
values={[
|
||||
{label: 'Postgres', value: 'postgres'},
|
||||
{label: 'API Gateway', value: 'kong'},
|
||||
]}>
|
||||
<TabItem value="postgres">
|
||||
|
||||
```sh
|
||||
# Default URL:
|
||||
postgresql://postgres:postgres@localhost:5432/postgres
|
||||
postgresql://postgres:postgres@localhost:54322/postgres
|
||||
```
|
||||
|
||||
The local Postgres instance can be accessed through [`psql`](https://www.postgresql.org/docs/current/app-psql.html)
|
||||
@@ -82,89 +107,146 @@ or any other Postgres client, such as [pgadmin](https://www.pgadmin.org/).
|
||||
For example:
|
||||
|
||||
```bash
|
||||
psql 'postgresql://postgres:postgres@localhost:5432/postgres'
|
||||
psql 'postgresql://postgres:postgres@localhost:54322/postgres'
|
||||
```
|
||||
|
||||
</TabsPanel>
|
||||
<TabsPanel id="Kong" label="Kong">
|
||||
</TabItem>
|
||||
<TabItem value="kong">
|
||||
|
||||
```sh
|
||||
# Default URL:
|
||||
http://localhost:54321
|
||||
```
|
||||
|
||||
All of the services are accessible through the API Gateway [Kong](https://github.com/Kong/kong)).
|
||||
If you are accessing these services without the client libraries, you may need to pass the client keys as an `Authorization` header.
|
||||
You can learn more about these JWT headers in our [Resources](/docs/learn/auth-deep-dive/auth-deep-dive-jwts).
|
||||
Learn more about JWT headers in our [Resources](/learn/auth-deep-dive/auth-deep-dive-jwts).
|
||||
|
||||
```sh
|
||||
curl 'http://localhost:54321/rest/v1/' \
|
||||
-H "apikey: SUPABASE_KEY" \
|
||||
-H "Authorization: Bearer SUPABASE_KEY"
|
||||
-H "apikey: <anon key>" \
|
||||
-H "Authorization: Bearer <anon key>"
|
||||
|
||||
http://localhost:54321/v1/rest/v1/ # REST (PostgREST)
|
||||
http://localhost:54321/v1/realtime/v1/ # Realtime
|
||||
http://localhost:54321/v1/storage/v1/ # Storage
|
||||
http://localhost:54321/v1/auth/v1/ # Auth (GoTrue)
|
||||
http://localhost:54321/rest/v1/ # REST (PostgREST)
|
||||
http://localhost:54321/realtime/v1/ # Realtime
|
||||
http://localhost:54321/storage/v1/ # Storage
|
||||
http://localhost:54321/auth/v1/ # Auth (GoTrue)
|
||||
```
|
||||
|
||||
</TabsPanel>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Example application
|
||||
:::note
|
||||
To access the database from an edge function in your local Supabase setup, replace `localhost` with `host.docker.internal`.
|
||||
:::
|
||||
|
||||
Now that we've learned how to install and start Supabase locally, let's see how you can use it with a frontend application.
|
||||
This quick-start guide will show you, how you can configure a React app which uses the Supabase stack.
|
||||
## Database migrations
|
||||
|
||||
Database changes are managed through "migrations." Database migrations are a common way of tracking changes to your database over time.
|
||||
|
||||
### Make database changes
|
||||
|
||||
For this guide, create a table called `employees`. In Supabase Studio, navigate to the **SQL Editor** page and run the following SQL command:
|
||||
|
||||
```sql
|
||||
create table employees (
|
||||
id integer primary key generated always as identity,
|
||||
name text
|
||||
);
|
||||
```
|
||||
|
||||
:::note
|
||||
You can execute any SQL using the `DB URL` shown by [`supabase status`](/reference/cli/usage#supabase-status).
|
||||
:::
|
||||
|
||||
Run the [`db diff`](/reference/cli/usage#supabase-db-diff) command to detect changes in the local database:
|
||||
|
||||
```sh
|
||||
supabase db diff create_employees -f create_employees
|
||||
```
|
||||
|
||||
This creates a new migration named `supabase/migrations/<timestamp>_create_employees.sql`, representing any changes made to the local database since [`supabase start`](/reference/cli/usage#supabase-start).
|
||||
|
||||
### Add sample data
|
||||
|
||||
Use the seed script in `supabase/seed.sql` (created with [`supabase init`](/reference/cli/usage#supabase-init)) to add sample data to the table.
|
||||
|
||||
```sql
|
||||
-- in supabase/seed.sql
|
||||
insert into public.employees (name)
|
||||
values
|
||||
('Erlich Backman'),
|
||||
('Richard Hendricks'),
|
||||
('Monica Hall');
|
||||
```
|
||||
|
||||
Rerun the migration and seed scripts:
|
||||
|
||||
```bash
|
||||
# create a fresh React app
|
||||
npx create-react-app react-demo --use-npm
|
||||
|
||||
# move into the new folder
|
||||
cd react-demo
|
||||
|
||||
# set up Supabase
|
||||
supabase init
|
||||
|
||||
# Save the install supabase-js library
|
||||
npm install --save @supabase/supabase-js
|
||||
supabase db reset
|
||||
```
|
||||
|
||||
Now that your application is prepared, start the backend:
|
||||
You should now see the contents of `employees` in Studio.
|
||||
|
||||
### Reset database changes
|
||||
|
||||
Use the [`reset`](/reference/cli/usage#supabase-db-reset) command to revert any changes to the local database.
|
||||
|
||||
```sql
|
||||
-- run on local database to make a change
|
||||
alter table employees
|
||||
add department text default 'Hooli';
|
||||
```
|
||||
|
||||
Run the following command to reset the local database:
|
||||
|
||||
```sh
|
||||
supabase db reset
|
||||
```
|
||||
|
||||
## Deploy your project
|
||||
|
||||
Go to the [Supabase Dashboard](https://app.supabase.com) and create a project to deploy the changes.
|
||||
|
||||
### Link your project
|
||||
|
||||
:::note
|
||||
There are a few commands required to link your project. We are in the process of consolidating these commands into a single command. Bear with us!
|
||||
:::
|
||||
|
||||
Associate your project with your remote project using [`supabase link`](/reference/cli/usage#supabase-link).
|
||||
|
||||
```bash
|
||||
supabase start # Start Supabase
|
||||
supabase link --project-ref <project-id>
|
||||
# You can get <project-id> from your project’s dashboard URL: https://app.supabase.com/project/<project-id>
|
||||
|
||||
supabase db remote commit
|
||||
# Capture any changes that you have made to your database before setting up the CLI
|
||||
```
|
||||
|
||||
You can use Supabase anywhere in your application. Copy this snippet into `App.js`:
|
||||
`supabase/migrations` is now populated with a migration in `..._remote_commit.sql`.
|
||||
This migration captures any changes required for your local database to match the schema of your remote Supabase project.
|
||||
|
||||
```js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
### Deploy database changes
|
||||
|
||||
const SUPABASE_URL = '<your API URL>'
|
||||
const SUPABASE_ANON_KEY = '<your anon key>'
|
||||
Deploy any local database migrations using [`db push`](/reference/cli/usage#supabase-db-push):
|
||||
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
|
||||
```sh
|
||||
supabase db push
|
||||
```
|
||||
|
||||
Then start the frontend in a separate terminal:
|
||||
### Deploy Edge Functions
|
||||
|
||||
```bash
|
||||
npm start # Start the React app
|
||||
Deploy any Edge Functions using [`functions deploy`](/reference/cli/usage#supabase-functions-deploy):
|
||||
|
||||
```sh
|
||||
supabase functions deploy <function_name>
|
||||
```
|
||||
|
||||
If everything is working, you should have a React app running on `http://localhost:3000` and Supabase services running on `http://localhost:54321`
|
||||
## Limitations
|
||||
|
||||
## Stop
|
||||
The local development environment is not as feature-complete as the Supabase Platform. Here are some of the differences:
|
||||
|
||||
When you're finished with Supabase, send <kbd>Ctrl-C</kbd> to stop the Docker services.
|
||||
|
||||
## Migrations
|
||||
|
||||
You can also use the CLI to manage your migrations. Follow the the tour [here](https://github.com/supabase/cli/tree/main/examples/tour) to get started.
|
||||
|
||||
## Next steps
|
||||
|
||||
- Got a question? [Ask here](https://github.com/supabase/supabase/discussions).
|
||||
- CLI repository: [GitHub](https://github.com/supabase/cli).
|
||||
- Sign in: [app.supabase.com](https://app.supabase.com)
|
||||
- The Storage interface is coming soon.
|
||||
- The Functions interface is coming soon.
|
||||
- Logs are not supported through the interface (however you can access them through the Docker containers).
|
||||
- You cannot update your project settings in the Dashboard—this must be done using the CLI.
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Introduction
|
||||
description: Introduction to Supabase
|
||||
title: 'Introduction'
|
||||
description: 'Introduction to Supabase'
|
||||
---
|
||||
|
||||
# Introduction
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
id: reference
|
||||
title: Reference Documentation
|
||||
sidebar_label: Reference Documentation
|
||||
hide_table_of_contents: true
|
||||
---
|
||||
|
||||
import ButtonCard from '../components/ButtonCard'
|
||||
|
||||
Reference documentation for the official Supabase client libraries, APIs, and tools.
|
||||
|
||||
<div class="container p-0">
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="w-1/2 border">
|
||||
<ButtonCard
|
||||
icon={"/img/libraries/javascript-icon.svg"}
|
||||
to={"/reference/javascript"}
|
||||
title="JavaScript"
|
||||
description="JavaScript and TypeScript documentation."
|
||||
/>
|
||||
</div>
|
||||
<div class="w-1/2 border">
|
||||
<ButtonCard
|
||||
icon={"/img/libraries/dart-icon.svg"}
|
||||
to={"/reference/dart"}
|
||||
title="Flutter"
|
||||
description="Flutter and Dart documentation."
|
||||
/>
|
||||
</div>
|
||||
<div class="w-1/2 border">
|
||||
<ButtonCard icon={"/img/libraries/cli-icon.svg"}
|
||||
to={"/reference/cli"}
|
||||
title="Supabase CLI"
|
||||
description="Tools for developing your Supabase projects locally."
|
||||
/>
|
||||
</div>
|
||||
<div class="w-1/2 border">
|
||||
<ButtonCard icon={"/img/libraries/api-icon.svg"}
|
||||
to={"/reference/api"}
|
||||
title="Supabase API"
|
||||
description="Manage your Supabase organizations and projects." />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Self-hosting
|
||||
|
||||
Reference documentation for self-hosting Supabase features.
|
||||
|
||||
<div class="container">
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="w-1/2 border">
|
||||
<ButtonCard
|
||||
to={'/reference/auth'}
|
||||
title={'Auth Server'}
|
||||
description=" JSON Web Token (JWT)-based API for managing users and issuing access tokens."
|
||||
/>
|
||||
</div>
|
||||
<div class="w-1/2 border">
|
||||
<ButtonCard
|
||||
to={'/reference/storage'}
|
||||
title={'Storage'}
|
||||
description="S3-compatible object storage service that integrates with Postgres."
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,120 @@
|
||||
import template from './templates/ApiTemplate'
|
||||
import { slugify, toArrayWithKey, toTitle, writeToDisk } from './helpers'
|
||||
import { OpenAPIV3, OpenAPIV2 } from 'openapi-types'
|
||||
import * as fs from 'fs'
|
||||
import * as ejs from 'ejs'
|
||||
|
||||
export default async function gen(inputFileName: string, outputDir: string, apiUrl: string) {
|
||||
const specRaw = fs.readFileSync(inputFileName, 'utf8')
|
||||
const spec = JSON.parse(specRaw) as any
|
||||
// console.log('spec', spec)
|
||||
|
||||
switch (spec.openapi || spec.swagger) {
|
||||
case '3.0.0':
|
||||
case '3.0.3':
|
||||
await gen_v3(spec, outputDir, { apiUrl })
|
||||
break
|
||||
|
||||
case '2.0':
|
||||
await gen_v2(spec, outputDir, { apiUrl })
|
||||
break
|
||||
|
||||
default:
|
||||
console.log('Unrecognized specification version:', spec.openapi)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Versioned Generator
|
||||
*/
|
||||
|
||||
// OPENAPI-SPEC-VERSION: 3.0.0
|
||||
type v3OperationWithPath = OpenAPIV3.OperationObject & {
|
||||
path: string
|
||||
}
|
||||
type enrichedOperation = OpenAPIV3.OperationObject & {
|
||||
path: string
|
||||
fullPath: string
|
||||
operationId: string
|
||||
}
|
||||
async function gen_v3(spec: OpenAPIV3.Document, dest: string, { apiUrl }: { apiUrl: string }) {
|
||||
const specLayout = spec.tags || []
|
||||
const operations: enrichedOperation[] = []
|
||||
Object.entries(spec.paths).forEach(([key, val]) => {
|
||||
const fullPath = `${apiUrl}${key}`
|
||||
|
||||
toArrayWithKey(val!, 'operation').forEach((o) => {
|
||||
const operation = o as v3OperationWithPath
|
||||
const enriched = {
|
||||
...operation,
|
||||
path: key,
|
||||
fullPath,
|
||||
operationId: slugify(operation.summary!),
|
||||
|
||||
responseList: toArrayWithKey(operation.responses!, 'responseCode') || [],
|
||||
}
|
||||
operations.push(enriched)
|
||||
})
|
||||
})
|
||||
|
||||
const sections = specLayout.map((section) => {
|
||||
return {
|
||||
...section,
|
||||
title: toTitle(section.name),
|
||||
id: slugify(section.name),
|
||||
operations: operations.filter((operation) => operation.tags?.includes(section.name)),
|
||||
}
|
||||
})
|
||||
|
||||
const content = ejs.render(template, {
|
||||
info: spec.info,
|
||||
sections,
|
||||
operations,
|
||||
})
|
||||
// console.log(content)
|
||||
// Write to disk
|
||||
await writeToDisk(dest, content)
|
||||
console.log('Saved: ', dest)
|
||||
}
|
||||
|
||||
// OPENAPI-SPEC-VERSION: 2.0
|
||||
async function gen_v2(spec: OpenAPIV2.Document, dest: string, { apiUrl }: { apiUrl: string }) {
|
||||
const specLayout = spec.tags || []
|
||||
const operations: enrichedOperation[] = []
|
||||
Object.entries(spec.paths).forEach(([key, val]) => {
|
||||
const fullPath = `${apiUrl}${key}`
|
||||
|
||||
toArrayWithKey(val!, 'operation').forEach((o) => {
|
||||
const operation = o as v3OperationWithPath
|
||||
const enriched = {
|
||||
...operation,
|
||||
path: key,
|
||||
fullPath,
|
||||
operationId: slugify(operation.summary!),
|
||||
|
||||
responseList: toArrayWithKey(operation.responses!, 'responseCode') || [],
|
||||
}
|
||||
operations.push(enriched)
|
||||
})
|
||||
})
|
||||
|
||||
const sections = specLayout.map((section) => {
|
||||
return {
|
||||
...section,
|
||||
title: toTitle(section.name),
|
||||
id: slugify(section.name),
|
||||
operations: operations.filter((operation) => operation.tags?.includes(section.name)),
|
||||
}
|
||||
})
|
||||
|
||||
const content = ejs.render(template, {
|
||||
info: spec.info,
|
||||
sections,
|
||||
operations,
|
||||
})
|
||||
// console.log(content)
|
||||
// Write to disk
|
||||
await writeToDisk(dest, content)
|
||||
console.log('Saved: ', dest)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import * as fs from 'fs'
|
||||
import * as ejs from 'ejs'
|
||||
import * as yaml from 'js-yaml'
|
||||
import { writeToDisk } from './helpers'
|
||||
import template from './templates/CliTemplate'
|
||||
import type { CliSpec } from './types/CliSpec'
|
||||
|
||||
export default async function gen(inputFileName: string, outputDir: string) {
|
||||
const spec = yaml.load(fs.readFileSync(inputFileName, 'utf8')) as any
|
||||
|
||||
switch (spec.clispec) {
|
||||
case '001':
|
||||
await gen_v001(spec, outputDir)
|
||||
break
|
||||
|
||||
default:
|
||||
console.log('Unrecognized specification version:', spec.clispec)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Versioned Generator
|
||||
*/
|
||||
async function gen_v001(spec: CliSpec, dest: string) {
|
||||
let commandMap = new Map(spec.commands.map((item) => [item.id, item]))
|
||||
|
||||
const commands = spec.commands.map((x) => {
|
||||
const isChild = x.subcommands.length < 1
|
||||
const heading = isChild ? `### ${x.summary} {#${x.id}}` : `## ${x.summary} {#${x.id}}`
|
||||
|
||||
return {
|
||||
...x,
|
||||
heading,
|
||||
subcommandList: x.subcommands.map((c) => commandMap.get(c)),
|
||||
}
|
||||
})
|
||||
|
||||
const content = ejs.render(template, {
|
||||
info: spec.info,
|
||||
commands,
|
||||
})
|
||||
// console.log(content)
|
||||
// Write to disk
|
||||
await writeToDisk(dest, content)
|
||||
console.log('Saved: ', dest)
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import template from './templates/ConfigTemplate'
|
||||
import type { ConfigSpec } from './types/ConfigSpec'
|
||||
|
||||
import * as fs from 'fs'
|
||||
import * as ejs from 'ejs'
|
||||
import * as yaml from 'js-yaml'
|
||||
import { writeToDisk } from './helpers'
|
||||
|
||||
type Section = {
|
||||
id: string
|
||||
title: string
|
||||
}
|
||||
|
||||
export default async function gen(inputFileName: string, outputDir: string) {
|
||||
const spec = yaml.load(fs.readFileSync(inputFileName, 'utf8')) as any
|
||||
// console.log('spec', spec)
|
||||
|
||||
switch (spec.configspec) {
|
||||
case '001':
|
||||
await gen_v001(spec, outputDir)
|
||||
break
|
||||
|
||||
default:
|
||||
console.log('Unrecognized specification version:', spec.configspec)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Versioned Generator
|
||||
*/
|
||||
async function gen_v001(spec: ConfigSpec, dest: string) {
|
||||
const specLayout = spec.info.tags
|
||||
const sections = specLayout.map((section: Section) => {
|
||||
const parameters = spec.parameters.filter((parameter) => parameter.tags[0] === section.id)
|
||||
return { ...section, parameters }
|
||||
})
|
||||
|
||||
const content = ejs.render(template, { info: spec.info, sections })
|
||||
// console.log(content)
|
||||
// Write to disk
|
||||
await writeToDisk(dest, content)
|
||||
console.log('Saved: ', dest)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import * as _ from 'lodash'
|
||||
import * as fs from 'fs'
|
||||
|
||||
export const slugify = (text: string) => {
|
||||
return text
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.replace(/[. )(]/g, '-') // Replace spaces and brackets -
|
||||
.replace(/[^\w\-]+/g, '') // Remove all non-word chars
|
||||
.replace(/\-\-+/g, '-') // Replace multiple - with single -
|
||||
.replace(/^-+/, '') // Trim - from start of text
|
||||
.replace(/-+$/, '') // Trim - from end of text
|
||||
}
|
||||
|
||||
// Uppercase the first letter of a string
|
||||
export const toTitle = (text: string) => {
|
||||
return text.charAt(0).toUpperCase() + text.slice(1)
|
||||
}
|
||||
|
||||
/**
|
||||
* writeToDisk()
|
||||
*/
|
||||
export const writeToDisk = (fileName: string, content: any) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.writeFile(fileName, content, (err: any) => {
|
||||
if (err) return reject(err)
|
||||
else return resolve(true)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Object to Array of values
|
||||
*/
|
||||
export const toArrayWithKey = (obj: object, keyAs: string) =>
|
||||
_.values(
|
||||
_.mapValues(obj, (value: any, key: string) => {
|
||||
value[keyAs] = key
|
||||
return value
|
||||
})
|
||||
)
|
||||
@@ -0,0 +1,64 @@
|
||||
import ApiGenerator from './api'
|
||||
import CliGenerator from './cli'
|
||||
import ConfigGenerator from './config'
|
||||
import SdkGenerator from './sdk'
|
||||
import LegacyGenerator from './legacy'
|
||||
|
||||
const main = (command: string[], options: any) => {
|
||||
handleInput(command[0], options)
|
||||
}
|
||||
|
||||
// Run everything
|
||||
const argv = require('minimist')(process.argv.slice(2))
|
||||
main(argv['_'], argv)
|
||||
|
||||
function handleInput(command: string, options: any) {
|
||||
switch (command) {
|
||||
case 'gen':
|
||||
DocGenerator(options)
|
||||
break
|
||||
|
||||
default:
|
||||
console.log('Unrecognized command:', command)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
export default async function DocGenerator({
|
||||
input,
|
||||
output,
|
||||
type,
|
||||
url,
|
||||
}: {
|
||||
input: string
|
||||
output: string
|
||||
type: 'cli' | 'config' | 'sdk' | 'api' | 'legacy'
|
||||
url?: string
|
||||
}) {
|
||||
switch (type) {
|
||||
case 'api':
|
||||
await ApiGenerator(input, output, url || '')
|
||||
break
|
||||
|
||||
case 'cli':
|
||||
await CliGenerator(input, output)
|
||||
break
|
||||
|
||||
case 'config':
|
||||
await ConfigGenerator(input, output)
|
||||
break
|
||||
|
||||
case 'sdk':
|
||||
await SdkGenerator(input, output)
|
||||
break
|
||||
|
||||
case 'legacy':
|
||||
await LegacyGenerator(input, output)
|
||||
break
|
||||
|
||||
default:
|
||||
await console.log('Unrecognized type: ', type)
|
||||
break
|
||||
}
|
||||
return 'Done'
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
// @ts-nocheck
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* ts-node ReferenceGenerator.ts -o {output_dir} {input}.yml
|
||||
*
|
||||
* Example:
|
||||
* ts-node ReferenceGenerator.ts -o docs/client spec/supabase.yml
|
||||
*/
|
||||
|
||||
import Example from './legacy/components/Example'
|
||||
import Page from './legacy/components/Page'
|
||||
import Tab from './legacy/components/Tab'
|
||||
import { slugify, tsDocCommentToMdComment, writeToDisk } from './legacy/lib/helpers'
|
||||
import { TsDoc, OpenRef } from './legacy/definitions'
|
||||
import { uniqBy } from 'lodash'
|
||||
import * as fs from 'fs'
|
||||
import * as yaml from 'js-yaml'
|
||||
|
||||
export default async function gen(inputFileName: string, outputDir: string) {
|
||||
const docSpec = yaml.load(fs.readFileSync(inputFileName, 'utf8'))
|
||||
const defRef = docSpec.info.definition ? fs.readFileSync(docSpec.info.definition, 'utf8') : '{}'
|
||||
|
||||
const definition = JSON.parse(defRef)
|
||||
const id = docSpec.info.id
|
||||
const allLanguages = docSpec.info.libraries
|
||||
const pages = Object.entries(docSpec.pages).map(([name, x]: [string, OpenRef.Page]) => ({
|
||||
...x,
|
||||
pageName: name,
|
||||
}))
|
||||
|
||||
// Index Page
|
||||
const indexFilename = outputDir + `/index.mdx`
|
||||
const index = generateDocsIndexPage(docSpec, inputFileName)
|
||||
await writeToDisk(indexFilename, index)
|
||||
console.log('The index was saved: ', indexFilename)
|
||||
|
||||
// Generate Pages
|
||||
pages.forEach(async (pageSpec: OpenRef.Page) => {
|
||||
try {
|
||||
const slug = slugify(pageSpec.pageName)
|
||||
const hasTsRef = pageSpec['$ref'] || null
|
||||
const tsDefinition = hasTsRef && extractTsDocNode(hasTsRef, definition)
|
||||
if (hasTsRef && !tsDefinition) throw new Error('Definition not found: ' + hasTsRef)
|
||||
|
||||
const description =
|
||||
pageSpec.description || tsDocCommentToMdComment(getDescriptionFromDefintion(tsDefinition))
|
||||
|
||||
// Create page
|
||||
const content = Page({
|
||||
slug: (docSpec.info.slugPrefix || '') + slug,
|
||||
id: slug,
|
||||
specFileName: docSpec.info.specUrl || inputFileName,
|
||||
title: pageSpec.title || pageSpec.pageName,
|
||||
description,
|
||||
parameters: hasTsRef ? generateParameters(tsDefinition) : '',
|
||||
spotlight: generateSpotlight(id, pageSpec['examples'] || [], allLanguages),
|
||||
examples: generateExamples(id, pageSpec['examples'] || [], allLanguages),
|
||||
notes: pageSpec.notes,
|
||||
})
|
||||
|
||||
// Write to disk
|
||||
const dest = outputDir + `/${slug}.mdx`
|
||||
await writeToDisk(dest, content)
|
||||
console.log('Saved: ', dest)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function generateParameters(tsDefinition: any) {
|
||||
let functionDeclaration = null
|
||||
if (tsDefinition.kindString == 'Method') {
|
||||
functionDeclaration = tsDefinition
|
||||
} else if (tsDefinition.kindString == 'Constructor') {
|
||||
functionDeclaration = tsDefinition
|
||||
} else functionDeclaration = tsDefinition?.type?.declaration
|
||||
if (!functionDeclaration) return ''
|
||||
|
||||
const paramDefinitions: TsDoc.TypeDefinition[] = functionDeclaration.signatures[0].parameters // PMC: seems flaky.. why the [0]?
|
||||
if (!paramDefinitions) return ''
|
||||
|
||||
// const paramsComments: TsDoc.CommentTag = tsDefinition.comment?.tags?.filter(x => x.tag == 'param')
|
||||
let parameters = paramDefinitions.map((x) => recurseThroughParams(x)).join(`\n`)
|
||||
return methodListGroup(parameters)
|
||||
}
|
||||
|
||||
function getDescriptionFromDefintion(tsDefinition) {
|
||||
if (!tsDefinition) return null
|
||||
if (['Method', 'Constructor', 'Constructor signature'].includes(tsDefinition.kindString))
|
||||
return tsDefinition?.signatures[0].comment
|
||||
else return tsDefinition?.comment || ''
|
||||
}
|
||||
|
||||
function recurseThroughParams(paramDefinition: TsDoc.TypeDefinition) {
|
||||
// If this is a reference to another Param, let's use the reference instead
|
||||
let param = isDereferenced(paramDefinition) ? paramDefinition.type?.dereferenced : paramDefinition
|
||||
const labelParams = generateLabelParam(param)
|
||||
let subContent = ''
|
||||
|
||||
let children = param?.children
|
||||
if (param.type?.declaration?.children) {
|
||||
children = param.type?.declaration?.children
|
||||
} else if (isUnion(param)) {
|
||||
// We don't want to show the union types if it's a literal
|
||||
const nonLiteralVariants = param.type.types.filter(({ type }) => type !== 'literal')
|
||||
|
||||
if (nonLiteralVariants.length === 0) {
|
||||
children = null
|
||||
} else {
|
||||
children = nonLiteralVariants
|
||||
}
|
||||
} else if (param.type === 'reflection') {
|
||||
children = param.declaration.children
|
||||
}
|
||||
|
||||
if (!!children) {
|
||||
let properties = children
|
||||
.sort((a, b) => a.name?.localeCompare(b.name)) // first alphabetical
|
||||
.sort((a, b) => (a.flags?.isOptional ? 1 : -1)) // required params first
|
||||
.map((x) => recurseThroughParams(x))
|
||||
|
||||
let heading = `<h5 class="method-list-title method-list-title-isChild expanded">Properties</h5>`
|
||||
subContent = methodListGroup([heading].concat(properties).join('\n'))
|
||||
return methodListItemLabel(labelParams, subContent)
|
||||
}
|
||||
return methodListItemLabel(labelParams, subContent)
|
||||
}
|
||||
|
||||
const isDereferenced = (paramDefinition: TsDoc.TypeDefinition) => {
|
||||
return paramDefinition.type?.type == 'reference' && paramDefinition.type?.dereferenced?.id
|
||||
}
|
||||
|
||||
const isUnion = (paramDefinition: TsDoc.TypeDefinition) => {
|
||||
return paramDefinition.type?.type == 'union'
|
||||
}
|
||||
|
||||
const mergeUnion = (paramDefinition: TsDoc.TypeDefinition) => {
|
||||
const joined = paramDefinition.type.types.reduce((acc, x) => {
|
||||
acc.push(...(x.declaration?.children || []))
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
return uniqBy(joined, 'name')
|
||||
}
|
||||
|
||||
const methodListGroup = (items) => `
|
||||
<ul className="method-list-group">
|
||||
${items}
|
||||
</ul>
|
||||
`
|
||||
|
||||
const methodListItemLabel = ({ name, isOptional, type, description }, subContent) => `
|
||||
<li className="method-list-item">
|
||||
<h4 className="method-list-item-label">
|
||||
<span className="method-list-item-label-name">
|
||||
${name}
|
||||
</span>
|
||||
<span className="method-list-item-label-badge ${!isOptional && 'required'}">
|
||||
${isOptional ? 'optional' : 'required'}
|
||||
</span>
|
||||
<span className="method-list-item-validation">
|
||||
${type}
|
||||
</span>
|
||||
</h4>
|
||||
<div class="method-list-item-description">
|
||||
|
||||
${description ? description : 'No description provided. '}
|
||||
|
||||
</div>
|
||||
${subContent}
|
||||
</li>
|
||||
`
|
||||
|
||||
function generateExamples(id: string, specExamples: any, allLanguages: any) {
|
||||
return specExamples.map((example) => {
|
||||
let allTabs = example.hideCodeBlock ? '' : generateCodeBlocks(allLanguages, example)
|
||||
return Example({
|
||||
name: example.name,
|
||||
description: example.description,
|
||||
tabs: allTabs,
|
||||
note: example.note,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* A spotlight is an example which appears at the top of the page.
|
||||
*/
|
||||
function generateSpotlight(id: string, specExamples: any | any[], allLanguages: any) {
|
||||
if (!Array.isArray(specExamples)) {
|
||||
throw new Error(`Examples for each spec should be an array, received: \n${specExamples}`)
|
||||
}
|
||||
const spotlight = (specExamples && specExamples.find((x) => x.isSpotlight)) || null
|
||||
const spotlightContent = !spotlight ? '' : generateCodeBlocks(allLanguages, spotlight)
|
||||
return spotlightContent
|
||||
}
|
||||
|
||||
function generateTabs(allLanguages: any, example: any) {
|
||||
return allLanguages
|
||||
.map((library) => {
|
||||
let content = example[library.id] || notImplemented
|
||||
return Tab(library.id, content)
|
||||
})
|
||||
.join('\n')
|
||||
}
|
||||
|
||||
function generateCodeBlocks(allLanguages: any, example: any) {
|
||||
return allLanguages
|
||||
.map((library) => {
|
||||
let content = example[library.id] || notImplemented
|
||||
return content
|
||||
})
|
||||
.join('\n')
|
||||
}
|
||||
|
||||
const notImplemented = `
|
||||
\`\`\`
|
||||
Not yet implemented
|
||||
\`\`\`
|
||||
`
|
||||
|
||||
function generateLabelParam(param: any) {
|
||||
let labelParams = {}
|
||||
if (typeof param.type === 'string' && param.type === 'literal') {
|
||||
labelParams = {
|
||||
name: param.name ?? param.value,
|
||||
isOptional: !!param.flags?.isOptional,
|
||||
type: param.type,
|
||||
description: param.comment ? tsDocCommentToMdComment(param.comment) : null,
|
||||
}
|
||||
} else {
|
||||
labelParams = {
|
||||
name: param.name ?? extractParamTypeAsString(param),
|
||||
isOptional: !!param.flags?.isOptional,
|
||||
type: extractParamTypeAsString(param),
|
||||
description: param.comment ? tsDocCommentToMdComment(param.comment) : null,
|
||||
}
|
||||
}
|
||||
return labelParams
|
||||
}
|
||||
|
||||
function extractParamTypeAsString(paramDefinition) {
|
||||
if (paramDefinition.type?.name) {
|
||||
return `<code>${paramDefinition.type.name}</code>`
|
||||
} else if (paramDefinition.type?.type == 'union') {
|
||||
return paramDefinition.type.types
|
||||
.map((x) =>
|
||||
x.value
|
||||
? `<code>${x.value}</code>`
|
||||
: x.name
|
||||
? `<code>${x.name}</code>`
|
||||
: x.type
|
||||
? `<code>${x.type}</code>`
|
||||
: ''
|
||||
)
|
||||
.join(' | ')
|
||||
}
|
||||
|
||||
return '<code>object</code>'
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through the definition to find the correct definition.
|
||||
* You can pass it a deeply nested node using dot notation. eg: 'LoggedInUser.data.email'
|
||||
*/
|
||||
function extractTsDocNode(nodeToFind: string, definition: any) {
|
||||
const nodePath = nodeToFind.split('.')
|
||||
let i = 0
|
||||
let previousNode = definition
|
||||
let currentNode = definition
|
||||
while (i < nodePath.length) {
|
||||
previousNode = currentNode
|
||||
currentNode = previousNode.children.find((x) => x.name == nodePath[i]) || null
|
||||
if (currentNode == null) {
|
||||
console.log(`Cant find ${nodePath[i]} in ${previousNode.children.map((x) => '\n' + x.name)}`)
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
return currentNode
|
||||
}
|
||||
|
||||
function generateDocsIndexPage(docSpec: any, inputFileName: string) {
|
||||
return Page({
|
||||
slug: (docSpec.info.slugPrefix || '') + slugify(docSpec.info.title),
|
||||
id: 'index',
|
||||
title: docSpec.info.title,
|
||||
specFileName: inputFileName,
|
||||
description: docSpec.info.description,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
type params = { name: string; description: string; tabs: string; note: string }
|
||||
|
||||
const Example = ({ name, description = '', tabs = '', note = '' }: params) => {
|
||||
if (note !== '') {
|
||||
const [noteTitle, ...noteText] = note.split('\n')
|
||||
console.log(noteText)
|
||||
note = `
|
||||
:::note ${noteTitle}
|
||||
${noteText.join('\n')}
|
||||
:::
|
||||
`
|
||||
}
|
||||
return `
|
||||
### ${name}
|
||||
|
||||
${description}
|
||||
|
||||
${tabs}
|
||||
|
||||
${note}
|
||||
`.trim()
|
||||
}
|
||||
|
||||
export default Example
|
||||
@@ -0,0 +1,65 @@
|
||||
type PageParmas = {
|
||||
id: string
|
||||
title: string
|
||||
slug: string
|
||||
specFileName: string
|
||||
description?: string
|
||||
parameters?: string
|
||||
examples?: string[]
|
||||
spotlight?: string
|
||||
notes?: string
|
||||
result?: string
|
||||
errors?: string
|
||||
}
|
||||
|
||||
const Page = ({
|
||||
id,
|
||||
title,
|
||||
slug,
|
||||
specFileName,
|
||||
description = '',
|
||||
parameters = '',
|
||||
examples = [],
|
||||
spotlight = '',
|
||||
notes = '',
|
||||
result = '',
|
||||
errors = '',
|
||||
}: PageParmas) =>
|
||||
`
|
||||
---
|
||||
id: ${id}
|
||||
title: "${title}"
|
||||
slug: ${slug}
|
||||
custom_edit_url: ${specFileName}
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
${description}
|
||||
|
||||
${spotlight}
|
||||
|
||||
${parameters ? `## Parameters` : ''}
|
||||
|
||||
${parameters}
|
||||
|
||||
${notes ? '## Notes' : ''}
|
||||
|
||||
${notes}
|
||||
|
||||
${result ? `## Result` : ''}
|
||||
|
||||
${result}
|
||||
|
||||
${errors ? `## Errors` : ''}
|
||||
|
||||
${errors}
|
||||
|
||||
${examples.length > 0 ? '## Examples' : ''}
|
||||
|
||||
${examples.join(`\n\n`)}
|
||||
|
||||
`.trim()
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,10 @@
|
||||
const Sidebar = (categories: string[]) =>
|
||||
`
|
||||
module.exports = {
|
||||
docs: [
|
||||
${categories.map((x) => x).join(',\n ')}
|
||||
],
|
||||
}
|
||||
`.trim()
|
||||
|
||||
export default Sidebar
|
||||
@@ -0,0 +1,8 @@
|
||||
export default function SidebarCategory(name: string, items: string[]) {
|
||||
return `{
|
||||
type: 'category',
|
||||
label: '${name}',
|
||||
items: [${items.join(', ')}],
|
||||
collapsed: true,
|
||||
}`
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
const Tab = (library: string, exampleText: string) =>
|
||||
`
|
||||
<TabItem value="${library}">
|
||||
|
||||
${exampleText}
|
||||
|
||||
</TabItem>
|
||||
`.trim()
|
||||
|
||||
export default Tab
|
||||
@@ -0,0 +1,15 @@
|
||||
// @ts-nocheck
|
||||
|
||||
const Tabs = (id: string, libraries = [], examples = []) =>
|
||||
`
|
||||
<Tabs
|
||||
defaultValue="${libraries[0].id}"
|
||||
groupId="${id}"
|
||||
values={[${libraries.map((x) => `{ label: '${x.name}', value: '${x.id}' }`).toString()}]}>
|
||||
|
||||
${examples}
|
||||
|
||||
</Tabs>
|
||||
`.trim()
|
||||
|
||||
export default Tabs
|
||||
@@ -0,0 +1,33 @@
|
||||
export namespace TsDoc {
|
||||
export type DocComment = {
|
||||
shortText?: string
|
||||
text?: string
|
||||
tags?: CommentTag[]
|
||||
}
|
||||
export type CommentTag = {
|
||||
tag: 'param' | 'returns'
|
||||
text: string
|
||||
param: string
|
||||
}
|
||||
|
||||
export type TypeDefinition = {
|
||||
id: string
|
||||
name: string
|
||||
kind: number
|
||||
kindString: 'Parameter' | 'Method'
|
||||
comment?: CommentTag
|
||||
flags: { isExported: boolean; isOptional?: boolean }
|
||||
type: { type: string; name: string; declaration: TypeDefinition }
|
||||
children: TypeDefinition[]
|
||||
}
|
||||
}
|
||||
|
||||
export namespace OpenRef {
|
||||
export type Page = {
|
||||
pageName: string
|
||||
title?: string
|
||||
description?: string
|
||||
notes?: string
|
||||
// $ref?: string // reference to a TSDoc node
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { TsDoc } from '../definitions'
|
||||
|
||||
const fs = require('fs')
|
||||
|
||||
/**
|
||||
* tsDocCommentToMdParams()
|
||||
*/
|
||||
export const tsDocCommentToMdParams = (tags: TsDoc.CommentTag[]) =>
|
||||
tags
|
||||
.filter((x) => x.tag == 'param')
|
||||
.map((x) =>
|
||||
`
|
||||
tag
|
||||
|
||||
`.trim()
|
||||
)
|
||||
.join('\n')
|
||||
|
||||
/**
|
||||
* tsDocCommentToMarkdown()
|
||||
*/
|
||||
export const tsDocCommentToMdComment = (commentObject: TsDoc.DocComment) =>
|
||||
`
|
||||
${commentObject?.shortText || ''}
|
||||
|
||||
${commentObject?.text || ''}
|
||||
|
||||
`.trim()
|
||||
|
||||
/**
|
||||
* slugify()
|
||||
*/
|
||||
export const slugify = (text: string) => {
|
||||
return text
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.replace(/[. )(]/g, '-') // Replace spaces and brackets -
|
||||
.replace(/[^\w\-]+/g, '') // Remove all non-word chars
|
||||
.replace(/\-\-+/g, '-') // Replace multiple - with single -
|
||||
.replace(/^-+/, '') // Trim - from start of text
|
||||
.replace(/-+$/, '') // Trim - from end of text
|
||||
}
|
||||
|
||||
/**
|
||||
* writeToDisk()
|
||||
*/
|
||||
export const writeToDisk = (fileName: string, content: any) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.writeFile(fileName, content, (err: Error) => {
|
||||
if (err) return reject(err)
|
||||
else return resolve(true)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import * as fs from 'fs'
|
||||
import * as ejs from 'ejs'
|
||||
import * as yaml from 'js-yaml'
|
||||
import template from './templates/SdkTemplate'
|
||||
import { writeToDisk } from './helpers'
|
||||
import type { SdkSpec } from './types/SdkSpec'
|
||||
|
||||
export default async function gen(inputFileName: string, outputDir: string) {
|
||||
const spec = yaml.load(fs.readFileSync(inputFileName, 'utf8')) as any
|
||||
// console.log('spec', spec)
|
||||
|
||||
switch (spec.sdkspec) {
|
||||
case '001':
|
||||
await gen_v001(spec, outputDir)
|
||||
break
|
||||
|
||||
default:
|
||||
console.log('Unrecognized specification version:', spec.sdkspec)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Versioned Generator
|
||||
*/
|
||||
async function gen_v001(spec: SdkSpec, dest: string) {
|
||||
const functions = spec.functions
|
||||
const types = spec.types
|
||||
|
||||
const content = ejs.render(template, {
|
||||
info: spec.info,
|
||||
functions,
|
||||
types,
|
||||
})
|
||||
// console.log(content)
|
||||
// Write to disk
|
||||
await writeToDisk(dest, content)
|
||||
console.log('Saved: ', dest)
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
const template = `
|
||||
---
|
||||
id: usage
|
||||
slug: /usage
|
||||
title: Usage
|
||||
toc_max_heading_level: 3
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
|
||||
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY IF THIS IS VERSION "next" -->
|
||||
|
||||
<%- info.description %>
|
||||
|
||||
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY IF THIS IS VERSION "next" -->
|
||||
|
||||
|
||||
<% sections.forEach(function(section){ %>
|
||||
|
||||
## <%- section.title %> {#<%= section.id %>}
|
||||
|
||||
<%- section.description %>
|
||||
|
||||
<% section.operations.forEach(function(operation){ %>
|
||||
|
||||
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY IF THIS IS VERSION "next" -->
|
||||
|
||||
### <%- operation.summary %> {#<%- operation.operationId %>}
|
||||
|
||||
\`\`\`
|
||||
<%- operation.operation.toUpperCase() %> <%- operation.fullPath %>
|
||||
\`\`\`
|
||||
|
||||
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY IF THIS IS VERSION "next" -->
|
||||
|
||||
<% if(operation.parameters && operation.parameters.filter((parameter) => parameter.in === 'path').length > 0){ %>
|
||||
#### Path Parameters
|
||||
|
||||
<ul className="method-list-group">
|
||||
<% operation.parameters
|
||||
.filter((parameter) => parameter.in === 'path').forEach(function(parameter){ %>
|
||||
<li className="method-list-item">
|
||||
<h4 className="method-list-item-label">
|
||||
<span className="method-list-item-label-name">
|
||||
<%- parameter.name %>
|
||||
</span>
|
||||
<span className="method-list-item-label-badge">
|
||||
<%- parameter.required ? 'required' : 'optional' %>
|
||||
</span>
|
||||
<span className="method-list-item-validation">
|
||||
<%- parameter.type %>
|
||||
</span>
|
||||
</h4>
|
||||
<% if(parameter.example){ %>
|
||||
<h4 className="method-list-item-label">
|
||||
Example:
|
||||
<span className="method-list-item-label-badge">
|
||||
<%- parameter.example %>
|
||||
</span>
|
||||
</h4>
|
||||
<% } %>
|
||||
<div class="method-list-item-description">
|
||||
<%- parameter.description %>
|
||||
</div>
|
||||
</li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
<% }; %>
|
||||
|
||||
<% if(operation.parameters && operation.parameters.filter((parameter) => parameter.in === 'header').length > 0){ %>
|
||||
#### Header Parameters
|
||||
<ul className="method-list-group">
|
||||
<% operation.parameters
|
||||
.filter((parameter) => parameter.in === 'header').forEach(function(parameter){ %>
|
||||
<li className="method-list-item">
|
||||
<h4 className="method-list-item-label">
|
||||
<span className="method-list-item-label-name">
|
||||
<%- parameter.name %>
|
||||
</span>
|
||||
<span className="method-list-item-label-badge">
|
||||
<%- parameter.required ? 'required' : 'optional' %>
|
||||
</span>
|
||||
<span className="method-list-item-validation">
|
||||
<%- parameter.type %>
|
||||
</span>
|
||||
</h4>
|
||||
<% if(parameter.example){ %>
|
||||
<h4 className="method-list-item-label">
|
||||
Example:
|
||||
<span className="method-list-item-label-badge">
|
||||
<%- parameter.example %>
|
||||
</span>
|
||||
</h4>
|
||||
<% } %>
|
||||
<div class="method-list-item-description">
|
||||
<%- parameter.description %>
|
||||
</div>
|
||||
</li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
<% }; %>
|
||||
|
||||
|
||||
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY IF THIS IS VERSION "next" -->
|
||||
|
||||
<% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %>
|
||||
#### Body Parameters
|
||||
|
||||
\`\`\`json
|
||||
<%- JSON.stringify(operation.requestBody?.content['application/json'], null, 2) %>
|
||||
\`\`\`
|
||||
<% }; %>
|
||||
|
||||
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY IF THIS IS VERSION "next" -->
|
||||
|
||||
#### Responses
|
||||
|
||||
<Tabs>
|
||||
<% operation.responseList.forEach(function(response){ %>
|
||||
<TabItem value="<%- response.responseCode %>">
|
||||
|
||||
<%- response.description %>
|
||||
|
||||
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY IF THIS IS VERSION "next" -->
|
||||
|
||||
<% if(response?.content && response?.content['application/json']){ %>
|
||||
\`\`\`json
|
||||
<%- JSON.stringify(response.content['application/json'], null, 2) %>
|
||||
\`\`\`
|
||||
<% }; %>
|
||||
|
||||
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY IF THIS IS VERSION "next" -->
|
||||
|
||||
</TabItem>
|
||||
<% }); %>
|
||||
</Tabs>
|
||||
|
||||
<br />
|
||||
<% }); %>
|
||||
<% }); %>
|
||||
|
||||
|
||||
`.trim()
|
||||
|
||||
export default template
|
||||
@@ -0,0 +1,52 @@
|
||||
const template = `
|
||||
---
|
||||
id: usage
|
||||
slug: /usage
|
||||
title: Usage
|
||||
toc_max_heading_level: 3
|
||||
---
|
||||
|
||||
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY -->
|
||||
|
||||
<%- info.description %>
|
||||
|
||||
<% commands.forEach(function(command){ %>
|
||||
|
||||
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY -->
|
||||
|
||||
<br /><br />
|
||||
|
||||
<%- command.heading %>
|
||||
|
||||
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY -->
|
||||
|
||||
<%- command.description %>
|
||||
|
||||
|
||||
<% if(command.usage){ %>
|
||||
<%- command.usage %>
|
||||
<% }; %>
|
||||
|
||||
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY -->
|
||||
|
||||
<% if(command.subcommands.length > 0){ %>
|
||||
**Available Commands**
|
||||
|
||||
<% command.subcommandList.forEach(function(subcommand){ %>
|
||||
- <a href="#<%- subcommand.id %>"><code><%- subcommand.title %></code></a>
|
||||
<% }); %>
|
||||
<% } %>
|
||||
|
||||
|
||||
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY -->
|
||||
|
||||
**Options**
|
||||
|
||||
<%- command.options %>
|
||||
|
||||
<% }); %>
|
||||
|
||||
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY -->
|
||||
`.trim()
|
||||
|
||||
export default template
|
||||
@@ -0,0 +1,41 @@
|
||||
const template = `
|
||||
---
|
||||
id: config
|
||||
slug: /config
|
||||
title: Configuration
|
||||
toc_max_heading_level: 3
|
||||
---
|
||||
|
||||
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY -->
|
||||
|
||||
<%= info.description %>
|
||||
|
||||
<% sections.forEach(function(section){ %>
|
||||
|
||||
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY -->
|
||||
|
||||
## <%= section.title %> {#<%= section.id %>}
|
||||
|
||||
<% section.parameters.forEach(function(parameter){ %>
|
||||
|
||||
### \`<%- parameter.title %>\` {#<%= parameter.id %>}
|
||||
|
||||
<%- parameter.description %>
|
||||
|
||||
<ul>
|
||||
<li>Required: <code><%= parameter.required %></code></li>
|
||||
<li>Default: <code><%- parameter?.default ? parameter?.default : 'None' %></code></li><% if(parameter?.links?.length){ %>
|
||||
<li>
|
||||
<span>See also:</span>
|
||||
<ul><% parameter.links?.forEach(function(link){ %><li><a href="<%- link.link %>"><%- link.name %></a></li><% }); %></ul>
|
||||
</li>
|
||||
<% } %>
|
||||
</ul>
|
||||
|
||||
<br />
|
||||
<% }); %>
|
||||
|
||||
<% }); %>
|
||||
`.trim()
|
||||
|
||||
export default template
|
||||
@@ -0,0 +1,51 @@
|
||||
const template = `
|
||||
---
|
||||
id: usage
|
||||
title: Usage
|
||||
toc_max_heading_level: 2
|
||||
---
|
||||
|
||||
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY IF THIS IS VERSION "next" -->
|
||||
|
||||
<%- info.description %>
|
||||
|
||||
|
||||
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY IF THIS IS VERSION "next" -->
|
||||
|
||||
|
||||
<% functions.forEach(function(fn){ %>
|
||||
|
||||
## <%- fn.summary %>
|
||||
|
||||
<%- fn.usage %>
|
||||
|
||||
<% if (fn.attributes) { %>
|
||||
|
||||
### Attributes
|
||||
|
||||
<% fn.attributes.forEach(function(attribute){ %>
|
||||
- <%- attribute.id %>
|
||||
<% }); %>
|
||||
|
||||
<% } %>
|
||||
|
||||
### Notes
|
||||
|
||||
<%- fn.description %>
|
||||
|
||||
### Examples
|
||||
|
||||
<% fn.examples.forEach(function(example){ %>
|
||||
|
||||
#### <%- example.summary %>
|
||||
|
||||
<%- example.code %>
|
||||
|
||||
<% }); %>
|
||||
|
||||
<% }); %>
|
||||
|
||||
|
||||
`.trim()
|
||||
|
||||
export default template
|
||||
@@ -0,0 +1,13 @@
|
||||
// generator files need their own tsconfig.json because they don't like "module": "esnext" setting that nextjs requires in the main tsconfig
|
||||
{
|
||||
"compilerOptions": {
|
||||
"incremental": true,
|
||||
"noImplicitAny": false,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { SpecLink } from './Spec'
|
||||
import { Url } from 'url'
|
||||
|
||||
export type CliInfo = {
|
||||
id: string
|
||||
version: string
|
||||
title: string
|
||||
language: string
|
||||
source: Url
|
||||
bugs: Url
|
||||
spec: Url
|
||||
description: string
|
||||
options: string
|
||||
}
|
||||
|
||||
export type CliCommand = {
|
||||
id: string
|
||||
title: string
|
||||
summary: string
|
||||
description: string
|
||||
tags: string[]
|
||||
links: SpecLink[]
|
||||
usage: string
|
||||
subcommands: string[]
|
||||
options: string
|
||||
}
|
||||
|
||||
export interface CliSpec {
|
||||
clispec: '001'
|
||||
|
||||
info: CliInfo
|
||||
|
||||
commands: CliCommand[]
|
||||
}
|
||||
Loaded 100 of 158 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user