mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 01:45:10 +03:00
prettier
This commit is contained in:
1 parent
c760942903
commit
893fb1fa4e
32 files changed
+687
-960
No files matched your search
+93
-443
File diff suppressed because it is too large.
Load diff
@@ -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 />
|
||||
|
||||
|
||||
@@ -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>
|
||||
```
|
||||
```
|
||||
@@ -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'
|
||||
}
|
||||
})()
|
||||
```
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user