diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fe66ea7fb32..b5d3188a5b3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,7 @@ To ensure a positive and inclusive environment, please read our [code of conduc If you find a bug, please create an Issue and we’ll triage it. -- Please search [existing Issues](https://github.com/supabase/supabase) before creating a new one. +- Please search [existing Issues](https://github.com/supabase/supabase/issues) before creating a new one. - Please include a clear description of the problem along with steps to reproduce it. Exact steps with screenshots and urls really help here. ## Pull Requests @@ -23,7 +23,7 @@ We actively welcome your Pull Requests! A couple of things to keep in mind befor - If you’re fixing an Issue, make sure someone else hasn’t already created a PR fixing the same issue. Likewise, make sure to link your PR to the related Issue(s). - We will always try to accept the first viable PR that resolves the Issue. -- If you're new, we encourage you to take a look at issues tagged with [good first issue](https://github.com/supabase/supabase/pulls?q=is%3Aopen+is%3Apr+label%3A%22good+first+issue%22). +- If you're new, we encourage you to take a look at issues tagged with [good first issue](https://github.com/supabase/supabase/labels/good%20first%20issue). - If you’re submitting a new feature, make sure you have opened a [Discussion](https://github.com/orgs/supabase/discussions/new/choose) to discuss the new feature before opening a PR. We’d love to accept your hard work, but unfortunately if a feature hasn’t gone through a proper design process, your PR will be closed. - Please use the PR message template and provide detailed context for quicker review. PRs without clear problem statements will be closed. diff --git a/README.md b/README.md index a3cd29f08a3..d63f425b65d 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,15 @@ Our approach for client libraries is modular. Each sub-library is a standalone i storage-dart functions-dart + + Swift + supabase-swift + postgrest-swift + auth-swift + realtime-swift + storage-swift + functions-swift + 💚 Community 💚 @@ -177,15 +186,6 @@ Our approach for client libraries is modular. Each sub-library is a standalone i - - - - Swift - supabase-swift - postgrest-swift - gotrue-swift - realtime-swift - storage-swift - functions-swift - Godot Engine (GDScript) supabase-gdscript diff --git a/apps/database-new/package.json b/apps/database-new/package.json index ea879606f3d..d7ed811868e 100644 --- a/apps/database-new/package.json +++ b/apps/database-new/package.json @@ -19,10 +19,10 @@ "common": "*", "config": "*", "dayjs": "^1.11.10", - "eslint-config-next": "^14.1.4", + "eslint-config-next": "^14.2.0", "lodash": "^4.17.21", "lucide-react": "^0.338.0", - "next": "^14.1.4", + "next": "^14.2.0", "openai": "^4.20.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/apps/docs/components/Feedback/Feedback.tsx b/apps/docs/components/Feedback/Feedback.tsx index 0359b20e360..4694e338e20 100644 --- a/apps/docs/components/Feedback/Feedback.tsx +++ b/apps/docs/components/Feedback/Feedback.tsx @@ -16,6 +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' const FeedbackButton = forwardRef< HTMLButtonElement, @@ -89,7 +90,7 @@ function Feedback() { vote: response, page: pathname, metadata: { - query: Object.fromEntries(new URLSearchParams(window.location.search).entries()), + query: getSanitizedTabParams(), }, }) if (error) console.error(error) diff --git a/apps/docs/components/Feedback/Feedback.utils.ts b/apps/docs/components/Feedback/Feedback.utils.ts new file mode 100644 index 00000000000..bc3683490f0 --- /dev/null +++ b/apps/docs/components/Feedback/Feedback.utils.ts @@ -0,0 +1,16 @@ +import { pick } from 'lodash' + +/** + * Gets the tab selection state from the URL search params. + * + * Sanitizes by including only those search params that are explicitly marked + * as query groups. + */ +const getSanitizedTabParams = () => { + const searchParams = new URLSearchParams(window.location.search) + const queryGroups = searchParams.getAll('queryGroups') + + return pick(Object.fromEntries(searchParams.entries()), queryGroups) +} + +export { getSanitizedTabParams } diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index b80ebbd89e3..bf1e5bda20e 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -112,6 +112,12 @@ export const HOMEPAGE_MENU_ITEMS: HomepageMenuItems = [ href: '/reference/dart/introduction', level: 'reference_dart', }, + { + label: 'Swift', + icon: 'reference-swift', + href: '/reference/swift/introduction', + level: 'reference_swift', + }, { label: 'Python', icon: 'reference-python', @@ -126,13 +132,6 @@ export const HOMEPAGE_MENU_ITEMS: HomepageMenuItems = [ level: 'reference_csharp', community: true, }, - { - label: 'Swift', - icon: 'reference-swift', - href: '/reference/swift/introduction', - level: 'reference_swift', - community: true, - }, { label: 'Kotlin', icon: 'reference-kotlin', @@ -1140,6 +1139,10 @@ export const functions: NavMenuConstant = { name: 'Handling Routing in Functions', url: '/guides/functions/routing', }, + { + name: 'Running AI Models', + url: '/guides/functions/ai-models', + }, { name: 'Regional invocations', url: '/guides/functions/regional-invocation', @@ -1188,6 +1191,10 @@ export const functions: NavMenuConstant = { name: 'Generating OG images ', url: '/guides/functions/examples/og-image', }, + { + name: 'Semantic AI Search', + url: '/guides/functions/examples/semantic-search', + }, { name: 'CAPTCHA support with Cloudflare Turnstile', url: '/guides/functions/examples/cloudflare-turnstile', diff --git a/apps/docs/content/guides/ai/quickstarts/generate-text-embeddings.mdx b/apps/docs/content/guides/ai/quickstarts/generate-text-embeddings.mdx index 840fd114122..816b5ccaf2e 100644 --- a/apps/docs/content/guides/ai/quickstarts/generate-text-embeddings.mdx +++ b/apps/docs/content/guides/ai/quickstarts/generate-text-embeddings.mdx @@ -1,15 +1,11 @@ --- id: 'ai-generating-embeddings' title: 'Generate Embeddings' -subtitle: 'Generate text embeddings using Edge Functions + Transformer.js.' +subtitle: 'Generate text embeddings using Edge Functions.' breadcrumb: 'AI Quickstarts' --- -This guide will walk you through how to generate high quality text embeddings in [Edge Functions](/docs/guides/functions) using Transformers.js. Inference is performed directly in Edge Functions using open source Hugging Face models, so no external API is required. - -## What is Transformers.js? - -[Transformers.js](https://huggingface.co/docs/transformers.js/index) is a library that allows you to perform inference using the JavaScript runtime. You can run Hugging Face embedding models through Transformers.js directly in Supabase [Edge Functions](/docs/guides/functions). +This guide will walk you through how to generate high quality text embeddings in [Edge Functions](/docs/guides/functions) using its built-in AI inference API, so no external API is required. ## Build the Edge Function @@ -60,89 +56,45 @@ Let's build an Edge Function that will accept an input string and generate an em - + - Let's modify the Edge Function to import the Transformers.js client and configure it for the Deno runtime. + Let's create a new inference session to be used in the lifetime of this function. Multiple requests can use the same inference session. - ```ts ./supabase/functions/embed/index.ts - import { serve } from 'https://deno.land/std@0.168.0/http/server.ts' - import { env, pipeline } from 'https://cdn.jsdelivr.net/npm/@xenova/transformers@2.5.0' - - // Configuration for Deno runtime - env.useBrowserCache = false; - env.allowLocalModels = false; - ``` + Currently, only the `gte-small` (https://huggingface.co/Supabase/gte-small) text embedding model is supported in Supabase's Edge Runtime. + + ```ts ./supabase/functions/embed/index.ts + const session = new Supabase.ai.Session('gte-small'); + ``` + + - - - Next let's construct the `pipe()` function we'll use to generate the embeddings. We use the Transformer.js `pipeline()` function to create this. - - - - - - ```ts ./supabase/functions/embed/index.ts - const pipe = await pipeline( - 'feature-extraction', - 'Supabase/gte-small', - ); - ``` - - - - - - Note the two arguments we pass to `pipeline()`: - - 1. The first argument specifies the type of [inference task](https://huggingface.co/docs/transformers.js/index#tasks) to perform. `feature-extraction` is used for embedding generation. - 2. The second argument specifies which Hugging Face model to use for the embedding generation. Supabase officially supports models available on the [Supabase org](https://huggingface.co/Supabase). - - - - We intentially construct the pipeline before the `serve()` function. This allows us to reuse the pre-loaded model in future warm-start requests, significantly speeding up future queries. Note that you'll only notice this speedup after deploying your function to Supabase - requests in local development will always be a cold-start. - - - - - - - - - - Modify our `serve()` request handler to accept an `input` string from the POST request JSON body. + Modify our request handler to accept an `input` string from the POST request JSON body. - Finally let's generate the embedding by: - - 1. Calling `pipe()` to perform the embedding generation - 2. Extracting the embedding from the output as a number array - 3. Returning it as a JSON response + Then generate the embedding by calling `session.run(input)`. ```ts ./supabase/functions/embed/index.ts - serve(async (req) => { + Deno.serve(async (req) => { // Extract input string from JSON body const { input } = await req.json(); // Generate the embedding from the user input - const output = await pipe(input, { - pooling: 'mean', + const embedding = await session.run(input, { + mean_pool: true, normalize: true, }); - // Extract the embedding output - const embedding = Array.from(output.data); - // Return the embedding return new Response( JSON.stringify({ embedding }), @@ -155,16 +107,16 @@ Let's build an Edge Function that will accept an input string and generate an em - Note the two options we pass to `pipe()`: + Note the two options we pass to `session.run()`: - - `pooling`: The first option sets `pooling` to `mean`. Pooling referes to how token-level embedding representations are compressed into a single sentence embedding that reflects the meaning of the entire sentence. Average pooling is the most common type of pooling for sentence embeddings and is the method supported by Transformers.js. - - `normalize`: The second option tells Transformers.js to normalize the embedding vector so that it can be used with distance measures like dot product. A normalized vector means its length (magnitude) is 1 - also referred to as a unit vector. A vector is normalized by dividing each element by the vector's length (magnitude), which maintains its direction but changes its length to 1. + - `mean_pool`: The first option sets `pooling` to `mean`. Pooling referes to how token-level embedding representations are compressed into a single sentence embedding that reflects the meaning of the entire sentence. Average pooling is the most common type of pooling for sentence embeddings. + - `normalize`: The second option tells to normalize the embedding vector so that it can be used with distance measures like dot product. A normalized vector means its length (magnitude) is 1 - also referred to as a unit vector. A vector is normalized by dividing each element by the vector's length (magnitude), which maintains its direction but changes its length to 1. - + diff --git a/apps/docs/content/guides/ai/semantic-search.mdx b/apps/docs/content/guides/ai/semantic-search.mdx index a9a219f7213..44744441840 100644 --- a/apps/docs/content/guides/ai/semantic-search.mdx +++ b/apps/docs/content/guides/ai/semantic-search.mdx @@ -26,7 +26,7 @@ Embeddings are generated using a language model, and embeddings are compared to ## Embedding models -There are many embedding models available today. Some can be accessed through third-party APIs like [OpenAI](https://platform.openai.com/docs/guides/embeddings), where you send your text in the request and receive an embedding vector in the response. Others can run locally on your own compute, such as through Transformers.js for JavaScript implementations. For more information on local implementation, see [Generate embeddings](/docs/guides/ai/quickstarts/generate-text-embeddings). +There are many embedding models available today. Supabase Edge Functions has [built in support](/docs/guides/functions/examples/semantic-search) for the `gte-small` model. Others can be accessed through third-party APIs like [OpenAI](https://platform.openai.com/docs/guides/embeddings), where you send your text in the request and receive an embedding vector in the response. Others can run locally on your own compute, such as through Transformers.js for JavaScript implementations. For more information on local implementation, see [Generate embeddings](/docs/guides/ai/quickstarts/generate-text-embeddings). It's crucial to remember that when using embedding models with semantic search, you must use the same model for all embedding comparisons. Comparing embeddings created by different models will yield meaningless results. diff --git a/apps/docs/content/guides/api/rest/client-libs.mdx b/apps/docs/content/guides/api/rest/client-libs.mdx index f14d2f49bbd..15f03b0d442 100644 --- a/apps/docs/content/guides/api/rest/client-libs.mdx +++ b/apps/docs/content/guides/api/rest/client-libs.mdx @@ -13,6 +13,7 @@ Supabase provides client libraries for the REST and Realtime APIs. Some librarie | --------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | | Javascript/Typescript | [supabase-js](https://github.com/supabase/supabase-js) | [Docs](https://supabase.com/docs/reference/javascript/introduction) | | Dart/Flutter | [supabase-flutter](https://github.com/supabase/supabase-flutter/tree/main/packages/supabase_flutter) | [Docs](https://supabase.com/docs/reference/dart/introduction) | +| Swift | [supabase-swift](https://github.com/supabase/supabase-swift) | [Docs](https://supabase.com/docs/reference/swift/introduction) | ## Community libraries @@ -23,5 +24,4 @@ Supabase provides client libraries for the REST and Realtime APIs. Some librarie | Kotlin | [supabase-kt](https://github.com/supabase-community/supabase-kt) | [Docs](https://supabase.com/docs/reference/kotlin/introduction) | | Python | [supabase-py](https://github.com/supabase-community/supabase-py) | [Docs](https://supabase.com/docs/reference/python/initializing) | | Ruby | [supabase-rb](https://github.com/supabase-community/supabase-rb) | | -| Swift | [supabase-swift](https://github.com/supabase-community/supabase-swift) | [Docs](https://supabase.com/docs/reference/swift/introduction) | | Godot Engine (GDScript) | [supabase-gdscript](https://github.com/supabase-community/godot-engine.supabase) | | diff --git a/apps/docs/content/guides/auth/auth-anonymous.mdx b/apps/docs/content/guides/auth/auth-anonymous.mdx index bd85f55c619..4a3b4aa0ad9 100644 --- a/apps/docs/content/guides/auth/auth-anonymous.mdx +++ b/apps/docs/content/guides/auth/auth-anonymous.mdx @@ -4,13 +4,7 @@ title: 'Anonymous Sign-Ins' subtitle: 'Create and use anonymous users to authenticate with Supabase' --- -Enable Anonymous Sign-In to build apps which provide users an authenticated experience without requiring users to enter an email address, password, use an OAuth provider or provide any other PII (Personally Identifiable Information). Later, when ready, they can attach an authentication method to their account. - -It's most useful for: - -- E-commerce applications, such as shopping carts before check-out -- Full-feature demos without collecting personal information -- Temporary or throw-away accounts +[Enable Anonymous Sign-Ins](/dashboard/project/_/settings/auth) to build apps which provide users an authenticated experience without requiring users to enter an email address, password, use an OAuth provider or provide any other PII (Personally Identifiable Information). Later, when ready, the user can link an authentication method to their account. @@ -22,7 +16,11 @@ This is different from the `anon` API key which does not create a user and can b -To get started, [enable anonymous sign-ins](/dashboard/project/_/settings/auth) for your Supabase Project. +Anonymous sign-ins can be used to build: + +- E-commerce applications, such as shopping carts before check-out +- Full-feature demos without collecting personal information +- Temporary or throw-away accounts @@ -87,7 +85,7 @@ supabase.auth.signInAnonymously() ## Convert an anonymous user to a permanent user -Converting an anonymous user to a permanent user requires linking an identity to the user. +Converting an anonymous user to a permanent user requires [linking an identity](/docs/guides/auth/auth-identity-linking#manual-linking-beta) to the user. This requires you to [enable manual linking](/dashboard/project/_/settings/auth) in your Supabase project. ### Link an Email / Phone identity @@ -100,10 +98,16 @@ Converting an anonymous user to a permanent user requires linking an identity to > -You can use the [`updateUser()`](/docs/reference/javascript/auth-updateuser) method to link an email or phone identity to the anonymous user. +You can use the [`updateUser()`](/docs/reference/javascript/auth-updateuser) method to link an email or phone identity to the anonymous user. To add a password for the anonymous user, the user's email or phone number needs to be verified first. ```js const { data, error } = await supabase.auth.updateUser({ email: 'example@email.com' }) + +// verify the user's email by clicking on the email change link +// or entering the 6-digit OTP sent to the email address + +// once the user has been verified, update the password +const { data, error } = await supabase.auth.updateUser({ password: 'password' }) ``` @@ -129,10 +133,10 @@ try await supabase.auth.updateUser( -You can use the [`modifyUser()`](/docs/reference/kotlin/auth-updateuser) method to link an email or phone identity to the anonymous user. +You can use the [`updateUser()`](/docs/reference/kotlin/auth-updateuser) method to link an email or phone identity to the anonymous user. ```kotlin -supabase.auth.modifyUser { +supabase.auth.updateUser { email = "example@email.com" } ``` @@ -233,7 +237,7 @@ where is_anonymous is true and created_at < now() - interval '30 days'; ## Resources -- [Supabase Account - Free Plan OK](https://supabase.com) +- [Supabase - Get started for free](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [Supabase Flutter Client](https://github.com/supabase/supabase-flutter) - [Supabase Kotlin Client](https://github.com/supabase-community/supabase-kt) diff --git a/apps/docs/content/guides/auth/auth-email.mdx b/apps/docs/content/guides/auth/auth-email.mdx index ab2636abfe6..6491a642830 100644 --- a/apps/docs/content/guides/auth/auth-email.mdx +++ b/apps/docs/content/guides/auth/auth-email.mdx @@ -207,7 +207,7 @@ suspend fun logout() { ## Resources -- [Supabase Account - Free Plan OK](https://supabase.com) +- [Supabase - Get started for free](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [Supabase Flutter Client](https://github.com/supabase/supabase-flutter) - [Supabase Kotlin Client](https://github.com/supabase-community/supabase-kt) diff --git a/apps/docs/content/guides/auth/concepts/redirect-urls.mdx b/apps/docs/content/guides/auth/concepts/redirect-urls.mdx index 3f9f3aab8ad..665b94f13a0 100644 --- a/apps/docs/content/guides/auth/concepts/redirect-urls.mdx +++ b/apps/docs/content/guides/auth/concepts/redirect-urls.mdx @@ -76,6 +76,22 @@ const { data, error } = await supabase.auth.signInWithOAuth({ }) ``` +## Email templates when using `redirectTo` + +When using a `redirectTo` option, you may need to replace the `{{ .SiteURL }}` with `{{ .RedirectTo }}` in your email templates. See the [Email Templates guide](/docs/guides/auth/auth-email-templates) for more information. + +For example, change the following: + +```html + +Confirm your mail + + +Confirm your mail +``` + ## Mobile deep linking URIs For mobile applications you can use deep linking URIs. For example, for your `SITE_URL` you can specify something like `com.supabase://login-callback/` and for additional redirect URLs something like `com.supabase.staging://login-callback/` if needed. diff --git a/apps/docs/content/guides/auth/passwordless-login/auth-email-otp.mdx b/apps/docs/content/guides/auth/passwordless-login/auth-email-otp.mdx index 2605da556fc..9dc8599500a 100644 --- a/apps/docs/content/guides/auth/passwordless-login/auth-email-otp.mdx +++ b/apps/docs/content/guides/auth/passwordless-login/auth-email-otp.mdx @@ -157,7 +157,7 @@ If successful the user will now be logged in and you should receive a valid sess ## Resources -- [Supabase Account - Free Plan OK](https://supabase.com) +- [Supabase - Get started for free](https://supabase.com) - [Phone SMS & WhatsApp OTP](/docs/guides/auth/phone-login) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [Supabase Flutter Client](https://github.com/supabase/supabase-flutter) diff --git a/apps/docs/content/guides/auth/passwordless-login/auth-magic-link.mdx b/apps/docs/content/guides/auth/passwordless-login/auth-magic-link.mdx index 001cab6c545..ed62d006f12 100644 --- a/apps/docs/content/guides/auth/passwordless-login/auth-magic-link.mdx +++ b/apps/docs/content/guides/auth/passwordless-login/auth-magic-link.mdx @@ -158,6 +158,6 @@ suspend fun logout() { ## Resources -- [Supabase Account - Free Plan OK](https://supabase.com) +- [Supabase - Get started for free](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [Supabase Flutter Client](https://github.com/supabase/supabase-flutter) diff --git a/apps/docs/content/guides/auth/passwords.mdx b/apps/docs/content/guides/auth/passwords.mdx index eb6a54ff65e..7ed8a1471ff 100644 --- a/apps/docs/content/guides/auth/passwords.mdx +++ b/apps/docs/content/guides/auth/passwords.mdx @@ -149,10 +149,10 @@ try await supabase.auth.updateUser(user: UserAttributes(password: newPassword)) -To update the password, call the [`.modifyUser`](/docs/reference/kotlin/auth-updateuser) method with the new password. +To update the password, call the [`.updateUser`](/docs/reference/kotlin/auth-updateuser) method with the new password. ```kotlin -supabase.gotrue.modifyUser { +supabase.auth.updateUser { password = "new_password" } ``` diff --git a/apps/docs/content/guides/auth/server-side/oauth-with-pkce-flow-for-ssr.mdx b/apps/docs/content/guides/auth/server-side/oauth-with-pkce-flow-for-ssr.mdx index 1b8ca766dfc..40af43d1fde 100644 --- a/apps/docs/content/guides/auth/server-side/oauth-with-pkce-flow-for-ssr.mdx +++ b/apps/docs/content/guides/auth/server-side/oauth-with-pkce-flow-for-ssr.mdx @@ -237,7 +237,7 @@ In the server, you need to handle the redirect to the OAuth provider's authentic const { data, error } = await supabase.auth.signInWithOAuth({ provider, options: { - redirectTo: '/auth/callback', + redirectTo: 'http://example.com/auth/callback', }, }) diff --git a/apps/docs/content/guides/auth/social-login/auth-bitbucket.mdx b/apps/docs/content/guides/auth/social-login/auth-bitbucket.mdx index 1cdde00d182..5b41ce54404 100644 --- a/apps/docs/content/guides/auth/social-login/auth-bitbucket.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-bitbucket.mdx @@ -112,6 +112,6 @@ suspend fun signOut() { ## Resources -- [Supabase Account - Free Plan OK](https://supabase.com) +- [Supabase - Get started for free](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [Bitbucket Account](https://bitbucket.org) diff --git a/apps/docs/content/guides/auth/social-login/auth-discord.mdx b/apps/docs/content/guides/auth/social-login/auth-discord.mdx index 1c15e17249e..2154fd37eb8 100644 --- a/apps/docs/content/guides/auth/social-login/auth-discord.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-discord.mdx @@ -113,7 +113,7 @@ suspend fun signOut() { ## Resources -- [Supabase Account - Free Plan OK](https://supabase.com) +- [Supabase - Get started for free](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [Discord Account](https://discord.com) - [Discord Developer Portal](https://discord.com/developers) diff --git a/apps/docs/content/guides/auth/social-login/auth-facebook.mdx b/apps/docs/content/guides/auth/social-login/auth-facebook.mdx index b195edf22d2..f15eb331425 100644 --- a/apps/docs/content/guides/auth/social-login/auth-facebook.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-facebook.mdx @@ -131,6 +131,6 @@ You can read more about App Review [here](https://developers.facebook.com/docs/a ## Resources -- [Supabase Account - Free Plan OK](https://supabase.com) +- [Supabase - Get started for free](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [Facebook Developers Dashboard](https://developers.facebook.com/) diff --git a/apps/docs/content/guides/auth/social-login/auth-figma.mdx b/apps/docs/content/guides/auth/social-login/auth-figma.mdx index 0db6671df23..1f982fb668c 100644 --- a/apps/docs/content/guides/auth/social-login/auth-figma.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-figma.mdx @@ -112,6 +112,6 @@ suspend fun signOut() { ## Resources -- [Supabase Account - Free Plan OK](https://supabase.com) +- [Supabase - Get started for free](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [Figma Developers page](https://www.figma.com/developers) diff --git a/apps/docs/content/guides/auth/social-login/auth-github.mdx b/apps/docs/content/guides/auth/social-login/auth-github.mdx index c0ffc5e8f04..0a2883ac3d8 100644 --- a/apps/docs/content/guides/auth/social-login/auth-github.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-github.mdx @@ -123,6 +123,6 @@ suspend fun signOut() { ## Resources -- [Supabase Account - Free Plan OK](https://supabase.com) +- [Supabase - Get started for free](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [GitHub Developer Settings](https://github.com/settings/developers) diff --git a/apps/docs/content/guides/auth/social-login/auth-gitlab.mdx b/apps/docs/content/guides/auth/social-login/auth-gitlab.mdx index 6dd71afa9be..3ffcfd43354 100644 --- a/apps/docs/content/guides/auth/social-login/auth-gitlab.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-gitlab.mdx @@ -109,6 +109,6 @@ suspend fun signOut() { ## Resources -- [Supabase Account - Free Plan OK](https://supabase.com) +- [Supabase - Get started for free](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [GitLab Account](https://gitlab.com) diff --git a/apps/docs/content/guides/auth/social-login/auth-linkedin.mdx b/apps/docs/content/guides/auth/social-login/auth-linkedin.mdx index f6ed6125535..865e7ba6ee5 100644 --- a/apps/docs/content/guides/auth/social-login/auth-linkedin.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-linkedin.mdx @@ -135,6 +135,6 @@ Do reach out to support if you have any concerns around this change. ## Resources -- [Supabase Account - Free Plan OK](https://supabase.com) +- [Supabase - Get started for free](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [LinkedIn Developer Dashboard](https://api.LinkedIn.com/apps) diff --git a/apps/docs/content/guides/auth/social-login/auth-notion.mdx b/apps/docs/content/guides/auth/social-login/auth-notion.mdx index 0c414ab4fdb..9b7a079155d 100644 --- a/apps/docs/content/guides/auth/social-login/auth-notion.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-notion.mdx @@ -112,7 +112,7 @@ suspend fun signOut() { ## Resources -- [Supabase Account - Free Plan OK](https://supabase.com) +- [Supabase - Get started for free](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [Notion Account](https://notion.so) - [Notion Developer Portal](https://www.notion.so/my-integrations) diff --git a/apps/docs/content/guides/auth/social-login/auth-slack.mdx b/apps/docs/content/guides/auth/social-login/auth-slack.mdx index 238b394b9d3..e1022ee7562 100644 --- a/apps/docs/content/guides/auth/social-login/auth-slack.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-slack.mdx @@ -130,6 +130,6 @@ suspend fun signOut() { ## Resources -- [Supabase Account - Free Plan OK](https://supabase.com) +- [Supabase - Get started for free](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [Slack Developer Dashboard](https://api.slack.com/apps) diff --git a/apps/docs/content/guides/auth/social-login/auth-spotify.mdx b/apps/docs/content/guides/auth/social-login/auth-spotify.mdx index c74d07bb473..071cf0f8b52 100644 --- a/apps/docs/content/guides/auth/social-login/auth-spotify.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-spotify.mdx @@ -124,6 +124,6 @@ suspend fun signOut() { ## Resources -- [Supabase Account - Free Plan OK](https://supabase.com) +- [Supabase - Get started for free](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [Spotify Developer Dashboard](https://developer.spotify.com/dashboard/) diff --git a/apps/docs/content/guides/auth/social-login/auth-twitch.mdx b/apps/docs/content/guides/auth/social-login/auth-twitch.mdx index 405477b5da4..a968201c747 100644 --- a/apps/docs/content/guides/auth/social-login/auth-twitch.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-twitch.mdx @@ -127,7 +127,7 @@ suspend fun signOut() { ## Resources -- [Supabase Account - Free Plan OK](https://supabase.com) +- [Supabase - Get started for free](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [Twitch Account](https://twitch.tv) - [Twitch Developer Console](https://dev.twitch.tv/console) diff --git a/apps/docs/content/guides/auth/social-login/auth-twitter.mdx b/apps/docs/content/guides/auth/social-login/auth-twitter.mdx index 749029d3c9f..67721da5d8b 100644 --- a/apps/docs/content/guides/auth/social-login/auth-twitter.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-twitter.mdx @@ -118,6 +118,6 @@ suspend fun signOut() { ## Resources -- [Supabase Account - Free Plan OK](https://supabase.com) +- [Supabase - Get started for free](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [Twitter Developer Dashboard](https://developer.twitter.com/en/portal/dashboard) diff --git a/apps/docs/content/guides/auth/social-login/auth-zoom.mdx b/apps/docs/content/guides/auth/social-login/auth-zoom.mdx index 37d6d07a8e8..ef4bf5e2d7d 100644 --- a/apps/docs/content/guides/auth/social-login/auth-zoom.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-zoom.mdx @@ -125,6 +125,6 @@ suspend fun signOut() { ## Resources -- [Supabase Account - Free Plan OK](https://supabase.com) +- [Supabase - Get started for free](https://supabase.com) - [Supabase JS Client](https://github.com/supabase/supabase-js) - [Zoom App Marketplace](https://marketplace.zoom.us/) diff --git a/apps/docs/content/guides/database/arrays.mdx b/apps/docs/content/guides/database/arrays.mdx index eb405bd293f..a7579573c17 100644 --- a/apps/docs/content/guides/database/arrays.mdx +++ b/apps/docs/content/guides/database/arrays.mdx @@ -210,5 +210,5 @@ returns: ## Resources - [Supabase JS Client](https://github.com/supabase/supabase-js) -- [Supabase Account - Free Plan OK](https://supabase.com) +- [Supabase - Get started for free](https://supabase.com) - [PostgreSQL Arrays](https://www.postgresql.org/docs/15/arrays.html) diff --git a/apps/docs/content/guides/database/extensions/index_advisor.mdx b/apps/docs/content/guides/database/extensions/index_advisor.mdx index 417db6a2f11..ebb1bace03b 100644 --- a/apps/docs/content/guides/database/extensions/index_advisor.mdx +++ b/apps/docs/content/guides/database/extensions/index_advisor.mdx @@ -4,7 +4,20 @@ title: 'index_advisor: query optimization' description: 'Automatically optimize SQL queries' --- -[Index advisor](https://database.dev/olirice/index_advisor) is a Postgres extension for recommending indexes to improve query performance. +[Index advisor](https://github.com/supabase/index_advisor) is a Postgres extension for recommending indexes to improve query performance. + +Features: + +- Supports generic parameters e.g. `$1`, `$2` +- Supports materialized views +- Identifies tables/columns obfuscated by views +- Skips duplicate indexes + +index_advisor is accessible directly through Supabase Studio by navigating to the [Query Performance Report](/dashboard/project/*/database/query-performance) and selecting a query and then the "indexes" tab. + +![Supabase Studio index_advisor integration.](/docs/img/index_advisor_studio.png) + +Alternatively, you can use index_advisor directly via SQL. For example: @@ -20,25 +33,12 @@ from (1 row) ``` -Features: - -- Supports generic parameters e.g. `$1`, `$2` -- Supports materialized views -- Identifies tables/columns obfuscated by views -- Skips duplicate indexes - ## Installation -index_advisor is a trusted language extension, which means it is directly installable by users from the [database.dev](https://database.dev/) SQL package repository. - -To get started, enable the dbdev client by executing the [setup SQL script](https://database.dev/installer). - -Then, install index_advisor by running +To get started, enable index_advisor by running ```sql -select dbdev.install('olirice-index_advisor'); -create extension if not exists hypopg; -create extension "olirice-index_advisor"; +create extension index_advisor; ``` ## API @@ -152,5 +152,4 @@ from ## Resources -- dbdev [`index_advisor`](https://database.dev/olirice/index_advisor) docs -- dbdev [Github Repository](https://github.com/supabase/dbdev) +- [`index_advisor`](https://github.com/supabase/index_advisor) repo diff --git a/apps/docs/content/guides/database/postgres/indexes.mdx b/apps/docs/content/guides/database/postgres/indexes.mdx index 12071e0c64c..584c1d292f9 100644 --- a/apps/docs/content/guides/database/postgres/indexes.mdx +++ b/apps/docs/content/guides/database/postgres/indexes.mdx @@ -104,3 +104,27 @@ reindex table concurrently persons; ``` Take note that `reindex` can be used inside a transaction, but `reindex [index/table] concurrently` cannot. + +## Index Advisor + +Indexes can improve query performance of your tables as they grow. The Supabase Dashboard offers an Index Advisor, which suggests potential indexes to add to your tables. + +For more information on the Index Advisor and its suggestions, see the [`index_advisor` extension](/docs/guides/database/extensions/index_advisor). + +To use the Dashboard Index Advisor: + +1. Go to the [Query Performance](/dashboard/project/_/database/query-performance) page. +1. Click on a query to bring up the Details side panel. +1. Select the Indexes tab. +1. Enable Index Advisor if prompted. + +### Understanding Index Advisor results + +The Indexes tab shows the existing indexes used in the selected query. Note that indexes suggested in the "New Index Recommendations" section may not be used when you create them. Postgres' query planner may intentionally ignore an available index if it determines that the query will be faster without. For example, on a small table, a sequential scan might be faster than an index scan. In that case, the planner will switch to using the index as the table size grows, helping to future proof the query. + +If additional indexes might improve your query, the Index Advisor shows the suggested indexes with the estimated improvement in startup and total costs: + +- Startup cost is the cost to fetch the first row +- Total cost is the cost to fetch all the rows + +Costs are in arbitrary units, where a single sequential page read costs 1.0 units. diff --git a/apps/docs/content/guides/database/postgres/row-level-security.mdx b/apps/docs/content/guides/database/postgres/row-level-security.mdx index 2475ed67604..cfd1e218542 100644 --- a/apps/docs/content/guides/database/postgres/row-level-security.mdx +++ b/apps/docs/content/guides/database/postgres/row-level-security.mdx @@ -362,7 +362,7 @@ Always use the Role of inside your policies, specified by the `TO` operator. For ```sql create policy "rls_test_select" on rls_test -using ( (select auth.uid()) = user_id ); +using ( auth.uid() = user_id ); ``` Use: diff --git a/apps/docs/content/guides/database/sql-to-api.mdx b/apps/docs/content/guides/database/sql-to-api.mdx index 65550a1c67e..76140737c95 100644 --- a/apps/docs/content/guides/database/sql-to-api.mdx +++ b/apps/docs/content/guides/database/sql-to-api.mdx @@ -77,7 +77,7 @@ const { count, error } = await supabase ## Resources -- [Supabase Account - Free Plan OK](https://supabase.com) +- [Supabase - Get started for free](https://supabase.com) - [Postgrest Operators](https://postgrest.org/en/stable/api.html#operators) - [Supabase API: JavaScript select](/docs/reference/javascript/select) - [Supabase API: JavaScript modifiers](/docs/reference/javascript/using-modifiers) diff --git a/apps/docs/content/guides/functions/ai-models.mdx b/apps/docs/content/guides/functions/ai-models.mdx new file mode 100644 index 00000000000..e895bb5568e --- /dev/null +++ b/apps/docs/content/guides/functions/ai-models.mdx @@ -0,0 +1,171 @@ +--- +id: 'function-ai-models' +title: 'Running AI Models' +description: 'How to run AI models in Edge Functions.' +subtitle: 'How to run AI models in Edge Functions.' +--- + +[Supabase Edge Runtime](https://github.com/supabase/edge-runtime) has a built-in API for running AI models. You can use this API to generate embeddings, build conversational workflows, and do other AI related tasks in your Edge Functions. + +## Setup + +There are no external dependencies or packages to install to enable the API. + +You can create a new inference session by doing: + +```ts +const model = new Supabase.ai.Session('model-name') +``` + + + +To get type hints and checks for the API we recommend adding this triple-slash directive to the start of the Edge Function code: `/// ` + + + +## Running a model inference + +Once the session is instantiated, you can call it with inputs to perform inferences. Depending on the model you run, you may need to provide different options (discussed below). + +```ts +const output = await model.run(input, options) +``` + +## How to generate text embeddings + +Now let's see how to write an Edge Function using the `Supabase.ai` API to generate text embeddings. Currently, `Supabase.ai` API only supports the [gte-small](https://huggingface.co/Supabase/gte-small) model. + + + +`gte-small` model exclusively caters to English texts, and any lengthy texts will be truncated to a maximum of 512 tokens. While you can provide inputs longer than 512 tokens, truncation may affect the accuracy. + + + +```ts +const model = new Supabase.ai.Session('gte-small') + +Deno.serve(async (req: Request) => { + const params = new URL(req.url).searchParams + const input = params.get('input') + const output = await model.run(input, { mean_pool: true, normalize: true }) + return new Response(JSON.stringify(output), { + headers: { + 'Content-Type': 'application/json', + Connection: 'keep-alive', + }, + }) +}) +``` + +## Using Large Language Models + +Inference via larger models is supported via [Ollama](https://ollama.com/). In the first iteration, the `llama2` and `mistral` models are supported and you would need to manage the Ollama server yourself. + + + +### Running locally + +1. Install Ollama and pull the Mistral model + + ``` + ollama pull mistral + ``` + +1. Run the Ollama server locally + + ``` + ollama serve + ``` + +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 + ``` + +1. Create a new function with the following code + + ``` + supabase functions new ollama-test + ``` + + ```ts + /// + const session = new Supabase.ai.Session('mistral') + + Deno.serve(async (req: Request) => { + const params = new URL(req.url).searchParams + const prompt = params.get('prompt') ?? '' + + // Get the output as a stream + const output = await session.run(prompt, { stream: true }) + + const headers = new Headers({ + 'Content-Type': 'text/event-stream', + Connection: 'keep-alive', + }) + + // Create a stream + const stream = new ReadableStream({ + async start(controller) { + const encoder = new TextEncoder() + + try { + for await (const chunk of output) { + controller.enqueue(encoder.encode(chunk.response ?? '')) + } + } catch (err) { + console.error('Stream error:', err) + } finally { + controller.close() + } + }, + }) + + // Return the stream to the user + return new Response(stream, { + headers, + }) + }) + ``` + +1. Execute the function + + ``` + curl --get "http://localhost:54321/functions/v1/ollama-test" \ + --data-urlencode "prompt=write a short rap song about Supabase, the Postgres Developer platform, as sung by Nicki Minaj" \ + -H "Authorization: $ANON_KEY" + ``` + +### Deploying to production + +Once the function is working locally, it's time to deploy to production. + +1. Deploy a Ollama server and set a function secret called `AI_INFERENCE_API_HOST` to point to the deployed Ollama server + + ``` + supabase secrets set AI_INFERENCE_API_HOST=https://path-to-your-ollama-server/ + ``` + +2. Deploy the Supabase function + + ``` + supabase functions deploy ollama-test + ``` + +3. Execute the function + + ``` + curl --get "https://project-ref.supabase.co/functions/v1/ollama-test" \ + --data-urlencode "prompt=write a short rap song about Supabase, the Postgres Developer platform, as sung by Nicki Minaj" \ + -H "Authorization: $ANON_KEY" + ``` + +As demonstrated in the video above, running Ollama locally is typically slower than running it in on a server with dedicated GPUs. We are collaborating with the Ollama team to improve local performance. + +In the future, a hosted Ollama API, will be provided as part of the Supabase platform. Supabase will scale and manage the API and GPUs for you. To sign up for early access, fill up [this form](https://forms.supabase.com/supabase.ai-llm-early-access). diff --git a/apps/docs/content/guides/functions/examples/semantic-search.mdx b/apps/docs/content/guides/functions/examples/semantic-search.mdx new file mode 100644 index 00000000000..b42077dd395 --- /dev/null +++ b/apps/docs/content/guides/functions/examples/semantic-search.mdx @@ -0,0 +1,129 @@ +--- +id: 'function-ai-models' +title: 'Semantic Search' +description: 'Semantic Search with pgvector and Supabase Edge Functions' +subtitle: 'Semantic Search with pgvector and Supabase Edge Functions' +tocVideo: 'w4Rr_1whU-U' +--- + +[Semantic search](/docs/guides/ai/semantic-search) interprets the meaning behind user queries rather than exact [keywords](/docs/guides/ai/keyword-search). It uses machine learning to capture the intent and context behind the query, handling language nuances like synonyms, phrasing variations, and word relationships. + +Since Supabase Edge Runtime [v1.36.0](https://github.com/supabase/edge-runtime/releases/tag/v1.36.0) you can run the [`gte-small` model](https://huggingface.co/Supabase/gte-small) natively within Supabase Edge Functions without any external dependencies! This allows you to easily generate text embeddings without calling any external APIs! + +In this tutorial you're implementing three parts: + +1. A [`generate-embedding`](https://github.com/supabase/supabase/tree/master/examples/ai/edge-functions/supabase/functions/generate-embedding/index.ts) database webhook edge function which generates embeddings when a content row is added (or updated) in the [`public.embeddings`](https://github.com/supabase/supabase/tree/master/examples/ai/edge-functions/supabase/migrations/20240408072601_embeddings.sql) table. +2. A [`query_embeddings` Postgres function](https://github.com/supabase/supabase/tree/master/examples/ai/edge-functions/supabase/migrations/20240410031515_vector-search.sql) which allows us to perform similarity search from an egde function via [Remote Procedure Call (RPC)](https://supabase.com/docs/guides/database/functions?language=js). +3. A [`search` edge function](https://github.com/supabase/supabase/tree/master/examples/ai/edge-functions/supabase/functions/search/index.ts) which generates the embedding for the search term, performs the similarity search via RPC function call, and returns the result. + +You can find the complete example code on [GitHub](https://github.com/supabase/supabase/tree/master/examples/ai/edge-functions) + +### Create the database table and webhook + +Given the [following table definition](https://github.com/supabase/supabase/blob/master/examples/ai/edge-functions/supabase/migrations/20240408072601_embeddings.sql): + +```sql +create extension if not exists vector with schema extensions; + +create table embeddings ( + id bigint primary key generated always as identity, + content text not null, + embedding vector (384) +); +alter table embeddings enable row level security; + +create index on embeddings using hnsw (embedding vector_ip_ops); +``` + +You can deploy the [following edge function](https://github.com/supabase/supabase/blob/master/examples/ai/edge-functions/supabase/functions/generate-embedding/index.ts) as a [database webhook](/docs/guides/database/webhooks) to generate the embeddings for any text content inserted into the table: + +```tsx +const model = new Supabase.ai.Session('gte-small') + +Deno.serve(async (req) => { + const payload: WebhookPayload = await req.json() + const { content, id } = payload.record + + // Generate embedding. + const embedding = await model.run(content, { + mean_pool: true, + normalize: true, + }) + + // Store in database. + const { error } = await supabase + .from('embeddings') + .update({ embedding: JSON.stringify(embedding) }) + .eq('id', id) + if (error) console.warn(error.message) + + return new Response('ok') +}) +``` + +## Create a Postgres Function and RPC + +With the embeddings now stored in your Postgres database table, you can query them from Supabase Edge Functions by utilizing [Remote Procedure Calls (RPC)](/docs/guides/database/functions?language=js). + +Given the [following Postgres Function](https://github.com/supabase/supabase/blob/master/examples/ai/edge-functions/supabase/migrations/20240410031515_vector-search.sql): + +```sql +-- Matches document sections using vector similarity search on embeddings +-- +-- Returns a setof embeddings so that we can use PostgREST resource embeddings (joins with other tables) +-- Additional filtering like limits can be chained to this function call +create or replace function query_embeddings(embedding vector(384), match_threshold float) +returns setof embeddings +language plpgsql +as $$ +begin + return query + select * + from embeddings + + -- The inner product is negative, so we negate match_threshold + where embeddings.embedding <#> embedding < -match_threshold + + -- Our embeddings are normalized to length 1, so cosine similarity + -- and inner product will produce the same query results. + -- Using inner product which can be computed faster. + -- + -- For the different distance functions, see https://github.com/pgvector/pgvector + order by embeddings.embedding <#> embedding; +end; +$$; +``` + +## Query vectors in Supabase Edge Functions + +You can use `supabase-js` to first generate the embedding for the search term and then invoke the Postgres function to find the relevant results from your stored embeddings, right from your [Supabase Edge Function](https://github.com/supabase/supabase/blob/master/examples/ai/edge-functions/supabase/functions/search/index.ts): + +```tsx +const model = new Supabase.ai.Session('gte-small') + +Deno.serve(async (req) => { + const { search } = await req.json() + if (!search) return new Response('Please provide a search param!') + // Generate embedding for search term. + const embedding = await model.run(search, { + mean_pool: true, + normalize: true, + }) + + // Query embeddings. + const { data: result, error } = await supabase + .rpc('query_embeddings', { + embedding, + match_threshold: 0.8, + }) + .select('content') + .limit(3) + if (error) { + return Response.json(error) + } + + return Response.json({ search, result }) +}) +``` + +That's it, you now have AI powered semantic search set up without any external dependencies! Just you, pgvector, and Supabase Edge Functions! diff --git a/apps/docs/content/guides/functions/regional-invocation.mdx b/apps/docs/content/guides/functions/regional-invocation.mdx index f5daa3f19f4..423d0127dda 100644 --- a/apps/docs/content/guides/functions/regional-invocation.mdx +++ b/apps/docs/content/guides/functions/regional-invocation.mdx @@ -66,6 +66,20 @@ These are the currently supported region values you can provide for `x-region` h - `us-west-1` - `us-west-2` +## Using the client library + +You can also specify the region when invoking a Function using the Supabase client library: + +```js +const { createClient, FunctionRegion } = require('@supabase/supabase-js') +const { data: ret, error } = await supabase.functions.invoke('my-function-name', { + headers: { 'Content-Type': 'application/json' }, + method: 'GET', + body: {}, + region: FunctionRegion.UsEast1, +}) +``` + ## Handling regional outages If you explicitly specify the region via `x-region` header, requests **will NOT** be automatically re-routed to another region and you should consider temporarily changing regions during the outage. diff --git a/apps/docs/content/guides/functions/routing.mdx b/apps/docs/content/guides/functions/routing.mdx index ab5496f0c41..193505e5c16 100644 --- a/apps/docs/content/guides/functions/routing.mdx +++ b/apps/docs/content/guides/functions/routing.mdx @@ -58,7 +58,7 @@ You can deploy the function to Supabase via: supabase functions deploy hello-world ``` -Once the function is deployed, you can try to call the two ednpoints using cURL (or Postman). +Once the function is deployed, you can try to call the two endpoints using cURL (or Postman). ```bash # https://supabase.com/docs/guides/functions/deploy#invoking-remote-functions diff --git a/apps/docs/content/guides/getting-started/architecture.mdx b/apps/docs/content/guides/getting-started/architecture.mdx index 943f13b702d..531cce08e21 100644 --- a/apps/docs/content/guides/getting-started/architecture.mdx +++ b/apps/docs/content/guides/getting-started/architecture.mdx @@ -145,7 +145,7 @@ Less, but better. To avoid lock-in, we make it easy to migrate in and out. Our cloud offering is compatible with our self-hosted product. We use existing standards to increase portability (like `pg_dump` and CSV files). If a new standard emerges which competes with a "Supabase" approach, we will deprecate the approach in favor of the standard. -This forces us compete on experience. We aim to be the best Postgres hosting service. +This forces us to compete on user experience. We aim to be the best Postgres hosting service. ### Play the long game diff --git a/apps/docs/content/guides/getting-started/features.mdx b/apps/docs/content/guides/getting-started/features.mdx index e7127c000ea..d9896c29346 100644 --- a/apps/docs/content/guides/getting-started/features.mdx +++ b/apps/docs/content/guides/getting-started/features.mdx @@ -12,51 +12,89 @@ This is a non-exhaustive list of features that Supabase provides for every proje Every project is a full Postgres database. [Docs](/docs/guides/database). -### Database extensions +### Vector database -Every database comes with a full set of Postgres extensions. [Docs](/docs/guides/database/extensions). +Store vector embeddings right next to the rest of your data. [Docs](/docs/guides/ai). -### Database functions +### Auto-generated Rest API via PostgREST -Create custom database functions which you can call from the browser. [Docs](/docs/guides/database/functions). +RESTful APIs are auto-generated from your database, without a single line of code. [Docs](/docs/guides/api#rest-api-overview). -### Database triggers +### Auto-generated GraphQL API via pg_graphql -Attach triggers to your tables to handle database changes. [Docs](/docs/guides/auth/managing-user-data#using-triggers). +Fast GraphQL APIs using our custom Postgres GraphQL extension. [Docs](/docs/guides/graphql/api). ### Database webhooks -Send database changes to any external service using Webhooks. [Link](https://supabase.com/blog/supabase-functions-updates#database-webhooks-alpha). - -### Database backups - -Projects are backed up daily with the option to upgrade to Point in Time recovery. - -### Search - -Build search functionality using Postgres Full Text Search. [Docs](/docs/guides/database/full-text-search). +Send database changes to any external service using Webhooks. [Docs](/docs/guides/database/webhooks). ### Secrets and encryption -Encrypt sensitive data and store secrets using our Postgres extension, Supabase Vault. [Link](https://supabase.com/blog/supabase-vault). +Encrypt sensitive data and store secrets using our Postgres extension, Supabase Vault. [Docs](/docs/guides/database/vault). -### Database migrations +## Platform -Develop locally and push your changes to your production database using migrations. [Docs](/docs/guides/getting-started/local-development#database-migrations) +### Database backups + +Projects are backed up daily with the option to upgrade to Point in Time recovery. [Docs](/docs/guides/platform/backups). + +### Custom domains + +White-label the Supabase APIs to create a branded experience for your users. [Docs](/docs/guides/platform/custom-domains). + +### Network restrictions + +Restrict IP ranges that can connect to your database. [Docs](/docs/guides/platform/network-restrictions). + +### SSL enforcement + +Enforce Postgres clients to connect via SSL. [Docs](/docs/guides/platform/ssl-enforcement). + +### Branching + +Use Supabase Branches to test and preview changes. [Docs](/docs/guides/platform/branching). + +### Terraform provider + +Manage Supabase infrastructure via Terraform, an Infrastructure as Code tool. [Docs](/docs/guides/platform/terraform). + +### Read replicas + +Deploy read-only databases across multiple regions, for lower latency and better resource management. [Docs](/docs/guides/platform/read-replicas). + +### Fly Postgres + +Launch the Supabase stack on Fly.io edge network. [Docs](/docs/guides/platform/fly-postgres). + +## Studio + +### Studio Single Sign On + +Login to the Supabase dashboard via SSO. [Docs](/docs/guides/platform/sso).
+## Realtime + +### Postgres changes + +Receive your database changes through websockets. [Docs](/docs/guides/realtime/postgres-changes). + +### Broadcast + +Send messages between connected users through websockets. [Docs](/docs/guides/realtime/broadcast). + +### Presence + +Synchronize shared state across your users, including online status and typing indicators. [Docs](/docs/guides/realtime/presence). + ## Auth -### Email & password logins +### Email login Build email logins for your application or website. [Docs](/docs/guides/auth/auth-email). -### Magic links - -Build passwordless logins for your application or website.[Docs](/docs/guides/auth/auth-magic-link). - -### Social logins +### Social login Provide social logins - everything from Apple, to GitHub, to Slack. [Docs](/docs/guides/auth/social-login). @@ -64,46 +102,21 @@ Provide social logins - everything from Apple, to GitHub, to Slack. [Docs](/docs Provide phone logins using a third-party SMS provider. [Docs](/docs/guides/auth/phone-login). -### Row Level Security +### Passwordless login + +Build passwordless logins via magic links for your application or website.[Docs](/docs/guides/auth/auth-magic-link). + +### Authorization via Row Level Security Control the data each user can access with Postgres Policies. [Docs](/docs/guides/auth/row-level-security). -### Server-side Auth helpers +### Captcha protection -Helpers for implementing user authentication in popular frameworks like [Next.js](/docs/guides/auth/auth-helpers/nextjs) and [SvelteKit](/docs/guides/auth/auth-helpers/sveltekit) +Add Captcha to your sign-in, sign-up, and password reset forms. [Docs](/docs/guides/auth/auth-captcha). -### Auth UI kit +### Server-side Auth -Build login and registration pages with custom themes. [Docs](/docs/guides/auth/auth-helpers/auth-ui). - -
- -## APIs & client libraries - -### Auto-generated REST API - -RESTful APIs are auto-generated from your database, without a single line of code. [Docs](/docs/guides/api#rest-api-overview). - -### Auto-generated GraphQL API - -Fast GraphQL APIs using our custom Postgres GraphQL extension. [Docs](/docs/guides/graphql/api). - -### Realtime database changes - -Receive your database changes through websockets. [Docs](/docs/guides/realtime/postgres-changes). - -### User broadcasting - -Send messages between connected users through websockets. [Docs](/docs/guides/realtime/broadcast). - -### User presence - -Synchronize shared state across your users, including online status and typing indicators. [Docs](/docs/guides/realtime/presence). - -### Client libraries - -Official client libraries for [JavaScript](/docs/reference/javascript/) and [Dart](/docs/reference/dart). -Unofficial libraries [supported by the community](https://github.com/supabase-community#client-libraries). +Helpers for implementing user authentication in popular server-side languages and frameworks like Next.js, SvelteKit and Remix. [Docs](/docs/guides/auth/server-side/overview).
@@ -113,19 +126,21 @@ Unofficial libraries [supported by the community](https://github.com/supabase-co Supabase Storage makes it simple to store and serve files. [Docs](/docs/guides/storage). -### Resumable uploads - -Upload large files easily using resumable uploads. [Docs](/docs/guides/storage/uploads/resumable-uploads). - -### Storage CDN +### Content Delivery Network Cache large files using the Supabase CDN. [Docs](/docs/guides/storage/cdn/fundamentals). +### Smart Content Delivery Network + +Automatically revalidate assets at the edge via the Smart CDN. [Docs](/docs/guides/storage/cdn/smart-cdn). + ### Image transformations Transform images on the fly. [Docs](/docs/guides/storage/serving/image-transformations). -
+### Resumable uploads + +Upload large files using resumable uploads. [Docs](/docs/guides/storage/uploads/resumable-uploads). ## Edge Functions @@ -133,7 +148,13 @@ Transform images on the fly. [Docs](/docs/guides/storage/serving/image-transform Globally distributed TypeScript functions to execute custom business logic. [Docs](/docs/guides/functions). -
+### Regional invocations + +Execute an Edge Function in a region close to your database. [Docs](/docs/guides/functions/regional-invocation). + +### NPM compatibility + +Edge functions natively support NPM modules and Node built-in APIs. [Link](https://supabase.com/blog/edge-functions-node-npm). ## Project management @@ -145,42 +166,75 @@ Use our CLI to develop your project locally and deploy to the Supabase Platform. 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). +Unofficial libraries are supported by the community. ## Feature status -Both Postgres and the Supabase Platform are production-ready. Some tools we offer on top of Postgres are still under development. +Supabase Features are in 4 different states - Private Alpha, Public Alpha, Beta and Generally Available. -| Product | Feature | Stage | Available on self-hosted | -| -------------------------- | ---------------------- | ------ | ------------------------------------------- | -| Database | Postgres | `GA` | ✅ | -| Database | Triggers | `GA` | ✅ | -| Database | Functions | `GA` | ✅ | -| Database | Extensions | `GA` | ✅ | -| Database | Full Text Search | `GA` | ✅ | -| Database | Webhooks | `GA` | ✅ | -| Database | Point-in-Time Recovery | `GA` | 🚧 [wal-g](https://github.com/wal-g/wal-g) | -| Database | Vault | `beta` | ✅ | -| Studio | | `GA` | ✅ | -| Realtime | Postgres Changes | `GA` | ✅ | -| Realtime | Broadcast | `beta` | ✅ | -| Realtime | Presence | `beta` | ✅ | -| Storage | | `GA` | ✅ | -| Storage | S3 Backend | `GA` | ✅ | -| Storage | CDN | `GA` | 🚧 [Cloudflare](https://www.cloudflare.com) | -| Storage | Smart CDN | `beta` | 🚧 [Cloudflare](https://www.cloudflare.com) | -| Storage | Image Transformations | `GA` | ✅ | -| Storage | Resumable Uploads | `beta` | ✅ | -| Edge Functions | | `beta` | ✅ | -| Auth | OAuth Providers | `GA` | ✅ | -| Auth | Passwordless | `GA` | ✅ | -| Auth | Next.js Auth Helpers | `beta` | ✅ | -| Auth | SvelteKit Auth Helpers | `beta` | ✅ | -| Auth | Remix Auth Helpers | `beta` | ✅ | -| CLI | | `beta` | ✅ Works with self-hosted | -| Management API | | `beta` | N/A | -| Client Library: JavaScript | | `GA` | N/A | -| Client Library: Dart | | `beta` | N/A | +### Private Alpha + +Features are initially launched as a private alpha to gather feedback from the community. To join our early access program, send an email to product-ops@supabase.io. + +### Public Alpha + +he alpha stage indicates that the API might change in the future, not that the service isn’t stable. Even though the [uptime Service Level Agreement](https://supabase.com/sla) does not cover products in Alpha, we do our best to have the service as stable as possible. + +### Beta + +Features in Beta are tested by an external penetration tester for security issues. The API is guaranteed to be stable and there is a strict communication process for breaking changes. + +### Generally Available + +In addition to the Beta requirements, features in GA are covered by the [uptime SLA](https://supabase.com/sla). + +| Product | Feature | Stage | Available on self-hosted | +| -------------- | -------------------------- | --------------- | ------------------------------------------- | +| Database | Postgres | `GA` | ✅ | +| Database | Vector Database | `GA` | ✅ | +| Database | Auto-generated Rest API | `GA` | ✅ | +| Database | Auto-generated GraphQL API | `GA` | ✅ | +| Database | Webhooks | `beta` | ✅ | +| Database | Vault | `public alpha` | ✅ | +| Platform | | `GA` | ✅ | +| Platform | Point-in-Time Recovery | `GA` | 🚧 [wal-g](https://github.com/wal-g/wal-g) | +| Platform | Custom Domains | `GA` | N/A | +| Platform | Network Restrictions | `beta` | N/A | +| Platform | SSL enforcement | `beta` | N/A | +| Platform | Branching | `public alpha` | N/A | +| Platform | Terraform Provider | `public alpha` | N/A | +| Platform | Read Replicas | `private alpha` | N/A | +| Platform | Fly Postgres | `private alpha` | N/A | +| Studio | | `GA` | ✅ | +| Studio | SSO | `GA` | ✅ | +| Realtime | Postgres Changes | `GA` | ✅ | +| Realtime | Broadcast | `GA` | ✅ | +| Realtime | Presence | `GA` | ✅ | +| Storage | | `GA` | ✅ | +| Storage | CDN | `GA` | 🚧 [Cloudflare](https://www.cloudflare.com) | +| Storage | Smart CDN | `GA` | 🚧 [Cloudflare](https://www.cloudflare.com) | +| Storage | Image Transformations | `GA` | ✅ | +| Storage | Resumable Uploads | `GA` | ✅ | +| Edge Functions | | `beta` | ✅ | +| Edge Functions | Regional Invocations | `beta` | ✅ | +| Edge Functions | NPM compatibility | `beta` | ✅ | +| Auth | | `GA` | ✅ | +| Auth | Email login | `GA` | ✅ | +| Auth | Social login | `GA` | ✅ | +| Auth | Phone login | `GA` | ✅ | +| Auth | Passwordless login | `GA` | ✅ | +| Auth | SSO with SAML | `GA` | ✅ | +| Auth | Authorization via RLS | `GA` | ✅ | +| Auth | Captcha protection | `GA` | ✅ | +| Auth | Server-side Auth | `beta` | ✅ | +| CLI | | `GA` | ✅ Works with self-hosted | +| Management API | | `GA` | N/A | +| Client Library | JavaScript | `GA` | N/A | +| Client Library | Flutter | `beta` | N/A | +| Client Library | Swift | `beta` | N/A | - ✅ = Fully Available - 🚧 = Available, but requires external tools or configuration diff --git a/apps/docs/content/guides/getting-started/tutorials/with-sveltekit.mdx b/apps/docs/content/guides/getting-started/tutorials/with-sveltekit.mdx index 567778326da..649a87dd98b 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-sveltekit.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-sveltekit.mdx @@ -143,14 +143,14 @@ Create a new `src/routes/+layout.server.ts` file to handle the session on the se // src/routes/+layout.server.ts import type { LayoutServerLoad } from './$types' -export const load: LayoutServerLoad = async ({ locals: { safeGetSession } }) => { +export const load = (async ({ locals: { safeGetSession } }) => { const { session, user } = await safeGetSession() return { session, user, } -} +}) satisfies LayoutServerLoad ``` > Start your dev server (`npm run dev`) in order to generate the `./$types` files we are referencing in our project. @@ -163,7 +163,7 @@ import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/publi import type { LayoutLoad } from './$types' import { createBrowserClient, isBrowser, parse } from '@supabase/ssr' -export const load: LayoutLoad = async ({ fetch, data, depends }) => { +export const load = (async ({ fetch, data, depends }) => { depends('supabase:auth') const supabase = createBrowserClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { @@ -192,7 +192,7 @@ export const load: LayoutLoad = async ({ fetch, data, depends }) => { } = await supabase.auth.getSession() return { supabase, session } -} +}) satisfies LayoutLoad ``` Update your `src/routes/+layout.svelte`: diff --git a/apps/docs/content/guides/getting-started/tutorials/with-swift.mdx b/apps/docs/content/guides/getting-started/tutorials/with-swift.mdx index 6c110b51b6e..e26045321ce 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-swift.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-swift.mdx @@ -23,9 +23,9 @@ Let's start building the SwiftUI app from scratch. Open Xcode and create a new SwiftUI project. -Add the [supabase-swift](https://github.com/supabase-community/supabase-swift) dependency. +Add the [supabase-swift](https://github.com/supabase/supabase-swift) dependency. -Add the `https://github.com/supabase-community/supabase-swift` package to your app. For instructions, see the [Apple tutorial on adding package dependencies](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app). +Add the `https://github.com/supabase/supabase-swift` package to your app. For instructions, see the [Apple tutorial on adding package dependencies](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app). Create a helper file to initialize the Supabase client. You need the API URL and the `anon` key that you copied [earlier](#get-the-api-keys). diff --git a/apps/docs/content/guides/platform/read-replicas.mdx b/apps/docs/content/guides/platform/read-replicas.mdx index 82511dc83a3..bd91e0545ac 100644 --- a/apps/docs/content/guides/platform/read-replicas.mdx +++ b/apps/docs/content/guides/platform/read-replicas.mdx @@ -1,16 +1,17 @@ --- title: 'Read Replicas' -description: 'Deploy additional read-only databases in multiple regions for your project.' -subtitle: 'Deploy additional read-only databases in multiple regions for your project' +description: 'Deploy read-only databases across multiple regions, for lower latency.' +subtitle: 'Deploy read-only databases across multiple regions, for lower latency and better resource management.' --- - +Read Replicas are additional databases that are kept in sync with your Primary database. You can read your data from a Read Replica, which helps with: -[Request early access](https://forms.supabase.com/read-replica) and you'll be notified once Read Replicas are available to your organization. - - +- **Load balancing:** Read Replicas reduce load on the Primary database. For example, you can use a Read Replica for complex analytical queries and reserve the Primary for user-facing create, update, and delete operations. +- **Improved latency:** For projects with a global user base, additional databases can be deployed closer to users to reduce latency. +- **Redundancy:** Read Replicas provide data redundancy.
+ Map view of all project databases -Read Replicas are Postgres databases that are continuously synced to a Supabase project's Primary database. You can both read from and write to a Primary database, but you can _only_ read from a Read Replica, such as using `select` statements. +## About Read Replicas + +The database you start with when launching a Supabase project is your Primary database. Read Replicas are kept in sync with the Primary through a process called "replication." Replication is asynchronous to ensure that transactions on the Primary aren't blocked. There is a delay between an update on the Primary and the time that a Read Replica receives the change. This delay is called "replication lag." + +You can only read data from a Read Replica. This is in contrast to a Primary database, where you can both read and write: | | select | insert | update | delete | | ------------ | ------ | ------ | ------ | ------ | | Primary | ✅ | ✅ | ✅ | ✅ | | Read Replica | ✅ | - | - | - | -A database is spun up automatically with every Supabase project. This database is the Primary database. You can choose to add Read Replicas to your project and those Replicas will receive and synchronize data from the Primary database. - -Replication is asynchronous and happens in the background so transactions on the Primary aren't blocked. The time delay between writing data to the Primary and the Read Replica receiving the change is replication lag. - -Read Replicas have many applications: - -- They reduce read load on the Primary database. - - For example, you can use a Read Replica for complex analytical queries and reserve the Primary for user-facing create, update, and delete operations. -- For projects that need to cover a global audience, additional databases can be deployed closer to users in any of our twelve supported regions to reduce latency. -- Read Replicas provide data redundancy. - ## Prerequisites -A project needs to satisfy these requirements to deploy a Read Replica: + + +Read replicas are available to projects created on the Teams and Enterprise plans after 2024 March 20. They will be progressively rolled out to older projects, and eventually available on the Pro plan. + +If your project doesn't meet these criteria, you can join the [early access queue](https://forms.supabase.com/read-replica). + + + +Projects must meet these requirements to use Read Replicas: 1. Running on AWS. - Support for projects on Fly.io is coming. 1. Running on at least a [Small compute add-on](/docs/guides/platform/compute-add-ons). - - Read Replicas are currently placed on the same compute instance as the database to make sure they can keep up with the Primary's activities. -1. Running on Postgres 15. - - For projects running on older versions of PostgreSQL, you will need to [upgrade to the latest platform version](/docs/guides/platform/migrating-and-upgrading-projects#pgupgrade). + - Read Replicas are started on the same compute instance as the Primary to keep up with changes. +1. Running on Postgres 15+. + - For projects running on older versions of Postgres, you will need to [upgrade to the latest platform version](/docs/guides/platform/migrating-and-upgrading-projects#pgupgrade). 1. Using [physical backups](/docs/guides/platform/backups#point-in-time-recovery) - Physical backups are automatically enabled if using [PITR](/docs/guides/platform/backups#point-in-time-recovery) - If you're not using PITR, you'll be able to switch to physical backups as part of the Read Replica setup process. Note that physical backups can't be downloaded from the dashboard in the way logical backups can. ## Getting started -Read Replicas can be managed from the [infrastructure settings page](https://supabase.com/dashboard/project/_/settings/infrastructure). For now, each project can only deploy up to two Read Replicas and all Read Replicas inherit the compute size of their Primary database. +To add a Read Replica, go to the [Infrastructure Settings page](/dashboard/project/_/settings/infrastructure) in your dashboard. Each project can deploy up to two Read Replicas and all Read Replicas inherit the compute size of their Primary database. + +## Features Read Replicas offer the following features: ### Dedicated endpoints -Each Read Replica has its own dedicated database and API endpoints. The database endpoint can be found in your [dashboard database settings](https://supabase.com/dashboard/project/_/settings/database) under **Connection info**. The API endpoint can be found in [dashboard API settings](https://supabase.com/dashboard/project/_/settings/api) under **Project URL**. +Each Read Replica has its own dedicated database and API endpoints. -A Read Replica's API endpoint only supports `GET` requests to the [REST API](https://supabase.com/docs/guides/api). Requests towards other Supabase products, such as Auth, Storage, and Realtime, are not able to utilize a Read Replica nor its API endpoint for now. We are working on adding Read Replica support for them. +- Find the database endpoint on the [Database Settings page](/dashboard/project/_/settings/database) under **Connection Info** +- Find the API endpoint on the [API Settings page](/dashboard/project/_/settings/api) under **Project URL** -### Dedicated connection pool +Read Replicas only support `GET` requests and the [REST API](/docs/guides/api). Requests to other Supabase products, such as Auth, Storage, and Realtime, aren't able to use a Read Replica or its API endpoint. Support for more products will be added in the future. -A connection pool through Supavisor is also available for each Read Replica. Its unique connection string can be found in your [dashboard database settings](https://supabase.com/dashboard/project/_/settings/database) under **Connection string**. +## Dedicated connection pool + +A connection pool through Supavisor is also available for each Read Replica. Find the connection string on the [Database Settings page](/dashboard/project/_/settings/database) under **Connection String**. + +## API load balancer + +A load balancer is deployed to balance requests between your Primary database and Read Replicas. Find its endpoint on the [API Settings page](/dashboard/project/_/settings/api). + +The load balancer uses a round-robin strategy to route `GET` requests to all available API endpoints, including the Primary database. Non-`GET` requests can also be sent through this endpoint, and will be routed to the Primary database. Read-only Postgres functions can be called using a [`GET` request](https://github.com/supabase/supabase-js/blob/v2.42.3/src/SupabaseClient.ts#L176-L177) as well. + +If you remove all Read Replicas from your project, the load balancer and its endpoint are removed as well. Make sure to redirect requests back to your Primary database before removal. ### Querying through the SQL editor +In the SQL editor, you can choose if you want to run the query on a particular Read Replica. +
+ SQL editor view. -In the SQL editor, you can choose if you want to run the query on the Primary or one of the Replicas. +### Metrics -### API load balancer +Observability and metrics for Read Replicas are not currently available in the Supabase Dashboard. -When Read Replicas are deployed, an API load balancer becomes available for the project. An endpoint for it is provisioned and can be found [here](https://supabase.com/dashboard/project/_/settings/api). - -The load balancer uses a round-robin strategy to route `GET` requests to any of the API endpoints available, including that of the Primary database. Non-`GET` requests can also be sent through this endpoint. They are immediately redirected to the Primary database. - -Similar to the Read Replica API endpoints, only requests to the REST API are available for now. Support for other Supabase products will become available once Read Replica support has been completed for them. - -When a project no longer has any Read Replicas, the load balancer and its endpoint are brought down as well. +If you're ingesting your [project's metrics](/docs/guides/platform/metrics#accessing-the-metrics-endpoint) into your own environment, you can update [your scraping setup](https://github.com/supabase/supabase-grafana?tab=readme-ov-file#read-replica-support) to additionally ingest metrics from your Read Replicas. ### Centralized configuration management -All settings made through the dashboard will be propagated across all databases of a project. This ensures that no Read Replica get out of sync with its Primary database or with other Read Replicas. +All settings configured through the dashboard will be propagated across all databases of a project. This ensures that no Read Replica get out of sync with the Primary database or with other Read Replicas. -## How are Read Replicas made? +## Operations blocked by Read Replicas + +### Project upgrades and data restorations + +The following procedures require all Read Replicas for a project to be brought down before they can be performed: + +1. [Project upgrades](/docs/guides/platform/migrating-and-upgrading-projects#pgupgrade) +1. [Data restorations](/docs/guides/platform/backups#pitr-restoration-process) + +These operations need to be completed before Read Replicas can be re-deployed. + +## About replication We use a hybrid approach to replicate data from a Primary to its Read Replicas, combining the native methods of streaming replication and file-based log shipping. @@ -135,14 +158,3 @@ In this replication method, the Primary continuously buffers WAL changes to a lo We bring these two methods together to achieve quick, stable, and reliable replication. Each method addresses the limitations of the other. Streaming replication minimizes replication lag, while file-based log shipping provides a fallback. For file-based log shipping, we use our existing Point In Time Recovery (PITR) infrastructure. We regularly archive files from the Primary using [WAL-G](https://github.com/wal-g/wal-g), an open source archival and restoration tool, and ship the WAL files to S3. We combine it with streaming replication to reduce replication lag. Once WAL-G files have been synced from S3, Read Replicas connect to the Primary and stream the WAL directly. - -## Operations blocked by Read Replicas - -### Project upgrades and data restorations - -The following procedures require all Read Replicas for a project to be brought down before they can be performed: - -1. [Project upgrades](/docs/guides/platform/migrating-and-upgrading-projects#pgupgrade) -1. [Data restorations](/docs/guides/platform/backups#pitr-restoration-process) - -These operations need to be completed before Read Replicas can be re-deployed. diff --git a/apps/docs/docs/ref/api/api.mdx b/apps/docs/docs/ref/api/api.mdx index d543ed53351..18e09385605 100644 --- a/apps/docs/docs/ref/api/api.mdx +++ b/apps/docs/docs/ref/api/api.mdx @@ -14,7 +14,7 @@ The Management API is in `beta`. It is usable in it's current state, but it's li ## Authentication -All API requests require a Supabase Personal token to be included in the Authorization header: `Authorization Bearer `. To generate or manage your API token, visit your [account](https://supabase.com/dashboard/account/tokens) page. Your API tokens carry the same privileges as your user account, so be sure to keep it secret. diff --git a/apps/docs/docs/ref/swift/installing.mdx b/apps/docs/docs/ref/swift/installing.mdx new file mode 100644 index 00000000000..cbc57f06a7b --- /dev/null +++ b/apps/docs/docs/ref/swift/installing.mdx @@ -0,0 +1,63 @@ +--- +id: installing +title: 'Installing' +slug: installing +custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supabase.yml +--- + +### Install using Swift Package Manager + + + + + You can install Supabase package using Swift Package Manager. + + The package exposes multiple libraries, you can choose between adding all of them using Supabase, or some of: + + - `Auth` + - `Realtime` + - `Postgrest` + - `Functions` + - `Storage` + + + + + + + + + ```swift + let package = Package( + ... + dependencies: [ + ... + .package( + url: "https://github.com/supabase/supabase-swift.git", + from: "2.0.0" + ), + ], + targets: [ + .target( + name: "YourTargetName", + dependencies: [ + .product( + name: "Supabase", // Auth, Realtime, Postgrest, Functions, or Storage + package: "supabase-swift" + ), + ] + ) + ] + ) + ``` + + + + + + diff --git a/apps/docs/docs/ref/swift/introduction.mdx b/apps/docs/docs/ref/swift/introduction.mdx index 07f405736fe..dadc4490275 100644 --- a/apps/docs/docs/ref/swift/introduction.mdx +++ b/apps/docs/docs/ref/swift/introduction.mdx @@ -8,25 +8,13 @@ hideTitle: true

Swift Client Library

-

- @supabase-community/supabase-swift -

+

@supabase/supabase-swift

{/* prettier-ignore */}
- This reference documents every object and method available in Supabase's Swift library, [supabase-swift](https://github.com/supabase-community/supabase-swift). You can use supabase-swift to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files. - - We also provide a [supabase](https://pub.dev/packages/supabase) package for non-Swift projects. - -
- -{/* prettier-ignore */} -
-The Swift 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 maintainers, [Guilherme](https://github.com/grdsdev) and [Maail](https://github.com/maail). + This reference documents every object and method available in Supabase's Swift library, [supabase-swift](https://github.com/supabase/supabase-swift). You can use supabase-swift to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
diff --git a/apps/docs/hooks/useHash.ts b/apps/docs/hooks/useHash.ts index 3c6f9567dab..29824c1742b 100644 --- a/apps/docs/hooks/useHash.ts +++ b/apps/docs/hooks/useHash.ts @@ -1,12 +1,17 @@ import { useState, useCallback, useEffect } from 'react' const useHash = () => { - const [hash, setHash] = useState(() => - typeof window !== 'undefined' ? window.location.hash.split('#')[1] : undefined - ) + const [hash, setHash] = useState(() => { + if (typeof window !== 'undefined') { + const url = new URL(window.location.href) + return url.hash.substring(1) // Removes the leading '#' + } + return undefined + }) const hashChangeHandler = useCallback(() => { - setHash(window.location.hash.split('#')[1]) + const url = new URL(window.location.href) + setHash(url.hash.substring(1)) }, []) useEffect(() => { @@ -18,7 +23,9 @@ const useHash = () => { const updateHash = useCallback( (newHash) => { - if (newHash !== hash) window.location.hash = newHash + if (newHash !== hash) { + window.location.hash = newHash + } }, [hash] ) diff --git a/apps/docs/package.json b/apps/docs/package.json index 428e3dc80ba..5b6df117e78 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -22,8 +22,8 @@ "@code-hike/mdx": "^0.9.0", "@mdx-js/loader": "^2.1.5", "@mdx-js/react": "^2.3.0", - "@next/bundle-analyzer": "^14.1.4", - "@next/mdx": "^14.1.4", + "@next/bundle-analyzer": "^14.2.0", + "@next/mdx": "^14.2.0", "@octokit/auth-app": "^6.0.3", "@octokit/core": "^5.1.0", "@octokit/graphql": "^7.0.2", @@ -54,7 +54,7 @@ "mdx-mermaid": "2.0.0-rc3", "mermaid": "^10.0.2", "micromark-extension-mdxjs": "^1.0.0", - "next": "^14.1.4", + "next": "^14.2.0", "next-mdx-remote": "^4.4.1", "next-plugin-yaml": "^1.0.1", "next-seo": "^6.5.0", diff --git a/apps/docs/public/img/index_advisor_studio.png b/apps/docs/public/img/index_advisor_studio.png new file mode 100644 index 00000000000..f23f4e0adee Binary files /dev/null and b/apps/docs/public/img/index_advisor_studio.png differ diff --git a/apps/docs/spec/cli_v1_commands.yaml b/apps/docs/spec/cli_v1_commands.yaml index aa603aaa103..5fe46e5b5d8 100644 --- a/apps/docs/spec/cli_v1_commands.yaml +++ b/apps/docs/spec/cli_v1_commands.yaml @@ -1,7 +1,7 @@ clispec: '001' info: id: cli - version: 1.151.1 + version: 1.157.2 title: Supabase CLI language: sh source: https://github.com/supabase/cli @@ -10,6 +10,8 @@ info: description: | Supabase CLI provides you with tools to develop your application locally, and deploy your application to the Supabase platform. tags: + - id: quick-start + title: Quick Start - id: local-dev title: Local Development - id: management-api @@ -1349,19 +1351,21 @@ commands: If your local and remote migration history goes out of sync, you can repair the remote history by marking specific migrations as `--status applied` or `--status reverted`. Marking as `reverted` will delete an existing record from the migration history table while marking as `applied` will insert a new record. - For example, your migration history table may look like this after running `db pull` for the first time. + For example, your migration history may look like the table below, with missing entries in either local or remote. ```bash $ supabase migration list LOCAL │ REMOTE │ TIME (UTC) ─────────────────┼────────────────┼────────────────────── - 20230103054303 │ 20230103054303 │ 2023-01-03 05:43:03 + │ 20230103054303 │ 2023-01-03 05:43:03 + 20230103054315 │ │ 2023-01-03 05:43:15 ``` To reset your migration history to a clean state, first delete your local migration file. ```bash - $ rm supabase/migrations/20230103054303_remote_commit.sql + $ rm supabase/migrations/20230103054315_remote_commit.sql + $ supabase migration list LOCAL │ REMOTE │ TIME (UTC) ─────────────────┼────────────────┼────────────────────── @@ -1372,13 +1376,30 @@ commands: ```bash $ supabase migration repair 20230103054303 --status reverted - Repaired migration history: 20230103054303 => reverted + Connecting to remote database... + Repaired migration history: [20220810154537] => reverted + Finished supabase migration repair. + $ supabase migration list LOCAL │ REMOTE │ TIME (UTC) ─────────────────┼────────────────┼────────────────────── ``` Now you can run `db pull` again to dump the remote schema as a local migration file. + + ```bash + $ supabase db pull + Connecting to remote database... + Schema written to supabase/migrations/20240414044403_remote_schema.sql + Update remote migration history table? [Y/n] + Repaired migration history: [20240414044403] => applied + Finished supabase db pull. + + $ supabase migration list + LOCAL │ REMOTE │ TIME (UTC) + ─────────────────┼────────────────┼────────────────────── + 20240414044403 │ 20240414044403 │ 2024-04-14 04:44:03 + ``` examples: - id: basic-usage name: Mark a migration as reverted @@ -1541,9 +1562,8 @@ commands: flags: - id: name name: --name - description: | - Name that will be used to store token in your settings, defaults to built-in token name generator - default_value: '' + description: Name that will be used to store token in your settings + default_value: built-in token name generator - id: no-browser name: --no-browser description: Do not open browser automatically @@ -2320,9 +2340,13 @@ commands: usage: supabase init [flags] subcommands: [] flags: + - id: force + name: --force + description: Overwrite existing supabase/config.toml. + default_value: 'false' - id: use-orioledb name: --use-orioledb - description: Use OrioleDB storage engine for Postgres + description: Use OrioleDB storage engine for Postgres. default_value: 'false' - id: with-intellij-settings name: --with-intellij-settings @@ -2828,6 +2852,39 @@ commands: Optionally, a new row can be inserted into the migration history table to reflect the current state of the remote database. If no entries exist in the migration history table, `pg_dump` will be used to capture all contents of the remote schemas you have created. Otherwise, this command will only diff schema changes against the remote database, similar to running `db diff --linked`. + examples: + - id: basic-usage + name: Basic usage + code: supabase db pull + response: | + Connecting to remote database... + Schema written to supabase/migrations/20240414044403_remote_schema.sql + Update remote migration history table? [Y/n] + Repaired migration history: [20240414044403] => applied + Finished supabase db pull. + The auth and storage schemas are excluded. Run supabase db pull --schema auth,storage again to diff them. + - id: local-studio + name: Local studio + code: supabase db pull --local + response: | + Connecting to local database... + Setting up initial schema.... + Creating custom roles supabase/roles.sql... + Applying migration 20240414044403_remote_schema.sql... + No schema changes found + The auth and storage schemas are excluded. Run supabase db pull --schema auth,storage again to diff them. + exit status 1 + - id: custom-schemas + name: Custom schemas + code: supabase db pull --schema auth,storage + response: | + Connecting to remote database... + Setting up initial schema.... + Creating custom roles supabase/roles.sql... + Applying migration 20240414044403_remote_schema.sql... + No schema changes found + Try rerunning the command with --debug to troubleshoot the error. + exit status 1 tags: [] links: [] usage: supabase db pull [migration name] [flags] @@ -3437,3 +3494,16 @@ commands: description: enable experimental features required: true default_value: 'false' + - id: supabase-bootstrap + title: supabase bootstrap + summary: Bootstrap a Supabase project from a starter template + tags: + - quick-start + links: [] + usage: supabase bootstrap [template] [flags] + subcommands: [] + flags: + - id: password + name: -p, --password + description: Password to your remote Postgres database. + default_value: '' diff --git a/apps/docs/spec/common-client-libs-sections.json b/apps/docs/spec/common-client-libs-sections.json index 5ee520ce08a..6a44fb3c877 100644 --- a/apps/docs/spec/common-client-libs-sections.json +++ b/apps/docs/spec/common-client-libs-sections.json @@ -33,6 +33,7 @@ "reference_python_v2", "reference_csharp_v0", "reference_swift_v1", + "reference_swift_v2", "reference_kotlin_v1", "reference_kotlin_v2" ] @@ -48,6 +49,7 @@ "reference_python_v2", "reference_csharp_v0", "reference_swift_v1", + "reference_swift_v2", "reference_kotlin_v1", "reference_kotlin_v2" ] @@ -986,6 +988,7 @@ "reference_python_v2", "reference_csharp_v0", "reference_swift_v1", + "reference_swift_v2", "reference_kotlin_v1", "reference_kotlin_v2" ], diff --git a/apps/docs/spec/supabase_js_v2.yml b/apps/docs/spec/supabase_js_v2.yml index d9d829db21a..53e7e8cf7fa 100644 --- a/apps/docs/spec/supabase_js_v2.yml +++ b/apps/docs/spec/supabase_js_v2.yml @@ -568,7 +568,7 @@ functions: notes: | - Requires either an email or phone number. - This method is used for passwordless sign-ins where a OTP is sent to the user's email or phone number. - - If the user doesn't exist, `signInWithOtp()` will signup the user instead. To restrict this behaviour, you can set `shouldCreateUser` in `SignInWithPasswordlessCredentials.options` to `false`. + - If the user doesn't exist, `signInWithOtp()` will signup the user instead. To restrict this behavior, you can set `shouldCreateUser` in `SignInWithPasswordlessCredentials.options` to `false`. - If you're using an email, you can configure whether you want the user to receive a magiclink or a OTP. - If you're using phone, you can configure whether you want the user to receive a OTP. - The magic link's destination URL is determined by the [`SITE_URL`](/docs/guides/auth/concepts/redirect-urls). @@ -4925,7 +4925,7 @@ functions: notes: | - Requires an Authorization header. - Invoke params generally match the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) spec. - - When you pass in a body to your function, we automatically attach the Content-Type header for `Blob`, `ArrayBuffer`, `File`, `FormData` and `String`. If it doesn't match any of these types we assume the payload is `json`, serialise it and attach the `Content-Type` header as `application/json`. You can override this behaviour by passing in a `Content-Type` header of your own. + - When you pass in a body to your function, we automatically attach the Content-Type header for `Blob`, `ArrayBuffer`, `File`, `FormData` and `String`. If it doesn't match any of these types we assume the payload is `json`, serialize it and attach the `Content-Type` header as `application/json`. You can override this behavior by passing in a `Content-Type` header of your own. - Responses are automatically parsed as `json`, `blob` and `form-data` depending on the `Content-Type` header sent by your function. Responses are parsed as `text` by default. examples: - id: basic-invocation @@ -4991,6 +4991,36 @@ functions: method: 'DELETE' }) ``` + - 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: | + ```js + import { createClient, FunctionRegion } from '@supabase/supabase-js' + + const { data, error } = await supabase.functions.invoke('hello', { + body: { foo: 'bar' }, + region: FunctionRegion.UsEast1 + }) + ``` + - id: calling-with-get-verb name: Calling with GET HTTP verb description: | diff --git a/apps/docs/spec/supabase_kt_v2.yml b/apps/docs/spec/supabase_kt_v2.yml index a613cd2cbd6..3d0a1b44996 100644 --- a/apps/docs/spec/supabase_kt_v2.yml +++ b/apps/docs/spec/supabase_kt_v2.yml @@ -57,7 +57,7 @@ functions: scheme = "deeplink scheme" // On Android only, you can set OAuth and SSO logins to open in a custom tab, rather than an external browser: - defaultExternalAuthAction = ExternalAuthAction.CUSTOM_TABS //defaults to EXTERNAL_BROWSER + defaultExternalAuthAction = ExternalAuthAction.CustomTabs() //defaults to ExternalAuthAction.ExternalBrowser } ``` 4. **Call platform specific function on startup** \ @@ -684,10 +684,10 @@ functions: isOptional: true # Not directly `optional`, as there are two overloads one with and one without `parameters`. type: T description: Parameters to pass to the function. T can be any serializable type. - - name: head + - name: method isOptional: true - type: Boolean - description: If true, delete will delete the selected data. + type: RpcMethod + description: The HTTP method to use. Defaults to `RpcMethod.POST` - name: request isOptional: true type: PostgrestRequestBuilder.() -> Unit @@ -3317,7 +3317,7 @@ functions: title: 'reauthenticate()' $ref: '@supabase/gotrue-js.GoTrueClient.reauthenticate' notes: | - - This method is used together with `modifyUser()` when a user's password needs to be updated. + - This method is used together with `updateUser()` when a user's password needs to be updated. - This method will send a nonce to the user's email. If the user doesn't have a confirmed email address, the method will send the nonce to the user's confirmed phone number instead. examples: - id: send-reauthentication-nonce @@ -3422,11 +3422,11 @@ functions: val user = supabase.auth.retrieveUser("JWT") ``` - id: update-user - title: 'modifyUser()' + title: 'updateUser()' $ref: '@supabase/gotrue-js.GoTrueClient.updateUser' notes: | Modifies the user data. - - In order to use the `modifyUser()` method, the user needs to be signed in first. + - In order to use the `updateUser()` method, the user needs to be signed in first. - By default, email updates sends a confirmation link to both the user's current and new email. To only send a confirmation link to the user's new email, disable **Secure email change** in your project's [email auth provider settings](https://supabase.com/dashboard/project/_/auth/providers). params: @@ -3469,7 +3469,7 @@ functions: isSpotlight: false code: | ```kotlin - val user = supabase.auth.modifyUser { + val user = supabase.auth.updateUser { email = "newEmail@email.com" } ``` @@ -3478,7 +3478,7 @@ functions: isSpotlight: false code: | ```kotlin - val user = supabase.auth.modifyUser { + val user = supabase.auth.updateUser { password = "secretPassword" } ``` @@ -3487,7 +3487,7 @@ functions: isSpotlight: true code: | ```kotlin - val user = supabase.auth.modifyUser { + val user = supabase.auth.updateUser { data { put("name", "John") } @@ -3681,11 +3681,11 @@ functions: Sends a password reset request to the given email address. - The password reset flow consist of 2 broad steps: (i) Allow the user to login via the password reset link; (ii) Update the user's password. - The `resetPasswordForEmail()` only sends a password reset link to the user's email. - To update the user's password, see [`modifyUser()`](/docs/reference/kotlin/auth-updateuser). + To update the user's password, see [`updateUser()`](/docs/reference/kotlin/auth-updateuser). - The user gets redirected back to your app, assuming you setup [OTP handling](/docs/reference/kotlin/initializing) - - After the user has been redirected successfully, prompt them for a new password and call `modifyUser()`: + - After the user has been redirected successfully, prompt them for a new password and call `updateUser()`: ```kotlin - supabase.auth.modifyUser { + supabase.auth.updateUser { password = "1234567" } ``` diff --git a/apps/docs/spec/supabase_swift_v2.yml b/apps/docs/spec/supabase_swift_v2.yml index 5cfd133983b..43fd69fb404 100644 --- a/apps/docs/spec/supabase_swift_v2.yml +++ b/apps/docs/spec/supabase_swift_v2.yml @@ -92,7 +92,7 @@ functions: - To fetch the currently logged-in user, refer to [`getUser()`](/docs/reference/swift/get-user). examples: - id: sign-up - name: Sign up + name: Sign up with email and password isSpotlight: true code: | ```swift @@ -101,6 +101,30 @@ functions: password: "example-password" ) ``` + - id: sign-up-phone + name: Sign up with a phone number and password (SMS) + isSpotlight: true + code: | + ```swift + try await supabase.auth.signUp( + phone: "123456789", + password: "example-password", + channel: "sms" + ) + ``` + - id: sign-up-phone-whatsapp + name: Sign up with a phone number and password (whatsapp) + isSpotlight: true + description: | + The user will be sent a WhatsApp message which contains a OTP. By default, a given user can only request a OTP once every 60 seconds. Note that a user will need to have a valid WhatsApp account that is linked to Twilio in order to use this feature. + code: | + ```swift + try await supabase.auth.signUp( + phone: "123456789", + password: "example-password", + channel: "whatsapp" + ) + ``` - id: sign-up-with-additional-user-metadata name: Sign up with additional user metadata isSpotlight: false @@ -120,6 +144,7 @@ functions: - id: sign-up-with-redirect name: Sign up with a redirect URL description: | + - You can provide a default redirect URL when initializing the client. - See [redirect URLs and wildcards](/docs/guides/auth/overview#redirect-urls-and-wildcards) to add additional redirect URLs to your project. code: | ```swift @@ -129,6 +154,69 @@ functions: redirectTo: URL(string: "https://example.com/welcome")! ) ``` + - id: on-auth-state-change + title: "onAuthStateChange()" + notes: | + - Subscribes to important events occurring on the user's session. + - Emitted events: + - `INITIAL_SESSION` + - Emitted right after the Supabase client is constructed and the initial session from storage is loaded. + - `SIGNED_IN` + - Emitted each time a user session is confirmed or re-established, including on user sign in. + - Avoid making assumptions as to when this event is fired, this may occur even when the user is already signed in. Instead, check the user object attached to the event to see if a new user has signed in and update your application's UI. + - `SIGNED_OUT` + - Emitted when the user signs out. This can be after: + - A call to `supabase.auth.signOut()`. + - After the user's session has expired for any reason: + - User has signed out on another device. + - The session has reached its timebox limit or inactivity timeout. + - User has signed in on another device with single session per user enabled. + - Check the [User Sessions](/docs/guides/auth/sessions) docs for more information. + - Use this to clean up any local storage your application has associated with the user. + - `TOKEN_REFRESHED` + - Emitted each time a new access and refresh token are fetched for the signed in user. + - It's best practice and highly recommended to extract the access token (JWT) and store it in memory for further use in your application. + - Avoid frequent calls to `supabase.auth.session` for the same purpose. + - There is a background process that keeps track of when the session should be refreshed so you will always receive valid tokens by listening to this event. + - The frequency of this event is related to the JWT expiry limit configured on your project. + - `USER_UPDATED` + - Emitted each time the `supabase.auth.update(user:)` method finishes successfully. Listen to it to update your application's UI based on new profile information. + - `PASSWORD_RECOVERY` + - Emitted instead of the `SIGNED_IN` event when the user lands on a page that includes a password recovery link in the URL. + - Use it to show a UI to the user where they can [reset their password](/docs/guides/auth/passwords#resetting-a-users-password-forgot-password). + + examples: + - id: listen-to-auth-changes + name: Listen to auth changes + isSpotlight: true + code: | + ```swift + + // Using AsyncStream + for await (event, session) in await supabase.auth.authStateChanges { + print(event, session) + } + + // Using Closure + let subscription = await supabase.auth.onAuthStateChange { event, session in + print(event, session) + } + + // call remove() to remove subscription + subscription.remove() + ``` + description: | + - When using closure based, remember to call `remove()` on the returned subscription. + - When using AsyncStream, it automatically removes the subscription when AsyncStream is canceled, or finishes. + - id: list-to-a-specific-event + name: Listen to a specific event + code: | + ```swift + for await (_, session) in await supabase.auth.authStateChanges + .filter({ $0.event == .signedIn }) { + // handle signIn event. + } + ``` - id: sign-in-anonymously title: "signInAnonymously()" notes: | @@ -187,14 +275,14 @@ functions: - id: sign-in-with-otp title: "signInWithOTP()" notes: | + - Requires either an email or phone number. - This method is used for passwordless sign-ins where a OTP is sent to the user's email or phone number. - - If the user doesn't exist, `signInWithOTP()` will signup the user instead. To restrict this behavior, you can set `shouldCreateUser` to `false`. + - If the user doesn't exist, `signInWithOTP()` will signup the user instead. To restrict this behavior, you can set `shouldCreateUser` to `false``. - If you're using an email, you can configure whether you want the user to receive a magiclink or a OTP. - If you're using phone, you can configure whether you want the user to receive a OTP. - The magic link's destination URL is determined by the [`SITE_URL`](/docs/guides/auth/concepts/redirect-urls). - See [redirect URLs and wildcards](/docs/guides/auth#redirect-urls-and-wildcards) to add additional redirect URLs to your project. - Magic links and OTPs share the same implementation. To send users a one-time code instead of a magic link, [modify the magic link email template](/dashboard/project/_/auth/templates) to include `{{ .Token }}` instead of `{{ .ConfirmationURL }}`. - - When using magic links, specify a `redirectTo` that matches a configured url scheme in your iOS app, so Supabase can correctly redirect back to your app. - See our [Twilio Phone Auth Guide](/docs/guides/auth/phone-login/twilio) for details about configuring WhatsApp sign in. examples: - id: sign-in-with-email @@ -216,13 +304,55 @@ functions: ```swift try await supabase.auth.signInWithOTP(phone: "+13334445555") ``` + - id: sign-in-with-whatsapp-otp + name: Sign in with WhatsApp OTP + isSpotlight: false + description: The user will be sent a WhatsApp message which contains a OTP. By default, a given user can only request a OTP once every 60 seconds. Note that a user will need to have a valid WhatsApp account that is linked to Twilio in order to use this feature. + code: | + ```swift + try await supabase.auth.signInWithOTP( + phone: "+13334445555", + channel: "whatsapp" + ) + ``` - id: sign-in-with-oauth - title: "getOAuthSignInURL()" + title: "signInWithOAuth()" notes: | - This method is used for signing in using a third-party provider. - Supabase supports many different [third-party providers](https://supabase.com/docs/guides/auth#providers). examples: - - id: sign-in-using-a-third-party-provider + - id: sign-in-using-ASWebAuthenticationSession + name: Sign in with OAuth using ASWebAuthenticationSession + isSpotlight: true + description: | + - Use `configure` parameter to customize the `ASWebAuthenticationSession` + code: | + ```swift + let session = try await supabase.auth.signInWithOAuth( + provider: .github + ) { (session: ASWebAuthenticationSession) in + // customize session + } + ``` + + - id: sign-in-using-generic-flow + name: Sign in with OAuth and customize flow + isSpotlight: true + description: | + - Use `launchFlow` parameter to customize the flow. + code: | + ```swift + let session = try await supabase.auth.signInWithOAuth( + provider: .github + ) { url in + // use url to start OAuth flow + // and return a result url that contains the OAuth token. + // ... + return resultURL + } + ``` + + - id: sign-in-using-manual-implementation name: Sign in using a third-party provider isSpotlight: true description: | @@ -231,7 +361,7 @@ functions: - When using `ASWebAuthenticationSession` or any other implementation, use the returning URL as input to `session(from:)` method. code: | ```swift - let url = try await supabase.auth.getOAuthSignInURL(provider: .github) + let url = try await supabase.auth.getOAuthSignInURL(provider: .github, redirectTo: URL(string: "my-app-scheme://")) let session = ASWebAuthenticationSession(url: url, callbackURLScheme: "my-app-scheme") { url, error in guard let url else { return } @@ -246,21 +376,6 @@ functions: session.start() ``` - - id: sign-in-using-a-third-party-provider-with-redirect - name: Sign in using a third-party provider with redirect - isSpotlight: false - description: | - - When the third-party provider successfully authenticates the user, the provider redirects the user to the URL specified in the `redirectTo` parameter. This parameter defaults to the [`SITE_URL`](/docs/guides/auth/concepts/redirect-urls). It does not redirect the user immediately after invoking this method. - - See [redirect URLs and wildcards](/docs/guides/auth/overview#redirect-urls-and-wildcards) to add additional redirect URLs to your project. - - getOAuthSignInURL() provides the URL which needs to be opened in a SFSafariViewController instance. - - The redirectTo URL needs to be setup correctly in your project under Authentication -> URL Configuration -> Redirect URLs. - code: | - ```swift - let url = try await supabase.auth.getOAuthSignInURL( - provider: .google, - redirectTo: URL(string: "https://example.com/welcome")! - ) - ``` - id: sign-in-with-scopes name: Sign in with scopes isSpotlight: false @@ -269,7 +384,7 @@ functions: You may also need to specify the scopes in the provider's OAuth app settings, depending on the provider. The list of scopes will be documented by the third-party provider you are using and specifying scopes will enable you to use the OAuth provider token to call additional APIs supported by the third-party provider to get more information. code: | ```swift - let url = try await supabase.auth.getOAuthSignInURL( + let url = try await supabase.auth.signInWithOAuth( provider: .github, scopes: "repo gist notifications" ) @@ -290,6 +405,45 @@ functions: ) ) ``` + - id: sign-in-with-sso + title: 'signInWithSSO()' + notes: | + - Before you can call this method you need to [establish a connection](/docs/guides/auth/sso/auth-sso-saml#managing-saml-20-connections) to an identity provider. Use the [CLI commands](/docs/reference/cli/supabase-sso) to do this. + - If you've associated an email domain to the identity provider, you can use the `domain` property to start a sign-in flow. + - In case you need to use a different way to start the authentication flow with an identity provider, you can use the `providerId` property. For example: + - Mapping specific user email addresses with an identity provider. + - Using different hints to identity the identity provider to be used by the user, like a company-specific page, IP address or other tracking information. + examples: + - id: sign-in-with-domain + name: Sign in with email domain + isSpotlight: true + code: | + ```swift + // You can extract the user's email domain and use it to trigger the + // authentication flow with the correct identity provider. + + let url = try await await supabase.auth.signInWithSSO{ + domain: "company.com" + } + + // Open the URL using your preferred method to complete sign-in process. + UIApplication.shared.open(url) + ``` + - id: sign-in-with-provider-uuid + name: Sign in with provider UUID + isSpotlight: true + code: | + ```swift + // Useful when you need to map a user's sign in request according + // to different rules that can't use email domains. + + let url = try await supabase.auth.signInWithSSO{ + providerId: "21648a9d-8d5a-4555-a9d1-d6375dc14e92" + } + + // Open the URL using your preferred method to complete sign-in process. + UIApplication.shared.open(url) + ``` - id: sign-out title: "signOut()" notes: | @@ -410,6 +564,30 @@ functions: ```swift let identities = try await supabase.auth.userIdentities() ``` + - id: unlink-identity + title: 'unlinkIdentity()' + notes: | + - The **Enable Manual Linking** option must be enabled from your [project's authentication settings](/dashboard/project/_/settings/auth). + - The user needs to be signed in to call `unlinkIdentity()`. + - The user must have at least 2 identities in order to unlink an identity. + - The identity to be unlinked must belong to the user. + examples: + - id: unlink-identity + name: Unlink an identity + isSpotlight: true + code: | + ```swift + // retrieve all identites linked to a user + let identities = try await supabase.auth.userIdentities() + + // find the google identity + let googleIdentity = identities.first { + $0.provider == .google + } + + // unlink the google identity + try await supabase.auth.unlinkIdentity(googleIdentity) + ``` - id: send-password-reauthentication title: "reauthenticate()" notes: | @@ -512,29 +690,7 @@ functions: ```swift let session = try await supabase.auth.refreshSession(refreshToken: "custom-refresh-token") ``` - - id: on-auth-state-change - title: "authStateChanges" - notes: | - - Types of auth events: `INITIAL_SESSION`, `SIGNED_IN`, `SIGNED_OUT`, `TOKEN_REFRESHED`, `USER_UPDATED`, `PASSWORD_RECOVERY`, `MFA_CHALLENGE_VERIFIED` - - The `INITIAL_SESSION` can be used to allow you to invoke the callback function when `authStateChanges` is first called. - examples: - - id: listen-to-auth-changes - name: Listen to auth changes - isSpotlight: true - code: | - ```swift - for await (event, session) in await supabase.auth.authStateChanges { - print(event, session) - } - ``` - - id: list-to-a-specific-event - name: Listen to a specific event - code: | - ```swift - for await (_, session) in await supabase.auth.authStateChanges.filter({ $0.event == .signedIn }) { - // handle signIn event. - } - ``` + - id: exchange-code-for-session title: "exchangeCodeForSession()" notes: | @@ -679,6 +835,41 @@ functions: ```swift let factors = try await supabase.auth.mfa.listFactors() ``` + - id: admin-api + title: 'Overview' + notes: | + - Any method under the `supabase.auth.admin` namespace requires a `service_role` key. + - These methods are considered admin methods and should be called on a trusted server. Never expose your `service_role` key in the browser. + examples: + - id: create-auth-admin-client + name: Create server-side auth client + isSpotlight: true + code: | + ```swift + import Supabase + + let supabase = SupabaseClient( + supabaseURL: supabaseURL, + supabaseKey: serviceRoleKey + ) + + // Access auth admin api + let adminAuthClient = supabase.auth.admin + ``` + - id: delete-user + title: 'deleteUser()' + notes: | + - The `deleteUser()` method requires the user's ID, which maps to the `auth.users.id` column. + examples: + - id: removes-a-user + name: Removes a user + isSpotlight: true + code: | + ```swift + try await supabase.auth.admin.deleteUser( + id: "715ed5db-f090-4b8c-a067-640ecee36aa0" + ) + ``` - id: select title: "Fetch data: select()" notes: | @@ -2899,133 +3090,140 @@ functions: isSpotlight: true code: | ```swift - let channel = supabase - .realtime - .channel("room1") + let channel = await supabase.channel("room1") - channel - .on("broadcast", filter: ChannelFilter(event: "cursor-pos")) { message in - print("Cursor position received!", message.payload) - } - .subscribe { status, error in - if status == .subscribed { - Task { - await channel.send( - type: .broadcast, - event: "cursor-pos", - payload: ["x": Double.random(in: 0...1), "y": Double.random(in: 0...1)] - ) - } - } + let broadcastStream = await channel.broadcastStream(event: "cursor-pos") + + await channel.subscribe() + + Task { + for await message in broadcastMessage { + print("Cursor position received", message.payload) } + } + + Task { + await channel.broadcast( + event: "cursor-pos", + message: [ + "x": .double(.random(in: 0...1)), + "y": .double(.random(in: 0...1)) + ] + ) + } ``` - - id: listen-to-presence-sync - name: Listen to presence sync - isSpotlight: true + + - id: listen-to-presence-updates + name: Listen to presence updates code: | ```swift - let channel = supabase.realtime.channel("room1") - channel - .on("presence", filter: ChannelFilter(event: "sync")) { _ in - print("Synced presence state: ", channel.presenceState()) + struct PresenceState: Codable { + let username: String + } + + let channel = await supabase.channel("channelId") + + let presenceChange = await channel.presenceChange() + + await channel.subscribe() + + Task { + for await presence in presenceChange { + let joins = try presence.decodeJoins(as: PresenceState.self) + let leaves = try presence.decodeLeaves(as: PresenceState.self) } - .subscribe { status, error in - if status == .subscribed { - Task { - await channel.track(["online_at": Date().ISO8601Format()]) - } - } - } - ``` - - id: listen-to-presence-join - name: Listen to presence join - isSpotlight: true - code: | - ```swift - let channel = supabase.realtime.channel("room1") - channel - .on("presence", filter: ChannelFilter(event: "join")) { message in - print("Newly joined presences: ", message.payload) - } - .subscribe { status, error in - if status == .subscribed { - Task { - await channel.track(["online_at": Date().ISO8601Format()]) - } - } - } - ``` - - id: listen-to-presence-leave - name: Listen to presence leave - isSpotlight: true - code: | - ```swift - let channel = supabase.realtime.channel("room1") - channel - .on("presence", filter: ChannelFilter(event: "leave")) { message in - print("Newly left presences: ", message.payload) - } - .subscribe { status, error in - if status == .subscribed { - Task { - await channel.track(["online_at": Date().ISO8601Format()]) - await channel.untrack() - } - } - } - ``` + } + + // Send your own state + Task { + try await channel.track(PresenceState(username: "John")) + } + - id: listen-to-all-database-changes name: Listen to all database changes - isSpotlight: true code: | ```swift - supabase.realtime - .channel("room1") - .on("postgres_changes", filter: ChannelFilter(event: "*", schema: "*")) { message in - print("Change received!", message.payload) + let channel = await supabase.channel("channelId") + + let changeStream = await channel.postgresChange(AnyAction.self, schema: "public") + + await channel.subscribe() + + for await change in changeStream { + 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)") } - .subscribe() + } ``` - id: listen-to-a-specific-table name: Listen to a specific table code: | ```swift - supabase.realtime - .channel("room1") - .on("postgres_changes", filter: ChannelFilter(event: "*", schema: "public", table: "countries")) { message in - print("Change received!", message.payload) + let channel = await supabase.channel("channelId") + + let changeStream = await channel.postgresChange( + AnyAction.self, + schema: "public", + table: "users" + ) + + await channel.subscribe() + + for await change in changeStream { + 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)") } - .subscribe() + } ``` - id: listen-to-inserts name: Listen to inserts code: | ```swift - supabase.realtime - .channel("room1") - .on("postgres_changes", filter: ChannelFilter(event: "INSERT", schema: "public", table: "countries")) { message in - print("Change received!", message.payload) - } - .subscribe() + let channel = await supabase.channel("channelId") + + let insertions = await channel.postgresChange( + InsertAction.self, + schema: "public", + table: "users" + ) + + await channel.subscribe() + + for await insert in insertions { + print("Inserted: \(insert.record)") + } ``` - id: listen-to-updates name: Listen to updates 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 to: + enable full replication for the table you are listening too: ```sql alter table "your_table" replica identity full; ``` code: | ```swift - supabase.realtime - .channel("room1") - .on("postgres_changes", filter: ChannelFilter(event: "UPDATE", schema: "public", table: "countries")) { message in - print("Change received!", message.payload) - } - .subscribe() - ``` + let channel = await supabase.channel("channelId") + + let updates = await channel.postgresChange( + UpdateAction.self, + schema: "public", + table: "users" + ) + + await channel.subscribe() + + for await update in updates { + print("Updated: \(update.oldRecord) with \(update.record)") + } + ``` - id: listen-to-deletes name: Listen to deletes description: | @@ -3037,131 +3235,96 @@ functions: ``` code: | ```swift - supabase.realtime - .channel("room1") - .on("postgres_changes", filter: ChannelFilter(event: "DELETE", schema: "public", table: "countries")) { message in - print("Change received!", message.payload) - } - .subscribe() - ``` - - id: listen-to-multiple-events - name: Listen to multiple events - description: You can chain listeners if you want to listen to multiple events for each table. - code: | - ```swift - supabase.realtime - .channel("room1") - .on("postgres_changes", filter: ChannelFilter(event: "INSERT", schema: "public", table: "countries"), handler: handleRecordInserted) - .on("postgres_changes", filter: ChannelFilter(event: "DELETE", schema: "public", table: "countries"), handler: handleRecordDeleted) - .subscribe() + let channel = await supabase.channel("channelId") - func handleRecordInserted(_ message: Message) { - // handle message - } + let deletions = await channel.postgresChange( + DeleteAction.self, + schema: "public", + table: "users" + ) - func handleRecordDeleted(_ message: Message) { - // handle message + await channel.subscribe() + + for await deletion in deletions { + print("Deleted: \(deletion.oldRecord)") } ``` - id: listening-to-row-level-changes name: Listen to row level changes - description: You can listen to individual rows using the format `{table}:{col}=eq.{val}` - where `{col}` is the column name, and `{val}` is the value which you want to match. - notes: | - - ``eq`` filter works with all database types as under the hood, it's casting both the filter value and the database value to the correct type and then comparing them. code: | ```swift - supabase.realtime - .channel("room1") - .on( - "postgres_changes", - filter: ChannelFilter( - event: "INSERT", - schema: "public", - table: "countries", - filter: "id=eq.200" - ), - handler: handleRecordInserted - ) - .subscribe() + let channel = await supabase.channel("channelId") - func handleRecordInserted(_ message: Message) { - // handle message - } - ``` - - id: broadcast-message - title: broadcastMessage() - description: | - Broadcast a message to all connected clients to a channel. - notes: | - - When using REST you don't need to subscribe to the channel - examples: - - id: send-a-message - name: Send a message via websocket - isSpotlight: true - code: | - ```swift - supabase.realtime - .channel("room1") - .subscribe { status, error in - if status == .subscribed { - Task { - await channel.send( - type: "broadcast", - event: "cursor-pos", - payload: ["x": Double.random(in: 0...1), "y": Double.random(in: 0...1)] - ) - } - } - } - ``` - - id: send-a-message-via-rest - name: Send a message via REST - isSpotlight: true - code: | - ```swift - await supabase.realtime - .channel("room1") - .send( - type: "broadcast", - event: "cursor-pos", - payload: ["x": Double.random(in: 0...1), "y": Double.random(in: 0...1)] - ) - ``` - - id: get-channels - title: channels - examples: - - id: get-all-channels - name: Get all channels - isSpotlight: true - code: | - ```swift - let channels = supabase.realtime.channels + let deletions = await channel.postgresChange( + DeleteAction.self, + schema: "public", + table: "users", + filter: "id=eq.1" + ) + + await channel.subscribe() + + for await deletion in deletions { + print("Deleted: \(deletion.oldRecord)") + } ``` - id: remove-channel - title: removeChannel() + description: | + Unsubscribes and removes Realtime channel from Realtime client. + title: "removeChannel()" notes: | - - Removing a channel is a great way to maintain the performance of your project's Realtime service as well as your database if you're listening to Postgres changes. Supabase will automatically handle cleanup 30 seconds after a client is disconnected, but unused channels may cause degradation as more clients are simultaneously subscribed. + - Removing a channel is a great way to maintain the performance of your project's Realtime service as well as your database if you're listening to Postgres changes. + - Supabase will automatically handle cleanup 30 seconds after a client is disconnected, but unused channels may cause degradation as more clients are simultaneously subscribed. + - If you removed all channels, the client automatically disconnects from the Realtime websocket. This can be disabled in the Realtime config by setting `disconnectOnNoSubscriptions` to false. examples: - id: removes-a-channel - name: Removes a channel + name: Remove a channel isSpotlight: true code: | ```swift - supabase.realtime.remove(myChannel) - ``` + let channel = await supabase.channel("channelId") + //... + await supabase.removeChannel(channel) + ``` + - id: unsubscribe-channel + name: Unsubscribe from a channel + isSpotlight: true + code: | + ```swift + let channel = await supabase.channel("channelId") + + //... + await channel.unsubscribe() + ``` - id: remove-all-channels title: removeAllChannels() + $ref: "@supabase/supabase-js.index.SupabaseClient.removeAllChannels" notes: | + Unsubscribes and removes all Realtime channels from Realtime client. - Removing channels is a great way to maintain the performance of your project's Realtime service as well as your database if you're listening to Postgres changes. Supabase will automatically handle cleanup 30 seconds after a client is disconnected, but unused channels may cause degradation as more clients are simultaneously subscribed. + - If you removed all channels, the client automatically disconnects from the Realtime websocket. This can be disabled in the Realtime config by setting `disconnectOnNoSubscriptions` to false. examples: - id: remove-all-channels name: Remove all channels isSpotlight: true code: | ```swift - supabase.realtime.removeAllChannels() + await supabase.removeAllChannels() + ``` + - id: get-channels + title: getChannels() + $ref: "@supabase/supabase-js.index.SupabaseClient.getChannels" + notes: | + Returns all Realtime channels. + examples: + - id: get-all-channels + name: Get all channels + isSpotlight: true + code: | + ```swift + let channels = await supabase.channels ``` - id: list-buckets diff --git a/apps/studio/components/interfaces/Account/Preferences/AccountDeletion.tsx b/apps/studio/components/interfaces/Account/Preferences/AccountDeletion.tsx new file mode 100644 index 00000000000..69eeb340a98 --- /dev/null +++ b/apps/studio/components/interfaces/Account/Preferences/AccountDeletion.tsx @@ -0,0 +1,27 @@ +import Panel from 'components/ui/Panel' +import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_ } from 'ui' +import { CriticalIcon } from 'ui-patterns/Icons/StatusIcons' +import { DeleteAccountButton } from './DeleteAccountButton' + +export const AccountDeletion = () => { + return ( + <> + + + + + Request for account deletion + + Deleting your account is permanent and cannot be undone. Your data will be deleted + within 30 days, except we may retain some metadata and logs for longer where required + or permitted by law. + + + + + + + + + ) +} diff --git a/apps/studio/components/interfaces/Account/Preferences/DeleteAccountButton.tsx b/apps/studio/components/interfaces/Account/Preferences/DeleteAccountButton.tsx new file mode 100644 index 00000000000..abe01bb2986 --- /dev/null +++ b/apps/studio/components/interfaces/Account/Preferences/DeleteAccountButton.tsx @@ -0,0 +1,173 @@ +import { zodResolver } from '@hookform/resolvers/zod' +import { SupportCategories } from '@supabase/shared-types/out/constants' +import { useEffect, useState } from 'react' +import { useForm } from 'react-hook-form' +import toast from 'react-hot-toast' +import * as z from 'zod' + +import { useSendSupportTicketMutation } from 'data/feedback/support-ticket-send' +import { useOrganizationsQuery } from 'data/organizations/organizations-query' +import { useProfile } from 'lib/profile' +import { + Button, + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogSection, + DialogSectionSeparator, + DialogTitle, + DialogTrigger, + FormControl_Shadcn_, + FormField_Shadcn_, + FormItem_Shadcn_, + FormLabel_Shadcn_, + Form_Shadcn_, + Input_Shadcn_, +} from 'ui' + +export const DeleteAccountButton = () => { + const { profile } = useProfile() + const [isOpen, setIsOpen] = useState(false) + const { data: organizations, isSuccess } = useOrganizationsQuery() + + const accountEmail = profile?.primary_email + const FormSchema = z.object({ account: z.string() }) + const form = useForm>({ + mode: 'onBlur', + reValidateMode: 'onBlur', + resolver: zodResolver(FormSchema), + defaultValues: { account: '' }, + }) + const { account } = form.watch() + + const { mutate: submitSupportTicket, isLoading } = useSendSupportTicketMutation({ + onSuccess: () => { + setIsOpen(false) + toast.success( + 'Successfully submitted account deletion request - we will reach out to you via email once the request is completed!', + { duration: 8000 } + ) + }, + onError: (error) => { + toast.error(`Failed to submit account deletion request: ${error}`) + }, + }) + + const onConfirmDelete = async () => { + if (!accountEmail) return console.error('Account information is required') + + const payload = { + subject: 'Account Deletion Request', + message: 'I want to delete my account.', + category: SupportCategories.PROBLEM, + severity: 'Low', + allowSupportAccess: false, + verified: true, + projectRef: 'no-project', + } + + submitSupportTicket(payload) + } + + useEffect(() => { + if (isOpen && form !== undefined) form.reset({ account: '' }) + }, [form, isOpen]) + + return ( + + + + + + + {(organizations ?? []).length > 0 ? ( + <> + Leave all organizations before requesting account deletion + + This will allow us to process your account deletion request faster + + + ) : ( + <> + Are you sure you want to delete your account? + + Deleting your account is permanent and{' '} + cannot be undone + + + )} + + + {isSuccess && ( + <> + {organizations.length > 0 ? ( + + + Before submitting an account deletion request, please ensure that your account is + not part of any organization. This can be done by leaving leaving or deleting the + organizations that you are a part of. + + + + ) : ( + +
onConfirmDelete())} + > + ( + + + Please type{' '} + {profile?.primary_email ?? ''} to + confirm + + + + + + )} + /> + +
+ +
+ +
+ )} + + )} +
+
+ ) +} diff --git a/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/LockedQuerySection.tsx b/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/LockedQuerySection.tsx index 02dd1daac6b..224a8499203 100644 --- a/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/LockedQuerySection.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/LockedQuerySection.tsx @@ -3,11 +3,11 @@ import { Lock } from 'lucide-react' import { useRouter } from 'next/router' import { useParams } from 'common' -import { useTableEditorStateSnapshot } from 'state/table-editor' import { Button } from 'ui' import { generateAlterPolicyQuery, generateCreatePolicyQuery } from './AIPolicyEditorPanel.utils' interface LockedCreateQuerySection { + schema: string selectedPolicy?: PostgresPolicy formFields: { name: string; table: string; behavior: string; command: string; roles: string } editorOneRef: any @@ -15,6 +15,7 @@ interface LockedCreateQuerySection { } export const LockedCreateQuerySection = ({ + schema, selectedPolicy, formFields, editorOneRef, @@ -22,7 +23,6 @@ export const LockedCreateQuerySection = ({ }: LockedCreateQuerySection) => { const router = useRouter() const { ref } = useParams() - const state = useTableEditorStateSnapshot() const isEditing = selectedPolicy !== undefined const { name, table, behavior, command, roles } = formFields @@ -44,7 +44,7 @@ export const LockedCreateQuerySection = ({ const query = isEditing ? generateCreatePolicyQuery({ name, - schema: state.selectedSchemaName, + schema, table, behavior, command, @@ -58,7 +58,7 @@ export const LockedCreateQuerySection = ({ : generateAlterPolicyQuery({ name: '', newName: name, - schema: state.selectedSchemaName, + schema, table, command, roles: roles.length === 0 ? 'public' : roles, @@ -84,7 +84,7 @@ export const LockedCreateQuerySection = ({

2

- on "{state.selectedSchemaName}"." + on "{schema}"." {table}"

@@ -124,16 +124,16 @@ export const LockedCreateQuerySection = ({ export const LockedRenameQuerySection = ({ oldName, newName, + schema, table, lineNumber, }: { oldName: string newName: string + schema: string table: string lineNumber: number }) => { - const state = useTableEditorStateSnapshot() - return (
@@ -153,7 +153,7 @@ export const LockedRenameQuerySection = ({

- on "{state.selectedSchemaName}"."{table}" + on "{schema}"."{table}"

diff --git a/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/PolicyDetailsV2.tsx b/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/PolicyDetailsV2.tsx index 07fb57e485f..a7d1e848a92 100644 --- a/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/PolicyDetailsV2.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/PolicyDetailsV2.tsx @@ -1,10 +1,8 @@ import { useEffect } from 'react' -import { useParams } from 'common' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import { useDatabaseRolesQuery } from 'data/database-roles/database-roles-query' import { useTablesQuery } from 'data/tables/tables-query' -import { useTableEditorStateSnapshot } from 'state/table-editor' import { FormControl_Shadcn_, FormField_Shadcn_, @@ -24,6 +22,7 @@ import { import { MultiSelectV2 } from 'ui-patterns/MultiSelect/MultiSelectV2' interface PolicyDetailsV2Props { + schema: string searchString?: string isEditing: boolean form: any @@ -31,18 +30,18 @@ interface PolicyDetailsV2Props { } export const PolicyDetailsV2 = ({ + schema, searchString, isEditing, form, onUpdateCommand, }: PolicyDetailsV2Props) => { const { project } = useProjectContext() - const snap = useTableEditorStateSnapshot() const { data: tables, isSuccess: isSuccessTables } = useTablesQuery({ projectRef: project?.ref, connectionString: project?.connectionString, - schema: snap.selectedSchemaName, + schema: schema, sortByProperty: 'name', includeColumns: true, }) @@ -66,7 +65,7 @@ export const PolicyDetailsV2 = ({ if (!isEditing) { const table = tables?.find( (table) => - table.schema === snap.selectedSchemaName && + table.schema === schema && (table.id.toString() === searchString || table.name === searchString) ) if (table) { @@ -117,7 +116,7 @@ export const PolicyDetailsV2 = ({ onValueChange={(value) => form.setValue('table', value)} > - {snap.selectedSchemaName}.{field.value} + {schema}.{field.value} diff --git a/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/PolicyTemplates.tsx b/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/PolicyTemplates.tsx index 520534f9a50..73fefa60219 100644 --- a/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/PolicyTemplates.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/PolicyTemplates.tsx @@ -9,9 +9,9 @@ import CardButton from 'components/ui/CardButton' import CopyButton from 'components/ui/CopyButton' import NoSearchResults from 'components/ui/NoSearchResults' import { getGeneralPolicyTemplates } from '../PolicyEditorModal/PolicyEditorModal.constants' -import { useTableEditorStateSnapshot } from 'state/table-editor' interface PolicyTemplatesProps { + schema: string table: string selectedPolicy?: PostgresPolicy selectedTemplate?: string @@ -19,17 +19,14 @@ interface PolicyTemplatesProps { } export const PolicyTemplates = ({ + schema, table, selectedPolicy, selectedTemplate, onSelectTemplate, }: PolicyTemplatesProps) => { const [search, setSearch] = useState('') - const state = useTableEditorStateSnapshot() - const templates = getGeneralPolicyTemplates( - state.selectedSchemaName, - table.length > 0 ? table : 'table_name' - ) + const templates = getGeneralPolicyTemplates(schema, table.length > 0 ? table : 'table_name') const baseTemplates = selectedPolicy !== undefined diff --git a/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/index.tsx b/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/index.tsx index fc78e0cbeba..730e5ce1e5e 100644 --- a/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/index.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/index.tsx @@ -15,7 +15,6 @@ import { Checkbox_Shadcn_, Form_Shadcn_, Label_Shadcn_, - Modal, ScrollArea, Sheet, SheetContent, @@ -45,7 +44,6 @@ import { useSelectedOrganization, useSelectedProject } from 'hooks' import { BASE_PATH, OPT_IN_TAGS } from 'lib/constants' import { uuidv4 } from 'lib/helpers' import Telemetry from 'lib/telemetry' -import { useTableEditorStateSnapshot } from 'state/table-editor' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { AIPolicyChat } from './AIPolicyChat' import { @@ -70,6 +68,7 @@ const DiffEditor = dynamic( interface AIPolicyEditorPanelProps { visible: boolean + schema: string searchString?: string selectedPolicy?: PostgresPolicy onSelectCancel: () => void @@ -80,6 +79,7 @@ interface AIPolicyEditorPanelProps { */ export const AIPolicyEditorPanel = memo(function ({ visible, + schema, searchString, selectedPolicy, onSelectCancel, @@ -91,7 +91,6 @@ export const AIPolicyEditorPanel = memo(function ({ const selectedOrganization = useSelectedOrganization() const telemetryProps = useTelemetryProps() - const state = useTableEditorStateSnapshot() const isAiAssistantEnabled = useIsRLSAIAssistantEnabled() // [Joshen] Hyrid form fields, just spit balling to get a decent POC out @@ -330,7 +329,7 @@ export const AIPolicyEditorPanel = memo(function ({ if (selectedPolicy === undefined) { const sql = generateCreatePolicyQuery({ name: name, - schema: state.selectedSchemaName, + schema, table, behavior, command, @@ -534,6 +533,7 @@ export const AIPolicyEditorPanel = memo(function ({ ) : ( <>
({}) const [selectedPolicyToDelete, setSelectedPolicyToDelete] = useState({}) @@ -137,7 +137,7 @@ const Policies = ({ return ( <>
- {isLocked && } + {isLocked && } {tables.length > 0 ? ( tables.map((table: any) => (
diff --git a/apps/studio/components/interfaces/Auth/Policies/PolicyEditor/PolicyRoles.tsx b/apps/studio/components/interfaces/Auth/Policies/PolicyEditor/PolicyRoles.tsx index c8373a59039..4586d246c21 100644 --- a/apps/studio/components/interfaces/Auth/Policies/PolicyEditor/PolicyRoles.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/PolicyEditor/PolicyRoles.tsx @@ -43,7 +43,7 @@ const PolicyRoles = ({ selectedRoles, onUpdateSelectedRoles }: PolicyRolesProps)
{isLoading && } - {isError && } + {isError && } {isSuccess && ( {createdAt?.format('DD MMM, YYYY HH:mm')} - {!isUserConfirmed ? ( - Waiting for verification.. - ) : user.last_sign_in_at ? ( + {user?.last_sign_in_at ? ( lastSignedIn?.format('DD MMM, YYYY HH:mm') + ) : !isUserConfirmed ? ( + Waiting for verification.. ) : ( 'Never' )} diff --git a/apps/studio/components/interfaces/Billing/Subscription/Subscription.utils.ts b/apps/studio/components/interfaces/Billing/Subscription/Subscription.utils.ts index 24ffa175b3a..af17a9ea6fa 100644 --- a/apps/studio/components/interfaces/Billing/Subscription/Subscription.utils.ts +++ b/apps/studio/components/interfaces/Billing/Subscription/Subscription.utils.ts @@ -17,3 +17,16 @@ export const subscriptionHasHipaaAddon = (subscription?: OrgSubscription): boole (addon) => addon.supabase_prod_id === 'addon_security_hipaa' ) } + +export const billingPartnerLabel = (billingPartner?: string) => { + if (!billingPartner) return billingPartner + + switch (billingPartner) { + case 'fly': + return 'Fly.io' + case 'aws': + return 'AWS' + default: + return billingPartner + } +} diff --git a/apps/studio/components/interfaces/DataWarehouse/CreateWarehouseAccessToken.tsx b/apps/studio/components/interfaces/DataWarehouse/CreateWarehouseAccessToken.tsx new file mode 100644 index 00000000000..8d027a35157 --- /dev/null +++ b/apps/studio/components/interfaces/DataWarehouse/CreateWarehouseAccessToken.tsx @@ -0,0 +1,43 @@ +import React from 'react' +import { Button, Input, Modal } from 'ui' +import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' + +type Props = {} + +const CreateWarehouseAccessToken = (props: Props) => { + const [open, setOpen] = React.useState(false) + + return ( + <> + + { + setOpen(false) + }} + header="Create access token" + visible={open} + alignFooter="right" + onConfirm={() => { + setOpen(false) + }} + > + +
+ + + +
+
+
+ + ) +} + +export default CreateWarehouseAccessToken diff --git a/apps/studio/components/interfaces/DataWarehouse/WarehouseAccessTokens.tsx b/apps/studio/components/interfaces/DataWarehouse/WarehouseAccessTokens.tsx new file mode 100644 index 00000000000..b019a31dacf --- /dev/null +++ b/apps/studio/components/interfaces/DataWarehouse/WarehouseAccessTokens.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import CreateWarehouseAccessToken from './CreateWarehouseAccessToken' + +type Props = {} + +const WarehouseAccessTokens = (props: Props) => { + return ( +
+ +
+ ) +} + +export default WarehouseAccessTokens diff --git a/apps/studio/components/interfaces/Database/Roles/CreateRolePanel.tsx b/apps/studio/components/interfaces/Database/Roles/CreateRolePanel.tsx index 3fc02a62cc7..de9d9e846ee 100644 --- a/apps/studio/components/interfaces/Database/Roles/CreateRolePanel.tsx +++ b/apps/studio/components/interfaces/Database/Roles/CreateRolePanel.tsx @@ -26,22 +26,22 @@ interface CreateRolePanelProps { const FormSchema = z.object({ name: z.string().trim().min(1, 'You must provide a name').default(''), - is_superuser: z.boolean().default(false), - can_login: z.boolean().default(false), - can_create_role: z.boolean().default(false), - can_create_db: z.boolean().default(false), - is_replication_role: z.boolean().default(false), - can_bypass_rls: z.boolean().default(false), + isSuperuser: z.boolean().default(false), + canLogin: z.boolean().default(false), + canCreateRole: z.boolean().default(false), + canCreateDb: z.boolean().default(false), + isReplicationRole: z.boolean().default(false), + canBypassRls: z.boolean().default(false), }) const initialValues = { name: '', - is_superuser: false, - can_login: false, - can_create_role: false, - can_create_db: false, - is_replication_role: false, - can_bypass_rls: false, + isSuperuser: false, + canLogin: false, + canCreateRole: false, + canCreateDb: false, + isReplicationRole: false, + canBypassRls: false, } const CreateRolePanel = ({ visible, onClose }: CreateRolePanelProps) => { @@ -54,8 +54,8 @@ const CreateRolePanel = ({ visible, onClose }: CreateRolePanelProps) => { }) const { mutate: createDatabaseRole, isLoading: isCreating } = useDatabaseRoleCreateMutation({ - onSuccess: (res) => { - toast.success(`Successfully created new role: ${res.name}`) + onSuccess: (_, vars) => { + toast.success(`Successfully created new role: ${vars.payload.name}`) handleClose() }, }) diff --git a/apps/studio/components/interfaces/Database/Roles/DeleteRoleModal.tsx b/apps/studio/components/interfaces/Database/Roles/DeleteRoleModal.tsx index efb5dfdd93c..4422024b5c2 100644 --- a/apps/studio/components/interfaces/Database/Roles/DeleteRoleModal.tsx +++ b/apps/studio/components/interfaces/Database/Roles/DeleteRoleModal.tsx @@ -27,7 +27,7 @@ const DeleteRoleModal = ({ role, visible, onClose }: DeleteRoleModalProps) => { deleteDatabaseRole({ projectRef: project.ref, connectionString: project.connectionString, - id: role.id.toString(), + id: role.id, }) } diff --git a/apps/studio/components/interfaces/Database/Roles/RoleRow.tsx b/apps/studio/components/interfaces/Database/Roles/RoleRow.tsx index a138fd08ee9..ae1f6eb55bd 100644 --- a/apps/studio/components/interfaces/Database/Roles/RoleRow.tsx +++ b/apps/studio/components/interfaces/Database/Roles/RoleRow.tsx @@ -1,5 +1,4 @@ import * as Tooltip from '@radix-ui/react-tooltip' -import type { PostgresRole } from '@supabase/postgres-meta' import { useState } from 'react' import toast from 'react-hot-toast' import { @@ -18,13 +17,14 @@ import { } from 'ui' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' +import { PgRole } from 'data/database-roles/database-roles-query' import { useDatabaseRoleUpdateMutation } from 'data/database-roles/database-role-update-mutation' import { ROLE_PERMISSIONS } from './Roles.constants' interface RoleRowProps { - role: PostgresRole + role: PgRole disabled?: boolean - onSelectDelete: (role: PostgresRole) => void + onSelectDelete: (role: PgRole) => void } const RoleRow = ({ role, disabled = false, onSelectDelete }: RoleRowProps) => { @@ -33,25 +33,22 @@ const RoleRow = ({ role, disabled = false, onSelectDelete }: RoleRowProps) => { const { mutate: updateDatabaseRole, isLoading: isUpdating } = useDatabaseRoleUpdateMutation() - const { - is_superuser, - can_login, - can_create_role, - can_create_db, - is_replication_role, - can_bypass_rls, - } = role + const { isSuperuser, canLogin, canCreateRole, canCreateDb, isReplicationRole, canBypassRls } = + role - const onSaveChanges = async (values: any, { resetForm }: any) => { + const onSaveChanges = async (values: Partial, { resetForm }: any) => { if (!project) return console.error('Project is required') - const { is_superuser, is_replication_role, ...payload } = values + const changed = Object.fromEntries( + Object.entries(values).filter(([k, v]) => v !== (role as any)[k]) + ) + updateDatabaseRole( { projectRef: project.ref, connectionString: project.connectionString, id: role.id, - payload, + payload: changed, }, { onSuccess: () => { @@ -66,12 +63,12 @@ const RoleRow = ({ role, disabled = false, onSelectDelete }: RoleRowProps) => {
{
- {role.active_connections > 0 && ( + {role.activeConnections > 0 && (
@@ -129,10 +126,10 @@ const RoleRow = ({ role, disabled = false, onSelectDelete }: RoleRowProps) => {

0 ? 'text-foreground' : 'text-foreground-light' + role.activeConnections > 0 ? 'text-foreground' : 'text-foreground-light' }`} > - {role.active_connections} connections + {role.activeConnections} connections

{!disabled && ( diff --git a/apps/studio/components/interfaces/Database/Roles/Roles.constants.ts b/apps/studio/components/interfaces/Database/Roles/Roles.constants.ts index a8b661eec69..377dd9589c6 100644 --- a/apps/studio/components/interfaces/Database/Roles/Roles.constants.ts +++ b/apps/studio/components/interfaces/Database/Roles/Roles.constants.ts @@ -33,35 +33,35 @@ export const SYSTEM_ROLES = [ ] as const export const ROLE_PERMISSIONS = { - can_login: { + canLogin: { disabled: false, description: 'User can login', grant_by_dashboard: true, }, - can_create_role: { + canCreateRole: { disabled: false, description: 'User can create roles', grant_by_dashboard: true, }, - can_create_db: { + canCreateDb: { disabled: false, description: 'User can create databases', grant_by_dashboard: true, }, - can_bypass_rls: { - disabled: true, + canBypassRls: { + disabled: false, description: 'User bypasses every row level security policy', - grant_by_dashboard: false, + grant_by_dashboard: true, }, - is_superuser: { + isSuperuser: { disabled: true, description: 'User is a Superuser', grant_by_dashboard: false, }, - is_replication_role: { - disabled: true, + isReplicationRole: { + disabled: false, description: 'User can initiate streaming replication and put the system in and out of backup mode', - grant_by_dashboard: false, + grant_by_dashboard: true, }, } as const diff --git a/apps/studio/components/interfaces/Database/Roles/RolesList.tsx b/apps/studio/components/interfaces/Database/Roles/RolesList.tsx index 302328cac7c..089efbe930e 100644 --- a/apps/studio/components/interfaces/Database/Roles/RolesList.tsx +++ b/apps/studio/components/interfaces/Database/Roles/RolesList.tsx @@ -42,19 +42,19 @@ const RolesList = () => { const roles = sortBy(data ?? [], (r) => r.name.toLocaleLowerCase()) const filteredRoles = ( - filterType === 'active' ? roles.filter((role) => role.active_connections > 0) : roles + filterType === 'active' ? roles.filter((role) => role.activeConnections > 0) : roles ).filter((role) => role.name.includes(filterString)) const [supabaseRoles, otherRoles] = partition(filteredRoles, (role) => SUPABASE_ROLES.includes(role.name as SUPABASE_ROLE) ) const totalActiveConnections = roles - .map((role) => role.active_connections) + .map((role) => role.activeConnections) .reduce((a, b) => a + b, 0) // order the roles with active connections by number of connections, most connections first const rolesWithActiveConnections = sortBy( - roles.filter((role) => role.active_connections > 0), - (r) => -r.active_connections + roles.filter((role) => role.activeConnections > 0), + (r) => -r.activeConnections ) return ( @@ -145,7 +145,7 @@ const RolesList = () => {

Connections by roles:

{rolesWithActiveConnections.map((role) => (
- {role.name}: {role.active_connections} + {role.name}: {role.activeConnections}
))}
diff --git a/apps/studio/components/interfaces/Database/Wrappers/CreateWrapper.tsx b/apps/studio/components/interfaces/Database/Wrappers/CreateWrapper.tsx index cfd602e31fb..2dfcda06694 100644 --- a/apps/studio/components/interfaces/Database/Wrappers/CreateWrapper.tsx +++ b/apps/studio/components/interfaces/Database/Wrappers/CreateWrapper.tsx @@ -16,6 +16,7 @@ import { FormSectionLabel, FormsContainer, } from 'components/ui/Forms' +import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query' import { invalidateSchemasQuery } from 'data/database/schemas-query' import { useFDWCreateMutation } from 'data/fdw/fdw-create-mutation' import { useCheckPermissions } from 'hooks' @@ -62,6 +63,16 @@ const CreateWrapper = () => { } : {} + const { data } = useDatabaseExtensionsQuery({ + projectRef: project?.ref, + connectionString: project?.connectionString, + }) + + const wrappersExtension = data?.find((ext) => ext.name === 'wrappers') + + const hasRequiredVersion = + (wrappersExtension?.installed_version ?? '') >= (wrapperMeta?.minimumExtensionVersion ?? '') + if (wrapperMeta === undefined) { return (
@@ -79,6 +90,37 @@ const CreateWrapper = () => { ) } + if (!hasRequiredVersion) { + const databaseNeedsUpgrading = + wrappersExtension?.installed_version !== wrappersExtension?.default_version + + return ( +
+
+

Your extension version is outdated for this wrapper.

+

+ The wrapper type {type} requires a minimum extension version of{' '} + {wrapperMeta.minimumExtensionVersion}. You have version{' '} + {wrappersExtension?.installed_version} installed. Please{' '} + {databaseNeedsUpgrading && 'upgrade your database then '}reinstall the extension to + create this wrapper. +

+
+ +
+ ) + } + const onUpdateTable = (values: any) => { setNewTables((prev) => { // if the new values have tableIndex, we are editing an existing table diff --git a/apps/studio/components/interfaces/Database/Wrappers/Wrappers.constants.ts b/apps/studio/components/interfaces/Database/Wrappers/Wrappers.constants.ts index 0747929f964..1e55b30080f 100644 --- a/apps/studio/components/interfaces/Database/Wrappers/Wrappers.constants.ts +++ b/apps/studio/components/interfaces/Database/Wrappers/Wrappers.constants.ts @@ -9,6 +9,10 @@ export const WRAPPER_HANDLERS = { BIG_QUERY: 'big_query_fdw_handler', AIRTABLE: 'airtable_fdw_handler', LOGFLARE: 'logflare_fdw_handler', + AUTH0: 'auth0_fdw_handler', + COGNITO: 'cognito_fdw_handler', + MSSQL: 'mssql_fdw_handler', + REDIS: 'redis_fdw_handler', } export const WRAPPERS: WrapperMeta[] = [ @@ -831,4 +835,335 @@ export const WRAPPERS: WrapperMeta[] = [ }, ], }, + { + name: 'auth0_wrapper', + handlerName: WRAPPER_HANDLERS.AUTH0, + validatorName: 'auth0_fdw_validator', + icon: `${BASE_PATH}/img/icons/auth0-icon.svg`, + extensionName: 'Auth0Fdw', + label: 'Auth0', + docsUrl: 'https://supabase.com/docs/guides/database/extensions/wrappers/auth0', + minimumExtensionVersion: '0.3.0', + server: { + options: [ + { + name: 'api_key_id', + label: 'Auth0 API key or PAT', + required: true, + encrypted: true, + hidden: true, + }, + { + name: 'url', + label: 'Auth0 API URL', + defaultValue: 'https://dev-.us.auth0.com/api/v2/users', + required: false, + encrypted: false, + hidden: false, + }, + ], + }, + tables: [ + { + label: 'Users', + description: 'Auth0 Users', + availableColumns: [ + { + name: 'user_id', + type: 'text', + }, + { + name: 'email', + type: 'text', + }, + { + name: 'email_verified', + type: 'boolean', + }, + { + name: 'username', + type: 'text', + }, + { + name: 'phone_number', + type: 'text', + }, + { + name: 'phone_verified', + type: 'boolean', + }, + { + name: 'created_at', + type: 'jsonb', + }, + { + name: 'updated_at', + type: 'jsonb', + }, + { + name: 'identities', + type: 'jsonb', + }, + { + name: 'app_metadata', + type: 'jsonb', + }, + { + name: 'user_metadata', + type: 'jsonb', + }, + { + name: 'picture', + type: 'text', + }, + { + name: 'name', + type: 'text', + }, + { + name: 'nickname', + type: 'text', + }, + { + name: 'multifactor', + type: 'jsonb', + }, + { + name: 'last_ip', + type: 'text', + }, + { + name: 'last_login', + type: 'jsonb', + }, + { + name: 'logins_count', + type: 'integer', + }, + { + name: 'blocked', + type: 'boolean', + }, + { + name: 'given_name', + type: 'text', + }, + { + name: 'family_name', + type: 'text', + }, + ], + options: [ + { + name: 'object', + defaultValue: 'users', + editable: false, + required: true, + type: 'text', + }, + ], + }, + ], + }, + { + name: 'cognito_wrapper', + handlerName: WRAPPER_HANDLERS.COGNITO, + validatorName: 'cognito_fdw_validator', + icon: `${BASE_PATH}/img/icons/cognito-icon.svg`, + extensionName: 'CognitoFdw', + label: 'Cognito', + docsUrl: 'https://supabase.com/docs/guides/database/extensions/wrappers/cognito', + minimumExtensionVersion: '0.3.0', + server: { + options: [ + { + name: 'aws_access_key_id', + label: 'AWS Access Key ID', + required: true, + encrypted: false, + hidden: false, + }, + { + name: 'api_key_id', + label: 'AWS Secret Key', + required: true, + encrypted: true, + hidden: true, + }, + { + name: 'region', + label: 'Region', + required: true, + encrypted: false, + hidden: false, + }, + { + name: 'user_pool_id', + label: 'User Pool ID', + required: true, + encrypted: false, + hidden: false, + }, + ], + }, + tables: [ + { + label: 'Users', + description: 'Cognito Users', + availableColumns: [ + { + name: 'email', + type: 'text', + }, + { + name: 'created_at', + type: 'text', + }, + { + name: 'email_verified', + type: 'boolean', + }, + { + name: 'identities', + type: 'jsonb', + }, + { + name: 'username', + type: 'text', + }, + { + name: 'status', + type: 'text', + }, + ], + options: [ + { + name: 'object', + defaultValue: 'users', + editable: false, + required: true, + type: 'text', + }, + ], + }, + ], + }, + { + name: 'mssql_wrapper', + handlerName: WRAPPER_HANDLERS.MSSQL, + validatorName: 'mssql_fdw_validator', + icon: `${BASE_PATH}/img/icons/mssql-icon.svg`, + extensionName: 'mssqlFdw', + label: 'Microsoft SQL Server', + docsUrl: 'https://supabase.com/docs/guides/database/extensions/wrappers/mssql', + minimumExtensionVersion: '0.3.0', + server: { + options: [ + { + name: 'conn_string_id', + label: 'Connection String', + required: true, + encrypted: true, + hidden: true, + }, + ], + }, + tables: [ + { + label: 'Microsoft SQL Server Table', + description: 'Map to an Microsoft SQL Server Table', + options: [ + { + name: 'table', + label: 'MSSQL Table', + editable: true, + required: true, + type: 'text', + }, + ], + }, + ], + }, + { + name: 'redis_wrapper', + handlerName: WRAPPER_HANDLERS.REDIS, + validatorName: 'redis_fdw_validator', + icon: `${BASE_PATH}/img/icons/redis-icon.svg`, + extensionName: 'redisFdw', + label: 'Redis', + docsUrl: 'https://supabase.com/docs/guides/database/extensions/wrappers/redis', + minimumExtensionVersion: '0.3.0', + server: { + options: [ + { + name: 'conn_url_id', + label: 'Connection URL', + required: true, + encrypted: true, + hidden: true, + }, + ], + }, + tables: [ + { + label: 'Redis Table', + description: 'Map to an Redis Table', + options: [ + { + name: 'src_type', + label: 'Source Type', + editable: true, + required: true, + type: 'select', + defaultValue: 'list', + options: [ + { + label: 'list', + value: 'list', + }, + { + label: 'set', + value: 'set', + }, + { + label: 'hash', + value: 'hash', + }, + { + label: 'zset', + value: 'zset', + }, + { + label: 'stream', + value: 'stream', + }, + { + label: 'multi_list', + value: 'multi_list', + }, + { + label: 'multi_set', + value: 'multi_set', + }, + { + label: 'multi_hash', + value: 'multi_hash', + }, + { + label: 'multi_zset', + value: 'multi_zset', + }, + ], + }, + { + name: 'src_key', + label: 'Source Key', + editable: true, + required: false, + type: 'text', + }, + ], + }, + ], + }, ] diff --git a/apps/studio/components/interfaces/Database/Wrappers/Wrappers.types.ts b/apps/studio/components/interfaces/Database/Wrappers/Wrappers.types.ts index d5b57e66937..a95aaed81b5 100644 --- a/apps/studio/components/interfaces/Database/Wrappers/Wrappers.types.ts +++ b/apps/studio/components/interfaces/Database/Wrappers/Wrappers.types.ts @@ -8,6 +8,7 @@ export type WrapperMeta = { server: Server tables: Table[] docsUrl: string + minimumExtensionVersion?: string } export type ServerOption = { diff --git a/apps/studio/components/interfaces/Database/Wrappers/WrappersDisabledState.tsx b/apps/studio/components/interfaces/Database/Wrappers/WrappersDisabledState.tsx index bff82310924..dd3f98d1a3c 100644 --- a/apps/studio/components/interfaces/Database/Wrappers/WrappersDisabledState.tsx +++ b/apps/studio/components/interfaces/Database/Wrappers/WrappersDisabledState.tsx @@ -67,7 +67,7 @@ const WrappersDisabledState = () => { } } finally { setIsEnabling(false) - if (!hasError) toast.success('Wrappers is now enabled!') + if (!hasError) toast.success('Wrappers are now enabled!') } } diff --git a/apps/studio/components/interfaces/Home/Connect/Connect.constants.ts b/apps/studio/components/interfaces/Home/Connect/Connect.constants.ts index 7293d31270b..9814e1e6dfe 100644 --- a/apps/studio/components/interfaces/Home/Connect/Connect.constants.ts +++ b/apps/studio/components/interfaces/Home/Connect/Connect.constants.ts @@ -169,6 +169,20 @@ export const FRAMEWORKS: ConnectionType[] = [ }, ], }, + { + key: 'flutter', + label: 'Flutter', + icon: 'flutter', + guideLink: 'https://supabase.com/docs/guides/getting-started/tutorials/with-flutter', + children: [ + { + key: 'supabaseflutter', + label: 'supabase-flutter', + children: [], + icon: 'supabase', + }, + ], + }, ] export const ORMS: ConnectionType[] = [ diff --git a/apps/studio/components/interfaces/Home/Connect/content/flutter/supabaseflutter/content.tsx b/apps/studio/components/interfaces/Home/Connect/content/flutter/supabaseflutter/content.tsx new file mode 100644 index 00000000000..16358840cfb --- /dev/null +++ b/apps/studio/components/interfaces/Home/Connect/content/flutter/supabaseflutter/content.tsx @@ -0,0 +1,93 @@ +import type { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types' + +import { + ConnectTabs, + ConnectTabTriggers, + ConnectTabTrigger, + ConnectTabContent, +} from 'components/interfaces/Home/Connect/ConnectTabs' +import SimpleCodeBlock from 'components/to-be-cleaned/SimpleCodeBlock' + +const ContentFile = ({ projectKeys }: ContentFileProps) => { + return ( + + + + + + + + + {` +import 'package:supabase_flutter/supabase_flutter.dart'; + +Future main() async { + await Supabase.initialize( + url: ${projectKeys.apiUrl ?? 'your-project-url'}, + anonKey: ${projectKeys.anonKey ?? 'your-anon-key'}, + ); + runApp(MyApp()); +} + `} + + + + + + {` +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + title: 'Todos', + home: HomePage(), + ); + } +} + +class HomePage extends StatefulWidget { + const HomePage({super.key}); + + @override + State createState() => _HomePageState(); +} + +class _HomePageState extends State { + final _future = Supabase.instance.client + .from('todos') + .select(); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: FutureBuilder( + future: _future, + builder: (context, snapshot) { + if (!snapshot.hasData) { + return const Center(child: CircularProgressIndicator()); + } + final todos = snapshot.data!; + return ListView.builder( + itemCount: todos.length, + itemBuilder: ((context, index) { + final todo = todos[index]; + return ListTile( + title: Text(todo['name']), + ); + }), + ); + }, + ), + ); + } +} +`} + + + + ) +} + +export default ContentFile diff --git a/apps/studio/components/interfaces/Home/Home.constants.ts b/apps/studio/components/interfaces/Home/Home.constants.ts index 7d6ba1254e4..ee33d1b4140 100644 --- a/apps/studio/components/interfaces/Home/Home.constants.ts +++ b/apps/studio/components/interfaces/Home/Home.constants.ts @@ -30,10 +30,10 @@ export const CLIENT_LIBRARIES = [ }, { language: 'Swift', - officialSupport: false, + officialSupport: true, releaseState: undefined, docsUrl: 'https://supabase.com/docs/reference/swift/initializing', - gitUrl: 'https://github.com/supabase-community/supabase-swift', + gitUrl: 'https://github.com/supabase/supabase-swift', }, { language: 'Kotlin', diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/BillingSettings.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/BillingSettings.tsx index fcdc869f523..6ac079293c9 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/BillingSettings.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/BillingSettings.tsx @@ -8,14 +8,25 @@ import CreditBalance from './CreditBalance' import PaymentMethods from './PaymentMethods/PaymentMethods' import Subscription from './Subscription/Subscription' import TaxID from './TaxID/TaxID' +import { useParams } from 'common' +import { useOrgSubscriptionQuery } from '../../../../data/subscriptions/org-subscription-query' const BillingSettings = () => { const { - billingAccountData: billingAccountDataEnabled, - billingPaymentMethods: billingPaymentMethodsEnabled, - billingCredits: billingCreditsEnabled, + billingAccountData: isBillingAccountDataEnabledOnProfileLevel, + billingPaymentMethods: isBillingPaymentMethodsEnabledOnProfileLevel, + billingCredits: isBillingCreditsEnabledOnProfileLevel, } = useIsFeatureEnabled(['billing:account_data', 'billing:payment_methods', 'billing:credits']) + const { slug: orgSlug } = useParams() + const { data: subscription } = useOrgSubscriptionQuery({ orgSlug }) + const isNotOrgWithPartnerBilling = !subscription?.billing_via_partner ?? true + + const billingAccountDataEnabled = + isBillingAccountDataEnabledOnProfileLevel && isNotOrgWithPartnerBilling + const billingPaymentMethodsEnabled = + isBillingPaymentMethodsEnabledOnProfileLevel && isNotOrgWithPartnerBilling + return ( <> @@ -34,7 +45,7 @@ const BillingSettings = () => { - {billingCreditsEnabled && ( + {isBillingCreditsEnabledOnProfileLevel && ( <> diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.tsx index ad2c6da28df..e6bcdf25432 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.tsx @@ -39,6 +39,7 @@ import ExitSurveyModal from './ExitSurveyModal' import UpgradeSurveyModal from './UpgradeModal' import MembersExceedLimitModal from './MembersExceedLimitModal' import PaymentMethodSelection from './PaymentMethodSelection' +import { billingPartnerLabel } from 'components/interfaces/Billing/Subscription/Subscription.utils' const PlanUpdateSidePanel = () => { const router = useRouter() @@ -545,8 +546,9 @@ const PlanUpdateSidePanel = () => { ) : (

- This organization is billed through our partner Fly.io and you will be charged by - them directly. + This organization is billed through our partner{' '} + {billingPartnerLabel(subscription?.billing_partner)} and you will be charged by them + directly.

{subscriptionPreview?.billed_via_partner && subscriptionPreview?.plan_change_type === 'downgrade' && ( diff --git a/apps/studio/components/interfaces/QueryPerformanceV2/IndexAdvisorDisabledState.tsx b/apps/studio/components/interfaces/QueryPerformanceV2/IndexAdvisorDisabledState.tsx index 8f277142a18..f25c2cf93ce 100644 --- a/apps/studio/components/interfaces/QueryPerformanceV2/IndexAdvisorDisabledState.tsx +++ b/apps/studio/components/interfaces/QueryPerformanceV2/IndexAdvisorDisabledState.tsx @@ -3,23 +3,23 @@ import { ExternalLink } from 'lucide-react' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import { useDatabaseExtensionEnableMutation } from 'data/database-extensions/database-extension-enable-mutation' import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query' -import { useIndexAdvisorEnableMutation } from 'data/database/index-advisor-enable-mutation' import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, Button } from 'ui' import { Markdown } from '../Markdown' +import Link from 'next/link' +import { useParams } from 'common' export const IndexAdvisorDisabledState = () => { + const { ref } = useParams() const { project } = useProjectContext() const { data: extensions } = useDatabaseExtensionsQuery({ projectRef: project?.ref, connectionString: project?.connectionString, }) const hypopgExtension = (extensions ?? []).find((ext) => ext.name === 'hypopg') + const indexAdvisorExtension = (extensions ?? []).find((ext) => ext.name === 'index_advisor') const { mutateAsync: enableExtension, isLoading: isEnablingExtension } = useDatabaseExtensionEnableMutation() - const { mutateAsync: enableIndexAdvisor, isLoading: isEnablingIndexAdvisor } = - useIndexAdvisorEnableMutation() - const isEnabling = isEnablingExtension || isEnablingIndexAdvisor const onEnableIndexAdvisor = async () => { if (project === undefined) return console.error('Project is required') @@ -32,33 +32,56 @@ export const IndexAdvisorDisabledState = () => { version: hypopgExtension.default_version, }) } - - // [Joshen] Once index_advisor ext is ready, it'll just be enabling an extension - await enableIndexAdvisor({ - projectRef: project.ref, - connectionString: project.connectionString, - }) + if (indexAdvisorExtension?.installed_version === null) { + await enableExtension({ + projectRef: project?.ref, + connectionString: project?.connectionString, + name: indexAdvisorExtension.name, + schema: indexAdvisorExtension?.schema ?? 'extensions', + version: indexAdvisorExtension.default_version, + }) + } } return ( - + - Get index suggestions to improve your query performance + - + +
- - + ) : ( + + )} + + ) default: assertUnreachable(title) return <> diff --git a/apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx b/apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx index 0bd784d291c..14204939c98 100644 --- a/apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx @@ -162,7 +162,7 @@ const GridHeaderActions = ({ table, isViewSelected, isTableSelected }: GridHeade > Add RLS policy @@ -202,7 +202,10 @@ const GridHeaderActions = ({ table, isViewSelected, isTableSelected }: GridHeade ) } > - + Auth {policies.length > 1 ? 'policies' : 'policy'} diff --git a/apps/studio/components/layouts/OrganizationLayout.tsx b/apps/studio/components/layouts/OrganizationLayout.tsx index 7e18c7a7df7..c0f60b76f7b 100644 --- a/apps/studio/components/layouts/OrganizationLayout.tsx +++ b/apps/studio/components/layouts/OrganizationLayout.tsx @@ -8,13 +8,17 @@ import { AccountLayout } from './' import { ScaffoldContainer, ScaffoldDivider, ScaffoldHeader, ScaffoldTitle } from './Scaffold' import SettingsLayout from './SettingsLayout/SettingsLayout' import Link from 'next/link' +import { useOrgSubscriptionQuery } from '../../data/subscriptions/org-subscription-query' const OrganizationLayout = ({ children }: PropsWithChildren<{}>) => { const selectedOrganization = useSelectedOrganization() const router = useRouter() const { slug } = useParams() - const invoicesEnabled = useIsFeatureEnabled('billing:invoices') + const invoicesEnabledOnProfileLevel = useIsFeatureEnabled('billing:invoices') + const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: slug }) + const isNotOrgWithPartnerBilling = !subscription?.billing_via_partner ?? true + const invoicesEnabled = invoicesEnabledOnProfileLevel && isNotOrgWithPartnerBilling const navLayoutV2 = useFlag('navigationLayoutV2') diff --git a/apps/studio/components/to-be-cleaned/Storage/StorageSettings/S3Connection.tsx b/apps/studio/components/to-be-cleaned/Storage/StorageSettings/S3Connection.tsx new file mode 100644 index 00000000000..1863d0b07c8 --- /dev/null +++ b/apps/studio/components/to-be-cleaned/Storage/StorageSettings/S3Connection.tsx @@ -0,0 +1,343 @@ +import { differenceInDays } from 'date-fns' +import React from 'react' +import toast from 'react-hot-toast' + +import { useParams } from 'common' +import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' +import Table from 'components/to-be-cleaned/Table' +import CopyButton from 'components/ui/CopyButton' +import { FormHeader } from 'components/ui/Forms' +import Panel from 'components/ui/Panel' +import { useProjectApiQuery } from 'data/config/project-api-query' +import { useS3AccessKeyCreateMutation } from 'data/storage/s3-access-key-create-mutation' +import { useS3AccessKeyDeleteMutation } from 'data/storage/s3-access-key-delete-mutation' +import { useStorageCredentialsQuery } from 'data/storage/s3-access-key-query' +import { + Button, + Dialog, + DialogContent, + DialogDescription, + DialogTitle, + DialogTrigger, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, + IconMoreVertical, + IconTrash, + cn, +} from 'ui' +import { GenericSkeletonLoader } from 'ui-patterns' +import { Input } from 'ui-patterns/DataInputs/Input' +import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' + +export const S3Connection = () => { + const [openCreateCred, setOpenCreateCred] = React.useState(false) + const [showSuccess, setShowSuccess] = React.useState(false) + const [openDeleteDialog, setOpenDeleteDialog] = React.useState(false) + const [deleteCredId, setDeleteCredId] = React.useState(null) + + const { ref: projectRef } = useParams() + const { project, isLoading: projectIsLoading } = useProjectContext() + + const { data: storageCreds, ...storageCredsQuery } = useStorageCredentialsQuery({ + projectRef, + }) + + const hasStorageCreds = storageCreds?.data && storageCreds.data.length > 0 + + const createS3AccessKey = useS3AccessKeyCreateMutation({ + projectRef, + }) + + const deleteS3AccessKey = useS3AccessKeyDeleteMutation({ + projectRef, + }) + + const { data: projectAPI } = useProjectApiQuery({ projectRef: projectRef }) + + function getConnectionURL() { + const projUrl = projectAPI + ? `${projectAPI.autoApiService.protocol}://${projectAPI.autoApiService.endpoint}` + : `https://${projectRef}.supabase.co` + + const url = new URL(projUrl) + url.pathname = '/storage/v1/s3' + return url.toString() + } + + const s3connectionUrl = getConnectionURL() + + return ( + <> +
+ + + + + + {projectIsLoading ? ( + <> + ) : ( + + + + )} + +
+ +
+ { + setOpenCreateCred(open) + if (!open) setShowSuccess(false) + }} + > + + + + + { + if (showSuccess) { + e.preventDefault() + } + }} + > + {showSuccess ? ( + <> + Save your new S3 access keys + + You won't be able to see them again. If you lose these credentials, you'll + need to create a new ones. + + + + + + + + +
+ +
+ + ) : ( + <> + Create new S3 access keys + { + e.preventDefault() + + const formData = new FormData(e.target as HTMLFormElement) + const description = formData.get('description') as string + + await createS3AccessKey.mutateAsync({ description }) + setShowSuccess(true) + }} + > + + + + +
+ +
+ + + )} +
+ + } + /> + +
+ {storageCredsQuery.isLoading ? ( +
+ +
+ ) : ( +
+ Description, + Access key id, + Created at, + , + ]} + body={ + hasStorageCreds ? ( + storageCreds.data?.map((cred: any) => ( + { + setDeleteCredId(cred.id) + setOpenDeleteDialog(true) + }} + /> + )) + ) : ( + + +

No credentials created

+

+ There are no credentials associated with your project yet +

+
+
+ ) + } + /> + + )} + + + + + + Revoke S3 access keys + + This action is irreversible and requests made with these credentials will stop working. + +
+ + +
+
+
+ + ) +} + +function StorageCredItem({ + description, + id, + created_at, + access_key, + onDeleteClick, +}: { + description: string + id: string + created_at: string + access_key: string + onDeleteClick: (id: string) => void +}) { + function daysSince(date: string) { + const now = new Date() + const created = new Date(date) + const diffInDays = differenceInDays(now, created) + + if (diffInDays === 0) { + return 'Today' + } else if (diffInDays === 1) { + return `${diffInDays} day ago` + } else { + return `${diffInDays} days ago` + } + } + + return ( +
+ + + + + + ) +} diff --git a/apps/studio/components/ui/Forms/FormHeader.tsx b/apps/studio/components/ui/Forms/FormHeader.tsx index 0e5c0ad4606..2660123980d 100644 --- a/apps/studio/components/ui/Forms/FormHeader.tsx +++ b/apps/studio/components/ui/Forms/FormHeader.tsx @@ -4,16 +4,19 @@ import ReactMarkdown from 'react-markdown' import { Markdown } from 'components/interfaces/Markdown' import { Button } from 'ui' +import { ReactNode } from 'react' const FormHeader = ({ title, description, docsUrl, + actions, className, }: { title: string description?: string docsUrl?: string + actions?: ReactNode className?: string }) => { return ( @@ -26,13 +29,16 @@ const FormHeader = ({ {description && } - {docsUrl !== undefined && ( - - )} +
+ {docsUrl !== undefined && ( + + )} + {actions} +
) } diff --git a/apps/studio/components/ui/Panel.tsx b/apps/studio/components/ui/Panel.tsx index 84f9a7b1a89..76965332a19 100644 --- a/apps/studio/components/ui/Panel.tsx +++ b/apps/studio/components/ui/Panel.tsx @@ -25,8 +25,13 @@ function Panel(props: PropsWithChildren) { )} > {props.title && ( -
-
{props.title}
+
+ {props.title}
)} {props.children} diff --git a/apps/studio/data/api.d.ts b/apps/studio/data/api.d.ts index 906f7db3d63..36751c96d84 100644 --- a/apps/studio/data/api.d.ts +++ b/apps/studio/data/api.d.ts @@ -628,7 +628,11 @@ export interface paths { } '/platform/projects/{ref}/analytics/warehouse/query': { /** Lists project's warehouse queries from logflare */ +<<<<<<< HEAD get: operations['WarehouseQueryController_runQuery'] +======= + get: operations['QueryController_runQuery'] +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 } '/platform/projects/{ref}/config/pgbouncer': { /** Gets project's pgbouncer config */ @@ -1464,7 +1468,11 @@ export interface paths { } '/v0/projects/{ref}/analytics/warehouse/query': { /** Lists project's warehouse queries from logflare */ +<<<<<<< HEAD get: operations['WarehouseQueryController_runQuery'] +======= + get: operations['QueryController_runQuery'] +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 } '/v0/projects/{ref}/config/pgbouncer': { /** Gets project's pgbouncer config */ @@ -1925,6 +1933,7 @@ export type webhooks = Record export interface components { schemas: { +<<<<<<< HEAD UpdateNotificationsBodyV1: Record UpdateNotificationBodyV2: Record ResetPasswordBody: Record @@ -1948,6 +1957,881 @@ export interface components { RemoveMemberBody: Record UpdateMemberBody: Record DetachPaymentMethodBody: Record +======= + NotificationResponseV1: { + id: string + inserted_at: string + project_id: number + notification_name: string + notification_status: string + data: Record + meta: Record + } + UpdateNotificationsBodyV1: { + ids: string[] + } + NotificationAction: { + label: string + url?: string + action_type?: string + } + NotificationData: { + org_slug?: string + project_ref?: string + title: string + message?: string + actions?: components['schemas']['NotificationAction'][] + } + NotificationResponseV2: { + /** @enum {string} */ + status: 'new' | 'seen' | 'archived' + /** @enum {string} */ + priority: 'Critical' | 'Warning' | 'Info' + data: components['schemas']['NotificationData'] + id: string + inserted_at: string + name: string + meta: Record + } + NotificationsSummary: { + unread_count: number + has_warning: boolean + has_critical: boolean + } + UpdateNotificationBodyV2: { + id: string + /** @enum {string} */ + status: 'new' | 'seen' | 'archived' + } + ResetPasswordBody: { + email: string + redirectTo?: string + } + SendFeedbackBody: { + message: string + affectedServices?: string + library?: string + category: string + subject?: string + tags: string[] + additionalRedirectUrls?: string + pathname?: string + projectRef?: string + organizationSlug?: string + severity?: string + siteUrl?: string + urlToAirTable?: string + allowSupportAccess?: boolean + verified?: boolean + browserInformation?: string + } + SendFeedbackResponse: { + result: string + } + SendExitSurveyBody: { + projectRef?: string + orgSlug?: string + reasons: string + additionalFeedback?: string + exitAction?: string + } + SendUpgradeSurveyBody: { + orgSlug?: string + prevPlan?: string + currentPlan?: string + reasons: string[] + additionalFeedback?: string + } + SendDocsFeedbackBody: { + page: string + isHelpful: boolean + title: string + feedback: string + } + SignUpBody: { + email: string + password: string + redirectTo?: string + } + ProjectResourceWarningsResponse: { + /** @enum {string|null} */ + need_pitr: 'critical' | 'warning' | null + /** @enum {string|null} */ + disk_io_exhaustion: 'critical' | 'warning' | null + /** @enum {string|null} */ + disk_space_exhaustion: 'critical' | 'warning' | null + /** @enum {string|null} */ + cpu_exhaustion: 'critical' | 'warning' | null + /** @enum {string|null} */ + memory_and_swap_exhaustion: 'critical' | 'warning' | null + /** @enum {string|null} */ + auth_rate_limit_exhaustion: 'critical' | 'warning' | null + project: string + is_readonly_mode_enabled: boolean + } + GoTrueConfigResponse: { + SITE_URL: string + DISABLE_SIGNUP: boolean + JWT_EXP: number + SMTP_ADMIN_EMAIL: string + SMTP_HOST: string + SMTP_PORT: string + SMTP_USER: string + SMTP_PASS: string + SMTP_MAX_FREQUENCY: number + SMTP_SENDER_NAME: string + MAILER_AUTOCONFIRM: boolean + MAILER_ALLOW_UNVERIFIED_EMAIL_SIGN_INS: boolean + MAILER_SUBJECTS_INVITE: string + MAILER_SUBJECTS_CONFIRMATION: string + MAILER_SUBJECTS_RECOVERY: string + MAILER_SUBJECTS_EMAIL_CHANGE: string + MAILER_SUBJECTS_MAGIC_LINK: string + MAILER_SUBJECTS_REAUTHENTICATION: string + MAILER_TEMPLATES_INVITE_CONTENT: string + MAILER_TEMPLATES_CONFIRMATION_CONTENT: string + MAILER_TEMPLATES_RECOVERY_CONTENT: string + MAILER_TEMPLATES_EMAIL_CHANGE_CONTENT: string + MAILER_TEMPLATES_MAGIC_LINK_CONTENT: string + MAILER_TEMPLATES_REAUTHENTICATION_CONTENT: string + MFA_MAX_ENROLLED_FACTORS: number + URI_ALLOW_LIST: string + EXTERNAL_ANONYMOUS_USERS_ENABLED: boolean + EXTERNAL_EMAIL_ENABLED: boolean + EXTERNAL_PHONE_ENABLED: boolean + SAML_ENABLED: boolean + SECURITY_CAPTCHA_ENABLED: boolean + SECURITY_CAPTCHA_PROVIDER: string + SECURITY_CAPTCHA_SECRET: string + SESSIONS_TIMEBOX: number + SESSIONS_INACTIVITY_TIMEOUT: number + SESSIONS_SINGLE_PER_USER: boolean + SESSIONS_TAGS: string + RATE_LIMIT_ANONYMOUS_USERS: number + RATE_LIMIT_EMAIL_SENT: number + RATE_LIMIT_SMS_SENT: number + RATE_LIMIT_VERIFY: number + RATE_LIMIT_TOKEN_REFRESH: number + MAILER_SECURE_EMAIL_CHANGE_ENABLED: boolean + REFRESH_TOKEN_ROTATION_ENABLED: boolean + PASSWORD_HIBP_ENABLED: boolean + PASSWORD_MIN_LENGTH: number + PASSWORD_REQUIRED_CHARACTERS: string + SECURITY_MANUAL_LINKING_ENABLED: boolean + SECURITY_UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION: boolean + SECURITY_REFRESH_TOKEN_REUSE_INTERVAL: number + MAILER_OTP_EXP: number + SMS_AUTOCONFIRM: boolean + SMS_MAX_FREQUENCY: number + SMS_OTP_EXP: number + SMS_OTP_LENGTH: number + SMS_PROVIDER: string + SMS_MESSAGEBIRD_ACCESS_KEY: string + SMS_MESSAGEBIRD_ORIGINATOR: string + SMS_TEXTLOCAL_API_KEY: string + SMS_TEXTLOCAL_SENDER: string + SMS_TWILIO_ACCOUNT_SID: string + SMS_TWILIO_AUTH_TOKEN: string + SMS_TWILIO_CONTENT_SID: string + SMS_TWILIO_MESSAGE_SERVICE_SID: string + SMS_TWILIO_VERIFY_ACCOUNT_SID: string + SMS_TWILIO_VERIFY_AUTH_TOKEN: string + SMS_TWILIO_VERIFY_MESSAGE_SERVICE_SID: string + SMS_VONAGE_API_KEY: string + SMS_VONAGE_API_SECRET: string + SMS_VONAGE_FROM: string + SMS_TEMPLATE: string + SMS_TEST_OTP: string + SMS_TEST_OTP_VALID_UNTIL: string + HOOK_MFA_VERIFICATION_ATTEMPT_ENABLED: boolean + HOOK_MFA_VERIFICATION_ATTEMPT_URI: string + HOOK_PASSWORD_VERIFICATION_ATTEMPT_ENABLED: boolean + HOOK_PASSWORD_VERIFICATION_ATTEMPT_URI: string + HOOK_CUSTOM_ACCESS_TOKEN_ENABLED: boolean + HOOK_CUSTOM_ACCESS_TOKEN_URI: string + HOOK_SEND_SMS_ENABLED: boolean + HOOK_SEND_SMS_URI: string + HOOK_SEND_EMAIL_ENABLED: boolean + HOOK_SEND_EMAIL_URI: string + EXTERNAL_APPLE_ENABLED: boolean + EXTERNAL_APPLE_CLIENT_ID: string + EXTERNAL_APPLE_SECRET: string + EXTERNAL_APPLE_ADDITIONAL_CLIENT_IDS: string + EXTERNAL_AZURE_ENABLED: boolean + EXTERNAL_AZURE_CLIENT_ID: string + EXTERNAL_AZURE_SECRET: string + EXTERNAL_AZURE_URL: string + EXTERNAL_BITBUCKET_ENABLED: boolean + EXTERNAL_BITBUCKET_CLIENT_ID: string + EXTERNAL_BITBUCKET_SECRET: string + EXTERNAL_DISCORD_ENABLED: boolean + EXTERNAL_DISCORD_CLIENT_ID: string + EXTERNAL_DISCORD_SECRET: string + EXTERNAL_FACEBOOK_ENABLED: boolean + EXTERNAL_FACEBOOK_CLIENT_ID: string + EXTERNAL_FACEBOOK_SECRET: string + EXTERNAL_FIGMA_ENABLED: boolean + EXTERNAL_FIGMA_CLIENT_ID: string + EXTERNAL_FIGMA_SECRET: string + EXTERNAL_GITHUB_ENABLED: boolean + EXTERNAL_GITHUB_CLIENT_ID: string + EXTERNAL_GITHUB_SECRET: string + EXTERNAL_GITLAB_ENABLED: boolean + EXTERNAL_GITLAB_CLIENT_ID: string + EXTERNAL_GITLAB_SECRET: string + EXTERNAL_GITLAB_URL: string + EXTERNAL_GOOGLE_ENABLED: boolean + EXTERNAL_GOOGLE_CLIENT_ID: string + EXTERNAL_GOOGLE_SECRET: string + EXTERNAL_GOOGLE_ADDITIONAL_CLIENT_IDS: string + EXTERNAL_GOOGLE_SKIP_NONCE_CHECK: boolean + EXTERNAL_KAKAO_ENABLED: boolean + EXTERNAL_KAKAO_CLIENT_ID: string + EXTERNAL_KAKAO_SECRET: string + EXTERNAL_KEYCLOAK_ENABLED: boolean + EXTERNAL_KEYCLOAK_CLIENT_ID: string + EXTERNAL_KEYCLOAK_SECRET: string + EXTERNAL_KEYCLOAK_URL: string + EXTERNAL_LINKEDIN_OIDC_ENABLED: boolean + EXTERNAL_LINKEDIN_OIDC_CLIENT_ID: string + EXTERNAL_LINKEDIN_OIDC_SECRET: string + EXTERNAL_NOTION_ENABLED: boolean + EXTERNAL_NOTION_CLIENT_ID: string + EXTERNAL_NOTION_SECRET: string + EXTERNAL_SLACK_ENABLED: boolean + EXTERNAL_SLACK_CLIENT_ID: string + EXTERNAL_SLACK_SECRET: string + EXTERNAL_SPOTIFY_ENABLED: boolean + EXTERNAL_SPOTIFY_CLIENT_ID: string + EXTERNAL_SPOTIFY_SECRET: string + EXTERNAL_TWITCH_ENABLED: boolean + EXTERNAL_TWITCH_CLIENT_ID: string + EXTERNAL_TWITCH_SECRET: string + EXTERNAL_TWITTER_ENABLED: boolean + EXTERNAL_TWITTER_CLIENT_ID: string + EXTERNAL_TWITTER_SECRET: string + EXTERNAL_WORKOS_ENABLED: boolean + EXTERNAL_WORKOS_CLIENT_ID: string + EXTERNAL_WORKOS_SECRET: string + EXTERNAL_WORKOS_URL: string + EXTERNAL_ZOOM_ENABLED: boolean + EXTERNAL_ZOOM_CLIENT_ID: string + EXTERNAL_ZOOM_SECRET: string + } + UpdateGoTrueConfigBody: { + SITE_URL?: string + DISABLE_SIGNUP?: boolean + JWT_EXP?: number + SMTP_ADMIN_EMAIL?: string + SMTP_HOST?: string + SMTP_PORT?: string + SMTP_USER?: string + SMTP_PASS?: string + SMTP_MAX_FREQUENCY?: number + SMTP_SENDER_NAME?: string + MAILER_ALLOW_UNVERIFIED_EMAIL_SIGN_INS?: boolean + MAILER_AUTOCONFIRM?: boolean + MAILER_SUBJECTS_INVITE?: string + MAILER_SUBJECTS_CONFIRMATION?: string + MAILER_SUBJECTS_RECOVERY?: string + MAILER_SUBJECTS_EMAIL_CHANGE?: string + MAILER_SUBJECTS_MAGIC_LINK?: string + MAILER_SUBJECTS_REAUTHENTICATION?: string + MAILER_TEMPLATES_INVITE_CONTENT?: string + MAILER_TEMPLATES_REAUTHENTICATION_CONTENT?: string + MAILER_TEMPLATES_CONFIRMATION_CONTENT?: string + MAILER_TEMPLATES_RECOVERY_CONTENT?: string + MAILER_TEMPLATES_EMAIL_CHANGE_CONTENT?: string + MAILER_TEMPLATES_MAGIC_LINK_CONTENT?: string + MFA_MAX_ENROLLED_FACTORS?: number + URI_ALLOW_LIST?: string + EXTERNAL_ANONYMOUS_USERS_ENABLED?: boolean + EXTERNAL_EMAIL_ENABLED?: boolean + EXTERNAL_PHONE_ENABLED?: boolean + SAML_ENABLED?: boolean + SECURITY_CAPTCHA_ENABLED?: boolean + SECURITY_CAPTCHA_PROVIDER?: string + SECURITY_CAPTCHA_SECRET?: string + SESSIONS_TIMEBOX?: number + SESSIONS_INACTIVITY_TIMEOUT?: number + SESSIONS_SINGLE_PER_USER?: boolean + SESSIONS_TAGS?: string + RATE_LIMIT_ANONYMOUS_USERS?: number + RATE_LIMIT_EMAIL_SENT?: number + RATE_LIMIT_SMS_SENT?: number + RATE_LIMIT_VERIFY?: number + RATE_LIMIT_TOKEN_REFRESH?: number + MAILER_SECURE_EMAIL_CHANGE_ENABLED?: boolean + REFRESH_TOKEN_ROTATION_ENABLED?: boolean + PASSWORD_HIBP_ENABLED?: boolean + PASSWORD_MIN_LENGTH?: number + /** @enum {string} */ + PASSWORD_REQUIRED_CHARACTERS?: + | 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789' + | 'abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789' + | 'abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789:!@#$%^&*()_+-=[]{};\'\\:"|<>?,./`~' + | '' + SECURITY_MANUAL_LINKING_ENABLED?: boolean + SECURITY_UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION?: boolean + SECURITY_REFRESH_TOKEN_REUSE_INTERVAL?: number + MAILER_OTP_EXP?: number + SMS_AUTOCONFIRM?: boolean + SMS_MAX_FREQUENCY?: number + SMS_OTP_EXP?: number + SMS_OTP_LENGTH?: number + SMS_PROVIDER?: string + SMS_MESSAGEBIRD_ACCESS_KEY?: string + SMS_MESSAGEBIRD_ORIGINATOR?: string + SMS_TEST_OTP?: string + SMS_TEST_OTP_VALID_UNTIL?: string + SMS_TEXTLOCAL_API_KEY?: string + SMS_TEXTLOCAL_SENDER?: string + SMS_TWILIO_ACCOUNT_SID?: string + SMS_TWILIO_AUTH_TOKEN?: string + SMS_TWILIO_CONTENT_SID?: string + SMS_TWILIO_MESSAGE_SERVICE_SID?: string + SMS_TWILIO_VERIFY_ACCOUNT_SID?: string + SMS_TWILIO_VERIFY_AUTH_TOKEN?: string + SMS_TWILIO_VERIFY_MESSAGE_SERVICE_SID?: string + SMS_VONAGE_API_KEY?: string + SMS_VONAGE_API_SECRET?: string + SMS_VONAGE_FROM?: string + SMS_TEMPLATE?: string + HOOK_MFA_VERIFICATION_ATTEMPT_ENABLED?: boolean + HOOK_MFA_VERIFICATION_ATTEMPT_URI?: string + HOOK_PASSWORD_VERIFICATION_ATTEMPT_ENABLED?: boolean + HOOK_PASSWORD_VERIFICATION_ATTEMPT_URI?: string + HOOK_CUSTOM_ACCESS_TOKEN_ENABLED?: boolean + HOOK_CUSTOM_ACCESS_TOKEN_URI?: string + HOOK_SEND_SMS_ENABLED?: boolean + HOOK_SEND_SMS_URI?: string + HOOK_SEND_EMAIL_ENABLED?: boolean + HOOK_SEND_EMAIL_URI?: string + EXTERNAL_APPLE_ENABLED?: boolean + EXTERNAL_APPLE_CLIENT_ID?: string + EXTERNAL_APPLE_SECRET?: string + EXTERNAL_APPLE_ADDITIONAL_CLIENT_IDS?: string + EXTERNAL_AZURE_ENABLED?: boolean + EXTERNAL_AZURE_CLIENT_ID?: string + EXTERNAL_AZURE_SECRET?: string + EXTERNAL_AZURE_URL?: string + EXTERNAL_BITBUCKET_ENABLED?: boolean + EXTERNAL_BITBUCKET_CLIENT_ID?: string + EXTERNAL_BITBUCKET_SECRET?: string + EXTERNAL_DISCORD_ENABLED?: boolean + EXTERNAL_DISCORD_CLIENT_ID?: string + EXTERNAL_DISCORD_SECRET?: string + EXTERNAL_FACEBOOK_ENABLED?: boolean + EXTERNAL_FACEBOOK_CLIENT_ID?: string + EXTERNAL_FACEBOOK_SECRET?: string + EXTERNAL_FIGMA_ENABLED?: boolean + EXTERNAL_FIGMA_CLIENT_ID?: string + EXTERNAL_FIGMA_SECRET?: string + EXTERNAL_GITHUB_ENABLED?: boolean + EXTERNAL_GITHUB_CLIENT_ID?: string + EXTERNAL_GITHUB_SECRET?: string + EXTERNAL_GITLAB_ENABLED?: boolean + EXTERNAL_GITLAB_CLIENT_ID?: string + EXTERNAL_GITLAB_SECRET?: string + EXTERNAL_GITLAB_URL?: string + EXTERNAL_GOOGLE_ENABLED?: boolean + EXTERNAL_GOOGLE_CLIENT_ID?: string + EXTERNAL_GOOGLE_SECRET?: string + EXTERNAL_GOOGLE_ADDITIONAL_CLIENT_IDS?: string + EXTERNAL_GOOGLE_SKIP_NONCE_CHECK?: boolean + EXTERNAL_KAKAO_ENABLED?: boolean + EXTERNAL_KAKAO_CLIENT_ID?: string + EXTERNAL_KAKAO_SECRET?: string + EXTERNAL_KEYCLOAK_ENABLED?: boolean + EXTERNAL_KEYCLOAK_CLIENT_ID?: string + EXTERNAL_KEYCLOAK_SECRET?: string + EXTERNAL_KEYCLOAK_URL?: string + EXTERNAL_LINKEDIN_OIDC_ENABLED?: boolean + EXTERNAL_LINKEDIN_OIDC_CLIENT_ID?: string + EXTERNAL_LINKEDIN_OIDC_SECRET?: string + EXTERNAL_NOTION_ENABLED?: boolean + EXTERNAL_NOTION_CLIENT_ID?: string + EXTERNAL_NOTION_SECRET?: string + EXTERNAL_SLACK_ENABLED?: boolean + EXTERNAL_SLACK_CLIENT_ID?: string + EXTERNAL_SLACK_SECRET?: string + EXTERNAL_SPOTIFY_ENABLED?: boolean + EXTERNAL_SPOTIFY_CLIENT_ID?: string + EXTERNAL_SPOTIFY_SECRET?: string + EXTERNAL_TWITCH_ENABLED?: boolean + EXTERNAL_TWITCH_CLIENT_ID?: string + EXTERNAL_TWITCH_SECRET?: string + EXTERNAL_TWITTER_ENABLED?: boolean + EXTERNAL_TWITTER_CLIENT_ID?: string + EXTERNAL_TWITTER_SECRET?: string + EXTERNAL_WORKOS_ENABLED?: boolean + EXTERNAL_WORKOS_CLIENT_ID?: string + EXTERNAL_WORKOS_SECRET?: string + EXTERNAL_WORKOS_URL?: string + EXTERNAL_ZOOM_ENABLED?: boolean + EXTERNAL_ZOOM_CLIENT_ID?: string + EXTERNAL_ZOOM_SECRET?: string + } + UserBody: { + id?: string + aud?: string + banned_until?: string + confirmation_sent_at?: string + confirmation_token?: string + confirmed_at?: string + created_at?: string + email?: string + email_change?: string + email_change_confirm_status?: number + email_change_sent_at?: string + email_change_token_current?: string + email_change_token_new?: string + email_confirmed_at?: string + encrypted_password?: string + instance_id?: string + invited_at?: string + is_super_admin?: boolean + last_sign_in_at?: string + phone?: string + phone_change?: string + phone_change_sent_at?: string + phone_change_token?: string + phone_confirmed_at?: string + raw_app_meta_data?: Record + raw_user_meta_data?: Record + reauthentication_sent_at?: string + reauthentication_token?: string + recovery_sent_at?: string + recovery_token?: string + role?: string + updated_at?: string + is_sso_user?: boolean + deleted_at?: string + is_anonymous?: boolean + } + UsersResponse: { + total: number + users: components['schemas']['UserBody'][] + } + Backup: { + id: number + isPhysicalBackup: boolean + project_id: number + status: Record + inserted_at: string + } + BackupsResponse: { + tierKey: string + region: string + walg_enabled: boolean + pitr_enabled: boolean + backups: components['schemas']['Backup'][] + physicalBackupData: { + earliestPhysicalBackupDateUnix?: number + latestPhysicalBackupDateUnix?: number + } + } + DownloadBackupBody: { + id: number + data: Record + inserted_at: string + project_id: number + s3_bucket: string + s3_path: string + status: string + } + DownloadBackupResponse: { + fileUrl: string + } + RestoreLogicalBackupBody: { + id: number + } + RestorePhysicalBackupBody: { + id: number + recovery_time_target: string + } + PointInTimeRestoreBody: { + recovery_time_target_unix: number + } + OwnerResponse: { + project_ref: string + /** @enum {string} */ + current: 'unmigrated' | 'temp_role' | 'migrated' + /** @enum {string} */ + desired: 'unmigrated' | 'temp_role' | 'migrated' + created_at: string + modified_at: string + migrated_at: string | null + } + OrganizationResponse: { + /** @enum {string|null} */ + restriction_status: 'grace_period' | 'grace_period_over' | 'restricted' | null + id: number + slug: string + name: string + billing_email: string | null + is_owner: boolean + stripe_customer_id: string | null + subscription_id: string | null + opt_in_tags: string[] + restriction_data: Record + } + GetOrganizationByFlyOrganizationIdResponse: { + slug: string + } + CreateOrganizationBody: { + name: string + kind?: string + size?: string + /** @enum {string} */ + tier: 'tier_payg' | 'tier_pro' | 'tier_free' | 'tier_team' | 'tier_enterprise' + payment_method?: string + } + UpdateOrganizationBody: { + name: string + billing_email: string + opt_in_tags: string[] + } + OrganizationSlugResponse: { + id: number + slug: string + name: string + billing_email?: string + stripe_customer_id?: string + opt_in_tags: string[] + } + CustomerResponse: { + id: string + email: string + address: string + balance: number + invoice_settings: Record + billing_via_partner: boolean + } + CustomerUpdateResponse: { + id: string + object: string + address: Record + balance: number + cash_balance?: Record + created: number + currency: string + default_currency?: string + default_source: string + delinquent: boolean + description: string + discount: Record + email: string + invoice_credit_balance?: Record + invoice_prefix: string + invoice_settings: Record + livemode: boolean + metadata: Record + name: string + next_invoice_sequence?: number + phone: string + preferred_locales: string[] + shipping: Record + sources?: Record + subscriptions?: Record + tax?: Record + tax_exempt?: string + tax_ids?: Record + test_clock?: Record + lastResponse: { + headers?: Record + requestId?: string + statusCode?: number + apiVersion?: string + idempotencyKey?: string + stripeAccount?: string + } + } + Role: { + id: number + name: string + } + TaxId: { + id: string + object: string + country: string + created: number + customer: Record + deleted?: Record + livemode: boolean + type: string + value: string + verification: Record + } + TaxIdResponse: { + object: string + data: components['schemas']['TaxId'][] + has_more: boolean + url: string + lastResponse: { + headers?: Record + requestId?: string + statusCode?: number + apiVersion?: string + idempotencyKey?: string + stripeAccount?: string + } + } + CreateTaxIdBody: { + type: Record + value: string + } + CreateTaxIdResponse: { + id: string + object: string + country: string + created: number + customer: Record + livemode: boolean + type: string + value: string + verification: Record + lastResponse: { + headers?: Record + requestId?: string + statusCode?: number + apiVersion?: string + idempotencyKey?: string + stripeAccount?: string + } + } + DeleteTaxIdBody: { + id: string + } + DeleteTaxIdResponse: { + id: string + object: string + deleted: boolean + lastResponse: { + headers?: Record + requestId?: string + statusCode?: number + apiVersion?: string + idempotencyKey?: string + stripeAccount?: string + } + } + TransferOrganizationBody: { + member_gotrue_id: string + member_id: number + org_id: number + } + ProjectAllocation: { + ref: string + usage: number + name: string + } + OrgMetricUsage: { + usage: number + usage_original: number + cost: number + unit_price_desc: string + available_in_plan: boolean + unlimited: boolean + capped: boolean + /** @enum {string} */ + metric: + | 'EGRESS' + | 'DATABASE_SIZE' + | 'STORAGE_SIZE' + | 'MONTHLY_ACTIVE_USERS' + | 'MONTHLY_ACTIVE_SSO_USERS' + | 'FUNCTION_INVOCATIONS' + | 'FUNCTION_COUNT' + | 'STORAGE_IMAGES_TRANSFORMED' + | 'REALTIME_MESSAGE_COUNT' + | 'REALTIME_PEAK_CONNECTIONS' + | 'COMPUTE_HOURS_BRANCH' + | 'COMPUTE_HOURS_XS' + | 'COMPUTE_HOURS_SM' + | 'COMPUTE_HOURS_MD' + | 'COMPUTE_HOURS_L' + | 'COMPUTE_HOURS_XL' + | 'COMPUTE_HOURS_2XL' + | 'COMPUTE_HOURS_4XL' + | 'COMPUTE_HOURS_8XL' + | 'COMPUTE_HOURS_12XL' + | 'COMPUTE_HOURS_16XL' + /** @enum {string} */ + pricing_strategy: 'UNIT' | 'PACKAGE' | 'NONE' + pricing_free_units?: number + pricing_package_price?: number + pricing_package_size?: number + pricing_per_unit_price?: number + project_allocations: components['schemas']['ProjectAllocation'][] + } + OrgUsageResponse: { + usage_billing_enabled: boolean + slugs: string[] + usages: components['schemas']['OrgMetricUsage'][] + } + OrgDocumentUrlResponse: { + fileUrl: string + } + AuditLogsResponse: { + result: Record[] + retention_period: number + } + Invite: { + invited_id: number + invited_at: string + invited_email: string + role_id: number + } + InviteUserBody: { + invited_email: string + owner_id: number + role_id: number + } + SendInviteResponse: { + invited_at: string + invited_email: string + role_id: number + } + InviteResponse: { + organization_name: string + invite_id: string + token_does_not_exist: boolean + email_match: boolean + authorized_user: boolean + expired_token: boolean + } + JoinResponse: { + billing_email: string + id: number + name: string + slug: string + stripe_customer_id: string + } + RemoveMemberBody: { + member_id: number + } + Member: { + gotrue_id: string + primary_email: string | null + role_ids: number[] + username: string + mfa_enabled: boolean + } + UpdateMemberBody: { + role_id: number + } + MemberWithFreeProjectLimit: { + free_project_limit: number + primary_email: string + username: string + } + Payment: { + id: string + object: string + acss_debit?: Record + affirm?: Record + afterpay_clearpay?: Record + alipay?: Record + au_becs_debit?: Record + bacs_debit?: Record + bancontact?: Record + billing_details: Record + blik?: Record + boleto?: Record + card?: Record + card_present?: Record + created: number + customer: Record + customer_balance?: Record + eps?: Record + fpx?: Record + giropay?: Record + grabpay?: Record + ideal?: Record + interac_present?: Record + klarna?: Record + konbini?: Record + link?: Record + livemode: boolean + metadata: Record + oxxo?: Record + p24?: Record + paynow?: Record + promptpay?: Record + radar_options?: Record + sepa_debit?: Record + sofort?: Record + type: string + us_bank_account?: Record + wechat_pay?: Record + } + PaymentsResponse: { + object: string + data: components['schemas']['Payment'][] + has_more: boolean + url: string + lastResponse: { + headers?: Record + requestId?: string + statusCode?: number + apiVersion?: string + idempotencyKey?: string + stripeAccount?: string + } + } + DetachPaymentMethodBody: { + card_id: string + } + DetachPaymentResponse: { + id: string + object: string + acss_debit?: Record + affirm?: Record + afterpay_clearpay?: Record + alipay?: Record + au_becs_debit?: Record + bacs_debit?: Record + bancontact?: Record + billing_details: Record + blik?: Record + boleto?: Record + card?: Record + card_present?: Record + created: number + customer: Record + customer_balance?: Record + eps?: Record + fpx?: Record + giropay?: Record + grabpay?: Record + ideal?: Record + interac_present?: Record + klarna?: Record + konbini?: Record + link?: Record + livemode: boolean + metadata: Record + oxxo?: Record + p24?: Record + paynow?: Record + promptpay?: Record + radar_options?: Record + sepa_debit?: Record + sofort?: Record + type: string + us_bank_account?: Record + wechat_pay?: Record + lastResponse: { + headers?: Record + requestId?: string + statusCode?: number + apiVersion?: string + idempotencyKey?: string + stripeAccount?: string + } + } +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 HCaptchaBody: { hcaptchaToken: string } @@ -2053,11 +2937,1269 @@ export interface components { | 'pitr_28' | 'ipv4_default' /** @enum {string} */ +<<<<<<< HEAD ProjectAddonType: 'custom_domain' | 'compute_instance' | 'pitr' | 'ipv4' +======= + ProjectAddonVariantPricingType: 'fixed' | 'usage' + /** @enum {string} */ + ProjectAddonPricingInterval: 'monthly' | 'hourly' + ProjectAddonVariantResponse: { + identifier: components['schemas']['AddonVariantId'] + price_type: components['schemas']['ProjectAddonVariantPricingType'] + price_interval: components['schemas']['ProjectAddonPricingInterval'] + name: string + price_description: string + price: number + meta?: Record + } + SelectedAddonResponse: { + type: components['schemas']['ProjectAddonType'] + variant: components['schemas']['ProjectAddonVariantResponse'] + } + BillingProjectAddonResponse: { + addons: components['schemas']['SelectedAddonResponse'][] + name: string + ref: string + } + BillingPricingOptionsUnit: { + perUnitPrice: number + freeUnits?: number + } + BillingPricingOptionsPackage: { + packageSize: number + packagePrice: number + freeUnits?: number + } + BillingPricingOptionsNone: { + freeUnits?: number + } + BillingUsageBasedPrice: { + /** @enum {string} */ + metric: + | 'EGRESS' + | 'DATABASE_SIZE' + | 'STORAGE_SIZE' + | 'MONTHLY_ACTIVE_USERS' + | 'MONTHLY_ACTIVE_SSO_USERS' + | 'FUNCTION_INVOCATIONS' + | 'FUNCTION_COUNT' + | 'STORAGE_IMAGES_TRANSFORMED' + | 'REALTIME_MESSAGE_COUNT' + | 'REALTIME_PEAK_CONNECTIONS' + | 'COMPUTE_HOURS_BRANCH' + | 'COMPUTE_HOURS_XS' + | 'COMPUTE_HOURS_SM' + | 'COMPUTE_HOURS_MD' + | 'COMPUTE_HOURS_L' + | 'COMPUTE_HOURS_XL' + | 'COMPUTE_HOURS_2XL' + | 'COMPUTE_HOURS_4XL' + | 'COMPUTE_HOURS_8XL' + | 'COMPUTE_HOURS_12XL' + | 'COMPUTE_HOURS_16XL' + /** @enum {string} */ + pricingStrategy: 'UNIT' | 'PACKAGE' | 'NONE' + pricingOptions: + | components['schemas']['BillingPricingOptionsUnit'] + | components['schemas']['BillingPricingOptionsPackage'] + | components['schemas']['BillingPricingOptionsNone'] + name: string + unit: string + } + PaymentMethodCardDetails: { + last_4_digits: string + brand: string + expiry_month: number + expiry_year: number + } + ScheduledPlanChange: { + target_plan: components['schemas']['BillingPlanId'] + /** Format: date-time */ + at: string + usage_billing_enabled: boolean + } + GetSubscriptionResponse: { + billing_cycle_anchor: number + current_period_end: number + current_period_start: number + next_invoice_at: number + usage_billing_enabled: boolean + plan: components['schemas']['BillingSubscriptionPlan'] + addons: components['schemas']['BillingSubscriptionAddon'][] + project_addons: components['schemas']['BillingProjectAddonResponse'][] + usage_fees: components['schemas']['BillingUsageBasedPrice'][] + payment_method_type: string + payment_method_id?: string + payment_method_card_details?: components['schemas']['PaymentMethodCardDetails'] + billing_via_partner: boolean + /** @enum {string} */ + billing_partner: 'fly' | 'aws' + scheduled_plan_change: components['schemas']['ScheduledPlanChange'] | null + customer_balance: number + nano_enabled: boolean + } + UpdateSubscriptionBody: { + payment_method?: string + /** @enum {string} */ + tier: 'tier_payg' | 'tier_pro' | 'tier_free' | 'tier_team' | 'tier_enterprise' + } + /** @enum {string} */ + BillingPlanChangeType: 'upgrade' | 'downgrade' | 'none' + /** @enum {string} */ + BillingPlanEffectiveAt: 'now' | 'end_of_billing_period' | 'none' + PlanResponse: { + id: components['schemas']['BillingPlanId'] + change_type: components['schemas']['BillingPlanChangeType'] + effective_at: components['schemas']['BillingPlanEffectiveAt'] + name: string + price: number + is_current: boolean + } + PlansResponse: { + plans: components['schemas']['PlanResponse'][] + } + UpcomingInvoice: Record + ColumnPrivilege: { + grantor: string + grantee: string + /** @enum {string} */ + privilege_type: 'ALL' | 'SELECT' | 'INSERT' | 'UPDATE' | 'REFERENCES' + is_grantable: boolean + } + PostgresColumnPrivileges: { + column_id: string + relation_schema: string + relation_name: string + column_name: string + privileges: components['schemas']['ColumnPrivilege'][] + } + GrantColumnPrivilegesBody: { + is_grantable?: boolean + column_id: string + grantee: string + /** @enum {string} */ + privilege_type: 'ALL' | 'SELECT' | 'INSERT' | 'UPDATE' | 'REFERENCES' + } + RevokeColumnPrivilegesBody: { + column_id: string + grantee: string + /** @enum {string} */ + privilege_type: 'ALL' | 'SELECT' | 'INSERT' | 'UPDATE' | 'REFERENCES' + } + PostgresColumn: { + table_id: number + schema: string + table: string + id: string + ordinal_position: number + name: string + default_value: Record + data_type: string + format: string + is_identity: boolean + identity_generation: Record + is_generated: boolean + is_nullable: boolean + is_updatable: boolean + is_unique: boolean + enums: string[] + check: string | null + comment: string | null + } + CreateColumnBody: { + tableId: number + name: string + type: string + check?: string + comment?: string + defaultValue?: Record + /** @enum {string} */ + defaultValueFormat?: 'expression' | 'literal' + /** @enum {string} */ + identityGeneration?: 'BY DEFAULT' | 'ALWAYS' + isIdentity?: boolean + isNullable?: boolean + isPrimaryKey?: boolean + isUnique?: boolean + } + UpdateColumnBody: { + dropDefault?: boolean + name?: string + type?: string + id?: number + check?: string + comment?: string + defaultValue?: Record + /** @enum {string} */ + defaultValueFormat?: 'expression' | 'literal' + /** @enum {string} */ + identityGeneration?: 'BY DEFAULT' | 'ALWAYS' + isIdentity?: boolean + isNullable?: boolean + isUnique?: boolean + } + PostgresExtension: { + name: string + schema: string | null + default_version: string + installed_version: string | null + comment: string | null + } + CreateExtensionBody: { + cascade: boolean + name: string + schema: string + version: string + } + PostgresForeignTable: { + id: number + schema: string + name: string + comment: string | null + columns?: components['schemas']['PostgresColumn'][] + } + PostgresFunction: { + id: number + schema: string + name: string + language: string + definition: string + complete_statement: string + argument_types: string + identity_argument_types: string + return_type: string + /** @enum {string} */ + behavior: 'VOLATILE' | 'STABLE' | 'IMMUTABLE' + security_definer: boolean + config_params: unknown + } + CreateFunctionBody: { + args: string[] + /** @enum {string} */ + behavior: 'VOLATILE' | 'STABLE' | 'IMMUTABLE' + config_params?: Record + definition: string + language: string + name: string + return_type: string + schema: string + security_definer: boolean + } + UpdateFunctionBody: { + id?: number + args?: string[] + /** @enum {string} */ + behavior?: 'VOLATILE' | 'STABLE' | 'IMMUTABLE' + config_params?: Record + definition?: string + language?: string + name?: string + return_type?: string + schema?: string + security_definer?: boolean + } + PostgresMaterializedView: { + id: number + schema: string + name: string + is_populated: boolean + comment: string | null + columns?: components['schemas']['PostgresColumn'][] + } + PostgresPolicy: { + id: number + schema: string + table: string + table_id: number + name: string + /** @enum {string} */ + action: 'PERMISSIVE' | 'RESTRICTIVE' + roles: string[] + /** @enum {string} */ + command: 'SELECT' | 'INSERT' | 'UPDATE' | 'DELETE' | 'ALL' + definition: string | null + check: string | null + } + CreatePolicyBody: { + name: string + table: string + schema?: string + definition?: string + check?: string + /** @enum {string} */ + action?: 'PERMISSIVE' | 'RESTRICTIVE' + /** @enum {string} */ + command?: 'SELECT' | 'INSERT' | 'UPDATE' | 'DELETE' | 'ALL' + roles?: string[] + } + UpdatePolicyBody: { + id?: number + name?: string + definition?: string + check?: string + roles?: string[] + } + Table: { + id: number + name: string + schema: string + } + PostgresPublication: { + id: number + name: string + owner: string + publish_insert: boolean + publish_update: boolean + publish_delete: boolean + publish_truncate: boolean + tables: components['schemas']['Table'][] | null + } + CreatePublicationBody: { + name: string + owner?: string + tables?: string[] | null + publish_insert?: boolean + publish_update?: boolean + publish_delete?: boolean + publish_truncate?: boolean + } + UpdatePublicationBody: { + id?: number + name?: string + owner?: string + tables?: string[] | null + publish_insert?: boolean + publish_update?: boolean + publish_delete?: boolean + publish_truncate?: boolean + } + RunQueryBody: { + query: string + } + FormatQueryBody: { + query: string + } + ValidateQueryBody: { + query: string + } + ValidateQueryResponse: { + valid: boolean + } + PostgresRole: { + id: number + name: string + is_superuser: boolean + can_create_db: boolean + can_create_role: boolean + inherit_role: boolean + can_login: boolean + is_replication_role: boolean + can_bypass_rls: boolean + active_connections: number + connection_limit: number + password: string + valid_until: string | null + config: string[] | null + } + CreateRoleBody: { + name: string + password?: string + inherit_role?: boolean + can_login?: boolean + is_superuser?: boolean + can_create_db?: boolean + can_create_role?: boolean + is_replication_role?: boolean + can_bypass_rls?: boolean + connection_limit?: number + member_of?: string[] + members?: string[] + admins?: string[] + valid_until?: string + config?: Record + } + UpdateRoleBody: { + name?: string + password?: string + inherit_role?: boolean + can_login?: boolean + is_superuser?: boolean + can_create_db?: boolean + can_create_role?: boolean + is_replication_role?: boolean + can_bypass_rls?: boolean + connection_limit?: number + valid_until?: string + config?: Record + } + PostgresSchema: { + id: number + name: string + owner: string + } + CreateSchemaBody: { + name: string + owner: string + } + UpdateSchemaBody: { + name?: string + owner?: string + } + SearchTableBody: { + name: string + schema: string + } + SearchColumnBody: { + name: string + schema: string + } + Column: { + id: number + name: string + schema: string + table_id: number + table: string + } + TablePrivilege: { + grantor: string + grantee: string + /** @enum {string} */ + privilege_type: + | 'ALL' + | 'SELECT' + | 'INSERT' + | 'UPDATE' + | 'DELETE' + | 'TRUNCATE' + | 'REFERENCES' + | 'TRIGGER' + is_grantable: boolean + } + PostgresTablePrivileges: { + schema: string + name: string + kind: string + privileges: components['schemas']['TablePrivilege'][] + } + GrantTablePrivilegesBody: { + is_grantable?: boolean + relation_id: number + grantee: string + /** @enum {string} */ + privilege_type: + | 'ALL' + | 'SELECT' + | 'INSERT' + | 'UPDATE' + | 'DELETE' + | 'TRUNCATE' + | 'REFERENCES' + | 'TRIGGER' + } + RevokeTablePrivilegesBody: { + relation_id: number + grantee: string + /** @enum {string} */ + privilege_type: + | 'ALL' + | 'SELECT' + | 'INSERT' + | 'UPDATE' + | 'DELETE' + | 'TRUNCATE' + | 'REFERENCES' + | 'TRIGGER' + } + PrimaryKey: { + schema: string + table_name: string + name: string + table_id: number + } + Relationship: { + id: number + constraint_name: string + source_schema: string + source_table_name: string + source_column_name: string + target_table_schema: string + target_table_name: string + target_column_name: string + } + PostgresTable: { + id: number + schema: string + name: string + rls_enabled: boolean + rls_forced: boolean + /** @enum {string} */ + replica_identity: 'DEFAULT' | 'INDEX' | 'FULL' | 'NOTHING' + bytes: number + size: string + live_rows_estimate: number + dead_rows_estimate: number + comment: string | null + columns?: components['schemas']['PostgresColumn'][] + primary_keys: components['schemas']['PrimaryKey'][] + relationships: components['schemas']['Relationship'][] + } + CreateTableBody: { + name: string + schema?: string + comment?: string + } + UpdateTableBody: { + id?: number + rls_enabled?: boolean + rls_forced?: boolean + /** @enum {string} */ + replica_identity?: 'DEFAULT' | 'INDEX' | 'FULL' | 'NOTHING' + replica_identity_index?: string + name?: string + schema?: string + comment?: string + } + PostgresTrigger: { + id: number + table_id: number + /** @enum {string} */ + enabled_mode: 'ORIGIN' | 'REPLICA' | 'ALWAYS' | 'DISABLED' + name: string + table: string + schema: string + condition: string | null + /** @enum {string} */ + orientation: 'ROW' | 'STATEMENT' + /** @enum {string} */ + activation: 'AFTER' | 'BEFORE' + events: string[] + function_schema: string + function_name: string + function_args: string[] + } + CreateTriggerBody: { + /** @enum {string} */ + activation: 'AFTER' | 'BEFORE' + /** @enum {string} */ + enabled_mode: 'ORIGIN' | 'REPLICA' | 'ALWAYS' | 'DISABLED' + events: ('INSERT' | 'UPDATE' | 'DELETE')[] + function_args: string[] + function_name: string + function_schema: string + name: string + /** @enum {string} */ + orientation: 'ROW' | 'STATEMENT' + schema: string + table: string + table_id?: number + } + UpdateTriggerBody: { + id?: number + /** @enum {string} */ + activation?: 'AFTER' | 'BEFORE' + /** @enum {string} */ + enabled_mode?: 'ORIGIN' | 'REPLICA' | 'ALWAYS' | 'DISABLED' + events?: ('INSERT' | 'UPDATE' | 'DELETE')[] + function_args?: string[] + function_name?: string + function_schema?: string + name?: string + /** @enum {string} */ + orientation?: 'ROW' | 'STATEMENT' + schema?: string + table?: string + table_id?: number + } + PostgresType: { + id: number + name: string + schema: string + format: string + enums: string[] + comment: string | null + } + PostgresView: { + id: number + schema: string + name: string + is_updatable: boolean + comment: string | null + columns?: components['schemas']['PostgresColumn'][] + } + AccessToken: { + created_at: string + id: number + token_alias: string + name: string + /** @enum {string} */ + scope?: 'V0' + } + CreateAccessTokenBody: { + name: string + /** @enum {string} */ + scope?: 'V0' + } + CreateAccessTokenResponse: { + created_at: string + id: number + token_alias: string + name: string + /** @enum {string} */ + scope?: 'V0' + token: string + } + SearchProfileBody: { + keywords: string + } + Profile: { + first_name: string + gotrue_id: string + id: number + last_name: string + username: string + } + SubscriptionStatisticsResponse: { + total_paid_projects: number + total_free_projects: number + total_active_free_projects: number + total_paused_free_projects: number + total_pro_projects: number + total_payg_projects: number + total_team_projects: number + total_enterprise_projects: number + } + PasswordCheckBody: { + password: string + } + PasswordCheckResponse: { + result: { + feedback?: { + warning?: string + suggestions?: string[] + } + score?: number + } + } + Permission: { + actions: ( + | 'analytics:Read' + | 'auth:Execute' + | 'billing:Read' + | 'billing:Write' + | 'write:Create' + | 'write:Delete' + | 'functions:Read' + | 'functions:Write' + | 'infra:Execute' + | 'read:Read' + | 'sql:Read:Select' + | 'sql:Write:Delete' + | 'sql:Write:Insert' + | 'sql:Write:Update' + | 'storage:Admin:Read' + | 'storage:Admin:Write' + | 'tenant:Sql:Admin:Read' + | 'tenant:Sql:Admin:Write' + | 'tenant:Sql:CreateTable' + | 'tenant:Sql:Write:Delete' + | 'tenant:Sql:Write:Insert' + | 'tenant:Sql:Query' + | 'tenant:Sql:Read:Select' + | 'tenant:Sql:Write:Update' + | 'write:Update' + )[] + condition: unknown + organization_id: number + resources: string[] + } + ProfileResponse: { + id: number + auth0_id: string + primary_email: string + username: string + first_name: string + last_name: string + mobile: string + is_alpha_user: boolean + gotrue_id: string + free_project_limit: number + disabled_features: ( + | 'organizations:create' + | 'organizations:delete' + | 'organization_members:create' + | 'organization_members:delete' + | 'projects:create' + | 'projects:transfer' + | 'project_auth:all' + | 'project_storage:all' + | 'project_edge_function:all' + | 'profile:update' + | 'billing:account_data' + | 'billing:credits' + | 'billing:invoices' + | 'billing:payment_methods' + | 'realtime:all' + )[] + } + UpdateProfileBody: { + first_name: string + last_name: string + } + /** @enum {string} */ + DbInstanceSize: + | 'nano' + | 'micro' + | 'small' + | 'medium' + | 'large' + | 'xlarge' + | '2xlarge' + | '4xlarge' + | '8xlarge' + | '12xlarge' + | '16xlarge' + ProjectInfo: { + infra_compute_size?: components['schemas']['DbInstanceSize'] + cloud_provider: string + id: number + inserted_at: string + name: string + organization_id: number + ref: string + region: string + status: string + subscription_id: string + is_branch_enabled: boolean + is_read_replicas_enabled: boolean + preview_branch_refs: string[] + disk_volume_size_gb?: number + } + GetProjectByFlyExtensionIdResponse: { + ref: string + } + /** @enum {string} */ + DesiredInstanceSize: + | 'micro' + | 'small' + | 'medium' + | 'large' + | 'xlarge' + | '2xlarge' + | '4xlarge' + | '8xlarge' + | '12xlarge' + | '16xlarge' + AmiSearchOptions: { + search_tags?: Record + } + CustomSupabaseInternalRequests: { + ami: components['schemas']['AmiSearchOptions'] + } + CreateProjectBody: { + /** @deprecated */ + kps_enabled?: boolean + desired_instance_size?: components['schemas']['DesiredInstanceSize'] + cloud_provider: string + org_id: number + name: string + db_pass: string + db_region: string + db_pricing_tier_id?: string + db_sql?: string + auth_site_url?: string + vercel_configuration_id?: string + custom_supabase_internal_requests?: components['schemas']['CustomSupabaseInternalRequests'] + } + CreateProjectResponse: { + infra_compute_size?: components['schemas']['DbInstanceSize'] + cloud_provider: string + id: number + inserted_at: string + name: string + organization_id: number + ref: string + region: string + status: string + subscription_id: string + is_branch_enabled: boolean + is_read_replicas_enabled: boolean + preview_branch_refs: string[] + disk_volume_size_gb?: number + endpoint: string + anon_key: string + service_key: string + } + GetUserContentObject: { + owner: { + id?: number + username?: string + } + updated_by: { + id?: number + username?: string + } + id: string + inserted_at: string + updated_at: string + type: Record + visibility: Record + name: string + description?: string + project_id: number + owner_id: number + last_updated_by: number + } + GetUserContentResponse: { + data: components['schemas']['GetUserContentObject'][] + } + CreateContentParams: { + id: string + name: string + description: string + /** @enum {string} */ + type: 'sql' | 'report' | 'log_sql' + /** @enum {string} */ + visibility: 'user' | 'project' | 'org' | 'public' + content?: Record + owner_id?: number + } + UserContentObject: { + id: string + inserted_at: string + updated_at: string + type: Record + visibility: Record + name: string + description?: string + project_id: number + owner_id: number + last_updated_by: number + } + UpsertContentParams: { + id: string + name: string + description: string + /** @enum {string} */ + type: 'sql' | 'report' | 'log_sql' + /** @enum {string} */ + visibility: 'user' | 'project' | 'org' | 'public' + content?: Record + owner_id?: number + project_id: number + } + UpdateContentParams: { + id?: string + name?: string + description?: string + /** @enum {string} */ + type?: 'sql' | 'report' | 'log_sql' + /** @enum {string} */ + visibility?: 'user' | 'project' | 'org' | 'public' + content?: Record + owner_id?: number + } + BulkDeleteUserContentResponse: { + id: string + } + DatabaseDetailResponse: { + /** @enum {string} */ + status: + | 'ACTIVE_HEALTHY' + | 'ACTIVE_UNHEALTHY' + | 'COMING_UP' + | 'GOING_DOWN' + | 'INIT_FAILED' + | 'REMOVED' + | 'RESTORING' + | 'UNKNOWN' + | 'UPGRADING' + | 'INIT_READ_REPLICA' + | 'INIT_READ_REPLICA_FAILED' + /** @enum {string} */ + cloud_provider: 'AWS' | 'FLY' + db_port: number + db_name: string + db_user: string + restUrl: string + db_host: string + connectionString: string + identifier: string + inserted_at: string + size: string + region: string + } + DatabaseStatusResponse: { + /** @enum {string} */ + status: + | 'ACTIVE_HEALTHY' + | 'ACTIVE_UNHEALTHY' + | 'COMING_UP' + | 'GOING_DOWN' + | 'INIT_FAILED' + | 'REMOVED' + | 'RESTORING' + | 'UNKNOWN' + | 'UPGRADING' + | 'INIT_READ_REPLICA' + | 'INIT_READ_REPLICA_FAILED' + identifier: string + } + UpdatePasswordBody: { + password: string + } + LoadBalancerDatabase: { + identifier: string + /** @enum {string} */ + type: 'PRIMARY' | 'READ_REPLICA' + status: string + } + LoadBalancerDetailResponse: { + endpoint: string + databases: components['schemas']['LoadBalancerDatabase'][] + } + Buffer: Record + ResizeBody: { + volume_size_gb: number + } + ServiceVersions: { + gotrue: string + postgrest: string + 'supabase-postgres': string + } + ProjectDetailResponse: { + infra_compute_size?: components['schemas']['DbInstanceSize'] + cloud_provider: string + db_host: string + id: number + inserted_at: string + name: string + organization_id: number + ref: string + region: string + /** @enum {string} */ + status: + | 'INACTIVE' + | 'ACTIVE_HEALTHY' + | 'ACTIVE_UNHEALTHY' + | 'COMING_UP' + | 'UNKNOWN' + | 'GOING_DOWN' + | 'INIT_FAILED' + | 'REMOVED' + | 'RESTORING' + | 'UPGRADING' + subscription_id: string + connectionString: string + kpsVersion?: string + dbVersion?: string + restUrl: string + serviceVersions?: components['schemas']['ServiceVersions'] + volumeSizeGb?: number + maxDatabasePreprovisionGb?: number + lastDatabaseResizeAt?: string + is_branch_enabled: boolean + parent_project_ref?: string + is_read_replicas_enabled: boolean + v2MaintenanceWindow: { + start?: string + end?: string + } + } + ProjectRefResponse: { + id: number + ref: string + name: string + } + UpdateProjectBody: { + name: string + } + BackupId: { + id: number + } + RestoreProjectInfo: { + id: number + status: string + organization_id: number + subscription_id: string + region: string + cloud_provider: string + back_ups: components['schemas']['BackupId'][] + } + RestartServiceRequest: { + services: ( + | 'adminapi' + | 'api-gateway' + | 'envoy' + | 'functions' + | 'gotrue' + | 'kong' + | 'pgbouncer' + | 'pgsodium' + | 'postgresql' + | 'postgrest' + | 'realtime' + | 'storage' + | 'walg' + | 'autoshutdown' + )[] + source_notification_id?: string + region: string + } + RestartServicesBody: { + restartRequest: components['schemas']['RestartServiceRequest'] + } + ProjectAppConfigResponse: { + db_schema: string + endpoint: string + } + ProjectServiceApiKeyResponse: { + api_key: string + name: string + tags: string + } + ProjectSettingsResponse: { + name: string + ref: string + status: string + inserted_at: string + db_dns_name: string + db_host: string + db_name: string + db_user: string + db_port: string + ssl_enforced: boolean + cloud_provider: string + region: string + app_config?: components['schemas']['ProjectAppConfigResponse'] + jwt_secret?: string + service_api_keys?: components['schemas']['ProjectServiceApiKeyResponse'][] + /** @enum {string|null} */ + db_ip_addr_config: 'legacy' | 'static-ipv4' | 'concurrent-ipv6' | 'ipv6' | null + } + TransferProjectBody: { + target_organization_slug: string + } + PreviewTransferInfo: { + key: string + message: string + } + MemberExceedingFreeProjectLimit: { + name: string + limit: number + } + PreviewTransferInvoiceItem: { + description: string + quantity: number + amount: number + } + PreviewProjectTransferResponse: { + source_subscription_plan: components['schemas']['BillingPlanId'] + target_subscription_plan: components['schemas']['BillingPlanId'] + valid: boolean + warnings: components['schemas']['PreviewTransferInfo'][] + errors: components['schemas']['PreviewTransferInfo'][] + members_exceeding_free_project_limit: components['schemas']['MemberExceedingFreeProjectLimit'][] + has_permissions_on_source_organization: boolean + has_access_to_target_organization: boolean + source_project_eligible: boolean + target_organization_eligible: boolean | null + target_organization_has_free_project_slots: boolean | null + credits_on_source_organization: number + costs_on_target_organization: number + charge_on_target_organization: number + source_invoice_items: components['schemas']['PreviewTransferInvoiceItem'][] + target_invoice_items: components['schemas']['PreviewTransferInvoiceItem'][] + } + AnalyticsResponse: { + error?: OneOf< + [ + { + code?: number + errors?: { + domain?: string + location?: string + locationType?: string + message?: string + reason?: string + }[] + message?: string + status?: string + }, + string, + ] + > + result?: Record[] + } + LFUser: { + token: string + metadata: { + project_ref?: string + } + } + LFSource: { + token: string + id: number + name: string + } + LFEndpoint: { + token: string + id: number + name: string + description: string + query: string + language: Record + sandboxable: boolean | null + cache_duration_seconds: number + proactive_requerying_seconds: number + max_limit: number + enable_auth: number + } + PgbouncerConfigResponse: { + default_pool_size?: number + ignore_startup_parameters?: string + max_client_conn?: number + /** @enum {string} */ + pool_mode?: 'transaction' | 'session' | 'statement' + inserted_at: string + db_dns_name: string + db_user: string + db_host: string + db_port: number + db_name: string + ssl_enforced: boolean + pgbouncer_enabled: boolean + supavisor_enabled: boolean + /** @enum {string} */ + pgbouncer_status: 'COMING_DOWN' | 'COMING_UP' | 'DISABLED' | 'ENABLED' | 'RELOADING' + connectionString: string + } + PgbouncerStatusResponse: { + active: boolean + } + UpdatePgbouncerConfigBody: { + default_pool_size?: number + max_client_conn?: number | null + ignore_startup_parameters: string + pgbouncer_enabled: boolean + /** @enum {string} */ + pool_mode: 'transaction' | 'session' | 'statement' + } + UpdatePoolingConfigResponse: { + default_pool_size?: number + max_client_conn?: number | null + ignore_startup_parameters: string + pgbouncer_enabled: boolean + /** @enum {string} */ + pool_mode: 'transaction' | 'session' | 'statement' + /** @enum {string} */ + pgbouncer_status: 'COMING_DOWN' | 'COMING_UP' | 'DISABLED' | 'ENABLED' | 'RELOADING' + } + PostgrestConfigResponse: { + db_schema: string + db_anon_role: string + role_claim_key: string + jwt_secret: string + max_rows: number + db_extra_search_path: string + } + UpdatePostgrestConfigBody: { + max_rows?: number + db_extra_search_path?: string + db_schema?: string + } + V1PostgrestConfigResponse: { + max_rows: number + db_schema: string + db_extra_search_path: string + } + PostgresConfigResponse: { + statement_timeout?: string + effective_cache_size?: string + maintenance_work_mem?: string + max_connections?: number + max_locks_per_transaction?: number + max_parallel_maintenance_workers?: number + max_parallel_workers?: number + max_parallel_workers_per_gather?: number + max_worker_processes?: number + shared_buffers?: string + work_mem?: string + /** @enum {string} */ + session_replication_role?: 'origin' | 'replica' | 'local' + } + UpdatePostgresConfigBody: { + statement_timeout?: string + effective_cache_size?: string + maintenance_work_mem?: string + max_connections?: number + max_locks_per_transaction?: number + max_parallel_maintenance_workers?: number + max_parallel_workers?: number + max_parallel_workers_per_gather?: number + max_worker_processes?: number + shared_buffers?: string + work_mem?: string + /** @enum {string} */ + session_replication_role?: 'origin' | 'replica' | 'local' + } + UpdateSecretsConfigBody: { + jwt_secret: string + change_tracking_id: string + } + UpdateSecretsResponse: { + message: string + } + StorageConfigResponse: { + isFreeTier: boolean + fileSizeLimit: number + } + UpdateStorageConfigBody: { + fileSizeLimit: number + } + UpdateStorageConfigResponse: { + fileSizeLimit: number + } + SupavisorConfigResponse: { + identifier: string + /** @enum {string} */ + database_type: 'PRIMARY' | 'READ_REPLICA' + is_using_scram_auth: boolean + db_user: string + db_host: string + db_port: number + db_name: string + connectionString: string + default_pool_size: number | null + max_client_conn: number | null + /** @enum {string|null} */ + pool_mode: 'transaction' | 'session' | 'statement' | null + } + UpdateSupavisorConfigBody: { + default_pool_size?: number + /** @enum {string} */ + pool_mode: 'transaction' | 'session' | 'statement' + } + UpdateSupavisorConfigResponse: { + default_pool_size?: number + /** @enum {string} */ + pool_mode: 'transaction' | 'session' | 'statement' + } + AvailableAddonResponse: { + type: components['schemas']['ProjectAddonType'] + name: string + variants: components['schemas']['ProjectAddonVariantResponse'][] + } + ProjectAddonsResponse: { + ref: string + selected_addons: components['schemas']['SelectedAddonResponse'][] + available_addons: components['schemas']['AvailableAddonResponse'][] + } +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 UpdateAddonBody: { addon_variant: components['schemas']['AddonVariantId'] addon_type: components['schemas']['ProjectAddonType'] } +<<<<<<< HEAD ApiResponse: Record SettingsResponse: Record UpdateStorageBucketBody: Record @@ -2084,17 +4226,483 @@ export interface components { CreateGitHubConnectionsBody: Record UpdateGitHubConnectionsBody: Record CreateCliLoginSessionBody: Record +======= + ServiceApiKey: { + api_key_encrypted?: string + tags: string + name: string + } + AutoApiService: { + protocol: string + endpoint: string + restUrl: string + project: { + ref?: string + } + defaultApiKey: string + serviceApiKey: string + id: number + name: string + app_config: Record + app: { + id?: number + name?: string + } + service_api_keys: components['schemas']['ServiceApiKey'][] + } + ApiResponse: { + autoApiService: components['schemas']['AutoApiService'] + } + JwtSecretUpdateStatus: { + jwtSecretUpdateStatus: { + change_tracking_id?: string + /** @enum {number} */ + error?: 0 | 1 | 2 | 3 | 4 | 5 + /** @enum {number} */ + progress?: 0 | 1 | 2 | 3 | 4 | 5 + /** @enum {number} */ + status?: 0 | 1 | 2 + } + } + ServiceApiKeyResponse: { + api_key_encrypted?: string + tags: string + name: string + api_key?: string + } + ServiceResponse: { + service_api_keys: components['schemas']['ServiceApiKeyResponse'][] + id: number + name: string + app_config: Record + app: { + id?: number + name?: string + } + } + ProjectResponse: { + jwt_secret: string + services?: components['schemas']['ServiceResponse'][] + id: number + name: string + ref: string + status: string + inserted_at: string + db_dns_name: string + db_host: string + db_name: string + db_user: string + db_port: string + ssl_enforced: boolean + cloud_provider: string + region: string + } + SettingsResponse: { + project: components['schemas']['ProjectResponse'] + services: components['schemas']['ServiceResponse'][] + } + StorageBucket: { + id: string + name: string + owner: string + created_at: string + updated_at: string + public: boolean + } + UpdateStorageBucketBody: { + public: boolean + file_size_limit: number + allowed_mime_types: string[] + } + CreateStorageBucketBody: { + id: string + public: boolean + file_size_limit: number + allowed_mime_types: string[] + } + StorageObjectSortBy: { + column?: string + order?: string + } + StorageObjectSearchOptions: { + limit?: number + offset?: number + sortBy?: components['schemas']['StorageObjectSortBy'] + search?: string + } + GetObjectsBody: { + path: string + options: components['schemas']['StorageObjectSearchOptions'] + } + StorageObject: { + name: string + bucket_id: string + owner: string + id: string + updated_at: string + created_at: string + last_accessed_at: string + metadata: Record + buckets: components['schemas']['StorageBucket'] + } + StorageObjectTransformOptions: { + width?: number + height?: number + /** @enum {string} */ + resize?: 'cover' | 'contain' | 'fill' + quality?: number + /** @enum {string} */ + format?: 'origin' + } + PublicUrlOptions: { + transform?: components['schemas']['StorageObjectTransformOptions'] + download?: boolean + downloadName?: string + } + GetPublicUrlBody: { + path: string + options?: components['schemas']['PublicUrlOptions'] + } + PublicUrlResponse: { + publicUrl: string + } + DownloadObjectOptions: { + transform?: components['schemas']['StorageObjectTransformOptions'] + download?: boolean + downloadName?: string + } + DownloadObjectBody: { + path: string + options?: components['schemas']['DownloadObjectOptions'] + } + SignedUrlOptions: { + transform?: components['schemas']['StorageObjectTransformOptions'] + download?: boolean + downloadName?: string + } + GetSignedUrlBody: { + path: string + expiresIn: number + options?: components['schemas']['SignedUrlOptions'] + } + SignedUrlResponse: { + signedUrl: string + } + SignedUrlsOptions: { + download?: boolean + downloadName?: string + } + GetSignedUrlsBody: { + path: string[] + expiresIn: number + options?: components['schemas']['SignedUrlsOptions'] + } + SignedUrlsResponse: { + signedUrl: string + error: string | null + path: string | null + } + CopyObjectBody: { + from: string + to: string + } + CopyObjectResponse: { + path: string + } + MoveObjectBody: { + from: string + to: string + } + DeleteObjectsBody: { + paths: string[] + } + GetStorageCredential: { + id: string + description: string + created_at: string + } + GetStorageCredentialsResponse: { + data: components['schemas']['GetStorageCredential'][] + } + CreateStorageCredentialBody: { + description: string + } + CreateStorageCredentialResponse: { + id: string + access_key: string + secret_key: string + description: string + } + Invoice: { + id: string + invoice_pdf: string + subscription: string + subtotal: number + number: string + period_end: number + status: string + } + OverdueInvoiceCount: { + organization_id: number + overdue_invoice_count: number + } + GoogleAnalyticBody: { + language?: string + screen_resolution?: string + session_id: string + } + TelemetryEventBody: { + category: string + action: string + label?: Record + value?: string + page_referrer?: string + page_title?: string + page_location?: string + ga?: components['schemas']['GoogleAnalyticBody'] + } + IdentifyUserBody: { + id: number + gotrue_id: string + mobile?: string + primary_email: string + username?: string + first_name?: string + last_name?: string + is_alpha_user?: boolean + free_project_limit?: number + auth0_id?: string + } + TelemetryIdentifyBody: { + user: components['schemas']['IdentifyUserBody'] + ga?: components['schemas']['GoogleAnalyticBody'] + } + TelemetryPageBody: { + referrer: string + title: string + route?: string + ga?: components['schemas']['GoogleAnalyticBody'] + } + PageBody: { + path: string + location: string + referrer?: string + title?: string + } + TelemetryActivityBody: { + activity: string + source: string + projectRef?: string + orgSlug?: string + data?: Record + page: components['schemas']['PageBody'] + } + TelemetryPageviewBody: { + projectRef?: string + orgSlug?: string + referrer: string + title: string + path: string + location: string + } + CreateVercelEnvironmentVariableBody: { + key: string + target: string[] + type: string + value: string + } + GetUserOrganizationIntegrationResponse: { + id: string + inserted_at: string + updated_at: string + metadata: Record + integration: { + name?: string + } + added_by: { + username?: string + primary_email?: string + } + organization: { + slug?: string + } + } + ProjectIntegrationConnection: { + id: string + inserted_at: string + updated_at: string + organization_integration_id: string + supabase_project_ref: string + added_by: { + username?: string + primary_email?: string + } + } + GetOrganizationIntegrationResponse: { + id: string + inserted_at: string + updated_at: string + metadata: Record + integration: { + name?: string + } + added_by: { + username?: string + primary_email?: string + } + organization: { + slug?: string + } + connections: components['schemas']['ProjectIntegrationConnection'][] + } + CreateVercelIntegrationBody: { + code: string + configuration_id: string + organization_slug: string + metadata: Record + source: string + teamId?: string + } + VercelProjectDeployHooks: { + createdAt?: number + id: string + name: string + ref: string + url: string + } + VercelProjectLink: { + type?: string + deployHooks: components['schemas']['VercelProjectDeployHooks'][] + gitCredentialId?: string + productionBranch?: string + } + IntegrationVercelProject: { + id: string + name: string + framework?: string | null + link?: components['schemas']['VercelProjectLink'] + } + GetVercelProjectsResponse: { + projects: components['schemas']['IntegrationVercelProject'][] + pagination: { + count?: number + next?: number | null + prev?: number | null + } + } + GetVercelConnections: { + id: string + inserted_at: string + updated_at: string + organization_integration_id: string + supabase_project_ref: string + foreign_project_id: string + metadata: Record + } + IntegrationConnectionVercel: { + foreign_project_id: string + supabase_project_ref: string + metadata: Record + } + CreateVercelConnectionsBody: { + organization_integration_id: string + connection: components['schemas']['IntegrationConnectionVercel'] + } + SyncVercelEnvError: { + message: string + } + CreateVercelConnectionResponse: { + id: string + env_sync_error?: components['schemas']['SyncVercelEnvError'] + } + UpdateVercelConnectionsBody: { + metadata: Record + } + DeleteVercelConnectionResponse: { + id: string + } + GitHubAuthorization: { + id: number + user_id: number + sender_id: number + } + CreateGitHubAuthorizationBody: { + code: string + } + ListGitHubConnectionsProject: { + id: number + ref: string + name: string + } + ListGitHubConnectionsRepository: { + id: number + name: string + } + ListGitHubConnectionsUser: { + id: number + username: string + primary_email: string | null + } + ListGitHubConnectionsConnection: { + id: number + inserted_at: string + updated_at: string + installation_id: number + project: components['schemas']['ListGitHubConnectionsProject'] + repository: components['schemas']['ListGitHubConnectionsRepository'] + user: components['schemas']['ListGitHubConnectionsUser'] | null + workdir: string + } + ListGitHubConnectionsResponse: { + connections: components['schemas']['ListGitHubConnectionsConnection'][] + } + CreateGitHubConnectionsBody: { + project_ref: string + installation_id: number + repository_id: number + } + UpdateGitHubConnectionsBody: { + workdir?: string + supabase_changes_only?: boolean + } + CreateCliLoginSessionBody: { + session_id: string + public_key: string + token_name?: string + } +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 V1UpdateFunctionBody: { name?: string body?: string verify_jwt?: boolean } +<<<<<<< HEAD + V1CreateFunctionBody: { +======= + FunctionResponse: { + id: string +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 + slug: string + name: string + body: string + verify_jwt?: boolean +<<<<<<< HEAD +======= + import_map?: boolean + entrypoint_path?: string + import_map_path?: string + } V1CreateFunctionBody: { slug: string name: string body: string verify_jwt?: boolean } + SecretResponse: { + name: string + value: string +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 + } CreateSecretBody: { /** * @description Secret name must not start with the SUPABASE_ prefix. @@ -2171,6 +4779,7 @@ export interface components { created_at: string database?: components['schemas']['SystemDatabaseResponse'] } +<<<<<<< HEAD UpdateSubscriptionV2AdminBody: Record UpdateRestrictionsBody: Record CreateAwsPartnerOrganizationBody: Record @@ -2180,6 +4789,98 @@ export interface components { /** @description Database password */ db_pass: string /** @description Name of your project, should not contain dots */ +======= + UpdateSubscriptionV2AdminBody: { + payment_method?: string + /** @enum {string} */ + tier: 'tier_payg' | 'tier_pro' | 'tier_free' | 'tier_team' | 'tier_enterprise' + price_id?: string + } + RestrictionData: { + grace_period_end?: string + /** @enum {string} */ + restrictions?: 'drop_requests_402' + violations?: ( + | 'exceed_db_size_quota' + | 'exceed_egress_quota' + | 'exceed_edge_functions_count_quota' + | 'exceed_edge_functions_invocations_quota' + | 'exceed_monthly_active_users_quota' + | 'exceed_realtime_connection_count_quota' + | 'exceed_realtime_message_count_quota' + | 'exceed_storage_size_quota' + | 'overdue_payment' + )[] + } + UpdateRestrictionsBody: { + /** @enum {string} */ + restriction_status: 'grace_period' | 'grace_period_over' | 'null' | 'restricted' + restriction_data?: components['schemas']['RestrictionData'] + no_notification?: boolean + } + UpdateRestrictionsResponse: { + slug: string + /** @enum {string} */ + restriction_status?: 'grace_period' | 'grace_period_over' | 'null' | 'restricted' + restriction_data?: components['schemas']['RestrictionData'] + message?: string + } + CreateAwsPartnerOrganizationBody: { + primary_email: string + name: string + } + AwsPartnerOrganizationResponse: { + id: number + slug: string + name: string + } + GetMetricsBody: { + /** @enum {string} */ + metric: 'user_queries' + /** @enum {string} */ + interval: '1d' | '3d' | '7d' + project_refs: string[] + region: string + } + ProjectMetric: { + project_ref: string + metric: string + value: number + } + GetMetricsResponse: { + metrics: components['schemas']['ProjectMetric'][] + } + BranchDetailResponse: { + db_port: number + ref: string + postgres_version: string + /** @enum {string} */ + status: + | 'ACTIVE_HEALTHY' + | 'ACTIVE_UNHEALTHY' + | 'COMING_UP' + | 'GOING_DOWN' + | 'INACTIVE' + | 'INIT_FAILED' + | 'REMOVED' + | 'RESTORING' + | 'UNKNOWN' + | 'UPGRADING' + | 'PAUSING' + db_host: string + db_user?: string + db_pass?: string + jwt_secret?: string + } + UpdateBranchBody: { + branch_name?: string + git_branch?: string + reset_on_push?: boolean + persistent?: boolean + } + BranchResponse: { + id: string +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 name: string /** @description Slug of your organization */ organization_id: string @@ -2244,7 +4945,21 @@ export interface components { */ created_at: string database?: components['schemas']['V1DatabaseResponse'] + /** @enum {string} */ + status: + | 'ACTIVE_HEALTHY' + | 'ACTIVE_UNHEALTHY' + | 'COMING_UP' + | 'GOING_DOWN' + | 'INACTIVE' + | 'INIT_FAILED' + | 'REMOVED' + | 'RESTORING' + | 'UNKNOWN' + | 'UPGRADING' + | 'PAUSING' } +<<<<<<< HEAD CreateBranchBody: Record UpdateCustomHostnameBody: Record RemoveNetworkBanRequest: Record @@ -2253,6 +4968,193 @@ export interface components { SslEnforcementRequest: Record VanitySubdomainBody: Record UpgradeDatabaseBody: Record +======= + V1CreateProjectBody: { + /** @description Database password */ + db_pass: string + /** @description Name of your project, should not contain dots */ + name: string + /** @description Slug of your organization */ + organization_id: string + /** + * @deprecated + * @description Subscription plan is now set on organization level and is ignored in this request + * @example free + * @enum {string} + */ + plan?: 'free' | 'pro' + /** + * @description Region you want your server to reside in + * @example us-east-1 + * @enum {string} + */ + region: + | 'us-east-1' + | 'us-west-1' + | 'us-west-2' + | 'ap-east-1' + | 'ap-southeast-1' + | 'ap-northeast-1' + | 'ap-northeast-2' + | 'ap-southeast-2' + | 'eu-west-1' + | 'eu-west-2' + | 'eu-west-3' + | 'eu-central-1' + | 'ca-central-1' + | 'ap-south-1' + | 'sa-east-1' + /** @deprecated */ + kps_enabled?: boolean + desired_instance_size?: components['schemas']['DesiredInstanceSize'] + /** + * @description Template URL used to create the project from the CLI. + * @example https://github.com/supabase/supabase/tree/master/examples/slack-clone/nextjs-slack-clone + */ + template_url?: string + } + ApiKeyResponse: { + name: string + api_key: string + } + CreateBranchBody: { + branch_name: string + git_branch?: string + region?: string + } + UpdateCustomHostnameResponse: { + /** @enum {string} */ + status: + | '1_not_started' + | '2_initiated' + | '3_challenge_verified' + | '4_origin_setup_completed' + | '5_services_reconfigured' + custom_hostname: string + data: Record + } + UpdateCustomHostnameBody: { + custom_hostname: string + } + NetworkBanResponse: { + banned_ipv4_addresses: string[] + } + RemoveNetworkBanRequest: { + ipv4_addresses: string[] + } + NetworkRestrictionsRequest: { + dbAllowedCidrs?: string[] + dbAllowedCidrsV6?: string[] + } + NetworkRestrictionsResponse: { + /** @enum {string} */ + entitlement: 'disallowed' | 'allowed' + config: components['schemas']['NetworkRestrictionsRequest'] + old_config?: components['schemas']['NetworkRestrictionsRequest'] + /** @enum {string} */ + status: 'stored' | 'applied' + } + PgsodiumConfigResponse: { + root_key: string + } + UpdatePgsodiumConfigBody: { + root_key: string + } + PostgrestConfigWithJWTSecretResponse: { + max_rows: number + db_schema: string + db_extra_search_path: string + jwt_secret?: string + } + V1ProjectRefResponse: { + id: number + ref: string + name: string + } + SslEnforcements: { + database: boolean + } + SslEnforcementResponse: { + currentConfig: components['schemas']['SslEnforcements'] + appliedSuccessfully: boolean + } + SslEnforcementRequest: { + requestedConfig: components['schemas']['SslEnforcements'] + } + TypescriptResponse: { + types: string + } + VanitySubdomainConfigResponse: { + /** @enum {string} */ + status: 'not-used' | 'custom-domain-used' | 'active' + custom_domain?: string + } + VanitySubdomainBody: { + vanity_subdomain: string + } + SubdomainAvailabilityResponse: { + available: boolean + } + ActivateVanitySubdomainResponse: { + custom_domain: string + } + UpgradeDatabaseBody: { + target_version: number + } + ProjectUpgradeInitiateResponse: { + tracking_id: string + } + ProjectVersion: { + postgres_version: number + app_version: string + } + ProjectUpgradeEligibilityResponse: { + eligible: boolean + current_app_version: string + latest_app_version: string + target_upgrade_versions: components['schemas']['ProjectVersion'][] + potential_breaking_changes: string[] + duration_estimate_hours: number + legacy_auth_custom_roles: string[] + extension_dependent_objects: string[] + } + DatabaseUpgradeStatus: { + initiated_at: string + target_version: number + /** @enum {string} */ + error?: + | '1_upgraded_instance_launch_failed' + | '2_volume_detachchment_from_upgraded_instance_failed' + | '3_volume_attachment_to_original_instance_failed' + | '4_data_upgrade_initiation_failed' + | '5_data_upgrade_completion_failed' + | '6_volume_detachchment_from_original_instance_failed' + | '7_volume_attachment_to_upgraded_instance_failed' + | '8_upgrade_completion_failed' + /** @enum {string} */ + progress?: + | '0_requested' + | '1_started' + | '2_launched_upgraded_instance' + | '3_detached_volume_from_upgraded_instance' + | '4_attached_volume_to_original_instance' + | '5_initiated_data_upgrade' + | '6_completed_data_upgrade' + | '7_detached_volume_from_original_instance' + | '8_attached_volume_to_upgraded_instance' + | '9_completed_upgrade' + /** @enum {number} */ + status: 0 | 1 | 2 + } + DatabaseUpgradeStatusResponse: { + databaseUpgradeStatus: components['schemas']['DatabaseUpgradeStatus'] | null + } + ReadOnlyStatusResponse: { + enabled: boolean + override_enabled: boolean + override_active_until: string + } +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 SetUpReadReplicaBody: { /** * @description Region you want your read replica to reside in @@ -2291,6 +5193,348 @@ export interface components { info: | components['schemas']['AuthHealthResponse'] | components['schemas']['RealtimeHealthResponse'] +<<<<<<< HEAD +======= + /** @enum {string} */ + name: 'auth' | 'db' | 'pooler' | 'realtime' | 'rest' | 'storage' + healthy: boolean + /** @enum {string} */ + status: 'COMING_UP' | 'ACTIVE_HEALTHY' | 'UNHEALTHY' + error?: string + } + V1PgbouncerConfigResponse: { + /** @enum {string} */ + pool_mode?: 'transaction' | 'session' | 'statement' + default_pool_size?: number + ignore_startup_parameters?: string + max_client_conn?: number + connection_string?: string + } + AuthConfigResponse: { + disable_signup: boolean | null + external_anonymous_users_enabled: boolean | null + external_apple_additional_client_ids: string | null + external_apple_client_id: string | null + external_apple_enabled: boolean | null + external_apple_secret: string | null + external_azure_client_id: string | null + external_azure_enabled: boolean | null + external_azure_secret: string | null + external_azure_url: string | null + external_bitbucket_client_id: string | null + external_bitbucket_enabled: boolean | null + external_bitbucket_secret: string | null + external_discord_client_id: string | null + external_discord_enabled: boolean | null + external_discord_secret: string | null + external_email_enabled: boolean | null + external_facebook_client_id: string | null + external_facebook_enabled: boolean | null + external_facebook_secret: string | null + external_figma_client_id: string | null + external_figma_enabled: boolean | null + external_figma_secret: string | null + external_github_client_id: string | null + external_github_enabled: boolean | null + external_github_secret: string | null + external_gitlab_client_id: string | null + external_gitlab_enabled: boolean | null + external_gitlab_secret: string | null + external_gitlab_url: string | null + external_google_additional_client_ids: string | null + external_google_client_id: string | null + external_google_enabled: boolean | null + external_google_secret: string | null + external_google_skip_nonce_check: boolean | null + external_kakao_client_id: string | null + external_kakao_enabled: boolean | null + external_kakao_secret: string | null + external_keycloak_client_id: string | null + external_keycloak_enabled: boolean | null + external_keycloak_secret: string | null + external_keycloak_url: string | null + external_linkedin_oidc_client_id: string | null + external_linkedin_oidc_enabled: boolean | null + external_linkedin_oidc_secret: string | null + external_notion_client_id: string | null + external_notion_enabled: boolean | null + external_notion_secret: string | null + external_phone_enabled: boolean | null + external_slack_client_id: string | null + external_slack_enabled: boolean | null + external_slack_secret: string | null + external_spotify_client_id: string | null + external_spotify_enabled: boolean | null + external_spotify_secret: string | null + external_twitch_client_id: string | null + external_twitch_enabled: boolean | null + external_twitch_secret: string | null + external_twitter_client_id: string | null + external_twitter_enabled: boolean | null + external_twitter_secret: string | null + external_workos_client_id: string | null + external_workos_enabled: boolean | null + external_workos_secret: string | null + external_workos_url: string | null + external_zoom_client_id: string | null + external_zoom_enabled: boolean | null + external_zoom_secret: string | null + hook_custom_access_token_enabled: boolean | null + hook_custom_access_token_uri: string | null + hook_mfa_verification_attempt_enabled: boolean | null + hook_mfa_verification_attempt_uri: string | null + hook_password_verification_attempt_enabled: boolean | null + hook_password_verification_attempt_uri: string | null + jwt_exp: number | null + mailer_allow_unverified_email_sign_ins: boolean | null + mailer_autoconfirm: boolean | null + mailer_otp_exp: number | null + mailer_secure_email_change_enabled: boolean | null + mailer_subjects_confirmation: string | null + mailer_subjects_email_change: string | null + mailer_subjects_invite: string | null + mailer_subjects_magic_link: string | null + mailer_subjects_recovery: string | null + mailer_subjects_reauthentication: string | null + mailer_templates_confirmation_content: string | null + mailer_templates_email_change_content: string | null + mailer_templates_invite_content: string | null + mailer_templates_magic_link_content: string | null + mailer_templates_recovery_content: string | null + mailer_templates_reauthentication_content: string | null + mfa_max_enrolled_factors: number | null + password_hibp_enabled: boolean | null + password_min_length: number | null + password_required_characters: string | null + rate_limit_anonymous_users: number | null + rate_limit_email_sent: number | null + rate_limit_sms_sent: number | null + rate_limit_token_refresh: number | null + rate_limit_verify: number | null + refresh_token_rotation_enabled: boolean | null + saml_enabled: boolean | null + security_captcha_enabled: boolean | null + security_captcha_provider: string | null + security_captcha_secret: string | null + security_manual_linking_enabled: boolean | null + security_refresh_token_reuse_interval: number | null + security_update_password_require_reauthentication: boolean | null + sessions_inactivity_timeout: number | null + sessions_single_per_user: boolean | null + sessions_tags: string | null + sessions_timebox: number | null + site_url: string | null + sms_autoconfirm: boolean | null + sms_max_frequency: number | null + sms_messagebird_access_key: string | null + sms_messagebird_originator: string | null + sms_otp_exp: number | null + sms_otp_length: number | null + sms_provider: string | null + sms_template: string | null + sms_test_otp: string | null + sms_test_otp_valid_until: string | null + sms_textlocal_api_key: string | null + sms_textlocal_sender: string | null + sms_twilio_account_sid: string | null + sms_twilio_auth_token: string | null + sms_twilio_content_sid: string | null + sms_twilio_message_service_sid: string | null + sms_twilio_verify_account_sid: string | null + sms_twilio_verify_auth_token: string | null + sms_twilio_verify_message_service_sid: string | null + sms_vonage_api_key: string | null + sms_vonage_api_secret: string | null + sms_vonage_from: string | null + smtp_admin_email: string | null + smtp_host: string | null + smtp_max_frequency: number | null + smtp_pass: string | null + smtp_port: string | null + smtp_sender_name: string | null + smtp_user: string | null + uri_allow_list: string | null + } + UpdateAuthConfigBody: { + site_url?: string + disable_signup?: boolean + jwt_exp?: number + smtp_admin_email?: string + smtp_host?: string + smtp_port?: string + smtp_user?: string + smtp_pass?: string + smtp_max_frequency?: number + smtp_sender_name?: string + mailer_allow_unverified_email_sign_ins?: boolean + mailer_autoconfirm?: boolean + mailer_subjects_invite?: string + mailer_subjects_confirmation?: string + mailer_subjects_recovery?: string + mailer_subjects_email_change?: string + mailer_subjects_magic_link?: string + mailer_subjects_reauthentication?: string + mailer_templates_invite_content?: string + mailer_templates_confirmation_content?: string + mailer_templates_recovery_content?: string + mailer_templates_email_change_content?: string + mailer_templates_magic_link_content?: string + mailer_templates_reauthentication_content?: string + mfa_max_enrolled_factors?: number + uri_allow_list?: string + external_anonymous_users_enabled?: boolean + external_email_enabled?: boolean + external_phone_enabled?: boolean + saml_enabled?: boolean + security_captcha_enabled?: boolean + security_captcha_provider?: string + security_captcha_secret?: string + sessions_timebox?: number + sessions_inactivity_timeout?: number + sessions_single_per_user?: boolean + sessions_tags?: string + rate_limit_anonymous_users?: number + rate_limit_email_sent?: number + rate_limit_sms_sent?: number + rate_limit_verify?: number + rate_limit_token_refresh?: number + mailer_secure_email_change_enabled?: boolean + refresh_token_rotation_enabled?: boolean + password_hibp_enabled?: boolean + password_min_length?: number + /** @enum {string} */ + password_required_characters?: + | 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789' + | 'abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789' + | 'abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789:!@#$%^&*()_+-=[]{};\'\\:"|<>?,./`~' + | '' + security_manual_linking_enabled?: boolean + security_update_password_require_reauthentication?: boolean + security_refresh_token_reuse_interval?: number + mailer_otp_exp?: number + sms_autoconfirm?: boolean + sms_max_frequency?: number + sms_otp_exp?: number + sms_otp_length?: number + sms_provider?: string + sms_messagebird_access_key?: string + sms_messagebird_originator?: string + sms_test_otp?: string + sms_test_otp_valid_until?: string + sms_textlocal_api_key?: string + sms_textlocal_sender?: string + sms_twilio_account_sid?: string + sms_twilio_auth_token?: string + sms_twilio_content_sid?: string + sms_twilio_message_service_sid?: string + sms_twilio_verify_account_sid?: string + sms_twilio_verify_auth_token?: string + sms_twilio_verify_message_service_sid?: string + sms_vonage_api_key?: string + sms_vonage_api_secret?: string + sms_vonage_from?: string + sms_template?: string + hook_mfa_verification_attempt_enabled?: boolean + hook_mfa_verification_attempt_uri?: string + hook_password_verification_attempt_enabled?: boolean + hook_password_verification_attempt_uri?: string + hook_custom_access_token_enabled?: boolean + hook_custom_access_token_uri?: string + hook_send_sms_enabled?: boolean + hook_send_sms_uri?: string + hook_send_email_enabled?: boolean + hook_send_email_uri?: string + external_apple_enabled?: boolean + external_apple_client_id?: string + external_apple_secret?: string + external_apple_additional_client_ids?: string + external_azure_enabled?: boolean + external_azure_client_id?: string + external_azure_secret?: string + external_azure_url?: string + external_bitbucket_enabled?: boolean + external_bitbucket_client_id?: string + external_bitbucket_secret?: string + external_discord_enabled?: boolean + external_discord_client_id?: string + external_discord_secret?: string + external_facebook_enabled?: boolean + external_facebook_client_id?: string + external_facebook_secret?: string + external_figma_enabled?: boolean + external_figma_client_id?: string + external_figma_secret?: string + external_github_enabled?: boolean + external_github_client_id?: string + external_github_secret?: string + external_gitlab_enabled?: boolean + external_gitlab_client_id?: string + external_gitlab_secret?: string + external_gitlab_url?: string + external_google_enabled?: boolean + external_google_client_id?: string + external_google_secret?: string + external_google_additional_client_ids?: string + external_google_skip_nonce_check?: boolean + external_kakao_enabled?: boolean + external_kakao_client_id?: string + external_kakao_secret?: string + external_keycloak_enabled?: boolean + external_keycloak_client_id?: string + external_keycloak_secret?: string + external_keycloak_url?: string + external_linkedin_oidc_enabled?: boolean + external_linkedin_oidc_client_id?: string + external_linkedin_oidc_secret?: string + external_notion_enabled?: boolean + external_notion_client_id?: string + external_notion_secret?: string + external_slack_enabled?: boolean + external_slack_client_id?: string + external_slack_secret?: string + external_spotify_enabled?: boolean + external_spotify_client_id?: string + external_spotify_secret?: string + external_twitch_enabled?: boolean + external_twitch_client_id?: string + external_twitch_secret?: string + external_twitter_enabled?: boolean + external_twitter_client_id?: string + external_twitter_secret?: string + external_workos_enabled?: boolean + external_workos_client_id?: string + external_workos_secret?: string + external_workos_url?: string + external_zoom_enabled?: boolean + external_zoom_client_id?: string + external_zoom_secret?: string + } + CreateThirdPartyAuthBody: { + oidc_issuer_url?: string + jwks_url?: string + custom_jwks?: Record + } + ThirdPartyAuth: { + id: string + type: string + oidc_issuer_url?: string | null + jwks_url?: string | null + custom_jwks?: unknown + resolved_jwks?: unknown + inserted_at: string + updated_at: string + resolved_at?: string | null + } + AttributeValue: { + default?: Record | number | string | boolean + name?: string + names?: string[] + } + AttributeMapping: { + keys: { + [key: string]: components['schemas']['AttributeValue'] + } +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 } UpdateAuthConfigBody: Record CreateThirdPartyAuthBody: Record @@ -2300,6 +5544,352 @@ export interface components { * @enum {string} */ type: 'saml' +<<<<<<< HEAD +======= + metadata_xml?: string + metadata_url?: string + domains?: string[] + attribute_mapping?: components['schemas']['AttributeMapping'] + } + SamlDescriptor: { + id: string + entity_id: string + metadata_url?: string + metadata_xml?: string + attribute_mapping?: components['schemas']['AttributeMapping'] + } + Domain: { + id: string + domain?: string + created_at?: string + updated_at?: string + } + CreateProviderResponse: { + id: string + saml?: components['schemas']['SamlDescriptor'] + domains?: components['schemas']['Domain'][] + created_at?: string + updated_at?: string + } + Provider: { + id: string + saml?: components['schemas']['SamlDescriptor'] + domains?: components['schemas']['Domain'][] + created_at?: string + updated_at?: string + } + ListProvidersResponse: { + items: components['schemas']['Provider'][] + } + GetProviderResponse: { + id: string + saml?: components['schemas']['SamlDescriptor'] + domains?: components['schemas']['Domain'][] + created_at?: string + updated_at?: string + } + UpdateProviderBody: { + metadata_xml?: string + metadata_url?: string + domains?: string[] + attribute_mapping?: components['schemas']['AttributeMapping'] + } + UpdateProviderResponse: { + id: string + saml?: components['schemas']['SamlDescriptor'] + domains?: components['schemas']['Domain'][] + created_at?: string + updated_at?: string + } + DeleteProviderResponse: { + id: string + saml?: components['schemas']['SamlDescriptor'] + domains?: components['schemas']['Domain'][] + created_at?: string + updated_at?: string + } + V1RunQueryBody: { + query: string + } + V1Backup: { + /** @enum {string} */ + status: 'COMPLETED' | 'FAILED' | 'PENDING' | 'REMOVED' | 'ARCHIVED' + is_physical_backup: boolean + inserted_at: string + } + V1PhysicalBackup: { + earliest_physical_backup_date_unix?: number + latest_physical_backup_date_unix?: number + } + V1BackupsResponse: { + region: string + walg_enabled: boolean + pitr_enabled: boolean + backups: components['schemas']['V1Backup'][] + physical_backup_data: components['schemas']['V1PhysicalBackup'] + } + V1RestorePitrBody: { + recovery_time_target_unix: number + } + FunctionSlugResponse: { + id: string + slug: string + name: string + /** @enum {string} */ + status: 'ACTIVE' | 'REMOVED' | 'THROTTLED' + version: number + created_at: number + updated_at: number + verify_jwt?: boolean + import_map?: boolean + entrypoint_path?: string + import_map_path?: string + } + V1StorageBucketResponse: { + id: string + name: string + owner: string + created_at: string + updated_at: string + public: boolean + } + OrganizationResponseV1: { + id: string + name: string + } + CreateOrganizationBodyV1: { + name: string + } + V1OrganizationMemberResponse: { + user_id: string + user_name: string + email?: string + role_name: string + mfa_enabled: boolean + } + V1OrganizationSlugResponse: { + plan?: components['schemas']['BillingPlanId'] + opt_in_tags: 'AI_SQL_GENERATOR_OPT_IN'[] + id: string + name: string + } + OAuthTokenBody: { + /** @enum {string} */ + grant_type: 'authorization_code' | 'refresh_token' + client_id: string + client_secret: string + code?: string + code_verifier?: string + redirect_uri?: string + refresh_token?: string + } + OAuthTokenResponse: { + /** @enum {string} */ + token_type: 'Bearer' + access_token: string + refresh_token: string + expires_in: number + } + SnippetProject: { + id: number + name: string + } + SnippetUser: { + id: number + username: string + } + SnippetMeta: { + id: string + inserted_at: string + updated_at: string + /** @enum {string} */ + type: 'sql' + /** @enum {string} */ + visibility: 'user' | 'project' | 'org' | 'public' + name: string + description: string | null + project: components['schemas']['SnippetProject'] + owner: components['schemas']['SnippetUser'] + updated_by: components['schemas']['SnippetUser'] + } + SnippetList: { + data: components['schemas']['SnippetMeta'][] + } + SnippetContent: { + favorite: boolean + schema_version: string + sql: string + } + SnippetResponse: { + id: string + inserted_at: string + updated_at: string + /** @enum {string} */ + type: 'sql' + /** @enum {string} */ + visibility: 'user' | 'project' | 'org' | 'public' + name: string + description: string | null + project: components['schemas']['SnippetProject'] + owner: components['schemas']['SnippetUser'] + updated_by: components['schemas']['SnippetUser'] + content: components['schemas']['SnippetContent'] + } + ServiceHealthResponse: { + /** + * @description Service name + * @enum {string} + */ + name: 'auth' | 'db' | 'pooler' | 'realtime' | 'rest' | 'storage' + /** @description Whether the service is healthy */ + healthy: boolean + /** + * @description Service health status + * @example COMING_UP + * @enum {string} + */ + status: 'COMING_UP' | 'ACTIVE_HEALTHY' | 'UNHEALTHY' + /** @description Service health check error */ + error?: string + } + ResourceWithServicesStatusResponse: { + /** @description Supabase project instance compute size */ + compute: string + /** @description Unique ID representing the fly extension */ + id: string + /** + * @description Supabase project status + * @example ACTIVE_HEALTHY + * @enum {string} + */ + status: + | 'REMOVED' + | 'COMING_UP' + | 'INACTIVE' + | 'ACTIVE_HEALTHY' + | 'ACTIVE_UNHEALTHY' + | 'UNKNOWN' + | 'GOING_DOWN' + | 'INIT_FAILED' + | 'RESTORING' + | 'UPGRADING' + | 'PAUSING' + /** + * @description Supabase organization id + * @example fly_123456789 + */ + supabase_org_id: string + /** @description Supabase project services health status */ + services: components['schemas']['ServiceHealthResponse'][] + } + ResourceProvisioningConfigResponse: { + /** + * @description PSQL connection string + * @example postgresql://postgres:dbpass@db.abcdefghijklmnop.supabase.co:5432/postgres + */ + DATABASE_URL: string + /** + * @description Pooler connection string + * @example postgres://postgres.abcdefghijklmnop:dbpass@aws-0-us-east-1.pooler.supabase.com:6543/postgres + */ + DATABASE_POOLER_URL: string + } + ResourceProvisioningResponse: { + /** @description Supabase envs config */ + config: components['schemas']['ResourceProvisioningConfigResponse'] + /** + * @description The target Fly application for internal traffic + * @example ext-db-pgshhamktpsgnptvcadw + */ + fly_app_name: string + /** + * @description Supabase project id + * @example pgshhamktpsgnptvcadw + */ + id: string + /** @description Welcome message */ + message: string + /** + * @description Supabase organization id + * @example fly_123456789 + */ + supabase_org_id: string + } + FlyOrganization: { + plan: components['schemas']['BillingPlanId'] + id: string + supabase_org_id: string + name: string + } + OrganizationExtensionStatus: { + /** @description Supabase project instance compute size */ + compute: string + /** @description Unique ID representing the fly extension */ + id: string + /** + * @description Supabase project status + * @example ACTIVE_HEALTHY + * @enum {string} + */ + status: + | 'REMOVED' + | 'COMING_UP' + | 'INACTIVE' + | 'ACTIVE_HEALTHY' + | 'ACTIVE_UNHEALTHY' + | 'UNKNOWN' + | 'GOING_DOWN' + | 'INIT_FAILED' + | 'RESTORING' + | 'UPGRADING' + | 'PAUSING' + /** + * @description Supabase organization id + * @example fly_123456789 + */ + supabase_org_id: string + } + ResourceBillingItem: { + /** + * @description Non-Unique identifier of the item + * @example usage_egress + */ + itemIdentifier: string + /** + * @description Descriptive name of the billing item + * @example Pro Plan + */ + itemName: string + /** @enum {string} */ + type: 'usage' | 'plan' | 'addon' | 'proration' | 'compute_credits' + /** + * @description In case of a usage item, the free usage included in the customers plan + * @example 100 + */ + freeUnitsInPlan?: number + /** + * @description In case of a usage item, the total usage + * @example 100 + */ + usageTotal?: number + /** + * @description In case of a usage item, the billable usage amount, free usage has been deducted + * @example 100 + */ + usageBillable?: number + /** + * @description Costs of the item in cents + * @example 100 + */ + costs: number + } + ResourceBillingResponse: { + /** @description Whether the user is exceeding the included quotas in the plan - only relevant for users on usage-capped plans. */ + exceedsPlanLimits: boolean + /** @description Whether the user is can have over-usage, which will be billed - this will be false on usage-capped plans. */ + overusageAllowed: boolean + items: components['schemas']['ResourceBillingItem'][] +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 } UpdateProviderBody: Record V1RunQueryBody: Record @@ -2436,6 +6026,14 @@ export interface operations { } } responses: { +<<<<<<< HEAD +======= + 201: { + content: { + 'application/json': components['schemas']['SendFeedbackResponse'] + } + } +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 /** @description Failed to send feedback for docs */ 500: { content: never @@ -2624,6 +6222,21 @@ export interface operations { } /** Gets GoTrue template */ TemplateController_getTemplate: { +<<<<<<< HEAD +======= + parameters: { + path: { + ref: string + template: + | 'confirmation' + | 'email-change' + | 'invite' + | 'magic-link' + | 'recovery' + | 'reauthentication' + } + } +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 responses: { /** @description Failed to retrieve GoTrue template */ 500: { @@ -4205,27 +7818,25 @@ export interface operations { } } } - /** Run sql query */ + /** Lists project's warehouse queries from logflare */ QueryController_runQuery: { parameters: { - header: { - 'x-connection-encrypted': string - } path: { /** @description Project ref */ ref: string } } - requestBody: { - content: { - 'application/json': components['schemas']['RunQueryBody'] - } - } responses: { +<<<<<<< HEAD +======= + 200: { + content: never + } +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 403: { content: never } - /** @description Failed to run sql query */ + /** @description Failed to fetch warehouse endpoints */ 500: { content: never } @@ -5646,6 +9257,14 @@ export interface operations { } } responses: { +<<<<<<< HEAD +======= + 200: { + content: { + 'application/json': components['schemas']['LFUser'] + } + } +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 403: { content: never } @@ -5664,6 +9283,14 @@ export interface operations { } } responses: { +<<<<<<< HEAD +======= + 200: { + content: { + 'application/json': components['schemas']['LFSource'][] + } + } +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 403: { content: never } @@ -5676,6 +9303,14 @@ export interface operations { /** Create a warehouse collection */ CollectionController_createCollection: { responses: { +<<<<<<< HEAD +======= + 201: { + content: { + 'application/json': components['schemas']['LFSource'] + } + } +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 403: { content: never } @@ -5688,6 +9323,14 @@ export interface operations { /** Get a warehouse collection */ CollectionController_getCollection: { responses: { +<<<<<<< HEAD +======= + 200: { + content: { + 'application/json': components['schemas']['LFSource'] + } + } +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 403: { content: never } @@ -5700,6 +9343,14 @@ export interface operations { /** Delete a warehouse collection */ CollectionController_deleteCollection: { responses: { +<<<<<<< HEAD +======= + 200: { + content: { + 'application/json': components['schemas']['LFSource'] + } + } +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 403: { content: never } @@ -5712,6 +9363,14 @@ export interface operations { /** Update a warehouse collection */ CollectionController_updateCollection: { responses: { +<<<<<<< HEAD +======= + 200: { + content: { + 'application/json': components['schemas']['LFSource'] + } + } +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 403: { content: never } @@ -5730,6 +9389,14 @@ export interface operations { } } responses: { +<<<<<<< HEAD +======= + 200: { + content: { + 'application/json': components['schemas']['LFSource'][] + } + } +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 403: { content: never } @@ -5742,6 +9409,14 @@ export interface operations { /** Create a warehouse access token */ AccessTokenController_createAccessToken: { responses: { +<<<<<<< HEAD +======= + 201: { + content: { + 'application/json': components['schemas']['LFSource'] + } + } +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 403: { content: never } @@ -5754,6 +9429,14 @@ export interface operations { /** Delete a warehouse access token */ AccessTokenController_deleteAccessToken: { responses: { +<<<<<<< HEAD +======= + 200: { + content: { + 'application/json': components['schemas']['LFSource'] + } + } +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 403: { content: never } @@ -5772,6 +9455,14 @@ export interface operations { } } responses: { +<<<<<<< HEAD +======= + 200: { + content: { + 'application/json': components['schemas']['LFEndpoint'][] + } + } +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 403: { content: never } @@ -5784,6 +9475,14 @@ export interface operations { /** Create a warehouse endpoint */ EndpointController_createEndpoint: { responses: { +<<<<<<< HEAD +======= + 201: { + content: { + 'application/json': components['schemas']['LFEndpoint'] + } + } +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 403: { content: never } @@ -5796,6 +9495,14 @@ export interface operations { /** Update a warehouse endpoint */ EndpointController_updateEndpoint: { responses: { +<<<<<<< HEAD +======= + 200: { + content: { + 'application/json': components['schemas']['LFEndpoint'] + } + } +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 403: { content: never } @@ -5808,6 +9515,12 @@ export interface operations { /** Delete a warehouse endpoint */ EndpointController_deleteEndpoint: { responses: { +<<<<<<< HEAD +======= + 200: { + content: never + } +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 403: { content: never } @@ -5817,6 +9530,7 @@ export interface operations { } } } +<<<<<<< HEAD /** Lists project's warehouse queries from logflare */ WarehouseQueryController_runQuery: { parameters: { @@ -5835,6 +9549,8 @@ export interface operations { } } } +======= +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 /** Gets project's pgbouncer config */ PgbouncerConfigController_getPgbouncerConfig: { parameters: { @@ -6489,6 +10205,83 @@ export interface operations { } /** Gets project storage credentials */ StorageS3CredentialsController_getAllCredentials: { +<<<<<<< HEAD +======= + parameters: { + path: { + /** @description Project ref */ + ref: string + } + } + responses: { + 200: { + content: { + 'application/json': components['schemas']['GetStorageCredentialsResponse'] + } + } + 403: { + content: never + } + /** @description Failed to get project storage credentials */ + 500: { + content: never + } + } + } + /** Creates project storage credential */ + StorageS3CredentialsController_createCredential: { + parameters: { + path: { + /** @description Project ref */ + ref: string + } + } + requestBody: { + content: { + 'application/json': components['schemas']['CreateStorageCredentialBody'] + } + } + responses: { + 201: { + content: { + 'application/json': components['schemas']['CreateStorageCredentialResponse'] + } + } + 403: { + content: never + } + /** @description Failed to create project storage credential */ + 500: { + content: never + } + } + } + /** Deletes project storage credential */ + StorageS3CredentialsController_deleteCredential: { + parameters: { + path: { + /** @description Project ref */ + ref: string + /** @description Storage credential id */ + id: string + } + } + responses: { + 200: { + content: never + } + 403: { + content: never + } + /** @description Failed to delete project storage credential */ + 500: { + content: never + } + } + } + /** Gets invoices for the given customer */ + InvoicesController_getInvoices: { +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 parameters: { path: { /** @description Project ref */ @@ -7029,6 +10822,21 @@ export interface operations { } /** Gets GoTrue template */ AuthTemplateController_getTemplate: { +<<<<<<< HEAD +======= + parameters: { + path: { + ref: string + template: + | 'confirmation' + | 'email-change' + | 'invite' + | 'magic-link' + | 'recovery' + | 'reauthentication' + } + } +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 responses: { /** @description Failed to retrieve GoTrue template */ 500: { @@ -7193,6 +11001,48 @@ export interface operations { } } } + /** + * Update a function + * @description Updates a function with the specified slug and project. + */ + SystemFunctionSlugController_updateFunction: { + parameters: { + query?: { + slug?: string + name?: string + verify_jwt?: boolean + import_map?: boolean + entrypoint_path?: string + import_map_path?: string + } + path: { + /** @description Project ref */ + ref: string + /** @description Function slug */ + function_slug: string + } + } + requestBody: { + content: { + 'application/json': components['schemas']['V1UpdateFunctionBody'] + 'application/vnd.denoland.eszip': components['schemas']['V1UpdateFunctionBody'] + } + } + responses: { + 200: { + content: { + 'application/json': components['schemas']['FunctionResponse'] + } + } + 403: { + content: never + } + /** @description Failed to update function with given slug */ + 500: { + content: never + } + } + } /** * List all functions * @description Returns all functions you've previously added to the specified project. @@ -7221,12 +11071,21 @@ export interface operations { SystemFunctionsController_createFunction: { parameters: { query?: { +<<<<<<< HEAD import_map_path?: string entrypoint_path?: string import_map?: boolean verify_jwt?: boolean name?: string slug?: string +======= + slug?: string + name?: string + verify_jwt?: boolean + import_map?: boolean + entrypoint_path?: string + import_map_path?: string +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 } path: { /** @description Project ref */ @@ -7240,6 +11099,14 @@ export interface operations { } } responses: { +<<<<<<< HEAD +======= + 201: { + content: { + 'application/json': components['schemas']['FunctionResponse'] + } + } +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 403: { content: never } @@ -7563,6 +11430,14 @@ export interface operations { } } responses: { +<<<<<<< HEAD +======= + 201: { + content: { + 'application/json': components['schemas']['AwsPartnerOrganizationResponse'] + } + } +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 /** @description Unexpected error creating a partner organization */ 500: { content: never @@ -8500,6 +12375,14 @@ export interface operations { } } responses: { +<<<<<<< HEAD +======= + 200: { + content: { + 'application/json': components['schemas']['ThirdPartyAuth'][] + } + } +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 403: { content: never } @@ -8519,6 +12402,14 @@ export interface operations { } } responses: { +<<<<<<< HEAD +======= + 201: { + content: { + 'application/json': components['schemas']['ThirdPartyAuth'] + } + } +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 403: { content: never } @@ -8530,9 +12421,21 @@ export interface operations { path: { /** @description Project ref */ ref: string +<<<<<<< HEAD } } responses: { +======= + tpa_id: string + } + } + responses: { + 200: { + content: { + 'application/json': components['schemas']['ThirdPartyAuth'] + } + } +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 403: { content: never } @@ -8544,9 +12447,21 @@ export interface operations { path: { /** @description Project ref */ ref: string +<<<<<<< HEAD } } responses: { +======= + tpa_id: string + } + } + responses: { + 200: { + content: { + 'application/json': components['schemas']['ThirdPartyAuth'] + } + } +>>>>>>> 2bf347a585720719a9bd87adaafeb376154207f8 403: { content: never } diff --git a/apps/studio/data/database-roles/database-role-create-mutation.ts b/apps/studio/data/database-roles/database-role-create-mutation.ts index ecaaa44fa47..7601b21e30d 100644 --- a/apps/studio/data/database-roles/database-role-create-mutation.ts +++ b/apps/studio/data/database-roles/database-role-create-mutation.ts @@ -1,12 +1,12 @@ import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' +import pgMeta from '@supabase/pg-meta' import { toast } from 'react-hot-toast' -import { post } from 'data/fetchers' import type { ResponseError } from 'types' -import { databaseRolesKeys } from './keys' -import type { components } from 'data/api' +import { executeSql } from 'data/sql/execute-sql-query' +import { invalidateRolesQuery } from './database-roles-query' -type CreateRoleBody = components['schemas']['CreateRoleBody'] +type CreateRoleBody = Parameters[0] export type DatabaseRoleCreateVariables = { projectRef: string @@ -19,20 +19,14 @@ export async function createDatabaseRole({ connectionString, payload, }: DatabaseRoleCreateVariables) { - let headers = new Headers() - if (connectionString) headers.set('x-connection-encrypted', connectionString) - - const { data, error } = await post('/platform/pg-meta/{ref}/roles', { - params: { - header: { 'x-connection-encrypted': connectionString! }, - path: { ref: projectRef }, - }, - body: payload, - headers, + const sql = pgMeta.roles.create(payload).sql + const { result } = await executeSql({ + projectRef, + connectionString, + sql, + queryKey: ['roles', 'create'], }) - - if (error) throw error - return data + return result } type DatabaseRoleCreateData = Awaited> @@ -52,7 +46,7 @@ export const useDatabaseRoleCreateMutation = ({ { async onSuccess(data, variables, context) { const { projectRef } = variables - await queryClient.invalidateQueries(databaseRolesKeys.list(projectRef)) + await invalidateRolesQuery(queryClient, projectRef) await onSuccess?.(data, variables, context) }, async onError(data, variables, context) { diff --git a/apps/studio/data/database-roles/database-role-delete-mutation.ts b/apps/studio/data/database-roles/database-role-delete-mutation.ts index ac0569614ab..c2e7ddda4e7 100644 --- a/apps/studio/data/database-roles/database-role-delete-mutation.ts +++ b/apps/studio/data/database-roles/database-role-delete-mutation.ts @@ -1,35 +1,34 @@ import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' +import pgMeta from '@supabase/pg-meta' import { toast } from 'react-hot-toast' -import { del } from 'data/fetchers' import type { ResponseError } from 'types' -import { databaseRolesKeys } from './keys' +import { executeSql } from 'data/sql/execute-sql-query' +import { invalidateRolesQuery } from './database-roles-query' + +type DropRoleBody = Parameters[1] export type DatabaseRoleDeleteVariables = { projectRef: string connectionString?: string - id: string + id: number + payload?: DropRoleBody } export async function deleteDatabaseRole({ projectRef, connectionString, id, + payload, }: DatabaseRoleDeleteVariables) { - let headers = new Headers() - if (connectionString) headers.set('x-connection-encrypted', connectionString) - - const { data, error } = await del('/platform/pg-meta/{ref}/roles', { - params: { - header: { 'x-connection-encrypted': connectionString! }, - path: { ref: projectRef }, - query: { id }, - }, - headers, + const sql = pgMeta.roles.remove({ id }, payload).sql + const { result } = await executeSql({ + projectRef, + connectionString, + sql, + queryKey: ['roles', 'delete'], }) - - if (error) throw error - return data + return result } type DatabaseRoleDeleteData = Awaited> @@ -49,7 +48,7 @@ export const useDatabaseRoleDeleteMutation = ({ { async onSuccess(data, variables, context) { const { projectRef } = variables - await queryClient.invalidateQueries(databaseRolesKeys.list(projectRef)) + await invalidateRolesQuery(queryClient, projectRef) await onSuccess?.(data, variables, context) }, async onError(data, variables, context) { diff --git a/apps/studio/data/database-roles/database-role-update-mutation.ts b/apps/studio/data/database-roles/database-role-update-mutation.ts index c8876fb55e7..a0d90520269 100644 --- a/apps/studio/data/database-roles/database-role-update-mutation.ts +++ b/apps/studio/data/database-roles/database-role-update-mutation.ts @@ -1,12 +1,12 @@ import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' +import pgMeta from '@supabase/pg-meta' import { toast } from 'react-hot-toast' -import { patch } from 'data/fetchers' import type { ResponseError } from 'types' -import { databaseRolesKeys } from './keys' -import type { components } from 'data/api' +import { executeSql } from 'data/sql/execute-sql-query' +import { invalidateRolesQuery } from './database-roles-query' -type UpdateRoleBody = components['schemas']['UpdateRoleBody'] +type UpdateRoleBody = Parameters[1] export type DatabaseRoleUpdateVariables = { projectRef: string @@ -21,21 +21,14 @@ export async function updateDatabaseRole({ id, payload, }: DatabaseRoleUpdateVariables) { - let headers = new Headers() - if (connectionString) headers.set('x-connection-encrypted', connectionString) - - const { data, error } = await patch('/platform/pg-meta/{ref}/roles', { - params: { - header: { 'x-connection-encrypted': connectionString! }, - path: { ref: projectRef }, - query: { id }, - }, - body: payload, - headers, + const sql = pgMeta.roles.update({ id }, payload).sql + const { result } = await executeSql({ + projectRef, + connectionString, + sql, + queryKey: ['roles', 'update'], }) - - if (error) throw error - return data + return result } type DatabaseRoleUpdateData = Awaited> @@ -55,7 +48,7 @@ export const useDatabaseRoleUpdateMutation = ({ { async onSuccess(data, variables, context) { const { projectRef } = variables - await queryClient.invalidateQueries(databaseRolesKeys.list(projectRef)) + await invalidateRolesQuery(queryClient, projectRef) await onSuccess?.(data, variables, context) }, async onError(data, variables, context) { diff --git a/apps/studio/data/database-roles/database-roles-query.ts b/apps/studio/data/database-roles/database-roles-query.ts index fc107b656a2..e347af6f236 100644 --- a/apps/studio/data/database-roles/database-roles-query.ts +++ b/apps/studio/data/database-roles/database-roles-query.ts @@ -1,49 +1,41 @@ -import { UseQueryOptions, useQuery } from '@tanstack/react-query' -import type { PostgresRole } from '@supabase/postgres-meta' +import pgMeta from '@supabase/pg-meta' +import { QueryClient, UseQueryOptions } from '@tanstack/react-query' +import { z } from 'zod' -import { get } from 'data/fetchers' -import type { ResponseError } from 'types' -import { databaseRolesKeys } from './keys' +import { ExecuteSqlData, useExecuteSqlQuery } from 'data/sql/execute-sql-query' +import { sqlKeys } from 'data/sql/keys' export type DatabaseRolesVariables = { projectRef?: string connectionString?: string } -export async function getDatabaseRoles( - { projectRef, connectionString }: DatabaseRolesVariables, - signal?: AbortSignal -) { - if (!projectRef) throw new Error('projectRef is required') +export type PgRole = z.infer - let headers = new Headers() - if (connectionString) headers.set('x-connection-encrypted', connectionString) +const pgMetaRolesList = pgMeta.roles.list() - const { data, error } = await get('/platform/pg-meta/{ref}/roles', { - params: { - header: { 'x-connection-encrypted': connectionString! }, - path: { ref: projectRef }, - }, - headers, - signal, - }) - - if (error) throw error - return data as PostgresRole[] -} - -export type DatabaseRolesData = Awaited> -export type DatabaseRolesError = ResponseError +export type DatabaseRolesData = z.infer +export type DatabaseRolesError = unknown export const useDatabaseRolesQuery = ( { projectRef, connectionString }: DatabaseRolesVariables, - { enabled = true, ...options }: UseQueryOptions = {} + options: UseQueryOptions = {} ) => - useQuery( - databaseRolesKeys.list(projectRef), - ({ signal }) => getDatabaseRoles({ projectRef, connectionString }, signal), + useExecuteSqlQuery( { - enabled: enabled && typeof projectRef !== 'undefined', + projectRef, + connectionString, + sql: pgMetaRolesList.sql, + queryKey: ['roles', 'list'], + }, + { + select(data) { + return data.result + }, ...options, } ) + +export function invalidateRolesQuery(client: QueryClient, projectRef: string | undefined) { + return client.invalidateQueries(sqlKeys.query(projectRef, ['roles', 'list'])) +} diff --git a/apps/studio/data/database-roles/keys.ts b/apps/studio/data/database-roles/keys.ts deleted file mode 100644 index 34ca7337bf4..00000000000 --- a/apps/studio/data/database-roles/keys.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const databaseRolesKeys = { - list: (projectRef: string | undefined) => ['projects', projectRef, 'database-roles'] as const, -} diff --git a/apps/studio/data/database/hooks-enable-mutation.ts b/apps/studio/data/database/hooks-enable-mutation.ts index 504e891968e..68563763610 100644 --- a/apps/studio/data/database/hooks-enable-mutation.ts +++ b/apps/studio/data/database/hooks-enable-mutation.ts @@ -3,7 +3,7 @@ import { toast } from 'react-hot-toast' import { post } from 'data/fetchers' import type { ResponseError } from 'types' -import { databaseKeys } from './keys' +import { invalidateSchemasQuery } from './schemas-query' export type HooksEnableVariables = { ref: string @@ -34,7 +34,7 @@ export const useHooksEnableMutation = ({ async onSuccess(data, variables, context) { const { ref } = variables await onSuccess?.(data, variables, context) - await queryClient.invalidateQueries(databaseKeys.schemaList(ref)) + await invalidateSchemasQuery(queryClient, ref) }, async onError(data, variables, context) { if (onError === undefined) { diff --git a/apps/studio/data/database/index-advisor-enable-mutation.ts b/apps/studio/data/database/index-advisor-enable-mutation.ts deleted file mode 100644 index 95419dd621b..00000000000 --- a/apps/studio/data/database/index-advisor-enable-mutation.ts +++ /dev/null @@ -1,250 +0,0 @@ -import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' -import { toast } from 'react-hot-toast' - -import { executeSql } from 'data/sql/execute-sql-query' -import type { ResponseError } from 'types' -import { sqlKeys } from 'data/sql/keys' - -// [Joshen] This is just temporary while we wait for Index Advisor to be baked into the image itself - -export type IndexAdvisorEnableVariables = { - projectRef: string - connectionString?: string -} - -export async function enableIndexAdvisor({ - projectRef, - connectionString, -}: IndexAdvisorEnableVariables) { - const sql = ` -create or replace function index_advisor( - query text -) - returns table ( - startup_cost_before jsonb, - startup_cost_after jsonb, - total_cost_before jsonb, - total_cost_after jsonb, - index_statements text[], - errors text[] - ) - volatile - language plpgsql - as $$ -declare - n_args int; - prepared_statement_name text = 'index_advisor_working_statement'; - hypopg_schema_name text = (select extnamespace::regnamespace::text from pg_extension where extname = 'hypopg'); - explain_plan_statement text; - error_message text; - rec record; - plan_initial jsonb; - plan_final jsonb; - statements text[] = '{}'; -begin - - -- Remove comment lines (its common that they contain semicolons) - query := trim( - regexp_replace( - regexp_replace( - regexp_replace(query,'\\/\\*.+\\*\\/', '', 'g'), - '--[^\\r\\n]*', ' ', 'g'), - '\\s+', ' ', 'g') - ); - - -- Remove trailing semicolon - query := regexp_replace(query, ';\\s*$', ''); - - begin - -- Disallow multiple statements - if query ilike '%;%' then - raise exception 'Query must not contain a semicolon'; - end if; - - -- Hack to support PostgREST because the prepared statement for args incorrectly defaults to text - query := replace(query, 'WITH pgrst_payload AS (SELECT $1 AS json_data)', 'WITH pgrst_payload AS (SELECT $1::json AS json_data)'); - - -- Create a prepared statement for the given query - deallocate all; - execute format('prepare %I as %s', prepared_statement_name, query); - - -- Detect how many arguments are present in the prepared statement - n_args = ( - select - coalesce(array_length(parameter_types, 1), 0) - from - pg_prepared_statements - where - name = prepared_statement_name - limit - 1 - ); - - -- Create a SQL statement that can be executed to collect the explain plan - explain_plan_statement = format( - 'set local plan_cache_mode = force_generic_plan; explain (format json) execute %I%s', - --'explain (format json) execute %I%s', - prepared_statement_name, - case - when n_args = 0 then '' - else format( - '(%s)', array_to_string(array_fill('null'::text, array[n_args]), ',') - ) - end - ); - - -- Store the query plan before any new indexes - execute explain_plan_statement into plan_initial; - - -- Create possible indexes - for rec in ( - with extension_regclass as ( - select - distinct objid as oid - from - pg_catalog.pg_depend - where - deptype = 'e' - ) - select - pc.relnamespace::regnamespace::text as schema_name, - pc.relname as table_name, - pa.attname as column_name, - format( - 'select %I.hypopg_create_index($i$create index on %I.%I(%I)$i$)', - hypopg_schema_name, - pc.relnamespace::regnamespace::text, - pc.relname, - pa.attname - ) hypopg_statement - from - pg_catalog.pg_class pc - join pg_catalog.pg_attribute pa - on pc.oid = pa.attrelid - left join extension_regclass er - on pc.oid = er.oid - left join pg_catalog.pg_index pi - on pc.oid = pi.indrelid - and (select array_agg(x) from unnest(pi.indkey) v(x)) = array[pa.attnum] - and pi.indexprs is null -- ignore expression indexes - and pi.indpred is null -- ignore partial indexes - where - pc.relnamespace::regnamespace::text not in ( -- ignore schema list - 'pg_catalog', 'pg_toast', 'information_schema' - ) - and er.oid is null -- ignore entities owned by extensions - and pc.relkind in ('r', 'm') -- regular tables, and materialized views - and pc.relpersistence = 'p' -- permanent tables (not unlogged or temporary) - and pa.attnum > 0 - and not pa.attisdropped - and pi.indrelid is null - and pa.atttypid in (20,16,1082,1184,1114,701,23,21,700,1083,2950,1700,25,18,1042,1043) - ) - loop - -- Create the hypothetical index - execute rec.hypopg_statement; - end loop; - - /* - for rec in select * from hypopg() - loop - raise notice '%', rec; - end loop; - */ - - -- Create a prepared statement for the given query - -- The original prepared statement MUST be dropped because its plan is cached - execute format('deallocate %I', prepared_statement_name); - execute format('prepare %I as %s', prepared_statement_name, query); - - -- Store the query plan after new indexes - execute explain_plan_statement into plan_final; - - --raise notice '%', plan_final; - - -- Idenfity referenced indexes in new plan - execute format( - 'select - coalesce(array_agg(hypopg_get_indexdef(indexrelid) order by indrelid, indkey::text), $i\${}$i$::text[]) - from - %I.hypopg() - where - %s ilike ($i$%%$i$ || indexname || $i$%%$i$) - ', - hypopg_schema_name, - quote_literal(plan_final)::text - ) into statements; - - -- Reset all hypothetical indexes - perform hypopg_reset(); - - -- Reset prepared statements - deallocate all; - - return query values ( - (plan_initial -> 0 -> 'Plan' -> 'Startup Cost'), - (plan_final -> 0 -> 'Plan' -> 'Startup Cost'), - (plan_initial -> 0 -> 'Plan' -> 'Total Cost'), - (plan_final -> 0 -> 'Plan' -> 'Total Cost'), - statements::text[], - array[]::text[] - ); - return; - - exception when others then - get stacked diagnostics error_message = MESSAGE_TEXT; - - return query values ( - null::jsonb, - null::jsonb, - null::jsonb, - null::jsonb, - array[]::text[], - array[error_message]::text[] - ); - return; - end; - -end; -$$; -` - const { result } = await executeSql({ - projectRef, - connectionString, - sql, - queryKey: ['enable-index-advisor'], - }) - - return result -} - -type IndexAdvisorEnableData = Awaited> - -export const useIndexAdvisorEnableMutation = ({ - onSuccess, - onError, - ...options -}: Omit< - UseMutationOptions, - 'mutationFn' -> = {}) => { - const queryClient = useQueryClient() - return useMutation( - (vars) => enableIndexAdvisor(vars), - { - async onSuccess(data, variables, context) { - const { projectRef } = variables - queryClient.invalidateQueries(sqlKeys.query(projectRef, ['index-advisor-function-check'])) - await onSuccess?.(data, variables, context) - }, - async onError(data, variables, context) { - if (onError === undefined) { - toast.error(`Failed to enable index advisor: ${data.message}`) - } else { - onError(data, variables, context) - } - }, - ...options, - } - ) -} diff --git a/apps/studio/data/database/keys.ts b/apps/studio/data/database/keys.ts index 605386f9616..f80f1063f18 100644 --- a/apps/studio/data/database/keys.ts +++ b/apps/studio/data/database/keys.ts @@ -1,5 +1,4 @@ export const databaseKeys = { - schemaList: (projectRef: string | undefined) => ['projects', projectRef, 'schemas'] as const, backups: (projectRef: string | undefined) => [projectRef, 'database', 'backups'] as const, poolingConfiguration: (projectRef: string | undefined) => [projectRef, 'database', 'pooling-configuration'] as const, diff --git a/apps/studio/data/database/schema-create-mutation.ts b/apps/studio/data/database/schema-create-mutation.ts index 7e69126af6a..81499a34b97 100644 --- a/apps/studio/data/database/schema-create-mutation.ts +++ b/apps/studio/data/database/schema-create-mutation.ts @@ -1,9 +1,10 @@ import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' +import pgMeta from '@supabase/pg-meta' import { toast } from 'react-hot-toast' -import { post } from 'data/fetchers' import type { ResponseError } from 'types' -import { databaseKeys } from './keys' +import { executeSql } from 'data/sql/execute-sql-query' +import { invalidateSchemasQuery } from './schemas-query' export type SchemaCreateVariables = { name: string @@ -12,22 +13,14 @@ export type SchemaCreateVariables = { } export async function createSchema({ name, projectRef, connectionString }: SchemaCreateVariables) { - if (!projectRef) throw new Error('projectRef is required') - - let headers = new Headers() - if (connectionString) headers.set('x-connection-encrypted', connectionString) - - const { data, error } = await post('/platform/pg-meta/{ref}/schemas', { - params: { - header: { 'x-connection-encrypted': connectionString! }, - path: { ref: projectRef }, - }, - body: { name, owner: 'postgres' }, - headers: Object.fromEntries(headers), + const sql = pgMeta.schemas.create({ name, owner: 'postgres' }).sql + const { result } = await executeSql({ + projectRef, + connectionString, + sql, + queryKey: ['schema', 'create'], }) - - if (error) throw error - return data + return result } type SchemaCreateData = Awaited> @@ -46,7 +39,7 @@ export const useSchemaCreateMutation = ({ { async onSuccess(data, variables, context) { const { projectRef } = variables - await queryClient.invalidateQueries(databaseKeys.schemaList(projectRef)) + await invalidateSchemasQuery(queryClient, projectRef) await onSuccess?.(data, variables, context) }, async onError(data, variables, context) { diff --git a/apps/studio/data/database/schemas-query.ts b/apps/studio/data/database/schemas-query.ts index b9bfaee5713..e93928dabc1 100644 --- a/apps/studio/data/database/schemas-query.ts +++ b/apps/studio/data/database/schemas-query.ts @@ -3,7 +3,7 @@ import { QueryClient, UseQueryOptions } from '@tanstack/react-query' import { z } from 'zod' import { ExecuteSqlData, useExecuteSqlQuery } from 'data/sql/execute-sql-query' -import { databaseKeys } from './keys' +import { sqlKeys } from 'data/sql/keys' export type SchemasVariables = { projectRef?: string @@ -37,5 +37,5 @@ export const useSchemasQuery = ( ) export function invalidateSchemasQuery(client: QueryClient, projectRef: string | undefined) { - return client.invalidateQueries(databaseKeys.schemaList(projectRef)) + return client.invalidateQueries(sqlKeys.query(projectRef, ['schemas', 'list'])) } diff --git a/apps/studio/data/lint/lint-query.ts b/apps/studio/data/lint/lint-query.ts index 69bb874caa6..2f8e54f6eaf 100644 --- a/apps/studio/data/lint/lint-query.ts +++ b/apps/studio/data/lint/lint-query.ts @@ -26,6 +26,7 @@ with foreign_keys as ( index_ as ( select indrelid::regclass as table_, + indrelid as table_oid, indexrelid::regclass as index_, string_to_array(indkey::text, ' ')::smallint[] as col_attnums from @@ -58,11 +59,15 @@ from left join index_ idx on fk.table_ = idx.table_ and fk.col_attnums = idx.col_attnums + left join pg_catalog.pg_depend dep + on idx.table_oid = dep.objid + and dep.deptype = 'e' where idx.index_ is null and fk.schema_::text not in ( - 'pg_catalog', 'information_schema', 'auth', 'extensions', 'graphql', 'graphql_public', 'net', 'pgsodium', 'storage', 'supabase_functions', 'vault' + '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault' ) + and dep.objid is null -- exclude tables owned by extensions order by fk.table_, fk.fkey_name) @@ -230,7 +235,7 @@ from where is_rls_active and schema_::text not in ( - 'pg_catalog', 'information_schema', 'auth', 'extensions', 'graphql', 'graphql_public', 'net', 'pgsodium', 'storage', 'supabase_functions', 'vault' + '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault' ) and ( ( @@ -276,11 +281,15 @@ from on pgns.oid = pgc.relnamespace left join pg_catalog.pg_index pgi on pgi.indrelid = pgc.oid + left join pg_catalog.pg_depend dep + on pgc.oid = dep.objid + and dep.deptype = 'e' where pgc.relkind = 'r' -- regular tables and pgns.nspname not in ( - 'pg_catalog', 'information_schema', 'auth', 'extensions', 'graphql', 'graphql_public', 'net', 'pgsodium', 'storage', 'supabase_functions', 'vault' + 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgsodium', 'pgsodium_masks', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'storage', 'supabase_functions', 'supabase_migrations', 'vault' ) + and dep.objid is null -- exclude tables owned by extensions group by pgc.oid, pgns.nspname, @@ -317,12 +326,16 @@ from pg_catalog.pg_stat_user_indexes psui join pg_catalog.pg_index pi on psui.indexrelid = pi.indexrelid + left join pg_catalog.pg_depend dep + on psui.relid = dep.objid + and dep.deptype = 'e' where psui.idx_scan = 0 and not pi.indisunique and not pi.indisprimary + and dep.objid is null -- exclude tables owned by extensions and psui.schemaname not in ( - 'pg_catalog', 'information_schema', 'auth', 'net', 'pgsodium', 'storage', 'supabase_functions', 'vault' + '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault' )) union all ( @@ -354,11 +367,16 @@ select ) as cache_key from pg_catalog.pg_policy p - join pg_catalog.pg_class c on p.polrelid = c.oid - join pg_catalog.pg_namespace n on c.relnamespace = n.oid + join pg_catalog.pg_class c + on p.polrelid = c.oid + join pg_catalog.pg_namespace n + on c.relnamespace = n.oid join pg_catalog.pg_roles r on p.polroles @> array[r.oid] - or p.polroles = array[0::oid], + or p.polroles = array[0::oid] + left join pg_catalog.pg_depend dep + on c.oid = dep.objid + and dep.deptype = 'e', lateral ( select x.cmd from unnest(( @@ -376,11 +394,12 @@ from where c.relkind = 'r' -- regular tables and n.nspname not in ( - 'pg_catalog', 'information_schema', 'auth', 'extensions', 'graphql', 'graphql_public', 'net', 'pgsodium', 'storage', 'supabase_functions', 'vault' + '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault' ) and r.rolname not like 'pg_%' and r.rolname not like 'supabase%admin' and not r.rolbypassrls + and dep.objid is null -- exclude tables owned by extensions group by n.nspname, c.relname, @@ -418,13 +437,17 @@ from on p.polrelid = c.oid join pg_catalog.pg_namespace n on c.relnamespace = n.oid + left join pg_catalog.pg_depend dep + on c.oid = dep.objid + and dep.deptype = 'e' where c.relkind = 'r' -- regular tables and n.nspname not in ( - 'pg_catalog', 'information_schema', 'auth', 'extensions', 'graphql', 'graphql_public', 'net', 'pgsodium', 'storage', 'supabase_functions', 'vault' + '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault' ) -- RLS is disabled and not c.relrowsecurity + and dep.objid is null -- exclude tables owned by extensions group by n.nspname, c.relname) @@ -457,14 +480,18 @@ from on p.polrelid = c.oid join pg_catalog.pg_namespace n on c.relnamespace = n.oid + left join pg_catalog.pg_depend dep + on c.oid = dep.objid + and dep.deptype = 'e' where c.relkind = 'r' -- regular tables and n.nspname not in ( - 'pg_catalog', 'information_schema', 'auth', 'extensions', 'graphql', 'graphql_public', 'net', 'pgsodium', 'storage', 'supabase_functions', 'vault' + '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault' ) -- RLS is enabled and c.relrowsecurity and p.polname is null + and dep.objid is null -- exclude tables owned by extensions group by n.nspname, c.relname) @@ -505,11 +532,15 @@ from join pg_catalog.pg_class c on pi.tablename = c.relname and n.oid = c.relnamespace + left join pg_catalog.pg_depend dep + on c.oid = dep.objid + and dep.deptype = 'e' where c.relkind in ('r', 'm') -- tables and materialized views and n.nspname not in ( - 'pg_catalog', 'information_schema', 'auth', 'extensions', 'graphql', 'graphql_public', 'net', 'pgsodium', 'storage', 'supabase_functions', 'vault' + '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault' ) + and dep.objid is null -- exclude tables owned by extensions group by n.nspname, c.relkind, @@ -544,9 +575,13 @@ from pg_catalog.pg_class c join pg_catalog.pg_namespace n on n.oid = c.relnamespace + left join pg_catalog.pg_depend dep + on c.oid = dep.objid + and dep.deptype = 'e' where c.relkind = 'v' and n.nspname = 'public' + and dep.objid is null -- exclude views owned by extensions and not ( lower(coalesce(c.reloptions::text,'{}'))::text[] && array[ @@ -584,10 +619,14 @@ from pg_catalog.pg_proc p join pg_catalog.pg_namespace n on p.pronamespace = n.oid + left join pg_catalog.pg_depend dep + on p.oid = dep.objid + and dep.deptype = 'e' where n.nspname not in ( - 'pg_catalog', 'information_schema', 'auth', 'extensions', 'graphql', 'graphql_public', 'net', 'pgsodium', 'storage', 'supabase_functions', 'vault' + '_timescaledb_internal', 'auth', 'cron', 'extensions', 'graphql', 'graphql_public', 'information_schema', 'net', 'pgroonga', 'pgsodium', 'pgsodium_masks', 'pgtle', 'pgbouncer', 'pg_catalog', 'pgtle', 'realtime', 'repack', 'storage', 'supabase_functions', 'supabase_migrations', 'tiger', 'topology', 'vault' ) + and dep.objid is null -- exclude functions owned by extensions -- Search path not set to '' and not coalesce(p.proconfig, '{}') && array['search_path=""']) union all @@ -621,7 +660,35 @@ where c.relkind = 'r' -- regular tables and n.nspname = 'public' -- RLS is disabled - and not c.relrowsecurity)`.trim() + and not c.relrowsecurity) +union all +( +select + 'extension_in_public' as name, + 'WARN' as level, + 'EXTERNAL' as facing, + 'Detects extensions installed in the \`public\` schema.' as description, + format( + 'Extension \`%s\` is installed in the public schema. Move it to another schema.', + pe.extname + ) as detail, + 'https://supabase.com/docs/guides/database/database-linter?lint=0014_extension_in_public' as remediation, + jsonb_build_object( + 'schema', pe.extnamespace::regnamespace, + 'name', pe.extname, + 'type', 'extension' + ) as metadata, + format( + 'extension_in_public_%s', + pe.extname + ) as cache_key +from + pg_catalog.pg_extension pe +where + -- plpgsql is installed by default in public and outside user control + -- confirmed safe + pe.extname not in ('plpgsql') + and pe.extnamespace::regnamespace::text = 'public')`.trim() // Array of all lint rules we handle right now. export const LINT_TYPES = [ @@ -637,6 +704,7 @@ export const LINT_TYPES = [ 'security_definer_view', 'function_search_path_mutable', 'rls_disabled_in_public', + 'extension_in_public', ] as const export type LINT_TYPES = (typeof LINT_TYPES)[number] diff --git a/apps/studio/data/sql/execute-sql-query.ts b/apps/studio/data/sql/execute-sql-query.ts index 5e8b4fbf8a1..b3aec849476 100644 --- a/apps/studio/data/sql/execute-sql-query.ts +++ b/apps/studio/data/sql/execute-sql-query.ts @@ -52,7 +52,7 @@ export async function executeSql( }, body: { query: sql }, headers: Object.fromEntries(headers), - }) + } as any) // Needed to fix generated api types for now if (error) { if ( diff --git a/apps/studio/data/storage/s3-access-key-create-mutation.ts b/apps/studio/data/storage/s3-access-key-create-mutation.ts new file mode 100644 index 00000000000..c97b186d714 --- /dev/null +++ b/apps/studio/data/storage/s3-access-key-create-mutation.ts @@ -0,0 +1,47 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query' +import { storageCredentialsKeys } from './s3-access-key-keys' +import { post } from 'data/fetchers' + +type CreateS3AccessKeyCredential = { + description: string + projectRef?: string +} +const createS3AccessKeyCredential = async ({ + description, + projectRef, +}: CreateS3AccessKeyCredential) => { + if (!projectRef) { + throw new Error('projectRef is required') + } + + const res = await post('/platform/storage/{ref}/credentials', { + params: { + path: { + ref: projectRef, + }, + }, + body: { + description, + }, + }) + + return res +} + +type S3AccessKeyCreateMutation = { + projectRef?: string +} + +export function useS3AccessKeyCreateMutation({ projectRef }: S3AccessKeyCreateMutation) { + const queryClient = useQueryClient() + + const keys = storageCredentialsKeys.credentials(projectRef) + + return useMutation({ + mutationFn: ({ description }: { description: string }) => + createS3AccessKeyCredential({ description, projectRef }), + onSettled: () => { + queryClient.invalidateQueries(keys) + }, + }) +} diff --git a/apps/studio/data/storage/s3-access-key-delete-mutation.ts b/apps/studio/data/storage/s3-access-key-delete-mutation.ts new file mode 100644 index 00000000000..06fef9a3717 --- /dev/null +++ b/apps/studio/data/storage/s3-access-key-delete-mutation.ts @@ -0,0 +1,38 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query' +import { storageCredentialsKeys } from './s3-access-key-keys' +import { del } from 'data/fetchers' + +type S3AccessKeyDeleteMutation = { + projectRef?: string + id?: string +} + +const deleteS3AccessKeyCredential = async ({ projectRef, id }: S3AccessKeyDeleteMutation) => { + if (!projectRef || !id) { + throw new Error('projectRef and id are required') + } + + const res = await del('/platform/storage/{ref}/credentials/{id}', { + params: { + path: { + ref: projectRef, + id, + }, + }, + }) + + return res +} + +export function useS3AccessKeyDeleteMutation({ projectRef }: S3AccessKeyDeleteMutation) { + const queryClient = useQueryClient() + + const keys = storageCredentialsKeys.credentials(projectRef) + + return useMutation({ + mutationFn: ({ id }: { id: string }) => deleteS3AccessKeyCredential({ projectRef, id }), + onSettled: () => { + queryClient.invalidateQueries(keys) + }, + }) +} diff --git a/apps/studio/data/storage/s3-access-key-keys.ts b/apps/studio/data/storage/s3-access-key-keys.ts new file mode 100644 index 00000000000..cdaf774b7c2 --- /dev/null +++ b/apps/studio/data/storage/s3-access-key-keys.ts @@ -0,0 +1,4 @@ +export const storageCredentialsKeys = { + credentials: (projectRef: string | undefined) => + ['projects', projectRef, 'storage-credentials'] as const, +} diff --git a/apps/studio/data/storage/s3-access-key-query.ts b/apps/studio/data/storage/s3-access-key-query.ts new file mode 100644 index 00000000000..c53c09f6271 --- /dev/null +++ b/apps/studio/data/storage/s3-access-key-query.ts @@ -0,0 +1,37 @@ +import { useQuery } from '@tanstack/react-query' +import { storageCredentialsKeys } from './s3-access-key-keys' +import { get } from 'data/fetchers' + +type FetchStorageCredentials = { + projectRef?: string +} +async function fetchStorageCredentials({ projectRef }: FetchStorageCredentials) { + if (!projectRef) { + throw new Error('projectRef is required') + } + + const res = await get('/platform/storage/{ref}/credentials', { + params: { + path: { + ref: projectRef, + }, + }, + }) + + return res.data +} + +type StorageCredentialsQuery = { + projectRef?: string +} +export function useStorageCredentialsQuery({ projectRef }: StorageCredentialsQuery) { + const keys = storageCredentialsKeys.credentials(projectRef) + + const query = useQuery({ + queryKey: keys, + queryFn: () => fetchStorageCredentials({ projectRef }), + enabled: projectRef !== undefined, + }) + + return query +} diff --git a/apps/studio/hooks/ui/useUrlState.ts b/apps/studio/hooks/ui/useUrlState.ts index ef8bf5c0657..d08217df167 100644 --- a/apps/studio/hooks/ui/useUrlState.ts +++ b/apps/studio/hooks/ui/useUrlState.ts @@ -1,11 +1,11 @@ -import { useCallback } from 'react' import { useRouter } from 'next/router' +import { Dispatch, SetStateAction, useCallback, useMemo } from 'react' export type UrlStateParams = { [k: string]: string | string[] | undefined } -export function useUrlState({ +export function useUrlState({ replace = true, arrayKeys = [], }: { @@ -14,22 +14,26 @@ export function useUrlState({ */ replace?: boolean arrayKeys?: string[] -} = {}) { - const arrayKeysSet = new Set(arrayKeys) +} = {}): [ValueParams, Dispatch>] { + const stringifiedArrayKeys = JSON.stringify(arrayKeys) + // eslint-disable-next-line react-hooks/exhaustive-deps + const arrayKeysSet = useMemo(() => new Set(arrayKeys), [stringifiedArrayKeys]) const router = useRouter() - const params: UrlStateParams = Object.fromEntries( - Object.entries(router.query).map(([key, value]) => { - if (arrayKeysSet.has(key)) { - return Array.isArray(value) ? [key, value] : [key, [value]] - } + const params: ValueParams = useMemo(() => { + return Object.fromEntries( + Object.entries(router.query).map(([key, value]) => { + if (arrayKeysSet.has(key)) { + return Array.isArray(value) ? [key, value] : [key, [value]] + } - return [key, value] - }) - ) + return [key, value] + }) + ) + }, [arrayKeysSet, router.query]) - const setParams = useCallback( - (newParams: UrlStateParams | ((previousParams: UrlStateParams) => UrlStateParams)) => { + const setParams: Dispatch> = useCallback( + (newParams) => { const nextParams = typeof newParams === 'function' ? newParams(params) : newParams let newQuery = Object.fromEntries( Object.entries({ ...params, ...nextParams }).filter(([, value]) => Boolean(value)) diff --git a/apps/studio/package.json b/apps/studio/package.json index 5d981b94221..8c5c8925f76 100644 --- a/apps/studio/package.json +++ b/apps/studio/package.json @@ -24,7 +24,7 @@ "@headlessui/react": "^1.7.17", "@heroicons/react": "^2.1.3", "@monaco-editor/react": "^4.6.0", - "@next/bundle-analyzer": "^14.1.4", + "@next/bundle-analyzer": "^14.2.0", "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-tooltip": "^1.0.7", "@scaleleap/pg-format": "^1.0.0", @@ -65,7 +65,7 @@ "mobx": "^6.10.2", "mobx-react-lite": "^4.0.5", "monaco-editor": "0.33.0", - "next": "^14.1.4", + "next": "^14.2.0", "next-themes": "^0.3.0", "openai": "^4.20.1", "openapi-fetch": "^0.6.2", @@ -141,7 +141,7 @@ "autoprefixer": "^10.4.14", "common": "*", "config": "*", - "eslint-config-next": "^14.1.4", + "eslint-config-next": "^14.2.0", "jest": "^29.7.0", "jest-canvas-mock": "^2.5.2", "jest-environment-jsdom": "^29.7.0", diff --git a/apps/studio/pages/account/me.tsx b/apps/studio/pages/account/me.tsx index d301d240215..8d1ddc8feb7 100644 --- a/apps/studio/pages/account/me.tsx +++ b/apps/studio/pages/account/me.tsx @@ -4,6 +4,7 @@ import { ThemeSettings, ThemeSettingsOld, } from 'components/interfaces/Account/Preferences' +import { AccountDeletion } from 'components/interfaces/Account/Preferences/AccountDeletion' import { ProfileInformation } from 'components/interfaces/Account/Preferences/ProfileInformation' import { AccountLayout } from 'components/layouts' import AlertError from 'components/ui/AlertError' @@ -74,6 +75,10 @@ const ProfileCard = () => {
+ +
+ +
) } diff --git a/apps/studio/pages/api/auth/[ref]/config.ts b/apps/studio/pages/api/auth/[ref]/config.ts index 26d07e972e6..a01dcca261b 100644 --- a/apps/studio/pages/api/auth/[ref]/config.ts +++ b/apps/studio/pages/api/auth/[ref]/config.ts @@ -1,5 +1,7 @@ import { NextApiRequest, NextApiResponse } from 'next' import apiWrapper from 'lib/api/apiWrapper' +import { get } from 'lib/common/fetch' +import { constructHeaders } from 'lib/api/apiHelpers' export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) @@ -18,6 +20,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { } const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { + const headers = constructHeaders({}) + const url = `${process.env.SUPABASE_URL}/auth/v1/settings` + const { external, disable_signup, mailer_autoconfirm, phone_autoconfirm } = await get(url, { + headers, + }) // Platform only API return res.status(200).json({ app_version: '', @@ -27,13 +34,13 @@ const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { isFreeTier: true, SITE_URL: '', OPERATOR_TOKEN: null, - DISABLE_SIGNUP: false, + DISABLE_SIGNUP: disable_signup ?? false, RATE_LIMIT_HEADER: null, JWT_EXP: 3600, JWT_AUD: '', JWT_DEFAULT_GROUP_NAME: '', URI_ALLOW_LIST: '', - MAILER_AUTOCONFIRM: false, + MAILER_AUTOCONFIRM: mailer_autoconfirm ?? false, MAILER_OTP_EXP: 86400, MAILER_URLPATHS_INVITE: '', MAILER_URLPATHS_CONFIRMATION: '', @@ -46,65 +53,65 @@ const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { SMTP_USER: null, SMTP_PASS: null, SMTP_MAX_FREQUENCY: 0, - EXTERNAL_ANONYMOUS_USERS_ENABLED: true, - EXTERNAL_EMAIL_ENABLED: true, - EXTERNAL_PHONE_ENABLED: true, - EXTERNAL_APPLE_ENABLED: false, + EXTERNAL_ANONYMOUS_USERS_ENABLED: external?.anonymous_users ?? true, + EXTERNAL_EMAIL_ENABLED: external?.email ?? true, + EXTERNAL_PHONE_ENABLED: external?.phone ?? true, + EXTERNAL_APPLE_ENABLED: external?.apple ?? false, EXTERNAL_APPLE_CLIENT_ID: null, EXTERNAL_APPLE_SECRET: null, - EXTERNAL_AZURE_ENABLED: false, + EXTERNAL_AZURE_ENABLED: external?.azure ?? false, EXTERNAL_AZURE_CLIENT_ID: null, EXTERNAL_AZURE_SECRET: null, EXTERNAL_AZURE_URL: null, - EXTERNAL_BITBUCKET_ENABLED: false, + EXTERNAL_BITBUCKET_ENABLED: external?.bitbucket ?? false, EXTERNAL_BITBUCKET_CLIENT_ID: null, EXTERNAL_BITBUCKET_SECRET: null, - EXTERNAL_DISCORD_ENABLED: false, + EXTERNAL_DISCORD_ENABLED: external?.discord ?? false, EXTERNAL_DISCORD_CLIENT_ID: null, EXTERNAL_DISCORD_SECRET: null, - EXTERNAL_FACEBOOK_ENABLED: false, + EXTERNAL_FACEBOOK_ENABLED: external?.facebook ?? false, EXTERNAL_FACEBOOK_CLIENT_ID: null, EXTERNAL_FACEBOOK_SECRET: null, - EXTERNAL_FIGMA_ENABLED: false, + EXTERNAL_FIGMA_ENABLED: external?.figma ?? false, EXTERNAL_FIGMA_CLIENT_ID: null, EXTERNAL_FIGMA_SECRET: null, - EXTERNAL_GITHUB_ENABLED: false, + EXTERNAL_GITHUB_ENABLED: external?.github ?? false, EXTERNAL_GITHUB_CLIENT_ID: null, EXTERNAL_GITHUB_SECRET: null, - EXTERNAL_GITLAB_ENABLED: false, + EXTERNAL_GITLAB_ENABLED: external?.gitlab ?? false, EXTERNAL_GITLAB_CLIENT_ID: null, EXTERNAL_GITLAB_SECRET: null, EXTERNAL_GITLAB_REDIRECT_URI: null, - EXTERNAL_GOOGLE_ENABLED: false, + EXTERNAL_GOOGLE_ENABLED: external?.google ?? false, EXTERNAL_GOOGLE_CLIENT_ID: null, EXTERNAL_GOOGLE_SECRET: null, - EXTERNAL_KAKAO_ENABLED: false, + EXTERNAL_KAKAO_ENABLED: external?.kakao ?? false, EXTERNAL_KAKAO_CLIENT_ID: null, EXTERNAL_KAKAO_SECRET: null, - EXTERNAL_KEYCLOAK_ENABLED: false, + EXTERNAL_KEYCLOAK_ENABLED: external?.keycloak ?? false, EXTERNAL_KEYCLOAK_CLIENT_ID: null, EXTERNAL_KEYCLOAK_SECRET: null, EXTERNAL_KEYCLOAK_URL: null, - EXTERNAL_NOTION_ENABLED: false, + EXTERNAL_NOTION_ENABLED: external?.notion ?? false, EXTERNAL_NOTION_CLIENT_ID: null, EXTERNAL_NOTION_SECRET: null, - EXTERNAL_SPOTIFY_ENABLED: false, + EXTERNAL_SPOTIFY_ENABLED: external?.spotify ?? false, EXTERNAL_SPOTIFY_CLIENT_ID: null, EXTERNAL_SPOTIFY_SECRET: null, - EXTERNAL_SLACK_ENABLED: false, + EXTERNAL_SLACK_ENABLED: external?.slack ?? false, EXTERNAL_SLACK_CLIENT_ID: null, EXTERNAL_SLACK_SECRET: null, - EXTERNAL_TWITTER_ENABLED: false, + EXTERNAL_TWITTER_ENABLED: external?.twitter ?? false, EXTERNAL_TWITTER_CLIENT_ID: null, EXTERNAL_TWITTER_SECRET: null, - EXTERNAL_TWITCH_ENABLED: false, + EXTERNAL_TWITCH_ENABLED: external?.twitch ?? false, EXTERNAL_TWITCH_CLIENT_ID: null, EXTERNAL_TWITCH_SECRET: null, - EXTERNAL_WORKOS_ENABLED: false, + EXTERNAL_WORKOS_ENABLED: external?.workos ?? false, EXTERNAL_WORKOS_CLIENT_ID: null, EXTERNAL_WORKOS_SECRET: null, EXTERNAL_WORKOS_URL: null, - EXTERNAL_ZOOM_ENABLED: false, + EXTERNAL_ZOOM_ENABLED: external?.zoom ?? false, EXTERNAL_ZOOM_CLIENT_ID: null, EXTERNAL_ZOOM_SECRET: null, MAILER_SUBJECTS_INVITE: 'You have been invited', @@ -129,7 +136,7 @@ const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { '

Magic Link

\n\n

Follow this link to login:

\n

Log In

', PASSWORD_MIN_LENGTH: 6, SMTP_SENDER_NAME: null, - SMS_AUTOCONFIRM: false, + SMS_AUTOCONFIRM: phone_autoconfirm ?? false, SMS_MAX_FREQUENCY: 0, SMS_OTP_EXP: 60, SMS_OTP_LENGTH: 6, diff --git a/apps/studio/pages/project/[ref]/auth/column-privileges.tsx b/apps/studio/pages/project/[ref]/auth/column-privileges.tsx index b3aa68fb561..40d8c3d0d7b 100644 --- a/apps/studio/pages/project/[ref]/auth/column-privileges.tsx +++ b/apps/studio/pages/project/[ref]/auth/column-privileges.tsx @@ -28,7 +28,7 @@ import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectConte import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' import AlertError from 'components/ui/AlertError' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' -import { useDatabaseRolesQuery } from 'data/database-roles/database-roles-query' +import { PgRole, useDatabaseRolesQuery } from 'data/database-roles/database-roles-query' import { useColumnPrivilegesQuery } from 'data/privileges/column-privileges-query' import { useTablePrivilegesQuery } from 'data/privileges/table-privileges-query' import { useTablesQuery } from 'data/tables/tables-query' @@ -133,9 +133,8 @@ const PrivilegesPage: NextPageWithLayout = () => { [allColumnPrivileges, selectedRole, selectedSchema, selectedTable] ) - const rolesList = - allRoles?.filter((role: PostgresRole) => EDITABLE_ROLES.includes(role.name)) ?? [] - const roles = rolesList.map((role: PostgresRole) => role.name) + const rolesList = allRoles?.filter((role: PgRole) => EDITABLE_ROLES.includes(role.name)) ?? [] + const roles = rolesList.map((role: PgRole) => role.name) const table = tableList?.find( (table) => table.schema === selectedSchema && table.name === selectedTable diff --git a/apps/studio/pages/project/[ref]/auth/policies.tsx b/apps/studio/pages/project/[ref]/auth/policies.tsx index 46b944bdb1a..48b0060ca3f 100644 --- a/apps/studio/pages/project/[ref]/auth/policies.tsx +++ b/apps/studio/pages/project/[ref]/auth/policies.tsx @@ -1,10 +1,7 @@ import type { PostgresPolicy, PostgresTable } from '@supabase/postgres-meta' import { PermissionAction } from '@supabase/shared-types/out/constants' -import { partition } from 'lodash' -import { useEffect, useState } from 'react' +import { useState } from 'react' -import { useParams } from 'common' -import { useIsRLSAIAssistantEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext' import { Policies } from 'components/interfaces/Auth/Policies' import { AIPolicyEditorPanel } from 'components/interfaces/Auth/Policies/AIPolicyEditorPanel' import { AuthLayout } from 'components/layouts' @@ -16,9 +13,9 @@ import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useDatabasePoliciesQuery } from 'data/database-policies/database-policies-query' import { useSchemasQuery } from 'data/database/schemas-query' import { useTablesQuery } from 'data/tables/tables-query' -import { useCheckPermissions, usePermissionsLoaded } from 'hooks' +import { useCheckPermissions, usePermissionsLoaded, useUrlState } from 'hooks' import { EXCLUDED_SCHEMAS } from 'lib/constants/schemas' -import { useTableEditorStateSnapshot } from 'state/table-editor' +import { partition } from 'lodash' import type { NextPageWithLayout } from 'types' import { Button, @@ -66,22 +63,16 @@ const onFilterTables = ( } const AuthPoliciesPage: NextPageWithLayout = () => { - const { search, schema } = useParams() + const [params, setParams] = useUrlState<{ + schema?: string + search?: string + }>() + const { schema = 'public', search: searchString = '' } = params const { project } = useProjectContext() - const snap = useTableEditorStateSnapshot() - const [searchString, setSearchString] = useState('') const [showPolicyAiEditor, setShowPolicyAiEditor] = useState(false) const [selectedPolicyToEdit, setSelectedPolicyToEdit] = useState() - useEffect(() => { - if (search) setSearchString(search) - }, [search]) - - useEffect(() => { - if (schema) snap.setSelectedSchemaName(schema) - }, [schema]) - const { data: schemas } = useSchemasQuery({ projectRef: project?.ref, connectionString: project?.connectionString, @@ -89,7 +80,7 @@ const AuthPoliciesPage: NextPageWithLayout = () => { const [protectedSchemas] = partition(schemas, (schema) => EXCLUDED_SCHEMAS.includes(schema?.name ?? '') ) - const selectedSchema = schemas?.find((schema) => schema.name === snap.selectedSchemaName) + const selectedSchema = schemas?.find((s) => s.name === schema) const isLocked = protectedSchemas.some((s) => s.id === selectedSchema?.id) const { data: policies } = useDatabasePoliciesQuery({ @@ -106,7 +97,7 @@ const AuthPoliciesPage: NextPageWithLayout = () => { } = useTablesQuery({ projectRef: project?.ref, connectionString: project?.connectionString, - schema: snap.selectedSchemaName, + schema: schema, }) const filteredTables = onFilterTables(tables ?? [], policies ?? [], searchString) @@ -128,18 +119,20 @@ const AuthPoliciesPage: NextPageWithLayout = () => { className="w-[260px]" size="small" showError={false} - selectedSchemaName={snap.selectedSchemaName} - onSelectSchema={(schema: string) => { - snap.setSelectedSchemaName(schema) - setSearchString('') + selectedSchemaName={schema} + onSelectSchema={(schema) => { + setParams({ ...params, search: undefined, schema }) }} /> setSearchString(e.target.value)} + value={searchString || ''} + onChange={(e) => { + const str = e.target.value + setParams({ ...params, search: str === '' ? undefined : str }) + }} icon={} />
@@ -169,7 +162,7 @@ const AuthPoliciesPage: NextPageWithLayout = () => { {!canCreatePolicies ? 'You need additional permissions to create RLS policies' : schemaHasNoTables - ? `No table in schema ${snap.selectedSchemaName} to create policies on` + ? `No table in schema ${schema} to create policies on` : null} )} @@ -184,6 +177,7 @@ const AuthPoliciesPage: NextPageWithLayout = () => { {isSuccess && ( 0} isLocked={isLocked} @@ -196,6 +190,7 @@ const AuthPoliciesPage: NextPageWithLayout = () => { { diff --git a/apps/studio/pages/project/[ref]/logs/collections/access-tokens.tsx b/apps/studio/pages/project/[ref]/logs/collections/access-tokens.tsx new file mode 100644 index 00000000000..b9206e177e6 --- /dev/null +++ b/apps/studio/pages/project/[ref]/logs/collections/access-tokens.tsx @@ -0,0 +1,10 @@ +import WarehouseAccessTokens from 'components/interfaces/DataWarehouse/WarehouseAccessTokens' +import { LogsLayout } from 'components/layouts' +import { observer } from 'mobx-react-lite' +import { NextPageWithLayout } from 'types' + +const PageLayout: NextPageWithLayout = () => + +PageLayout.getLayout = (page) => {page} + +export default observer(PageLayout) diff --git a/apps/studio/pages/project/[ref]/settings/storage.tsx b/apps/studio/pages/project/[ref]/settings/storage.tsx index ecbcb6b4fdc..8895ab41832 100644 --- a/apps/studio/pages/project/[ref]/settings/storage.tsx +++ b/apps/studio/pages/project/[ref]/settings/storage.tsx @@ -1,11 +1,16 @@ import { SettingsLayout } from 'components/layouts' import { StorageSettings } from 'components/to-be-cleaned/Storage' +import { S3Connection } from 'components/to-be-cleaned/Storage/StorageSettings/S3Connection' +import { useFlag } from 'hooks' import type { NextPageWithLayout } from 'types' const PageLayout: NextPageWithLayout = () => { + const showS3Connection = useFlag('showS3Connection') + return ( -
+
+ {showS3Connection && }
) } diff --git a/apps/studio/public/img/icons/auth0-icon.svg b/apps/studio/public/img/icons/auth0-icon.svg new file mode 100644 index 00000000000..5b6732f68f8 --- /dev/null +++ b/apps/studio/public/img/icons/auth0-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/studio/public/img/icons/cognito-icon.svg b/apps/studio/public/img/icons/cognito-icon.svg new file mode 100644 index 00000000000..3e6af00742e --- /dev/null +++ b/apps/studio/public/img/icons/cognito-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/studio/public/img/icons/mssql-icon.svg b/apps/studio/public/img/icons/mssql-icon.svg new file mode 100644 index 00000000000..481af02ed9a --- /dev/null +++ b/apps/studio/public/img/icons/mssql-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/studio/public/img/icons/redis-icon.svg b/apps/studio/public/img/icons/redis-icon.svg new file mode 100644 index 00000000000..c2f62d8766f --- /dev/null +++ b/apps/studio/public/img/icons/redis-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/studio/styles/code.scss b/apps/studio/styles/code.scss index 3009884e0a3..6bc7f701e15 100644 --- a/apps/studio/styles/code.scss +++ b/apps/studio/styles/code.scss @@ -90,7 +90,10 @@ opacity: 0; position: absolute; visibility: hidden; - transition: opacity 200ms ease-in-out, visibility 200ms ease-in-out, bottom 200ms ease-in-out; + transition: + opacity 200ms ease-in-out, + visibility 200ms ease-in-out, + bottom 200ms ease-in-out; button { @apply rounded text-xs; diff --git a/apps/studio/styles/editor.scss b/apps/studio/styles/editor.scss index 4a76731d766..9c9236d76b3 100644 --- a/apps/studio/styles/editor.scss +++ b/apps/studio/styles/editor.scss @@ -27,4 +27,4 @@ input { @apply pr-10; } -} \ No newline at end of file +} diff --git a/apps/studio/styles/graphiql-base.scss b/apps/studio/styles/graphiql-base.scss index 1ef8ebdf1d9..eebcac28006 100644 --- a/apps/studio/styles/graphiql-base.scss +++ b/apps/studio/styles/graphiql-base.scss @@ -132,8 +132,7 @@ button.graphiql-tab-add > svg { /* The query editor and the toolbar */ .graphiql-container .graphiql-query-editor { - border-bottom: 1px solid - hsla(var(--color-neutral), var(--alpha-background-heavy)); + border-bottom: 1px solid hsla(var(--color-neutral), var(--alpha-background-heavy)); padding: var(--px-16); column-gap: var(--px-16); display: flex; @@ -175,9 +174,7 @@ button.graphiql-tab-add > svg { } /* The tab buttons to switch between editor tools */ -.graphiql-container - .graphiql-editor-tools - > button:not(.graphiql-toggle-editor-tools) { +.graphiql-container .graphiql-editor-tools > button:not(.graphiql-toggle-editor-tools) { padding: var(--px-8) var(--px-12); } @@ -219,14 +216,12 @@ button.graphiql-tab-add > svg { /* The footer below the response view */ .graphiql-container .graphiql-footer { - border-top: 1px solid - hsla(var(--color-neutral), var(--alpha-background-heavy)); + border-top: 1px solid hsla(var(--color-neutral), var(--alpha-background-heavy)); } /* The plugin container */ .graphiql-container .graphiql-plugin { - border-left: 1px solid - hsla(var(--color-neutral), var(--alpha-background-heavy)); + border-left: 1px solid hsla(var(--color-neutral), var(--alpha-background-heavy)); flex: 1; overflow-y: auto; padding: var(--px-16); @@ -239,8 +234,7 @@ button.graphiql-tab-add > svg { } .graphiql-horizontal-drag-bar:hover::after { - border: var(--px-2) solid - hsla(var(--color-neutral), var(--alpha-background-heavy)); + border: var(--px-2) solid hsla(var(--color-neutral), var(--alpha-background-heavy)); border-radius: var(--border-radius-2); content: ''; display: block; @@ -292,8 +286,7 @@ button.graphiql-tab-add > svg { /* A section inside the settings dialog */ .graphiql-dialog .graphiql-dialog-section { align-items: center; - border-top: 1px solid - hsla(var(--color-neutral), var(--alpha-background-heavy)); + border-top: 1px solid hsla(var(--color-neutral), var(--alpha-background-heavy)); display: flex; justify-content: space-between; padding: var(--px-24); diff --git a/apps/studio/styles/markdown-preview.scss b/apps/studio/styles/markdown-preview.scss index 742983e6b76..07e97c62972 100644 --- a/apps/studio/styles/markdown-preview.scss +++ b/apps/studio/styles/markdown-preview.scss @@ -2,7 +2,7 @@ @media (prefers-color-scheme: dark) { .markdown-body, - [data-theme="dark"] { + [data-theme='dark'] { /*dark*/ color-scheme: dark; --color-prettylights-syntax-comment: #8b949e; @@ -42,14 +42,14 @@ --color-canvas-subtle: #161b22; --color-border-default: #30363d; --color-border-muted: #21262d; - --color-neutral-muted: rgba(110,118,129,0.4); + --color-neutral-muted: rgba(110, 118, 129, 0.4); --color-accent-fg: #2f81f7; --color-accent-emphasis: #1f6feb; --color-success-fg: #3fb950; --color-success-emphasis: #238636; --color-attention-fg: #d29922; --color-attention-emphasis: #9e6a03; - --color-attention-subtle: rgba(187,128,9,0.15); + --color-attention-subtle: rgba(187, 128, 9, 0.15); --color-danger-fg: #f85149; --color-danger-emphasis: #da3633; --color-done-fg: #a371f7; @@ -59,7 +59,7 @@ @media (prefers-color-scheme: light) { .markdown-body, - [data-theme="light"] { + [data-theme='light'] { /*light*/ color-scheme: light; --color-prettylights-syntax-comment: #57606a; @@ -92,14 +92,14 @@ --color-prettylights-syntax-brackethighlighter-angle: #57606a; --color-prettylights-syntax-sublimelinter-gutter-mark: #8c959f; --color-prettylights-syntax-constant-other-reference-link: #0a3069; - --color-fg-default: #1F2328; + --color-fg-default: #1f2328; --color-fg-muted: #656d76; --color-fg-subtle: #6e7781; --color-canvas-default: #ffffff; --color-canvas-subtle: #f6f8fa; --color-border-default: #d0d7de; - --color-border-muted: hsla(210,18%,87%,1); - --color-neutral-muted: rgba(175,184,193,0.2); + --color-border-muted: hsla(210, 18%, 87%, 1); + --color-neutral-muted: rgba(175, 184, 193, 0.2); --color-accent-fg: #0969da; --color-accent-emphasis: #0969da; --color-success-fg: #1a7f37; @@ -118,7 +118,8 @@ -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; margin: 0; - font-family: -apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, + sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'; font-size: 16px; line-height: 1.5; word-wrap: break-word; @@ -181,9 +182,9 @@ } .markdown-body h1 { - margin: .67em 0; + margin: 0.67em 0; font-weight: var(--base-text-weight-semibold, 600); - padding-bottom: .3em; + padding-bottom: 0.3em; font-size: 2em; @apply border-b; } @@ -237,7 +238,7 @@ overflow: hidden; background: transparent; @apply border-b; - height: .25em; + height: 0.25em; padding: 0; margin: 24px 0; background-color: var(--color-border-default); @@ -253,33 +254,33 @@ line-height: inherit; } -.markdown-body [type=button], -.markdown-body [type=reset], -.markdown-body [type=submit] { +.markdown-body [type='button'], +.markdown-body [type='reset'], +.markdown-body [type='submit'] { -webkit-appearance: button; appearance: button; } -.markdown-body [type=checkbox], -.markdown-body [type=radio] { +.markdown-body [type='checkbox'], +.markdown-body [type='radio'] { box-sizing: border-box; padding: 0; } -.markdown-body [type=number]::-webkit-inner-spin-button, -.markdown-body [type=number]::-webkit-outer-spin-button { +.markdown-body [type='number']::-webkit-inner-spin-button, +.markdown-body [type='number']::-webkit-outer-spin-button { height: auto; } -.markdown-body [type=search]::-webkit-search-cancel-button, -.markdown-body [type=search]::-webkit-search-decoration { +.markdown-body [type='search']::-webkit-search-cancel-button, +.markdown-body [type='search']::-webkit-search-decoration { -webkit-appearance: none; appearance: none; } .markdown-body ::-webkit-input-placeholder { color: inherit; - opacity: .54; + opacity: 0.54; } .markdown-body ::-webkit-file-upload-button { @@ -299,13 +300,13 @@ .markdown-body hr::before { display: table; - content: ""; + content: ''; } .markdown-body hr::after { display: table; clear: both; - content: ""; + content: ''; } .markdown-body table { @@ -326,30 +327,30 @@ cursor: pointer; } -.markdown-body details:not([open])>*:not(summary) { +.markdown-body details:not([open]) > *:not(summary) { display: none !important; } .markdown-body a:focus, -.markdown-body [role=button]:focus, -.markdown-body input[type=radio]:focus, -.markdown-body input[type=checkbox]:focus { +.markdown-body [role='button']:focus, +.markdown-body input[type='radio']:focus, +.markdown-body input[type='checkbox']:focus { outline: 2px solid var(--color-accent-fg); outline-offset: -2px; box-shadow: none; } .markdown-body a:focus:not(:focus-visible), -.markdown-body [role=button]:focus:not(:focus-visible), -.markdown-body input[type=radio]:focus:not(:focus-visible), -.markdown-body input[type=checkbox]:focus:not(:focus-visible) { +.markdown-body [role='button']:focus:not(:focus-visible), +.markdown-body input[type='radio']:focus:not(:focus-visible), +.markdown-body input[type='checkbox']:focus:not(:focus-visible) { outline: solid 1px transparent; } .markdown-body a:focus-visible, -.markdown-body [role=button]:focus-visible, -.markdown-body input[type=radio]:focus-visible, -.markdown-body input[type=checkbox]:focus-visible { +.markdown-body [role='button']:focus-visible, +.markdown-body input[type='radio']:focus-visible, +.markdown-body input[type='checkbox']:focus-visible { outline: 2px solid var(--color-accent-fg); outline-offset: -2px; box-shadow: none; @@ -357,17 +358,24 @@ .markdown-body a:not([class]):focus, .markdown-body a:not([class]):focus-visible, -.markdown-body input[type=radio]:focus, -.markdown-body input[type=radio]:focus-visible, -.markdown-body input[type=checkbox]:focus, -.markdown-body input[type=checkbox]:focus-visible { +.markdown-body input[type='radio']:focus, +.markdown-body input[type='radio']:focus-visible, +.markdown-body input[type='checkbox']:focus, +.markdown-body input[type='checkbox']:focus-visible { outline-offset: 0; } .markdown-body kbd { display: inline-block; padding: 3px 5px; - font: 11px ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; + font: + 11px ui-monospace, + SFMono-Regular, + SF Mono, + Menlo, + Consolas, + Liberation Mono, + monospace; line-height: 10px; color: var(--color-fg-default); vertical-align: middle; @@ -392,7 +400,7 @@ .markdown-body h2 { font-weight: var(--base-text-weight-semibold, 600); - padding-bottom: .3em; + padding-bottom: 0.3em; font-size: 1.5em; @apply border-b; } @@ -409,12 +417,12 @@ .markdown-body h5 { font-weight: var(--base-text-weight-semibold, 600); - font-size: .875em; + font-size: 0.875em; } .markdown-body h6 { font-weight: var(--base-text-weight-semibold, 600); - font-size: .85em; + font-size: 0.85em; color: var(--color-fg-muted); } @@ -427,7 +435,7 @@ margin: 0; padding: 0 1em; color: var(--color-fg-muted); - border-left: .25em solid var(--color-border-default); + border-left: 0.25em solid var(--color-border-default); } .markdown-body ul, @@ -456,14 +464,28 @@ .markdown-body tt, .markdown-body code, .markdown-body samp { - font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; + font-family: + ui-monospace, + SFMono-Regular, + SF Mono, + Menlo, + Consolas, + Liberation Mono, + monospace; font-size: 12px; } .markdown-body pre { margin-top: 0; margin-bottom: 0; - font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; + font-family: + ui-monospace, + SFMono-Regular, + SF Mono, + Menlo, + Consolas, + Liberation Mono, + monospace; font-size: 12px; word-wrap: normal; } @@ -488,20 +510,20 @@ .markdown-body::before { display: table; - content: ""; + content: ''; } .markdown-body::after { display: table; clear: both; - content: ""; + content: ''; } -.markdown-body>*:first-child { +.markdown-body > *:first-child { margin-top: 0 !important; } -.markdown-body>*:last-child { +.markdown-body > *:last-child { margin-bottom: 0 !important; } @@ -537,11 +559,11 @@ margin-bottom: 16px; } -.markdown-body blockquote>:first-child { +.markdown-body blockquote > :first-child { margin-top: 0; } -.markdown-body blockquote>:last-child { +.markdown-body blockquote > :last-child { margin-bottom: 0; } @@ -586,7 +608,7 @@ .markdown-body h5 code, .markdown-body h6 tt, .markdown-body h6 code { - padding: 0 .2em; + padding: 0 0.2em; font-size: inherit; } @@ -620,27 +642,27 @@ list-style-type: none; } -.markdown-body ol[type="a s"] { +.markdown-body ol[type='a s'] { list-style-type: lower-alpha; } -.markdown-body ol[type="A s"] { +.markdown-body ol[type='A s'] { list-style-type: upper-alpha; } -.markdown-body ol[type="i s"] { +.markdown-body ol[type='i s'] { list-style-type: lower-roman; } -.markdown-body ol[type="I s"] { +.markdown-body ol[type='I s'] { list-style-type: upper-roman; } -.markdown-body ol[type="1"] { +.markdown-body ol[type='1'] { list-style-type: decimal; } -.markdown-body div>ol:not([type]) { +.markdown-body div > ol:not([type]) { list-style-type: decimal; } @@ -652,12 +674,12 @@ margin-bottom: 0; } -.markdown-body li>p { +.markdown-body li > p { margin-top: 16px; } -.markdown-body li+li { - margin-top: .25em; +.markdown-body li + li { + margin-top: 0.25em; } .markdown-body dl { @@ -687,7 +709,7 @@ border: 1px solid var(--color-border-default); } -.markdown-body table td>:last-child { +.markdown-body table td > :last-child { margin-bottom: 0; } @@ -704,11 +726,11 @@ background-color: transparent; } -.markdown-body img[align=right] { +.markdown-body img[align='right'] { padding-left: 20px; } -.markdown-body img[align=left] { +.markdown-body img[align='left'] { padding-right: 20px; } @@ -723,7 +745,7 @@ overflow: hidden; } -.markdown-body span.frame>span { +.markdown-body span.frame > span { display: block; float: left; width: auto; @@ -751,7 +773,7 @@ clear: both; } -.markdown-body span.align-center>span { +.markdown-body span.align-center > span { display: block; margin: 13px auto 0; overflow: hidden; @@ -769,7 +791,7 @@ clear: both; } -.markdown-body span.align-right>span { +.markdown-body span.align-right > span { display: block; margin: 13px 0 0; overflow: hidden; @@ -799,7 +821,7 @@ overflow: hidden; } -.markdown-body span.float-right>span { +.markdown-body span.float-right > span { display: block; margin: 13px auto 0; overflow: hidden; @@ -808,7 +830,7 @@ .markdown-body code, .markdown-body tt { - padding: .2em .4em; + padding: 0.2em 0.4em; margin: 0; font-size: 85%; white-space: break-spaces; @@ -833,7 +855,7 @@ font-size: 100%; } -.markdown-body pre>code { +.markdown-body pre > code { padding: 0; margin: 0; word-break: normal; @@ -903,11 +925,11 @@ } .markdown-body [data-footnote-ref]::before { - content: "["; + content: '['; } .markdown-body [data-footnote-ref]::after { - content: "]"; + content: ']'; } .markdown-body .footnotes { @@ -937,7 +959,7 @@ bottom: -8px; left: -24px; pointer-events: none; - content: ""; + content: ''; border: 2px solid var(--color-accent-emphasis); border-radius: 6px; } @@ -1073,7 +1095,7 @@ .markdown-body g-emoji { display: inline-block; min-width: 1ch; - font-family: "Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"; + font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 1em; font-style: normal !important; font-weight: var(--base-text-weight-normal, 400); @@ -1098,7 +1120,7 @@ cursor: pointer; } -.markdown-body .task-list-item+.task-list-item { +.markdown-body .task-list-item + .task-list-item { margin-top: 4px; } @@ -1107,12 +1129,12 @@ } .markdown-body .task-list-item-checkbox { - margin: 0 .2em .25em -1.4em; + margin: 0 0.2em 0.25em -1.4em; vertical-align: middle; } .markdown-body .contains-task-list:dir(rtl) .task-list-item-checkbox { - margin: 0 -1.6em .25em .2em; + margin: 0 -1.6em 0.25em 0.2em; } .markdown-body .contains-task-list { @@ -1136,14 +1158,14 @@ padding: var(--base-size-8) var(--base-size-16); margin-bottom: 16px; color: inherit; - border-left: .25em solid var(--color-border-default); + border-left: 0.25em solid var(--color-border-default); } -.markdown-body .markdown-alert>:first-child { +.markdown-body .markdown-alert > :first-child { margin-top: 0; } -.markdown-body .markdown-alert>:last-child { +.markdown-body .markdown-alert > :last-child { margin-bottom: 0; } @@ -1192,4 +1214,4 @@ .markdown-body .markdown-alert.markdown-alert-caution .markdown-alert-title { color: var(--color-danger-fg); -} \ No newline at end of file +} diff --git a/apps/studio/styles/reactflow.scss b/apps/studio/styles/reactflow.scss index 083b194f818..3907847eae9 100644 --- a/apps/studio/styles/reactflow.scss +++ b/apps/studio/styles/reactflow.scss @@ -15,4 +15,4 @@ to { stroke-dashoffset: 100; } -} \ No newline at end of file +} diff --git a/apps/studio/styles/toast.scss b/apps/studio/styles/toast.scss index 03e7719f9a7..4463dff48b1 100644 --- a/apps/studio/styles/toast.scss +++ b/apps/studio/styles/toast.scss @@ -64,7 +64,9 @@ margin-bottom: 1rem; padding: 8px; border-radius: 1px; - box-shadow: 0 1px 10px 0 rgba(0, 0, 0, 0.1), 0 2px 15px 0 rgba(0, 0, 0, 0.05); + box-shadow: + 0 1px 10px 0 rgba(0, 0, 0, 0.1), + 0 2px 15px 0 rgba(0, 0, 0, 0.05); display: -ms-flexbox; display: flex; -ms-flex-pack: justify; @@ -559,4 +561,4 @@ } } -/*# sourceMappingURL=ReactToastify.css.map */ \ No newline at end of file +/*# sourceMappingURL=ReactToastify.css.map */ diff --git a/apps/www/_blog/2023-04-14-launch-week-7-community-highlights.mdx b/apps/www/_blog/2023-04-14-launch-week-7-community-highlights.mdx index 5bb4d6cacb3..0fbd8124bba 100644 --- a/apps/www/_blog/2023-04-14-launch-week-7-community-highlights.mdx +++ b/apps/www/_blog/2023-04-14-launch-week-7-community-highlights.mdx @@ -148,7 +148,7 @@ It’s becoming easier to build applications with any language. In the past few months, the community has rallied and shipped 3 new client libraries, complete with Docs: - Swift: - - [Documentation](/docs/reference/swift/introduction) | [Source Code](https://github.com/supabase-community/supabase-swift) + - [Documentation](/docs/reference/swift/introduction) | [Source Code](https://github.com/supabase/supabase-swift) - Shout-out to: [grsouza](https://github.com/grsouza) & [@maail](https://github.com/maail) - Python: - [Documentation](/docs/reference/python/initializing) | [Source Code](https://github.com/supabase-community/supabase-py) diff --git a/apps/www/_blog/2024-04-12-supabase-oss-hackathon.mdx b/apps/www/_blog/2024-04-12-supabase-oss-hackathon.mdx new file mode 100644 index 00000000000..a3f4254c1c9 --- /dev/null +++ b/apps/www/_blog/2024-04-12-supabase-oss-hackathon.mdx @@ -0,0 +1,103 @@ +--- +title: 'Supabase Open Source Hackathon 2024' +description: Build an Open Source Project over 10 days. 5 prize categories. +author: ant_wilson +image: oss-hackathon/thumbnail.png +thumb: oss-hackathon/thumbnail.png +launchweek: 8 +categories: + - launch-week +tags: + - launch-week + - hackathon +date: '2024-04-12' +toc_depth: 2 +--- + +[To celebrate the Supabase Special Announcement on April 15th](https://supabase.com/special-announcement) we're running a special 10 day open source hackathon. + +The hackathon starts right now! (Friday 12th April at 09:00 am PT) and ends Sunday 21st April at 11:59 pm PT. You could win a set of Apple AirPods along with extremely limited edition Supabase swag and add your name to the Supabase Hackathon Hall of Fame. + +For some inspiration check out all the [winners from previous hackathons](https://supabase.com/blog/tags/hackathon). + +This is the perfect excuse to "Build in a weekend, scale to millions". Since you retain all the rights to your submissions, you can use the hackathon as a launch pad for your new Startup ideas, side-project, or indie hack. + +## Key Facts + +- You have 10 days to build a new **Open Source** project using Supabase in some capacity. + - Starting 09:00 am PT Friday 12 April 2024 + - The submission deadline is 11:59 pm Sunday midnight PT 21st April 2024 +- Enter as an individual, or as a team of up to 4 people +- Build whatever you want - a project, app, tool, library. Anything. + +## Prizes + +There are 5 categories, there will be prizes for: + +- Best overall project (one set of Apple AirPods for each team member!) +- Best use of AI (Swag kit) +- Most fun / best easter egg (Swag kit) +- Most technically impressive (Swag kit) +- Most visually pleasing (Swag kit) + +There will be a winner and a runner-up prize for each category. Every team member on winning/runner-up teams gets a Supabase Launch Week swag kit. + +![Supabase Swag](/images/blog/oss-hackathon/prize.png) + +## Submission + +You should submit your project using [this form](https://madewithsupabase.com/hackathons/open-source-2024) before 11:59 pm Sunday midnight PT 21st April 2024. Extra points if you include a simple video demoing the app in the description. + +## Judges + +The Supabase team will judge the winners for each category. +We will be looking for: + +- creativity/inventiveness +- functions correctly/smoothly +- visually pleasing +- technically impressive +- use of Supabase features +- FUN! 😃 + +## Rules + +- Team size 1-4 (all team members on winning teams will receive a prize) +- You cannot be on multiple teams +- One submission per team +- It's not a requirement to use AI +- All design elements, code, etc. for your project must be created **during** the event +- All entries must be Open Source (link to source code required in entry) +- Must use Supabase in some capacity +- Can be any language or framework +- You must submit before the deadline (no late entries) +- (optional) Include a link to a demo video along with the submission for extra points + +## Resources + +### Supabase resources to get you started + +- [Real world RAG, the missing pieces for your AI application](https://www.youtube.com/watch?v=ibzlEQmgPPY) +- [Simplify complex SQL queries with Views](https://www.youtube.com/watch?v=IOYFS-2lFjU&t) +- [Supabase Swift Client Library](https://supabase.com/docs/reference/swift/introduction) +- [Row Level Security in Supabase](https://supabase.com/docs/guides/database/postgres/row-level-security) + +### Community help + +The Supabase Team will be taking part in the Hackathon and you'll find us live to build in our discord all week. Please join us by building in public: + +- Text channel: [#hackathon](https://discord.gg/UYyweApy) +- Audio channel: [#hackathon](https://discord.gg/Vj3mTPwH) + +If you need help or advice when building, find other people to join your team, or if you just want to chill and watch people build, come and join us! + +[Join our Discord](https://discord.supabase.com/) + +![Supabase Discord Channel](/images/blog/lw7-hackathon/discord.png) + +- [Previous Hackathon Prize Winners](https://supabase.com/blog/tags/hackathon) + +## Additional Info + +- Any intellectual property developed during the hackathon will belong to the team that developed it. We expect that each team will have an agreement between themselves regarding the IP, but this is not required. +- By making a submission you grant Supabase permission to use screenshots, code snippets, and/or links to your project or content of your README on our Twitter, blog, website, email updates, and in the Supabase discord server. Supabase does not make any claims over your IP. diff --git a/apps/www/_blog/2024-04-15-branching-publicly-available.mdx b/apps/www/_blog/2024-04-15-branching-publicly-available.mdx new file mode 100644 index 00000000000..5dcd0954e15 --- /dev/null +++ b/apps/www/_blog/2024-04-15-branching-publicly-available.mdx @@ -0,0 +1,72 @@ +--- +title: 'Branching now Publicly Available' +description: 'Supabase Branching is now available on Pro Plan and above.' +author: alaister,qiao +image: ga-week/branching-publicly-available/og.png +thumb: ga-week/branching-publicly-available/thumb.png +categories: + - product +tags: + - launch-week + - database +date: '2024-04-15' +toc_depth: 3 +launchweek: 11 +--- + +tl;dr: Supabase Branching is now in open beta! You can enable it on any project that's Pro plan or above. + +## What is Branching? + +Branching is a seamless integration of Git with your development workflow, extending beyond your local environment to a remote database. Leveraging Git, particularly focusing on GitHub initially, each time a Pull Request is opened, a corresponding "Preview Environment" is spawned. + +Every Supabase Preview is a dedicated instance, with a full suite of Supabase services. + +Preview Branches are essentially full Supabase instances. Every push triggers migrations from the `./supabase/migrations` folder, ensuring team synchronization and a shared source of truth. When you merge a Pull Request, your migrations are applied to the Production database. + +We [announced Branching](https://supabase.com/blog/supabase-branching) a few months ago in our previous Launch Week, with a deep dive on a few of the features like data seeding, integrations with Vercel, and seamless handling of environment variables. Since launching Branching for early-access we've worked with early users of all sizes. Today we're making Branching available to everyone. + +## New Features + +Our open Beta introduces a number of requested features: + +### Edge Function support + +Branching now deploys your Edge Functions along with your migrations. Any Functions added or changed in your `./supabase/functions` will automatically be deployed without any extra configuration. + +### Monorepo support + +You can now set a custom Supabase directory path which allows for monorepo support. You can also choose to only spin up new branches when there are changes inside your Supabase directory. See all the configuration settings in your projects [here](https://supabase.com/dashboard/project/_/settings/integrations). + +### Persistent branches + +Every Supabase Preview is a dedicated instance, with a full suite of Supabase services. + +We had quite a few users of branching request for long-running branches so we added the concept of persistent branches. In persistent mode, a branch will remain active even after the underlying PR merges or closes. + +Please note that branches should still be treated as replaceable at any time. Persistent or ephemeral Branches should not be used for production data. + +## Feedback + +A special thank you to all our early-access branching users who provided lots of actionable feedback. Our feature development was largely driven by the direct feedback from our users. + +We still have many features to add to branching before 1.0, so please continue [sending us your feedback](https://github.com/orgs/supabase/discussions/18937)! + +## Getting Started + +You can easily get started with Branching by following our [Getting Started Guide](https://supabase.com/docs/guides/platform/branching#how-to-use-supabase-branching). diff --git a/apps/www/_blog/2024-04-15-supabase-aquires-oriole.mdx b/apps/www/_blog/2024-04-15-supabase-aquires-oriole.mdx new file mode 100644 index 00000000000..45567ffc532 --- /dev/null +++ b/apps/www/_blog/2024-04-15-supabase-aquires-oriole.mdx @@ -0,0 +1,93 @@ +--- +title: 'Oriole joins Supabase' +description: 'The Oriole team are joining Supabase to build a faster storage engine for Postgres.' +author: paul_copplestone +image: launch-week-11/oriole/og.png +thumb: launch-week-11/oriole/thumb.png +categories: + - company +tags: + - launch-week + - database +date: '2024-04-15' +toc_depth: 3 +launchweek: 11 +--- + +[Oriole](https://github.com/orioledb/orioledb) is a **table storage extension for Postgres**. It is designed to be a drop-in replacement for Postgres' existing storage engine. + +The Oriole team are joining Supabase to: + +1. Build a faster storage engine for Postgres. +2. Help develop pluggable storage in Postgres. +3. Work on decoupled storage and compute in Postgres. + +Let's explore all of these below: + +## Building a faster storage engine + +Oriole acts as a drop-in replacement for the default Postgres storage engine using the Table Access Method APIs: + +```sql +create extension orioledb; -- enable the extension + +create table posts ( + id int8 not null, + title text not null, + body text not null, + primary key (id) +) using orioledb; -- Specify the storage format +``` + +The `using` clause might look familiar if you have used other storage engines in Postgres like [Citus columnar](https://docs.citusdata.com/en/v11.1/admin_guide/table_management.html#columnar-storage), [Hydra](https://docs.hydra.so/concepts/using-hydra-columnar), [ParadeDB](https://docs.paradedb.com/search/quickstart), or [zheap](https://github.com/cybertec-postgresql/zheap). These all use the [Table Access Method API](https://www.postgresql.org/docs/current/tableam.html) - a set of methods that provide pluggable storage. + +The `orioledb` storage engine changes the representation of table data on disk. Its [architecture](https://github.com/orioledb/orioledb/blob/main/doc/arch.md) is designed to take advantage of modern hardware like SSDs and NVRAM. + +It implements MVCC, the feature that allows multiple connected users to see different versions of the data depending on when their transaction started, via an UNDO log rather than tuple versioning. Orioles architecture prevents bloat and provides several features and benefits: + +2. **Reduced IO**: It implements row-level WAL (Write-Ahead Log) and a non-persistent undo log. This significantly reduces IO operations for write transactions. +1. **Scalability**: It implements lockless access for in-memory data. This helps to eliminate common bottlenecks found in traditional database engines. +1. **Data Compression**: It implements page-level data compression, which can reduce the overall size of a typical database by approximately 4 to 5 times, saving storage space. +1. **Bloat Reduction**: Efficiently manages updates in-place with an undo log, which contributes to reducing database bloat — a common issue where the database size grows due to unused or obsolete data occupying space. +1. **Index-organized tables:** Oriole stores table rows in the leafs of primary key index making primary key lookups very cheap. In many cases, this saves significant storage space. + +
+ ![Faster reads with Oriole](/images/blog/launch-week-11/oriole/read-only-results-amazon.png) +
Reads and writes are significantly faster with Oriole
+
+ +## Pluggable storage in Postgres + +We've [written previously](https://supabase.com/blog/postgres-pluggable-strorage) about Pluggable Storage: it gives developers the ability to use different storage engines for different tables _within the same database_. This system is [available in MySQL](https://en.wikipedia.org/wiki/Comparison_of_MySQL_database_engines), which uses the [InnoDB](https://en.wikipedia.org/wiki/InnoDB) as the default storage engine since MySQL 5.5 (replacing [MyISAM](https://en.wikipedia.org/wiki/MyISAM)). + +Oriole aims to be a drop-in replacement for Postgres' default storage engine and supports similar use-cases with improved performance. Other storage engines, to name a few possibilities, could implement columnar storage for OLAP workloads, highly compressed timeseries storage for event data, or compressed storage for minimizing disk usage. + +![Pluggable storage](/images/blog/launch-week-11/oriole/table-storage.png) + +In version 12, PostgreSQL introduced support for pluggable storage with the goal of [adding ZHeap](https://anarazel.de/talks/2019-05-30-pgcon-pluggable-table-storage/pluggable.pdf) - a previous effort to solve some shortcomings of Postgres' default storage format. We hope to contribute towards these efforts. + +OrioleDB currently requires a [set of patches to Postgres](https://github.com/orioledb/postgres/commits/patches16/) to expand on the type of features external storage engines extensions can implement. We remain committed to open source we'll work with the Oriole team and Postgres community with the goal of upstreaming patches so that Oriole can be used with any Postgres installation. We have no timeline for this, but it's safe to expect that it could be a few major Postgres versions away. + +## Decoupled Storage and Compute + +The Oriole storage engine's reduction in disk IO is significant enough that it unlocks performant databases backed by S3 compatible blob storage. + +We've been working with the Oriole team for a few months to develop [experimental support decoupled storage and compute](https://github.com/orioledb/orioledb/blob/main/doc/usage.md#s3-database-storage-experimental): + +
+
{description} +
+ {access_key} + + + +
+
{daysSince(created_at)} + + + + + + { + e.preventDefault() + onDeleteClick(id) + }} + > + + Revoke credentials + + + +