diff --git a/.github/workflows/avoid-typos.yml b/.github/workflows/avoid-typos.yml index 98b952ef4c2..ce792cbde4c 100644 --- a/.github/workflows/avoid-typos.yml +++ b/.github/workflows/avoid-typos.yml @@ -21,5 +21,6 @@ jobs: *.css **/package.json **/package-lock.json - .git/* + ./.git/* *.ipynb + ./i18n/README.*.md diff --git a/DEVELOPERS.md b/DEVELOPERS.md index e88f3cb89f0..447daaa6c39 100644 --- a/DEVELOPERS.md +++ b/DEVELOPERS.md @@ -17,14 +17,13 @@ ## Getting started -Thanks for your interest in [Supabase](https://supabase.com) and for wanting to contribute! Before you begin, read the -[code of conduct](https://github.com/supabase/.github/blob/main/CODE_OF_CONDUCT.md) and check out the -[existing issues](https://github.com/supabase/supabase/issues). -This document describes how to set up your development environment to build and test [Supabase](https://supabase.com). +Thank you for expressing your interest in [Supabase](https://supabase.com) and your willingness to contribute! + +To ensure a positive and inclusive environment, we kindly request you to read our [code of conduct](https://github.com/supabase/.github/blob/main/CODE_OF_CONDUCT.md). Additionally, we encourage you to explore the existing [issues](https://github.com/supabase/supabase/issues) to see how you can make a meaningful impact. This document will guide you through the process of setting up your development environment, enabling you to successfully build and test [Supabase](https://supabase.com). ### Install dependencies -You need to install and configure the following dependencies on your machine to build [Supabase](https://supabase.com): +You will need to install and configure the following dependencies on your machine to build [Supabase](https://supabase.com): - [Git](http://git-scm.com/) - [Node.js v16.x (LTS)](http://nodejs.org) @@ -48,7 +47,7 @@ To contribute code to [Supabase](https://supabase.com), you must fork the [Supab git clone https://github.com//supabase.git ``` -1. Go to the Supabase directory: +2. Go to the Supabase directory: ```sh cd supabase ``` @@ -63,7 +62,7 @@ To contribute code to [Supabase](https://supabase.com), you must fork the [Supab npm install # install dependencies ``` -2. You can then run the apps simultaneously with the following. +2. After that you can run the apps simultaneously with the following. ```sh npm run dev # start all the applications ``` @@ -121,7 +120,7 @@ Now when you run a local development docs server you will see the new docs site. After making your changes, open a pull request (PR). Once you submit your pull request, others from the Supabase team/community will review it with you. -Did you have an issue, like a merge conflict, or don't know how to open a pull request? Check out [GitHub's pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests) tutorial on how to resolve merge conflicts and other issues. Once your PR has been merged, you will be proudly listed as a contributor in the [contributor chart](https://github.com/supabase/supabase/graphs/contributors). +If you have an issue, like a merge conflict, or don't know how to open a pull request then check out [GitHub's pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests) tutorial on how to resolve merge conflicts and other issues. Once your PR has been merged, you will be proudly listed as a contributor in the [contributor chart](https://github.com/supabase/supabase/graphs/contributors). --- @@ -135,7 +134,7 @@ Create a new entry in the [`redirects.js`](https://github.com/supabase/supabase/ ## Community channels -Stuck somewhere? Have any questions? Join the [Discord Community Server](https://discord.supabase.com/) or the [Github Discussions](https://github.com/supabase/supabase/discussions). We are here to help! +If you are stuck somewhere or have any questions, join our [Discord Community Server](https://discord.supabase.com/) or the [Github Discussions](https://github.com/supabase/supabase/discussions). We are here to help! ## Contributors diff --git a/README.md b/README.md index 37941659f32..b71944fad9c 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,7 @@ Our approach for client libraries is modular. Each sub-library is a standalone i Client Feature-Clients (bundled in Supabase client) + Supabase @@ -100,7 +101,9 @@ Our approach for client libraries is modular. Each sub-library is a standalone i storage-lang END ROW --> + ⚡️ Official ⚡️ + JavaScript (TypeScript) supabase-js @@ -119,7 +122,9 @@ Our approach for client libraries is modular. Each sub-library is a standalone i storage-dart functions-dart + 💚 Community 💚 + C# supabase-csharp @@ -201,6 +206,7 @@ Our approach for client libraries is modular. Each sub-library is a standalone i storage-gdscript functions-gdscript + @@ -213,23 +219,28 @@ Our approach for client libraries is modular. Each sub-library is a standalone i - [Bangla / বাংলা](/i18n/README.bn.md) - [Bulgarian / Български](/i18n/README.bg.md) - [Catalan / Català](/i18n/README.ca.md) +- [Czech / čeština](/i18n/README.cs.md) - [Danish / Dansk](/i18n/README.da.md) - [Dutch / Nederlands](/i18n/README.nl.md) - [English](https://github.com/supabase/supabase) +- [Estonian / eesti keel](/i18n/README.et.md) - [Finnish / Suomalainen](/i18n/README.fi.md) - [French / Français](/i18n/README.fr.md) - [German / Deutsch](/i18n/README.de.md) -- [Greek / Ελληνικά](/i18n/README.gr.md) +- [Greek / Ελληνικά](/i18n/README.el.md) +- [Gujarati / ગુજરાતી](/i18n/README.gu.md) - [Hebrew / עברית](/i18n/README.he.md) - [Hindi / हिंदी](/i18n/README.hi.md) - [Hungarian / Magyar](/i18n/README.hu.md) - [Nepali / नेपाली](/i18n/README.ne.md) - [Indonesian / Bahasa Indonesia](/i18n/README.id.md) -- [Italian / Italiano](/i18n/README.it.md) -- [Japanese / 日本語](/i18n/README.jp.md) +- [Italiano / Italian](/i18n/README.it.md) +- [Japanese / 日本語](/i18n/README.ja.md) - [Korean / 한국어](/i18n/README.ko.md) +- [Lithuanian / lietuvių](/i18n/README.lt.md) +- [Latvian / latviski](/i18n/README.lv.md) - [Malay / Bahasa Malaysia](/i18n/README.ms.md) -- [Norwegian (Bokmål) / Norsk (Bokmål)](/i18n/README.nb-no.md) +- [Norwegian (Bokmål) / Norsk (Bokmål)](/i18n/README.nb.md) - [Persian / فارسی](/i18n/README.fa.md) - [Polish / Polski](/i18n/README.pl.md) - [Portuguese / Português](/i18n/README.pt.md) @@ -238,6 +249,8 @@ Our approach for client libraries is modular. Each sub-library is a standalone i - [Russian / Pусский](/i18n/README.ru.md) - [Serbian / Srpski](/i18n/README.sr.md) - [Sinhala / සිංහල](/i18n/README.si.md) +- [Slovak / slovenský](/i18n/README.sk.md) +- [Slovenian / Slovenščina](/i18n/README.sl.md) - [Spanish / Español](/i18n/README.es.md) - [Simplified Chinese / 简体中文](/i18n/README.zh-cn.md) - [Swedish / Svenska](/i18n/README.sv.md) diff --git a/apps/docs/code-hike.theme.json b/apps/docs/code-hike.theme.json new file mode 100644 index 00000000000..f5b66a261b0 --- /dev/null +++ b/apps/docs/code-hike.theme.json @@ -0,0 +1,361 @@ +{ + "name": "supabase", + "type": "from-css", + "tokenColors": [ + { + "scope": ["comment", "punctuation.definition.comment", "string.comment"], + "settings": { + "foreground": "var(--ch-1)" + } + }, + { + "scope": [ + "constant", + "entity.name.constant", + "variable.other.constant", + "variable.other.enummember", + "variable.language", + "entity" + ], + "settings": { + "foreground": "var(--ch-2)" + } + }, + { + "scope": ["entity.name", "meta.export.default", "meta.definition.variable"], + "settings": { + "foreground": "var(--ch-3)" + } + }, + { + "scope": [ + "variable.parameter.function", + "meta.jsx.children", + "meta.block", + "meta.tag.attributes", + "entity.name.constant", + "meta.object.member", + "meta.embedded.expression" + ], + "settings": { + "foreground": "var(--ch-4)" + } + }, + { + "scope": "entity.name.function", + "settings": { + "foreground": "var(--ch-5)" + } + }, + { + "scope": ["entity.name.tag", "support.class.component"], + "settings": { + "foreground": "var(--ch-6)" + } + }, + { + "scope": "keyword", + "settings": { + "foreground": "var(--ch-7)" + } + }, + { + "scope": ["storage", "storage.type"], + "settings": { + "foreground": "var(--ch-7)" + } + }, + { + "scope": ["storage.modifier.package", "storage.modifier.import", "storage.type.java"], + "settings": { + "foreground": "var(--ch-4)" + } + }, + { + "scope": ["string", "string punctuation.section.embedded source"], + "settings": { + "foreground": "var(--ch-8)" + } + }, + { + "scope": "support", + "settings": { + "foreground": "var(--ch-2)" + } + }, + { + "scope": "meta.property-name", + "settings": { + "foreground": "var(--ch-2)" + } + }, + { + "scope": "variable", + "settings": { + "foreground": "var(--ch-3)" + } + }, + { + "scope": "variable.other", + "settings": { + "foreground": "var(--ch-4)" + } + }, + { + "scope": "invalid.broken", + "settings": { + "foreground": "var(--ch-9)", + "fontStyle": "italic" + } + }, + { + "scope": "invalid.deprecated", + "settings": { + "foreground": "var(--ch-9)", + "fontStyle": "italic" + } + }, + { + "scope": "invalid.illegal", + "settings": { + "foreground": "var(--ch-9)", + "fontStyle": "italic" + } + }, + { + "scope": "invalid.unimplemented", + "settings": { + "foreground": "var(--ch-9)", + "fontStyle": "italic" + } + }, + { + "scope": "carriage-return", + "settings": { + "background": "var(--ch-7)", + "foreground": "var(--ch-10)", + "fontStyle": "italic underline" + } + }, + { + "scope": "message.error", + "settings": { + "foreground": "var(--ch-9)" + } + }, + { + "scope": "string variable", + "settings": { + "foreground": "var(--ch-2)" + } + }, + { + "scope": ["source.regexp", "string.regexp"], + "settings": { + "foreground": "var(--ch-8)" + } + }, + { + "scope": [ + "string.regexp.character-class", + "string.regexp constant.character.escape", + "string.regexp source.ruby.embedded", + "string.regexp string.regexp.arbitrary-repitition" + ], + "settings": { + "foreground": "var(--ch-8)" + } + }, + { + "scope": "string.regexp constant.character.escape", + "settings": { + "foreground": "var(--ch-6)", + "fontStyle": "bold" + } + }, + { + "scope": "support.constant", + "settings": { + "foreground": "var(--ch-2)" + } + }, + { + "scope": "support.variable", + "settings": { + "foreground": "var(--ch-2)" + } + }, + { + "scope": "support.type.property-name.json", + "settings": { + "foreground": "var(--ch-6)" + } + }, + { + "scope": "meta.module-reference", + "settings": { + "foreground": "var(--ch-2)" + } + }, + { + "scope": "punctuation.definition.list.begin.markdown", + "settings": { + "foreground": "var(--ch-3)" + } + }, + { + "scope": ["markup.heading", "markup.heading entity.name"], + "settings": { + "foreground": "var(--ch-2)", + "fontStyle": "bold" + } + }, + { + "scope": "markup.quote", + "settings": { + "foreground": "var(--ch-6)" + } + }, + { + "scope": "markup.italic", + "settings": { + "foreground": "var(--ch-4)", + "fontStyle": "italic" + } + }, + { + "scope": "markup.bold", + "settings": { + "foreground": "var(--ch-4)", + "fontStyle": "bold" + } + }, + { + "scope": ["markup.underline"], + "settings": { + "fontStyle": "underline" + } + }, + { + "scope": ["markup.strikethrough"], + "settings": { + "fontStyle": "strikethrough" + } + }, + { + "scope": "markup.inline.raw", + "settings": { + "foreground": "var(--ch-2)" + } + }, + { + "scope": ["markup.deleted", "meta.diff.header.from-file", "punctuation.definition.deleted"], + "settings": { + "background": "var(--ch-11)", + "foreground": "var(--ch-9)" + } + }, + { + "scope": ["punctuation.section.embedded"], + "settings": { + "foreground": "var(--ch-7)" + } + }, + { + "scope": ["markup.inserted", "meta.diff.header.to-file", "punctuation.definition.inserted"], + "settings": { + "background": "var(--ch-12)", + "foreground": "var(--ch-6)" + } + }, + { + "scope": ["markup.changed", "punctuation.definition.changed"], + "settings": { + "background": "var(--ch-13)", + "foreground": "var(--ch-3)" + } + }, + { + "scope": ["markup.ignored", "markup.untracked"], + "settings": { + "background": "var(--ch-2)", + "foreground": "var(--ch-14)" + } + }, + { + "scope": "meta.diff.range", + "settings": { + "foreground": "var(--ch-5)", + "fontStyle": "bold" + } + }, + { + "scope": "meta.diff.header", + "settings": { + "foreground": "var(--ch-2)" + } + }, + { + "scope": "meta.separator", + "settings": { + "foreground": "var(--ch-2)", + "fontStyle": "bold" + } + }, + { + "scope": "meta.output", + "settings": { + "foreground": "var(--ch-2)" + } + }, + { + "scope": [ + "brackethighlighter.tag", + "brackethighlighter.curly", + "brackethighlighter.round", + "brackethighlighter.square", + "brackethighlighter.angle", + "brackethighlighter.quote" + ], + "settings": { + "foreground": "var(--ch-15)" + } + }, + { + "scope": "brackethighlighter.unmatched", + "settings": { + "foreground": "var(--ch-9)" + } + }, + { + "scope": ["constant.other.reference.link", "string.other.link"], + "settings": { + "foreground": "var(--ch-8)", + "fontStyle": "underline" + } + } + ], + "colors": { + "editor.background": "var(--ch-16)", + "editor.foreground": "var(--ch-4)", + "editor.selectionBackground": "var(--ch-17)", + "editor.infoForeground": "var(--ch-18)", + "editor.rangeHighlightBackground": "var(--ch-19)", + "editorLineNumber.foreground": "var(--ch-20)", + "tab.activeBackground": "var(--ch-16)", + "tab.inactiveBackground": "var(--ch-21)", + "tab.activeForeground": "var(--ch-4)", + "tab.inactiveForeground": "var(--ch-15)", + "tab.border": "var(--ch-22)", + "tab.activeBorder": "var(--ch-16)", + "tab.activeBorderTop": "var(--ch-23)", + "tab.hoverBackground": "var(--ch-16)", + "tab.hoverForeground": "var(--ch-15)", + "editorGroupHeader.tabsBorder": "var(--ch-22)", + "editorGroupHeader.tabsBackground": "var(--ch-21)", + "list.inactiveSelectionBackground": "var(--ch-24)", + "list.inactiveSelectionForeground": "var(--ch-4)", + "list.hoverBackground": "var(--ch-25)", + "list.hoverForeground": "var(--ch-4)" + } +} diff --git a/apps/docs/codeHikeTheme.js b/apps/docs/codeHikeTheme.js deleted file mode 100644 index e34b5b89779..00000000000 --- a/apps/docs/codeHikeTheme.js +++ /dev/null @@ -1,372 +0,0 @@ -module.exports = { - name: 'Stripe Docs Blue', - type: 'dark', - colors: { - 'editor.background': '#232323', - 'editor.foreground': '#fafafa', - 'activityBar.background': 'var(--colors-scale2)', - 'sideBar.background': 'yellow', - 'editorGroupHeader.tabsBackground': 'var(--colors-scale2)', - 'sideBarSectionHeader.background': 'var(--colors-scale2)', - 'tab.activeBackground': 'var(--colors-scale3)', - 'tab.inactiveBackground': 'var(--colors-scale2)', - 'tab.border': 'var(--colors-scale2)', - 'input.background': '#ffffff1a', - 'panel.background': '#1A2652', - 'panel.border': '#1A2652', - 'editorWidget.background': '#0d0f2b', - 'editorWidget.foreground': '#ffffff4d', - 'editorWidget.border': 'var(--colors-scale5)', - 'list.hoverBackground': '#ffffff1a', - 'list.activeSelectionBackground': '#ffffff1a', - 'list.inactiveSelectionBackground': '#ffffff1a', - 'editor.hoverHighlightBackground': '#ffffff1a', - 'editor.selectionHighlightBackground': '#ffffff1a', - 'activityBarBadge.background': 'yellow', - 'sideBarTitle.foreground': 'var(--colors-scale2)', - 'statusBar.background': 'var(--colors-scale2)', - }, - tokenColors: [ - { - name: 'Comment', - scope: ['comment', 'punctuation.definition.comment'], - settings: { - foreground: '#a3acb9', - fontStyle: '', - }, - }, - { - name: 'Variables', - scope: ['source', 'variable', 'variable.other.object', 'string constant.other.placeholder'], - settings: { - foreground: '#f5fbff', - }, - }, - { - name: 'Colors', - scope: ['variable.other.constant', 'constant.other.color'], - settings: { - foreground: '#ffffff', - fontStyle: 'bold', - }, - }, - { - name: 'Invalid', - scope: ['invalid', 'invalid.illegal'], - settings: { - foreground: '#FF5370', - }, - }, - { - name: 'Keyword, Storage', - scope: ['keyword', 'storage.type', 'storage.modifier'], - settings: { - foreground: '#98C1FE', - fontStyle: 'bold', - }, - }, - - { - name: 'Function', - scope: ['entity.name.function'], - settings: { - foreground: '#7fd3ed', - fontStyle: 'bold', - }, - }, - - { - name: 'Tag', - scope: ['entity.name.tag', 'meta.tag.sgml', 'markup.deleted.git_gutter'], - settings: { - foreground: '#98C1FE', - fontStyle: 'bold', - }, - }, - - { - name: 'Parameter, Property', - scope: [ - 'variable.parameter', - 'variable.other.object.property', - 'variable.other.property', - 'keyword.other.unit', - 'keyword.other', - ], - settings: { - foreground: '#F2AFE3', - }, - }, - { - name: 'Number, Constant, Function Argument, Tag Attribute, Embedded', - scope: [ - 'constant.numeric', - 'constant.language', - 'support.constant', - 'constant.character', - 'constant.escape', - ], - settings: { - foreground: '#f8b886', - }, - }, - { - name: 'String, Symbols, Inherited Class, Markup Heading', - scope: [ - 'string', - 'constant.other.symbol', - 'constant.other.key', - 'entity.other.inherited-class', - 'markup.heading', - 'markup.inserted.git_gutter', - 'meta.group.braces.curly constant.other.object.key.js string.unquoted.label.js', - ], - settings: { - foreground: '#85d99e', - }, - }, - { - name: 'Entity Types', - scope: ['support.type'], - settings: { - foreground: '#B2CCD6', - }, - }, - { - name: 'CSS Class and Support', - scope: [ - 'source.css support.type.property-name', - 'source.sass support.type.property-name', - 'source.scss support.type.property-name', - 'source.less support.type.property-name', - 'source.stylus support.type.property-name', - 'source.postcss support.type.property-name', - ], - settings: { - foreground: '#B2CCD6', - }, - }, - { - name: 'Language methods', - scope: ['variable.language'], - settings: { - fontStyle: 'italic', - foreground: '#FF5370', - }, - }, - - { - name: 'Attributes', - scope: ['entity.other.attribute-name'], - settings: { - foreground: '#98C1FE', - fontStyle: 'italic', - }, - }, - { - name: 'Inserted', - scope: ['markup.inserted'], - settings: { - foreground: '#C3E88D', - }, - }, - { - name: 'Deleted', - scope: ['markup.deleted'], - settings: { - foreground: '#FF5370', - }, - }, - { - name: 'Changed', - scope: ['markup.changed'], - settings: { - foreground: '#C792EA', - }, - }, - { - name: 'Regular Expressions', - scope: ['string.regexp'], - settings: { - foreground: '#89DDFF', - }, - }, - { - name: 'Escape Characters', - scope: ['constant.character.escape'], - settings: { - foreground: '#89DDFF', - }, - }, - { - name: 'URL', - scope: ['*url*', '*link*', '*uri*'], - settings: { - fontStyle: 'underline', - }, - }, - { - name: 'ES7 Bind Operator', - scope: ['source.js constant.other.object.key.js string.unquoted.label.js'], - settings: { - fontStyle: 'italic', - foreground: '#FF5370', - }, - }, - - { - name: 'Markdown - Plain', - scope: ['text.html', 'punctuation.definition.list_item'], - settings: { - foreground: '#f5fbff', - }, - }, - { - name: 'Markdown - Markup Raw Inline', - scope: ['text.html.markdown markup.inline.raw.markdown'], - settings: { - foreground: '#C792EA', - }, - }, - { - name: 'Markdown - Markup Raw Inline Punctuation', - scope: ['text.html.markdown markup.inline.raw.markdown punctuation.definition.raw.markdown'], - settings: { - foreground: '#65737E', - }, - }, - { - name: 'Markdown - Heading', - scope: [ - 'markdown.heading', - 'markup.heading | markup.heading entity.name', - 'markup.heading.markdown punctuation.definition.heading.markdown', - ], - settings: { - foreground: '#C3E88D', - }, - }, - { - name: 'Markup - Italic', - scope: ['markup.italic'], - settings: { - fontStyle: 'italic', - foreground: '#f07178', - }, - }, - { - name: 'Markup - Bold', - scope: ['markup.bold', 'markup.bold string'], - settings: { - fontStyle: 'bold', - foreground: '#f07178', - }, - }, - { - name: 'Markup - Bold-Italic', - scope: [ - 'markup.bold markup.italic', - 'markup.italic markup.bold', - 'markup.quote markup.bold', - 'markup.bold markup.italic string', - 'markup.italic markup.bold string', - 'markup.quote markup.bold string', - ], - settings: { - fontStyle: 'bold', - foreground: '#f07178', - }, - }, - { - name: 'Markup - Underline', - scope: ['markup.underline'], - settings: { - fontStyle: 'underline', - foreground: '#F78C6C', - }, - }, - { - name: 'Markdown - Blockquote', - scope: ['markup.quote punctuation.definition.blockquote.markdown'], - settings: { - foreground: '#65737E', - }, - }, - { - name: 'Markup - Quote', - scope: ['markup.quote'], - settings: { - fontStyle: 'italic', - }, - }, - - { - name: 'Markdown - Link Description', - scope: ['string.other.link.description.title.markdown'], - settings: { - foreground: '#C792EA', - }, - }, - { - name: 'Markdown - Link Anchor', - scope: ['constant.other.reference.link.markdown'], - settings: { - foreground: '#FFCB6B', - }, - }, - { - name: 'Markup - Raw Block', - scope: ['markup.raw.block'], - settings: { - foreground: '#C792EA', - }, - }, - { - name: 'Markdown - Raw Block Fenced', - scope: ['markup.raw.block.fenced.markdown'], - settings: { - foreground: '#00000050', - }, - }, - { - name: 'Markdown - Fenced Bode Block', - scope: ['punctuation.definition.fenced.markdown'], - settings: { - foreground: '#00000050', - }, - }, - { - name: 'Markdown - Fenced Bode Block Variable', - scope: [ - 'markup.raw.block.fenced.markdown', - 'variable.language.fenced.markdown', - 'punctuation.section.class.end', - ], - settings: { - foreground: '#EEFFFF', - }, - }, - { - name: 'Markdown - Fenced Language', - scope: ['variable.language.fenced.markdown'], - settings: { - foreground: '#65737E', - }, - }, - { - name: 'Markdown - Separator', - scope: ['meta.separator'], - settings: { - fontStyle: 'bold', - foreground: '#65737E', - }, - }, - { - name: 'Markup - Table', - scope: ['markup.table'], - settings: { - foreground: '#EEFFFF', - }, - }, - ], -} diff --git a/apps/docs/components/MDX/storage_management.mdx b/apps/docs/components/MDX/storage_management.mdx index 1eebf7cb575..e8f0184b88e 100644 --- a/apps/docs/components/MDX/storage_management.mdx +++ b/apps/docs/components/MDX/storage_management.mdx @@ -11,7 +11,7 @@ Enable the [http extension for the `extensions` schema](https://app.supabase.com Then, define the following SQL functions in the SQL Editor to delete storage objects via the API: -```SQL +```sql create or replace function delete_storage_object(bucket text, object text, out status int, out content text) returns record language 'plpgsql' @@ -52,7 +52,7 @@ $$; Next, add a trigger that removes any obsolete avatar whenever the profile is updated or deleted: -```SQL +```sql create or replace function delete_old_avatar() returns trigger language 'plpgsql' @@ -89,7 +89,7 @@ Finally, delete the `public.profile` row before a user is deleted. If this step is omitted, you won't be able to delete users without first manually deleting their avatar image. -```SQL +```sql create or replace function delete_old_profile() returns trigger language 'plpgsql' diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index ea64f7fa165..e22da537402 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -942,6 +942,10 @@ export const ai: NavMenuConstant = { name: 'Google Colab', url: '/guides/ai/google-colab', }, + { + name: 'LlamaIndex', + url: '/guides/ai/integrations/llamaindex', + }, ], }, ], diff --git a/apps/docs/components/index.tsx b/apps/docs/components/index.tsx index 92b92392a38..5a50214c12e 100644 --- a/apps/docs/components/index.tsx +++ b/apps/docs/components/index.tsx @@ -22,7 +22,7 @@ import QuickstartIntro from './MDX/quickstart_intro.mdx' import SocialProviderSettingsSupabase from './MDX/social_provider_settings_supabase.mdx' import SocialProviderSetup from './MDX/social_provider_setup.mdx' import StorageManagement from './MDX/storage_management.mdx' -// import { CH } from '@code-hike/mdx/components' +import { CH } from '@code-hike/mdx/components' import RefHeaderSection from './reference/RefHeaderSection' // Ref version specific @@ -59,6 +59,7 @@ const components = { Admonition, Button, ButtonCard, + CH, CodeBlock, GlassPanel, Link, diff --git a/apps/docs/docs/ref/javascript/v1/upgrade-guide.mdx b/apps/docs/docs/ref/javascript/v1/upgrade-guide.mdx index d13e0311cae..d031f4a0778 100644 --- a/apps/docs/docs/ref/javascript/v1/upgrade-guide.mdx +++ b/apps/docs/docs/ref/javascript/v1/upgrade-guide.mdx @@ -41,7 +41,7 @@ _Optionally_ if you are using custom configuration with `createClient` then foll > -```ts title=src/supabaseClient.ts +```ts src/supabaseClient.ts const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, { schema: 'custom', persistSession: false, @@ -51,7 +51,7 @@ const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, { -```ts title=src/supabaseClient.ts +```ts src/supabaseClient.ts const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, { db: { schema: 'custom', diff --git a/apps/docs/docs/reference/dart/initializing.mdx b/apps/docs/docs/reference/dart/initializing.mdx index ba76c217cce..80c69925a6a 100644 --- a/apps/docs/docs/reference/dart/initializing.mdx +++ b/apps/docs/docs/reference/dart/initializing.mdx @@ -10,7 +10,7 @@ For `supabase-flutter`, you will be using the static `initialize()` method on `S ### Flutter `initialize()` -```dart title=main.dart +```dart main.dart Future main() async { await Supabase.initialize(url: 'https://xyzcompany.supabase.co', anonKey: 'public-anon-key'); runApp(MyApp()); @@ -30,7 +30,7 @@ final supabase = Supabase.instance.client; You can pass `headers` to initialize your Supabase client with customer headers. Here is an example of passing a custom auth header to Supabase client. -```dart title=main.dart +```dart main.dart Future main() async { await Supabase.initialize( url: 'https://xyzcompany.supabase.co', diff --git a/apps/docs/lib/mdx/generateRefMarkdown.tsx b/apps/docs/lib/mdx/generateRefMarkdown.tsx index 5d959560ade..790591280f7 100644 --- a/apps/docs/lib/mdx/generateRefMarkdown.tsx +++ b/apps/docs/lib/mdx/generateRefMarkdown.tsx @@ -1,7 +1,9 @@ import fs from 'fs' +import { CodeHikeConfig, remarkCodeHike } from '@code-hike/mdx' import matter from 'gray-matter' import { serialize } from 'next-mdx-remote/serialize' +import codeHikeTheme from '~/code-hike.theme.json' assert { type: 'json' } import { ICommonMarkdown } from '~/components/reference/Reference.types' async function generateRefMarkdown(sections: ICommonMarkdown[], slug: string) { @@ -31,6 +33,14 @@ async function generateRefMarkdown(sections: ICommonMarkdown[], slug: string) { const fileContents = markdownExists ? fs.readFileSync(pathName, 'utf8') : '' const { data, content } = matter(fileContents) + const codeHikeOptions: CodeHikeConfig = { + theme: codeHikeTheme, + lineNumbers: true, + showCopyButton: true, + skipLanguages: [], + autoImport: false, + } + markdownContent.push({ id: section.id, title: section.title, @@ -41,8 +51,8 @@ async function generateRefMarkdown(sections: ICommonMarkdown[], slug: string) { // MDX's available options, see the MDX docs for more info. // https://mdxjs.com/packages/mdx/#compilefile-options mdxOptions: { - // remarkPlugins: [[remarkCodeHike, { autoImport: false, theme }]], useDynamicImport: true, + remarkPlugins: [[remarkCodeHike, codeHikeOptions]], }, // Indicates whether or not to parse the frontmatter from the mdx source }) diff --git a/apps/docs/next.config.mjs b/apps/docs/next.config.mjs index 43c8c63d4eb..d6f21fa8b8b 100644 --- a/apps/docs/next.config.mjs +++ b/apps/docs/next.config.mjs @@ -2,22 +2,18 @@ import nextMdx from '@next/mdx' import remarkGfm from 'remark-gfm' import rehypeSlug from 'rehype-slug' - -//import theme from 'shiki/themes/nord.json' assert { type: 'json' } +import { remarkCodeHike } from '@code-hike/mdx' import withTM from 'next-transpile-modules' import withYaml from 'next-plugin-yaml' import configureBundleAnalyzer from '@next/bundle-analyzer' +import codeHikeTheme from './code-hike.theme.json' assert { type: 'json' } + const withBundleAnalyzer = configureBundleAnalyzer({ enabled: process.env.ANALYZE === 'true', }) -// import admonitions from 'remark-admonitions' - -// import { remarkCodeHike } from '@code-hike/mdx' -// import codeHikeTheme from './codeHikeTheme.js' - /** * Rewrites and redirects are handled by * apps/www nextjs config @@ -29,20 +25,18 @@ const withMDX = nextMdx({ extension: /\.mdx?$/, options: { remarkPlugins: [ - // [ - // remarkCodeHike, - // { - // theme: codeHikeTheme, - // autoImport: false, - // lineNumbers: true, - // showCopyButton: true, - // }, - // ], + [ + remarkCodeHike, + { + theme: codeHikeTheme, + lineNumbers: true, + showCopyButton: true, + }, + ], remarkGfm, ], rehypePlugins: [rehypeSlug], - // This is required for `MDXProvider` component - // providerImportSource: '@mdx-js/react', + providerImportSource: '@mdx-js/react', }, }) diff --git a/apps/docs/package.json b/apps/docs/package.json index 08ec7066021..5d79d59dddd 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -45,6 +45,7 @@ "dependencies": { "@algolia/autocomplete-js": "^1.7.2", "@algolia/autocomplete-plugin-recent-searches": "^1.7.2", + "@code-hike/mdx": "^0.8.3", "@docsearch/react": "^3.3.0", "@mdx-js/loader": "^2.1.5", "@mdx-js/react": "^2.1.5", diff --git a/apps/docs/pages/_app.tsx b/apps/docs/pages/_app.tsx index 81b415e063c..2132434d66b 100644 --- a/apps/docs/pages/_app.tsx +++ b/apps/docs/pages/_app.tsx @@ -1,7 +1,7 @@ import '../../../packages/ui/build/css/themes/light.css' import '../../../packages/ui/build/css/themes/dark.css' -import '../styles/ch.scss' +import '../styles/code-hike.scss' import '../styles/main.scss?v=1.0.0' import '../styles/new-docs.scss' import '../styles/prism-okaidia.scss' diff --git a/apps/docs/pages/guides/ai.mdx b/apps/docs/pages/guides/ai.mdx index 7f4c2b481a9..c7df810f3fc 100644 --- a/apps/docs/pages/guides/ai.mdx +++ b/apps/docs/pages/guides/ai.mdx @@ -88,19 +88,24 @@ export const integrations = [ name: 'OpenAI', description: 'OpenAI is an AI research and deployment company. Supabase provides a simple way to use OpenAI in your applications.', - href: '/docs/guides/ai/examples/building-chatgpt-plugins', + href: '/guides/ai/examples/building-chatgpt-plugins', }, { name: 'Hugging Face', description: "Hugging Face is an open-source provider of NLP technologies. Supabase provides a simple way to use Hugging Face's models in your applications.", - href: '/docs/guides/ai/hugging-face', + href: '/guides/ai/hugging-face', }, { name: 'LangChain', description: 'LangChain is a language-agnostic, open-source, and self-hosted API for text translation, summarization, and sentiment analysis.', - href: '/docs/guides/ai/langchain', + href: '/guides/ai/langchain', + }, + { + name: 'LlamaIndex', + description: 'LlamaIndex is a data framework for your LLM applications.', + href: '/guides/ai/integrations/llamaindex', }, ] diff --git a/apps/docs/pages/guides/ai/integrations/llamaindex.mdx b/apps/docs/pages/guides/ai/integrations/llamaindex.mdx new file mode 100644 index 00000000000..59db32b9cd3 --- /dev/null +++ b/apps/docs/pages/guides/ai/integrations/llamaindex.mdx @@ -0,0 +1,66 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'ai-integration-llamaindex', + title: + 'Learn how to integrate Supabase with LlamaIndex, a data framework for your LLM applications.', + subtitle: + 'Learn how to integrate Supabase with LlamaIndex, a data framework for your LLM applications.', + breadcrumb: 'AI Integrations', +} + +This guide will walk you through a basic example using the LlamaIndex [SupabaseVectorStore](https://github.com/supabase/supabase/blob/master/examples/ai/llamaindex/llamaindex.ipynb). + + + +## Launching a notebook + +Launch our [LlamaIndex](https://github.com/supabase/supabase/blob/master/examples/ai/llamaindex/llamaindex.ipynb) notebook in Colab: + + + + + +At the top of the notebook, you'll see a button `Copy to Drive`. Click this button to copy the notebook to your Google Drive. + +## Fill in your OpenAI credentials + +Inside the Notebook, add your `OPENAI_API_KEY` key. Find the cell which contains this code: + +```py +import os +os.environ['OPENAI_API_KEY'] = "[your_openai_api_key]" +``` + +## Connecting to your database + +Inside the Notebook, find the cell which specifies the `DB_CONNECTION`. It will contain some code like this: + +```python +DB_CONNECTION = "postgresql://:@:/" + +# create vector store client +vx = vecs.create_client(DB_CONNECTION) +``` + +Replace the `DB_CONNECTION` with your own connection string for your database, which you set up in first step of this guide. + +## Stepping through the notebook + +Now all that's left is to step through the notebook. You can do this by clicking the "execute" button (`ctrl+enter`) at the top left of each code cell. The notebook guides you through the process of creating a collection, adding data to it, and querying it. + +You can view the inserted items in the [Table Editor](https://app.supabase.com/project/_/editor/), by selecting the `vecs` schema from the schema dropdown. + +![Colab documents](/docs/img/ai/google-colab/colab-documents.png) + +## Resources + +- Visit the LlamaIndex + SupabaseVectorStore [docs](https://gpt-index.readthedocs.io/en/latest/examples/vector_stores/SupabaseVectorIndexDemo.html) +- Visit the official LlamaIndex [repo](https://github.com/jerryjliu/llama_index/) + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/ai/python/[slug].tsx b/apps/docs/pages/guides/ai/python/[slug].tsx index 86a025efe2d..b52312424d7 100644 --- a/apps/docs/pages/guides/ai/python/[slug].tsx +++ b/apps/docs/pages/guides/ai/python/[slug].tsx @@ -1,9 +1,11 @@ +import { CodeHikeConfig, remarkCodeHike } from '@code-hike/mdx' import { GetStaticPaths, GetStaticProps } from 'next' import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote' import { serialize } from 'next-mdx-remote/serialize' -import { join, relative } from 'path' +import { relative } from 'path' import rehypeSlug from 'rehype-slug' import remarkGfm from 'remark-gfm' +import codeHikeTheme from '~/code-hike.theme.json' assert { type: 'json' } import components from '~/components' import Layout from '~/layouts/DefaultGuideLayout' import { UrlTransformFunction, linkTransform } from '~/lib/mdx/plugins/rehypeLinkTransform' @@ -116,9 +118,25 @@ export const getStaticProps: GetStaticProps = async ({ pa } } + const codeHikeOptions: CodeHikeConfig = { + theme: codeHikeTheme, + lineNumbers: true, + showCopyButton: true, + skipLanguages: [], + autoImport: false, + } + const mdxSource = await serialize(source, { + scope: { + chCodeConfig: codeHikeOptions, + }, mdxOptions: { - remarkPlugins: [remarkGfm, remarkMkDocsAdmonition, [removeTitle, meta.title]], + remarkPlugins: [ + remarkGfm, + remarkMkDocsAdmonition, + [removeTitle, meta.title], + [remarkCodeHike, codeHikeOptions], + ], rehypePlugins: [[linkTransform, urlTransform], rehypeSlug], }, }) diff --git a/apps/docs/pages/guides/auth/auth-captcha.mdx b/apps/docs/pages/guides/auth/auth-captcha.mdx index b2546a5c7b6..59f0d901318 100644 --- a/apps/docs/pages/guides/auth/auth-captcha.mdx +++ b/apps/docs/pages/guides/auth/auth-captcha.mdx @@ -90,7 +90,7 @@ const [captchaToken, setCaptchaToken] = useState() Now lets add the HCaptcha component to the JSX section of our code -```html +```jsx ``` diff --git a/apps/docs/pages/guides/auth/auth-helpers/auth-ui.mdx b/apps/docs/pages/guides/auth/auth-helpers/auth-ui.mdx index 9544beb30fe..8256063a9b8 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/auth-ui.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/auth-ui.mdx @@ -25,7 +25,7 @@ npm install @supabase/supabase-js @supabase/auth-ui-react @supabase/auth-ui-shar Pass `supabaseClient` from `@supabase/supabase-js` as a prop to the component. -```js title=/src/index.js +```js /src/index.js import { createClient } from '@supabase/supabase-js' import { Auth } from '@supabase/auth-ui-react' @@ -38,7 +38,7 @@ This renders the Auth component without any styling. We recommend using one of the predefined themes to style the UI. Import the theme you want to use and pass it to the `appearance.theme` prop. -```js lines=4,16 title=/src/index.js +```js mark=4,16 /src/index.js import { Auth } from '@supabase/auth-ui-react' import { // Import predefined theme @@ -63,7 +63,7 @@ const App = () => ( The Auth component also supports login with [official social providers](../../auth#providers). -```js lines=13 title=/src/index.js +```js mark=11 /src/index.js import { createClient } from '@supabase/supabase-js' import { Auth } from '@supabase/auth-ui-react' import { ThemeSupa } from '@supabase/auth-ui-shared' @@ -81,18 +81,19 @@ const App = () => ( ### Options -Options are available via 'queryParams': +Options are available via `queryParams`: -``` +```jsx + ``` ### Supported Views @@ -122,7 +123,7 @@ There are several ways to customize Auth UI: Auth UI comes with several themes to customize the appearance. Each predefined theme comes with at least two variations, a `default` variation, and a `dark` variation. You can switch between these themes using the `theme` prop. Import the theme you want to use and pass it to the `appearance.theme` prop. -```js lines=2,13 title=/src/index.js +```js mark=3,14 /src/index.js import { createClient } from '@supabase/supabase-js' import { Auth } from '@supabase/auth-ui-react' import { ThemeSupa } from '@supabase/auth-ui-shared' @@ -151,7 +152,7 @@ Currently there is only one predefined theme available, but we plan to add more. Auth UI comes with two theme variations: `default` and `dark`. You can switch between these themes with the `theme` prop. -```js lines=14 title=/src/index.js +```js mark=15 /src/index.js import { createClient } from '@supabase/supabase-js' import { Auth } from '@supabase/auth-ui-react' import { ThemeSupa } from '@supabase/auth-ui-shared' @@ -177,7 +178,7 @@ If you don't pass a value to `theme` it uses the `"default"` theme. You can pass Auth UI themes can be overridden using variable tokens. See the [list of variable tokens](https://github.com/supabase/auth-ui/blob/main/packages/shared/src/theming/Themes.ts). -```js lines=14-21 title=/src/index.js +```js mark=12:19 /src/index.js import { createClient } from '@supabase/supabase-js' import { Auth } from '@supabase/auth-ui-react' import { ThemeSupa } from '@supabase/auth-ui-shared' @@ -209,7 +210,7 @@ If you created your own theme, you may not need to override any of the them. You can create your own theme by following the same structure within a `appearance.theme` property. See the list of [tokens within a theme](https://github.com/supabase/auth-ui/blob/main/packages/shared/src/theming/Themes.ts). -```js title=/src/index.js +```js /src/index.js import { createClient } from '@supabase/supabase-js' import { Auth } from '@supabase/auth-ui-react' @@ -259,7 +260,7 @@ You can swich between different variations of your theme with the ["theme" prop] You can use custom CSS classes for the following elements: `"button"`, `"container"`, `"anchor"`, `"divider"`, `"label"`, `"input"`, `"loader"`, `"message"`. -```js title=/src/index.js +```js /src/index.js import { createClient } from '@supabase/supabase-js' import { Auth } from '@supabase/auth-ui-react' @@ -287,7 +288,7 @@ const App = () => ( You can use custom CSS inline styles for the following elements: `"button"`, `"container"`, `"anchor"`, `"divider"`, `"label"`, `"input"`, `"loader"`, `"message"`. -```js title=/src/index.js +```js /src/index.js import { createClient } from '@supabase/supabase-js' import { Auth } from '@supabase/auth-ui-react' @@ -311,7 +312,7 @@ const App = () => ( You can use custom labels with `localization.variables`. See the [list of labels](https://github.com/supabase/auth-ui/blob/main/packages/shared/src/localization/en.json) that can be overwritten. -```js title=/src/index.js +```js mark=10:15 /src/index.js import { createClient } from '@supabase/supabase-js' import { Auth } from '@supabase/auth-ui-react' @@ -320,7 +321,6 @@ const supabase = createClient('', ' ( ( }, }, }} - //highlight-end /> ) ``` diff --git a/apps/docs/pages/guides/auth/auth-helpers/nextjs-pages.mdx b/apps/docs/pages/guides/auth/auth-helpers/nextjs-pages.mdx index 1063118b065..11d7f549a42 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/nextjs-pages.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/nextjs-pages.mdx @@ -62,7 +62,7 @@ yarn add @supabase/auth-helpers-react Retrieve your project URL and anon key in your project's [API settings](https://app.supabase.com/project/_/settings/api) in the Dashboard to set up the following environment variables. For local development you can set them in a `.env.local` file. See an [example](https://github.com/supabase/auth-helpers/blob/main/examples/nextjs/.env.local.example). -```bash title=.env.local +```bash .env.local NEXT_PUBLIC_SUPABASE_URL=your-supabase-url NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key ``` @@ -79,7 +79,7 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key Wrap your `pages/_app.js` component with the `SessionContextProvider` component: -```jsx title=pages/_app.js +```jsx pages/_app.js import { createPagesBrowserClient } from '@supabase/auth-helpers-nextjs' import { SessionContextProvider } from '@supabase/auth-helpers-react' import { useState } from 'react' @@ -104,7 +104,7 @@ function MyApp({ Component, pageProps }) { Wrap your `pages/_app.tsx` component with the `SessionContextProvider` component: -```tsx lines=2,8 title=pages/_app.tsx +```tsx mark=2,8 pages/_app.tsx import { createPagesBrowserClient } from '@supabase/auth-helpers-nextjs' import { SessionContextProvider, Session } from '@supabase/auth-helpers-react' import { useState } from 'react' @@ -148,7 +148,7 @@ The `Code Exchange` API route is required for the [server-side auth flow](https: Create a new file at `pages/api/auth/callback.js` and populate with the following: -```jsx title="pages/api/auth/callback.js" +```jsx pages/api/auth/callback.js import { NextApiHandler } from 'next' import { createPagesServerClient } from '@supabase/auth-helpers-nextjs' @@ -172,7 +172,7 @@ export default handler Create a new file at `pages/api/auth/callback.ts` and populate with the following: -```tsx title="pages/api/auth/callback.ts" +```tsx pages/api/auth/callback.ts import { NextApiHandler } from 'next' import { createPagesServerClient } from '@supabase/auth-helpers-nextjs' @@ -242,7 +242,7 @@ export default async (req: NextApiRequest, res: NextApiResponse) => { For [row level security](/docs/learn/auth-deep-dive/auth-row-level-security) to work properly when fetching data client-side, you need to make sure to use the `supabaseClient` from the `useSupabaseClient` hook and only run your query once the user is defined client-side in the `useUser()` hook: -```jsx lines=10-17 +```jsx mark=10:17 import { Auth } from '@supabase/auth-ui-react' import { ThemeSupa } from '@supabase/auth-ui-shared' import { useUser, useSupabaseClient } from '@supabase/auth-helpers-react' @@ -291,7 +291,7 @@ export default LoginPage Create a server supabase client to retrieve the logged in user's session: -```jsx title=pages/profile.js +```jsx pages/profile.js import { createPagesServerClient } from '@supabase/auth-helpers-nextjs' export default function Profile({ user }) { @@ -553,7 +553,7 @@ Create a server supabase client to retrieve the logged in user's session: > -```jsx title=pages/api/protected-route.js +```jsx pages/api/protected-route.js import { createPagesServerClient } from '@supabase/auth-helpers-nextjs' const ProtectedRoute = async (req, res) => { @@ -581,7 +581,7 @@ export default ProtectedRoute -```tsx title=pages/api/protected-route.ts +```tsx pages/api/protected-route.ts import { NextApiHandler } from 'next' import { createPagesServerClient } from '@supabase/auth-helpers-nextjs' @@ -614,7 +614,7 @@ export default ProtectedRoute As an alternative to protecting individual pages you can use a [Next.js Middleware](https://nextjs.org/docs/middleware) to protect the entire directory or those that match the config object. In the following example, all requests to `/middleware-protected/*` will check whether a user is signed in, if successful the request will be forwarded to the destination route, otherwise the user will be redirected: -```ts title=middleware.ts +```ts middleware.ts import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs' import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' @@ -698,7 +698,7 @@ Use `createPagesServerClient` within your `NextApiHandler`: > -```tsx title=pages/api/protected-route.ts +```tsx pages/api/protected-route.ts import { withApiAuth } from '@supabase/auth-helpers-nextjs' export default withApiAuth(async function ProtectedRoute(req, res, supabase) { @@ -711,7 +711,7 @@ export default withApiAuth(async function ProtectedRoute(req, res, supabase) { -```tsx title=pages/api/protected-route.ts +```tsx pages/api/protected-route.ts import { NextApiHandler } from 'next' import { createPagesServerClient } from '@supabase/auth-helpers-nextjs' @@ -752,7 +752,7 @@ Use `createPagesServerClient` within `getServerSideProps`: > -```tsx title=pages/profile.tsx +```tsx pages/profile.tsx import { withPageAuth, User } from '@supabase/auth-helpers-nextjs' export default function Profile({ user }: { user: User }) { @@ -765,7 +765,7 @@ export const getServerSideProps = withPageAuth({ redirectTo: '/' }) -```tsx title=pages/profile.js +```tsx pages/profile.js import { createPagesServerClient, User } from '@supabase/auth-helpers-nextjs' import { GetServerSidePropsContext } from 'next' @@ -811,7 +811,7 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { > -```tsx title=middleware.ts +```tsx middleware.ts import { withMiddlewareAuth } from '@supabase/auth-helpers-nextjs' export const middleware = withMiddlewareAuth({ @@ -832,7 +832,7 @@ export const config = { -```tsx title=middleware.ts +```tsx middleware.ts import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs' import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' diff --git a/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx b/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx index 4ffafc921aa..d783f804feb 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx @@ -43,7 +43,7 @@ yarn add @supabase/auth-helpers-nextjs Retrieve your project's URL and anon key from your [API settings](https://app.supabase.com/project/_/settings/api), and create a `.env.local` file with the following environment variables: -```bash title=".env.local" +```bash .env.local NEXT_PUBLIC_SUPABASE_URL=your-supabase-url NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key ``` @@ -62,7 +62,7 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key Create a new `middleware.js` file in the root of your project and populate with the following: -```jsx title="middleware.js" +```jsx middleware.js import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs' import { NextResponse } from 'next/server' @@ -80,7 +80,7 @@ export async function middleware(req) { Create a new `middleware.ts` file in the root of your project and populate with the following: -```tsx title="middleware.ts" +```tsx middleware.ts import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs' import { NextResponse } from 'next/server' @@ -116,7 +116,7 @@ The `Code Exchange` route is required for the [server-side auth flow](https://su Create a new file at `app/auth/callback/route.js` and populate with the following: -```jsx title="app/auth/callback.route.js" +```jsx app/auth/callback/route.js import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs' import { cookies } from 'next/headers' import { NextResponse } from 'next/server' @@ -141,7 +141,7 @@ export async function GET(request) { Create a new file at `app/auth/callback/route.ts` and populate with the following: -```tsx title="app/auth/callback.route.ts" +```tsx app/auth/callback/route.ts import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs' import { cookies } from 'next/headers' import { NextResponse } from 'next/server' @@ -186,7 +186,7 @@ Client Components can be used to trigger the authentication process from event h > -```jsx title="app/login.js" +```jsx app/login.js 'use client' import { createClientComponentClient } from '@supabase/auth-helpers-nextjs' @@ -244,7 +244,7 @@ export default function Login() { -```tsx title="app/login.ts" +```tsx app/login.ts 'use client' import { createClientComponentClient } from '@supabase/auth-helpers-nextjs' @@ -319,7 +319,7 @@ The combination of [Server Components](https://nextjs.org/docs/getting-started/r > -```jsx title="app/login.js" +```jsx app/login.js import { createServerActionClient } from '@supabase/auth-helpers-nextjs' import { revalidatePath } from 'next/cache' import { cookies } from 'next/headers' @@ -379,7 +379,7 @@ export default async function Login() { -```tsx title="app/login.ts" +```tsx app/login.ts import { createServerActionClient } from '@supabase/auth-helpers-nextjs' import { revalidatePath } from 'next/cache' import { cookies } from 'next/headers' @@ -456,7 +456,7 @@ export default async function Login() { > -```jsx title="app/client/page.jsx" +```jsx app/client/page.jsx 'use client' import { createClientComponentClient } from '@supabase/auth-helpers-nextjs' @@ -483,7 +483,7 @@ export default function Home() { -```jsx title="app/new-post.tsx" +```jsx app/new-post.tsx "use client"; import { createClientComponentClient } from "@supabase/auth-helpers-nextjs"; @@ -543,7 +543,7 @@ const supabase = createClientComponentClient({ isSingleton: false }) > -```jsx title="app/page.jsx" +```jsx app/page.jsx import { cookies } from 'next/headers' import { createServerComponentClient } from '@supabase/auth-helpers-nextjs' @@ -558,7 +558,7 @@ export default async function Home() { -```tsx title="app/page.tsx" +```tsx app/page.tsx import { cookies } from 'next/headers' import { createServerComponentClient } from '@supabase/auth-helpers-nextjs' @@ -592,7 +592,7 @@ export default async function ServerComponent() { > -```jsx title="app/new-post.jsx" +```jsx app/new-post.jsx import { cookies } from 'next/headers' import { createServerActionClient } from '@supabase/auth-helpers-nextjs' import { revalidatePath } from 'next/cache' @@ -619,7 +619,7 @@ export default async function NewTodo() { -```tsx title="app/new-post.tsx" +```tsx app/new-post.tsx import { cookies } from 'next/headers' import { createServerActionClient } from '@supabase/auth-helpers-nextjs' import { revalidatePath } from 'next/cache' @@ -661,7 +661,7 @@ export default async function NewTodo() { > -```jsx title="app/api/todos/route.jsx" +```jsx app/api/todos/route.jsx import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs' import { NextResponse } from 'next/server' import { cookies } from 'next/headers' @@ -678,7 +678,7 @@ export async function POST(request) { -```tsx title="app/api/todos/route.tsx" +```tsx app/api/todos/route.tsx import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs' import { NextResponse } from 'next/server' import { cookies } from 'next/headers' diff --git a/apps/docs/pages/guides/auth/auth-helpers/remix.mdx b/apps/docs/pages/guides/auth/auth-helpers/remix.mdx index 98ff6f57c94..80b91640f8e 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/remix.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/remix.mdx @@ -56,7 +56,7 @@ This library supports the following tooling versions: Retrieve your project URL and anon key in your project's [API settings](https://app.supabase.com/project/_/settings/api) in the Dashboard to set up the following environment variables. For local development you can set them in a `.env` file. See an [example](https://github.com/supabase/auth-helpers/blob/main/examples/remix/.env.example). -```bash title=.env +```bash .env SUPABASE_URL=YOUR_SUPABASE_URL SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY ``` @@ -75,7 +75,7 @@ The `Code Exchange` route is required for the [server-side auth flow](https://su Create a new file at `app/routes/auth.callback.jsx` and populate with the following: -```jsx title="app/routes/auth.callback.jsx" +```jsx app/routes/auth.callback.jsx import { redirect } from '@remix-run/node' import { createServerClient } from '@supabase/auth-helpers-remix' @@ -105,7 +105,7 @@ export const loader = async ({ request }) => { Create a new file at `app/routes/auth.callback.tsx` and populate with the following: -```tsx title="app/routes/auth.callback.tsx" +```tsx app/routes/auth.callback.tsx import { redirect } from '@remix-run/node' import { createServerClient } from '@supabase/auth-helpers-remix' @@ -328,7 +328,7 @@ Since our environment variables are not available client-side, we need to plumb > -```jsx title=app/root.jsx +```jsx app/root.jsx export const loader = () => { const env = { SUPABASE_URL: process.env.SUPABASE_URL, @@ -343,26 +343,26 @@ export const loader = () => { Next, we call the `useLoaderData` hook in our component to get the `env` object. -```jsx title=app/root.jsx +```jsx app/root.jsx const { env } = useLoaderData() ``` We then want to instantiate a single instance of a Supabase browser client, to be used across our client-side components. -```jsx title=app/root.jsx +```jsx app/root.jsx const [supabase] = useState(() => createBrowserClient(env.SUPABASE_URL, env.SUPABASE_ANON_KEY)) ``` And then we can share this instance across our application with Outlet Context. -```jsx title=app/root.jsx +```jsx app/root.jsx ``` -```tsx title=app/root.tsx +```tsx app/root.tsx export const loader = ({}: LoaderArgs) => { const env = { SUPABASE_URL: process.env.SUPABASE_URL!, @@ -377,13 +377,13 @@ export const loader = ({}: LoaderArgs) => { Next, we call the `useLoaderData` hook in our component to get the `env` object. -```tsx title=app/root.tsx +```tsx app/root.tsx const { env } = useLoaderData() ``` We then want to instantiate a single instance of a Supabase browser client, to be used across our client-side components. -```tsx title=app/root.tsx +```tsx app/root.tsx const [supabase] = useState(() => createBrowserClient(env.SUPABASE_URL, env.SUPABASE_ANON_KEY) ) @@ -391,7 +391,7 @@ const [supabase] = useState(() => And then we can share this instance across our application with Outlet Context. -```tsx title=app/root.tsx +```tsx app/root.tsx ``` @@ -417,7 +417,7 @@ Let's pipe that through from our loader. -```jsx title=app/root.jsx +```jsx app/root.jsx export const loader = async ({ request }) => { const env = { SUPABASE_URL: process.env.SUPABASE_URL, @@ -451,7 +451,7 @@ export const loader = async ({ request }) => { -```tsx title=app/root.tsx +```tsx app/root.tsx export const loader = async ({ request }: LoaderArgs) => { const env = { SUPABASE_URL: process.env.SUPABASE_URL!, @@ -496,7 +496,7 @@ And then use the revalidator, inside the `onAuthStateChange` hook. -```jsx title=app/root.jsx +```jsx app/root.jsx const { env, session } = useLoaderData() const { revalidate } = useRevalidator() @@ -524,7 +524,7 @@ useEffect(() => { -```tsx title=app/root.tsx +```tsx app/root.tsx const { env, session } = useLoaderData() const { revalidate } = useRevalidator() @@ -569,7 +569,7 @@ Now we can use our outlet context to access our single instance of Supabase and -```jsx title=app/components/login.jsx +```jsx app/components/login.jsx export default function Login() { const { supabase } = useOutletContext() @@ -607,7 +607,7 @@ export default function Login() { -```tsx title=app/components/login.tsx +```tsx app/components/login.tsx export default function Login() { const { supabase } = useOutletContext<{ supabase: SupabaseClient }>() @@ -656,7 +656,7 @@ export default function Login() { -```jsx title=app/routes/realtime.jsx +```jsx app/routes/realtime.jsx import { useLoaderData, useOutletContext } from '@remix-run/react' import { createServerClient } from '@supabase/auth-helpers-remix' import { json } from '@remix-run/node' @@ -704,7 +704,7 @@ export default function Index() { -```tsx title=app/routes/realtime.tsx +```tsx app/routes/realtime.tsx import { useLoaderData, useOutletContext } from '@remix-run/react' import { createServerClient } from '@supabase/auth-helpers-remix' import { json } from '@remix-run/node' diff --git a/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx b/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx index 13e109beae9..31130b2b2b6 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx @@ -9,31 +9,77 @@ export const meta = { This submodule provides convenience helpers for implementing user authentication in [SvelteKit](https://kit.svelte.dev/) applications. -## Installation +## Configuration + +### Install SvelteKit Auth Helpers library This library supports Node.js `^16.15.0`. -```sh +```sh Terminal npm install @supabase/auth-helpers-sveltekit ``` -## Getting Started +### Declare Environment Variables -### Configuration +Retrieve your project's URL and anon key from your [API settings](https://app.supabase.com/project/_/settings/api), and create a `.env.local` file with the following environment variables: -Set up the following env vars. For local development you can set them in a `.env` file. See an [example](https://github.com/supabase/auth-helpers/blob/main/examples/sveltekit/.env.example). - -```bash +```bash .env.local # Find these in your Supabase project settings https://app.supabase.com/project/_/settings/api PUBLIC_SUPABASE_URL=https://your-project.supabase.co PUBLIC_SUPABASE_ANON_KEY=your-anon-key ``` -### Set up the Supabase client +### Creating a Supabase Client -Create a server supabase client in a handle hook: + + -```ts title=src/hooks.server.ts +Create a new `hooks.server.js` file in the root of your project and populate with the following: + +```js src/hooks.server.js +// src/hooks.server.js +import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public' +import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit' + +export const handle = async ({ event, resolve }) => { + event.locals.supabase = createSupabaseServerClient({ + supabaseUrl: PUBLIC_SUPABASE_URL, + supabaseKey: PUBLIC_SUPABASE_ANON_KEY, + event, + }) + + /** + * a little helper that is written for convenience so that instead + * of calling `const { data: { session } } = await supabase.auth.getSession()` + * you just call this `await getSession()` + */ + event.locals.getSession = async () => { + const { + data: { session }, + } = await event.locals.supabase.auth.getSession() + return session + } + + return resolve(event, { + filterSerializedResponseHeaders(name) { + return name === 'content-range' + }, + }) +} +``` + + + + + +Create a new `hooks.server.ts` file in the root of your project and populate with the following: + +```ts src/hooks.server.ts // src/hooks.server.ts import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public' import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit' @@ -59,11 +105,6 @@ export const handle: Handle = async ({ event, resolve }) => { } return resolve(event, { - /** - * There´s an issue with `filterSerializedResponseHeaders` not working when using `sequence` - * - * https://github.com/sveltejs/kit/issues/8061 - */ filterSerializedResponseHeaders(name) { return name === 'content-range' }, @@ -71,96 +112,71 @@ export const handle: Handle = async ({ event, resolve }) => { } ``` -> Note that we are specifying filterSerializedResponseHeaders here. We need to tell SvelteKit that supabase needs the content-range header. + + -### Send session to client + -In order to make the session available to the UI (pages, layouts) we need to pass the session in the root layout server load function: +Note that we are specifying filterSerializedResponseHeaders here. We need to tell SvelteKit that supabase needs the content-range header. -```ts title=src/routes/+layout.server.ts -// src/routes/+layout.server.ts -import type { LayoutServerLoad } from './$types' + -export const load: LayoutServerLoad = async ({ locals: { getSession } }) => { - return { - session: await getSession(), +### Code Exchange Route + +The `Code Exchange` route is required for the [server-side auth flow](https://supabase.com/docs/guides/auth/server-side-rendering) implemented by the SvelteKit Auth Helpers. It exchanges an auth `code` for the user's `session`, which is set as a cookie for future requests made to Supabase. + + + + +Create a new file at `src/routes/auth/callback/+server.js` and populate with the following: + +```js src/routes/auth/callback/+server.js +import { redirect } from '@sveltejs/kit' + +export const GET = async ({ url, locals: { supabase } }) => { + const code = url.searchParams.get('code') + + if (code) { + await supabase.auth.exchangeCodeForSession(code) } + + throw redirect(303, '/') } ``` -### Shared Load functions and pages + -To be able to use Supabase in shared load functions and inside pages you need to create a Supabase client in the root layout load: + -```ts -// src/routes/+layout.ts -import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public' -import { createSupabaseLoadClient } from '@supabase/auth-helpers-sveltekit' -import type { LayoutLoad } from './$types' -import type { Database } from '../DatabaseDefinitions' +Create a new file at `src/routes/auth/callback/+server.ts` and populate with the following: -export const load: LayoutLoad = async ({ fetch, data, depends }) => { - depends('supabase:auth') +```ts src/routes/auth/callback/+server.ts +import { redirect } from '@sveltejs/kit' - const supabase = createSupabaseLoadClient({ - supabaseUrl: PUBLIC_SUPABASE_URL, - supabaseKey: PUBLIC_SUPABASE_ANON_KEY, - event: { fetch }, - serverSession: data.session, - }) +export const GET = async ({ url, locals: { supabase } }) => { + const code = url.searchParams.get('code') - const { - data: { session }, - } = await supabase.auth.getSession() + if (code) { + await supabase.auth.exchangeCodeForSession(code) + } - return { supabase, session } + throw redirect(303, '/') } ``` -Access the client inside pages by `$page.data.supabase` or `data.supabase` when using `export let data: PageData`. - -The usage of `depends` tells sveltekit that this load function should be executed whenever `invalidate` is called to keep the page store in sync. - -`createSupabaseLoadClient` caches the client when running in a browser environment and therefore does not create a new client for every time the load function runs. - -### Setting up the event listener on the client side - -We need to create an event listener in the root `+layout.svelte` file in order catch supabase events being triggered. - -```svelte - - - - -``` - -The usage of `invalidate` tells sveltekit that the root `+layout.ts` load function should be executed whenever the session updates to keep the page store in sync. + + ### Generate types from your database In order to get the most out of TypeScript and it's intellisense, you should import the generated Database types into the `app.d.ts` type definition file that comes with your SvelteKit project, where `import('./DatabaseDefinitions')` points to the generated types file outlined in [v2 docs here](https://supabase.com/docs/reference/javascript/release-notes#typescript-support) after you have logged in, linked, and generated types through the Supabase CLI. -```ts +```ts src/app.d.ts // src/app.d.ts import { SupabaseClient, Session } from '@supabase/supabase-js' @@ -181,78 +197,324 @@ declare global { } ``` -## Client-side data fetching with RLS +## Authentication -For [row level security](https://supabase.com/docs/guides/auth/row-level-security) to work properly when fetching data client-side, you need to use `supabaseClient` from `PageData` and only run your query once the session is defined client-side: +Authentication can be initiated [client](/docs/guides/auth/auth-helpers/sveltekit#client-side) or [server-side](/docs/guides/auth/auth-helpers/sveltekit#server-side). All of the [supabase-js authentication strategies](http://localhost:3001/docs/reference/javascript/auth-api) are supported with the Auth Helpers client. -```html - +### Client-side -{#if data.session} -

client-side data fetching with RLS

-
{JSON.stringify(loadedData, null, 2)}
-{/if} -``` +#### Send session to client -## Server-side data fetching with RLS +To make the session available across the UI, including pages and layouts, it is crucial to pass the session as a parameter in the root layout's server load function. -```html - - - -
Protected content for {user.email}
-
{JSON.stringify(tableData, null, 2)}
-
{JSON.stringify(user, null, 2)}
-``` - -```ts -// src/routes/profile/+page.ts -import type { PageLoad } from './$types' -import { redirect } from '@sveltejs/kit' - -export const load: PageLoad = async ({ parent }) => { - const { supabase, session } = await parent() - if (!session) { - throw redirect(303, '/') - } - const { data: tableData } = await supabase.from('test').select('*') + + +```js src/routes/+layout.server.js +// src/routes/+layout.server.js +export const load = async ({ locals: { getSession } }) => { return { - user: session.user, - tableData, + session: await getSession(), } } ``` -## Protecting API routes + + + +```ts src/routes/+layout.server.ts +// src/routes/+layout.server.ts +export const load = async ({ locals: { getSession } }) => { + return { + session: await getSession(), + } +} +``` + + + + +#### Shared Load functions and pages + +To utilize Supabase in shared load functions and within pages, it is essential to create a Supabase client in the root layout load. + + + + +```ts src/routes/+layout.js +// src/routes/+layout.js +import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public' +import { createSupabaseLoadClient } from '@supabase/auth-helpers-sveltekit' + +export const load = async ({ fetch, data, depends }) => { + depends('supabase:auth') + + const supabase = createSupabaseLoadClient({ + supabaseUrl: PUBLIC_SUPABASE_URL, + supabaseKey: PUBLIC_SUPABASE_ANON_KEY, + event: { fetch }, + serverSession: data.session, + }) + + const { + data: { session }, + } = await supabase.auth.getSession() + + return { supabase, session } +} +``` + + + + + +```ts src/routes/+layout.ts +// src/routes/+layout.ts +import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public' +import { createSupabaseLoadClient } from '@supabase/auth-helpers-sveltekit' +import type { Database } from '../DatabaseDefinitions' + +export const load = async ({ fetch, data, depends }) => { + depends('supabase:auth') + + const supabase = createSupabaseLoadClient({ + supabaseUrl: PUBLIC_SUPABASE_URL, + supabaseKey: PUBLIC_SUPABASE_ANON_KEY, + event: { fetch }, + serverSession: data.session, + }) + + const { + data: { session }, + } = await supabase.auth.getSession() + + return { supabase, session } +} +``` + + + +TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createSupabaseLoadClient` to add type support to the Supabase client. + + + + + + +Access the client inside pages by `$page.data.supabase` or `data.supabase` when using `export let data`. + +The usage of `depends` tells sveltekit that this load function should be executed whenever `invalidate` is called to keep the page store in sync. + +`createSupabaseLoadClient` caches the client when running in a browser environment and therefore does not create a new client for every time the load function runs. + +#### Setting up the event listener on the client side + +We need to create an event listener in the root `+layout.svelte` file in order to catch supabase events being triggered. + +```svelte src/routes/+layout.svelte + + + + +``` + +The usage of `invalidate` tells SvelteKit that the root `+layout.ts` load function should be executed whenever the session updates to keep the page store in sync. + +#### Sign in / Sign up / Sign out + +We can access the supabase instance in our `+page.svelte` file through the data object. + +```svelte src/routes/auth/+page.svelte + + + +
+ + + +
+ + + +``` + +### Server-side + +[Form Actions](https://kit.svelte.dev/docs/form-actions) can be used to trigger the authentication process from form submissions. + + + + +```js src/routes/login/+page.server.js +// src/routes/login/+page.server.js +export const actions = { + default: async ({ request, url, locals: { supabase } }) => { + const formData = await request.formData() + const email = formData.get('email') + const password = formData.get('password') + + const { error } = await supabase.auth.signUp({ + email, + password, + options: { + emailRedirectTo: `${url.origin}/auth/callback`, + }, + }) + + if (error) { + return fail(500, { message: 'Server error. Try again later.', success: false, email }) + } + + return { + message: 'Please check your email for a magic link to log into the website.', + success: true, + } + }, +} +``` + +```svelte src/routes/login/+page.svelte + + + +
+ + + +
+``` + +
+ + + +```js src/routes/login/+page.server.ts +// src/routes/login/+page.server.ts +export const actions = { + default: async ({ request, url, locals: { supabase } }) => { + const formData = await request.formData() + const email = formData.get('email') as string + const password = formData.get('password') as string + + const { error } = await supabase.auth.signUp({ + email, + password, + options: { + emailRedirectTo: `${url.origin}/auth/callback`, + }, + }) + + if (error) { + return fail(500, { message: 'Server error. Try again later.', success: false, email }) + } + + return { + message: 'Please check your email for a magic link to log into the website.', + success: true, + } + }, +} +``` + +```svelte src/routes/login/+page.svelte + + + +
+ + + +
+``` + +
+
+ +## Authorization + +### Protecting API routes Wrap an API Route to check that the user has a valid session. If they're not logged in the session is `null`. -```ts +```ts src/routes/api/protected-route/+server.ts // src/routes/api/protected-route/+server.ts -import type { RequestHandler } from './$types' import { json, error } from '@sveltejs/kit' -export const GET: RequestHandler = async ({ locals: { supabase, getSession } }) => { +export const GET = async ({ locals: { supabase, getSession } }) => { const session = await getSession() if (!session) { // the user is not signed in @@ -266,16 +528,15 @@ export const GET: RequestHandler = async ({ locals: { supabase, getSession } }) If you visit `/api/protected-route` without a valid session cookie, you will get a 401 response. -## Protecting Actions +### Protecting Actions Wrap an Action to check that the user has a valid session. If they're not logged in the session is `null`. -```ts +```ts src/routes/posts/+page.server.ts // src/routes/posts/+page.server.ts -import type { Actions } from './$types' import { error, fail } from '@sveltejs/kit' -export const actions: Actions = { +export const actions = { createPost: async ({ request, locals: { supabase, getSession } }) => { const session = await getSession() @@ -305,14 +566,148 @@ export const actions: Actions = { If you try to submit a form with the action `?/createPost` without a valid session cookie, you will get a 401 error response. +### Protecting multiple routes + +To avoid writing the same auth logic in every single route you can use the handle hook to +protect multiple routes at once. + + + + +```js src/hooks.server.js +// src/hooks.server.js +import { redirect, error } from '@sveltejs/kit' + +export const handle = async ({ event, resolve }) => { + // protect requests to all routes that start with /protected-routes + if (event.url.pathname.startsWith('/protected-routes')) { + const session = await event.locals.getSession() + if (!session) { + // the user is not signed in + throw redirect(303, '/') + } + } + + // protect POST requests to all routes that start with /protected-posts + if (event.url.pathname.startsWith('/protected-posts') && event.request.method === 'POST') { + const session = await event.locals.getSession() + if (!session) { + // the user is not signed in + throw error(303, '/') + } + } + + return resolve(event) +} +``` + + + + + +```ts src/hooks.server.ts +// src/hooks.server.ts +import { type Handle, redirect, error } from '@sveltejs/kit' + +export const handle: Handle = async ({ event, resolve }) => { + // protect requests to all routes that start with /protected-routes + if (event.url.pathname.startsWith('/protected-routes')) { + const session = await event.locals.getSession() + if (!session) { + // the user is not signed in + throw redirect(303, '/') + } + } + + // protect POST requests to all routes that start with /protected-posts + if (event.url.pathname.startsWith('/protected-posts') && event.request.method === 'POST') { + const session = await event.locals.getSession() + if (!session) { + // the user is not signed in + throw error(303, '/') + } + } + + return resolve(event) +} +``` + + + + +## Data fetching + +### Client-side data fetching with RLS + +For [row level security](https://supabase.com/docs/guides/auth/row-level-security) to work properly when fetching data client-side, you need to use `supabaseClient` from `PageData` and only run your query once the session is defined client-side: + +```svelte src/routes/+page.svelte + + +{#if data.session} +

client-side data fetching with RLS

+
{JSON.stringify(loadedData, null, 2)}
+{/if} +``` + +### Server-side data fetching with RLS + +```svelte src/routes/profile/+page.svelte + + + +
Protected content for {user.email}
+
{JSON.stringify(tableData, null, 2)}
+
{JSON.stringify(user, null, 2)}
+``` + +```ts src/routes/profile/+page.ts +// src/routes/profile/+page.ts +import { redirect } from '@sveltejs/kit' + +export const load = async ({ parent }) => { + const { supabase, session } = await parent() + if (!session) { + throw redirect(303, '/') + } + const { data: tableData } = await supabase.from('test').select('*') + + return { + user: session.user, + tableData, + } +} +``` + ## Saving and deleting the session ```ts -import type { Actions } from './$types' import { fail, redirect } from '@sveltejs/kit' import { AuthApiError } from '@supabase/supabase-js' -export const actions: Actions = { +export const actions = { signin: async ({ request, locals: { supabase } }) => { const formData = await request.formData() @@ -351,42 +746,33 @@ export const actions: Actions = { } ``` -## Protecting multiple routes +## Migration Guide [#migration] -To avoid writing the same auth logic in every single route you can use the handle hook to -protect multiple routes at once. +### Migrate to 0.10 + +#### PKCE Auth Flow + +Proof Key for Code Exchange (PKCE) is the new server-side auth flow implemented by the SvelteKit Auth Helpers. It requires a server endpoint for `/auth/callback` that exchanges an auth `code` for the user's `session`. + +Check the [Code Exchange Route steps](/docs/guides/auth/auth-helpers/sveltekit#code-exchange-route) above to implement this server endpoint. + +#### Authentication + +For authentication methods that have a `redirectTo` or `emailRedirectTo`, this must be set to this new code exchange route handler - `/auth/callback`. This is an example with the `signUp` function: ```ts -// src/hooks.server.ts -import type { RequestHandler } from './$types' -import { redirect, error } from '@sveltejs/kit' - -export const handle: Handle = async ({ event, resolve }) => { - // protect requests to all routes that start with /protected-routes - if (event.url.pathname.startsWith('/protected-routes')) { - const session = await event.locals.getSession() - if (!session) { - // the user is not signed in - throw redirect(303, '/') - } - } - - // protect POST requests to all routes that start with /protected-posts - if (event.url.pathname.startsWith('/protected-posts') && event.request.method === 'POST') { - const session = await event.locals.getSession() - if (!session) { - // the user is not signed in - throw error(303, '/') - } - } - - return resolve(event) -} +await supabase.auth.signUp({ + email: 'jon@example.com', + password: 'sup3rs3cur3', + options: { + emailRedirectTo: 'http://localhost:3000/auth/callback', + }, +}) ``` -## Migrate from 0.8.x to 0.9 [#migration] +### Migrate from 0.8.x to 0.9 [#migration-0-9] -### Set up the Supabase client [#migration-set-up-supabase-client] +#### Set up the Supabase client [#migration-set-up-supabase-client] In version 0.9 we now setup our Supabase client for the server inside of a `hooks.server.ts` file. @@ -398,7 +784,7 @@ In version 0.9 we now setup our Supabase client for the server inside of a `hook > -```js title=src/lib/db.ts +```js src/lib/db.ts // src/lib/db.ts import { createClient } from '@supabase/auth-helpers-sveltekit' import { env } from '$env/dynamic/public' @@ -412,7 +798,7 @@ export const supabaseClient = createClient(env.PUBLIC_SUPABASE_URL, env.PUBLIC_S -```js title=src/hooks.server.ts +```js src/hooks.server.ts // src/hooks.server.ts import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public' import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit' @@ -448,7 +834,7 @@ export const handle: Handle = async ({ event, resolve }) => { -### Initialize the client [#migration-initialize-client] +#### Initialize the client [#migration-initialize-client] In order to use the Supabase library in your client code you will need to setup a shared load function inside the root `+layout.ts` and create a `+layout.svelte` to handle our event listening for Auth events. @@ -460,7 +846,7 @@ In order to use the Supabase library in your client code you will need to setup > -```html title=src/routes/+layout.svelte +```svelte src/routes/+layout.svelte @@ -1319,7 +1705,7 @@ declare namespace App { -```html title=src/routes/+page.svelte +```svelte src/routes/+page.svelte @@ -1335,7 +1721,7 @@ declare namespace App { -### withPageAuth +#### withPageAuth -```html title=src/routes/protected-route.svelte +```svelte src/routes/protected-route.svelte @@ -246,7 +246,7 @@ Every Supabase project is configured with [Storage](/docs/guides/storage) for ma Let's create an avatar for the user so that they can upload a profile photo. We can start by creating a new component: -```vue title=components/Avatar.vue +```vue components/Avatar.vue