Merge branch 'master' into fix/storage-file-explorer-drag-state-2

This commit is contained in:
Terry Sutton committed 2022-10-06 09:50:25 -02:30
commit 77556b8e15
1265 files changed
+50736 -13557

No files matched your search

+14 -15
View File
@@ -5,16 +5,15 @@ name: Studio Unit Tests
on:
push:
branches: [ master ]
branches: [master]
paths:
- 'studio/**'
pull_request:
branches: [ master ]
branches: [master]
paths:
- 'studio/**'
jobs:
build:
runs-on: ubuntu-latest
strategy:
@@ -23,15 +22,15 @@ jobs:
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install deps
run: npm install
working-directory: ./
- name: Run tests
run: npm test
working-directory: ./studio
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install deps
run: npm ci
working-directory: ./
- name: Run tests
run: npm run test:studio
working-directory: ./
+9
View File
@@ -195,6 +195,15 @@ The monorepo has a set of shared components under `/packages`:
- `/packages/config`: All shared config
- `/packages/spec`: Generates documentation using spec files.
- `/packages/tsconfig`: Shared Typescript settings
- `/packages/ui`: Shared UI components (formerly @supabase/ui)
To use these 'packages', or any of their components from a Next.JS app, you must use `next-transpile-modules` in the `next.config.js` file. This looks like:
```tsx
// next.config.js
const withTM = require('next-transpile-modules')(['ui', 'common'])
module.exports = withTM({})
```
### Installing packages
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 />
+15
View File
@@ -0,0 +1,15 @@
---
id: usage
slug: /usage
title: Usage
toc_max_heading_level: 3
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY IF THIS IS VERSION "next" -->
Documentation of the gotrue API.
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY IF THIS IS VERSION "next" -->
@@ -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_legacy.yml
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_legacy.yml
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_legacy.yml
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_legacy.yml
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
@@ -278,10 +278,10 @@ final res = await supabase
### Database Functions vs Edge Functions
For data-intensive operations we recommend using [Database Functions](../../guides/database/functions), which are executed within your database
For data-intensive operations, use Database Functions, which are executed within your database
and can be called remotely using the [REST and GraphQL API](../api).
For use-cases which require low-latency we recommend [Edge Functions](../../guides/functions), which are globally-distributed and can be written in Typescript.
For use-cases which require low-latency, use [Edge Functions](../../guides/functions), which are globally-distributed and can be written in Typescript.
### Security `definer` vs `invoker`
@@ -302,6 +302,18 @@ $$;
It is best practice to use `security invoker` (which is also the default). If you ever use `security definer`, you _must_ set the `search_path`.
This limits the potential damage if you allow access to schemas which the user executing the function should not have.
### Function privileges
By default, database functions can be executed by any role. You can restrict this by altering the default privileges and then choosing which roles can execute functions.
```sql
ALTER DEFAULT PRIVILEGES REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC;
-- Choose which roles can execute functions
GRANT EXECUTE ON FUNCTION hello_world TO authenticated;
GRANT EXECUTE ON FUNCTION hello_world TO service_role;
```
## Resources
- Official Client libraries: [JavaScript](../../reference/javascript/rpc) and [Dart](../../reference/dart/rpc)
@@ -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>
{/* ... */}
+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>
+10 -1
View File
@@ -130,7 +130,16 @@ function recurseThroughParams(paramDefinition: TsDoc.TypeDefinition) {
if (param.type?.declaration?.children) {
children = param.type?.declaration?.children
} else if (isUnion(param)) {
children = param.type.types
// We don't want to show the union types if it's a literal
const nonLiteralVariants = param.type.types.filter(
({ type }) => type !== 'literal'
)
if (nonLiteralVariants.length === 0) {
children = null
} else {
children = nonLiteralVariants
}
} else if (param.type === 'reflection') {
children = param.declaration.children
}
+1
View File
@@ -18,6 +18,7 @@ const sidebars = {
sidebar: [
'intro',
// 'usage', // Hide until the OpenAPI spec is updated to v3
'config',
'release-notes',
// {
+6 -5
View File
@@ -24,19 +24,20 @@
"gen:cli": "npm-run-all gen:cli:commands gen:cli:config",
"gen:cli:commands": "ts-node ./generator/index.ts gen --type cli --input ../../spec/cli_v1_commands.yaml --output ./_cli/generated/usage.mdx ",
"gen:cli:config": "ts-node ./generator/index.ts gen --type config --input ../../spec/cli_v1_config.yaml --output ./_cli/generated/config.mdx",
"gen:gotrue": "npm-run-all gen:gotrue:config",
"gen:gotrue": "npm-run-all gen:gotrue:config gen:gotrue:usage",
"gen:gotrue:config": "ts-node ./generator/index.ts gen --type config --input ../../spec/gotrue_v1_config.yaml --output ./_gotrue/config.mdx",
"gen:gotrue:usage": "ts-node ./generator/index.ts gen --type api --input ../../spec/transforms/auth_v1_openapi_deparsed.json --output ./_gotrue/usage.mdx",
"gen:storage": "npm-run-all gen:storage:api gen:storage:config",
"gen:storage:api": "ts-node ./generator/index.ts gen --type api --input ../../spec/transforms/storage_v0_openapi_deparsed.json --output ./_storage/generated/usage.mdx",
"gen:storage:config": "ts-node ./generator/index.ts gen --type config --input ../../spec/storage_v0_config.yaml --output ./_storage/generated/config.mdx",
"gen:supabase-dart:v0": "npm-run-all gen:supabase-dart:v0:ref",
"gen:supabase-dart:v0:ref": "ts-node ./generator/index.ts gen --type legacy --input ../../spec/supabase_dart_v0_legacy.yml --output ./_supabase_dart_versioned_docs/version-v0/generated",
"gen:supabase-dart:v0:ref": "ts-node ./generator/index.ts gen --type legacy --input ../../spec/supabase_dart_v0.yml --output ./_supabase_dart_versioned_docs/version-v0/generated",
"gen:supabase-dart:v1": "npm-run-all gen:supabase-dart:v1:ref",
"gen:supabase-dart:v1:ref": "ts-node ./generator/index.ts gen --type legacy --input ../../spec/supabase_dart_v1_legacy.yml --output ./_supabase_dart/generated",
"gen:supabase-dart:v1:ref": "ts-node ./generator/index.ts gen --type legacy --input ../../spec/supabase_dart_v1.yml --output ./_supabase_dart/generated",
"gen:supabase-js:v1": "npm-run-all gen:supabase-js:v1:ref",
"gen:supabase-js:v1:ref": "ts-node ./generator/index.ts gen --type legacy --input ../../spec/supabase_js_v1_legacy.yml --output ./_supabase_js_versioned_docs/version-v1/generated",
"gen:supabase-js:v1:ref": "ts-node ./generator/index.ts gen --type legacy --input ../../spec/supabase_js_v1.yml --output ./_supabase_js_versioned_docs/version-v1/generated",
"gen:supabase-js:v2": "npm-run-all gen:supabase-js:v2:ref",
"gen:supabase-js:v2:ref": "ts-node ./generator/index.ts gen --type legacy --input ../../spec/supabase_js_v2_legacy.yml --output ./_supabase_js/generated"
"gen:supabase-js:v2:ref": "ts-node ./generator/index.ts gen --type legacy --input ../../spec/supabase_js_v2.yml --output ./_supabase_js/generated"
},
"dependencies": {
"@docusaurus/core": "^2.0.1",
@@ -13,8 +13,6 @@ date: '06-01-2020'
It has been a monster month at [Supabase](/) and we're back with a video update. It's a longer video this month because we have a couple of housekeeping items.
<!--truncate-->
<iframe
className="w-full"
width="700"
@@ -14,8 +14,6 @@ date: '06-15-2020'
This month we welcome [Steve Chavez](https://github.com/steve-chavez) to the team. Steve is a maintainer of PostgREST, one of the core tools which makes Supabase possible.
<!--truncate-->
Steve and I have been in contact since he [added one of my old blog posts](https://github.com/PostgREST/postgrest-docs/commit/3dde972d31519d3afc319015bce1dc0f73adeb8e#commitcomment-35312841) to the PostgREST docs. He has a rich history in open source and we're excited to have him in our team.
Steve is helping us build one of our most demanded features - our Auth system. We will support him to build some exciting features which he has planned for PostgREST, including enhanced PostGIS support.
@@ -13,8 +13,6 @@ date: '07-01-2020'
We're now 4 months into building [Supabase](/), which means another major update. Here's a few things we think you'll love in this release.
<!--truncate-->
### 4 minute demo
Watch a full demo:
@@ -13,8 +13,6 @@ date: '07-09-2020'
Whenever you create a new database in Postgres, you are actually [basing it off an already present database](https://www.postgresql.org/docs/current/manage-ag-templatedbs.html) in your cluster.
<!--truncate-->
This database, `template1`, and another, called `template0`, are standard system databases that exist in every newly created database cluster. Don't believe me? Why not quickly [spin up a database](/docs/postgres/server/about) and see it for yourself with this query:
```sql
@@ -13,8 +13,6 @@ date: '07-10-2020'
On May 27 Supabase hit the [top of Hacker News](https://news.ycombinator.com/item?id=23319901) and stayed on the front page for more than 24 hours.
<!--truncate-->
Since then, Supabase has been featured on the [Stack Overflow podcast](https://stackoverflow.blog/2020/06/05/podcast-241-new-tools-for-new-times/), hit the [trending page](https://twitter.com/supabase/status/1268062559023685633) on GitHub, and scaled to over 1000 databases.
Here is everything that went wrong along the way.
@@ -15,8 +15,6 @@ PostgreSQL backups can be categorized into two types: _logical_ and _physical_.
We'll cover some of the many tools you can use for Postgres backups in future posts.
<!--truncate-->
## Logical Backups
This form of backup is typically achieved by [translating all the data into a set of SQL commands and writing it into a single file](https://www.postgresql.org/docs/current/backup-dump.html). This can then be fed to any database cluster to recreate everything. In your CLI, performing logical backups can be as easy as:
@@ -13,8 +13,6 @@ date: '08-02-2020'
Have you ever wanted to restore your database's state to a particular moment in time? This post explains how, using WAL-G.
<!--truncate-->
## Introduction
[WAL-G](https://github.com/wal-g/wal-g) is an [open-source continuous archiving tool](https://www.citusdata.com/blog/introducing-wal-g-faster-restores-for-postgres/) used to easily set up and recover from [physical backups](/blog/postgresql-physical-logical-backups) in Postgres. It mainly handles the storage and retrieval of physical backups and WAL archives to and from a chosen cloud storage provider. In this post, we will walk you through on how to effortlessly set up WAL-G for your database as well as guide you on what to do if and when disaster strikes.
@@ -13,8 +13,6 @@ date: '08-02-2020'
After 5 months of building, we're releasing one of our most anticipated features: Supabase Auth.
<!--truncate-->
### Quick Demo
Watch a full demo:
@@ -16,8 +16,6 @@ Supabase is an open source Firebase alternative. We are building the features of
Two months ago a developer discovered Supabase and (unexpectedly) [launched us on Hacker News](https://news.ycombinator.com/item?id=23319901). Although we had completed only 3 months of development the community support was both incredible and humbling.
<!--truncate-->
![This image shows all of the top dev tool launches on Hacker news. The most popular is Stripe, with 1249 upvotes, the next popular is Supabase with 1120 upvotes, and third is Fly.io with 626 upvotes.](/images/blog/supabase-hn-launch.png)
Developers were obviously excited about the prospect of an open source Firebase alternative, but the comments were dominated by one emphatic feature request: Auth.
@@ -13,8 +13,6 @@ date: '09-03-2020'
We're 6 months into building our hosted database platform and we've made some major improvements to our auth system and table view.
<!--truncate-->
### Quick Demo
Watch a full demo:
@@ -14,8 +14,6 @@ date: '09-11-2020'
October is looming, and for open source communities and contributors this means one thing: [Hacktoberfest](https://hacktoberfest.digitalocean.com/).
<!--truncate-->
## What is Hacktoberfest?
Hacktoberfest is a time to contribute to open source, improve your skills, and build greater technology in the process. Last year during Hacktoberfest 483,127 pull requests were opened and 61,871 people completed the challenge.
@@ -14,8 +14,6 @@ date: '10-03-2020'
We're now 7 months into building Supabase. This update is a big one!
<!--truncate-->
### Quick demo
Watch a full demo:
@@ -14,8 +14,6 @@ date: '10-30-2020'
**UPDATE 16/08/2022**: supabase-js v2 is out and focuses on “quality-of-life” improvements for developers. V2 includes Type support, new auth methods, realtime multiplayer sneak peek, and more: [**Read the blog post**](https://supabase.com/blog/supabase-js-v2)
<!--truncate-->
### New Docs
Before digging into the improvements, we're excited to point out our new [developer docs](/docs/reference/javascript/installing). While they're still a work in progress, here are some things we think you'll like:
@@ -14,8 +14,6 @@ date: '11-02-2020'
We're now 8 months into building Supabase. We're focused on performance, stability, and reliability but that hasn't prevented us from shipping some great features.
<!--truncate-->
### Quick demo
Watch a full demo:
@@ -14,8 +14,6 @@ date: '11-18-2020'
A quick summary of Postgres views, materialized views, and why you should use them.
<!--truncate-->
### What is a View?
A [view](https://www.postgresql.org/docs/12/sql-createview.html) is a convenient shortcut to a query. Creating a view does not involve new tables or data. When run, an underlying query is executed, returning its results to the user.
@@ -14,8 +14,6 @@ date: '12-01-2020'
We've been building for 9 months now, and we're getting even closer to Beta.
<!--truncate-->
### Quick demo
Watch a full demo:
@@ -17,8 +17,6 @@ Omar Kamali is the founder of [Monitoro](https://www.monitoro.xyz/), a service f
Learn how Monitoro was able to build fast, ensure reliability, and keep scaling by building with Supabase and Svelte.
<!--truncate-->
### From Zero to One Million
In one month, Monitoro scaled from nothing to nearly one million API requests per day.
@@ -15,8 +15,6 @@ date: '12-02-2020'
[**TAYFA**](https://usetayfa.com) is the fastest no-code approach to create and iterate on bespoke frontends. Learn how its solo founder, [Sarup Banskota](https://twitter.com/sarupbanskota?lang=en), was able to launch quickly with [Supabase](/), Next.js, and Vercel.
<!--truncate-->
### Meet the founder
Sarup's background is in open-source, web frontends, and devtools. In the summer of 2020, he was rapidly building MVPs as he explored different problem spaces. Sarup found that reusing his frontend work across projects, wasn't as simple as he'd have liked.
@@ -16,8 +16,6 @@ date: '12-02-2020'
[Xendit](https://www.xendit.co/) is one of South East Asia's largest payment processors. They use Supabase to run automated checks against international sanctions lists.
<!--truncate-->
### About Xendit
Xendit is a leading payment gateway for Indonesia and Southeast Asia. They enable businesses to accept payments in Indonesia with a single integration for credit and debit cards, e-wallets, and bank transfer.
@@ -14,8 +14,6 @@ tags:
date: '12-02-2020'
---
<!--truncate-->
### Supabase and Strive School - YC Summer 20 batchmates
We are proud to announce we are teaming up with fellow YC Summer 20 Alumni [Strive School](https://strive.school/), to help teach the next generation of software developers about open source software development.
@@ -30,8 +28,6 @@ Tobia and Diego, the founders, launched Strive School to help individuals gain t
Their business model means the better students, who want it the most, have a shot at making it - regardless of their socio-economic background.
<!--truncate-->
### Working together to help students learn open source
Strive School has an initial focus on full-stack Web Engineering and AI Engineering. They deeply care about open-source and help their students understand the importance of open-source software in the modern workforce. Almost all technology companies use open-source software, so Strive School sees it as essential that their students leave the course with an understanding of open source development.
@@ -42,8 +38,6 @@ We are huge Postgres fans and believe that Supabase will make learning and imple
We will be working closely with Strive School in the coming months to produce exclusive content for their code school students. We are also happy to announce we will be taking on a Strive school graduate as an intern at Supabase. This isn't a role we were actively hiring for, but we have been so impressed with the standard of their graduates that we didn't want to miss out of the chance to work with talent from Strive School.
<!--truncate-->
### Launching the Supabase Student Developer Pack with Strive School
As part of this process, we have put together a Student Developer Pack. Strive School are the perfect partner to launch our Student Developer Pack with as we both feel passionate about the importance of open-source software to empower developers. We are looking forward to working closely together in the coming months to help their students embrace the world of open-source.
@@ -11,8 +11,6 @@ tags:
date: '12-13-2020'
---
<!--truncate-->
The Supabase dashboard has become more feature-rich in the last month. We have a powerful SQL editor backed by [Monaco](https://microsoft.github.io/monaco-editor/). We built an Airtable-like view of your database, making editing a breeze.
> Features, performance, DX - choose three
@@ -15,8 +15,6 @@ date: '02-02-2021'
New year, new features. We've been busy at Supabase during January and our community has been even busier. Here's a few things you'll find interesting.
<!--truncate-->
### Quick demo
Watch a full demo:
@@ -44,14 +44,6 @@ Over 100K users submitted drawings in a 24 hour span. At this point, Brad knew h
Paint.Wtf has continued to get sustained coverage in the media and has continued to pick up new and unexpected use cases. For example, remote teams are using Paint.wtf as part of their daily ice breaker activities during COVID to keep up team social cohesion.
<!-- > _"Supabase meant we could have the exact functionality we needed for our leaderboard._
>
> _We got the benefits of PostgreSQL with a great developer experience._
>
> _With Supabase we could launch our product quickly and yet still handle the huge user volumes that Paint.wtf generated."_
>
> Brad Dwyer, CTO Roboflow. -->
![Supabase and Strive are partnering up to teach OSS.](/images/blog/roboflow-quote.png)
## Ship fast, and carry on scaling
@@ -14,8 +14,6 @@ date: '03-02-2021'
Supabase is an open source Firebase alternative. We've now been building for one year. Here's what we released last month.
<!--truncate-->
### Quick demo
Watch a full demo:
@@ -17,8 +17,6 @@ A Supabase user asked recently if they can trigger a webhook periodically. We ha
It is. Here's how.
<!--truncate-->
### What's cron?
A "cron job" is a script[^1] that runs periodically at fixed times, dates, or intervals. Traditionally you'd set it up on a Linux server. An example might be an hourly script that downloads emails to your computer.
@@ -39,11 +39,11 @@ We were absolutely blown away by [all the amazing projects](/blog/hackathon-winn
<blockquote class="twitter-tweet" data-dnt="true" data-theme="dark">
<p lang="en" dir="ltr">
Who else deserves the Gold Supabase Tee? 👀{' '}
Who else deserves the Gold Supabase Tee? 👀
<a href="https://t.co/XPWw02kZad">https://t.co/XPWw02kZad</a>
</p>
&mdash; Supabase (@supabase) <a href="https://twitter.com/supabase/status/1440737587895799809?ref_src=twsrc%5Etfw">September 22, 2021</a>
</blockquote> <script async src="https://platform.twitter.com/widgets.js" charSet="utf-8"></script>{' '}
</blockquote> <script async src="https://platform.twitter.com/widgets.js" charSet="utf-8"></script>
### Prize categories
@@ -91,12 +91,11 @@ We'll be contacting and announcing winners [on Twitter](https://twitter.com/supa
<blockquote class="twitter-tweet" data-dnt="true" data-theme="dark">
<p lang="en" dir="ltr">
Absolutely buzzing to have won the{' '}
<a href="https://twitter.com/supabase?ref_src=twsrc%5Etfw">@supabase</a> hackathon! 🥳{' '}
<a href="https://t.co/rm5HBuju73">https://t.co/rm5HBuju73</a>
Absolutely buzzing to have won the
<a href="https://twitter.com/supabase?ref_src=twsrc%5Etfw">@supabase</a> hackathon! 🥳 <a href="https://t.co/rm5HBuju73">https://t.co/rm5HBuju73</a>
</p>
&mdash; Josh Cawthorne (@cawthornejosh) <a href="https://twitter.com/cawthornejosh/status/1424985377136390183?ref_src=twsrc%5Etfw">August 10, 2021</a>
</blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>{' '}
</blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
### Rules
@@ -19,16 +19,16 @@ Back in May 2020, we [tweeted about](https://twitter.com/supabase/status/1265922
<div>
<blockquote className="twitter-tweet" data-theme="dark">
<p lang="en" dir="ltr">
Really impressed with{' '}
<a href="https://twitter.com/logflare_logs?ref_src=twsrc%5Etfw">@logflare_logs</a>. We&#39;ve
been on the front page of{' '}
Really impressed with
<a href="https://twitter.com/logflare_logs?ref_src=twsrc%5Etfw">@logflare_logs</a>. We&#39;ve been
on the front page of
<a href="https://twitter.com/hashtag/hackernews?src=hash&amp;ref_src=twsrc%5Etfw">
#hackernews
</a>{' '}
for 24 hours anticipating the HN hug of death. Looks like we made it through OK and Logflare
has been ingesting everything without breaking a sweat{' '}
<a href="https://t.co/RWKHIgmWh6">https://t.co/RWKHIgmWh6</a>{' '}
<a href="https://t.co/dQ2xqFTdco">pic.twitter.com/dQ2xqFTdco</a>
</a>
for 24 hours anticipating the HN hug of death. Looks like we made it through OK and Logflare has
been ingesting everything without breaking a sweat <a href="https://t.co/RWKHIgmWh6">
https://t.co/RWKHIgmWh6
</a> <a href="https://t.co/dQ2xqFTdco">pic.twitter.com/dQ2xqFTdco</a>
</p>
&mdash; Supabase (@supabase) <a href="https://twitter.com/supabase/status/1265922290237071361?ref_src=twsrc%5Etfw">May 28, 2020</a>
</blockquote>
@@ -115,7 +115,6 @@ Over the next few weeks, we plan to release these features (once we have a chanc
allowFullScreen
></iframe>
## Open source
Right now, Logflare works natively with Big Query. But as our track record has proven, we're 100% committed to open source. We're working on the future of a fully open-source version of Logflare, with support for various storage engines.
@@ -125,5 +124,3 @@ Right now, Logflare works natively with Big Query. But as our track record has p
If you're an existing Logflare user, we'll be migrating everything to Supabase in the coming months. We'll work closely with you to ensure that the transition is seamless, and introduce the same [pricing levels](https://logflare.app/pricing) that Logflare offers today.
We've worked with Chase on a list of potential questions you might have about the transition. If you have any concerns or questions, you can reach out to support@supabase.io.
<script async src="https://platform.twitter.com/widgets.js" charSet="utf-8"></script>
@@ -34,8 +34,8 @@ The Supabase team will be participating as well, for example [Jon](https://twitt
<blockquote class="twitter-tweet" data-theme="dark">
<p lang="en" dir="ltr">
Hacktoberfest Hackathon Swag finally arrived! 🥇🥈🥨 see the winners here:{' '}
<a href="https://t.co/wgRBu92Bq4">https://t.co/wgRBu92Bq4</a>{' '}
Hacktoberfest Hackathon Swag finally arrived! 🥇🥈🥨 see the winners here:
<a href="https://t.co/wgRBu92Bq4">https://t.co/wgRBu92Bq4</a>
<a href="https://t.co/i1eD1pdA8I">pic.twitter.com/i1eD1pdA8I</a>
</p>
&mdash; Supabase (@supabase) <a href="https://twitter.com/supabase/status/1463355998928736262?ref_src=twsrc%5Etfw">November 24, 2021</a>
@@ -84,12 +84,12 @@ We'll be contacting and announcing winners [on Twitter](https://twitter.com/supa
<blockquote class="twitter-tweet" data-dnt="true" data-theme="dark">
<p lang="en" dir="ltr">
Absolutely buzzing to have won the{' '}
<a href="https://twitter.com/supabase?ref_src=twsrc%5Etfw">@supabase</a> hackathon! 🥳{' '}
Absolutely buzzing to have won the
<a href="https://twitter.com/supabase?ref_src=twsrc%5Etfw">@supabase</a> hackathon! 🥳
<a href="https://t.co/rm5HBuju73">https://t.co/rm5HBuju73</a>
</p>
&mdash; Josh Cawthorne (@cawthornejosh) <a href="https://twitter.com/cawthornejosh/status/1424985377136390183?ref_src=twsrc%5Etfw">August 10, 2021</a>
</blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>{' '}
</blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
### Rules
@@ -17,12 +17,10 @@ Last week [Supabase UI](https://ui.supabase.com/), our open-source UI component
<blockquote class="twitter-tweet" data-lang="en" data-theme="dark">
<p lang="en" dir="ltr">
🏆 Supabase UI has been nominated for the{' '}
<a href="https://twitter.com/ProductHunt?ref_src=twsrc%5Etfw">@ProductHunt</a>{' '}
<a href="https://twitter.com/hashtag/GoldenKittyAwards2021?src=hash&amp;ref_src=twsrc%5Etfw">
🏆 Supabase UI has been nominated for the
<a href="https://twitter.com/ProductHunt?ref_src=twsrc%5Etfw">@ProductHunt</a> <a href="https://twitter.com/hashtag/GoldenKittyAwards2021?src=hash&amp;ref_src=twsrc%5Etfw">
#GoldenKittyAwards2021
</a>{' '}
<p>💚 Thank you to all the contributors and upvoters out there, we appreciate you!</p>
</a> <p>💚 Thank you to all the contributors and upvoters out there, we appreciate you!</p>
<a href="https://t.co/xOek6sM4LG">https://t.co/xOek6sM4LG</a>
</p>
&mdash; Supabase (@supabase) <a href="https://twitter.com/supabase/status/1481672574023221260?ref_src=twsrc%5Etfw">January 13, 2022</a>
@@ -111,15 +111,16 @@ Divjoy announced their [Supabase Integration](https://divjoy.com/?database=supab
<blockquote class="twitter-tweet" data-theme="dark">
<p lang="en" dir="ltr">
We released n8n@0.158.0 with new nodes for{' '}
<a href="https://twitter.com/supabase?ref_src=twsrc%5Etfw">@supabase</a>,{' '}
<a href="https://twitter.com/syncromsp?ref_src=twsrc%5Etfw">@syncromsp</a>, and{' '}
We released n8n@0.158.0 with new nodes for
<a href="https://twitter.com/supabase?ref_src=twsrc%5Etfw">@supabase</a>,<a href="https://twitter.com/syncromsp?ref_src=twsrc%5Etfw">
@syncromsp
</a>, and
<a href="https://twitter.com/Microsoft?ref_src=twsrc%5Etfw">@Microsoft</a> Graph Security. We
also made improvements to existing nodes ✨{' '}
also made improvements to existing nodes ✨
<a href="https://t.co/IdeUe4eWBK">pic.twitter.com/IdeUe4eWBK</a>
</p>
&mdash; n8n.io (@n8n_io) <a href="https://twitter.com/n8n_io/status/1480502781320572931?ref_src=twsrc%5Etfw">January 10, 2022</a>
</blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>{' '}
</blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
### Python Upgrades
@@ -138,7 +139,7 @@ We're having a 24/7\*365 meme-fest on [Twitter](https://twitter.com/supabase)
invest in good relationships <a href="https://t.co/bGiBE7FRFQ">pic.twitter.com/bGiBE7FRFQ</a>
</p>
&mdash; Supabase (@supabase) <a href="https://twitter.com/supabase/status/1475876907212316678?ref_src=twsrc%5Etfw">December 28, 2021</a>
</blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>{' '}
</blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
### TikTok
+3 -3
View File
@@ -116,9 +116,9 @@ create table audit.record_version(
<details>
<summary>Postgres version compatibility</summary>
<p>
The table above uses PostgreSQL's built-in{' '}
<a href="https://www.postgresql.org/docs/14/functions-uuid.html">uuid functionality</a>, which
is available from version 14. For backwards compatibility you can use the uuid-ossp extension.
The table above uses PostgreSQL's built-in
<a href="https://www.postgresql.org/docs/14/functions-uuid.html">uuid functionality</a>, which is
available from version 14. For backwards compatibility you can use the uuid-ossp extension.
</p>
<p>
<code>create extension if not exists "uuid-ossp";</code>
@@ -200,8 +200,6 @@ With the release of our new Enterprise Tier, we're announcing a tonne of new fea
### Point-in-Time Recovery
<!-- - [ ] IMAGE -->
Disaster Recovery is a critical process for any company, even for the most fault-tolerant products. What happens when you accidentally delete that database column
because of a clumsy `where` clause? Even with Supabase's daily backups, a day's worth of data can be lost if disaster strikes at the most inopportune time.
@@ -219,8 +217,6 @@ If you want access to the Prometheus Endpoint, [contact the Supabase Enterprise
### SLAs & Enterprise Support
<!-- - [ ] TODO - image -->
For our enterprise customers we know that service level agreements and support response times are also critical features. We continue to treat support as an
important priority for all tiers, though enterprise users require confidence that our response times meet their business needs. We now offer faster response times for Enterprise customers,
alongside Priority and Priority Plus support packages for those who need more comprehensive support. You can find further details around our SLAs & support
@@ -239,8 +235,7 @@ We have a lot more Enterprise features under development, available to early-acc
### SOC2
Supabase is now SOC2 Type 1 compliant, as announced during Launch Week 5. You can read all about the process we went through to get there in this [blog post](https://supabase.com/blog/supabase-soc2). Getting the Type 1 certification is just the start, and we will be working on getting certified for SOC2 Type 2 and HIPAA next.
Supabase is now SOC2 Type 1 compliant, as announced during Launch Week 5. You can read all about the process we went through to get there in this [blog post](https://supabase.com/blog/supabase-soc2). Getting the Type 1 certification is just the start, and we will be working on getting certified for SOC2 Type 2 and HIPAA next.
### Log ingestion
@@ -38,9 +38,8 @@ Finally, we're delighted to be partnering with [the Future Forest Company](https
<blockquote class="twitter-tweet" data-theme="dark">
<p lang="en" dir="ltr">
Hacktoberfest Hackathon Swag finally arrived! 🥇🥈🥨 see the winners here:{' '}
<a href="https://t.co/wgRBu92Bq4">https://t.co/wgRBu92Bq4</a>{' '}
<a href="https://t.co/i1eD1pdA8I">pic.twitter.com/i1eD1pdA8I</a>
Hacktoberfest Hackathon Swag finally arrived! 🥇🥈🥨 see the winners here:
<a href="https://t.co/wgRBu92Bq4">https://t.co/wgRBu92Bq4</a> <a href="https://t.co/i1eD1pdA8I">pic.twitter.com/i1eD1pdA8I</a>
</p>
&mdash; Supabase (@supabase) <a href="https://twitter.com/supabase/status/1463355998928736262?ref_src=twsrc%5Etfw">November 24, 2021</a>
</blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
@@ -91,12 +90,11 @@ We'll be contacting and announcing winners [on Twitter](https://twitter.com/supa
<blockquote class="twitter-tweet" data-dnt="true" data-theme="dark">
<p lang="en" dir="ltr">
Absolutely buzzing to have won the{' '}
<a href="https://twitter.com/supabase?ref_src=twsrc%5Etfw">@supabase</a> hackathon! 🥳{' '}
<a href="https://t.co/rm5HBuju73">https://t.co/rm5HBuju73</a>
Absolutely buzzing to have won the
<a href="https://twitter.com/supabase?ref_src=twsrc%5Etfw">@supabase</a> hackathon! 🥳 <a href="https://t.co/rm5HBuju73">https://t.co/rm5HBuju73</a>
</p>
&mdash; Josh Cawthorne (@cawthornejosh) <a href="https://twitter.com/cawthornejosh/status/1424985377136390183?ref_src=twsrc%5Etfw">August 10, 2021</a>
</blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>{' '}
</blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
### Rules
@@ -34,7 +34,7 @@ To take advantage of the open-source version of Metabase, you can use the Metaba
### Launching Metabase
To launch Metabase, simply go to "<http://localhost:3000/setup/>" which is the default port that the Metabase server will be listening to.
To launch Metabase, simply go to [http://localhost:3000/setup/](http://localhost:3000/setup/) which is the default port that the Metabase server will be listening to.
After Metabase is launched, select your preferred language and add your contact information. In the _Add your data_ markdown, you will need to choose PostgreSQL.
@@ -135,6 +135,7 @@ With our [free tier Supabase account](https://app.supabase.com/), you can start
If you have any questions please reach out via [Twitter](https://twitter.com/supabase) or join our [Discord](https://discord.supabase.com).
## More Python and Supabase resources
- [supabase-py](https://github.com/supabase-community/supabase-py)
- [Slack Consolidate: a slackbot built with Python and Supabase](slack-consolidate-slackbot-to-consolidate-messages)
- [Supabase-py (Database) on Replit](https://replit.com/@Supabase/Supabase-py-Database?v=1)
@@ -34,9 +34,8 @@ This time, we're switching things around:
little beauty (the hat, not the cat).
<br />
<br />
Now I've just got to pry it away from Peanut 🥜 😂 <a href="https://t.co/POBdVZrF5N">
pic.twitter.com/POBdVZrF5N
</a>
Now I've just got to pry it away from Peanut 🥜 😂
<a href="https://t.co/POBdVZrF5N">pic.twitter.com/POBdVZrF5N</a>
</p>
&mdash; Daniel Cranney 🇬🇧 (@danielcranney) <a href="https://twitter.com/danielcranney/status/1557249589182447616?ref_src=twsrc%5Etfw">August 10, 2022</a>
</blockquote>
@@ -26,8 +26,15 @@ Along our jurney we will:
- implement **magic link sign in** and user authentication with Angular
- make use of **the realtime capabilities**!
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/jmCiI_OSarA" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> Watch the video version of the tutorial.
<iframe
width="560"
height="315"
src="https://www.youtube-nocookie.com/embed/jmCiI_OSarA"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
></iframe> Watch the video version of the tutorial.
Since there are quite some code snippets we need I've put together the [full source code on Github](https://github.com/saimon24/supabase-trello-angular) so you can easily run the project yourself!
@@ -205,7 +212,7 @@ create policy "Users can edit cards if they are part of the board" on cards for
Finally we need a **trigger** that reacts to changes in our database.
In our case we want to listen to the creation of new boards, which will automatically create the board <-> user connection in the `user_boards` table.
In our case we want to listen to the creation of new boards, which will automatically create the board < - > user connection in the `user_boards` table.
Additionally we will also add every new authenticated user to our `users` table since you later don't have access to the internal auth table of Supabase!
@@ -0,0 +1,106 @@
---
title: Supabase Beta September 2022
description: We were too focused on clearing out the backlog so we didn't ship anything new last month... or did we?!
author: ant_wilson
image: 2022-september/monthly-update-september-2022.jpg
thumb: 2022-september/monthly-update-september-2022.jpg
tags:
- release-notes
date: '2022-10-05'
toc_depth: 3
---
We were too focused on clearing out the backlog so we didn't ship anything new last month... or did we?!
## Kaizen Week
We did something a bit strange during September - we *didn't* work on features. Quite the opposite. After Launch Week we did three subsequent weeks of [Kaizen](https://en.wikipedia.org/wiki/Kaizen), a term we use internally to deliver constant and incremental improvement. Each week had a different focus:
- Week 1: QA and testing
- Week 2: Documentation
- Week 3: Issue Backlog and Automation
It's pretty rare for a company to stop feature development altogether, but luckily we're just a bunch of developers so we all know the pain of technical debt. After 5 Launch Weeks, working on testing and backlogs feels like a bit of a relief.
We saw a lot of progress across our Open Issues - closing over 250 issues and 50 Pull Requests.
![Kaizen Week - Github Chart](/images/blog/2022-september/kaizen.jpg)
… <br/>
… <br/>
… <br/>
OK, we lied. We shipped a few things:
## Auth UI on Product Hunt
![Auth UI on Product Hunt](/images/blog/2022-september/auth-ui-product-hunt.png)
We launched the new Auth UI on Product Hunt! Auth UI is a pre-built React component for authenticating users with Supabase Auth. It supports custom themes and extensible styles to match your brand and aesthetic.
[Check out the launch.](https://www.producthunt.com/posts/auth-ui-login-ui-for-your-web-app)
## Postgres WASM
![Postgres WASM with Snaplet and Supabase](/images/blog/2022-september/postgres-wasm.jpg)
If you've ever wondered, "can I run Postgres inside a browser, using an embeddable Linux Virtual Machine?", wonder no longer. With our friends at [Snaplet](https://www.snaplet.dev/?utm_campaign=Beta%20update%20emails&utm_source=hs_email&utm_medium=email&_hsenc=p2ANqtz-8u0x9grbiydYqh56hC_8Dta4ZiTdWbW0eXWkVCnUEUGWWoVDFXCpWzJWImkLfM-lG-o2JX), we've released an open source Postgres WASM.
[Read the blog post](https://supabase.com/blog/postgres-wasm) <br/>
[Comment on Hacker News](https://news.ycombinator.com/item?id=33067962) <br/>
Visit the repo: [Snaplet](https://github.com/snaplet/postgres-wasm) | [Supabase](https://github.com/supabase-community/postgres-wasm)
## Security updates
We're making some changes to the way the Dashboard interacts with your database. These changes simplify the database permissions so that it's easier for you to migrate in and out of Supabase, and they reduce the security surface area considerably. The change will be applied automatically in November, or you can run it today via the Dashboard.
[Review the Security Notice](https://github.com/supabase/supabase/discussions/9314).
## Quick product updates
- Edge functions free plan script size is bumped to 2 MB <br/>
- [functions-go](https://github.com/supabase-community/functions-go) was just contributed to supabase-community by [Zain Khan](https://twitter.com/Za1n_K) <br/>
- A new [guide](https://supabase.com/docs/guides/auth/auth-captcha) to add captcha to your sign-in, sign-up, and password reset forms <br/>
- New Postgres Extension for monitoring your PostgreSQL database network traffic: [pg_netstat](https://github.com/supabase/pg_netstat) <br/>
- Added [docs](https://supabase.com/docs/guides/storage-cdn) for how caching works in Supabase <br/>
## Migrate from Heroku to Supabase
![Migrate from Heroku to Supabase](/images/blog/2022-september/migrate-from-heroku-to-supabase.jpg)
In case you missed it, Heroku is shutting down their free tier. We're big fans of Heroku and take a lot of inspiration from what they've built. Supabase is compatible with Heroku's PostgreSQL product (because we're just Postgres too), and if you have a free project running on Heroku we've created a [tool to help migrate](http://migrate.supabase.com/) to our free tier.
## Made with Supabase
![Made with Supabase - Supabase Schema](/images/blog/2022-september/madewithsupabase.jpg)
[Supabase Schema](https://supabase-schema.vercel.app/): An open source, free, login-less and secured Supabase Schema Visualizer. Simply plug in your URL and anon key and see a visual representation of your database schema! Built by the amazing [Zernonia](https://twitter.com/zernonia) using Vue 3, Viti, and WindiCSS.
## Extended Community Highlights
![Community](/images/blog/2022-june/community.jpg)
- Building a viral application to visualize train routes. [Blog](https://vercel.com/blog/building-a-viral-application-to-visualize-train-routes) <br/>
- OTP Authentication with Supabase and Twilio in React. [Tutorial](https://refine.dev/blog/supabase-twilio-otp-authentication-in-react/) <br/>
- Join Vercel and us for [Next.js Conf](https://nextjs.org/conf). <br/>
- Building $1K - $10K MRR Micro SaaS around Supabase. [Blog](https://microsaasidea.substack.com/p/micro-saas-products-around-supabase) <br/>
- Watch How Netlify and Supabase Enables "Supa" DX. [Webinar](https://www.netlify.com/resources/webinars/how-netlify-supabase-enables-supa-dx/) <br/>
- Postgres database functions are your next favorite feature! [Blog](https://mirror.xyz/0x7D15080A13c8128dBAf90a2c2326058b5c1D5eac/MaiOq5m1xSa9QfMuiLYq4bJdA0owWoTpOZNlQv76_so) <br/>
- Create a blog with Vue, Tailwindcss & Supabase. [Part 1](https://youtube.com/watch?v=0NB9lgFcOT4) and [Part 2](https://youtube.com/watch?v=UP2mHUpcv6g) <br/>
- Flutter Supabase Authentication. [Tutorial](https://www.sandromaglione.com/techblog/flutter-supabase-authentication-complete-tutorial) <br/>
- Vue 3 + Pinia + Supabase Auth (Email). [Tutorial](https://towardsdev.com/vue-3-pinia-supabase-auth-email-1228f5f8502e) <br/>
- Power up your ClickUp skills with Supabase, Figma, and DhiWise in a day. [Tutorial](https://dev.to/saloni137/power-up-your-clickup-skills-with-supabase-figma-and-dhiwise-in-a-day-3pak) <br/>
- Creating an Email Authentication using Supabase in React. [Tutorial](https://dev.to/surajondev/creating-an-email-authentication-using-supabase-in-react-5883) <br/>
## Meme Zone
If you made it this far in the blog post you deserve a treat. [Follow us on Twitter](https://twitter.com/supabase) for more.
![Supabase meme September 2022](/images/blog/2022-september/beta-update-september-meme.jpeg)
## Get started
- Start using Supabase today: **[app.supabase.com](https://app.supabase.com/)**
- Make sure to **[star us on GitHub](https://github.com/supabase/supabase)**
- Follow us **[on Twitter](https://twitter.com/supabase)**
- Subscribe to our **[YouTube channel](https://www.youtube.com/c/supabase)**
+2 -2
View File
@@ -51,9 +51,9 @@ const BlogListItem = ({ post }: Props) => {
<p className="text-scale-1100 max-w-sm text-base">{post.description}</p>
</div>
<div className="flex items-center -space-x-2">
{author.map((author: any) => {
{author.map((author: any, i: number) => {
return (
<div className="dark:ring-scale-200 w-10 rounded-full ring-2 ring-white">
<div className="dark:ring-scale-200 w-10 rounded-full ring-2 ring-white" key={i}>
{author.author_image_url && (
<Image
src={author.author_image_url}
+13 -16
View File
@@ -42,22 +42,19 @@ const EnterpriseContactForm = () => {
const [formSubmitted, setFormSubmitted] = useState<boolean>(false)
const handleFormSubmit = async (values: typeof INITIAL_VALUES, { resetForm }: any) => {
const { error } = await supabase.from('enterprise_contacts').insert(
[
{
company_name: values.companyName,
contact_name: `${values.contactFirstName} ${values.contactLastName}`,
contact_first_name: values.contactFirstName,
contact_last_name: values.contactLastName,
contact_email: values.contactEmail,
contact_phone: values.contactPhone,
company_size: values.companySize,
details: values.details,
country: values.country,
},
],
{ returning: 'minimal' }
)
const { error } = await supabase.from('enterprise_contacts').insert([
{
company_name: values.companyName,
contact_name: `${values.contactFirstName} ${values.contactLastName}`,
contact_first_name: values.contactFirstName,
contact_last_name: values.contactLastName,
contact_email: values.contactEmail,
contact_phone: values.contactPhone,
company_size: values.companySize,
details: values.details,
country: values.country,
},
])
// TODO: handle error
console.log('error:', error)
@@ -117,9 +117,9 @@ const CodeExamples = () => {
activeId={example}
type="underlined"
>
{Object.values(exampleList).map((x) => {
{Object.values(exampleList).map((x, i) => {
return (
<Tabs.Panel id={x.id} label={x.name}>
<Tabs.Panel id={x.id} label={x.name} key={i}>
<span></span>
</Tabs.Panel>
)
+13 -16
View File
@@ -42,22 +42,19 @@ const MfaContactForm = () => {
const [formSubmitted, setFormSubmitted] = useState<boolean>(false)
const handleFormSubmit = async (values: typeof INITIAL_VALUES, { resetForm }: any) => {
const { error } = await supabase.from('mfa_early_access_contacts').insert(
[
{
company_name: values.companyName,
contact_name: `${values.contactFirstName} ${values.contactLastName}`,
contact_first_name: values.contactFirstName,
contact_last_name: values.contactLastName,
contact_email: values.contactEmail,
contact_phone: values.contactPhone,
company_size: values.companySize,
details: values.details,
country: values.country,
},
],
{ returning: 'minimal' }
)
const { error } = await supabase.from('mfa_early_access_contacts').insert([
{
company_name: values.companyName,
contact_name: `${values.contactFirstName} ${values.contactLastName}`,
contact_first_name: values.contactFirstName,
contact_last_name: values.contactLastName,
contact_email: values.contactEmail,
contact_phone: values.contactPhone,
company_size: values.companySize,
details: values.details,
country: values.country,
},
])
// TODO: handle error
console.log('error:', error)
@@ -1,8 +1,7 @@
import { SupabaseClient } from '@supabase/supabase-js'
import { Button, Form, Input, InputNumber, Select } from '@supabase/ui'
import { useState } from 'react'
import countries from '~/data/Countries.json'
import { PartnerContact } from '~/types/partners'
import { SupabaseClient } from '~/lib/supabase'
const INITIAL_VALUES = {
type: 'expert',
@@ -41,24 +40,21 @@ export default function BecomeAPartner({ supabase }: { supabase: SupabaseClient
const [formSubmitted, setFormSubmitted] = useState<boolean>(false)
const handleFormSubmit = async (values: any) => {
const { error } = await supabase.from<PartnerContact>('partner_contacts').insert(
[
{
type: values.type,
first: values.first,
last: values.last,
company: values.company,
size: Number(values.size),
title: values.title,
email: values.email,
website: values.email.split('@')[1],
phone: values.phone,
country: values.country,
details: values.details,
},
],
{ returning: 'minimal' }
)
const { error } = await supabase.from('partner_contacts').insert([
{
type: values.type,
first: values.first,
last: values.last,
company: values.company,
size: Number(values.size),
title: values.title,
email: values.email,
website: values.email.split('@')[1],
phone: values.phone,
country: values.country,
details: values.details,
},
])
// TODO: handle error
console.log('error:', error)
@@ -172,8 +168,10 @@ export default function BecomeAPartner({ supabase }: { supabase: SupabaseClient
name="country"
layout="vertical"
>
{countries.map(({ code, name }) => (
<Select.Option value={code}>{name}</Select.Option>
{countries.map(({ code, name }: any, i: number) => (
<Select.Option key={i} value={code}>
{name}
</Select.Option>
))}
</Select>
</div>
+2 -2
View File
@@ -67,9 +67,9 @@ export const PricingTableRowDesktop = (props: any) => {
<td className="bg-scale-50 dark:bg-scale-300 px-6 py-5"></td>
</tr>
{category.features.map((feat: any) => {
{category.features.map((feat: any, i: number) => {
return (
<tr className="divide-scale-600 dark:divide-scale-400 divide-x">
<tr className="divide-scale-600 dark:divide-scale-400 divide-x" key={i}>
<th
className="text-scale-1200 flex items-center px-6 py-3 text-left text-sm font-normal dark:text-white"
scope="row"
+1 -1
View File
@@ -1,5 +1,5 @@
{
"show": true,
"show": false,
"text": "Auth UI Launch 🚀",
"cta": "Vote now",
"link": "https://www.producthunt.com/posts/pre-built-auth-ui-for-react",
+4
View File
@@ -79,6 +79,10 @@
{
"text": "Guides",
"url": "/docs/guides/examples"
},
{
"text": "Changelog",
"url": "/changelog"
}
]
},
+7 -8
View File
@@ -1,25 +1,22 @@
import authors from 'lib/authors.json'
import hydrate from 'next-mdx-remote/hydrate'
import { NextSeo } from 'next-seo'
import Image from 'next/image'
import Link from 'next/link'
import { useRouter } from 'next/router'
import React from 'react'
import { Gfm } from 'remark-gfm'
import CTABanner from '~/components/CTABanner'
import DefaultLayout from '~/components/Layouts/Default'
import { generateReadingTime } from '~/lib/helpers'
import { MDXRemote } from 'next-mdx-remote'
interface Props {
components: React.ReactNode
props: any
gfm: Gfm
slug: string
}
const LayoutComparison = ({ components, props, gfm, slug }: Props) => {
const LayoutComparison = ({ components, props }: Props) => {
// @ts-ignore
const content = hydrate(props.blog.content, { components })
const content = props.blog.content
const authorArray = props.blog.author.split(',')
@@ -98,7 +95,7 @@ const LayoutComparison = ({ components, props, gfm, slug }: Props) => {
<div className="text-scale-900 flex justify-center space-x-3 text-sm">
<p>{props.blog.date}</p>
<p>•</p>
<p>{generateReadingTime(props.blog.content.renderedOutput)}</p>
<p>{generateReadingTime(props.blog.source)}</p>
</div>
<div className="flex justify-center gap-3">
{author.map((author: any) => {
@@ -133,7 +130,9 @@ const LayoutComparison = ({ components, props, gfm, slug }: Props) => {
</div>
<div className="">
{/* Content */}
<div className="prose prose-docs max-w-none">{content}</div>
<div className="prose prose-docs max-w-none">
<MDXRemote {...content} components={components} />
</div>
<div className="py-16">
<div className="text-scale-900 dark:text-scale-1000 text-sm">Share this article</div>
<div className="mt-4 flex items-center space-x-4">
+112
View File
@@ -0,0 +1,112 @@
export type Json = string | number | boolean | null | { [key: string]: Json } | Json[]
export interface Database {
public: {
Tables: {
partners: {
Row: {
id: number
slug: string
type: Database['public']['Enums']['partner_type']
category: string
developer: string
title: string
description: string
logo: string
images: string[]
video: string
overview: string
website: string
docs: string
approved: boolean
}
Insert: {
id?: number
slug?: string
type?: Database['public']['Enums']['partner_type']
category?: string
developer?: string
title?: string
description?: string
logo?: string
images?: string[]
video?: string
overview?: string
website?: string
docs?: string
approved?: boolean
}
Update: {
id?: number
slug?: string
type?: Database['public']['Enums']['partner_type']
category?: string
developer?: string
title?: string
description?: string
logo?: string
images?: string[]
video?: string
overview?: string
website?: string
docs?: string
approved?: boolean
}
}
partner_contacts: {
Row: {
id: number
type: Database['public']['Enums']['partner_type']
company: string
country: string
details?: string
email: string
first: string
last: string
phone?: string
size?: number
title?: string
website: string
}
Insert: {
id?: number
type?: Database['public']['Enums']['partner_type']
company?: string
country?: string
details?: string
email?: string
first?: string
last?: string
phone?: string
size?: number
title?: string
website?: string
}
Update: {
id?: number
type?: Database['public']['Enums']['partner_type']
company?: string
country?: string
details?: string
email?: string
first?: string
last?: string
phone?: string
size?: number
title?: string
website?: string
}
}
}
Views: {}
Functions: {
derive_label_sort_from_label: {
Args: { label: string }
Returns: string
}
}
Enums: {
partner_type: 'technology' | 'expert'
}
}
}
+49
View File
@@ -0,0 +1,49 @@
import Image from 'next/image'
import Avatar from '~/components/Avatar'
import CodeBlock from '~/components/CodeBlock/CodeBlock'
import ImageGrid from '~/components/ImageGrid'
import Quote from '~/components/Quote'
// import all components used in blog articles here
// to do: move this into a helper/utils, it is used elsewhere
const ignoreClass = 'ignore-on-export'
export default function mdxComponents(type?: 'blog' | 'lp' | undefined) {
const components = {
CodeBlock,
Quote,
Avatar,
pre: (props: any) => {
if (props.className !== ignoreClass) {
return <CodeBlock {...props.children.props} />
} else {
return <code {...props} />
}
},
ImageGrid,
img: (props: any) => {
if (props.className !== ignoreClass) {
return (
<div
className={[
'next-image--dynamic-fill',
type === 'blog' && 'to-scale-400 from-scale-500 rounded-lg border bg-gradient-to-r',
].join(' ')}
>
<Image
{...props}
className={['next-image--dynamic-fill', type === 'blog' && 'rounded-md border'].join(
' '
)}
layout="fill"
/>
</div>
)
}
return <img {...props} />
},
}
return components
}
+17
View File
@@ -0,0 +1,17 @@
import { serialize } from 'next-mdx-remote/serialize'
import rehypeSlug from 'rehype-slug'
import remarkGfm from 'remark-gfm'
export async function mdxSerialize(source: string) {
const mdxSource: any = await serialize(source, {
mdxOptions: {
remarkPlugins: [remarkGfm],
rehypePlugins: [
// @ts-ignore
rehypeSlug, // add IDs to any h1-h6 tag that doesn't have one, using a slug made from its text
],
},
})
return mdxSource
}
File diff suppressed because it is too large. Load diff
+69
View File
@@ -0,0 +1,69 @@
module.exports = [
{
source: '/:path*',
destination: `/:path*`,
},
{
source: '/docs',
destination: `${process.env.NEXT_PUBLIC_DOCS_URL}`,
},
{
// redirect /docs/
// trailing slash caused by docusaurus issue with multizone
source: '/docs/',
destination: `${process.env.NEXT_PUBLIC_DOCS_URL}`,
},
{
source: '/docs/:path*',
destination: `${process.env.NEXT_PUBLIC_DOCS_URL}/:path*`,
},
// rewrite to keep existing ticket urls working
{
source: '/launch-week/tickets/:path',
destination: `${process.env.NEXT_PUBLIC_LAUNCHWEEKSITE_URL}/tickets/:path`,
},
// rewrite to move ticket website to another path
{
source: '/launch-week-register',
destination: `${process.env.NEXT_PUBLIC_LAUNCHWEEKSITE_URL}`,
},
{
source: '/launch-week-register/:path*',
destination: `${process.env.NEXT_PUBLIC_LAUNCHWEEKSITE_URL}/:path*`,
},
{
source: '/new-docs',
destination: `${process.env.NEXT_PUBLIC_REFERENCE_DOCS_URL}`,
},
{
// redirect /docs/
// trailing slash caused by docusaurus issue with multizone
source: '/new-docs/',
destination: `${process.env.NEXT_PUBLIC_REFERENCE_DOCS_URL}`,
},
{
source: '/new-docs/:path*',
destination: `${process.env.NEXT_PUBLIC_REFERENCE_DOCS_URL}/:path*`,
},
// misc rewrites
{
source: '/humans.txt',
destination: `${process.env.NEXT_PUBLIC_DOCS_URL}/humans.txt`,
},
{
source: '/lawyers.txt',
destination: `${process.env.NEXT_PUBLIC_DOCS_URL}/lawyers.txt`,
},
{
source: '/.well-known/security.txt',
destination: `${process.env.NEXT_PUBLIC_DOCS_URL}/.well-known/security.txt`,
},
{
source: '/oss',
destination: `${process.env.NEXT_PUBLIC_DOCS_URL}/oss`,
},
{
source: '/feed.xml',
destination: `/rss.xml`,
},
]
+4 -1
View File
@@ -1,9 +1,12 @@
import { createClient } from '@supabase/supabase-js'
import { Database } from './database.types'
const SUPABASE_URL = 'https://obuldanrptloktxcffvn.supabase.co'
const SUPABASE_ANON_KEY =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYzNzY1NjAxNSwiZXhwIjoxOTUzMjMyMDE1fQ.0sfp_Njf7l4g-nOCF5a1TQE11rPqtz8Y10uctIetkBA'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
const supabase = createClient<Database>(SUPABASE_URL, SUPABASE_ANON_KEY)
export type SupabaseClient = typeof supabase
export default supabase
File diff suppressed because it is too large. Load diff
+68
View File
@@ -0,0 +1,68 @@
import nextMdx from '@next/mdx'
import remarkGfm from 'remark-gfm'
import rehypeSlug from 'rehype-slug'
import rewrites from './lib/rewrites.js'
import redirects from './lib/redirects.js'
const withMDX = nextMdx({
extension: /\.mdx?$/,
options: {
remarkPlugins: [remarkGfm],
rehypePlugins: [rehypeSlug],
},
})
const nextConfig = {
basePath: '',
pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'],
trailingSlash: false,
images: {
dangerouslyAllowSVG: true,
domains: [
'avatars.githubusercontent.com',
'github.com',
'ca.slack-edge.com',
'res.cloudinary.com',
'images.unsplash.com',
'supabase.com',
'obuldanrptloktxcffvn.supabase.co',
'avatars.githubusercontent.com',
'colab.research.google.com',
'api.producthunt.com',
'https://s3-us-west-2.amazonaws.com',
's3-us-west-2.amazonaws.com',
'user-images.githubusercontent.com',
],
},
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Strict-Transport-Security',
value: '',
},
{
key: 'X-Robots-Tag',
value: 'all',
},
{
key: 'X-Frame-Options',
value: 'DENY',
},
],
},
]
},
async rewrites() {
return rewrites
},
async redirects() {
return redirects
},
}
export default withMDX(nextConfig)
+5 -5
View File
@@ -18,7 +18,7 @@
"@mdx-js/react": "^1.6.22",
"@next/mdx": "^10.0.7",
"@supabase/auth-ui-react": "^0.1.6",
"@supabase/supabase-js": "^1.6.0",
"@supabase/supabase-js": "^2.0.0-rc.10",
"@supabase/ui": "v0.37.0-alpha.80",
"classnames": "^2.3.1",
"date-fns": "2.10.0",
@@ -28,7 +28,7 @@
"markdown-toc": "^1.2.0",
"marked": "^4.0.12",
"next": "latest",
"next-mdx-remote": "^2.1.3",
"next-mdx-remote": "^4.1.0",
"next-seo": "^4.20.0",
"nodemailer": "^6.7.3",
"parse-numeric-range": "^1.3.0",
@@ -40,10 +40,10 @@
"react-syntax-highlighter": "^15.5.0",
"react-tooltip": "^4.2.17",
"react-transition-group": "^4.4.1",
"rehype-slug": "^4.0.1",
"remark": "^13.0.0",
"remark-gfm": "^1.0.0",
"remark-html": "10.0.0",
"rehype-slug": "^5.0.1",
"remark-gfm": "^3.0.1",
"remark-html": "^15.0.1",
"swiper": "^6.5.0",
"swr": "^0.5.4",
"typescript": "^4.0.3",
+7 -30
View File
@@ -1,30 +1,12 @@
import matter from 'gray-matter'
import authors from 'lib/authors.json'
import hydrate from 'next-mdx-remote/hydrate'
import renderToString from 'next-mdx-remote/render-to-string'
import React from 'react'
import Avatar from '~/components/Avatar'
import CodeBlock from '~/components/CodeBlock/CodeBlock'
import ImageGrid from '~/components/ImageGrid'
import Quote from '~/components/Quote'
import LayoutComparison from '~/layouts/comparison'
import mdxComponents from '~/lib/mdx/mdxComponents'
import { mdxSerialize } from '~/lib/mdx/mdxSerialize'
import { getAllPostSlugs, getPostdata, getSortedPosts } from '~/lib/posts'
// import all components used in blog articles here
// for instance, if you use a button, you must add `Button` in the components object below.
const components = {
CodeBlock,
Quote,
Avatar,
code: (props: any) => {
return <CodeBlock {...props} />
},
ImageGrid,
}
// plugins for next-mdx-remote
const gfm = require('remark-gfm')
const slug = require('rehype-slug')
// table of contents extractor
const toc = require('markdown-toc')
@@ -42,14 +24,7 @@ export async function getStaticProps({ params }: any) {
const postContent = await getPostdata(filePath, '_alternatives')
const { data, content } = matter(postContent)
const mdxSource: any = await renderToString(content, {
components,
scope: data,
mdxOptions: {
remarkPlugins: [gfm],
rehypePlugins: [slug],
},
})
const mdxSource: any = await mdxSerialize(content)
const relatedPosts = getSortedPosts('_alternatives', 5, mdxSource.scope.tags)
@@ -72,6 +47,7 @@ export async function getStaticProps({ params }: any) {
blog: {
slug: `${params.slug}`,
content: mdxSource,
source: content,
...data,
toc: toc(content, { maxdepth: data.toc_depth ? data.toc_depth : 2 }),
},
@@ -81,7 +57,8 @@ export async function getStaticProps({ params }: any) {
function BlogPostPage(props: any) {
// @ts-ignore
const content = hydrate(props.blog.content, { components })
// const content = hydrate(props.blog.content, { components })
// const content = props.blog.content
const authorArray = props.blog.author.split(',')
const author = []
@@ -95,7 +72,7 @@ function BlogPostPage(props: any) {
)
}
return <LayoutComparison components={components} props={props} gfm={gfm} slug={slug} />
return <LayoutComparison components={mdxComponents()} props={props} />
}
export default BlogPostPage
+2 -2
View File
@@ -112,9 +112,9 @@ function AuthPage() {
/>
</svg>
</div>
{AuthProviders.map((auth) => {
{AuthProviders.map((auth, i) => {
return (
<div className="flex w-fit items-center">
<div className="flex w-fit items-center" key={i}>
<Image
src={`${basePath}/images/product/auth/${auth.name}-icon.svg`}
alt={`${auth.name} auth login icon`}
+2 -2
View File
@@ -167,9 +167,9 @@ function FeaturedThumb(blog: PostTypes) {
</div>
<div className="grid w-max grid-flow-col grid-rows-4 gap-4">
{author.map((author: any) => {
{author.map((author: any, i: number) => {
return (
<div className="flex items-center space-x-3">
<div className="flex items-center space-x-3" key={i}>
{author.author_image_url && (
<div className="relative h-10 w-10 overflow-auto">
<Image
+17 -132
View File
@@ -1,61 +1,19 @@
import { Badge, Card, Divider, IconChevronLeft, IconFile, Space } from '@supabase/ui'
import { Badge, Divider, IconChevronLeft, IconFile } from '@supabase/ui'
import matter from 'gray-matter'
import authors from 'lib/authors.json'
import hydrate from 'next-mdx-remote/hydrate'
import renderToString from 'next-mdx-remote/render-to-string'
import { MDXRemote } from 'next-mdx-remote'
import { NextSeo } from 'next-seo'
import Link from 'next/link'
import Image from 'next/image'
import Link from 'next/link'
import { useRouter } from 'next/router'
import React from 'react'
import ReactMarkdown from 'react-markdown'
import CodeBlock from '~/components/CodeBlock/CodeBlock'
import CTABanner from '~/components/CTABanner'
import DefaultLayout from '~/components/Layouts/Default'
import Quote from '~/components/Quote'
import Avatar from '~/components/Avatar'
import ImageGrid from '~/components/ImageGrid'
import { generateReadingTime } from '~/lib/helpers'
import mdxComponents from '~/lib/mdx/mdxComponents'
import { mdxSerialize } from '~/lib/mdx/mdxSerialize'
import { getAllPostSlugs, getPostdata, getSortedPosts } from '~/lib/posts'
const ignoreClass = 'ignore-on-export'
// import all components used in blog articles here
// for instance, if you use a button, you must add `Button` in the components object below.
const components = {
CodeBlock,
Quote,
Avatar,
code: (props: any) => {
if (props.className !== ignoreClass) {
return <CodeBlock {...props} />
} else {
return <code {...props} />
}
},
ImageGrid,
img: (props: any) => {
if (props.className !== ignoreClass) {
return (
<div
className="
next-image--dynamic-fill
to-scale-400
from-scale-500 rounded-lg
border bg-gradient-to-r
"
>
<Image {...props} className="next-image--dynamic-fill rounded-md border" layout="fill" />
</div>
)
}
return <img {...props} />
},
}
// plugins for next-mdx-remote
const gfm = require('remark-gfm')
const slug = require('rehype-slug')
// table of contents extractor
const toc = require('markdown-toc')
@@ -72,14 +30,7 @@ export async function getStaticProps({ params }: any) {
const postContent = await getPostdata(filePath, '_blog')
const { data, content } = matter(postContent)
const mdxSource: any = await renderToString(content, {
components,
scope: data,
mdxOptions: {
remarkPlugins: [gfm],
rehypePlugins: [slug],
},
})
const mdxSource: any = await mdxSerialize(content)
const relatedPosts = getSortedPosts('_blog', 5, mdxSource.scope.tags)
@@ -102,6 +53,7 @@ export async function getStaticProps({ params }: any) {
blog: {
slug: `${params.slug}`,
content: mdxSource,
source: content,
...data,
toc: toc(content, { maxdepth: data.toc_depth ? data.toc_depth : 2 }),
},
@@ -110,8 +62,7 @@ export async function getStaticProps({ params }: any) {
}
function BlogPostPage(props: any) {
// @ts-ignore
const content = hydrate(props.blog.content, { components })
const content = props.blog.content
const authorArray = props.blog.author.split(',')
const author = []
@@ -166,7 +117,7 @@ function BlogPostPage(props: any) {
<div>
<p className="text-scale-1200 mb-4">On this page</p>
<div className="prose-toc">
<ReactMarkdown plugins={[gfm]}>{props.blog.toc.content}</ReactMarkdown>
<ReactMarkdown>{props.blog.toc.content}</ReactMarkdown>
</div>
</div>
</div>
@@ -235,12 +186,12 @@ function BlogPostPage(props: any) {
<div className="text-scale-900 flex space-x-3 text-sm">
<p>{props.blog.date}</p>
<p>•</p>
<p>{generateReadingTime(props.blog.content.renderedOutput)}</p>
<p>{generateReadingTime(props.blog.source)}</p>
</div>
<div className="flex flex-col gap-3 pt-6 md:flex-row md:gap-0 lg:gap-3">
{author.map((author: any) => {
{author.map((author: any, i: number) => {
return (
<div className="mr-4 w-max">
<div className="mr-4 w-max" key={i}>
<Link href={author.author_url}>
<a className="cursor-pointer">
<div className="flex items-center gap-3">
@@ -284,7 +235,9 @@ function BlogPostPage(props: any) {
/>
</div>
)}
<article className={['prose prose-docs'].join(' ')}>{content}</article>
<article className={['prose prose-docs'].join(' ')}>
<MDXRemote {...content} components={mdxComponents()} />
</article>
<div className="py-16">
<div className="text-scale-900 dark:text-scale-1000 text-sm">
Share this article
@@ -348,8 +301,8 @@ function BlogPostPage(props: any) {
<p className="text-scale-1200 text-sm">Related articles</p>
</div>
<div className="space-y-3">
{props.relatedPosts.map((post: any) => (
<Link href={`${post.path}`} as={`${post.path}`}>
{props.relatedPosts.map((post: any, i: number) => (
<Link href={`${post.path}`} as={`${post.path}`} key={i}>
<div>
<p className="cursor-pointer">
<div className="flex gap-2">
@@ -381,78 +334,10 @@ function BlogPostPage(props: any) {
</div>
</div>
{/* <div className="container px-8 sm:px-16 xl:px-20 py-16 mx-auto">
<div className="max-w-6xl mx-auto">
<div className="py-4 grid grid-cols-12 lg:gap-16">
<div className="col-span-12 lg:col-span-8">
{props.blog.thumb && (
<img
src={'/images/blog/' + props.blog.thumb}
className="object-cover -mt-32 mb-8 border dark:border-gray-600"
style={{ maxHeight: '520px', width: '100%' }}
/>
)}
<div className="lg:hidden">{toc}</div>
<article className={blogStyles['article']}>
<Typography>{content}</Typography>
</article>
</div>
<div className="col-span-12 lg:col-span-4">
<Space direction="vertical" size={8} className="lg:mb-16 lg:top-16 lg:sticky">
<div className="hidden lg:block">{toc}</div>
<div>
<h5 className="mb-4">
Related articles
</h5>
<Space direction="vertical">
{props.relatedPosts.map((post: any) => (
<Link href={`${post.path}`} as={`${post.path}`}>
<div>
<p className="cursor-pointer">
<Space>
<IconFile size={'small'} style={{ minWidth: '1.2rem' }} />
<span className="hover:text-gray-900 dark:hover:text-white">
{post.title}
</span>
</Space>
</p>
<Divider light className="mt-2" />
</div>
</Link>
))}
<Link href={`/blog`} as={`/blog`}>
<div>
<p>
<span className="hover:text-gray-900 dark:hover:text-white cursor-pointer">
View all posts
</span>
</p>
</div>
</Link>
</Space>
</div>
</Space>
</div>
</div>
<div className="grid lg:grid-cols-2 gap-8 py-8">
<div>{props.prevPost && <NextCard post={props.prevPost} label="Last post" />}</div>
<div>
{props.nextPost && (
<NextCard post={props.nextPost} label="Next post" className="text-right" />
)}
</div>
</div>
</div>
</div> */}
<CTABanner />
</DefaultLayout>
</>
)
}
// function BlogPostPage() {
// return <h1>blog post</h1>
// }
export default BlogPostPage
+5 -2
View File
@@ -45,8 +45,11 @@ function TagBlogsPage(props: Props) {
</div>
<ol className="grid grid-cols-12 gap-8 py-16 lg:gap-16">
{blogs.map((blog: PostTypes, idx: number) => (
<div className="col-span-12 mb-16 md:col-span-12 lg:col-span-6 xl:col-span-4">
<BlogListItem post={blog} key={idx} />
<div
className="col-span-12 mb-16 md:col-span-12 lg:col-span-6 xl:col-span-4"
key={idx}
>
<BlogListItem post={blog} />
</div>
))}
</ol>
+123
View File
@@ -0,0 +1,123 @@
import { IconGitCommit } from '@supabase/ui'
import dayjs from 'dayjs'
import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote'
import { NextSeo } from 'next-seo'
import CTABanner from '~/components/CTABanner'
import DefaultLayout from '~/components/Layouts/Default'
import mdxComponents from '~/lib/mdx/mdxComponents'
import { mdxSerialize } from '~/lib/mdx/mdxSerialize'
export async function getStaticProps() {
const response = await fetch('https://api.github.com/repos/supabase/supabase/releases')
const data = await response.json()
if (!data) {
return {
props: {
notFound: true,
},
}
}
const changelogRenderToString = await Promise.all(
data.map(async (item: any): Promise<any> => {
const mdxSource: MDXRemoteSerializeResult = await mdxSerialize(item.body)
return {
...item,
source: mdxSource,
}
})
)
return {
props: {
changelog: changelogRenderToString,
},
}
}
function ChangelogPage(props: any) {
return (
<>
<NextSeo
title={'Changelog'}
openGraph={{
title: 'Changelog',
description: 'props.blog.description',
url: `https://supabase.com/changelog`,
type: 'article',
article: {
//
// to do: add expiration and modified dates
// https://github.com/garmeeh/next-seo#article
// publishedTime: props.blog.date,
},
// images: [
// {
// url: `https://supabase.com${basePath}/images/blog/${
// props.blog.image ? props.blog.image : props.blog.thumb
// }`,
// },
// ],
}}
/>
<DefaultLayout>
<div
className="
container mx-auto flex flex-col
gap-20
px-8 py-10 sm:px-16
xl:px-20
"
>
{/* Title and description */}
<div className="py-10">
<h1 className="h1">Changelog</h1>
<p className="text-scale-900 text-lg">New updates and product improvements</p>
</div>
{/* Content */}
<div>
{props.changelog.map((changelog: any, i: number) => {
const date = changelog.published_at.split('T')
return (
<div
key={i}
className="border-scale-400 grid border-l pb-10 lg:grid-cols-12 lg:gap-8"
>
<div
className="col-span-12 mb-8 self-start lg:sticky lg:top-0 lg:col-span-4 lg:-mt-32 lg:pt-32
"
>
<div className="flex w-full items-baseline gap-6">
<div className="bg-scale-100 dark:bg-scale-500 border-scale-400 dark:border-scale-600 text-scale-900 -ml-2.5 flex h-5 w-5 items-center justify-center rounded border drop-shadow-sm">
<IconGitCommit size={14} strokeWidth={1.5} />
</div>
<div className="flex w-full flex-col gap-1">
{changelog.name && (
<h3 className="text-scale-1200 text-2xl">{changelog.name}</h3>
)}
<p className="text-scale-900 text-lg">
{dayjs(date[0]).format('MMM D, YYYY')}
</p>
</div>
</div>
</div>
<div className="col-span-8 ml-8 lg:ml-0">
<article className="prose prose-docs max-w-none">
<MDXRemote {...changelog.source} components={mdxComponents('blog')} />
</article>
</div>
</div>
)
})}
</div>
</div>
<CTABanner />
</DefaultLayout>
</>
)
}
export default ChangelogPage
@@ -139,9 +139,12 @@ function Database() {
<SectionContainer>
<div className="col-span-12 mb-10 space-y-12 lg:col-span-3 lg:mb-0 ">
<div className="grid gap-8 rounded md:grid-cols-2 xl:grid-cols-4">
{featureBlocks.map((item) => {
{featureBlocks.map((item, i) => {
return (
<div className="bg-scale-100 dark:bg-scale-300 group flex flex-col gap-4 rounded border px-8 py-6">
<div
className="bg-scale-100 dark:bg-scale-300 group flex flex-col gap-4 rounded border px-8 py-6"
key={i}
>
<div className="bg-scale-300 dark:bg-scale-500 text-scale-1200 group-hover:text-brand-900 flex h-12 w-12 items-center justify-center rounded-md border transition-all group-hover:scale-105">
{item.icon ? item.icon : <IconCode strokeWidth={2} />}
</div>
+2 -2
View File
@@ -201,7 +201,7 @@ function Partner({ partner }: { partner: Partner }) {
// This function gets called at build time
export const getStaticPaths: GetStaticPaths = async () => {
const { data: slugs } = await supabase.from<Partner>('partners').select('slug')
const { data: slugs } = await supabase.from('partners').select('slug')
const paths: {
params: { slug: string }
@@ -222,7 +222,7 @@ export const getStaticPaths: GetStaticPaths = async () => {
// This also gets called at build time
export const getStaticProps: GetStaticProps = async ({ params }) => {
let { data: partner } = await supabase
.from<Partner>('partners')
.from('partners')
.select('*')
.eq('slug', params!.slug as string)
.single()
+1 -1
View File
@@ -10,7 +10,7 @@ import TileGrid from '../../../components/Partners/TileGrid'
export async function getStaticProps() {
const { data: partners } = await supabase
.from<Partner>('partners')
.from('partners')
.select('*')
.eq('approved', true)
.eq('type', 'expert')
Loaded 100 of 1265 files, more files were not shown because too many files have changed in this diff. Show more