This commit is contained in:
Copple authored and Bobbie Soedirgo committed 2022-10-05 20:31:31 +08:00
1 parent c760942903
commit 893fb1fa4e
32 files changed
+687 -960

No files matched your search

File diff suppressed because it is too large. Load diff
+6 -10
View File
@@ -11,25 +11,21 @@ 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>
<li>
Required: <code>true</code>
</li>
<li>
Default: <code>None</code>
</li>
</ul>
<br />
+2 -5
View File
@@ -5,14 +5,11 @@ title: Usage
toc_max_heading_level: 3
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
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" -->
@@ -1,12 +1,12 @@
---
id: generating-types
title: "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';
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).
@@ -19,12 +19,12 @@ If you don't provide a type for the row you need to explicitly pass `from<any>('
```ts
type Message = {
id: number;
inserted_at: string;
message: string;
user_id: string;
channel_id: number;
author: { username: string };
id: number
inserted_at: string
message: string
user_id: string
channel_id: number
author: { username: string }
}
const response = await supabase
@@ -39,4 +39,4 @@ const response = await supabase
.select('*, author:user_id(username)')
.match({ channel_id: 2 })
response.data // Response data will be of type Array<any>.
```
```
@@ -1,12 +1,11 @@
---
id: index
title: "Supabase Client"
title: 'Supabase Client'
slug: supabase-client
custom_edit_url: ../../spec/supabase_js_v1.yml
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
Supabase JavaScript.
Supabase JavaScript.
@@ -1,24 +1,20 @@
---
id: initializing
title: "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';
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">
@@ -41,7 +37,6 @@ The unique Supabase URL which is supplied when you create a new project in your
</li>
<li className="method-list-item">
<h4 className="method-list-item-label">
<span className="method-list-item-label-name">
@@ -62,7 +57,6 @@ The unique Supabase Key which is supplied when you create a new project in your
</li>
<li className="method-list-item">
<h4 className="method-list-item-label">
<span className="method-list-item-label-name">
@@ -77,7 +71,7 @@ The unique Supabase Key which is supplied when you create a new project in your
</h4>
<div class="method-list-item-description">
No description provided.
No description provided.
</div>
@@ -85,36 +79,22 @@ No description provided.
</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')
const supabase = createClient(
'https://xyzcompany.supabase.co',
'public-anon-key'
)
```
### With additional parameters
```js
import { createClient } from '@supabase/supabase-js'
@@ -123,22 +103,28 @@ const options = {
headers: { 'x-my-custom-header': 'my-app-name' },
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: true
detectSessionInUrl: true,
}
const supabase = createClient("https://xyzcompany.supabase.co", "public-anon-key", options)
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'
})
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.
@@ -148,16 +134,18 @@ Note: each client connection can only access a single schema, so the code above
### Custom `fetch` implementation
```js
import { createClient } from '@supabase/supabase-js'
const supabase = createClient('https://xyzcompany.supabase.co', 'public-anon-key', {
fetch: fetch.bind(globalThis)
})
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).
This is most useful in environments where `cross-fetch` is not compatible (for instance Cloudflare Workers).
@@ -1,12 +1,12 @@
---
id: installing
title: "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';
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
All JavaScript libraries are built directly by the Supabase team.
@@ -15,11 +15,13 @@ 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
```
@@ -27,8 +29,9 @@ 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"></script>
//or
<script src="https://unpkg.com/@supabase/supabase-js"></script>
```
```
+8 -3
View File
@@ -79,7 +79,7 @@ You can use it completely, or just the features you need.
description={'User management with Row Level Security.'}
style={{ height: '100%' }}
/>
</div>
</div>
{/* Auto-generated APIs */}
<div class="col col--4">
<ButtonCard
@@ -150,7 +150,7 @@ Supabase is just Postgres, which makes it compatible with a large number of tool
<div>
<div class="row is-multiline">
{frameworks.map((x) => (
<div key={x.name} class="col col--3">
<div key={x.name} class="col col--3">
<ButtonCard
layout="horizontal"
icon={
@@ -162,7 +162,12 @@ Supabase is just Postgres, which makes it compatible with a large number of tool
/>
) : (
<ThemedImage
style={{ display: 'block', maxHeight: 20, minWidth: 20, margin: 0 }}
style={{
display: 'block',
maxHeight: 20,
minWidth: 20,
margin: 0,
}}
alt={x.name}
width="20"
sources={{
@@ -21,14 +21,16 @@ A collection of framework-specific Auth utilities for working with Supabase.
description={'A pre-built React component for authenticating users.'}
style={{ height: '100%' }}
/>
</div>
</div>
{/* Next.js */}
<div class="col col--4">
<ButtonCard
class="card"
to={useBaseUrl('/guides/auth/auth-helpers/nextjs')}
title={'Next.js'}
description={'Helpers for authenticating users in Next.js applications.'}
description={
'Helpers for authenticating users in Next.js applications.'
}
style={{ height: '100%' }}
/>
</div>
@@ -38,7 +40,9 @@ A collection of framework-specific Auth utilities for working with Supabase.
class="card"
to={useBaseUrl('/guides/auth/auth-helpers/sveltekit')}
title={'SvelteKit'}
description={'Helpers for authenticating users in SvelteKit applications.'}
description={
'Helpers for authenticating users in SvelteKit applications.'
}
style={{ height: '100%' }}
/>
</div>
@@ -2,7 +2,7 @@
id: nextjs
title: Supabase Auth with Next.js
description: Authentication helpers for Next.js API routes, middleware, and SSR.
sidebar_label: "Next.js"
sidebar_label: 'Next.js'
---
This submodule provides convenience helpers for implementing user authentication in Next.js applications.
@@ -56,24 +56,28 @@ PUBLIC_SUPABASE_ANON_KEY=your-anon-key
Start off by creating a `db.ts` file inside of the `src/lib` directory and instantiate the `supabaseClient`.
```ts title="src/lib/db.ts"
import { createClient } from '@supabase/supabase-js';
import { setupSupabaseHelpers } from '@supabase/auth-helpers-sveltekit';
import { dev } from '$app/environment';
import { env } from '$env/dynamic/public';
import { createClient } from '@supabase/supabase-js'
import { setupSupabaseHelpers } from '@supabase/auth-helpers-sveltekit'
import { dev } from '$app/environment'
import { env } from '$env/dynamic/public'
// or use the static env
// import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public';
export const supabaseClient = createClient(env.PUBLIC_SUPABASE_URL, env.PUBLIC_SUPABASE_ANON_KEY, {
persistSession: false,
autoRefreshToken: false
});
export const supabaseClient = createClient(
env.PUBLIC_SUPABASE_URL,
env.PUBLIC_SUPABASE_ANON_KEY,
{
persistSession: false,
autoRefreshToken: false,
}
)
setupSupabaseHelpers({
supabaseClient,
cookieOptions: {
secure: !dev
}
});
supabaseClient,
cookieOptions: {
secure: !dev,
},
})
```
### Initialize the client
@@ -83,16 +87,16 @@ Edit your `+layout.svelte` file and set up the client-side.
```html title="src/routes/+layout.svelte"
<script lang="ts">
// make sure the supabase instance is initialized on the client
import '$lib/db';
import { startSupabaseSessionSync } from '@supabase/auth-helpers-sveltekit';
import { page } from '$app/stores';
import { invalidateAll } from '$app/navigation';
import '$lib/db'
import { startSupabaseSessionSync } from '@supabase/auth-helpers-sveltekit'
import { page } from '$app/stores'
import { invalidateAll } from '$app/navigation'
// this sets up automatic token refreshing
startSupabaseSessionSync({
page,
handleRefresh: () => invalidateAll()
});
page,
handleRefresh: () => invalidateAll(),
})
</script>
<slot />
@@ -104,16 +108,16 @@ The `hooks.ts` file is where the heavy lifting of this library happens:
```ts title="src/hooks.server.ts"
// make sure the supabase instance is initialized on the server
import '$lib/db';
import { dev } from '$app/environment';
import { auth } from '@supabase/auth-helpers-sveltekit/server';
import '$lib/db'
import { dev } from '$app/environment'
import { auth } from '@supabase/auth-helpers-sveltekit/server'
export const handle = auth();
export const handle = auth()
// use the sequence helper if you have additional Handle methods
import { sequence } from '@sveltejs/kit/hooks';
import { sequence } from '@sveltejs/kit/hooks'
export const handle = sequence(auth(), yourHandler);
export const handle = sequence(auth(), yourHandler)
```
There are three handle methods available:
@@ -130,13 +134,13 @@ There are three handle methods available:
In order to make the session available to the UI (e.g., pages and layouts), pass the session in the root layout load function:
```ts title="src/routes/+layout.server.ts"
import type { LayoutServerLoad } from './$types';
import type { LayoutServerLoad } from './$types'
export const load: LayoutServerLoad = async ({ locals }) => {
return {
session: locals.session
};
};
session: locals.session,
}
}
```
### Typings
@@ -151,10 +155,10 @@ In order to get the most out of TypeScript and IntelliSense, import the types in
// and what to do when importing types
declare namespace App {
interface Locals {
session: import('@supabase/auth-helpers-sveltekit').SupabaseSession;
session: import('@supabase/auth-helpers-sveltekit').SupabaseSession
}
interface PageData {
session: import('@supabase/auth-helpers-sveltekit').SupabaseSession;
session: import('@supabase/auth-helpers-sveltekit').SupabaseSession
}
// interface Error {}
// interface Platform {}
@@ -168,22 +172,23 @@ This library has provided a method to delete the session cookie.
Create a server only page with an action to delete the cookie
```ts title="src/routes/logout/+page.server.ts"
import { deleteSession } from '@supabase/auth-helpers-sveltekit/server';
import { redirect } from '@sveltejs/kit';
import type { Actions } from './$types';
import { deleteSession } from '@supabase/auth-helpers-sveltekit/server'
import { redirect } from '@sveltejs/kit'
import type { Actions } from './$types'
export const actions: Actions = {
async default({ cookies }) {
deleteSession(cookies);
throw redirect(303, '/');
}
};
async default({ cookies }) {
deleteSession(cookies)
throw redirect(303, '/')
},
}
```
Then make a **POST** request with a form to the server only page above.
```html
<script lang="ts">
import { enhanceAndInvalidate } from '@supabase/auth-helpers-sveltekit';
import { enhanceAndInvalidate } from '@supabase/auth-helpers-sveltekit'
</script>
<form action="/logout" method="post" use:enhanceAndInvalidate>
@@ -197,14 +202,14 @@ You can now determine if a user is authenticated on the client-side by checking
```html title="src/routes/+page.svelte"
<script>
import { page } from '$app/stores';
import { page } from '$app/stores'
</script>
{#if !$page.data.session.user}
<h1>I am not logged in</h1>
<h1>I am not logged in</h1>
{:else}
<h1>Welcome {$page.data.session.user.email}</h1>
<p>I am logged in!</p>
<h1>Welcome {$page.data.session.user.email}</h1>
<p>I am logged in!</p>
{/if}
```
@@ -214,23 +219,23 @@ For [row level security](https://supabase.com/docs/learn/auth-deep-dive/auth-row
```html
<script>
import { supabaseClient } from '$lib/db';
import { page } from '$app/stores';
import { supabaseClient } from '$lib/db'
import { page } from '$app/stores'
let loadedData = [];
let loadedData = []
async function loadData() {
const { data } = await supabaseClient.from('test').select('*').limit(20);
loadedData = data;
const { data } = await supabaseClient.from('test').select('*').limit(20)
loadedData = data
}
$: if ($page.data.session.user) {
loadData();
loadData()
}
</script>
{#if $page.data.session.user}
<p>client-side data fetching with RLS</p>
<pre>{JSON.stringify(loadedData, null, 2)}</pre>
<p>client-side data fetching with RLS</p>
<pre>{JSON.stringify(loadedData, null, 2)}</pre>
{/if}
```
@@ -239,8 +244,8 @@ For [row level security](https://supabase.com/docs/learn/auth-deep-dive/auth-row
```html title="src/routes/profile/+page.svelte"
<script>
/** @type {import('./$types').PageData} */
export let data;
$: ({ user, tableData } = data);
export let data
$: ({ user, tableData } = data)
</script>
<div>Protected content for {user.email}</div>
@@ -281,14 +286,14 @@ Always use the instance returned by `getSupabaseClient()` directly!
```ts
// Bad
const supabaseClient = getSupabaseClient();
const supabaseClient = getSupabaseClient()
await supabaseClient.from('table1').select();
await supabaseClient.from('table2').select();
await supabaseClient.from('table1').select()
await supabaseClient.from('table2').select()
// Good
await getSupabaseClient().from('table1').select();
await getSupabaseClient().from('table2').select();
await getSupabaseClient().from('table1').select()
await getSupabaseClient().from('table2').select()
```
## Protecting API routes
@@ -324,34 +329,34 @@ If you visit `/api/protected-route` without a valid session cookie, you will get
Wrap an Action to check that the user has a valid session. If they're not logged in, the session is `null`.
```ts title="src/routes/posts/+page.server.ts"
import type { Actions } from './$types';
import { withAuth } from '@supabase/auth-helpers-sveltekit';
import { error, invalid } from '@sveltejs/kit';
import type { Actions } from './$types'
import { withAuth } from '@supabase/auth-helpers-sveltekit'
import { error, invalid } from '@sveltejs/kit'
export const actions: Actions = {
createPost: withAuth(async ({ session, getSupabaseClient, request }) => {
if (!session.user) {
// the user is not signed in
throw error(403, { message: 'Unauthorized' });
throw error(403, { message: 'Unauthorized' })
}
// we are save, let the user create the post
const formData = await request.formData();
const content = formData.get('content');
const formData = await request.formData()
const content = formData.get('content')
const { error: createPostError, data: newPost } = await getSupabaseClient()
.from('posts')
.insert({ content });
.insert({ content })
if (createPostError) {
return invalid(500, {
supabaseErrorMessage: createPostError.message
});
supabaseErrorMessage: createPostError.message,
})
}
return {
newPost
};
})
};
newPost,
}
}),
}
```
If you try to submit a form with the action `?/createPost` without a valid session cookie, you will get a 403 error response.
@@ -361,62 +366,62 @@ If you try to submit a form with the action `?/createPost` without a valid sessi
Use `saveSession` to save the session cookies:
```ts
import type { Actions } from './$types';
import { supabaseClient } from '$lib/db';
import { invalid, redirect } from '@sveltejs/kit';
import { saveSession } from '@supabase/auth-helpers-sveltekit/server';
import type { Actions } from './$types'
import { supabaseClient } from '$lib/db'
import { invalid, redirect } from '@sveltejs/kit'
import { saveSession } from '@supabase/auth-helpers-sveltekit/server'
export const actions: Actions = {
async signin({ request, cookies, url }) {
const formData = await request.formData();
const formData = await request.formData()
const email = formData.get('email') as string;
const password = formData.get('password') as string;
const email = formData.get('email') as string
const password = formData.get('password') as string
const { data, error } = await supabaseClient.auth.api.signInWithEmail(
email,
password,
{
redirectTo: `${url.origin}/logging-in`
redirectTo: `${url.origin}/logging-in`,
}
);
)
if (error || !data) {
if (error?.status === 400) {
return invalid(400, {
error: 'Invalid credentials',
values: {
email
}
});
email,
},
})
}
return invalid(500, {
error: 'Server error. Try again later.',
values: {
email
}
});
email,
},
})
}
saveSession(cookies, data);
throw redirect(303, '/dashboard');
}
};
saveSession(cookies, data)
throw redirect(303, '/dashboard')
},
}
```
Use `deleteSession` to delete the session cookies:
```ts
import type { Actions } from './$types';
import { deleteSession } from '@supabase/auth-helpers-sveltekit/server';
import { redirect } from '@sveltejs/kit';
import type { Actions } from './$types'
import { deleteSession } from '@supabase/auth-helpers-sveltekit/server'
import { redirect } from '@sveltejs/kit'
export const actions: Actions = {
async logout({ cookies }) {
deleteSession(cookies);
throw redirect(303, '/');
}
};
deleteSession(cookies)
throw redirect(303, '/')
},
}
```
## Custom session namespace
@@ -426,10 +431,10 @@ If you want to use something other than `locals.session` and `$page.data.session
```ts title="src/app.d.ts"
declare namespace App {
interface Locals {
mySupabaseSession: import('@supabase/auth-helpers-sveltekit').SupabaseSession;
mySupabaseSession: import('@supabase/auth-helpers-sveltekit').SupabaseSession
}
interface PageData {
mySupabaseSession: import('@supabase/auth-helpers-sveltekit').SupabaseSession;
mySupabaseSession: import('@supabase/auth-helpers-sveltekit').SupabaseSession
}
}
```
@@ -438,20 +443,20 @@ declare namespace App {
setupSupabaseServer({
supabaseClient,
cookieOptions: {
secure: !dev
secure: !dev,
},
// --- change location within locals ---
getSessionFromLocals: (locals) => locals.mySupabaseSession,
setSessionToLocals: (locals, session) => (locals.mySupabaseSession = session)
});
setSessionToLocals: (locals, session) => (locals.mySupabaseSession = session),
})
```
```ts title="src/lib/db.ts"
setupSupabaseClient({
supabaseClient,
// --- change location within pageData ---
getSessionFromPageData: (data) => data.mySupabaseSession
});
getSessionFromPageData: (data) => data.mySupabaseSession,
})
```
## Migrate from 0.6.11 and below to 0.7.0 {#migration}
@@ -489,25 +494,29 @@ export { supabaseClient };
<TabItem value="latest">
```js title="src/lib/db.ts"
import { createClient } from '@supabase/supabase-js';
import { setupSupabaseHelpers } from '@supabase/auth-helpers-sveltekit';
import { dev } from '$app/environment';
import { env } from '$env/dynamic/public';
import { createClient } from '@supabase/supabase-js'
import { setupSupabaseHelpers } from '@supabase/auth-helpers-sveltekit'
import { dev } from '$app/environment'
import { env } from '$env/dynamic/public'
// or use the static env
// import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public';
export const supabaseClient = createClient(env.PUBLIC_SUPABASE_URL, env.PUBLIC_SUPABASE_ANON_KEY, {
persistSession: false,
autoRefreshToken: false
});
export const supabaseClient = createClient(
env.PUBLIC_SUPABASE_URL,
env.PUBLIC_SUPABASE_ANON_KEY,
{
persistSession: false,
autoRefreshToken: false,
}
)
setupSupabaseHelpers({
supabaseClient,
cookieOptions: {
secure: !dev
}
});
secure: !dev,
},
})
```
</TabItem>
@@ -526,9 +535,9 @@ setupSupabaseHelpers({
```html title="src/routes/__layout.svelte"
<script>
import { session } from '$app/stores';
import { supabaseClient } from '$lib/db';
import { SupaAuthHelper } from '@supabase/auth-helpers-svelte';
import { session } from '$app/stores'
import { supabaseClient } from '$lib/db'
import { SupaAuthHelper } from '@supabase/auth-helpers-svelte'
</script>
<SupaAuthHelper {supabaseClient} {session}>
@@ -545,16 +554,16 @@ The `@supabase/auth-helpers-svelte` library is no longer required as the `@supab
```html title="src/routes/+layout.svelte"
<script lang="ts">
// make sure the supabase instance is initialized on the client
import '$lib/db';
import { startSupabaseSessionSync } from '@supabase/auth-helpers-sveltekit';
import { page } from '$app/stores';
import { invalidateAll } from '$app/navigation';
import '$lib/db'
import { startSupabaseSessionSync } from '@supabase/auth-helpers-sveltekit'
import { page } from '$app/stores'
import { invalidateAll } from '$app/navigation'
// this sets up automatic token refreshing
startSupabaseSessionSync({
page,
handleRefresh: () => invalidateAll()
});
page,
handleRefresh: () => invalidateAll(),
})
</script>
<slot />
@@ -575,18 +584,18 @@ The `@supabase/auth-helpers-svelte` library is no longer required as the `@supab
<TabItem value="older">
```ts title="src/hooks.ts"
import { handleAuth } from '@supabase/auth-helpers-sveltekit';
import type { GetSession, Handle } from '@sveltejs/kit';
import { sequence } from '@sveltejs/kit/hooks';
import { handleAuth } from '@supabase/auth-helpers-sveltekit'
import type { GetSession, Handle } from '@sveltejs/kit'
import { sequence } from '@sveltejs/kit/hooks'
export const handle: Handle = sequence(...handleAuth());
export const handle: Handle = sequence(...handleAuth())
export const getSession: GetSession = async (event) => {
const { user, accessToken, error } = event.locals;
return {
user,
accessToken,
error
const { user, accessToken, error } = event.locals
return {
user,
accessToken,
error,
}
}
```
@@ -597,23 +606,23 @@ export const getSession: GetSession = async (event) => {
```ts title="src/hooks.server.ts"
// make sure the supabase instance is initialized on the server
import '$lib/db';
import { dev } from '$app/environment';
import { auth } from '@supabase/auth-helpers-sveltekit/server';
import '$lib/db'
import { dev } from '$app/environment'
import { auth } from '@supabase/auth-helpers-sveltekit/server'
export const handle = auth();
export const handle = auth()
```
**Optional** *if using additional handle methods*
**Optional** _if using additional handle methods_
```ts title="src/hooks.server.ts"
// make sure the supabase instance is initialized on the server
import '$lib/db';
import { dev } from '$app/environment';
import { auth } from '@supabase/auth-helpers-sveltekit/server';
import { sequence } from '@sveltejs/kit/hooks';
import '$lib/db'
import { dev } from '$app/environment'
import { auth } from '@supabase/auth-helpers-sveltekit/server'
import { sequence } from '@sveltejs/kit/hooks'
export const handle = sequence(auth(), yourHandler);
export const handle = sequence(auth(), yourHandler)
```
</TabItem>
@@ -636,16 +645,16 @@ export const handle = sequence(auth(), yourHandler);
// for information about these interfaces
declare namespace App {
interface UserSession {
user: import('@supabase/supabase-js').User;
accessToken?: string;
user: import('@supabase/supabase-js').User
accessToken?: string
}
interface Locals extends UserSession {
error: import('@supabase/supabase-js').ApiError;
error: import('@supabase/supabase-js').ApiError
}
interface Session extends UserSession {}
// interface Platform {}
// interface Stuff {}
}
@@ -663,11 +672,11 @@ declare namespace App {
// and what to do when importing types
declare namespace App {
interface Locals {
session: import('@supabase/auth-helpers-sveltekit').SupabaseSession;
session: import('@supabase/auth-helpers-sveltekit').SupabaseSession
}
interface PageData {
session: import('@supabase/auth-helpers-sveltekit').SupabaseSession;
session: import('@supabase/auth-helpers-sveltekit').SupabaseSession
}
// interface Error {}
@@ -691,14 +700,14 @@ declare namespace App {
```html title="src/routes/index.svelte"
<script>
import { session } from '$app/stores';
import { session } from '$app/stores'
</script>
{#if !$session.user}
<h1>I am not logged in</h1>
<h1>I am not logged in</h1>
{:else}
<h1>Welcome {$session.user.email}</h1>
<p>I am logged in!</p>
<h1>Welcome {$session.user.email}</h1>
<p>I am logged in!</p>
{/if}
```
@@ -708,14 +717,14 @@ declare namespace App {
```html title="src/routes/+page.svelte"
<script>
import { page } from '$app/stores';
import { page } from '$app/stores'
</script>
{#if !$page.data.session.user}
<h1>I am not logged in</h1>
<h1>I am not logged in</h1>
{:else}
<h1>Welcome {$page.data.session.user.email}</h1>
<p>I am logged in!</p>
<h1>Welcome {$page.data.session.user.email}</h1>
<p>I am logged in!</p>
{/if}
```
@@ -735,25 +744,30 @@ declare namespace App {
```html title="src/routes/protected-route.svelte"
<script lang="ts" context="module">
import { supabaseServerClient, withPageAuth } from '@supabase/auth-helpers-sveltekit';
import type { Load } from './__types/protected-page';
import {
supabaseServerClient,
withPageAuth,
} from '@supabase/auth-helpers-sveltekit'
import type { Load } from './__types/protected-page'
export const load: Load = async ({ session }) =>
withPageAuth(
{
redirectTo: '/',
user: session.user
},
async () => {
const { data } = await supabaseServerClient(session.accessToken).from('test').select('*');
return { props: { data, user: session.user } };
}
);
export const load: Load = async ({ session }) =>
withPageAuth(
{
redirectTo: '/',
user: session.user,
},
async () => {
const { data } = await supabaseServerClient(session.accessToken)
.from('test')
.select('*')
return { props: { data, user: session.user } }
}
)
</script>
<script>
export let data;
export let user;
export let data
export let user
</script>
<div>Protected content for {user.email}</div>
@@ -769,13 +783,12 @@ export let user;
```html title="src/routes/protected-route/+page.svelte"
<script lang="ts">
import type { PageData } from './$types';
import type { PageData } from './$types'
export let data: PageData;
$: ({ tableData, user } = data);
export let data: PageData
$: ({ tableData, user } = data)
</script>
<div>Protected content for {user.email}</div>
<p>server-side fetched data with RLS:</p>
<pre>{JSON.stringify(tableData, null, 2)}</pre>
@@ -784,18 +797,22 @@ export let user;
```
```ts title="src/routes/protected-route/+page.ts"
import { withAuth } from '@supabase/auth-helpers-sveltekit';
import { redirect } from '@sveltejs/kit';
import type { PageLoad } from './$types';
import { withAuth } from '@supabase/auth-helpers-sveltekit'
import { redirect } from '@sveltejs/kit'
import type { PageLoad } from './$types'
export const load: PageLoad = withAuth(async ({ session, getSupabaseClient }) => {
if (!session.user) {
throw redirect(303, '/');
export const load: PageLoad = withAuth(
async ({ session, getSupabaseClient }) => {
if (!session.user) {
throw redirect(303, '/')
}
const { data: tableData } = await getSupabaseClient()
.from('test')
.select('*')
return { tableData, user: session.user }
}
const { data: tableData } = await getSupabaseClient().from('test').select('*');
return { tableData, user: session.user };
});
)
```
</TabItem>
@@ -813,28 +830,33 @@ export const load: PageLoad = withAuth(async ({ session, getSupabaseClient }) =>
<TabItem value="older">
```ts title="src/routes/api/protected-route.ts"
import { supabaseServerClient, withApiAuth } from "@supabase/auth-helpers-sveltekit";
import type { RequestHandler } from "./__types/protected-route";
import {
supabaseServerClient,
withApiAuth,
} from '@supabase/auth-helpers-sveltekit'
import type { RequestHandler } from './__types/protected-route'
interface TestTable {
id: string;
created_at: string;
id: string
created_at: string
}
interface GetOutput {
data: TestTable[];
data: TestTable[]
}
export const GET: RequestHandler<GetOutput> = async ({ locals, request }) =>
withApiAuth({ user: locals.user }, async () => {
// Run queries with RLS on the server
const { data } = await supabaseServerClient(request).from("test").select("*");
const { data } = await supabaseServerClient(request)
.from('test')
.select('*')
return {
status: 200,
body: { data }
};
});
body: { data },
}
})
```
</TabItem>
@@ -110,7 +110,7 @@ http://localhost:54321/auth/v1/ # Auth (GoTrue)
:::note
To access the database from an edge function in your local Supabase setup, replace `localhost` with `host.docker.internal`.
:::
## Database migrations
Database changes are managed through "migrations." Database migrations are a common way of tracking changes to your database over time.
@@ -112,7 +112,12 @@ This is the most granular option. Connections are returned to the pool after eve
3. Find your Connection Info and Connection String. Connection pooling is on port `6543`.
<video width="99%" muted playsInline controls="true">
<source src="/docs/videos/connection-pool-config.mp4" type="video/mp4" muted playsInline />
<source
src="/docs/videos/connection-pool-config.mp4"
type="video/mp4"
muted
playsInline
/>
</video>
## Connecting with SSL
@@ -16,11 +16,13 @@ Migrating projects can be achieved using standard PostgreSQL tooling. This is pa
## Migrate the database
In your old project:
1. Run `ALTER ROLE postgres SUPERUSER` in the [SQL editor](https://app.supabase.com/project/_/sql).
1. Run `pg_dump --clean --if-exists --quote-all-identifiers -h $OLD_DB_URL -U postgres > dump.sql` from your terminal.
1. Run `ALTER ROLE postgres NOSUPERUSER` in the [SQL editor](https://app.supabase.com/project/_/sql).
In your new project:
1. Run `ALTER ROLE postgres SUPERUSER` in the [SQL editor](https://app.supabase.com/project/_/sql).
1. Run `psql -h $NEW_DB_URL -U postgres -f dump.sql` from your terminal.
1. Run `TRUNCATE storage.objects` in the [SQL editor](https://app.supabase.com/project/_/sql).
@@ -94,4 +96,3 @@ const NEW_PROJECT_SERVICE_KEY = 'new-project-service-key-yyy'
}
})()
```
+9 -9
View File
@@ -241,15 +241,15 @@ You can find a list of useful [Edge Function Examples](https://github.com/supaba
<div class="container" style={{ padding: 0 }}>
<div class="row is-multiline">
{examples.map((x) => (
<div class="col col--6">
<ButtonCard
class="card"
to={x.href}
title={x.name}
description={x.description}
style={{ height: '100%' }}
/>
</div>
<div class="col col--6">
<ButtonCard
class="card"
to={x.href}
title={x.name}
description={x.description}
style={{ height: '100%' }}
/>
</div>
))}
</div>
</div>
@@ -29,17 +29,22 @@ Organizations are a way to group your projects. Each organization can be configu
You can invite your team members into your organizations to collaborate on projects.
<video width="99%" muted playsInline controls="true">
<source src="/docs/videos/invite-team.mp4" type="video/mp4" muted playsInline />
<source
src="/docs/videos/invite-team.mp4"
type="video/mp4"
muted
playsInline
/>
</video>
You can also assign roles to your team members with different access levels. The table below shows the corresponding permissions for each available role in the Dashboard.
| Permissions | Owner | Administrator | Developer |
| ------------------------ | ----- | ------------- | --------- |
| **Organization** |
| **Organization** |
| Change organization name | ✅ | | |
| Delete organization | ✅ | | |
| **Members** |
| **Members** |
| Add an Owner | ✅ | | |
| Remove an Owner | ✅ | | |
| Add an Adminstrator | ✅ | ✅ | |
@@ -48,7 +53,7 @@ You can also assign roles to your team members with different access levels. The
| Remove a Developer | ✅ | ✅ | |
| Revoke an invite | ✅ | ✅ | |
| Resend an invite | ✅ | ✅ | |
| **Billing** |
| **Billing** |
| Read invoices | ✅ | ✅ | ✅ |
| Read billing email | ✅ | ✅ | ✅ |
| Change billing email | ✅ | | |
@@ -60,7 +65,7 @@ You can also assign roles to your team members with different access levels. The
| Update tax codes | ✅ | ✅ | |
| Read payment methods | ✅ | ✅ | ✅ |
| Update payment methods | ✅ | ✅ | |
| **Projects** |
| **Projects** |
| Create a project | ✅ | ✅ | |
| Delete a project | ✅ | ✅ | |
| Update a project | ✅ | ✅ | |
@@ -346,7 +346,7 @@ export default function HomePage({ user, isEnrolled }: Props) {
<main>
<section>
<div> Signed in as: {user?.email}</div>
<button
<button
onClick={async (e) => {
e.preventDefault();
@@ -540,6 +540,7 @@ export default withApiAuth(async function mfa(
res.send({ mfaUrl });
});
```
Because the user should be authenticated with Supabase to set up or manage MFA, we can use Supabase's `withApiAuth` wrapper to protect this route.
The `redirectToSettings` param specifies whether the user should be redirected to the MFA page settings panel after a challenge, rather than redirecting them immediately back to the application.
@@ -60,4 +60,4 @@ That's it.
## All done!
You can read more about PolyScale [here](https://www.polyscale.ai/) or check out our [documentation](https://docs.polyscale.ai/).
You can read more about PolyScale [here](https://www.polyscale.ai/) or check out our [documentation](https://docs.polyscale.ai/).
@@ -16,19 +16,23 @@ Supabase provides several [tools](https://github.com/supabase-community/firebase
## Set up the migration tool {#set-up-migration-tool}
1. Clone the [firebase-to-supabase](https://github.com/supabase-community/firebase-to-supabase) repository:
```
git clone https://github.com/supabase-community/firebase-to-supabase.git
```
```
git clone https://github.com/supabase-community/firebase-to-supabase.git
```
1. In the `/auth` directory, create a file named `supabase-service.json` with the following contents:
```json
{
"host": "database.server.com",
"password": "secretpassword",
"user": "postgres",
"database": "postgres",
"port": 5432
}
```
```json
{
"host": "database.server.com",
"password": "secretpassword",
"user": "postgres",
"database": "postgres",
"port": 5432
}
```
1. Go to the [Database settings](https://app.supabase.com/project/_/settings/database) for your project in the Supabase Dashboard.
1. Under **Connection Info**, copy the Host string and replace the entry in your `supabase-service.json` file.
1. Enter the password you used when you created your Supabase project in the `password` entry in the `supabase-service.json` file.
@@ -6,15 +6,18 @@ sidebar_label: Firebase Storage
---
Supabase provides several [tools](https://github.com/supabase-community/firebase-to-supabase/tree/main/storage) to convert storage files from Firebase Storage to Supabase Storage. Conversion is a two-step process:
1. Files are downloaded from a Firebase storage bucket to a local filesystem.
2. Files are uploaded from the local filesystem to a Supabase storage bucket.
## Set up the migration tool {#set-up-migration-tool}
1. Clone the [firebase-to-supabase](https://github.com/supabase-community/firebase-to-supabase) repository:
```
git clone https://github.com/supabase-community/firebase-to-supabase.git
```
```
git clone https://github.com/supabase-community/firebase-to-supabase.git
```
1. In the `/storage` directory, rename [supabase-keys-sample.js](https://github.com/supabase-community/firebase-to-supabase/blob/main/storage/supabase-keys-sample.js) to `supabase-keys.js`.
1. Go to your Supabase project's [API settings](https://app.supabase.com/project/_/settings/api) in the Dashboard.
1. Copy the **Project URL** and update the `SUPABASE_URL` value in `supabase-keys.js`.
@@ -45,6 +48,7 @@ To process in batches using multiple command-line executions, you must use the s
### Upload files to Supabase Storage bucket {#upload-to-supabase-storage-bucket}
`node upload.js <prefix> <folder> <bucket>`
- `<prefix>`: The prefix of the files to download. To process all files, use an empty prefix: "".
- `<folder>`: Name of subfolder of files to upload. The selected folder is read as a subfolder of the current folder (e.g., `./downloads/`). The default is `downloads`.
- `<bucket>`: Name of the bucket to upload to.
@@ -7,24 +7,28 @@ sidebar_label: Firestore Data
Supabase provides several [tools](https://github.com/supabase-community/firebase-to-supabase/tree/main/firestore) to convert data from a Firebase Firestore database to a Supabase PostgreSQL database. The process copies the entire contents of a single Firestore `collection` to a single PostgreSQL `table`.
The Firestore `collection` is "flattened" and converted to a table with basic columns of one of the following types: `text`, `numeric`, `boolean`, or `jsonb`. If your structure is more complex, you can write a program to split the newly-created `json` file into multiple, related tables before you import your `json` file(s) to Supabase.
The Firestore `collection` is "flattened" and converted to a table with basic columns of one of the following types: `text`, `numeric`, `boolean`, or `jsonb`. If your structure is more complex, you can write a program to split the newly-created `json` file into multiple, related tables before you import your `json` file(s) to Supabase.
## Set up the migration tool {#set-up-migration-tool}
1. Clone the [firebase-to-supabase](https://github.com/supabase-community/firebase-to-supabase) repository:
```
git clone https://github.com/supabase-community/firebase-to-supabase.git
```
```
git clone https://github.com/supabase-community/firebase-to-supabase.git
```
1. In the `/firestore` directory, create a file named `supabase-service.json` with the following contents:
```json
{
"host": "database.server.com",
"password": "secretpassword",
"user": "postgres",
"database": "postgres",
"port": 5432
}
```
```json
{
"host": "database.server.com",
"password": "secretpassword",
"user": "postgres",
"database": "postgres",
"port": 5432
}
```
1. Go to the [Database settings](https://app.supabase.com/project/_/settings/database) for your project in the Supabase Dashboard.
1. Under **Connection Info**, copy the Host string and replace the entry in your `supabase-service.json` file.
1. Enter the password you used when you created your Supabase project in the `password` entry in the `supabase-service.json` file.
@@ -40,56 +44,52 @@ The Firestore `collection` is "flattened" and converted to a table with basic co
## Command line options
### List all Firestore collections
`node collections.js`
### Dump Firestore collection to JSON file
`node firestore2json.js <collectionName> [<batchSize>] [<limit>]`
* `batchSize` (optional) defaults to 1000
* output filename is `<collectionName>.json`
* `limit` (optional) defaults to 0 (no limit)
- `batchSize` (optional) defaults to 1000
- output filename is `<collectionName>.json`
- `limit` (optional) defaults to 0 (no limit)
#### Customize the JSON file with hooks
You can customize the way your JSON file is written using a [custom hook](#custom-hooks). A common use for this is to "flatten" the JSON file, or to split nested data into separate, related database tables. For example, you could take a Firestore document that looks like this:
```json title="Firestore document"
[{ "user": "mark",
"score": 100,
"items": ["hammer","nail","glue"]
}]
[{ "user": "mark", "score": 100, "items": ["hammer", "nail", "glue"] }]
```
And split it into two files (one table for users and one table for items):
```json title="Users table"
[{ "user": "mark",
"score": 100
}]
[{ "user": "mark", "score": 100 }]
```
```json title="Items table"
[{ "user": "mark",
"item": "hammer"},
{"user": "mark",
"item": "nail"},
{"user": "mark",
"item": "glue"}]
[
{ "user": "mark", "item": "hammer" },
{ "user": "mark", "item": "nail" },
{ "user": "mark", "item": "glue" }
]
```
### Import JSON file to Supabase (PostgreSQL) {#import-to-supabase}
`node json2supabase.js <path_to_json_file> [<primary_key_strategy>] [<primary_key_name>]`
* `<path_to_json_file>` The full path of the file you created in the previous step (`Dump Firestore collection to JSON file
`), such as `./my_collection.json`
* `[<primary_key_strategy>]` (optional) Is one of:
* `none` (default) No primary key is added to the table.
* `smallserial` Creates a key using `(id SMALLSERIAL PRIMARY KEY)` (autoincrementing 2-byte integer).
* `serial` Creates a key using `(id SERIAL PRIMARY KEY)` (autoincrementing 4-byte integer).
* `bigserial` Creates a key using `(id BIGSERIAL PRIMARY KEY)` (autoincrementing 8-byte integer).
* `uuid` Creates a key using `(id UUID PRIMARY KEY DEFAULT uuid_generate_v4())` (randomly generated UUID).
* `firestore_id` Creates a key using `(id TEXT PRIMARY KEY)` (uses existing `firestore_id` random text as key).
* `[<primary_key_name>]` (optional) Name of primary key. Defaults to "id".
- `<path_to_json_file>` The full path of the file you created in the previous step (`Dump Firestore collection to JSON file `), such as `./my_collection.json`
- `[<primary_key_strategy>]` (optional) Is one of:
- `none` (default) No primary key is added to the table.
- `smallserial` Creates a key using `(id SMALLSERIAL PRIMARY KEY)` (autoincrementing 2-byte integer).
- `serial` Creates a key using `(id SERIAL PRIMARY KEY)` (autoincrementing 4-byte integer).
- `bigserial` Creates a key using `(id BIGSERIAL PRIMARY KEY)` (autoincrementing 8-byte integer).
- `uuid` Creates a key using `(id UUID PRIMARY KEY DEFAULT uuid_generate_v4())` (randomly generated UUID).
- `firestore_id` Creates a key using `(id TEXT PRIMARY KEY)` (uses existing `firestore_id` random text as key).
- `[<primary_key_name>]` (optional) Name of primary key. Defaults to "id".
## Custom hooks
@@ -108,10 +108,11 @@ If your Firestore collection is called `users`, create a file called `users.js`
#### Construct your .js file
The basic format of a hook file looks like this:
```js
module.exports = (collectionName, doc, recordCounters, writeRecord) => {
// modify the doc here
return doc;
return doc
}
```
@@ -121,9 +122,9 @@ module.exports = (collectionName, doc, recordCounters, writeRecord) => {
- `doc`: The current document (JSON object) being processed.
- `recordCounters`: An internal object that keeps track of how many records have been processed in each collection.
- `writeRecord`: This function automatically handles the process of writing data to other JSON files (useful for "flatting" your document into separate JSON files to be written to separate database tables). `writeRecord` takes the following parameters:
- `name`: Name of the JSON file to write to.
- `doc`: The document to write to the file.
- `recordCounters`: The same `recordCounters` object that was passed to this hook (just passes it on).
- `name`: Name of the JSON file to write to.
- `doc`: The document to write to the file.
- `recordCounters`: The same `recordCounters` object that was passed to this hook (just passes it on).
### Examples
@@ -131,8 +132,8 @@ module.exports = (collectionName, doc, recordCounters, writeRecord) => {
```js
module.exports = (collectionName, doc, recordCounters, writeRecord) => {
doc.unique_key = (recordCounter[collectionName] + 1);
return doc;
doc.unique_key = recordCounter[collectionName] + 1
return doc
}
```
@@ -140,8 +141,8 @@ module.exports = (collectionName, doc, recordCounters, writeRecord) => {
```js
module.exports = (collectionName, doc, recordCounters, writeRecord) => {
doc.dump_time = new Date().toISOString();
return doc;
doc.dump_time = new Date().toISOString()
return doc
}
```
@@ -150,56 +151,56 @@ module.exports = (collectionName, doc, recordCounters, writeRecord) => {
Flatten the `users` collection into separate files:
```json
[{"uid": "abc123",
"name": "mark",
"score": 100,
"weapons": ["toothpick","needle","rock"]},
{"uid": "xyz789",
"name": "chuck",
"score": 9999999,
"weapons": ["hand","foot","head"]}]
[
{
"uid": "abc123",
"name": "mark",
"score": 100,
"weapons": ["toothpick", "needle", "rock"]
},
{
"uid": "xyz789",
"name": "chuck",
"score": 9999999,
"weapons": ["hand", "foot", "head"]
}
]
```
The `users.js` hook file:
```js
module.exports = (collectionName, doc, recordCounters, writeRecord) => {
for (let i=0; i < doc.weapons.length; i++) {
for (let i = 0; i < doc.weapons.length; i++) {
const weapon = {
"uid": doc.uid,
"weapon": doc.weapons[i]
uid: doc.uid,
weapon: doc.weapons[i],
}
writeRecord("weapons", weapon, recordCounters);
writeRecord('weapons', weapon, recordCounters)
}
delete doc.weapons; // moved to separate file
return doc;
delete doc.weapons // moved to separate file
return doc
}
```
The result is two separate JSON files:
```json title="users.json"
[{"uid": "abc123",
"name": "mark",
"score": 100},
{"uid": "xyz789",
"name": "chuck",
"score": 9999999}]
[
{ "uid": "abc123", "name": "mark", "score": 100 },
{ "uid": "xyz789", "name": "chuck", "score": 9999999 }
]
```
```json title="weapons.json"
[{"uid": "abc123",
"weapon": "toothpick"},
{"uid": "abc123",
"weapon": "needle"},
{"uid": "abc123",
"weapon": "rock"},
{"uid": "xyz789",
"weapon": "hand"},
{"uid": "xyz789",
"weapon": "foot"},
{"uid": "xyz789",
"weapon": "head"}]
[
{ "uid": "abc123", "weapon": "toothpick" },
{ "uid": "abc123", "weapon": "needle" },
{ "uid": "abc123", "weapon": "rock" },
{ "uid": "xyz789", "weapon": "hand" },
{ "uid": "xyz789", "weapon": "foot" },
{ "uid": "xyz789", "weapon": "head" }
]
```
## Resources
@@ -5,7 +5,7 @@ description: 'Migrate your Heroku Postgres database to Supabase.'
sidebar_label: Heroku
---
Supabase is one of the best [free alternatives to Heroku Postgres](https://supabase.com/alternatives/supabase-vs-heroku-postgres). This guide shows how to migrate your Heroku Postgres database to Supabase. This migration requires the [pg_dump](https://www.postgresql.org/docs/current/app-pgdump.html) and [psql](https://www.postgresql.org/docs/current/app-psql.html) CLI tools, which are installed automatically as part of the complete PostgreSQL installation package.
Supabase is one of the best [free alternatives to Heroku Postgres](https://supabase.com/alternatives/supabase-vs-heroku-postgres). This guide shows how to migrate your Heroku Postgres database to Supabase. This migration requires the [pg_dump](https://www.postgresql.org/docs/current/app-pgdump.html) and [psql](https://www.postgresql.org/docs/current/app-psql.html) CLI tools, which are installed automatically as part of the complete PostgreSQL installation package.
Alternatively, use the [Heroku to Supabase migration tool](https://migrate.supabase.com/) to migrate in just a few clicks.
@@ -25,7 +25,7 @@ Alternatively, use the [Heroku to Supabase migration tool](https://migrate.supab
1. Log in to your [Heroku account](https://heroku.com) and select the project you want to migrate.
1. Click **Resources** in the menu and select your **Heroku Postgres** database.
1. Click **Settings** in the menu.
1. Click **View Credentials** and save the following information:
1. Click **View Credentials** and save the following information:
- Host (`$HEROKU_HOST`)
- Database (`$HEROKU_DATABASE`)
- User (`$HEROKU_USER`)
@@ -52,7 +52,7 @@ pg_dump --clean --if-exists --quote-all-identifiers \
Use `psql` to import the Heroku database file to your Supabase project.
```
psql -h $SUPABASE_HOST -U postgres -f heroku_dump.sql
psql -h $SUPABASE_HOST -U postgres -f heroku_dump.sql
```
## (Optional) Post processing {#post-processing}
@@ -67,7 +67,7 @@ select graphql.rebuild_schema();
- To only migrate a single database schema, add the `--schema=PATTERN` parameter to your `pg_dump` command.
- To exclude a schema: `--exclude-schema=PATTERN`.
- To only migrate a single table: `--table=PATTERN`.
- To only migrate a single table: `--table=PATTERN`.
- To exclude a table: `--exclude-table=PATTERN`.
Run `pg_dump --help` for a full list of options.
@@ -4,7 +4,6 @@ title: Disk space usage
description: Learn how database disk space usage is reported.
---
Database disk space usage refers to the _monthly average disk usage_, as reported by Postgres. This metric is reported in your project's [billing page](https://app.supabase.com/project/_/settings/billing) and is updated daily.
For an instantaneous live view of the DB disk space being used by your project, you can execute in Postgres:
+27 -27
View File
@@ -6,8 +6,8 @@ description: Getting started with Supabase Platform Log Browser
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
import ThemedImage from '@theme/ThemedImage';
import useBaseUrl from '@docusaurus/useBaseUrl';
import ThemedImage from '@theme/ThemedImage'
import useBaseUrl from '@docusaurus/useBaseUrl'
The Supabase Platform includes a Logs Explorer that allows log tracing and debugging. Log retention is based on your [project's pricing plan](https://supabase.com/pricing).
@@ -124,6 +124,7 @@ For example, to query the edge logs without any joins:
```sql
select timestamp, metadata from edge_logs t
```
The resulting `metadata` key is rendered as an array of objects in the Logs Explorer. In the following diagram, each box represents a nested array of objects:
<!-- Scene is here https://app.excalidraw.com/s/8gj16loJfGZ/3HzccK9MyLx -->
@@ -131,13 +132,12 @@ The resulting `metadata` key is rendered as an array of objects in the Logs Expl
<ThemedImage
alt="Without Unnesting"
sources={{
light: useBaseUrl("/img/guides/platform/logs/unnesting-none.png"),
dark: useBaseUrl("/img/guides/platform/logs/unnesting-none-dark.png"),
light: useBaseUrl('/img/guides/platform/logs/unnesting-none.png'),
dark: useBaseUrl('/img/guides/platform/logs/unnesting-none-dark.png'),
}}
style={{maxHeight: 200}}
style={{ maxHeight: 200 }}
/>
Perform a `cross join unnest()` to work with the keys nested in the `metadata` key.
To query for a nested value, add a join for each array level:
@@ -155,10 +155,10 @@ This surfaces the following columns available for selection:
<ThemedImage
alt="With Two Level Unnesting"
sources={{
light: useBaseUrl("/img/guides/platform/logs/unnesting-2.png"),
dark: useBaseUrl("/img/guides/platform/logs/unnesting-2-dark.png"),
light: useBaseUrl('/img/guides/platform/logs/unnesting-2.png'),
dark: useBaseUrl('/img/guides/platform/logs/unnesting-2-dark.png'),
}}
style={{maxHeight: 200}}
style={{ maxHeight: 200 }}
/>
This allows you to select the `method` and `cf_ipcountry` columns. In JS dot notation, the full paths for each selected column are:
@@ -174,30 +174,30 @@ The Logs Explorer has a maximum of 1000 rows per run. Use `LIMIT` to optimize yo
1. Include a filter over **timestamp**
Querying your entire log history might seem appealing. For **Enterprise** customers that have a large retention range, you run the risk of timeouts due additional time required to scan the larger dataset.
Querying your entire log history might seem appealing. For **Enterprise** customers that have a large retention range, you run the risk of timeouts due additional time required to scan the larger dataset.
2. Avoid selecting large nested objects. Select individual values instead.
When querying large objects, the columnar storage engine selects each column associated with each nested key, resulting in a large number of columns being selected. This inadvertently impacts the query speed and may result in timeouts or memory errors, especially for projects with a lot of logs.
When querying large objects, the columnar storage engine selects each column associated with each nested key, resulting in a large number of columns being selected. This inadvertently impacts the query speed and may result in timeouts or memory errors, especially for projects with a lot of logs.
Instead, select only the values required.
Instead, select only the values required.
```sql
-- ❌ Avoid doing this
select
datetime(timestamp),
m as metadata -- <- metadata contains many nested keys
from edge_logs t
cross join unnest(t.metadata) as m;
-- ✅ Do this
select
```sql
-- ❌ Avoid doing this
select
datetime(timestamp),
r.method -- <- select only the required values
from edge_logs t
cross join unnest(t.metadata) as m
cross join unnest(m.request) as r
```
m as metadata -- <- metadata contains many nested keys
from edge_logs t
cross join unnest(t.metadata) as m;
-- ✅ Do this
select
datetime(timestamp),
r.method -- <- select only the required values
from edge_logs t
cross join unnest(t.metadata) as m
cross join unnest(m.request) as r
```
### Examples and Templates
@@ -4,7 +4,7 @@ title: Broadcast
description: Getting started with Realtime's Broadcast feature
---
Supabase Broadcast allows a client to send messages, and multiple receivers to receive the messages.
Supabase Broadcast allows a client to send messages, and multiple receivers to receive the messages.
The broadcasted messages are ephemeral. They are not persisted to the database and are directly relayed through the Realtime server. This is ideal for sending information like mouse moments where latency is important, but need not be persisted.
+58 -22
View File
@@ -165,7 +165,7 @@ These variables will be exposed on the browser, and that's completely fine since
[Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.
```ts title="lib/supabase.ts"
import AsyncStorage from '@react-native-async-storage/async-storage';
import AsyncStorage from '@react-native-async-storage/async-storage'
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = YOUR_REACT_NATIVE_SUPABASE_URL
@@ -243,10 +243,18 @@ export default function Auth() {
/>
</View>
<View style={[styles.verticallySpaced, styles.mt20]}>
<Button title="Sign in" disabled={loading} onPress={() => signInWithEmail()} />
<Button
title="Sign in"
disabled={loading}
onPress={() => signInWithEmail()}
/>
</View>
<View style={styles.verticallySpaced}>
<Button title="Sign up" disabled={loading} onPress={() => signUpWithEmail()} />
<Button
title="Sign up"
disabled={loading}
onPress={() => signUpWithEmail()}
/>
</View>
</View>
)
@@ -360,16 +368,26 @@ export default function Account({ session }: { session: Session }) {
<Input label="Email" value={session?.user?.email} disabled />
</View>
<View style={styles.verticallySpaced}>
<Input label="Username" value={username || ''} onChangeText={(text) => setUsername(text)} />
<Input
label="Username"
value={username || ''}
onChangeText={(text) => setUsername(text)}
/>
</View>
<View style={styles.verticallySpaced}>
<Input label="Website" value={website || ''} onChangeText={(text) => setWebsite(text)} />
<Input
label="Website"
value={website || ''}
onChangeText={(text) => setWebsite(text)}
/>
</View>
<View style={[styles.verticallySpaced, styles.mt20]}>
<Button
title={loading ? 'Loading ...' : 'Update'}
onPress={() => updateProfile({ username, website, avatar_url: avatarUrl })}
onPress={() =>
updateProfile({ username, website, avatar_url: avatarUrl })
}
disabled={loading}
/>
</View>
@@ -425,7 +443,11 @@ export default function App() {
return (
<View>
{session && session.user ? <Account key={session.user.id} session={session} /> : <Auth />}
{session && session.user ? (
<Account key={session.user.id} session={session} />
) : (
<Auth />
)}
</View>
)
}
@@ -461,12 +483,16 @@ We can start by creating a new component:
import { useState, useEffect } from 'react'
import { supabase } from '../lib/supabase'
import { StyleSheet, View, Alert, Image, Button } from 'react-native'
import DocumentPicker, { isCancel, isInProgress, types } from 'react-native-document-picker'
import DocumentPicker, {
isCancel,
isInProgress,
types,
} from 'react-native-document-picker'
interface Props {
size: number
url: string | null
onUpload: (filePath: string) => void
url: string | null
onUpload: (filePath: string) => void
}
export default function Avatar({ url, size = 150, onUpload }: Props) {
@@ -483,7 +509,7 @@ export default function Avatar({ url, size = 150, onUpload }: Props) {
const { data, error } = await supabase.storage
.from('avatars')
.download(path)
if (error) {
throw error
}
@@ -508,17 +534,17 @@ export default function Avatar({ url, size = 150, onUpload }: Props) {
presentationStyle: 'fullScreen',
copyTo: 'cachesDirectory',
type: types.images,
mode: 'open'
mode: 'open',
})
const photo = {
uri: file.fileCopyUri,
type: file.type,
name: file.name
name: file.name,
}
const formData = new FormData()
formData.append("file", photo)
formData.append('file', photo)
const fileExt = file.name.split('.').pop()
const filePath = `${Math.random()}.${fileExt}`
@@ -537,7 +563,9 @@ export default function Avatar({ url, size = 150, onUpload }: Props) {
console.warn('cancelled')
// User cancelled the picker, exit any dialogs or menus and move on
} else if (isInProgress(error)) {
console.warn('multiple pickers were opened, only the last will be considered')
console.warn(
'multiple pickers were opened, only the last will be considered'
)
} else if (error instanceof Error) {
Alert.alert(error.message)
} else {
@@ -551,12 +579,20 @@ export default function Avatar({ url, size = 150, onUpload }: Props) {
return (
<View>
{avatarUrl ? (
<Image source={{ uri: avatarUrl }} accessibilityLabel="Avatar" style={[avatarSize, styles.avatar, styles.image]} />
<Image
source={{ uri: avatarUrl }}
accessibilityLabel="Avatar"
style={[avatarSize, styles.avatar, styles.image]}
/>
) : (
<View style={[avatarSize, styles.avatar, styles.noImage]} />
)}
<View>
<Button title={uploading ? 'Uploading ...' : 'Upload'} onPress={uploadAvatar} disabled={uploading} />
<Button
title={uploading ? 'Uploading ...' : 'Upload'}
onPress={uploadAvatar}
disabled={uploading}
/>
</View>
</View>
)
@@ -566,7 +602,7 @@ const styles = StyleSheet.create({
avatar: {
borderRadius: 5,
overflow: 'hidden',
maxWidth: '100%'
maxWidth: '100%',
},
image: {
objectFit: 'cover',
@@ -575,7 +611,7 @@ const styles = StyleSheet.create({
noImage: {
backgroundColor: '#333',
border: '1px solid rgb(200, 200, 200)',
borderRadius: 5
borderRadius: 5,
},
})
```
@@ -593,13 +629,13 @@ import Avatar from './Avatar'
<View>
{/* Add to the body */}
<View>
<Avatar
<Avatar
size={200}
url={avatarUrl}
url={avatarUrl}
onUpload={(url: string) => {
setAvatarUrl(url)
updateProfile({ username, website, avatar_url: url })
}}
}}
/>
</View>
{/* ... */}
@@ -441,7 +441,7 @@ import { Session } from '@supabase/supabase-js'
setupIonicReact()
const App: React.FC = () => {
const [session, setSession] = useState<Session | null>(null)
const [session, setSession] = (useState < Session) | (null > null)
useEffect(() => {
setSession(supabase.auth.session())
supabase.auth.onAuthStateChange((_event, session) => {
+8 -8
View File
@@ -188,7 +188,7 @@ Let's set up a Vue component to manage logins and sign ups. We'll use Magic Link
```html title="/components/Auth.vue"
<template>
<form class="flex row flex-center" @submit.prevent="handleLogin">
<form class="row flex-center flex" @submit.prevent="handleLogin">
<div class="col-6 form-widget">
<h1 class="header">Supabase + Nuxt 3</h1>
<p class="description">Sign in via magic link with your email below</p>
@@ -203,7 +203,7 @@ Let's set up a Vue component to manage logins and sign ups. We'll use Magic Link
<div>
<input
type="submit"
class="block button"
class="button block"
:value="loading ? 'Loading' : 'Send magic link'"
:disabled="loading"
/>
@@ -260,14 +260,14 @@ Let's create a new component for that called `Account.vue`.
<div>
<input
type="submit"
class="block button primary"
class="button primary block"
:value="loading ? 'Loading ...' : 'Update'"
:disabled="loading"
/>
</div>
<div>
<button class="block button" @click="signOut" :disabled="loading">
<button class="button block" @click="signOut" :disabled="loading">
Sign Out
</button>
</div>
@@ -346,7 +346,7 @@ Now that we have all the components in place, let's update `app.vue`:
</template>
<script setup>
const user = useSupabaseUser();
const user = useSupabaseUser()
</script>
```
@@ -385,7 +385,7 @@ Let's create an avatar for the user so that they can upload a profile photo. We
/>
<div style="width: 10em; position: relative;">
<label class="block button primary" for="single">
<label class="button primary block" for="single">
{{ uploading ? "Uploading ..." : "Upload" }}
</label>
<input
@@ -481,14 +481,14 @@ And then we can add the widget to the Account page:
<div>
<input
type="submit"
class="block button primary"
class="button primary block"
:value="loading ? 'Loading ...' : 'Update'"
:disabled="loading"
/>
</div>
<div>
<button class="block button" @click="signOut" :disabled="loading">
<button class="button block" @click="signOut" :disabled="loading">
Sign Out
</button>
</div>
+14 -6
View File
@@ -272,14 +272,14 @@ import { Component, createEffect, createSignal } from 'solid-js'
import { supabase } from './supabaseClient'
interface Props {
session: AuthSession
session: AuthSession;
}
const Account: Component<Props> = ({ session }) => {
const [loading, setLoading] = createSignal(true)
const [username, setUsername] = createSignal<string | null>(null)
const [website, setWebsite] = createSignal<string | null>(null)
const [avatarUrl, setAvatarUrl] = createSignal<string | null>(null)
const [username, setUsername] = (createSignal < string) | (null > null)
const [website, setWebsite] = (createSignal < string) | (null > null)
const [avatarUrl, setAvatarUrl] = (createSignal < string) | (null > null)
createEffect(() => {
getProfile()
@@ -366,11 +366,19 @@ const Account: Component<Props> = ({ session }) => {
/>
</div>
<div>
<button type="submit" class="button primary block" disabled={loading()}>
<button
type="submit"
class="button primary block"
disabled={loading()}
>
{loading() ? 'Saving ...' : 'Update profile'}
</button>
</div>
<button type="button" class="button block" onClick={() => supabase.auth.signOut()}>
<button
type="button"
class="button block"
onClick={() => supabase.auth.signOut()}
>
Sign Out
</button>
</form>
+32 -34
View File
@@ -192,7 +192,7 @@ Let's set up a Svelte component to manage logins and sign ups. We'll use Magic L
```html title="src/lib/Auth.svelte"
<script lang="ts">
import { supabase } from "src/supabaseClient";
import { supabase } from 'src/supabaseClient'
let loading = false
let email = ''
@@ -217,7 +217,7 @@ Let's set up a Svelte component to manage logins and sign ups. We'll use Magic L
<div class="col-6 form-widget" aria-live="polite">
<h1 class="header">Supabase + Svelte</h1>
<p class="description">Sign in via magic link with your email below</p>
<form class="form-widget" on:submit|preventDefault={handleLogin}>
<form class="form-widget" on:submit|preventDefault="{handleLogin}">
<div>
<label for="email">Email</label>
<input
@@ -225,11 +225,16 @@ Let's set up a Svelte component to manage logins and sign ups. We'll use Magic L
class="inputField"
type="email"
placeholder="Your email"
bind:value={email}
bind:value="{email}"
/>
</div>
<div>
<button type="submit" class="button block" aria-live="polite" disabled={loading}>
<button
type="submit"
class="button block"
aria-live="polite"
disabled="{loading}"
>
<span>{loading ? 'Loading' : 'Send magic link'}</span>
</button>
</div>
@@ -264,13 +269,13 @@ Let's create a new component for that called `Account.svelte`.
try {
loading = true
const { user } = session
const { data, error, status } = await supabase
.from('profiles')
.select('username, website, avatar_url')
.eq('id', user.id)
.single()
if (error && status !== 406) throw error
if (data) {
@@ -301,7 +306,7 @@ Let's create a new component for that called `Account.svelte`.
}
let { error } = await supabase.from('profiles').upsert(updates)
if (error) {
throw error
}
@@ -342,10 +347,10 @@ Now that we have all the components in place, let's update `App.svelte`:
```html title="src/App.svelte"
<script lang="ts">
import { onMount } from 'svelte';
import { supabase } from './supabaseClient';
import type { AuthSession } from '@supabase/supabase-js';
import Account from './lib/Account.svelte';
import { onMount } from 'svelte'
import { supabase } from './supabaseClient'
import type { AuthSession } from '@supabase/supabase-js'
import Account from './lib/Account.svelte'
import Auth from './lib/Auth.svelte'
let session: AuthSession
@@ -363,9 +368,9 @@ Now that we have all the components in place, let's update `App.svelte`:
<div class="container" style="padding: 50px 0 100px 0">
{#if !session}
<Auth />
<Auth />
{:else}
<Account {session} />
<Account {session} />
{/if}
</div>
```
@@ -392,12 +397,12 @@ Let's create an avatar for the user so that they can upload a profile photo. We
```html title="src/lib/Avatar.svelte"
<script lang="ts">
import { createEventDispatcher } from "svelte";
import { supabase } from "../supabaseClient"
import { createEventDispatcher } from 'svelte'
import { supabase } from '../supabaseClient'
export let size: number
export let url: string
let avatarUrl: string = null
let uploading = false
let files: FileList
@@ -406,7 +411,9 @@ Let's create an avatar for the user so that they can upload a profile photo. We
const downloadImage = async (path: string) => {
try {
const { data, error } = await supabase.storage.from('avatars').download(path)
const { data, error } = await supabase.storage
.from('avatars')
.download(path)
if (error) {
throw error
@@ -436,7 +443,7 @@ Let's create an avatar for the user so that they can upload a profile photo. We
let { error } = await supabase.storage
.from('avatars')
.upload(filePath, file)
if (error) {
throw error
}
@@ -456,18 +463,9 @@ Let's create an avatar for the user so that they can upload a profile photo. We
</script>
<div style="width: {size}px" aria-live="polite">
{#if avatarUrl}
<img
src={avatarUrl}
alt={avatarUrl ? 'Avatar' : 'No image'}
class="avatar image"
style="height: {size}px, width: {size}px"
/>
{:else}
<div
class="avatar no-image"
style="height: {size}px, width: {size}px"
/>
{#if avatarUrl} <img src={avatarUrl} alt={avatarUrl ? 'Avatar' : 'No image'}
class="avatar image" style="height: {size}px, width: {size}px" /> {:else}
<div class="avatar no-image" style="height: {size}px, width: {size}px" />
{/if}
<div style="width: {size}px">
<label class="button primary block" for="single">
@@ -479,8 +477,8 @@ Let's create an avatar for the user so that they can upload a profile photo. We
id="single"
accept="image/*"
bind:files
on:change={uploadAvatar}
disabled={uploading}
on:change="{uploadAvatar}"
disabled="{uploading}"
/>
</span>
</div>
@@ -497,9 +495,9 @@ And then we can add the widget to the Account page:
import Avatar from './Avatar.svelte'
</script>
<form on:submit|preventDefault={updateProfile} class="form-widget">
<form on:submit|preventDefault="{updateProfile}" class="form-widget">
<!-- Add to body -->
<Avatar bind:url={avatarUrl} size={150} on:upload={updateProfile} />
<Avatar bind:url="{avatarUrl}" size="{150}" on:upload="{updateProfile}" />
<!-- Other form elements -->
</form>
+18 -19
View File
@@ -252,27 +252,27 @@ Let's create a new component for that called `Profile.svelte`.
let avatar_url = null
function getProfile(node) {
try {
loading = true;
const user = supabase.auth.user();
try {
loading = true
const user = supabase.auth.user()
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;
});
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);
alert(error.message)
} finally {
loading = false;
loading = false
}
}
@@ -311,7 +311,6 @@ Let's create a new component for that called `Profile.svelte`.
} finally {
loading = false
}
}
</script>
+3 -1
View File
@@ -131,7 +131,9 @@ function recurseThroughParams(paramDefinition: TsDoc.TypeDefinition) {
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')
const nonLiteralVariants = param.type.types.filter(
({ type }) => type !== 'literal'
)
if (nonLiteralVariants.length === 0) {
children = null