diff --git a/.github/workflows/publish_image.yml b/.github/workflows/publish_image.yml index 8ed1db87295..84c8b8a88c2 100644 --- a/.github/workflows/publish_image.yml +++ b/.github/workflows/publish_image.yml @@ -2,6 +2,8 @@ name: Publish to Image Registry on: push: + branches: + - 'studio' tags: - '*' workflow_dispatch: @@ -25,6 +27,7 @@ jobs: latest=false tags: | type=ref,event=tag + type=sha,prefix={{date 'YYYYMMDD'}},enable=${{ github.ref_type == 'branch' }} type=raw,value=${{ inputs.version }},enable=${{ github.event_name != 'push' }} - uses: docker/setup-qemu-action@v2 diff --git a/apps/reference/docs/guides/auth/auth-helpers/nextjs.mdx b/apps/reference/docs/guides/auth/auth-helpers/nextjs.mdx index 4e8ffdd87dc..b509792f914 100644 --- a/apps/reference/docs/guides/auth/auth-helpers/nextjs.mdx +++ b/apps/reference/docs/guides/auth/auth-helpers/nextjs.mdx @@ -70,6 +70,7 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY ## Basic Setup +```jsx title="app/root.jsx" +export const loader = async ({ request }) => { + // Environment variables may be stored somewhere other than `process.env` in runtimes other than node. + // We need to pipe these Supabase environment variables to the browser. + const { SUPABASE_URL, SUPABASE_ANON_KEY } = process.env + + // We can retrieve the session on the server and hand it to the client. + // This is used to make sure the session is available immediately upon rendering. + const response = new Response() + const supabase = createServerClient( + process.env.SUPABASE_URL!, + process.env.SUPABASE_ANON_KEY!, + { request, response } + ) + const { + data: { session: initialSession }, + } = await supabase.auth.getSession() + + // In order for the set-cookie header to be set, headers must be returned as part of the loader response. + return json( + { + initialSession, + env: { + SUPABASE_URL, + SUPABASE_ANON_KEY, + }, + }, + { + headers: response.headers, + } + ) +} +``` + + + + +```tsx title="app/root.tsx" +export const loader: LoaderFunction = async ({ request }) => { + // Environment variables may be stored somewhere other than `process.env` in runtimes other than node. + // We need to pipe these Supabase environment variables to the browser. + const { SUPABASE_URL, SUPABASE_ANON_KEY } = process.env + + // We can retrieve the session on the server and hand it to the client. + // This is used to make sure the session is available immediately upon rendering. + const response = new Response() + const supabase = createServerClient( + process.env.SUPABASE_URL!, + process.env.SUPABASE_ANON_KEY!, + { request, response } + ) + const { + data: { session: initialSession }, + } = await supabase.auth.getSession() + + // In order for the set-cookie header to be set, headers must be returned as part of the loader response. + return json( + { + initialSession, + env: { + SUPABASE_URL, + SUPABASE_ANON_KEY, + }, + }, + { + headers: response.headers, + } + ) +} +``` + + + + +Next, upon the initial client-side rendering of your application, set up a browser client and listen to session changes and expose both throughout your routes via the outlet context: + + + + +```jsx title="app/root.jsx" +import { useEffect, useState } from 'react' +import { json } from '@remix-run/node' +import { Outlet, useLoaderData } from '@remix-run/react' +import { + createServerClient, + createBrowserClient, +} from '@supabase/auth-helpers-remix' + +export const loader: LoaderFunction = async ({ request }) => { + // ... see loader function above. +} + +export default function App() { + const { env, initialSession } = useLoaderData() + const [supabase, setSupabase] = useState(null) + const [session, setSession] = useState(initialSession) + + const context = { supabase, session } + + useEffect(() => { + if (!supabase) { + const supabase = createBrowserClient( + env.SUPABASE_URL, + env.SUPABASE_ANON_KEY + ) + setSupabase(supabase) + const { + data: { subscription }, + } = supabase.auth.onAuthStateChange((_, session) => setSession(session)) + return () => { + subscription.unsubscribe() + } + } + }, []) + + return ( + + + + + + ) +} +``` + + + + +```tsx title="app/root.tsx" +import { useEffect, useState } from 'react' +import { json, LoaderFunction } from '@remix-run/node' +import { Outlet, useLoaderData } from '@remix-run/react' +import { + createServerClient, + createBrowserClient, + supabase, + Session, +} from '@supabase/auth-helpers-remix' +import { Database } from '../db_types' + +export type ContextType = { + supabase: supabase | null + session: Session | null +} + +type LoaderData = { + env: { SUPABASE_URL: string; SUPABASE_ANON_KEY: string } + initialSession: Session | null +} + +export const loader: LoaderFunction = async ({ request }) => { + // ... see loader function above. +} + +export default function App() { + const { env, initialSession } = useLoaderData() + const [supabase, setSupabase] = useState(null) + const [session, setSession] = useState(initialSession) + + const context: ContextType = { supabase, session } + + useEffect(() => { + if (!supabase) { + const supabase = createBrowserClient( + env.SUPABASE_URL, + env.SUPABASE_ANON_KEY + ) + setSupabase(supabase) + const { + data: { subscription }, + } = supabase.auth.onAuthStateChange((_, session) => setSession(session)) + return () => { + subscription.unsubscribe() + } + } + }, []) + + return ( + + + + + + ) +} +``` + + + + +Now the supabase client and session are available throughout the application via the `useOutletContext`: + + + + +```jsx title="app/routes/index.jsx" +import { useOutletContext } from '@remix-run/react' + +export default function Index() { + const { supabase, session } = useOutletContext() + + return
{JSON.stringify({ session }, null, 2)}
+} +``` + +
+ + +```tsx title="app/routes/index.tsx" +import { useOutletContext } from '@remix-run/react' +import type { ContextType } from '../root' + +export default function Index() { + const { supabase, session } = useOutletContext() + + return
{JSON.stringify({ session }, null, 2)}
+} +``` + +
+
+ +You can find a full working example in the [source code on GitHub](https://github.com/supabase/auth-helpers/tree/main/examples/remix). + +## Usage with TypeScript + +You can pass types that were [generated with the Supabase CLI](/docs/reference/javascript/typescript-support#generating-types) to the `createServerClient` or `createBrowserClient` functions to get enhanced type safety and auto completion: + +### Server-side + +```tsx +import { createServerClient } from '@supabase/auth-helpers-remix' +import { Database } from '../../db_types' + +export const loader = async ({ request }) => { + const response = new Response() + + const supabase = createServerClient( + process.env.SUPABASE_URL, + process.env.SUPABASE_ANON_KEY, + { request, response } + ) +} +``` + +### Client-side + +```tsx +import { createBrowserClient } from '@supabase/auth-helpers-remix' +import { Database } from '../../db_types' + +const supabase = createBrowserClient(SUPABASE_URL, SUPABASE_ANON_KEY) +``` + +## Loader + Loader functions run on the server immediately before the component is rendered. They respond to all GET requests on a route. You can create an authenticated Supabase client by calling the `createServerClient` function and passing it your `SUPABASE_URL`, `SUPABASE_ANON_KEY`, and a `Request` and `Response`. + + + ```jsx import { json } from '@remix-run/node' // change this import to whatever runtime you are using import { createServerClient } from '@supabase/auth-helpers-remix' @@ -74,13 +354,13 @@ export const loader = async ({ request }) => { // an empty response is required for the auth helpers // to set cookies to manage auth - const supabaseClient = createServerClient( + const supabase = createServerClient( process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY, { request, response } ) - const { data } = await supabaseClient.from('test').select('*') + const { data } = await supabase.from('test').select('*') // in order for the set-cookie header to be set, // headers must be returned as part of the loader response @@ -93,30 +373,22 @@ export const loader = async ({ request }) => { } ``` -> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Loader` function. - -Loader functions run on the server immediately before the component is rendered. You can create an authenticated Supabase client by calling the `createServerClient` function and passing it your `SUPABASE_URL`, `SUPABASE_ANON_KEY`, and a `Request` and `Response`. - -```jsx +```tsx import { LoaderFunction, json } from '@remix-run/node' // change this import to whatever runtime you are using import { createServerClient } from '@supabase/auth-helpers-remix' -export const loader: LoaderFunction = async ({ - request, -}: { - request: Request, -}) => { +export const loader: LoaderFunction = async ({ request }) => { const response = new Response() - const supabaseClient = createServerClient( + const supabase = createServerClient( process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY, { request, response } ) - const { data } = await supabaseClient.from('test').select('*') + const { data } = await supabase.from('test').select('*') return json( { data }, @@ -127,14 +399,17 @@ export const loader: LoaderFunction = async ({ } ``` -> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Loader` function. - +> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Loader` function. + ## Action +Action functions run on the server and respond to HTTP requests to a route, other than GET - POST, PUT, PATCH, DELETE etc. You can create an authenticated Supabase client by calling the `createServerClient` function and passing it your `SUPABASE_URL`, `SUPABASE_ANON_KEY`, and a `Request` and `Response`. + -Action functions run on the server and respond to HTTP requests to a route, other than GET - POST, PUT, PATCH, DELETE etc. You can create an authenticated Supabase client by calling the `createServerClient` function and passing it your `SUPABASE_URL`, `SUPABASE_ANON_KEY`, and a `Request` and `Response`. - ```jsx import { json } from '@remix-run/node' // change this import to whatever runtime you are using import { createServerClient } from '@supabase/auth-helpers-remix' @@ -151,13 +424,13 @@ import { createServerClient } from '@supabase/auth-helpers-remix' export const action = async ({ request }) => { const response = new Response() - const supabaseClient = createServerClient( + const supabase = createServerClient( process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY, { request, response } ) - const { data } = await supabaseClient.from('test').select('*') + const { data } = await supabase.from('test').select('*') return json( { data }, @@ -168,31 +441,23 @@ export const action = async ({ request }) => { } ``` -> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Action` function. - -Action functions run on the server and respond to HTTP requests to a route, other than GET - POST, PUT, PATCH, DELETE etc. You can create an authenticated Supabase client by calling the `createServerClient` function and passing it your `SUPABASE_URL`, `SUPABASE_ANON_KEY`, and a `Request` and `Response`. - -```jsx +```tsx import { ActionFunction, json } from '@remix-run/node' // change this import to whatever runtime you are using import { createServerClient } from '@supabase/auth-helpers-remix' -export const action: ActionFunction = async ({ - request, -}: { - request: Request, -}) => { +export const action: ActionFunction = async ({ request }) => { const response = new Response() - const supabaseClient = createServerClient( + const supabase = createServerClient( process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY, { request, response } ) - const { data } = await supabaseClient.from('test').select('*') + const { data } = await supabase.from('test').select('*') return json( { data }, @@ -203,11 +468,11 @@ export const action: ActionFunction = async ({ } ``` -> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Action` function. - +> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Action` function. + ## Session and User You can determine if a user is authenticated by checking their session using the `getSession` function. @@ -215,7 +480,7 @@ You can determine if a user is authenticated by checking their session using the ```jsx const { data: { session }, -} = await supabaseClient.auth.getSession() +} = await supabase.auth.getSession() ``` The session contains a user property. @@ -224,681 +489,418 @@ The session contains a user property. const user = session?.user ``` -> This is the recommended way for accessing the logged in user. There is also a `getUser()` function but this does not refresh the session if it has expired. +## Authentication UI -## Client-side +### Pre-built Auth UI + +We recommend using our [pre-built Auth UI](/docs/guides/auth/auth-helpers/auth-ui) for authenticating users. It supports custom themes and extensible styles to match your brand and aesthetic. + +### Custom Auth UI + +Should you prefer to build your own Auth UI, you can use our [JavaScript Library](/docs/reference/javascript/) to interact with Supabase Auth from the client-side. + ]}> -In order to use the Supabase client in the browser - fetching data in `useEffect` or subscribing to realtime events - we need to do a little more plumbing. Remix does not include a way to make environment variables available to the browser, so we need to pipe them through from a `loader` function in our `root.jsx` route and attach them to the `window`. +```jsx title="app/routes/signin.jsx" +import { useOutletContext } from '@remix-run/react' -```jsx title="app/root.jsx" -export const loader = () => { - const { SUPABASE_URL, SUPABASE_ANON_KEY } = process.env - return json({ - env: { - SUPABASE_URL, - SUPABASE_ANON_KEY, - }, - }) -} -``` +export default function Auth() { + const { supabase, session } = useOutletContext() -> These may not be stored in `process.env` for environments other than Node. + const handleSubmit = async (event) => { + event.preventDefault() + if (!supabase) return + const formData = new FormData(event.currentTarget) + const email = formData.get('loginEmail') + const password = formData.get('loginPassword') + // Sign in + const { error } = await supabase.auth.signInWithPassword({ + email, + password, + }) + if (error) alert(error.message) + } -Next, we call the `useLoaderData` hook in our component to get the `env` object. - -```jsx title="app/root.jsx" -const { env } = useLoaderData() -``` - -And then, add a ` -
+ diff --git a/apps/reference/nav/supabase_dart_sidebars.js b/apps/reference/nav/supabase_dart_sidebars.js index 106bf0ca333..a3da7c80ebf 100644 --- a/apps/reference/nav/supabase_dart_sidebars.js +++ b/apps/reference/nav/supabase_dart_sidebars.js @@ -43,18 +43,6 @@ const sidebars = { 'generated/upsert', 'generated/delete', 'generated/rpc', - { - type: 'category', - label: 'Modifiers', - items: [ - 'generated/using-modifiers', - 'generated/limit', - 'generated/order', - 'generated/range', - 'generated/single', - ], - collapsed: true, - }, { type: 'category', label: 'Filters', @@ -86,6 +74,18 @@ const sidebars = { ], collapsed: true, }, + { + type: 'category', + label: 'Modifiers', + items: [ + 'generated/using-modifiers', + 'generated/limit', + 'generated/order', + 'generated/range', + 'generated/single', + ], + collapsed: true, + }, ], collapsed: true, }, diff --git a/apps/www/_blog/2022-11-02-supabase-beta-update-october-2022.mdx b/apps/www/_blog/2022-11-02-supabase-beta-update-october-2022.mdx new file mode 100644 index 00000000000..3b575fcab3d --- /dev/null +++ b/apps/www/_blog/2022-11-02-supabase-beta-update-october-2022.mdx @@ -0,0 +1,122 @@ +--- +title: Supabase Beta October 2022 +description: New SDKs, quickstarts, Functions tricks, and more. But, more importantly, Launch Week 6️ has a date! +author: ant_wilson +image: 2022-october/monthly-update-october-2022.jpg +thumb: 2022-october/monthly-update-october-2022.jpg +tags: + - release-notes +date: '2022-11-02' +toc_depth: 3 +--- + +During October, we shipped something for everybody: new SDKs, quickstarts, Functions tricks, and more. All are listed here just for you. But, more importantly, Launch Week 6️⃣ has a date! + +## supabase-js v2 and supabase-flutter v1 released + +![supabase-js v2 and supabase-flutter v1 released](/images/blog/2022-october/supabase-js-v2-supabase-flutter-v1-header.jpg) + +The new versions of our two most popular SDKs have been fully released. It couldn’t have happened without our amazing community, thanks to everyone involved. Now, it’s time to build 🛠 + +[Try supabase-js V2](https://supabase.com/docs/reference/javascript/)
+[Try flutter-supabase V1](https://supabase.com/docs/reference/dart) + +## New Next.js quickstart & Next.js 13 example + +![New Next.js quickstart & Next.js 13 example](/images/blog/2022-october/next-header.jpg) + +Next.js is all the rage right now 🔥  + +Next.js Conf raised the bar for dev conferences. We had the honor of being a Gold Sponsor, so we revamped our [Next.js Quickstart guide](https://supabase.com/docs/guides/with-nextjs) to include our pre-built Auth UI and [Auth Helpers.](https://supabase.com/docs/guides/auth/auth-helpers/nextjs) + +And Next.js 13 was announced! Making it extremely easy to fetch and cache data from our [Serverless API](https://supabase.com/docs/guides/api). So we put together an [example to try it out](https://github.com/supabase/supabase/tree/master/examples/caching/with-nextjs-13). + +## Supabase Auth and Server-Side Rendering + +![Supabase Auth and Server-Side Rendering](/images/blog/2022-october/authentication-server-side-docs.jpg) + +The Auth team published an in-depth doc explaining how Supabase Auth supports server-side rendering. Includes an explanation of the authentication flow and answers to some of the more common questions. + +[Read the docs](https://supabase.com/docs/guides/auth/server-side-rendering). + +## Database Testing with pgTAP + +![Database Testing with pgTAP](/images/blog/2022-october/database-testing-postgres-pgtap.jpg) + +Do you have 100% code coverage? Probably not, because that’s usually the last thing you think of, but *definitely* not if you don’t have database tests. We just shipped a framework for Database Tests which makes it incredibly easy to test your database using pgTAP an pg_prove. + +[Read the docs](https://supabase.com/docs/guides/database/testing). + +## Edge Functions Update + +![Edge Functions Update](/images/blog/2022-october/functions-new-methods.jpg) + +Functions now support GET requests! Other HTTP verbs such as PUT, PATCH, and DELETE are supported too. + +[Check this example](https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/restful-tasks/index.ts) + +## Quick Product Updates + +- supabase-py 🐍 now has functions support! [PR](https://github.com/supabase-community/supabase-py/pull/179) +- You can now detect users’ location from Edge Functions easily by using `X-Forwarded-For header`. [Example](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/location) +- Return `provider_refresh_token` along with `provider_access_token`. [PR](https://github.com/supabase/gotrue/pull/641) +- Added a `refreshSession` method to allow users to forcefully refresh a session instead of waiting for it to autorefresh upon expiry. Thanks to [@j4w8n](https://github.com/j4w8n) for the [PR](https://github.com/supabase/gotrue-js/pull/505) 🙇🏻‍♂️ +- Logging: realtime, storage, postgrest, and pgbouncer released. +- Trigger a file download by adding the download query parameter to your storage objects. storage-api. [PR](https://github.com/supabase/storage-api/issues/122) + +## Made with Supabase + +![Made with Supabase](/images/blog/2022-october/twitter-card-header.jpg) + +There were so many amazing apps Made with Supabase this month that we couldn’t choose just one… so here are three: + +🚰 [Remote Water Feeder](https://twitter.com/SomangshuG/status/1581888060643774465) by Somangshu Goswami
+📊 [Decile App](https://github.com/decileapp/decile) by [Krishna](https://twitter.com/ntkris)
+📑 [Explainpaper](https://www.explainpaper.com/) by [Aman Jha](https://twitter.com/amanjha__) and [Jade](https://twitter.com/functionofjade) + +If you want to see +100 other apps, check [the replies](https://twitter.com/supabase/status/1585226233742229504). + +## Extended Community Highlights + +![Community](/images/blog/2022-june/community.jpg) + +- Mark Buggraf sat down to chat about postgres-wasm with LogRocket. [Podcast](https://podrocket.logrocket.com/opensourcing-postgres-wasm) +- A collection of awesome Supabase resources. [awesome-supabase](https://github.com/lyqht/awesome-supabase) +- All the Happy Hour Hacktoberfest episodes. [Episode 1](https://www.youtube.com/watch?v=qRSYenBLHHQ) | [Episode 2](https://www.youtube.com/watch?v=-RlivqzNIso) | [Episode 3](https://www.youtube.com/watch?v=LzDIptBGtUw) +- Hacking the PostgREST Headers: Oh, the Things You Can Do! [Article](https://dev.to/burggraf/hacking-the-postgrest-headers-oh-the-things-you-can-do-ck2) +- Why we’re moving away from Firebase. [Blog post](https://koptional.com/article/why-we%E2%80%99re-moving-away-from-firebase) +- Basejump SaaS starter for Supabase. [Repo](https://github.com/usebasejump/basejump) +- SvelteKit + Supabase CRUD with RLS: Row-Level Security! [Video](https://www.youtube.com/watch?v=iKzjfHARXpc) +- Building a startup (build log 2#): Authenticating students with Supabase. [Blog Post](https://dev.to/paul_emechebe/building-a-startup-build-log-2-authenticating-users-with-supabase-1p1b) +- CRUD with Supabase in Flutter. [Tutorial](https://sadabwasim.medium.com/crud-with-supabase-in-flutter-dbe97fa0c15) +- Phone Auth with self-hosted Supabase. [Video](https://www.youtube.com/watch?v=z3sE-ix2uok) +- My Future with Elixir: set-theoretic types. [Blog Post](https://elixir-lang.org/blog/2022/10/05/my-future-with-elixir-set-theoretic-types/) +- Upload media to Supabase from remote URL with nodejs. [Blog Post](https://www.antoinemesnil.com/posts/upload-media-supabase) +- Say goodbye to backend development. [Video](https://www.youtube.com/watch?v=9CuTxeioKF4) + +## Launch Week 6 date announced + +We are ending 2022 on a high note ✊. Mark your calendars: Launch Week 6 is happenning on **December 12-16**. + +In the meantime, [revisit LW5](https://supabase.com/launch-week). + +## We’re hiring + +Come join one of the fastest-growing open source projects ever 🤗 + +- [Sales Engineer](https://boards.greenhouse.io/supabase/jobs/4711141004) +- [Lead Billing and API Engineer](https://boards.greenhouse.io/supabase/jobs/4652333004) +- [View all our openings](https://boards.greenhouse.io/supabase) + +## Meme Zone + +If you made it this far in the blog post you deserve a treat. [Follow us on Twitter](https://twitter.com/supabase) for more. + +![Supabase meme October 2022](/images/blog/2022-october/beta-update-october-meme.jpeg) + +## Get started + +- Start using Supabase today: **[app.supabase.com](https://app.supabase.com/)** +- Make sure to **[star us on GitHub](https://github.com/supabase/supabase)** +- Follow us **[on Twitter](https://twitter.com/supabase)** +- Subscribe to our **[YouTube channel](https://www.youtube.com/c/supabase)** diff --git a/apps/www/public/images/blog/2022-october/authentication-server-side-docs.jpg b/apps/www/public/images/blog/2022-october/authentication-server-side-docs.jpg new file mode 100644 index 00000000000..82b6386b377 Binary files /dev/null and b/apps/www/public/images/blog/2022-october/authentication-server-side-docs.jpg differ diff --git a/apps/www/public/images/blog/2022-october/beta-update-october-meme.jpeg b/apps/www/public/images/blog/2022-october/beta-update-october-meme.jpeg new file mode 100644 index 00000000000..eec6660397e Binary files /dev/null and b/apps/www/public/images/blog/2022-october/beta-update-october-meme.jpeg differ diff --git a/apps/www/public/images/blog/2022-october/database-testing-postgres-pgtap.jpg b/apps/www/public/images/blog/2022-october/database-testing-postgres-pgtap.jpg new file mode 100644 index 00000000000..cff6435db94 Binary files /dev/null and b/apps/www/public/images/blog/2022-october/database-testing-postgres-pgtap.jpg differ diff --git a/apps/www/public/images/blog/2022-october/functions-new-methods.jpg b/apps/www/public/images/blog/2022-october/functions-new-methods.jpg new file mode 100644 index 00000000000..1fa61196425 Binary files /dev/null and b/apps/www/public/images/blog/2022-october/functions-new-methods.jpg differ diff --git a/apps/www/public/images/blog/2022-october/monthly-update-october-2022.jpg b/apps/www/public/images/blog/2022-october/monthly-update-october-2022.jpg new file mode 100644 index 00000000000..3aa5e3e6369 Binary files /dev/null and b/apps/www/public/images/blog/2022-october/monthly-update-october-2022.jpg differ diff --git a/apps/www/public/images/blog/2022-october/next-header.jpg b/apps/www/public/images/blog/2022-october/next-header.jpg new file mode 100644 index 00000000000..789a878ac74 Binary files /dev/null and b/apps/www/public/images/blog/2022-october/next-header.jpg differ diff --git a/apps/www/public/images/blog/2022-october/supabase-js-v2-supabase-flutter-v1-header.jpg b/apps/www/public/images/blog/2022-october/supabase-js-v2-supabase-flutter-v1-header.jpg new file mode 100644 index 00000000000..e1d80d2aaf2 Binary files /dev/null and b/apps/www/public/images/blog/2022-october/supabase-js-v2-supabase-flutter-v1-header.jpg differ diff --git a/apps/www/public/images/blog/2022-october/twitter-card-header.jpg b/apps/www/public/images/blog/2022-october/twitter-card-header.jpg new file mode 100644 index 00000000000..fcce0cf3c44 Binary files /dev/null and b/apps/www/public/images/blog/2022-october/twitter-card-header.jpg differ diff --git a/spec/supabase_dart_v1.yml b/spec/supabase_dart_v1.yml index d83ffa4af3c..382c76bae08 100644 --- a/spec/supabase_dart_v1.yml +++ b/spec/supabase_dart_v1.yml @@ -1100,9 +1100,14 @@ pages: ``` Using Modifiers: description: | - Modifiers can be used on `select()` queries. + Filters work on the row level—they allow you to return rows that + only match certain conditions without changing the shape of the rows. + Modifiers are everything that don't fit that definition—allowing you to + change the format of the response (e.g., returning a CSV string). - If a Stored Procedure returns a table response, you can also apply modifiers to the `rpc()` function. + Modifiers must be specified after filters. Some modifiers only apply for + queries that return rows (e.g., `select()` or `rpc()` on a function that + returns a table response). limit(): description: | @@ -1180,13 +1185,17 @@ pages: Using Filters: description: | + Filters allow you to only return rows that match certain conditions. + Filters can be used on `select()`, `update()`, and `delete()` queries. - If a Stored Procedure returns a table response, you can also apply filters. + If a Database function returns a table response, you can also apply filters. ### Applying Filters - You must apply your filters to the end of your query. For example: + Filters must be applied after any of `select()`, `update()`, `upsert()`, + `delete()`, and `rpc()` and before + [modifiers](/docs/reference/dart/using-modifiers). ```dart final data = await supabase @@ -1202,7 +1211,8 @@ pages: ### Chaining - Filters can be chained together to produce advanced queries. For example: + Filters can be chained together to produce advanced queries. For example, + to query cities with population between 1,000 and 10,000: ```dart final data = await supabase @@ -1212,6 +1222,146 @@ pages: .lt('population', 10000); ``` + ### Conditional Chaining + + Filters can be built up one step at a time and then executed. For example: + + ```dart + final filterByName = null; + final filterPopLow = 1000; + final filterPopHigh = 10000; + + var query = supabase + .from('cities') + .select('name, country_id'); + + if (filterByName != null) { query = query.eq('name', filterByName); } + if (filterPopLow != null) { query = query.gte('population', filterPopLow); } + if (filterPopHigh != null) { query = query.lt('population', filterPopHigh); } + + final data = await query; + ``` + + ### Filter by values within a JSON column + + + + + ```sql + create table + users ( + id int8 primary key, + name text, + address jsonb + ); + + insert into + users (id, name, address) + values + (1, 'Michael', '{ "postcode": 90210 }'), + (2, 'Jane', null); + ``` + + + + + ```dart + final data = await supabase + .from('users') + .select() + .eq('address->postcode', 90210); + ``` + + + + + ```json + { + "data": [ + { + "id": 1, + "name": "Michael", + "address": { + "postcode": 90210 + } + } + ], + "status": 200, + "statusText": "OK" + } + ``` + + + + + ### Filter Foreign Tables + + You can filter on foreign tables in your `select()` query using dot + notation: + + + + + ```sql + create table + countries (id int8 primary key, name text); + create table + cities ( + id int8 primary key, + country_id int8 not null references countries, + name text + ); + + insert into + countries (id, name) + values + (1, 'Germany'), + (2, 'Indonesia'); + insert into + cities (id, country_id, name) + values + (1, 2, 'Bali'), + (2, 1, 'Munich'); + ``` + + + + + ```dart + final data = await supabase + .from('countries') + .select(''' + name, + cities!inner ( + name + ) + ''') + .eq('cities.name', 'Bali'); + ``` + + + + + ```json + { + "data": [ + { + "name": "Indonesia", + "cities": [ + { + "name": "Bali" + } + ] + } + ], + "status": 200, + "statusText": "OK" + } + ``` + + + + .or(): description: | Finds all rows satisfying at least one of the filters. diff --git a/studio/pages/project/[ref]/logs/pgbouncer-logs.tsx b/studio/pages/project/[ref]/logs/pgbouncer-logs.tsx index 0a4ddf0b520..6d257379b45 100644 --- a/studio/pages/project/[ref]/logs/pgbouncer-logs.tsx +++ b/studio/pages/project/[ref]/logs/pgbouncer-logs.tsx @@ -1,7 +1,7 @@ import React from 'react' import { useRouter } from 'next/router' import { observer } from 'mobx-react-lite' -import { DatabaseLayout } from 'components/layouts' +import { LogsLayout } from 'components/layouts' import LogsPreviewer from 'components/interfaces/Settings/Logs/LogsPreviewer' import { NextPageWithLayout } from 'types' import { LogsTableName } from 'components/interfaces/Settings/Logs' @@ -20,6 +20,6 @@ export const LogPage: NextPageWithLayout = () => { ) } -LogPage.getLayout = (page) => {page} +LogPage.getLayout = (page) => {page} export default observer(LogPage) diff --git a/studio/pages/project/[ref]/logs/postgrest-logs.tsx b/studio/pages/project/[ref]/logs/postgrest-logs.tsx index 78efcdc97f6..cd31e9f66c9 100644 --- a/studio/pages/project/[ref]/logs/postgrest-logs.tsx +++ b/studio/pages/project/[ref]/logs/postgrest-logs.tsx @@ -1,7 +1,7 @@ import React from 'react' import { useRouter } from 'next/router' import { observer } from 'mobx-react-lite' -import { DatabaseLayout } from 'components/layouts' +import { LogsLayout } from 'components/layouts' import LogsPreviewer from 'components/interfaces/Settings/Logs/LogsPreviewer' import { NextPageWithLayout } from 'types' import { LogsTableName } from 'components/interfaces/Settings/Logs' @@ -20,6 +20,6 @@ export const LogPage: NextPageWithLayout = () => { ) } -LogPage.getLayout = (page) => {page} +LogPage.getLayout = (page) => {page} export default observer(LogPage)