merge master

This commit is contained in:
Jordi Enric committed 2024-04-25 12:05:36 +02:00
commit 3e916eebf7
127 files changed
+19176 -33822

No files matched your search

+3
View File
@@ -23,3 +23,6 @@ yarn-error.log*
**/*/generated/**/*
!**/*/generated/.gitkeep
!**/*/generated/**/.gitkeep
# Sitemap
public/sitemap.xml
+2 -1
View File
@@ -16,7 +16,7 @@ import { Button, cn } from 'ui'
import { useSendFeedbackMutation } from '~/lib/fetch/feedback'
import { useSendTelemetryEvent } from '~/lib/telemetry'
import { FeedbackModal, type FeedbackFields } from './FeedbackModal'
import { getSanitizedTabParams } from './Feedback.utils'
import { getNotionTeam, getSanitizedTabParams } from './Feedback.utils'
const FeedbackButton = forwardRef<
HTMLButtonElement,
@@ -125,6 +125,7 @@ function Feedback() {
title,
// @ts-expect-error -- can't click this button without having a state.response
isHelpful: state.response === 'yes',
team: getNotionTeam(pathname),
})
setModalOpen(false)
refocusButton()
@@ -1,5 +1,39 @@
import { pick } from 'lodash'
/**
* Gets the Notion team to send feedback to based on the pathname.
*/
const getNotionTeam = (pathname: string) => {
const DEFAULT_TEAM = 'team-docs'
// Pathname has format `/guides/(team)/**`
const pathParts = pathname.split('/')
if (pathParts[1] !== 'guides' || !pathParts[2]) return DEFAULT_TEAM
switch (pathParts[2]) {
case 'database':
return 'team-postgres'
case 'auth':
return 'team-auth'
case 'storage':
return 'team-storage'
case 'functions':
return 'team-functions'
case 'realtime':
return 'team-realtime'
case 'ai':
return 'team-ai'
case 'cli':
return 'team-cli'
// Ignoring platform for now because that section is a mix of teams.
case 'platform':
default:
return DEFAULT_TEAM
}
}
/**
* Gets the tab selection state from the URL search params.
*
@@ -13,4 +47,4 @@ const getSanitizedTabParams = () => {
return pick(Object.fromEntries(searchParams.entries()), queryGroups)
}
export { getSanitizedTabParams }
export { getNotionTeam, getSanitizedTabParams }
@@ -4,3 +4,9 @@ The next step requires a callback URL, which looks like this: `https://<project-
- Click on the `Authentication` icon in the left sidebar
- Click on [`Providers`](https://supabase.com/dashboard/project/_/auth/providers) under the Configuration section
- Click on **{props.provider}** from the accordion list to expand and you'll find your **Callback URL**, you can click `Copy` to copy it to the clipboard
<Admonition type="note">
For testing OAuth locally with the Supabase CLI please see the [local development docs](/docs/guides/cli/local-development#use-auth-locally).
</Admonition>
@@ -26,7 +26,7 @@ import {
IconMenuDevCli,
} from './HomeMenuIcons'
function getMenuIcon(menuKey: string, width: number = 16, height: number = 16, className) {
function getMenuIcon(menuKey: string, width: number = 16, height: number = 16, className: string) {
switch (menuKey) {
case 'home':
return <IconMenuHome width={width} height={height} className={className} />
@@ -195,7 +195,7 @@ export const REFERENCES: References = {
csharp: {
name: 'C#',
library: 'supabase-csharp',
versions: ['v0'],
versions: ['v1', 'v0'],
icon: '/docs/img/libraries/c-sharp-icon.svg',
},
swift: {
@@ -624,6 +624,10 @@ export const auth = {
name: 'Next.js guide',
url: '/guides/auth/server-side/nextjs',
},
{
name: 'SvelteKit guide',
url: '/guides/auth/server-side/sveltekit',
},
{
name: 'Creating a client',
url: '/guides/auth/server-side/creating-a-client',
@@ -715,6 +719,10 @@ export const database: NavMenuConstant = {
name: 'Managing tables, views, and data',
url: '/guides/database/tables',
},
{
name: 'Working with arrays',
url: '/guides/database/arrays',
},
{ name: 'Managing indexes', url: '/guides/database/postgres/indexes' },
{
name: 'Querying joins and nested tables',
@@ -890,7 +898,7 @@ export const database: NavMenuConstant = {
url: '/guides/database/extensions/pg-safeupdate',
},
{
name: 'pgsodium: Encryption Features',
name: 'pgsodium (pending deprecation): Encryption Features',
url: '/guides/database/extensions/pgsodium',
},
{
@@ -1057,6 +1065,10 @@ export const api: NavMenuConstant = {
name: 'Using custom schemas',
url: '/guides/api/using-custom-schemas',
},
{
name: 'Converting from SQL to JavaScript API',
url: '/guides/api/sql-to-api',
},
],
},
],
@@ -2081,6 +2093,13 @@ export const reference_csharp_v0 = {
parent: '/reference',
}
export const reference_csharp_v1 = {
icon: 'reference-csharp',
title: 'C#',
url: 'guides/reference/csharp',
parent: '/reference',
}
export const reference_python_v2 = {
icon: 'reference-python',
title: 'Python',
@@ -24,6 +24,7 @@ enum MenuId {
RefDartV1 = 'reference_dart_v1',
RefDartV2 = 'reference_dart_v2',
RefCSharpV0 = 'reference_csharp_v0',
RefCSharpV1 = 'reference_csharp_v1',
RefPythonV2 = 'reference_python_v2',
RefSwiftV1 = 'reference_swift_v1',
RefSwiftV2 = 'reference_swift_v2',
@@ -154,6 +155,13 @@ const menus: Menu[] = [
commonSectionsFile: 'common-client-libs-sections.json',
specFile: 'supabase_csharp_v0.yml',
type: 'reference',
path: '/reference/csharp/v0',
},
{
id: MenuId.RefCSharpV1,
commonSectionsFile: 'common-client-libs-sections.json',
specFile: 'supabase_csharp_v1.yml',
type: 'reference',
path: '/reference/csharp',
},
{
@@ -109,7 +109,7 @@ Similarity search is the most common use case for vectors. `pgvector` support 3
Choosing the right operator depends on your needs. Dot product tends to be the fastest if your vectors are normalized. For more information on how embeddings work and how they relate to each other, see [What are Embeddings?](/docs/guides/ai/concepts#what-are-embeddings).
Supabase client libraries like `supabase-js` connect to your Postgres instance via [PostgREST](docs/guides/getting-started/architecture#postgrest-api). PostgREST does not currently support `pgvector` similarity operators, so we'll need to wrap our query in a Postgres function and call it via the `rpc()` method:
Supabase client libraries like `supabase-js` connect to your Postgres instance via [PostgREST](/docs/guides/getting-started/architecture#postgrest-api). PostgREST does not currently support `pgvector` similarity operators, so we'll need to wrap our query in a Postgres function and call it via the `rpc()` method:
```sql
create or replace function match_documents (
@@ -195,7 +195,7 @@ Read more about [PostgREST's pre-request function](https://postgrest.org/en/stab
>
<TabPanel id="rate-limit-per-ip" label="Rate limit per IP">
You can only rate-limit `POST`, `PUT`, `PATCH` and `DELETE` requests. This is because `GET` and `HEAD` requests run in read-only mode, and will be served by [Read Replicas](/guides/platform/read-replicas) which do not support writing to the database.
You can only rate-limit `POST`, `PUT`, `PATCH` and `DELETE` requests. This is because `GET` and `HEAD` requests run in read-only mode, and will be served by [Read Replicas](/docs/guides/platform/read-replicas) which do not support writing to the database.
Outline:
@@ -265,7 +265,7 @@ alter role authenticator
notify pgrst, 'reload config';
```
To clear old entries in the `private.rate_limits` table, set up a [pg_cron](/guides/database/extensions/pg_cron) job to clean them up.
To clear old entries in the `private.rate_limits` table, set up a [pg_cron](/docs/guides/database/extensions/pg_cron) job to clean them up.
</TabPanel>
@@ -4,7 +4,9 @@ title: 'Converting SQL to JavaScript API'
description: 'Implementing common SQL patterns in the JavaScript API'
---
{/* @TODO: this one is a very daunting read on first attempt. Need to check what is the purpose of it */}
Many common SQL queries can be written using the JavaScript API, provided by the SDK to wrap Data API calls. Below are a few examples of conversions between SQL and JavaScript patterns.
## Select statement with basic clauses
Select a set of columns from a single table with where, order by, and limit clauses.
@@ -28,6 +30,8 @@ const { data, error } = await supabase
.limit(20)
```
## Select statement with complex Boolean Logic clause
Select all columns from a single table with a complex where clause: OR AND OR
```sql
@@ -42,7 +46,6 @@ const { data, error } = await supabase
.select() // or .select('*')
.or('team_id.eq.CHN,team_id.is.null')
.or('age.gt.35,age.is.null') // additional filters imply "AND"
.not('team_id', 'eq', 'STL')
```
Select all columns from a single table with a complex where clause: AND OR AND
@@ -60,21 +63,6 @@ const { data, error } = await supabase
.or('and(team_id.eq.CHN,age.gt.35),and(team_id.neq.CHN,.not.age.is.null)')
```
Get a count of rows, but don't return any data.
```sql
select count(*)
from players
where team_id = 'NYM';
```
```js
const { count, error } = await supabase
.from('players')
.select('*', { count: 'exact', head: true }) // exact, planned, or executed
.eq('team_id', 'NYM')
```
## Resources
- [Supabase - Get started for free](https://supabase.com)
@@ -27,7 +27,11 @@ The `auth-helpers` package has been replaced with the `@supabase/ssr` package. W
This submodule provides convenience helpers for implementing user authentication in Next.js applications using the pages directory.
> Note: As of [Next.js 13.4](https://nextjs.org/blog/next-13-4), the App Router has reached stable status. This is now the recommended path for new Next.js app. Check out our guide on using [Auth Helpers with the Next.js App Directory](/docs/guides/auth/auth-helpers/nextjs).
<Admonition type="note">
Note: As of [Next.js 13.4](https://nextjs.org/blog/next-13-4), the App Router has reached stable status. This is now the recommended path for new Next.js app. Check out our guide on using [Auth Helpers with the Next.js App Directory](/docs/guides/auth/auth-helpers/nextjs).
</Admonition>
## Install the Next.js helper library
@@ -36,7 +36,11 @@ This submodule provides convenience helpers for implementing user authentication
></iframe>
</div>
> For a complete implementation example, check out [this free egghead course](https://egghead.io/courses/build-a-realtime-chat-app-with-remix-and-supabase-d36e2618) or [this GitHub repo](https://github.com/supabase/auth-helpers/tree/main/examples/remix).
<Admonition type="tip">
For a complete implementation example, check out [this free egghead course](https://egghead.io/courses/build-a-realtime-chat-app-with-remix-and-supabase-d36e2618) or [this GitHub repo](https://github.com/supabase/auth-helpers/tree/main/examples/remix).
</Admonition>
## Install the Remix helper library
@@ -179,7 +183,11 @@ export const loader = async ({ request }) => {
}
```
> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Loader` function.
<Admonition type="tip">
Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Loader` function.
</Admonition>
</TabPanel>
<TabPanel id="ts" label="TypeScript">
@@ -209,7 +217,11 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
}
```
> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Loader` function.
<Admonition type="tip">
Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Loader` function.
</Admonition>
</TabPanel>
</Tabs>
@@ -251,7 +263,11 @@ export const action = async ({ request }) => {
}
```
> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Action` function.
<Admonition type="tip">
Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Action` function.
</Admonition>
</TabPanel>
<TabPanel id="ts" label="TypeScript">
@@ -282,7 +298,11 @@ export const action = async ({ request }: ActionFunctionArgs) => {
}
```
> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Action` function.
<Admonition type="tip">
Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Action` function.
</Admonition>
</TabPanel>
</Tabs>
@@ -341,7 +361,11 @@ export const loader = () => {
}
```
> These may not be stored in `process.env` for environments other than Node.
<Admonition type="tip">
These may not be stored in `process.env` for environments other than Node.
</Admonition>
Next, we call the `useLoaderData` hook in our component to get the `env` object.
@@ -375,7 +399,11 @@ export const loader = ({}: LoaderFunctionArgs) => {
}
```
> These may not be stored in `process.env` for environments other than Node.
<Admonition type="tip">
These may not be stored in `process.env` for environments other than Node.
</Admonition>
Next, we call the `useLoaderData` hook in our component to get the `env` object.
@@ -558,7 +586,11 @@ useEffect(() => {
</Tabs>
> Check out [this repo](https://github.com/supabase/auth-helpers/tree/main/examples/remix) for full implementation example
<Admonition type="tip">
Check out [this repo](https://github.com/supabase/auth-helpers/tree/main/examples/remix) for full implementation example
</Admonition>
### Authentication
@@ -771,7 +803,11 @@ export default function Index() {
</Tabs>
> Ensure you have [enabled replication](https://supabase.com/dashboard/project/_/database/publications) on the table you are subscribing to.
<Admonition type="tip">
Ensure you have [enabled replication](https://supabase.com/dashboard/project/_/database/publications) on the table you are subscribing to.
</Admonition>
## Migration guide
@@ -125,6 +125,20 @@ on my_table for select
using ( true );
```
### Protect views using `security_invoker`
Views bypass RLS by default because they are usually created with the `postgres` user. This is a feature of Postgres, which automatically creates views with `security definer`.
In Postgres 15 and above, you can make a view obey the RLS policies of the underlying tables when invoked by `anon` and `authenticated` roles by setting `security_invoker = true`.
```sql
create view <VIEW_NAME>
with(security_invoker = true)
as select <QUERY>
```
In older versions of Postgres, protect your views by revoking access from the `anon` and `authenticated` roles, or by putting them in an unexposed schema.
### Using external authorization systems
If you want to use another authorization method for your applications that's also fine. Supabase is "just Postgres", so if your application works with Postgres, then it also works with Supabase. If you take this path, don't put your tables in the `public` schema - instead create a new schema for your tables and functions:
@@ -469,22 +469,27 @@ export const handle: Handle = async ({ event, resolve }) => {
})
/**
* Unlike `supabase.auth.getSession`, which is unsafe on the server because it
* doesn't validate the JWT, this function validates the JWT by first calling
* `getUser` and aborts early if the JWT signature is invalid.
* Unlike `supabase.auth.getSession()`, which returns the session _without_
* validating the JWT, this function also calls `getUser()` to validate the
* JWT before returning the session.
*/
event.locals.safeGetSession = async () => {
const {
data: { session },
} = await event.locals.supabase.auth.getSession()
if (!session) {
return { session: null, user: null }
}
const {
data: { user },
error,
} = await event.locals.supabase.auth.getUser()
if (error) {
// JWT validation has failed
return { session: null, user: null }
}
const {
data: { session },
} = await event.locals.supabase.auth.getSession()
return { session, user }
}
@@ -9,15 +9,22 @@ When using Supabase with server-side languages and frameworks - such as Next.js,
## Framework quickstarts
<div className="grid cols-[repeat(auto-fit,min-max(250px,1fr)] gap-6 mb-6 not-prose">
<div className="grid grid-cols-[repeat(auto-fit,minmax(300px,1fr))] gap-6 mb-6 not-prose">
{[
{
title: 'Next.js',
href: '/guides/auth/server-side/nextjs',
description:
'Automatically configure Supabase to use cookies, making your user and their session available on the client and server.',
'Automatically configure Supabase in Next.js to use cookies, making your user and their session available on the client and server.',
icon: '/docs/img/icons/nextjs-icon',
},
{
title: 'SvelteKit',
href: '/guides/auth/server-side/sveltekit',
description:
'Automatically configure Supabase in SvelteKit to use cookies, making your user and their session available on the client and server.',
icon: '/docs/img/icons/svelte-icon',
},
].map((item) => {
return (
<Link href={`${item.href}`} key={item.title} passHref>
@@ -0,0 +1,636 @@
---
title: 'Setting up Server-Side Auth for SvelteKit'
hideToc: true
---
Set up Server-Side Auth to use cookie-based authentication with SvelteKit.
<StepHikeCompact>
<StepHikeCompact.Step step={1}>
<StepHikeCompact.Details title="Install Supabase packages">
Install the `@supabase/supabase-js` package and the helper `@supabase/ssr` package.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```sh
npm install @supabase/supabase-js @supabase/ssr
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={2}>
<StepHikeCompact.Details title="Set up environment variables">
Create a `.env.local` file in your project root directory.
Fill in your `PUBLIC_SUPABASE_URL` and `PUBLIC_SUPABASE_ANON_KEY`:
<ProjectConfigVariables variable="url" />
<ProjectConfigVariables variable="anonKey" />
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```txt .env.local
PUBLIC_SUPABASE_URL=<your_supabase_project_url>
PUBLIC_SUPABASE_ANON_KEY=<your_supabase_anon_key>
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={3}>
<StepHikeCompact.Details title="Set up server-side hooks">
Set up server-side hooks in `src/hooks.server.ts`. The hooks:
- Create a request-specific Supabase client, using the user credentials from the request cookie. This client is used for server-only code.
- Check user authentication.
- Guard protected pages.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```ts src/hooks.server.ts
import { createServerClient } from '@supabase/ssr'
import { type Handle, redirect } from '@sveltejs/kit'
import { sequence } from '@sveltejs/kit/hooks'
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'
const supabase: Handle = async ({ event, resolve }) => {
/**
* Creates a Supabase client specific to this server request.
*
* The Supabase client gets the Auth token from the request cookies.
*/
event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
cookies: {
get: (key) => event.cookies.get(key),
/**
* SvelteKit's cookies API requires `path` to be explicitly set in
* the cookie options. Setting `path` to `/` replicates previous/
* standard behavior.
*/
set: (key, value, options) => {
event.cookies.set(key, value, { ...options, path: '/' })
},
remove: (key, options) => {
event.cookies.delete(key, { ...options, path: '/' })
},
},
})
/**
* Unlike `supabase.auth.getSession()`, which returns the session _without_
* validating the JWT, this function also calls `getUser()` to validate the
* JWT before returning the session.
*/
event.locals.safeGetSession = async () => {
const {
data: { session },
} = await event.locals.supabase.auth.getSession()
if (!session) {
return { session: null, user: null }
}
const {
data: { user },
error,
} = await event.locals.supabase.auth.getUser()
if (error) {
// JWT validation has failed
return { session: null, user: null }
}
return { session, user }
}
return resolve(event, {
filterSerializedResponseHeaders(name) {
/**
* Supabase libraries use the `content-range` header, so we need to
* tell SvelteKit to pass it through.
*/
return name === 'content-range'
},
})
}
const authGuard: Handle = async ({ event, resolve }) => {
const { session, user } = await event.locals.safeGetSession()
event.locals.session = session
event.locals.user = user
if (!event.locals.session && event.url.pathname.startsWith('/private')) {
return redirect(303, '/auth')
}
if (event.locals.session && event.url.pathname === '/auth') {
return redirect(303, '/private')
}
return resolve(event)
}
export const handle: Handle = sequence(supabase, authGuard)
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={4}>
<StepHikeCompact.Details title="Create TypeScript definitions">
To prevent TypeScript errors, add type definitions for the new `event.locals` properties.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```ts src/app.d.ts
import type { Session, SupabaseClient, User } from '@supabase/supabase-js'
declare global {
namespace App {
// interface Error {}
interface Locals {
supabase: SupabaseClient
safeGetSession: () => Promise<{ session: Session | null; user: User | null }>
session: Session | null
user: User | null
}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {}
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={5}>
<StepHikeCompact.Details title="Create a Supabase client in your root layout">
Create a Supabase client in your root `+layout.ts`. This client can be used to access Supabase from the client or the server. In order to get access to the Auth token on the server, use a `+layout.server.ts` file to pass in the session from `event.locals`.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
<CH.Code>
```ts src/routes/+layout.ts
import { createBrowserClient, createServerClient, isBrowser, parse } from '@supabase/ssr'
import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public'
import type { LayoutLoad } from './$types'
export const load: LayoutLoad = async ({ data, depends, fetch }) => {
/**
* Declare a dependency so the layout can be invalidated, for example, on
* session refresh.
*/
depends('supabase:auth')
const supabase = isBrowser()
? createBrowserClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
global: {
fetch,
},
cookies: {
get(key) {
const cookie = parse(document.cookie)
return cookie[key]
},
},
})
: createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
global: {
fetch,
},
cookies: {
get() {
return JSON.stringify(data.session)
},
},
})
/**
* It's fine to use `getSession` here, because on the client, `getSession` is
* safe, and on the server, it reads `session` from the `LayoutData`, which
* safely checked the session using `safeGetSession`.
*/
const {
data: { session },
} = await supabase.auth.getSession()
const {
data: { user },
} = await supabase.auth.getUser()
return { session, supabase, user }
}
```
```ts src/routes/+layout.server.ts
import type { LayoutServerLoad } from './$types'
export const load: LayoutServerLoad = async ({ locals: { session } }) => {
return {
session,
}
}
```
</CH.Code>
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={6}>
<StepHikeCompact.Details title="Listen to Auth events">
Set up a listener for Auth events on the client, to handle session refreshes and signouts.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```svelte src/routes/+layout.svelte
<script>
import { goto, invalidate } from '$app/navigation';
import { onMount } from 'svelte';
export let data;
$: ({ session, supabase } = data);
onMount(() => {
const { data } = supabase.auth.onAuthStateChange((_, newSession) => {
if (!newSession) {
/**
* Queue this as a task so the navigation won't prevent the
* triggering function from completing
*/
setTimeout(() => {
goto('/', { invalidateAll: true });
});
}
if (newSession?.expires_at !== session?.expires_at) {
invalidate('supabase:auth');
}
});
return () => data.subscription.unsubscribe();
});
</script>
<slot />
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={7}>
<StepHikeCompact.Details title="Create your first page">
Create your first page. This example page calls Supabase from the server to get a list of countries from the database.
This is an example of a public page that uses publicly readable data.
To populate your database, run the [countries quickstart](/dashboard/project/_/sql/quickstarts) from your dashboard.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
<CH.Code>
```ts src/routes/+page.server.ts
import type { PageServerLoad } from './$types'
export const load: PageServerLoad = async ({ locals: { supabase } }) => {
const { data: countries } = await supabase.from('countries').select('name').limit(5).order('name')
return { countries: countries ?? [] }
}
```
```svelte src/routes/+page.svelte
<script>
export let data;
$: ({ countries } = data);
</script>
<h1>Welcome to Supabase!</h1>
<ul>
{#each countries as country}
<li>{country.name}</li>
{/each}
</ul>
```
</CH.Code>
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={8}>
<StepHikeCompact.Details title="Change the Auth confirmation path">
If you have email confirmation turned on (the default), a new user will receive an email confirmation after signing up.
Change the email template to support a server-side authentication flow.
Go to the [Auth templates](https://supabase.com/dashboard/project/_/auth/templates) page in your dashboard. In the `Confirm signup` template, change `{{ .ConfirmationURL }}` to `{{ .SiteURL }}/api/auth/confirm?token_hash={{ .TokenHash }}&type=signup`.
</StepHikeCompact.Details>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={9}>
<StepHikeCompact.Details title="Create a login page">
Next, create a login page to let users sign up and log in.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
<CH.Code>
```ts src/routes/auth/+page.server.ts
import { redirect } from '@sveltejs/kit'
import type { Actions } from './$types'
export const actions: Actions = {
signup: async ({ request, locals: { supabase } }) => {
const formData = await request.formData()
const email = formData.get('email') as string
const password = formData.get('password') as string
const { error } = await supabase.auth.signUp({ email, password })
if (error) {
console.error(error)
return redirect(303, '/auth/error')
} else {
return redirect(303, '/')
}
},
login: async ({ request, locals: { supabase } }) => {
const formData = await request.formData()
const email = formData.get('email') as string
const password = formData.get('password') as string
const { error } = await supabase.auth.signInWithPassword({ email, password })
if (error) {
console.error(error)
return redirect(303, '/auth/error')
} else {
return redirect(303, '/private')
}
},
}
```
```svelte src/routes/auth/+page.svelte
<form method="POST" action="?/login">
<label>
Email
<input name="email" type="email" />
</label>
<label>
Password
<input name="password" type="password" />
</label>
<button>Login</button>
<button formaction="?/signup">Sign up</button>
</form>
```
```svelte src/routes/auth/+layout.svelte
<header>
<nav>
<a href="/">Home</a>
</nav>
</header>
<slot />
```
```svelte src/routes/auth/error/+page.svelte
<p>Login error</p>
```
</CH.Code>
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={10}>
<StepHikeCompact.Details title="Create the signup confirmation route">
Finish the signup flow by creating the API route to handle email verification.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```ts src/routes/api/auth/confirm/+server.ts
import type { EmailOtpType } from '@supabase/supabase-js'
import { redirect } from '@sveltejs/kit'
import type { RequestHandler } from './$types'
export const GET: RequestHandler = async ({ url, locals: { supabase } }) => {
const token_hash = url.searchParams.get('token_hash')
const type = url.searchParams.get('type') as EmailOtpType | null
const next = url.searchParams.get('next') ?? '/'
/**
* Clean up the redirect URL by deleting the Auth flow parameters.
*
* `next` is preserved for now, because it's needed in the error case.
*/
const redirectTo = new URL(url)
redirectTo.pathname = next
redirectTo.searchParams.delete('token_hash')
redirectTo.searchParams.delete('type')
if (token_hash && type) {
const { error } = await supabase.auth.verifyOtp({ type, token_hash })
if (!error) {
redirectTo.searchParams.delete('next')
return redirect(303, redirectTo)
}
}
redirectTo.pathname = '/auth/error'
return redirect(303, redirectTo)
}
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={11}>
<StepHikeCompact.Details title="Create private routes">
Create private routes that can only be accessed by authenticated users. The routes in the `private` directory are protected by the route guard in `hooks.server.ts`.
To ensure that `hooks.server.ts` runs for every nested path, put a `+layout.server.ts` file in the `private` directory. This file can be empty, but must exist to protect routes that don't have their own `+layout|page.server.ts`.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
<CH.Code className="min-h-[30rem]">
```ts src/routes/private/+layout.server.ts
/**
* This file is necessary to ensure protection of all routes in the `private`
* directory. It makes the routes in this directory _dynamic_ routes, which
* send a server request, and thus trigger `hooks.server.ts`.
**/
```
```svelte src/routes/private/+layout.svelte
<script>
export let data;
$: ({ supabase } = data);
$: logout = async () => {
const { error } = await supabase.auth.signOut();
if (error) {
console.error(error);
}
};
</script>
<header>
<nav>
<a href="/">Home</a>
</nav>
<button on:click={logout}>Logout</button>
</header>
<main>
<slot />
</main>
```
```sql SQL
-- Run this SQL against your database to create a `notes` table.
create table notes (
id bigint primary key generated always as identity,
created_at timestamp with time zone not null default now(),
user_id uuid references auth.users on delete cascade not null default auth.uid(),
note text not null
);
alter table notes enable row level security;
revoke all on table notes from authenticated;
revoke all on table notes from anon;
grant all (note) on table notes to authenticated;
grant select (id) on table notes to authenticated;
grant delete on table notes to authenticated;
create policy "Users can access and modify their own notes"
on notes
for all
to authenticated
using ((select auth.uid()) = user_id);
```
```svelte src/routes/private/+page.server.ts
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ depends, locals: { supabase } }) => {
depends('supabase:db:notes');
const { data: notes } = await supabase.from('notes').select('id,note').order('id');
return { notes: notes ?? [] };
};
```
```svelte src/routes/private/+page.svelte
<script lang="ts">
import { invalidate } from '$app/navigation';
import type { EventHandler } from 'svelte/elements';
import type { PageData } from './$types';
export let data: PageData;
$: ({ notes, supabase, user } = data);
let handleSubmit: EventHandler<SubmitEvent, HTMLFormElement>;
$: handleSubmit = async (evt) => {
evt.preventDefault();
if (!evt.target) return;
const form = evt.target as HTMLFormElement;
const note = (new FormData(form).get('note') ?? '') as string;
if (!note) return;
const { error } = await supabase.from('notes').insert({ note });
if (error) console.error(error);
invalidate('supabase:db:notes');
form.reset();
};
</script>
<h1>Private page for user: {user?.email}</h1>
<h2>Notes</h2>
<ul>
{#each notes as note}
<li>{note.note}</li>
{/each}
</ul>
<form on:submit={handleSubmit}>
<label>
Add a note
<input name="note" type="text" />
</label>
</form>
```
</CH.Code>
</StepHikeCompact.Code>
</StepHikeCompact.Step>
</StepHikeCompact>
@@ -311,7 +311,7 @@ Before you can use Sign in with Google, you need to obtain a [Google Cloud Platf
## Google sign-in for Web, macOS, Windows, and Linux
Google sign-in with Supabase on Web, macOS, Windows, and Linux is done through the [signInWithOAuth](docs/reference/dart/auth-signinwithoauth) method.
Google sign-in with Supabase on Web, macOS, Windows, and Linux is done through the [signInWithOAuth](/docs/reference/dart/auth-signinwithoauth) method.
This method of signing in is web based, and will open a browser window to perform the sign in. For non-web platforms, the user is brought back to the app via [deep linking](/docs/guides/auth/native-mobile-deep-linking?platform=flutter).
+1 -1
View File
@@ -6,7 +6,7 @@ subtitle: 'Developing locally using the Supabase CLI.'
sidebar_label: 'Overview'
---
You can use the Supabase CLI to run the entire Supabase stack locally on your machine, simply by running `supabase init` and then `supabase start`.
You can use the Supabase CLI to run the entire Supabase stack locally on your machine, simply by running `supabase init` and then `supabase start`. To install the CLI, see the [installation guide](/docs/guides/cli/getting-started#installing-the-supabase-cli).
The Supabase CLI provides tools to develop your project locally, deploy to the Supabase Platform, handle database migrations, and generate types directly from your database schema.
@@ -20,7 +20,7 @@ Usage:
supabase test db [flags]
```
This is powered by the [pgTAP](https://supabase.com/docs/guides/database/extensions/pgtap) extension. You can find a full guide to writing an running tests in the [Testing your database](/docs/guides/database/testing) section.
This is powered by the [pgTAP](https://supabase.com/docs/guides/database/extensions/pgtap) extension. You can find a full guide to writing and running tests in the [Testing your database](/docs/guides/database/testing) section.
### Test helpers
@@ -28,7 +28,7 @@ Our friends at [Basejump](https://usebasejump.com/) have created a useful set of
### Running database tests in CI
Use our GitHub Action to [automate your database tests](docs/guides/cli/github-action/testing#testing-your-database).
Use our GitHub Action to [automate your database tests](/docs/guides/cli/github-action/testing#testing-your-database).
## Testing your Edge Functions
@@ -7,7 +7,7 @@ subtitle: 'Connecting to your Postgres database in serverless environments.'
Supabase provides several options for connecting to your Postgres database from serverless environments.
[supabase-js](/reference/javascript/introduction) is an isomorphic JavaScript client that uses the [auto-generated REST API](/guides/api) and therefore works in any environment that supports HTTPS connections. This API has a built-in [connection pooler](/guides/database/connecting-to-postgres#connection-pooler) and can serve thousands of simultaneous requests, and therefore is ideal for Serverless workloads.
[supabase-js](/docs/reference/javascript/introduction) is an isomorphic JavaScript client that uses the [auto-generated REST API](/docs/guides/api) and therefore works in any environment that supports HTTPS connections. This API has a built-in [connection pooler](/docs/guides/database/connecting-to-postgres#connection-pooler) and can serve thousands of simultaneous requests, and therefore is ideal for Serverless workloads.
## Vercel Edge Functions
@@ -214,7 +214,11 @@ where rolname = 'postgres';
PGAudit was designed for storing logs as CSV files with the following headers:
> Referenced from the [PGAudit official docs](https://github.com/pgaudit/pgaudit/blob/master/README.md#format)
<Admonition type="note">
Referenced from the [PGAudit official docs](https://github.com/pgaudit/pgaudit/blob/master/README.md#format)
</Admonition>
| header | Description |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
@@ -1,90 +1,23 @@
---
id: 'pgsodium'
title: 'pgsodium: Encryption Features'
title: 'pgsodium (pending deprecation): Encryption Features'
description: 'Encryption library for PostgreSQL'
---
[`pgsodium`](https://github.com/michelp/pgsodium) is a PostgreSQL extension which provides SQL access to [`libsodium`'s](https://doc.libsodium.org/) high-level cryptographic algorithms.
Supabase DOES NOT RECOMMEND any new usage of [`pgsodium`](https://github.com/michelp/pgsodium).
`libsodium` is a modern, easy-to-use software library for encryption, decryption, signatures, password hashing, and more. It is a portable, cross-compilable, installable, and packageable fork of the [NaCl](http://nacl.cr.yp.to/) library, with a compatible but extended API to improve usability even further.
The [`pgsodium`](https://github.com/michelp/pgsodium) extension is expected to go through a deprecation cycle in the near future. We will reach out to owners of impacted projects to assist with migrations away from [`pgsodium`](https://github.com/michelp/pgsodium) once the deprecation process begins.
The design choices emphasize security and ease of use. But despite the emphasis on high security, primitives are faster across-the-board than most implementations.
[`pgsodium`](https://github.com/michelp/pgsodium) is a Postgres extension which provides SQL access to [`libsodium`'s](https://doc.libsodium.org/) high-level cryptographic algorithms.
`pgsodium` exposes the following `libsodium` APIs to SQL:
Supabase previously documented two features derived from pgsodium. Namely [Server Key Management](https://github.com/michelp/pgsodium#server-key-management) and [Transparent Column Encryption](https://github.com/michelp/pgsodium#transparent-column-encryption). At this time, we do not recommend using either on the Supabase platform due to their high level of operational complexity and misconfiguration risk.
- [Generating Random Data](https://github.com/michelp/pgsodium/#generating-random-data)
- [Secret key cryptography](https://github.com/michelp/pgsodium/#secret-key-cryptography)
- [Authenticated encryption](https://github.com/michelp/pgsodium/#authenticated-encryption)
- [Authentication](https://github.com/michelp/pgsodium/#authentication)
- [Public key cryptography](https://github.com/michelp/pgsodium/#public-key-cryptography)
- [Authenticated encryption](https://github.com/michelp/pgsodium/#authenticated-encryption-1)
- [Public key signatures](https://github.com/michelp/pgsodium/#public-key-signatures)
- [Sealed boxes](https://github.com/michelp/pgsodium/#sealed-boxes)
- [Hashing](https://github.com/michelp/pgsodium/#hashing)
- [Password hashing](https://github.com/michelp/pgsodium/#password-hashing)
- [Key Derivation](https://github.com/michelp/pgsodium/#key-derivation)
- [Key Exchange](https://github.com/michelp/pgsodium/#key-exchange)
- [HMAC512](https://github.com/michelp/pgsodium/#hmac512)
- [Advanced Stream API](https://github.com/michelp/pgsodium/#advanced-stream-api)
- [XChaCha20-SIV](https://github.com/michelp/pgsodium/#xchacha20-siv)
- [Signcryption](https://github.com/michelp/pgsodium/#signcryption)
It also enables some Postgres specific features including:
- [Server Key Management](https://github.com/michelp/pgsodium#server-key-management)
- [Transparent Column Encryption](https://github.com/michelp/pgsodium#transparent-column-encryption)
Note that column encryption should only be used in highly sensitive scenarios as it has a meaningful impact on statement performance and flexibility.
Specifically:
- Encryption and decryption both take time. Inserting and selecting encrypted data takes more time than a "plain" column of data.
- Encrypted columns should never be indexed. This is because the index will store the encrypted value of a column, which would not be useful.
- Encrypted columns can be queried in a `WHERE` clause, but this can also have some negative performance consequences, since the value must be decrypted in order to matched to any `WHERE` qualifiers.
- While you can encrypt multiple columns in the same table, each column must go through a full encryption cycle, so two columns will take twice the time as one etc.
In general, it is a bad idea to over-use column encryption for mundane data or data that you need to search against such as names, user or account types, addresses, country codes, etc. Column encryption is intended to be used for very sensitive data that would cause serious issues if it were to leak, such as API keys, payment keys, highly sensitive personal information, etc.
Note that Supabase projects are already encrypted at rest by default.
Note that Supabase projects are encrypted at rest by default which likely is sufficient for your compliance needs e.g. SOC2 & HIPAA.
## Get the root encryption key for your Supabase project
Encryption requires keys. Keeping the keys in the same database as the encrypted data would be unsafe. For more information about managing the `pgsodium` root encryption key on your Supabase project see **[encryption key location](/docs/guides/database/vault#encryption-key-location)**. This key is required to decrypt values stored in [Supabase Vault](/docs/guides/database/vault) and data encrypted with Transparent Column Encryption.
## Enable the extension
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard"
queryGroup="database-method"
>
<TabPanel id="dashboard" label="Dashboard">
1. Go to the [Database](https://supabase.com/dashboard/project/_/database/tables) page in the Dashboard.
2. Click on **Extensions** in the sidebar.
3. Search for `pgsodium` and enable the extension.
</TabPanel>
<TabPanel id="sql" label="SQL">
{/* prettier-ignore */}
```sql
-- Enable the "pgsodium" extension
create extension pgsodium;
-- Disable the "pgsodium" extension
drop extension if exists pgsodium;
```
</TabPanel>
</Tabs>
## Resources
- [Supabase Vault](/docs/guides/database/vault)
@@ -1,27 +0,0 @@
---
id: 'managing-passwords'
title: 'Passwords'
description: 'How to change your PostgreSQL database password.'
---
Your Postgres database is the core of your Supabase project, so it's important that it has a strong, secure password at all times.
If you use special symbols in your postgres password, you must remember to [percent-encode](https://en.wikipedia.org/wiki/Percent-encoding) your password later if using the postgres connection string e.g. `postgresql://postgres.projectref:p%3Dword@aws-0-us-east-1.pooler.supabase.com:6543/postgres`
## Changing your project password
When you created your project you were also asked to enter a password. This is actually the password for your database, specifically for the `postgres` user.
You can update this from the Dashboard under the [database settings](https://supabase.com/dashboard/project/_/settings/database) page.
## Creating a secure password
It's absolutely critical that you store your customers' data safely. Here are some tips for creating a secure password.
- Use a password manager to generate it.
- Make a long password (12 characters at least).
- Don't use any common dictionary words.
- Use both upper and lower case characters, numbers, and special symbols.
## Resources
- [PostgreSQL `ALTER USER` Documentation](https://www.postgresql.org/docs/12/sql-alteruser.html)
@@ -1,77 +0,0 @@
---
id: 'managing-timezones'
title: 'Timezones'
slug: 'managing-timezones'
description: 'How to change your database timezone.'
---
Every Supabase database is set to UTC timezone by default. We strongly recommend keeping it this way, even if your users are in a different location.
This is because it makes it much easier to calculate differences between timezones if you adopt the mental model that "everything in my database is in UTC time."
### Change timezone
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="sql"
queryGroup="database-method"
>
<TabPanel id="sql" label="SQL">
```sql
alter database postgres
set timezone to 'America/New_York';
```
</TabPanel>
</Tabs>
### Full list of timezones
Get a full list of timezones supported by your database. This will return the following columns:
- `name`: Time zone name
- `abbrev`: Time zone abbreviation
- `utc_offset`: Offset from UTC (positive means east of Greenwich)
- `is_dst`: True if currently observing daylight savings
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="sql"
queryGroup="database-method"
>
<TabPanel id="sql" label="SQL">
```sql
select name, abbrev, utc_offset, is_dst
from pg_timezone_names()
order by name;
```
</TabPanel>
</Tabs>
### Search for a specific timezone
Use `ilike` (case insensitive search) to find specific timezones.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="sql"
queryGroup="database-method"
>
<TabPanel id="sql" label="SQL">
```sql
select *
from pg_timezone_names()
where name ilike '%york%';
```
</TabPanel>
</Tabs>
+1 -2
View File
@@ -79,8 +79,7 @@ You must define the "data type" when you create a column.
### Data types
Every column is a predefined type. PostgreSQL provides many [default types](https://www.postgresql.org/docs/current/datatype.html), and you can even design your own (or use extensions)
if the default types don't fit your needs. You can use any data type that Postgres supports via the SQL editor. We only support a subset of these in the Table Editor in an effort to keep the experience simple for people with less experience with databases.
Every column is a predefined type. PostgreSQL provides many [default types](https://www.postgresql.org/docs/current/datatype.html), and you can even design your own (or use extensions) if the default types don't fit your needs. You can use any data type that Postgres supports via the SQL editor. We only support a subset of these in the Table Editor in an effort to keep the experience simple for people with less experience with databases.
<details>
<summary>Show/Hide default data types</summary>
@@ -70,7 +70,7 @@ Inference via larger models is supported via [Ollama](https://ollama.com/). In t
### Running locally
1. Install Ollama and pull the Mistral model
1. [Install Ollama](https://github.com/ollama/ollama?tab=readme-ov-file#ollama) and pull the Mistral model
```
ollama pull mistral
@@ -85,7 +85,7 @@ Inference via larger models is supported via [Ollama](https://ollama.com/). In t
1. Set a function secret called AI_INFERENCE_API_HOST to point to the Ollama server
```
echo "AI_INFERENCE_API_HOST=http://host.docker.internal:11434/api/generate" >> supabase/functions/.env
echo "AI_INFERENCE_API_HOST=http://host.docker.internal:11434" >> supabase/functions/.env
```
1. Create a new function with the following code
@@ -172,7 +172,7 @@ Manage your projects programmatically. [Docs](/docs/reference/api).
## Client libraries
Official client libraries for [JavaScript](/docs/reference/javascript/start), [Flutter](docs/reference/dart/initializing) and [Swift](/docs/reference/swift/introduction).
Official client libraries for [JavaScript](/docs/reference/javascript/start), [Flutter](/docs/reference/dart/initializing) and [Swift](/docs/reference/swift/introduction).
Unofficial libraries are supported by the community.
## Feature status
@@ -138,7 +138,11 @@ export class SupabaseService {
Let's set up a route to manage logins and signups. We'll use Magic Links so users can sign in with their email without using passwords.
Create a **LoginPage** with the `ionic g page login` Ionic CLI command.
> This guide will show the template inline, but the example app will have `templateUrl`s
<Admonition type="tip">
This guide will show the template inline, but the example app will have `templateUrl`s
</Admonition>
```ts src/app/login/login.page.ts
import { Component, OnInit } from '@angular/core'
@@ -456,7 +456,11 @@ const HomePage = () => {
export default HomePage
```
> What we're doing here is showing the sign in form if you aren't logged in and your account profile if you are.
<Admonition type="note">
What we're doing here is showing the sign in form if you aren't logged in and your account profile if you are.
</Admonition>
### Launch!
@@ -85,22 +85,27 @@ export const handle: Handle = async ({ event, resolve }) => {
})
/**
* Unlike `supabase.auth.getSession`, which is unsafe on the server because it
* doesn't validate the JWT, this function validates the JWT by first calling
* `getUser` and aborts early if the JWT signature is invalid.
* Unlike `supabase.auth.getSession()`, which returns the session _without_
* validating the JWT, this function also calls `getUser()` to validate the
* JWT before returning the session.
*/
event.locals.safeGetSession = async () => {
const {
data: { session },
} = await event.locals.supabase.auth.getSession()
if (!session) {
return { session: null, user: null }
}
const {
data: { user },
error,
} = await event.locals.supabase.auth.getUser()
if (error) {
// JWT validation has failed
return { session: null, user: null }
}
const {
data: { session },
} = await event.locals.supabase.auth.getSession()
return { session, user }
}
@@ -153,7 +158,11 @@ export const load = (async ({ locals: { safeGetSession } }) => {
}) satisfies LayoutServerLoad
```
> Start your dev server (`npm run dev`) in order to generate the `./$types` files we are referencing in our project.
<Admonition type="tip">
Start your dev server (`npm run dev`) in order to generate the `./$types` files we are referencing in our project.
</Admonition>
Create a new `src/routes/+layout.ts` file to handle the session and the supabase object on the client-side.
@@ -24,6 +24,14 @@ Supabase Branching works with Git. You can test changes in a separate, temporary
You can run multiple Preview Branches for every Supabase project. Branches contain all the Supabase features with their own API credentials. Preview Environments pause automatically after 5 minutes of inactivity.
<Admonition type="caution" label="Some features may not work as expected on Preview Branches">
Due to Preview Branches running on Fly Postgres instances, some features like `pg-cron` are not fully supported.
For a full list of limitations, see [Fly Postgres limitations](/docs/guides/platform/fly-postgres#limitations).
</Admonition>
<figure className="max-w-[700px] mx-auto">
<Image
className="hidden dark:block"
@@ -212,7 +212,6 @@ Sentry.init({
}),
new Sentry.Integrations.Undici({
shouldCreateSpanForRequest: (url) => {
console.log('server', `${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest`, url)
return !url.startsWith(`${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest`)
},
}),
@@ -1,87 +0,0 @@
---
id: 'realtime'
title: 'Throttling messages'
description: 'Use client-side throttling to manage message frequency.'
subtitle: 'Use client-side throttling to manage message frequency.'
---
The Supabase clients include functionality for throttling messages.
## Managing client-side throttling
You can customize the client-side throttling when creating the client:
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
queryGroup="language"
>
<TabPanel id="js" label="JavaScript">
```js
import { createClient } from '@supabase/supabase-js'
const SUPABASE_URL = 'https://<project>.supabase.co'
const SUPABASE_ANON_KEY = '<your-anon-key>'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
realtime: {
params: {
eventsPerSecond: 2,
},
},
})
```
</TabPanel>
<TabPanel id="dart" label="Dart">
```dart
import 'package:supabase_flutter/supabase_flutter.dart';
void main() async {
Supabase.initialize(
url: 'https://<project>.supabase.co',
anonKey: '<your-anon-key>',
realtimeClientOptions: const RealtimeClientOptions(
eventsPerSecond: 2,
),
);
runApp(const MyApp());
}
final supabase = Supabase.instance.client;
```
</TabPanel>
<TabPanel id="kotlin" label="Kotlin">
```kotlin
val supabase = createSupabaseClient(
"https://<project>.supabase.co",
"<your-anon-key>"
) {
install(Realtime) {
eventsPerSecond = 2
}
}
```
</TabPanel>
</Tabs>
## Default client throttling
By default the Supabase clients throttles messages to 10 messages per-second (1 message every 100 milliseconds). This is a soft-limit provided as a safe-guard when you're getting started. You'll rarely need to send more messages than this.
Each client has their own throttling behavior. If you instantiate two clients, by default you would send 20 messages per-second to your project.
## Project quotas
Each Broadcast and Presence message counts towards your [Project Quotas](/docs/guides/realtime/quotas).
It's common to unintentionally flood the Realtime service with messages. For example, tracking mouse movements without throttling would send hundreds of events per second. It's rare that you need so many messages. Updating a mouse movement even a few times per second is usually enough for the human eye.
The throttling parameter protects against these unintended floods.
@@ -32,6 +32,6 @@ on storage.objects
for delete
TO authenticated
USING (
owner_id = (select auth.uid())
owner = (select auth.uid())
);
```
+2 -2
View File
@@ -10,7 +10,7 @@ custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supab
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
You can install Supabase package from [nuget.org](https://www.nuget.org/packages/supabase-csharp/)
You can install Supabase package from [nuget.org](https://www.nuget.org/packages/supabase/)
</RefSubLayout.Details>
@@ -25,7 +25,7 @@ custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supab
<TabPanel id="csharp" label="Terminal">
```sh Terminal
dotnet add package supabase-csharp
dotnet add package supabase
```
</TabPanel>
+2 -2
View File
@@ -16,7 +16,7 @@ hideTitle: true
{/* prettier-ignore */}
<div className="max-w-xl">
This reference documents every object and method available in Supabase's C# library, [supabase-csharp](https://www.nuget.org/packages/supabase-csharp). You can use supabase-csharp to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
This reference documents every object and method available in Supabase's C# library, [supabase](https://www.nuget.org/packages/supabase). You can use `Supabase` to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
</div>
{/* prettier-ignore */}
@@ -24,6 +24,6 @@ hideTitle: true
The C# client library is created and maintained by the Supabase community, and is not an official library. Please be tolerant of areas where the library is still being developed, and — as with all the libraries — feel free to contribute wherever you find issues.
Huge thanks to official maintainer, [Joseph Schultz](https://github.com/acupofjose), and to [Ben Randall](https://github.com/veleek) and [Rhuan Barros](https://github.com/rhuanbarros) for their help.
Huge thanks to official maintainer, [Joseph Schultz](https://github.com/acupofjose). As well as [Will Iverson](https://github.com/wiverson), [Ben Randall](https://github.com/veleek), and [Rhuan Barros](https://github.com/rhuanbarros) for their help.
</div>
+33 -369
View File
@@ -3,372 +3,36 @@ id: release-notes
title: Release Notes
---
## 0.11.0 - 2023-05-24
- Update dependency: postgrest-csharp@3.2.0
- General codebase and QOL improvements. Exceptions are generally thrown through `PostgrestException` now instead
of `Exception`. A `FailureHint.Reason` is provided with failures if possible to parse.
- `AddDebugListener` is now available on the client to help with debugging
- Merges [#65](https://github.com/supabase-community/postgrest-csharp/pull/65) Cleanup + Add better exception handling
- Merges [#66](https://github.com/supabase-community/postgrest-csharp/pull/66) Local test Fixes
- Fixes [#67](https://github.com/supabase-community/postgrest-csharp/issues/67) Postgrest Reference attribute is
producing StackOverflow for circular references
- Update dependency: gotrue-csharp@4.0.2
- [#58](https://github.com/supabase-community/gotrue-csharp/issues/58) - Add support for the `reauthentication` endpoint which allows for secure password changes.
- Update dependency: realtime-csharp@6.0.1
- Updates publishing action for future packages, includes README and icon.
- Merges [#28](https://github.com/supabase-community/realtime-csharp/pull/28) and [#30](https://github.com/supabase-community/realtime-csharp/pull/30)
- The realtime client now takes a "fail-fast" approach. On establishing an initial connection, client will throw
a `RealtimeException` in `ConnectAsync()` if the socket server is unreachable. After an initial connection has been
established, the **client will continue attempting reconnections indefinitely until disconnected.**
- [Major, New] C# `EventHandlers` have been changed to `delegates`. This should allow for cleaner event data access over
the previous subclassed `EventArgs` setup. Events are scoped accordingly. For example, the `RealtimeSocket` error
handlers will receive events regarding socket connectivity; whereas the `RealtimeChannel` error handlers will receive
events according to `Channel` joining/leaving/etc. This is implemented with the following methods prefixed by (
Add/Remove/Clear):
- `RealtimeBroadcast.AddBroadcastEventHandler`
- `RealtimePresence.AddPresenceEventHandler`
- `RealtimeSocket.AddStateChangedHandler`
- `RealtimeSocket.AddMessageReceivedHandler`
- `RealtimeSocket.AddHeartbeatHandler`
- `RealtimeSocket.AddErrorHandler`
- `RealtimeClient.AddDebugHandler`
- `RealtimeClient.AddStateChangedHandler`
- `RealtimeChannel.AddPostgresChangeHandler`
- `RealtimeChannel.AddMessageReceivedHandler`
- `RealtimeChannel.AddErrorHandler`
- `Push.AddMessageReceivedHandler`
- [Major, new] `ClientOptions.Logger` has been removed in favor of `Client.AddDebugHandler()` which allows for
implementing custom logging solutions if desired.
- A simple logger can be set up with the following:
```c#
client.AddDebugHandler((sender, message, exception) => Debug.WriteLine(message));
```
- [Major] `Connect()` has been marked `Obsolete` in favor of `ConnectAsync()`
- Custom reconnection logic has been removed in favor of using the built-in logic from `Websocket.Client@4.6.1`.
- Exceptions that are handled within this library have been marked as `RealtimeException`s.
- The local, docker-composed test suite has been brought back (as opposed to remotely testing on live supabase servers)
to test against.
- Comments have been added throughout the entire codebase and an `XML` file is now generated on build.
## 0.10.0 - 2023-05-14
- Changes options to require `Supabase.SupabaseOptions.SessionPersistor` from using `ISupabaseSessionHandler`
to `IGotrueSessionPersistance<Session>` (these are now synchronous operations).
- Update dependency: gotrue-csharp@4.0.1
- [#60](https://github.com/supabase-community/gotrue-csharp/pull/60) - Add interfaces, bug fixes, additional error
reason detection. Thanks [@wiverson](https://github.com/wiverson)!
- [#57](https://github.com/supabase-community/gotrue-csharp/pull/57) Refactor exceptions, code cleanup, and move to
delegate auth state changes
- Huge thank you to [@wiverson](https://github.com/wiverson) for his help on this refactor and release!
- Changes
- Exceptions have been simplified to a single `GotrueException`. A `Reason` field has been added
to `GotrueException` to clarify what happened. This should also be easier to manage as the Gotrue
server API & messages evolve.
- The session delegates for `Save`/`Load`/`Destroy` have been simplified to no longer require `async`.
- Console logging in a few places (most notable the background refresh thread) has been removed
in favor of a notification method. See `Client.AddDebugListener()` and the test cases for examples.
This will allow you to implement your own logging strategy (write to temp file, console, user visible
err console, etc).
- The client now more reliably emits AuthState changes.
- There is now a single source of truth for headers in the stateful Client - the `Options` headers.
- New feature:
- Added a `Settings` request to the stateless API only - you can now query the server instance to
determine if it's got the settings you need. This might allow for things like a visual
component in a tool to verify the GoTrue settings are working correctly, or tests that run differently
depending on the server configuration.
- Implementation notes:
- Test cases have been added to help ensure reliability of auth state change notifications
and persistence.
- Persistence is now managed via the same notifications as auth state change
## 0.9.1 - 2023-04-28
- Update dependency: gotrue-csharp@3.1.1
- Implements `SignInWithIdToken` for Apple/Google signing from LW7. A HUGE thank you
to [@wiverson](https://github.com/wiverson)!
- Update dependency: realtime-csharp@5.0.5
- Re: [#27](https://github.com/supabase-community/realtime-csharp/issues/27) `PostgresChangesOptions` was not
setting `listenType` in constructor. Thanks [@Kuffs2205](https://github.com/Kuffs2205)
- Update dependency: supabase-storage-csharp@1.2.10
- Re: [#7](https://github.com/supabase-community/storage-csharp/issues/7) Implements a `DownloadPublicFile` method.
## 0.9.0 - 2023-04-12
- Update dependency: gotrue-csharp@3.1.0
- [Minor] Implements PKCE auth flow. SignIn using a provider now returns an instance of `ProviderAuthState` rather
than a `string`.
- Update dependency: supabase-storage-csharp@1.2.9
- Implements storage features from LW7:
- feat: custom file size limit and mime types at bucket
level [supabase/storage-js#151](https://github.com/supabase/storage-js/pull/151) file size and mime type
limits per bucket
- feat: quality option, image
transformation [supabase/storage-js#145](https://github.com/supabase/storage-js/pull/152) quality option for
image transformations
- feat: format option for webp
support [supabase/storage-js#142](https://github.com/supabase/storage-js/pull/142) format option for image
transformation
## 0.8.8 - 2023-03-29
- Update dependency: gotrue-csharp@3.0.6
- Supports adding `SignInOptions` (i.e. `RedirectTo`) on `OAuth Provider` SignIn requests.
## 0.8.7 - 2023-03-23
- Update dependency: realtime-csharp@5.0.4
- Re: [#26](https://github.com/supabase-community/realtime-csharp/pull/26) - Fixes Connect() not returning callback
result when the socket isn't null. Thanks [@BlueWaterCrystal](https://github.com/BlueWaterCrystal)!
## 0.8.6 - 2023-03-23
- Update dependency: supabase-storage-csharp@1.2.8
- [Merge #5](https://github.com/supabase-community/storage-csharp/pull/5) Added search string as an optional search
parameter. Thanks [@ElectroKnight22](https://github.com/ElectroKnight22)!
## 0.8.5 - 2023-03-10
- Update dependency: realtime-csharp@5.0.3
- Re: [#25](https://github.com/supabase-community/realtime-csharp/issues/25) - Support Channel being resubscribed
after having been unsubscribed, fixes rejoin timer being erroneously called on channel `Unsubscribe`.
Thanks [@Kuffs2205](https://github.com/Kuffs2205)!
## 0.8.4 - 2023-03-03
- Update dependency: supabase-storage-csharp@1.2.7
- Re: [#4](https://github.com/supabase-community/storage-csharp/issues/4) Implementation for `ClientOptions` which
supports specifying Upload, Download, and Request timeouts.
- Update dependency: realtime-csharp@5.0.2
- Re: [#24](https://github.com/supabase-community/realtime-csharp/issues/24) - Fixes join failing until reconnect
happened + adds access token push on channel join. Big thank you to [@Honeyhead](https://github.com/honeyhead) for
the help debugging and identifying!
## 0.8.3 - 2023-02-26
- Update dependency: supabase-storage-csharp@1.2.5
- Provides fix
for [supabase-community/supabase-csharp#54](https://github.com/supabase-community/supabase-csharp/issues/54) -
Dynamic headers were always being overwritten by initialized token headers, so the storage client would not
receive user's access token as expected.
- Provides fix for upload progress not reporting
in [supabase-community/storage-csharp#3](https://github.com/supabase-community/storage-csharp/issues/3)
- Update dependency: gotrue-csharp@3.0.5
- Fixes [#44](https://github.com/supabase-community/gotrue-csharp/issues/44) - refresh timer should automatically
reattempt (interval of 5s) for HTTP exceptions - gracefully exits on invalid refresh and triggers
an `AuthState.Changed` event
## 0.8.2 - 2023-02-26
- Update dependency: supabase-storage-csharp@1.2.4
- `UploadOrUpdate` now appropriately throws request exceptions
## 0.8.1 - 2023-02-06
- Update dependency: realtime-csharp@5.0.1
- Re: [#22](https://github.com/supabase-community/realtime-csharp/issues/22) - `SerializerSettings` were not being
passed to `PostgresChangesResponse` - Thanks [@Shenrak](https://github.com/Shenrak) for the help debugging!
## 0.8.0 - 2023-01-31
- Update dependency: realtime-csharp@5.0.0
- Re: [#21](https://github.com/supabase-community/realtime-csharp/pull/21) Provide API for `presence`, `broadcast` and `postgres_changes`
- [Major, New] `Channel.PostgresChanges` event will receive the wildcard `*` changes event, not `Channel.OnMessage`.
- [Major] `Channel.OnInsert`, `Channel.OnUpdate`, and `Channel.OnDelete` now conform to the server's payload of `Response.Payload.**Data**`
- [Major] `Channel.OnInsert`, `Channel.OnUpdate`, and `Channel.OnDelete` now return `PostgresChangesEventArgs`
- [Minor] Rename `Channel` to `RealtimeChannel`
- Supports better handling of disconnects in `RealtimeSocket` and adds a `Client.OnReconnect` event.
- [Minor] Moves `ChannelOptions` to `Channel.ChannelOptions`
- [Minor] Moves `ChannelStateChangedEventArgs` to `Channel.ChannelStateChangedEventArgs`
- [Minor] Moves `Push` to `Channel.Push`
- [Minor] Moves `Channel.ChannelState` to `Constants.ChannelState`
- [Minor] Moves `SocketResponse`, `SocketRequest`, `SocketResponsePayload`, `SocketResponseEventArgs`, and `SocketStateChangedEventArgs` to `Socket` namespace.
- [New] Adds `RealtimeBroadcast`
- [New] Adds `RealtimePresence`
- [Improvement] Better handling of disconnection/reconnection
- Update dependency: postgrest-csharp@3.1.3
- Another fix for [#61](https://github.com/supabase-community/postgrest-csharp/issues/61) which further typechecks nullable values.
## 0.7.2 - 2023-01-27
- Update dependency: gotrue-csharp@3.0.4
- Makes `Session.CreatedAt` a publicly settable property, which should fix incorrect dates on retrieved `Session`s.
- Update dependency: postgrest-csharp@3.1.2
- Fix [#61](https://github.com/supabase-community/postgrest-csharp/issues/61) which did not correctly parse Linq `Where` when encountering a nullable type.
- Add missing support for transforming for `== null` and `!= null`
## 0.7.1 - 2023-01-17
- Update dependency: postgrest-csharp@3.1.1
- Fix issue from supabase-community/supabase-csharp#48 where boolean model properties would not be evaluated in predicate expressions
## 0.7.0 - 2023-01-16
- Update dependency: postgrest-csharp@3.1.0
- [Minor] Breaking API Change: `PrimaryKey` attribute defaults to `shouldInsert: false` as most uses will have the Database generate the primary key.
- Merged [#60](https://github.com/supabase-community/postgrest-csharp/pull/60) which Added linq support for `Select`, `Where`, `OnConflict`, `Columns`, `Order`, `Update`, `Set`, and `Delete`
## 0.6.2 - 2022-11-22
- Update dependency: postgrest-csharp@3.0.4
- `GetHeaders` is now passed to `ModeledResponse` and `BaseModel` so that the default `Update` and `Delete` methods use the latest credentials
- `GetHeaders` is used in `Rpc` calls (re: [#39](https://github.com/supabase-community/supabase-csharp/issues/39))
## 0.6.1 - 2022-11-12
- [Hotfix] `GetHeaders` was not passing properly to `SupabaseTable` and `Gotrue.Api`
## 0.6.0 - 2022-11-12
[BREAKING CHANGES]
- `Client` is no longer a singleton, singleton interactions (if desired) are left to the developer to implement.
- `Client` supports injection of dependent clients after initialization via property:
- `Auth`
- `Functions`
- `Realtime`
- `Postgrest`
- `Storage`
- `SupabaseModel` contains no logic but remains for backwards compatibility. (Marked `Obsolete`)
- `ClientOptions.ShouldInitializeRealtime` was removed (no longer auto initialized)
- `ClientOptions` now references an `ISupabaseSessionHandler` which specifies expected functionality for session persistence on Gotrue (replaces `ClientOptions.SessionPersistor`, `ClientOptions.SessionRetriever`, and `ClientOptions.SessionDestroyer`).
- `supabase-csharp` and all child libraries now have support `nullity`
Other Changes:
- Update dependency: functions-csharp@1.2.1
- Update dependency: gotrue-csharp@3.0.2
- Update dependency: postgrest-csharp@3.0.2
- Update dependency: realtime-csharp@4.0.1
- Update dependency: supabase-storage-csharp@1.2.3
- Update dependency: supabase-core@0.0.2
Big thank you to [@veleek](https://github.com/veleek) for his insight into these changes.
Re: [#35](https://github.com/supabase-community/supabase-csharp/issues/35), [#34](https://github.com/supabase-community/supabase-csharp/issues/34), [#23](https://github.com/supabase-community/supabase-csharp/issues/23), [#36](https://github.com/supabase-community/supabase-csharp/pull/36)
## 0.5.3 - 2022-10-11
- Update dependency: postgrest-csharp@2.1.0
- [Minor] Breaking API change: Remove `BaseModel.PrimaryKeyValue` and `BaseModel.PrimaryKeyColumn` in favor of a `PrimaryKey` dictionary with support for composite keys.
- Re: [#48](https://github.com/supabase-community/postgrest-csharp/issues/48) - Add support for derived models on `ReferenceAttribute`
- Re: [#49](https://github.com/supabase-community/postgrest-csharp/issues/49) - Added `Match(T model)`
## 0.5.2 - 2022-9-13
- Update dependency: postgrest-csharp@2.0.12
- Merged [#47](https://github.com/supabase-community/postgrest-csharp/pull/49) which added cancellation token support to `Table<T>` methods. Thanks [@devpikachu](https://github.com/devpikachu)!
## 0.5.1 - 2022-8-1
- Update dependency: postgrest-csharp@2.0.11
- Update dependency: supabase-storage-csharp@1.1.1
## 0.5.0 - 2022-7-17
- Update dependency: postgrest-csharp@2.0.9
- Update dependency: realtime-csharp@3.0.1
- Update dependency: supabase-storage-csharp@1.1.0
- API Change [Breaking/Minor] Library no longer uses `WebClient` and instead leverages `HttpClient`. Progress events on `Upload` and `Download` are now handled with `EventHandler<float>` instead of `WebClient` EventHandlers.
## 0.4.4 - 2022-5-24
- Update dependency: gotrue-csharp@2.4.5
- Update dependency: postgrest-csharp@2.0.8
## 0.4.3 - 2022-5-13
- Update dependency: gotrue-csharp@2.4.4
## 0.4.2 - 2022-4-30
- Update dependency: gotrue-csharp@2.4.3
## 0.4.1 - 2022-4-23
- Update dependency: gotrue-csharp@2.4.2
## 0.4.0 - 2022-4-12
- Add support for functions-csharp@1.0.1, giving access to invoking Supabase's edge functions.
- Update dependency: gotrue-csharp@2.4.1
## 0.3.5 - 2022-4-11
- Update dependency: postgres-csharp@2.0.7
## 0.3.4 - 2022-03-28
- Update dependency: gotrue-csharp@2.4.0
## 0.3.3 - 2022-02-27
- Update dependency: gotrue-csharp@2.3.6
- Update dependency: supabase-storage-csharp@1.0.2
## 0.3.2 - 2022-02-18
- Update dependency: realtime-csharp@3.0.0
- Exchange existing websocket client: [WebSocketSharp](https://github.com/sta/websocket-sharp) for [Marfusios/websocket-client](https://github.com/Marfusios/websocket-client) which adds support for Blazor WASM apps.
Ref: [#14](https://github.com/supabase-community/realtime-csharp/pull/14)
## 0.3.1 - 2022-01-20
- Update dependency: gotrue-csharp@2.3.5
- [#23](https://github.com/supabase-community/gotrue-csharp/pull/23) Added `redirect_url` option for MagicLink sign in (Thanks [@MisterJimson](https://github.com/MisterJimson))
- [#21](https://github.com/supabase-community/gotrue-csharp/pull/21) Added SignOut method to Stateless Client (Thanks [@fplaras](https://github.com/fplaras))
## 0.3.0 - 2021-12-30
- Update dependency: postgrest-csharp@2.0.6
- Add support for `NullValueHandling` to be specified on a `Column` Attribute and for it to be honored on Inserts and Updates. Defaults to: `NullValueHandling.Include`.
- Implements [#38](https://github.com/supabase-community/postgrest-csharp/issues/38)
- Update dependency: realtime-csharp@2.0.8
- Implement Upstream Realtime RLS Error Broadcast Handler
- Implements [#12](https://github.com/supabase-community/realtime-csharp/issues/12)
- `SocketResponse` now exposes a method: `OldModel`, that hydrates the `OldRecord` property into a model.
## 0.2.12 - 2021-12-29
- Update dependency: gotrue-csharp@2.3.3
- `SignUp` will return a `Session` with a _populated `User` object_ on an unconfirmed signup.
- Fixes [#19](https://github.com/supabase-community/gotrue-csharp/issues/19)
- Developers who were using a `null` check on `Session.User` will need to adjust accordingly.
- Update dependency: postgrest-csharp@2.0.5
- Fix for [#37](https://github.com/supabase-community/postgrest-csharp/issues/37) - Return Type `minimal` would fail to resolve because of incorrect `Accept` headers. Added header and test to verify for future.
- Fix for [#36](https://github.com/supabase-community/postgrest-csharp/issues/36) - Inserting/Upserting bulk records would fail while doing an unnecessary generic coercion.
## 0.2.11 - 2021-12-24
- Update dependency: gotrue-csharp@2.3.2 (changes CreateUser parameters to conform to `AdminUserAttributes`)
- See [#15](https://github.com/supabase-community/supabase-csharp/issues/15)
- See [#16](https://github.com/supabase-community/supabase-csharp/issues/16)
- Update dependency: realtime-csharp@2.0.7
- See [#13](https://github.com/supabase-community/supabase-csharp/issues/13)
## 0.2.10 - 2021-12-23
- Update dependency: gotrue-csharp@2.3.0 (adds metadata support for user signup, see [#14](https://github.com/supabase/community/issues/14))
## 0.2.9 - 2021-12-9
- Separate Storage client from Supabase repo and into `storage-csharp`, `supabase-csharp` now references new repo.
## 0.2.8 - 2021-12-4
- Update gotrue-csharp to 2.2.4
- Adds support for `ListUsers` (paginate, sort, filter), `GetUserById`, `CreateUser`, and `UpdateById`
## 0.2.7 - 2021-12-2
- Update gotrue-csharp to 2.2.3
- Adds support for sending password resets to users.
## 0.2.6 - 2021-11-29
- Support for [#12](https://github.com/supabase-community/supabase-csharp/issues/12)
- Update realtime-csharp to 2.0.6
- Update gotrue-csharp to 2.2.2
- Add `StatelessClient` re:[#7](https://github.com/supabase-community/supabase-csharp/issues/7)
## 1.0.0 - 2024-04-21
- Assembly Name has been changed to `Supabase.dll`
- Update dependency: `postgrest-csharp@5.0.0`
- [MAJOR] Moves namespaces from `Postgrest` to `Supabase.Postgrest`
- Re: [#135](https://github.com/supabase-community/supabase-csharp/issues/135) Update nuget package
name `postgrest-csharp` to `Supabase.Postgrest`
- Update dependency: `gotrue-csharp@5.0.0`
- Re: [#135](supabase-community/supabase-csharp#135) Update nuget package name `gotrue-csharp` to `Supabase.Gotrue`
- Re: [#89](https://github.com/supabase-community/gotrue-csharp/issues/89), Only add `access_token` to request body
when it is explicitly declared.
- [MINOR] Re: [#89](https://github.com/supabase-community/gotrue-csharp/issues/89) Update signature
for `SignInWithIdToken` which adds an optional `accessToken` parameter, update doc comments, and
call `DestroySession` in method
- Re: [#88](https://github.com/supabase-community/gotrue-csharp/issues/88), Add `IsAnonymous` property to `User`
- Re: [#90](https://github.com/supabase-community/gotrue-csharp/issues/90) Implement `LinkIdentity`
and `UnlinkIdentity`
- Update dependency: `realtime-csharp@7.0.0`
- Merges [#45](https://github.com/supabase-community/realtime-csharp/pull/45) - Updating
the `Websocket.Client@5.1.1`
- Re: [#135](https://github.com/supabase-community/supabase-csharp/issues/135) Update nuget package
name `realtime-csharp` to `Supabase.Realtime`
- Update dependency: `storage-csharp@2.0.0`
- Re: [#135](https://github.com/supabase-community/supabase-csharp/issues/135) Update nuget package
name `storage-csharp` to `Supabase.Storage`
- Update dependency: `functions-csharp@2.0.0`
- Re: [#135](https://github.com/supabase-community/supabase-csharp/issues/135) Update nuget package
name `functions-csharp` to `Supabase.Functions`
- Update dependency: `core-csharp@1.0.0`
- Re: [#135](https://github.com/supabase-community/supabase-csharp/issues/135) Update nuget package
name `supabase-core`
to `Supabase.Core`
- Adds comments to the remaining undocumented code.
@@ -0,0 +1,35 @@
---
id: installing
title: 'Installing & Initialization'
slug: installing
custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supabase.yml
---
### Install from NuGet
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
You can install Supabase package from [nuget.org](https://www.nuget.org/packages/supabase-csharp/)
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
size="small"
type="underlined"
defaultActiveId="csharp"
queryGroup="language"
>
<TabPanel id="csharp" label="Terminal">
```sh Terminal
dotnet add package supabase-csharp
```
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
@@ -0,0 +1,29 @@
---
id: introduction
title: Introduction
hideTitle: true
---
<div className="flex items-start gap-6 not-prose" id="introduction">
<IconMenuCsharp width={35} height={35} />
<div className="flex flex-col gap-2">
<h1 className="text-3xl text-foreground m-0">C# Client Library</h1>
<h2 className="text-base font-mono text-foreground-light">
@supabase-community/supabase-csharp
</h2>
</div>
</div>
{/* prettier-ignore */}
<div className="max-w-xl">
This reference documents every object and method available in Supabase's C# library, [supabase-csharp](https://www.nuget.org/packages/supabase-csharp). You can use `Supabase` to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
</div>
{/* prettier-ignore */}
<div className="max-w-xl bg-slate-300 px-4 py-2 rounded-md">
The C# client library is created and maintained by the Supabase community, and is not an official library. Please be tolerant of areas where the library is still being developed, and — as with all the libraries — feel free to contribute wherever you find issues.
Huge thanks to official maintainer, [Joseph Schultz](https://github.com/acupofjose). As well as [Will Iverson](https://github.com/wiverson), [Ben Randall](https://github.com/veleek), and [Rhuan Barros](https://github.com/rhuanbarros) for their help.
</div>
+562 -1
View File
@@ -3,4 +3,565 @@ id: release-notes
title: Release Notes
---
## dart v0 this is the release notes file.
## 0.16.2 - 2024-04-02
- Update dependency: `gotrue-csharp@4.2.7`
- [#88](https://github.com/supabase-community/gotrue-csharp/issues/88) Implement `signInAnonymously` from the JS
client
- Include additional 3rd party providers in constants.
## 0.16.1 - 2024-03-15
- Update dependency: `postgrest-csharp@3.5.1`
- Re: [#147](https://github.com/supabase-community/supabase-csharp/issues/147) - Supports `Rpc` specifying a generic
type for its return.
## 0.16.0 - 2024-03-12
- Update dependency: `postgrest-csharp@3.5.0`
- Re: [#78](https://github.com/supabase-community/postgrest-csharp/issues/78), Generalize query filtering creation
in `Table` so that it matches new generic signatures.
- Move from `QueryFilter` parameters to a more generic `IPosgrestQueryFilter` to support constructing new
QueryFilters from a LINQ expression.
- Note: Lists of `QueryFilter`s will now need to be defined
as: `new List<IPostgrestQueryFilter> { new QueryFilter(), ... }`
- Adjust serialization of timestamps within a `QueryFilter` to support `DateTime` and `DateTimeOffset` using the
ISO-8601 (https://stackoverflow.com/a/115002)
- Update dependency: `functions-csharp@1.3.2`
- Re: [#5](https://github.com/supabase-community/functions-csharp/issues/5) Add support for specifying Http Timeout
on a function call by adding `HttpTimeout` to `InvokeFunctionOptions`
## 0.15.0 - 2024-01-08
- Update Dependency: `gotrue-csharp@4.2.6`
- [#83](https://github.com/supabase-community/gotrue-csharp/pull/83) Replaces JWTDecoder package with
System.IdentityModel.Tokens.Jwt. Thanks [@FantasyTeddy](https://github.com/FantasyTeddy)!
- Update Dependency: `postgrest-csharp@3.4.1`
- Re: [#85](https://github.com/supabase-community/postgrest-csharp/issues/85) Fixes problem when using multiple
.Order()
methods by merging [#86](https://github.com/supabase-community/postgrest-csharp/pull/86).
Thanks [@hunsra](https://github.com/hunsra)!
- Re: [#81](https://github.com/supabase-community/postgrest-csharp/issues/81)
- [Minor] Removes `IgnoreOnInsert`and `IgnoreOnUpdate` from `ReferenceAttribute` as changing these properties
to `false` does not currently provide the expected functionality.
- Fixes `Insert` and `Update` not working on models that have `Reference` specified on a property with a
non-null
value.
## 0.14.0 - 2023-12-15
- Update Dependency: `gotrue-csharp@4.2.5`
- [#82](https://github.com/supabase-community/gotrue-csharp/issues/81) - Implements #82 - Creates a `GenerateLink`
method on the `AdminClient` that supports `signup`, `invite`, `magiclink`, `recovery`, `email_change_new`
and `email_change_current`
- [#81](https://github.com/supabase-community/gotrue-csharp/issues/81) - Adds `InviteUserByEmailOptions` as a
parameter to the Gotrue Admin Client
- Update Dependency: `postgrest-csharp@3.3.0`
- Re: [#78](https://github.com/supabase-community/postgrest-csharp/issues/78) Updates signatures for `Not`
and `Filter` to include generic types for a better development experience.
- Updates internal generic type names to be more descriptive.
- Add support for LINQ predicates on `Table<TModel>.Not()` signatures
## 0.13.7 - 2023-11-13
- Update Dependency: `postgrest-csharp@3.2.10`
- Re: [#76](https://github.com/supabase-community/postgrest-csharp/issues/76) Removes the
incorrect `ToUniversalTime` conversion in the LINQ `Where` parser.
## 0.13.6 - 2023-10-12
- Update Dependency: `gotrue-csharp@4.2.3`
- Re: [#80](https://github.com/supabase-community/gotrue-csharp/pull/80) Fixes `Session.Expires()` not being
calculated correctly. Thanks [@dayjay](https://github.com/Dayjay)!
## 0.13.5 - 2023-10-09
- Update Dependency: `postgrest-csharp@3.2.9`
- Re: [supabase-csharp#115](https://github.com/supabase-community/supabase-csharp/discussions/115) Additional
support for a model referencing another model with multiple foreign keys.
- Re: [supabase-csharp#115](https://github.com/supabase-community/supabase-csharp/discussions/115) Adds support for
multiple references attached to the same model (foreign keys) on a single C# Model.
## 0.13.4 - 2023-10-08
- Update Dependency: `gotrue-csharp@4.2.2`
- Re: [#78](https://github.com/supabase-community/gotrue-csharp/issues/78) - Implements PKCE flow support
for `ResetPasswordForEmail`.
## 0.13.3 - 2023-09-15
- Re: [#107](https://github.com/supabase-community/supabase-csharp/issues/107) - removes Realtime socket being
disconnected on a User sign-out - only the subscriptions should be removed.
## 0.13.2 - 2023-09-15
- Update dependency: `postgrest-csharp@3.2.7`
- Implements a `TableWithCache` for `Get` requests that can pull reactive Models from cache before making a remote
request.
- Re: [supabase-csharp#85](https://github.com/supabase-community/supabase-csharp/issues/85) Includes sourcelink
support.
- Re: [#75](https://github.com/supabase-community/postgrest-csharp/pull/75) Fix issue with marshalling of stored
procedure arguments. Big thank you to [@corrideat](https://github.com/corrideat)!
## 0.13.1 - 2023-08-26
- Update dependency: `supabase-storage-csharp@1.4.0`
- Fixes [#11](https://github.com/supabase-community/storage-csharp/issues/11) - Which implements
missing `SupabaseStorageException` on failure status codes for `Upload`, `Download`, `Move`, `CreateSignedUrl`
and `CreateSignedUrls`.
## 0.13.0 - 2023-08-26
- Update dependency: `gotrue-csharp@4.2.1`
- [#74](https://github.com/supabase-community/gotrue-csharp/pull/74) - Fixes bug where token refresh interval was
not honored by client. Thanks [@slater1](https://github.com/slater1)!
- **Minor Breaking changes:** [#72](https://github.com/supabase-community/gotrue-csharp/pull/72) - Fixes
Calling `SetAuth` does not actually set Authorization Headers for subsequent requests by implementing `SetSession`
- Removes `RefreshToken(string refreshToken)` and `SetAuth(string accessToken` in favor
of `SetSession(string accessToken, string refreshToken)`
- Makes `RefreshAccessToken` require `accessToken` and `refreshToken` as parameters - overrides the
authorization
headers to use the supplied token
- Migrates project internal times to use `DateTime.UtcNow` over `DateTime.Now`.
## 0.12.2 - 2023-07-28
- Update dependency: `realtime-csharp@6.0.4`
- Fixes [#29](https://github.com/supabase-community/realtime-csharp/issues/29) Where the Realtime client could
disconnect from channels after a few hours and fail to reconnect by removing the case where the `IsSubscribe` flag
is flipped when encountering a channel error.
- Update dependency: `postgrest-csharp@3.2.5`
- Re: [supabase-community/supabase-csharp#81](https://github.com/supabase-community/supabase-csharp/discussions/81):
Clarifies `ReferenceAttribute` by changing `shouldFilterTopLevel` to `useInnerJoin` and adds an additional
constructor for `ReferenceAttribute` with a shortcut for specifying the `JoinType`
## 0.12.1 - 2023-06-29
- Update dependency: `gotrue-csharp@4.1.1`
- [#68](https://github.com/supabase-community/gotrue-csharp/pull/68) Changes Network Status to use the interface
instead of client
- Update dependency: `postgrest-csharp@3.2.4`
- [#70](https://github.com/supabase-community/postgrest-csharp/pull/70) Minor Unity related fixes
## 0.12.0 - 2023-06-25
- Update dependency: `gotrue-csharp@4.1.0`
- **Minor** [#66](https://github.com/supabase-community/gotrue-csharp/pull/66) - Separates out Admin JWT
functionality into a separate `AdminClient`
- [#67](https://github.com/supabase-community/gotrue-csharp/pull/67) - Adds shutdown method which terminates the
background refresh threads.
- Movement of much of the documentation for methods out of their classes and into their interfaces.
- Language features locked to C#9
- Update dependency: `postgrest-csharp@3.2.3`
- [#69](https://github.com/supabase-community/postgrest-csharp/pull/69) Locks language version to C#9
- [#68](https://github.com/supabase-community/postgrest-csharp/pull/68) Makes RPC parameters optional
Thanks [@wiverson](https://github.com/wiverson) for the work in this release!
## 0.11.1 - 2023-06-10
- Update dependencies: `functions-csharp@1.3.1`, `gotrue-csharp@4.0.4`, `postgrest-csharp@3.2.2`,
`realtime-csharp@6.0.3`, `supabase-storage-csharp@1.3.2`, `supabase-core@0.0.3`
- Namespaces assembly names to make them unique among other dependencies, i.e: `Core.dll`
becomes `Supabase.Core.dll` which will hopefully prevent future collisions.
## 0.11.0 - 2023-05-24
- Update dependency: postgrest-csharp@3.2.0
- General codebase and QOL improvements. Exceptions are generally thrown through `PostgrestException` now instead
of `Exception`. A `FailureHint.Reason` is provided with failures if possible to parse.
- `AddDebugListener` is now available on the client to help with debugging
- Merges [#65](https://github.com/supabase-community/postgrest-csharp/pull/65) Cleanup + Add better exception
handling
- Merges [#66](https://github.com/supabase-community/postgrest-csharp/pull/66) Local test Fixes
- Fixes [#67](https://github.com/supabase-community/postgrest-csharp/issues/67) Postgrest Reference attribute is
producing StackOverflow for circular references
- Update dependency: gotrue-csharp@4.0.2
- [#58](https://github.com/supabase-community/gotrue-csharp/issues/58) - Add support for the `reauthentication`
endpoint which allows for secure password changes.
- Update dependency: realtime-csharp@6.0.1
- Updates publishing action for future packages, includes README and icon.
- Merges [#28](https://github.com/supabase-community/realtime-csharp/pull/28)
and [#30](https://github.com/supabase-community/realtime-csharp/pull/30)
- The realtime client now takes a "fail-fast" approach. On establishing an initial connection, client will throw
a `RealtimeException` in `ConnectAsync()` if the socket server is unreachable. After an initial connection has
been
established, the **client will continue attempting reconnections indefinitely until disconnected.**
- [Major, New] C# `EventHandlers` have been changed to `delegates`. This should allow for cleaner event data access
over
the previous subclassed `EventArgs` setup. Events are scoped accordingly. For example, the `RealtimeSocket` error
handlers will receive events regarding socket connectivity; whereas the `RealtimeChannel` error handlers will
receive
events according to `Channel` joining/leaving/etc. This is implemented with the following methods prefixed by (
Add/Remove/Clear):
- `RealtimeBroadcast.AddBroadcastEventHandler`
- `RealtimePresence.AddPresenceEventHandler`
- `RealtimeSocket.AddStateChangedHandler`
- `RealtimeSocket.AddMessageReceivedHandler`
- `RealtimeSocket.AddHeartbeatHandler`
- `RealtimeSocket.AddErrorHandler`
- `RealtimeClient.AddDebugHandler`
- `RealtimeClient.AddStateChangedHandler`
- `RealtimeChannel.AddPostgresChangeHandler`
- `RealtimeChannel.AddMessageReceivedHandler`
- `RealtimeChannel.AddErrorHandler`
- `Push.AddMessageReceivedHandler`
- [Major, new] `ClientOptions.Logger` has been removed in favor of `Client.AddDebugHandler()` which allows for
implementing custom logging solutions if desired.
- A simple logger can be set up with the following:
```c#
client.AddDebugHandler((sender, message, exception) => Debug.WriteLine(message));
```
- [Major] `Connect()` has been marked `Obsolete` in favor of `ConnectAsync()`
- Custom reconnection logic has been removed in favor of using the built-in logic from `Websocket.Client@4.6.1`.
- Exceptions that are handled within this library have been marked as `RealtimeException`s.
- The local, docker-composed test suite has been brought back (as opposed to remotely testing on live supabase
servers)
to test against.
- Comments have been added throughout the entire codebase and an `XML` file is now generated on build.
## 0.10.0 - 2023-05-14
- Changes options to require `Supabase.SupabaseOptions.SessionPersistor` from using `ISupabaseSessionHandler`
to `IGotrueSessionPersistance<Session>` (these are now synchronous operations).
- Update dependency: gotrue-csharp@4.0.1
- [#60](https://github.com/supabase-community/gotrue-csharp/pull/60) - Add interfaces, bug fixes, additional error
reason detection. Thanks [@wiverson](https://github.com/wiverson)!
- [#57](https://github.com/supabase-community/gotrue-csharp/pull/57) Refactor exceptions, code cleanup, and move to
delegate auth state changes
- Huge thank you to [@wiverson](https://github.com/wiverson) for his help on this refactor and release!
- Changes
- Exceptions have been simplified to a single `GotrueException`. A `Reason` field has been added
to `GotrueException` to clarify what happened. This should also be easier to manage as the Gotrue
server API & messages evolve.
- The session delegates for `Save`/`Load`/`Destroy` have been simplified to no longer require `async`.
- Console logging in a few places (most notable the background refresh thread) has been removed
in favor of a notification method. See `Client.AddDebugListener()` and the test cases for examples.
This will allow you to implement your own logging strategy (write to temp file, console, user visible
err console, etc).
- The client now more reliably emits AuthState changes.
- There is now a single source of truth for headers in the stateful Client - the `Options` headers.
- New feature:
- Added a `Settings` request to the stateless API only - you can now query the server instance to
determine if it's got the settings you need. This might allow for things like a visual
component in a tool to verify the GoTrue settings are working correctly, or tests that run differently
depending on the server configuration.
- Implementation notes:
- Test cases have been added to help ensure reliability of auth state change notifications
and persistence.
- Persistence is now managed via the same notifications as auth state change
## 0.9.1 - 2023-04-28
- Update dependency: gotrue-csharp@3.1.1
- Implements `SignInWithIdToken` for Apple/Google signing from LW7. A HUGE thank you
to [@wiverson](https://github.com/wiverson)!
- Update dependency: realtime-csharp@5.0.5
- Re: [#27](https://github.com/supabase-community/realtime-csharp/issues/27) `PostgresChangesOptions` was not
setting `listenType` in constructor. Thanks [@Kuffs2205](https://github.com/Kuffs2205)
- Update dependency: supabase-storage-csharp@1.2.10
- Re: [#7](https://github.com/supabase-community/storage-csharp/issues/7) Implements a `DownloadPublicFile` method.
## 0.9.0 - 2023-04-12
- Update dependency: gotrue-csharp@3.1.0
- [Minor] Implements PKCE auth flow. SignIn using a provider now returns an instance of `ProviderAuthState` rather
than a `string`.
- Update dependency: supabase-storage-csharp@1.2.9
- Implements storage features from LW7:
- feat: custom file size limit and mime types at bucket
level [supabase/storage-js#151](https://github.com/supabase/storage-js/pull/151) file size and mime type
limits per bucket
- feat: quality option, image
transformation [supabase/storage-js#145](https://github.com/supabase/storage-js/pull/152) quality option for
image transformations
- feat: format option for webp
support [supabase/storage-js#142](https://github.com/supabase/storage-js/pull/142) format option for image
transformation
## 0.8.8 - 2023-03-29
- Update dependency: gotrue-csharp@3.0.6
- Supports adding `SignInOptions` (i.e. `RedirectTo`) on `OAuth Provider` SignIn requests.
## 0.8.7 - 2023-03-23
- Update dependency: realtime-csharp@5.0.4
- Re: [#26](https://github.com/supabase-community/realtime-csharp/pull/26) - Fixes Connect() not returning callback
result when the socket isn't null. Thanks [@BlueWaterCrystal](https://github.com/BlueWaterCrystal)!
## 0.8.6 - 2023-03-23
- Update dependency: supabase-storage-csharp@1.2.8
- [Merge #5](https://github.com/supabase-community/storage-csharp/pull/5) Added search string as an optional search
parameter. Thanks [@ElectroKnight22](https://github.com/ElectroKnight22)!
## 0.8.5 - 2023-03-10
- Update dependency: realtime-csharp@5.0.3
- Re: [#25](https://github.com/supabase-community/realtime-csharp/issues/25) - Support Channel being resubscribed
after having been unsubscribed, fixes rejoin timer being erroneously called on channel `Unsubscribe`.
Thanks [@Kuffs2205](https://github.com/Kuffs2205)!
## 0.8.4 - 2023-03-03
- Update dependency: supabase-storage-csharp@1.2.7
- Re: [#4](https://github.com/supabase-community/storage-csharp/issues/4) Implementation for `ClientOptions` which
supports specifying Upload, Download, and Request timeouts.
- Update dependency: realtime-csharp@5.0.2
- Re: [#24](https://github.com/supabase-community/realtime-csharp/issues/24) - Fixes join failing until reconnect
happened + adds access token push on channel join. Big thank you to [@Honeyhead](https://github.com/honeyhead) for
the help debugging and identifying!
## 0.8.3 - 2023-02-26
- Update dependency: supabase-storage-csharp@1.2.5
- Provides fix
for [supabase-community/supabase-csharp#54](https://github.com/supabase-community/supabase-csharp/issues/54) -
Dynamic headers were always being overwritten by initialized token headers, so the storage client would not
receive user's access token as expected.
- Provides fix for upload progress not reporting
in [supabase-community/storage-csharp#3](https://github.com/supabase-community/storage-csharp/issues/3)
- Update dependency: gotrue-csharp@3.0.5
- Fixes [#44](https://github.com/supabase-community/gotrue-csharp/issues/44) - refresh timer should automatically
reattempt (interval of 5s) for HTTP exceptions - gracefully exits on invalid refresh and triggers
an `AuthState.Changed` event
## 0.8.2 - 2023-02-26
- Update dependency: supabase-storage-csharp@1.2.4
- `UploadOrUpdate` now appropriately throws request exceptions
## 0.8.1 - 2023-02-06
- Update dependency: realtime-csharp@5.0.1
- Re: [#22](https://github.com/supabase-community/realtime-csharp/issues/22) - `SerializerSettings` were not being
passed to `PostgresChangesResponse` - Thanks [@Shenrak](https://github.com/Shenrak) for the help debugging!
## 0.8.0 - 2023-01-31
- Update dependency: realtime-csharp@5.0.0
- Re: [#21](https://github.com/supabase-community/realtime-csharp/pull/21) Provide API for `presence`, `broadcast`
and `postgres_changes`
- [Major, New] `Channel.PostgresChanges` event will receive the wildcard `*` changes event,
not `Channel.OnMessage`.
- [Major] `Channel.OnInsert`, `Channel.OnUpdate`, and `Channel.OnDelete` now conform to the server's payload
of `Response.Payload.**Data**`
- [Major] `Channel.OnInsert`, `Channel.OnUpdate`, and `Channel.OnDelete` now return `PostgresChangesEventArgs`
- [Minor] Rename `Channel` to `RealtimeChannel`
- Supports better handling of disconnects in `RealtimeSocket` and adds a `Client.OnReconnect` event.
- [Minor] Moves `ChannelOptions` to `Channel.ChannelOptions`
- [Minor] Moves `ChannelStateChangedEventArgs` to `Channel.ChannelStateChangedEventArgs`
- [Minor] Moves `Push` to `Channel.Push`
- [Minor] Moves `Channel.ChannelState` to `Constants.ChannelState`
- [Minor] Moves `SocketResponse`, `SocketRequest`, `SocketResponsePayload`, `SocketResponseEventArgs`,
and `SocketStateChangedEventArgs` to `Socket` namespace.
- [New] Adds `RealtimeBroadcast`
- [New] Adds `RealtimePresence`
- [Improvement] Better handling of disconnection/reconnection
- Update dependency: postgrest-csharp@3.1.3
- Another fix for [#61](https://github.com/supabase-community/postgrest-csharp/issues/61) which futher typechecks
nullable values.
## 0.7.2 - 2023-01-27
- Update dependency: gotrue-csharp@3.0.4
- Makes `Session.CreatedAt` a publicly settable property, which should fix incorrect dates on retrieved `Session`s.
- Update dependency: postgrest-csharp@3.1.2
- Fix [#61](https://github.com/supabase-community/postgrest-csharp/issues/61) which did not correctly parse
Linq `Where` when encountering a nullable type.
- Add missing support for transforming for `== null` and `!= null`
## 0.7.1 - 2023-01-17
- Update dependency: postgrest-csharp@3.1.1
- Fix issue from supabase-community/supabase-csharp#48 where boolean model properties would not be evaluated in
predicate expressions
## 0.7.0 - 2023-01-16
- Update dependency: postgrest-csharp@3.1.0
- [Minor] Breaking API Change: `PrimaryKey` attribute defaults to `shouldInsert: false` as most uses will have the
Database generate the primary key.
- Merged [#60](https://github.com/supabase-community/postgrest-csharp/pull/60) which Added linq support
for `Select`, `Where`, `OnConflict`, `Columns`, `Order`, `Update`, `Set`, and `Delete`
## 0.6.2 - 2022-11-22
- Update dependency: postgrest-csharp@3.0.4
- `GetHeaders` is now passed to `ModeledResponse` and `BaseModel` so that the default `Update` and `Delete` methods
use the latest credentials
- `GetHeaders` is used in `Rpc` calls (re: [#39](https://github.com/supabase-community/supabase-csharp/issues/39))
## 0.6.1 - 2022-11-12
- [Hotfix] `GetHeaders` was not passing properly to `SupabaseTable` and `Gotrue.Api`
## 0.6.0 - 2022-11-12
[BREAKING CHANGES]
- `Client` is no longer a singleton, singleton interactions (if desired) are left to the developer to implement.
- `Client` supports injection of dependent clients after initialization via property:
- `Auth`
- `Functions`
- `Realtime`
- `Postgrest`
- `Storage`
- `SupabaseModel` contains no logic but remains for backwards compatibility. (Marked `Obsolete`)
- `ClientOptions.ShouldInitializeRealtime` was removed (no longer auto initialized)
- `ClientOptions` now references an `ISupabaseSessionHandler` which specifies expected functionality for session
persistence on Gotrue (replaces `ClientOptions.SessionPersistor`, `ClientOptions.SessionRetriever`,
and `ClientOptions.SessionDestroyer`).
- `supabase-csharp` and all child libraries now have support `nullity`
Other Changes:
- Update dependency: functions-csharp@1.2.1
- Update dependency: gotrue-csharp@3.0.2
- Update dependency: postgrest-csharp@3.0.2
- Update dependency: realtime-csharp@4.0.1
- Update dependency: supabase-storage-csharp@1.2.3
- Update dependency: supabase-core@0.0.2
Big thank you to [@veleek](https://github.com/veleek) for his insight into these changes.
Re: [#35](https://github.com/supabase-community/supabase-csharp/issues/35), [#34](https://github.com/supabase-community/supabase-csharp/issues/34), [#23](https://github.com/supabase-community/supabase-csharp/issues/23), [#36](https://github.com/supabase-community/supabase-csharp/pull/36)
## 0.5.3 - 2022-10-11
- Update dependency: postgrest-csharp@2.1.0
- [Minor] Breaking API change: Remove `BaseModel.PrimaryKeyValue` and `BaseModel.PrimaryKeyColumn` in favor of
a `PrimaryKey` dictionary with support for composite keys.
- Re: [#48](https://github.com/supabase-community/postgrest-csharp/issues/48) - Add support for derived models
on `ReferenceAttribute`
- Re: [#49](https://github.com/supabase-community/postgrest-csharp/issues/49) - Added `Match(T model)`
## 0.5.2 - 2022-9-13
- Update dependency: postgrest-csharp@2.0.12
- Merged [#47](https://github.com/supabase-community/postgrest-csharp/pull/49) which added cancellation token
support to `Table<T>` methods. Thanks [@devpikachu](https://github.com/devpikachu)!
## 0.5.1 - 2022-8-1
- Update dependency: postgrest-csharp@2.0.11
- Update dependency: supabase-storage-csharp@1.1.1
## 0.5.0 - 2022-7-17
- Update dependency: postgrest-csharp@2.0.9
- Update dependency: realtime-csharp@3.0.1
- Update dependency: supabase-storage-csharp@1.1.0
- API Change [Breaking/Minor] Library no longer uses `WebClient` and instead leverages `HttpClient`. Progress events
on `Upload` and `Download` are now handled with `EventHandler<float>` instead of `WebClient` EventHandlers.
## 0.4.4 - 2022-5-24
- Update dependency: gotrue-csharp@2.4.5
- Update dependency: postgrest-csharp@2.0.8
## 0.4.3 - 2022-5-13
- Update dependency: gotrue-csharp@2.4.4
## 0.4.2 - 2022-4-30
- Update dependency: gotrue-csharp@2.4.3
## 0.4.1 - 2022-4-23
- Update dependency: gotrue-csharp@2.4.2
## 0.4.0 - 2022-4-12
- Add support for functions-csharp@1.0.1, giving access to invoking Supabase's edge functions.
- Update dependency: gotrue-csharp@2.4.1
## 0.3.5 - 2022-4-11
- Update dependency: postgres-csharp@2.0.7
## 0.3.4 - 2022-03-28
- Update dependency: gotrue-csharp@2.4.0
## 0.3.3 - 2022-02-27
- Update dependency: gotrue-csharp@2.3.6
- Update dependency: supabase-storage-csharp@1.0.2
## 0.3.2 - 2022-02-18
- Update dependency: realtime-csharp@3.0.0
- Exchange existing websocket client: [WebSocketSharp](https://github.com/sta/websocket-sharp)
for [Marfusios/websocket-client](https://github.com/Marfusios/websocket-client) which adds support for Blazor WASM
apps.
Ref: [#14](https://github.com/supabase-community/realtime-csharp/pull/14)
## 0.3.1 - 2022-01-20
- Update dependency: gotrue-csharp@2.3.5
- [#23](https://github.com/supabase-community/gotrue-csharp/pull/23) Added `redirect_url` option for MagicLink sign
in (Thanks [@MisterJimson](https://github.com/MisterJimson))
- [#21](https://github.com/supabase-community/gotrue-csharp/pull/21) Added SignOut method to Stateless Client (
Thanks [@fplaras](https://github.com/fplaras))
## 0.3.0 - 2021-12-30
- Update dependency: postgrest-csharp@2.0.6
- Add support for `NullValueHandling` to be specified on a `Column` Attribute and for it to be honored on Inserts
and Updates. Defaults to: `NullValueHandling.Include`.
- Implements [#38](https://github.com/supabase-community/postgrest-csharp/issues/38)
- Update dependency: realtime-csharp@2.0.8
- Implement Upstream Realtime RLS Error Broadcast Handler
- Implements [#12](https://github.com/supabase-community/realtime-csharp/issues/12)
- `SocketResponse` now exposes a method: `OldModel`, that hydrates the `OldRecord` property into a model.
## 0.2.12 - 2021-12-29
- Update dependency: gotrue-csharp@2.3.3
- `SignUp` will return a `Session` with a _populated `User` object_ on an unconfirmed signup.
- Fixes [#19](https://github.com/supabase-community/gotrue-csharp/issues/19)
- Developers who were using a `null` check on `Session.User` will need to adjust accordingly.
- Update dependency: postgrest-csharp@2.0.5
- Fix for [#37](https://github.com/supabase-community/postgrest-csharp/issues/37) - Return Type `minimal` would fail
to resolve because of incorrect `Accept` headers. Added header and test to verify for future.
- Fix for [#36](https://github.com/supabase-community/postgrest-csharp/issues/36) - Inserting/Upserting bulk records
would fail while doing an unnecessary generic coercion.
## 0.2.11 - 2021-12-24
- Update dependency: gotrue-csharp@2.3.2 (changes CreateUser parameters to conform to `AdminUserAttributes`)
- See [#15](https://github.com/supabase-community/supabase-csharp/issues/15)
- See [#16](https://github.com/supabase-community/supabase-csharp/issues/16)
- Update dependency: realtime-csharp@2.0.7
- See [#13](https://github.com/supabase-community/supabase-csharp/issues/13)
## 0.2.10 - 2021-12-23
- Update dependency: gotrue-csharp@2.3.0 (adds metadata support for user signup,
see [#14](https://github.com/supabase/community/issues/14))
## 0.2.9 - 2021-12-9
- Separate Storage client from Supabase repo and into `storage-csharp`, `supabase-csharp` now references new repo.
## 0.2.8 - 2021-12-4
- Update gotrue-csharp to 2.2.4
- Adds support for `ListUsers` (paginate, sort, filter), `GetUserById`, `CreateUser`, and `UpdateById`
## 0.2.7 - 2021-12-2
- Update gotrue-csharp to 2.2.3
- Adds support for sending password resets to users.
## 0.2.6 - 2021-11-29
- Support for [#12](https://github.com/supabase-community/supabase-csharp/issues/12)
- Update realtime-csharp to 2.0.6
- Update gotrue-csharp to 2.2.2
- Add `StatelessClient` re:[#7](https://github.com/supabase-community/supabase-csharp/issues/7)
@@ -36,6 +36,13 @@ custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supab
```
</TabPanel>
<TabPanel id="pnpm" label="pnpm">
```sh Terminal
pnpm add @supabase/supabase-js
```
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
+4
View File
@@ -96,6 +96,10 @@ const levelsData = {
icon: 'reference-csharp',
name: 'C# Reference v0.0',
},
reference_csharp_v1: {
icon: 'reference-csharp',
name: 'C# Reference v1.0',
},
reference_python_v2: {
icon: 'reference-python',
name: 'Python Reference v2.0',
+9 -1
View File
@@ -8,15 +8,23 @@ type SendFeedbackVariables = {
message: string
isHelpful: boolean
pathname?: string
team?: string
}
export async function sendFeedback({ message, pathname, title, isHelpful }: SendFeedbackVariables) {
export async function sendFeedback({
message,
pathname,
title,
isHelpful,
team,
}: SendFeedbackVariables) {
const { data, error } = await post('/platform/feedback/docs', {
body: {
page: pathname,
isHelpful,
title,
feedback: message,
team,
},
})
if (error) throw Error(`Couldn't send feedback`, { cause: error })
+1
View File
@@ -68,6 +68,7 @@ const nextConfig = {
'mdx-mermaid',
'dayjs',
'shared-data',
'api-types',
'icons',
],
async headers() {
+2 -3
View File
@@ -7,7 +7,6 @@
"dev:secrets:pull": "AWS_PROFILE=supabase-dev node ../../scripts/getSecrets.js -n local/docs",
"build": "next build",
"build:analyze": "ANALYZE=true next build",
"codegen": "openapi-typescript http://localhost:8080/api-json -o types/api.d.ts && prettier --write types/api.d.ts",
"start": "next start",
"lint": "next lint",
"typecheck": "tsc --noEmit",
@@ -96,9 +95,9 @@
"jest-environment-jsdom": "^29.7.0",
"npm-run-all": "^4.1.5",
"openapi-types": "^12.1.3",
"openapi-typescript": "^6.7.4",
"tsconfig": "*",
"tsx": "^4.6.2",
"typescript": "^5.4.3"
"typescript": "^5.4.3",
"api-types": "*"
}
}
@@ -1,5 +1,5 @@
import clientLibsCommonSections from '~/spec/common-client-libs-sections.json'
import spec from '~/spec/supabase_csharp_v0.yml' assert { type: 'yml' }
import spec from '~/spec/supabase_csharp_v1.yml' assert { type: 'yml' }
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
@@ -12,7 +12,7 @@ const libraryPath = '/csharp'
export default function CSharpReference(props) {
return (
<RefSectionHandler
menuId={MenuId.RefCSharpV0}
menuId={MenuId.RefCSharpV1}
sections={sections}
spec={spec}
pageProps={props}
@@ -1,6 +1,6 @@
import clientLibsCommonSections from '~/spec/common-client-libs-sections.json'
import typeSpec from '~/spec/enrichments/tsdoc_v2/combined.json'
import spec from '~/spec/supabase_csharp_v0.yml' assert { type: 'yml' }
import spec from '~/spec/supabase_csharp_v1.yml' assert { type: 'yml' }
import RefSectionHandler from '~/components/reference/RefSectionHandler'
import { flattenSections } from '~/lib/helpers'
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
@@ -8,6 +8,7 @@ import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
import { useRouter } from 'next/router'
import RefSEO from '~/components/reference/RefSEO'
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
import type { TypeSpec } from '~/components/reference/Reference.types'
const sections = flattenSections(clientLibsCommonSections)
const libraryPath = '/csharp'
@@ -26,10 +27,10 @@ export default function CSharpReference(props) {
<RefSEO title={pageTitle} />
<RefSectionHandler
menuId={MenuId.RefCSharpV0}
menuId={MenuId.RefCSharpV1}
sections={filteredSection}
spec={spec}
typeSpec={typeSpec}
typeSpec={typeSpec as TypeSpec}
pageProps={props}
type="client-lib"
/>
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,96 @@
import { readdir } from 'node:fs/promises'
import { extname, join } from 'node:path'
import {
HOMEPAGE_MENU_ITEMS,
gettingstarted,
cli,
auth,
database,
api,
graphql,
functions,
realtime,
storage,
ai,
supabase_cli,
platform,
resources,
self_hosting,
migrate,
} from '../../components/Navigation/NavigationMenu/NavigationMenu.constants'
const DOCS_ROOT_DIR = join(__dirname, '..', '..')
const DEFAULT_DOCS_CONTENT_DIR = join(DOCS_ROOT_DIR, 'content')
const DOCS_CONTENT_DIR = process.env.DOCS_CONTENT_DIR || DEFAULT_DOCS_CONTENT_DIR
const IGNORE_LIST = [
/**
* This is linked from the Edge Functions examples page though not from the
* main nav.
*/
'guides/functions/examples/github-actions',
/**
* Auth helpers are deprecated and hidden but still available for legacy
* users if they need it.
*/
'guides/auth/auth-helpers',
'guides/auth/auth-helpers/nextjs-pages',
'guides/auth/auth-helpers/nextjs',
'guides/auth/auth-helpers/remix',
'guides/auth/auth-helpers/sveltekit',
]
type RefItem = {
href?: string
url?: string
items?: RefItem[]
}
const recGetUrl = (items: readonly RefItem[], acc: string[] = []) =>
items.reduce((acc, item) => {
if (item.href || item.url) acc.push(item.href || item.url)
if (item.items) acc.push(...recGetUrl(item.items, acc))
return acc
}, acc)
const main = async () => {
try {
const savedFiles = (await readdir(DOCS_CONTENT_DIR, { recursive: true }))
.filter((file) => extname(file) === '.mdx')
.map((file) => file.replace(/\.mdx$/, ''))
const flattenedHomepageMenuItems = HOMEPAGE_MENU_ITEMS.flat() as RefItem[]
const pagesToPublish = [
flattenedHomepageMenuItems,
gettingstarted.items,
cli.items,
auth.items,
database.items,
api.items,
graphql.items,
functions.items,
realtime.items,
storage.items,
ai.items,
supabase_cli.items,
platform.items,
resources.items,
self_hosting.items,
migrate.items,
]
.flatMap((items) => recGetUrl(items))
// Remove initial slash
.map((path) => path.substring(1))
const extraPages = savedFiles.filter(
(file) => !pagesToPublish.includes(file) && !IGNORE_LIST.includes(file)
)
console.log(extraPages)
} catch (err) {
console.error(err)
process.exit(1)
}
}
main()
+28 -4
View File
@@ -1,7 +1,7 @@
clispec: '001'
info:
id: cli
version: 1.157.2
version: 1.163.2
title: Supabase CLI
language: sh
source: https://github.com/supabase/cli
@@ -3303,7 +3303,7 @@ commands:
description: Update a preview branch by its ID.
tags: []
links: []
usage: supabase branches update <branch-id> [flags]
usage: supabase branches update [branch-id] [flags]
subcommands: []
flags:
- id: git-branch
@@ -3323,6 +3323,10 @@ commands:
description: enable experimental features
required: true
default_value: 'false'
- id: project-ref
name: --project-ref <string>
description: Project ref of the Supabase project.
default_value: ''
- id: supabase-branches-list
title: supabase branches list
summary: List all preview branches
@@ -3337,13 +3341,17 @@ commands:
description: enable experimental features
required: true
default_value: 'false'
- id: project-ref
name: --project-ref <string>
description: Project ref of the Supabase project.
default_value: ''
- id: supabase-branches-get
title: supabase branches get
summary: Retrieve details of a preview branch
description: Retrieve details of the specified preview branch.
tags: []
links: []
usage: supabase branches get <branch-id>
usage: supabase branches get [branch-id]
subcommands: []
flags:
- id: experimental
@@ -3351,6 +3359,10 @@ commands:
description: enable experimental features
required: true
default_value: 'false'
- id: project-ref
name: --project-ref <string>
description: Project ref of the Supabase project.
default_value: ''
- id: supabase-branches-disable
title: supabase branches disable
summary: Disable preview branching
@@ -3365,13 +3377,17 @@ commands:
description: enable experimental features
required: true
default_value: 'false'
- id: project-ref
name: --project-ref <string>
description: Project ref of the Supabase project.
default_value: ''
- id: supabase-branches-delete
title: supabase branches delete
summary: Delete a preview branch
description: Delete a preview branch by its ID.
tags: []
links: []
usage: supabase branches delete <branch-id>
usage: supabase branches delete [branch-id]
subcommands: []
flags:
- id: experimental
@@ -3379,6 +3395,10 @@ commands:
description: enable experimental features
required: true
default_value: 'false'
- id: project-ref
name: --project-ref <string>
description: Project ref of the Supabase project.
default_value: ''
- id: supabase-branches-create
title: supabase branches create
summary: Create a preview branch
@@ -3494,6 +3514,10 @@ commands:
description: enable experimental features
required: true
default_value: 'false'
- id: project-ref
name: --project-ref <string>
description: Project ref of the Supabase project.
default_value: ''
- id: supabase-bootstrap
title: supabase bootstrap
summary: Bootstrap a Supabase project from a starter template
@@ -32,6 +32,7 @@
"reference_dart_v2",
"reference_python_v2",
"reference_csharp_v0",
"reference_csharp_v1",
"reference_swift_v1",
"reference_swift_v2",
"reference_kotlin_v1",
@@ -48,6 +49,7 @@
"reference_dart_v1",
"reference_python_v2",
"reference_csharp_v0",
"reference_csharp_v1",
"reference_swift_v1",
"reference_swift_v2",
"reference_kotlin_v1",
@@ -986,7 +988,6 @@
"reference_dart_v1",
"reference_dart_v2",
"reference_python_v2",
"reference_csharp_v0",
"reference_swift_v1",
"reference_swift_v2",
"reference_kotlin_v1",
+1 -1
View File
@@ -22,7 +22,7 @@ functions:
Initializing a new client is pretty straightforward. Find your project url and public key from the
admin panel and pass it into your client initialization function.
`supabase-csharp` is heavily dependent on Models deriving from `BaseModel`. To interact with the API, one must have the associated model (see example) specified.
`Supabase` is heavily dependent on Models deriving from `BaseModel`. To interact with the API, one must have the associated model (see example) specified.
Leverage `Table`, `PrimaryKey`, and `Column` attributes to specify names of classes/properties that are different from their C# Versions.
File diff suppressed because it is too large. Load diff
+317
View File
@@ -2954,6 +2954,95 @@ functions:
By default, the data is returned in JSON format, but can also be returned as Comma Separated Values.
hideCodeBlock: true
isSpotlight: true
- id: explain
title: Using Explain
description: |
For debugging slow queries, you can get the [Postgres `EXPLAIN` execution plan](https://www.postgresql.org/docs/current/sql-explain.html) of a query
using the `explain()` method. This works on any query, even for `rpc()` or writes.
Explain is not enabled by default as it can reveal sensitive information about your database.
It's best to only enable this for testing environments but if you wish to enable it for production you can provide additional protection by using a `pre-request` function.
Follow the [Performance Debugging Guide](/docs/guides/database/debugging-performance) to enable the functionality on your project.
examples:
- id: get-execution-plan
name: Get the execution plan
code: |
```swift
try await supabase
.from("countries")
.select()
.explain()
.execute()
.value
```
data:
sql: |
```sql
create table
countries (id int8 primary key, name text);
insert into
countries (id, name)
values
(1, 'Afghanistan'),
(2, 'Albania'),
(3, 'Algeria');
```
response: |
```
Aggregate (cost=33.34..33.36 rows=1 width=112)
-> Limit (cost=0.00..18.33 rows=1000 width=40)
-> Seq Scan on countries (cost=0.00..22.00 rows=1200 width=40)
```
description: |
By default, the data is returned in TEXT format, but can also be returned as JSON by using the `format` parameter.
hideCodeBlock: true
isSpotlight: true
- id: get-execution-plan-with-analyze-and-verbose
name: Get the execution plan with analyze and verbose
code: |
```swift
try await supabase
.from("countries")
.select()
.explain(
analyze: true,
verbose: true
)
.execute()
.value
```
data:
sql: |
```sql
create table
countries (id int8 primary key, name text);
insert into
countries (id, name)
values
(1, 'Afghanistan'),
(2, 'Albania'),
(3, 'Algeria');
```
response: |
```
Aggregate (cost=33.34..33.36 rows=1 width=112) (actual time=0.041..0.041 rows=1 loops=1)
Output: NULL::bigint, count(ROW(countries.id, countries.name)), COALESCE(json_agg(ROW(countries.id, countries.name)), '[]'::json), NULLIF(current_setting('response.headers'::text, true), ''::text), NULLIF(current_setting('response.status'::text, true), ''::text)
-> Limit (cost=0.00..18.33 rows=1000 width=40) (actual time=0.005..0.006 rows=3 loops=1)
Output: countries.id, countries.name
-> Seq Scan on public.countries (cost=0.00..22.00 rows=1200 width=40) (actual time=0.004..0.005 rows=3 loops=1)
Output: countries.id, countries.name
Query Identifier: -4730654291623321173
Planning Time: 0.407 ms
Execution Time: 0.119 ms
```
description: |
By default, the data is returned in TEXT format, but can also be returned as JSON by using the `format` parameter.
hideCodeBlock: true
isSpotlight: false
- id: invoke
title: invoke()
description: |
@@ -2997,6 +3086,18 @@ functions:
print(type(of: response)) // String?
```
- id: invocation-with-streamed-response
name: Invocation with streamed response
description: |
- Use this method to return an `AsyncStream<Data>` when the function returns `Content-Type` = `text/event-stream`.
isSpotlight: true
code: |
```swift
var response = Data()
for try await data try await supabase.functions._invokeWithStreamedResponse("hello") {
response.append(data)
}
```
- id: error-handling
name: Error handling
description: |
@@ -3040,6 +3141,37 @@ functions:
)
)
```
- id: regional-invocation
name: Invoking a Function in the UsEast1 region
description: |
Here are the available regions:
- `FunctionRegion.any`
- `FunctionRegion.apNortheast1`
- `FunctionRegion.apNortheast2`
- `FunctionRegion.apSouth1`
- `FunctionRegion.apSoutheast1`
- `FunctionRegion.apSoutheast2`
- `FunctionRegion.caCentral1`
- `FunctionRegion.euCentral1`
- `FunctionRegion.euWest1`
- `FunctionRegion.euWest2`
- `FunctionRegion.euWest3`
- `FunctionRegion.saEast1`
- `FunctionRegion.usEast1`
- `FunctionRegion.usWest1`
- `FunctionRegion.usWest2`
isSpotlight: true
code: |
```swift
let response = try await supabase.functions
.invoke(
"hello",
options: FunctionInvokeOptions(
body: ["foo": "bar"],
region: .usEast1
)
)
```
- id: calling-with-delete-verb
name: Calling with DELETE HTTP verb
description: |
@@ -3084,6 +3216,7 @@ functions:
- By default, listening to database changes is disabled for new projects due to database performance and security concerns. You can turn it on by managing Realtime's [replication](/docs/guides/api#realtime-api-overview).
- You can receive the "previous" data for updates and deletes by setting the table's `REPLICA IDENTITY` to `FULL` (e.g., `ALTER TABLE your_table REPLICA IDENTITY FULL;`).
- Row level security is not applied to delete statements. When RLS is enabled and replica identity is set to full, only the primary key is sent to clients.
- Use AsyncStream or callbacks for listening to changes.
examples:
- id: listen-to-broadcast
name: Listen to broadcast messages
@@ -3113,6 +3246,31 @@ functions:
}
```
- id: listen-to-broadcast-callback
name: Listen to broadcast messages using callback
isSpotlight: true
code: |
```swift
let channel = await supabase.channel("room1")
let subscription = await channel.onBroadcast(event: "cursor-pos") { message in
print("Cursor position received", message.payload)
}
await channel.subscribe()
await channel.broadcast(
event: "cursor-pos",
message: [
"x": .double(.random(in: 0...1)),
"y": .double(.random(in: 0...1))
]
)
// remove subscription some time later
subscription.remove()
```
- id: listen-to-presence-updates
name: Listen to presence updates
code: |
@@ -3139,6 +3297,30 @@ functions:
try await channel.track(PresenceState(username: "John"))
}
- id: listen-to-presence-updates-callback
name: Listen to presence updates using callback
code: |
```swift
struct PresenceState: Codable {
let username: String
}
let channel = await supabase.channel("channelId")
let subscription = await channel.onPresenceChange() { presence in
let joins = try presence.decodeJoins(as: PresenceState.self)
let leaves = try presence.decodeLeaves(as: PresenceState.self)
}
await channel.subscribe()
// Send your own state
try await channel.track(PresenceState(username: "John"))
// remove subscription some time later
subscription.remove()
```
- id: listen-to-all-database-changes
name: Listen to all database changes
code: |
@@ -3158,6 +3340,26 @@ functions:
}
}
```
- id: listen-to-all-database-changes-callback
name: Listen to all database changes using callback
code: |
```swift
let channel = await supabase.channel("channelId")
let subscription = await channel.onPostgresChange(AnyAction.self, schema: "public") { change in
switch change {
case .delete(let action): print("Deleted: \(action.oldRecord)")
case .insert(let action): print("Inserted: \(action.record)")
case .select(let action): print("Selected: \(action.record)")
case .update(let action): print("Updated": \(action.oldRecord) with \(action.record)")
}
}
await channel.subscribe()
// remove subscription some time later
subscription.remove()
```
- id: listen-to-a-specific-table
name: Listen to a specific table
code: |
@@ -3181,6 +3383,30 @@ functions:
}
}
```
- id: listen-to-a-specific-table-callback
name: Listen to a specific table using callback
code: |
```swift
let channel = await supabase.channel("channelId")
let subscription = await channel.onPostgresChange(
AnyAction.self,
schema: "public",
table: "users"
) { change in
switch change {
case .delete(let action): print("Deleted: \(action.oldRecord)")
case .insert(let action): print("Inserted: \(action.record)")
case .select(let action): print("Selected: \(action.record)")
case .update(let action): print("Updated": \(action.oldRecord) with \(action.record)")
}
}
await channel.subscribe()
// remove subscription some time later
subscription.remove()
```
- id: listen-to-inserts
name: Listen to inserts
code: |
@@ -3199,6 +3425,25 @@ functions:
print("Inserted: \(insert.record)")
}
```
- id: listen-to-inserts-callback
name: Listen to inserts using callback
code: |
```swift
let channel = await supabase.channel("channelId")
let subscription = await channel.onPostgresChange(
InsertAction.self,
schema: "public",
table: "users"
) { insert in
print("Inserted: \(insert.record)")
}
await channel.subscribe()
// remove subscription some time later
subscription.remove()
```
- id: listen-to-updates
name: Listen to updates
description: |
@@ -3224,6 +3469,32 @@ functions:
print("Updated: \(update.oldRecord) with \(update.record)")
}
```
- id: listen-to-updates-callback
name: Listen to updates using callback
description: |
By default, Supabase will send only the updated record. If you want to receive the previous values as well you can
enable full replication for the table you are listening too:
```sql
alter table "your_table" replica identity full;
```
code: |
```swift
let channel = await supabase.channel("channelId")
let subscription = await channel.onPostgresChange(
UpdateAction.self,
schema: "public",
table: "users"
) { update in
print("Updated: \(update.oldRecord) with \(update.record)")
}
await channel.subscribe()
// remove subscription some time later
subscription.remove()
```
- id: listen-to-deletes
name: Listen to deletes
description: |
@@ -3249,6 +3520,32 @@ functions:
print("Deleted: \(deletion.oldRecord)")
}
```
- id: listen-to-deletes-callback
name: Listen to deletes using callback
description: |
By default, Supabase does not send deleted records. If you want to receive the deleted record you can
enable full replication for the table you are listening too:
```sql
alter table "your_table" replica identity full;
```
code: |
```swift
let channel = await supabase.channel("channelId")
let subscription = await channel.onPostgresChange(
DeleteAction.self,
schema: "public",
table: "users"
) { deletion in
print("Deleted: \(deletion.oldRecord)")
}
await channel.subscribe()
// remove subscription some time later
subscription.remove()
```
- id: listening-to-row-level-changes
name: Listen to row level changes
code: |
@@ -3268,6 +3565,26 @@ functions:
print("Deleted: \(deletion.oldRecord)")
}
```
- id: listening-to-row-level-changes-callback
name: Listen to row level changes using callback
code: |
```swift
let channel = await supabase.channel("channelId")
let subscription = await channel.onPostgresChange(
DeleteAction.self,
schema: "public",
table: "users",
filter: "id=eq.1"
) { deletion in
print("Deleted: \(deletion.oldRecord)")
}
await channel.subscribe()
// remove subscription some time later
subscription.remove()
```
- id: remove-channel
description: |
+2 -13659
View File
File diff suppressed because it is too large. Load diff
@@ -35,25 +35,24 @@ const PROVIDER_EMAIL = {
type: 'boolean',
},
MAILER_OTP_EXP: {
title: 'Mailer OTP Expiration',
title: 'Email OTP Expiration',
type: 'number',
description: 'Duration before an email otp / link expires.',
units: 'seconds',
},
PASSWORD_MIN_LENGTH: {
title: 'Min password length',
description: 'Users will not be able to use a password shorter than this.',
MAILER_OTP_LENGTH: {
title: 'Email OTP Length',
type: 'number',
description: 'Number of digits in the email OTP',
units: 'number',
},
},
validationSchema: object().shape({
PASSWORD_MIN_LENGTH: number()
.required('A password is required.')
.min(6, 'Password length must be at least 6 characters long'),
MAILER_OTP_EXP: number()
.min(0, 'Must be more than 0')
.max(86400, 'Must be no more than 86400')
.required('This is required'),
MAILER_OTP_LENGTH: number().min(6, 'Must be at least 6').max(10, 'Must be no more than 10'),
}),
misc: {
iconKey: 'email-icon2',
@@ -1,15 +1,26 @@
import { useEffect } from 'react'
import { useEffect, useState } from 'react'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { useDatabaseRolesQuery } from 'data/database-roles/database-roles-query'
import { useTablesQuery } from 'data/tables/tables-query'
import { Check, ChevronsUpDown } from 'lucide-react'
import {
Button,
CommandEmpty_Shadcn_,
CommandGroup_Shadcn_,
CommandInput_Shadcn_,
CommandItem_Shadcn_,
CommandList_Shadcn_,
Command_Shadcn_,
FormControl_Shadcn_,
FormField_Shadcn_,
FormItem_Shadcn_,
FormLabel_Shadcn_,
FormMessage_Shadcn_,
Input_Shadcn_,
PopoverContent_Shadcn_,
PopoverTrigger_Shadcn_,
Popover_Shadcn_,
RadioGroupLargeItem_Shadcn_,
RadioGroup_Shadcn_,
ScrollArea,
@@ -24,6 +35,7 @@ import { MultiSelectV2 } from 'ui-patterns/MultiSelect/MultiSelectV2'
interface PolicyDetailsV2Props {
schema: string
searchString?: string
selectedTable?: string
isEditing: boolean
form: any
onUpdateCommand: (command: string) => void
@@ -32,11 +44,13 @@ interface PolicyDetailsV2Props {
export const PolicyDetailsV2 = ({
schema,
searchString,
selectedTable,
isEditing,
form,
onUpdateCommand,
}: PolicyDetailsV2Props) => {
const { project } = useProjectContext()
const [open, setOpen] = useState(false)
const { data: tables, isSuccess: isSuccessTables } = useTablesQuery({
projectRef: project?.ref,
@@ -62,7 +76,7 @@ export const PolicyDetailsV2 = ({
.sort((a, b) => a.name.localeCompare(b.name))
useEffect(() => {
if (!isEditing) {
if (!isEditing && selectedTable === undefined) {
const table = tables?.find(
(table) =>
table.schema === schema &&
@@ -74,7 +88,7 @@ export const PolicyDetailsV2 = ({
form.setValue('table', tables[0].name)
}
}
}, [isEditing, form, searchString, tables, isSuccessTables])
}, [isEditing, form, searchString, tables, isSuccessTables, selectedTable])
return (
<>
@@ -110,30 +124,63 @@ export const PolicyDetailsV2 = ({
</p>
</FormLabel_Shadcn_>
<FormControl_Shadcn_>
<Select_Shadcn_
disabled={isEditing}
value={field.value}
onValueChange={(value) => form.setValue('table', value)}
>
<SelectTrigger_Shadcn_ className="text-sm h-10">
{schema}.{field.value}
</SelectTrigger_Shadcn_>
<SelectContent_Shadcn_>
<SelectGroup_Shadcn_>
<ScrollArea className={(tables ?? []).length > 7 ? 'h-[200px]' : ''}>
{(tables ?? []).map((table) => (
<SelectItem_Shadcn_
key={table.id}
value={table.name}
className="text-sm"
>
{table.name}
</SelectItem_Shadcn_>
))}
</ScrollArea>
</SelectGroup_Shadcn_>
</SelectContent_Shadcn_>
</Select_Shadcn_>
<Popover_Shadcn_ open={open} onOpenChange={setOpen} modal={false}>
<PopoverTrigger_Shadcn_ asChild>
<Button
type="default"
className="w-full [&>span]:w-full h-[38px] text-sm"
iconRight={
<ChevronsUpDown
className="text-foreground-muted"
strokeWidth={2}
size={14}
/>
}
>
<div className="w-full flex gap-1">
<span className="text-foreground">
{schema}.{field.value}
</span>
</div>
</Button>
</PopoverTrigger_Shadcn_>
<PopoverContent_Shadcn_
className="p-0"
side="bottom"
align="start"
sameWidthAsTrigger
>
<Command_Shadcn_>
<CommandInput_Shadcn_ placeholder="Find a table..." />
<CommandList_Shadcn_>
<CommandEmpty_Shadcn_>No tables found</CommandEmpty_Shadcn_>
<CommandGroup_Shadcn_>
<ScrollArea className={(tables ?? []).length > 7 ? 'h-[200px]' : ''}>
{(tables ?? []).map((table) => (
<CommandItem_Shadcn_
key={table.id}
className="cursor-pointer flex items-center justify-between space-x-2 w-full"
onSelect={() => {
form.setValue('table', table.name)
setOpen(false)
}}
onClick={() => {
form.setValue('table', table.name)
setOpen(false)
}}
>
<span className="flex items-center gap-1.5">
{field.value === table.name ? <Check size={13} /> : ''}
{table.name}
</span>
</CommandItem_Shadcn_>
))}
</ScrollArea>
</CommandGroup_Shadcn_>
</CommandList_Shadcn_>
</Command_Shadcn_>
</PopoverContent_Shadcn_>
</Popover_Shadcn_>
</FormControl_Shadcn_>
<FormMessage_Shadcn_ />
</FormItem_Shadcn_>
@@ -70,6 +70,7 @@ interface AIPolicyEditorPanelProps {
visible: boolean
schema: string
searchString?: string
selectedTable?: string
selectedPolicy?: PostgresPolicy
onSelectCancel: () => void
}
@@ -81,6 +82,7 @@ export const AIPolicyEditorPanel = memo(function ({
visible,
schema,
searchString,
selectedTable,
selectedPolicy,
onSelectCancel,
}: AIPolicyEditorPanelProps) {
@@ -415,6 +417,8 @@ export const AIPolicyEditorPanel = memo(function ({
if (selectedPolicy.check && selectedPolicy.command !== 'INSERT') {
setShowCheckBlock(true)
}
} else if (selectedTable !== undefined) {
form.reset({ ...defaultValues, table: selectedTable })
}
}
}, [visible])
@@ -535,6 +539,7 @@ export const AIPolicyEditorPanel = memo(function ({
<PolicyDetailsV2
schema={schema}
searchString={searchString}
selectedTable={selectedTable}
isEditing={selectedPolicy !== undefined}
form={form}
onUpdateCommand={(command: string) => {
@@ -21,6 +21,7 @@ interface PoliciesProps {
tables: PostgresTable[]
hasTables: boolean
isLocked: boolean
onSelectCreatePolicy: (table: string) => void
onSelectEditPolicy: (policy: PostgresPolicy) => void
}
@@ -29,6 +30,7 @@ const Policies = ({
tables,
hasTables,
isLocked,
onSelectCreatePolicy,
onSelectEditPolicy: onSelectEditPolicyAI,
}: PoliciesProps) => {
const router = useRouter()
@@ -139,12 +141,13 @@ const Policies = ({
<div className="flex flex-col gap-y-4 pb-4">
{isLocked && <ProtectedSchemaWarning schema={schema} entity="policies" />}
{tables.length > 0 ? (
tables.map((table: any) => (
tables.map((table) => (
<section key={table.id}>
<PolicyTableRow
table={table}
isLocked={isLocked}
onSelectToggleRLS={onSelectToggleRLS}
onSelectCreatePolicy={() => onSelectCreatePolicy(table.name)}
onSelectEditPolicy={onSelectEditPolicy}
onSelectDeletePolicy={onSelectDeletePolicy}
/>
@@ -2,25 +2,31 @@ import * as Tooltip from '@radix-ui/react-tooltip'
import type { PostgresTable } from '@supabase/postgres-meta'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { noop } from 'lodash'
import { Lock } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useIsRLSAIAssistantEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { useCheckPermissions } from 'hooks'
import { Badge, Button, IconLock } from 'ui'
import { Badge, Button } from 'ui'
interface PolicyTableRowHeaderProps {
table: PostgresTable
isLocked: boolean
onSelectToggleRLS: (table: PostgresTable) => void
onSelectCreatePolicy: () => void
}
const PolicyTableRowHeader = ({
table,
isLocked,
onSelectToggleRLS = noop,
onSelectCreatePolicy,
}: PolicyTableRowHeaderProps) => {
const router = useRouter()
const { ref } = router.query
const isAiAssistantEnabled = useIsRLSAIAssistantEnabled()
const canToggleRLS = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables')
return (
@@ -33,7 +39,7 @@ const PolicyTableRowHeader = ({
{isLocked && (
<Badge>
<span className="flex gap-2 items-center text-xs uppercase text-foreground-lighter">
<IconLock width={12} /> Locked
<Lock size={12} /> Locked
</span>
</Badge>
)}
@@ -73,6 +79,15 @@ const PolicyTableRowHeader = ({
</Tooltip.Portal>
)}
</Tooltip.Root>
{!isAiAssistantEnabled && (
<Button
type="default"
disabled={!canToggleRLS}
onClick={() => onSelectCreatePolicy()}
>
Create policy
</Button>
)}
</div>
</div>
)}
@@ -13,6 +13,7 @@ interface PolicyTableRowProps {
table: PostgresTable
isLocked: boolean
onSelectToggleRLS: (table: PostgresTable) => void
onSelectCreatePolicy: () => void
onSelectEditPolicy: (policy: PostgresPolicy) => void
onSelectDeletePolicy: (policy: PostgresPolicy) => void
}
@@ -21,6 +22,7 @@ const PolicyTableRow = ({
table,
isLocked,
onSelectToggleRLS = noop,
onSelectCreatePolicy,
onSelectEditPolicy = noop,
onSelectDeletePolicy = noop,
}: PolicyTableRowProps) => {
@@ -41,6 +43,7 @@ const PolicyTableRow = ({
table={table}
isLocked={isLocked}
onSelectToggleRLS={onSelectToggleRLS}
onSelectCreatePolicy={onSelectCreatePolicy}
/>
}
>
@@ -40,6 +40,8 @@ const STATUS_TO_LABEL: Record<Status, string> = {
RUNNING_MIGRATIONS: 'Running migrations',
MIGRATIONS_FAILED: 'Migrations failed',
MIGRATIONS_PASSED: 'Migrations applied successfully',
FUNCTIONS_DEPLOYED: 'Functions deployed',
FUNCTIONS_FAILED: 'Functions failed to deploy',
}
const BranchStatusBadge = ({ status }: BranchStatusBadgeProps) => {
@@ -1,6 +1,6 @@
import { zodResolver } from '@hookform/resolvers/zod'
import Link from 'next/link'
import { useRef } from 'react'
import { useEffect, useRef } from 'react'
import { DragDropContext, Droppable, DroppableProvided } from 'react-beautiful-dnd'
import { useFieldArray, useForm } from 'react-hook-form'
import toast from 'react-hot-toast'
@@ -51,6 +51,10 @@ const CreateEnumeratedTypeSidePanel = ({
},
})
useEffect(() => {
form.reset(initialValues)
}, [visible])
const FormSchema = z.object({
name: z
.string()
@@ -17,3 +17,7 @@ export const HIDDEN_EXTENSIONS = [
'xml2',
'pg_tle',
]
export const SEARCH_TERMS: Record<string, string[]> = {
vector: ['pgvector', 'pg_vector'],
}
@@ -13,7 +13,7 @@ import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-ex
import { useCheckPermissions, usePermissionsLoaded } from 'hooks'
import ExtensionCard from './ExtensionCard'
import ExtensionCardSkeleton from './ExtensionCardSkeleton'
import { HIDDEN_EXTENSIONS } from './Extensions.constants'
import { HIDDEN_EXTENSIONS, SEARCH_TERMS } from './Extensions.constants'
const Extensions = () => {
const { filter } = useParams()
@@ -28,11 +28,13 @@ const Extensions = () => {
const extensions =
filterString.length === 0
? data ?? []
: (data ?? []).filter((ext) =>
// if the filter string contains pg, ignore it since some of the extensions may be listed without the
// pg prefix (pgvector is listed as vector)
ext.name.includes(filterString.replace('pg', ''))
)
: (data ?? []).filter((ext) => {
const nameMatchesSearch = ext.name.toLowerCase().includes(filterString.toLowerCase())
const searchTermsMatchesSearch = (SEARCH_TERMS[ext.name] || []).some((x) =>
x.includes(filterString.toLowerCase())
)
return nameMatchesSearch || searchTermsMatchesSearch
})
const extensionsWithoutHidden = extensions.filter((ext) => !HIDDEN_EXTENSIONS.includes(ext.name))
const [enabledExtensions, disabledExtensions] = partition(
extensionsWithoutHidden,
@@ -60,7 +60,7 @@ const FormSchema = z.object({
schema: z.string().trim().min(1),
args: z.array(z.object({ name: z.string().trim().min(1), type: z.string().trim() })),
behavior: z.enum(['IMMUTABLE', 'STABLE', 'VOLATILE']),
definition: z.string().trim(),
definition: z.string().trim().min(1),
language: z.string().trim(),
return_type: z.string().trim(),
security_definer: z.boolean(),
@@ -255,7 +255,9 @@ const CreateFunction = ({ func, visible, setVisible }: CreateFunctionProps) => {
Definition
</FormLabel_Shadcn_>
<FormDescription_Shadcn_ className="text-sm text-foreground-light">
<p>The language below should be written in `plpgsql`.</p>
<p>
The language below should be written in <code>plpgsql</code>.
</p>
{!isEditing && <p>Change the language in the Advanced Settings below.</p>}
</FormDescription_Shadcn_>
</div>
@@ -272,7 +274,7 @@ const CreateFunction = ({ func, visible, setVisible }: CreateFunctionProps) => {
/>
</div>
<FormMessage_Shadcn_ />
<FormMessage_Shadcn_ className="px-content" />
</FormItem_Shadcn_>
)}
/>
@@ -46,8 +46,8 @@ const DeleteFunction = ({ func, visible, setVisible }: DeleteFunctionProps) => {
confirmString={name}
text={
<>
This will delete the function
<span className="text-bold text-foreground">{name}</span> from the schema
<span>This will delete the function</span>{' '}
<span className="text-bold text-foreground">{name}</span> <span>from the schema</span>{' '}
<span className="text-bold text-foreground">{schema}</span>
</>
}
@@ -6,10 +6,11 @@ export const SUPABASE_ROLES = [
'dashboard_user',
'supabase_admin',
'supabase_auth_admin',
'supabase_functions_admin',
'supabase_read_only_user',
'supabase_realtime_admin',
'supabase_replication_admin',
'supabase_storage_admin',
'supabase_functions_admin',
'pgbouncer',
'pgsodium_keyholder',
'pgsodium_keyiduser',
@@ -230,9 +230,11 @@ const TableList = ({
<p>{x.name}</p>
)}
</Table.td>
<Table.td className="hidden max-w-sm truncate lg:table-cell break-all whitespace-normal">
<Table.td className="hidden lg:table-cell ">
{x.comment !== null ? (
<p title={x.comment}>{x.comment}</p>
<span className="lg:max-w-48 truncate inline-block" title={x.comment}>
{x.comment}
</span>
) : (
<p className="text-border-stronger">No description</p>
)}
@@ -13,6 +13,7 @@ import { useOrganizationsQuery } from 'data/organizations/organizations-query'
import { usePermissionsQuery } from 'data/permissions/permissions-query'
import { ProjectInfo, useProjectsQuery } from 'data/projects/projects-query'
import { ResourceWarning, useResourceWarningsQuery } from 'data/usage/resource-warnings-query'
import { useIsFeatureEnabled } from 'hooks'
import { IS_PLATFORM } from 'lib/constants'
import { makeRandomString } from 'lib/helpers'
import type { Organization, ResponseError } from 'types'
@@ -244,17 +245,20 @@ const OrganizationProjects = ({
}
const NoProjectsState = ({ slug }: { slug: string }) => {
const projectCreationEnabled = useIsFeatureEnabled('projects:create')
return (
<div className="col-span-4 space-y-4 rounded-lg border border-dashed p-6 text-center">
<div className="space-y-1">
<p>No projects</p>
<p className="text-sm text-foreground-light">Get started by creating a new project.</p>
</div>
<div>
{projectCreationEnabled && (
<Button asChild icon={<IconPlus />}>
<Link href={`/new/${slug}`}>New Project</Link>
</Button>
</div>
)}
</div>
)
}
@@ -5,6 +5,7 @@ import ProjectLinker from 'components/interfaces/Integrations/ProjectLinker'
import { Markdown } from 'components/interfaces/Markdown'
import { useGitHubAuthorizationQuery } from 'data/integrations/github-authorization-query'
import { useGitHubConnectionCreateMutation } from 'data/integrations/github-connection-create-mutation'
import { useGitHubConnectionDeleteMutation } from 'data/integrations/github-connection-delete-mutation'
import { useGitHubConnectionsQuery } from 'data/integrations/github-connections-query'
import { useGitHubRepositoriesQuery } from 'data/integrations/github-repositories-query'
import type { IntegrationConnectionsCreateVariables } from 'data/integrations/integrations.types'
@@ -14,7 +15,6 @@ import { openInstallGitHubIntegrationWindow } from 'lib/github'
import { EMPTY_ARR } from 'lib/void'
import { useSidePanelsStateSnapshot } from 'state/side-panels'
import { Button, SidePanel } from 'ui'
import { useGitHubConnectionDeleteMutation } from 'data/integrations/github-connection-delete-mutation'
const GITHUB_ICON = (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 98 96" className="w-6">
@@ -39,8 +39,11 @@ const SidePanelGitHubRepoLinker = ({ projectRef }: SidePanelGitHubRepoLinkerProp
useGitHubAuthorizationQuery()
// [Alaister]: temp override with <any> until the typegen is fixed
const { data: githubReposData, isLoading: isLoadingGitHubRepos } =
useGitHubRepositoriesQuery<any[]>()
const { data: githubReposData, isLoading: isLoadingGitHubRepos } = useGitHubRepositoriesQuery<
any[]
>({
enabled: Boolean(gitHubAuthorization),
})
/**
* Supabase projects available
@@ -0,0 +1,223 @@
import * as Tooltip from '@radix-ui/react-tooltip'
import Image from 'next/legacy/image'
import { Dispatch, SetStateAction, useState } from 'react'
import toast from 'react-hot-toast'
import Table from 'components/to-be-cleaned/Table'
import { OrganizationMember } from 'data/organizations/organization-members-query'
import { usePermissionsQuery } from 'data/permissions/permissions-query'
import { useSelectedOrganization } from 'hooks'
import { useProfile } from 'lib/profile'
import { Role } from 'types'
import { Badge, IconAlertCircle, IconCheck, IconLoader, IconUser, IconX, Listbox } from 'ui'
import { getUserDisplayName, isInviteExpired } from '../Organization.utils'
import MemberActions from './MemberActions'
import { useGetRolesManagementPermissions } from './TeamSettings.utils'
export interface SelectedMember extends OrganizationMember {
oldRoleId: number
newRoleId: number
}
interface MemberRowProps {
member: OrganizationMember
roles: Role[]
isLoadingRoles?: boolean
showMfaEnabledColumn?: boolean
setUserRoleChangeModalVisible: Dispatch<SetStateAction<boolean>>
setSelectedMember: Dispatch<SetStateAction<SelectedMember | undefined>>
}
const MemberRow = ({
member,
roles,
isLoadingRoles = false,
showMfaEnabledColumn = false,
setUserRoleChangeModalVisible,
setSelectedMember,
}: MemberRowProps) => {
const { profile } = useProfile()
const selectedOrganization = useSelectedOrganization()
const { data: permissions } = usePermissionsQuery()
const { rolesAddable, rolesRemovable } = useGetRolesManagementPermissions(
selectedOrganization?.id,
roles,
permissions ?? []
)
const [memberRoleId] = member.role_ids ?? []
const role = (roles || []).find((role) => role.id === memberRoleId)
const memberIsUser = member.primary_email == profile?.primary_email
const isInvitedUser = Boolean(member.invited_id)
const canRemoveRole = rolesRemovable.includes(memberRoleId)
const disableRoleEdit = !canRemoveRole || memberIsUser || isInvitedUser
const isEmailUser = member.username === member.primary_email
const validateSelectedRoleToChange = (roleId: any) => {
if (!role || role.id === roleId) return
const selectedRole = (roles || []).find((role) => role.id === roleId)
const canAddRole = rolesAddable.includes(selectedRole?.id ?? -1)
if (!canAddRole) {
return toast.error(
`You do not have permission to update this team member to ${selectedRole!.name}`
)
}
setUserRoleChangeModalVisible(true)
setSelectedMember({ ...member, oldRoleId: role.id, newRoleId: roleId })
}
const [hasInvalidImg, setHasInvalidImg] = useState(false)
return (
<Table.tr>
<Table.td>
<div className="flex items-center space-x-4">
<div>
{isInvitedUser || isEmailUser || hasInvalidImg ? (
<div className="w-[40px] h-[40px] bg-surface-100 border border-overlay rounded-full text-foreground-lighter flex items-center justify-center">
<IconUser strokeWidth={1.5} />
</div>
) : (
<Image
alt={member.username}
src={`https://github.com/${member.username}.png?size=80`}
width="40"
height="40"
className="border rounded-full"
onError={() => {
setHasInvalidImg(true)
}}
/>
)}
</div>
<div>
<p className="text-foreground">{getUserDisplayName(member)}</p>
{isInvitedUser === undefined && (
<p className="text-foreground-light">{member.primary_email}</p>
)}
</div>
</div>
</Table.td>
<Table.td>
{isInvitedUser && member.invited_at && (
<Badge variant={isInviteExpired(member.invited_at) ? 'destructive' : 'warning'}>
{isInviteExpired(member.invited_at) ? 'Expired' : 'Invited'}
</Badge>
)}
</Table.td>
{showMfaEnabledColumn && (
<Table.td>
<div className="flex items-center justify-center">
{member.mfa_enabled ? (
<IconCheck className="text-brand" strokeWidth={2} />
) : (
<IconX className="text-foreground-light" strokeWidth={1.5} />
)}
</div>
</Table.td>
)}
<Table.td>
{isLoadingRoles ? (
<div className="w-[140px]">
<IconLoader className="animate-spin" size={16} strokeWidth={1.5} />
</div>
) : role !== undefined ? (
<Tooltip.Root delayDuration={0}>
<Tooltip.Trigger className="w-[140px]" asChild>
<div>
<Listbox
className={disableRoleEdit ? 'pointer-events-none' : ''}
disabled={disableRoleEdit}
value={role.id}
onChange={validateSelectedRoleToChange}
>
{roles.map((r: any) => (
<Listbox.Option
key={r.id}
value={r.id}
label={r.name}
disabled={disableRoleEdit}
className="w-36"
>
{r.name}
</Listbox.Option>
))}
</Listbox>
</div>
</Tooltip.Trigger>
{isInvitedUser ? (
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-alternative py-1 px-2 leading-none shadow', // background
'border border-background', //border
].join(' ')}
>
<span className="text-xs text-foreground">
Role can only be changed after the user has accepted the invite
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
) : !memberIsUser && !canRemoveRole ? (
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-alternative py-1 px-2 leading-none shadow', // background
'border border-background', //border
].join(' ')}
>
<span className="text-xs text-foreground">
You need additional permissions to manage this team member
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
) : (
<></>
)}
</Tooltip.Root>
) : (
<div className="flex items-center space-x-2">
<p className="text-sm text-foreground-light">Invalid role</p>
<Tooltip.Root delayDuration={0}>
<Tooltip.Trigger>
<IconAlertCircle size={16} strokeWidth={1.5} />
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-alternative py-1 px-2 leading-none shadow', // background
'border border-background', //border
].join(' ')}
>
<span className="text-xs text-foreground">
This user has an invalid role, please reach out to us via support
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</div>
)}
</Table.td>
<Table.td>{!memberIsUser && <MemberActions member={member} roles={roles} />}</Table.td>
</Table.tr>
)
}
export default MemberRow
@@ -1,6 +1,4 @@
import * as Tooltip from '@radix-ui/react-tooltip'
import Image from 'next/legacy/image'
import { Fragment, useState } from 'react'
import { useState } from 'react'
import toast from 'react-hot-toast'
import { useParams } from 'common'
@@ -8,35 +6,12 @@ import Table from 'components/to-be-cleaned/Table'
import AlertError from 'components/ui/AlertError'
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
import { useOrganizationMemberUpdateMutation } from 'data/organizations/organization-member-update-mutation'
import {
OrganizationMember,
useOrganizationMembersQuery,
} from 'data/organizations/organization-members-query'
import { useOrganizationMembersQuery } from 'data/organizations/organization-members-query'
import { useOrganizationRolesQuery } from 'data/organizations/organization-roles-query'
import { usePermissionsQuery } from 'data/permissions/permissions-query'
import { useSelectedOrganization } from 'hooks'
import { useProfile } from 'lib/profile'
import {
Badge,
Button,
IconAlertCircle,
IconCheck,
IconLoader,
IconUser,
IconX,
Listbox,
Loading,
Modal,
} from 'ui'
import { getUserDisplayName, isInviteExpired } from '../Organization.utils'
import MemberActions from './MemberActions'
import { Button, IconAlertCircle, Loading, Modal } from 'ui'
import { getUserDisplayName } from '../Organization.utils'
import MemberRow, { SelectedMember } from './MemberRow'
import RolesHelperModal from './RolesHelperModal/RolesHelperModal'
import { useGetRolesManagementPermissions } from './TeamSettings.utils'
interface SelectedMember extends OrganizationMember {
oldRoleId: number
newRoleId: number
}
export interface MembersViewProps {
searchString: string
@@ -44,10 +19,7 @@ export interface MembersViewProps {
const MembersView = ({ searchString }: MembersViewProps) => {
const { slug } = useParams()
const selectedOrganization = useSelectedOrganization()
const { profile } = useProfile()
const { data: permissions } = usePermissionsQuery()
const {
data: members,
error: membersError,
@@ -75,11 +47,6 @@ const MembersView = ({ searchString }: MembersViewProps) => {
const allMembers = members ?? []
const roles = rolesData?.roles ?? []
const { rolesAddable, rolesRemovable } = useGetRolesManagementPermissions(
selectedOrganization?.id,
roles,
permissions ?? []
)
// [Joshen] Proactively adding this, can be removed once infra API changes are in
const showMfaEnabledColumn = allMembers.some(
@@ -91,21 +58,23 @@ const MembersView = ({ searchString }: MembersViewProps) => {
const filteredMembers = (
!searchString
? allMembers
: allMembers.filter((x: any) => {
if (x.invited_at) {
return x.primary_email.includes(searchString)
: allMembers.filter((member) => {
if (member.invited_at) {
return member.primary_email?.includes(searchString)
}
if (x.id || x.gotrue_id) {
return x.username.includes(searchString) || x.primary_email.includes(searchString)
if (member.gotrue_id) {
return (
member.username.includes(searchString) || member.primary_email?.includes(searchString)
)
}
})
)
.slice()
.sort((a: any, b: any) => a.username.localeCompare(b.username))
.sort((a, b) => a.username.localeCompare(b.username))
const getRoleNameById = (id: number | undefined) => {
if (!roles) return id
return roles.find((x: any) => x.id === id)?.name
return roles.find((member) => member.id === id)?.name
}
const handleRoleChange = async () => {
@@ -150,188 +119,17 @@ const MembersView = ({ searchString }: MembersViewProps) => {
<Table.th key="header-action"></Table.th>,
]}
body={[
...filteredMembers.map((x, i: number) => {
const [memberRoleId] = x.role_ids ?? []
const role = (roles || []).find((role) => role.id === memberRoleId)
const memberIsUser = x.primary_email == profile?.primary_email
const memberIsPendingInvite = !!x.invited_id
const canRemoveRole = rolesRemovable.includes(memberRoleId)
const disableRoleEdit = !canRemoveRole || memberIsUser || memberIsPendingInvite
const isEmailUser = x.username === x.primary_email
const validateSelectedRoleToChange = (roleId: any) => {
if (!role || role.id === roleId) return
const selectedRole = (roles || []).find((role) => role.id === roleId)
const canAddRole = rolesAddable.includes(selectedRole?.id ?? -1)
if (!canAddRole) {
return toast.error(
`You do not have permission to update this team member to ${
selectedRole!.name
}`
)
}
setUserRoleChangeModalVisible(true)
setSelectedMember({ ...x, oldRoleId: role.id, newRoleId: roleId })
}
return (
<Fragment key={`member-row-${i}`}>
<Table.tr>
<Table.td>
<div className="flex items-center space-x-4">
<div>
{x.invited_id ? (
<span className="flex p-2 border-2 rounded-full border-strong">
<IconUser size={20} strokeWidth={2} />
</span>
) : isEmailUser ? (
<div className="w-[40px] h-[40px] bg-surface-100 border border-overlay rounded-full text-foreground-lighter flex items-center justify-center">
<IconUser strokeWidth={1.5} />
</div>
) : (
<Image
alt={x.username}
src={`https://github.com/${x.username}.png?size=80`}
width="40"
height="40"
className="border rounded-full"
/>
)}
</div>
<div>
<p className="text-foreground">{getUserDisplayName(x)}</p>
{x.invited_id === undefined && (
<p className="text-foreground-light">{x.primary_email}</p>
)}
</div>
</div>
</Table.td>
<Table.td>
{x.invited_id && x.invited_at && (
<Badge
variant={isInviteExpired(x.invited_at) ? 'destructive' : 'warning'}
>
{isInviteExpired(x.invited_at) ? 'Expired' : 'Invited'}
</Badge>
)}
</Table.td>
{showMfaEnabledColumn && (
<Table.td>
<div className="flex items-center justify-center">
{x.mfa_enabled ? (
<IconCheck className="text-brand" strokeWidth={2} />
) : (
<IconX className="text-foreground-light" strokeWidth={1.5} />
)}
</div>
</Table.td>
)}
<Table.td>
{isLoadingRoles ? (
<div className="w-[140px]">
<IconLoader className="animate-spin" size={16} strokeWidth={1.5} />
</div>
) : role !== undefined ? (
<Tooltip.Root delayDuration={0}>
<Tooltip.Trigger className="w-[140px]" asChild>
<div>
<Listbox
className={disableRoleEdit ? 'pointer-events-none' : ''}
disabled={disableRoleEdit}
value={role.id}
onChange={validateSelectedRoleToChange}
>
{roles.map((r: any) => (
<Listbox.Option
key={r.id}
value={r.id}
label={r.name}
disabled={disableRoleEdit}
className="w-36"
>
{r.name}
</Listbox.Option>
))}
</Listbox>
</div>
</Tooltip.Trigger>
{memberIsPendingInvite ? (
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-alternative py-1 px-2 leading-none shadow', // background
'border border-background', //border
].join(' ')}
>
<span className="text-xs text-foreground">
Role can only be changed after the user has accepted the
invite
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
) : !memberIsUser && !canRemoveRole ? (
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-alternative py-1 px-2 leading-none shadow', // background
'border border-background', //border
].join(' ')}
>
<span className="text-xs text-foreground">
You need additional permissions to manage this team member
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
) : (
<></>
)}
</Tooltip.Root>
) : (
<div className="flex items-center space-x-2">
<p className="text-sm text-foreground-light">Invalid role</p>
<Tooltip.Root delayDuration={0}>
<Tooltip.Trigger>
<IconAlertCircle size={16} strokeWidth={1.5} />
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-alternative py-1 px-2 leading-none shadow', // background
'border border-background', //border
].join(' ')}
>
<span className="text-xs text-foreground">
This user has an invalid role, please reach out to us via
support
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</div>
)}
</Table.td>
<Table.td>
{!memberIsUser && <MemberActions member={x} roles={roles} />}
</Table.td>
</Table.tr>
</Fragment>
)
}),
...filteredMembers.map((member) => (
<MemberRow
key={member.gotrue_id}
member={member}
roles={roles}
isLoadingRoles={isLoadingRoles}
showMfaEnabledColumn={showMfaEnabledColumn}
setUserRoleChangeModalVisible={setUserRoleChangeModalVisible}
setSelectedMember={setSelectedMember}
/>
)),
...(searchString.length > 0 && filteredMembers.length === 0
? [
<Table.tr key="no-results" className="bg-panel-secondary-light">
@@ -50,7 +50,7 @@ const ProjectAPIDocs = () => {
: 'SUPABASE_CLIENT_ANON_KEY'
const endpoint =
customDomainData?.customDomain?.status === 'active'
? `https://${customDomainData.customDomain?.hostname}/auth/v1/callback`
? `https://${customDomainData.customDomain?.hostname}`
: `https://${data?.autoApiService.endpoint ?? ''}`
return (
@@ -28,7 +28,7 @@ export const QueryPerformanceFilterBar = ({
}) => {
const router = useRouter()
const { project } = useProjectContext()
const [showBottomSection, setShowBottomSection] = useLocalStorageQuery(
const [showBottomSection] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.QUERY_PERF_SHOW_BOTTOM_SECTION,
true
)
@@ -161,7 +161,7 @@ export const useRealtimeMessages = (
}
// Finally, subscribe to the Channel we just setup
newChannel.subscribe(async (status, error) => {
newChannel.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
// Let LiveView know we connected so we can update the button text
// pushMessageTo('#conn_info', 'broadcast_subscribed', { host: host })
@@ -174,8 +174,6 @@ export const useRealtimeMessages = (
payload: { name: name, t: performance.now() },
})
}
} else if (status === 'CLOSED') {
// console.log(`Realtime Channel status: ${status}`)
} else if (status === 'CHANNEL_ERROR') {
toast.error(
'Failed to connect to the channel: This may be due to restrictive RLS policies. Check your role and try again.'
@@ -184,9 +182,6 @@ export const useRealtimeMessages = (
newChannel.unsubscribe()
setChannel(undefined)
setRealtimeConfig({ ...config, enabled: false })
} else {
// console.error(`Realtime Channel error status: ${status}`)
// console.error(`Realtime Channel error: ${error}`)
}
})
@@ -37,7 +37,7 @@ export const useQueryPerformanceQuery = ({
searchQuery.length > 0 ? `statements.query ~ '${searchQuery}'` : '',
]
.filter((x) => x.length > 0)
.join(' OR ')
.join(' AND ')
const orderBySql = orderBy && `ORDER BY ${orderBy.column} ${orderBy.order}`
const sql = baseSQL.sql([], whereSql.length > 0 ? `WHERE ${whereSql}` : undefined, orderBySql)
@@ -164,10 +164,7 @@ export function ChartConfig({ results = { rows: [] }, config, onConfigChange }:
id="cumulative"
name="cumulative"
checked={config.cumulative}
onClick={(e) => {
console.log(e)
onConfigChange({ ...config, cumulative: !config.cumulative })
}}
onClick={() => onConfigChange({ ...config, cumulative: !config.cumulative })}
/>
<Label_Shadcn_ className="text-foreground-light p-1.5" htmlFor="cumulative">
Cumulative
@@ -55,23 +55,35 @@ const Results = ({ id, rows }: { id: string; rows: readonly any[] }) => {
return <div className="flex h-full items-center justify-center font-mono text-xs">{name}</div>
}
const columns: CalculatedColumn<any>[] = Object.keys(rows?.[0] ?? []).map((key, idx) => ({
idx,
key,
name: key,
resizable: true,
parent: undefined,
level: 0,
width: 120,
minWidth: 120,
maxWidth: undefined,
draggable: false,
frozen: false,
sortable: false,
isLastFrozenColumn: false,
renderCell: ({ row }: any) => formatter(key, row),
renderHeaderCell: () => columnRender(key),
}))
const EST_CHAR_WIDTH = 8.25
const MIN_COLUMN_WIDTH = 100
const MAX_COLUMN_WIDTH = 500
const columns: CalculatedColumn<any>[] = Object.keys(rows?.[0] ?? []).map((key, idx) => {
const maxColumnValueLength = Math.max(...rows.map((row) => String(row[key]).length))
const columnWidth = Math.max(
Math.min(maxColumnValueLength * EST_CHAR_WIDTH, MAX_COLUMN_WIDTH),
MIN_COLUMN_WIDTH
)
return {
idx,
key,
name: key,
resizable: true,
parent: undefined,
level: 0,
width: columnWidth,
minWidth: MIN_COLUMN_WIDTH,
maxWidth: undefined,
draggable: false,
frozen: false,
sortable: false,
isLastFrozenColumn: false,
renderCell: ({ row }: any) => formatter(key, row),
renderHeaderCell: () => columnRender(key),
}
})
function onSelectedCellChange(position: any) {
setCellPosition(position)
@@ -2,7 +2,6 @@ import { useQueryClient } from '@tanstack/react-query'
import { useParams } from 'common'
import toast from 'react-hot-toast'
import { useContentQuery } from 'data/content/content-query'
import { useContentUpsertMutation } from 'data/content/content-upsert-mutation'
import { contentKeys } from 'data/content/keys'
import { useSqlEditorStateSnapshot } from 'state/sql-editor'
@@ -38,7 +37,6 @@ const UtilityPanel = ({
}: UtilityPanelProps) => {
const snap = useSqlEditorStateSnapshot()
const { ref } = useParams()
const { data } = useContentQuery(ref)
const snippet = snap.snippets[id]?.snippet
const queryKeys = contentKeys.list(ref)
@@ -1,30 +1,42 @@
import * as Tooltip from '@radix-ui/react-tooltip'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { PermissionAction, SupportCategories } from '@supabase/shared-types/out/constants'
import dayjs from 'dayjs'
import { Info } from 'lucide-react'
import Link from 'next/link'
import { useState } from 'react'
import toast from 'react-hot-toast'
import { number, object } from 'yup'
import { useParams } from 'common'
import { Markdown } from 'components/interfaces/Markdown'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { FormHeader } from 'components/ui/Forms'
import Panel from 'components/ui/Panel'
import { useProjectDiskResizeMutation } from 'data/config/project-disk-resize-mutation'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useCheckPermissions, useSelectedOrganization } from 'hooks'
import { Alert, Button, Form, InputNumber, Modal } from 'ui'
import {
AlertDescription_Shadcn_,
AlertTitle_Shadcn_,
Alert_Shadcn_,
Button,
Form,
InputNumber,
Modal,
} from 'ui'
import { WarningIcon } from 'ui-patterns/Icons/StatusIcons'
export interface DiskSizeConfigurationProps {
disabled?: boolean
}
const DiskSizeConfiguration = ({ disabled = false }: DiskSizeConfigurationProps) => {
const { project } = useProjectContext()
const organization = useSelectedOrganization()
const { ref: projectRef } = useParams()
const { project } = useProjectContext()
const { lastDatabaseResizeAt } = project ?? {}
const organization = useSelectedOrganization()
const timeTillNextAvailableDatabaseResize =
lastDatabaseResizeAt === null ? 0 : 6 * 60 - dayjs().diff(lastDatabaseResizeAt, 'minutes')
const isAbleToResizeDatabase = timeTillNextAvailableDatabaseResize <= 0
@@ -68,9 +80,9 @@ const DiskSizeConfiguration = ({ disabled = false }: DiskSizeConfigurationProps)
const diskSizeValidationSchema = object({
'new-disk-size': number()
.required('Please enter a GB amount you want to resize the disk up to.')
.moreThan(Number(currentDiskSize ?? 0), `Must be more than ${currentDiskSize} GB`)
.min(Number(currentDiskSize ?? 0), `Must be more than ${currentDiskSize} GB`)
// to do, update with max_disk_volume_size_gb
.lessThan(Number(maxDiskSize), 'Must be no more than 200 GB'),
.max(Number(maxDiskSize), 'Must not be more than 200 GB'),
})
return (
@@ -129,51 +141,33 @@ const DiskSizeConfiguration = ({ disabled = false }: DiskSizeConfigurationProps)
</div>
</Panel.Content>
</Panel>
<Alert withIcon variant="info" title={'Importing a lot of data?'}>
<p className=" max-w-2xl">
We auto-scale your disk as you need more storage, but can only do this every 6 hours.
If you upload more than 1.5x the current size of your storage, your database will go
into read-only mode. If you know how big your database is going to be, you can
manually increase the size here.
</p>
<Alert_Shadcn_>
<Info size={16} />
<AlertTitle_Shadcn_>Importing a lot of data?</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
<Markdown
className="max-w-full"
content={`
We auto-scale your disk as you need more storage, but can only do this every 6 hours.
If you upload more than 1.5x the current size of your storage, your database will go
into read-only mode. If you know how big your database is going to be, you can
manually increase the size here.
<p className="mt-4">
Read more about{' '}
<a
className="underline"
href="https://supabase.com/docs/guides/platform/database-size#disk-management"
>
disk management
</a>
.
</p>
</Alert>
Read more about [disk management](https://supabase.com/docs/guides/platform/database-size#disk-management).
`}
/>
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
</div>
) : (
<Alert
withIcon
variant="info"
title={
projectSubscriptionData?.plan?.id === 'free'
<Alert_Shadcn_>
<Info size={16} />
<AlertTitle_Shadcn_>
{projectSubscriptionData?.plan?.id === 'free'
? 'Disk size configuration is not available for projects on the Free plan'
: 'Disk size configuration is only available when disabling the spend cap.'
}
actions={
<Button asChild type="default">
<Link
href={`/org/${organization?.slug}/billing?panel=${
projectSubscriptionData?.plan?.id === 'free' ? 'subscriptionPlan' : 'costControl'
}`}
target="_blank"
>
{projectSubscriptionData?.plan?.id === 'free'
? 'Upgrade subscription'
: 'Disable spend cap'}
</Link>
</Button>
}
>
<div>
: 'Disk size configuration is only available when the spend cap has been disabled'}
</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
{projectSubscriptionData?.plan?.id === 'free' ? (
<p>
If you are intending to use more than 500MB of disk space, then you will need to
@@ -185,8 +179,20 @@ const DiskSizeConfiguration = ({ disabled = false }: DiskSizeConfigurationProps)
disable your spend cap.
</p>
)}
</div>
</Alert>
<Button asChild type="default" className="mt-3">
<Link
href={`/org/${organization?.slug}/billing?panel=${
projectSubscriptionData?.plan?.id === 'free' ? 'subscriptionPlan' : 'costControl'
}`}
target="_blank"
>
{projectSubscriptionData?.plan?.id === 'free'
? 'Upgrade subscription'
: 'Disable spend cap'}
</Link>
</Button>
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
)}
<Modal
@@ -205,29 +211,42 @@ const DiskSizeConfiguration = ({ disabled = false }: DiskSizeConfigurationProps)
>
{() =>
currentDiskSize >= maxDiskSize ? (
<>
<Alert withIcon variant="warning" title="Maximum manual disk size increase reached">
You cannot manually expand the disk size any more than {maxDiskSize} GB. If you
need more than this, contact us to learn more about the Enterprise plan.
</Alert>
</>
<Alert_Shadcn_ variant="warning" className="rounded-t-none border-0">
<WarningIcon />
<AlertTitle_Shadcn_>Maximum manual disk size increase reached</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
<p>
You cannot manually expand the disk size any more than {maxDiskSize}GB. If you
need more than this, contact us via support for help.
</p>
<Button asChild type="default" className="mt-3">
<Link
href={`/support/new?ref=${projectRef}&category=${SupportCategories.PERFORMANCE_ISSUES}&subject=Increase%20disk%20size%20beyond%20200GB`}
>
Contact support
</Link>
</Button>
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
) : (
<>
<Modal.Content>
<div className="w-full space-y-4 py-6">
<Alert
withIcon
variant={isAbleToResizeDatabase ? 'info' : 'warning'}
title="This operation is only possible every 6 hours"
>
{isAbleToResizeDatabase
? `Upon updating your disk size, the next disk size update will only be available from ${dayjs().format(
'DD MMM YYYY, HH:mm (ZZ)'
)}`
: `Your database was last resized at ${dayjs(lastDatabaseResizeAt).format(
'DD MMM YYYY, HH:mm (ZZ)'
)}. You can resize your database again in approximately ${formattedTimeTillNextAvailableResize}`}
</Alert>
<Alert_Shadcn_ variant={isAbleToResizeDatabase ? 'default' : 'warning'}>
<Info size={16} />
<AlertTitle_Shadcn_>
This operation is only possible every 6 hours
</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
{isAbleToResizeDatabase
? `Upon updating your disk size, the next disk size update will only be available from ${dayjs().format(
'DD MMM YYYY, HH:mm (ZZ)'
)}`
: `Your database was last resized at ${dayjs(lastDatabaseResizeAt).format(
'DD MMM YYYY, HH:mm (ZZ)'
)}. You can resize your database again in approximately ${formattedTimeTillNextAvailableResize}`}
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
<InputNumber
required
id="new-disk-size"
@@ -155,7 +155,7 @@ const InfrastructureInfo = () => {
<Input
readOnly
disabled
label="GoTrue version"
label="Auth version"
value={project?.serviceVersions?.gotrue ?? ''}
/>
)}
@@ -20,7 +20,7 @@ const ForgotPasswordForm = () => {
const { mutate: resetPassword, isLoading } = useResetPasswordMutation({
onSuccess: async () => {
toast.success(
`If you registered using your email and password, you will receive a password reset email.`
`If you registered using your email and password, you will receive a password reset email. The password reset link expires in 10 minutes.`
)
await router.push('/sign-in')
},
@@ -60,7 +60,7 @@ const SignUpForm = () => {
>
<Alert className="w-full" withIcon variant="success" title="Check your email to confirm">
You've successfully signed up. Please check your email to confirm your account before
signing in to the Supabase dashboard
signing in to the Supabase dashboard. The confirmation link expires in 10 minutes.
</Alert>
</div>
<Form
@@ -9,10 +9,6 @@ import {
Alert_Shadcn_,
Button,
Checkbox,
IconAlertCircle,
IconAlertTriangle,
IconExternalLink,
IconPlus,
Input,
SidePanel,
Toggle,
@@ -31,7 +27,9 @@ import {
} from 'data/database/foreign-key-constraints-query'
import { useEnumeratedTypesQuery } from 'data/enumerated-types/enumerated-types-query'
import { EXCLUDED_SCHEMAS_WITHOUT_EXTENSIONS } from 'lib/constants/schemas'
import { ExternalLink, Plus } from 'lucide-react'
import type { Dictionary } from 'types'
import { WarningIcon } from 'ui-patterns/Icons/StatusIcons'
import ActionBar from '../ActionBar'
import type { ForeignKey } from '../ForeignKeySelector/ForeignKeySelector.types'
import { formatForeignKeys } from '../ForeignKeySelector/ForeignKeySelector.utils'
@@ -52,7 +50,6 @@ import {
import ColumnForeignKey from './ColumnForeignKey'
import ColumnType from './ColumnType'
import HeaderTitle from './HeaderTitle'
import toast from 'react-hot-toast'
export interface ColumnEditorProps {
column?: PostgresColumn
@@ -142,6 +139,22 @@ const ColumnEditor = ({
changes.defaultValue = null
}
const changedName = 'name' in changes && changes.name !== columnFields.name
if (
changedName &&
fkRelations.find((fk) => fk.columns.find(({ source }) => source === columnFields?.name))
) {
setFkRelations(
fkRelations.map((relation) => ({
...relation,
columns: relation.columns.map((col) =>
col.source === columnFields?.name ? { ...col, source: changes.name! } : col
),
}))
)
}
const updatedColumnFields = { ...columnFields, ...changes } as ColumnField
setColumnFields(updatedColumnFields)
updateEditorDirty()
@@ -225,7 +238,7 @@ const ColumnEditor = ({
asChild
type="default"
size="tiny"
icon={<IconPlus size={14} strokeWidth={2} />}
icon={<Plus size={14} strokeWidth={2} />}
>
<Link href={`/project/${ref}/database/types`} target="_blank" rel="noreferrer">
Create enum types
@@ -235,7 +248,7 @@ const ColumnEditor = ({
asChild
type="default"
size="tiny"
icon={<IconExternalLink size={14} strokeWidth={2} />}
icon={<ExternalLink size={14} strokeWidth={2} />}
>
<Link
href="https://supabase.com/docs/guides/database/tables#data-types"
@@ -361,7 +374,7 @@ const ColumnEditor = ({
>
<FormSectionContent loading={false} className="lg:!col-span-8">
<Alert_Shadcn_>
<IconAlertCircle />
<WarningIcon />
<AlertTitle_Shadcn_>
Column encryption has been removed from the GUI
</AlertTitle_Shadcn_>
@@ -376,7 +389,7 @@ const ColumnEditor = ({
</Link>{' '}
Transparent Column Encryption (TCE).
</p>
<Button asChild type="default" icon={<IconExternalLink />} className="mt-2">
<Button asChild type="default" icon={<ExternalLink />} className="mt-2">
<Link
target="_blank"
rel="noreferrer"
@@ -391,16 +404,16 @@ const ColumnEditor = ({
</FormSection>
<SidePanel.Separator />
{/* TODO: need to pull column privileges in here
if any columns are using column-level privileges, show this warning */}
<FormSection
{/* TODO: need to pull column privileges in here if any columns are using column-level privileges, show this warning */}
{/* [Joshen] This shouldn't show up for all tables */}
{/* <FormSection
header={
<FormSectionLabel className="lg:!col-span-4">Column privileges</FormSectionLabel>
}
>
<FormSectionContent loading={false} className="lg:!col-span-8">
<Alert_Shadcn_ variant="warning">
<IconAlertTriangle strokeWidth={2} />
<WarningIcon />
<AlertTitle_Shadcn_>This table uses column-privileges</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
<p>
@@ -417,7 +430,7 @@ const ColumnEditor = ({
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
</FormSectionContent>
</FormSection>
</FormSection> */}
</>
)}
</SidePanel>
@@ -111,7 +111,10 @@ const ColumnForeignKey = ({
columns:
column.isNewColumn && column.name
? (table.columns as any[]).concat(column)
: (table.columns as any[]),
: (table.columns as any[]).map((c) => {
if (c.id === column.id) return { ...c, name: column.name }
else return c
}),
}}
foreignKey={selectedFk}
onClose={() => {
@@ -351,7 +351,6 @@ const SidePanelEditor = ({
})
}
} catch (error: any) {
console.log({ error })
toast.error(`Failed to update realtime for ${table.name}: ${error.message}`)
}
}
@@ -166,8 +166,7 @@ export const getAddForeignKeySQL = ({
const onUpdateSql = getOnUpdateSql(updateAction)
return `
ALTER TABLE "${table.schema}"."${table.name}"
ADD CONSTRAINT "${table.schema}_${table.name}_${relation.columns.map((x) => x.source).join('_')}_fkey"
FOREIGN KEY (${relation.columns.map((column) => `"${column.source}"`).join(',')})
ADD FOREIGN KEY (${relation.columns.map((column) => `"${column.source}"`).join(',')})
REFERENCES "${relation.schema}"."${relation.table}" (${relation.columns.map((column) => `"${column.target}"`).join(',')})
${onUpdateSql}
${onDeleteSql}
@@ -1,13 +1,33 @@
import { noop } from 'lodash'
import { Checkbox, IconMenu, IconSettings, IconX, Input, Popover } from 'ui'
import { Link, Menu, Plus, Settings, X } from 'lucide-react'
import {
Badge,
Button,
Checkbox,
CommandGroup_Shadcn_,
CommandItem_Shadcn_,
CommandList_Shadcn_,
CommandSeparator_Shadcn_,
Command_Shadcn_,
Input,
Popover,
PopoverContent_Shadcn_,
PopoverTrigger_Shadcn_,
Popover_Shadcn_,
cn,
} from 'ui'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { useForeignKeyConstraintsQuery } from 'data/database/foreign-key-constraints-query'
import type { EnumeratedType } from 'data/enumerated-types/enumerated-types-query'
import { EMPTY_ARR, EMPTY_OBJ } from 'lib/void'
import { useState } from 'react'
import { typeExpressionSuggestions } from '../ColumnEditor/ColumnEditor.constants'
import type { Suggestion } from '../ColumnEditor/ColumnEditor.types'
import ColumnType from '../ColumnEditor/ColumnType'
import InputWithSuggestions from '../ColumnEditor/InputWithSuggestions'
import { ForeignKey } from '../ForeignKeySelector/ForeignKeySelector.types'
import type { ColumnField } from '../SidePanelEditor.types'
import { checkIfRelationChanged } from './ForeignKeysManagement/ForeignKeysManagement.utils'
/**
* [Joshen] For context:
@@ -29,6 +49,7 @@ import type { ColumnField } from '../SidePanelEditor.types'
interface ColumnProps {
column: ColumnField
relations: ForeignKey[]
enumTypes: EnumeratedType[]
isNewRecord: boolean
hasForeignKeys: boolean
@@ -36,18 +57,23 @@ interface ColumnProps {
dragHandleProps?: any
onUpdateColumn: (changes: Partial<ColumnField>) => void
onRemoveColumn: () => void
onEditForeignKey: (relation?: ForeignKey) => void
}
const Column = ({
column = EMPTY_OBJ as ColumnField,
relations = EMPTY_ARR as ForeignKey[],
enumTypes = EMPTY_ARR as EnumeratedType[],
isNewRecord = false,
hasForeignKeys = false,
hasImportContent = false,
dragHandleProps = EMPTY_OBJ,
onUpdateColumn = noop,
onRemoveColumn = noop,
onUpdateColumn,
onRemoveColumn,
onEditForeignKey,
}: ColumnProps) => {
const { project } = useProjectContext()
const [open, setOpen] = useState(false)
const suggestions: Suggestion[] = typeExpressionSuggestions?.[column.format] ?? []
const settingsCount = [
@@ -57,26 +83,142 @@ const Column = ({
column.isArray ? 1 : 0,
].reduce((a, b) => a + b, 0)
const { data } = useForeignKeyConstraintsQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
schema: column.schema,
})
const getRelationStatus = (fk: ForeignKey) => {
const existingRelation = (data ?? []).find((x) => x.id === fk.id)
const stateRelation = relations.find((x) => x.id === fk.id)
if (stateRelation?.toRemove) return 'REMOVE'
if (existingRelation === undefined && stateRelation !== undefined) return 'ADD'
if (existingRelation !== undefined && stateRelation !== undefined) {
const hasUpdated = checkIfRelationChanged(existingRelation, stateRelation)
if (hasUpdated) return 'UPDATE'
else return undefined
}
}
const hasChangesInRelations = relations
.map((r) => getRelationStatus(r))
.some((x) => x !== undefined)
return (
<div className="flex w-full items-center">
<div className={`w-[5%] ${!isNewRecord ? 'hidden' : ''}`}>
<div className="cursor-drag" {...dragHandleProps}>
<IconMenu strokeWidth={1} size={15} />
<Menu strokeWidth={1} size={16} />
</div>
</div>
<div className="w-[25%]">
<div className="flex w-[95%] items-center justify-between">
<Input
value={column.name}
size="small"
value={column.name}
title={column.name}
disabled={hasImportContent}
placeholder="column_name"
className={`table-editor-columns-input bg-surface-100 lg:gap-0 ${
className={cn(
'[&>div>div>div>input]:py-1.5 [&>div>div>div>input]:border-r-transparent [&>div>div>div>input]:rounded-r-none',
hasImportContent ? 'opacity-50' : ''
} rounded-md`}
)}
onChange={(event: any) => onUpdateColumn({ name: event.target.value })}
/>
{relations.filter((r) => !r.toRemove).length === 0 ? (
<Button
type="dashed"
className="rounded-l-none h-[30px] py-0 px-2"
onClick={() => onEditForeignKey()}
>
<Link size={12} />
</Button>
) : (
<Popover_Shadcn_ open={open} onOpenChange={setOpen} modal={false}>
<PopoverTrigger_Shadcn_ asChild>
<Button type="default" className="rounded-l-none h-[30px] py-0 px-2">
<Link size={12} />
</Button>
</PopoverTrigger_Shadcn_>
<PopoverContent_Shadcn_
className={cn('p-0', hasChangesInRelations ? 'w-96' : 'w-72')}
side="bottom"
align="end"
>
<div className="text-xs px-2 pt-2">
Involved in {relations.length} foreign key{relations.length > 1 ? 's' : ''}
</div>
<Command_Shadcn_>
<CommandList_Shadcn_>
<CommandGroup_Shadcn_>
{relations.map((relation, idx) => {
const key = String(relation?.id ?? `${column.id}-relation-${idx}`)
const status = getRelationStatus(relation)
if (status === 'REMOVE') return null
return (
<CommandItem_Shadcn_
key={key}
value={key}
className="cursor-pointer w-full"
onSelect={() => onEditForeignKey(relation)}
onClick={() => onEditForeignKey(relation)}
>
{status === undefined ? (
<div className="w-full flex items-center justify-between truncate">
{relation.name}
</div>
) : (
<div className="flex items-center gap-x-2 truncate">
<Badge variant={status === 'ADD' ? 'brand' : 'warning'}>
{status}
</Badge>
<p className="truncate">
{relation.name || (
<>
To{' '}
{relation.columns
.filter((c) => c.source === column.name)
.map((c) => {
return (
<code key={`${c.source}-${c.target}`}>
{relation.schema}.{relation.table}.{c.target}
</code>
)
})}
{relation.columns.length > 1 && (
<>
and {relation.columns.length - 1} other column
{relation.columns.length > 2 ? 's' : ''}
</>
)}
</>
)}
</p>
</div>
)}
</CommandItem_Shadcn_>
)
})}
</CommandGroup_Shadcn_>
<CommandSeparator_Shadcn_ />
<CommandGroup_Shadcn_>
<CommandItem_Shadcn_
className="cursor-pointer w-full gap-x-2"
onSelect={() => onEditForeignKey()}
onClick={() => onEditForeignKey()}
>
<Plus size={14} strokeWidth={1.5} />
<p>Add foreign key relation</p>
</CommandItem_Shadcn_>
</CommandGroup_Shadcn_>
</CommandList_Shadcn_>
</Command_Shadcn_>
</PopoverContent_Shadcn_>
</Popover_Shadcn_>
)}
</div>
</div>
<div className="w-[25%]">
@@ -200,12 +342,12 @@ const Column = ({
>
<div className="group flex items-center -space-x-1">
{settingsCount > 0 && (
<div className="rounded-full bg-foreground py-0.5 px-2 text-xs text-background">
<div className="rounded-full bg-foreground h-4 w-4 flex items-center justify-center text-xs text-background">
{settingsCount}
</div>
)}
<div className="text-foreground-light transition-colors group-hover:text-foreground">
<IconSettings size={18} strokeWidth={1} />
<Settings size={16} strokeWidth={1} />
</div>
</div>
</Popover>
@@ -215,7 +357,7 @@ const Column = ({
{!hasImportContent && (
<div className="flex w-[5%] justify-end">
<button className="cursor-pointer" onClick={() => onRemoveColumn()}>
<IconX strokeWidth={1} />
<X size={16} strokeWidth={1} />
</button>
</div>
)}
@@ -1,5 +1,7 @@
import * as Tooltip from '@radix-ui/react-tooltip'
import { isEmpty, noop, partition } from 'lodash'
import { HelpCircle, Key } from 'lucide-react'
import { useState } from 'react'
import {
DragDropContext,
Draggable,
@@ -7,18 +9,29 @@ import {
Droppable,
DroppableProvided,
} from 'react-beautiful-dnd'
import { Alert, Button, IconEdit, IconExternalLink, IconHelpCircle, IconKey, IconTrash } from 'ui'
import InformationBox from 'components/ui/InformationBox'
import type { EnumeratedType } from 'data/enumerated-types/enumerated-types-query'
import {
AlertDescription_Shadcn_,
AlertTitle_Shadcn_,
Alert_Shadcn_,
Button,
IconEdit,
IconExternalLink,
IconTrash,
} from 'ui'
import { WarningIcon } from 'ui-patterns/Icons/StatusIcons'
import { generateColumnField } from '../ColumnEditor/ColumnEditor.utils'
import { ForeignKeySelector } from '../ForeignKeySelector/ForeignKeySelector'
import type { ForeignKey } from '../ForeignKeySelector/ForeignKeySelector.types'
import { TEXT_TYPES } from '../SidePanelEditor.constants'
import type { ColumnField, ExtendedPostgresRelationship } from '../SidePanelEditor.types'
import Column from './Column'
import type { ImportContent } from './TableEditor.types'
import type { ImportContent, TableField } from './TableEditor.types'
interface ColumnManagementProps {
table: TableField
columns?: ColumnField[]
relations: ForeignKey[]
enumTypes: EnumeratedType[]
@@ -27,9 +40,11 @@ interface ColumnManagementProps {
onColumnsUpdated: (columns: ColumnField[]) => void
onSelectImportData: () => void
onClearImportContent: () => void
onUpdateFkRelations: (relations: ForeignKey[]) => void
}
const ColumnManagement = ({
table,
columns = [],
relations,
enumTypes = [],
@@ -38,7 +53,12 @@ const ColumnManagement = ({
onColumnsUpdated = noop,
onSelectImportData = noop,
onClearImportContent = noop,
onUpdateFkRelations,
}: ColumnManagementProps) => {
const [open, setOpen] = useState(false)
const [selectedColumn, setSelectedColumn] = useState<ColumnField>()
const [selectedFk, setSelectedFk] = useState<ForeignKey>()
const hasImportContent = !isEmpty(importContent)
const [primaryKeyColumns, otherColumns] = partition(
columns,
@@ -154,20 +174,22 @@ const ColumnManagement = ({
)}
{primaryKeyColumns.length === 0 && (
<Alert title="Warning: No primary keys selected" variant="warning" withIcon>
Tables require at least one column as a primary key in order to uniquely identify each
row. Without a primary key, you will not be able to update or delete rows from the
table.
</Alert>
<Alert_Shadcn_ variant="warning">
<WarningIcon />
<AlertTitle_Shadcn_>Warning: No primary keys selected</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
Tables should have at least one column as the primary key to identify each row.
Without a primary key, you will not be able to update or delete rows from the table.
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
)}
{primaryKeyColumns.length > 1 && (
<InformationBox
block
icon={<IconKey className="text-white" size="large" />}
icon={<Key size={16} />}
title="Composite primary key selected"
description="The columns that you've selected will be grouped as a primary key, and will serve
as the unique identifier for the rows in your table"
description="The columns that you've selected will be grouped as a primary key, and will serve as the unique identifier for the rows in your table"
/>
)}
@@ -181,7 +203,7 @@ const ColumnManagement = ({
<Tooltip.Root delayDuration={0}>
<Tooltip.Trigger>
<h5 className="text-xs text-foreground-lighter">
<IconHelpCircle size={15} strokeWidth={1.5} />
<HelpCircle size={15} strokeWidth={1.5} />
</h5>
</Tooltip.Trigger>
<Tooltip.Portal>
@@ -211,7 +233,7 @@ const ColumnManagement = ({
<Tooltip.Root delayDuration={0}>
<Tooltip.Trigger>
<h5 className="text-xs text-foreground-lighter">
<IconHelpCircle size={15} strokeWidth={1.5} />
<HelpCircle size={15} strokeWidth={1.5} />
</h5>
</Tooltip.Trigger>
<Tooltip.Portal>
@@ -262,6 +284,9 @@ const ColumnManagement = ({
>
<Column
column={column}
relations={relations.filter((relation) => {
return relation.columns.some((x) => x.source === column.name)
})}
enumTypes={enumTypes}
hasForeignKeys={checkIfHaveForeignKeys(column)}
isNewRecord={isNewRecord}
@@ -269,6 +294,11 @@ const ColumnManagement = ({
dragHandleProps={draggableProvided.dragHandleProps}
onUpdateColumn={(changes) => onUpdateColumn(column, changes)}
onRemoveColumn={() => onRemoveColumn(column)}
onEditForeignKey={(fk) => {
setOpen(true)
setSelectedColumn(column)
if (fk) setSelectedFk(fk)
}}
/>
</div>
)}
@@ -294,6 +324,9 @@ const ColumnManagement = ({
<div ref={draggableProvided.innerRef} {...draggableProvided.draggableProps}>
<Column
column={column}
relations={relations.filter((relation) => {
return relation.columns.some((x) => x.source === column.name)
})}
enumTypes={enumTypes}
isNewRecord={isNewRecord}
hasForeignKeys={checkIfHaveForeignKeys(column)}
@@ -301,6 +334,11 @@ const ColumnManagement = ({
dragHandleProps={draggableProvided.dragHandleProps}
onUpdateColumn={(changes) => onUpdateColumn(column, changes)}
onRemoveColumn={() => onRemoveColumn(column)}
onEditForeignKey={(fk) => {
setOpen(true)
setSelectedColumn(column)
if (fk) setSelectedFk(fk)
}}
/>
</div>
)}
@@ -321,6 +359,31 @@ const ColumnManagement = ({
</div>
)}
</div>
<ForeignKeySelector
visible={open}
column={selectedColumn}
table={{ id: table.id, name: table.name, columns: table.columns }}
foreignKey={selectedFk}
onClose={() => {
setOpen(false)
setSelectedFk(undefined)
setSelectedColumn(undefined)
}}
onSaveRelation={(fk) => {
const existingRelationIds = relations.map((x) => x.id)
if (fk.id !== undefined && existingRelationIds.includes(fk.id)) {
onUpdateFkRelations(
relations.map((x) => {
if (x.id === fk.id) return fk
return x
})
)
} else {
onUpdateFkRelations(relations.concat([fk]))
}
}}
/>
</>
)
}
@@ -2,7 +2,8 @@ import clsx from 'clsx'
import { useParams } from 'common'
import Link from 'next/link'
import SVG from 'react-inlinesvg'
import { Badge, Button, IconArrowRight } from 'ui'
import { Badge, Button, cn } from 'ui'
import { ArrowRight } from 'lucide-react'
import { BASE_PATH } from 'lib/constants'
import type { ForeignKey } from '../../ForeignKeySelector/ForeignKeySelector.types'
@@ -89,8 +90,12 @@ export const ForeignKeyRow = ({
<div className="flex flex-col gap-y-1">
{foreignKey.columns.map((x, idx) => (
<div key={`relation-${idx}}`} className="flex items-center gap-x-2">
<code className="text-xs">{x.source}</code>
<IconArrowRight />
<code
className={cn('text-xs', (x?.source ?? '').length === 0 && 'text-foreground-light')}
>
{x.source || '[column_name]'}
</code>
<ArrowRight size={16} />
<code className="text-xs">
{foreignKey.schema}.{foreignKey.table}.{x.target}
</code>
@@ -1,9 +1,8 @@
import type { PostgresTable } from '@supabase/postgres-meta'
import { isEmpty, isUndefined, noop } from 'lodash'
import Link from 'next/link'
import { useEffect, useState } from 'react'
import toast from 'react-hot-toast'
import { Alert, Badge, Button, Checkbox, IconBookOpen, Input, Modal, SidePanel } from 'ui'
import { Alert, Badge, Button, Checkbox, Input, SidePanel } from 'ui'
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
@@ -20,6 +19,7 @@ import {
import { useEnumeratedTypesQuery } from 'data/enumerated-types/enumerated-types-query'
import { useIsFeatureEnabled } from 'hooks'
import { EXCLUDED_SCHEMAS_WITHOUT_EXTENSIONS } from 'lib/constants/schemas'
import { ExternalLink } from 'lucide-react'
import { useTableEditorStateSnapshot } from 'state/table-editor'
import { SpreadsheetImport } from '../'
import ActionBar from '../ActionBar'
@@ -306,23 +306,20 @@ const TableEditor = ({
title="Policies are required to query data"
>
<p>
You need to write an access policy before you can query data from this table. Without
a policy, querying this table will result in an <u>empty array</u> of results.
</p>
{isNewRecord && (
<p className="mt-3">You can create policies after you create this table.</p>
)}
<p className="mt-4">
<Button asChild type="default" icon={<IconBookOpen strokeWidth={1.5} />}>
<Link
href="https://supabase.com/docs/guides/auth/row-level-security"
target="_blank"
rel="noreferrer"
>
RLS Documentation
</Link>
</Button>
You need to create an access policy before you can query data from this table. Without
a policy, querying this table will return an{' '}
<u className="text-foreground">empty array</u> of results.{' '}
{isNewRecord ? 'You can create policies after saving this table.' : ''}
</p>
<Button asChild type="default" icon={<ExternalLink />} className="mt-4">
<a
target="_blank"
rel="noreferrer"
href="https://supabase.com/docs/guides/auth/row-level-security"
>
RLS Documentation
</a>
</Button>
</Alert>
) : (
<Alert
@@ -335,17 +332,15 @@ const TableEditor = ({
{tableFields.name ? `The table ${tableFields.name}` : 'Your table'} will be publicly
writable and readable
</p>
<p className="mt-4">
<Button asChild type="default" icon={<IconBookOpen strokeWidth={1.5} />}>
<Link
href="https://supabase.com/docs/guides/auth/row-level-security"
target="_blank"
rel="noreferrer"
>
RLS Documentation
</Link>
</Button>
</p>
<Button asChild type="default" icon={<ExternalLink />} className="mt-4">
<a
target="_blank"
rel="noreferrer"
href="https://supabase.com/docs/guides/auth/row-level-security"
>
RLS Documentation
</a>
</Button>
</Alert>
)}
{realtimeEnabled && (
@@ -363,6 +358,7 @@ const TableEditor = ({
<SidePanel.Content className="space-y-10 py-6">
{!isDuplicating && (
<ColumnManagement
table={tableFields}
columns={tableFields?.columns}
relations={fkRelations}
enumTypes={enumTypes}
@@ -374,6 +370,7 @@ const TableEditor = ({
onUpdateField({ columns: DEFAULT_COLUMNS })
setImportContent(undefined)
}}
onUpdateFkRelations={onUpdateFkRelations}
/>
)}
{isDuplicating && (
@@ -23,7 +23,6 @@ import { useBranchCreateMutation } from 'data/branches/branch-create-mutation'
import { useProjectUpgradeEligibilityQuery } from 'data/config/project-upgrade-eligibility-query'
import { useCheckGithubBranchValidity } from 'data/integrations/github-branch-check-query'
import { useGitHubConnectionsQuery } from 'data/integrations/github-connections-query'
import { useGitHubRepositoriesQuery } from 'data/integrations/github-repositories-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
import { useSelectedOrganization } from 'hooks'
@@ -43,14 +42,6 @@ const EnableBranchingModal = () => {
// and makes the validation run onChange instead. This is a workaround
const [isValid, setIsValid] = useState(false)
const {
data: repositories,
error: repositoriesError,
isLoading: isLoadingRepositories,
isSuccess: isSuccessRepositories,
isError: isErrorRepositories,
} = useGitHubRepositoriesQuery()
const {
data: connections,
error: connectionsError,
@@ -123,9 +114,9 @@ const EnableBranchingModal = () => {
defaultValues: { branchName: '' },
})
const isLoading = isLoadingRepositories
const isError = isErrorRepositories || isErrorUpgradeEligibility
const isSuccess = isSuccessRepositories && isSuccessUpgradeEligibility
const isLoading = isLoadingConnections || isLoadingUpgradeEligibility
const isError = isErrorConnections || isErrorUpgradeEligibility
const isSuccess = isSuccessConnections && isSuccessUpgradeEligibility
const canSubmit = form.getValues('branchName').length > 0 && !isChecking && isValid
const onSubmit = (data: z.infer<typeof FormSchema>) => {
@@ -187,11 +178,8 @@ const EnableBranchingModal = () => {
<>
<Modal.Separator />
<Modal.Content className="px-7 py-6">
{isErrorRepositories ? (
<AlertError
error={repositoriesError}
subject="Failed to retrieve repositories"
/>
{isErrorConnections ? (
<AlertError error={connectionsError} subject="Failed to retrieve connections" />
) : isErrorUpgradeEligibility ? (
<AlertError
error={upgradeEligibilityError}
@@ -111,6 +111,7 @@ const NavigationBar = () => {
<Link
href={IS_PLATFORM ? '/projects' : `/project/${projectRef}`}
className="mx-2 flex items-center h-[40px]"
onClick={onCloseNavigationIconLink}
>
<img
alt="Supabase"
@@ -8,24 +8,16 @@ import toast from 'react-hot-toast'
import { useParams } from 'common'
import { untitledSnippetTitle } from 'components/interfaces/SQLEditor/SQLEditor.constants'
import { createSqlSnippetSkeleton } from 'components/interfaces/SQLEditor/SQLEditor.utils'
import AlertError from 'components/ui/AlertError'
import { useContentDeleteMutation } from 'data/content/content-delete-mutation'
import { SqlSnippet, useSqlSnippetsQuery } from 'data/content/sql-snippets-query'
import { useCheckPermissions, useSelectedProject } from 'hooks'
import { uuidv4 } from 'lib/helpers'
import { useProfile } from 'lib/profile'
import { useSnippets, useSqlEditorStateSnapshot } from 'state/sql-editor'
import {
AlertDescription_Shadcn_,
AlertTitle_Shadcn_,
Alert_Shadcn_,
Button,
Modal,
TooltipContent_Shadcn_,
TooltipTrigger_Shadcn_,
Tooltip_Shadcn_,
} from 'ui'
import { ResponseError } from 'types'
import { Button, TooltipContent_Shadcn_, TooltipTrigger_Shadcn_, Tooltip_Shadcn_ } from 'ui'
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
import { WarningIcon } from 'ui-patterns/Icons/StatusIcons'
import {
InnerSideBarEmptyPanel,
InnerSideBarFilterSearchInput,
@@ -51,7 +43,7 @@ const SideBarContent = () => {
const [selectedQueries, setSelectedQueries] = useState<string[]>([])
const snap = useSqlEditorStateSnapshot()
const { isLoading, isSuccess } = useSqlSnippetsQuery(ref, {
const { isLoading, isSuccess, isError, error } = useSqlSnippetsQuery(ref, {
refetchOnWindowFocus: false,
staleTime: 300, // 5 minutes
onSuccess(data) {
@@ -205,37 +197,43 @@ const SideBarContent = () => {
return (
<>
<div className="mt-6">
{isLoading ? (
<InnerSideBarShimmeringLoaders />
) : isSuccess ? (
<div>
<div className="flex flex-col gap-4">
<Button
type="default"
className="justify-start mx-4"
onClick={() => handleNewQuery()}
icon={<Plus className="text-foreground-muted" strokeWidth={1} size={14} />}
>
New query
</Button>
<div className="flex flex-col gap-4">
<Button
type="default"
className="justify-start mx-4"
onClick={() => handleNewQuery()}
icon={<Plus className="text-foreground-muted" strokeWidth={1} size={14} />}
>
New query
</Button>
<div className="px-2">
<InnerSideMenuItem
title="Templates"
isActive={router.asPath === `/project/${ref}/sql/templates`}
href={`/project/${ref}/sql/templates`}
>
Templates
</InnerSideMenuItem>
<InnerSideMenuItem
title="Quickstarts"
isActive={router.asPath === `/project/${ref}/sql/quickstarts`}
href={`/project/${ref}/sql/quickstarts`}
>
Quickstarts
</InnerSideMenuItem>
</div>
<div className="px-2">
<InnerSideMenuItem
title="Templates"
isActive={router.asPath === `/project/${ref}/sql/templates`}
href={`/project/${ref}/sql/templates`}
>
Templates
</InnerSideMenuItem>
<InnerSideMenuItem
title="Quickstarts"
isActive={router.asPath === `/project/${ref}/sql/quickstarts`}
href={`/project/${ref}/sql/quickstarts`}
>
Quickstarts
</InnerSideMenuItem>
</div>
{isLoading && <InnerSideBarShimmeringLoaders />}
{isError && (
<div className="px-4">
<AlertError error={error as ResponseError} subject="Failed to load SQL snippets" />
</div>
)}
{isSuccess && (
<>
{snippets.length > 0 && (
<InnerSideBarFilters className="mx-2">
<InnerSideBarFilterSearchInput
@@ -343,9 +341,9 @@ const SideBarContent = () => {
</InnerSideMenuCollapsibleContent>
</InnerSideMenuCollapsible>
)}
</div>
</div>
) : null}
</>
)}
</div>
</div>
<ConfirmationModal
@@ -13,14 +13,8 @@ import { EmptyBucketModal } from 'components/to-be-cleaned/Storage/EmptyBucketMo
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import { useBucketsQuery } from 'data/storage/buckets-query'
import { useCheckPermissions, useSelectedProject } from 'hooks'
import {
AlertDescription_Shadcn_,
AlertTitle_Shadcn_,
Alert_Shadcn_,
Button,
IconEdit,
Menu,
} from 'ui'
import { ArrowUpRight, Edit } from 'lucide-react'
import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, Button, Menu } from 'ui'
import BucketRow from './BucketRow'
const StorageMenu = () => {
@@ -56,8 +50,8 @@ const StorageMenu = () => {
block
type="default"
icon={
<div className="text-foreground-lighter">
<IconEdit size={14} />
<div className="text-foreground-light">
<Edit size={14} />
</div>
}
disabled={!canCreateBuckets}
@@ -152,6 +146,14 @@ const StorageMenu = () => {
<p className="truncate">Policies</p>
</Menu.Item>
</Link>
<Link href={`/project/${ref}/settings/storage`}>
<Menu.Item rounded>
<div className="flex items-center justify-between">
<p className="truncate">Settings</p>
<ArrowUpRight strokeWidth={1} className="h-4 w-4" />
</div>
</Menu.Item>
</Link>
</div>
</div>
</Menu>
@@ -7,7 +7,7 @@ import toast from 'react-hot-toast'
import { useProjectSettingsQuery } from 'data/config/project-settings-query'
import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query'
import type { Bucket } from 'data/storage/buckets-query'
import { DEFAULT_PROJECT_API_SERVICE_ID } from 'lib/constants'
import { DEFAULT_PROJECT_API_SERVICE_ID, IS_PLATFORM } from 'lib/constants'
import { copyToClipboard } from 'lib/helpers'
import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore'
import { STORAGE_ROW_TYPES, STORAGE_VIEWS } from '../Storage.constants'
@@ -18,6 +18,7 @@ import FileExplorerHeader from './FileExplorerHeader'
import FileExplorerHeaderSelection from './FileExplorerHeaderSelection'
import MoveItemsModal from './MoveItemsModal'
import PreviewPane from './PreviewPane'
import { useProjectStorageConfigQuery } from 'data/config/project-storage-config-query'
interface StorageExplorerProps {
bucket: Bucket
@@ -58,6 +59,11 @@ const StorageExplorer = ({ bucket }: StorageExplorerProps) => {
const { ref } = useParams()
const { data: customDomainData } = useCustomDomainsQuery({ projectRef: ref })
const { data: projectSettings } = useProjectSettingsQuery({ projectRef: ref })
// [Joshen] This is to ensure that StorageExplorerStore can get the storage file size limit
// Will be better once we deprecate the mobx store entirely, which we will get there
useProjectStorageConfigQuery({ projectRef: ref }, { enabled: IS_PLATFORM })
const apiService = (projectSettings?.services ?? []).find(
(x) => x.app.id == DEFAULT_PROJECT_API_SERVICE_ID
)
@@ -30,7 +30,7 @@ const StorageSettings = () => {
isError,
} = useProjectStorageConfigQuery({ projectRef }, { enabled: IS_PLATFORM })
const { fileSizeLimit, isFreeTier } = config || {}
const { value, unit } = convertFromBytes(fileSizeLimit)
const { value, unit } = convertFromBytes(fileSizeLimit ?? 0)
const [selectedUnit, setSelectedUnit] = useState(unit)
let initialValues = { fileSizeLimit: value, unformattedFileSizeLimit: fileSizeLimit }
+2 -12947
View File
File diff suppressed because it is too large. Load diff
@@ -1,32 +1,29 @@
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { get } from 'lib/common/fetch'
import { get, handleError } from 'data/fetchers'
import { API_URL } from 'lib/constants'
import { configKeys } from './keys'
import type { ResponseError } from 'types'
import { components } from 'data/api'
export type ProjectStorageConfigVariables = {
projectRef?: string
}
// TODO: Add proper type
export type ProjectStorageConfigResponse = any
export type ProjectStorageConfigResponse = components['schemas']['StorageConfigResponse']
export async function getProjectStorageConfig(
{ projectRef }: ProjectStorageConfigVariables,
signal?: AbortSignal
) {
if (!projectRef) {
throw new Error('projectRef is required')
}
if (!projectRef) throw new Error('projectRef is required')
const response = await get(`${API_URL}/projects/${projectRef}/config/storage`, {
const { data, error } = await get('/platform/projects/{ref}/config/storage', {
params: { path: { ref: projectRef } },
signal,
})
if (response.error) {
throw response.error
}
return response as ProjectStorageConfigResponse
if (error) throw handleError(error)
return data
}
export type ProjectStorageConfigData = Awaited<ReturnType<typeof getProjectStorageConfig>>
Loaded 100 of 127 files, more files were not shown because too many files have changed in this diff. Show more