diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b7074fd4b1a..40167c5036e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -3,6 +3,9 @@ /apps/studio/ @supabase/Dashboard +# temporary until we've ironed out all the problems with sync workflow +/apps/docs/content/troubleshooting/ @charislam + /apps/www/ @supabase/marketing /apps/www/public/images/blog @supabase/marketing /apps/www/lib/redirects.js diff --git a/.github/workflows/docs-sync-troubleshooting.yml b/.github/workflows/docs-sync-troubleshooting.yml new file mode 100644 index 00000000000..0b2d3d04e86 --- /dev/null +++ b/.github/workflows/docs-sync-troubleshooting.yml @@ -0,0 +1,51 @@ +name: '[Docs] Sync troubleshooting guides to GitHub Discussions' + +on: + push: + branches: + - master + paths: + - 'apps/docs/content/troubleshooting/**' + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + update-troubleshooting: + runs-on: ubuntu-latest + + env: + SEARCH_GITHUB_APP_ID: ${{ secrets.SEARCH_GITHUB_APP_ID }} + SEARCH_GITHUB_APP_INSTALLATION_ID: ${{ secrets.SEARCH_GITHUB_APP_INSTALLATION_ID }} + SEARCH_GITHUB_APP_PRIVATE_KEY: ${{ secrets.SEARCH_GITHUB_APP_PRIVATE_KEY }} + NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.SEARCH_SUPABASE_URL }} + SUPABASE_SECRET_KEY: ${{ secrets.SEARCH_SUPABASE_SERVICE_ROLE_KEY }} + + steps: + - uses: actions/checkout@v4 + with: + sparse-checkout: | + apps/docs + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run Troubleshooting script + run: npm --prefix=apps/docs run troubleshooting:sync + + - name: Create Pull Request + uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5 + with: + commit-message: '[bot] sync troubleshooting guides to db' + title: '[bot] sync troubleshooting guides to db' + author: 'github-docs-sync-bot ' + branch: 'bot/docs-sync-troubleshooting' + branch-suffix: 'random' diff --git a/apps/design-system/__registry__/index.tsx b/apps/design-system/__registry__/index.tsx index 57cf860d20b..b91cfecb325 100644 --- a/apps/design-system/__registry__/index.tsx +++ b/apps/design-system/__registry__/index.tsx @@ -1798,13 +1798,68 @@ export const Index: Record = { subcategory: "undefined", chunks: [] }, - "multi-select-form": { - name: "multi-select-form", + "multi-select-disabled": { + name: "multi-select-disabled", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/multi-select-form")), + component: React.lazy(() => import("@/registry/default/example/multi-select-disabled")), source: "", - files: ["registry/default/example/multi-select-form.tsx"], + files: ["registry/default/example/multi-select-disabled.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "multi-select-badge-limit-wrap": { + name: "multi-select-badge-limit-wrap", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/multi-select-badge-limit-wrap")), + source: "", + files: ["registry/default/example/multi-select-badge-limit-wrap.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "multi-select-badge-limit": { + name: "multi-select-badge-limit", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/multi-select-badge-limit")), + source: "", + files: ["registry/default/example/multi-select-badge-limit.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "multi-select-combobox": { + name: "multi-select-combobox", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/multi-select-combobox")), + source: "", + files: ["registry/default/example/multi-select-combobox.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "multi-select-deletable-badge": { + name: "multi-select-deletable-badge", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/multi-select-deletable-badge")), + source: "", + files: ["registry/default/example/multi-select-deletable-badge.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "multi-select-inline-search-input": { + name: "multi-select-inline-search-input", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/multi-select-inline-search-input")), + source: "", + files: ["registry/default/example/multi-select-inline-search-input.tsx"], category: "undefined", subcategory: "undefined", chunks: [] diff --git a/apps/design-system/content/docs/changelog.mdx b/apps/design-system/content/docs/changelog.mdx index cbee68f1521..1ae4506b73a 100644 --- a/apps/design-system/content/docs/changelog.mdx +++ b/apps/design-system/content/docs/changelog.mdx @@ -4,6 +4,14 @@ description: Latest updates and announcements. toc: false --- +## 18th Oct 2024 - Update multi-select + +Improved Multi Select with combobox, badge limit, and more examples. + +- [Multi Select](/design-system/components/multi-select) + +[PR](https://github.com/supabase/supabase/pull/29695) + ## 12th June 2024 - Button styles updated - [` + + Limit: {limit} + + + + + + + + Apple + Banana + Cherry + Date + Elderberrie + Fig + Grape + Kiwi + Mango + Strawberry + + + + + ) +} diff --git a/apps/design-system/registry/default/example/multi-select-combobox.tsx b/apps/design-system/registry/default/example/multi-select-combobox.tsx new file mode 100644 index 00000000000..5a79a6057b1 --- /dev/null +++ b/apps/design-system/registry/default/example/multi-select-combobox.tsx @@ -0,0 +1,34 @@ +import { useState } from 'react' +import { + MultiSelector, + MultiSelectorContent, + MultiSelectorInput, + MultiSelectorItem, + MultiSelectorList, + MultiSelectorTrigger, +} from 'ui-patterns/multi-select' + +export default function MultiSelectDemo() { + const [selectedValues, setSelectedValues] = useState([]) + + return ( + + + + + + Apple + Banana + Cherry + Date + Elderberrie + Fig + Grape + Kiwi + Mango + Strawberry + + + + ) +} diff --git a/apps/design-system/registry/default/example/multi-select-deletable-badge.tsx b/apps/design-system/registry/default/example/multi-select-deletable-badge.tsx new file mode 100644 index 00000000000..daf693d3edf --- /dev/null +++ b/apps/design-system/registry/default/example/multi-select-deletable-badge.tsx @@ -0,0 +1,37 @@ +import { useState } from 'react' +import { + MultiSelector, + MultiSelectorContent, + MultiSelectorItem, + MultiSelectorList, + MultiSelectorTrigger, +} from 'ui-patterns/multi-select' + +export default function MultiSelectDemo() { + const [selectedValues, setSelectedValues] = useState(['Apple', 'Banana']) + + return ( + + + + + Banana + Cherry + Date + Elderberrie + Fig + Grape + Kiwi + Mango + Strawberry + + + + ) +} diff --git a/apps/design-system/registry/default/example/multi-select-demo.tsx b/apps/design-system/registry/default/example/multi-select-demo.tsx index faa684876f7..fafed2a1c4c 100644 --- a/apps/design-system/registry/default/example/multi-select-demo.tsx +++ b/apps/design-system/registry/default/example/multi-select-demo.tsx @@ -2,25 +2,42 @@ import { useState } from 'react' import { MultiSelector, MultiSelectorContent, - MultiSelectorInput, MultiSelectorItem, MultiSelectorList, MultiSelectorTrigger, } from 'ui-patterns/multi-select' export default function MultiSelectDemo() { - const [value, setValue] = useState([]) + const [selectedValues, setSelectedValues] = useState([]) + + const fruits = [ + { value: 'Apple', isDisabled: false }, + { value: 'Banana', isDisabled: false }, + { value: 'Cherry', isDisabled: false }, + { value: 'Date', isDisabled: false }, + { value: 'Elderberrie', isDisabled: false }, + { value: 'Fig', isDisabled: false }, + { value: 'Grape', isDisabled: false }, + { value: 'Kiwi', isDisabled: true }, + { value: 'Mango', isDisabled: false }, + { value: 'Strawberry', isDisabled: false }, + ] return ( - - - - + + - Item 1 - Item 2 - Item 3 + {fruits.map(({ value, isDisabled }) => ( + + {value} + + ))} diff --git a/apps/design-system/registry/default/example/multi-select-disabled.tsx b/apps/design-system/registry/default/example/multi-select-disabled.tsx new file mode 100644 index 00000000000..16a4ba9aad8 --- /dev/null +++ b/apps/design-system/registry/default/example/multi-select-disabled.tsx @@ -0,0 +1,18 @@ +import { useState } from 'react' +import { + MultiSelector, + MultiSelectorContent, + MultiSelectorItem, + MultiSelectorList, + MultiSelectorTrigger, +} from 'ui-patterns/multi-select' + +export default function MultiSelectDemo() { + const [selectedValues, setSelectedValues] = useState([]) + + return ( + + + + ) +} diff --git a/apps/design-system/registry/default/example/multi-select-form.tsx b/apps/design-system/registry/default/example/multi-select-form.tsx deleted file mode 100644 index 83892ee5bab..00000000000 --- a/apps/design-system/registry/default/example/multi-select-form.tsx +++ /dev/null @@ -1,105 +0,0 @@ -'use client' - -import { zodResolver } from '@hookform/resolvers/zod' -import Image from 'next/image' -import { useForm } from 'react-hook-form' -import { toast } from 'sonner' -import z from 'zod' - -import { - Button, - Form_Shadcn_, - FormDescription_Shadcn_, - FormField_Shadcn_, - FormItem_Shadcn_, - FormLabel_Shadcn_, - FormMessage_Shadcn_, -} from 'ui' -import { - MultiSelector, - MultiSelectorContent, - MultiSelectorInput, - MultiSelectorItem, - MultiSelectorList, - MultiSelectorTrigger, -} from 'ui-patterns/multi-select' - -const form = z.object({ - value: z.array(z.string()).nonempty('Please select at least one person'), -}) - -type Form = z.infer - -const users = [ - { - name: 'ThePrimeagen', - picture: 'https://pbs.twimg.com/profile_images/1759330620160049152/2i_wkOoK_400x400.jpg', - }, - { - name: 'Shadcn', - picture: 'https://pbs.twimg.com/profile_images/1593304942210478080/TUYae5z7_400x400.jpg', - }, - { - name: 'Theo', - picture: 'https://pbs.twimg.com/profile_images/1605762947686375425/lsoGWWty_400x400.jpg', - }, -] - -const MultiSelectZod = () => { - const multiForm = useForm
({ - resolver: zodResolver(form), - defaultValues: form.parse({ value: [users[0].name] }), - }) - - const onSubmit = (data: Form) => { - toast('Form submitted ', { description: JSON.stringify(data, null, 2) }) - } - - return ( - - - ( - - Invite people - - - - - - - {users.map((user) => ( - -
- {user.name} - {user.name} -
-
- ))} -
-
-
- - Select people to invite to this event - - -
- )} - /> - - -
- ) -} - -export default MultiSelectZod diff --git a/apps/design-system/registry/default/example/multi-select-inline-search-input.tsx b/apps/design-system/registry/default/example/multi-select-inline-search-input.tsx new file mode 100644 index 00000000000..775762a39f5 --- /dev/null +++ b/apps/design-system/registry/default/example/multi-select-inline-search-input.tsx @@ -0,0 +1,39 @@ +import { useState } from 'react' +import { + MultiSelector, + MultiSelectorContent, + MultiSelectorItem, + MultiSelectorList, + MultiSelectorTrigger, +} from 'ui-patterns/multi-select' + +export default function MultiSelectDemo() { + const [selectedValues, setSelectedValues] = useState(['Apple']) + + return ( + + + + + Apple + Banana + Cherry + Date + Elderberrie + Fig + Grape + Kiwi + Mango + Strawberry + + + + ) +} diff --git a/apps/design-system/registry/examples.ts b/apps/design-system/registry/examples.ts index a7e7a595bb7..6cd26e5e819 100644 --- a/apps/design-system/registry/examples.ts +++ b/apps/design-system/registry/examples.ts @@ -1036,9 +1036,34 @@ export const examples: Registry = [ files: ['example/multi-select-demo.tsx'], }, { - name: 'multi-select-form', + name: 'multi-select-disabled', type: 'components:example', - files: ['example/multi-select-form.tsx'], + files: ['example/multi-select-disabled.tsx'], + }, + { + name: 'multi-select-badge-limit-wrap', + type: 'components:example', + files: ['example/multi-select-badge-limit-wrap.tsx'], + }, + { + name: 'multi-select-badge-limit', + type: 'components:example', + files: ['example/multi-select-badge-limit.tsx'], + }, + { + name: 'multi-select-combobox', + type: 'components:example', + files: ['example/multi-select-combobox.tsx'], + }, + { + name: 'multi-select-deletable-badge', + type: 'components:example', + files: ['example/multi-select-deletable-badge.tsx'], + }, + { + name: 'multi-select-inline-search-input', + type: 'components:example', + files: ['example/multi-select-inline-search-input.tsx'], }, { name: 'tree-view-demo', diff --git a/apps/docs/app/guides/troubleshooting/[slug]/page.tsx b/apps/docs/app/guides/troubleshooting/[slug]/page.tsx index 428a2e84adc..01d40abeef7 100644 --- a/apps/docs/app/guides/troubleshooting/[slug]/page.tsx +++ b/apps/docs/app/guides/troubleshooting/[slug]/page.tsx @@ -4,6 +4,10 @@ import TroubleshootingPage from '~/features/docs/Troubleshooting.page' import { getAllTroubleshootingEntries, getArticleSlug } from '~/features/docs/Troubleshooting.utils' import { PROD_URL } from '~/lib/constants' +// 60 seconds/minute * 60 minutes/hour * 24 hours/day +export const revalidate = 86_400 +export const dynamicParams = false + export default async function TroubleshootingEntryPage({ params: { slug }, }: { diff --git a/apps/docs/app/guides/troubleshooting/page.tsx b/apps/docs/app/guides/troubleshooting/page.tsx index 587e84fa1ae..ac0be072bfe 100644 --- a/apps/docs/app/guides/troubleshooting/page.tsx +++ b/apps/docs/app/guides/troubleshooting/page.tsx @@ -16,6 +16,9 @@ import { TROUBLESHOOTING_CONTAINER_ID } from '~/features/docs/Troubleshooting.ut import { SidebarSkeleton } from '~/layouts/MainSkeleton' import { PROD_URL } from '~/lib/constants' +// 60 seconds/minute * 60 minutes/hour * 24 hours/day +export const revalidate = 86_400 + export default async function GlobalTroubleshootingPage() { const troubleshootingEntries = await getAllTroubleshootingEntries() const keywords = await getAllTroubleshootingKeywords() diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 27a91a80c11..1eff5c9286e 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -963,10 +963,6 @@ export const database: NavMenuConstant = { name: 'pg_net: Async Networking', url: '/guides/database/extensions/pg_net', }, - { - name: 'pg_partman: Partition Maintenance', - url: '/guides/database/extensions/pg_partman', - }, { name: 'pg_plan_filter: Restrict Total Cost', url: '/guides/database/extensions/pg_plan_filter', @@ -987,10 +983,6 @@ export const database: NavMenuConstant = { name: 'PostGIS: Geo queries', url: '/guides/database/extensions/postgis', }, - { - name: 'pgmq: Queues', - url: '/guides/database/extensions/pgmq', - }, { name: 'pgsodium (pending deprecation): Encryption Features', url: '/guides/database/extensions/pgsodium', diff --git a/apps/docs/content/guides/ai/structured-unstructured.mdx b/apps/docs/content/guides/ai/structured-unstructured.mdx index 3b722be9605..52872c07c71 100644 --- a/apps/docs/content/guides/ai/structured-unstructured.mdx +++ b/apps/docs/content/guides/ai/structured-unstructured.mdx @@ -59,6 +59,12 @@ Client libraries like python's [vecs](https://github.com/supabase/vecs) use this #!/usr/bin/env python3 import vecs +# In practice, do not hard-code your password. Use environment variables. +DB_CONNECTION = "postgresql://:@:/" + +# create vector store client +vx = vecs.create_client(DB_CONNECTION) + docs = vx.get_or_create_collection(name="docs", dimension=1536) docs.upsert(vectors=[ diff --git a/apps/docs/content/guides/auth/auth-anonymous.mdx b/apps/docs/content/guides/auth/auth-anonymous.mdx index 2db2e513187..cf0ee68c4c7 100644 --- a/apps/docs/content/guides/auth/auth-anonymous.mdx +++ b/apps/docs/content/guides/auth/auth-anonymous.mdx @@ -264,6 +264,53 @@ Depending on your application requirements, data conflicts can arise when an ano 2. Overwrite the items in the cart with those from the anonymous user 3. Merge the items in the cart together +### Linking an anonymous user to an existing account + +In some cases, you may need to link an anonymous user to an existing account rather than creating a new permanent account. This process requires manual handling of potential conflicts. Here's a general approach: + +```javascript +// 1. Sign in anonymously (assuming the user is already signed in anonymously) +const { data: anonData, error: anonError } = await supabase.auth.getSession() + +// 2. Attempt to update the user with the existing email +const { data: updateData, error: updateError } = await supabase.auth.updateUser({ + email: 'existing_user@example.com', +}) + +// 3. Handle the error (since the email belongs to an existing user) +if (updateError) { + console.log('This email belongs to an existing user. Please sign in to that account.') + + // 4. Sign in to the existing account + const { + data: { user: existingUser }, + error: signInError, + } = await supabase.auth.signInWithPassword({ + email: 'existing_user@example.com', + password: 'user_password', + }) + + if (existingUser) { + // 5. Reassign entities tied to the anonymous user + // This step will vary based on your specific use case and data model + const { data: reassignData, error: reassignError } = await supabase + .from('your_table') + .update({ user_id: existingUser.id }) + .eq('user_id', anonData.session.user.id) + + // 6. Implement your chosen conflict resolution strategy + // This could involve merging data, overwriting, or other custom logic + await resolveDataConflicts(anonData.session.user.id, existingUser.id) + } +} + +// Helper function to resolve data conflicts (implement based on your strategy) +async function resolveDataConflicts(anonymousUserId, existingUserId) { + // Implement your conflict resolution logic here + // This could involve ignoring the anonymous user's metadata, overwriting the existing user's metadata, or merging the data of both the anonymous and existing user. +} +``` + ## Abuse prevention and rate limits Since anonymous users are stored in your database, bad actors can abuse the endpoint to increase your database size drastically. It is strongly recommended to [enable invisible Captcha or Cloudflare Turnstile](/docs/guides/auth/auth-captcha) to prevent abuse for anonymous sign-ins. An IP-based rate limit is enforced at 30 requests per hour which can be modified in your [dashboard](/dashboard/project/_/auth/rate-limits). You can refer to the full list of rate limits [here](/docs/guides/platform/going-into-prod#rate-limiting-resource-allocation--abuse-prevention). diff --git a/apps/docs/content/guides/auth/auth-email-templates.mdx b/apps/docs/content/guides/auth/auth-email-templates.mdx index cdff2487be6..f185d2a5f90 100644 --- a/apps/docs/content/guides/auth/auth-email-templates.mdx +++ b/apps/docs/content/guides/auth/auth-email-templates.mdx @@ -27,7 +27,7 @@ The templating system provides the following variables for use: ## Editing email templates -On hosted Supabase projects, edit your email templates on the [Email Templates](/dashboard/project/_/auth/templates) page. On self-hosted projects or in local development, edit your [configuration files](/docs/guides/cli/customizing-email-templates). +On hosted Supabase projects, edit your email templates on the [Email Templates](/dashboard/project/_/auth/templates) page. On self-hosted projects or in local development, edit your [configuration files](/docs/guides/local-development/customizing-email-templates). ## Mobile Deep Linking diff --git a/apps/docs/content/guides/auth/auth-hooks/send-email-hook.mdx b/apps/docs/content/guides/auth/auth-hooks/send-email-hook.mdx index f9df8e203c7..c6e2f30ab0b 100644 --- a/apps/docs/content/guides/auth/auth-hooks/send-email-hook.mdx +++ b/apps/docs/content/guides/auth/auth-hooks/send-email-hook.mdx @@ -6,6 +6,17 @@ subtitle: 'Use a custom email provider to send authentication messages' The Send Email Hook runs before an email is sent and allows for flexibility around email sending. You can use this hook to configure a back-up email provider or add internationalization to your emails. +## Email Sending Behavior + +Email sending depends on two settings: Email Provider and Auth Hook status. + +| Email Provider | Auth Hook | Result | +| -------------- | --------- | -------------------------------------------------------------------- | +| Enabled | Enabled | Auth Hook handles email sending (SMTP not used) | +| Enabled | Disabled | SMTP handles email sending (custom if configured, default otherwise) | +| Disabled | Enabled | Email Signups Disabled | +| Disabled | Disabled | Email Signups Disabled | + **Inputs** | Field | Type | Description | @@ -630,6 +641,11 @@ const templates = { }, } +function generateConfirmationURL(email_data) { + // TODO: replace the ref with your project ref + return `https://.supabase.co/auth/v1/verify?token=${email_data.token}&type=${email_data.email_action_type}&redirect_to=${email_data.redirect_to}` +} + Deno.serve(async (req) => { const payload = await req.text() const serverToken = Deno.env.get('POSTMARK_SERVER_TOKEN') @@ -642,8 +658,9 @@ Deno.serve(async (req) => { const subject = subjects[language][email_data.email_action_type] || 'Notification' let template = templates[language][email_data.email_action_type] + const confirmation_url = generateConfirmationURL(email_data) let htmlBody = template - .replace('{{confirmation_url}}', email_data.confirmation_url) + .replace('{{confirmation_url}}', confirmation_url) .replace('{{token}}', email_data.token || '') .replace('{{new_token}}', email_data.new_token || '') .replace('{{site_url}}', email_data.site_url || '') @@ -736,10 +753,16 @@ const templates = { reauthentication: `

Confirm reauthentication

Enter the code: {{token}}

` }; +function generateConfirmationURL(email_data) { + // TODO: replace the ref with your project ref + return `https://.supabase.co/auth/v1/verify?token=${email_data.token}&type=${email_data.email_action_type}&redirect_to=${email_data.redirect_to}` +} + async function sendEmailWithPostmark(user: any, email_data: any, serverToken: string): Promise { const subject = subjects[email_data.email_action_type] || 'Notification'; + const confirmation_url = generateConfirmationURL(email_data) let template = templates[email_data.email_action_type]; - let htmlBody = template.replace('{{confirmation_url}}', email_data.confirmation_url) + let htmlBody = template.replace('{{confirmation_url}}', confirmation_url) .replace('{{token}}', email_data.token || '') .replace('{{new_token}}', email_data.new_token || '') .replace('{{site_url}}', email_data.site_url || '') @@ -767,7 +790,8 @@ async function sendEmailWithPostmark(user: any, email_data: any, serverToken: st async function sendEmailWithSendGrid(user: any, email_data: any, apiKey: string): Promise { const subject = subjects[email_data.email_action_type] || 'Notification'; let template = templates[email_data.email_action_type]; - let htmlBody = template.replace('{{confirmation_url}}', email_data.confirmation_url) + cont confirmation_url = generateConfirmationURL(email_data) + let htmlBody = template.replace('{{confirmation_url}}', confirmation_url) .replace('{{token}}', email_data.token || '') .replace('{{new_token}}', email_data.new_token || '') .replace('{{site_url}}', email_data.site_url || '') 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 70b3e077f61..db3e01abe02 100644 --- a/apps/docs/content/guides/auth/social-login/auth-github.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-github.mdx @@ -77,6 +77,20 @@ Future signInWithGithub() async { } ``` + + + +When your user signs in, call [signInWithWithOAuth](/docs/reference/swift/auth-signinwithoauth) with `.github` as the `Provider`: + +```swift +func signInWithGithub() async throws { + try await supabase.auth.signInWithOAuth( + provider: .github, + redirectTo: URL(string: "my-custom-scheme://my-app-host") + ) +} +``` + diff --git a/apps/docs/content/guides/database/connecting-to-postgres.mdx b/apps/docs/content/guides/database/connecting-to-postgres.mdx index 09ede71962c..dbf6406a713 100644 --- a/apps/docs/content/guides/database/connecting-to-postgres.mdx +++ b/apps/docs/content/guides/database/connecting-to-postgres.mdx @@ -128,7 +128,7 @@ If you'd like us to add a new quickstart guide, share your [feedback](https://su - [Prisma](/docs/guides/database/prisma) - [Drizzle](/docs/guides/database/drizzle) -- [Postgres.js](/docs/guides/database/drizzle) +- [Postgres.js](/docs/guides/database/postgres-js) ### External tools diff --git a/apps/docs/content/guides/database/extensions/pg_partman.mdx b/apps/docs/content/guides/database/extensions/pg_partman.mdx deleted file mode 100644 index d907d55cdca..00000000000 --- a/apps/docs/content/guides/database/extensions/pg_partman.mdx +++ /dev/null @@ -1,100 +0,0 @@ ---- -id: 'pg_partman' -title: 'pg_partman: partition management' -description: 'Automated partition management' ---- - -[`pg_partman`](https://github.com/pgpartman/pg_partman) is a PostgreSQL extension that enables automated partition management. It removes the need to create and detach partitions for tables partitioned by time or an integer series. - -## Enable the extension - -To enable `pg_partman`, first enable the extension in your database inside a dedicated schema. - -{/* prettier-ignore */} -```sql -create schema partman; -create extension pg_partman schema partman; -``` - -## Setting up Partitioning - -pg_partman supports partitioning based on time (e.g., by day, month, or year) or by a sequential integer. Setup is performed using the `create_parent` function. - -For time-based partitioning: - -{/* prettier-ignore */} -```sql -select partman.create_parent('schema_name.table_name', 'partition_column', 'time', 'daily'); -``` - -or for integer-based partitioning: - -{/* prettier-ignore */} -```sql -select partman.create_parent('schema_name.table_name', 'partition_column', 'integer', '1000'); -``` - -## API - -- partman.create_parent: creates a parent table for partitioning -- partman.run_maintenance: ensures partitions are created and dropped as per the defined settings. This should be run periodically, such as via a cron job scheduled by pg_cron - -## Example - -Let’s set up a simple partitioning example using pg_partman with time-based partitions. - -{/* prettier-ignore */} -```sql --- Create the table -create table public.foo ( - id serial primary key, - data text, - created_at timestamp not null -); - --- Create the parent table -select - partman.create_parent( - 'public.foo', - 'created_at', - 'time', - 'daily' - ); - --- Configure the partitioned table -update - partman.part_config -set - infinite_time_partitions = true, - retention = '3 months', - retention_keep_table=true -where - parent_table = 'public.foo'; -``` - -For more information about available configuration, see the [pg_partman docs](https://github.com/pgpartman/pg_partman/blob/de93f4a104b8566087f4b9b2a0edf25893672da8/doc/pg_partman.md#configuration-tables). - -Following these statements, partitions for `public.foo` will be automatically managed as necessary when the `partman.run_maintenance()` function is called. - -## Running Maintenance - -It is important to call the `partman.run_maintenance()` procedure frequently to ensure partitions are created and detached according to the partitioned table's configuration. - -{/* prettier-ignore */} -```sql -call partman.run_maintenance_proc(); -``` - -To automate this task, consider using `pg_cron`. - -{/* prettier-ignore */} -```sql -create extension pg_cron; - -select - cron.schedule('@hourly', $$call partman.run_maintenance_proc()$$); -``` - -## Resources - -- Official [pg_partman documentation](https://github.com/pgpartman/pg_partman/blob/master/doc/pg_partman_howto.md) diff --git a/apps/docs/content/guides/database/extensions/pgmq.mdx b/apps/docs/content/guides/database/extensions/pgmq.mdx deleted file mode 100644 index bd33c51dad7..00000000000 --- a/apps/docs/content/guides/database/extensions/pgmq.mdx +++ /dev/null @@ -1,707 +0,0 @@ ---- -id: 'pgmq' -title: 'pgmq: Queues' -description: 'pgmq: Managed queues in Postgres' ---- - -pgmq is a lightweight message queue built on Postgres. - -## Features - -- Lightweight - No background worker or external dependencies, just Postgres functions packaged in an extension -- "exactly once" delivery of messages to a consumer within a visibility timeout -- API parity with AWS SQS and RSMQ -- Messages stay in the queue until explicitly removed -- Messages can be archived, instead of deleted, for long-term retention and replayability - -## Enable the extension - -```sql -create extension pgmq; -``` - -## Usage [#get-usage] - -### Sending Messages - -#### send - -Send a single message to a queue. - -{/* prettier-ignore */} -```sql -pgmq.send( - queue_name text, - msg jsonb, - delay integer default 0 -) -returns setof bigint -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :------ | :----------------------------------------------------------------- | -| queue_name | text | The name of the queue | -| msg | jsonb | The message to send to the queue | -| delay | integer | Time in seconds before the message becomes visible. Defaults to 0. | - -Example: - -{/* prettier-ignore */} -```sql -select * from pgmq.send('my_queue', '{"hello": "world"}'); - send ------- - 4 -``` - ---- - -#### send_batch - -Send 1 or more messages to a queue. - -{/* prettier-ignore */} -```sql -pgmq.send_batch( - queue_name text, - msgs jsonb[], - delay integer default 0 -) -returns setof bigint -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :------ | :------------------------------------------------------------------ | -| queue_name | text | The name of the queue | -| msgs | jsonb[] | Array of messages to send to the queue | -| delay | integer | Time in seconds before the messages becomes visible. Defaults to 0. | - -{/* prettier-ignore */} -```sql -select * from pgmq.send_batch( - 'my_queue', - array[ - '{"hello": "world_0"}'::jsonb, - '{"hello": "world_1"}'::jsonb - ] -); - send_batch ------------- - 1 - 2 -``` - ---- - -### Reading Messages - -#### read - -Read 1 or more messages from a queue. The VT specifies the delay in seconds between reading and the message becoming invisible to other consumers. - -{/* prettier-ignore */} -```sql -pgmq.read( - queue_name text, - vt integer, - qty integer -) - -returns setof pgmq.message_record -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :------ | :-------------------------------------------------------------- | -| queue_name | text | The name of the queue | -| vt | integer | Time in seconds that the message become invisible after reading | -| qty | integer | The number of messages to read from the queue. Defaults to 1 | - -Example: - -{/* prettier-ignore */} -```sql -select * from pgmq.read('my_queue', 10, 2); - msg_id | read_ct | enqueued_at | vt | message ---------+---------+-------------------------------+-------------------------------+---------------------- - 1 | 1 | 2023-10-28 19:14:47.356595-05 | 2023-10-28 19:17:08.608922-05 | {"hello": "world_0"} - 2 | 1 | 2023-10-28 19:14:47.356595-05 | 2023-10-28 19:17:08.608974-05 | {"hello": "world_1"} -(2 rows) -``` - ---- - -#### read_with_poll - -Same as read(). Also provides convenient long-poll functionality. -When there are no messages in the queue, the function call will wait for `max_poll_seconds` in duration before returning. -If messages reach the queue during that duration, they will be read and returned immediately. - -{/* prettier-ignore */} -```sql - pgmq.read_with_poll( - queue_name text, - vt integer, - qty integer, - max_poll_seconds integer default 5, - poll_interval_ms integer default 100 -) -returns setof pgmq.message_record -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------------- | :------ | :-------------------------------------------------------------------------- | -| queue_name | text | The name of the queue | -| vt | integer | Time in seconds that the message become invisible after reading. | -| qty | integer | The number of messages to read from the queue. Defaults to 1. | -| max_poll_seconds | integer | Time in seconds to wait for new messages to reach the queue. Defaults to 5. | -| poll_interval_ms | integer | Milliseconds between the internal poll operations. Defaults to 100. | - -Example: - -{/* prettier-ignore */} -```sql -select * from pgmq.read_with_poll('my_queue', 1, 1, 5, 100); - msg_id | read_ct | enqueued_at | vt | message ---------+---------+-------------------------------+-------------------------------+-------------------- - 1 | 1 | 2023-10-28 19:09:09.177756-05 | 2023-10-28 19:27:00.337929-05 | {"hello": "world"} -``` - ---- - -#### pop - -Reads a single message from a queue and deletes it upon read. - -Note: utilization of pop() results in at-most-once delivery semantics if the consuming application does not guarantee processing of the message. - -{/* prettier-ignore */} -```sql -pgmq.pop(queue_name text) -returns setof pgmq.message_record -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :--- | :-------------------- | -| queue_name | text | The name of the queue | - -Example: - -{/* prettier-ignore */} -```sql -pgmq=# select * from pgmq.pop('my_queue'); - msg_id | read_ct | enqueued_at | vt | message ---------+---------+-------------------------------+-------------------------------+-------------------- - 1 | 2 | 2023-10-28 19:09:09.177756-05 | 2023-10-28 19:27:00.337929-05 | {"hello": "world"} -``` - ---- - -### Deleting/Archiving Messages - -#### delete (single) - -Deletes a single message from a queue. - -{/* prettier-ignore */} -```sql -pgmq.delete (queue_name text, msg_id: bigint) -returns boolean -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :----- | :---------------------------------- | -| queue_name | text | The name of the queue | -| msg_id | bigint | Message ID of the message to delete | - -Example: - -{/* prettier-ignore */} -```sql -select pgmq.delete('my_queue', 5); - delete --------- - t -``` - ---- - -#### delete (batch) - -Delete one or many messages from a queue. - -{/* prettier-ignore */} -```sql -pgmq.delete (queue_name text, msg_ids: bigint[]) -returns setof bigint -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :------- | :----------------------------- | -| queue_name | text | The name of the queue | -| msg_ids | bigint[] | Array of message IDs to delete | - -Examples: - -Delete two messages that exist. - -{/* prettier-ignore */} -```sql -select * from pgmq.delete('my_queue', array[2, 3]); - delete --------- - 2 - 3 -``` - -Delete two messages, one that exists and one that does not. Message `999` does not exist. - -```sql -select * from pgmq.delete('my_queue', array[6, 999]); - delete --------- - 6 -``` - ---- - -#### purge_queue - -Permanently deletes all messages in a queue. Returns the number of messages that were deleted. - -```text -purge_queue(queue_name text) -returns bigint -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :--- | :-------------------- | -| queue_name | text | The name of the queue | - -Example: - -Purge the queue when it contains 8 messages; - -{/* prettier-ignore */} -```sql -select * from pgmq.purge_queue('my_queue'); - purge_queue -------------- - 8 -``` - ---- - -#### archive (single) - -Removes a single requested message from the specified queue and inserts it into the queue's archive. - -{/* prettier-ignore */} -```sql -pgmq.archive(queue_name text, msg_id bigint) -returns boolean -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :----- | :----------------------------------- | -| queue_name | text | The name of the queue | -| msg_id | bigint | Message ID of the message to archive | - -Returns -Boolean value indicating success or failure of the operation. - -Example; remove message with ID 1 from queue `my_queue` and archive it: - -{/* prettier-ignore */} -```sql -select * from pgmq.archive('my_queue', 1); - archive ---------- - t -``` - ---- - -#### archive (batch) - -Deletes a batch of requested messages from the specified queue and inserts them into the queue's archive. -Returns an array of message ids that were successfully archived. - -```text -pgmq.archive(queue_name text, msg_ids bigint[]) -RETURNS SETOF bigint -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :------- | :------------------------------ | -| queue_name | text | The name of the queue | -| msg_ids | bigint[] | Array of message IDs to archive | - -Examples: - -Delete messages with ID 1 and 2 from queue `my_queue` and move to the archive. - -{/* prettier-ignore */} -```sql -select * from pgmq.archive('my_queue', array[1, 2]); - archive ---------- - 1 - 2 -``` - -Delete messages 4, which exists and 999, which does not exist. - -{/* prettier-ignore */} -```sql -select * from pgmq.archive('my_queue', array[4, 999]); - archive ---------- - 4 -``` - ---- - -### Queue Management - -#### create - -Create a new queue. - -{/* prettier-ignore */} -```sql -pgmq.create(queue_name text) -returns void -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :--- | :-------------------- | -| queue_name | text | The name of the queue | - -Example: - -{/* prettier-ignore */} -```sql -select from pgmq.create('my_queue'); - create --------- -``` - ---- - -#### create_partitioned - -Create a partitioned queue. - -{/* prettier-ignore */} -```sql -pgmq.create_partitioned ( - queue-ue_name text, - partition_interval text default '10000'::text, - retention_interval text default '100000'::text -) -returns void -``` - -**Parameters:** - -| Parameter | Type | Description | -| :----------------- | :--- | :-------------------- | -| queue_name | text | The name of the queue | -| partition_interval | text | The name of the queue | -| retention_interval | text | The name of the queue | - -Example: - -Create a queue with 100,000 messages per partition, and will retain 10,000,000 messages on old partitions. Partitions greater than this will be deleted. - -{/* prettier-ignore */} -```sql -select * from pgmq.create_partitioned( - 'my_partitioned_queue', - '100000', - '10000000' -); - create_partitioned --------------------- -``` - ---- - -#### create_unlogged - -Creates an unlogged table. This is useful when write throughput is more important that durability. -See Postgres documentation for [unlogged tables](https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-UNLOGGED) for more information. - -{/* prettier-ignore */} -```sql -pgmq.create_unlogged(queue_name text) -returns void -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :--- | :-------------------- | -| queue_name | text | The name of the queue | - -Example: - -{/* prettier-ignore */} -```sql -select pgmq.create_unlogged('my_unlogged'); - create_unlogged ------------------ -``` - ---- - -#### detach_archive - -Drop the queue's archive table as a member of the PGMQ extension. Useful for preventing the queue's archive table from being drop when `drop extension pgmq` is executed. -This does not prevent the further archives() from appending to the archive table. - -{/* prettier-ignore */} -```sql -pgmq.detach_archive(queue_name text) -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :--- | :-------------------- | -| queue_name | text | The name of the queue | - -Example: - -{/* prettier-ignore */} -```sql -select * from pgmq.detach_archive('my_queue'); - detach_archive ----------------- -``` - ---- - -#### drop_queue - -Deletes a queue and its archive table. - -{/* prettier-ignore */} -```sql -pgmq.drop_queue(queue_name text) -returns boolean -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :--- | :-------------------- | -| queue_name | text | The name of the queue | - -Example: - -{/* prettier-ignore */} -```sql -select * from pgmq.drop_queue('my_unlogged'); - drop_queue ------------- - t -``` - -### Utilities - -#### set_vt - -Sets the visibility timeout of a message to a specified time duration in the future. Returns the record of the message that was updated. - -{/* prettier-ignore */} -```sql -pgmq.set_vt( - queue_name text, - msg_id bigint, - vt_offset integer -) -returns pgmq.message_record -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :------ | :-------------------------------------------------------------------- | -| queue_name | text | The name of the queue | -| msg_id | bigint | ID of the message to set visibility time | -| vt_offset | integer | Duration from now, in seconds, that the message's VT should be set to | - -Example: - -Set the visibility timeout of message 1 to 30 seconds from now. - -```sql -select * from pgmq.set_vt('my_queue', 11, 30); - msg_id | read_ct | enqueued_at | vt | message ---------+---------+-------------------------------+-------------------------------+---------------------- - 1 | 0 | 2023-10-28 19:42:21.778741-05 | 2023-10-28 19:59:34.286462-05 | {"hello": "world_0"} -``` - ---- - -#### list_queues - -List all the queues that currently exist. - -{/* prettier-ignore */} -```sql -list_queues() -RETURNS TABLE( - queue_name text, - created_at timestamp with time zone, - is_partitioned boolean, - is_unlogged boolean -) -``` - -Example: - -{/* prettier-ignore */} -```sql -select * from pgmq.list_queues(); - queue_name | created_at | is_partitioned | is_unlogged -----------------------+-------------------------------+----------------+------------- - my_queue | 2023-10-28 14:13:17.092576-05 | f | f - my_partitioned_queue | 2023-10-28 19:47:37.098692-05 | t | f - my_unlogged | 2023-10-28 20:02:30.976109-05 | f | t -``` - ---- - -#### metrics - -Get metrics for a specific queue. - -{/* prettier-ignore */} -```sql -pgmq.metrics(queue_name: text) -returns table( - queue_name text, - queue_length bigint, - newest_msg_age_sec integer, - oldest_msg_age_sec integer, - total_messages bigint, - scrape_time timestamp with time zone -) -``` - -**Parameters:** - -| Parameter | Type | Description | -| :--------- | :--- | :-------------------- | -| queue_name | text | The name of the queue | - -**Returns:** - -| Attribute | Type | Description | -| :----------------- | :----------------------- | :------------------------------------------------------------------------ | -| queue_name | text | The name of the queue | -| queue_length | bigint | Number of messages currently in the queue | -| newest_msg_age_sec | integer \| null | Age of the newest message in the queue, in seconds | -| oldest_msg_age_sec | integer \| null | Age of the oldest message in the queue, in seconds | -| total_messages | bigint | Total number of messages that have passed through the queue over all time | -| scrape_time | timestamp with time zone | The current timestamp | - -Example: - -{/* prettier-ignore */} -```sql -select * from pgmq.metrics('my_queue'); - queue_name | queue_length | newest_msg_age_sec | oldest_msg_age_sec | total_messages | scrape_time -------------+--------------+--------------------+--------------------+----------------+------------------------------- - my_queue | 16 | 2445 | 2447 | 35 | 2023-10-28 20:23:08.406259-05 -``` - ---- - -#### metrics_all - -Get metrics for all existing queues. - -```text -pgmq.metrics_all() -RETURNS TABLE( - queue_name text, - queue_length bigint, - newest_msg_age_sec integer, - oldest_msg_age_sec integer, - total_messages bigint, - scrape_time timestamp with time zone -) -``` - -**Returns:** - -| Attribute | Type | Description | -| :----------------- | :----------------------- | :------------------------------------------------------------------------ | -| queue_name | text | The name of the queue | -| queue_length | bigint | Number of messages currently in the queue | -| newest_msg_age_sec | integer \| null | Age of the newest message in the queue, in seconds | -| oldest_msg_age_sec | integer \| null | Age of the oldest message in the queue, in seconds | -| total_messages | bigint | Total number of messages that have passed through the queue over all time | -| scrape_time | timestamp with time zone | The current timestamp | - -{/* prettier-ignore */} -```sql -select * from pgmq.metrics_all(); - queue_name | queue_length | newest_msg_age_sec | oldest_msg_age_sec | total_messages | scrape_time -----------------------+--------------+--------------------+--------------------+----------------+------------------------------- - my_queue | 16 | 2563 | 2565 | 35 | 2023-10-28 20:25:07.016413-05 - my_partitioned_queue | 1 | 11 | 11 | 1 | 2023-10-28 20:25:07.016413-05 - my_unlogged | 1 | 3 | 3 | 1 | 2023-10-28 20:25:07.016413-05 -``` - -### Types - -#### message_record - -The complete representation of a message in a queue. - -| Attribute Name | Type | Description | -| :------------- | :----------------------- | :--------------------------------------------------------------------- | -| msg_id | bigint | Unique ID of the message | -| read_ct | bigint | Number of times the message has been read. Increments on read(). | -| enqueued_at | timestamp with time zone | time that the message was inserted into the queue | -| vt | timestamp with time zone | Timestamp when the message will become available for consumers to read | -| message | jsonb | The message payload | - -Example: - -{/* prettier-ignore */} -```sql - msg_id | read_ct | enqueued_at | vt | message ---------+---------+-------------------------------+-------------------------------+-------------------- - 1 | 1 | 2023-10-28 19:06:19.941509-05 | 2023-10-28 19:06:27.419392-05 | {"hello": "world"} -``` - -## Resources - -- Official Docs: [pgmq/api](https://tembo.io/pgmq/#creating-a-queue) diff --git a/apps/docs/content/guides/database/supavisor.mdx b/apps/docs/content/guides/database/supavisor.mdx index 25f755a2d89..007e57e7dcf 100644 --- a/apps/docs/content/guides/database/supavisor.mdx +++ b/apps/docs/content/guides/database/supavisor.mdx @@ -14,3 +14,4 @@ Supavisor logs are available under [Pooler Logs](/dashboard/project/_/logs/poole | Subscribe error: \{:error, :worker_not_found} | This log event is emitted when the client tries to connect to the database, but Supavisor does not have the necessary information to route the connection. Try reconnecting to the database as it can take some time for the project information to propagate to Supavisor. | N/A | | Subscribe error: \{:error, \{:badrpc, \{:error, \{:erpc, :timeout}}}} | This is a timeout error when the communication between different Supavisor nodes takes longer than expected. Try reconnecting to the database. | N/A | | Terminating with reason :client_termination when state was :busy | This error happens when the client terminates the connection before the connection with the database is completed. | N/A | +| Error: received invalid response to GSSAPI negotiation: S | This error happens due to "gssencmode" parameter not set to disabled. | Follow this [guide](https://github.com/orgs/supabase/discussions/30173) to resolve. | diff --git a/apps/docs/content/guides/deployment.mdx b/apps/docs/content/guides/deployment.mdx index c1c38083702..e4d27adf30e 100644 --- a/apps/docs/content/guides/deployment.mdx +++ b/apps/docs/content/guides/deployment.mdx @@ -25,5 +25,5 @@ See the [self-hosting guides](/docs/guides/self-hosting) for instructions on hos You can automate deployments using: - The [Supabase GitHub integration](/dashboard/project/_/settings/integrations) (with branching enabled) -- The [Supabase CLI](/docs/local-development/cli) in your own continuous deployment pipeline +- The [Supabase CLI](/docs/guides/local-development) in your own continuous deployment pipeline - The [Supabase Terraform provider](/docs/guides/deployment/terraform) diff --git a/apps/docs/content/guides/functions/dart-edge.mdx b/apps/docs/content/guides/functions/dart-edge.mdx index 7c50737ddd5..3f022d5c988 100644 --- a/apps/docs/content/guides/functions/dart-edge.mdx +++ b/apps/docs/content/guides/functions/dart-edge.mdx @@ -2,169 +2,14 @@ id: 'functions-dart-edge' title: 'Dart Edge' description: 'Write your functions using Dart.' -tocVideo: '53jhgrDONW4' --- -[Dart Edge](https://docs.dartedge.dev/), a project built and maintained by [Invertase](https://invertase.io/), enables Dart code to be run on different edge environments. This guide will walk you through how you can start using Dart Edge to write your backend code using Dart and run it on Supabase. - -Dart Edge is an experimental project - it's probably not ready for production usage unless you're okay with living on the 'edge'. Open issues on [Dart Edge repo](https://github.com/invertase/dart_edge) if you encounter any issues. +Please be aware that the Dart Edge project is currently not actively maintained due to numerous breaking changes in Dart's development of (WASM) support. -## Prerequisites +[Dart Edge](https://docs.dartedge.dev/) is an experimental project that enables you to write Supabase Edge Functions using Dart. It's built and maintained by [Invertase](https://invertase.io/). -Make sure you have [the edge CLI](https://docs.dartedge.dev/#getting-started) installed in addition to [the Supabase CLI](https://supabase.com/docs/guides/cli#installation). - -```bash -# install the edge CLI -dart pub global activate edge -``` - -## Setting up your first Dart Edge project - -Create a new Dart Edge project using the `new` command on the edge CLI. This will create a familiar boilerplate for your Dart Edge project such as `main.dart` and `pubspec.yaml` file. - -```bash -edge new supabase_functions new_project -``` - -`cd` into the project and initialize a new Supabase project inside the project you just created. - -```bash -cd new_project -supabase init -``` - -Run `pub get` to install the dependencies. - -```bash -dart pub get -``` - -Open the `lib/main.dart` and look at the initial template. - -```dart -void main() { - SupabaseFunctions(fetch: (request) { - return Response("Hello from Supabase Edge Functions!"); - }); -} -``` - -`SupabaseFunctions` class has a `fetch` handler, which is expected to return a `Response` object, and has a `request` parameter, which contains information about the request such as body and headers. - -## Supporting multiple edge functions - -By default, Dart Edge will create a single `main.dart` file in your project, which will be compiled into a function called `dart_edge`. -You can override this behavior and create multiple functions by creating an `edge.yaml` file in the root of your project. -Specify how to map which of your dart files should compile to what Supabase edge functions using the `functions` option. - -```yaml -supabase: - functions: - api_handler: 'lib/api.dart' - webhook_handler: 'lib/webhooks.dart' -``` - -## Using Supabase client - -Now let’s try to interact with our Supabase database. We will start by installing the dependencies. - -### Add dependencies - -Add [`supabase`](https://pub.dev/packages/supabase) to interact with Supabase service. - -```bash -dart pub add supabase -``` - -We also need to use a special HTTP client to HTTP requests in the edge environment. - -```bash -dart pub add edge_http_client -``` - -### Initialize `SupabaseClient` - -At this point, you can initialize a `SupabaseClient` by passing the [EdgeHttpClient](https://pub.dev/packages/edge_http_client). We can access the Supabase credentials through the [environment variables](https://supabase.com/docs/guides/functions/secrets) using `Deno.env.get()` method. Note that [Supabase credentials are available by default](https://supabase.com/docs/guides/functions/secrets), so no configurations are necessary. - -Now we have the following sample functions code to query Supabase database using the Supabase client. - -```dart -import 'dart:convert'; - -import 'package:supabase_functions/supabase_functions.dart'; -import 'package:edge_http_client/edge_http_client.dart'; -import 'package:supabase/supabase.dart'; - -void main() { - final supabase = SupabaseClient( - Deno.env.get('SUPABASE_URL')!, - Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!, // Use service role key to bypass RLS - httpClient: EdgeHttpClient(), - ); - - SupabaseFunctions(fetch: (request) async { - // You can query `public.users` table for example. - final List users = await supabase.from('users').select().limit(10); - return Response.json(users); - }); -} -``` - -## Run the Functions - -### Run locally - -Create a `edge.yaml` file if you don't have one yet in the root of your project. Then, add the following configuration to specify the path to your Supabase project. - -```yaml -supabase: - project_path: './supabase' -``` - -Also, currently Dart Edge doesn't work with the latest versions of Flutter. If you do have the latest version of Flutter on your machine, you can use [Flutter Version Management (FVM)](https://fvm.app/) to use an older version of Flutter for your Dart Edge project. - -```bash -fvm use 3.7.12 -f -``` - -You can read more about how to configure FVM for your project in the [FVM documentation](https://fvm.app/documentation/getting-started). - -Run the following command to compile your Dart code. This command will start a watcher that will recompile anytime you make changes to your code. - -```bash -edge build supabase_functions --dev - -# Or with FVM -fvm dart pub run edge build supabase_functions --dev -``` - -With docker running, start your local Supabase with the following command - -```bash -supabase start -``` - -While you have the above code running, open another terminal and run the following to start the Supabase local development environment. - -```bash -supabase functions serve dart_edge --no-verify-jwt -``` - -You should be able to access your local function here: - -[http://localhost:54321/functions/v1/dart_edge](http://localhost:54321/functions/v1/dart_edge) - -### Deploy to the Edge - -Run the following commands to compile and deploy your function on Supabase Edge functions. - -```bash -edge build supabase_functions -supabase functions deploy dart_edge -``` - -You will be asked to provide the project reference of your Supabase instance to which you want to deploy the function. You can [link your local project to your Supabase instance](https://supabase.com/docs/guides/getting-started/local-development#link-your-project) to avoid having to provide the Supabase reference every time you deploy. +For detailed information on how to set up and use Dart Edge with Supabase, please refer to the [official Dart Edge documentation for Supabase](https://invertase.docs.page/dart_edge/platform/supabase). diff --git a/apps/docs/content/guides/functions/limits.mdx b/apps/docs/content/guides/functions/limits.mdx index fe7c2475d6a..c21f46009fd 100644 --- a/apps/docs/content/guides/functions/limits.mdx +++ b/apps/docs/content/guides/functions/limits.mdx @@ -14,8 +14,6 @@ subtitle: "Limits applied Edge Functions in Supabase's hosted platform." - Maximum Function Size (after bundling via CLI): 10MB - Maximum log message length: 10,000 characters - Log event threshold: 100 events per 10 seconds -- Max Request: 5GB -- Max Response: No limit ## Other limits & restrictions diff --git a/apps/docs/content/guides/getting-started/quickstarts/nuxtjs.mdx b/apps/docs/content/guides/getting-started/quickstarts/nuxtjs.mdx index cbb726b72fb..4de7d702372 100644 --- a/apps/docs/content/guides/getting-started/quickstarts/nuxtjs.mdx +++ b/apps/docs/content/guides/getting-started/quickstarts/nuxtjs.mdx @@ -53,18 +53,18 @@ hideToc: true - In `src/App.vue`, create a Supabase client using your project URL and public API (anon) key: + In `app.vue`, create a Supabase client using your project URL and public API (anon) key: - Replace the existing content in your `src/App.vue` file with the following code. + Replace the existing content in your `app.vue` file with the following code. - ```vue src/App.vue + ```vue app.vue